diff --git a/.ctags-exclude b/.ctags-exclude
index ba7733050..0b0a4b0dd 100644
--- a/.ctags-exclude
+++ b/.ctags-exclude
@@ -3,19 +3,16 @@ bin
build
android17.d.ts
ios.d.ts
-ng-sample/app
-ng-sample/src/angular2
-ng-sample/src/nativescript-angular
+ng-sample/app/nativescript-angular
ng-sample/platforms
+ng-sample/node_modules
ng-sample/lib
ng-sample/src/typings
-todomvc/app
-todomvc/src/angular2
-todomvc/src/nativescript-angular
-todomvc/platforms
-todomvc/lib
-todomvc/src/typings
-todomvc/typings
-src/angular2
-web/*.js
-deps/angular/dist
+tests/app/nativescript-angular
+tests/platforms
+tests/node_modules
+tests/lib
+tests/src/typings
+tests/typings
+web
+*.js
diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 000000000..c515349ff
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,105 @@
+{
+ "extends": "eslint:recommended",
+ "parser": "babel-eslint",
+ "plugins": [ "react" ],
+ "ecmaFeatures": {
+
+ },
+ "rules": {
+
+ "no-unexpected-multiline": 2,
+ "block-scoped-var": 2,
+ "complexity": [ 1, 15 ],
+ "consistent-return": 2,
+ "curly": 2,
+ "default-case": 2,
+ "dot-location": [2, "property"],
+ "eqeqeq": [2, "smart"],
+ "no-alert": 2,
+ "no-caller": 2,
+ "no-else-return": 2,
+ "no-eval": 2,
+ "no-implied-eval": 2,
+ "no-extend-native": 2,
+ "no-extra-bind": 2,
+ "no-implicit-coercion": 2,
+ "no-invalid-this": 2,
+ "no-lone-blocks": 2,
+ "no-loop-func": 2,
+ "no-magic-numbers": [2, { "ignore": [ 0, -1 ] }],
+ "no-multi-spaces": 2,
+ "no-native-reassign": 2,
+ "no-new": 2,
+ "no-param-reassign": 2,
+ "no-return-assign": 2,
+ "no-sequences": 2,
+ "no-useless-call": 2,
+ "array-bracket-spacing": [2, "never"],
+ "brace-style": [2, "1tbs", { "allowSingleLine": true }],
+ "camelcase": 2,
+ "comma-style": 2,
+ "consistent-this": [2, "that"],
+ "indent": 2,
+ "jsx-quotes": 2,
+ "key-spacing": [2, {"beforeColon": false, "afterColon": true}],
+ "linebreak-style": [2, "unix"],
+ "lines-around-comment": 2,
+ "max-params": [2, 4],
+ "no-multiple-empty-lines": [2, {"max": 2}],
+ "no-nested-ternary": 2,
+ "no-spaced-func": 2,
+ "no-trailing-spaces": 2,
+ "object-curly-spacing": 2,
+ "semi-spacing": 2,
+ "space-after-keywords": 2,
+ "space-before-blocks": 2,
+ "space-before-function-paren": [2, "never"],
+ "space-before-keywords": [2, "always"],
+ "space-infix-ops": [2, {"int32Hint": false} ],
+ "arrow-body-style": 2,
+ "no-arrow-condition": 2,
+ "no-class-assign": 2,
+ "no-const-assign": 2,
+ "no-dupe-class-members": 2,
+ "no-this-before-super": 2,
+ "no-var": 2,
+ "prefer-template": 2,
+
+ "react/display-name": 0,
+ "react/forbid-prop-types": 2,
+ "react/jsx-boolean-value": 2,
+ "react/jsx-closing-bracket-location": 2,
+ "react/jsx-curly-spacing": 2,
+ "react/jsx-indent-props": 2,
+ "react/jsx-key": 2,
+ "react/jsx-max-props-per-line": [2, { "maximum": 4 }],
+ "react/jsx-no-bind": 2,
+ "react/jsx-no-duplicate-props": 2,
+ "react/jsx-no-literals": 0,
+ "react/jsx-no-undef": 2,
+ "react/jsx-pascal-case": 2,
+ "react/jsx-sort-prop-types": 2,
+ "react/jsx-sort-props": 2,
+ "react/jsx-uses-react": 2,
+ "react/jsx-uses-vars": 2,
+ "react/no-danger": 2,
+ "react/no-did-mount-set-state": 2,
+ "react/no-did-update-set-state": 2,
+ "react/no-direct-mutation-state": 2,
+ "react/no-multi-comp": 2,
+ "react/no-set-state": 2,
+ "react/no-unknown-property": 2,
+ "react/prefer-es6-class": 2,
+ "react/prop-types": 2,
+ "react/react-in-jsx-scope": 2,
+ "react/require-extension": 2,
+ "react/self-closing-comp": 2,
+ "react/sort-comp": 2,
+ "react/wrap-multilines": 2
+ },
+ "env": {
+ "browser": true,
+ "node": true,
+ "jasmine": true
+ }
+}
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 000000000..c4b4052f5
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# Use LF for shell scripts
+*.sh eol=lf
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 000000000..70c9beb9d
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,31 @@
+---
+name: Bug report
+about: 'We really appreciate your effort to provide feedback. Before opening a new
+ issue, please make sure that this case is not already reported in GitHub as an
+ issue or in StackOverflow as a question.'
+
+---
+
+**Environment**
+Provide version numbers for the following components (information can be retrieved by running `tns info` in your project folder or by inspecting the `package.json` of the project):
+ - CLI:
+ - Cross-platform modules:
+ - Android Runtime:
+ - iOS Runtime:
+ - Plugin(s):
+ - NativeScript-Angular:
+ - Angular:
+
+**Describe the bug**
+
+
+**To Reproduce**
+
+
+**Expected behavior**
+
+**Sample project**
+
+
+**Additional context**
+
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 000000000..397090987
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,17 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+
+
+**Describe the solution you'd like**
+
+
+**Describe alternatives you've considered**
+
+
+**Additional context**
+
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 000000000..fa27f8911
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,37 @@
+
+
+
+
+
+## PR Checklist
+
+- [ ] The PR title follows our guidelines: https://github.com/NativeScript/NativeScript/blob/master/CONTRIBUTING.md#commit-messages.
+- [ ] There is an issue for the bug/feature this PR is for. To avoid wasting your time, it's best to open a suggestion issue first and wait for approval before working on it.
+- [ ] You have signed the [CLA](http://www.nativescript.org/cla).
+- [ ] All existing tests are passing: https://github.com/NativeScript/nativescript-angular/blob/master/DevelopmentWorkflow.md#running-the-tests
+- [ ] Tests for the changes are included.
+
+## What is the current behavior?
+
+
+## What is the new behavior?
+
+
+Fixes/Implements/Closes #[Issue Number].
+
+
+
+
+
diff --git a/.gitignore b/.gitignore
index 053cab1bd..ca4ea8626 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,27 +1,59 @@
-bin/dist
node_modules
+platforms
+hooks
tags
-todomvc/typings
-!/src/nativescript-angular/
-!/src/dependencies.d.ts
-!/src/global.d.ts
-deps/angular/dist
-.baseDir.ts
+dist
+**/*.js.map
+**/*.metadata.json
+
+/nativescript-angular/**/*.d.ts
+/nativescript-angular/**/*.js
+
+!/nativescript-angular/global.d.ts
+!/nativescript-angular/postinstall.js
+!/nativescript-angular/hooks/**/*.js
+!/nativescript-angular/gulpfile.js
+!/nativescript-angular/zone-js/dist/*.js
+
+/nativescript-angular-package/**/*.d.ts
+/nativescript-angular-package/**/*.js
+
+!/nativescript-angular-package/global.d.ts
+!/nativescript-angular-package/postinstall.js
+!/nativescript-angular-package/hooks/**/*.js
+!/nativescript-angular-package/gulpfile.js
+!/nativescript-angular-package/zone-js/dist/*.js
+
.tscache
+.nvm
+.vscode
+.DS_Store
+npm-debug.log
nativescript-angular*.tgz
-angular2-*.tgz
-
-
-ng-sample/app
-ng-sample/platforms
-ng-sample/lib
-ng-sample/node_modules
-ng-sample/src/tns_modules
-ng-sample/src/typings
-ng-sample/src/global.d.ts
-ng-sample/src/angular2
-ng-sample/src/nativescript-angular
-
-startup-test/platforms
-startup-test/lib
-startup-test/node_modules
+package-lock.json
+
+# Apps' JS files
+tests/app/**/*.js
+tests/test-output.txt
+ng-sample/app/**/*.js
+
+# Webpack configuration files
+webpack.config.js
+tsconfig.esm.json
+tsconfig.tns.json
+
+# IDEs and editors
+/.idea
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+
+# IDE - VSCode
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index 5a54197fc..000000000
--- a/.gitmodules
+++ /dev/null
@@ -1,6 +0,0 @@
-[submodule "deps/angular"]
- path = deps/angular
- url = git@github.com:hdeshev/angular.git
-[submodule "deps/NativeScript"]
- path = deps/NativeScript
- url = git@github.com:NativeScript/NativeScript.git
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 000000000..7f437f40b
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,8 @@
+language: node_js
+node_js:
+ - "node"
+script:
+ - cd nativescript-angular
+ - npm install
+ - npm run format-check
+ - npm pack
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 000000000..e7e0c6b06
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,1060 @@
+# [11.2.0](https://github.com/NativeScript/nativescript-angular/compare/11.0.1...11.2.0) (2021-03-24)
+
+
+### Bug Fixes
+
+* **ivy:** nsRouterLinkActive works on nsRouterLink ([#2322](https://github.com/NativeScript/nativescript-angular/issues/2322)) ([13a3562](https://github.com/NativeScript/nativescript-angular/commit/13a3562146ef8516fea8bb3d988f8e0ddc9617a7))
+
+
+
+## [11.0.1](https://github.com/NativeScript/nativescript-angular/compare/11.0.0...11.0.1) (2021-01-22)
+
+
+### Bug Fixes
+
+* blank screen on hmr ([#2317](https://github.com/NativeScript/nativescript-angular/issues/2317)) ([197d802](https://github.com/NativeScript/nativescript-angular/commit/197d802976ddf4ae3bba45f2d000a687049c8027))
+
+
+
+# [11.0.0](https://github.com/NativeScript/nativescript-angular/compare/10.1.7...11.0.0) (2020-11-17)
+
+
+### Features
+
+* **angular:** v11 ([#2297](https://github.com/NativeScript/nativescript-angular/issues/2297)) ([e3657c4](https://github.com/NativeScript/nativescript-angular/commit/e3657c446db7af23d50f8514d001e5709c2b7788))
+
+
+
+## [10.1.7](https://github.com/NativeScript/nativescript-angular/compare/10.1.5...10.1.7) (2020-10-12)
+
+
+### Bug Fixes
+
+* Export injection token used by plugins ([#2268](https://github.com/NativeScript/nativescript-angular/issues/2268)) ([75865f2](https://github.com/NativeScript/nativescript-angular/commit/75865f214d9e53eb36990c779808d50fb697f1ce))
+* expose NSLocationStrategy and NSRouteReuseStrategy ([9b8435e](https://github.com/NativeScript/nativescript-angular/commit/9b8435e13b7f6f42098e79895af5ab3156d1aac3))
+
+
+### Features
+
+* export injectiontoken for template items ([52a56d7](https://github.com/NativeScript/nativescript-angular/commit/52a56d7201b70b67edb2ca77e9042737f216a07a))
+
+
+
+## [10.1.5](https://github.com/NativeScript/nativescript-angular/compare/10.1.4...10.1.5) (2020-09-21)
+
+
+### Bug Fixes
+
+* **renderer:** dynamic views can't be prepended ([#2262](https://github.com/NativeScript/nativescript-angular/issues/2262)) ([5735a01](https://github.com/NativeScript/nativescript-angular/commit/5735a01695d7967b5a63222dcec760ddac0533e3))
+
+
+
+## [10.1.4](https://github.com/NativeScript/nativescript-angular/compare/10.1.3...10.1.4) (2020-09-21)
+
+
+### Bug Fixes
+
+* **detached-loader:** detach loadWithFactory ([#2260](https://github.com/NativeScript/nativescript-angular/issues/2260)) ([1dca81b](https://github.com/NativeScript/nativescript-angular/commit/1dca81bbc4a1a05a9eefe13988848124885f3178))
+* **renderer:** order not preserved ([#2261](https://github.com/NativeScript/nativescript-angular/issues/2261)) ([07abb9e](https://github.com/NativeScript/nativescript-angular/commit/07abb9e62c2408be83e8a694384cd529ba5d3309))
+
+
+
+## [10.1.3](https://github.com/NativeScript/nativescript-angular/compare/10.1.0...10.1.3) (2020-09-21)
+
+
+### Bug Fixes
+
+* **detached-loader:** completely deatch components ([#2257](https://github.com/NativeScript/nativescript-angular/issues/2257)) ([623d2f7](https://github.com/NativeScript/nativescript-angular/commit/623d2f79e014017fd993685051d125b3c5c72ada))
+* **ivy:** support view references in insertBefore ([#2258](https://github.com/NativeScript/nativescript-angular/issues/2258)) ([c2eaef5](https://github.com/NativeScript/nativescript-angular/commit/c2eaef52a66dbbee982f73bb968a706cee38f633))
+
+
+
+# [10.1.0](https://github.com/NativeScript/nativescript-angular/compare/10.0.3...10.1.0) (2020-09-04)
+
+
+### Bug Fixes
+
+* **list-view:** fix crash when used with ngFor ([#2121](https://github.com/NativeScript/nativescript-angular/issues/2121)) ([302afb3](https://github.com/NativeScript/nativescript-angular/commit/302afb350dea56ddbf1a0d772f6d9405413890a5))
+
+
+### Features
+
+* **angular:** ng 10.1 and ns 7 ([#2237](https://github.com/NativeScript/nativescript-angular/issues/2237)) ([548e074](https://github.com/NativeScript/nativescript-angular/commit/548e0743eb092ba3fa371ea4d26879ee5bf03983))
+
+
+
+## [10.0.3](https://github.com/NativeScript/nativescript-angular/compare/10.0.2...10.0.3) (2020-08-27)
+
+
+### Bug Fixes
+
+* **router:** page navigation bug if there's not outlet for frame after clearing history ([#2233](https://github.com/NativeScript/nativescript-angular/issues/2233)) ([8bedc2d](https://github.com/NativeScript/nativescript-angular/commit/8bedc2d14fccc8d6c1ea18151bda7ce7c87fddae))
+
+
+
+## [10.0.2](https://github.com/NativeScript/nativescript-angular/compare/10.0.1...10.0.2) (2020-08-19)
+
+
+### Bug Fixes
+
+* **router:** link active directive ([#2227](https://github.com/NativeScript/nativescript-angular/issues/2227)) ([5e165b5](https://github.com/NativeScript/nativescript-angular/commit/5e165b56c7a61d28fdb279ed8da07288e6dbb7af))
+
+
+
+# [10.0.0](https://github.com/NativeScript/nativescript-angular/compare/8.20.4...10.0.0) (2020-07-30)
+
+### Features
+
+* **angular:** support for v10 ([#2189](https://github.com/NativeScript/nativescript-angular/issues/2189)) ([fd71458](https://github.com/NativeScript/nativescript-angular/commit/fd714588dc93762c2d5a129d3a08c92317df68e4))
+* ivy support ([#2169](https://github.com/NativeScript/nativescript-angular/issues/2169)) ([f10a8fb](https://github.com/NativeScript/nativescript-angular/commit/f10a8fb9f7c8bbe35be10c736210a451f8c52670)), closes [#2152](https://github.com/NativeScript/nativescript-angular/issues/2152) [#2060](https://github.com/NativeScript/nativescript-angular/issues/2060)
+* support for async APP_INITIALIZER and animated launch screens ([#2170](https://github.com/NativeScript/nativescript-angular/issues/2170)) ([b6ac290](https://github.com/NativeScript/nativescript-angular/commit/b6ac29038e27c310803ce956f7d93aca4d8ecd2a))
+
+### Bug Fixes
+
+* **router:** query params are now preserved when navigating back ([#2062](https://github.com/NativeScript/nativescript-angular/issues/2062)) ([221e404](https://github.com/NativeScript/nativescript-angular/commit/221e404da244b64004cfb3cd3e947858133af347))
+* **segmented-bar:** listview crash when scrolling ([#2128](https://github.com/NativeScript/nativescript-angular/issues/2128)) ([1b191b0](https://github.com/NativeScript/nativescript-angular/commit/1b191b06ccd0df2871ef741ab97f14c04911864e))
+
+
+
+
+# [9.0.0](https://github.com/NativeScript/nativescript-angular/compare/8.20.4...9.0.0) (2020-06-03)
+
+
+
+### Bug Fixes
+
+* **bindable:** parent referenced expression-values now load properly using an update call ([#8670](https://github.com/NativeScript/NativeScript/issues/8670)) ([6b0028a](https://github.com/NativeScript/NativeScript/commit/6b0028afd7b554914b039cdf371e8e30f6e02dac)), closes [#8666](https://github.com/NativeScript/NativeScript/issues/8666) [#6981](https://github.com/NativeScript/NativeScript/issues/6981) [#5054](https://github.com/NativeScript/NativeScript/issues/5054)
+* **scroll-view:** android 'isScrollEnabled' will apply if changed while gesture is underway ([#8695](https://github.com/NativeScript/NativeScript/issues/8695)) ([02ec7f1](https://github.com/NativeScript/NativeScript/commit/02ec7f104d327df53df687ddd1b8ac5b1cdc04ba))
+* **snapshots:** android is not defined ([#8691](https://github.com/NativeScript/NativeScript/issues/8691)) ([a8bbd7c](https://github.com/NativeScript/NativeScript/commit/a8bbd7c1e580e77e7ad5ddc7be6845e3d8fb02de))
+* **text-view:** only reload text if hint is showing on ios ([#8662](https://github.com/NativeScript/NativeScript/issues/8662)) ([ec17727](https://github.com/NativeScript/NativeScript/commit/ec17727e91f7a3209ada2c7de0bcf59c98c4e62a))
+
+
+### Features
+
+* **connectivity:** getActiveNetworkInfo and NetworkInfo modern compliance [#8580](https://github.com/NativeScript/NativeScript/issues/8580) ([#8652](https://github.com/NativeScript/NativeScript/issues/8652)) ([635f31f](https://github.com/NativeScript/NativeScript/commit/635f31f81f7826112142c707aff2a66c2b480b0e))
+* **dialog:** ios destructive style from options ([#8676](https://github.com/NativeScript/NativeScript/issues/8676)) ([bb531ce](https://github.com/NativeScript/NativeScript/commit/bb531ce71028f9c4fd4d753df16c82104f158e35))
+* **ImageSource:** resize method ([#8678](https://github.com/NativeScript/NativeScript/issues/8678)) ([bd12baf](https://github.com/NativeScript/NativeScript/commit/bd12bafb4aae8f1c523be4c7e04fa73722092304))
+* **text-view:** allow easy subclassing on ios ([#8663](https://github.com/NativeScript/NativeScript/issues/8663)) ([7d36447](https://github.com/NativeScript/NativeScript/commit/7d364474c23e17acf7696f159d3945d8a73d63e6))
+
+
+### Features
+
+* angular 9 ivy ([fbe2450](https://github.com/NativeScript/nativescript-angular/commit/fbe2450))
+
+
+
+## [8.20.4](https://github.com/NativeScript/nativescript-angular/compare/8.20.3...8.20.4) (2020-01-07)
+
+
+### Bug Fixes
+
+* add context to frame navigation ([#2100](https://github.com/NativeScript/nativescript-angular/pull/2100))
+* wrong import path in compat package ([#2097](https://github.com/NativeScript/nativescript-angular/pull/2097))
+
+
+
+
+## [8.20.3](https://github.com/NativeScript/nativescript-angular/compare/8.20.2...8.20.3) (2019-11-13)
+
+
+### Bug Fixes
+
+* add exports for backwards compatibility ([fbd46c1](https://github.com/NativeScript/nativescript-angular/commit/fbd46c1))
+
+
+## [8.20.2](https://github.com/NativeScript/nativescript-angular/compare/8.20.1...8.20.2) (2019-11-12)
+
+
+### Bug Fixes
+
+* add exports in `nativescript-angular` package for backwards compatibility ([4a5d022](https://github.com/NativeScript/nativescript-angular/commit/4a5d022))
+
+
+
+# [8.20.1](https://github.com/NativeScript/nativescript-angular/compare/8.20.0...8.20.1) (2019-11-07)
+
+### Bug Fixes
+
+* add exports in `nativescript-angular` package for backwards compatibility
+
+# [8.20.0](https://github.com/NativeScript/nativescript-angular/compare/8.2.2...8.20.0) (2019-10-23)
+
+### Features
+* add scoped package @nativescript/angular ([#2014](https://github.com/NativeScript/nativescript-angular/pull/2014))
+
+### Bug Fixes
+
+* **animations:** resolve issue with "query animations" on iOS 13 ([#2022](https://github.com/NativeScript/nativescript-angular/issues/2022)) ([c382682](https://github.com/NativeScript/nativescript-angular/commit/c382682))
+
+
+## [8.2.2](https://github.com/NativeScript/nativescript-angular/compare/8.2.1...8.2.2) (2019-10-16)
+
+### Features
+
+* Support for adding scoped CSS without triggering global refresh ([#1999](https://github.com/NativeScript/nativescript-angular/pull/1999)) ([662c122](https://github.com/NativeScript/nativescript-angular/commit/662c122))
+
+
+
+
+## [8.2.1](https://github.com/NativeScript/nativescript-angular/compare/8.2.0...8.2.1) (2019-08-28)
+
+
+### Bug Fixes
+
+* **hmr:** close modal views during livesync [#7669](https://github.com/NativeScript/nativescript-angular/issues/7669) ([#1944](https://github.com/NativeScript/nativescript-angular/issues/1944)) ([73d83ed](https://github.com/NativeScript/nativescript-angular/commit/73d83ed))
+
+
+
+
+# [8.2.0](https://github.com/NativeScript/nativescript-angular/compare/8.1.0...8.2.0) (2019-08-13)
+
+
+### Features
+
+* Update to Angular 8.2.x ([dbe983b](https://github.com/NativeScript/nativescript-angular/commit/dbe983b))
+* Remove `@angular/http` from the dependecies ([#1842](https://github.com/NativeScript/nativescript-angular/issues/1842)) ([c6af21d](https://github.com/NativeScript/nativescript-angular/commit/c6af21d))
+
+### BREAKING CHANGES:
+
+* The `NativeScriptHttpModule` is removed
+* `@angular/http` is removed from the dependencies
+
+Migration steps:
+* If using `NativeScriptHttpModule`, replace with `NativeScriptHttpClientModule` as done [here](https://github.com/NativeScript/nativescript-sdk-examples-ng/commit/16d3caee2b0ee2d88d328b75bde49eea6c96920a)
+* as `@angular/http` is no longer a peerDependency of `nativescript-angular`, you can remove if from your dependencies
+
+
+
+# [8.1.0](https://github.com/NativeScript/nativescript-angular/compare/8.0.3...8.1.0) (2019-07-31)
+
+
+### Features
+
+* update to Angular 8.1.x ([#1907](https://github.com/NativeScript/nativescript-angular/issues/1907)) ([e1db5f8](https://github.com/NativeScript/nativescript-angular/commit/e1db5f8))
+
+
+
+## [8.0.3](https://github.com/NativeScript/nativescript-angular/compare/8.0.2...8.0.3) (2019-07-26)
+
+
+### Bug Fixes
+
+* add backwards compatibility for 'tns-core-modules' version 5.4.x ([635bb36](https://github.com/NativeScript/nativescript-angular/commit/635bb36))
+
+
+### Features
+
+* **tabs:** register tabs elements ([#1883](https://github.com/NativeScript/nativescript-angular/issues/1883)) ([092a833](https://github.com/NativeScript/nativescript-angular/commit/092a833))
+
+
+
+
+## [8.0.2](https://github.com/NativeScript/nativescript-angular/compare/8.0.1...8.0.2) (2019-06-28)
+
+### Bug Fixes
+
+ * remove deprecated API usage ([#1874](https://github.com/NativeScript/nativescript-angular/pull/1874)) ([ab740cd](https://github.com/NativeScript/nativescript-angular/commit/ab740cd))
+ * migrate from ReflectiveInjector (deprecated) to StaticInjector ([#1868](https://github.com/NativeScript/nativescript-angular/pull/1868)) ([e432841](https://github.com/NativeScript/nativescript-angular/pull/1868/commits/e432841e9b474ad188f87044b74666b322d68b5d))
+
+ ### Features
+
+* Update peer dependencies to Angular 8.x.x ([#1857](https://github.com/NativeScript/nativescript-angular/pull/1857)) ([060aabf](https://github.com/NativeScript/nativescript-angular/pull/1857/commits/060aabf146aa0f132f3617dcfaeea0efa7baf228))
+* **tabs:** register tabs elements ([#1883](https://github.com/NativeScript/nativescript-angular/issues/1883)) ([092a833](https://github.com/NativeScript/nativescript-angular/commit/092a833))
+
+
+
+## [8.0.1](https://github.com/NativeScript/nativescript-angular/compare/8.0.0...8.0.1) (2019-06-12)
+
+
+### Bug Fixes
+
+* do not throw if element already registered ([#1838](https://github.com/NativeScript/nativescript-angular/issues/1838)) ([e90f8b5](https://github.com/NativeScript/nativescript-angular/commit/e90f8b5))
+* **1845:** CSS special selector ":host" only work first time ([#1852](https://github.com/NativeScript/nativescript-angular/issues/1852)) ([59a5bd8](https://github.com/NativeScript/nativescript-angular/commit/59a5bd8))
+
+
+
+
+# [8.0.0](https://github.com/NativeScript/nativescript-angular/compare/7.2.4...8.0.0) (2019-05-29)
+
+
+### Bug Fixes
+
+* **router:** fix return value for getTransition of NSRouterLink ([d20b645](https://github.com/NativeScript/nativescript-angular/commit/d20b645)), closes [#1784](https://github.com/NativeScript/nativescript-angular/issues/1784)
+* **tsconfig:** exclude aot files from compilation ([a4eb409](https://github.com/NativeScript/nativescript-angular/commit/a4eb409))
+
+
+### Features
+
+* upgrade to Angular 8 ([b16046d](https://github.com/NativeScript/nativescript-angular/commit/b16046d))
+
+
+### BREAKING CHANGES
+
+* In Angular version 8, it's required that all `@ViewChild` and `@ContentChild` queries have a `static` flag specifying whether the query is `static` or `dynamic`. More details about this change can be found [here](https://angular.io/guide/static-query-migration).
+
+Migration steps:
+
+Anywhere you previously had `@ViewChild` with a single param you now have to provide a second param with a `static` property set to either `true` or `false`
+
+**Previous code:**
+```
+import { ElementRef } from "@angular/core";
+
+@ViewChild("myElement") myElement: ElementRef;
+```
+
+**Migrated code:**
+```
+import { ElementRef } from "@angular/core";
+
+@ViewChild("myElement", { static: false }) myElement: ElementRef;
+```
+
+
+
+## [7.2.4](https://github.com/NativeScript/nativescript-angular/compare/7.2.3...7.2.4) (2019-05-14)
+
+
+### Bug Fixes
+
+* **router:** routing services should be provided in forRoot only ([#1729](https://github.com/NativeScript/nativescript-angular/issues/1729)) ([0f6a975](https://github.com/NativeScript/nativescript-angular/commit/0f6a975))
+* mark reattached view for CD ([#1803](https://github.com/NativeScript/nativescript-angular/issues/1803)) ([b6dbe57](https://github.com/NativeScript/nativescript-angular/commit/b6dbe57))
+* Potentiel leak: NativeScript views not cleaned up on removal ([#1738](https://github.com/NativeScript/nativescript-angular/issues/1738)) ([59a1cde](https://github.com/NativeScript/nativescript-angular/commit/59a1cde))
+
+### Features
+
+* **modal:** add ‘ios presentationStyle’ option to ModalDialogParams ([9cfa127](https://github.com/NativeScript/nativescript-angular/commit/9cfa127))
+
+
+
+
+## [7.2.3](https://github.com/NativeScript/nativescript-angular/compare/7.2.2...7.2.3) (2019-03-14)
+
+
+### Bug Fixes
+
+* **location-strategy:** crash on going back with router-outlet after closing modal ([#1748](https://github.com/NativeScript/nativescript-angular/issues/1748)) ([0ed7de6](https://github.com/NativeScript/nativescript-angular/commit/0ed7de6))
+
+
+
+
+## [7.2.2](https://github.com/NativeScript/nativescript-angular/compare/7.2.1...7.2.2) (2019-02-19)
+
+
+### Bug Fixes
+
+* **list-view:** add support for default item template ([4061cc7](https://github.com/NativeScript/nativescript-angular/commit/4061cc7))
+
+
+
+
+## [7.2.1](https://github.com/NativeScript/nativescript-angular/compare/7.2.0...7.2.1) (2019-02-10)
+
+
+### Bug Fixes
+
+* **location-strategy:** extend support for nested primary outlets ([566896d](https://github.com/NativeScript/nativescript-angular/commit/566896d))
+* Router tracing does not work with webpack ([e87ef68](https://github.com/NativeScript/nativescript-angular/commit/e87ef68))
+
+
+
+
+# [7.2.0](https://github.com/NativeScript/nativescript-angular/compare/7.1.2...7.2.0) (2019-01-31)
+
+
+### Bug Fixes
+
+* **p-r-o:** needless forward navigation after back inside nested named outlet ([130e392](https://github.com/NativeScript/nativescript-angular/commit/130e392))
+
+
+### Features
+
+* **router:** detach change detection on navigation ([#1507](https://github.com/NativeScript/nativescript-angular/issues/1507)) ([86cd290](https://github.com/NativeScript/nativescript-angular/commit/86cd290))
+
+
+
+
+## [7.1.2](https://github.com/NativeScript/nativescript-angular/compare/7.1.1...7.1.2) (2019-01-21)
+
+
+### Bug Fixes
+
+ * **page-router-outlet:** fix(empty-outlet): remove unnecessary moduleId ([#1686](https://github.com/NativeScript/nativescript-angular/issues/1686)) ([1222e57](https://github.com/NativeScript/nativescript-angular/commit/1222e57))
+
+
+
+## [7.1.1](https://github.com/NativeScript/nativescript-angular/compare/7.1.0...7.1.1) (2018-12-20)
+
+
+### Bug Fixes
+
+ * **page-router-outlet:** prevent needless forward navigation after back inside nested named outlet ([d8a0653](https://github.com/NativeScript/nativescript-angular/commit/d8a0653))
+
+
+
+
+# [7.1.0](https://github.com/NativeScript/nativescript-angular/compare/7.0.3...7.1.0) (2018-12-07)
+
+
+### Features
+
+* Angular 7.1 support
+
+
+## [7.0.3](https://github.com/NativeScript/nativescript-angular/compare/7.0.2...7.0.3) (2018-12-05)
+
+
+### Bug Fixes
+
+* **router:** handle nested primary p-r-o ([#1645](https://github.com/NativeScript/nativescript-angular/issues/1645)) ([e632fc5](https://github.com/NativeScript/nativescript-angular/commit/e632fc5))
+
+
+
+
+## [7.0.2](https://github.com/NativeScript/nativescript-angular/compare/7.0.1...7.0.2) (2018-11-26)
+
+
+### Bug Fixes
+
+* **page-router-outlet:** actionBarVisibility not applied ([#1621](https://github.com/NativeScript/nativescript-angular/issues/1621)) ([a6ff509](https://github.com/NativeScript/nativescript-angular/commit/a6ff509))
+* **router-extensions:** unable to go back with relativeTo param ([#1632](https://github.com/NativeScript/nativescript-angular/issues/1632)) ([63900dc](https://github.com/NativeScript/nativescript-angular/commit/63900dc))
+
+
+
+
+## [7.0.1](https://github.com/NativeScript/nativescript-angular/compare/7.0.0...7.0.1) (2018-11-20)
+
+
+### Bug Fixes
+
+* crash in deactivate page router outlet ([#1590](https://github.com/NativeScript/nativescript-angular/issues/1590)) ([f8c7468](https://github.com/NativeScript/nativescript-angular/commit/f8c7468))
+* **dialogs:** unable to reopen shared modal view when tab as root ([199c245](https://github.com/NativeScript/nativescript-angular/commit/199c245))
+* **location-strategy:** crash when going back on nested named lazy loaded module ([#1618](https://github.com/NativeScript/nativescript-angular/issues/1618)) ([d9ffb83](https://github.com/NativeScript/nativescript-angular/commit/d9ffb83))
+
+
+
+
+# [7.0.0](https://github.com/NativeScript/nativescript-angular/compare/6.2.0...7.0.0) (2018-11-12)
+
+
+### Bug Fixes
+
+* crash in deactivate page router outlet ([#1590](https://github.com/NativeScript/nativescript-angular/issues/1590)) ([f8c7468](https://github.com/NativeScript/nativescript-angular/commit/f8c7468))
+
+* **location-strategy:** crash on going back to TabView with nested outlets ([#1582](https://github.com/NativeScript/nativescript-angular/issues/1582)) ([f755991](https://github.com/NativeScript/nativescript-angular/commit/f755991))
+
+
+### Features
+
+* Angular 7 support
+
+
+
+
+# [6.2.0](https://github.com/NativeScript/nativescript-angular/compare/6.1.0...6.2.0) (2018-10-30)
+
+
+### Bug Fixes
+
+* **frame-service:** move FrameService provider to NativeScriptModule ([#1489](https://github.com/NativeScript/nativescript-angular/issues/1489)) ([3b434c9](https://github.com/NativeScript/nativescript-angular/commit/3b434c9))
+* Import reflect-metadata(needed in JIT mode) before [@angular](https://github.com/angular) ([#1530](https://github.com/NativeScript/nativescript-angular/issues/1530)) ([6e45af0](https://github.com/NativeScript/nativescript-angular/commit/6e45af0))
+* Persist the original "parentNode" when "retrieving" the root View created by createEmbeddedView ([#1542](https://github.com/NativeScript/nativescript-angular/issues/1542)) ([0b8d2c5](https://github.com/NativeScript/nativescript-angular/commit/0b8d2c5))
+
+
+### Features
+
+* add actionBarVisibility property to page-router-outlet ([#1573](https://github.com/NativeScript/nativescript-angular/issues/1573)) ([c645ca8](https://github.com/NativeScript/nativescript-angular/commit/c645ca8))
+* enable nesting named page router outlets ([#1556](https://github.com/NativeScript/nativescript-angular/issues/1556)) ([46a0dc0](https://github.com/NativeScript/nativescript-angular/commit/46a0dc0))
+* HMR bootstrap and livesync options ([#1531](https://github.com/NativeScript/nativescript-angular/issues/1531)) ([1e92c7b](https://github.com/NativeScript/nativescript-angular/commit/1e92c7b))
+
+
+
+
+## [6.1.0](https://github.com/NativeScript/nativescript-angular/compare/6.0.6...6.1.0) (2018-08-06)
+
+
+### Bug Fixes
+
+* mark NativeScriptModule as root injector ([#1418](https://github.com/NativeScript/nativescript-angular/issues/1418)) ([ce70add](https://github.com/NativeScript/nativescript-angular/commit/ce70add))
+* provide NullViewportScroller in NativeScriptModule ([dd412bf](https://github.com/NativeScript/nativescript-angular/commit/dd412bf))
+* **animations:** inject document object in the animation engine ([#1395](https://github.com/NativeScript/nativescript-angular/issues/1395)) ([379e958](https://github.com/NativeScript/nativescript-angular/commit/379e958)), closes [angular/angular#23300](https://github.com/angular/angular/issues/23300) [#1393](https://github.com/NativeScript/nativescript-angular/issues/1393)
+* **forms:** TextValueAccessor raises onTouched on blur ([#1230](https://github.com/NativeScript/nativescript-angular/issues/1230)) ([06ca3a0](https://github.com/NativeScript/nativescript-angular/commit/06ca3a0))
+* remove global document object ([2b201be](https://github.com/NativeScript/nativescript-angular/commit/2b201be))
+* **location-strategy:** find the correct outlet when navigating back and forward ([#1404](https://github.com/NativeScript/nativescript-angular/issues/1404)) ([f0119a0](https://github.com/NativeScript/nativescript-angular/commit/f0119a0))
+* **modal:** lower isModalNavigation flag when closing modal ([#1378](https://github.com/NativeScript/nativescript-angular/issues/1378)) ([6ab1cac](https://github.com/NativeScript/nativescript-angular/commit/6ab1cac))
+* **modal:** throw from tns-core-modules is now properly caught and rejected ([70730d9](https://github.com/NativeScript/nativescript-angular/commit/70730d9))
+* **router:** avoiding throw for app stability improvements ([#1344](https://github.com/NativeScript/nativescript-angular/issues/1344)) ([82747df](https://github.com/NativeScript/nativescript-angular/commit/82747df))
+* **tabview:** implement setter for TabViewItem Directive's configuration ([#845](https://github.com/NativeScript/nativescript-angular/issues/845)) ([#1370](https://github.com/NativeScript/nativescript-angular/issues/1370)) ([1d44679](https://github.com/NativeScript/nativescript-angular/commit/1d44679))
+
+
+### Features
+
+* **test-bed:** Run render fixtures in a full-page container ([#1416](https://github.com/NativeScript/nativescript-angular/issues/1416)) ([e551df2](https://github.com/NativeScript/nativescript-angular/commit/e551df2))
+
+
+### BREAKING CHANGES
+
+* The `document` object is no longer property of the `global` object. This
+may cause behavioral changes in some plugin that use the `document`
+object to determine if they're running in browser context.
+
+Fixes https://github.com/NativeScript/nativescript-angular/issues/1144.
+
+
+
+## [6.0.6](https://github.com/NativeScript/nativescript-angular/compare/6.0.0...6.0.6) (2018-06-22)
+
+> IMPORTANT! You should use this version with @angular/* v6.0.6 and up.
+
+### Bug Fixes
+
+* clean up properly shared modal page router outlets ([#1360](https://github.com/NativeScript/nativescript-angular/issues/1360)) ([3332ca2](https://github.com/NativeScript/nativescript-angular/commit/3332ca2))
+* **animations:** inject document object in the animation engine ([#1395](https://github.com/NativeScript/nativescript-angular/issues/1395)) ([379e958](https://github.com/NativeScript/nativescript-angular/commit/379e958)), closes [#1393](https://github.com/NativeScript/nativescript-angular/issues/1393)
+* **modal:** lower isModalNavigation flag when closing modal ([#1378](https://github.com/NativeScript/nativescript-angular/issues/1378)) ([6ab1cac](https://github.com/NativeScript/nativescript-angular/commit/6ab1cac))
+
+
+
+# [6.0.0](https://github.com/NativeScript/nativescript-angular/compare/5.3.0...6.0.0) (2018-05-30)
+
+
+### Bug Fixes
+
+* Use cssType (uglify safe) instead typeName of for view metadata. ([d85910c](https://github.com/NativeScript/nativescript-angular/commit/d85910c))
+* **modal:** closeCallback(...) should not have side effects when called multiple times ([#1349](https://github.com/NativeScript/nativescript-angular/issues/1349)) ([bffbbc2](https://github.com/NativeScript/nativescript-angular/commit/bffbbc2))
+* **modal:** missing animated & stretched params ([#1293](https://github.com/NativeScript/nativescript-angular/issues/1293)) ([a9a901b](https://github.com/NativeScript/nativescript-angular/commit/a9a901b))
+* **router:** state is not guarded before use ([#1331](https://github.com/NativeScript/nativescript-angular/issues/1331)) ([d27a893](https://github.com/NativeScript/nativescript-angular/commit/d27a893)), closes [/github.com/NativeScript/nativescript-angular/commit/b98da83adb3f5c51ee448fa38a51b7c65274c82e#diff-a7820fa2a2eb0ce14f3f0b8bfc666dd5R49](https://github.com//github.com/NativeScript/nativescript-angular/commit/b98da83adb3f5c51ee448fa38a51b7c65274c82e/issues/diff-a7820fa2a2eb0ce14f3f0b8bfc666dd5R49)
+* **TabViewItemDirective :** textTransform property added ([#1315](https://github.com/NativeScript/nativescript-angular/issues/1315)) ([11d01f9](https://github.com/NativeScript/nativescript-angular/commit/11d01f9))
+
+
+### Features
+
+* Angular 6 support
+* **router:** enable flexible page router outlets ([#1298](https://github.com/NativeScript/nativescript-angular/issues/1298)) ([b98da83](https://github.com/NativeScript/nativescript-angular/commit/b98da83))
+* **testing:** Testing Components with TestBed ([#1175](https://github.com/NativeScript/nativescript-angular/issues/1175)) ([52f3ec6](https://github.com/NativeScript/nativescript-angular/commit/52f3ec6))
+
+
+
+# [5.3.0](https://github.com/NativeScript/nativescript-angular/compare/5.2.0...v5.3.0) (2018-04-10)
+
+> This version requires NativeScript 4.0.
+
+### Bug Fixes
+
+* **animations:** provide fake document object in both AoT and JiT mode ([#1164](https://github.com/NativeScript/nativescript-angular/issues/1164)) ([040e0e3](https://github.com/NativeScript/nativescript-angular/commit/040e0e3)), closes [#1163](https://github.com/NativeScript/nativescript-angular/issues/1163)
+* App crashes on restart in android ([#1261](https://github.com/NativeScript/nativescript-angular/issues/1261)) ([331b878](https://github.com/NativeScript/nativescript-angular/commit/331b878))
+
+
+### Features
+
+* NS 4.0 Integration ([#1250](https://github.com/NativeScript/nativescript-angular/issues/1250)) ([f84fbdc](https://github.com/NativeScript/nativescript-angular/commit/f84fbdc))
+* prevent core modules from getting loaded multiple times ([#1196](https://github.com/NativeScript/nativescript-angular/issues/1196)) ([010fed7](https://github.com/NativeScript/nativescript-angular/commit/010fed7))
+
+
+### BREAKING CHANGES
+
+#### Importing `NativeScriptModule` and `NativeScriptAnimationsModule` in multiple ngModules is no longer allowed.
+
+To migrate:
+ * in `AppModule`:
+ * import `NativeScriptModule`
+ * import`NativeScriptAnimationsModule` - only if you are planning to use Angular Animations
+ * in the remaining modules:
+ * remove `NativeScriptModule` imports and replace with `NativeScriptCommonModule` import
+ * remove `NativeScriptAnimationsModule` imports
+
+BEFORE:
+
+app.module.ts:
+```
+import { NativeScriptModule } from 'nativescript-angular/nativescript.module';
+import { NativeScriptAnimationsModule } from 'nativescript-angular/animations';
+...
+@NgModule({
+ imports: [
+ NativeScriptModule,
+ NativeScriptAnimationsModule
+ ],
+...
+})
+```
+
+another.module.ts:
+```
+import { NativeScriptModule } from 'nativescript-angular/nativescript.module';
+import { NativeScriptAnimationsModule } from 'nativescript-angular/animations';
+...
+@NgModule({
+ imports: [
+ NativeScriptModule,
+ NativeScriptAnimationsModule
+ ],
+...
+})
+```
+
+AFTER:
+
+app.module.ts:
+```
+import { NativeScriptModule } from 'nativescript-angular/nativescript.module';
+import { NativeScriptAnimationsModule } from 'nativescript-angular/animations';
+...
+@NgModule({
+ imports: [
+ NativeScriptModule,
+ NativeScriptAnimationsModule
+ ],
+...
+})
+```
+
+another.module.ts:
+```
+import { NativeScriptCommonModule } from 'nativescript-angular/common';
+...
+@NgModule({
+ imports: [
+ NativeScriptCommonModule
+ ],
+...
+})
+```
+
+#### NativeScript 4.0 Compatible Bootstrap and Navigation
+NativeScript 4.0 allows you to put any view as the root (not only Frame) of the application. To support in angular projects we had to introduce some changes in how A{N}gular apps are bootstrapped.
+
+PREVIOUS BEHAVIOR
+
+Bootstrap creates a root `Frame` and initial `Page`. Then it bootstraps the angular application inside this page. Navigation with `` will always navigate in the `Frame` created by the bootstrap.
+
+Limitations:
+- You cannot change the root view of the app (to `RadSideDrawer` for example). It is always the `Frame` created by the bootstrap.
+- You can have only one `` as there is only one `Frame`.
+- You always have a `Page` view wrapping your components. Because the `ActionBar` is part of that `Page` you can always change it with the `` component.
+
+NEW BEHAVIOR
+
+Bootstrap will **not** create root view by default. It will use the root view of your main application component as the root view of the application. The `` component will create its own `Frame` and will use it for navigation. It will also wrap the components you navigate to in a `Page` and will navigate to it as it did before.
+
+Which means:
+
+- You can use any view for application root. Finally, you can have application-wide `RadSideDrawer`.
+
+- You have more flexibility over where to place the ``, you can even have more than one for more advanced scenarios.
+
+- If you **don't use ``** in your app you will not get the default `Page` and `Frame`, which means you will not be able to inject them in you components or show the `ActionBar`. There is special `createFrameOnBootstrap` option you can pass on bootstrap to make things as _before_:
+```
+platformNativeScript({ createFrameOnBootstrap: true })
+ .bootstrapModuleFactory(AppModuleNgFactory);
+```
+
+- If you **are using ``** you probably don't have to do any changes. Bootstrap will not create `Frame` and `Page`, but the outlet will do that. It will also take care of providing `Page` and so the `ActionBar` should work as _before_.
+
+
+WORKING WITH FRAMES
+
+There might be multiple frames (if you have multiple ``'s). Angular DI works with singletons, so it will always return one instance of `Frame`. We have introduced `FrameService` (still experimental) which has a `getFrame()` method. It will return the current frame (the one you have navigated last).
+
+#### Signature of `onAfterLivesync` changed
+
+The signature `onAfterLivesync` observable changed from:
+```
+export const onAfterLivesync = new EventEmitter>();
+```
+to:
+```
+export const onAfterLivesync = new EventEmitter<{ moduleRef?: NgModuleRef; error?: Error }>();
+```
+
+
+
+# [5.2.0](https://github.com/NativeScript/nativescript-angular/compare/5.0.0...5.2.0) (2018-01-17)
+
+
+### Features
+
+* add support for Angular 5.2 ([#1154](https://github.com/NativeScript/nativescript-angular/issues/1154)) ([faa690](https://github.com/NativeScript/nativescript-angular/commit/faa690))
+
+* enable typescript 2.6 ([#1156](https://github.com/NativeScript/nativescript-angular/issues/1156)) ([2a5742b](https://github.com/NativeScript/nativescript-angular/commit/2a5742b))
+
+
+
+
+# [5.1.0](https://github.com/NativeScript/nativescript-angular/compare/5.0.0...5.1.0) (2018-01-10)
+
+
+### Features
+
+* add support for Angular 5.1 ([#1134](https://github.com/NativeScript/nativescript-angular/issues/1134)) ([2e944a8](https://github.com/NativeScript/nativescript-angular/commit/2e944a8))
+
+
+
+# [5.0.0](https://github.com/NativeScript/nativescript-angular/compare/5.0.0-rc.0...v5.0.0) (2017-12-20)
+
+
+### Features
+
+* UI must be created before first render, drainMicroTasks when the first page is created.
+This removes the white screen displayed between the launch screen and the initial page view.
+Speeds up roughly 300ms startup times for iOS with Angular. ([#1103](https://github.com/NativeScript/nativescript-angular/pull/1103))
+
+
+* update to Angular 5 animations and add support for AnimationBuilder ([#1114](https://github.com/NativeScript/nativescript-angular/issues/1114)) ([191f2a0](https://github.com/NativeScript/nativescript-angular/commit/191f2a0))
+
+### DEPRECATION
+
+NSModuleFactoryLoader is no longer needed for {N} apps. ([192a3d0](https://github.com/NativeScript/nativescript-angular/commit/192a3d0))
+
+
+Before:
+
+```
+// app.module.ts
+
+@NgModule({
+ providers: [
+ { provide: NgModuleFactoryLoader, useClass: NSModuleFactoryLoader }
+ // ...
+ ],
+ // ...
+})
+class AppModule { }
+```
+
+After:
+
+```
+// app.module.ts
+
+@NgModule({
+ providers: [
+ // ...
+ ],
+ // ...
+})
+class AppModule { }
+```
+
+
+# [5.0.0-rc.0](https://github.com/NativeScript/nativescript-angular/compare/4.4.1...5.0.0-rc.0) (2017-11-06)
+
+### Features
+
+* Initial Angular 5.0 support ([#1073](https://github.com/NativeScript/nativescript-angular/issues/1073))
+
+
+
+
+# [4.4.1](https://github.com/NativeScript/nativescript-angular/compare/4.4.0...4.4.1) (2017-10-13)
+> This is the last version of NativeScript Angular that supports Angular 4.
+
+### Bug Fixes
+
+* **forms:** add base-value-accessor.ts for <4.4.0 versions compatibility (#1039) ([79e425c](https://github.com/NativeScript/nativescript-angular/commit/79e425c))
+
+
+
+
+# [4.4.0](https://github.com/NativeScript/nativescript-angular/compare/4.2.0...4.4.0) (2017-10-11)
+
+
+### Bug Fixes
+
+* **forms:** default to unsetValue for value accessors (#846) ([6940955](https://github.com/NativeScript/nativescript-angular/commit/6940955))
+* **forms:** disable onTouch for date, number and selectedIndex value accessors (#986) ([b4b5ef6](https://github.com/NativeScript/nativescript-angular/commit/b4b5ef6)), closes [#887](https://github.com/NativeScript/nativescript-angular/issues/887)
+
+
+### Features
+
+* add `exportAs` logic for `isActive` on `routerLinkActive` directive (#940) ([147d35a](https://github.com/NativeScript/nativescript-angular/commit/147d35a))
+* **Http:** expand support for request on local files (#982) ([b95184f](https://github.com/NativeScript/nativescript-angular/commit/b95184f))
+* **styling:** Allow loading .css files as a fallback if no .scss file is found(#954) (#955) ([696e914](https://github.com/NativeScript/nativescript-angular/commit/696e914))
+* Angular 4.4 support (#1002) ([c264453](https://github.com/NativeScript/nativescript-angular/commit/c264453))
+
+
+
+
+# [4.2.0](https://github.com/NativeScript/nativescript-angular/compare/3.1.3...4.2.0) (2017-08-09)
+
+### Features
+
+* Angular 4.2 support ([#842](https://github.com/NativeScript/nativescript-angular/issues/842)) ([eb3fd81](https://github.com/NativeScript/nativescript-angular/commit/eb3fd81))
+
+
+### BREAKING CHANGES
+
+* `NativeScriptModule` should be imported only in the root application
+module (usually named `AppModule`).
+All other NgModules in the app (both feature and lazy-loaded ones)
+should import the `NativeScriptCommonModule` instead.
+The behavior is aligned with `BrowserModule` and `CommonModule` in web
+Angular apps described in [this](https://angular.io/guide/ngmodule-faq#q-browser-vs-common-module) guide.
+
+Migration steps:
+In all NgModules, except the root one (`AppModule`), replace:
+```
+import { NativeScriptModule } from "@nativescript/angular";
+…
+@NgModule({
+ imports: [
+ NativeScriptModule,
+ ]
+…
+})
+```
+with:
+```
+import { NativeScriptCommonModule } from "nativescript-angular/common";
+…
+@NgModule({
+ imports: [
+ NativeScriptCommonModule,
+ ]
+…
+})
+```
+
+
+
+
+## [3.1.3](https://github.com/NativeScript/nativescript-angular/compare/3.1.2...3.1.3) (2017-07-19)
+
+
+### Bug Fixes
+
+* **action-bar:** ignore InvisibleNodes when adding title ([#903](https://github.com/NativeScript/nativescript-angular/issues/903)) ([8308e45](https://github.com/NativeScript/nativescript-angular/commit/8308e45)), closes [#897](https://github.com/NativeScript/nativescript-angular/issues/897)
+* asynchronously destroy items evicted on clearHistory navigation ([#847](https://github.com/NativeScript/nativescript-angular/issues/847)) ([448412a](https://github.com/NativeScript/nativescript-angular/commit/448412a)), closes [#829](https://github.com/NativeScript/nativescript-angular/issues/829)
+
+
+
+
+## [3.1.2](https://github.com/NativeScript/nativescript-angular/compare/3.1.1...3.1.2) (2017-07-12)
+
+
+### Bug Fixes
+
+* **renderer:** attach `CommentNode`s to visual tree ([#888](https://github.com/NativeScript/nativescript-angular/issues/888)) ([65359fa](https://github.com/NativeScript/nativescript-angular/commit/65359fa)), closes [#872](https://github.com/NativeScript/nativescript-angular/issues/872)
+
+
+
+
+## [3.1.1](https://github.com/NativeScript/nativescript-angular/compare/3.1.0...3.1.1) (2017-06-29)
+
+
+### Bug Fixes
+
+* **forms:** add directives for formControl ([#861](https://github.com/NativeScript/nativescript-angular/issues/861)) ([#864](https://github.com/NativeScript/nativescript-angular/issues/864)) ([d29c8e1](https://github.com/NativeScript/nativescript-angular/commit/d29c8e1))
+
+
+
+
+# [3.1.0](https://github.com/NativeScript/nativescript-angular/compare/3.0.0...3.1.0) (2017-06-19)
+
+
+### Bug Fixes
+
+* **animations:** use parsers from core modules ([#844](https://github.com/NativeScript/nativescript-angular/issues/844)) ([1abebb6](https://github.com/NativeScript/nativescript-angular/commit/1abebb6)), closes [#738](https://github.com/NativeScript/nativescript-angular/issues/738)
+* **dom-adapter:** add fake implementation for getUserAgent ([#835](https://github.com/NativeScript/nativescript-angular/issues/835)) ([743131c](https://github.com/NativeScript/nativescript-angular/commit/743131c)), closes [#831](https://github.com/NativeScript/nativescript-angular/issues/831)
+* **forms:** add (touch) event and [disabled] property for controls ([#836](https://github.com/NativeScript/nativescript-angular/issues/836)) ([c8a6404](https://github.com/NativeScript/nativescript-angular/commit/c8a6404)), closes [#804](https://github.com/NativeScript/nativescript-angular/issues/804)
+* **ns-router-link:** convert clearHistory string to boolean ([#834](https://github.com/NativeScript/nativescript-angular/issues/834)) ([ff99984](https://github.com/NativeScript/nativescript-angular/commit/ff99984)), closes [#832](https://github.com/NativeScript/nativescript-angular/issues/832)
+* **renderer:** add styles when ViewEncapsulation is None ([#812](https://github.com/NativeScript/nativescript-angular/issues/812)) ([8d013e2](https://github.com/NativeScript/nativescript-angular/commit/8d013e2)), closes [#794](https://github.com/NativeScript/nativescript-angular/issues/794)
+* **renderer:** set templateParent to comment and text nodes ([#785](https://github.com/NativeScript/nativescript-angular/issues/785)) ([b127ba7](https://github.com/NativeScript/nativescript-angular/commit/b127ba7)), closes [#777](https://github.com/NativeScript/nativescript-angular/issues/777) [#787](https://github.com/NativeScript/nativescript-angular/issues/787)
+* **renderer:** stop performing value conversions ([#806](https://github.com/NativeScript/nativescript-angular/issues/806)) ([354074d](https://github.com/NativeScript/nativescript-angular/commit/354074d)), closes [#799](https://github.com/NativeScript/nativescript-angular/issues/799)
+
+### Features
+
+* **Modal:** allow modal to be lazily loaded from a module on demand ([#772](https://github.com/NativeScript/nativescript-angular/issues/772)) ([6a1f6a9](https://github.com/NativeScript/nativescript-angular/commit/6a1f6a9))
+
+
+# [3.0.0](https://github.com/NativeScript/nativescript-angular/compare/v1.5.2..3.0.0) (2017-05-03)
+
+
+### Bug Fixes
+
+* **action-bar:** Don't remove action items twice. ([b96b61f](https://github.com/NativeScript/nativescript-angular/commit/b96b61f))
+* **animations:** add onDestroy method to NativeScriptAnimationPlayer ([ec07ec3](https://github.com/NativeScript/nativescript-angular/commit/ec07ec3))
+* **animations:** set nodeType 'element' to newly created views ([#720](https://github.com/NativeScript/nativescript-angular/issues/720)) ([8af20ad](https://github.com/NativeScript/nativescript-angular/commit/8af20ad))
+* **gitignore:** Add editor files into gitignore ([9beea98](https://github.com/NativeScript/nativescript-angular/commit/9beea98))
+* **init:** Bootstrap Angular on page "navigatingTo" event. ([85b9d01](https://github.com/NativeScript/nativescript-angular/commit/85b9d01))
+* **list-view:** Destroy item views on unload ([71301aa](https://github.com/NativeScript/nativescript-angular/commit/71301aa))
+* **list-view:** Do not access destroyed items' ng views. ([c6f7549](https://github.com/NativeScript/nativescript-angular/commit/c6f7549))
+* **list-view-comp:** IterableDiffer is now parameterized on ([f692c5f](https://github.com/NativeScript/nativescript-angular/commit/f692c5f))
+* **ns-http:** make defaultOptions of type RequestOptions ([073c95d](https://github.com/NativeScript/nativescript-angular/commit/073c95d))
+* **action bar:** attach #comment nodes with _addView ([#729](https://github.com/NativeScript/nativescript-angular/issues/729)) ([0490605](https://github.com/NativeScript/nativescript-angular/commit/0490605)), closes [#725](https://github.com/NativeScript/nativescript-angular/issues/725)
+* **ns-router-link:** navigate with urlTree ([#728](https://github.com/NativeScript/nativescript-angular/issues/728)) ([3c6f5ab](https://github.com/NativeScript/nativescript-angular/commit/3c6f5ab)), closes [#724](https://github.com/NativeScript/nativescript-angular/issues/724)
+* use providers' map for injectors in page-router-outlet ([#744](https://github.com/NativeScript/nativescript-angular/issues/744)) ([07fe66c](https://github.com/NativeScript/nativescript-angular/commit/07fe66c)), closes [#741](https://github.com/NativeScript/nativescript-angular/issues/741)
+
+
+### Code Refactoring
+
+* stop exporting NativeScriptModule from platform ([#701](https://github.com/NativeScript/nativescript-angular/issues/701)) ([0bd2ba5](https://github.com/NativeScript/nativescript-angular/commit/0bd2ba5))
+
+
+### Features
+
+* **animations:** introduce NativeScriptAnimationsModule ([#704](https://github.com/NativeScript/nativescript-angular/issues/704)) ([f9ad6a5](https://github.com/NativeScript/nativescript-angular/commit/f9ad6a5))
+* **renderer:** use EmulatedRenderer to scope component styles ([70603c4](https://github.com/NativeScript/nativescript-angular/commit/70603c4))
+* **renderer:** implement createComment and createText methods using ([0f128ad](https://github.com/NativeScript/nativescript-angular/commit/0f128ad))
+* **renderer:** support namespaced attributes ([#719](https://github.com/NativeScript/nativescript-angular/issues/719)) ([9b5b413](https://github.com/NativeScript/nativescript-angular/commit/9b5b413))
+
+
+### BREAKING CHANGES
+
+* **animations:** To use animations, you need to import the
+NativeScriptAnimationsModule from "nativescript-angular/animations" in
+your root NgModule.
+* User applications cannot import NativeScriptModule from
+"nativescript-angular/platform" anymore.
+Migration:
+Before:
+```
+import { NativeScriptModule } from "nativescript-angular/platform";
+```
+After
+```
+import { NativeScriptModule } from
+"nativescript-angular/nativescript.module";
+```
+
+
+
+
+
+## [1.5.2](https://github.com/NativeScript/nativescript-angular/compare/v1.5.1...v1.5.2) (2017-04-18)
+
+
+### Bug Fixes
+
+* use providers' map for injectors in page-router-outlet ([#744](https://github.com/NativeScript/nativescript-angular/issues/744)) ([#748](https://github.com/NativeScript/nativescript-angular/issues/748)) ([c1f5d98](https://github.com/NativeScript/nativescript-angular/commit/c1f5d98)), closes [#741](https://github.com/NativeScript/nativescript-angular/issues/741)
+
+
+
+
+## [1.5.1](https://github.com/NativeScript/nativescript-angular/compare/v1.5.0...v1.5.1) (2017-03-30)
+
+
+### Bug Fixes
+
+* **action bar:** attach #comment nodes with _addView ([#729](https://github.com/NativeScript/nativescript-angular/issues/729)) ([be93db6](https://github.com/NativeScript/nativescript-angular/commit/be93db6)), closes [#725](https://github.com/NativeScript/nativescript-angular/issues/725)
+* **ns-router-link:** navigate with urlTree ([#728](https://github.com/NativeScript/nativescript-angular/issues/728)) ([71058f8](https://github.com/NativeScript/nativescript-angular/commit/71058f8)), closes [#724](https://github.com/NativeScript/nativescript-angular/issues/724)
+
+
+
+
+# [1.5.0](https://github.com/NativeScript/nativescript-angular/compare/v1.4.1...v1.5.0) (2017-03-22)
+
+### Bug Fixes
+
+* **action-bar:** Don't remove action items twice. ([677d7e0](https://github.com/NativeScript/nativescript-angular/commit/677d7e0))
+* **animations:** add onDestroy method to NativeScriptAnimationPlayer ([2e24010](https://github.com/NativeScript/nativescript-angular/commit/2e24010))
+* **dom_adapter:** add missing `contains` method signature ([bae45f6](https://github.com/NativeScript/nativescript-angular/commit/bae45f6))
+* **dom_adapter:** update setTitle and getGlobalEventTarget to be compliant with Angular API ([25c134d](https://github.com/NativeScript/nativescript-angular/commit/25c134d))
+* **gitignore:** Add editor files into gitignore ([819a960](https://github.com/NativeScript/nativescript-angular/commit/819a960))
+* **init:** Bootstrap Angular on page "navigatingTo" event. ([ab04aba](https://github.com/NativeScript/nativescript-angular/commit/ab04aba))
+* **list-view-comp:** IterableDiffer is now parameterized on \ ([780967d](https://github.com/NativeScript/nativescript-angular/commit/780967d))
+* **ns-http:** make defaultOptions of type RequestOptions ([db730e2](https://github.com/NativeScript/nativescript-angular/commit/db730e2))
+* **page-router-outlet:** activateWith instead of activate method ([8d832bc](https://github.com/NativeScript/nativescript-angular/commit/8d832bc))
+* **page-router-outlet:** manually run detect changes when navigating to new page ([07caa74](https://github.com/NativeScript/nativescript-angular/commit/07caa74))
+* **platform:** import InjectionToken and ViewEncapsulation instead of OpaqueToken ([c4dc8d4](https://github.com/NativeScript/nativescript-angular/commit/c4dc8d4))
+* **platform:** import MissingTranslationStrategy ([d2328a5](https://github.com/NativeScript/nativescript-angular/commit/d2328a5))
+* **renderer:** implement createComment and createText methods using Placeholders ([c0ec870](https://github.com/NativeScript/nativescript-angular/commit/c0ec870))
+* **renderer:** use _eachChildView for nextSibling ([150c1ce](https://github.com/NativeScript/nativescript-angular/commit/150c1ce))
+* **renderer:** use flags in `setStyle` and `removeStyle` instead of booleans ([a6d9247](https://github.com/NativeScript/nativescript-angular/commit/a6d9247))
+* **ts:** ship package with reference to iterable interface ([7edfa6b](https://github.com/NativeScript/nativescript-angular/commit/7edfa6b))
+
+
+### Code Refactoring
+
+* stop exporting NativeScriptModule from platform ([#701](https://github.com/NativeScript/nativescript-angular/issues/701)) ([409e717](https://github.com/NativeScript/nativescript-angular/commit/409e717))
+
+
+### Features
+
+* **animations:** introduce NativeScriptAnimationsModule ([b5874ba](https://github.com/NativeScript/nativescript-angular/commit/b5874ba))
+* **renderer:** implement simple nextSibling method using parent's _eachChildView ([98d9d20](https://github.com/NativeScript/nativescript-angular/commit/98d9d20))
+* **renderer:** upgrade to be compliant with Angular 4's Renderer2 and RendererFactory2 ([a3adcca](https://github.com/NativeScript/nativescript-angular/commit/a3adcca))
+* **renderer:** use EmulatedRenderer to scope component styles ([25f5111](https://github.com/NativeScript/nativescript-angular/commit/25f5111))
+
+
+### BREAKING CHANGES
+
+* **NativeScriptModule:** User applications cannot import NativeScriptModule from
+"nativescript-angular/platform" anymore.
+Migration:
+Before:
+```
+import { NativeScriptModule } from "nativescript-angular/platform";
+```
+After
+```
+import { NativeScriptModule } from
+"nativescript-angular/nativescript.module";
+```
+* **animations:** To use animations, you need to import the
+NativeScriptAnimationsModule from "nativescript-angular/animations" in
+your root NgModule. Also you need a dependency to "@angular/animations".
+
+* **typescript:** The required version of TypeScript is ~2.1. Support for ~2.2 requires changes in `tns-core-modules` and will be provided with NativeScript 3.0.
+
+
+# 1.1.2 (2016-10-28)
+
+- Angular 2.1.2 compatibility release
+
+# 1.1.1 (2016-10-21)
+
+- Angular 2.1.1 compatibility release
+- [Experimental] Support for ahead-of-time(AOT) compilation.
+
+# 1.1.0 (2016-10-13)
+
+- Angular 2.1.0 compatibility release
+
+# 1.0.2 (2016-10-13)
+
+- Angular 2.0.2 compatibility release
+- Modal dialogs no longer require that you create a ModalDialogService provider in your component.
+
+# 0.4.0 (2016-08-19)
+
+- Migrate to Angular 2 RC5.
+- Bootstrapping apps using NgModule's. The old `nativescriptBootstrap` method is gone, and apps should switch to the `platformNativeScriptDynamic().bootstrapModule(MyAppModule)` API
+- The library entrypoint is now the `nativescript-angular/platform` module. Import `NativeScriptRouterModule` from `nativescript-angular/router` and `NativeScriptFormsModule` from `nativescript-angular/forms` respectively if you want to use routing and form value accessor directives.
+
+# 0.1.8 (2016-06-22)
+
+- Migrate to Migrate to Angular RC3 and Angular Router 3.0.0-alpha.7:
+ - [Angular RC3 Release Notes](https://github.com/angular/angular/blob/master/CHANGELOG.md#200-rc3-2016-06-21)
+ - [Router Alpha.7 Release Notes](https://github.com/angular/angular/blob/master/modules/%40angular/router/CHANGELOG.md#300-alpha7-2016-06-17)
+
+- Build no more requires globally installed **typings**
+
+# 0.1.7 (2016-06-21)
+
+## Features
+
+- [(#291)](https://github.com/NativeScript/nativescript-angular/issues/291) Migrate to Angular RC2
+
+- [(#218)](https://github.com/NativeScript/nativescript-angular/issues/218) Support the new router
+
+## Bug Fixes
+
+- [(#273)](https://github.com/NativeScript/nativescript-angular/issues/273) ModalDialogService.showModal() doesn't show modal
+
+- [(#257)](https://github.com/NativeScript/nativescript-angular/issues/257) iOS navigation bug
+
+- [(#252)](https://github.com/NativeScript/nativescript-angular/issues/252) Using text-decoration in a template causes iOS app to crash
+
+- [(#262)](https://github.com/NativeScript/nativescript-angular/issues/262) Critical - Memory and cpu usage.
+
+- [(#242)](https://github.com/NativeScript/nativescript-angular/issues/242) Use the ComponentFactory API instead of deprecated DynamicComponentLoader
+
+- [(#229)](https://github.com/NativeScript/nativescript-angular/issues/229) Implement ngStyle directive
+
+## Breaking Changes
+
+- The Beta Angular Router moved to `nativescript-angular/router-deprecated` to continue using it change imports:
+ - `nativescript-angular/router` -> `nativescript-angular/router-deprecated`
+ - `nativescript-angular/router/ns-router` -> `nativescript-angular/router-deprecated/ns-router-deprecated`
+
+- Build requires globally installed **typings** (`npm install -g typings`)
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 000000000..1c845d0b1
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,83 @@
+# NativeScript Community Code of Conduct
+
+Our community members come from all walks of life and are all at different stages of their personal and professional journeys. To support everyone, we've prepared a short code of conduct. Our mission is best served in an environment that is friendly, safe, and accepting; free from intimidation or harassment.
+
+Towards this end, certain behaviors and practices will not be tolerated.
+
+## tl;dr
+
+- Be respectful.
+- We're here to help.
+- Abusive behavior is never tolerated.
+- Violations of this code may result in swift and permanent expulsion from the NativeScript community channels.
+
+## Administrators
+
+- Dan Wilson (@DanWilson on Slack)
+- Jen Looper (@jen.looper on Slack)
+- TJ VanToll (@tjvantoll on Slack)
+
+## Scope
+
+We expect all members of the NativeScript community, including administrators, users, facilitators, and vendors to abide by this Code of Conduct at all times in our community venues, online and in person, and in one-on-one communications pertaining to NativeScript affairs.
+
+This policy covers the usage of the NativeScript Slack community, as well as the NativeScript support forums, NativeScript GitHub repositories, the NativeScript website, and any NativeScript-related events. This Code of Conduct is in addition to, and does not in any way nullify or invalidate, any other terms or conditions related to use of NativeScript.
+
+The definitions of various subjective terms such as "discriminatory", "hateful", or "confusing" will be decided at the sole discretion of the NativeScript administrators.
+
+## Friendly, Harassment-Free Space
+
+We are committed to providing a friendly, safe, and welcoming environment for all, regardless of gender identity, sexual orientation, disability, ethnicity, religion, age, physical appearance, body size, race, or similar personal characteristics.
+
+We ask that you please respect that people have differences of opinion regarding technical choices, and acknowledge that every design or implementation choice carries a trade-off and numerous costs. There is seldom a single right answer. A difference of technology preferences is never a license to be rude.
+
+Any spamming, trolling, flaming, baiting, or other attention-stealing behaviour is not welcome, and will not be tolerated.
+
+Harassing other users of NativeScript is never tolerated, whether via public or private media.
+
+Avoid using offensive or harassing package names, nicknames, or other identifiers that might detract from a friendly, safe, and welcoming environment for all.
+
+Harassment includes, but is not limited to: harmful or prejudicial verbal or written comments related to gender identity, sexual orientation, disability, ethnicity, religion, age, physical appearance, body size, race, or similar personal characteristics; inappropriate use of nudity, sexual images, and/or sexually explicit language in public spaces; threats of physical or non-physical harm; deliberate intimidation, stalking or following; harassing photography or recording; sustained disruption of talks or other events; inappropriate physical contact; and unwelcome sexual attention.
+
+## Acceptable Content
+
+The NativeScript administrators reserve the right to make judgement calls about what is and isn't appropriate in published content. These are guidelines to help you be successful in our community.
+
+Content must contain something applicable to the previously stated goals of the NativeScript community. "Spamming", that is, publishing any form of content that is not applicable, is not allowed.
+
+Content must not contain illegal or infringing content. You should only publish content to NativeScript properties if you have the right to do so. This includes complying with all software license agreements or other intellectual property restrictions. For example, redistributing an MIT-licensed module with the copyright notice removed, would not be allowed. You will be responsible for any violation of laws or others’ intellectual property rights.
+
+Content must not be malware. For example, content (code, video, pictures, words, etc.) which is designed to maliciously exploit or damage computer systems, is not allowed.
+
+Content name, description, and other visible metadata must not include abusive, inappropriate, or harassing content.
+
+## Reporting Violations of this Code of Conduct
+
+If you believe someone is harassing you or has otherwise violated this Code of Conduct, please contact the administrators and send us an abuse report. If this is the initial report of a problem, please include as much detail as possible. It is easiest for us to address issues when we have more context.
+
+## Consequences
+
+All content published to the NativeScript community channels is hosted at the sole discretion of the NativeScript administrators.
+
+Unacceptable behavior from any community member, including sponsors, employees, customers, or others with decision-making authority, will not be tolerated.
+
+Anyone asked to stop unacceptable behavior is expected to comply immediately.
+
+If a community member engages in unacceptable behavior, the NativeScript administrators may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event or service).
+
+## Addressing Grievances
+
+If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify the administrators. We will do our best to ensure that your grievance is handled appropriately.
+
+In general, we will choose the course of action that we judge as being most in the interest of fostering a safe and friendly community.
+
+## Contact Info
+Please contact Dan Wilson @DanWilson if you need to report a problem or address a grievance related to an abuse report.
+
+You are also encouraged to contact us if you are curious about something that might be "on the line" between appropriate and inappropriate content. We are happy to provide guidance to help you be a successful part of our community.
+
+## Credit and License
+
+This Code of Conduct borrows heavily from the WADE Code of Conduct, which is derived from the NodeBots Code of Conduct, which in turn borrows from the npm Code of Conduct, which was derived from the Stumptown Syndicate Citizen's Code of Conduct, and the Rust Project Code of Conduct.
+
+This document may be reused under a Creative Commons Attribution-ShareAlike License.
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..80bff2ab2
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,186 @@
+# Contributing to NativeScript Angular
+
+:+1: First of all, thank you for taking the time to contribute! :+1:
+
+Here are some guides on how to do that:
+
+
+
+- [Code of Conduct](#code-of-conduct)
+- [Reporting Bugs](#reporting-bugs)
+- [Requesting Features](#requesting-features)
+- [Submitting a PR](#submitting-a-pr)
+- [Where to Start](#where-to-start)
+- [Publishing new versions](#publishing-new-versions)
+
+
+
+## Code of Conduct
+Help us keep a healthy and open community. We expect all participants in this project to adhere to the [NativeScript Code Of Conduct](https://github.com/NativeScript/codeofconduct).
+
+
+## Reporting Bugs
+
+1. Always update to the most recent master release; the bug may already be resolved.
+2. Search for similar issues in the issues list for this repo; it may already be an identified problem.
+3. If this is a bug or problem that is clear, simple, and is unlikely to require any discussion -- it is OK to open an issue on GitHub with a reproduction of the bug including workflows and screenshots. If possible, submit a Pull Request with a failing test, entire application or module. If you'd rather take matters into your own hands, fix the bug yourself (jump down to the [Submitting a PR](#submitting-a-pr) section).
+
+## Requesting Features
+
+1. Use Github Issues to submit feature requests.
+2. First, search for a similar request and extend it if applicable. This way it would be easier for the community to track the features.
+3. When requesting a new feature, please provide as much detail as possible about why you need the feature in your apps. We prefer that you explain a need rather than explain a technical solution for it. That might trigger a nice conversation on finding the best and broadest technical solution to a specific need.
+
+## Submitting a PR
+
+Before you begin:
+* Read and sign the [NativeScript Contribution License Agreement](http://www.nativescript.org/cla).
+* Make sure there is an issue for the bug or feature you will be working on.
+
+Following these steps is the best way to get you code included in the project:
+
+1. Fork and clone the nativescript-angular repo:
+```bash
+git clone https://github.com//nativescript-angular.git
+# Navigate to the newly cloned directory
+cd nativescript-angular
+# Add an "upstream" remote pointing to the original repo.
+git remote add upstream https://github.com/NativeScript/nativescript-angular.git
+```
+
+2. Read our [development workflow guide](DevelopmentWorkflow.md) for local setup:
+
+3. Create a branch for your PR
+```bash
+git checkout -b master
+```
+
+4. The fun part! Make your code changes. Make sure you:
+ - Follow the [code conventions guide](https://github.com/NativeScript/NativeScript/blob/master/CodingConvention.md).
+ - Write unit tests for your fix or feature.
+
+5. Before you submit your PR:
+ - Rebase your changes to the latest master: `git pull --rebase upstream master`.
+ - Ensure all unit test are green. Check [running unit tests](DevelopmentWorkflow.md#running-the-tests).
+ - Ensure your changes pass tslint validation. (run `npm run tslint` in the root of the repo).
+
+6. Push your fork. If you have rebased you might have to use force-push your branch:
+```
+git push origin --force
+```
+
+7. [Submit your pull request](https://github.com/NativeScript/nativescript-angular/compare). Please, fill in the Pull Request template - it will help us better understand the PR and increase the chances of it getting merged quickly.
+
+It's our turn from there on! We will review the PR and discuss changes you might have to make before merging it! Thanks!
+
+
+## Where to Start
+
+If you want to contribute, but you are not sure where to start - look for [issues labeled `help wanted`](https://github.com/NativeScript/nativescript-angular/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22).
+
+
+## Publishing new versions
+
+
+## Releasing new versions
+Instructions how to release a new version for **NativeScript Core Team Members**.
+
+
+
+1. Checkout release branch
+```
+cd nativescript-angular/nativescript-angular && git checkout release && git pull
+```
+#### If we prepare major or minor release, merge master in release branch else **skip this step**.
+```
+git merge --ff-only origin/master
+```
+*** Note: If there are commits in release branch which are not merged in master branch '-ff-merge' command will fail.
+In this case the commits should be merge firstly from release in master branch as explained in section 'Merge changes from release into master' and then repeat step 1.
+
+2. Execute `npm i` to install dependencies:
+```
+cd nativescript-angular && npm i
+cd nativescript-angular-package && npm i
+```
+3. Execute [`npm version`](https://docs.npmjs.com/cli/version) to bump the version in both `nativescript-angular` and `nativescript-angular-package` folders:
+```
+npm --no-git-tag-version version [patch|minor|major] -m "release: cut the %s release"
+```
+or
+```
+npm --no-git-tag-version version [version] --allow-same-version -m "release: cut the %s release"
+```
+NOTE: Check the changelog!!!
+
+4. Create release-branch with change log
+```
+git checkout -b release-[version]
+```
+5. Add changes
+```
+git add changed-files
+git commit -m "release: cut the %s release"
+git push
+```
+NOTE: Make sure the PR is based on release branch
+
+6. Merge PR into release branch.
+
+7. The merge will produce package with rc tag in npm. If all checks have passed, publish official package. Usually the night builds will be triggered and the package will be ready to be released on the next day.
+
+8. Don't forget to tag the release branch
+```
+git tag [version]
+git push --tags
+```
+Only if needed to Tips to remove tags:
+```
+git push --delete origin [version]
+git tag -d [version]
+```
+
+## Checkout master branch and bump version usually should be minor or major.
+
+## Merge changes from release into master
+
+## NOTE: Don't use git IDE/WEB
+
+
+
+### Here are steps described in the diagram above.
+
+1. Make sure you are in release branch:
+```
+git checkout release && git pull
+```
+2. Create PR to merge changes back in master and preserve history:
+```
+git checkout -b merge-release-in-master-[branch]/[sha]
+git push --set-upstream origin merge-release-in-master-branch-[branch]/[sha]
+git merge origin/master
+```
+3. Resolve conflicts. Choose to keep the version of master branch. If it is needed to revert versions of modules, see at the bottom.
+
+4. Add conflicts:
+```
+git add resolved files
+```
+5. Commit changes with default merge message:
+```
+git commit
+git push
+```
+
+6. Create pull request which should be based on master. Replace replace env merge-release-in-master-branch with its value
+```
+curl -d '{"title": "chore: merge release in master","body": "chore: merge release in master","head": "merge-release-in-master","base": "master"}' -X POST https://api.github.com/repos/NativeScript/NativeScript/pulls -H "Authorization: token ${GIT_TOKEN}"
+```
+
+**If needed, to revert file and take it from master:**
+```
+git checkout origin/master nativescript-angular/[some-file]
+git commit --amend
+git push --force-with-lease
+```
+This could require to repeat steps from 1 to 4, since we need to keep the branches with the same history
diff --git a/DEVELOPERS.md b/DEVELOPERS.md
deleted file mode 100644
index d0156d4e5..000000000
--- a/DEVELOPERS.md
+++ /dev/null
@@ -1,46 +0,0 @@
-# NativeScript - Angular 2 integration
-
-## Running stuff locally
-
-Clone the repo and cd to the local dir.
-
-Fetch the git submodules:
-
-```sh
- git submodule --init
- git submodule --update
-```
-
-Install the npm requirements:
-
-```sh
- npm install
-```
-
-Install the angular npm requirements:
-
-```sh
- cd angular
- npm install
- cd .. # back to the project root
-```
-
-Install the angular typings:
-
-```sh
- cd deps/angular/modules/angular2
- tsd reinstall
- cd ../../../../ # back to the project root
-```
-
-Prepare the local angular2 & NativeScript codebases in src/*:
-
-```sh
- grunt prepare
-```
-
-Compile the project:
-
-```sh
- grunt ts
-```
diff --git a/DevelopmentWorkflow.md b/DevelopmentWorkflow.md
new file mode 100644
index 000000000..5df7f425b
--- /dev/null
+++ b/DevelopmentWorkflow.md
@@ -0,0 +1,125 @@
+# Development Workflow
+
+
+
+- [Development Workflow](#development-workflow)
+ - [Running locally](#running-locally)
+ - [Prerequisites](#prerequisites)
+ - [Clone repository](#clone-repository)
+ - [Install dependencies of the compatibility package (nativescript-angular)](#install-dependencies-of-the-compatibility-package-nativescript-angular)
+ - [Install dependencies of the scoped package (@nativescript/angular)](#install-dependencies-of-the-scoped-package-nativescriptangular)
+ - [Run some of the e2e applications e.g. router-tab-view](#run-some-of-the-e2e-applications-eg-router-tab-view)
+ - [Running the tests](#running-the-tests)
+ - [Testing locally by running e2e tests](#testing-locally-by-running-e2e-tests)
+
+
+
+## Running locally
+
+### Prerequisites
+
+Install your native toolchain and NativeScript as described in the docs:
+
+https://docs.nativescript.org/angular/start/quick-setup
+
+### Clone repository
+
+```
+$ git clone git@github.com:NativeScript/nativescript-angular.git
+$ cd nativescript-angular
+```
+
+### Install dependencies of the compatibility package (nativescript-angular)
+
+```
+$ cd nativescript-angular
+$ npm install
+```
+
+### Install dependencies of the scoped package (@nativescript/angular)
+
+```
+$ cd nativescript-angular-package
+$ npm install
+```
+
+### Run some of the e2e applications e.g. router-tab-view
+
+Install NPM packages (use the local copy of `nativescript-angular`):
+```
+$ cd e2e/router-tab-view
+$ npm install
+```
+
+Start the app:
+
+```
+$ tns run android
+$ tns run ios
+```
+
+Make changes to `nativescript-angular` (in `./nativescript-angular-package` folder) or `@nativescript/angular` (in `./nativescript-angular` folder) and see them applied in the running app.
+
+## Running the tests
+
+Install the NPM dependencies (use the local copy of `nativescript-angular`):
+```
+$ cd tests
+$ npm install
+```
+
+Run the tests:
+
+```
+$ tns test ios
+$ tns test android
+```
+
+## Testing locally by running e2e tests
+
+NOTE: The steps below describe how to run `renderer` tests, but the same approach can be used to run `router` or any other `e2e` tests.
+
+1. Navigate to `e2e/renderer`
+ ``` bash
+ cd e2e/renderer
+ ```
+
+2. Install dependencies. This also installs your local copy of the nativescript-angular plugin.
+ ``` bash
+ npm install
+ ```
+3. Make sure to have an emulator set up or connect a physical Android/iOS device.
+
+4. Build the app for Android or iOS
+ ```bash
+ tns run android/ios
+ ```
+
+5. Install [appium](http://appium.io/) globally.
+ ``` bash
+ npm install -g appium
+ ```
+
+6. Follow the instructions in the [nativescript-dev-appium](https://github.com/nativescript/nativescript-dev-appium#custom-appium-capabilities) plugin to add an appium capability for your device inside `./e2e/renderer/e2e/config/appium.capabilities.json`.
+
+7. Run the automated tests. The value of the `runType` argument should match the name of the capability that you just added.
+ ``` bash
+ npm run e2e -- --runType capabilityName
+ ```
+
+## Building Packages
+
+1. Build `@nativescript/angular` (a.k.a. "scoped package"):
+ ```
+ cd nativescript-angular
+ npm install
+ npm run pack
+ ```
+
+2. Build `nativescript-angular` (a.k.a. "compat-package"):
+ ```
+ cd nativescript-angular-package
+ npm install
+ npm run pack-with-scoped-version -- ../dist/nativescript-angular-scoped.tgz
+ ```
+Packages are available in the `dist` folder.
diff --git a/LICENSE b/LICENSE
index 49e2a9faf..e519ff171 100755
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,192 @@
- Copyright (c) 2015 Telerik AD
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright (c) 2020 nStudio, LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -10,4 +198,4 @@
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
- limitations under the License.
\ No newline at end of file
+ limitations under the License.
diff --git a/NativeScript-dependencies.md b/NativeScript-dependencies.md
new file mode 100644
index 000000000..060d19bfc
--- /dev/null
+++ b/NativeScript-dependencies.md
@@ -0,0 +1,30 @@
+Working with NativeScript dependencies
+======================================
+
+Shipping features or bug fixes for the nativescript-angular plugin sometimes requires changes to the core NativeScript framework. This document outlines a strategy for upgrading dependencies and configuring client apps.
+
+## Depending on an unofficial NativeScript modules version
+
+NativeScript modules are configured and built in the `deps/NativeScript` git submodule. This lets us work with changed versions locally (`ng-sample`, etc), but doesn't allow customers to test the new builds. To be able to distribute unofficial builds, we introduced the `angular` NPM tag for the `tns-core-modules` project.
+
+Switching to an unofficial `tns-core-modules` build:
+
+1. Checkout the correct version of the `deps/NativeScript` subrepo.
+2. Modify `package.json` and change the version. We use a versioning scheme that looks like "1.6.0-angular-0". The "1.6.0" part above is the base version, and you can increment the "angular-0" part when shipping modified versions.
+3. Commit the modified `package.json` and force push that to the [angular](https://github.com/NativeScript/NativeScript/tree/angular) branch.
+4. Update the submodule reference to the new branch and push to master.
+5. Build your tns-core-modules package.
+6. CAREFUL! Publish the new versions USING THE `angular` NPM TAG: `npm publish tns-core-modules-1.6.0.tgz --tag angular`. See below if you mess things up and forget the tag part.
+7. Update `nativescript-angular` package.json dependency to point to the new modules package you just published.
+8. NPM publish the `nativescript-angular` package.
+
+## Angular client app configuration
+
+Apps need to have their package.json files set up so that they depend on the `nativescript-angular` package. It should pull the correct `x.y.z-angular-w` build from NPM.
+
+## Fixing broken NPM publishes (forgotten angular tag)
+
+So, you forgot the `--tag angular` part, and now everyone will get the unofficial build when s/he creates a new app now, eh? Here's how to fix it:
+
+1. Check the NPM tags: `npm dist-tag ls tns-core-modules`
+2. Switch the `latest` tag back to the correct version: `npm dist-tag add tns-core-modules@1.5.2 latest`
diff --git a/PULL_REQUEST_TESTS.md b/PULL_REQUEST_TESTS.md
new file mode 100644
index 000000000..db4207541
--- /dev/null
+++ b/PULL_REQUEST_TESTS.md
@@ -0,0 +1,24 @@
+### Pull Requests Checks
+
+Builds distributed between Travis CI and internal Jenkins CI execute on pull requests. By default, Travis CI builds trigger on community pull requests and both Travis CI and Jenkins CI builds trigger on core team pull requests. Only NativeScript members have rights to trigger internal Jenkins CI builds. In long term, we aim to migrate all the builds in a public cloud environment.
+
+### Travis
+
+This builds the `nativescript-angular` package and the [tests](https://github.com/NativeScript/nativescript-angular/tree/master/tests) app for Android. Please, refer to the [.travis.yml](https://github.com/NativeScript/nativescript-angular/blob/master/.travis.yml) config file for complete information.
+
+### Jenkins
+
+More extensive tests execute in Jenkins CI. As it is an internal environment, these builds cannot be triggered from people outside the NativeScript organization. In order to trigger a build mannually, you need to comment inside the pull request. The table below describes the comment messages and the locations of the app/tests of all builds available to execute tests on pull requests.
+
+|Comment |Description|
+|:------------------:|:---------:|
+|`run ci` |Executes all described below.|
+|`renderer-android` |Executes [renderer](https://github.com/NativeScript/nativescript-angular/tree/master/e2e/renderer) tests app for Android.|
+|`renderer-ios` |Executes [renderer](https://github.com/NativeScript/nativescript-angular/tree/master/e2e/renderer) tests app for iOS.|
+|`router-android` |Executes [router](https://github.com/NativeScript/nativescript-angular/tree/master/e2e/router) tests app for Android.|
+|`router-ios` |Executes [router](https://github.com/NativeScript/nativescript-angular/tree/master/e2e/router) tests app for iOS.|
+|`sdkwebpack` |Webpacks [SDK](https://github.com/NativeScript/nativescript-sdk-examples-ng) examples app for both Android and iOS.|
+|`tests-android` |Executes [tests](https://github.com/NativeScript/nativescript-angular/tree/master/tests) app for Android.|
+|`tests-ios` |Executes [tests](https://github.com/NativeScript/nativescript-angular/tree/master/tests) app for iOS.|
+|`testsappng-android`|Executes [TestsAppNg](https://github.com/NativeScript/tests-app-ng) app for Android.|
+|`testsappng-ios` |Executes [TestsAppNg](https://github.com/NativeScript/tests-app-ng) app for iOS.|
diff --git a/README.md b/README.md
index 81ef7e346..8819a1595 100644
--- a/README.md
+++ b/README.md
@@ -1,94 +1,49 @@
-Integrating NativeScript with Angular 2.
+## Note on repo
-# Running locally
+This repo is for Angular <= 11. If looking for 12+ (including 18+, etc) it's maintained here [NativeScript/angular](https://github.com/NativeScript/angular).
-## Get submodule code
+# NativeScript Angular
+[](https://travis-ci.org/NativeScript/nativescript-angular)
-The project bundles the NativeScript modules and Angular as source code dependencies set up as git submodules. You need to get them by running:
+This repository contains the code for integration of NativeScript with Angular.
-```
-$ git submodule update --init
-```
+[NativeScript](https://www.nativescript.org/) is a framework which enables developers to write truly native mobile applications for Android and iOS using JavaScript and CSS. [Angular](https://angular.io/) is one of the most popular open source JavaScript frameworks for application development. We [worked closely with developers at Google](http://angularjs.blogspot.bg/2015/12/building-mobile-apps-with-angular-2-and.html) to make Angular in NativeScript a reality. The result is a software architecture that allows you to build mobile apps using the same framework—and in some cases the same code—that you use to build Angular web apps, with the performance you’d expect from native code. [Read more about building truly native mobile apps with NativeScript and Angular](https://docs.nativescript.org/tutorial/ng-chapter-0).
-## Install dependencies
-```
-$ npm install -g grunt-cli
-$ npm install -g tsd
-$ npm install -g nativescript
-```
+
-You may need to configure your `tsd` GitHub access token to avoid rate-limit-related download errors. See the token installation instructions in the `.tsdrc` section [here](https://github.com/DefinitelyTyped/tsd#tsdrc).
+- [NativeScript Angular](#nativescript-angular)
+ - [Watch the video explaining Angular and NativeScript](#watch-the-video-explaining-angular-and-nativescript)
+ - [Explore the examples](#explore-the-examples)
+ - [Contribute](#contribute)
+ - [Known issues](#known-issues)
+ - [Get Help](#get-help)
-You will also need the [Android SDK](https://developer.android.com/sdk/) to build this project. Install it manually before continuing with the next steps.
+
-Then install the NativeScript tools according to: [this article](http://docs.nativescript.org/setup/quick-setup).
-Then install the needed NPM packages:
-
-```
-$ npm install
-```
-
-## Compile and prepare NativeScript and Angular
-
-```
-$ (cd deps/angular/modules/angular2 && tsd reinstall)
-$ grunt prepare
-```
-
-## Initialize the test NativeScript app (ng-sample)
-
-```
-$ grunt shell:ngSampleInit
-```
-
-## Compile the app and update its `./src` dir:
-
-```
-$ grunt ng-sample
-```
-
-# Developer workflow:
-
-1. Make some changes to the app or `src/nativescript-angular`
-2. Navigate to the ng-sample subdir: `$ cd ng-sample`
-2. Compile app: `$ grunt app`
-3. Run in emulator `$ tns emulate android --avd `
-
-Protip: combine #2 and #3 above in a single command run from the project root directory (works only on Unixy shells):
-
-```
-$ (cd ng-sample && grunt app && tns emulate android --avd nexus4-x64)
-```
-
-On Windows, you can wrap those commands in a BAT file.
-
-# Finding your way around
-
-1. The `./src/nativescript-angular` folder holds the integration source code.
-2. The sample app below `ng-sample` is assembled by copying typescript dependencies in its `src` folder:
- * angular2 source code
- * nativescript-angular code
- * NativeScript typings: typings/nativescript
-3. All required JavaScript packages (including compiled NativeScript modules) are copied to `ng-sample/app/tns_modules`
-
-# How the integration code works
+## Watch the video explaining Angular and NativeScript
+[NativeScript session on AngularConnect conference](https://www.youtube.com/watch?v=4SbiiyRSIwo)
-1. Use the Angular Parse5DomAdapter to parse component markup.
-2. Provide a custom renderer (`NativeScriptRenderer`) that works with the parsed DOM and creates NativeScript UI elements. Only limited number of visual elements supported so far.
+## Explore the examples
-# Watch the video explaining Angular 2 and NativeScript
-[NativeScript session on AngularConnect conference](https://www.youtube.com/watch?v=4SbiiyRSIwo)
+The `e2e` apps are meant for testing stuff. You can take a look at these additional sample apps that use the published builds from npm:
-# Explore the examples
+* [Hello world starter](https://github.com/NativeScript/nativescript-app-templates/tree/master/packages/template-hello-world-ng)
+* [Master-detail template](https://github.com/NativeScript/nativescript-app-templates/tree/master/packages/template-master-detail-ng)
+* [Drawer navigation template](https://github.com/NativeScript/nativescript-app-templates/tree/master/packages/template-drawer-navigation-ng)
+* [TabView navigation template](https://github.com/NativeScript/nativescript-app-templates/tree/master/packages/template-tab-navigation-ng)
+* [NativeScript Angular SDK examples](https://github.com/NativeScript/nativescript-sdk-examples-ng)
-* [Hello world starter](https://github.com/NativeScript/template-hello-world-ng)
-* [TodoMVC sample implementation](https://github.com/NativeScript/sample-ng-todomvc)
+## Contribute
+We love PRs! Check out the [contributing guidelines](CONTRIBUTING.md) and [development workflow for local setup](DevelopmentWorkflow.md). If you want to contribute, but you are not sure where to start - look for [issues labeled `help wanted`](https://github.com/NativeScript/nativescript-angular/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22).
-# Known issues
+## Known issues
1. There are certain issues with the Parse5DomAdapter and we'll likely need to provide our own later on:
- * Element and attribute names always get lowercased.
* Self-closing elements (``) get parsed wrong (in this case Button gets parsed as a Label child.
-2. The renderer implementation is by no means complete: we are still need to support view (de)hydration, DOM text updates, event dispatching, actions, etc.
+
+## Get Help
+Please, use [github issues](https://github.com/NativeScript/nativescript-angular/issues) strictly for [reporting bugs](CONTRIBUTING.md#reporting-bugs) or [requesting features](CONTRIBUTING.md#requesting-new-features). For general questions and support, check out [Stack Overflow](https://stackoverflow.com/questions/tagged/nativescript) or ask our experts in [NativeScript community Slack channel](http://developer.telerik.com/wp-login.php?action=slack-invitation).
+
+
diff --git a/build-doc-snippets.sh b/build-doc-snippets.sh
new file mode 100755
index 000000000..0550b3203
--- /dev/null
+++ b/build-doc-snippets.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+set -e
+
+ENV="${ENV:-dev}"
+DIST_DIR="bin/dist"
+TARGET_DIR="$DIST_DIR/snippets"
+PACKAGE_VERSION="${PACKAGE_VERSION:-0.0.0}"
+
+extractSnippets() {
+ BIN="./node_modules/markdown-snippet-injector/extract.js"
+ node "$BIN" --root="." --target="$TARGET_DIR" \
+ --sourceext=".js|.ts|.xml|.html|.css"
+}
+
+npm install markdown-snippet-injector
+rm -rf "$TARGET_DIR"
+mkdir -p "$TARGET_DIR"
+
+extractSnippets
+
+(cd "$DIST_DIR" && tar zcvf "nativescript-angular-snippets-$ENV-$PACKAGE_VERSION.tar.gz" snippets)
diff --git a/build-docs.sh b/build-docs.sh
new file mode 100755
index 000000000..a0f39ec85
--- /dev/null
+++ b/build-docs.sh
@@ -0,0 +1,9 @@
+set -e
+
+ENV="${ENV:-dev}"
+DIST_DIR="nativescript-angular/bin/dist"
+APIREF_DIR="$DIST_DIR/ng-api-reference"
+rm -rf "$APIREF_DIR"
+cd "nativescript-angular"
+npm install
+npm run typedoc
diff --git a/build/pack-scripts/pack-compat.ts b/build/pack-scripts/pack-compat.ts
new file mode 100644
index 000000000..1df263165
--- /dev/null
+++ b/build/pack-scripts/pack-compat.ts
@@ -0,0 +1,73 @@
+import * as path from "path";
+import * as fs from "fs-extra";
+import { execSync } from "child_process";
+
+/**
+ * Use this script to pack .tgz for nativescript-angular package. The first passed param can be:
+ * 1. Path to .tgz file - in this case the script replaces the scoped dependency (@nativescript/angular) with it in the package.json file. Then packs.
+ * 2. Tag or exact version - in this case the script does `npm install --save-exact` to save the exact version (in case if tag). Then packs.
+ * 3. `auto-version` - this is interpreted by getting version from the scoped package.json file (nativescript-angular folder).
+ */
+
+var scopedVersion = process.argv[2];
+
+console.log(`Packing nativescript-angular package with @nativescript/angular: ${scopedVersion}`);
+
+const distFolderPath = path.resolve("../../dist");
+const tempFolderPath = path.resolve("./temp-compat");
+const outFileName = "nativescript-angular-compat.tgz";
+
+const nsAngularScopedPackageJSONPath = path.resolve("../../nativescript-angular/package.json");
+const nsAngularPackagePath = path.resolve("../../nativescript-angular-package");
+const packageJsonPath = path.resolve(`${nsAngularPackagePath}/package.json`);
+console.log("Getting package.json from", packageJsonPath);
+
+
+function prepareCompatPackageJSON(scopedVersion: string) {
+ const packageJsonObject = JSON.parse(fs.readFileSync(packageJsonPath, { encoding: "utf8" }));
+ packageJsonObject.dependencies["@nativescript/angular"] = scopedVersion;
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJsonObject, null, 4));
+}
+
+if (scopedVersion === "auto-version") {
+ // We use this when build for release. In this case we need to get version from scoped package (nativescript-angular)
+ // and use it in the compat package.
+
+ scopedVersion = JSON.parse(fs.readFileSync(nsAngularScopedPackageJSONPath, { encoding: "utf8" })).version;
+ prepareCompatPackageJSON(scopedVersion)
+} else {
+ let npmInstallParams = "";
+
+ if (scopedVersion.indexOf(".tgz") > 0) {
+ // If building with .tgz, we need to update the package.json of the compat package
+ prepareCompatPackageJSON(scopedVersion)
+ } else {
+ // If building with tag or exact version, just install it with --save-exact
+ npmInstallParams = `@nativescript/angular@${scopedVersion}`;
+ }
+
+ execSync(`npm install --save-exact ${npmInstallParams}`, {
+ cwd: nsAngularPackagePath
+ });
+}
+
+// ensure empty temp and existing dist folders
+fs.emptyDirSync(tempFolderPath);
+fs.ensureDirSync(distFolderPath);
+
+// Install, run tsc and run ngc
+execSync(`npm i && npm run tsc && npm run ngc`, {
+ cwd: nsAngularPackagePath
+});
+
+// create .tgz in temp folder
+execSync(`npm pack ${nsAngularPackagePath}`, {
+ cwd: tempFolderPath
+});
+
+// assume we have a single file built in temp folder, take its name
+const currentFileName = fs.readdirSync(tempFolderPath)[0];
+
+// move built file and remove temp folder
+fs.moveSync(`${tempFolderPath}/${currentFileName}`, `${distFolderPath}/${outFileName}`, { overwrite: true });
+fs.removeSync(`${tempFolderPath}`);
diff --git a/build/pack-scripts/pack-scoped.ts b/build/pack-scripts/pack-scoped.ts
new file mode 100644
index 000000000..8d5605b84
--- /dev/null
+++ b/build/pack-scripts/pack-scoped.ts
@@ -0,0 +1,33 @@
+import * as path from "path";
+import * as fs from "fs-extra";
+import { execSync } from "child_process";
+
+console.log(`Packing @nativescript/angular package`);
+
+const distFolderPath = path.resolve("../../dist");
+const outFileName = "nativescript-angular-scoped.tgz";
+
+const nsAngularPackagePath = path.resolve("../../nativescript-angular");
+const nsAngularPackageDistPath = path.resolve(nsAngularPackagePath + "/dist");
+
+function getFilesFromPath(path, extension) {
+ let files = fs.readdirSync( path );
+ return files.filter(file => file.match(new RegExp(`.*\.(${extension})`, 'ig')));
+}
+
+// execSync(`npm install --save-exact`, {
+// cwd: nsAngularPackagePath
+// });
+
+// ensure empty temp and dist folders
+fs.ensureDirSync(distFolderPath);
+
+// create .tgz in temp folder
+execSync(`cd ${nsAngularPackagePath} && npm run build.pack`);
+
+// assume we have a single file built in temp folder, take its name
+const currentFileName = getFilesFromPath(nsAngularPackageDistPath, ".tgz")[0];
+console.log('currentFileName:', currentFileName);
+
+// move built file and remove temp folder
+fs.moveSync(`${nsAngularPackageDistPath}/${currentFileName}`, `${distFolderPath}/${outFileName}`, { overwrite: true });
diff --git a/build/pack-scripts/package.json b/build/pack-scripts/package.json
new file mode 100644
index 000000000..3cdfbcd0e
--- /dev/null
+++ b/build/pack-scripts/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "build",
+ "version": "1.0.0",
+ "description": "",
+ "main": "prepublish-next.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC",
+ "devDependencies": {
+ "@types/node": "^12.7.12",
+ "fs-extra": "^9.0.0",
+ "rimraf": "^3.0.0",
+ "ts-node": "^8.10.2",
+ "typescript": "^3.8.3"
+ }
+}
diff --git a/build/pack-scripts/tsconfig.json b/build/pack-scripts/tsconfig.json
new file mode 100644
index 000000000..0bda36752
--- /dev/null
+++ b/build/pack-scripts/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es2015",
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "noEmitHelpers": true,
+ "noEmitOnError": true,
+ "skipLibCheck": true,
+ "lib": [
+ "es2017",
+ "dom",
+ "es6"
+ ],
+ "types": [
+ "node"
+ ],
+ "typeRoots": [ "./node_modules/@types" ]
+ },
+ "include": [
+ "./**/*.ts"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}
\ No newline at end of file
diff --git a/build/prepublish-next.js b/build/prepublish-next.js
new file mode 100644
index 000000000..e320c99f8
--- /dev/null
+++ b/build/prepublish-next.js
@@ -0,0 +1,33 @@
+#!/usr/bin/env node
+const fs = require("fs");
+const path = require("path");
+
+const getPackageJson = projectDir => {
+ const packageJsonPath = getPackageJsonPath(projectDir);
+ return JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
+};
+
+const writePackageJson = (content, projectDir) => {
+ const packageJsonPath = getPackageJsonPath(projectDir);
+ fs.writeFileSync(packageJsonPath, JSON.stringify(content, null, 2))
+}
+
+const getPackageJsonPath = projectDir => path.resolve(projectDir, "package.json");
+
+const tag = "next";
+const projectDir = "nativescript-angular";
+const packageJson = getPackageJson(projectDir);
+const [, , packageVersion = new Date() ] = process.argv;
+
+packageJson.publishConfig = Object.assign(
+ packageJson.publishConfig || {},
+ { tag }
+);
+
+delete packageJson.private;
+
+const currentVersion = packageJson.version;
+const nextVersion = `${currentVersion}-${packageVersion}`;
+const newPackageJson = Object.assign(packageJson, { version: nextVersion });
+
+writePackageJson(newPackageJson, projectDir);
diff --git a/deps/NativeScript b/deps/NativeScript
deleted file mode 160000
index 57b3d9412..000000000
--- a/deps/NativeScript
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 57b3d9412fde21b5ce671e4113d75d3d02f0b0b5
diff --git a/deps/angular b/deps/angular
deleted file mode 160000
index 3de60a412..000000000
--- a/deps/angular
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 3de60a4120371dd232c2d9089d7363bd16c3aa51
diff --git a/doc/README.md b/doc/README.md
deleted file mode 100644
index bdf188f44..000000000
--- a/doc/README.md
+++ /dev/null
@@ -1,101 +0,0 @@
-Bringing NativeScript and Angular 2 together. This project implements a renderer that lets you build native mobile apps with Angular using the NativeScript widgets.
-
-# Prerequisites
-
-## TSD for TypeScript declarations
-
-You may need to configure your `tsd` GitHub access token to avoid rate-limit-related download errors. See the token installation instructions in the `.tsdrc` section [here](https://github.com/DefinitelyTyped/tsd#tsdrc).
-
-## Mobile SDK's
-
-You will also need the [Android SDK](https://developer.android.com/sdk/) to build this project. Install it manually before continuing with the next steps.
-
-iOS development requires an OS X machine. Details available in the NativeScript [documentation](http://docs.nativescript.org/setup/ns-cli-setup/ns-setup-os-x).
-
-## NativeScript
-
-Then install the NativeScript tools according to: [this article](http://docs.nativescript.org/setup/quick-setup).
-
-# Adding dependencies
-
-Edit your `package.json` file and add the following dependencies:
-
-```json
-
- "dependencies": {
- "angular2": "2.0.0-alpha.36",
- "nativescript-angular": "0.0.2",
- "parse5": "1.4.2",
- "punycode": "1.3.2",
- "querystring": "0.2.0",
- "reflect-metadata": "0.1.0",
- "rtts_assert": "2.0.0-alpha.36",
- "rx": "2.5.1",
- "url": "0.10.3",
- "zone.js": "0.5.3"
- }
-
-```
-
-Remember to run `npm install` to fetch the modules from NPM.
-
-# Getting TypeScript declarations
-
-Once you have the `tsd` tool installed you can use it to get the needed TypeScript definitions.
-
-
-Get the ones for Angular and related components:
-
-`$ tsd install angular2 rx es6-promise --save`
-
-Then include the bundled `d.ts` files from the `nativescript-angular` package.
-
-`$ tsd link`
-
-
-# Bootstrapping an Angular app with NativeScript
-
-This is not much different than a regular Angular web app. You need to import the `nativeScriptBootstrap` function:
-
-```typescript
-import 'reflect-metadata';
-import {nativeScriptBootstrap} from 'nativescript-angular/application';
-```
-
-(Note that you need `reflect-metadata` imported beforehand -- the Angular DI system depends on it.)
-
-
-Then call the bootstrap function in a `loaded` event handler:
-
-```typescript
-export function pageLoaded(args) {
- console.log('BOOTSTRAPPING...');
- nativeScriptBootstrap(MainPage, []).then((appRef) => {
- console.log('ANGULAR BOOTSTRAP DONE.');
- }, (err) =>{
- console.log('ERROR BOOTSTRAPPING ANGULAR');
- let errorMessage = err.message + "\n\n" + err.stack;
- console.log(errorMessage);
- });
-}
-```
-
-In the example above `MainPage` is an Angular component that looks roughly like this:
-
-```typescript
-@Component({
- selector: 'main'
-})
-@View({
- template: `
-
-`,
-})
-class MainPage {
- //...
-}
-```
-
-# Sample apps
-
-* [TodoMVC](https://github.com/NativeScript/sample-ng-todomvc)
diff --git a/doc/upgrading-zonejs.md b/doc/upgrading-zonejs.md
new file mode 100644
index 000000000..6ea5d173f
--- /dev/null
+++ b/doc/upgrading-zonejs.md
@@ -0,0 +1,15 @@
+# Upgrading Zone.js
+
+`nativescript-angular` uses a fork of the `zone.js` package in order to work around incompatibilities between node, browser, and mobile implementations.
+
+The fork resides at https://github.com/NativeScript/zone.js in the `zone-nativescript` branch. It adds a separate `lib/nativescript/nativescript.ts` entry point that is used to generate a new bundle: `zone-nativescript.js`.
+
+To upgrade to a newer release of `zone.js`:
+
+1. Identify the upgrade target -- most likely a release tag.
+2. Rebase the `zone-nativescript` branch on top of the upgrade target.
+3. Rebuild: `gulp build`
+4. Run the node-based smoke tests: `gulp test/nativescript`
+5. Run the browser tests: `node_modules/.bin/karma start karma.conf.js --single-run` (You need to run node `test/ws-server.js` in a separate console first)
+6. Commit `zone-nativescript.js`, drop the previous `zone-nativescript.js` commit from the branch. Force push the new `zone-nativescript` branch to GitHub.
+7. Update your copy of `nativescript-angular/zone.js/dist/zone-nativescript.js` with the bundle you just built.
diff --git a/e2e/README.md b/e2e/README.md
new file mode 100644
index 000000000..32bd92ad0
--- /dev/null
+++ b/e2e/README.md
@@ -0,0 +1,50 @@
+# NativeScript Angular E2E Tests
+
+This folder contains a number of projects containing e2e tests for NativeScript Angular integration.
+
+## Projects Description
+
+ - [`renderer`](renderer) - Tests for the nativescript-angular renderer features. Creating/adding/removing elements for the nativescript visual tree.
+
+ - [`router`](router) - Tests for angular router and navigation. This app contains a mixed scenario with `` and ``.
+
+ - [`single-page`](single-page) - Tests for angular router and navigation. This is a simple app with only one level navigation using ``.
+
+ - [`modal-navigation-ng`](modal-navigation-ng) - Tests for different scenarios of showing and navigation in modal dialogs.
+
+ - [`router-tab-view`](router-tab-view) - Tests for navigation in a TabComponent containing named(aux) `` instances.
+
+## Global Appium Setup
+
+Install external dependencies for nativescript-dev-appium described [here](https://github.com/NativeScript/nativescript-dev-appium#setup).
+
+
+All projects use the same [appium capabilities files](config/appium.capabilities.json).
+
+
+## Running Tests
+
+Make sure you build or run the project with the NativeScript CLI first:
+```
+tns build android
+tns run ios
+```
+
+Run appium tests with the `e2e` npm task. Check ns-dev-appium options [here](https://github.com/NativeScript/nativescript-dev-appium#options)
+
+Examples:
+
+```
+npm run e2e -- --runType android23
+
+npm run e2e -- --runType sim.iPhone8.iOS112
+```
+
+For **development** you can use `--devMode` flag to reuse the current emulator and the currently installed application:
+
+Examples:
+```
+npm run e2e -- --runType android23 --devMode
+
+npm run e2e -- --runType sim.iPhone8.iOS112 --devMode
+```
diff --git a/e2e/animation-examples/.gitignore b/e2e/animation-examples/.gitignore
new file mode 100644
index 000000000..2e6e00a8f
--- /dev/null
+++ b/e2e/animation-examples/.gitignore
@@ -0,0 +1,21 @@
+node_modules
+platforms
+hooks
+.vscode
+
+app/**/*.js
+app/**/*.map
+
+e2e/**/*.js
+e2e/**/*.map
+
+# Webpack files
+tsconfig.esm.json
+webpack.config.js
+
+#tests
+instruments*
+e2e/reports
+test-results.xml
+.DS_Store
+mochawesome-report
\ No newline at end of file
diff --git a/e2e/animation-examples/CODE_OF_CONDUCT.md b/e2e/animation-examples/CODE_OF_CONDUCT.md
new file mode 100644
index 000000000..1c845d0b1
--- /dev/null
+++ b/e2e/animation-examples/CODE_OF_CONDUCT.md
@@ -0,0 +1,83 @@
+# NativeScript Community Code of Conduct
+
+Our community members come from all walks of life and are all at different stages of their personal and professional journeys. To support everyone, we've prepared a short code of conduct. Our mission is best served in an environment that is friendly, safe, and accepting; free from intimidation or harassment.
+
+Towards this end, certain behaviors and practices will not be tolerated.
+
+## tl;dr
+
+- Be respectful.
+- We're here to help.
+- Abusive behavior is never tolerated.
+- Violations of this code may result in swift and permanent expulsion from the NativeScript community channels.
+
+## Administrators
+
+- Dan Wilson (@DanWilson on Slack)
+- Jen Looper (@jen.looper on Slack)
+- TJ VanToll (@tjvantoll on Slack)
+
+## Scope
+
+We expect all members of the NativeScript community, including administrators, users, facilitators, and vendors to abide by this Code of Conduct at all times in our community venues, online and in person, and in one-on-one communications pertaining to NativeScript affairs.
+
+This policy covers the usage of the NativeScript Slack community, as well as the NativeScript support forums, NativeScript GitHub repositories, the NativeScript website, and any NativeScript-related events. This Code of Conduct is in addition to, and does not in any way nullify or invalidate, any other terms or conditions related to use of NativeScript.
+
+The definitions of various subjective terms such as "discriminatory", "hateful", or "confusing" will be decided at the sole discretion of the NativeScript administrators.
+
+## Friendly, Harassment-Free Space
+
+We are committed to providing a friendly, safe, and welcoming environment for all, regardless of gender identity, sexual orientation, disability, ethnicity, religion, age, physical appearance, body size, race, or similar personal characteristics.
+
+We ask that you please respect that people have differences of opinion regarding technical choices, and acknowledge that every design or implementation choice carries a trade-off and numerous costs. There is seldom a single right answer. A difference of technology preferences is never a license to be rude.
+
+Any spamming, trolling, flaming, baiting, or other attention-stealing behaviour is not welcome, and will not be tolerated.
+
+Harassing other users of NativeScript is never tolerated, whether via public or private media.
+
+Avoid using offensive or harassing package names, nicknames, or other identifiers that might detract from a friendly, safe, and welcoming environment for all.
+
+Harassment includes, but is not limited to: harmful or prejudicial verbal or written comments related to gender identity, sexual orientation, disability, ethnicity, religion, age, physical appearance, body size, race, or similar personal characteristics; inappropriate use of nudity, sexual images, and/or sexually explicit language in public spaces; threats of physical or non-physical harm; deliberate intimidation, stalking or following; harassing photography or recording; sustained disruption of talks or other events; inappropriate physical contact; and unwelcome sexual attention.
+
+## Acceptable Content
+
+The NativeScript administrators reserve the right to make judgement calls about what is and isn't appropriate in published content. These are guidelines to help you be successful in our community.
+
+Content must contain something applicable to the previously stated goals of the NativeScript community. "Spamming", that is, publishing any form of content that is not applicable, is not allowed.
+
+Content must not contain illegal or infringing content. You should only publish content to NativeScript properties if you have the right to do so. This includes complying with all software license agreements or other intellectual property restrictions. For example, redistributing an MIT-licensed module with the copyright notice removed, would not be allowed. You will be responsible for any violation of laws or others’ intellectual property rights.
+
+Content must not be malware. For example, content (code, video, pictures, words, etc.) which is designed to maliciously exploit or damage computer systems, is not allowed.
+
+Content name, description, and other visible metadata must not include abusive, inappropriate, or harassing content.
+
+## Reporting Violations of this Code of Conduct
+
+If you believe someone is harassing you or has otherwise violated this Code of Conduct, please contact the administrators and send us an abuse report. If this is the initial report of a problem, please include as much detail as possible. It is easiest for us to address issues when we have more context.
+
+## Consequences
+
+All content published to the NativeScript community channels is hosted at the sole discretion of the NativeScript administrators.
+
+Unacceptable behavior from any community member, including sponsors, employees, customers, or others with decision-making authority, will not be tolerated.
+
+Anyone asked to stop unacceptable behavior is expected to comply immediately.
+
+If a community member engages in unacceptable behavior, the NativeScript administrators may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event or service).
+
+## Addressing Grievances
+
+If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify the administrators. We will do our best to ensure that your grievance is handled appropriately.
+
+In general, we will choose the course of action that we judge as being most in the interest of fostering a safe and friendly community.
+
+## Contact Info
+Please contact Dan Wilson @DanWilson if you need to report a problem or address a grievance related to an abuse report.
+
+You are also encouraged to contact us if you are curious about something that might be "on the line" between appropriate and inappropriate content. We are happy to provide guidance to help you be a successful part of our community.
+
+## Credit and License
+
+This Code of Conduct borrows heavily from the WADE Code of Conduct, which is derived from the NodeBots Code of Conduct, which in turn borrows from the npm Code of Conduct, which was derived from the Stumptown Syndicate Citizen's Code of Conduct, and the Rust Project Code of Conduct.
+
+This document may be reused under a Creative Commons Attribution-ShareAlike License.
\ No newline at end of file
diff --git a/e2e/animation-examples/LICENSE b/e2e/animation-examples/LICENSE
new file mode 100755
index 000000000..061c44028
--- /dev/null
+++ b/e2e/animation-examples/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright (c) 2015-2019 Progress Software Corporation
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/e2e/animation-examples/README.md b/e2e/animation-examples/README.md
new file mode 100644
index 000000000..f1e68bd59
--- /dev/null
+++ b/e2e/animation-examples/README.md
@@ -0,0 +1,3 @@
+# NativeScript Angular Animations Examples
+
+A NativeScript Angular applications showcasing the [New Wave of Animation Features in Angular](https://www.yearofmoo.com/2017/06/new-wave-of-animation-features.html).
\ No newline at end of file
diff --git a/e2e/animation-examples/app/App_Resources/Android/app.gradle b/e2e/animation-examples/app/App_Resources/Android/app.gradle
new file mode 100644
index 000000000..0ad328b9a
--- /dev/null
+++ b/e2e/animation-examples/app/App_Resources/Android/app.gradle
@@ -0,0 +1,23 @@
+// Add your native dependencies here:
+
+// Uncomment to add recyclerview-v7 dependency
+//dependencies {
+// compile 'com.android.support:recyclerview-v7:+'
+//}
+
+android {
+ defaultConfig {
+ generatedDensities = []
+ applicationId = "org.nativescript.ng4animations"
+
+ //override supported platforms
+ // ndk {
+ // abiFilters.clear()
+ // abiFilters "armeabi-v7a"
+ // }
+
+ }
+ aaptOptions {
+ additionalParameters "--no-version-vectors"
+ }
+}
diff --git a/e2e/animation-examples/app/App_Resources/Android/src/main/AndroidManifest.xml b/e2e/animation-examples/app/App_Resources/Android/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..1673c6800
--- /dev/null
+++ b/e2e/animation-examples/app/App_Resources/Android/src/main/AndroidManifest.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-hdpi/background.png b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-hdpi/background.png
new file mode 100644
index 000000000..eb381c258
Binary files /dev/null and b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-hdpi/background.png differ
diff --git a/ng-sample/src/App_Resources/Android/drawable-hdpi/icon.png b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-hdpi/icon.png
similarity index 100%
rename from ng-sample/src/App_Resources/Android/drawable-hdpi/icon.png
rename to e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-hdpi/icon.png
diff --git a/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-hdpi/logo.png b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-hdpi/logo.png
new file mode 100644
index 000000000..5218f4c90
Binary files /dev/null and b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-hdpi/logo.png differ
diff --git a/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-ldpi/background.png b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-ldpi/background.png
new file mode 100644
index 000000000..748b2adf5
Binary files /dev/null and b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-ldpi/background.png differ
diff --git a/ng-sample/src/App_Resources/Android/drawable-ldpi/icon.png b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-ldpi/icon.png
similarity index 100%
rename from ng-sample/src/App_Resources/Android/drawable-ldpi/icon.png
rename to e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-ldpi/icon.png
diff --git a/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-ldpi/logo.png b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-ldpi/logo.png
new file mode 100644
index 000000000..b9e102a76
Binary files /dev/null and b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-ldpi/logo.png differ
diff --git a/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-mdpi/background.png b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-mdpi/background.png
new file mode 100644
index 000000000..efeaf2907
Binary files /dev/null and b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-mdpi/background.png differ
diff --git a/ng-sample/src/App_Resources/Android/drawable-mdpi/icon.png b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-mdpi/icon.png
similarity index 100%
rename from ng-sample/src/App_Resources/Android/drawable-mdpi/icon.png
rename to e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-mdpi/icon.png
diff --git a/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-mdpi/logo.png b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-mdpi/logo.png
new file mode 100644
index 000000000..626338766
Binary files /dev/null and b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-mdpi/logo.png differ
diff --git a/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml
new file mode 100644
index 000000000..ada77f92c
--- /dev/null
+++ b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml
@@ -0,0 +1,8 @@
+
+ -
+
+
+ -
+
+
+
\ No newline at end of file
diff --git a/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-xhdpi/background.png b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-xhdpi/background.png
new file mode 100644
index 000000000..612bbd072
Binary files /dev/null and b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-xhdpi/background.png differ
diff --git a/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-xhdpi/icon.png b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-xhdpi/icon.png
new file mode 100644
index 000000000..f29188209
Binary files /dev/null and b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-xhdpi/icon.png differ
diff --git a/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-xhdpi/logo.png b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-xhdpi/logo.png
new file mode 100644
index 000000000..ad8ee2f4b
Binary files /dev/null and b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-xhdpi/logo.png differ
diff --git a/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png
new file mode 100644
index 000000000..0fa88e235
Binary files /dev/null and b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png differ
diff --git a/ng-sample/src/App_Resources/iOS/icon-72@2x.png b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-xxhdpi/icon.png
old mode 100755
new mode 100644
similarity index 100%
rename from ng-sample/src/App_Resources/iOS/icon-72@2x.png
rename to e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-xxhdpi/icon.png
diff --git a/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-xxhdpi/logo.png b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-xxhdpi/logo.png
new file mode 100644
index 000000000..668327832
Binary files /dev/null and b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-xxhdpi/logo.png differ
diff --git a/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png
new file mode 100644
index 000000000..c650f6438
Binary files /dev/null and b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png differ
diff --git a/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/icon.png b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/icon.png
new file mode 100644
index 000000000..50887a856
Binary files /dev/null and b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/icon.png differ
diff --git a/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/logo.png b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/logo.png
new file mode 100644
index 000000000..fa6331c8d
Binary files /dev/null and b/e2e/animation-examples/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/logo.png differ
diff --git a/e2e/animation-examples/app/App_Resources/Android/src/main/res/values-v21/colors.xml b/e2e/animation-examples/app/App_Resources/Android/src/main/res/values-v21/colors.xml
new file mode 100644
index 000000000..a64641a9d
--- /dev/null
+++ b/e2e/animation-examples/app/App_Resources/Android/src/main/res/values-v21/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #3d5afe
+
\ No newline at end of file
diff --git a/e2e/animation-examples/app/App_Resources/Android/src/main/res/values-v21/styles.xml b/e2e/animation-examples/app/App_Resources/Android/src/main/res/values-v21/styles.xml
new file mode 100644
index 000000000..dac8727c8
--- /dev/null
+++ b/e2e/animation-examples/app/App_Resources/Android/src/main/res/values-v21/styles.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/animation-examples/app/App_Resources/Android/src/main/res/values/colors.xml b/e2e/animation-examples/app/App_Resources/Android/src/main/res/values/colors.xml
new file mode 100644
index 000000000..74ad8829c
--- /dev/null
+++ b/e2e/animation-examples/app/App_Resources/Android/src/main/res/values/colors.xml
@@ -0,0 +1,7 @@
+
+
+ #F5F5F5
+ #757575
+ #33B5E5
+ #272734
+
\ No newline at end of file
diff --git a/e2e/animation-examples/app/App_Resources/Android/src/main/res/values/styles.xml b/e2e/animation-examples/app/App_Resources/Android/src/main/res/values/styles.xml
new file mode 100644
index 000000000..1e8c7f29b
--- /dev/null
+++ b/e2e/animation-examples/app/App_Resources/Android/src/main/res/values/styles.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000..1953734f4
--- /dev/null
+++ b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,92 @@
+{
+ "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"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png
new file mode 100644
index 000000000..9e15af09d
Binary files /dev/null and b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png differ
diff --git a/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png
new file mode 100644
index 000000000..7b9e55537
Binary files /dev/null and b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png differ
diff --git a/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png
new file mode 100644
index 000000000..76f61ec1f
Binary files /dev/null and b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png differ
diff --git a/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png
new file mode 100644
index 000000000..15b06db11
Binary files /dev/null and b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png differ
diff --git a/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png
new file mode 100644
index 000000000..585065f94
Binary files /dev/null and b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png differ
diff --git a/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png
new file mode 100644
index 000000000..a450c421d
Binary files /dev/null and b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png differ
diff --git a/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png
new file mode 100644
index 000000000..457b6d94c
Binary files /dev/null and b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png differ
diff --git a/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png
new file mode 100644
index 000000000..fa5a6ac86
Binary files /dev/null and b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png differ
diff --git a/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png
new file mode 100644
index 000000000..94abcf70d
Binary files /dev/null and b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png differ
diff --git a/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png
new file mode 100644
index 000000000..2e71dd3a0
Binary files /dev/null and b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png differ
diff --git a/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png
new file mode 100644
index 000000000..4abc9ec50
Binary files /dev/null and b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png differ
diff --git a/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/Contents.json b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/Contents.json
new file mode 100644
index 000000000..da4a164c9
--- /dev/null
+++ b/e2e/animation-examples/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/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
new file mode 100644
index 000000000..4414bad08
--- /dev/null
+++ b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
@@ -0,0 +1,158 @@
+{
+ "images" : [
+ {
+ "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/ng-sample/src/App_Resources/iOS/Default-568h@2x.png b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png
old mode 100755
new mode 100644
similarity index 100%
rename from ng-sample/src/App_Resources/iOS/Default-568h@2x.png
rename to e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png
diff --git a/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png b/e2e/animation-examples/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/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png differ
diff --git a/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png b/e2e/animation-examples/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/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png differ
diff --git a/ng-sample/src/App_Resources/iOS/Default-Landscape.png b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png
old mode 100755
new mode 100644
similarity index 100%
rename from ng-sample/src/App_Resources/iOS/Default-Landscape.png
rename to e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png
diff --git a/ng-sample/src/App_Resources/iOS/Default-Landscape@2x.png b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png
old mode 100755
new mode 100644
similarity index 100%
rename from ng-sample/src/App_Resources/iOS/Default-Landscape@2x.png
rename to e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png
diff --git a/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png b/e2e/animation-examples/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/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png differ
diff --git a/ng-sample/src/App_Resources/iOS/Default-Portrait.png b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png
old mode 100755
new mode 100644
similarity index 100%
rename from ng-sample/src/App_Resources/iOS/Default-Portrait.png
rename to e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png
diff --git a/ng-sample/src/App_Resources/iOS/Default-Portrait@2x.png b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png
old mode 100755
new mode 100644
similarity index 100%
rename from ng-sample/src/App_Resources/iOS/Default-Portrait@2x.png
rename to e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png
diff --git a/ng-sample/src/App_Resources/iOS/Default.png b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png
old mode 100755
new mode 100644
similarity index 100%
rename from ng-sample/src/App_Resources/iOS/Default.png
rename to e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png
diff --git a/ng-sample/src/App_Resources/iOS/Default@2x.png b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png
old mode 100755
new mode 100644
similarity index 100%
rename from ng-sample/src/App_Resources/iOS/Default@2x.png
rename to e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png
diff --git a/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json
new file mode 100644
index 000000000..4f4e9c506
--- /dev/null
+++ b/e2e/animation-examples/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/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png b/e2e/animation-examples/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/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png differ
diff --git a/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png b/e2e/animation-examples/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/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png differ
diff --git a/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json b/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json
new file mode 100644
index 000000000..23c0ffd7a
--- /dev/null
+++ b/e2e/animation-examples/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/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png b/e2e/animation-examples/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/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png differ
diff --git a/e2e/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png b/e2e/animation-examples/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/animation-examples/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png differ
diff --git a/e2e/animation-examples/app/App_Resources/iOS/Info.plist b/e2e/animation-examples/app/App_Resources/iOS/Info.plist
new file mode 100644
index 000000000..ea3e3ea23
--- /dev/null
+++ b/e2e/animation-examples/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/animation-examples/app/App_Resources/iOS/LaunchScreen.storyboard b/e2e/animation-examples/app/App_Resources/iOS/LaunchScreen.storyboard
new file mode 100644
index 000000000..2ad9471e1
--- /dev/null
+++ b/e2e/animation-examples/app/App_Resources/iOS/LaunchScreen.storyboard
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/animation-examples/app/App_Resources/iOS/build.xcconfig b/e2e/animation-examples/app/App_Resources/iOS/build.xcconfig
new file mode 100644
index 000000000..e77e78db9
--- /dev/null
+++ b/e2e/animation-examples/app/App_Resources/iOS/build.xcconfig
@@ -0,0 +1,6 @@
+// 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;
diff --git a/e2e/animation-examples/app/animate-child.component.ts b/e2e/animation-examples/app/animate-child.component.ts
new file mode 100644
index 000000000..2af68882d
--- /dev/null
+++ b/e2e/animation-examples/app/animate-child.component.ts
@@ -0,0 +1,46 @@
+import {
+ animate,
+ animateChild,
+ query,
+ style,
+ transition,
+ trigger,
+} from "@angular/animations";
+import { Component } from "@angular/core";
+
+@Component({
+ template: `
+
+ >
+
+ `,
+ animations: [
+ trigger("parent", [
+ transition("* => *", [
+ style({ transform: "translate(200px)" }),
+ animate(1000, style({ transform: "translate(0px)" })),
+ query("@child", animateChild())
+ ])
+ ]),
+ trigger("child", [
+ transition("* => *", [
+ style({ opacity: 0 }),
+ animate(400, style({ opacity: 1 }))
+ ])
+ ])
+ ],
+ styles: [
+ `.parent {
+ background-color: red;
+ }`,
+ `.child {
+ height: 50%;
+ width: 50%;
+ background-color: green;
+ }`,
+ ],
+})
+export class AnimateChildComponent {
+ public parentVal;
+ public childVal;
+}
diff --git a/e2e/animation-examples/app/animation-builder.component.scss b/e2e/animation-examples/app/animation-builder.component.scss
new file mode 100644
index 000000000..ca3407610
--- /dev/null
+++ b/e2e/animation-examples/app/animation-builder.component.scss
@@ -0,0 +1,3 @@
+.btn-primary {
+ background-color: pink;
+}
\ No newline at end of file
diff --git a/e2e/animation-examples/app/animation-builder.component.ts b/e2e/animation-examples/app/animation-builder.component.ts
new file mode 100644
index 000000000..3713242c8
--- /dev/null
+++ b/e2e/animation-examples/app/animation-builder.component.ts
@@ -0,0 +1,25 @@
+import { AnimationBuilder, style, animate } from '@angular/animations';
+import { Component, ViewChild } from '@angular/core';
+
+@Component({
+ template: `
+
+ `,
+ styleUrls: ['./animation-builder.component.scss']
+})
+export class AnimationBuilderComponent {
+ @ViewChild('button', { static: false }) button;
+
+ constructor(private _builder: AnimationBuilder) {}
+
+ makeAnimation() {
+ const myAnimation = this._builder.build([
+ style({ "opacity": 1 }),
+ animate(1000, style({ "opacity": 0 }))
+ ]);
+
+ const player = myAnimation.create(this.button.nativeElement);
+
+ player.play();
+ }
+}
diff --git a/e2e/animation-examples/app/animations-list.component.ts b/e2e/animation-examples/app/animations-list.component.ts
new file mode 100644
index 000000000..52da0deea
--- /dev/null
+++ b/e2e/animation-examples/app/animations-list.component.ts
@@ -0,0 +1,33 @@
+import { Component } from '@angular/core';
+
+class Link {
+ constructor(public title: string, public link: string, public id?: string) {
+ this.id = this.id || this.link.replace("/", "");
+ }
+}
+
+@Component({
+ template: `
+
+
+
+ `
+})
+export class AnimationsListComponent {
+ public links: Link[] = [
+ new Link("Animation builder", "/builder"),
+ new Link("External animation", "/external"),
+ new Link("Selector", "/selector"),
+ new Link("Query with stagger", "/query-stagger"),
+ new Link("Fade in/out animation", "/fade-in-out"),
+ new Link("Animation with options", "/options"),
+ new Link("Animation with default options", "/options-default"),
+ new Link("Animate child", "/animate-child"),
+ new Link("Angular docs animations", "/hero"),
+ ]
+}
diff --git a/e2e/animation-examples/app/animations.ts b/e2e/animation-examples/app/animations.ts
new file mode 100644
index 000000000..15531b7aa
--- /dev/null
+++ b/e2e/animation-examples/app/animations.ts
@@ -0,0 +1,9 @@
+import { animation, style, animate } from "@angular/animations";
+
+export const fadeAnimation = animation([
+ style({ opacity: "{{ from }}" }),
+ animate("{{ time }}", style({ opacity: "{{ to }}" }))
+], {
+ params: { time: "1s" }
+});
+
diff --git a/e2e/animation-examples/app/app.android.css b/e2e/animation-examples/app/app.android.css
new file mode 100644
index 000000000..3886f4ffe
--- /dev/null
+++ b/e2e/animation-examples/app/app.android.css
@@ -0,0 +1,10 @@
+Button {
+ font-size: 8px;
+ padding: 0px;
+ margin: 0px;
+}
+
+Label {
+ font-size: 8px;
+}
+
diff --git a/e2e/animation-examples/app/app.component.ts b/e2e/animation-examples/app/app.component.ts
new file mode 100644
index 000000000..74eb76f2f
--- /dev/null
+++ b/e2e/animation-examples/app/app.component.ts
@@ -0,0 +1,8 @@
+import { Component } from '@angular/core';
+
+@Component({
+ moduleId: module.id,
+ template: ``
+})
+export class AppComponent {
+}
\ No newline at end of file
diff --git a/e2e/animation-examples/app/app.ios.css b/e2e/animation-examples/app/app.ios.css
new file mode 100644
index 000000000..e69de29bb
diff --git a/e2e/animation-examples/app/app.module.ngfactory.d.ts b/e2e/animation-examples/app/app.module.ngfactory.d.ts
new file mode 100644
index 000000000..9d15b9419
--- /dev/null
+++ b/e2e/animation-examples/app/app.module.ngfactory.d.ts
@@ -0,0 +1 @@
+export const AppModuleNgFactory: any;
\ No newline at end of file
diff --git a/e2e/animation-examples/app/app.module.ts b/e2e/animation-examples/app/app.module.ts
new file mode 100644
index 000000000..4a729f4b2
--- /dev/null
+++ b/e2e/animation-examples/app/app.module.ts
@@ -0,0 +1,64 @@
+import {
+ NgModule,
+ NO_ERRORS_SCHEMA,
+ NgModuleFactoryLoader,
+ APP_INITIALIZER
+} from "@angular/core";
+
+import { NativeScriptModule, NativeScriptAnimationsModule } from "@nativescript/angular";
+
+import { AppRoutingModule } from "./app.routing";
+import { AnimationsListComponent } from "./animations-list.component";
+import { AnimationBuilderComponent } from "./animation-builder.component";
+import { ExternalAnimationComponent } from "./external-animation.component";
+import { FadeInOutComponent } from "./fade-in-out.component";
+import { OptionsComponent } from "./options.component";
+import { OptionsDefaultComponent } from "./options-default.component";
+import { AnimateChildComponent } from "./animate-child.component";
+import { SelectorAllComponent } from "./selector-all.component";
+import { QueryStaggerComponent } from "./query-stagger.component";
+
+import { AppComponent } from "./app.component";
+
+export function asyncBoot(): Function {
+ return (): Promise => new Promise(resolve => {
+ setTimeout(() => {
+ resolve();
+ }, 5000);
+ })
+}
+
+@NgModule({
+ bootstrap: [
+ AppComponent,
+ ],
+ declarations: [
+ AppComponent,
+ AnimationsListComponent,
+ AnimationBuilderComponent,
+ ExternalAnimationComponent,
+ FadeInOutComponent,
+ OptionsComponent,
+ OptionsDefaultComponent,
+ AnimateChildComponent,
+ SelectorAllComponent,
+ QueryStaggerComponent,
+ ],
+ imports: [
+ NativeScriptModule,
+ NativeScriptAnimationsModule,
+ AppRoutingModule,
+ ],
+ /**
+ * Uncomment to test APP_INITIALIZER
+ */
+ // providers: [
+ // {
+ // provide: APP_INITIALIZER,
+ // useFactory: asyncBoot,
+ // multi: true
+ // },
+ // ],
+ schemas: [NO_ERRORS_SCHEMA],
+})
+export class AppModule {}
diff --git a/e2e/animation-examples/app/app.routing.ts b/e2e/animation-examples/app/app.routing.ts
new file mode 100644
index 000000000..305e244e9
--- /dev/null
+++ b/e2e/animation-examples/app/app.routing.ts
@@ -0,0 +1,33 @@
+import { NgModule } from "@angular/core";
+import { Routes } from "@angular/router";
+import { NativeScriptRouterModule } from "@nativescript/angular";
+
+import { AnimationsListComponent } from "./animations-list.component";
+import { AnimationBuilderComponent } from "./animation-builder.component";
+import { ExternalAnimationComponent } from "./external-animation.component";
+import { FadeInOutComponent } from "./fade-in-out.component";
+import { OptionsComponent } from "./options.component";
+import { OptionsDefaultComponent } from "./options-default.component";
+import { AnimateChildComponent } from "./animate-child.component";
+import { SelectorAllComponent } from "./selector-all.component";
+import { QueryStaggerComponent } from "./query-stagger.component";
+
+const routes: Routes = [
+ { path: "", pathMatch: "full", redirectTo: "list" },
+ { path: "list", component: AnimationsListComponent },
+ { path: "builder", component: AnimationBuilderComponent },
+ { path: "external", component: ExternalAnimationComponent },
+ { path: "fade-in-out", component: FadeInOutComponent },
+ { path: "options", component: OptionsComponent },
+ { path: "options-default", component: OptionsDefaultComponent },
+ { path: "animate-child", component: AnimateChildComponent },
+ { path: "selector", component: SelectorAllComponent },
+ { path: "query-stagger", component: QueryStaggerComponent },
+ { path: "hero", loadChildren: () => import("./hero/hero.module").then(m => m.HeroModule) },
+];
+
+@NgModule({
+ imports: [NativeScriptRouterModule.forRoot(routes)],
+ exports: [NativeScriptRouterModule],
+})
+export class AppRoutingModule { }
diff --git a/e2e/animation-examples/app/external-animation.component.ts b/e2e/animation-examples/app/external-animation.component.ts
new file mode 100644
index 000000000..80eb38fbb
--- /dev/null
+++ b/e2e/animation-examples/app/external-animation.component.ts
@@ -0,0 +1,58 @@
+import { useAnimation, trigger, transition, style, animate } from "@angular/animations";
+import { Component } from "@angular/core";
+
+import { fadeAnimation } from "./animations";
+
+@Component({
+ template: `
+
+
+
+
+
+ `,
+ animations: [
+ trigger("coolAnimation", [
+ transition("invisible => visible", [
+ useAnimation(fadeAnimation, {
+ params: {
+ from: 0,
+ to: 1,
+ time: "1s",
+ },
+ })
+ ]),
+
+ transition("visible => invisible", [
+ useAnimation(fadeAnimation, {
+ params: {
+ from: 1,
+ to: 0,
+ time: "1.0s",
+ },
+ })
+ ]),
+
+ ])
+ ],
+})
+export class ExternalAnimationComponent {
+ public isVisible = "visible";
+
+ toggle() {
+ this.isVisible = this.isVisible === "visible" ?
+ "invisible" :
+ "visible";
+ }
+}
+
diff --git a/e2e/animation-examples/app/fade-in-out.component.ts b/e2e/animation-examples/app/fade-in-out.component.ts
new file mode 100644
index 000000000..2d3ebe149
--- /dev/null
+++ b/e2e/animation-examples/app/fade-in-out.component.ts
@@ -0,0 +1,54 @@
+import { Component } from "@angular/core";
+import { trigger, transition, style, animate } from "@angular/animations";
+
+@Component({
+ template: `
+
+
+
+
+
+ `,
+ animations: [
+ trigger("someCoolAnimation", [
+ transition("* => fadeIn", [
+ style({ opacity: 0 }),
+ animate(600, style({ opacity: 1 }))
+ ]),
+ transition("* => fadeOut", [
+ animate(600, style({ opacity: 0 }))
+ ])
+ ])
+ ]
+})
+export class FadeInOutComponent {
+ bindingVar = "";
+
+ fadeIn() {
+ this.bindingVar = "fadeIn";
+ }
+
+ fadeOut() {
+ this.bindingVar = "fadeOut";
+ }
+
+ toggle() {
+ this.bindingVar == "fadeOut" ? this.fadeIn() : this.fadeOut();
+ }
+
+ hide() {
+ this.fadeOut();
+ }
+}
diff --git a/e2e/animation-examples/app/hero/hero-list-basic.component.ts b/e2e/animation-examples/app/hero/hero-list-basic.component.ts
new file mode 100644
index 000000000..260fa4008
--- /dev/null
+++ b/e2e/animation-examples/app/hero/hero-list-basic.component.ts
@@ -0,0 +1,56 @@
+import {
+ Component,
+ Input
+} from "@angular/core";
+import {
+ trigger,
+ state,
+ style,
+ animate,
+ transition
+} from "@angular/animations";
+
+import { Heroes } from "./hero.service";
+
+@Component({
+ moduleId: module.id,
+ selector: "hero-list-basic",
+ /* The click event calls hero.toggleState(), which
+ * causes the state of that hero to switch from
+ * active to inactive or vice versa.
+ */
+ template: `
+
+
+
+ `,
+ styleUrls: ["./hero-list.component.css"],
+ /**
+ * Define two states, "inactive" and "active", and the end
+ * styles that apply whenever the element is in those states.
+ * Then define animations for transitioning between the states,
+ * one in each direction
+ */
+ animations: [
+ trigger("heroState", [
+ state("inactive", style({
+ backgroundColor: "#eee",
+ transform: "scale(1)"
+ })),
+ state("active", style({
+ backgroundColor: "#cfd8dc",
+ transform: "scale(1.1)"
+ })),
+
+ transition("inactive => active", animate("100ms ease-in")),
+ transition("active => inactive", animate("100ms ease-out")),
+ ])
+ ]
+})
+export class HeroListBasicComponent {
+ @Input() heroes: Heroes;
+}
diff --git a/e2e/animation-examples/app/hero/hero-list-combined-transitions.component.ts b/e2e/animation-examples/app/hero/hero-list-combined-transitions.component.ts
new file mode 100644
index 000000000..bf588976a
--- /dev/null
+++ b/e2e/animation-examples/app/hero/hero-list-combined-transitions.component.ts
@@ -0,0 +1,51 @@
+import {
+ Component,
+ Input
+} from "@angular/core";
+import {
+ trigger,
+ state,
+ style,
+ animate,
+ transition
+} from "@angular/animations";
+
+import { Heroes } from "./hero.service";
+
+@Component({
+ moduleId: module.id,
+ selector: "hero-list-combined-transitions",
+ template: `
+
+
+
+ `,
+ styleUrls: ["./hero-list.component.css"],
+ /*
+ * Define two states, "inactive" and "active", and the end
+ * styles that apply whenever the element is in those states.
+ * Then define an animated transition between these two
+ * states, in *both* directions.
+ */
+ animations: [
+ trigger("heroState", [
+ state("inactive", style({
+ backgroundColor: "#eee",
+ transform: "scale(1)"
+ })),
+ state("active", style({
+ backgroundColor: "#cfd8dc",
+ transform: "scale(1.1)"
+ })),
+ transition("inactive => active, active => inactive",
+ animate("100ms ease-out"))
+ ])
+ ]
+})
+export class HeroListCombinedTransitionsComponent {
+ @Input() heroes: Heroes;
+}
diff --git a/e2e/animation-examples/app/hero/hero-list-enter-leave-states.component.ts b/e2e/animation-examples/app/hero/hero-list-enter-leave-states.component.ts
new file mode 100644
index 000000000..f8c63da1b
--- /dev/null
+++ b/e2e/animation-examples/app/hero/hero-list-enter-leave-states.component.ts
@@ -0,0 +1,60 @@
+import {
+ Component,
+ Input
+} from "@angular/core";
+import {
+ trigger,
+ state,
+ style,
+ animate,
+ transition
+} from "@angular/animations";
+
+import { Heroes } from "./hero.service";
+
+@Component({
+ moduleId: module.id,
+ selector: "hero-list-enter-leave-states",
+ template: `
+
+
+
+ `,
+ styleUrls: ["./hero-list.component.css"],
+ /* The elements here have two possible states based
+ * on the hero state, "active", or "inactive". We animate
+ * six transitions: Between the two states in both directions,
+ * and between each state and void. With this we can animate
+ * the enter and leave of elements differently based on which
+ * state they are in when they are added and removed.
+ */
+ animations: [
+ trigger("heroState", [
+ state("inactive", style({ transform: "translateX(0) scale(1)" })),
+ state("active", style({ transform: "translateX(0) scale(1.1)" })),
+ transition("inactive => active", animate("100ms ease-in")),
+ transition("active => inactive", animate("100ms ease-out")),
+ transition("void => inactive", [
+ style({ transform: "translateX(-100%) scale(1)" }),
+ animate(100)
+ ]),
+ transition("inactive => void", [
+ animate(100, style({ transform: "translateX(100%) scale(1)" }))
+ ]),
+ transition("void => active", [
+ style({ transform: "translateX(0) scale(0)" }),
+ animate(200)
+ ]),
+ transition("active => void", [
+ animate(200, style({ transform: "translateX(0) scale(0)" }))
+ ])
+ ])
+ ]
+})
+export class HeroListEnterLeaveStatesComponent {
+ @Input() heroes: Heroes;
+}
diff --git a/e2e/animation-examples/app/hero/hero-list-enter-leave.component.ts b/e2e/animation-examples/app/hero/hero-list-enter-leave.component.ts
new file mode 100644
index 000000000..87468a59e
--- /dev/null
+++ b/e2e/animation-examples/app/hero/hero-list-enter-leave.component.ts
@@ -0,0 +1,48 @@
+import {
+ Component,
+ Input
+} from "@angular/core";
+import {
+ trigger,
+ state,
+ style,
+ animate,
+ transition
+} from "@angular/animations";
+
+import { Heroes } from "./hero.service";
+
+@Component({
+ moduleId: module.id,
+ selector: "hero-list-enter-leave",
+ template: `
+
+
+
+ `,
+ styleUrls: ["./hero-list.component.css"],
+ /* The element here always has the state "in" when it
+ * is present. We animate two transitions: From void
+ * to in and from in to void, to achieve an animated
+ * enter and leave transition. The element enters from
+ * the left and leaves to the right using translateX.
+ */
+ animations: [
+ trigger("flyInOut", [
+ state("in", style({ transform: "translateX(0)" })),
+ transition("void => *", [
+ style({ transform: "translateX(-100%)" }),
+ animate(100)
+ ]),
+ transition("* => void", [
+ animate(100, style({ transform: "translateX(100%)" }))
+ ])
+ ])
+ ]
+})
+export class HeroListEnterLeaveComponent {
+ @Input() heroes: Heroes;
+}
diff --git a/e2e/animation-examples/app/hero/hero-list-inline-styles.component.ts b/e2e/animation-examples/app/hero/hero-list-inline-styles.component.ts
new file mode 100644
index 000000000..6c378942a
--- /dev/null
+++ b/e2e/animation-examples/app/hero/hero-list-inline-styles.component.ts
@@ -0,0 +1,52 @@
+import {
+ Component,
+ Input,
+} from "@angular/core";
+import {
+ trigger,
+ style,
+ animate,
+ transition
+} from "@angular/animations";
+
+import { Heroes } from "./hero.service";
+
+@Component({
+ moduleId: module.id,
+ selector: "hero-list-inline-styles",
+ template: `
+
+
+
+ `,
+ styleUrls: ["./hero-list.component.css"],
+ /**
+ * Define two states, "inactive" and "active", and the end
+ * styles that apply whenever the element is in those states.
+ * Then define an animation for the inactive => active transition.
+ * This animation has no end styles, but only styles that are
+ * defined inline inside the transition and thus are only kept
+ * as long as the animation is running.
+ */
+ animations: [
+ trigger("heroState", [
+ transition("inactive => active", [
+ style({
+ backgroundColor: "#cfd8dc",
+ transform: "scale(1.3)"
+ }),
+ animate("80ms ease-in", style({
+ backgroundColor: "#eee",
+ transform: "scale(1)"
+ }))
+ ]),
+ ])
+ ]
+})
+export class HeroListInlineStylesComponent {
+ @Input() heroes: Heroes;
+}
diff --git a/e2e/animation-examples/app/hero/hero-list-timings.component.ts b/e2e/animation-examples/app/hero/hero-list-timings.component.ts
new file mode 100644
index 000000000..46deaacf4
--- /dev/null
+++ b/e2e/animation-examples/app/hero/hero-list-timings.component.ts
@@ -0,0 +1,57 @@
+import {
+ Component,
+ Input
+} from "@angular/core";
+import {
+ trigger,
+ state,
+ style,
+ animate,
+ transition
+} from "@angular/animations";
+
+import { Heroes } from "./hero.service";
+
+@Component({
+ moduleId: module.id,
+ selector: "hero-list-timings",
+ template: `
+
+
+
+ `,
+ styleUrls: ["./hero-list.component.css"],
+ /* The element here always has the state "in" when it
+ * is present. We animate two transitions: From void
+ * to in and from in to void, to achieve an animated
+ * enter and leave transition. The element enters from
+ * the left and leaves to the right using translateX,
+ * and fades in/out using opacity. We use different easings
+ * for enter and leave.
+ */
+ animations: [
+ trigger("flyInOut", [
+ state("in", style({ opacity: 1, transform: "translateX(0)" })),
+ transition("void => *", [
+ style({
+ opacity: 0,
+ transform: "translateX(-100%)"
+ }),
+ animate("0.2s ease-in")
+ ]),
+ transition("* => void", [
+ animate("0.2s 0.1s ease-out", style({
+ opacity: 0,
+ transform: "translateX(100%)"
+ }))
+ ])
+ ])
+ ]
+})
+export class HeroListTimingsComponent {
+ @Input() heroes: Heroes;
+}
diff --git a/e2e/animation-examples/app/hero/hero-list-twoway.component.ts b/e2e/animation-examples/app/hero/hero-list-twoway.component.ts
new file mode 100644
index 000000000..4367814b4
--- /dev/null
+++ b/e2e/animation-examples/app/hero/hero-list-twoway.component.ts
@@ -0,0 +1,50 @@
+import {
+ Component,
+ Input
+} from "@angular/core";
+import {
+ trigger,
+ state,
+ style,
+ animate,
+ transition
+} from "@angular/animations";
+
+import { Heroes } from "./hero.service";
+
+@Component({
+ moduleId: module.id,
+ selector: "hero-list-twoway",
+ template: `
+
+
+
+ `,
+ styleUrls: ["./hero-list.component.css"],
+ /*
+ * Define two states, "inactive" and "active", and the end
+ * styles that apply whenever the element is in those states.
+ * Then define an animated transition between these two
+ * states, in *both* directions.
+ */
+ animations: [
+ trigger("heroState", [
+ state("inactive", style({
+ backgroundColor: "#eee",
+ transform: "scale(1)"
+ })),
+ state("active", style({
+ backgroundColor: "#cfd8dc",
+ transform: "scale(1.1)"
+ })),
+ transition("inactive <=> active", animate("100ms ease-out"))
+ ])
+ ]
+})
+export class HeroListTwowayComponent {
+ @Input() heroes: Heroes;
+}
diff --git a/e2e/animation-examples/app/hero/hero-list.component.css b/e2e/animation-examples/app/hero/hero-list.component.css
new file mode 100644
index 000000000..d432e70ae
--- /dev/null
+++ b/e2e/animation-examples/app/hero/hero-list.component.css
@@ -0,0 +1,8 @@
+.active {
+ background-color: #cfd8dc;
+ transform: scale(1.1);
+}
+.inactive {
+ background-color: #eee;
+ transform: scale(1);
+}
diff --git a/e2e/animation-examples/app/hero/hero-routing.module.ts b/e2e/animation-examples/app/hero/hero-routing.module.ts
new file mode 100644
index 000000000..6f65be89b
--- /dev/null
+++ b/e2e/animation-examples/app/hero/hero-routing.module.ts
@@ -0,0 +1,19 @@
+import { NgModule } from "@angular/core";
+import { Routes } from "@angular/router";
+import { NativeScriptRouterModule } from "@nativescript/angular";
+
+import { HeroTeamBuilderComponent } from './hero-team-builder.component';
+
+export const routes: Routes = [
+ { path: "", pathMatch: "full", component: HeroTeamBuilderComponent },
+];
+
+@NgModule({
+ imports: [NativeScriptRouterModule.forChild(routes)],
+ exports: [NativeScriptRouterModule],
+})
+export class HeroRoutingModule { }
+
+export const routedComponents = [
+ HeroTeamBuilderComponent,
+];
diff --git a/e2e/animation-examples/app/hero/hero-team-builder.component.html b/e2e/animation-examples/app/hero/hero-team-builder.component.html
new file mode 100644
index 000000000..843813e0f
--- /dev/null
+++ b/e2e/animation-examples/app/hero/hero-team-builder.component.html
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/animation-examples/app/hero/hero-team-builder.component.ts b/e2e/animation-examples/app/hero/hero-team-builder.component.ts
new file mode 100644
index 000000000..cb029a15b
--- /dev/null
+++ b/e2e/animation-examples/app/hero/hero-team-builder.component.ts
@@ -0,0 +1,13 @@
+import { Component } from "@angular/core";
+
+import { Heroes } from "./hero.service";
+
+@Component({
+ moduleId: module.id,
+ selector: "hero-team-builder",
+ templateUrl: "./hero-team-builder.component.html",
+ providers: [Heroes]
+})
+export class HeroTeamBuilderComponent {
+ constructor(public heroes: Heroes) { }
+}
diff --git a/e2e/animation-examples/app/hero/hero.module.ts b/e2e/animation-examples/app/hero/hero.module.ts
new file mode 100644
index 000000000..938ec409c
--- /dev/null
+++ b/e2e/animation-examples/app/hero/hero.module.ts
@@ -0,0 +1,31 @@
+import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
+import { NativeScriptCommonModule } from "@nativescript/angular";
+
+import { HeroRoutingModule, routedComponents } from "./hero-routing.module";
+
+import { HeroListBasicComponent } from './hero-list-basic.component';
+import { HeroListInlineStylesComponent } from './hero-list-inline-styles.component';
+import { HeroListEnterLeaveComponent } from './hero-list-enter-leave.component';
+import { HeroListEnterLeaveStatesComponent } from './hero-list-enter-leave-states.component';
+import { HeroListCombinedTransitionsComponent } from './hero-list-combined-transitions.component';
+import { HeroListTwowayComponent } from './hero-list-twoway.component';
+import { HeroListTimingsComponent } from './hero-list-timings.component';
+
+@NgModule({
+ declarations: [
+ ...routedComponents,
+ HeroListBasicComponent,
+ HeroListInlineStylesComponent,
+ HeroListEnterLeaveComponent,
+ HeroListEnterLeaveStatesComponent,
+ HeroListCombinedTransitionsComponent,
+ HeroListTwowayComponent,
+ HeroListTimingsComponent,
+ ],
+ imports: [
+ NativeScriptCommonModule,
+ HeroRoutingModule,
+ ],
+ schemas: [NO_ERRORS_SCHEMA]
+})
+export class HeroModule { }
diff --git a/e2e/animation-examples/app/hero/hero.service.ts b/e2e/animation-examples/app/hero/hero.service.ts
new file mode 100644
index 000000000..d61874cd6
--- /dev/null
+++ b/e2e/animation-examples/app/hero/hero.service.ts
@@ -0,0 +1,70 @@
+import { Injectable } from "@angular/core";
+
+class Hero {
+ constructor(
+ public name: string,
+ public state = "inactive"
+ ) { }
+
+ toggleState() {
+ this.state = (this.state === "active" ? "inactive" : "active");
+ }
+}
+
+let ALL_HEROES = [
+ "Windstorm",
+ "RubberMan",
+ "Bombasto",
+ "Magneta",
+ "Dynama",
+ "Narco",
+ "Celeritas",
+ "Dr IQ",
+ "Magma",
+ "Tornado",
+ "Mr. Nice"
+].map(name => new Hero(name));
+
+@Injectable()
+export class Heroes implements Iterable {
+
+ currentHeroes: Hero[] = [new Hero("Narco")];
+
+ [Symbol.iterator]() {
+ return makeIterator(this.currentHeroes);
+ }
+
+ addActive() {
+ let hero = ALL_HEROES[this.currentHeroes.length];
+ hero.state = "active";
+ this.currentHeroes.push(hero);
+ }
+
+ addInactive() {
+ let hero = ALL_HEROES[this.currentHeroes.length];
+ hero.state = "inactive";
+ this.currentHeroes.push(hero);
+ }
+
+ remove() {
+ if (this.currentHeroes.length) {
+ this.currentHeroes.splice(this.currentHeroes.length - 1, 1);
+ }
+ }
+
+ reset() {
+ this.currentHeroes = [];
+ }
+}
+
+function makeIterator(array) {
+ let nextIndex = 0;
+
+ return {
+ next: function() {
+ return nextIndex < array.length ?
+ { value: array[nextIndex++], done: false } :
+ { value: "empty", done: true };
+ }
+ };
+}
diff --git a/e2e/animation-examples/app/main.ts b/e2e/animation-examples/app/main.ts
new file mode 100644
index 000000000..92fc7dbca
--- /dev/null
+++ b/e2e/animation-examples/app/main.ts
@@ -0,0 +1,74 @@
+import { platformNativeScriptDynamic, NativeScriptDebug, AppLaunchView } from "@nativescript/angular";
+import { Trace, GridLayout, GridUnitType, ItemSpec, Application } from "@nativescript/core";
+
+import { AppModule } from "./app.module";
+
+Trace.setCategories(NativeScriptDebug.animationsTraceCategory);
+Trace.enable();
+
+class LaunchAnimation extends GridLayout implements AppLaunchView {
+ circle: GridLayout;
+ finished = false;
+ complete: () => void;
+
+ constructor() {
+ super();
+ this.backgroundColor = "#4caef7";
+ this.className = "w-full h-full";
+
+ // construct any creative animation
+ this.circle = new GridLayout();
+ this.circle.width = 30;
+ this.circle.height = 30;
+ this.circle.borderRadius = 15;
+ this.circle.horizontalAlignment = "center";
+ this.circle.verticalAlignment = "middle";
+ this.circle.backgroundColor = "#fff";
+
+ this.addChild(this.circle);
+ }
+
+ async startAnimation() {
+ await this.circle.animate({
+ scale: { x: 2, y: 2 },
+ duration: 800,
+ });
+
+ await this.circle.animate({
+ scale: { x: 1, y: 1 },
+ duration: 800,
+ });
+
+ if (this.finished) {
+ await this.circle.animate({
+ scale: { x: 30, y: 30 },
+ duration: 400,
+ });
+ this.fadeOut();
+ } else {
+ // keep looping
+ this.startAnimation();
+ }
+ }
+
+ cleanup() {
+ return new Promise((resolve) => {
+ this.complete = resolve;
+ this.finished = true;
+ });
+ }
+
+ async fadeOut() {
+ await this.animate({
+ opacity: 0,
+ duration: 400,
+ });
+ this.complete();
+ }
+}
+
+platformNativeScriptDynamic({
+ launchView: new LaunchAnimation(),
+ // backgroundColor: 'purple',
+ // async: true
+}).bootstrapModule(AppModule);
diff --git a/e2e/animation-examples/app/options-default.component.ts b/e2e/animation-examples/app/options-default.component.ts
new file mode 100644
index 000000000..f7246ab3d
--- /dev/null
+++ b/e2e/animation-examples/app/options-default.component.ts
@@ -0,0 +1,77 @@
+import { Component } from '@angular/core';
+import {
+ trigger,
+ state,
+ style,
+ animate,
+ transition
+} from '@angular/animations';
+
+@Component({
+ template: `
+
+
+
+
+
+
+
+ `,
+ styles: [
+ '.enabled: { background-color: green; color: white }',
+ '.disabled: { background-color: grey; color: #777 }'
+ ],
+ animations: [
+ trigger('flyInOut', [
+ state('in', style({transform: 'translate(0)'})),
+
+ transition('void => *', [
+ style({transform: 'translate(100%)'}),
+ animate('{{ appearTime }}')
+ ], {
+ params: {
+ appearTime: '1s'
+ }
+ }),
+
+ transition('* => void', [
+ animate("{{ disappearTime }}",
+ style({transform: 'translateX(100%)'}))
+ ], {
+ params: {
+ disappearTime: '0.4s'
+ }
+ })
+ ])
+ ]
+})
+export class OptionsDefaultComponent {
+ public heroes = ["Harley Quinn", "Wonder Woman", "Joker", "Aquaman"];
+ public extraHeroes = ["Batman", "Superman", "Killer Frost", "The Flash"];
+
+ flyOut(hero) {
+ const heroIndex = this.heroes.indexOf(hero);
+
+ this.extraHeroes.push(hero);
+ this.heroes.splice(heroIndex, 1);
+ }
+
+ addNew() {
+ const newHero = this.extraHeroes.pop();
+ this.heroes.push(newHero);
+ }
+}
diff --git a/e2e/animation-examples/app/options.component.ts b/e2e/animation-examples/app/options.component.ts
new file mode 100644
index 000000000..8f86d8613
--- /dev/null
+++ b/e2e/animation-examples/app/options.component.ts
@@ -0,0 +1,44 @@
+import { trigger, transition, style, animate } from "@angular/animations";
+import { Component } from "@angular/core";
+
+@Component({
+ template: `
+
+
+
+
+
+ `,
+ animations: [
+ trigger("someCoolAnimation", [
+ transition(":leave", [
+ animate("{{ time }}", style({ opacity: "{{ minOpacity }}" }))
+ ]),
+ ]),
+ ],
+})
+export class OptionsComponent {
+ public animationData = {
+ value: "active",
+ params: {
+ time: "0.5s",
+ minOpacity: 0,
+ }
+ };
+
+ fadeOut() {
+ this.animationData.value = "inactive";
+ }
+}
+
diff --git a/e2e/animation-examples/app/query-stagger.component.ts b/e2e/animation-examples/app/query-stagger.component.ts
new file mode 100644
index 000000000..6047d1ecf
--- /dev/null
+++ b/e2e/animation-examples/app/query-stagger.component.ts
@@ -0,0 +1,50 @@
+import {
+ animate,
+ query,
+ stagger,
+ style,
+ transition,
+ trigger,
+} from "@angular/animations";
+import { Component } from "@angular/core";
+
+@Component({
+ moduleId: module.id,
+ animations: [
+ trigger("listAnimation", [
+ transition("* => *", [
+ // this hides everything right away
+ query(":enter", style({ opacity: 0 })),
+
+ // starts to animate things with a stagger in between
+ query(":enter", stagger(200, [
+ animate(1000, style({ opacity: 1 }))
+ ]), { delay: 200 })
+ ])
+ ])
+ ],
+ template: `
+
+
+
+
+
+
+
+ `
+})
+export class QueryStaggerComponent {
+ public items = [
+ "Dramatic",
+ "Entrance",
+ "With",
+ "Really",
+ "Cool",
+ "Stagger",
+ ];
+
+ add() {
+ const newItem = `Item ${this.items.length}`;
+ this.items.push(newItem);
+ }
+}
diff --git a/e2e/animation-examples/app/selector-all.component.ts b/e2e/animation-examples/app/selector-all.component.ts
new file mode 100644
index 000000000..e2ab9768f
--- /dev/null
+++ b/e2e/animation-examples/app/selector-all.component.ts
@@ -0,0 +1,74 @@
+import {
+ animate,
+ query,
+ style,
+ transition,
+ trigger,
+} from "@angular/animations";
+import { Component, NgZone } from "@angular/core";
+
+@Component({
+ moduleId: module.id,
+ animations: [
+ trigger("listAnimation", [
+
+ transition(":enter", [
+ query("*", [
+ style({ opacity: 0 }),
+ animate(1000, style({ opacity: 1 }))
+ ])
+ ]),
+
+ transition(":leave", [
+ query("*", [
+ style({ opacity: 1 }),
+ animate(1000, style({ opacity: 0 }))
+ ])
+
+ ])
+ ]),
+ ],
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+})
+export class SelectorAllComponent {
+ public items = [
+ "first",
+ "second",
+ ];
+
+ constructor(private zone: NgZone) {}
+
+ add() {
+ const newItem = `Item ${this.items.length}`;
+ this.items.push(newItem);
+ }
+
+ addRandom() {
+ // this.zone.run(() => {
+ this.items.push('random');
+ // });
+ }
+
+ remove(item) {
+ const index = this.items.indexOf(item);
+ this.items.splice(index, 1)
+ }
+}
diff --git a/e2e/animation-examples/e2e/helper/utils.ts b/e2e/animation-examples/e2e/helper/utils.ts
new file mode 100644
index 000000000..76b2fe75e
--- /dev/null
+++ b/e2e/animation-examples/e2e/helper/utils.ts
@@ -0,0 +1,18 @@
+import { UIElement } from "nativescript-dev-appium";
+
+export const sort = async (array: Array) => {
+ var done = false;
+ while (!done) {
+ done = true;
+ for (var i = 1; i < array.length; i++) {
+ if ((await (await array[i - 1]).location()).y > (await (await array[i]).location()).y) {
+ done = false;
+ var tmp = array[i - 1];
+ array[i - 1] = array[i];
+ array[i] = tmp;
+ }
+ }
+ }
+
+ return array;
+}
\ No newline at end of file
diff --git a/e2e/animation-examples/e2e/pages/animate-child-page.ts b/e2e/animation-examples/e2e/pages/animate-child-page.ts
new file mode 100644
index 000000000..05e8810d3
--- /dev/null
+++ b/e2e/animation-examples/e2e/pages/animate-child-page.ts
@@ -0,0 +1,40 @@
+import { AppiumDriver, UIElement } from "nativescript-dev-appium";
+import { assert } from "chai";
+import { BasePage } from "./base-page";
+
+export class AnimateChildPage extends BasePage {
+ private _parent: UIElement;
+ private _child: UIElement;
+
+ constructor(_driver: AppiumDriver) {
+ super(_driver)
+ }
+
+ async enterExample() {
+ const exampleBtn = await this._driver.findElementByAccessibilityId("animate-child");
+ await exampleBtn.click();
+ }
+
+ async waitParentToAppear() {
+ this._parent = (await this.awaitItemToAppear("parent")).element;
+ const startTime = Date.now();
+ while ((await this._parent.location()).x !== 0 && Date.now() - startTime < 3000) { }
+ }
+
+ async waitChildToAppear() {
+ this._child = (await this.awaitItemToAppear("child")).element;
+ const startTime = Date.now();
+ while ((await this._child.location()).y !== (await this._parent.location()).y && Date.now() - startTime < 3000) { }
+ }
+
+ async assertContainersPosition() {
+ assert.isTrue((await this._parent.location()).x === 0);
+ assert.isTrue((await this._parent.location()).y === (await this._child.location()).y);
+ }
+
+ private async awaitItemToAppear(item: string, wait: number = 3000): Promise<{ isVisible: boolean, element: UIElement }> {
+ const findBtn = async () => { return await this._driver.findElementByXPathIfExists(this._elementHelper.findByTextLocator("*", item, true)); };
+
+ return await this.waitElementTo(findBtn, true, wait);;
+ }
+}
\ No newline at end of file
diff --git a/e2e/animation-examples/e2e/pages/animation-builder-page.ts b/e2e/animation-examples/e2e/pages/animation-builder-page.ts
new file mode 100644
index 000000000..5c6349a7d
--- /dev/null
+++ b/e2e/animation-examples/e2e/pages/animation-builder-page.ts
@@ -0,0 +1,25 @@
+import { AppiumDriver, UIElement } from "nativescript-dev-appium";
+import { BasePage } from "./base-page";
+
+export class AnimationBuilderPage extends BasePage {
+ static tapToDisappear: string = "tapToDisappear";
+
+ constructor(driver: AppiumDriver) {
+ super(driver);
+ }
+
+ async enterExample() {
+ const exampleBtn = await this._driver.findElementByAutomationText("builder");
+ await exampleBtn.click();
+ }
+
+ async executeAnimation() {
+ const btnTapToDisappear = await this._driver.waitForElement(AnimationBuilderPage.tapToDisappear);
+ console.log("Btn tap to disappear should disappear");
+ await btnTapToDisappear.click();
+ }
+
+ async waitElementToHide(wait: number) {
+ return this.waitElementTo(() => this._driver.waitForElement(AnimationBuilderPage.tapToDisappear), false, wait);
+ }
+}
\ No newline at end of file
diff --git a/e2e/animation-examples/e2e/pages/animation-with-options-page.ts b/e2e/animation-examples/e2e/pages/animation-with-options-page.ts
new file mode 100644
index 000000000..131ac8599
--- /dev/null
+++ b/e2e/animation-examples/e2e/pages/animation-with-options-page.ts
@@ -0,0 +1,44 @@
+import { AppiumDriver, Point, UIElement } from "nativescript-dev-appium";
+import { assert } from "chai";
+import { BasePage } from "./base-page";
+
+export class AnimationWithOptionsPage extends BasePage {
+ private initialPositionOfAnimatedBtn: Point
+
+ constructor(driver: AppiumDriver) {
+ super(driver);
+ }
+
+ async enterExample() {
+ const exampleBtn = await this._driver.findElementByAccessibilityId("options");
+ await exampleBtn.click();
+ const animatedBtn = await this.animatedBtn();
+ this.initialPositionOfAnimatedBtn = await animatedBtn.location();
+ }
+
+ async btnToggleAnimation() {
+ return await this._driver.findElementByAccessibilityId("toggleAnimation");
+ }
+
+ async animatedBtn() {
+ await this._driver.wait(2000);
+ const btn = await this._driver.findElementByAccessibilityIdIfExists("animatedBtn");
+ return btn;
+ }
+
+ async toggleAnimation() {
+ const btnTapToDisappear = await this.btnToggleAnimation();
+ await btnTapToDisappear.click();
+ }
+
+ async waitElementToHide() {
+ await this._driver.wait(2000);
+ return await this._driver.findElementByAccessibilityIdIfExists("animatedBtn");
+ }
+
+ async assertPositionOfToggleAnimationBtn() {
+ await this.waitElementTo(() => this.btnToggleAnimation(), true, 5000);
+ const point: Point = await (await this.btnToggleAnimation()).location();
+ assert.isTrue(point.y === this.initialPositionOfAnimatedBtn.y);
+ }
+}
\ No newline at end of file
diff --git a/e2e/animation-examples/e2e/pages/animations-with-default-options-page.ts b/e2e/animation-examples/e2e/pages/animations-with-default-options-page.ts
new file mode 100644
index 000000000..76e34ae04
--- /dev/null
+++ b/e2e/animation-examples/e2e/pages/animations-with-default-options-page.ts
@@ -0,0 +1,71 @@
+import { AppiumDriver, UIElement } from "nativescript-dev-appium";
+import { assert } from "chai";
+import { sort } from "../helper/utils";
+import { BasePage } from "./base-page";
+
+export class AnimationsWithDefaultOptionsPage extends BasePage {
+ private _btnAddItem: UIElement;
+ private _children: Array;
+ constructor(driver: AppiumDriver) {
+ super(driver);
+ }
+
+ async enterExample() {
+ const exampleBtn = await this._driver.findElementByAccessibilityId("options-default");
+ await exampleBtn.click();
+ this._children = await this.getChildren();
+ }
+
+ async addItem() {
+ this._btnAddItem = await this._driver.findElementByAccessibilityId("add");
+
+ await this._btnAddItem.click();
+ }
+
+ async clickOnItem(item: string) {
+ const btn = await this.getItem(item);
+
+ await btn.click();
+ }
+
+ private getItem(item) {
+ return this._driver.findElementByXPathIfExists(`${this._elementHelper.getXPathByTextAttributes("//*", "itemsContainer", true)}${this._elementHelper.getXPathByTextAttributes("//*", item, false)}`);
+ }
+
+ async awaitItemToDisappear(item: string, wait: number = 3000) {
+ const startTime = Date.now();
+ let btn = await this.getItem(item);
+ while (btn && await btn.isDisplayed() && Date.now() - startTime <= wait) {
+ btn = await this.getItem(item);
+ }
+
+ this._children = await this.getChildren();
+ }
+
+ async awaitItemToAppear(item: string, wait: number = 3000) {
+ const startTime = Date.now();
+ let btn = await this.getItem(item);
+ while (!btn && !(btn && !(await btn.isDisplayed())) && Date.now() - startTime <= wait) {
+ btn = await this.getItem(item);
+ }
+
+ this._children = await this.getChildren();
+ }
+
+ async getChildren() {
+ const children: Array = await this._driver.findElementsByXPath(`${this._elementHelper.getXPathByTextAttributes("//*", "itemsContainer", true)}/*`);
+ const orderedList: Array = await sort(children);
+
+ return orderedList;
+ }
+
+ async assertItemPosition(text: string, itemIndex: number, expectedElementsCount: number) {
+ const children = this._children;
+
+ assert.isTrue(children.length === expectedElementsCount, `Expected items count: ${expectedElementsCount} is not as actual: ${children.length}`);
+ const element = children[itemIndex];
+ console.log("Element text: ", await element.text());
+ const currentElementText = await element.text();
+ assert.isTrue(currentElementText.toLowerCase() === text.toLowerCase(), `Expected element text: ${text} at position: ${itemIndex} is not as actual: ${currentElementText}!`);
+ }
+}
\ No newline at end of file
diff --git a/e2e/animation-examples/e2e/pages/base-page.ts b/e2e/animation-examples/e2e/pages/base-page.ts
new file mode 100644
index 000000000..5fe48dba3
--- /dev/null
+++ b/e2e/animation-examples/e2e/pages/base-page.ts
@@ -0,0 +1,29 @@
+import { AppiumDriver, ElementHelper, UIElement } from "nativescript-dev-appium";
+
+export class BasePage {
+ protected _elementHelper: ElementHelper;
+
+ constructor(protected _driver: AppiumDriver) {
+ this._elementHelper = new ElementHelper(this._driver.nsCapabilities);
+ }
+
+ async waitElementTo(element: () => Promise, shouldBeVisible: boolean, wait: number) {
+ const start = Date.now();
+ let btn = await element();
+ while ((await this.isBtnDisplayed(btn)) !== shouldBeVisible && Date.now() - start <= wait) {
+ btn = await element();
+ }
+
+ return { isVisible: await this.isBtnDisplayed(btn), element: btn };
+ }
+
+ async isBtnDisplayed(element: UIElement) {
+ let btn: UIElement = await element;
+ let isBtnDisplayed = false
+ try {
+ isBtnDisplayed = btn ? await btn.isDisplayed() : false;
+ } catch (error) { }
+
+ return isBtnDisplayed;
+ }
+}
\ No newline at end of file
diff --git a/e2e/animation-examples/e2e/pages/extarnal-animation-page.ts b/e2e/animation-examples/e2e/pages/extarnal-animation-page.ts
new file mode 100644
index 000000000..96eeda94b
--- /dev/null
+++ b/e2e/animation-examples/e2e/pages/extarnal-animation-page.ts
@@ -0,0 +1,26 @@
+import { AppiumDriver, UIElement } from "nativescript-dev-appium";
+import { BasePage } from "./base-page";
+
+export class ExternalAnimationPage extends BasePage {
+ constructor(driver: AppiumDriver) {
+ super(driver);
+ }
+
+ async enterExample() {
+ const exampleBtn = await this._driver.waitForElement("external");
+ await exampleBtn.click();
+ }
+
+ async toggleAnimation() {
+ const btnTapToDisappear = await this._driver.waitForElement("toggleAnimation");
+ await btnTapToDisappear.click();
+ }
+
+ animatedBtn() {
+ return this._driver.waitForElement("animatedBtn");
+ }
+
+ async waitElementToToggleVisibilityTo(shouldBeVisible: boolean) {
+ return this.waitElementTo(() => this.animatedBtn(), shouldBeVisible, 10000);
+ }
+}
\ No newline at end of file
diff --git a/e2e/animation-examples/e2e/pages/fade-in-out-page.ts b/e2e/animation-examples/e2e/pages/fade-in-out-page.ts
new file mode 100644
index 000000000..d44c8e460
--- /dev/null
+++ b/e2e/animation-examples/e2e/pages/fade-in-out-page.ts
@@ -0,0 +1,26 @@
+import { AppiumDriver } from "nativescript-dev-appium";
+import { BasePage } from "./base-page";
+
+export class FadeInOutPage extends BasePage {
+ constructor(driver: AppiumDriver) {
+ super(driver);
+ }
+
+ async enterExample() {
+ const exampleBtn = await this._driver.findElementByAccessibilityId("fade-in-out");
+ await exampleBtn.click();
+ }
+
+ async toggleAnimation() {
+ const btnTapToDisappear = await this._driver.findElementByAccessibilityId("toggleAnimation");
+ await btnTapToDisappear.click();
+ }
+
+ animatedBtn() {
+ return this._driver.findElementByAccessibilityIdIfExists("animatedBtn");
+ }
+
+ async waitElementToToggleVisibility(shouldBeVisible: boolean) {
+ return this.waitElementTo(() => this.animatedBtn(), shouldBeVisible, 10000);
+ }
+}
\ No newline at end of file
diff --git a/e2e/animation-examples/e2e/pages/hero-page.ts b/e2e/animation-examples/e2e/pages/hero-page.ts
new file mode 100644
index 000000000..503716a3c
--- /dev/null
+++ b/e2e/animation-examples/e2e/pages/hero-page.ts
@@ -0,0 +1,34 @@
+import { AppiumDriver } from "nativescript-dev-appium";
+import { BasePage } from "./base-page";
+
+export class HeroPage extends BasePage{
+ constructor(driver: AppiumDriver) {
+ super(driver);
+ }
+
+ async enterExample() {
+ const exampleBtn = await this._driver.findElementByAccessibilityId("hero");
+ await exampleBtn.click();
+ }
+
+ public async addActive() {
+ await this.executeAction("addActive");
+ }
+
+ public async addInactive() {
+ await this.executeAction("addInactive");
+ }
+
+ public async remove() {
+ await this.executeAction("remove");
+ }
+
+ public async reset() {
+ await this.executeAction("reset");
+ }
+
+ public async executeAction(name: string) {
+ const btn = await this._driver.findElementByAccessibilityId(name);
+ await btn.click();
+ }
+}
\ No newline at end of file
diff --git a/e2e/animation-examples/e2e/pages/query-stagger-page.ts b/e2e/animation-examples/e2e/pages/query-stagger-page.ts
new file mode 100644
index 000000000..354428624
--- /dev/null
+++ b/e2e/animation-examples/e2e/pages/query-stagger-page.ts
@@ -0,0 +1,42 @@
+import { AppiumDriver, UIElement, SearchOptions } from "nativescript-dev-appium";
+import { assert } from "chai";
+import { sort } from "../helper/utils";
+import { BasePage } from "./base-page";
+
+export class QueryWithStaggerPage extends BasePage {
+ private _btnAddItem: UIElement;
+ constructor(driver: AppiumDriver) {
+ super(driver);
+ }
+
+ async enterExample() {
+ const exampleBtn = await this._driver.findElementByAccessibilityId("query-stagger");
+ await exampleBtn.click();
+ }
+
+ async addItem() {
+ this._btnAddItem = await this._driver.findElementByText("ADD", SearchOptions.contains);
+
+ await this._btnAddItem.click();
+ }
+
+ async getChildren() {
+ const children: Array = await this._driver.findElementsByXPath(`${this._elementHelper.getXPathByTextAttributes("//*", "container", true)}/*`);
+ const orderedList: Array = await sort(children);
+
+ return orderedList;
+ }
+
+ async assertItemPosition(text: string, itemIndex: number, expectedElementsCount: number) {
+ const startTime = Date.now();
+ let item = await this._driver.findElementByTextIfExists(text)
+ while ((!item || !(await item.isDisplayed())) && Date.now() - startTime <= 3000) { }
+ const children = await this.getChildren();
+ assert.isTrue(children.length === expectedElementsCount);
+ const element = children[itemIndex];
+ const elementText = await element.text();
+ console.log("Element text: ", elementText);
+
+ assert.isTrue(elementText.toLowerCase() === text.toLowerCase());
+ }
+}
\ No newline at end of file
diff --git a/e2e/animation-examples/e2e/pages/selector-page.ts b/e2e/animation-examples/e2e/pages/selector-page.ts
new file mode 100644
index 000000000..612bb18db
--- /dev/null
+++ b/e2e/animation-examples/e2e/pages/selector-page.ts
@@ -0,0 +1,58 @@
+import { AppiumDriver, UIElement } from "nativescript-dev-appium";
+import { assert } from "chai";
+import { sort } from "../helper/utils";
+import { BasePage } from "./base-page";
+
+export class SelectorPage extends BasePage {
+ private _btnAddItem: UIElement;
+ private _itemsContainerXpath;
+ private _itemsContainerChildrenXpath;
+
+ constructor(_driver: AppiumDriver) {
+ super(_driver);
+ this._itemsContainerXpath = this._elementHelper.getXPathByTextAttributes("//*", "itemsContainer", true);
+ this._itemsContainerChildrenXpath = `${this._itemsContainerXpath}/*`
+ }
+
+ async enterExample() {
+ const exampleBtn = await this._driver.findElementByAccessibilityId("selector");
+ await exampleBtn.click();
+ }
+
+ async addItem() {
+ this._btnAddItem = await this._driver.findElementByText("add");
+ await this._btnAddItem.click();
+ }
+
+ async clickOnItem(item: string) {
+ const btn = await this._driver.findElementByXPath(this.itemXpath(item));
+
+ await btn.click();
+ }
+
+ async waitItemToToggleVisibility(item: string, visibility: boolean) {
+ return this.waitElementTo(() => this._driver.findElementByXPathIfExists(this.itemXpath(item)), visibility, 5000);
+ }
+
+ async getChildren() {
+ const children: Array = await this._driver.findElementsByXPath(this._itemsContainerChildrenXpath);
+ const orderedList: Array = await sort(children);
+
+ return orderedList;
+ }
+
+ async assertElementPosition(expectedElementsCount: number) {
+ const children = await this.getChildren();
+ assert.isTrue(children.length === expectedElementsCount)
+ for (let index = 0; index < children.length - 1; index++) {
+ const element = children[index];
+ const el = await (element.driver()).elementByXPathIfExists(this._elementHelper.getXPathByTextAttributes("//*", `Item No.${index}`, true));
+ console.log(await el.text());
+ assert.isTrue(el && el !== null);
+ }
+ }
+
+ private itemXpath(item) {
+ return `${this._itemsContainerXpath}${this._elementHelper.getXPathByTextAttributes("//*", item, false)}`;
+ }
+}
\ No newline at end of file
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api19-Default/add_active_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api19-Default/add_active_items.png
new file mode 100644
index 000000000..270d1b007
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api19-Default/add_active_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api19-Default/add_inactive_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api19-Default/add_inactive_items.png
new file mode 100644
index 000000000..7201c5550
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api19-Default/add_inactive_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api19-Default/add_remove_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api19-Default/add_remove_items.png
new file mode 100644
index 000000000..270d1b007
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api19-Default/add_remove_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api19-Default/add_reset_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api19-Default/add_reset_items.png
new file mode 100644
index 000000000..47d1bbb7e
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api19-Default/add_reset_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api22-Default/add_active_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api22-Default/add_active_items.png
new file mode 100644
index 000000000..7ec26ac9a
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api22-Default/add_active_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api22-Default/add_inactive_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api22-Default/add_inactive_items.png
new file mode 100644
index 000000000..48e4b3fed
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api22-Default/add_inactive_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api22-Default/add_remove_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api22-Default/add_remove_items.png
new file mode 100644
index 000000000..f5afdfa80
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api22-Default/add_remove_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api22-Default/add_reset_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api22-Default/add_reset_items.png
new file mode 100644
index 000000000..9fc42c191
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api22-Default/add_reset_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api22-Google/add_active_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api22-Google/add_active_items.png
new file mode 100644
index 000000000..16a366e55
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api22-Google/add_active_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api22-Google/add_inactive_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api22-Google/add_inactive_items.png
new file mode 100644
index 000000000..c2b6ae485
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api22-Google/add_inactive_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api22-Google/add_remove_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api22-Google/add_remove_items.png
new file mode 100644
index 000000000..8f43603c6
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api22-Google/add_remove_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api22-Google/add_reset_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api22-Google/add_reset_items.png
new file mode 100644
index 000000000..0efdf2fdf
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api22-Google/add_reset_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api23-Default/add_active_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api23-Default/add_active_items.png
new file mode 100644
index 000000000..b00ca72c1
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api23-Default/add_active_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api23-Default/add_inactive_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api23-Default/add_inactive_items.png
new file mode 100644
index 000000000..b5af980ac
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api23-Default/add_inactive_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api23-Default/add_remove_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api23-Default/add_remove_items.png
new file mode 100644
index 000000000..89a245a89
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api23-Default/add_remove_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api23-Default/add_reset_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api23-Default/add_reset_items.png
new file mode 100644
index 000000000..8c1a81384
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api23-Default/add_reset_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api24-Default/add_active_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api24-Default/add_active_items.png
new file mode 100644
index 000000000..ba1bc9e28
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api24-Default/add_active_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api24-Default/add_inactive_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api24-Default/add_inactive_items.png
new file mode 100644
index 000000000..48c57ada3
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api24-Default/add_inactive_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api24-Default/add_remove_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api24-Default/add_remove_items.png
new file mode 100644
index 000000000..ba1bc9e28
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api24-Default/add_remove_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api24-Default/add_reset_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api24-Default/add_reset_items.png
new file mode 100644
index 000000000..d18eb2357
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api24-Default/add_reset_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api25-Google/add_active_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api25-Google/add_active_items.png
new file mode 100644
index 000000000..24517e39a
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api25-Google/add_active_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api25-Google/add_inactive_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api25-Google/add_inactive_items.png
new file mode 100644
index 000000000..295cf5363
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api25-Google/add_inactive_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api25-Google/add_remove_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api25-Google/add_remove_items.png
new file mode 100644
index 000000000..952bf2257
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api25-Google/add_remove_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api25-Google/add_reset_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api25-Google/add_reset_items.png
new file mode 100644
index 000000000..f225ab1f9
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api25-Google/add_reset_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api26-Google/add_active_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api26-Google/add_active_items.png
new file mode 100644
index 000000000..24517e39a
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api26-Google/add_active_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api26-Google/add_inactive_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api26-Google/add_inactive_items.png
new file mode 100644
index 000000000..295cf5363
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api26-Google/add_inactive_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api26-Google/add_remove_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api26-Google/add_remove_items.png
new file mode 100644
index 000000000..952bf2257
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api26-Google/add_remove_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api26-Google/add_reset_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api26-Google/add_reset_items.png
new file mode 100644
index 000000000..f225ab1f9
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api26-Google/add_reset_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api27-Google/add_active_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api27-Google/add_active_items.png
new file mode 100644
index 000000000..24517e39a
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api27-Google/add_active_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api27-Google/add_inactive_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api27-Google/add_inactive_items.png
new file mode 100644
index 000000000..295cf5363
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api27-Google/add_inactive_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api27-Google/add_remove_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api27-Google/add_remove_items.png
new file mode 100644
index 000000000..952bf2257
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api27-Google/add_remove_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api27-Google/add_reset_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api27-Google/add_reset_items.png
new file mode 100644
index 000000000..f225ab1f9
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api27-Google/add_reset_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api28-Google/add_active_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api28-Google/add_active_items.png
new file mode 100644
index 000000000..a8720d5b2
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api28-Google/add_active_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api28-Google/add_inactive_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api28-Google/add_inactive_items.png
new file mode 100644
index 000000000..e681c7b4d
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api28-Google/add_inactive_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api28-Google/add_remove_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api28-Google/add_remove_items.png
new file mode 100644
index 000000000..a8720d5b2
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api28-Google/add_remove_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api28-Google/add_reset_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api28-Google/add_reset_items.png
new file mode 100644
index 000000000..c02a2a924
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api28-Google/add_reset_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api29-Google/add_active_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api29-Google/add_active_items.png
new file mode 100644
index 000000000..b39b8fa55
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api29-Google/add_active_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api29-Google/add_inactive_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api29-Google/add_inactive_items.png
new file mode 100644
index 000000000..69326e20d
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api29-Google/add_inactive_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api29-Google/add_remove_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api29-Google/add_remove_items.png
new file mode 100644
index 000000000..b39b8fa55
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api29-Google/add_remove_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api29-Google/add_reset_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api29-Google/add_reset_items.png
new file mode 100644
index 000000000..255c1c881
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/Emulator-Api29-Google/add_reset_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 7 110/add_active_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 7 110/add_active_items.png
new file mode 100644
index 000000000..ecdc5a3ce
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 7 110/add_active_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 7 110/add_inactive_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 7 110/add_inactive_items.png
new file mode 100644
index 000000000..3797ea51d
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 7 110/add_inactive_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 7 110/add_remove_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 7 110/add_remove_items.png
new file mode 100644
index 000000000..ecdc5a3ce
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 7 110/add_remove_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 7 110/add_reset_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 7 110/add_reset_items.png
new file mode 100644
index 000000000..650669e53
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 7 110/add_reset_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 7 12/add_active_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 7 12/add_active_items.png
new file mode 100644
index 000000000..121886eda
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 7 12/add_active_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 7 12/add_inactive_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 7 12/add_inactive_items.png
new file mode 100644
index 000000000..c8d8ffe29
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 7 12/add_inactive_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 7 12/add_remove_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 7 12/add_remove_items.png
new file mode 100644
index 000000000..121886eda
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 7 12/add_remove_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 7 12/add_reset_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 7 12/add_reset_items.png
new file mode 100644
index 000000000..996b6c4e5
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 7 12/add_reset_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 8/add_active_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 8/add_active_items.png
new file mode 100644
index 000000000..421bfc51a
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 8/add_active_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 8/add_inactive_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 8/add_inactive_items.png
new file mode 100644
index 000000000..b83c77259
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 8/add_inactive_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 8/add_remove_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 8/add_remove_items.png
new file mode 100644
index 000000000..33116d0f1
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 8/add_remove_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 8/add_reset_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 8/add_reset_items.png
new file mode 100644
index 000000000..0a788d779
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone 8/add_reset_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X 110/add_active_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X 110/add_active_items.png
new file mode 100644
index 000000000..fae72a389
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X 110/add_active_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X 110/add_inactive_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X 110/add_inactive_items.png
new file mode 100644
index 000000000..39fbf2243
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X 110/add_inactive_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X 110/add_remove_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X 110/add_remove_items.png
new file mode 100644
index 000000000..fae72a389
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X 110/add_remove_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X 110/add_reset_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X 110/add_reset_items.png
new file mode 100644
index 000000000..6f35565ae
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X 110/add_reset_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X 12/add_active_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X 12/add_active_items.png
new file mode 100644
index 000000000..2abe37a94
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X 12/add_active_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X 12/add_inactive_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X 12/add_inactive_items.png
new file mode 100644
index 000000000..69c569cd4
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X 12/add_inactive_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X 12/add_remove_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X 12/add_remove_items.png
new file mode 100644
index 000000000..cabf56466
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X 12/add_remove_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X 12/add_reset_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X 12/add_reset_items.png
new file mode 100644
index 000000000..034b4696d
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X 12/add_reset_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone XR 12/add_active_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone XR 12/add_active_items.png
new file mode 100644
index 000000000..f431a03c8
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone XR 12/add_active_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone XR 12/add_inactive_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone XR 12/add_inactive_items.png
new file mode 100644
index 000000000..1f39e498e
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone XR 12/add_inactive_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone XR 12/add_remove_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone XR 12/add_remove_items.png
new file mode 100644
index 000000000..f431a03c8
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone XR 12/add_remove_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone XR 12/add_reset_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone XR 12/add_reset_items.png
new file mode 100644
index 000000000..b74aa4345
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone XR 12/add_reset_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone XR 13/add_active_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone XR 13/add_active_items.png
new file mode 100644
index 000000000..cf0b582db
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone XR 13/add_active_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone XR 13/add_inactive_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone XR 13/add_inactive_items.png
new file mode 100644
index 000000000..dd0a15a1a
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone XR 13/add_inactive_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone XR 13/add_remove_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone XR 13/add_remove_items.png
new file mode 100644
index 000000000..cf0b582db
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone XR 13/add_remove_items.png differ
diff --git a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone XR 13/add_reset_items.png b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone XR 13/add_reset_items.png
new file mode 100644
index 000000000..df8a19656
Binary files /dev/null and b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone XR 13/add_reset_items.png differ
diff --git "a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X\312\200/add_active_items.png" "b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X\312\200/add_active_items.png"
new file mode 100644
index 000000000..b2576fe65
Binary files /dev/null and "b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X\312\200/add_active_items.png" differ
diff --git "a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X\312\200/add_inactive_items.png" "b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X\312\200/add_inactive_items.png"
new file mode 100644
index 000000000..7d5881652
Binary files /dev/null and "b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X\312\200/add_inactive_items.png" differ
diff --git "a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X\312\200/add_remove_items.png" "b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X\312\200/add_remove_items.png"
new file mode 100644
index 000000000..0a9a802d3
Binary files /dev/null and "b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X\312\200/add_remove_items.png" differ
diff --git "a/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X\312\200/add_reset_items.png" "b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X\312\200/add_reset_items.png"
new file mode 100644
index 000000000..ae2bc14bb
Binary files /dev/null and "b/e2e/animation-examples/e2e/resources/images/ng4animations/iPhone X\312\200/add_reset_items.png" differ
diff --git a/e2e/animation-examples/e2e/setup.ts b/e2e/animation-examples/e2e/setup.ts
new file mode 100644
index 000000000..e7143ae78
--- /dev/null
+++ b/e2e/animation-examples/e2e/setup.ts
@@ -0,0 +1,18 @@
+import { startServer, stopServer, ITestReporter, nsCapabilities, LogImageType } from "nativescript-dev-appium";
+const addContext = require('mochawesome/addContext');
+
+const testReporterContext = {};
+testReporterContext.name = "mochawesome";
+testReporterContext.reportDir = "mochawesome-report";
+testReporterContext.log = addContext;
+testReporterContext.logImageTypes = [LogImageType.screenshots, LogImageType.everyImage];
+nsCapabilities.testReporter = testReporterContext;
+
+before("start server", async function () {
+ nsCapabilities.testReporter.context = this;
+ await startServer();
+});
+
+after("stop server", async function () {
+ await stopServer();
+});
diff --git a/e2e/animation-examples/e2e/smoke.e2e-spec.ts b/e2e/animation-examples/e2e/smoke.e2e-spec.ts
new file mode 100644
index 000000000..9d929b361
--- /dev/null
+++ b/e2e/animation-examples/e2e/smoke.e2e-spec.ts
@@ -0,0 +1,165 @@
+import { AppiumDriver, createDriver, nsCapabilities } from "nativescript-dev-appium";
+import { assert } from "chai";
+import { AnimationBuilderPage } from "./pages/animation-builder-page";
+import { ExternalAnimationPage } from "./pages/extarnal-animation-page";
+import { SelectorPage } from "./pages/selector-page";
+import { QueryWithStaggerPage } from "./pages/query-stagger-page";
+import { FadeInOutPage } from "./pages/fade-in-out-page";
+import { AnimationWithOptionsPage } from "./pages/animation-with-options-page";
+import { AnimationsWithDefaultOptionsPage } from "./pages/animations-with-default-options-page";
+import { AnimateChildPage } from "./pages/animate-child-page";
+import { HeroPage } from "./pages/hero-page";
+import { isSauceLab } from "nativescript-dev-appium/lib/parser";
+import { ImageOptions } from "nativescript-dev-appium/lib/image-options";
+
+const QUEUE_WAIT_TIME: number = 600000; // Sometimes SauceLabs threads are not available and the tests wait in a queue to start. Wait 10 min before timeout.
+const isSauceRun = isSauceLab;
+
+describe("smoke-tests", async function () {
+ let driver: AppiumDriver;
+
+ before(async function () {
+ this.timeout(QUEUE_WAIT_TIME);
+ nsCapabilities.testReporter.context = this;
+ driver = await createDriver();
+ driver.imageHelper.defaultTolerance = 50;
+ driver.imageHelper.defaultToleranceType = ImageOptions.pixel;
+ });
+
+ after(async function () {
+ if (isSauceRun) {
+ driver.sessionId().then(function (sessionId) {
+ console.log("Report https://saucelabs.com/beta/tests/" + sessionId);
+ });
+ }
+ await driver.quit();
+ console.log("Quit driver!");
+ });
+
+ afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logTestArtifacts(this.currentTest.title);
+ }
+ try {
+ await driver.navBack();
+ } catch (error) {
+ await driver.logTestArtifacts(`${this.currentTest.title}_navBack_fail`);
+ }
+ });
+
+ it("animation builder - btn should disappear", async function () {
+ const animationBuilder = new AnimationBuilderPage(driver);
+ await animationBuilder.enterExample();
+ await animationBuilder.executeAnimation();
+ const result = await animationBuilder.waitElementToHide(driver.defaultWaitTime);
+ assert.isFalse(!result || result.isVisible, "The btn should disappear");
+ });
+
+ it("external animation - visibility", async function () {
+ this.retries(1);
+ const externalAnimationPage = new ExternalAnimationPage(driver);
+ await externalAnimationPage.enterExample();
+ await externalAnimationPage.toggleAnimation();
+ let result = await externalAnimationPage.waitElementToToggleVisibilityTo(false);
+ assert.isFalse(result.isVisible, "The button should disappear!");
+
+ await externalAnimationPage.toggleAnimation();
+ result = await externalAnimationPage.waitElementToToggleVisibilityTo(true);
+ assert.isTrue(result.isVisible, "The button should appear!");
+ });
+
+ it("selector", async function () {
+ const selectorPage = new SelectorPage(driver);
+ await selectorPage.enterExample();
+ await selectorPage.addItem();
+ await selectorPage.waitItemToToggleVisibility("Item No.2", true);
+ await selectorPage.assertElementPosition(4);
+
+ await selectorPage.clickOnItem("second");
+ await selectorPage.waitItemToToggleVisibility("second", false);
+ await selectorPage.assertElementPosition(3);
+ });
+
+ it("query with stagger", async function () {
+ const queryWithStaggerPage = new QueryWithStaggerPage(driver);
+ await queryWithStaggerPage.enterExample();
+ await queryWithStaggerPage.addItem();
+ await queryWithStaggerPage.assertItemPosition("Item 6", 6, 7);
+ });
+
+ it("fade in - out", async function () {
+ this.retries(1);
+ const fadeInOutPage = new FadeInOutPage(driver);
+ await fadeInOutPage.enterExample();
+ await fadeInOutPage.toggleAnimation();
+ let result = await fadeInOutPage.waitElementToToggleVisibility(false);
+ assert.isFalse(result.isVisible, "The button should disappear!");
+
+ await fadeInOutPage.toggleAnimation();
+ result = await fadeInOutPage.waitElementToToggleVisibility(true);
+ assert.isTrue(result.isVisible, "The button should appear!");
+ });
+
+ it("animation with options", async function () {
+ const animationWithOptionsPage = new AnimationWithOptionsPage(driver);
+ await animationWithOptionsPage.enterExample();
+ await animationWithOptionsPage.toggleAnimation();
+ const result = await animationWithOptionsPage.waitElementToHide();
+ assert.isUndefined(result, "The button should disappear!");
+
+ await animationWithOptionsPage.assertPositionOfToggleAnimationBtn();
+ });
+
+ it("animation with default options", async function () {
+ const animationWithOptionsPage = new AnimationsWithDefaultOptionsPage(driver);
+ await animationWithOptionsPage.enterExample();
+ let examplesCount = 5;
+ await animationWithOptionsPage.assertItemPosition("Harley Quinn", 1, examplesCount);
+ await animationWithOptionsPage.assertItemPosition("Wonder Woman", 2, examplesCount);
+ await animationWithOptionsPage.assertItemPosition("Joker", 3, examplesCount);
+ await animationWithOptionsPage.assertItemPosition("Aquaman", 4, examplesCount);
+
+ await animationWithOptionsPage.clickOnItem("Harley Quinn");
+ examplesCount--;
+ await animationWithOptionsPage.awaitItemToDisappear("Harley Quinn");
+ await animationWithOptionsPage.assertItemPosition("Wonder Woman", 1, examplesCount);
+ await animationWithOptionsPage.assertItemPosition("Joker", 2, examplesCount);
+ await animationWithOptionsPage.assertItemPosition("Aquaman", 3, examplesCount);
+
+ await animationWithOptionsPage.addItem();
+ examplesCount++;
+ await animationWithOptionsPage.awaitItemToAppear("Harley Quinn");
+ await animationWithOptionsPage.assertItemPosition("Wonder Woman", 1, examplesCount);
+ await animationWithOptionsPage.assertItemPosition("Joker", 2, examplesCount);
+ await animationWithOptionsPage.assertItemPosition("Aquaman", 3, examplesCount);
+ await animationWithOptionsPage.assertItemPosition("Harley Quinn", 4, examplesCount);
+ });
+
+ it("animate child", async function () {
+ this.retries(1);
+ const animateChildPage = new AnimateChildPage(driver);
+ await animateChildPage.enterExample();
+ await animateChildPage.waitParentToAppear();
+ await animateChildPage.waitChildToAppear();
+ await animateChildPage.assertContainersPosition();
+ });
+
+ it("angular docs", async function () {
+ const heroPage = new HeroPage(driver);
+ await heroPage.enterExample();
+ await heroPage.addActive();
+ let result = await driver.compareScreen("add_active_items", 5);
+
+ await heroPage.addInactive();
+ result = await driver.compareScreen("add_inactive_items", 5) && result;
+
+ await heroPage.remove();
+ result = await driver.compareScreen("add_remove_items", 5) && result;
+
+ await heroPage.reset();
+ result = await driver.compareScreen("add_reset_items", 5) && result;
+
+ assert.isTrue(result, "Image verification failed!");
+
+ });
+});
\ No newline at end of file
diff --git a/e2e/animation-examples/e2e/tsconfig.json b/e2e/animation-examples/e2e/tsconfig.json
new file mode 100644
index 000000000..6517ca58d
--- /dev/null
+++ b/e2e/animation-examples/e2e/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es6",
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "importHelpers": false,
+ "sourceMap": true,
+ "types": [
+ "node",
+ "mocha",
+ "chai"
+ ],
+ "lib": [
+ "es2015",
+ "dom"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/e2e/animation-examples/nativescript.config.ts b/e2e/animation-examples/nativescript.config.ts
new file mode 100644
index 000000000..d117ed3e9
--- /dev/null
+++ b/e2e/animation-examples/nativescript.config.ts
@@ -0,0 +1,11 @@
+import { NativeScriptConfig } from '@nativescript/core'
+
+export default {
+ id: 'org.nativescript.ng4animations',
+ appResourcesPath: 'app/App_Resources',
+ android: {
+ v8Flags: '--expose_gc',
+ markingMode: 'none',
+ },
+ appPath: 'app',
+} as NativeScriptConfig
diff --git a/e2e/animation-examples/package.json b/e2e/animation-examples/package.json
new file mode 100644
index 000000000..a56c99369
--- /dev/null
+++ b/e2e/animation-examples/package.json
@@ -0,0 +1,53 @@
+{
+ "description": "NativeScript Application",
+ "main": "main.js",
+ "license": "SEE LICENSE IN ",
+ "readme": "NativeScript Application",
+ "repository": "",
+ "dependencies": {
+ "@angular/animations": "~11.0.0",
+ "@angular/common": "~11.0.0",
+ "@angular/compiler": "~11.0.0",
+ "@angular/core": "~11.0.0",
+ "@angular/forms": "~11.0.0",
+ "@angular/platform-browser": "~11.0.0",
+ "@angular/platform-browser-dynamic": "~11.0.0",
+ "@angular/router": "~11.0.0",
+ "@nativescript/angular": "file:../../dist/nativescript-angular-scoped.tgz",
+ "nativescript-theme-core": "~1.0.2",
+ "reflect-metadata": "~0.1.13",
+ "rxjs": "~6.6.0",
+ "@nativescript/core": "~7.0.0",
+ "zone.js": "^0.11.1"
+ },
+ "devDependencies": {
+ "@angular/compiler-cli": "~11.0.0",
+ "@nativescript/ios": "7.0.5",
+ "@nativescript/webpack": "~3.0.0",
+ "@ngtools/webpack": "~11.0.0",
+ "@types/chai": "~4.2.0",
+ "@types/mocha": "~7.0.0",
+ "@types/node": "~14.0.0",
+ "babel-traverse": "~6.26.0",
+ "babel-types": "~6.26.0",
+ "babylon": "~6.18.0",
+ "chai": "^4.2.0",
+ "lazy": "~1.0.11",
+ "mocha": "~8.0.1",
+ "mochawesome": "~6.1.1",
+ "nativescript-css-loader": "~0.26.0",
+ "node-sass": "~4.14.1",
+ "typescript": "~4.0.0"
+ },
+ "scripts": {
+ "clean": "ns clean",
+ "setup": "cd ../../nativescript-angular && npm run prep.apps && cd ../e2e/animation-examples && npm run clean",
+ "u": "update-ns-webpack",
+ "e2e": "tsc -p e2e && mocha --opts ../config/mocha.opts --recursive e2e --appiumCapsLocation ../config/appium.capabilities.json",
+ "e2e-watch": "tsc -p e2e --watch",
+ "ns-verify-bundle": "ns-verify-bundle",
+ "update-ns-webpack": "update-ns-webpack",
+ "ios": "ns debug ios --emulator --no-hmr",
+ "android": "ns debug android --emulator --no-hmr"
+ }
+}
diff --git a/e2e/animation-examples/tsconfig.json b/e2e/animation-examples/tsconfig.json
new file mode 100644
index 000000000..ff6adb22e
--- /dev/null
+++ b/e2e/animation-examples/tsconfig.json
@@ -0,0 +1,32 @@
+{
+ "compilerOptions": {
+ "module": "ESNext",
+ "target": "es2015",
+ "moduleResolution": "node",
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "noEmitHelpers": true,
+ "noEmitOnError": true,
+ "skipLibCheck": true,
+ "lib": [
+ "es2017",
+ "dom",
+ "es6"
+ ],
+ "baseUrl": ".",
+ "paths": {
+ "~/*": [
+ "app/*"
+ ]
+ }
+ },
+ "files": [
+ "./app/main.ts"
+ ],
+ "exclude": [
+ "node_modules",
+ "platforms",
+ "**/*.aot",
+ "e2e"
+ ]
+}
\ No newline at end of file
diff --git a/e2e/config/appium.capabilities.json b/e2e/config/appium.capabilities.json
new file mode 100644
index 000000000..695030f6a
--- /dev/null
+++ b/e2e/config/appium.capabilities.json
@@ -0,0 +1,224 @@
+{
+ "travis-e2e-tests": {
+ "platformName": "Android",
+ "platformVersion": "4.4",
+ "deviceName": "test",
+ "lt": 60000,
+ "newCommandTimeout": 720,
+ "noReset": true,
+ "fullReset": false
+ },
+ "nexus5": {
+ "browserName": "",
+ "platformName": "Android",
+ "platformVersion": "6.0",
+ "deviceName": "device",
+ "udid": "077e4a47003b7698"
+ },
+ "android19": {
+ "platformName": "Android",
+ "platformVersion": "4.4",
+ "deviceName": "Emulator-Api19-Default",
+ "avd": "Emulator-Api19-Default",
+ "lt": 60000,
+ "newCommandTimeout": 720,
+ "noReset": false,
+ "fullReset": false
+ },
+ "android21": {
+ "platformName": "Android",
+ "platformVersion": "5.0",
+ "deviceName": "Emulator-Api21-Default",
+ "avd": "Emulator-Api21-Default",
+ "lt": 60000,
+ "newCommandTimeout": 720,
+ "noReset": false,
+ "fullReset": false
+ },
+ "android22": {
+ "platformName": "Android",
+ "platformVersion": "5.1",
+ "deviceName": "Emulator-Api22-Default",
+ "avd": "Emulator-Api22-Default",
+ "lt": 60000,
+ "newCommandTimeout": 720,
+ "noReset": false,
+ "fullReset": false
+ },
+ "android22.sauce": {
+ "platformName": "Android",
+ "platformVersion": "5.1",
+ "deviceName": "Android GoogleAPI Emulator",
+ "lt": 60000,
+ "newCommandTimeout": 720,
+ "appiumVersion": "1.13.0",
+ "noReset": true,
+ "fullReset": false,
+ "app": "",
+ "idleTimeout": 120,
+ "automationName": "Appium",
+ "density":3.2,
+ "offsetPixels":51
+ },
+ "android23": {
+ "platformName": "Android",
+ "platformVersion": "6.0",
+ "deviceName": "Emulator-Api23-Default",
+ "avd": "Emulator-Api23-Default",
+ "lt": 60000,
+ "newCommandTimeout": 720,
+ "noReset": false,
+ "fullReset": false
+ },
+ "android24": {
+ "platformName": "Android",
+ "platformVersion": "7.0",
+ "deviceName": "Emulator-Api24-Default",
+ "avd": "Emulator-Api24-Default",
+ "lt": 60000,
+ "newCommandTimeout": 720,
+ "noReset": false,
+ "fullReset": false
+ },
+ "android25": {
+ "platformName": "Android",
+ "platformVersion": "7.1",
+ "deviceName": "Emulator-Api25-Google",
+ "avd": "Emulator-Api25-Google",
+ "lt": 60000,
+ "newCommandTimeout": 720,
+ "noReset": false,
+ "fullReset": false
+ },
+ "android26": {
+ "platformName": "Android",
+ "platformVersion": "8.0",
+ "deviceName": "Emulator-Api26-Google",
+ "avd": "Emulator-Api26-Google",
+ "lt": 60000,
+ "automationName": "UIAutomator2",
+ "newCommandTimeout": 720,
+ "noReset": false,
+ "fullReset": false
+ },
+ "android27": {
+ "platformName": "Android",
+ "platformVersion": "27",
+ "deviceName": "Emulator-Api27-Google",
+ "avd": "Emulator-Api27-Google",
+ "lt": 60000,
+ "newCommandTimeout": 720,
+ "noReset": true,
+ "fullReset": false
+ },
+ "android28": {
+ "platformName": "Android",
+ "platformVersion": "28",
+ "deviceName": "Emulator-Api28-Google",
+ "avd": "Emulator-Api28-Google",
+ "lt": 60000,
+ "newCommandTimeout": 720,
+ "noReset": true,
+ "fullReset": false
+ },
+ "android29": {
+ "platformName": "Android",
+ "platformVersion": "29",
+ "deviceName": "Emulator-Api29-Google",
+ "avd": "Emulator-Api29-Google",
+ "lt": 60000,
+ "newCommandTimeout": 720,
+ "noReset": true,
+ "fullReset": false
+ },
+ "sim.iPhone7.iOS100": {
+ "platformName": "iOS",
+ "platformVersion": "10.0",
+ "deviceName": "iPhone 7 100",
+ "noReset": true,
+ "fullReset": false
+ },
+ "sim.iPhone7.iOS110": {
+ "platformName": "iOS",
+ "platformVersion": "11.4",
+ "deviceName": "iPhone 7 110",
+ "noReset": true,
+ "fullReset": false
+ },
+ "sim.iPhone7.ios12": {
+ "platformName": "iOS",
+ "platformVersion": "12.0",
+ "deviceName": "iPhone 7",
+ "noReset": true,
+ "fullReset": false
+ },
+ "sim.iPhone8.iOS112": {
+ "platformName": "iOS",
+ "platformVersion": "11.2",
+ "deviceName": "iPhone 8 112",
+ "noReset": true,
+ "fullReset": false
+ },
+ "sim.iPhone8": {
+ "platformName": "iOS",
+ "platformVersion": "12",
+ "deviceName": "iPhone 8",
+ "noReset": true,
+ "fullReset": false
+ },
+ "sim.iPhoneX.iOS110": {
+ "platformName": "iOS",
+ "platformVersion": "11.2",
+ "deviceName": "iPhone X 110",
+ "noReset": true,
+ "fullReset": false
+ },
+ "sim.iPhoneX": {
+ "platformName": "iOS",
+ "platformVersion": "12.0",
+ "deviceName": "iPhone X",
+ "noReset": true,
+ "fullReset": false
+ },
+ "sim.iPhoneXS": {
+ "platformName": "iOS",
+ "platformVersion": "12.0",
+ "deviceName": "iPhone XS",
+ "noReset": true,
+ "fullReset": false
+ },
+ "sim.iPhoneXS.latest": {
+ "platformName": "iOS",
+ "platformVersion": "12.1",
+ "deviceName": "iPhone XS",
+ "noReset": true,
+ "fullReset": false
+ },
+ "sim.iPhoneXR": {
+ "platformName": "iOS",
+ "platformVersion": "12.0",
+ "deviceName": "iPhone XR",
+ "noReset": true,
+ "fullReset": false
+ },
+ "sim.iPhoneXR.ios12": {
+ "platformName": "iOS",
+ "platformVersion": "12.0",
+ "deviceName": "iPhone XR 12",
+ "noReset": true,
+ "fullReset": false
+ },
+ "sim.iPhoneXR.ios13": {
+ "platformName": "iOS",
+ "platformVersion": "13.0",
+ "deviceName": "iPhone XR 13",
+ "appiumVersion": "1.15.0",
+ "noReset": true,
+ "fullReset": false,
+ "density": 3,
+ "offsetPixels": 87,
+ "app": "",
+ "idleTimeout": 120,
+ "automationName": "Appium"
+ }
+}
\ No newline at end of file
diff --git a/e2e/config/mocha.opts b/e2e/config/mocha.opts
new file mode 100644
index 000000000..92a64e8fe
--- /dev/null
+++ b/e2e/config/mocha.opts
@@ -0,0 +1,5 @@
+--timeout 120000
+--recursive e2e
+--reporter mochawesome
+--reporter-options quiet=true,html=true,inline=true
+--exit
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/.gitignore b/e2e/modal-navigation-ng/.gitignore
new file mode 100644
index 000000000..23d2d7b1d
--- /dev/null
+++ b/e2e/modal-navigation-ng/.gitignore
@@ -0,0 +1,9 @@
+platforms
+node_modules
+hooks
+
+app/**/*.js
+e2e/**/*.js
+e2e/reports
+test-results.xml
+mochawesome-report
diff --git a/e2e/modal-navigation-ng/app/App_Resources/Android/app.gradle b/e2e/modal-navigation-ng/app/App_Resources/Android/app.gradle
new file mode 100644
index 000000000..924f7c70f
--- /dev/null
+++ b/e2e/modal-navigation-ng/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.modalnavigationng"
+ }
+ aaptOptions {
+ additionalParameters "--no-version-vectors"
+ }
+}
diff --git a/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/AndroidManifest.xml b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..9db832151
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/AndroidManifest.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-hdpi/background.png b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-hdpi/background.png
new file mode 100644
index 000000000..eb381c258
Binary files /dev/null and b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-hdpi/background.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-hdpi/icon.png b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-hdpi/icon.png
new file mode 100755
index 000000000..9cde84cd5
Binary files /dev/null and b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-hdpi/icon.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-hdpi/logo.png b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-hdpi/logo.png
new file mode 100644
index 000000000..5218f4c90
Binary files /dev/null and b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-hdpi/logo.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-ldpi/background.png b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-ldpi/background.png
new file mode 100644
index 000000000..748b2adf5
Binary files /dev/null and b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-ldpi/background.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-ldpi/icon.png b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-ldpi/icon.png
new file mode 100755
index 000000000..4d6a674b3
Binary files /dev/null and b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-ldpi/icon.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-ldpi/logo.png b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-ldpi/logo.png
new file mode 100644
index 000000000..b9e102a76
Binary files /dev/null and b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-ldpi/logo.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-mdpi/background.png b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-mdpi/background.png
new file mode 100644
index 000000000..efeaf2907
Binary files /dev/null and b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-mdpi/background.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-mdpi/icon.png b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-mdpi/icon.png
new file mode 100755
index 000000000..92ccc85a6
Binary files /dev/null and b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-mdpi/icon.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-mdpi/logo.png b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-mdpi/logo.png
new file mode 100644
index 000000000..626338766
Binary files /dev/null and b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-mdpi/logo.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml
new file mode 100644
index 000000000..ada77f92c
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml
@@ -0,0 +1,8 @@
+
+ -
+
+
+ -
+
+
+
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xhdpi/background.png b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xhdpi/background.png
new file mode 100644
index 000000000..612bbd072
Binary files /dev/null and b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xhdpi/background.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xhdpi/icon.png b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xhdpi/icon.png
new file mode 100644
index 000000000..8bcde6277
Binary files /dev/null and b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xhdpi/icon.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xhdpi/logo.png b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xhdpi/logo.png
new file mode 100644
index 000000000..ad8ee2f4b
Binary files /dev/null and b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xhdpi/logo.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png
new file mode 100644
index 000000000..0fa88e235
Binary files /dev/null and b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xxhdpi/icon.png b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xxhdpi/icon.png
new file mode 100644
index 000000000..9d81c85dc
Binary files /dev/null and b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xxhdpi/icon.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xxhdpi/logo.png b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xxhdpi/logo.png
new file mode 100644
index 000000000..668327832
Binary files /dev/null and b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xxhdpi/logo.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png
new file mode 100644
index 000000000..c650f6438
Binary files /dev/null and b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/icon.png b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/icon.png
new file mode 100644
index 000000000..9a34d0d43
Binary files /dev/null and b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/icon.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/logo.png b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/logo.png
new file mode 100644
index 000000000..fa6331c8d
Binary files /dev/null and b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/logo.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/values-v21/colors.xml b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/values-v21/colors.xml
new file mode 100644
index 000000000..a64641a9d
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/values-v21/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #3d5afe
+
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/values-v21/styles.xml b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/values-v21/styles.xml
new file mode 100644
index 000000000..dac8727c8
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/values-v21/styles.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/values/colors.xml b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/values/colors.xml
new file mode 100644
index 000000000..74ad8829c
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/values/colors.xml
@@ -0,0 +1,7 @@
+
+
+ #F5F5F5
+ #757575
+ #33B5E5
+ #272734
+
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/values/styles.xml b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/values/styles.xml
new file mode 100644
index 000000000..c793e6d4c
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/App_Resources/Android/src/main/res/values/styles.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000..4034b76e6
--- /dev/null
+++ b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/Contents.json b/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/Contents.json
new file mode 100644
index 000000000..da4a164c9
--- /dev/null
+++ b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json b/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
new file mode 100644
index 000000000..11bfcf55c
--- /dev/null
+++ b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-1125h.png b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-1125h.png differ
diff --git a/startup-test/app/App_Resources/iOS/Default-568h@2x.png b/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png
old mode 100755
new mode 100644
similarity index 100%
rename from startup-test/app/App_Resources/iOS/Default-568h@2x.png
rename to e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png
diff --git a/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape-X.png b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape-X.png differ
diff --git a/startup-test/app/App_Resources/iOS/Default-Landscape.png b/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png
old mode 100755
new mode 100644
similarity index 100%
rename from startup-test/app/App_Resources/iOS/Default-Landscape.png
rename to e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png
diff --git a/startup-test/app/App_Resources/iOS/Default-Landscape@2x.png b/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png
old mode 100755
new mode 100644
similarity index 100%
rename from startup-test/app/App_Resources/iOS/Default-Landscape@2x.png
rename to e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png
diff --git a/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png differ
diff --git a/startup-test/app/App_Resources/iOS/Default-Portrait.png b/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png
old mode 100755
new mode 100644
similarity index 100%
rename from startup-test/app/App_Resources/iOS/Default-Portrait.png
rename to e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png
diff --git a/startup-test/app/App_Resources/iOS/Default-Portrait@2x.png b/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png
old mode 100755
new mode 100644
similarity index 100%
rename from startup-test/app/App_Resources/iOS/Default-Portrait@2x.png
rename to e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png
diff --git a/startup-test/app/App_Resources/iOS/Default.png b/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png
old mode 100755
new mode 100644
similarity index 100%
rename from startup-test/app/App_Resources/iOS/Default.png
rename to e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png
diff --git a/startup-test/app/App_Resources/iOS/Default@2x.png b/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png
old mode 100755
new mode 100644
similarity index 100%
rename from startup-test/app/App_Resources/iOS/Default@2x.png
rename to e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png
diff --git a/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json b/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json
new file mode 100644
index 000000000..4f4e9c506
--- /dev/null
+++ b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json b/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json
new file mode 100644
index 000000000..23c0ffd7a
--- /dev/null
+++ b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png differ
diff --git a/e2e/modal-navigation-ng/app/App_Resources/iOS/Info.plist b/e2e/modal-navigation-ng/app/App_Resources/iOS/Info.plist
new file mode 100644
index 000000000..ea3e3ea23
--- /dev/null
+++ b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/App_Resources/iOS/LaunchScreen.storyboard b/e2e/modal-navigation-ng/app/App_Resources/iOS/LaunchScreen.storyboard
new file mode 100644
index 000000000..c4e5a3f39
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/App_Resources/iOS/LaunchScreen.storyboard
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/modal-navigation-ng/app/App_Resources/iOS/build.xcconfig b/e2e/modal-navigation-ng/app/App_Resources/iOS/build.xcconfig
new file mode 100644
index 000000000..4b0118490
--- /dev/null
+++ b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/CODE_OF_CONDUCT.md b/e2e/modal-navigation-ng/app/CODE_OF_CONDUCT.md
new file mode 100644
index 000000000..1c845d0b1
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/CODE_OF_CONDUCT.md
@@ -0,0 +1,83 @@
+# NativeScript Community Code of Conduct
+
+Our community members come from all walks of life and are all at different stages of their personal and professional journeys. To support everyone, we've prepared a short code of conduct. Our mission is best served in an environment that is friendly, safe, and accepting; free from intimidation or harassment.
+
+Towards this end, certain behaviors and practices will not be tolerated.
+
+## tl;dr
+
+- Be respectful.
+- We're here to help.
+- Abusive behavior is never tolerated.
+- Violations of this code may result in swift and permanent expulsion from the NativeScript community channels.
+
+## Administrators
+
+- Dan Wilson (@DanWilson on Slack)
+- Jen Looper (@jen.looper on Slack)
+- TJ VanToll (@tjvantoll on Slack)
+
+## Scope
+
+We expect all members of the NativeScript community, including administrators, users, facilitators, and vendors to abide by this Code of Conduct at all times in our community venues, online and in person, and in one-on-one communications pertaining to NativeScript affairs.
+
+This policy covers the usage of the NativeScript Slack community, as well as the NativeScript support forums, NativeScript GitHub repositories, the NativeScript website, and any NativeScript-related events. This Code of Conduct is in addition to, and does not in any way nullify or invalidate, any other terms or conditions related to use of NativeScript.
+
+The definitions of various subjective terms such as "discriminatory", "hateful", or "confusing" will be decided at the sole discretion of the NativeScript administrators.
+
+## Friendly, Harassment-Free Space
+
+We are committed to providing a friendly, safe, and welcoming environment for all, regardless of gender identity, sexual orientation, disability, ethnicity, religion, age, physical appearance, body size, race, or similar personal characteristics.
+
+We ask that you please respect that people have differences of opinion regarding technical choices, and acknowledge that every design or implementation choice carries a trade-off and numerous costs. There is seldom a single right answer. A difference of technology preferences is never a license to be rude.
+
+Any spamming, trolling, flaming, baiting, or other attention-stealing behaviour is not welcome, and will not be tolerated.
+
+Harassing other users of NativeScript is never tolerated, whether via public or private media.
+
+Avoid using offensive or harassing package names, nicknames, or other identifiers that might detract from a friendly, safe, and welcoming environment for all.
+
+Harassment includes, but is not limited to: harmful or prejudicial verbal or written comments related to gender identity, sexual orientation, disability, ethnicity, religion, age, physical appearance, body size, race, or similar personal characteristics; inappropriate use of nudity, sexual images, and/or sexually explicit language in public spaces; threats of physical or non-physical harm; deliberate intimidation, stalking or following; harassing photography or recording; sustained disruption of talks or other events; inappropriate physical contact; and unwelcome sexual attention.
+
+## Acceptable Content
+
+The NativeScript administrators reserve the right to make judgement calls about what is and isn't appropriate in published content. These are guidelines to help you be successful in our community.
+
+Content must contain something applicable to the previously stated goals of the NativeScript community. "Spamming", that is, publishing any form of content that is not applicable, is not allowed.
+
+Content must not contain illegal or infringing content. You should only publish content to NativeScript properties if you have the right to do so. This includes complying with all software license agreements or other intellectual property restrictions. For example, redistributing an MIT-licensed module with the copyright notice removed, would not be allowed. You will be responsible for any violation of laws or others’ intellectual property rights.
+
+Content must not be malware. For example, content (code, video, pictures, words, etc.) which is designed to maliciously exploit or damage computer systems, is not allowed.
+
+Content name, description, and other visible metadata must not include abusive, inappropriate, or harassing content.
+
+## Reporting Violations of this Code of Conduct
+
+If you believe someone is harassing you or has otherwise violated this Code of Conduct, please contact the administrators and send us an abuse report. If this is the initial report of a problem, please include as much detail as possible. It is easiest for us to address issues when we have more context.
+
+## Consequences
+
+All content published to the NativeScript community channels is hosted at the sole discretion of the NativeScript administrators.
+
+Unacceptable behavior from any community member, including sponsors, employees, customers, or others with decision-making authority, will not be tolerated.
+
+Anyone asked to stop unacceptable behavior is expected to comply immediately.
+
+If a community member engages in unacceptable behavior, the NativeScript administrators may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event or service).
+
+## Addressing Grievances
+
+If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify the administrators. We will do our best to ensure that your grievance is handled appropriately.
+
+In general, we will choose the course of action that we judge as being most in the interest of fostering a safe and friendly community.
+
+## Contact Info
+Please contact Dan Wilson @DanWilson if you need to report a problem or address a grievance related to an abuse report.
+
+You are also encouraged to contact us if you are curious about something that might be "on the line" between appropriate and inappropriate content. We are happy to provide guidance to help you be a successful part of our community.
+
+## Credit and License
+
+This Code of Conduct borrows heavily from the WADE Code of Conduct, which is derived from the NodeBots Code of Conduct, which in turn borrows from the npm Code of Conduct, which was derived from the Stumptown Syndicate Citizen's Code of Conduct, and the Rust Project Code of Conduct.
+
+This document may be reused under a Creative Commons Attribution-ShareAlike License.
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/app/LICENSE b/e2e/modal-navigation-ng/app/LICENSE
new file mode 100755
index 000000000..ced13b45c
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/LICENSE
@@ -0,0 +1,201 @@
+Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright (c) 2015-2018 Telerik AD
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/app/README.md b/e2e/modal-navigation-ng/app/README.md
new file mode 100644
index 000000000..132ede3b0
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/README.md
@@ -0,0 +1,25 @@
+# NativeScript Angular Template
+
+This template creates a "Hello, world" NativeScript app using TypeScript and Angular.
+
+You can create a new app that uses this template with either the `--template` option.
+
+```
+tns create my-app-name --template tns-template-hello-world-ng
+```
+
+Or the `--ng` shorthand.
+
+```
+tns create my-app-name --ng
+```
+
+> Note: Both commands will create a new NativeScript app that uses the latest version of this template published to [npm] (https://www.npmjs.com/package/tns-template-hello-world-ng).
+
+If you want to create a new app that uses the source of the template from the `master` branch, you can execute the following:
+
+```
+tns create my-app-name --template https://github.com/NativeScript/template-hello-world-ng.git#master
+```
+
+**NB:** Please, have in mind that the master branch may refer to dependencies that are not on NPM yet!
diff --git a/e2e/modal-navigation-ng/app/app.android.css b/e2e/modal-navigation-ng/app/app.android.css
new file mode 100644
index 000000000..529b4cbd8
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/app.android.css
@@ -0,0 +1,12 @@
+Button{
+ font-size: 8;
+ margin: 0px;
+ padding: 0px;
+ height: 40;
+}
+
+TextView {
+ font-size: 10;
+ margin: 0px;
+ padding: 0px;
+}
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/app/app.component.html b/e2e/modal-navigation-ng/app/app.component.html
new file mode 100644
index 000000000..8a2c1a75e
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/app.component.html
@@ -0,0 +1,2 @@
+
+
diff --git a/e2e/modal-navigation-ng/app/app.component.ts b/e2e/modal-navigation-ng/app/app.component.ts
new file mode 100644
index 000000000..c5617460f
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/app.component.ts
@@ -0,0 +1,50 @@
+import { Component, ViewContainerRef } from "@angular/core";
+import { Router, NavigationEnd } from "@angular/router";
+import { NSLocationStrategy } from "@nativescript/angular";
+
+import { ViewContainerRefService } from "./shared/ViewContainerRefService";
+
+import { AppModule } from "./app.module";
+import { ModalDialogOptions, ModalDialogService } from "@nativescript/angular";
+import { ModalViewComponent } from "./modal-shared/modal-view.component";
+
+@Component({
+ selector: "ns-app",
+ templateUrl: "app.component.html"
+})
+export class AppComponent {
+ constructor(
+ router: Router,
+ location: NSLocationStrategy,
+ private modal: ModalDialogService,
+ private _vcRef: ViewContainerRef,
+ private _viewContainerRefService: ViewContainerRefService) {
+ router.events.subscribe(e => {
+ if (e instanceof NavigationEnd) {
+ console.log("[ROUTER]: " + e.toString());
+ console.log(location.toString());
+ }
+ });
+
+ this._viewContainerRefService.root = this._vcRef;
+ }
+
+ ngOnInit() {
+ if (AppModule.root === "page-router-modal") {
+ this.onRootModalTap();
+ console.log("Page modal page from frame root");
+ }
+ }
+
+ onRootModalTap(): void {
+ const options: ModalDialogOptions = {
+ viewContainerRef: this._viewContainerRefService.root,
+ context: {},
+ fullscreen: true
+ };
+
+ this.modal.showModal(ModalViewComponent, options).then((result: string) => {
+ console.log(result);
+ });
+ }
+}
diff --git a/e2e/modal-navigation-ng/app/app.css b/e2e/modal-navigation-ng/app/app.css
new file mode 100644
index 000000000..d132e79b9
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/app.css
@@ -0,0 +1,14 @@
+/*
+In NativeScript, the app.css file is where you place CSS rules that
+you would like to apply to your entire application. Check out
+http://docs.nativescript.org/ui/styling for a full list of the CSS
+selectors and properties you can use to style UI components.
+
+/*
+In many cases you may want to use the NativeScript core theme instead
+of writing your own CSS rules. For a full list of class names in the theme
+refer to http://docs.nativescript.org/ui/theme.
+*/
+Button {
+ font-size: 10px;
+}
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/app/app.ios.css b/e2e/modal-navigation-ng/app/app.ios.css
new file mode 100644
index 000000000..16f77c1ba
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/app.ios.css
@@ -0,0 +1,12 @@
+#home-page {
+ font-size: 10;
+ margin: 0px;
+ padding: 0px;
+}
+#home-page Button {
+ margin-bottom: 20px;
+ padding: 20px;
+ border-color: gray;
+ border-width: 2px;
+ border-radius: 8px;
+}
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/app/app.module.ngfactory.d.ts b/e2e/modal-navigation-ng/app/app.module.ngfactory.d.ts
new file mode 100644
index 000000000..793157de3
--- /dev/null
+++ b/e2e/modal-navigation-ng/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/modal-navigation-ng/app/app.module.ts b/e2e/modal-navigation-ng/app/app.module.ts
new file mode 100644
index 000000000..78e2e08ea
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/app.module.ts
@@ -0,0 +1,97 @@
+import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
+import { NativeScriptModule } from "@nativescript/angular";
+import { AppRoutingModule } from "./app.routing";
+import { AppComponent } from "./app.component";
+import { NamedRouterComponent } from "./named-router.component";
+import { TabComponent } from "./tab.component";
+import { LayoutComponent } from "./layout.component";
+
+import { HomeComponent } from "./home/home.component";
+import { RootSectionComponent } from "./navigation/root.section.component";
+import { BasicsNavigationComponent } from "./navigation/basic.navigation.component";
+import { SecondComponent } from "./second/second.component";
+import { ModalSecondComponent } from "./modal-second/modal-second.component";
+import { ModalComponent } from "./modal/modal.component";
+import { NestedModalComponent } from "./modal-nested/modal-nested.component";
+import { ModalRouterComponent } from "./modal/modal-router/modal-router.component";
+import { ModalViewComponent } from "./modal-shared/modal-view.component";
+import { ModalViewContentComponent } from "./modal-shared/modal-view-content.component";
+import { ModalSharedSecondComponent } from "./modal-shared/modal-shared-second.component";
+import { ViewContainerRefService } from "./shared/ViewContainerRefService";
+
+import { enable as traceEnable, addCategories } from "@nativescript/core/trace";
+import { routerTraceCategory } from "@nativescript/angular/trace";
+import { NativeScriptPlatformRef } from "@nativescript/angular";
+
+addCategories(routerTraceCategory);
+traceEnable();
+
+@NgModule({
+ imports: [
+ NativeScriptModule,
+ AppRoutingModule
+ ],
+ entryComponents: [
+ AppComponent,
+ NamedRouterComponent,
+ TabComponent,
+ LayoutComponent,
+ ModalRouterComponent,
+ NestedModalComponent,
+ ModalComponent,
+ ModalViewComponent
+ ],
+ declarations: [
+ AppComponent,
+ NamedRouterComponent,
+ TabComponent,
+ LayoutComponent,
+ RootSectionComponent,
+ BasicsNavigationComponent,
+ HomeComponent,
+ SecondComponent,
+ ModalComponent,
+ NestedModalComponent,
+ ModalRouterComponent,
+ ModalSecondComponent,
+ ModalViewComponent,
+ ModalViewContentComponent,
+ ModalSharedSecondComponent
+ ],
+ providers: [
+ ViewContainerRefService
+ ],
+ schemas: [
+ NO_ERRORS_SCHEMA
+ ]
+})
+/*
+Pass your application module to the bootstrapModule function located in main.ts to start your app
+*/
+
+export class AppModule {
+ private static appRef: any;
+ public static platformRef: NativeScriptPlatformRef;
+ public static root: string = "page-router";
+
+ ngDoBootstrap(app) {
+ AppModule.appRef = app;
+ AppModule.bootstrapRootComponent();
+ }
+
+ static bootstrapRootComponent() {
+ const options = {
+ 'page-router': AppComponent,
+ 'page-router-modal': AppComponent,
+ 'named-page-router': NamedRouterComponent,
+ 'named-page-router-modal': NamedRouterComponent,
+ 'tab': TabComponent,
+ 'tab-modal': TabComponent,
+ 'layout': LayoutComponent,
+ 'layout-modal': LayoutComponent,
+ };
+
+ const component = options[AppModule.root];
+ AppModule.appRef.bootstrap(component);
+ }
+}
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/app/app.routing.ts b/e2e/modal-navigation-ng/app/app.routing.ts
new file mode 100644
index 000000000..f03ef2a1a
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/app.routing.ts
@@ -0,0 +1,144 @@
+import { NgModule } from "@angular/core";
+import { NativeScriptRouterModule } from "@nativescript/angular";
+import { Routes, Router } from "@angular/router";
+
+import { HomeComponent } from "./home/home.component";
+import { SecondComponent } from "./second/second.component";
+import { ModalSecondComponent } from "./modal-second/modal-second.component";
+import { ModalComponent } from "./modal/modal.component";
+import { NestedModalComponent } from "./modal-nested/modal-nested.component";
+import { ModalViewContentComponent } from "./modal-shared/modal-view-content.component";
+import { ModalSharedSecondComponent } from "./modal-shared/modal-shared-second.component";
+
+import { AppModule } from "./app.module";
+
+const routes: Routes = [
+ { path: "", redirectTo: "/home", pathMatch: "full" },
+ {
+ path: "home", component: HomeComponent, children: [
+ {
+ path: "modal", component: ModalComponent, children: [
+ { path: "nested-frame-modal", component: NestedModalComponent }]
+ },
+ { path: "modal-second", component: ModalSecondComponent }
+ ]
+ },
+ {
+ path: "second", component: SecondComponent, children: [
+ {
+ path: "modal", component: ModalComponent, children: [
+ { path: "nested-frame-modal", component: NestedModalComponent }]
+ },
+ { path: "modal-second", component: ModalSecondComponent }
+ ]
+ },
+ {
+ path: "modal-shared", component: ModalViewContentComponent, outlet: "modalOutlet"
+ },
+ {
+ path: "modal-shared-second-host", component: ModalSharedSecondComponent
+ }
+];
+
+const namedOutletRoutes: Routes = [
+ { path: "", redirectTo: "/(namedRouter:home)", pathMatch: "full" },
+ {
+ path: "home", component: HomeComponent, outlet: "namedRouter", children: [
+ {
+ path: "modal", component: ModalComponent, children: [
+ { path: "nested-frame-modal", component: NestedModalComponent }]
+ },
+ { path: "modal-second", component: ModalSecondComponent }
+ ]
+ },
+ {
+ path: "second", outlet: "namedRouter", component: SecondComponent, children: [
+ {
+ path: "modal", component: ModalComponent, children: [
+ { path: "nested-frame-modal", component: NestedModalComponent }]
+ },
+ { path: "modal-second", component: ModalSecondComponent }
+ ]
+ },
+ {
+ path: "modal-shared", component: ModalViewContentComponent, outlet: "modalOutlet"
+ },
+ {
+ path: "modal-shared-second-host", outlet: "namedRouter", component: ModalSharedSecondComponent
+ }
+];
+
+const routesTab: Routes = [
+ { path: "", redirectTo: "/home(secondOutlet:second)", pathMatch: "full" },
+ {
+ path: "home", component: HomeComponent, children: [
+ {
+ path: "modal", component: ModalComponent, children: [
+ { path: "nested-frame-modal", component: NestedModalComponent }]
+ },
+ {
+ path: "modal-second", component: ModalSecondComponent
+ }
+ ]
+ },
+ {
+ path: "second", component: SecondComponent, children: [
+ {
+ path: "modal", component: ModalComponent, children: [
+ { path: "nested-frame-modal", component: NestedModalComponent }]
+ },
+ {
+ path: "modal-second", component: ModalSecondComponent
+ }
+ ]
+ },
+ {
+ path: "second", outlet: "secondOutlet", component: SecondComponent, children: [
+ {
+ path: "modal", component: ModalComponent, children: [
+ { path: "nested-frame-modal", component: NestedModalComponent }]
+ },
+ {
+ path: "modal-second", component: ModalSecondComponent
+ }
+ ]
+ },
+ {
+ path: "modal-shared", component: ModalViewContentComponent, outlet: "modalOutlet"
+ },
+ {
+ path: "modal-shared-second-host", component: ModalSharedSecondComponent
+ }
+];
+
+const routesLayout: Routes = [
+ {
+ path: "modal", component: ModalComponent, children: [
+ { path: "nested-frame-modal", component: NestedModalComponent }]
+ },
+ { path: "modal-second", component: ModalSecondComponent },
+ {
+ path: "modal-shared", component: ModalViewContentComponent, outlet: "modalOutlet"
+ },
+ {
+ path: "modal-shared-second-host", component: ModalSharedSecondComponent
+ }
+]
+
+@NgModule({
+ imports: [NativeScriptRouterModule.forRoot(routes)],
+ exports: [NativeScriptRouterModule]
+})
+export class AppRoutingModule {
+ constructor(private router: Router) {
+ if (AppModule.root === "page-router" || AppModule.root === "page-router-modal") {
+ this.router.resetConfig(routes);
+ } else if (AppModule.root === "layout" || AppModule.root === "layout-modal") {
+ this.router.resetConfig(routesLayout);
+ } else if (AppModule.root === "named-page-router" || AppModule.root === "named-page-router-modal") {
+ this.router.resetConfig(namedOutletRoutes);
+ } else if(AppModule.root === "tab" || AppModule.root === "tab-modal"){
+ this.router.resetConfig(routesTab);
+ }
+ }
+}
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/app/home/home.component.html b/e2e/modal-navigation-ng/app/home/home.component.html
new file mode 100644
index 000000000..89de6c594
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/home/home.component.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/app/home/home.component.ts b/e2e/modal-navigation-ng/app/home/home.component.ts
new file mode 100644
index 000000000..49c26d63d
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/home/home.component.ts
@@ -0,0 +1,34 @@
+import { Component, ViewContainerRef } from "@angular/core";
+import { ModalDialogService, ModalDialogOptions, RouterExtensions } from "@nativescript/angular";
+import { EventData } from "@nativescript/core";
+
+import { ViewContainerRefService } from "../shared/ViewContainerRefService";
+import { ModalRouterComponent } from "../modal/modal-router/modal-router.component";
+import { ModalComponent } from "../modal/modal.component";
+import { ModalViewComponent } from "../modal-shared/modal-view.component";
+import { confirm } from "@nativescript/core/ui/dialogs";
+
+import { AppModule } from "../app.module";
+
+@Component({
+ moduleId: module.id,
+ selector: "home-page",
+ templateUrl: "./home.component.html"
+})
+export class HomeComponent {
+ constructor(
+ private modal: ModalDialogService,
+ private vcRef: ViewContainerRef,
+ private viewContainerRefService: ViewContainerRefService,
+ private routerExtension: RouterExtensions) {
+
+ }
+
+ onNavigateSecond() {
+ this.routerExtension.navigate(["second"]);
+ }
+
+ onNavigateSecondWithOutlet() {
+ this.routerExtension.navigate([ { outlets: { namedRouter:["second"] } }]);
+ }
+}
diff --git a/e2e/modal-navigation-ng/app/layout.component.html b/e2e/modal-navigation-ng/app/layout.component.html
new file mode 100644
index 000000000..c512f39e2
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/layout.component.html
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/app/layout.component.ts b/e2e/modal-navigation-ng/app/layout.component.ts
new file mode 100644
index 000000000..e31c18856
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/layout.component.ts
@@ -0,0 +1,49 @@
+import { Component, ViewContainerRef } from "@angular/core";
+import { Router, NavigationEnd } from "@angular/router";
+import { NSLocationStrategy } from "@nativescript/angular";
+import { ModalDialogService, ModalDialogOptions } from "@nativescript/angular";
+import { ModalViewComponent } from "./modal-shared/modal-view.component";
+import { ViewContainerRefService } from "./shared/ViewContainerRefService";
+import { AppModule } from "./app.module";
+
+@Component({
+ selector: "ns-layout",
+ templateUrl: "layout.component.html",
+})
+
+export class LayoutComponent {
+ constructor(
+ private modal: ModalDialogService,
+ private router: Router,
+ private location: NSLocationStrategy,
+ private vcRef: ViewContainerRef,
+ private viewContainerRefService: ViewContainerRefService) {
+ this.router.events.subscribe(e => {
+ if (e instanceof NavigationEnd) {
+ console.log("[ROUTER]: " + e.toString());
+ console.log(this.location.toString());
+ }
+ });
+
+ this.viewContainerRefService.root = this.vcRef;
+ }
+
+ ngOnInit() {
+ if (AppModule.root === "layout-modal") {
+ console.log("Show modal page from tab root view!");
+ this.onRootModalTap();
+ }
+ }
+
+ onRootModalTap(): void {
+ const options: ModalDialogOptions = {
+ viewContainerRef: this.viewContainerRefService.root,
+ context: {},
+ fullscreen: true
+ };
+
+ this.modal.showModal(ModalViewComponent, options).then((result: string) => {
+ console.log(result);
+ });
+ }
+}
diff --git a/e2e/modal-navigation-ng/app/main.ts b/e2e/modal-navigation-ng/app/main.ts
new file mode 100644
index 000000000..36e8adda7
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/main.ts
@@ -0,0 +1,8 @@
+// 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";
+import { NativeScriptPlatformRef } from "@nativescript/angular";
+
+AppModule.platformRef = platformNativeScriptDynamic();
+AppModule.platformRef.bootstrapModule(AppModule);
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/app/modal-nested/modal-nested.component.html b/e2e/modal-navigation-ng/app/modal-nested/modal-nested.component.html
new file mode 100644
index 000000000..3ffe16a18
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/modal-nested/modal-nested.component.html
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/app/modal-nested/modal-nested.component.ts b/e2e/modal-navigation-ng/app/modal-nested/modal-nested.component.ts
new file mode 100644
index 000000000..6fb247ad6
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/modal-nested/modal-nested.component.ts
@@ -0,0 +1,26 @@
+import { Component } from "@angular/core";
+import { View, ShownModallyData } from "@nativescript/core/ui/core/view"
+import { ModalDialogParams } from "@nativescript/angular";
+
+@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/modal-navigation-ng/app/modal-second/modal-second.component.html b/e2e/modal-navigation-ng/app/modal-second/modal-second.component.html
new file mode 100644
index 000000000..f1086298f
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/modal-second/modal-second.component.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 000000000..0a6d41587
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/modal-second/modal-second.component.ts
@@ -0,0 +1,25 @@
+import { Component } from "@angular/core";
+import { View } from "@nativescript/core/ui/core/view"
+import { ActivatedRoute } from "@angular/router";
+import { RouterExtensions } from "@nativescript/angular";
+
+@Component({
+ moduleId: module.id,
+ selector: "modal-second-page",
+ templateUrl: "./modal-second.component.html"
+})
+export class ModalSecondComponent {
+ constructor(private routerExtension: RouterExtensions, private activeRoute: ActivatedRoute) { }
+
+ onLoaded(args) {
+ console.log("modal-second loaded");
+ }
+
+ goBack() {
+ this.routerExtension.back({ relativeTo: this.activeRoute });
+ }
+
+ close(layoutRoot: View) {
+ layoutRoot.closeModal();
+ }
+}
diff --git a/e2e/modal-navigation-ng/app/modal-shared/modal-shared-second.component.ts b/e2e/modal-navigation-ng/app/modal-shared/modal-shared-second.component.ts
new file mode 100644
index 000000000..21aebefe3
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/modal-shared/modal-shared-second.component.ts
@@ -0,0 +1,44 @@
+import { Component } from "@angular/core";
+import { ModalDialogOptions, ModalDialogService } from "@nativescript/angular";
+
+import { ViewContainerRefService } from "../shared/ViewContainerRefService";
+import { ModalViewComponent } from "../modal-shared/modal-view.component";
+import { RouterExtensions } from "@nativescript/angular";
+
+@Component({
+ selector: "ns-second",
+ moduleId: module.id,
+ template: `
+
+
+
+
+
+ `
+})
+export class ModalSharedSecondComponent {
+ constructor(
+ private _modalService: ModalDialogService,
+ private _viewContainerRefService: ViewContainerRefService,
+ private _routerExtensions: RouterExtensions
+ ) { }
+
+ onRootModalTap(): void {
+ const options: ModalDialogOptions = {
+ context: {},
+ fullscreen: true,
+ viewContainerRef: this._viewContainerRefService.root,
+ };
+
+ this._modalService.showModal(ModalViewComponent, options)
+ .then((result: string) => {
+ console.log(result);
+ });
+ }
+
+ onBackTap() {
+ if (this._routerExtensions.canGoBack()) {
+ this._routerExtensions.back();
+ }
+ }
+}
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/app/modal-shared/modal-view-content.component.ts b/e2e/modal-navigation-ng/app/modal-shared/modal-view-content.component.ts
new file mode 100644
index 000000000..a3f2a3c8e
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/modal-shared/modal-view-content.component.ts
@@ -0,0 +1,27 @@
+import { Component } from "@angular/core";
+import { ModalDialogParams } from "@nativescript/angular";
+
+@Component({
+ selector: "ModalViewContent",
+ moduleId: module.id,
+ template: `
+
+
+
+
+
+
+ `,
+ styles: [`
+ .action-bar, .page {
+ background-color: chocolate;
+ }
+ `]
+})
+export class ModalViewContentComponent {
+ constructor(private _params: ModalDialogParams) { }
+
+ onTap(): void {
+ this._params.closeCallback("return value");
+ }
+}
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/app/modal-shared/modal-view.component.ts b/e2e/modal-navigation-ng/app/modal-shared/modal-view.component.ts
new file mode 100644
index 000000000..2e5e2a9fc
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/modal-shared/modal-view.component.ts
@@ -0,0 +1,17 @@
+import { Component, OnInit } from "@angular/core";
+import { RouterExtensions } from "@nativescript/angular";
+
+@Component({
+ selector: "ModalView",
+ moduleId: module.id,
+ template:`
+
+ `
+})
+export class ModalViewComponent implements OnInit {
+ constructor(private _routerExtensions: RouterExtensions) {}
+
+ ngOnInit(): void {
+ this._routerExtensions.navigate([{ outlets: { modalOutlet: ["modal-shared"]}}]);
+ }
+}
diff --git a/e2e/modal-navigation-ng/app/modal/modal-router/modal-router.component.html b/e2e/modal-navigation-ng/app/modal/modal-router/modal-router.component.html
new file mode 100644
index 000000000..1265aa9c8
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/modal/modal-router/modal-router.component.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/app/modal/modal-router/modal-router.component.ts b/e2e/modal-navigation-ng/app/modal/modal-router/modal-router.component.ts
new file mode 100644
index 000000000..87eea3b27
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/modal/modal-router/modal-router.component.ts
@@ -0,0 +1,22 @@
+import { Component, OnInit } from "@angular/core";
+import { ActivatedRoute } from "@angular/router";
+import { RouterExtensions } from "@nativescript/angular";
+import { ModalDialogParams } from "@nativescript/angular";
+
+@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 });
+ }
+}
diff --git a/e2e/modal-navigation-ng/app/modal/modal.component.html b/e2e/modal-navigation-ng/app/modal/modal.component.html
new file mode 100644
index 000000000..d49c5720f
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/modal/modal.component.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/app/modal/modal.component.ts b/e2e/modal-navigation-ng/app/modal/modal.component.ts
new file mode 100644
index 000000000..6c4ee4649
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/modal/modal.component.ts
@@ -0,0 +1,86 @@
+import { Component, ViewContainerRef } from "@angular/core";
+import { ModalDialogParams, ModalDialogOptions, ModalDialogService } from "@nativescript/angular";
+import { RouterExtensions, PageRoute } from "@nativescript/angular";
+import { ActivatedRoute } from "@angular/router";
+import { View, ShownModallyData, EventData } from "@nativescript/core/ui/core/view"
+import { confirm } from "@nativescript/core/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/modal-navigation-ng/app/named-router.component.html b/e2e/modal-navigation-ng/app/named-router.component.html
new file mode 100644
index 000000000..83ba115c4
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/named-router.component.html
@@ -0,0 +1 @@
+
diff --git a/e2e/modal-navigation-ng/app/named-router.component.ts b/e2e/modal-navigation-ng/app/named-router.component.ts
new file mode 100644
index 000000000..69fbfc851
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/named-router.component.ts
@@ -0,0 +1,50 @@
+import { Component, ViewContainerRef } from "@angular/core";
+import { Router, NavigationEnd } from "@angular/router";
+import { NSLocationStrategy } from "@nativescript/angular";
+
+import { ViewContainerRefService } from "./shared/ViewContainerRefService";
+import { AppModule } from "./app.module";
+import { ModalDialogOptions, ModalDialogService } from "@nativescript/angular";
+import { ModalViewComponent } from "./modal-shared/modal-view.component";
+
+@Component({
+ selector: "named-router",
+ templateUrl: "named-router.component.html"
+})
+export class NamedRouterComponent {
+ constructor(
+ router: Router,
+ location: NSLocationStrategy,
+ private modal: ModalDialogService,
+ private _vcRef: ViewContainerRef,
+ private _viewContainerRefService: ViewContainerRefService
+ ) {
+ router.events.subscribe(e => {
+ if (e instanceof NavigationEnd) {
+ console.log("[ROUTER]: " + e.toString());
+ console.log(location.toString());
+ }
+ });
+
+ this._viewContainerRefService.root = this._vcRef;
+ }
+
+ ngOnInit() {
+ if (AppModule.root === "named-page-router-modal") {
+ console.log("Show modal page from tab root view!");
+ this.onRootModalTap();
+ }
+ }
+
+ onRootModalTap(): void {
+ const options: ModalDialogOptions = {
+ viewContainerRef: this._viewContainerRefService.root,
+ context: {},
+ fullscreen: true
+ };
+
+ this.modal.showModal(ModalViewComponent, options).then((result: string) => {
+ console.log(result);
+ });
+ }
+}
diff --git a/e2e/modal-navigation-ng/app/navigation/basic.navigation.component.ts b/e2e/modal-navigation-ng/app/navigation/basic.navigation.component.ts
new file mode 100644
index 000000000..c203da370
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/navigation/basic.navigation.component.ts
@@ -0,0 +1,105 @@
+import { Component, ViewContainerRef, Input, ViewChild, ElementRef } from "@angular/core";
+import { Router, NavigationEnd } from "@angular/router";
+import { ModalDialogService, ModalDialogOptions } from "@nativescript/angular";
+import { ModalComponent } from "../modal/modal.component";
+import { ModalRouterComponent } from "../modal/modal-router/modal-router.component";
+import { confirm } from "@nativescript/core/ui/dialogs";
+
+import { ViewContainerRefService } from "../shared/ViewContainerRefService";
+import { ModalViewComponent } from "~/modal-shared/modal-view.component";
+
+declare var UIModalPresentationStyle;
+
+@Component({
+ selector: "basic-nav",
+ template: `
+
+
+
+
+
+
+
+`
+})
+
+export class BasicsNavigationComponent {
+
+ @ViewChild("popoverButtonComp", { static: false }) popoverButtonComp: ElementRef;
+ @Input() col: number;
+ constructor(
+ private modal: ModalDialogService,
+ private router: Router,
+ private vcf: ViewContainerRef,
+ private viewContainerRefService: ViewContainerRefService) {
+ }
+
+ onModalNoFrame() {
+ const options: ModalDialogOptions = {
+ context: {
+ navigationVisibility: false
+ },
+ fullscreen: true,
+ viewContainerRef: this.vcf
+ };
+
+ this.modal.showModal(ModalComponent, options).then((res: string) => {
+ console.log("modal-no-frame closed");
+ });
+ }
+
+ onModalFrame() {
+ const options: ModalDialogOptions = {
+ context: {
+ navigationVisibility: true,
+ modalRoute: "modal"
+ },
+ fullscreen: true,
+ viewContainerRef: this.vcf
+ };
+
+ this.modal.showModal(ModalRouterComponent, options).then((res: string) => {
+ console.log("modal-frame closed");
+ });
+ }
+
+ onShowDialog() {
+ let options = {
+ title: "Dialog",
+ message: "Message",
+ okButtonText: "Yes",
+ cancelButtonText: "No"
+ }
+
+ confirm(options).then((result: boolean) => {
+ console.log(result);
+ })
+ }
+
+ onRootModalTap(): void {
+ const options: ModalDialogOptions = {
+ viewContainerRef: this.viewContainerRefService.root,
+ context: {},
+ fullscreen: true
+ };
+
+ this.modal.showModal(ModalViewComponent, options)
+ .then((result: string) => {
+ console.log(result);
+ });
+ }
+
+ onPopoverModal() {
+ const options: ModalDialogOptions = {
+ viewContainerRef: this.viewContainerRefService.root,
+ context: {},
+ ios: {
+ presentationStyle: UIModalPresentationStyle.Popover
+ },
+ target: this.popoverButtonComp.nativeElement
+ };
+
+ this.modal.showModal(ModalViewComponent, options)
+ .then((result: string) => { console.log(result);});
+ }
+}
diff --git a/e2e/modal-navigation-ng/app/navigation/root.section.component.html b/e2e/modal-navigation-ng/app/navigation/root.section.component.html
new file mode 100644
index 000000000..2a5bf9b84
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/navigation/root.section.component.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/app/navigation/root.section.component.ts b/e2e/modal-navigation-ng/app/navigation/root.section.component.ts
new file mode 100644
index 000000000..cc8dab627
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/navigation/root.section.component.ts
@@ -0,0 +1,31 @@
+import { Component } from "@angular/core";
+import { AppModule } from "../app.module";
+
+@Component({
+ moduleId: module.id,
+ selector: "root-section",
+ templateUrl: "./root.section.component.html"
+})
+export class RootSectionComponent {
+ constructor() { }
+
+ onFrameRootViewReset(showModal?: boolean) {
+ AppModule.root = showModal ? "page-router-modal" : "page-router";
+ AppModule.platformRef._livesync();
+ }
+
+ onNamedFrameRootViewReset(showModal?: boolean) {
+ AppModule.root = showModal ? "named-page-router-modal" : "named-page-router";
+ AppModule.platformRef._livesync();
+ }
+
+ onTabRootViewReset(showModal?: boolean) {
+ AppModule.root = showModal ? "tab-modal" : "tab";
+ AppModule.platformRef._livesync();
+ }
+
+ onLayoutRootViewReset(showModal?: boolean) {
+ AppModule.root = showModal ? "layout-modal" : "layout";
+ AppModule.platformRef._livesync();
+ }
+}
diff --git a/e2e/modal-navigation-ng/app/package.json b/e2e/modal-navigation-ng/app/package.json
new file mode 100644
index 000000000..e6521c70c
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/package.json
@@ -0,0 +1,9 @@
+{
+ "android": {
+ "v8Flags": "--expose_gc",
+ "markingMode": "none"
+ },
+ "main": "main.js",
+ "name": "tns-template-hello-world-ng",
+ "version": "3.4.3"
+}
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/app/second/second.component.html b/e2e/modal-navigation-ng/app/second/second.component.html
new file mode 100644
index 000000000..069453908
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/second/second.component.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/app/second/second.component.ts b/e2e/modal-navigation-ng/app/second/second.component.ts
new file mode 100644
index 000000000..290441c61
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/second/second.component.ts
@@ -0,0 +1,50 @@
+import { Component, ViewContainerRef } from "@angular/core";
+import { ModalDialogService, ModalDialogOptions } from "@nativescript/angular";
+import { EventData } from "@nativescript/core";
+import { Frame } from "@nativescript/core";
+import { View } from "@nativescript/core";
+import { ModalRouterComponent } from "../modal/modal-router/modal-router.component";
+import { RouterExtensions } from "@nativescript/angular";
+import { ModalComponent } from "../modal/modal.component";
+import { AppModule } from "../app.module";
+@Component({
+ moduleId: module.id,
+ selector: "second-page",
+ templateUrl: "./second.component.html"
+})
+export class SecondComponent {
+ constructor(private modal: ModalDialogService, private vcRef: ViewContainerRef, private routerExtension: RouterExtensions) { }
+
+ onModalNoFrame() {
+ const options: ModalDialogOptions = {
+ context: {
+ navigationVisibility: false
+ },
+ fullscreen: true,
+ viewContainerRef: this.vcRef
+ };
+
+ this.modal.showModal(ModalComponent, options).then((res: string) => {
+ console.log("moda-no-frame closed");
+ });
+ }
+
+ 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");
+ });
+ }
+
+ goBack() {
+ this.routerExtension.back();
+ }
+}
diff --git a/e2e/modal-navigation-ng/app/shared/ViewContainerRefService.ts b/e2e/modal-navigation-ng/app/shared/ViewContainerRefService.ts
new file mode 100644
index 000000000..e74a57a15
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/shared/ViewContainerRefService.ts
@@ -0,0 +1,14 @@
+import { Injectable, ViewContainerRef } from "@angular/core";
+
+@Injectable()
+export class ViewContainerRefService {
+ private _rootViewContainerRef: ViewContainerRef;
+
+ get root():ViewContainerRef {
+ return this._rootViewContainerRef;
+ }
+
+ set root(viewContainerRef: ViewContainerRef) {
+ this._rootViewContainerRef = viewContainerRef;
+ }
+}
diff --git a/e2e/modal-navigation-ng/app/tab.component.html b/e2e/modal-navigation-ng/app/tab.component.html
new file mode 100644
index 000000000..d84edd2c6
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/tab.component.html
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/e2e/modal-navigation-ng/app/tab.component.ts b/e2e/modal-navigation-ng/app/tab.component.ts
new file mode 100644
index 000000000..b0afb94e3
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/tab.component.ts
@@ -0,0 +1,49 @@
+import { Component, ViewContainerRef } from "@angular/core";
+import { Router, NavigationEnd } from "@angular/router";
+import { NSLocationStrategy } from "@nativescript/angular";
+
+import { ViewContainerRefService } from "./shared/ViewContainerRefService";
+import { AppModule } from "./app.module";
+import { ModalDialogOptions, ModalDialogService } from "@nativescript/angular";
+import { ModalViewComponent } from "./modal-shared/modal-view.component";
+
+@Component({
+ selector: "ns-tab",
+ templateUrl: "tab.component.html",
+})
+export class TabComponent {
+ constructor(
+ router: Router,
+ location: NSLocationStrategy,
+ private _vcRef: ViewContainerRef,
+ private _viewContainerRefService: ViewContainerRefService,
+ private modal: ModalDialogService) {
+ router.events.subscribe(e => {
+ if (e instanceof NavigationEnd) {
+ console.log("[ROUTER]: " + e.toString());
+ console.log(location.toString());
+ }
+ });
+
+ this._viewContainerRefService.root = this._vcRef;
+ }
+
+ ngOnInit() {
+ if (AppModule.root === "tab-modal") {
+ console.log("Show modal page from tab root view!");
+ this.onRootModalTap();
+ }
+ }
+
+ onRootModalTap(): void {
+ const options: ModalDialogOptions = {
+ viewContainerRef: this._viewContainerRefService.root,
+ context: {},
+ fullscreen: true
+ };
+
+ this.modal.showModal(ModalViewComponent, options).then((result: string) => {
+ console.log(result);
+ });
+ }
+}
diff --git a/e2e/modal-navigation-ng/e2e/modal-frame.e2e-spec.ts b/e2e/modal-navigation-ng/e2e/modal-frame.e2e-spec.ts
new file mode 100644
index 000000000..4a1591617
--- /dev/null
+++ b/e2e/modal-navigation-ng/e2e/modal-frame.e2e-spec.ts
@@ -0,0 +1,100 @@
+import { AppiumDriver, createDriver, nsCapabilities } from "nativescript-dev-appium";
+import { Screen } from "./screens/screen";
+import {
+ roots,
+ modalFrameBackground,
+ testSecondPageBackground,
+ testSecondPageClose,
+ testNestedModalFrameBackground,
+ testNestedModalPageBackground,
+ testDialogBackground
+} from "./screens/shared-screen";
+import { isSauceLab } from "nativescript-dev-appium/lib/parser";
+import { ImageOptions } from "nativescript-dev-appium/lib/image-options";
+
+const QUEUE_WAIT_TIME: number = 600000; // Sometimes SauceLabs threads are not available and the tests wait in a queue to start. Wait 10 min before timeout.
+const isSauceRun = isSauceLab;
+
+describe("modal-frame:", async function () {
+
+ let driver: AppiumDriver;
+ let screen: Screen;
+
+ before(async function () {
+ this.timeout(QUEUE_WAIT_TIME);
+ nsCapabilities.testReporter.context = this;
+ driver = await createDriver();
+ driver.imageHelper.defaultTolerance = 50;
+ driver.imageHelper.defaultToleranceType = ImageOptions.pixel;
+ screen = new Screen(driver);
+ });
+
+ after(async function () {
+ if (isSauceRun) {
+ driver.sessionId().then(function (sessionId) {
+ console.log("Report https://saucelabs.com/beta/tests/" + sessionId);
+ });
+ }
+ await driver.quit();
+ console.log("Quit driver!");
+ });
+
+ for (let index = 0; index < roots.length; index++) {
+ const root = roots[index];
+ describe(`${root} modal frame background scenarios:`, async function () {
+
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ await screen[root]();
+ });
+
+ afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logTestArtifacts(this.currentTest.title);
+ await driver.resetApp();
+ await screen[root]();
+ }
+ });
+
+ after(async function () {
+ await screen.closeModal();
+ await screen.loadedHome();
+ });
+
+ it("should show dialog confirm, run in background", async function () {
+ await screen.loadModalFrame(true);
+ await testDialogBackground(driver, screen);
+ });
+
+ it("should run modal page with frame in background", async function () {
+ await screen.loadModalFrame(false);
+ await modalFrameBackground(driver, screen);
+ });
+
+ it("should navigate to second page, run in background, go back", async function () {
+ await screen.loadModalFrame(false);
+ await testSecondPageBackground(driver, screen);
+ });
+
+ it("should show nested modal page with frame, run in background, close", async function () {
+ await screen.loadModalFrame(false);
+ await testNestedModalFrameBackground(driver, screen);
+ });
+
+ it("should show nested modal page, run in background, close", async function () {
+ await screen.loadModalFrame(false);
+ await testNestedModalPageBackground(driver, screen);
+ });
+
+ it("should navigate to second page, close", async function () {
+ await screen.loadModalFrame(false);
+ await testSecondPageClose(driver, screen);
+ });
+
+ it("should navigate to second page, run in background, go back", async function () {
+ await screen.loadModalFrame(true);
+ await testSecondPageBackground(driver, screen);
+ });
+ });
+ };
+});
diff --git a/e2e/modal-navigation-ng/e2e/modal-layout.e2e-spec.ts b/e2e/modal-navigation-ng/e2e/modal-layout.e2e-spec.ts
new file mode 100644
index 000000000..40ccb5538
--- /dev/null
+++ b/e2e/modal-navigation-ng/e2e/modal-layout.e2e-spec.ts
@@ -0,0 +1,70 @@
+import { AppiumDriver, createDriver, nsCapabilities } from "nativescript-dev-appium";
+import { Screen } from "./screens/screen"
+import {
+ roots,
+ testNestedModalPageBackground,
+ testDialogBackground,
+} from "./screens/shared-screen";
+import { isSauceLab } from "nativescript-dev-appium/lib/parser";
+import { ImageOptions } from "nativescript-dev-appium/lib/image-options";
+
+const QUEUE_WAIT_TIME: number = 600000; // Sometimes SauceLabs threads are not available and the tests wait in a queue to start. Wait 10 min before timeout.
+const isSauceRun = isSauceLab;
+
+describe("modal-layout:", async function () {
+ let driver: AppiumDriver;
+ let screen: Screen;
+
+ before(async function () {
+ this.timeout(QUEUE_WAIT_TIME);
+ nsCapabilities.testReporter.context = this;
+ driver = await createDriver();
+ driver.imageHelper.defaultTolerance = 50;
+ driver.imageHelper.defaultToleranceType = ImageOptions.pixel;
+ screen = new Screen(driver);
+ });
+
+ after(async function () {
+ if (isSauceRun) {
+ driver.sessionId().then(function (sessionId) {
+ console.log("Report https://saucelabs.com/beta/tests/" + sessionId);
+ });
+ }
+ await driver.quit();
+ console.log("Quit driver!");
+ });
+
+ for (let index = 0; index < roots.length; index++) {
+ const root = roots[index];
+ describe(`${root} modal no frame background scenarios:`, async function () {
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ await screen[root]();
+ });
+
+ afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logTestArtifacts(this.currentTest.title);
+ await driver.resetApp();
+ await screen[root]();
+ }
+ });
+
+ after(async function () {
+ await screen.closeModal();
+ await screen.loadedHome();
+ });
+
+ it("should show nested modal page, run in background, close", async function () {
+ await screen.loadModalNoFrame(true);
+ await testNestedModalPageBackground(driver, screen, false);
+
+ });
+
+ it("should show dialog confirm inside modal view with no frame, run in background", async function () {
+ await screen.loadModalNoFrame(false);
+ await testDialogBackground(driver, screen, false);
+ });
+ });
+ };
+});
diff --git a/e2e/modal-navigation-ng/e2e/modal-on-init.e2e-spec.ts b/e2e/modal-navigation-ng/e2e/modal-on-init.e2e-spec.ts
new file mode 100644
index 000000000..2f0af1251
--- /dev/null
+++ b/e2e/modal-navigation-ng/e2e/modal-on-init.e2e-spec.ts
@@ -0,0 +1,172 @@
+import { AppiumDriver, createDriver, nsCapabilities } from "nativescript-dev-appium";
+import { Screen, sharedModalView, homeComponent } from "./screens/screen";
+import {
+ assertComponent,
+ goBack,
+ navigateToSecondComponent
+} from "./screens/shared-screen";
+import { isSauceLab } from "nativescript-dev-appium/lib/parser";
+import { ImageOptions } from "nativescript-dev-appium/lib/image-options";
+
+const roots = [
+ "setTabRootViewModal",
+ "setFrameRootViewModal",
+ "setNamedFrameRootViewModal",
+];
+const QUEUE_WAIT_TIME: number = 600000; // Sometimes SauceLabs threads are not available and the tests wait in a queue to start. Wait 10 min before timeout.
+const isSauceRun = isSauceLab;
+
+describe("modal-on-init:", async function () {
+ let driver: AppiumDriver;
+ let screen: Screen;
+
+ before(async function () {
+ this.timeout(QUEUE_WAIT_TIME);
+ nsCapabilities.testReporter.context = this;
+
+ driver = await createDriver();
+ driver.imageHelper.defaultTolerance = 50;
+ driver.imageHelper.defaultToleranceType = ImageOptions.pixel;
+ screen = new Screen(driver);
+ });
+
+ after(async function () {
+ if (isSauceRun) {
+ driver.sessionId().then(function (sessionId) {
+ console.log("Report https://saucelabs.com/beta/tests/" + sessionId);
+ });
+ }
+ await driver.quit();
+ console.log("Quit driver!");
+ });
+
+ for (let index = 0; index < roots.length; index++) {
+ const root = roots[index];
+ describe("Shared Modal on Init", async function () {
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ await screen[root]();
+ console.log(`Root: ${root}`);
+ });
+
+ beforeEach(async function () { });
+
+ afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logTestArtifacts(this.currentTest.title);
+ await driver.resetApp();
+ await screen[root]();
+ }
+ });
+
+ after("root after all hook", async function () {
+ await driver.logTestArtifacts(`${root}_root_after_all_hook`);
+ });
+
+ it("should shared modal view", async function () {
+ await assertComponent(driver, sharedModalView);
+ });
+
+ it("run in background", async function () {
+ await driver.backgroundApp(1);
+ await assertComponent(driver, sharedModalView);
+ });
+
+ it("should close shared modal ", async function () {
+ await screen.closeModal();
+ await screen.loadedHome();
+ });
+
+ it("should open\\close shared modal", async function () {
+ await screen.loadSharedModal(true);
+ await screen.closeModal();
+ await screen.loadedHome();
+ });
+
+ it("should open\\close shared modal again", async function () {
+ await screen.loadSharedModal(true);
+ if (driver.isAndroid) {
+ await driver.navBack();
+ } else {
+ await screen.closeModal();
+ }
+ await screen.loadedHome();
+ });
+
+ it("should open\\close modal with frame", async function () {
+ await screen.loadModalFrame(true);
+ await screen.closeModal();
+ });
+
+ it("should open\\close shared modal again", async function () {
+ await screen.loadSharedModal(true);
+ await screen.closeModal();
+ });
+
+ it("run in background again", async function () {
+ await driver.backgroundApp(1);
+ await screen.loadedHome();
+ });
+
+ it("should open\\close shared modal second", async function () {
+ await screen.loadModalFrame(true);
+ await screen.closeModal();
+ });
+ });
+ };
+
+ describe("Shared Modal on Init", async function () {
+ const root = "setLayoutRootViewModal";
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ await screen[root]();
+ console.log(`Root: ${root}`);
+ });
+
+ afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logTestArtifacts(this.currentTest.title);
+ await driver.resetApp();
+ await screen[root]();
+ }
+ });
+
+ after("root after all hook", async function () {
+ await driver.logTestArtifacts(`${root}_root_after_all_hook`);
+ });
+
+ it("should shared modal view", async function () {
+ await assertComponent(driver, sharedModalView);
+ });
+
+ it("run in background", async function () {
+ await driver.backgroundApp(1);
+ await assertComponent(driver, sharedModalView);
+ });
+
+ it("should close shared modal ", async function () {
+ await screen.closeModal();
+ await screen.loadedHome();
+ });
+
+ it("should open\\close shared modal", async function () {
+ await screen.loadModalFrame(true);
+ await screen.closeModal();
+ });
+
+ it("run in background again", async function () {
+ await driver.backgroundApp(1);
+ await screen.loadedHome();
+ });
+
+ it("should open\\close shared modal second", async function () {
+ await screen.loadModalFrame(true);
+ await screen.closeModal();
+ });
+
+ it("should open\\close shared modal", async function () {
+ await screen.loadSharedModal(true);
+ await screen.closeModal();
+ });
+ });
+});
diff --git a/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts b/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts
new file mode 100644
index 000000000..f6613b088
--- /dev/null
+++ b/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts
@@ -0,0 +1,165 @@
+import { AppiumDriver, createDriver, nsCapabilities } from "nativescript-dev-appium";
+import { Screen } from "./screens/screen";
+import { assertComponent, goBack, navigateToSecondComponent } from "./screens/shared-screen";
+import { isSauceLab } from "nativescript-dev-appium/lib/parser";
+import { ImageOptions } from "nativescript-dev-appium/lib/image-options";
+
+const homeComponent = "Home Component";
+const roots = ["setFrameRootView", "setTabRootView"];
+const QUEUE_WAIT_TIME: number = 600000; // Sometimes SauceLabs threads are not available and the tests wait in a queue to start. Wait 10 min before timeout.
+const isSauceRun = isSauceLab;
+
+describe("modal-shared:", async function () {
+ let driver: AppiumDriver;
+ let screen: Screen;
+
+ before(async function () {
+ this.timeout(QUEUE_WAIT_TIME);
+ nsCapabilities.testReporter.context = this;
+ driver = await createDriver();
+ driver.imageHelper.defaultTolerance = 50;
+ driver.imageHelper.defaultToleranceType = ImageOptions.pixel;
+ screen = new Screen(driver);
+ });
+
+ after(async function () {
+ if (isSauceRun) {
+ driver.sessionId().then(function (sessionId) {
+ console.log("Report https://saucelabs.com/beta/tests/" + sessionId);
+ });
+ }
+ await driver.quit();
+ console.log("Quit driver!");
+ });
+
+ for (let index = 0; index < roots.length; index++) {
+ const root = roots[index];
+ describe("Shared modal from home component and back", async function () {
+
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ await screen[root]();
+ });
+
+ afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logTestArtifacts(this.currentTest.title);
+ await driver.resetApp();
+ await screen[root]();
+ }
+ });
+
+ it("should find home component", async function () {
+ await assertComponent(driver, homeComponent);
+ });
+
+ it("should open\\close shared modal from home component", async function () {
+ await screen.loadSharedModal(true);
+ await screen.closeModal();
+ });
+
+ it("should open\\close shared modal from home component again", async function () {
+ await screen.loadSharedModal(true);
+ await screen.closeModal();
+ });
+
+ it("should open\\close shared modal with presentation style from home component", async function () {
+ await screen.loadSharedModalWithPresentationStyle(true);
+ await screen.closeModal();
+ });
+
+ it("should find home component again", async function () {
+ await screen.loadedHome();
+ });
+
+ it("should navigate to second component", async function () {
+ await navigateToSecondComponent(driver);
+ });
+
+ it("should find second component", async function () {
+ await assertComponent(driver, "second component");
+ });
+
+ it("should open\\close shared modal from second component", async function () {
+ await screen.loadSharedModal(true);
+ await screen.closeModal();
+ });
+
+ it("should find second component again", async function () {
+ await assertComponent(driver, "second component");
+ });
+
+ it("should navigate back to home component", async function () {
+ await goBack(driver);
+ await assertComponent(driver, homeComponent);
+ });
+ });
+ };
+
+ describe("modal-shared-different-component:", async function () {
+
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ });
+
+ afterEach(async function () {
+ if (this.currentTest.state && this.currentTest.state === "failed") {
+ await driver.logTestArtifacts(this.currentTest.title);
+ }
+ });
+
+ for (let index = 0; index < roots.length; index++) {
+ describe("Shared modal from different components", async function () {
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ await driver.resetApp();
+ });
+
+ it("should find home component", async function () {
+ await screen.loadedHome();
+ });
+
+ it("should open\\close shared modal from home component", async function () {
+ await screen.loadSharedModal(true);
+ await screen.closeModal();
+ });
+
+ it("should find home component again", async function () {
+ await screen.loadedHome();
+ });
+
+ it("should navigate to second component", async function () {
+ await navigateToSecondComponent(driver);
+ });
+
+ it("should find second component", async function () {
+ await assertComponent(driver, "second component");
+ });
+
+ it("should open\\close shared modal from second component", async function () {
+ await screen.loadSharedModal(true);
+ await screen.closeModal();
+ });
+
+ it("should find second component again", async function () {
+ await assertComponent(driver, "second component");
+ });
+
+ it("should navigate back to home component", async function () {
+ await goBack(driver);
+ await screen.loadedHome();
+ });
+
+ it("should open\\close shared modal from home component after manipulations with second", async function () {
+ await screen.loadSharedModal(true);
+ await screen.closeModal();
+ });
+
+ it("should find home component again", async function () {
+ await screen.loadedHome();
+ });
+ });
+ };
+ });
+});
+
diff --git a/e2e/modal-navigation-ng/e2e/screens/screen.ts b/e2e/modal-navigation-ng/e2e/screens/screen.ts
new file mode 100644
index 000000000..690edd0ab
--- /dev/null
+++ b/e2e/modal-navigation-ng/e2e/screens/screen.ts
@@ -0,0 +1,327 @@
+import { AppiumDriver, SearchOptions } from "nativescript-dev-appium";
+import { assert } from "chai";
+
+const home = "Home Component"
+const first = "First"
+const modal = "Modal";
+const modalFirst = "Modal First";
+const dialogConfirm = "Dialog";
+const modalSecond = "Modal Second";
+const modalNested = "Modal Nested";
+
+const modalFrame = "Show Modal Page With Frame";
+const modalNoFrame = "Show Modal Without Frame";
+const modalLayout = "Show Modal Layout";
+const modalTabView = "Show Modal TabView";
+const navToSecondPage = "Navigate To Second Page";
+const showDialog = "Show Dialog";
+const resetFrameRootView = "Reset Frame Root View";
+const resetFrameRootViewModal = "Reset Frame Root View Modal";
+const resetNamedFrameRootView = "Reset Named Frame Root View";
+const resetNamedFrameRootViewModal = "Reset Named Frame Root View Modal";
+const resetTabRootView = "Reset Tab Root View";
+const resetTabRootViewModal = "Reset Tab Root View Modal";
+const resetLayoutRootView = "Reset Layout Root View";
+const resetLayoutRootViewModal = "Reset Layout Root View Modal";
+
+const showNestedModalFrame = "Show Nested Modal Page With Frame";
+const showNestedModalPage = "Show Nested Modal Page";
+
+const confirmDialog = "Yes";
+const confirmDialogMessage = "Message";
+const closeModalNested = "Close Modal Nested";
+const closeModal = "Close Modal";
+const goBack = "Go Back(activatedRoute)";
+export const sharedModalView = "SHARED MODAL VIEW";
+export const homeComponent = "Home Component";
+
+export class Screen {
+
+ private _driver: AppiumDriver
+
+ constructor(driver: AppiumDriver) {
+ this._driver = driver;
+ }
+
+ loadedHome = async () => {
+ const lblHome = await this._driver.waitForElement(home);
+ assert.isTrue(await lblHome.isDisplayed());
+ console.log(home + " loaded!");
+ }
+
+ resetFrameRootView = async () => {
+ console.log("Setting frame root ...");
+ const btnResetFrameRootView = await this._driver.findElementByAutomationText(resetFrameRootView);
+ await btnResetFrameRootView.tap();
+ }
+
+ resetNamedFrameRootView = async () => {
+ console.log("Setting named frame root ...");
+ const btnResetFrameRootView = await this._driver.findElementByAutomationText(resetNamedFrameRootView);
+ await btnResetFrameRootView.tap();
+ }
+
+ resetLayoutRootView = async () => {
+ console.log("Setting layout root ...");
+ const btnResetLayoutRootView = await this._driver.waitForElement(resetLayoutRootView);
+ await btnResetLayoutRootView.click();
+ }
+
+ resetTabRootView = async () => {
+ const btnResetTabRootView = await this._driver.findElementByAutomationText(resetTabRootView);
+ await btnResetTabRootView.tap();
+ }
+
+ resetTabRootViewModal = async () => {
+ const btnResetTabRootViewModal = await this._driver.findElementByAutomationText(resetTabRootViewModal);
+ await btnResetTabRootViewModal.click();
+ }
+
+ resetFrameRootViewModal = async () => {
+ const btnResetTabRootViewModal = await this._driver.findElementByAutomationText(resetFrameRootViewModal);
+ await btnResetTabRootViewModal.click();
+ }
+
+ resetNamedFrameRootViewModal = async () => {
+ const btnResetTabRootViewModal = await this._driver.findElementByAutomationText(resetNamedFrameRootViewModal);
+ await btnResetTabRootViewModal.click();
+ }
+
+ resetLayoutRootViewModal = async () => {
+ const btnResetTabRootViewModal = await this._driver.findElementByAutomationText(resetLayoutRootViewModal);
+ await btnResetTabRootViewModal.click();
+ }
+
+ loadedTabRootView = async () => {
+ const tabFirst = await this._driver.findElementByAutomationText(first);
+ assert.isTrue(await tabFirst.isDisplayed());
+ console.log("Tab root view loaded!");
+ }
+
+ setFrameRootView = async () => {
+ // should load frame root, no need to verify it is loaded
+ await this.loadedHome();
+ await this.resetFrameRootView();
+ }
+
+ setNamedFrameRootView = async () => {
+ // should load named frame root, no need to verify it is loaded
+ await this.loadedHome();
+ await this.resetNamedFrameRootView();
+ }
+
+ setTabRootView = async () => {
+ // should load tab root
+ await this.loadedHome();
+ await this.resetTabRootView();
+ await this.loadedTabRootView();
+ }
+
+ setLayoutRootView = async () => {
+ // should load layout root, no need to verify it is loaded
+ await this.loadedHome();
+ await this.resetLayoutRootView();
+ }
+
+ setTabRootViewModal = async () => {
+ await this.loadedHome();
+ await this.resetTabRootViewModal();
+ }
+
+ setFrameRootViewModal = async () => {
+ await this.loadedHome();
+ await this.resetFrameRootViewModal();
+ }
+
+ setNamedFrameRootViewModal = async () => {
+ await this.loadedHome();
+ await this.resetNamedFrameRootViewModal();
+ }
+
+ setLayoutRootViewModal = async () => {
+ await this.loadedHome();
+ await this.resetLayoutRootViewModal();
+ }
+
+ showModalFrame = async () => {
+ const btnModalFrame = await this._driver.findElementByAutomationText(modalFrame);
+ await btnModalFrame.tap();
+ }
+
+ loadedModalFrame = async () => {
+ const lblModal = await this._driver.waitForElement(modal, 5000);
+ assert.isTrue(await lblModal.isDisplayed(), `${modal} is not displayed!`);
+ console.log(modal + " loaded!");
+ }
+
+ showModalNoFrame = async () => {
+ const btnModalPage = await this._driver.findElementByAutomationText(modalNoFrame);
+ await btnModalPage.tap();
+ }
+
+
+ private showSharedModal = async () => {
+ const btnTap = await this._driver.waitForElement("Show Shared Modal");
+ await btnTap.click();
+ }
+
+ private showSharedModalPresentationStyle = async () => {
+ const btnTap = await this._driver.findElementByText("popover", SearchOptions.contains);
+ await btnTap.click();
+ }
+
+ loadedModalPage = async () => {
+ const btnShowNestedModalPage = await this._driver.findElementByAutomationText(showNestedModalPage);
+ assert.isTrue(await btnShowNestedModalPage.isDisplayed(), `${showNestedModalPage} is not displayed`);
+ console.log("Modal Page loaded!");
+ }
+
+ showModalLayout = async () => {
+ const btnModalLayout = await this._driver.findElementByAutomationText(modalLayout);
+ await btnModalLayout.tap();
+ }
+
+ loadedModalLayout = async () => {
+ await this.loadedModalFrame();
+ }
+
+ showModalTabView = async () => {
+ const btnModalTabView = await this._driver.findElementByAutomationText(modalTabView);
+ await btnModalTabView.tap();
+ }
+
+ loadedModalTabView = async () => {
+ const itemModalFirst = await this._driver.findElementByAutomationText(modalFirst);
+ assert.isTrue(await itemModalFirst.isDisplayed());
+ console.log("Modal TabView loaded!");
+ }
+
+ navigateToSecondPage = async () => {
+ const btnNavToSecondPage = await this._driver.findElementByAutomationText(navToSecondPage);
+ await btnNavToSecondPage.tap();
+ }
+
+ showDialogConfirm = async () => {
+ const btnShowDialogConfirm = await this._driver.findElementByAutomationText(showDialog);
+ await btnShowDialogConfirm.tap();
+ }
+
+ navigateToFirstItem = async () => {
+ const itemModalFirst = await this._driver.findElementByAutomationText(modalFirst);
+ await itemModalFirst.tap();
+ }
+
+ navigateToSecondItem = async () => {
+ const itemModalSecond = await this._driver.findElementByAutomationText(modalSecond);
+ await itemModalSecond.tap();
+ }
+
+ loadedModalNoFrame = async () => {
+ await this._driver.wait(2000);
+ const btnShowDialogConfirm = await this._driver.waitForElement(showDialog);
+ const btnCloseModal = await this._driver.waitForElement(closeModal);
+ assert.isTrue(await btnShowDialogConfirm.isDisplayed());
+ assert.isTrue(await btnCloseModal.isDisplayed());
+ console.log("Modal Without Frame shown!");
+ }
+
+ loadedConfirmDialog = async () => {
+ const lblDialogMessage = await this._driver.findElementByAutomationText(confirmDialogMessage);
+ assert.isTrue(await lblDialogMessage.isDisplayed());
+ console.log(dialogConfirm + " shown!");
+ }
+
+ loadedSecondPage = async () => {
+ const lblModalSecond = await this._driver.waitForElement(modalSecond, 5000);
+ assert.isTrue(await lblModalSecond.isDisplayed());
+ console.log(modalSecond + " loaded!");
+ }
+
+ loadedFirstItem = async () => {
+ const lblModal = await this._driver.findElementByAutomationText(modal);
+ assert.isTrue(await lblModal.isDisplayed());
+ console.log("First Item loaded!");
+ }
+
+ loadedSecondItem = async () => {
+ const btnGoBack = await this._driver.findElementByAutomationText(goBack);
+ assert.isTrue(await btnGoBack.isDisplayed());
+ console.log("Second Item loaded!");
+ }
+
+ closeDialog = async () => {
+ const btnYesDialog = await this._driver.findElementByAutomationText(confirmDialog);
+ await btnYesDialog.tap();
+ }
+
+ goBackFromSecondPage = async () => {
+ const btnGoBackFromSecondPage = await this._driver.findElementByAutomationText(goBack);
+ await btnGoBackFromSecondPage.tap();
+ }
+
+ showNestedModalFrame = async () => {
+ const btnShowNestedModalFrame = await this._driver.findElementByAutomationText(showNestedModalFrame);
+ await btnShowNestedModalFrame.tap();
+ }
+
+ loadedNestedModalFrame = async () => {
+ const lblModalNested = await this._driver.waitForElement(modalNested, 5000);
+ assert.isTrue(await lblModalNested.isDisplayed());
+ console.log(modalNested + " loaded!");
+ }
+
+ closeModalNested = async () => {
+ const btnCloseNestedModal = await this._driver.findElementByAutomationText(closeModalNested);
+ await btnCloseNestedModal.tap();
+ }
+
+ showNestedModalPage = async () => {
+ const btnShowNestedModalPage = await this._driver.findElementByAutomationText(showNestedModalPage);
+ await btnShowNestedModalPage.tap();
+ }
+
+ loadedNestedModalPage = async () => {
+ const btnCloseModalNested = await this._driver.findElementByAutomationText(closeModalNested);
+ assert.isTrue(await btnCloseModalNested.isDisplayed(), `${closeModalNested} is not shown`);
+ console.log(closeModalNested + " loaded!");
+ }
+
+ closeModal = async () => {
+ const btnCloseModal = await this._driver.waitForElement(closeModal, 10000);
+ await btnCloseModal.click();
+ }
+
+ loadModalNoFrame = async (loadShowModalPageWithFrame: boolean) => {
+ if (loadShowModalPageWithFrame) {
+ await this.showModalNoFrame();
+ }
+
+ await this.loadedModalNoFrame();
+ }
+
+ loadModalFrame = async (loadShowModalPageWithFrame: boolean) => {
+ if (loadShowModalPageWithFrame) {
+ await this.showModalFrame();
+ }
+
+ await this.loadedModalFrame();
+ }
+
+ loadSharedModal = async (loadShowModalPageWithFrame: boolean) => {
+ if (loadShowModalPageWithFrame) {
+ await this.showSharedModal();
+ }
+
+ const lbl = await this._driver.waitForElement(sharedModalView, 5000);
+ assert.isTrue(await lbl.isDisplayed());
+ }
+
+ loadSharedModalWithPresentationStyle = async (loadShowModalPageWithFrame: boolean) => {
+ if (loadShowModalPageWithFrame) {
+ await this.showSharedModalPresentationStyle();
+ }
+
+ const lbl = await this._driver.waitForElement(sharedModalView, 5000);
+ assert.isTrue(await lbl.isDisplayed());
+ }
+}
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/e2e/screens/shared-screen.ts b/e2e/modal-navigation-ng/e2e/screens/shared-screen.ts
new file mode 100644
index 000000000..8b742de23
--- /dev/null
+++ b/e2e/modal-navigation-ng/e2e/screens/shared-screen.ts
@@ -0,0 +1,101 @@
+import { AppiumDriver } from "nativescript-dev-appium";
+import { Screen } from "./screen"
+import { assert } from "chai";
+
+const time = 1;
+export const roots = ["setFrameRootView", "setLayoutRootView", "setTabRootView", "setNamedFrameRootView"];
+
+export async function modalFrameBackground(driver: AppiumDriver, screen: Screen) {
+ await driver.backgroundApp(time);
+ await screen.loadedModalFrame();
+}
+
+export async function testDialogBackground(driver: AppiumDriver, screen: Screen, isInFrame: boolean = true) {
+ await screen.showDialogConfirm();
+ await screen.loadedConfirmDialog();
+
+ await driver.backgroundApp(time);
+ await screen.loadedConfirmDialog();
+
+ await screen.closeDialog();
+ if (isInFrame) {
+ await screen.loadedModalFrame();
+ }
+}
+
+export async function testSecondPageBackground(driver: AppiumDriver, screen: Screen) {
+ await screen.navigateToSecondPage();
+ await screen.loadedSecondPage();
+
+ await driver.backgroundApp(time);
+ await screen.loadedSecondPage();
+
+ await screen.goBackFromSecondPage();
+ await screen.loadedModalFrame();
+}
+
+export async function testSecondPageClose(driver: AppiumDriver, screen: Screen) {
+ await screen.navigateToSecondPage();
+ await screen.loadedSecondPage();
+
+ await screen.closeModal();
+ await screen.loadedHome();
+}
+
+export async function testNestedModalFrameBackground(driver: AppiumDriver, screen: Screen, isInFrame: boolean = true) {
+ await screen.showNestedModalFrame();
+ await screen.loadedNestedModalFrame();
+
+ await driver.backgroundApp(time);
+ await screen.loadedNestedModalFrame();
+
+ await screen.closeModalNested();
+ isInFrame ? await screen.loadedModalFrame() : await screen.loadedModalPage();
+}
+
+export async function testNestedModalPageBackground(driver: AppiumDriver, screen: Screen, isInFrame: boolean = true) {
+ await screen.showNestedModalPage();
+ await screen.loadedNestedModalPage();
+
+ await driver.backgroundApp(time);
+ await screen.loadedNestedModalPage();
+
+ await screen.closeModalNested();
+ isInFrame ? await screen.loadedModalFrame() : await screen.loadedModalPage();
+}
+
+export async function modalPageBackground(driver: AppiumDriver, screen: Screen, isInFrame: boolean = true) {
+ await driver.backgroundApp(time);
+ isInFrame ? await screen.loadedModalFrame() : await screen.loadedModalPage();
+}
+
+export async function modalTabViewBackground(driver: AppiumDriver, screen: Screen) {
+ await driver.backgroundApp(time);
+ await screen.loadedModalTabView();
+}
+
+export async function testSecondItemBackground(driver: AppiumDriver, screen: Screen) {
+ await screen.navigateToSecondItem();
+ await screen.loadedSecondItem();
+
+ await driver.backgroundApp(time);
+ await screen.loadedSecondItem();
+
+ await screen.navigateToFirstItem();
+ await screen.loadedFirstItem();
+}
+
+export async function assertComponent(driver: AppiumDriver, message: string) {
+ const lbl = await driver.waitForElement(message, 5000);
+ assert.isTrue(await lbl.isDisplayed());
+}
+
+export async function navigateToSecondComponent(driver: AppiumDriver) {
+ const navigateBtnTap = await driver.findElementByAutomationText("Go To Second (to open shared modal)");
+ await navigateBtnTap.click();
+}
+
+export async function goBack(driver: AppiumDriver) {
+ const backBtnTap = await driver.findElementByAutomationText("go back");
+ await backBtnTap.click();
+}
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/e2e/setup.ts b/e2e/modal-navigation-ng/e2e/setup.ts
new file mode 100644
index 000000000..acfc6f70a
--- /dev/null
+++ b/e2e/modal-navigation-ng/e2e/setup.ts
@@ -0,0 +1,18 @@
+import { startServer, stopServer, ITestReporter, nsCapabilities, LogImageType } from "nativescript-dev-appium";
+const addContext = require('mochawesome/addContext');
+
+const testReporterContext = {};
+testReporterContext.name = "mochawesome";
+testReporterContext.reportDir = "mochawesome-report";
+testReporterContext.log = addContext;
+testReporterContext.logImageTypes = [LogImageType.screenshots];
+nsCapabilities.testReporter = testReporterContext;
+
+before("start server", async function () {
+ nsCapabilities.testReporter.context = this;
+ await startServer();
+});
+
+after("stop server", async function () {
+ await stopServer();
+});
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/e2e/tsconfig.json b/e2e/modal-navigation-ng/e2e/tsconfig.json
new file mode 100644
index 000000000..c297b2347
--- /dev/null
+++ b/e2e/modal-navigation-ng/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/modal-navigation-ng/ngcc.config.js b/e2e/modal-navigation-ng/ngcc.config.js
new file mode 100644
index 000000000..2d2413164
--- /dev/null
+++ b/e2e/modal-navigation-ng/ngcc.config.js
@@ -0,0 +1,19 @@
+module.exports = {
+ packages: {
+ "@nativescript/angular": {
+ entryPoints: {
+ ".": {
+ override: {
+ main: "./index.js",
+ typings: "./index.d.ts",
+ },
+ ignoreMissingDependencies: true,
+ }
+ },
+ ignorableDeepImportMatchers: [
+ /tns-core-modules\//,
+ /@nativescript\/core\//,
+ ]
+ }
+ }
+};
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/package.json b/e2e/modal-navigation-ng/package.json
new file mode 100644
index 000000000..0ad335400
--- /dev/null
+++ b/e2e/modal-navigation-ng/package.json
@@ -0,0 +1,59 @@
+{
+ "description": "NativeScript Application",
+ "license": "SEE LICENSE IN ",
+ "readme": "NativeScript Application",
+ "repository": "",
+ "nativescript": {
+ "id": "org.nativescript.modalnavigationng",
+ "tns-ios": {
+ "version": "6.0.2"
+ },
+ "tns-android": {
+ "version": "6.0.1"
+ }
+ },
+ "dependencies": {
+ "@angular/animations": "~9.1.0",
+ "@angular/common": "~9.1.0",
+ "@angular/compiler": "~9.1.0",
+ "@angular/core": "~9.1.0",
+ "@angular/forms": "~9.1.0",
+ "@angular/platform-browser": "~9.1.0",
+ "@angular/platform-browser-dynamic": "~9.1.0",
+ "@angular/router": "~9.1.0",
+ "@nativescript/angular": "file:../../dist/nativescript-angular-scoped.tgz",
+ "nativescript-theme-core": "~1.0.4",
+ "reflect-metadata": "~0.1.8",
+ "rxjs": "~6.5.5",
+ "@nativescript/core": "next",
+ "zone.js": "^0.10.3"
+ },
+ "devDependencies": {
+ "@angular/compiler-cli": "~9.1.0",
+ "@ngtools/webpack": "~9.1.0",
+ "@types/chai": "~4.1.7",
+ "@types/mocha": "~5.2.5",
+ "@types/node": "~10.12.18",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0",
+ "babylon": "6.18.0",
+ "chai": "^4.2.0",
+ "lazy": "1.0.11",
+ "mocha": "~5.2.0",
+ "mochawesome": "~3.1.2",
+ "nativescript-dev-appium": "^6.0.0",
+ "nativescript-dev-webpack": "next",
+ "tns-platform-declarations": "next",
+ "typescript": "~3.8.3"
+ },
+ "scripts": {
+ "e2e": "tsc -p e2e && mocha --opts ../config/mocha.opts --recursive e2e --appiumCapsLocation ../config/appium.capabilities.json",
+ "e2e-watch": "tsc -p e2e --watch",
+ "clean": "npx rimraf hooks node_modules platforms package-lock.json",
+ "setup": "cd ../../nativescript-angular && npm run prep.apps && cd ../e2e/modal-navigation-ng && npm run clean",
+ "ngcc": "ngcc --properties es2015 module main --first-only",
+ "postinstall": "npm run ngcc",
+ "ios": "tns debug ios --env.aot --emulator --no-hmr",
+ "android": "tns debug android --env.aot --emulator --no-hmr"
+ }
+}
diff --git a/e2e/modal-navigation-ng/references.d.ts b/e2e/modal-navigation-ng/references.d.ts
new file mode 100644
index 000000000..992a504a0
--- /dev/null
+++ b/e2e/modal-navigation-ng/references.d.ts
@@ -0,0 +1,2 @@
+///
+///
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/tsconfig.esm.json b/e2e/modal-navigation-ng/tsconfig.esm.json
new file mode 100644
index 000000000..a96f6bbc6
--- /dev/null
+++ b/e2e/modal-navigation-ng/tsconfig.esm.json
@@ -0,0 +1,7 @@
+{
+ "extends": "./tsconfig",
+ "compilerOptions": {
+ "module": "ESNext",
+ "moduleResolution": "node"
+ }
+}
diff --git a/e2e/modal-navigation-ng/tsconfig.json b/e2e/modal-navigation-ng/tsconfig.json
new file mode 100644
index 000000000..1d8622eed
--- /dev/null
+++ b/e2e/modal-navigation-ng/tsconfig.json
@@ -0,0 +1,34 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es5",
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "noEmitHelpers": true,
+ "noEmitOnError": true,
+ "skipLibCheck": true,
+ "lib": [
+ "es2017",
+ "dom",
+ "es6"
+ ],
+ "baseUrl": ".",
+ "paths": {
+ "~/*": [
+ "app/*"
+ ]
+ }
+ },
+ "includes": [
+ "./references.d.ts"
+ ],
+ "files": [
+ "./app/main.ts"
+ ],
+ "exclude": [
+ "node_modules",
+ "platforms",
+ "**/*.aot",
+ "e2e"
+ ]
+}
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/.gitignore b/e2e/nested-router-tab-view/.gitignore
new file mode 100644
index 000000000..e2c82407c
--- /dev/null
+++ b/e2e/nested-router-tab-view/.gitignore
@@ -0,0 +1,13 @@
+.vscode
+
+platforms
+node_modules
+hooks
+
+/**/*.js
+/**/*.map
+e2e/reports
+test-results.xml
+
+instrumentscli*.trace
+mochawesome-report
\ No newline at end of file
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/src/main/AndroidManifest.xml b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..9db832151
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/AndroidManifest.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/drawable-hdpi/background.png b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/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/src/main/res/drawable-hdpi/background.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/drawable-hdpi/icon.png b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/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/src/main/res/drawable-hdpi/icon.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/drawable-hdpi/logo.png b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/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/src/main/res/drawable-hdpi/logo.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/drawable-ldpi/background.png b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/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/src/main/res/drawable-ldpi/background.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/drawable-ldpi/icon.png b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/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/src/main/res/drawable-ldpi/icon.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/drawable-ldpi/logo.png b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/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/src/main/res/drawable-ldpi/logo.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/drawable-mdpi/background.png b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/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/src/main/res/drawable-mdpi/background.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/drawable-mdpi/icon.png b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/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/src/main/res/drawable-mdpi/icon.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/drawable-mdpi/logo.png b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/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/src/main/res/drawable-mdpi/logo.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml
new file mode 100644
index 000000000..ada77f92c
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/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/src/main/res/drawable-xhdpi/background.png b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/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/src/main/res/drawable-xhdpi/background.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/drawable-xhdpi/icon.png b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/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/src/main/res/drawable-xhdpi/icon.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/drawable-xhdpi/logo.png b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/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/src/main/res/drawable-xhdpi/logo.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/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/src/main/res/drawable-xxhdpi/background.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/drawable-xxhdpi/icon.png b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/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/src/main/res/drawable-xxhdpi/icon.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/drawable-xxhdpi/logo.png b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/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/src/main/res/drawable-xxhdpi/logo.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/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/src/main/res/drawable-xxxhdpi/background.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/icon.png b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/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/src/main/res/drawable-xxxhdpi/icon.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/logo.png b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/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/src/main/res/drawable-xxxhdpi/logo.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/values-v21/colors.xml b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/values-v21/colors.xml
new file mode 100644
index 000000000..a64641a9d
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/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/src/main/res/values-v21/styles.xml b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/values-v21/styles.xml
new file mode 100644
index 000000000..dac8727c8
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/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/src/main/res/values/colors.xml b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/values/colors.xml
new file mode 100644
index 000000000..74ad8829c
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/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/src/main/res/values/styles.xml b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/values/styles.xml
new file mode 100644
index 000000000..c793e6d4c
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/App_Resources/Android/src/main/res/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/about/about-nested.component.html b/e2e/nested-router-tab-view/app/about/about-nested.component.html
new file mode 100644
index 000000000..0e39308c8
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/about/about-nested.component.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/about/about-nested.component.ts b/e2e/nested-router-tab-view/app/about/about-nested.component.ts
new file mode 100644
index 000000000..ff1197b7e
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/about/about-nested.component.ts
@@ -0,0 +1,9 @@
+import { Component } from "@angular/core";
+
+@Component({
+ moduleId: module.id,
+ selector: "about-nested-page",
+ templateUrl: "./about-nested.component.html"
+})
+export class AboutNestedComponent {
+}
diff --git a/e2e/nested-router-tab-view/app/about/about.component.html b/e2e/nested-router-tab-view/app/about/about.component.html
new file mode 100644
index 000000000..9f9ae3731
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/about/about.component.html
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/about/about.component.ts b/e2e/nested-router-tab-view/app/about/about.component.ts
new file mode 100644
index 000000000..f1132181c
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/about/about.component.ts
@@ -0,0 +1,21 @@
+import { Component } from "@angular/core";
+import { RouterExtensions } from "@nativescript/angular/router";
+import { ActivatedRoute } from "@angular/router";
+
+@Component({
+ moduleId: module.id,
+ selector: "about-page",
+ templateUrl: "./about.component.html"
+})
+export class AboutComponent {
+ constructor(
+ private activeRoute: ActivatedRoute,
+ private routerExtension: RouterExtensions) { }
+
+ ngOnInit() {
+ }
+
+ backActivatedRoute() {
+ this.routerExtension.back({ relativeTo: this.activeRoute });
+ }
+}
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..74b776fc0
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/app.component.html
@@ -0,0 +1 @@
+
\ 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..8a132b0a1
--- /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";
+
+
+@Component({
+ selector: "ns-app",
+ templateUrl: "app.component.html",
+})
+
+export class AppComponent {
+ private isInitialNavigation = true;
+
+ @ViewChild(TabViewDirective, { static: false }) 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..57ce8e6d5
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/app.css
@@ -0,0 +1,43 @@
+Button{
+ color: blue;
+}
+
+TextView{
+ color: green;
+}
+
+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.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..acb5e56fb
--- /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";
+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 "@nativescript/core/trace";
+import { routerTraceCategory } from "@nativescript/angular/trace";
+
+// addCategories(routerTraceCategory);
+traceEnable();
+
+export 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..745991f51
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/app.routing.ts
@@ -0,0 +1,77 @@
+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 { HomeLazyModule } from "./home-lazy/home-lazy.module";
+import { CustomTabsModule } from "./custom-tabs/custom-tabs.module"
+import { AboutComponent } from "./about/about.component";
+import { AboutNestedComponent } from "./about/about-nested.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: () => HomeLazyModule,
+ },
+ {
+ path: "custom-tabs",
+ loadChildren: () => CustomTabsModule,
+ },
+ {
+ 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" },
+ ]
+ },
+ {
+ path: "about", component: AboutComponent, children: [
+ { path: "about-nested", component: AboutNestedComponent },
+ ]
+ }
+];
+
+@NgModule({
+ imports: [NativeScriptRouterModule.forRoot(routes)],
+ exports: [NativeScriptRouterModule],
+})
+export class AppRoutingModule { }
diff --git a/e2e/nested-router-tab-view/app/custom-tabs/custom-tabs.component.html b/e2e/nested-router-tab-view/app/custom-tabs/custom-tabs.component.html
new file mode 100644
index 000000000..851c8765a
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/custom-tabs/custom-tabs.component.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/custom-tabs/custom-tabs.component.ts b/e2e/nested-router-tab-view/app/custom-tabs/custom-tabs.component.ts
new file mode 100644
index 000000000..d46cb3d2b
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/custom-tabs/custom-tabs.component.ts
@@ -0,0 +1,44 @@
+import { Component, OnInit } 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 "@nativescript/core/ui/dialogs";
+import { Page } from 'tns-core-modules/ui/page/page';
+
+@Component({
+ moduleId: module.id,
+ selector: 'custom-tabs',
+ templateUrl: './custom-tabs.component.html'
+})
+export class CustomTabsComponent implements OnInit {
+
+ constructor(
+ private activeRoute: ActivatedRoute,
+ private routerExtension: RouterExtensions,
+ private page: Page) { }
+
+ ngOnInit() {
+ }
+
+ canGoBackParentRoute() {
+ const canGoBackParentRoute = this.routerExtension.canGoBack({ relativeTo: this.activeRoute });
+ const title = "CanGoBack(ParentRoute)";
+ this.onShowDialog(title, title + ` ${canGoBackParentRoute}`);
+ }
+
+ onRootBack() {
+ this.page.frame.goBack();
+ }
+
+ 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/custom-tabs/custom-tabs.module.ts b/e2e/nested-router-tab-view/app/custom-tabs/custom-tabs.module.ts
new file mode 100644
index 000000000..cd29535d2
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/custom-tabs/custom-tabs.module.ts
@@ -0,0 +1,38 @@
+import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
+import { NativeScriptCommonModule } from 'nativescript-angular/common';
+import { NativeScriptRouterModule } from 'nativescript-angular/router';
+
+import { CustomTabsComponent } from './custom-tabs.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";
+import { Route } from "@angular/router";
+import { SharedModule } from "../shared.module";
+
+const routes: Route[] = [
+ {
+ path: 'tabs',
+ component: CustomTabsComponent,
+ children: [
+ { path: "players", component: PlayerComponent },
+ { path: "player/:id", component: PlayerDetailComponent },
+
+ { path: "teams", component: TeamsComponent },
+ { path: "team/:id", component: TeamDetailComponent },
+ ]
+ },
+];
+
+@NgModule({
+ declarations: [CustomTabsComponent
+ ],
+ imports: [
+ NativeScriptCommonModule,
+ NativeScriptRouterModule,
+ NativeScriptRouterModule.forChild(routes),
+ SharedModule
+ ],
+ schemas: [NO_ERRORS_SCHEMA]
+})
+export class CustomTabsModule { }
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..a331ba054
--- /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..bfd588cb0
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/home/home.component.html
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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..d0e7cdcb3
--- /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 "@nativescript/core/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..26e1c80ac
--- /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..6eee0a982
--- /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 "@nativescript/core/data/observable";
+import { confirm } from "@nativescript/core/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.ts b/e2e/nested-router-tab-view/app/main.ts
new file mode 100644
index 000000000..f61a19110
--- /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..4dd3c7ee6
--- /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 "@nativescript/core/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..27a997c2f
--- /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..3eb901a0d
--- /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 "@nativescript/core/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..4c7105224
--- /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..8ba0c2ae8
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/modal/modal.component.ts
@@ -0,0 +1,87 @@
+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 } from "@nativescript/core/ui/core/view"
+import { confirm } from "@nativescript/core/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) {
+ this.modal.closeModal();
+ // 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..e6521c70c
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/package.json
@@ -0,0 +1,9 @@
+{
+ "android": {
+ "v8Flags": "--expose_gc",
+ "markingMode": "none"
+ },
+ "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..ea76ab3cd
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/player/player-detail.component.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
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..c9b431d12
--- /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..7dc729f6d
--- /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..65ca694b7
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/player/players.component.ts
@@ -0,0 +1,37 @@
+import { Component, OnInit, ViewContainerRef } from "@angular/core";
+import { DataService, DataItem } from "../data.service";
+
+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 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..a26b47d0d
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/shared.module.ts
@@ -0,0 +1,47 @@
+import { NgModule, NO_ERRORS_SCHEMA, ErrorHandler, NgModuleFactoryLoader } from "@angular/core";
+// import { NativeScriptModule } from "@nativescript/angular";
+// 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 { AboutComponent } from "./about/about.component";
+import { AboutNestedComponent } from "./about/about-nested.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,
+ AboutComponent,
+ AboutNestedComponent,
+ PlayerComponent,
+ PlayerDetailComponent,
+ TeamsComponent,
+ TeamDetailComponent
+ ],
+ exports: [
+ HomeComponent,
+ AboutComponent,
+ AboutNestedComponent,
+ 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..4e1ddec84
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/tabs/tabs.component.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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..35dd5fc13
--- /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 "@nativescript/core/data/observable";
+import { ActivatedRoute } from "@angular/router";
+import { confirm } from "@nativescript/core/ui/dialogs";
+import { Page } from "@nativescript/core/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..18086c638
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/team/team-detail.component.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
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..1bb7c3f85
--- /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..4af94ca66
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/team/teams.component.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
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/custom-tabs.e2e-spec.ts b/e2e/nested-router-tab-view/e2e/custom-tabs.e2e-spec.ts
new file mode 100644
index 000000000..43b13ebbf
--- /dev/null
+++ b/e2e/nested-router-tab-view/e2e/custom-tabs.e2e-spec.ts
@@ -0,0 +1,84 @@
+import { AppiumDriver, createDriver, nsCapabilities } from "nativescript-dev-appium";
+import { Screen } from "./screen"
+import {
+ testPlayerNavigated,
+ testTeamNavigated
+} from "./shared.e2e-spec";
+import { isSauceLab } from "nativescript-dev-appium/lib/parser";
+import { ImageOptions } from "nativescript-dev-appium/lib/image-options";
+
+const QUEUE_WAIT_TIME: number = 600000; // Sometimes SauceLabs threads are not available and the tests wait in a queue to start. Wait 10 min before timeout.
+const isSauceRun = isSauceLab;
+
+describe("custom-tabs:", async function () {
+ let driver: AppiumDriver;
+ let screen: Screen;
+
+ before(async function () {
+ this.timeout(QUEUE_WAIT_TIME);
+ nsCapabilities.testReporter.context = this;
+ driver = await createDriver();
+ driver.imageHelper.defaultTolerance = 50;
+ driver.imageHelper.defaultToleranceType = ImageOptions.pixel;
+ screen = new Screen(driver);
+ });
+
+ after(async function () {
+ if (isSauceRun) {
+ driver.sessionId().then(function (sessionId) {
+ console.log("Report https://saucelabs.com/beta/tests/" + sessionId);
+ });
+ }
+ await driver.quit();
+ console.log("Quit driver!");
+ });
+
+ afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logTestArtifacts(this.currentTest.title);
+ }
+ });
+
+ it("loaded custom tab component and tabs", async function () {
+ await screen.navigateCustomTabsPage();
+ await screen.loadedCustomTabsPage();
+ await screen.loadedPlayersList();
+ await gotoTeamsTab(driver);
+ await screen.loadedTeamList();
+ });
+
+ it("navigate back to login and again to custom tabs", async function () {
+ await backRoot(driver);
+ await screen.loadedLogin();
+ await screen.navigateCustomTabsPage();
+ await screen.loadedCustomTabsPage();
+ await screen.loadedPlayersList();
+ await gotoTeamsTab(driver);
+ await screen.loadedTeamList();
+ });
+
+ it("navigate to custom tabs player and team details", async function () {
+ await gotoPlayersTab(driver);
+ await testPlayerNavigated(screen, screen.playerOne);
+ await gotoTeamsTab(driver);
+ await screen.loadedTeamList();
+ await testTeamNavigated(screen, screen.teamOne);
+ await backRoot(driver);
+ await screen.loadedLogin();
+ });
+});
+
+async function backRoot(driver: AppiumDriver) {
+ const btnBackRoot = await driver.findElementByAutomationText("Root Back");
+ await btnBackRoot.tap();
+}
+
+async function gotoPlayersTab(driver: AppiumDriver) {
+ const btnTabTeams = await driver.findElementByAutomationText("Players Tab");
+ await btnTabTeams.tap();
+}
+
+async function gotoTeamsTab(driver: AppiumDriver) {
+ const btnTabTeams = await driver.findElementByAutomationText("Teams Tab");
+ await btnTabTeams.tap();
+}
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..a6319c29c
--- /dev/null
+++ b/e2e/nested-router-tab-view/e2e/home-tabs.e2e-spec.ts
@@ -0,0 +1,161 @@
+import { AppiumDriver, createDriver, SearchOptions, nsCapabilities } from "nativescript-dev-appium";
+import { Screen } from "./screen"
+import {
+ testPlayerNavigated,
+ testTeamNavigated,
+ testPlayerNextNavigated
+} from "./shared.e2e-spec";
+import { isSauceLab } from "nativescript-dev-appium/lib/parser";
+import { ImageOptions } from "nativescript-dev-appium/lib/image-options";
+
+const QUEUE_WAIT_TIME: number = 600000; // Sometimes SauceLabs threads are not available and the tests wait in a queue to start. Wait 10 min before timeout.
+const isSauceRun = isSauceLab;
+
+const pages = ["Go To Home Page", "Go To Lazy Home Page"];
+
+describe("home-tabs:", async function () {
+ let driver: AppiumDriver;
+ let screen: Screen;
+
+ before(async function () {
+ this.timeout(QUEUE_WAIT_TIME);
+ nsCapabilities.testReporter.context = this;
+ driver = await createDriver();
+ driver.imageHelper.defaultTolerance = 50;
+ driver.imageHelper.defaultToleranceType = ImageOptions.pixel;
+ screen = new Screen(driver);
+ });
+
+ after(async function () {
+ if (isSauceRun) {
+ driver.sessionId().then(function (sessionId) {
+ console.log("Report https://saucelabs.com/beta/tests/" + sessionId);
+ });
+ }
+ await driver.quit();
+ console.log("Quit driver!");
+ });
+
+ for (let index = 0; index < pages.length; index++) {
+ const page = pages[index];
+ describe(`${page} home-tab:`, async function () {
+
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ });
+
+ afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logTestArtifacts(this.currentTest.title);
+ }
+ });
+
+ it("loaded home component and lists", async function () {
+ await screen.navigateToHomePage(page);
+ await screen.loadedHome();
+ await screen.loadedPlayersList();
+ await screen.loadedTeamList();
+ });
+
+ it("should navigate to Tabs then to About forward", async function () {
+ await screen.navigateToTabsPage();
+ await screen.loadedTabs();
+ await screen.loadedPlayersList();
+
+ // TO DO: This is related to the tns-core-modules animations overhaul intiative (removing Animators in favor of androidx Transitions)
+ // Angular test case: navigating from Page with 3 nested frames and back crashes with "IllegalArgumentException: parameter must be a descendant of this view" only from these commented appium tests (does not reproduce with manual testing). This is fixed in androidx.fragment:fragment:1.2.0 and tests must be uncommented when migrating to it.
+ // await screen.navigateToAboutPage();
+ // await screen.loadedAbout();
+ // await screen.loadedNestedAbout();
+ });
+
+ it("should go back to Tabs and then back to Home", async function () {
+ // TO DO: This is related to the tns-core-modules animations overhaul intiative (removing Animators in favor of androidx Transitions)
+ // Angular test case: navigating from Page with 3 nested frames and back crashes with "IllegalArgumentException: parameter must be a descendant of this view" only from these commented appium tests (does not reproduce with manual testing). This is fixed in androidx.fragment:fragment:1.2.0 and tests must be uncommented when migrating to it.
+ // await backActivatedRoute(driver);
+ // await screen.loadedTabs();
+ // await screen.loadedPlayersList();
+ await backActivatedRoute(driver);
+ await screen.loadedHome();
+ await screen.loadedPlayersList();
+ await screen.loadedTeamList();
+ });
+
+ it("should navigate to Tabs without Players\\Teams navigation", async function () {
+ 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 function () {
+ 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 function () {
+ 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.findElementByAutomationText("Back(ActivatedRoute)");
+ await btnBack.tap();
+}
+
+async function backPlayers(driver: AppiumDriver) {
+ const btnBackPlayers = await driver.findElementByAutomationText("Back(Players)");
+ await btnBackPlayers.tap();
+}
+
+async function backBoth(driver: AppiumDriver) {
+ const btnBackBoth = await driver.findElementByAutomationText("Back(Both)");
+ await btnBackBoth.tap();
+}
+
+async function gotoPlayersTab(driver: AppiumDriver) {
+ const btnTabPlayers = await driver.findElementByAutomationText("Players Tab");
+ await btnTabPlayers.tap();
+}
+
+async function gotoTeamsTab(driver: AppiumDriver) {
+ const btnTabTeams = await driver.findElementByAutomationText("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..f880d33c8
--- /dev/null
+++ b/e2e/nested-router-tab-view/e2e/screen.ts
@@ -0,0 +1,178 @@
+import { AppiumDriver, SearchOptions } from "nativescript-dev-appium";
+import { assert } from "chai";
+
+const customTabs = "Custom Tabs Component";
+const home = "Home Component";
+const about = "About Component";
+const aboutNested = "Nested About 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 gotoCustomTabPage = "Go To Lazy Custom Tabs";
+const gotoAboutPage = "Go To About 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.findElementByAutomationText(title);
+ await btnShowDialogConfirm.tap();
+ }
+
+ loadedConfirmDialog = async (dialogMessage) => {
+ const lblDialogMessage = await this._driver.findElementByAutomationText(dialogMessage);
+ assert.isTrue(await lblDialogMessage.isDisplayed());
+ console.log(dialogMessage + " shown!");
+ }
+
+ closeDialog = async () => {
+ const btnYesDialog = await this._driver.findElementByAutomationText(confirmDialog);
+ await btnYesDialog.click();
+ }
+
+ loadedLogin = async () => {
+ const lblLogin = await this._driver.findElementByAutomationText(login);
+ assert.isTrue(await lblLogin.isDisplayed());
+ console.log(login + " loaded!");
+ }
+
+ loadedHome = async () => {
+ const lblHome = await this._driver.findElementByAutomationText(home);
+ assert.isTrue(await lblHome.isDisplayed());
+ console.log(home + " loaded!");
+ }
+
+ loadedCustomTabsPage= async () => {
+ const lblCustomTabs = await this._driver.findElementByAutomationText(customTabs);
+ assert.isTrue(await lblCustomTabs.isDisplayed());
+ console.log(home + " loaded!");
+ }
+
+ loadedAbout= async () => {
+ const lblAbout = await this._driver.findElementByAutomationText(about);
+ assert.isTrue(await lblAbout.isDisplayed());
+ console.log(about + " loaded!");
+ }
+
+ loadedNestedAbout= async () => {
+ const lblAboutNested = await this._driver.findElementByAutomationText(aboutNested);
+ assert.isTrue(await lblAboutNested.isDisplayed());
+ console.log(aboutNested + " loaded!");
+ }
+
+ loadedTabs = async () => {
+ const lblTabs = await this._driver.findElementByAutomationText(tabs);
+ assert.isTrue(await lblTabs.isDisplayed());
+ console.log(tabs + " loaded!");
+ }
+
+ navigateToTabsPage = async () => {
+ const btnNavToTabsPage = await this._driver.findElementByAutomationText(gotoTabsPage);
+ await btnNavToTabsPage.tap();
+ }
+
+ loadedPlayersList = async () => {
+ const lblPlayerList = await this._driver.findElementByAutomationText(playerList);
+ assert.isTrue(await lblPlayerList.isDisplayed());
+ console.log(playerList + " loaded!");
+ }
+
+ loadedPlayerDetails = async (player) => {
+ const lblPlayerDetail = await this._driver.findElementByAutomationText(playerDetails);
+ assert.isTrue(await lblPlayerDetail.isDisplayed());
+
+ const lblPlayer = await this._driver.findElementByAutomationText(player + " Details");
+ assert.isTrue(await lblPlayer.isDisplayed());
+
+ console.log(player + " Details" + " loaded!");
+ }
+
+ loadedTeamList = async () => {
+ const lblTeamList = await this._driver.findElementByAutomationText(teamList, 10);
+ assert.isTrue(await lblTeamList.isDisplayed());
+ console.log(teamList + " loaded!");
+ }
+
+ loadedTeamDetails = async (team) => {
+ const lblTeamDetail = await this._driver.findElementByAutomationText(teamDetails);
+ assert.isTrue(await lblTeamDetail.isDisplayed());
+
+ const lblTeam = await this._driver.findElementByAutomationText(team + " Details");
+ assert.isTrue(await lblTeam.isDisplayed());
+
+ console.log(team + " Details" + " loaded!");
+ }
+
+ navigateToHomePage = async (homePageButton?) => {
+ const btnNavToHomePage = await this._driver.findElementByAutomationText(homePageButton || gotoHomePage);
+ await btnNavToHomePage.tap();
+ }
+
+ navigateCustomTabsPage = async () => {
+ const btnNavToHomePage = await this._driver.findElementByAutomationText(gotoCustomTabPage);
+ await btnNavToHomePage.tap();
+ }
+
+ navigateToAboutPage = async () => {
+ const btnNavToAboutPage = await this._driver.findElementByAutomationText(gotoAboutPage);
+ await btnNavToAboutPage.tap();
+ }
+
+ navigateToPlayer = async (player: string) => {
+ const btnNavPlayerPage = await this._driver.findElementByAutomationText(player);
+ await btnNavPlayerPage.tap();
+ }
+
+ navigateToNextPlayer = async () => {
+ const btnNavPlayerNextPage = await this._driver.findElementByAutomationText(gotoNextPlayer);
+ await btnNavPlayerNextPage.tap();
+ }
+
+ navigateToPlayers = async () => {
+ const btnNavPlayersPage = await this._driver.findElementByAutomationText(gotoPlayers);
+ await btnNavPlayersPage.tap();
+ }
+
+ navigateToTeam = async (team: string) => {
+ const btnNavTeamPage = await this._driver.findElementByAutomationText(team);
+ await btnNavTeamPage.tap();
+ }
+
+ navigateToNextTeam = async () => {
+ const btnNavTeamNextPage = await this._driver.findElementByAutomationText(gotoNextTeam);
+ await btnNavTeamNextPage.tap();
+ }
+
+ navigateToTeams = async () => {
+ const btnNavTeamsPage = await this._driver.findElementByAutomationText(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..cd73140bb
--- /dev/null
+++ b/e2e/nested-router-tab-view/e2e/setup.ts
@@ -0,0 +1,18 @@
+import { startServer, stopServer, ITestReporter, nsCapabilities, LogImageType } from "nativescript-dev-appium";
+const addContext = require('mochawesome/addContext');
+
+const testReporterContext = {};
+testReporterContext.name = "mochawesome";
+testReporterContext.reportDir = "mochawesome-report";
+testReporterContext.log = addContext;
+testReporterContext.logImageTypes = [LogImageType.screenshots];
+nsCapabilities.testReporter = testReporterContext;
+
+before("start server", async function () {
+ nsCapabilities.testReporter.context = this;
+ await startServer();
+});
+
+after("stop server", async function () {
+ 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..f3a0490fe
--- /dev/null
+++ b/e2e/nested-router-tab-view/e2e/split-view.e2e-spec.ts
@@ -0,0 +1,196 @@
+import { AppiumDriver, createDriver, SearchOptions, nsCapabilities } from "nativescript-dev-appium";
+import { Screen } from "./screen"
+import {
+ testPlayerNavigated,
+ testTeamNavigated,
+ testPlayerNextNavigated,
+ testTeamNextNavigated,
+ testPlayersNavigated,
+ canGoBack
+} from "./shared.e2e-spec";
+import { isSauceLab } from "nativescript-dev-appium/lib/parser";
+import { ImageOptions } from "nativescript-dev-appium/lib/image-options";
+
+const QUEUE_WAIT_TIME: number = 600000; // Sometimes SauceLabs threads are not available and the tests wait in a queue to start. Wait 10 min before timeout.
+const isSauceRun = isSauceLab;
+
+const pages = ["Go To Home Page", "Go To Lazy Home Page"];
+
+describe("split-view:", async function () {
+ let driver: AppiumDriver;
+ let screen: Screen;
+
+ before(async function () {
+ this.timeout(QUEUE_WAIT_TIME);
+ nsCapabilities.testReporter.context = this;
+ driver = await createDriver();
+ driver.imageHelper.defaultTolerance = 50;
+ driver.imageHelper.defaultToleranceType = ImageOptions.pixel;
+ screen = new Screen(driver);
+ });
+
+ after(async function () {
+ if (isSauceRun) {
+ driver.sessionId().then(function (sessionId) {
+ console.log("Report https://saucelabs.com/beta/tests/" + sessionId);
+ });
+ }
+ await driver.quit();
+ console.log("Quit driver!");
+ });
+
+ for (let index = 0; index < pages.length; index++) {
+ const page = pages[index];
+ describe(`${page} split-view:`, async function () {
+
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ });
+
+ afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logTestArtifacts(this.currentTest.title);
+ }
+ });
+
+ it("loaded home component and lists", async function () {
+ 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 function () {
+ 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 function () {
+ 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 function () {
+ 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 function () {
+ 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 function () {
+ 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 function () {
+ 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 function () {
+ 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.findElementByAutomationText("Back(ActivatedRoute)");
+ await btnBack.tap();
+}
+
+async function back(driver: AppiumDriver) {
+ const btnBack = await driver.findElementByAutomationText("Back()");
+ await btnBack.tap();
+}
+
+async function backPlayers(driver: AppiumDriver) {
+ const btnBackPlayers = await driver.findElementByAutomationText("Back(Players)");
+ await btnBackPlayers.tap();
+}
+
+async function backTeams(driver: AppiumDriver) {
+ const btnBackTeams = await driver.findElementByAutomationText("Back(Teams)");
+ await btnBackTeams.tap();
+}
+
+async function backBoth(driver: AppiumDriver) {
+ const btnBackBoth = await driver.findElementByAutomationText("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..a1955d5d4
--- /dev/null
+++ b/e2e/nested-router-tab-view/e2e/tab-view.e2e-spec.ts
@@ -0,0 +1,111 @@
+import { AppiumDriver, createDriver, nsCapabilities } from "nativescript-dev-appium";
+import { Screen } from "./screen"
+import {
+ testPlayerNavigated,
+ testTeamNavigated,
+ testPlayerNextNavigated,
+ testTeamNextNavigated,
+} from "./shared.e2e-spec";
+import { isSauceLab } from "nativescript-dev-appium/lib/parser";
+import { ImageOptions } from "nativescript-dev-appium/lib/image-options";
+
+const QUEUE_WAIT_TIME: number = 600000; // Sometimes SauceLabs threads are not available and the tests wait in a queue to start. Wait 10 min before timeout.
+const isSauceRun = isSauceLab;
+
+describe("tab-view:", async function () {
+ let driver: AppiumDriver;
+ let screen: Screen;
+
+ before(async function () {
+ this.timeout(QUEUE_WAIT_TIME);
+ nsCapabilities.testReporter.context = this;
+ driver = await createDriver();
+ driver.imageHelper.defaultTolerance = 50;
+ driver.imageHelper.defaultToleranceType = ImageOptions.pixel;
+ screen = new Screen(driver);
+ });
+
+ after(async function () {
+ if (isSauceRun) {
+ driver.sessionId().then(function (sessionId) {
+ console.log("Report https://saucelabs.com/beta/tests/" + sessionId);
+ });
+ }
+ 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 function () {
+ await screen.navigateToHomePage();
+ await screen.loadedHome();
+ await screen.loadedPlayersList();
+ await screen.loadedTeamList();
+ });
+
+ it("loaded tabs component, Players List and Teams List pages", async function () {
+ 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 function () {
+ this.retries(2);
+ 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 function () {
+ 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.findElementByAutomationText("Players Tab");
+ await btnTabPlayers.tap();
+}
+
+async function gotoTeamsTab(driver: AppiumDriver) {
+ const btnTabTeams = await driver.findElementByAutomationText("Teams Tab");
+ await btnTabTeams.tap();
+}
+
+async function backTeams(driver: AppiumDriver) {
+ const btnBackTeams = await driver.findElementByAutomationText("Back-Teams");
+ await btnBackTeams.tap();
+}
+
+async function backPlayers(driver: AppiumDriver) {
+ const btnBackPlayers = await driver.findElementByAutomationText("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..791cbc65f
--- /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": "6.5.1"
+ }
+ },
+ "dependencies": {
+ "@angular/animations": "~9.1.0",
+ "@angular/common": "~9.1.0",
+ "@angular/compiler": "~9.1.0",
+ "@angular/core": "~9.1.0",
+ "@angular/forms": "~9.1.0",
+ "@angular/platform-browser": "~9.1.0",
+ "@angular/platform-browser-dynamic": "~9.1.0",
+ "@angular/router": "~9.1.0",
+ "@nativescript/angular": "file:../../dist/nativescript-angular-scoped.tgz",
+ "nativescript-theme-core": "~1.0.4",
+ "reflect-metadata": "~0.1.8",
+ "rxjs": "~6.5.5",
+ "@nativescript/core": "next",
+ "zone.js": "^0.10.3"
+ },
+ "devDependencies": {
+ "@angular/compiler-cli": "~9.1.0",
+ "@ngtools/webpack": "~9.1.0",
+ "@types/chai": "~4.1.7",
+ "@types/mocha": "~5.2.5",
+ "@types/node": "~10.12.18",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0",
+ "babylon": "6.18.0",
+ "chai": "^4.2.0",
+ "lazy": "1.0.11",
+ "mocha": "~5.2.0",
+ "mochawesome": "~3.1.2",
+ "nativescript-dev-appium": "^6.0.0",
+ "nativescript-dev-webpack": "next",
+ "typescript": "~3.8.3"
+ },
+ "scripts": {
+ "compile-tests": "tsc -p e2e --watch",
+ "e2e": "tsc -p e2e && mocha --opts ../config/mocha.opts --recursive e2e --appiumCapsLocation ../config/appium.capabilities.json",
+ "e2e-watch": "tsc -p e2e --watch",
+ "clean": "npx rimraf hooks node_modules platforms package-lock.json",
+ "setup": "cd ../../nativescript-angular && npm run prep.apps && cd ../e2e/nested-router-tab-view && npm run clean",
+ "ngcc": "ngcc --properties es2015 module main --first-only",
+ "postinstall": "npm run ngcc"
+ }
+}
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..1d8622eed
--- /dev/null
+++ b/e2e/nested-router-tab-view/tsconfig.json
@@ -0,0 +1,34 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es5",
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "noEmitHelpers": true,
+ "noEmitOnError": true,
+ "skipLibCheck": true,
+ "lib": [
+ "es2017",
+ "dom",
+ "es6"
+ ],
+ "baseUrl": ".",
+ "paths": {
+ "~/*": [
+ "app/*"
+ ]
+ }
+ },
+ "includes": [
+ "./references.d.ts"
+ ],
+ "files": [
+ "./app/main.ts"
+ ],
+ "exclude": [
+ "node_modules",
+ "platforms",
+ "**/*.aot",
+ "e2e"
+ ]
+}
\ 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..a96f6bbc6
--- /dev/null
+++ b/e2e/nested-router-tab-view/tsconfig.tns.json
@@ -0,0 +1,7 @@
+{
+ "extends": "./tsconfig",
+ "compilerOptions": {
+ "module": "ESNext",
+ "moduleResolution": "node"
+ }
+}
diff --git a/e2e/renderer/.gitignore b/e2e/renderer/.gitignore
new file mode 100644
index 000000000..f2486e508
--- /dev/null
+++ b/e2e/renderer/.gitignore
@@ -0,0 +1,10 @@
+platforms
+node_modules
+hooks
+
+app/**/*.js
+e2e/**/*.js
+test-results.xml
+e2e/reports
+mochawesome-report
+
diff --git a/e2e/renderer/.vscode/launch.json b/e2e/renderer/.vscode/launch.json
new file mode 100644
index 000000000..57e3caf4c
--- /dev/null
+++ b/e2e/renderer/.vscode/launch.json
@@ -0,0 +1,24 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "node",
+ "request": "launch",
+ "name": "Mocha Tests",
+ "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
+ "args": [
+ "-u",
+ "tdd",
+ "--timeout",
+ "999999",
+ "--colors",
+ "${workspaceRoot}/e2e",
+ "-a",
+ "--grep",
+ "actionBarVisibility 'auto' shows action bars based on page"
+
+ ],
+ "internalConsoleOptions": "openOnSessionStart"
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/e2e/renderer/.vscode/settings.json b/e2e/renderer/.vscode/settings.json
new file mode 100644
index 000000000..5d4d7a687
--- /dev/null
+++ b/e2e/renderer/.vscode/settings.json
@@ -0,0 +1,14 @@
+{
+ "files.exclude": {
+ "**/.git": true,
+ "**/.svn": true,
+ "**/.hg": true,
+ "**/CVS": true,
+ "**/.DS_Store": true,
+ // "**/*.js": true,
+ "**/*.map": true,
+ // "node_modules": true,
+ "hooks": true,
+ "platforms": true
+ }
+}
\ No newline at end of file
diff --git a/e2e/renderer/app/App_Resources/Android/app.gradle b/e2e/renderer/app/App_Resources/Android/app.gradle
new file mode 100644
index 000000000..b9e2b2982
--- /dev/null
+++ b/e2e/renderer/app/App_Resources/Android/app.gradle
@@ -0,0 +1,23 @@
+// Add your native dependencies here:
+
+// Uncomment to add recyclerview-v7 dependency
+//dependencies {
+// compile 'com.android.support:recyclerview-v7:+'
+//}
+
+android {
+ defaultConfig {
+ generatedDensities = []
+ applicationId = "org.nativescript.renderer"
+
+ //override supported platforms
+ // ndk {
+ // abiFilters.clear()
+ // abiFilters "armeabi-v7a"
+ // }
+
+ }
+ aaptOptions {
+ additionalParameters "--no-version-vectors"
+ }
+}
diff --git a/e2e/renderer/app/App_Resources/Android/src/main/AndroidManifest.xml b/e2e/renderer/app/App_Resources/Android/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..9db832151
--- /dev/null
+++ b/e2e/renderer/app/App_Resources/Android/src/main/AndroidManifest.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-hdpi/background.png b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-hdpi/background.png
new file mode 100644
index 000000000..eb381c258
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-hdpi/background.png differ
diff --git a/startup-test/app/App_Resources/Android/drawable-hdpi/icon.png b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-hdpi/icon.png
similarity index 100%
rename from startup-test/app/App_Resources/Android/drawable-hdpi/icon.png
rename to e2e/renderer/app/App_Resources/Android/src/main/res/drawable-hdpi/icon.png
diff --git a/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-hdpi/logo.png b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-hdpi/logo.png
new file mode 100644
index 000000000..5218f4c90
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-hdpi/logo.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-ldpi/background.png b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-ldpi/background.png
new file mode 100644
index 000000000..748b2adf5
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-ldpi/background.png differ
diff --git a/startup-test/app/App_Resources/Android/drawable-ldpi/icon.png b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-ldpi/icon.png
similarity index 100%
rename from startup-test/app/App_Resources/Android/drawable-ldpi/icon.png
rename to e2e/renderer/app/App_Resources/Android/src/main/res/drawable-ldpi/icon.png
diff --git a/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-ldpi/logo.png b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-ldpi/logo.png
new file mode 100644
index 000000000..b9e102a76
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-ldpi/logo.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-mdpi/background.png b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-mdpi/background.png
new file mode 100644
index 000000000..efeaf2907
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-mdpi/background.png differ
diff --git a/startup-test/app/App_Resources/Android/drawable-mdpi/icon.png b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-mdpi/icon.png
similarity index 100%
rename from startup-test/app/App_Resources/Android/drawable-mdpi/icon.png
rename to e2e/renderer/app/App_Resources/Android/src/main/res/drawable-mdpi/icon.png
diff --git a/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-mdpi/logo.png b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-mdpi/logo.png
new file mode 100644
index 000000000..626338766
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-mdpi/logo.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml
new file mode 100644
index 000000000..ada77f92c
--- /dev/null
+++ b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml
@@ -0,0 +1,8 @@
+
+ -
+
+
+ -
+
+
+
\ No newline at end of file
diff --git a/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xhdpi/background.png b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xhdpi/background.png
new file mode 100644
index 000000000..612bbd072
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xhdpi/background.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xhdpi/icon.png b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xhdpi/icon.png
new file mode 100644
index 000000000..f29188209
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xhdpi/icon.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xhdpi/logo.png b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xhdpi/logo.png
new file mode 100644
index 000000000..ad8ee2f4b
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xhdpi/logo.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png
new file mode 100644
index 000000000..0fa88e235
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png differ
diff --git a/startup-test/app/App_Resources/iOS/icon-72@2x.png b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xxhdpi/icon.png
old mode 100755
new mode 100644
similarity index 100%
rename from startup-test/app/App_Resources/iOS/icon-72@2x.png
rename to e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xxhdpi/icon.png
diff --git a/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xxhdpi/logo.png b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xxhdpi/logo.png
new file mode 100644
index 000000000..668327832
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xxhdpi/logo.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png
new file mode 100644
index 000000000..c650f6438
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/browse.png b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/browse.png
new file mode 100644
index 000000000..9a4d6ce88
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/browse.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/home.png b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/home.png
new file mode 100644
index 000000000..372293f0f
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/home.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/icon.png b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/icon.png
new file mode 100644
index 000000000..50887a856
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/icon.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/logo.png b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/logo.png
new file mode 100644
index 000000000..fa6331c8d
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/logo.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/src/main/res/values-v21/colors.xml b/e2e/renderer/app/App_Resources/Android/src/main/res/values-v21/colors.xml
new file mode 100644
index 000000000..a64641a9d
--- /dev/null
+++ b/e2e/renderer/app/App_Resources/Android/src/main/res/values-v21/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #3d5afe
+
\ No newline at end of file
diff --git a/e2e/renderer/app/App_Resources/Android/src/main/res/values-v21/styles.xml b/e2e/renderer/app/App_Resources/Android/src/main/res/values-v21/styles.xml
new file mode 100644
index 000000000..dac8727c8
--- /dev/null
+++ b/e2e/renderer/app/App_Resources/Android/src/main/res/values-v21/styles.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/renderer/app/App_Resources/Android/src/main/res/values/colors.xml b/e2e/renderer/app/App_Resources/Android/src/main/res/values/colors.xml
new file mode 100644
index 000000000..74ad8829c
--- /dev/null
+++ b/e2e/renderer/app/App_Resources/Android/src/main/res/values/colors.xml
@@ -0,0 +1,7 @@
+
+
+ #F5F5F5
+ #757575
+ #33B5E5
+ #272734
+
\ No newline at end of file
diff --git a/e2e/renderer/app/App_Resources/Android/src/main/res/values/styles.xml b/e2e/renderer/app/App_Resources/Android/src/main/res/values/styles.xml
new file mode 100644
index 000000000..1e8c7f29b
--- /dev/null
+++ b/e2e/renderer/app/App_Resources/Android/src/main/res/values/styles.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000..1953734f4
--- /dev/null
+++ b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,92 @@
+{
+ "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"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png
new file mode 100644
index 000000000..9e15af09d
Binary files /dev/null and b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png
new file mode 100644
index 000000000..7b9e55537
Binary files /dev/null and b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png
new file mode 100644
index 000000000..76f61ec1f
Binary files /dev/null and b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png
new file mode 100644
index 000000000..15b06db11
Binary files /dev/null and b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png
new file mode 100644
index 000000000..585065f94
Binary files /dev/null and b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png
new file mode 100644
index 000000000..a450c421d
Binary files /dev/null and b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png
new file mode 100644
index 000000000..457b6d94c
Binary files /dev/null and b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png
new file mode 100644
index 000000000..fa5a6ac86
Binary files /dev/null and b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png
new file mode 100644
index 000000000..94abcf70d
Binary files /dev/null and b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png
new file mode 100644
index 000000000..2e71dd3a0
Binary files /dev/null and b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png
new file mode 100644
index 000000000..4abc9ec50
Binary files /dev/null and b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/Contents.json b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/Contents.json
new file mode 100644
index 000000000..da4a164c9
--- /dev/null
+++ b/e2e/renderer/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/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
new file mode 100644
index 000000000..4414bad08
--- /dev/null
+++ b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
@@ -0,0 +1,158 @@
+{
+ "images" : [
+ {
+ "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/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png b/e2e/renderer/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/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png b/e2e/renderer/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/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png b/e2e/renderer/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/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png b/e2e/renderer/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/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png b/e2e/renderer/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/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png b/e2e/renderer/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/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png b/e2e/renderer/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/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png b/e2e/renderer/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/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png
new file mode 100644
index 000000000..9f1f6ce3e
Binary files /dev/null and b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png b/e2e/renderer/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/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json
new file mode 100644
index 000000000..4f4e9c506
--- /dev/null
+++ b/e2e/renderer/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/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png b/e2e/renderer/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/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png b/e2e/renderer/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/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json b/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json
new file mode 100644
index 000000000..23c0ffd7a
--- /dev/null
+++ b/e2e/renderer/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/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png b/e2e/renderer/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/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png b/e2e/renderer/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/renderer/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/Info.plist b/e2e/renderer/app/App_Resources/iOS/Info.plist
new file mode 100644
index 000000000..ea3e3ea23
--- /dev/null
+++ b/e2e/renderer/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/renderer/app/App_Resources/iOS/LaunchScreen.storyboard b/e2e/renderer/app/App_Resources/iOS/LaunchScreen.storyboard
new file mode 100644
index 000000000..2ad9471e1
--- /dev/null
+++ b/e2e/renderer/app/App_Resources/iOS/LaunchScreen.storyboard
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/renderer/app/App_Resources/iOS/build.xcconfig b/e2e/renderer/app/App_Resources/iOS/build.xcconfig
new file mode 100644
index 000000000..4b0118490
--- /dev/null
+++ b/e2e/renderer/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/renderer/app/App_Resources/iOS/tabIcons/browse.png b/e2e/renderer/app/App_Resources/iOS/tabIcons/browse.png
new file mode 100644
index 000000000..cde8ecf30
Binary files /dev/null and b/e2e/renderer/app/App_Resources/iOS/tabIcons/browse.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/tabIcons/browse@2x.png b/e2e/renderer/app/App_Resources/iOS/tabIcons/browse@2x.png
new file mode 100644
index 000000000..37e94bd5c
Binary files /dev/null and b/e2e/renderer/app/App_Resources/iOS/tabIcons/browse@2x.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/tabIcons/browse@3x.png b/e2e/renderer/app/App_Resources/iOS/tabIcons/browse@3x.png
new file mode 100644
index 000000000..5adce0609
Binary files /dev/null and b/e2e/renderer/app/App_Resources/iOS/tabIcons/browse@3x.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/tabIcons/home.png b/e2e/renderer/app/App_Resources/iOS/tabIcons/home.png
new file mode 100644
index 000000000..ca969cb8c
Binary files /dev/null and b/e2e/renderer/app/App_Resources/iOS/tabIcons/home.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/tabIcons/home@2x.png b/e2e/renderer/app/App_Resources/iOS/tabIcons/home@2x.png
new file mode 100644
index 000000000..ace1ca2aa
Binary files /dev/null and b/e2e/renderer/app/App_Resources/iOS/tabIcons/home@2x.png differ
diff --git a/e2e/renderer/app/App_Resources/iOS/tabIcons/home@3x.png b/e2e/renderer/app/App_Resources/iOS/tabIcons/home@3x.png
new file mode 100644
index 000000000..ca2bef9e8
Binary files /dev/null and b/e2e/renderer/app/App_Resources/iOS/tabIcons/home@3x.png differ
diff --git a/e2e/renderer/app/README.md b/e2e/renderer/app/README.md
new file mode 100644
index 000000000..ebe60c416
--- /dev/null
+++ b/e2e/renderer/app/README.md
@@ -0,0 +1,5 @@
+# NativeScript Tutorial Angular Template
+
+This repo serves as the starting point for NativeScript’s [Angular Getting Started Guide](https://docs.nativescript.org/angular/tutorial/ng-chapter-0).
+
+Please file any issues with this template on the [NativeScript/docs repository](https://github.com/nativescript/docs), which is where the tutorial content lives.
\ No newline at end of file
diff --git a/e2e/renderer/app/action-bar/action-bar-dynamic-items.component.ts b/e2e/renderer/app/action-bar/action-bar-dynamic-items.component.ts
new file mode 100644
index 000000000..0614e0798
--- /dev/null
+++ b/e2e/renderer/app/action-bar/action-bar-dynamic-items.component.ts
@@ -0,0 +1,27 @@
+import { Component } from "@angular/core";
+
+@Component({
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+})
+export class ActionBarDynamicItemsComponent {
+ public showNavigationButton = true;
+ public show1 = true;
+ public show2 = true;
+}
+
diff --git a/e2e/renderer/app/action-bar/action-bar-extension.component.ts b/e2e/renderer/app/action-bar/action-bar-extension.component.ts
new file mode 100644
index 000000000..7d3b9b8e8
--- /dev/null
+++ b/e2e/renderer/app/action-bar/action-bar-extension.component.ts
@@ -0,0 +1,16 @@
+import { Component } from "@angular/core";
+
+@Component({
+ template: `
+
+
+
+
+
+
+
+ `
+})
+export class ActionBarExtensionComponent {
+ public show = true;
+}
diff --git a/e2e/renderer/app/app-routing.module.ts b/e2e/renderer/app/app-routing.module.ts
new file mode 100644
index 000000000..04ab25978
--- /dev/null
+++ b/e2e/renderer/app/app-routing.module.ts
@@ -0,0 +1,143 @@
+import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
+import { NativeScriptRouterModule, NSEmptyOutletComponent } from "@nativescript/angular/router";
+
+import { ActionBarDynamicItemsComponent } from "./action-bar/action-bar-dynamic-items.component";
+import { ActionBarExtensionComponent } from "./action-bar/action-bar-extension.component";
+
+import { ActionBarVisibilityAlwaysComponent } from "./page-router-outlet/action-bar-visibility-always.component";
+import { ActionBarVisibilityAutoComponent } from "./page-router-outlet/action-bar-visibility-auto.component"
+import { ActionBarVisibilityNeverComponent } from "./page-router-outlet/action-bar-visibility-never.component"
+import { NestedPageComponent } from "./page-router-outlet/nested-page.component"
+
+import { TabItemBindingComponent } from "./tab-view/tab-item-binding.component";
+
+import { ListComponent } from "./list.component";
+import { NgForComponent } from "./ngfor.component";
+import { NgForOfComponent } from "./ngforof.component";
+import { NgIfNoLayoutComponent } from "./ngif-no-layout.component";
+import { NgIfInbetweenComponent } from "./ngif-inbetween.component";
+import { NgIfElseComponent } from "./ngifelse.component";
+import { NgIfThenElseComponent } from "./ngif-then-else.component";
+import { NgIfSubsequent } from "./ngif-subsequent.component";
+import { ContentViewComponent } from "./content-view.component";
+
+export const routes = [
+ {
+ path: "",
+ redirectTo: "/list",
+ pathMatch: "full"
+ },
+ {
+ path: "action-bar-visibility-always",
+ component: ActionBarVisibilityAlwaysComponent,
+ children: [{
+ path: "nested",
+ outlet: "nested",
+ component: NestedPageComponent
+ }]
+ },
+ {
+ path: "action-bar-visibility-never",
+ component: ActionBarVisibilityNeverComponent,
+ children: [{
+ path: "nested",
+ outlet: "nested",
+ component: NestedPageComponent
+ }]
+ },
+ {
+ path: "action-bar-visibility-auto",
+ component: ActionBarVisibilityAutoComponent,
+ children: [{
+ path: "nested",
+ outlet: "nested",
+ component: NestedPageComponent
+ }]
+ },
+ {
+ path: "action-bar-visibility-never-lazy",
+ component: ActionBarVisibilityNeverComponent,
+ children: [{
+ path: "nested",
+ outlet: "nested",
+ component: NSEmptyOutletComponent,
+ loadChildren:"~/page-router-outlet/nested-lazy-page.module#NestedLazyPageModule"
+ }]
+ },
+ {
+ path: "action-bar-dynamic",
+ component: ActionBarDynamicItemsComponent,
+ },
+ {
+ path: "action-bar-extension",
+ component: ActionBarExtensionComponent,
+ },
+ {
+ path: "tab-item-binding",
+ component: TabItemBindingComponent,
+ },
+ {
+ path: "list",
+ component: ListComponent,
+ },
+ {
+ path: "ngfor",
+ component: NgForComponent,
+ },
+ {
+ path: "ngforof",
+ component: NgForOfComponent,
+ },
+ {
+ path: "ngif-no-layout",
+ component: NgIfNoLayoutComponent,
+ },
+ {
+ path: "ngif-inbetween",
+ component: NgIfInbetweenComponent,
+ },
+ {
+ path: "ngifelse",
+ component: NgIfElseComponent,
+ },
+ {
+ path: "ngif-then-else",
+ component: NgIfThenElseComponent,
+ },
+ {
+ path: "ngif-subsequent",
+ component: NgIfSubsequent,
+ },
+ {
+ path: "content-view",
+ component: ContentViewComponent,
+ },
+];
+
+export const navigatableComponents = [
+ ActionBarDynamicItemsComponent,
+ ActionBarExtensionComponent,
+
+ ActionBarVisibilityAlwaysComponent,
+ ActionBarVisibilityNeverComponent,
+ ActionBarVisibilityAutoComponent,
+
+ TabItemBindingComponent,
+
+ ListComponent,
+ NgForComponent,
+ NgForOfComponent,
+ NgIfNoLayoutComponent,
+ NgIfInbetweenComponent,
+ NgIfElseComponent,
+ NgIfThenElseComponent,
+ NgIfSubsequent,
+ ContentViewComponent,
+];
+
+@NgModule({
+ imports: [ NativeScriptRouterModule.forRoot(routes) ],
+ exports: [ NativeScriptRouterModule ],
+})
+export class AppRoutingModule { }
+
diff --git a/e2e/renderer/app/app.component.ts b/e2e/renderer/app/app.component.ts
new file mode 100644
index 000000000..2311d63e0
--- /dev/null
+++ b/e2e/renderer/app/app.component.ts
@@ -0,0 +1,7 @@
+import { Component } from "@angular/core";
+
+@Component({
+ template: ``
+})
+export class AppComponent { }
+
diff --git a/e2e/renderer/app/app.css b/e2e/renderer/app/app.css
new file mode 100644
index 000000000..e69de29bb
diff --git a/e2e/renderer/app/app.module.ngfactory.d.ts b/e2e/renderer/app/app.module.ngfactory.d.ts
new file mode 100644
index 000000000..793157de3
--- /dev/null
+++ b/e2e/renderer/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/renderer/app/app.module.ts b/e2e/renderer/app/app.module.ts
new file mode 100644
index 000000000..3d8f3e5c1
--- /dev/null
+++ b/e2e/renderer/app/app.module.ts
@@ -0,0 +1,48 @@
+import { NgModule, NO_ERRORS_SCHEMA, ErrorHandler } from "@angular/core";
+import { NativeScriptModule } from "@nativescript/angular";
+
+import {
+ AppRoutingModule,
+ navigatableComponents,
+} from "./app-routing.module";
+
+import { AppComponent } from "./app.component";
+import { ItemsService } from "./items.service";
+
+import { rendererTraceCategory, viewUtilCategory, bootstrapCategory } from "@nativescript/angular/trace";
+import { addCategories, enable, categories } from "@nativescript/core/trace";
+import { SharedModule } from "./shared.module";
+
+addCategories(bootstrapCategory);
+addCategories(rendererTraceCategory);
+addCategories(viewUtilCategory);
+addCategories(categories.ViewHierarchy);
+enable();
+
+export class MyErrorHandler implements ErrorHandler {
+ handleError(error) {
+ console.log("### ErrorHandler Error: " + error.toString());
+ console.log("### ErrorHandler Stack: " + error.stack);
+ }
+}
+
+
+@NgModule({
+ declarations: [
+ AppComponent,
+ ...navigatableComponents,
+ ],
+ bootstrap: [AppComponent],
+ providers: [
+ ItemsService,
+ { provide: ErrorHandler, useClass: MyErrorHandler }
+ ],
+ imports: [
+ NativeScriptModule,
+ AppRoutingModule,
+ SharedModule
+ ],
+ schemas: [NO_ERRORS_SCHEMA],
+})
+export class AppModule {}
+
diff --git a/e2e/renderer/app/content-view.component.ts b/e2e/renderer/app/content-view.component.ts
new file mode 100644
index 000000000..a2aeb50ba
--- /dev/null
+++ b/e2e/renderer/app/content-view.component.ts
@@ -0,0 +1,30 @@
+import { Component } from "@angular/core";
+
+@Component({
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+})
+export class ContentViewComponent {
+ public show = true;
+
+ toggle() {
+ this.show = !this.show;
+ }
+}
+
diff --git a/e2e/renderer/app/items-accessor.ts b/e2e/renderer/app/items-accessor.ts
new file mode 100644
index 000000000..77659cc7a
--- /dev/null
+++ b/e2e/renderer/app/items-accessor.ts
@@ -0,0 +1,19 @@
+import { ItemsService } from "./items.service";
+
+export class ItemsAccessor {
+ public items: number[];
+
+ constructor(public itemsService: ItemsService) {
+ this.items = itemsService.getAll();
+ }
+
+ add() {
+ this.items = this.itemsService.add(this.items);
+ }
+
+ remove(item?: number) {
+ item = item || this.items[this.items.length - 1];
+ this.items = this.itemsService.remove(this.items, item);
+ }
+
+}
diff --git a/e2e/renderer/app/items.service.ts b/e2e/renderer/app/items.service.ts
new file mode 100644
index 000000000..94a33ef3f
--- /dev/null
+++ b/e2e/renderer/app/items.service.ts
@@ -0,0 +1,34 @@
+import { Injectable } from '@angular/core';
+
+@Injectable()
+export class ItemsService {
+ private items = [ 0 ];
+
+ getAll() {
+ return [...this.items];
+ }
+
+ add(items) {
+ return [
+ ...items,
+ items.length,
+ ];
+ }
+
+ remove(items, item?: number) {
+ const index = item ? items.indexOf(item) : items.length - 1;
+
+ return this.removeAt(items, index);
+ }
+
+ private removeAt(items: any[], index: number) {
+ items = [
+ ...items.slice(0, index),
+ ...items.slice(index + 1),
+ ];
+
+ console.log(`Removed ${index}th element`);
+
+ return items;
+ }
+}
diff --git a/e2e/renderer/app/list.component.ts b/e2e/renderer/app/list.component.ts
new file mode 100644
index 000000000..9996d5a79
--- /dev/null
+++ b/e2e/renderer/app/list.component.ts
@@ -0,0 +1,26 @@
+import { Component } from "@angular/core";
+
+@Component({
+ styles: ["Button { font-size: 10; margin: 0; padding: 0 }"],
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+})
+export class ListComponent {
+}
diff --git a/e2e/renderer/app/main.ts b/e2e/renderer/app/main.ts
new file mode 100644
index 000000000..1b1b3c521
--- /dev/null
+++ b/e2e/renderer/app/main.ts
@@ -0,0 +1,4 @@
+import { platformNativeScriptDynamic } from "@nativescript/angular/platform";
+import { AppModule } from "./app.module";
+
+platformNativeScriptDynamic().bootstrapModule(AppModule);
diff --git a/e2e/renderer/app/ngfor.component.ts b/e2e/renderer/app/ngfor.component.ts
new file mode 100644
index 000000000..a3a6238fc
--- /dev/null
+++ b/e2e/renderer/app/ngfor.component.ts
@@ -0,0 +1,21 @@
+import { Component } from "@angular/core";
+
+import { ItemsService } from "./items.service";
+import { ItemsAccessor } from "./items-accessor";
+
+@Component({
+ template: `
+
+
+
+
+
+
+ `
+})
+export class NgForComponent extends ItemsAccessor {
+ constructor(public itemsService: ItemsService) {
+ super(itemsService);
+ }
+}
+
diff --git a/e2e/renderer/app/ngforof.component.ts b/e2e/renderer/app/ngforof.component.ts
new file mode 100644
index 000000000..79ad8e3d9
--- /dev/null
+++ b/e2e/renderer/app/ngforof.component.ts
@@ -0,0 +1,25 @@
+import { Component } from "@angular/core";
+
+import { ItemsService } from "./items.service";
+import { ItemsAccessor } from "./items-accessor";
+
+@Component({
+ template: `
+
+
+
+
+
+
+
+
+
+
+ `
+})
+export class NgForOfComponent extends ItemsAccessor {
+ constructor(public itemsService: ItemsService) {
+ super(itemsService);
+ }
+}
+
diff --git a/e2e/renderer/app/ngif-inbetween.component.ts b/e2e/renderer/app/ngif-inbetween.component.ts
new file mode 100644
index 000000000..a8a644186
--- /dev/null
+++ b/e2e/renderer/app/ngif-inbetween.component.ts
@@ -0,0 +1,30 @@
+import { Component } from "@angular/core";
+
+@Component({
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+})
+export class NgIfInbetweenComponent {
+ public show = true;
+
+ toggle() {
+ this.show = !this.show;
+ }
+}
+
diff --git a/e2e/renderer/app/ngif-no-layout.component.ts b/e2e/renderer/app/ngif-no-layout.component.ts
new file mode 100644
index 000000000..087e14b55
--- /dev/null
+++ b/e2e/renderer/app/ngif-no-layout.component.ts
@@ -0,0 +1,24 @@
+import { Component } from "@angular/core";
+
+@Component({
+ template: `
+
+
+
+
+
+
+
+
+
+
+ `
+})
+export class NgIfNoLayoutComponent {
+ public show = false;
+
+ toggle() {
+ this.show = !this.show;
+ }
+}
+
diff --git a/e2e/renderer/app/ngif-subsequent.component.ts b/e2e/renderer/app/ngif-subsequent.component.ts
new file mode 100644
index 000000000..1e7125c95
--- /dev/null
+++ b/e2e/renderer/app/ngif-subsequent.component.ts
@@ -0,0 +1,23 @@
+import { Component} from "@angular/core";
+
+@Component({
+ moduleId: module.id,
+ selector: "renderer-test",
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+ `
+})
+export class NgIfSubsequent {
+ public first: boolean = false;
+ public second: boolean = false;
+}
diff --git a/e2e/renderer/app/ngif-then-else.component.ts b/e2e/renderer/app/ngif-then-else.component.ts
new file mode 100644
index 000000000..6d4113190
--- /dev/null
+++ b/e2e/renderer/app/ngif-then-else.component.ts
@@ -0,0 +1,33 @@
+import { Component } from "@angular/core";
+
+@Component({
+ selector: "ng-if-then-else",
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+})
+export class NgIfThenElseComponent {
+ public show: boolean = true;
+
+ toggle() {
+ this.show = !this.show;
+ }
+}
+
+
diff --git a/e2e/renderer/app/ngifelse.component.ts b/e2e/renderer/app/ngifelse.component.ts
new file mode 100644
index 000000000..645c0a201
--- /dev/null
+++ b/e2e/renderer/app/ngifelse.component.ts
@@ -0,0 +1,26 @@
+import { Component } from "@angular/core";
+
+@Component({
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+})
+export class NgIfElseComponent {
+ public show: boolean = true;
+
+ toggle() {
+ this.show = !this.show;
+ }
+}
+
diff --git a/e2e/renderer/app/package.json b/e2e/renderer/app/package.json
new file mode 100644
index 000000000..706d1a31b
--- /dev/null
+++ b/e2e/renderer/app/package.json
@@ -0,0 +1,9 @@
+{
+ "main": "main.js",
+ "name": "nativescript-template-ng-tutorial",
+ "version": "3.1.0",
+ "android": {
+ "v8Flags": "--expose_gc",
+ "markingMode": "none"
+ }
+}
\ No newline at end of file
diff --git a/e2e/renderer/app/page-router-outlet/action-bar-visibility-always.component.ts b/e2e/renderer/app/page-router-outlet/action-bar-visibility-always.component.ts
new file mode 100644
index 000000000..2887fac3d
--- /dev/null
+++ b/e2e/renderer/app/page-router-outlet/action-bar-visibility-always.component.ts
@@ -0,0 +1,26 @@
+import { Component, OnInit } from "@angular/core";
+import { RouterExtensions } from "@nativescript/angular/router";
+import { ActivatedRoute } from "@angular/router";
+
+@Component({
+ template: `
+
+
+
+
+
+
+
+
+ `
+})
+export class ActionBarVisibilityAlwaysComponent implements OnInit {
+ constructor(
+ private routerExtension: RouterExtensions,
+ private activeRoute: ActivatedRoute) {
+ }
+
+ ngOnInit(): void {
+ this.routerExtension.navigate([{outlets: { nested: ["nested"]}}], { relativeTo: this.activeRoute });
+ }
+}
diff --git a/e2e/renderer/app/page-router-outlet/action-bar-visibility-auto.component.ts b/e2e/renderer/app/page-router-outlet/action-bar-visibility-auto.component.ts
new file mode 100644
index 000000000..b06225f2e
--- /dev/null
+++ b/e2e/renderer/app/page-router-outlet/action-bar-visibility-auto.component.ts
@@ -0,0 +1,26 @@
+import { Component, OnInit } from "@angular/core";
+import { RouterExtensions } from "@nativescript/angular/router";
+import { ActivatedRoute } from "@angular/router";
+
+@Component({
+ template: `
+
+
+
+
+
+
+
+
+ `
+})
+export class ActionBarVisibilityAutoComponent implements OnInit {
+ constructor(
+ private routerExtension: RouterExtensions,
+ private activeRoute: ActivatedRoute) {
+ }
+
+ ngOnInit(): void {
+ this.routerExtension.navigate([{ outlets: { nested: ["nested"] } }], { relativeTo: this.activeRoute });
+ }
+}
diff --git a/e2e/renderer/app/page-router-outlet/action-bar-visibility-never.component.ts b/e2e/renderer/app/page-router-outlet/action-bar-visibility-never.component.ts
new file mode 100644
index 000000000..c0f7200ac
--- /dev/null
+++ b/e2e/renderer/app/page-router-outlet/action-bar-visibility-never.component.ts
@@ -0,0 +1,26 @@
+import { Component, OnInit } from "@angular/core";
+import { RouterExtensions } from "@nativescript/angular/router";
+import { ActivatedRoute } from "@angular/router";
+
+@Component({
+ template: `
+
+
+
+
+
+
+
+
+ `
+})
+export class ActionBarVisibilityNeverComponent implements OnInit {
+ constructor(
+ private routerExtension: RouterExtensions,
+ private activeRoute: ActivatedRoute) {
+ }
+
+ ngOnInit(): void {
+ this.routerExtension.navigate([{ outlets: { nested: ["nested"] } }], { relativeTo: this.activeRoute });
+ }
+}
diff --git a/e2e/renderer/app/page-router-outlet/nested-lazy-page.module.ts b/e2e/renderer/app/page-router-outlet/nested-lazy-page.module.ts
new file mode 100644
index 000000000..26d3ce76f
--- /dev/null
+++ b/e2e/renderer/app/page-router-outlet/nested-lazy-page.module.ts
@@ -0,0 +1,20 @@
+import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
+import { NativeScriptRouterModule } from "@nativescript/angular/router";
+import { NativeScriptCommonModule } from "@nativescript/angular/common";
+import { SharedModule } from "~/shared.module";
+import { NestedPageComponent } from "./nested-page.component";
+
+@NgModule({
+ imports: [
+ SharedModule,
+ NativeScriptCommonModule,
+ NativeScriptRouterModule,
+ NativeScriptRouterModule.forChild([
+ { path: "", component: NestedPageComponent }
+ ])
+ ],
+ providers: [
+ ],
+ schemas: [NO_ERRORS_SCHEMA]
+})
+export class NestedLazyPageModule { }
\ No newline at end of file
diff --git a/e2e/renderer/app/page-router-outlet/nested-page.component.ts b/e2e/renderer/app/page-router-outlet/nested-page.component.ts
new file mode 100644
index 000000000..fa7f81761
--- /dev/null
+++ b/e2e/renderer/app/page-router-outlet/nested-page.component.ts
@@ -0,0 +1,24 @@
+import { Component } from "@angular/core";
+
+@Component({
+ template: `
+
+
+
+
+
+
+
+
+
+ `
+})
+export class NestedPageComponent {
+ showActionBar(args): void {
+ args.object.page.actionBarHidden = false;
+ }
+
+ hideActionBar(args): void {
+ args.object.page.actionBarHidden = true;
+ }
+}
diff --git a/e2e/renderer/app/shared.module.ts b/e2e/renderer/app/shared.module.ts
new file mode 100644
index 000000000..d8e40bd32
--- /dev/null
+++ b/e2e/renderer/app/shared.module.ts
@@ -0,0 +1,14 @@
+import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
+import { NativeScriptRouterModule } from "@nativescript/angular/router";
+import { NativeScriptCommonModule } from "@nativescript/angular/common";
+import { NestedPageComponent } from "./page-router-outlet/nested-page.component";
+
+@NgModule({
+ imports: [
+ NativeScriptCommonModule,
+ NativeScriptRouterModule
+ ],
+ declarations:[NestedPageComponent],
+ schemas: [NO_ERRORS_SCHEMA]
+})
+export class SharedModule { }
\ No newline at end of file
diff --git a/e2e/renderer/app/tab-view/tab-item-binding.component.ts b/e2e/renderer/app/tab-view/tab-item-binding.component.ts
new file mode 100644
index 000000000..f99f45b7e
--- /dev/null
+++ b/e2e/renderer/app/tab-view/tab-item-binding.component.ts
@@ -0,0 +1,56 @@
+import { Component } from "@angular/core";
+import { isAndroid } from "@nativescript/core/platform";
+
+function getIconSource(icon: string): string {
+ const iconPrefix = isAndroid ? "res://" : "res://tabIcons/";
+
+ return iconPrefix + icon;
+}
+
+const notSelected = {
+ title: "Not Selected",
+ textTransform: "lowercase",
+ iconSource: getIconSource("home")
+};
+
+const selected = {
+ title: "Selected",
+ textTransform: "uppercase",
+ iconSource: getIconSource("browse")
+};
+
+@Component({
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+})
+export class TabItemBindingComponent {
+ public items = [
+ notSelected,
+ notSelected,
+ notSelected
+ ];
+
+ onIndexChange(args): void {
+ const selectedIndex = args.object.selectedIndex;
+
+ for (let i = 0; i < this.items.length; i++) {
+ this.items[i] = notSelected;
+ }
+
+ this.items[selectedIndex] = selected;
+ }
+}
+
diff --git a/e2e/renderer/e2e/action-bar.e2e-spec.ts b/e2e/renderer/e2e/action-bar.e2e-spec.ts
new file mode 100644
index 000000000..e4d34f011
--- /dev/null
+++ b/e2e/renderer/e2e/action-bar.e2e-spec.ts
@@ -0,0 +1,174 @@
+import {
+ AppiumDriver,
+ createDriver,
+ SearchOptions,
+ UIElement,
+ nsCapabilities
+} from "nativescript-dev-appium";
+import { isSauceLab } from "nativescript-dev-appium/lib/parser";
+
+const QUEUE_WAIT_TIME: number = 600000; // Sometimes SauceLabs threads are not available and the tests wait in a queue to start. Wait 10 min before timeout.
+const isSauceRun = isSauceLab;
+
+import { isOnTheLeft } from "./helpers/location";
+import { assert } from "chai";
+import { ImageOptions } from "nativescript-dev-appium/lib/image-options";
+
+describe("Action Bar scenario", async function () {
+ let driver: AppiumDriver;
+
+ before(async function () {
+ this.timeout(QUEUE_WAIT_TIME);
+ nsCapabilities.testReporter.context = this;
+ driver = await createDriver();
+ driver.imageHelper.defaultTolerance = 50;
+ driver.imageHelper.defaultToleranceType = ImageOptions.pixel;
+ await driver.driver.resetApp();
+ });
+
+ after(async function () {
+ if (isSauceRun) {
+ driver.sessionId().then(function (sessionId) {
+ console.log("Report https://saucelabs.com/beta/tests/" + sessionId);
+ });
+ }
+ await driver.quit();
+ console.log("Quit driver!");
+ });
+
+ afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logTestArtifacts(this.currentTest.title);
+ }
+ });
+
+
+ describe("dynamically add\\remove ActionItems", async function () {
+ let firstActionItem: UIElement;
+ let secondActionItem: UIElement;
+ let toggleFirstButton: UIElement;
+ let toggleSecondButton: UIElement;
+
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ });
+
+ it("should navigate to page", async function () {
+ const navigationButton =
+ await driver.findElementByAutomationText("ActionBar dynamic");
+ await navigationButton.click();
+
+ const actionBar =
+ await driver.findElementByAutomationText("Action Bar Dynamic Items");
+ });
+
+ it("should find elements", async function () {
+ firstActionItem = await driver.findElementByAutomationText("one");
+ secondActionItem = await driver.findElementByAutomationText("two");
+
+ toggleFirstButton = await driver.findElementByAutomationText("toggle 1");
+ toggleSecondButton = await driver.findElementByAutomationText("toggle 2");
+ });
+
+ it("should initially render the action items in the correct order", async function () {
+ await checkOrderIsCorrect();
+ });
+
+ it("should detach first element when its condition is false", done => {
+ (async function () {
+ await toggleFirst();
+
+ try {
+ await driver.findElementByAutomationText("one");
+ } catch (e) {
+ done();
+ }
+ })();
+ });
+
+ it("should attach first element in the correct position", async function () {
+ await toggleFirst();
+ await checkOrderIsCorrect();
+ });
+
+ it("should detach second element when its condition is false", done => {
+ (async function () {
+ await toggleSecond();
+
+ try {
+ await driver.findElementByAutomationText("two");
+ } catch (e) {
+ done();
+ }
+ })();
+ });
+
+ it("should attach second element in the correct position", async function () {
+ await toggleSecond();
+ await checkOrderIsCorrect();
+ });
+
+ it("should detach and then reattach both at correct places", async function () {
+ await toggleFirst();
+ await toggleSecond();
+
+ await toggleFirst();
+ await toggleSecond();
+
+ await checkOrderIsCorrect();
+ });
+
+ const checkOrderIsCorrect = async function () {
+ await isOnTheLeft(firstActionItem, secondActionItem);
+ };
+
+ const toggleFirst = async function () {
+ await toggleFirstButton.click();
+ };
+
+ const toggleSecond = async function () {
+ await toggleSecondButton.click();
+ };
+
+ });
+
+ describe("Action Bar extension with dynamic ActionItem", async function () {
+ let toggleButton: UIElement;
+ let conditional: UIElement;
+
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ await driver.driver.resetApp();
+ });
+
+ it("should navigate to page", async function () {
+ const navigationButton =
+ await driver.findElementByAutomationText("ActionBarExtension");
+ await navigationButton.click();
+ });
+
+ it("should find elements", async function () {
+ toggleButton = await driver.findElementByAutomationText("toggle");
+ conditional = await driver.findElementByAutomationText("conditional");
+ });
+
+ it("should detach conditional action item when its condition is false", async function () {
+ await toggle();
+ const conditionalBtn = await driver.waitForElement("conditional", 1000);
+ assert.isUndefined(conditionalBtn, "Conditional button should not be visible!");
+ });
+
+ it("should reattach conditional action item at correct place", async function () {
+ await toggle();
+ await checkOrderIsCorrect();
+ });
+
+ const checkOrderIsCorrect = async function () {
+ await isOnTheLeft(toggleButton, conditional);
+ };
+
+ const toggle = async function () {
+ await toggleButton.click();
+ };
+ });
+});
diff --git a/e2e/renderer/e2e/helpers/location.ts b/e2e/renderer/e2e/helpers/location.ts
new file mode 100644
index 000000000..9f2c07400
--- /dev/null
+++ b/e2e/renderer/e2e/helpers/location.ts
@@ -0,0 +1,19 @@
+import { assert } from "chai";
+import { UIElement } from "nativescript-dev-appium";
+
+export const isAbove = async (first: UIElement, second: UIElement) => {
+
+ const { y: firstY } = await first.location();
+ const { y: secondY } = await second.location();
+
+ assert.isTrue(firstY < secondY);
+}
+
+export const isOnTheLeft = async (first: UIElement, second: UIElement) => {
+
+ const { x: firstX } = await first.location();
+ const { x: secondX } = await second.location();
+
+ assert.isTrue(firstX < secondX);
+}
+
diff --git a/e2e/renderer/e2e/ngfor.e2e-spec.ts b/e2e/renderer/e2e/ngfor.e2e-spec.ts
new file mode 100644
index 000000000..a003f2c69
--- /dev/null
+++ b/e2e/renderer/e2e/ngfor.e2e-spec.ts
@@ -0,0 +1,143 @@
+import {
+ AppiumDriver,
+ createDriver,
+ SearchOptions,
+ UIElement,
+ nsCapabilities
+} from "nativescript-dev-appium";
+
+import { isAbove } from "./helpers/location";
+import { isSauceLab } from "nativescript-dev-appium/lib/parser";
+import { ImageOptions } from "nativescript-dev-appium/lib/image-options";
+
+const QUEUE_WAIT_TIME: number = 600000; // Sometimes SauceLabs threads are not available and the tests wait in a queue to start. Wait 10 min before timeout.
+const isSauceRun = isSauceLab;
+
+describe("ngFor scenario", async function () {
+ let driver: AppiumDriver;
+ let addButton: UIElement;
+ let removeButton: UIElement;
+ let elements: UIElement[] = [];
+ let lastAddedElementId = 0;
+
+ before(async function () {
+ this.timeout(QUEUE_WAIT_TIME);
+ nsCapabilities.testReporter.context = this;
+ driver = await createDriver();
+ driver.imageHelper.defaultTolerance = 50;
+ driver.imageHelper.defaultToleranceType = ImageOptions.pixel;
+ await driver.driver.resetApp();
+ });
+
+ after(async function () {
+ if (isSauceRun) {
+ driver.sessionId().then(function (sessionId) {
+ console.log("Report https://saucelabs.com/beta/tests/" + sessionId);
+ });
+ }
+ await driver.quit();
+ console.log("Quit driver!");
+ });
+
+ afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logTestArtifacts(this.currentTest.title);
+ }
+ });
+
+ it("should navigate to page", async function () {
+ const navigationButton =
+ await driver.findElementByAutomationText("NgFor");
+ await navigationButton.click();
+
+ const actionBar =
+ await driver.findElementByAutomationText("ngFor");
+ });
+
+ it("should find elements", async function () {
+ const first = await driver.findElementByAutomationText(
+ lastAddedElementId.toString());
+ elements.push(first);
+
+ addButton = await driver.findElementByAutomationText("add");
+ removeButton = await driver.findElementByAutomationText("remove");
+
+ await isAbove(first, addButton);
+ });
+
+ it("should render elements in correct order", async function () {
+ await isAbove(elements[0], addButton);
+ await isAbove(addButton, removeButton);
+ });
+
+ it("should place new elements in the right places", async function () {
+ for (let i = 0; i < 3; i += 1) {
+ await addElement();
+ await checkAppendedCorrectly();
+ }
+ });
+
+ it("shouldn't reorder elements when last is removed", async function () {
+ while (elements.length) {
+ await removeElement();
+ await checkCorrectOrderAll();
+ }
+ });
+
+ it("should render new elements correctly after all old ones are removed", async function () {
+ for (let i = 0; i < 3; i += 1) {
+ await addElement();
+ await checkCorrectOrderAll();
+ }
+ });
+
+ it("shouldn't reorder elements when middle is removed", async function () {
+ const middleIndex = Math.floor(elements.length / 2);
+ await removeElement(middleIndex);
+ await checkCorrectOrderAll();
+ });
+
+ const addElement = async function () {
+ await addButton.click();
+
+ lastAddedElementId += 1;
+ const newElement = await driver.findElementByAutomationText(
+ lastAddedElementId.toString());
+
+ elements.push(newElement);
+ };
+
+ const removeElement = async (index?: number) => {
+ index;
+ if (index) {
+ let element = await elements[index];
+ await element.click();
+ } else {
+ index = elements.length - 1;
+ await removeButton.click();
+ }
+
+ elements.splice(index, 1);
+ lastAddedElementId -= 1;
+ };
+
+ const checkAppendedCorrectly = async function () {
+ const lastAdded = await driver.findElementByAutomationText(
+ lastAddedElementId.toString());
+
+ await isAbove(elements.slice(-2)[0], lastAdded);
+ await isAbove(lastAdded, addButton);
+ await isAbove(addButton, removeButton);
+ };
+
+ const checkCorrectOrderAll = async function () {
+ for (let i = 0; i < elements.length - 1; i += 1) {
+ await isAbove(elements[i], elements[i + 1]);
+ }
+
+ if (elements.length) {
+ await isAbove(elements.slice(-1)[0], addButton);
+ }
+ await isAbove(addButton, removeButton);
+ };
+});
\ No newline at end of file
diff --git a/e2e/renderer/e2e/ngforof.e2e-spec.ts b/e2e/renderer/e2e/ngforof.e2e-spec.ts
new file mode 100644
index 000000000..2a8cc9eb6
--- /dev/null
+++ b/e2e/renderer/e2e/ngforof.e2e-spec.ts
@@ -0,0 +1,188 @@
+import {
+ AppiumDriver,
+ createDriver,
+ SearchOptions,
+ UIElement,
+ nsCapabilities
+} from "nativescript-dev-appium";
+
+import { isAbove } from "./helpers/location";
+import { isSauceLab } from "nativescript-dev-appium/lib/parser";
+import { ImageOptions } from "nativescript-dev-appium/lib/image-options";
+
+const QUEUE_WAIT_TIME: number = 600000; // Sometimes SauceLabs threads are not available and the tests wait in a queue to start. Wait 10 min before timeout.
+
+interface ElementTuple {
+ label: UIElement,
+ button: UIElement,
+}
+
+describe("ngForOf scenario", function () {
+ this.retries(2);
+ let driver: AppiumDriver;
+ let addButton: UIElement;
+ let removeButton: UIElement;
+ let elements: ElementTuple[] = [];
+ let lastAddedElementId = 0;
+
+ before(async function () {
+ this.timeout(QUEUE_WAIT_TIME);
+ nsCapabilities.testReporter.context = this;
+ driver = await createDriver();
+ driver.imageHelper.defaultTolerance = 50;
+ driver.imageHelper.defaultToleranceType = ImageOptions.pixel;
+ await driver.driver.resetApp();
+ });
+
+ after(async function () {
+ if (isSauceLab) {
+ driver.sessionId().then(function (sessionId) {
+ console.log("Report https://saucelabs.com/beta/tests/" + sessionId);
+ });
+ }
+ await driver.quit();
+ console.log("Quit driver!");
+ });
+
+ afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logTestArtifacts(this.currentTest.title);
+ }
+ });
+
+ it("should navigate to page", async function () {
+ const navigationButton =
+ await driver.findElementByAutomationText("NgForOf");
+ await navigationButton.click();
+
+ const actionBar =
+ await driver.findElementByAutomationText("ngForOf");
+ });
+
+ it("should find elements", async function () {
+ const firstElement = await getElement(lastAddedElementId);
+ elements.push(firstElement);
+
+ addButton = await driver.findElementByAutomationText("add");
+ removeButton = await driver.findElementByAutomationText("remove");
+
+ await elementTupleCorrectlyRendered(firstElement);
+ await isAbove(firstElement.button, addButton);
+ });
+
+ it("should render elements in correct order", async function () {
+ await elementTupleCorrectlyRendered(elements[0]);
+ await isAbove(elements[0].button, addButton);
+ await isAbove(addButton, removeButton);
+ });
+
+
+ it("should place new elements in the right places", async function () {
+ for (let i = 0; i < 2; i += 1) {
+ await addElement();
+ await checkAppendedCorrectly();
+ }
+ });
+
+ it("shouldn't reorder elements when last is removed", async function () {
+ while (elements.length) {
+ await removeElement();
+ await checkCorrectOrderAll();
+ }
+ });
+
+ it("should render new elements correctly after all old ones are removed", async function () {
+ for (let i = 0; i < 3; i += 1) {
+ await addElement();
+ await checkCorrectOrderAll();
+ }
+ });
+
+ it("shouldn't reorder elements when middle is removed", async function () {
+ const middleIndex = Math.floor(elements.length / 2);
+ await removeElement(middleIndex);
+ await checkCorrectOrderAll();
+ });
+
+
+ const addElement = async function () {
+ await addButton.click();
+
+ lastAddedElementId += 1;
+ const newElement = await getElement(lastAddedElementId);
+
+ elements.push(newElement);
+ };
+
+ const removeElement = async (index?: number) => {
+ if (index) {
+ let { button } = await elements[index];
+ await button.click();
+ } else {
+ index = elements.length - 1;
+ if (driver.platformName.toLowerCase().includes("ios")) {
+ await removeButton.tap();
+ } else {
+ await removeButton.click();
+ }
+ }
+
+ elements.splice(index, 1);
+ lastAddedElementId -= 1;
+ };
+
+ const checkAppendedCorrectly = async function () {
+ const lastAdded = await getElement(lastAddedElementId);
+
+ await elementIsAbove(elements.slice(-2)[0], lastAdded);
+ await isAbove(lastAdded.button, addButton);
+ await isAbove(addButton, removeButton);
+ };
+
+ const checkCorrectOrderAll = async function () {
+ for (let i = 0; i < elements.length - 1; i += 1) {
+ await elementIsAbove(elements[i], elements[i + 1]);
+ }
+
+ if (elements.length) {
+ const last = elements.slice(-1)[0];
+ await elementTupleCorrectlyRendered(last);
+ await isAbove(last.button, addButton);
+ }
+
+ await isAbove(addButton, removeButton);
+ };
+
+ const elementIsAbove = async (first: ElementTuple, second: ElementTuple) => {
+ await elementTupleCorrectlyRendered(first);
+ await elementTupleCorrectlyRendered(second);
+
+ await isAbove(first.button, second.label);
+ };
+
+ const elementTupleCorrectlyRendered = async (element: ElementTuple) => {
+ await isAbove(element.label, element.button);
+ };
+
+ const getElement = async (id: number) => {
+ let label = null;
+ let button = null;
+
+ if (driver.platformName.toLowerCase().includes("ios")) {
+ label = await driver.findElementByAccessibilityId(
+ "label: " + id.toString());
+
+ button = await driver.findElementByAccessibilityId(
+ id.toString());
+ } else {
+ label = await driver.findElementByAutomationText(
+ "label: " + id.toString());
+
+ button = await driver.findElementByAutomationText(
+ id.toString());
+ }
+
+ return { label, button };
+ };
+});
+
diff --git a/e2e/renderer/e2e/ngif.e2e-spec.ts b/e2e/renderer/e2e/ngif.e2e-spec.ts
new file mode 100644
index 000000000..48bdce7ed
--- /dev/null
+++ b/e2e/renderer/e2e/ngif.e2e-spec.ts
@@ -0,0 +1,396 @@
+import {
+ AppiumDriver,
+ createDriver,
+ SearchOptions,
+ UIElement,
+ nsCapabilities
+} from "nativescript-dev-appium";
+
+import { isAbove } from "./helpers/location";
+
+import { assert } from "chai";
+import { isSauceLab } from "nativescript-dev-appium/lib/parser";
+import { ImageOptions } from "nativescript-dev-appium/lib/image-options";
+
+const QUEUE_WAIT_TIME: number = 600000; // Sometimes SauceLabs threads are not available and the tests wait in a queue to start. Wait 10 min before timeout.
+
+
+describe("ngIf scenario", async function () {
+ let driver: AppiumDriver;
+ let toggleButton: UIElement;
+
+ before(async function () {
+ this.timeout(QUEUE_WAIT_TIME);
+ nsCapabilities.testReporter.context = this;
+ driver = await createDriver();
+ driver.imageHelper.defaultTolerance = 50;
+ driver.imageHelper.defaultToleranceType = ImageOptions.pixel;
+ await driver.driver.resetApp();
+ });
+
+ after(async function () {
+ if (isSauceLab) {
+ driver.sessionId().then(function (sessionId) {
+ console.log("Report https://saucelabs.com/beta/tests/" + sessionId);
+ });
+ }
+ await driver.quit();
+ console.log("Quit driver!");
+ });
+
+ afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logTestArtifacts(this.currentTest.title);
+ }
+ });
+
+ describe("without layout", async function () {
+ it("should navigate to page", async function () {
+ const navigationButton =
+ await driver.findElementByAutomationText("NgIf no layout");
+ await navigationButton.click();
+
+ const actionBar =
+ await driver.findElementByAutomationText("ngIf - no layout");
+ });
+
+ it("should find elements", async function () {
+ await driver.findElementByAutomationText("false");
+ toggleButton = await driver.findElementByAutomationText("Toggle");
+ });
+
+ it("show 'true' button when show is true", async function () {
+ await toggleButton.click();
+
+ await driver.findElementByAutomationText("true");
+ });
+ });
+
+ describe("label inbetween", async function () {
+ let firstButton: UIElement;
+ let secondButton: UIElement;
+ let conditionalLabel: UIElement;
+ let toggle: UIElement;
+
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ await driver.driver.resetApp();
+ });
+
+ it("should navigate to page", async function () {
+ const navigationButton =
+ await driver.findElementByAutomationText("NgIf inbetween");
+ await navigationButton.click();
+
+ const actionBar =
+ await driver.findElementByAutomationText("ngIf - inbetween");
+ });
+
+ it("should find elements", async function () {
+ firstButton = await driver.findElementByAutomationText("Button 1");
+ secondButton = await driver.findElementByAutomationText("Button 2");
+ toggleButton = await driver.findElementByAutomationText("Toggle");
+
+ conditionalLabel = await driver.findElementByAutomationText("Label");
+ const labelIsDisplayed = await conditionalLabel.isDisplayed();
+ assert.isTrue(labelIsDisplayed);
+ });
+
+ it("detach label when condition is false", done => {
+ (async function () {
+ await toggleButton.click();
+
+ try {
+ await driver.findElementByAutomationText("Label");
+ } catch (e) {
+ done();
+ }
+ })();
+ });
+ });
+
+ describe("with else template", async function () {
+ let ifButton: UIElement;
+ let elseButton: UIElement;
+ let toggle: UIElement;
+
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ await driver.driver.resetApp();
+ });
+
+ it("should navigate to page", async function () {
+ const navigationButton =
+ await driver.findElementByAutomationText("NgIfElse");
+ await navigationButton.click();
+
+ const actionBar =
+ await driver.findElementByAutomationText("ngIfElse");
+ });
+
+ it("should find elements", async function () {
+ toggleButton = await driver.findElementByAutomationText("Toggle");
+ ifButton = await driver.findElementByAutomationText("If");
+ });
+
+ it("shouldn't render 'else' template when condition is true", done => {
+ driver.findElementByAutomationText("Else", SearchOptions.exact)
+ .then(_ => { throw new Error("Else template found!"); })
+ .catch(() => done());
+ });
+
+ it("should attach 'else' template when condition is changed to false", async function () {
+ await toggleButton.click();
+
+ elseButton = await driver.findElementByAutomationText("Else");
+ });
+
+ it("should detach 'if' template when condition is changed to false", done => {
+ driver.findElementByAutomationText("If", SearchOptions.exact)
+ .then(_ => { throw new Error("If template found!"); })
+ .catch(() => done());
+ });
+
+ it("should swap the content when condition is changed", done => {
+ (async function () {
+ await toggleButton.click();
+
+ try {
+ await driver.findElementByAutomationText("Else");
+ } catch (e) {
+ done();
+ }
+ })();
+ });
+ });
+
+ describe("with then-else template", async function () {
+ let thenButton: UIElement;
+ let elseButton: UIElement;
+ let toggle: UIElement;
+
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ await driver.driver.resetApp();
+ });
+
+ it("should navigate to page", async function () {
+ const navigationButton =
+ await driver.findElementByAutomationText("NgIf Then Else");
+ await navigationButton.click();
+
+ const actionBar =
+ await driver.findElementByAutomationText("ngIf Then Else");
+ });
+
+ it("should find elements", async function () {
+ toggleButton = await driver.findElementByAutomationText("Toggle");
+ thenButton = await driver.findElementByAutomationText("Then");
+ });
+
+ it("shouldn't render 'else' template when condition is true", done => {
+ driver.findElementByAutomationText("Else", SearchOptions.exact)
+ .then(_ => { throw new Error("Else template found!"); })
+ .catch(() => done());
+ });
+
+ it("should attach 'else' template when condition is changed to false", async function () {
+ await toggleButton.click();
+
+ elseButton = await driver.findElementByAutomationText("Else");
+ });
+
+ it("should detach 'then' template when condition is changed to false", done => {
+ driver.findElementByAutomationText("Then", SearchOptions.exact)
+ .then(_ => { throw new Error("Then template found!"); })
+ .catch(() => done());
+ });
+
+ it("should swap the content when condition is changed", done => {
+ (async function () {
+ await toggleButton.click();
+
+ try {
+ await driver.findElementByAutomationText("Else");
+ } catch (e) {
+ done();
+ }
+ })();
+ });
+ });
+
+ describe("then-else templates inside content view", async function () {
+ let thenButton: UIElement;
+ let elseButton: UIElement;
+ let toggle: UIElement;
+
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ await driver.driver.resetApp();
+ });
+
+ it("should navigate to page", async function () {
+ const navigationButton =
+ await driver.findElementByAutomationText("Content view");
+ await navigationButton.click();
+
+ const actionBar =
+ await driver.findElementByAutomationText("Content View");
+ });
+
+ it("should find elements", async function () {
+ toggleButton = await driver.findElementByAutomationText("Toggle");
+ thenButton = await driver.findElementByAutomationText("Then");
+ });
+
+ it("shouldn't render 'else' template when condition is true", done => {
+ driver.findElementByAutomationText("Else", SearchOptions.exact)
+ .then(_ => { throw new Error("Else template found!"); })
+ .catch(() => done());
+ });
+
+ it("should attach 'else' template when condition is changed to false", async function () {
+ await toggleButton.click();
+
+ elseButton = await driver.findElementByAutomationText("Else");
+ });
+
+ it("should detach 'then' template when condition is changed to false", done => {
+ driver.findElementByAutomationText("Then", SearchOptions.exact)
+ .then(_ => { throw new Error("Then template found!"); })
+ .catch(() => done());
+ });
+
+ it("should swap the content when condition is changed", done => {
+ (async function () {
+ await toggleButton.click();
+
+ try {
+ await driver.findElementByAutomationText("Else");
+ } catch (e) {
+ done();
+ }
+ })();
+ });
+ });
+
+ describe("subsequent ifs", async function () {
+ let firstButton: UIElement;
+ let secondButton: UIElement;
+ let firstLabel: UIElement;
+ let secondLabel: UIElement;
+
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ await driver.driver.resetApp();
+ });
+
+ it("should navigate to page", async function () {
+ const navigationButton =
+ await driver.findElementByAutomationText("NgIf Subsequent Ifs");
+ await navigationButton.click();
+ });
+
+ it("should find elements", async function () {
+ firstButton = await driver.findElementByAutomationText("Toggle first");
+ secondButton = await driver.findElementByAutomationText("Toggle second");
+
+ firstLabel = await driver.findElementByAutomationText("== 1 ==");
+ secondLabel = await driver.findElementByAutomationText("== 2 ==");
+
+ assert.isDefined(firstButton);
+ assert.isDefined(secondButton);
+ assert.isDefined(firstLabel);
+ assert.isDefined(secondLabel);
+ });
+
+ it("should toggle on first view", async function () {
+ await firstButton.click();
+
+ let conditional = await driver.findElementByAutomationText("first");
+
+ await isAbove(firstLabel, conditional);
+ await isAbove(conditional, secondLabel);
+ });
+
+ it("should toggle off first view", done => {
+ (async function () {
+ await firstButton.click();
+
+ driver.findElementsByAutomationText("first", 500)
+ .then(_ => { throw new Error("first label found!"); })
+ .catch(() => done());
+ })();
+ });
+
+ it("should toggle on second view", async function () {
+ await secondButton.click();
+
+ let conditional = await driver.findElementByAutomationText("second");
+ await isAbove(firstLabel, conditional);
+ await isAbove(conditional, secondLabel);
+ });
+
+ it("should toggle off second view", done => {
+ (async function () {
+ await secondButton.click();
+
+ driver.findElementByAutomationText("first", 500)
+ .then(_ => { throw new Error("first label found!"); })
+ .catch(() => done());
+ })();
+ });
+
+ it("should toggle on both views", async function () {
+ await firstButton.click();
+ await secondButton.click();
+
+ let conditional1 = await driver.findElementByAutomationText("first");
+ let conditional2 = await driver.findElementByAutomationText("second");
+ await isAbove(firstLabel, conditional1);
+ await isAbove(conditional1, conditional2);
+ await isAbove(conditional2, secondLabel);
+ });
+
+ it("should toggle off both views", done => {
+ (async function () {
+ await firstButton.click();
+ await secondButton.click();
+
+ driver.findElementByAutomationText("first", 500)
+ .then(_ => { throw new Error("first label found!"); })
+ .catch(async function () {
+ driver.findElementByAutomationText("second", 500)
+ .then(_ => { throw new Error("second label found!"); })
+ .catch(() => done());
+ });
+ })();
+ });
+
+ it("should toggle on both views in reverse", async function () {
+ await secondButton.click();
+ await firstButton.click();
+
+ let conditional1 = await driver.findElementByAutomationText("first");
+ let conditional2 = await driver.findElementByAutomationText("second");
+ await isAbove(firstLabel, conditional1);
+ await isAbove(conditional1, conditional2);
+ await isAbove(conditional2, secondLabel);
+ });
+
+ it("should toggle off both views in reverse", done => {
+ (async function () {
+ await secondButton.click();
+ await firstButton.click();
+
+ driver.findElementByAutomationText("first", 500)
+ .then(_ => { throw new Error("first label found!"); })
+ .catch(async function () {
+ driver.findElementByAutomationText("second", 500)
+ .then(_ => { throw new Error("second label found!"); })
+ .catch(() => done());
+ });
+ })();
+ });
+ });
+});
diff --git a/e2e/renderer/e2e/page-router-outlet.e2e-spec.ts b/e2e/renderer/e2e/page-router-outlet.e2e-spec.ts
new file mode 100644
index 000000000..8adc9e9cf
--- /dev/null
+++ b/e2e/renderer/e2e/page-router-outlet.e2e-spec.ts
@@ -0,0 +1,191 @@
+import {
+ AppiumDriver,
+ createDriver,
+ SearchOptions,
+ UIElement,
+ nsCapabilities
+} from "nativescript-dev-appium";
+import { assert } from "chai";
+import { isSauceLab } from "nativescript-dev-appium/lib/parser";
+import { ImageOptions } from "nativescript-dev-appium/lib/image-options";
+
+const QUEUE_WAIT_TIME: number = 600000; // Sometimes SauceLabs threads are not available and the tests wait in a queue to start. Wait 10 min before timeout.
+
+describe("page-router-outlet-scenario", async function () {
+ let driver: AppiumDriver;
+
+ before(async function () {
+ this.timeout(QUEUE_WAIT_TIME);
+ nsCapabilities.testReporter.context = this;
+ driver = await createDriver();
+ driver.imageHelper.defaultTolerance = 50;
+ driver.imageHelper.defaultToleranceType = ImageOptions.pixel;
+ await driver.driver.resetApp();
+ });
+
+ after(async function () {
+ if (isSauceLab) {
+ driver.sessionId().then(function (sessionId) {
+ console.log("Report https://saucelabs.com/beta/tests/" + sessionId);
+ });
+ }
+ await driver.quit();
+ console.log("Quit driver!");
+ });
+
+ afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logTestArtifacts(this.currentTest.title);
+ }
+ });
+
+ describe("actionBarVisibility 'always' shows action bars", async function () {
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ });
+ it("should navigate to page", async function () {
+ const navigationButton =
+ await driver.findElementByAutomationText("ActionBarVisibility Always");
+ await navigationButton.click();
+
+ await driver.findElementByAutomationText("ShowActionBar");
+ });
+
+ it("should not hide action bar by default", async function () {
+ const screenMatches = await driver.compareScreen("actionBarVisibility-always-default", 5, 50, ImageOptions.pixel);
+ assert(screenMatches);
+ });
+
+ it("should not hide action bar when hidden by page", async function () {
+ const hideActionBarButton = await driver.findElementByAutomationText("HideActionBar");
+ await hideActionBarButton.click();
+
+ const screenMatches = await driver.compareScreen("actionBarVisibility-always-hidden", 5, 50, ImageOptions.pixel);
+ assert(screenMatches);
+ });
+
+ it("should not do anything when shown action bar by page", async function () {
+ const showActionBarButton = await driver.findElementByAutomationText("ShowActionBar");
+ await showActionBarButton.click();
+
+ const screenMatches = await driver.compareScreen("actionBarVisibility-always-shown", 5, 50, ImageOptions.pixel);
+ assert(screenMatches);
+ });
+ });
+
+ describe("actionBarVisibility 'never' doesn't show action bars", async function () {
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ await driver.driver.resetApp();
+ });
+
+ afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logTestArtifacts(this.currentTest.title);
+ }
+ });
+
+ it("should navigate to page", async function () {
+ const navigationButton =
+ await driver.findElementByAutomationText("ActionBarVisibility Never");
+ await navigationButton.click();
+
+ await driver.findElementByAutomationText("ShowActionBar");
+ });
+
+ it("should hide action bar by default", async function () {
+ const screenMatches = await driver.compareScreen("actionBarVisibility-never-default", 5);
+ assert(screenMatches);
+ });
+
+ it("should not show action bar when shown by page", async function () {
+ const showActionBarButton = await driver.findElementByAutomationText("ShowActionBar");
+ await showActionBarButton.click();
+
+ const screenMatches = await driver.compareScreen("actionBarVisibility-never-shown", 5);
+ assert(screenMatches);
+ });
+
+ it("should not do anything when hidden action bar by page", async function () {
+ const hideActionBarButton = await driver.findElementByAutomationText("HideActionBar");
+ await hideActionBarButton.click();
+
+ const screenMatches = await driver.compareScreen("actionBarVisibility-never-hidden", 5);
+ assert(screenMatches);
+ });
+ });
+
+ describe("actionBarVisibility 'never' doesn't show action bars in lazy module page", async function () {
+ let imagePostFix = "";
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ await driver.driver.resetApp();
+ if (driver.isIOS && driver.nsCapabilities.device.name.toLowerCase().includes("x")) {
+ imagePostFix = "-lazy";
+ }
+ });
+
+ it("should navigate to page", async function () {
+ const navigationButton =
+ await driver.findElementByAutomationText("ActionBarVisibility Never Lazy");
+ await navigationButton.click();
+
+ await driver.findElementByAutomationText("ShowActionBar");
+ });
+
+ it("should hide action bar by default", async function () {
+ const screenMatches = await driver.compareScreen(`actionBarVisibility-never-default${imagePostFix}`, 5);
+ assert(screenMatches);
+ });
+
+ it("should not show action bar when shown by page", async function () {
+ const showActionBarButton = await driver.findElementByAutomationText("ShowActionBar");
+ await showActionBarButton.click();
+
+ const screenMatches = await driver.compareScreen(`actionBarVisibility-never-shown${imagePostFix}`, 5);
+ assert(screenMatches);
+ });
+
+ it("should not do anything when hidden action bar by page", async function () {
+ const hideActionBarButton = await driver.findElementByAutomationText("HideActionBar");
+ await hideActionBarButton.click();
+
+ const screenMatches = await driver.compareScreen(`actionBarVisibility-never-hidden${imagePostFix}`, 5);
+ assert(screenMatches);
+ });
+ });
+
+ describe("actionBarVisibility 'auto' shows action bars based on page", async function () {
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ await driver.driver.resetApp();
+ });
+
+ it("should navigate to page", async function () {
+ const navigationButton =
+ await driver.findElementByAutomationText("ActionBarVisibility Auto");
+ await navigationButton.click();
+
+ await driver.findElementByAutomationText("ShowActionBar");
+ });
+
+ it("should show action bar by default", async function () {
+ const screenMatches = await driver.compareScreen("actionBarVisibility-auto-default", 5, 50, ImageOptions.pixel);
+ assert(screenMatches);
+ });
+
+ it("should hide action bar when hidden by page", async function () {
+ const hideActionBarButton = await driver.findElementByAutomationText("HideActionBar");
+ await hideActionBarButton.click();
+ const screenMatches = await driver.compareScreen("actionBarVisibility-auto-hidden", 5, 50, ImageOptions.pixel);
+ assert(screenMatches);
+ });
+
+ it("should show action bar when shown by page", async function () {
+ const showActionBarButton = await driver.findElementByAutomationText("ShowActionBar");
+ await showActionBarButton.click();
+ const screenMatches = await driver.compareScreen("actionBarVisibility-auto-shown", 5, 50, ImageOptions.pixel);
+ assert(screenMatches);
+ });
+ });
+});
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-always-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-always-default.png
new file mode 100644
index 000000000..af6e05907
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-always-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-always-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-always-hidden.png
new file mode 100644
index 000000000..af6e05907
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-always-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-always-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-always-shown.png
new file mode 100644
index 000000000..af6e05907
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-always-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-auto-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-auto-default.png
new file mode 100644
index 000000000..22ffb753d
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-auto-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-auto-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-auto-hidden.png
new file mode 100644
index 000000000..1820d9968
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-auto-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-auto-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-auto-shown.png
new file mode 100644
index 000000000..22ffb753d
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-auto-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-never-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-never-default.png
new file mode 100644
index 000000000..5e169fead
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-never-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-never-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-never-hidden.png
new file mode 100644
index 000000000..77157b59b
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-never-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-never-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-never-shown.png
new file mode 100644
index 000000000..77157b59b
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/actionBarVisibility-never-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/tab-view-binding-first-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/tab-view-binding-first-tab.png
new file mode 100644
index 000000000..dbde0ae87
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/tab-view-binding-first-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/tab-view-binding-second-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/tab-view-binding-second-tab.png
new file mode 100644
index 000000000..acf56dcb9
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/tab-view-binding-second-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/tab-view-binding-third-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/tab-view-binding-third-tab.png
new file mode 100644
index 000000000..bea843a8b
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api19-Default/tab-view-binding-third-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-always-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-always-default.png
new file mode 100644
index 000000000..96b65ee91
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-always-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-always-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-always-hidden.png
new file mode 100644
index 000000000..96b65ee91
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-always-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-always-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-always-shown.png
new file mode 100644
index 000000000..69387cb82
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-always-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-auto-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-auto-default.png
new file mode 100644
index 000000000..49b4436e5
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-auto-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-auto-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-auto-hidden.png
new file mode 100644
index 000000000..6a3cd82d5
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-auto-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-auto-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-auto-shown.png
new file mode 100644
index 000000000..85fca7cb7
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-auto-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-never-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-never-default.png
new file mode 100644
index 000000000..a2518a90d
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-never-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-never-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-never-hidden.png
new file mode 100644
index 000000000..3e918bcfe
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-never-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-never-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-never-shown.png
new file mode 100644
index 000000000..a2518a90d
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/actionBarVisibility-never-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/tab-view-binding-first-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/tab-view-binding-first-tab.png
new file mode 100644
index 000000000..87ffb5fcc
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/tab-view-binding-first-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/tab-view-binding-second-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/tab-view-binding-second-tab.png
new file mode 100644
index 000000000..46636cc03
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/tab-view-binding-second-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/tab-view-binding-third-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/tab-view-binding-third-tab.png
new file mode 100644
index 000000000..37cdd55e7
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Default/tab-view-binding-third-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-always-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-always-default.png
new file mode 100644
index 000000000..d992e38a5
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-always-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-always-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-always-hidden.png
new file mode 100644
index 000000000..b6ca95617
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-always-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-always-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-always-shown.png
new file mode 100644
index 000000000..b6ca95617
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-always-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-auto-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-auto-default.png
new file mode 100644
index 000000000..f63b5fe8d
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-auto-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-auto-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-auto-hidden.png
new file mode 100644
index 000000000..3b6875b90
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-auto-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-auto-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-auto-shown.png
new file mode 100644
index 000000000..413ddf8e5
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-auto-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-never-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-never-default.png
new file mode 100644
index 000000000..0f2fca64a
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-never-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-never-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-never-hidden.png
new file mode 100644
index 000000000..35816ac63
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-never-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-never-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-never-shown.png
new file mode 100644
index 000000000..d5393b063
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/actionBarVisibility-never-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/tab-view-binding-first-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/tab-view-binding-first-tab.png
new file mode 100644
index 000000000..54f6fd597
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/tab-view-binding-first-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/tab-view-binding-second-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/tab-view-binding-second-tab.png
new file mode 100644
index 000000000..8f3410ad4
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/tab-view-binding-second-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/tab-view-binding-third-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/tab-view-binding-third-tab.png
new file mode 100644
index 000000000..da64f2b00
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api22-Google/tab-view-binding-third-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-always-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-always-default.png
new file mode 100644
index 000000000..c25401fb3
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-always-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-always-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-always-hidden.png
new file mode 100644
index 000000000..c25401fb3
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-always-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-always-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-always-shown.png
new file mode 100644
index 000000000..c25401fb3
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-always-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-auto-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-auto-default.png
new file mode 100644
index 000000000..3bd8a4d64
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-auto-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-auto-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-auto-hidden.png
new file mode 100644
index 000000000..b3a3ad9fd
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-auto-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-auto-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-auto-shown.png
new file mode 100644
index 000000000..3bd8a4d64
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-auto-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-never-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-never-default.png
new file mode 100644
index 000000000..6bb8876e8
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-never-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-never-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-never-hidden.png
new file mode 100644
index 000000000..6bb8876e8
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-never-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-never-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-never-shown.png
new file mode 100644
index 000000000..6bb8876e8
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/actionBarVisibility-never-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/tab-view-binding-first-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/tab-view-binding-first-tab.png
new file mode 100644
index 000000000..c4f79265e
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/tab-view-binding-first-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/tab-view-binding-second-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/tab-view-binding-second-tab.png
new file mode 100644
index 000000000..c95e16540
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/tab-view-binding-second-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/tab-view-binding-third-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/tab-view-binding-third-tab.png
new file mode 100644
index 000000000..d1ebe07b0
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api23-Default/tab-view-binding-third-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-always-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-always-default.png
new file mode 100644
index 000000000..782f0f32c
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-always-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-always-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-always-hidden.png
new file mode 100644
index 000000000..782f0f32c
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-always-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-always-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-always-shown.png
new file mode 100644
index 000000000..1608b2c8b
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-always-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-auto-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-auto-default.png
new file mode 100644
index 000000000..e7a1e22ab
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-auto-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-auto-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-auto-hidden.png
new file mode 100644
index 000000000..e7a1e22ab
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-auto-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-auto-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-auto-shown.png
new file mode 100644
index 000000000..75b52701a
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-auto-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-never-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-never-default.png
new file mode 100644
index 000000000..8fd59b248
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-never-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-never-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-never-hidden.png
new file mode 100644
index 000000000..b8581a8bf
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-never-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-never-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-never-shown.png
new file mode 100644
index 000000000..8fd59b248
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/actionBarVisibility-never-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/tab-view-binding-first-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/tab-view-binding-first-tab.png
new file mode 100644
index 000000000..bfa18e232
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/tab-view-binding-first-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/tab-view-binding-second-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/tab-view-binding-second-tab.png
new file mode 100644
index 000000000..90083f434
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/tab-view-binding-second-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/tab-view-binding-third-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/tab-view-binding-third-tab.png
new file mode 100644
index 000000000..ff122eae4
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api24-Default/tab-view-binding-third-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-always-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-always-default.png
new file mode 100644
index 000000000..6a817111d
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-always-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-always-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-always-hidden.png
new file mode 100644
index 000000000..6a817111d
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-always-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-always-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-always-shown.png
new file mode 100644
index 000000000..cbbde7786
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-always-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-auto-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-auto-default.png
new file mode 100644
index 000000000..313a64685
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-auto-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-auto-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-auto-hidden.png
new file mode 100644
index 000000000..71f266277
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-auto-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-auto-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-auto-shown.png
new file mode 100644
index 000000000..313a64685
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-auto-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-never-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-never-default.png
new file mode 100644
index 000000000..def293798
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-never-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-never-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-never-hidden.png
new file mode 100644
index 000000000..79ad4bb71
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-never-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-never-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-never-shown.png
new file mode 100644
index 000000000..def293798
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/actionBarVisibility-never-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/tab-view-binding-first-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/tab-view-binding-first-tab.png
new file mode 100644
index 000000000..4904d61c6
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/tab-view-binding-first-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/tab-view-binding-second-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/tab-view-binding-second-tab.png
new file mode 100644
index 000000000..b4515ca00
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/tab-view-binding-second-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/tab-view-binding-third-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/tab-view-binding-third-tab.png
new file mode 100644
index 000000000..bfa8544c5
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api25-Google/tab-view-binding-third-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-always-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-always-default.png
new file mode 100644
index 000000000..f056bfa0d
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-always-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-always-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-always-hidden.png
new file mode 100644
index 000000000..f056bfa0d
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-always-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-always-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-always-shown.png
new file mode 100644
index 000000000..015a4398b
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-always-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-auto-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-auto-default.png
new file mode 100644
index 000000000..938b019c9
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-auto-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-auto-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-auto-hidden.png
new file mode 100644
index 000000000..b47cb9e45
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-auto-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-auto-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-auto-shown.png
new file mode 100644
index 000000000..938b019c9
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-auto-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-never-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-never-default.png
new file mode 100644
index 000000000..69c7c667c
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-never-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-never-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-never-hidden.png
new file mode 100644
index 000000000..3f2ee27fd
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-never-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-never-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-never-shown.png
new file mode 100644
index 000000000..69c7c667c
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/actionBarVisibility-never-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/tab-view-binding-first-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/tab-view-binding-first-tab.png
new file mode 100644
index 000000000..71686fb63
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/tab-view-binding-first-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/tab-view-binding-second-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/tab-view-binding-second-tab.png
new file mode 100644
index 000000000..af47cc7ed
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/tab-view-binding-second-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/tab-view-binding-third-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/tab-view-binding-third-tab.png
new file mode 100644
index 000000000..b8501b98e
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api26-Google/tab-view-binding-third-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-always-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-always-default.png
new file mode 100644
index 000000000..1ddb743a1
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-always-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-always-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-always-hidden.png
new file mode 100644
index 000000000..1ddb743a1
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-always-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-always-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-always-shown.png
new file mode 100644
index 000000000..b52ebf70a
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-always-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-auto-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-auto-default.png
new file mode 100644
index 000000000..ebea2dea8
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-auto-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-auto-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-auto-hidden.png
new file mode 100644
index 000000000..836da97a3
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-auto-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-auto-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-auto-shown.png
new file mode 100644
index 000000000..40cd6413a
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-auto-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-never-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-never-default.png
new file mode 100644
index 000000000..203e68d79
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-never-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-never-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-never-hidden.png
new file mode 100644
index 000000000..738e9dccc
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-never-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-never-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-never-shown.png
new file mode 100644
index 000000000..cd58d6bbe
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/actionBarVisibility-never-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/tab-view-binding-first-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/tab-view-binding-first-tab.png
new file mode 100644
index 000000000..cfe2421db
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/tab-view-binding-first-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/tab-view-binding-second-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/tab-view-binding-second-tab.png
new file mode 100644
index 000000000..a88ef58f3
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/tab-view-binding-second-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/tab-view-binding-third-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/tab-view-binding-third-tab.png
new file mode 100644
index 000000000..d97035d23
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api27-Google/tab-view-binding-third-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-always-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-always-default.png
new file mode 100644
index 000000000..b8deed4a0
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-always-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-always-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-always-hidden.png
new file mode 100644
index 000000000..b8deed4a0
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-always-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-always-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-always-shown.png
new file mode 100644
index 000000000..8cf735126
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-always-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-auto-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-auto-default.png
new file mode 100644
index 000000000..6d616d8fb
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-auto-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-auto-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-auto-hidden.png
new file mode 100644
index 000000000..410dcb8c3
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-auto-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-auto-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-auto-shown.png
new file mode 100644
index 000000000..6c4ad9dea
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-auto-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-never-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-never-default.png
new file mode 100644
index 000000000..1bbd6b2e1
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-never-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-never-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-never-hidden.png
new file mode 100644
index 000000000..cadcf67b6
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-never-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-never-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-never-shown.png
new file mode 100644
index 000000000..cadcf67b6
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/actionBarVisibility-never-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/tab-view-binding-first-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/tab-view-binding-first-tab.png
new file mode 100644
index 000000000..2a6479be2
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/tab-view-binding-first-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/tab-view-binding-second-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/tab-view-binding-second-tab.png
new file mode 100644
index 000000000..d5a33e4d4
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/tab-view-binding-second-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/tab-view-binding-third-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/tab-view-binding-third-tab.png
new file mode 100644
index 000000000..4432ba921
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api28-Google/tab-view-binding-third-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-always-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-always-default.png
new file mode 100644
index 000000000..6fcbd1b78
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-always-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-always-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-always-hidden.png
new file mode 100644
index 000000000..6fcbd1b78
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-always-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-always-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-always-shown.png
new file mode 100644
index 000000000..6fcbd1b78
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-always-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-auto-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-auto-default.png
new file mode 100644
index 000000000..9ac141c2f
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-auto-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-auto-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-auto-hidden.png
new file mode 100644
index 000000000..bee8777f6
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-auto-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-auto-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-auto-shown.png
new file mode 100644
index 000000000..9ac141c2f
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-auto-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-never-default.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-never-default.png
new file mode 100644
index 000000000..1f80e5739
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-never-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-never-hidden.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-never-hidden.png
new file mode 100644
index 000000000..1f80e5739
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-never-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-never-shown.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-never-shown.png
new file mode 100644
index 000000000..1f80e5739
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/actionBarVisibility-never-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/tab-view-binding-first-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/tab-view-binding-first-tab.png
new file mode 100644
index 000000000..f59d65640
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/tab-view-binding-first-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/tab-view-binding-second-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/tab-view-binding-second-tab.png
new file mode 100644
index 000000000..a60bc2cf2
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/tab-view-binding-second-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/tab-view-binding-third-tab.png b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/tab-view-binding-third-tab.png
new file mode 100644
index 000000000..559e2567c
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/Emulator-Api29-Google/tab-view-binding-third-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 100/tab-view-binding-first-tab.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 100/tab-view-binding-first-tab.png
new file mode 100644
index 000000000..86e41b580
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 100/tab-view-binding-first-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 100/tab-view-binding-second-tab.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 100/tab-view-binding-second-tab.png
new file mode 100644
index 000000000..888039781
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 100/tab-view-binding-second-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 100/tab-view-binding-third-tab.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 100/tab-view-binding-third-tab.png
new file mode 100644
index 000000000..266dbc621
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 100/tab-view-binding-third-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-always-default.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-always-default.png
new file mode 100644
index 000000000..f7cad6d50
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-always-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-always-hidden.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-always-hidden.png
new file mode 100644
index 000000000..f7cad6d50
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-always-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-always-shown.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-always-shown.png
new file mode 100644
index 000000000..f7cad6d50
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-always-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-auto-default.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-auto-default.png
new file mode 100644
index 000000000..c8509ceab
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-auto-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-auto-hidden.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-auto-hidden.png
new file mode 100644
index 000000000..ee4064e34
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-auto-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-auto-shown.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-auto-shown.png
new file mode 100644
index 000000000..33f839482
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-auto-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-never-default-lazy_actual.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-never-default-lazy_actual.png
new file mode 100644
index 000000000..ab50e264d
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-never-default-lazy_actual.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-never-default.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-never-default.png
new file mode 100644
index 000000000..ea4509395
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-never-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-never-hidden-lazy_actual.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-never-hidden-lazy_actual.png
new file mode 100644
index 000000000..afe670a4f
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-never-hidden-lazy_actual.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-never-hidden.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-never-hidden.png
new file mode 100644
index 000000000..ea4509395
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-never-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-never-shown-lazy_actual.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-never-shown-lazy_actual.png
new file mode 100644
index 000000000..afe670a4f
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-never-shown-lazy_actual.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-never-shown.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-never-shown.png
new file mode 100644
index 000000000..ea4509395
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/actionBarVisibility-never-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/tab-view-binding-first-tab.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/tab-view-binding-first-tab.png
new file mode 100644
index 000000000..fb8a587b7
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/tab-view-binding-first-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/tab-view-binding-second-tab.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/tab-view-binding-second-tab.png
new file mode 100644
index 000000000..b00d32eda
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/tab-view-binding-second-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/tab-view-binding-third-tab.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/tab-view-binding-third-tab.png
new file mode 100644
index 000000000..bb19588cc
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 110/tab-view-binding-third-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-always-default.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-always-default.png
new file mode 100644
index 000000000..81bf64f76
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-always-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-always-hidden.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-always-hidden.png
new file mode 100644
index 000000000..f0212faf3
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-always-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-always-shown.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-always-shown.png
new file mode 100644
index 000000000..f0212faf3
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-always-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-auto-default.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-auto-default.png
new file mode 100644
index 000000000..283f26ca8
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-auto-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-auto-hidden.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-auto-hidden.png
new file mode 100644
index 000000000..f20e481c0
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-auto-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-auto-shown.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-auto-shown.png
new file mode 100644
index 000000000..81b463120
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-auto-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-never-default.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-never-default.png
new file mode 100644
index 000000000..0b5d71b42
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-never-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-never-hidden.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-never-hidden.png
new file mode 100644
index 000000000..12af4ab42
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-never-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-never-shown.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-never-shown.png
new file mode 100644
index 000000000..12af4ab42
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/actionBarVisibility-never-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/tab-view-binding-first-tab.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/tab-view-binding-first-tab.png
new file mode 100644
index 000000000..a781c90f1
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/tab-view-binding-first-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/tab-view-binding-second-tab.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/tab-view-binding-second-tab.png
new file mode 100644
index 000000000..2a0c98ebf
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/tab-view-binding-second-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/tab-view-binding-third-tab.png b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/tab-view-binding-third-tab.png
new file mode 100644
index 000000000..ecd473fae
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone 7 12/tab-view-binding-third-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-always-default.png b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-always-default.png
new file mode 100644
index 000000000..8eb8f1870
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-always-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-always-hidden.png b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-always-hidden.png
new file mode 100644
index 000000000..8eb8f1870
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-always-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-always-shown.png b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-always-shown.png
new file mode 100644
index 000000000..8eb8f1870
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-always-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-auto-default.png b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-auto-default.png
new file mode 100644
index 000000000..44c48ceb9
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-auto-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-auto-hidden.png b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-auto-hidden.png
new file mode 100644
index 000000000..5e06beb38
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-auto-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-auto-shown.png b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-auto-shown.png
new file mode 100644
index 000000000..44c48ceb9
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-auto-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-never-default-lazy.png b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-never-default-lazy.png
new file mode 100644
index 000000000..9ebe2b597
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-never-default-lazy.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-never-default.png b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-never-default.png
new file mode 100644
index 000000000..99ebc13a7
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-never-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-never-hidden-lazy.png b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-never-hidden-lazy.png
new file mode 100644
index 000000000..6ed7234b1
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-never-hidden-lazy.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-never-hidden.png b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-never-hidden.png
new file mode 100644
index 000000000..a9efd56f9
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-never-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-never-shown-lazy.png b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-never-shown-lazy.png
new file mode 100644
index 000000000..6ed7234b1
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-never-shown-lazy.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-never-shown.png b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-never-shown.png
new file mode 100644
index 000000000..e434e2d18
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/actionBarVisibility-never-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/tab-view-binding-first-tab.png b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/tab-view-binding-first-tab.png
new file mode 100644
index 000000000..cf2353d74
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/tab-view-binding-first-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/tab-view-binding-second-tab.png b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/tab-view-binding-second-tab.png
new file mode 100644
index 000000000..c4a909467
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/tab-view-binding-second-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/tab-view-binding-third-tab.png b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/tab-view-binding-third-tab.png
new file mode 100644
index 000000000..469c82750
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone X 110/tab-view-binding-third-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-always-default.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-always-default.png
new file mode 100644
index 000000000..6724bef38
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-always-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-always-hidden.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-always-hidden.png
new file mode 100644
index 000000000..61c63323c
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-always-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-always-shown.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-always-shown.png
new file mode 100644
index 000000000..61c63323c
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-always-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-auto-default.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-auto-default.png
new file mode 100644
index 000000000..4482f1334
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-auto-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-auto-hidden.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-auto-hidden.png
new file mode 100644
index 000000000..306e7275e
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-auto-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-auto-shown.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-auto-shown.png
new file mode 100644
index 000000000..1fab9e6f3
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-auto-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-never-default-lazy.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-never-default-lazy.png
new file mode 100644
index 000000000..e6afdad26
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-never-default-lazy.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-never-default.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-never-default.png
new file mode 100644
index 000000000..666767c49
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-never-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-never-hidden-lazy.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-never-hidden-lazy.png
new file mode 100644
index 000000000..ac403f9f9
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-never-hidden-lazy.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-never-hidden.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-never-hidden.png
new file mode 100644
index 000000000..2c44f8ccd
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-never-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-never-shown-lazy.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-never-shown-lazy.png
new file mode 100644
index 000000000..ac403f9f9
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-never-shown-lazy.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-never-shown.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-never-shown.png
new file mode 100644
index 000000000..2c44f8ccd
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/actionBarVisibility-never-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/tab-view-binding-first-tab.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/tab-view-binding-first-tab.png
new file mode 100644
index 000000000..e13752607
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/tab-view-binding-first-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/tab-view-binding-second-tab.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/tab-view-binding-second-tab.png
new file mode 100644
index 000000000..1998b81c6
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/tab-view-binding-second-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/tab-view-binding-third-tab.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/tab-view-binding-third-tab.png
new file mode 100644
index 000000000..2297f4b97
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 12/tab-view-binding-third-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-always-default.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-always-default.png
new file mode 100644
index 000000000..0d641d88b
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-always-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-always-hidden.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-always-hidden.png
new file mode 100644
index 000000000..0d641d88b
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-always-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-always-shown.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-always-shown.png
new file mode 100644
index 000000000..0d641d88b
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-always-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-auto-default.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-auto-default.png
new file mode 100644
index 000000000..ed051c10f
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-auto-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-auto-hidden.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-auto-hidden.png
new file mode 100644
index 000000000..65f428e84
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-auto-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-auto-shown.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-auto-shown.png
new file mode 100644
index 000000000..ed051c10f
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-auto-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-never-default-lazy.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-never-default-lazy.png
new file mode 100644
index 000000000..6a61c9b25
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-never-default-lazy.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-never-default.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-never-default.png
new file mode 100644
index 000000000..74560afcb
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-never-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-never-hidden-lazy.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-never-hidden-lazy.png
new file mode 100644
index 000000000..6a61c9b25
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-never-hidden-lazy.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-never-hidden.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-never-hidden.png
new file mode 100644
index 000000000..74560afcb
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-never-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-never-shown-lazy.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-never-shown-lazy.png
new file mode 100644
index 000000000..6a61c9b25
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-never-shown-lazy.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-never-shown.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-never-shown.png
new file mode 100644
index 000000000..74560afcb
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/actionBarVisibility-never-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/tab-view-binding-first-tab.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/tab-view-binding-first-tab.png
new file mode 100644
index 000000000..714d335fa
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/tab-view-binding-first-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/tab-view-binding-second-tab.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/tab-view-binding-second-tab.png
new file mode 100644
index 000000000..70b0ab1fb
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/tab-view-binding-second-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/tab-view-binding-third-tab.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/tab-view-binding-third-tab.png
new file mode 100644
index 000000000..4ea08ec99
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XR 13/tab-view-binding-third-tab.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-always-default.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-always-default.png
new file mode 100644
index 000000000..bf236a332
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-always-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-always-hidden.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-always-hidden.png
new file mode 100644
index 000000000..36532a957
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-always-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-always-shown.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-always-shown.png
new file mode 100644
index 000000000..67a2d2515
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-always-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-auto-default.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-auto-default.png
new file mode 100644
index 000000000..2db5cadce
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-auto-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-auto-hidden.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-auto-hidden.png
new file mode 100644
index 000000000..c31877f38
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-auto-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-auto-shown.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-auto-shown.png
new file mode 100644
index 000000000..e547d9e3a
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-auto-shown.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-never-default-lazy.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-never-default-lazy.png
new file mode 100644
index 000000000..54f0ffdd4
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-never-default-lazy.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-never-default.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-never-default.png
new file mode 100644
index 000000000..f22f58278
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-never-default.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-never-hidden-lazy.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-never-hidden-lazy.png
new file mode 100644
index 000000000..d3b34b6ab
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-never-hidden-lazy.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-never-hidden.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-never-hidden.png
new file mode 100644
index 000000000..e3059e1ed
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-never-hidden.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-never-shown-lazy.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-never-shown-lazy.png
new file mode 100644
index 000000000..d3b34b6ab
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-never-shown-lazy.png differ
diff --git a/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-never-shown.png b/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-never-shown.png
new file mode 100644
index 000000000..11225f72b
Binary files /dev/null and b/e2e/renderer/e2e/resources/images/renderer/iPhone XS 12/actionBarVisibility-never-shown.png differ
diff --git a/e2e/renderer/e2e/setup.ts b/e2e/renderer/e2e/setup.ts
new file mode 100644
index 000000000..11b854fca
--- /dev/null
+++ b/e2e/renderer/e2e/setup.ts
@@ -0,0 +1,19 @@
+import { startServer, stopServer, ITestReporter, nsCapabilities, LogImageType } from "nativescript-dev-appium";
+const addContext = require('mochawesome/addContext');
+
+const testReporterContext = {};
+testReporterContext.name = "mochawesome";
+testReporterContext.reportDir = "mochawesome-report";
+testReporterContext.log = addContext;
+testReporterContext.logImageTypes = [LogImageType.screenshots];
+nsCapabilities.testReporter = testReporterContext;
+
+before("start server", async function () {
+ nsCapabilities.testReporter.context = this;
+ await startServer();
+});
+
+after("stop server", async function () {
+ nsCapabilities.testReporter.context = this;
+ await stopServer();
+});
diff --git a/e2e/renderer/e2e/tab-view.e2e-spec.ts b/e2e/renderer/e2e/tab-view.e2e-spec.ts
new file mode 100644
index 000000000..93950c6a6
--- /dev/null
+++ b/e2e/renderer/e2e/tab-view.e2e-spec.ts
@@ -0,0 +1,98 @@
+import {
+ AppiumDriver,
+ createDriver,
+ SearchOptions,
+ UIElement,
+ nsCapabilities
+} from "nativescript-dev-appium";
+import { assert } from "chai";
+import { isSauceLab } from "nativescript-dev-appium/lib/parser";
+import { ImageOptions } from "nativescript-dev-appium/lib/image-options";
+
+const QUEUE_WAIT_TIME: number = 600000; // Sometimes SauceLabs threads are not available and the tests wait in a queue to start. Wait 10 min before timeout.
+
+describe("TabView-scenario", async function(){
+ let driver: AppiumDriver;
+
+ before(async function(){
+ this.timeout(QUEUE_WAIT_TIME);
+ nsCapabilities.testReporter.context = this;
+ driver = await createDriver();
+ driver.imageHelper.defaultTolerance = 50;
+ driver.imageHelper.defaultToleranceType = ImageOptions.pixel;
+ await driver.driver.resetApp();
+ });
+
+ after(async function () {
+ if (isSauceLab) {
+ driver.sessionId().then(function (sessionId) {
+ console.log("Report https://saucelabs.com/beta/tests/" + sessionId);
+ });
+ }
+ await driver.quit();
+ console.log("Quit driver!");
+ });
+
+ afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logTestArtifacts(this.currentTest.title);
+ }
+ });
+
+ describe("dynamically change TabView item title, icon and textTransform", async function(){
+ let firstTabItem: UIElement;
+ let secondTabItem: UIElement;
+ let thirdTabItem: UIElement;
+
+ it("should navigate to page", async function(){
+ const navigationButton =
+ await driver.findElementByAutomationText("TabItem Binding");
+ await navigationButton.click();
+
+ await driver.findElementByAutomationText("Tab Item Binding");
+ });
+
+ it("should find elements", async function(){
+ await driver.findElementByAutomationText("First Tab");
+
+ const notSelectedTabItems = await driver.findElementsByText("not selected");
+
+ firstTabItem = await driver.findElementByAutomationText("SELECTED");
+ secondTabItem = notSelectedTabItems[0];
+ thirdTabItem = notSelectedTabItems[1];
+
+ const screenMatches = await driver.compareScreen("tab-view-binding-first-tab", 5, 50, ImageOptions.pixel);
+ assert(screenMatches);
+ });
+
+ it("should navigate to second tab item", async function(){
+ await secondTabItem.click();
+
+ await driver.findElementByAutomationText("Second Tab");
+
+ const notSelectedTabItems = await driver.findElementsByText("not selected");
+
+ firstTabItem = notSelectedTabItems[0];
+ secondTabItem = await driver.findElementByAutomationText("SELECTED");
+ thirdTabItem = notSelectedTabItems[1];
+
+ const screenMatches = await driver.compareScreen("tab-view-binding-second-tab", 5, 50, ImageOptions.pixel);
+ assert(screenMatches);
+ });
+
+ it("should navigate to third tab item", async function(){
+ await thirdTabItem.click();
+
+ await driver.findElementByAutomationText("Third Tab");
+
+ const notSelectedTabItems = await driver.findElementsByText("not selected");
+
+ firstTabItem = notSelectedTabItems[0];
+ secondTabItem = notSelectedTabItems[1];
+ thirdTabItem = await driver.findElementByAutomationText("SELECTED");
+
+ const screenMatches = await driver.compareScreen("tab-view-binding-third-tab", 5, 50, ImageOptions.pixel);
+ assert(screenMatches);
+ });
+ });
+});
diff --git a/e2e/renderer/e2e/tsconfig.json b/e2e/renderer/e2e/tsconfig.json
new file mode 100644
index 000000000..6517ca58d
--- /dev/null
+++ b/e2e/renderer/e2e/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es6",
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "importHelpers": false,
+ "sourceMap": true,
+ "types": [
+ "node",
+ "mocha",
+ "chai"
+ ],
+ "lib": [
+ "es2015",
+ "dom"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/e2e/renderer/package.json b/e2e/renderer/package.json
new file mode 100644
index 000000000..3126f8f2e
--- /dev/null
+++ b/e2e/renderer/package.json
@@ -0,0 +1,46 @@
+{
+ "description": "NativeScript Application",
+ "license": "SEE LICENSE IN ",
+ "readme": "NativeScript Application",
+ "repository": "",
+ "nativescript": {
+ "id": "org.nativescript.renderer"
+ },
+ "dependencies": {
+ "@angular/animations": "~9.1.0",
+ "@angular/common": "~9.1.0",
+ "@angular/compiler": "~9.1.0",
+ "@angular/core": "~9.1.0",
+ "@angular/forms": "~9.1.0",
+ "@angular/platform-browser": "~9.1.0",
+ "@angular/platform-browser-dynamic": "~9.1.0",
+ "@angular/router": "~9.1.0",
+ "@nativescript/angular": "file:../../dist/nativescript-angular-scoped.tgz",
+ "nativescript-theme-core": "~1.0.4",
+ "reflect-metadata": "~0.1.8",
+ "rxjs": "~6.5.5",
+ "@nativescript/core": "next",
+ "zone.js": "^0.10.3"
+ },
+ "devDependencies": {
+ "@angular/compiler-cli": "~9.1.0",
+ "@ngtools/webpack": "~9.1.0",
+ "@types/chai": "~4.1.7",
+ "@types/mocha": "~5.2.5",
+ "@types/node": "~10.12.18",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0",
+ "babylon": "6.18.0",
+ "chai": "^4.2.0",
+ "lazy": "1.0.11",
+ "mocha": "~5.2.0",
+ "mochawesome": "~3.1.2",
+ "nativescript-dev-appium": "^6.0.0",
+ "nativescript-dev-webpack": "next",
+ "typescript": "~3.8.3"
+ },
+ "scripts": {
+ "e2e": "tsc -p e2e && mocha --opts ../config/mocha.opts --recursive e2e --appiumCapsLocation ../config/appium.capabilities.json",
+ "compile-tests-w": "tsc -p e2e --watch"
+ }
+}
diff --git a/e2e/renderer/tsconfig.json b/e2e/renderer/tsconfig.json
new file mode 100644
index 000000000..a3ba8b51a
--- /dev/null
+++ b/e2e/renderer/tsconfig.json
@@ -0,0 +1,40 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es5",
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "noEmitHelpers": true,
+ "noEmitOnError": true,
+ "skipLibCheck": true,
+ "lib": [
+ "es2017",
+ "dom",
+ "es6"
+ ],
+ "baseUrl": ".",
+ "paths": {
+ "~/*": [
+ "app/*"
+ ],
+ "*": [
+ "./node_modules/*"
+ ]
+ }
+ },
+ "include": [
+ "../../nativescript-angular-package",
+ "../../nativescript-angular",
+ "**/*"
+ ],
+ "exclude": [
+ "../../nativescript-angular-package/node_modules",
+ "../../nativescript-angular-package/**/*.d.ts",
+ "../../nativescript-angular/node_modules",
+ "../../nativescript-angular/**/*.d.ts",
+ "node_modules",
+ "platforms",
+ "**/*.aot",
+ "e2e"
+ ]
+}
\ No newline at end of file
diff --git a/e2e/routable-animations/.gitignore b/e2e/routable-animations/.gitignore
new file mode 100644
index 000000000..4b67c8c63
--- /dev/null
+++ b/e2e/routable-animations/.gitignore
@@ -0,0 +1,19 @@
+**/.DS_Store
+
+hooks
+node_modules
+platforms
+
+app/**/*.js
+e2e/**/*.js
+e2e/**/*.map
+e2e/reports/
+test-results.xml
+
+# Webpack files
+tsconfig.esm.json
+webpack.config.js
+
+# Tests
+/**/mochawesome-report
+
diff --git a/e2e/routable-animations/.vscode/launch.json b/e2e/routable-animations/.vscode/launch.json
new file mode 100644
index 000000000..0a26473cc
--- /dev/null
+++ b/e2e/routable-animations/.vscode/launch.json
@@ -0,0 +1,60 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "node",
+ "request": "launch",
+ "name": "Mocha Tests",
+ "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
+ "args": [
+ "-u",
+ "tdd",
+ "--timeout",
+ "999999",
+ "--colors",
+ "--opts", "./e2e/config/mocha.opts",
+ "--runType", "sim.iPhone8",
+ "--attachToDebug",
+ "--port",
+ "8300"
+ ],
+ "internalConsoleOptions": "openOnSessionStart"
+ },
+ {
+ "name": "Launch on iOS",
+ "type": "nativescript",
+ "request": "launch",
+ "platform": "ios",
+ "appRoot": "${workspaceRoot}",
+ "sourceMaps": true,
+ "watch": true
+ },
+ {
+ "name": "Attach on iOS",
+ "type": "nativescript",
+ "request": "attach",
+ "platform": "ios",
+ "appRoot": "${workspaceRoot}",
+ "sourceMaps": true,
+ "watch": false
+ },
+ {
+ "name": "Launch on Android",
+ "type": "nativescript",
+ "request": "launch",
+ "platform": "android",
+ "appRoot": "${workspaceRoot}",
+ "sourceMaps": true,
+ "watch": true
+ },
+ {
+ "name": "Attach on Android",
+ "type": "nativescript",
+ "request": "attach",
+ "platform": "android",
+ "appRoot": "${workspaceRoot}",
+ "sourceMaps": true,
+ "watch": false
+ }
+ ]
+}
\ No newline at end of file
diff --git a/e2e/routable-animations/README.md b/e2e/routable-animations/README.md
new file mode 100644
index 000000000..a52a3d621
--- /dev/null
+++ b/e2e/routable-animations/README.md
@@ -0,0 +1,3 @@
+# NativeScript Routable Animations
+
+A NativeScript Angular applications showcasing [angular routable animations](https://www.yearofmoo.com/2017/06/new-wave-of-animation-features.html#routable-animations).
\ No newline at end of file
diff --git a/e2e/routable-animations/app/App_Resources/Android/app.gradle b/e2e/routable-animations/app/App_Resources/Android/app.gradle
new file mode 100644
index 000000000..c714809c9
--- /dev/null
+++ b/e2e/routable-animations/app/App_Resources/Android/app.gradle
@@ -0,0 +1,23 @@
+// Add your native dependencies here:
+
+// Uncomment to add recyclerview-v7 dependency
+//dependencies {
+// compile 'com.android.support:recyclerview-v7:+'
+//}
+
+android {
+ defaultConfig {
+ generatedDensities = []
+ applicationId = "org.nativescript.nsroanimations"
+
+ //override supported platforms
+ // ndk {
+ // abiFilters.clear()
+ // abiFilters "armeabi-v7a"
+ // }
+
+ }
+ aaptOptions {
+ additionalParameters "--no-version-vectors"
+ }
+}
diff --git a/e2e/routable-animations/app/App_Resources/Android/src/main/AndroidManifest.xml b/e2e/routable-animations/app/App_Resources/Android/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..9db832151
--- /dev/null
+++ b/e2e/routable-animations/app/App_Resources/Android/src/main/AndroidManifest.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-hdpi/background.png b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-hdpi/background.png
new file mode 100644
index 000000000..eb381c258
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-hdpi/background.png differ
diff --git a/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-hdpi/icon.png b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-hdpi/icon.png
new file mode 100755
index 000000000..1034356e2
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-hdpi/icon.png differ
diff --git a/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-hdpi/logo.png b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-hdpi/logo.png
new file mode 100644
index 000000000..5218f4c90
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-hdpi/logo.png differ
diff --git a/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-ldpi/background.png b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-ldpi/background.png
new file mode 100644
index 000000000..748b2adf5
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-ldpi/background.png differ
diff --git a/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-ldpi/icon.png b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-ldpi/icon.png
new file mode 100755
index 000000000..ddfc17a71
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-ldpi/icon.png differ
diff --git a/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-ldpi/logo.png b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-ldpi/logo.png
new file mode 100644
index 000000000..b9e102a76
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-ldpi/logo.png differ
diff --git a/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-mdpi/background.png b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-mdpi/background.png
new file mode 100644
index 000000000..efeaf2907
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-mdpi/background.png differ
diff --git a/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-mdpi/icon.png b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-mdpi/icon.png
new file mode 100755
index 000000000..486e41091
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-mdpi/icon.png differ
diff --git a/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-mdpi/logo.png b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-mdpi/logo.png
new file mode 100644
index 000000000..626338766
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-mdpi/logo.png differ
diff --git a/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml
new file mode 100644
index 000000000..ada77f92c
--- /dev/null
+++ b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml
@@ -0,0 +1,8 @@
+
+ -
+
+
+ -
+
+
+
\ No newline at end of file
diff --git a/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xhdpi/background.png b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xhdpi/background.png
new file mode 100644
index 000000000..612bbd072
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xhdpi/background.png differ
diff --git a/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xhdpi/icon.png b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xhdpi/icon.png
new file mode 100644
index 000000000..f29188209
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xhdpi/icon.png differ
diff --git a/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xhdpi/logo.png b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xhdpi/logo.png
new file mode 100644
index 000000000..ad8ee2f4b
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xhdpi/logo.png differ
diff --git a/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png
new file mode 100644
index 000000000..0fa88e235
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png differ
diff --git a/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xxhdpi/icon.png b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xxhdpi/icon.png
new file mode 100644
index 000000000..4f69cb25b
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xxhdpi/icon.png differ
diff --git a/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xxhdpi/logo.png b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xxhdpi/logo.png
new file mode 100644
index 000000000..668327832
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xxhdpi/logo.png differ
diff --git a/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png
new file mode 100644
index 000000000..c650f6438
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png differ
diff --git a/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/icon.png b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/icon.png
new file mode 100644
index 000000000..50887a856
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/icon.png differ
diff --git a/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/logo.png b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/logo.png
new file mode 100644
index 000000000..fa6331c8d
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/logo.png differ
diff --git a/e2e/routable-animations/app/App_Resources/Android/src/main/res/values-v21/colors.xml b/e2e/routable-animations/app/App_Resources/Android/src/main/res/values-v21/colors.xml
new file mode 100644
index 000000000..a64641a9d
--- /dev/null
+++ b/e2e/routable-animations/app/App_Resources/Android/src/main/res/values-v21/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #3d5afe
+
\ No newline at end of file
diff --git a/e2e/routable-animations/app/App_Resources/Android/src/main/res/values-v21/styles.xml b/e2e/routable-animations/app/App_Resources/Android/src/main/res/values-v21/styles.xml
new file mode 100644
index 000000000..dac8727c8
--- /dev/null
+++ b/e2e/routable-animations/app/App_Resources/Android/src/main/res/values-v21/styles.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/routable-animations/app/App_Resources/Android/src/main/res/values/colors.xml b/e2e/routable-animations/app/App_Resources/Android/src/main/res/values/colors.xml
new file mode 100644
index 000000000..74ad8829c
--- /dev/null
+++ b/e2e/routable-animations/app/App_Resources/Android/src/main/res/values/colors.xml
@@ -0,0 +1,7 @@
+
+
+ #F5F5F5
+ #757575
+ #33B5E5
+ #272734
+
\ No newline at end of file
diff --git a/e2e/routable-animations/app/App_Resources/Android/src/main/res/values/styles.xml b/e2e/routable-animations/app/App_Resources/Android/src/main/res/values/styles.xml
new file mode 100644
index 000000000..1e8c7f29b
--- /dev/null
+++ b/e2e/routable-animations/app/App_Resources/Android/src/main/res/values/styles.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000..1953734f4
--- /dev/null
+++ b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,92 @@
+{
+ "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"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png
new file mode 100644
index 000000000..9e15af09d
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png differ
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png
new file mode 100644
index 000000000..7b9e55537
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png differ
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png
new file mode 100644
index 000000000..76f61ec1f
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png differ
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png
new file mode 100644
index 000000000..15b06db11
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png differ
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png
new file mode 100644
index 000000000..585065f94
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png differ
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png
new file mode 100644
index 000000000..a450c421d
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png differ
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png
new file mode 100644
index 000000000..457b6d94c
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png differ
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png
new file mode 100644
index 000000000..fa5a6ac86
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png differ
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png
new file mode 100644
index 000000000..94abcf70d
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png differ
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png
new file mode 100644
index 000000000..2e71dd3a0
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png differ
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png
new file mode 100644
index 000000000..4abc9ec50
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png differ
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/Contents.json b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/Contents.json
new file mode 100644
index 000000000..da4a164c9
--- /dev/null
+++ b/e2e/routable-animations/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/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
new file mode 100644
index 000000000..4414bad08
--- /dev/null
+++ b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
@@ -0,0 +1,158 @@
+{
+ "images" : [
+ {
+ "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/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png b/e2e/routable-animations/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/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png differ
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png b/e2e/routable-animations/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/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png differ
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png b/e2e/routable-animations/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/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png differ
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png b/e2e/routable-animations/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/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png differ
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png b/e2e/routable-animations/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/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png differ
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png b/e2e/routable-animations/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/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png differ
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png b/e2e/routable-animations/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/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png differ
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png b/e2e/routable-animations/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/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png differ
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png
new file mode 100644
index 000000000..9f1f6ce3e
Binary files /dev/null and b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png differ
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png b/e2e/routable-animations/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/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png differ
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json
new file mode 100644
index 000000000..4f4e9c506
--- /dev/null
+++ b/e2e/routable-animations/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/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png b/e2e/routable-animations/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/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png differ
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png b/e2e/routable-animations/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/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png differ
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json b/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json
new file mode 100644
index 000000000..23c0ffd7a
--- /dev/null
+++ b/e2e/routable-animations/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/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png b/e2e/routable-animations/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/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png differ
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png b/e2e/routable-animations/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/routable-animations/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png differ
diff --git a/e2e/routable-animations/app/App_Resources/iOS/Info.plist b/e2e/routable-animations/app/App_Resources/iOS/Info.plist
new file mode 100644
index 000000000..ea3e3ea23
--- /dev/null
+++ b/e2e/routable-animations/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/routable-animations/app/App_Resources/iOS/LaunchScreen.storyboard b/e2e/routable-animations/app/App_Resources/iOS/LaunchScreen.storyboard
new file mode 100644
index 000000000..2ad9471e1
--- /dev/null
+++ b/e2e/routable-animations/app/App_Resources/iOS/LaunchScreen.storyboard
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/routable-animations/app/App_Resources/iOS/build.xcconfig b/e2e/routable-animations/app/App_Resources/iOS/build.xcconfig
new file mode 100644
index 000000000..4b0118490
--- /dev/null
+++ b/e2e/routable-animations/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/routable-animations/app/README.md b/e2e/routable-animations/app/README.md
new file mode 100644
index 000000000..ebe60c416
--- /dev/null
+++ b/e2e/routable-animations/app/README.md
@@ -0,0 +1,5 @@
+# NativeScript Tutorial Angular Template
+
+This repo serves as the starting point for NativeScript’s [Angular Getting Started Guide](https://docs.nativescript.org/angular/tutorial/ng-chapter-0).
+
+Please file any issues with this template on the [NativeScript/docs repository](https://github.com/nativescript/docs), which is where the tutorial content lives.
\ No newline at end of file
diff --git a/e2e/routable-animations/app/app-routing.module.ts b/e2e/routable-animations/app/app-routing.module.ts
new file mode 100644
index 000000000..c1f60b84c
--- /dev/null
+++ b/e2e/routable-animations/app/app-routing.module.ts
@@ -0,0 +1,29 @@
+import { NgModule } from '@angular/core';
+import { Routes } from '@angular/router';
+import { NativeScriptRouterModule } from "@nativescript/angular/router";
+
+import { HomeComponent } from './home/home.component';
+import { SupportComponent } from './support/support.component';
+
+const routes: Routes = [
+ {
+ path: '',
+ component: HomeComponent,
+ data: {
+ animation: 'homePage'
+ },
+ },
+ {
+ path: 'support',
+ component: SupportComponent,
+ data: {
+ animation: 'supportPage'
+ }
+ },
+];
+
+@NgModule({
+ imports: [NativeScriptRouterModule.forRoot(routes)],
+ exports: [NativeScriptRouterModule]
+})
+export class AppRoutingModule { }
diff --git a/e2e/routable-animations/app/app.component.ts b/e2e/routable-animations/app/app.component.ts
new file mode 100644
index 000000000..f3883127e
--- /dev/null
+++ b/e2e/routable-animations/app/app.component.ts
@@ -0,0 +1,51 @@
+import { Component } from "@angular/core";
+import {
+ animate,
+ animateChild,
+ group,
+ query,
+ style,
+ transition,
+ trigger,
+} from "@angular/animations";
+
+const slideLeft = [
+ query(':leave', style({ transform: 'translateX(0)' })),
+ query(':enter', style({ transform: 'translateX(-400)' })),
+
+ group([
+ query(':leave', animate(500, style({ transform: 'translateX(400)' }))),
+ query(':enter', animate(500, style({ transform: 'translateX(0)' }))),
+ ], { delay: 10 }) // Needed because a wierd animation scheduling bug in IOS
+]
+
+const slideRight = [
+ query(':leave', style({ transform: 'translateX(0)'})),
+ query(':enter', style({ transform: 'translateX(400)'})),
+
+ group([
+ query(':leave', animate(500, style({ transform: 'translateX(-400)' })), { delay: 100 }),
+ query(':enter', animate(500, style({ transform: 'translateX(0)' })), { delay: 100 }),
+ ], { delay: 10 }) // Needed because a wierd animation scheduling bug in IOS
+]
+
+@Component({
+ template: `
+
+
+
+ `,
+ animations: [
+ trigger('routeAnimation', [
+ transition('homePage => supportPage', slideRight),
+ transition('supportPage => homePage', slideLeft),
+ ])
+ ],
+})
+export class AppComponent {
+ prepRouteState(outlet: any) {
+ return outlet.activatedRouteData['animation'] || 'firstPage';
+ }
+}
+
+
diff --git a/e2e/routable-animations/app/app.css b/e2e/routable-animations/app/app.css
new file mode 100644
index 000000000..8aca5e29a
--- /dev/null
+++ b/e2e/routable-animations/app/app.css
@@ -0,0 +1 @@
+@import "~nativescript-theme-core/css/core.light.css";
diff --git a/e2e/routable-animations/app/app.module.ngfactory.d.ts b/e2e/routable-animations/app/app.module.ngfactory.d.ts
new file mode 100644
index 000000000..9d15b9419
--- /dev/null
+++ b/e2e/routable-animations/app/app.module.ngfactory.d.ts
@@ -0,0 +1 @@
+export const AppModuleNgFactory: any;
\ No newline at end of file
diff --git a/e2e/routable-animations/app/app.module.ts b/e2e/routable-animations/app/app.module.ts
new file mode 100644
index 000000000..a20ebebb1
--- /dev/null
+++ b/e2e/routable-animations/app/app.module.ts
@@ -0,0 +1,29 @@
+import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
+import { NativeScriptModule } from "@nativescript/angular";
+import { NativeScriptAnimationsModule } from "@nativescript/angular/animations";
+
+import { AppComponent } from "./app.component";
+import { AppRoutingModule } from './app-routing.module';
+import { HomeComponent } from './home/home.component';
+import { SupportComponent } from './support/support.component';
+
+import { NativeScriptDebug } from "@nativescript/angular/trace";
+import { setCategories, enable } from "@nativescript/core/trace";
+setCategories(NativeScriptDebug.animationsTraceCategory);
+enable();
+
+@NgModule({
+ declarations: [
+ AppComponent,
+ HomeComponent,
+ SupportComponent
+ ],
+ bootstrap: [AppComponent],
+ imports: [
+ NativeScriptModule,
+ NativeScriptAnimationsModule,
+ AppRoutingModule,
+ ],
+ schemas: [NO_ERRORS_SCHEMA],
+})
+export class AppModule { }
diff --git a/e2e/routable-animations/app/home/home.component.css b/e2e/routable-animations/app/home/home.component.css
new file mode 100644
index 000000000..e69de29bb
diff --git a/e2e/routable-animations/app/home/home.component.html b/e2e/routable-animations/app/home/home.component.html
new file mode 100644
index 000000000..4adabcc24
--- /dev/null
+++ b/e2e/routable-animations/app/home/home.component.html
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/e2e/routable-animations/app/home/home.component.ts b/e2e/routable-animations/app/home/home.component.ts
new file mode 100644
index 000000000..f577257f0
--- /dev/null
+++ b/e2e/routable-animations/app/home/home.component.ts
@@ -0,0 +1,15 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+ moduleId: module.id,
+ templateUrl: './home.component.html',
+ styleUrls: ['./home.component.css']
+})
+export class HomeComponent implements OnInit {
+
+ constructor() { }
+
+ ngOnInit() {
+ }
+
+}
diff --git a/e2e/routable-animations/app/main.ts b/e2e/routable-animations/app/main.ts
new file mode 100644
index 000000000..1b1b3c521
--- /dev/null
+++ b/e2e/routable-animations/app/main.ts
@@ -0,0 +1,4 @@
+import { platformNativeScriptDynamic } from "@nativescript/angular/platform";
+import { AppModule } from "./app.module";
+
+platformNativeScriptDynamic().bootstrapModule(AppModule);
diff --git a/e2e/routable-animations/app/package.json b/e2e/routable-animations/app/package.json
new file mode 100644
index 000000000..f156bfc8f
--- /dev/null
+++ b/e2e/routable-animations/app/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "nativescript-template-ng-tutorial",
+ "version": "3.1.0",
+ "main": "main.js",
+ "android": {
+ "v8Flags": "--expose_gc",
+ "markingMode": "none"
+ }
+}
\ No newline at end of file
diff --git a/e2e/routable-animations/app/support/support.component.css b/e2e/routable-animations/app/support/support.component.css
new file mode 100644
index 000000000..e69de29bb
diff --git a/e2e/routable-animations/app/support/support.component.html b/e2e/routable-animations/app/support/support.component.html
new file mode 100644
index 000000000..e743bbd92
--- /dev/null
+++ b/e2e/routable-animations/app/support/support.component.html
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/e2e/routable-animations/app/support/support.component.ts b/e2e/routable-animations/app/support/support.component.ts
new file mode 100644
index 000000000..c31fbc51a
--- /dev/null
+++ b/e2e/routable-animations/app/support/support.component.ts
@@ -0,0 +1,17 @@
+import { Component, OnInit } from '@angular/core';
+import { RouterExtensions } from 'nativescript-angular/router';
+
+@Component({
+ moduleId: module.id,
+ templateUrl: './support.component.html',
+ styleUrls: ['./support.component.css']
+})
+export class SupportComponent implements OnInit {
+
+ constructor(public router: RouterExtensions) {
+ }
+
+ ngOnInit() {
+ }
+
+}
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api19-Default/home.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api19-Default/home.png
new file mode 100644
index 000000000..e52f73d80
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api19-Default/home.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api19-Default/support.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api19-Default/support.png
new file mode 100644
index 000000000..3046d7116
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api19-Default/support.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api22-Default/home.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api22-Default/home.png
new file mode 100644
index 000000000..6ff400d3d
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api22-Default/home.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api22-Default/support.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api22-Default/support.png
new file mode 100644
index 000000000..a55be3589
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api22-Default/support.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api22-Google/home.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api22-Google/home.png
new file mode 100644
index 000000000..8c8e081af
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api22-Google/home.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api22-Google/support.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api22-Google/support.png
new file mode 100644
index 000000000..ee833c142
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api22-Google/support.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api23-Default/home.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api23-Default/home.png
new file mode 100644
index 000000000..4f4da09ef
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api23-Default/home.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api23-Default/support.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api23-Default/support.png
new file mode 100644
index 000000000..e042e48b1
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api23-Default/support.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api24-Default/home.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api24-Default/home.png
new file mode 100644
index 000000000..7e55d9dde
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api24-Default/home.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api24-Default/support.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api24-Default/support.png
new file mode 100644
index 000000000..6272bebf8
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api24-Default/support.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api25-Google/home.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api25-Google/home.png
new file mode 100644
index 000000000..d54e5fc0e
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api25-Google/home.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api25-Google/support.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api25-Google/support.png
new file mode 100644
index 000000000..d583b6dcf
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api25-Google/support.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api26-Google/home.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api26-Google/home.png
new file mode 100644
index 000000000..40de7e9c1
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api26-Google/home.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api26-Google/support.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api26-Google/support.png
new file mode 100644
index 000000000..bad8bb6e4
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api26-Google/support.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api27-Google/home.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api27-Google/home.png
new file mode 100644
index 000000000..c52b30285
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api27-Google/home.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api27-Google/support.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api27-Google/support.png
new file mode 100644
index 000000000..e83f97176
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api27-Google/support.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api28-Google/home.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api28-Google/home.png
new file mode 100644
index 000000000..985b2bacd
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api28-Google/home.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api28-Google/support.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api28-Google/support.png
new file mode 100644
index 000000000..e83f97176
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api28-Google/support.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api29-Google/home.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api29-Google/home.png
new file mode 100644
index 000000000..198993618
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api29-Google/home.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api29-Google/support.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api29-Google/support.png
new file mode 100644
index 000000000..26238af12
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/Emulator-Api29-Google/support.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone 7 100/home.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone 7 100/home.png
new file mode 100644
index 000000000..e45e0e7e6
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone 7 100/home.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone 7 100/support.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone 7 100/support.png
new file mode 100644
index 000000000..37ed5d73d
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone 7 100/support.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone 7 110/home.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone 7 110/home.png
new file mode 100644
index 000000000..e45e0e7e6
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone 7 110/home.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone 7 110/support.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone 7 110/support.png
new file mode 100644
index 000000000..1a8839513
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone 7 110/support.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone 7 12/home.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone 7 12/home.png
new file mode 100644
index 000000000..81be31153
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone 7 12/home.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone 7 12/support.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone 7 12/support.png
new file mode 100644
index 000000000..aaf591a50
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone 7 12/support.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone 8/home.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone 8/home.png
new file mode 100644
index 000000000..e45e0e7e6
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone 8/home.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone 8/support.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone 8/support.png
new file mode 100644
index 000000000..37ed5d73d
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone 8/support.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone X 110/home.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone X 110/home.png
new file mode 100644
index 000000000..49139d5a6
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone X 110/home.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone X 110/support.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone X 110/support.png
new file mode 100644
index 000000000..64dd2209f
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone X 110/support.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone XR 12/home.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone XR 12/home.png
new file mode 100644
index 000000000..f32265ad8
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone XR 12/home.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone XR 12/support.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone XR 12/support.png
new file mode 100644
index 000000000..43a8a2257
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone XR 12/support.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone XR 13/home.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone XR 13/home.png
new file mode 100644
index 000000000..d039c537e
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone XR 13/home.png differ
diff --git a/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone XR 13/support.png b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone XR 13/support.png
new file mode 100644
index 000000000..f21417d83
Binary files /dev/null and b/e2e/routable-animations/e2e/resources/images/nsroanimations/iPhone XR 13/support.png differ
diff --git a/e2e/routable-animations/e2e/setup.ts b/e2e/routable-animations/e2e/setup.ts
new file mode 100644
index 000000000..11b854fca
--- /dev/null
+++ b/e2e/routable-animations/e2e/setup.ts
@@ -0,0 +1,19 @@
+import { startServer, stopServer, ITestReporter, nsCapabilities, LogImageType } from "nativescript-dev-appium";
+const addContext = require('mochawesome/addContext');
+
+const testReporterContext = {};
+testReporterContext.name = "mochawesome";
+testReporterContext.reportDir = "mochawesome-report";
+testReporterContext.log = addContext;
+testReporterContext.logImageTypes = [LogImageType.screenshots];
+nsCapabilities.testReporter = testReporterContext;
+
+before("start server", async function () {
+ nsCapabilities.testReporter.context = this;
+ await startServer();
+});
+
+after("stop server", async function () {
+ nsCapabilities.testReporter.context = this;
+ await stopServer();
+});
diff --git a/e2e/routable-animations/e2e/tests.e2e.ts b/e2e/routable-animations/e2e/tests.e2e.ts
new file mode 100644
index 000000000..6f693b745
--- /dev/null
+++ b/e2e/routable-animations/e2e/tests.e2e.ts
@@ -0,0 +1,53 @@
+import { AppiumDriver, createDriver, SearchOptions, nsCapabilities } from "nativescript-dev-appium";
+import { assert } from "chai";
+import { isSauceLab } from "nativescript-dev-appium/lib/parser";
+import { ImageOptions } from "nativescript-dev-appium/lib/image-options";
+
+const QUEUE_WAIT_TIME: number = 600000; // Sometimes SauceLabs threads are not available and the tests wait in a queue to start. Wait 10 min before timeout.
+
+describe("sample scenario", function () {
+ const defaultWaitTime = 5000;
+ let driver: AppiumDriver;
+
+ before(async function () {
+ this.timeout(QUEUE_WAIT_TIME);
+ nsCapabilities.testReporter.context = this;
+ driver = await createDriver();
+ driver.imageHelper.defaultTolerance = 50;
+ driver.imageHelper.defaultToleranceType = ImageOptions.pixel;
+ });
+
+ after(async function () {
+ if (isSauceLab) {
+ driver.sessionId().then(function (sessionId) {
+ console.log("Report https://saucelabs.com/beta/tests/" + sessionId);
+ });
+ }
+ await driver.quit();
+ console.log("Quit driver!");
+ });
+
+ afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logTestArtifacts(this.currentTest.title);
+ }
+ });
+
+ it("should go to support page", async function () {
+ const btnGoToSupportPage = await driver.findElementByAutomationText("go to support page");
+ const homeImage = await driver.compareScreen("home", 5, 20, ImageOptions.pixel);
+ assert.isTrue(homeImage);
+ await btnGoToSupportPage.click();
+ const titleSupportPage = await driver.findElementByAutomationText("Support Page");
+ console.log(await titleSupportPage.text());
+ });
+
+ it("should go back to home page", async function () {
+ const btnGoBackToHomePage = await driver.findElementByAutomationText("go back to home page");
+ const supportImage = await driver.compareScreen("support", 5, 20, ImageOptions.pixel);
+ assert.isTrue(supportImage);
+ await btnGoBackToHomePage.click();
+ const titleHomePage = await driver.findElementByAutomationText("Home Page");
+ console.log(await titleHomePage.text());
+ });
+});
diff --git a/e2e/routable-animations/e2e/tsconfig.json b/e2e/routable-animations/e2e/tsconfig.json
new file mode 100644
index 000000000..6517ca58d
--- /dev/null
+++ b/e2e/routable-animations/e2e/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es6",
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "importHelpers": false,
+ "sourceMap": true,
+ "types": [
+ "node",
+ "mocha",
+ "chai"
+ ],
+ "lib": [
+ "es2015",
+ "dom"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/e2e/routable-animations/package.json b/e2e/routable-animations/package.json
new file mode 100644
index 000000000..fb02e7d67
--- /dev/null
+++ b/e2e/routable-animations/package.json
@@ -0,0 +1,51 @@
+{
+ "description": "NativeScript Application",
+ "license": "SEE LICENSE IN ",
+ "readme": "NativeScript Application",
+ "repository": "",
+ "nativescript": {
+ "id": "org.nativescript.nsroanimations"
+ },
+ "dependencies": {
+ "@angular/animations": "~9.1.0",
+ "@angular/common": "~9.1.0",
+ "@angular/compiler": "~9.1.0",
+ "@angular/core": "~9.1.0",
+ "@angular/forms": "~9.1.0",
+ "@angular/platform-browser": "~9.1.0",
+ "@angular/platform-browser-dynamic": "~9.1.0",
+ "@angular/router": "~9.1.0",
+ "@nativescript/angular": "file:../../dist/nativescript-angular-scoped.tgz",
+ "nativescript-theme-core": "~1.0.2",
+ "reflect-metadata": "~0.1.8",
+ "rxjs": "~6.5.5",
+ "@nativescript/core": "next",
+ "zone.js": "^0.10.3"
+ },
+ "devDependencies": {
+ "@ngtools/webpack": "~9.1.0",
+ "@angular/compiler-cli": "~9.1.0",
+ "@types/chai": "~4.1.3",
+ "@types/mocha": "~5.2.1",
+ "@types/node": "~10.12.18",
+ "babel-traverse": "6.25.0",
+ "babel-types": "6.25.0",
+ "babylon": "6.17.4",
+ "chai": "^4.2.0",
+ "colors": "^1.1.2",
+ "lazy": "1.0.11",
+ "mocha": "~5.2.0",
+ "mochawesome": "~3.1.2",
+ "nativescript-css-loader": "~0.26.0",
+ "nativescript-dev-appium": "^6.0.0",
+ "nativescript-dev-webpack": "next",
+ "typescript": "~3.8.3"
+ },
+ "scripts": {
+ "ns-bundle": "ns-bundle",
+ "e2e": "tsc -p e2e && mocha --opts ../config/mocha.opts --recursive e2e --appiumCapsLocation ../config/appium.capabilities.json",
+ "e2e-watch": "tsc -p e2e --watch",
+ "ns-verify-bundle": "ns-verify-bundle",
+ "update-ns-webpack": "update-ns-webpack"
+ }
+}
diff --git a/e2e/routable-animations/tsconfig.esm.json b/e2e/routable-animations/tsconfig.esm.json
new file mode 100644
index 000000000..a96f6bbc6
--- /dev/null
+++ b/e2e/routable-animations/tsconfig.esm.json
@@ -0,0 +1,7 @@
+{
+ "extends": "./tsconfig",
+ "compilerOptions": {
+ "module": "ESNext",
+ "moduleResolution": "node"
+ }
+}
diff --git a/e2e/routable-animations/tsconfig.json b/e2e/routable-animations/tsconfig.json
new file mode 100644
index 000000000..a3ba8b51a
--- /dev/null
+++ b/e2e/routable-animations/tsconfig.json
@@ -0,0 +1,40 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es5",
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "noEmitHelpers": true,
+ "noEmitOnError": true,
+ "skipLibCheck": true,
+ "lib": [
+ "es2017",
+ "dom",
+ "es6"
+ ],
+ "baseUrl": ".",
+ "paths": {
+ "~/*": [
+ "app/*"
+ ],
+ "*": [
+ "./node_modules/*"
+ ]
+ }
+ },
+ "include": [
+ "../../nativescript-angular-package",
+ "../../nativescript-angular",
+ "**/*"
+ ],
+ "exclude": [
+ "../../nativescript-angular-package/node_modules",
+ "../../nativescript-angular-package/**/*.d.ts",
+ "../../nativescript-angular/node_modules",
+ "../../nativescript-angular/**/*.d.ts",
+ "node_modules",
+ "platforms",
+ "**/*.aot",
+ "e2e"
+ ]
+}
\ No newline at end of file
diff --git a/e2e/router-tab-view/.gitignore b/e2e/router-tab-view/.gitignore
new file mode 100644
index 000000000..e2c82407c
--- /dev/null
+++ b/e2e/router-tab-view/.gitignore
@@ -0,0 +1,13 @@
+.vscode
+
+platforms
+node_modules
+hooks
+
+/**/*.js
+/**/*.map
+e2e/reports
+test-results.xml
+
+instrumentscli*.trace
+mochawesome-report
\ No newline at end of file
diff --git a/e2e/router-tab-view/app/App_Resources/Android/app.gradle b/e2e/router-tab-view/app/App_Resources/Android/app.gradle
new file mode 100644
index 000000000..095407c3b
--- /dev/null
+++ b/e2e/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.routertabview"
+ }
+ aaptOptions {
+ additionalParameters "--no-version-vectors"
+ }
+}
diff --git a/e2e/router-tab-view/app/App_Resources/Android/src/main/AndroidManifest.xml b/e2e/router-tab-view/app/App_Resources/Android/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..9db832151
--- /dev/null
+++ b/e2e/router-tab-view/app/App_Resources/Android/src/main/AndroidManifest.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-hdpi/background.png b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-hdpi/background.png
new file mode 100644
index 000000000..eb381c258
Binary files /dev/null and b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-hdpi/background.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-hdpi/icon.png b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-hdpi/icon.png
new file mode 100755
index 000000000..9cde84cd5
Binary files /dev/null and b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-hdpi/icon.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-hdpi/logo.png b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-hdpi/logo.png
new file mode 100644
index 000000000..5218f4c90
Binary files /dev/null and b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-hdpi/logo.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-ldpi/background.png b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-ldpi/background.png
new file mode 100644
index 000000000..748b2adf5
Binary files /dev/null and b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-ldpi/background.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-ldpi/icon.png b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-ldpi/icon.png
new file mode 100755
index 000000000..4d6a674b3
Binary files /dev/null and b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-ldpi/icon.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-ldpi/logo.png b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-ldpi/logo.png
new file mode 100644
index 000000000..b9e102a76
Binary files /dev/null and b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-ldpi/logo.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-mdpi/background.png b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-mdpi/background.png
new file mode 100644
index 000000000..efeaf2907
Binary files /dev/null and b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-mdpi/background.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-mdpi/icon.png b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-mdpi/icon.png
new file mode 100755
index 000000000..92ccc85a6
Binary files /dev/null and b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-mdpi/icon.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-mdpi/logo.png b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-mdpi/logo.png
new file mode 100644
index 000000000..626338766
Binary files /dev/null and b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-mdpi/logo.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml
new file mode 100644
index 000000000..ada77f92c
--- /dev/null
+++ b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml
@@ -0,0 +1,8 @@
+
+ -
+
+
+ -
+
+
+
\ No newline at end of file
diff --git a/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xhdpi/background.png b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xhdpi/background.png
new file mode 100644
index 000000000..612bbd072
Binary files /dev/null and b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xhdpi/background.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xhdpi/icon.png b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xhdpi/icon.png
new file mode 100644
index 000000000..8bcde6277
Binary files /dev/null and b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xhdpi/icon.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xhdpi/logo.png b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xhdpi/logo.png
new file mode 100644
index 000000000..ad8ee2f4b
Binary files /dev/null and b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xhdpi/logo.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png
new file mode 100644
index 000000000..0fa88e235
Binary files /dev/null and b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xxhdpi/icon.png b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xxhdpi/icon.png
new file mode 100644
index 000000000..9d81c85dc
Binary files /dev/null and b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xxhdpi/icon.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xxhdpi/logo.png b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xxhdpi/logo.png
new file mode 100644
index 000000000..668327832
Binary files /dev/null and b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xxhdpi/logo.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png
new file mode 100644
index 000000000..c650f6438
Binary files /dev/null and b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/icon.png b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/icon.png
new file mode 100644
index 000000000..9a34d0d43
Binary files /dev/null and b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/icon.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/logo.png b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/logo.png
new file mode 100644
index 000000000..fa6331c8d
Binary files /dev/null and b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/drawable-xxxhdpi/logo.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/Android/src/main/res/values-v21/colors.xml b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/values-v21/colors.xml
new file mode 100644
index 000000000..a64641a9d
--- /dev/null
+++ b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/values-v21/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #3d5afe
+
\ No newline at end of file
diff --git a/e2e/router-tab-view/app/App_Resources/Android/src/main/res/values-v21/styles.xml b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/values-v21/styles.xml
new file mode 100644
index 000000000..dac8727c8
--- /dev/null
+++ b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/values-v21/styles.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/router-tab-view/app/App_Resources/Android/src/main/res/values/colors.xml b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/values/colors.xml
new file mode 100644
index 000000000..74ad8829c
--- /dev/null
+++ b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/values/colors.xml
@@ -0,0 +1,7 @@
+
+
+ #F5F5F5
+ #757575
+ #33B5E5
+ #272734
+
\ No newline at end of file
diff --git a/e2e/router-tab-view/app/App_Resources/Android/src/main/res/values/styles.xml b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/values/styles.xml
new file mode 100644
index 000000000..c793e6d4c
--- /dev/null
+++ b/e2e/router-tab-view/app/App_Resources/Android/src/main/res/values/styles.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000..4034b76e6
--- /dev/null
+++ b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/Contents.json b/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/Contents.json
new file mode 100644
index 000000000..da4a164c9
--- /dev/null
+++ b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json b/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
new file mode 100644
index 000000000..11bfcf55c
--- /dev/null
+++ b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-1125h.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-1125h.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape-X.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape-X.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json b/e2e/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/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json b/e2e/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/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png b/e2e/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/router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png differ
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/Info.plist b/e2e/router-tab-view/app/App_Resources/iOS/Info.plist
new file mode 100644
index 000000000..ea3e3ea23
--- /dev/null
+++ b/e2e/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/router-tab-view/app/App_Resources/iOS/LaunchScreen.storyboard b/e2e/router-tab-view/app/App_Resources/iOS/LaunchScreen.storyboard
new file mode 100644
index 000000000..2ad9471e1
--- /dev/null
+++ b/e2e/router-tab-view/app/App_Resources/iOS/LaunchScreen.storyboard
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/router-tab-view/app/App_Resources/iOS/build.xcconfig b/e2e/router-tab-view/app/App_Resources/iOS/build.xcconfig
new file mode 100644
index 000000000..4b0118490
--- /dev/null
+++ b/e2e/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/router-tab-view/app/app.component.html b/e2e/router-tab-view/app/app.component.html
new file mode 100644
index 000000000..8872891ae
--- /dev/null
+++ b/e2e/router-tab-view/app/app.component.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/router-tab-view/app/app.component.ts b/e2e/router-tab-view/app/app.component.ts
new file mode 100644
index 000000000..385bd9c12
--- /dev/null
+++ b/e2e/router-tab-view/app/app.component.ts
@@ -0,0 +1,25 @@
+import { Component, ViewChild } from "@angular/core";
+import { TabViewDirective } from "@nativescript/angular/directives";
+import { Router, NavigationEnd } from "@angular/router";
+import { NSLocationStrategy } from "@nativescript/angular";
+
+@Component({
+ selector: "ns-app",
+ templateUrl: "app.component.html",
+})
+
+export class AppComponent {
+ private isInitialNavigation = true;
+
+ @ViewChild(TabViewDirective, { static: false }) 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/router-tab-view/app/app.css b/e2e/router-tab-view/app/app.css
new file mode 100644
index 000000000..d23504c4e
--- /dev/null
+++ b/e2e/router-tab-view/app/app.css
@@ -0,0 +1,12 @@
+/*
+In NativeScript, the app.css file is where you place CSS rules that
+you would like to apply to your entire application. Check out
+http://docs.nativescript.org/ui/styling for a full list of the CSS
+selectors and properties you can use to style UI components.
+
+/*
+In many cases you may want to use the NativeScript core theme instead
+of writing your own CSS rules. For a full list of class names in the theme
+refer to http://docs.nativescript.org/ui/theme.
+*/
+@import '~nativescript-theme-core/css/core.light.css';
diff --git a/e2e/router-tab-view/app/app.module.ngfactory.d.ts b/e2e/router-tab-view/app/app.module.ngfactory.d.ts
new file mode 100644
index 000000000..793157de3
--- /dev/null
+++ b/e2e/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/router-tab-view/app/app.module.ts b/e2e/router-tab-view/app/app.module.ts
new file mode 100644
index 000000000..d6142a91a
--- /dev/null
+++ b/e2e/router-tab-view/app/app.module.ts
@@ -0,0 +1,43 @@
+import { NgModule, NO_ERRORS_SCHEMA, ErrorHandler } from "@angular/core";
+import { NativeScriptModule } from "@nativescript/angular";
+import { AppRoutingModule, COMPONENTS } from "./app.routing";
+import { AppComponent } from "./app.component";
+
+import { DataService } from "./data.service";
+
+import { enable as traceEnable } from "@nativescript/core/trace";
+
+// addCategories(routerTraceCategory);
+traceEnable();
+
+export class MyErrorHandler implements ErrorHandler {
+ handleError(error) {
+ console.log("### ErrorHandler Error: " + error.toString());
+ console.log("### ErrorHandler Stack: " + error.stack);
+ }
+}
+
+@NgModule({
+ bootstrap: [
+ AppComponent
+ ],
+ imports: [
+ NativeScriptModule,
+ AppRoutingModule
+ ],
+ declarations: [
+ AppComponent,
+ ...COMPONENTS
+ ],
+ providers: [
+ DataService,
+ { provide: ErrorHandler, useClass: MyErrorHandler }
+ ],
+ 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/router-tab-view/app/app.routing.ts b/e2e/router-tab-view/app/app.routing.ts
new file mode 100644
index 000000000..82cab77a3
--- /dev/null
+++ b/e2e/router-tab-view/app/app.routing.ts
@@ -0,0 +1,26 @@
+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";
+
+export const COMPONENTS = [PlayerComponent, PlayerDetailComponent, TeamsComponent, TeamDetailComponent];
+
+const routes: Routes = [
+ { path: "", redirectTo: "/(playerTab:players//teamTab:teams)", pathMatch: "full" },
+
+ { 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/router-tab-view/app/data.service.ts b/e2e/router-tab-view/app/data.service.ts
new file mode 100644
index 000000000..583a37013
--- /dev/null
+++ b/e2e/router-tab-view/app/data.service.ts
@@ -0,0 +1,37 @@
+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" },
+ { id: 3, name: "Player Three" },
+ );
+
+ private teams = new Array(
+ { id: 1, name: "Team One" },
+ { id: 2, name: "Team Two" },
+ { id: 3, name: "Team Three" },
+ );
+
+ 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/router-tab-view/app/main.ts b/e2e/router-tab-view/app/main.ts
new file mode 100644
index 000000000..f61a19110
--- /dev/null
+++ b/e2e/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/router-tab-view/app/package.json b/e2e/router-tab-view/app/package.json
new file mode 100644
index 000000000..e6521c70c
--- /dev/null
+++ b/e2e/router-tab-view/app/package.json
@@ -0,0 +1,9 @@
+{
+ "android": {
+ "v8Flags": "--expose_gc",
+ "markingMode": "none"
+ },
+ "main": "main.js",
+ "name": "tns-template-hello-world-ng",
+ "version": "3.4.3"
+}
\ No newline at end of file
diff --git a/e2e/router-tab-view/app/player/player-detail.component.html b/e2e/router-tab-view/app/player/player-detail.component.html
new file mode 100644
index 000000000..25be11a6d
--- /dev/null
+++ b/e2e/router-tab-view/app/player/player-detail.component.html
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/e2e/router-tab-view/app/player/player-detail.component.ts b/e2e/router-tab-view/app/player/player-detail.component.ts
new file mode 100644
index 000000000..8b18da264
--- /dev/null
+++ b/e2e/router-tab-view/app/player/player-detail.component.ts
@@ -0,0 +1,31 @@
+import { Component, OnInit } from "@angular/core";
+import { ActivatedRoute } from "@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
+ ) { }
+
+ ngOnInit(): void {
+ this.subscription = this.route.params.subscribe(params => {
+ const id = +params["id"];
+ this.item = this.data.getPlayer(id);
+ })
+ }
+
+ ngOnDestroy() {
+ this.subscription.unsubscribe();
+ }
+}
diff --git a/e2e/router-tab-view/app/player/players.component.html b/e2e/router-tab-view/app/player/players.component.html
new file mode 100644
index 000000000..532703892
--- /dev/null
+++ b/e2e/router-tab-view/app/player/players.component.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/router-tab-view/app/player/players.component.ts b/e2e/router-tab-view/app/player/players.component.ts
new file mode 100644
index 000000000..934d76a45
--- /dev/null
+++ b/e2e/router-tab-view/app/player/players.component.ts
@@ -0,0 +1,22 @@
+import { Component, OnInit } from "@angular/core";
+import { DataService, DataItem } from "../data.service";
+import { RouterExtensions } from "@nativescript/angular/router";
+
+@Component({
+ selector: "ns-players",
+ moduleId: module.id,
+ templateUrl: "./players.component.html",
+})
+export class PlayerComponent implements OnInit {
+ items: DataItem[];
+
+ constructor(private itemService: DataService, private router: RouterExtensions) { }
+
+ ngOnInit(): void {
+ this.items = this.itemService.getPlayers();
+ }
+
+ navigateAbs() {
+ this.router.navigateByUrl("/(playerTab:players//teamTab:team/3)")
+ }
+}
\ No newline at end of file
diff --git a/e2e/router-tab-view/app/team/team-detail.component.html b/e2e/router-tab-view/app/team/team-detail.component.html
new file mode 100644
index 000000000..40d2f72fd
--- /dev/null
+++ b/e2e/router-tab-view/app/team/team-detail.component.html
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/e2e/router-tab-view/app/team/team-detail.component.ts b/e2e/router-tab-view/app/team/team-detail.component.ts
new file mode 100644
index 000000000..1f055e296
--- /dev/null
+++ b/e2e/router-tab-view/app/team/team-detail.component.ts
@@ -0,0 +1,31 @@
+import { Component, OnInit } from "@angular/core";
+import { ActivatedRoute } from "@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
+ ) { }
+
+ ngOnInit(): void {
+ this.subscription = this.route.params.subscribe(params => {
+ const id = +params["id"];
+ this.item = this.data.getTeam(id);
+ })
+ }
+
+ ngOnDestroy() {
+ this.subscription.unsubscribe();
+ }
+}
diff --git a/e2e/router-tab-view/app/team/teams.component.html b/e2e/router-tab-view/app/team/teams.component.html
new file mode 100644
index 000000000..dea20ffc7
--- /dev/null
+++ b/e2e/router-tab-view/app/team/teams.component.html
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
diff --git a/e2e/router-tab-view/app/team/teams.component.ts b/e2e/router-tab-view/app/team/teams.component.ts
new file mode 100644
index 000000000..119d1d2fe
--- /dev/null
+++ b/e2e/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/router-tab-view/e2e/setup.ts b/e2e/router-tab-view/e2e/setup.ts
new file mode 100644
index 000000000..cd73140bb
--- /dev/null
+++ b/e2e/router-tab-view/e2e/setup.ts
@@ -0,0 +1,18 @@
+import { startServer, stopServer, ITestReporter, nsCapabilities, LogImageType } from "nativescript-dev-appium";
+const addContext = require('mochawesome/addContext');
+
+const testReporterContext = {};
+testReporterContext.name = "mochawesome";
+testReporterContext.reportDir = "mochawesome-report";
+testReporterContext.log = addContext;
+testReporterContext.logImageTypes = [LogImageType.screenshots];
+nsCapabilities.testReporter = testReporterContext;
+
+before("start server", async function () {
+ nsCapabilities.testReporter.context = this;
+ await startServer();
+});
+
+after("stop server", async function () {
+ await stopServer();
+});
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
new file mode 100644
index 000000000..7b316de75
--- /dev/null
+++ b/e2e/router-tab-view/e2e/tab-view-navigation.e2e-spec.ts
@@ -0,0 +1,215 @@
+import { AppiumDriver, createDriver, SearchOptions, nsCapabilities } from "nativescript-dev-appium";
+import { assert } from "chai";
+import { isSauceLab } from "nativescript-dev-appium/lib/parser";
+import { ImageOptions } from "nativescript-dev-appium/lib/image-options";
+
+const QUEUE_WAIT_TIME: number = 600000; // Sometimes SauceLabs threads are not available and the tests wait in a queue to start. Wait 10 min before timeout.
+
+describe("TabView with page-router-outlet in each tab", async function () {
+ let driver: AppiumDriver;
+
+ before(async function () {
+ this.timeout(QUEUE_WAIT_TIME);
+ nsCapabilities.testReporter.context = this;
+ driver = await createDriver();
+ driver.imageHelper.defaultTolerance = 50;
+ driver.imageHelper.defaultToleranceType = ImageOptions.pixel;
+ });
+
+ after(async function () {
+ if (isSauceLab) {
+ driver.sessionId().then(function (sessionId) {
+ console.log("Report https://saucelabs.com/beta/tests/" + sessionId);
+ });
+ }
+ await driver.quit();
+ console.log("Quit driver!");
+ });
+
+ afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logTestArtifacts(this.currentTest.title);
+ }
+ });
+
+ it("should find any tabs by text", async function () {
+ await driver.findElementByAutomationText("Players");
+ await driver.findElementByAutomationText("Teams");
+ await driver.findElementByAutomationText("Player List");
+ });
+
+ it("should be able to switch between tabs", async function () {
+ await driver.findElementByAutomationText("Player List");
+
+ await selectTeamTab(driver);
+
+ await selectPlayerTab(driver);
+ });
+
+ it("should go forward and go back on first(player) tab", async function () {
+ await driver.findElementByAutomationText("Player List");
+ let player = await driver.findElementByAutomationText("Player One");
+ await player.click();
+ await driver.wait(1000);
+ player = await driver.findElementByAutomationText("Player One");
+ if (player) {
+ await player.click(); // First click does not open the details page
+ await driver.wait(2000); // Even its clicked twice it takes time to open the view
+ }
+ await driver.findElementByAutomationText("Player Details");
+ await driver.findElementByAutomationText("1");
+ await driver.findElementByAutomationText("Player One");
+
+ await driver.navBack();
+ await driver.findElementByAutomationText("Player List");
+ });
+
+ it("should go forward and go back on second(team) tab", async function () {
+ await driver.findElementByAutomationText("Player List");
+
+ await selectTeamTab(driver);
+
+ await navigateToTeamItem(driver, "Team Two", "2");
+
+ await driver.navBack();
+ await driver.findElementByAutomationText("Team List");
+
+ await selectPlayerTab(driver);
+ });
+
+ it("should navigate first(player) tab, second(team) tab and back in the same order ", async function () {
+ await driver.findElementByAutomationText("Player List");
+
+ // Go forward in player tab
+ await navigateToPlayerItem(driver, "Player Three", "3");
+
+ // Go forward in team tab
+ await selectTeamTab(driver);
+ await navigateToTeamItem(driver, "Team One", "1");
+
+ // Check both tabs
+ await selectPlayerTab(driver, false);
+ await selectTeamTab(driver, false);
+
+ // Go back in team tab
+ await driver.navBack();
+ await driver.findElementByAutomationText("Team List");
+
+ // Go back in player tab
+ await selectPlayerTab(driver, false);
+ await driver.navBack();
+ await driver.findElementByAutomationText("Player List");
+ });
+
+ it("should navigate second(team) tab, first(player) and back in the same order ", async function () {
+ await driver.findElementByAutomationText("Player List");
+
+ // Go forward in team tab
+ await selectTeamTab(driver);
+ await navigateToTeamItem(driver, "Team One", "1");
+
+ // Go forward in player tab
+ await selectPlayerTab(driver);
+ await navigateToPlayerItem(driver, "Player Three", "3");
+
+ // Check both tabs
+ await selectTeamTab(driver, false);
+ await selectPlayerTab(driver, false);
+
+ // Go back in player tab
+ await driver.navBack();
+ await driver.findElementByAutomationText("Player List");
+
+ // Go back in team tab
+ await selectTeamTab(driver, false);
+ await driver.navBack();
+ await driver.findElementByAutomationText("Team List");
+
+ await selectPlayerTab(driver);
+ });
+
+ it("should navigate first(player) tab, second(team) tab and back in reverse order ", async function () {
+ await driver.findElementByAutomationText("Player List");
+
+ // Go forward in player tab
+ await navigateToPlayerItem(driver, "Player Three", "3");
+
+ // Go forward in team tab
+ await selectTeamTab(driver);
+ await navigateToTeamItem(driver, "Team One", "1");
+
+ // Go back in player tab
+ await selectPlayerTab(driver, false);
+ await driver.navBack();
+ await driver.findElementByAutomationText("Player List");
+
+ // Go back in player tab
+ await selectTeamTab(driver, false);
+ await driver.findElementByAutomationText("1");
+ await driver.findElementByAutomationText("Team One");
+
+ await driver.navBack();
+ await driver.findElementByAutomationText("Team List");
+
+ await selectPlayerTab(driver);
+ });
+
+ it("should navigate second(team) tab, first(player) tab and back in reverse order ", async function () {
+ await driver.findElementByAutomationText("Player List");
+
+ // Go forward in team tab
+ await selectTeamTab(driver);
+ await navigateToTeamItem(driver, "Team One", "1");
+
+ // Go forward in player tab
+ await selectPlayerTab(driver);
+ await navigateToPlayerItem(driver, "Player Three", "3");
+
+ // Go back in team tab
+ await selectTeamTab(driver, false);
+ await driver.navBack();
+ await driver.findElementByAutomationText("Team List");
+
+ // Go back in player tab
+ await selectPlayerTab(driver, false);
+ await driver.findElementByAutomationText("3");
+ await driver.findElementByAutomationText("Player Three");
+
+ await driver.navBack();
+ await driver.findElementByAutomationText("Player List");
+ });
+
+});
+
+async function navigateToTeamItem(driver: AppiumDriver, name: string, id: string) {
+ const team = await driver.findElementByAutomationText(name);
+ await team.click();
+ await driver.findElementByAutomationText("Team Details");
+ await driver.findElementByAutomationText(id);
+ await driver.findElementByAutomationText(name);
+}
+
+async function navigateToPlayerItem(driver: AppiumDriver, name: string, id: string) {
+ let player = await driver.findElementByAutomationText(name);
+ await player.click();
+
+ await driver.findElementByAutomationText("Player Details");
+ await driver.findElementByAutomationText(id);
+ await driver.findElementByAutomationText(name);
+}
+
+async function selectTeamTab(driver: AppiumDriver, expectList = true) {
+ const teamsTab = await driver.findElementByAutomationText("Teams");
+ await teamsTab.click();
+
+ const expectedTitle = expectList ? "Team List" : "Team Details";
+ await driver.findElementByAutomationText(expectedTitle);
+}
+
+async function selectPlayerTab(driver: AppiumDriver, expectList = true) {
+ const playerTab = await driver.findElementByAutomationText("Players");
+ await playerTab.click();
+
+ const expectedTitle = expectList ? "Player List" : "Player Details";
+ await driver.findElementByAutomationText(expectedTitle);
+}
diff --git a/e2e/router-tab-view/e2e/tsconfig.json b/e2e/router-tab-view/e2e/tsconfig.json
new file mode 100644
index 000000000..c297b2347
--- /dev/null
+++ b/e2e/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/router-tab-view/package.json b/e2e/router-tab-view/package.json
new file mode 100644
index 000000000..b2c52772b
--- /dev/null
+++ b/e2e/router-tab-view/package.json
@@ -0,0 +1,45 @@
+{
+ "description": "NativeScript Application",
+ "license": "SEE LICENSE IN ",
+ "readme": "NativeScript Application",
+ "repository": "",
+ "nativescript": {
+ "id": "org.nativescript.routertabview"
+ },
+ "dependencies": {
+ "@angular/animations": "~9.1.0",
+ "@angular/common": "~9.1.0",
+ "@angular/compiler": "~9.1.0",
+ "@angular/core": "~9.1.0",
+ "@angular/forms": "~9.1.0",
+ "@angular/platform-browser": "~9.1.0",
+ "@angular/platform-browser-dynamic": "~9.1.0",
+ "@angular/router": "~9.1.0",
+ "@nativescript/angular": "file:../../dist/nativescript-angular-scoped.tgz",
+ "nativescript-theme-core": "~1.0.4",
+ "reflect-metadata": "~0.1.8",
+ "rxjs": "~6.5.5",
+ "@nativescript/core": "next",
+ "zone.js": "^0.10.3"
+ },
+ "devDependencies": {
+ "@types/chai": "~4.1.7",
+ "@types/mocha": "~5.2.5",
+ "@types/node": "~10.12.18",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0",
+ "babylon": "6.18.0",
+ "lazy": "1.0.11",
+ "mocha": "~5.2.0",
+ "mochawesome": "~3.1.2",
+ "nativescript-dev-appium": "^6.0.0",
+ "nativescript-dev-webpack": "next",
+ "typescript": "~3.8.3",
+ "@angular/compiler-cli": "~9.1.0",
+ "@ngtools/webpack": "~9.1.0"
+ },
+ "scripts": {
+ "e2e": "tsc -p e2e && mocha --opts ../config/mocha.opts --recursive e2e --appiumCapsLocation ../config/appium.capabilities.json",
+ "compile-tests": "tsc -p e2e --watch"
+ }
+}
diff --git a/e2e/router-tab-view/tsconfig.json b/e2e/router-tab-view/tsconfig.json
new file mode 100644
index 000000000..a3ba8b51a
--- /dev/null
+++ b/e2e/router-tab-view/tsconfig.json
@@ -0,0 +1,40 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es5",
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "noEmitHelpers": true,
+ "noEmitOnError": true,
+ "skipLibCheck": true,
+ "lib": [
+ "es2017",
+ "dom",
+ "es6"
+ ],
+ "baseUrl": ".",
+ "paths": {
+ "~/*": [
+ "app/*"
+ ],
+ "*": [
+ "./node_modules/*"
+ ]
+ }
+ },
+ "include": [
+ "../../nativescript-angular-package",
+ "../../nativescript-angular",
+ "**/*"
+ ],
+ "exclude": [
+ "../../nativescript-angular-package/node_modules",
+ "../../nativescript-angular-package/**/*.d.ts",
+ "../../nativescript-angular/node_modules",
+ "../../nativescript-angular/**/*.d.ts",
+ "node_modules",
+ "platforms",
+ "**/*.aot",
+ "e2e"
+ ]
+}
\ No newline at end of file
diff --git a/e2e/router/.gitignore b/e2e/router/.gitignore
new file mode 100644
index 000000000..e3eb2283a
--- /dev/null
+++ b/e2e/router/.gitignore
@@ -0,0 +1,11 @@
+.vscode
+
+platforms
+node_modules
+hooks
+
+/**/*.js
+/**/*.map
+e2e/reports
+test-results.xml
+mochawesome-report
\ No newline at end of file
diff --git a/e2e/router/app/App_Resources/Android/app.gradle b/e2e/router/app/App_Resources/Android/app.gradle
new file mode 100644
index 000000000..8df47fb37
--- /dev/null
+++ b/e2e/router/app/App_Resources/Android/app.gradle
@@ -0,0 +1,23 @@
+// Add your native dependencies here:
+
+// Uncomment to add recyclerview-v7 dependency
+//dependencies {
+// compile 'com.android.support:recyclerview-v7:+'
+//}
+
+android {
+ defaultConfig {
+ generatedDensities = []
+ applicationId = "org.nativescript.router"
+
+ //override supported platforms
+ // ndk {
+ // abiFilters.clear()
+ // abiFilters "armeabi-v7a"
+ // }
+
+ }
+ aaptOptions {
+ additionalParameters "--no-version-vectors"
+ }
+}
diff --git a/e2e/router/app/App_Resources/Android/src/main/AndroidManifest.xml b/e2e/router/app/App_Resources/Android/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..9db832151
--- /dev/null
+++ b/e2e/router/app/App_Resources/Android/src/main/AndroidManifest.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/router/app/App_Resources/Android/src/main/res/drawable-nodpi/background.png b/e2e/router/app/App_Resources/Android/src/main/res/drawable-nodpi/background.png
new file mode 100644
index 000000000..748b2adf5
Binary files /dev/null and b/e2e/router/app/App_Resources/Android/src/main/res/drawable-nodpi/background.png differ
diff --git a/e2e/router/app/App_Resources/Android/src/main/res/drawable-nodpi/icon.png b/e2e/router/app/App_Resources/Android/src/main/res/drawable-nodpi/icon.png
new file mode 100755
index 000000000..ddfc17a71
Binary files /dev/null and b/e2e/router/app/App_Resources/Android/src/main/res/drawable-nodpi/icon.png differ
diff --git a/e2e/router/app/App_Resources/Android/src/main/res/drawable-nodpi/logo.png b/e2e/router/app/App_Resources/Android/src/main/res/drawable-nodpi/logo.png
new file mode 100644
index 000000000..b9e102a76
Binary files /dev/null and b/e2e/router/app/App_Resources/Android/src/main/res/drawable-nodpi/logo.png differ
diff --git a/e2e/router/app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml b/e2e/router/app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml
new file mode 100644
index 000000000..ada77f92c
--- /dev/null
+++ b/e2e/router/app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml
@@ -0,0 +1,8 @@
+
+ -
+
+
+ -
+
+
+
\ No newline at end of file
diff --git a/e2e/router/app/App_Resources/Android/src/main/res/values-v21/colors.xml b/e2e/router/app/App_Resources/Android/src/main/res/values-v21/colors.xml
new file mode 100644
index 000000000..a64641a9d
--- /dev/null
+++ b/e2e/router/app/App_Resources/Android/src/main/res/values-v21/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #3d5afe
+
\ No newline at end of file
diff --git a/e2e/router/app/App_Resources/Android/src/main/res/values-v21/styles.xml b/e2e/router/app/App_Resources/Android/src/main/res/values-v21/styles.xml
new file mode 100644
index 000000000..dac8727c8
--- /dev/null
+++ b/e2e/router/app/App_Resources/Android/src/main/res/values-v21/styles.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/router/app/App_Resources/Android/src/main/res/values/colors.xml b/e2e/router/app/App_Resources/Android/src/main/res/values/colors.xml
new file mode 100644
index 000000000..74ad8829c
--- /dev/null
+++ b/e2e/router/app/App_Resources/Android/src/main/res/values/colors.xml
@@ -0,0 +1,7 @@
+
+
+ #F5F5F5
+ #757575
+ #33B5E5
+ #272734
+
\ No newline at end of file
diff --git a/e2e/router/app/App_Resources/Android/src/main/res/values/styles.xml b/e2e/router/app/App_Resources/Android/src/main/res/values/styles.xml
new file mode 100644
index 000000000..1e8c7f29b
--- /dev/null
+++ b/e2e/router/app/App_Resources/Android/src/main/res/values/styles.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000..1953734f4
--- /dev/null
+++ b/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,92 @@
+{
+ "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"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png b/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png
new file mode 100644
index 000000000..9e15af09d
Binary files /dev/null and b/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png differ
diff --git a/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png b/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png
new file mode 100644
index 000000000..7b9e55537
Binary files /dev/null and b/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png differ
diff --git a/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png b/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png
new file mode 100644
index 000000000..76f61ec1f
Binary files /dev/null and b/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png differ
diff --git a/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png b/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png
new file mode 100644
index 000000000..15b06db11
Binary files /dev/null and b/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png differ
diff --git a/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png b/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png
new file mode 100644
index 000000000..585065f94
Binary files /dev/null and b/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png differ
diff --git a/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png b/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png
new file mode 100644
index 000000000..a450c421d
Binary files /dev/null and b/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png differ
diff --git a/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png b/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png
new file mode 100644
index 000000000..457b6d94c
Binary files /dev/null and b/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png differ
diff --git a/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png b/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png
new file mode 100644
index 000000000..fa5a6ac86
Binary files /dev/null and b/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png differ
diff --git a/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png b/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png
new file mode 100644
index 000000000..94abcf70d
Binary files /dev/null and b/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png differ
diff --git a/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png b/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png
new file mode 100644
index 000000000..2e71dd3a0
Binary files /dev/null and b/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png differ
diff --git a/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png b/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png
new file mode 100644
index 000000000..4abc9ec50
Binary files /dev/null and b/e2e/router/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png differ
diff --git a/e2e/router/app/App_Resources/iOS/Assets.xcassets/Contents.json b/e2e/router/app/App_Resources/iOS/Assets.xcassets/Contents.json
new file mode 100644
index 000000000..da4a164c9
--- /dev/null
+++ b/e2e/router/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/router/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json b/e2e/router/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
new file mode 100644
index 000000000..4414bad08
--- /dev/null
+++ b/e2e/router/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
@@ -0,0 +1,158 @@
+{
+ "images" : [
+ {
+ "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/router/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png b/e2e/router/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/router/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png differ
diff --git a/e2e/router/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png b/e2e/router/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/router/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png differ
diff --git a/e2e/router/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png b/e2e/router/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/router/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png differ
diff --git a/e2e/router/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png b/e2e/router/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/router/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png differ
diff --git a/e2e/router/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png b/e2e/router/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/router/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png differ
diff --git a/e2e/router/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png b/e2e/router/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/router/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png differ
diff --git a/e2e/router/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png b/e2e/router/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/router/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png differ
diff --git a/e2e/router/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png b/e2e/router/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/router/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png differ
diff --git a/e2e/router/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png b/e2e/router/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png
new file mode 100644
index 000000000..9f1f6ce3e
Binary files /dev/null and b/e2e/router/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png differ
diff --git a/e2e/router/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png b/e2e/router/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/router/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png differ
diff --git a/e2e/router/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json b/e2e/router/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json
new file mode 100644
index 000000000..4f4e9c506
--- /dev/null
+++ b/e2e/router/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/router/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png b/e2e/router/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/router/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png differ
diff --git a/e2e/router/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png b/e2e/router/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/router/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png differ
diff --git a/e2e/router/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json b/e2e/router/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json
new file mode 100644
index 000000000..23c0ffd7a
--- /dev/null
+++ b/e2e/router/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/router/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png b/e2e/router/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/router/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png differ
diff --git a/e2e/router/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png b/e2e/router/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/router/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png differ
diff --git a/e2e/router/app/App_Resources/iOS/Info.plist b/e2e/router/app/App_Resources/iOS/Info.plist
new file mode 100644
index 000000000..ea3e3ea23
--- /dev/null
+++ b/e2e/router/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/router/app/App_Resources/iOS/LaunchScreen.storyboard b/e2e/router/app/App_Resources/iOS/LaunchScreen.storyboard
new file mode 100644
index 000000000..2ad9471e1
--- /dev/null
+++ b/e2e/router/app/App_Resources/iOS/LaunchScreen.storyboard
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/router/app/App_Resources/iOS/build.xcconfig b/e2e/router/app/App_Resources/iOS/build.xcconfig
new file mode 100644
index 000000000..4b0118490
--- /dev/null
+++ b/e2e/router/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/router/app/README.md b/e2e/router/app/README.md
new file mode 100644
index 000000000..ebe60c416
--- /dev/null
+++ b/e2e/router/app/README.md
@@ -0,0 +1,5 @@
+# NativeScript Tutorial Angular Template
+
+This repo serves as the starting point for NativeScript’s [Angular Getting Started Guide](https://docs.nativescript.org/angular/tutorial/ng-chapter-0).
+
+Please file any issues with this template on the [NativeScript/docs repository](https://github.com/nativescript/docs), which is where the tutorial content lives.
\ No newline at end of file
diff --git a/e2e/router/app/app-routing.module.ts b/e2e/router/app/app-routing.module.ts
new file mode 100644
index 000000000..302f562db
--- /dev/null
+++ b/e2e/router/app/app-routing.module.ts
@@ -0,0 +1,58 @@
+import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
+import { NativeScriptRouterModule, NSEmptyOutletComponent } from "@nativescript/angular";
+
+import { FirstComponent } from "./first/first.component"
+import { SecondComponent } from "./second/second.component"
+import { MasterComponent } from "./second/master.component"
+import { DetailComponent } from "./second/detail.component"
+
+export const routes = [
+ {
+ path: "",
+ redirectTo: "/first",
+ pathMatch: "full"
+ },
+ {
+ path: "first",
+ component: FirstComponent,
+ },
+ {
+ path: "second/:depth",
+ component: SecondComponent,
+ children: [
+ { path: "", component: MasterComponent },
+ { path: "detail/:id", component: DetailComponent },
+ {
+ path: "lazy-named",
+ outlet: "lazyNameOutlet",
+ component: NSEmptyOutletComponent,
+ loadChildren: () => import('./lazy-named/lazy-named.module').then(m => m.LazyNamedModule),
+ }
+ ]
+ },
+ {
+ path: "c-less",
+ children: [
+ {
+ path: "deep/:depth",
+ component: SecondComponent,
+ children: [
+ { path: "", component: MasterComponent },
+ { path: "detail/:id", component: DetailComponent }
+ ]
+ }
+ ]
+ },
+ {
+ path: "lazy",
+ component: NSEmptyOutletComponent,
+ loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule),
+ }
+];
+
+@NgModule({
+ imports: [NativeScriptRouterModule, NativeScriptRouterModule.forRoot(routes)],
+ exports: [NativeScriptRouterModule],
+})
+export class AppRoutingModule { }
+
diff --git a/e2e/router/app/app.component.ts b/e2e/router/app/app.component.ts
new file mode 100644
index 000000000..2311d63e0
--- /dev/null
+++ b/e2e/router/app/app.component.ts
@@ -0,0 +1,7 @@
+import { Component } from "@angular/core";
+
+@Component({
+ template: ``
+})
+export class AppComponent { }
+
diff --git a/e2e/router/app/app.css b/e2e/router/app/app.css
new file mode 100644
index 000000000..dc9ceffbf
--- /dev/null
+++ b/e2e/router/app/app.css
@@ -0,0 +1,22 @@
+.header {
+ font-size: 32;
+ white-space: normal;
+}
+
+.nested-header {
+ font-size: 16;
+}
+
+.nested-outlet {
+ margin: 20;
+ background-color: lightgreen;
+}
+
+Label {
+ text-align: center;
+ font-size: 12;
+}
+
+Button {
+ font-size: 12;
+}
\ No newline at end of file
diff --git a/e2e/router/app/app.module.ts b/e2e/router/app/app.module.ts
new file mode 100644
index 000000000..2df018787
--- /dev/null
+++ b/e2e/router/app/app.module.ts
@@ -0,0 +1,36 @@
+import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
+import { NativeScriptModule, NativeScriptAnimationsModule } from "@nativescript/angular";
+
+import {
+ AppRoutingModule,
+} from "./app-routing.module";
+import { FirstComponent } from "./first/first.component"
+import { SecondComponent } from "./second/second.component"
+import { MasterComponent } from "./second/master.component"
+import { DetailComponent } from "./second/detail.component"
+
+import { AppComponent } from "./app.component";
+
+import { rendererTraceCategory, viewUtilCategory, routeReuseStrategyTraceCategory, routerTraceCategory } from "@nativescript/angular/trace";
+import { setCategories, enable } from "@nativescript/core/trace";
+setCategories(routerTraceCategory + "," + routeReuseStrategyTraceCategory);
+enable();
+
+@NgModule({
+ declarations: [
+ AppComponent,
+ FirstComponent,
+ SecondComponent,
+ MasterComponent,
+ DetailComponent,
+ ],
+ bootstrap: [AppComponent],
+ imports: [
+ NativeScriptModule,
+ // NativeScriptAnimationsModule,
+ AppRoutingModule,
+ ],
+ schemas: [NO_ERRORS_SCHEMA],
+})
+export class AppModule { }
+
diff --git a/e2e/router/app/counter.service.ts b/e2e/router/app/counter.service.ts
new file mode 100644
index 000000000..15fe455c1
--- /dev/null
+++ b/e2e/router/app/counter.service.ts
@@ -0,0 +1,13 @@
+import { Injectable } from "@angular/core";
+import { BehaviorSubject } from "rxjs";
+
+@Injectable({
+ providedIn: "root"
+})
+export class CounterService {
+ counter$ = new BehaviorSubject(0);
+
+ tick() {
+ this.counter$.next(this.counter$.value + 1);
+ }
+}
\ No newline at end of file
diff --git a/e2e/router/app/first/first.component.ts b/e2e/router/app/first/first.component.ts
new file mode 100644
index 000000000..a792abc27
--- /dev/null
+++ b/e2e/router/app/first/first.component.ts
@@ -0,0 +1,76 @@
+import { Component, OnInit, OnDestroy, OnChanges, DoCheck } from "@angular/core";
+import { RouterExtensions } from "@nativescript/angular";
+import { Page } from "@nativescript/core";
+
+import { CounterService } from "../counter.service";
+import { ActivatedRoute } from "@angular/router";
+import { Subscription } from "rxjs";
+
+@Component({
+ selector: "first",
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+})
+export class FirstComponent implements OnInit, OnDestroy, DoCheck {
+ public message: string = "";
+ public doCheckCount: number = 0;
+ sub: Subscription;
+
+ constructor(
+ private routerExt: RouterExtensions,
+ private route: ActivatedRoute,
+ public service: CounterService,
+ page: Page) {
+
+ console.log("FirstComponent - constructor() page: " + page);
+ }
+
+ tapMe() {
+ console.log('here!!!')
+ }
+
+ ngOnInit() {
+ console.log("FirstComponent - ngOnInit()");
+ this.sub = this.route.queryParams.subscribe((params) =>{
+ console.log("FIRST PARAMS:");
+ console.log(params);
+ });
+ }
+
+ ngOnDestroy() {
+ this.sub.unsubscribe();
+ console.log("FirstComponent - ngOnDestroy()");
+ }
+
+ ngDoCheck() {
+ this.doCheckCount++;
+ console.log("FirstComponent - ngDoCheck(): " + this.doCheckCount);
+ }
+
+ reset() {
+ this.doCheckCount = 0;
+ }
+
+ goBack() {
+ this.message = "";
+ if (this.routerExt.canGoBack()) {
+ this.routerExt.back();
+ } else {
+ this.message = "canGoBack() - false"
+ }
+ }
+}
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..f0016b05c
--- /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, NativeScriptRouterModule } from "@nativescript/angular";
+
+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:[
+ NativeScriptRouterModule,
+ 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..705351aa6
--- /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 "@nativescript/core";
+import { RouterExtensions } from "@nativescript/angular";
+
+@Component({
+ selector: "nested-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..f1d2c57bb
--- /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 "@nativescript/core";
+@Component({
+ selector: "nested-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/lazy/lazy-componentless-route.component.ts b/e2e/router/app/lazy/lazy-componentless-route.component.ts
new file mode 100644
index 000000000..a3ba7abce
--- /dev/null
+++ b/e2e/router/app/lazy/lazy-componentless-route.component.ts
@@ -0,0 +1,43 @@
+import { Component, OnInit, OnDestroy, OnChanges } from "@angular/core";
+import { ActivatedRoute, Router, Route } from "@angular/router";
+import { Location } from "@angular/common";
+import { RouterExtensions } from "@nativescript/angular";
+
+import { Page } from "@nativescript/core/ui/page";
+
+@Component({
+ selector: "lazy-cmp-less-route",
+ template: `
+
+
+
+
+
+
+
+
+ `
+})
+export class LazyComponentlessRouteComponent implements OnInit, OnDestroy {
+ public message: string = "";
+ constructor(private routerExt: RouterExtensions, page: Page) {
+ console.log("LazyNestedRouteComponent - constructor() page: " + page);
+ }
+
+ ngOnInit() {
+ console.log("LazyNestedRouteComponent - ngOnInit()");
+ }
+
+ ngOnDestroy() {
+ console.log("LazyNestedRouteComponent - ngOnDestroy()");
+ }
+
+ goBack() {
+ this.message = "";
+ if (this.routerExt.canGoBack()) {
+ this.routerExt.back();
+ } else {
+ this.message = "canGoBack() - false"
+ }
+ }
+}
diff --git a/e2e/router/app/lazy/lazy.component.ts b/e2e/router/app/lazy/lazy.component.ts
new file mode 100644
index 000000000..5e41be109
--- /dev/null
+++ b/e2e/router/app/lazy/lazy.component.ts
@@ -0,0 +1,43 @@
+import { Component, OnInit, OnDestroy, OnChanges } from "@angular/core";
+import { ActivatedRoute, Router, Route } from "@angular/router";
+import { Location } from "@angular/common";
+import { RouterExtensions } from "@nativescript/angular";
+
+import { Page } from "@nativescript/core";
+
+@Component({
+ selector: "lazy",
+ template: `
+
+
+
+
+
+
+
+
+ `
+})
+export class LazyComponent implements OnInit, OnDestroy {
+ public message: string = "";
+ constructor(private routerExt: RouterExtensions, page: Page) {
+ console.log("LazyComponent - constructor() page: " + page);
+ }
+
+ ngOnInit() {
+ console.log("LazyComponent - ngOnInit()");
+ }
+
+ ngOnDestroy() {
+ console.log("LazyComponent - ngOnDestroy()");
+ }
+
+ goBack() {
+ this.message = "";
+ if (this.routerExt.canGoBack()) {
+ this.routerExt.back();
+ } else {
+ this.message = "canGoBack() - false"
+ }
+ }
+}
diff --git a/e2e/router/app/lazy/lazy.module.ts b/e2e/router/app/lazy/lazy.module.ts
new file mode 100644
index 000000000..b136ea276
--- /dev/null
+++ b/e2e/router/app/lazy/lazy.module.ts
@@ -0,0 +1,40 @@
+import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
+import { Route } from "@angular/router";
+
+import { NativeScriptCommonModule, NativeScriptRouterModule } from "@nativescript/angular";
+
+import { LazyComponent } from "./lazy.component";
+import { LazyComponentlessRouteComponent } from "./lazy-componentless-route.component";
+
+const routes: Route[] = [
+ {
+ path: "home",
+ component: LazyComponent
+ },
+ {
+ path: "nest",
+ children: [
+ {
+ path: "more",
+ component: LazyComponentlessRouteComponent
+ }
+ ]
+ }
+];
+
+@NgModule({
+ schemas: [NO_ERRORS_SCHEMA],
+ imports: [
+ NativeScriptCommonModule,
+ NativeScriptRouterModule,
+ NativeScriptRouterModule.forChild(routes)
+ ],
+ declarations: [
+ LazyComponent,
+ LazyComponentlessRouteComponent
+ ],
+ exports: [
+ NativeScriptRouterModule
+ ]
+})
+export class LazyModule { }
\ No newline at end of file
diff --git a/e2e/router/app/main.ts b/e2e/router/app/main.ts
new file mode 100644
index 000000000..1b1b3c521
--- /dev/null
+++ b/e2e/router/app/main.ts
@@ -0,0 +1,4 @@
+import { platformNativeScriptDynamic } from "@nativescript/angular/platform";
+import { AppModule } from "./app.module";
+
+platformNativeScriptDynamic().bootstrapModule(AppModule);
diff --git a/e2e/router/app/package.json b/e2e/router/app/package.json
new file mode 100644
index 000000000..706d1a31b
--- /dev/null
+++ b/e2e/router/app/package.json
@@ -0,0 +1,9 @@
+{
+ "main": "main.js",
+ "name": "nativescript-template-ng-tutorial",
+ "version": "3.1.0",
+ "android": {
+ "v8Flags": "--expose_gc",
+ "markingMode": "none"
+ }
+}
\ No newline at end of file
diff --git a/e2e/router/app/second/detail.component.ts b/e2e/router/app/second/detail.component.ts
new file mode 100644
index 000000000..f8b9acf6c
--- /dev/null
+++ b/e2e/router/app/second/detail.component.ts
@@ -0,0 +1,33 @@
+import { Component} from "@angular/core";
+import { ActivatedRoute } from "@angular/router";
+import { Page } from "@nativescript/core";
+import { Observable } from "rxjs";
+import { map } from "rxjs/operators";
+
+@Component({
+ selector: "detail",
+ template: `
+
+
+
+
+
+ `
+})
+export class DetailComponent {
+ public id$: Observable;
+
+ constructor(private route: ActivatedRoute, private page: Page) {
+ page.actionBarHidden = true;
+ console.log("DetailComponent - constructor()");
+ this.id$ = route.params.pipe(map(r => r["id"]));
+ }
+
+ ngOnInit() {
+ console.log("DetailComponent - ngOnInit()");
+ }
+
+ ngOnDestroy() {
+ console.log("DetailComponent - ngOnDestroy()");
+ }
+}
diff --git a/e2e/router/app/second/master.component.ts b/e2e/router/app/second/master.component.ts
new file mode 100644
index 000000000..a7b0d52b9
--- /dev/null
+++ b/e2e/router/app/second/master.component.ts
@@ -0,0 +1,30 @@
+import { Component, OnInit, OnDestroy } from "@angular/core";
+import { Page } from "@nativescript/core";
+@Component({
+ selector: "master",
+ template: `
+
+
+
+
+
+
+
+ `
+})
+export class MasterComponent implements OnInit, OnDestroy {
+ public details: Array = [1, 2, 3];
+
+ constructor(private page: Page) {
+ this.page.actionBarHidden = true;
+ 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/second.component.ts b/e2e/router/app/second/second.component.ts
new file mode 100644
index 000000000..390dd1c5f
--- /dev/null
+++ b/e2e/router/app/second/second.component.ts
@@ -0,0 +1,70 @@
+import { Component, OnInit, OnDestroy } from "@angular/core";
+import { ActivatedRoute } from "@angular/router";
+
+import { RouterExtensions } from "@nativescript/angular";
+import { Page } from "@nativescript/core";
+import { Observable, Subscription } from "rxjs";
+import { map } from "rxjs/operators";
+import { CounterService } from "../counter.service";
+
+@Component({
+ selector: "second",
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+})
+export class SecondComponent implements OnInit, OnDestroy {
+ public depth$: Observable;
+ public nextDepth$: Observable;
+ sub: Subscription;
+
+ constructor(
+ private routerExt: RouterExtensions,
+ private route: ActivatedRoute,
+ public service: CounterService,
+ 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));
+ }
+
+ ngOnInit() {
+ this.sub = this.route.queryParams.subscribe((params) => {
+ console.log("route.queryParams:", params);
+ });
+ console.log("SecondComponent - ngOnInit()");
+ }
+
+ ngOnDestroy() {
+ this.sub.unsubscribe();
+ console.log("SecondComponent - ngOnDestroy()");
+ }
+
+ goBack() {
+ 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
new file mode 100644
index 000000000..e22514e2c
--- /dev/null
+++ b/e2e/router/e2e/router.e2e-spec.ts
@@ -0,0 +1,542 @@
+import {
+ AppiumDriver,
+ UIElement,
+ createDriver,
+ SearchOptions,
+ nsCapabilities,
+} from "nativescript-dev-appium";
+import { isSauceLab } from "nativescript-dev-appium/lib/parser";
+import { ImageOptions } from "nativescript-dev-appium/lib/image-options";
+
+const QUEUE_WAIT_TIME: number = 600000; // Sometimes SauceLabs threads are not available and the tests wait in a queue to start. Wait 10 min before timeout.
+
+describe("Router", async function () {
+ let driver: AppiumDriver;
+
+ before(async function () {
+ this.timeout(QUEUE_WAIT_TIME);
+ nsCapabilities.testReporter.context = this;
+ driver = await createDriver();
+ driver.imageHelper.defaultTolerance = 50;
+ driver.imageHelper.defaultToleranceType = ImageOptions.pixel;
+ await driver.resetApp();
+ });
+
+ after(async function () {
+ if (isSauceLab) {
+ driver.sessionId().then(function (sessionId) {
+ console.log("Report https://saucelabs.com/beta/tests/" + sessionId);
+ });
+ }
+ await driver.quit();
+ console.log("Quit driver!");
+ });
+
+ afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logTestArtifacts(this.currentTest.title);
+ }
+ });
+
+ describe("Simple navigate and back", async function () {
+
+ it("should find First", async function () {
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate to Second(1)\\master", async function () {
+ await findAndClick(driver, "GO TO SECOND");
+
+ await assureSecondComponent(driver, 1);
+ await assureNestedMasterComponent(driver);
+ });
+
+ it("should navigate back to First", async function () {
+ await goBack(driver);
+ await assureFirstComponent(driver);
+ });
+ });
+
+ describe("Navigate inside nested outlet", async function () {
+
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ await driver.resetApp();
+ });
+
+ it("should find First", async function () {
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate to Second(1)\\master", async function () {
+ await findAndClick(driver, "GO TO SECOND");
+
+ await assureSecondComponent(driver, 1)
+ await assureNestedMasterComponent(driver);
+ });
+
+ it("should navigate to Second(1)\\detail(1) and back", async function () {
+ const detailBtn = await driver.findElementByAutomationText("DETAIL 1");
+ detailBtn.click();
+ await assureSecondComponent(driver, 1)
+ await assureNestedDetailComponent(driver, 1);
+
+ await goBack(driver);
+ await assureSecondComponent(driver, 1)
+ await assureNestedMasterComponent(driver);
+ });
+
+ it("should navigate to Second(1)\\detail(2) and back", async function () {
+ const detailBtn = await driver.findElementByAutomationText("DETAIL 2");
+ detailBtn.click();
+ await assureSecondComponent(driver, 1)
+ await assureNestedDetailComponent(driver, 2);
+
+ await goBack(driver);
+ await assureSecondComponent(driver, 1)
+ await assureNestedMasterComponent(driver);
+ });
+
+ it("should navigate back to First", async function () {
+ await goBack(driver);
+ await assureFirstComponent(driver);
+ });
+ });
+
+ describe("Navigate to same component with different param", async function () {
+
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ await driver.resetApp();
+ });
+
+ it("should find First", async function () {
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate to Second(1)\\master", async function () {
+ await findAndClick(driver, "GO TO SECOND");
+
+ await assureSecondComponent(driver, 1)
+ await assureNestedMasterComponent(driver);
+ });
+
+ it("should navigate to Second(2)\\master", async function () {
+ const navigationButton =
+ await driver.findElementByAutomationText("GO TO NEXT SECOND");
+ navigationButton.click();
+
+ await assureSecondComponent(driver, 2)
+ await assureNestedMasterComponent(driver);
+ });
+
+ it("should navigate back to Second(1)\\master", async function () {
+ await goBack(driver);
+
+ await assureSecondComponent(driver, 1)
+ await assureNestedMasterComponent(driver);
+ });
+
+ it("should navigate back to First", async function () {
+ await goBack(driver);
+ await assureFirstComponent(driver);
+ });
+ });
+
+ describe("Nested navigation + page navigation", async function () {
+
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ await driver.resetApp();
+ });
+
+ it("should find First", async function () {
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate to Second(1)\\master", async function () {
+ await findAndClick(driver, "GO TO SECOND");
+
+ await assureSecondComponent(driver, 1)
+ await assureNestedMasterComponent(driver);
+ });
+
+ it("should navigate to Second(1)\\detail(1)", async function () {
+ const detailBtn = await driver.findElementByAutomationText("DETAIL 1");
+ detailBtn.click();
+
+ await assureSecondComponent(driver, 1)
+ await assureNestedDetailComponent(driver, 1);
+ });
+
+ it("should navigate to Second(2)\\master", async function () {
+ const navigationButton =
+ await driver.findElementByAutomationText("GO TO NEXT SECOND");
+ navigationButton.click();
+
+ await assureSecondComponent(driver, 2)
+ await assureNestedMasterComponent(driver);
+ });
+
+ it("should navigate to Second(2)\\detail(2)", async function () {
+ const detailBtn = await driver.findElementByAutomationText("DETAIL 2");
+ detailBtn.click();
+
+ await assureSecondComponent(driver, 2)
+ await assureNestedDetailComponent(driver, 2);
+ });
+
+ it("should navigate to First", async function () {
+ await findAndClick(driver, "GO TO FIRST");
+
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate the whole stack", async function () {
+ await goBack(driver);
+ await assureSecondComponent(driver, 2)
+ await assureNestedDetailComponent(driver, 2);
+
+ await goBack(driver);
+ await assureSecondComponent(driver, 2)
+ await assureNestedMasterComponent(driver);
+
+ await goBack(driver);
+ await assureSecondComponent(driver, 1)
+ await assureNestedDetailComponent(driver, 1);
+
+ await goBack(driver);
+ await assureSecondComponent(driver, 1)
+ await assureNestedMasterComponent(driver);
+
+ await goBack(driver);
+ await assureFirstComponent(driver);
+ });
+ });
+
+ describe("Nested name navigation + page navigation", async function () {
+
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ await driver.resetApp();
+ });
+
+ it("should find First", async function () {
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate to Second(1)\\master", async function () {
+ await findAndClick(driver, "GO TO SECOND");
+
+ await assureSecondComponent(driver, 1)
+ await assureNestedMasterComponent(driver);
+ });
+
+ it("should load nested named Master", async function () {
+ await findAndClick(driver, "LOAD NESTED NAMED OUTLET");
+ await assureNamedNestedMasterComponent(driver);
+ });
+
+ it("should navigate to nested named Master Detail\\1", async function () {
+ const navigationButton =
+ await driver.findElementByAutomationText("DETAIL-NAMED 1");
+ navigationButton.click();
+
+ await assureNamedNestedDetailComponent(driver, 1);
+ });
+
+ it("should navigate back to Master and navigate to Detail\\2", async function () {
+ let navigationButton =
+ await driver.findElementByAutomationText("BACK-NESTED");
+ navigationButton.click();
+
+ await assureNamedNestedMasterComponent(driver);
+
+ navigationButton =
+ await driver.findElementByAutomationText("DETAIL-NAMED 2");
+ navigationButton.click();
+
+ await assureNamedNestedDetailComponent(driver, 2);
+ });
+ });
+
+ describe("Shouldn't be able to navigate back on startup", async function () {
+
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ await driver.resetApp();
+ });
+
+ it("should find First", async function () {
+ await assureFirstComponent(driver);
+ });
+
+ it("shouldn't be able to go back", async function () {
+ await goBack(driver);
+ await driver.findElementByAutomationText("canGoBack() - false");
+ });
+ });
+
+ describe("Shouldn't be able to navigate back after cleared history", async function () {
+
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ await driver.resetApp();
+ });
+
+ it("should find First", async function () {
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate to Second(1)\\master", async function () {
+ await findAndClick(driver, "GO TO SECOND");
+
+ await assureSecondComponent(driver, 1)
+ await assureNestedMasterComponent(driver);
+ });
+
+ it("should navigate to Second(1)\\master", async function () {
+ await findAndClick(driver, "GO TO FIRST(CLEAR)");
+
+ await assureFirstComponent(driver);
+ });
+
+ it("shouldn't be able to go back", async function () {
+ await goBack(driver);
+ await driver.findElementByAutomationText("canGoBack() - false");
+ });
+ });
+
+ describe("Navigate to componentless route", async function () {
+
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ await driver.resetApp();
+ });
+
+ it("should find First", async function () {
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate to ComponentlessSecond(100)/detail(200)", async function () {
+ const navigationButton =
+ await driver.findElementByAutomationText("GO TO C-LESS SECOND");
+ navigationButton.click();
+
+ await assureSecondComponent(driver, 100)
+ await assureNestedDetailComponent(driver, 200);
+ });
+
+ it("should navigate to First", async function () {
+ await findAndClick(driver, "GO TO FIRST");
+
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate the whole stack", async function () {
+ await goBack(driver);
+ await assureSecondComponent(driver, 100)
+ await assureNestedDetailComponent(driver, 200);
+
+ await goBack(driver);
+ await assureFirstComponent(driver);
+ });
+ });
+
+ describe("Navigate to lazy module", async function () {
+
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ await driver.resetApp();
+ });
+
+ it("should find First", async function () {
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate to lazy\\home", async function () {
+ await findAndClick(driver, "GO TO LAZY HOME");
+ await assureLazyComponent(driver);
+ });
+
+ it("should navigate to First", async function () {
+ await findAndClick(driver, "GO TO FIRST");
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate back to lazy\\home", async function () {
+ await goBack(driver);
+ await assureLazyComponent(driver);
+ });
+
+ it("should navigate to First again", async function () {
+ await findAndClick(driver, "GO TO FIRST");
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate the whole stack", async function () {
+ await goBack(driver);
+ await assureLazyComponent(driver);
+
+ await goBack(driver);
+ await assureFirstComponent(driver);
+ });
+ });
+
+ describe("Navigate to componentless lazy module route", async function () {
+
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ await driver.resetApp();
+ });
+
+ it("should find First", async function () {
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate to nest\\more (componentless lazy route)", async function () {
+ await findAndClick(driver, "GO TO C-LESS LAZY");
+
+ await assureComponentlessLazyComponent(driver);
+ });
+
+ it("should navigate to lazy\\home", async function () {
+ await findAndClick(driver, "GO TO LAZY HOME");
+
+ await assureLazyComponent(driver);
+ });
+
+ it("should navigate to First", async function () {
+ await findAndClick(driver, "GO TO FIRST");
+
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate the whole stack", async function () {
+ await goBack(driver);
+ await assureLazyComponent(driver);
+
+ await goBack(driver);
+ await assureComponentlessLazyComponent(driver);
+
+ await goBack(driver);
+ await assureFirstComponent(driver);
+ });
+ });
+
+ describe("Simple navigate and back should trigger only one CD on FirstComponent", async function () {
+
+ before(async function () {
+ nsCapabilities.testReporter.context = this;
+ await driver.resetApp();
+ });
+
+ it("should find First", async function () {
+ await assureFirstComponent(driver);
+ });
+
+ it("should reset counter", async function () {
+ await findAndClick(driver, "RESET");
+ await driver.waitForElement("CHECK: 1");
+ });
+
+ it("should navigate to Second(1)\\master", async function () {
+ await findAndClick(driver, "GO TO SECOND");
+
+ await assureSecondComponent(driver, 1);
+ await assureNestedMasterComponent(driver);
+ });
+
+ it("should navigate back to First", async function () {
+ await goBack(driver);
+ await assureFirstComponent(driver);
+ await driver.waitForElement("CHECK: 2");
+ });
+ });
+
+ describe("Simple navigate and back should trigger only one CD on FirstComponent even with 3 changes in service", function () {
+
+ before(async function () {
+ driver = await createDriver();
+ driver.imageHelper.defaultTolerance = 50;
+ driver.imageHelper.defaultToleranceType = ImageOptions.pixel;
+ await driver.resetApp();
+ });
+
+ it("should find First", async function () {
+ await assureFirstComponent(driver);
+ });
+
+ it("should reset counter", async function () {
+ await findAndClick(driver, "RESET");
+ await driver.waitForElement("CHECK: 1");
+ await driver.waitForElement("COUNTER: 0");
+ });
+
+ it("should navigate to Second(1)\\master", async function () {
+ await findAndClick(driver, "GO TO SECOND");
+
+ await assureSecondComponent(driver, 1);
+ await assureNestedMasterComponent(driver);
+ });
+
+ it("should increase counter", async function () {
+ await findAndClick(driver, "TICK");
+ await findAndClick(driver, "TICK");
+ await findAndClick(driver, "TICK");
+ });
+
+ it("should navigate back to First", async function () {
+ await goBack(driver);
+ await assureFirstComponent(driver);
+ await driver.waitForElement("CHECK: 2");
+ await driver.waitForElement("COUNTER: 3");
+ });
+ });
+});
+
+async function assureFirstComponent(driver: AppiumDriver) {
+ await driver.findElementByAutomationText("FirstComponent");
+}
+
+async function assureLazyComponent(driver: AppiumDriver) {
+ await driver.findElementByAutomationText("LazyComponent");
+}
+
+async function assureComponentlessLazyComponent(driver: AppiumDriver) {
+ await driver.findElementByAutomationText("Lazy Componentless Route");
+}
+
+async function assureNamedNestedMasterComponent(driver: AppiumDriver) {
+ await driver.findElementByAutomationText("NamedNestedMaster");
+}
+
+async function assureNamedNestedDetailComponent(driver: AppiumDriver, param: number) {
+ await driver.findElementByAutomationText("NamedNestedDetail");
+ await driver.findElementByAutomationText(`nested-named-param: ${param}`);
+
+}
+
+async function assureSecondComponent(driver: AppiumDriver, param: number) {
+ await driver.findElementByAutomationText("SecondComponent");
+ await driver.findElementByAutomationText(`param: ${param}`);
+}
+
+async function assureNestedMasterComponent(driver: AppiumDriver) {
+ await driver.findElementByAutomationText("NestedMaster");
+}
+
+async function assureNestedDetailComponent(driver: AppiumDriver, param: number) {
+ await driver.findElementByAutomationText("NestedDetail");
+ await driver.findElementByAutomationText(`nested-param: ${param}`);
+}
+
+async function goBack(driver: AppiumDriver) {
+ const backButton = await driver.waitForElement("BACK");
+ await backButton.click();
+}
+
+async function findAndClick(driver: AppiumDriver, text: string) {
+ const navigationButton = await driver.waitForElement(text);
+ await navigationButton.click();
+}
\ No newline at end of file
diff --git a/e2e/router/e2e/setup.ts b/e2e/router/e2e/setup.ts
new file mode 100644
index 000000000..11b854fca
--- /dev/null
+++ b/e2e/router/e2e/setup.ts
@@ -0,0 +1,19 @@
+import { startServer, stopServer, ITestReporter, nsCapabilities, LogImageType } from "nativescript-dev-appium";
+const addContext = require('mochawesome/addContext');
+
+const testReporterContext = {};
+testReporterContext.name = "mochawesome";
+testReporterContext.reportDir = "mochawesome-report";
+testReporterContext.log = addContext;
+testReporterContext.logImageTypes = [LogImageType.screenshots];
+nsCapabilities.testReporter = testReporterContext;
+
+before("start server", async function () {
+ nsCapabilities.testReporter.context = this;
+ await startServer();
+});
+
+after("stop server", async function () {
+ nsCapabilities.testReporter.context = this;
+ await stopServer();
+});
diff --git a/e2e/router/e2e/tsconfig.json b/e2e/router/e2e/tsconfig.json
new file mode 100644
index 000000000..43e3b7ee3
--- /dev/null
+++ b/e2e/router/e2e/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es6",
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "importHelpers": false,
+ "sourceMap": true,
+ "types": [
+ "node",
+ "mocha",
+ "chai"
+ ],
+ "lib": [
+ "es6",
+ "dom"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/e2e/router/package.json b/e2e/router/package.json
new file mode 100644
index 000000000..b25f2ad6e
--- /dev/null
+++ b/e2e/router/package.json
@@ -0,0 +1,60 @@
+{
+ "description": "NativeScript Application",
+ "license": "SEE LICENSE IN ",
+ "readme": "NativeScript Application",
+ "repository": "",
+ "nativescript": {
+ "id": "org.nativescript.router",
+ "tns-ios": {
+ "version": "6.5.1"
+ }
+ },
+ "dependencies": {
+ "@angular/animations": "~9.1.0",
+ "@angular/common": "~9.1.0",
+ "@angular/compiler": "~9.1.0",
+ "@angular/core": "~9.1.0",
+ "@angular/forms": "~9.1.0",
+ "@angular/platform-browser": "~9.1.0",
+ "@angular/platform-browser-dynamic": "~9.1.0",
+ "@angular/router": "~9.1.0",
+ "@nativescript/angular": "file:../../dist/nativescript-angular-scoped.tgz",
+ "reflect-metadata": "~0.1.8",
+ "rxjs": "~6.5.5",
+ "@nativescript/core": "next",
+ "zone.js": "^0.10.3"
+ },
+ "devDependencies": {
+ "@angular/compiler-cli": "~9.1.0",
+ "@ngtools/webpack": "~9.1.0",
+ "@types/chai": "~4.1.7",
+ "@types/mocha": "~5.2.5",
+ "@types/node": "^10.12.12",
+ "babel-traverse": "6.25.0",
+ "babel-types": "6.25.0",
+ "babylon": "6.17.4",
+ "chai-as-promised": "~7.1.1",
+ "colors": "^1.1.2",
+ "lazy": "1.0.11",
+ "mocha": "~5.2.0",
+ "mochawesome": "~3.1.2",
+ "nativescript-dev-appium": "^6.0.0",
+ "nativescript-dev-webpack": "next",
+ "tslib": "^1.7.1",
+ "typescript": "~3.8.3"
+ },
+ "scripts": {
+ "clean": "npx rimraf hooks node_modules platforms package-lock.json",
+ "e2e": "tsc -p e2e && mocha --opts ../config/mocha.opts --recursive e2e --appiumCapsLocation ../config/appium.capabilities.json",
+ "e2e-watch": "tsc -p e2e --watch",
+ "compile-tests-w": "tsc -p e2e --watch",
+ "setup": "cd ../../nativescript-angular && npm run prep.apps && cd ../e2e/router && npm run clean",
+ "u": "update-ns-webpack",
+ "ns-verify-bundle": "ns-verify-bundle",
+ "update-ns-webpack": "update-ns-webpack",
+ "ngcc": "ngcc --properties es2015 module main --first-only",
+ "postinstall": "npm run ngcc",
+ "ios": "tns debug ios --env.aot --emulator --no-hmr",
+ "android": "tns debug android --env.aot --emulator --no-hmr"
+ }
+}
diff --git a/e2e/router/references.d.ts b/e2e/router/references.d.ts
new file mode 100644
index 000000000..b14f3837d
--- /dev/null
+++ b/e2e/router/references.d.ts
@@ -0,0 +1 @@
+/// Needed for autocompletion and compilation.
\ No newline at end of file
diff --git a/e2e/router/tsconfig.esm.json b/e2e/router/tsconfig.esm.json
new file mode 100644
index 000000000..a96f6bbc6
--- /dev/null
+++ b/e2e/router/tsconfig.esm.json
@@ -0,0 +1,7 @@
+{
+ "extends": "./tsconfig",
+ "compilerOptions": {
+ "module": "ESNext",
+ "moduleResolution": "node"
+ }
+}
diff --git a/e2e/router/tsconfig.json b/e2e/router/tsconfig.json
new file mode 100644
index 000000000..ac4e60f3a
--- /dev/null
+++ b/e2e/router/tsconfig.json
@@ -0,0 +1,31 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es5",
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "noEmitHelpers": true,
+ "noEmitOnError": true,
+ "skipLibCheck": true,
+ "lib": [
+ "es2017",
+ "dom",
+ "es6"
+ ],
+ "baseUrl": ".",
+ "paths": {
+ "~/*": [
+ "app/*"
+ ]
+ }
+ },
+ "files": [
+ "./app/main.ts"
+ ],
+ "exclude": [
+ "node_modules",
+ "platforms",
+ "**/*.aot",
+ "e2e"
+ ]
+}
\ No newline at end of file
diff --git a/e2e/router/tsconfig.tns.json b/e2e/router/tsconfig.tns.json
new file mode 100644
index 000000000..a96f6bbc6
--- /dev/null
+++ b/e2e/router/tsconfig.tns.json
@@ -0,0 +1,7 @@
+{
+ "extends": "./tsconfig",
+ "compilerOptions": {
+ "module": "ESNext",
+ "moduleResolution": "node"
+ }
+}
diff --git a/e2e/single-page/.gitignore b/e2e/single-page/.gitignore
new file mode 100644
index 000000000..e3eb2283a
--- /dev/null
+++ b/e2e/single-page/.gitignore
@@ -0,0 +1,11 @@
+.vscode
+
+platforms
+node_modules
+hooks
+
+/**/*.js
+/**/*.map
+e2e/reports
+test-results.xml
+mochawesome-report
\ No newline at end of file
diff --git a/e2e/single-page/app/App_Resources/Android/app.gradle b/e2e/single-page/app/App_Resources/Android/app.gradle
new file mode 100644
index 000000000..23f5c481b
--- /dev/null
+++ b/e2e/single-page/app/App_Resources/Android/app.gradle
@@ -0,0 +1,23 @@
+// Add your native dependencies here:
+
+// Uncomment to add recyclerview-v7 dependency
+//dependencies {
+// compile 'com.android.support:recyclerview-v7:+'
+//}
+
+android {
+ defaultConfig {
+ generatedDensities = []
+ applicationId = "org.nativescript.singlepage"
+
+ //override supported platforms
+ // ndk {
+ // abiFilters.clear()
+ // abiFilters "armeabi-v7a"
+ // }
+
+ }
+ aaptOptions {
+ additionalParameters "--no-version-vectors"
+ }
+}
diff --git a/e2e/single-page/app/App_Resources/Android/src/main/AndroidManifest.xml b/e2e/single-page/app/App_Resources/Android/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..9db832151
--- /dev/null
+++ b/e2e/single-page/app/App_Resources/Android/src/main/AndroidManifest.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/single-page/app/App_Resources/Android/src/main/res/drawable-nodpi/background.png b/e2e/single-page/app/App_Resources/Android/src/main/res/drawable-nodpi/background.png
new file mode 100644
index 000000000..748b2adf5
Binary files /dev/null and b/e2e/single-page/app/App_Resources/Android/src/main/res/drawable-nodpi/background.png differ
diff --git a/e2e/single-page/app/App_Resources/Android/src/main/res/drawable-nodpi/icon.png b/e2e/single-page/app/App_Resources/Android/src/main/res/drawable-nodpi/icon.png
new file mode 100755
index 000000000..ddfc17a71
Binary files /dev/null and b/e2e/single-page/app/App_Resources/Android/src/main/res/drawable-nodpi/icon.png differ
diff --git a/e2e/single-page/app/App_Resources/Android/src/main/res/drawable-nodpi/logo.png b/e2e/single-page/app/App_Resources/Android/src/main/res/drawable-nodpi/logo.png
new file mode 100644
index 000000000..b9e102a76
Binary files /dev/null and b/e2e/single-page/app/App_Resources/Android/src/main/res/drawable-nodpi/logo.png differ
diff --git a/e2e/single-page/app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml b/e2e/single-page/app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml
new file mode 100644
index 000000000..ada77f92c
--- /dev/null
+++ b/e2e/single-page/app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml
@@ -0,0 +1,8 @@
+
+ -
+
+
+ -
+
+
+
\ No newline at end of file
diff --git a/e2e/single-page/app/App_Resources/Android/src/main/res/values-v21/colors.xml b/e2e/single-page/app/App_Resources/Android/src/main/res/values-v21/colors.xml
new file mode 100644
index 000000000..a64641a9d
--- /dev/null
+++ b/e2e/single-page/app/App_Resources/Android/src/main/res/values-v21/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #3d5afe
+
\ No newline at end of file
diff --git a/e2e/single-page/app/App_Resources/Android/src/main/res/values-v21/styles.xml b/e2e/single-page/app/App_Resources/Android/src/main/res/values-v21/styles.xml
new file mode 100644
index 000000000..dac8727c8
--- /dev/null
+++ b/e2e/single-page/app/App_Resources/Android/src/main/res/values-v21/styles.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/single-page/app/App_Resources/Android/src/main/res/values/colors.xml b/e2e/single-page/app/App_Resources/Android/src/main/res/values/colors.xml
new file mode 100644
index 000000000..74ad8829c
--- /dev/null
+++ b/e2e/single-page/app/App_Resources/Android/src/main/res/values/colors.xml
@@ -0,0 +1,7 @@
+
+
+ #F5F5F5
+ #757575
+ #33B5E5
+ #272734
+
\ No newline at end of file
diff --git a/e2e/single-page/app/App_Resources/Android/src/main/res/values/styles.xml b/e2e/single-page/app/App_Resources/Android/src/main/res/values/styles.xml
new file mode 100644
index 000000000..1e8c7f29b
--- /dev/null
+++ b/e2e/single-page/app/App_Resources/Android/src/main/res/values/styles.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000..1953734f4
--- /dev/null
+++ b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,92 @@
+{
+ "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"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png
new file mode 100644
index 000000000..9e15af09d
Binary files /dev/null and b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png differ
diff --git a/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png
new file mode 100644
index 000000000..7b9e55537
Binary files /dev/null and b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png differ
diff --git a/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png
new file mode 100644
index 000000000..76f61ec1f
Binary files /dev/null and b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png differ
diff --git a/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png
new file mode 100644
index 000000000..15b06db11
Binary files /dev/null and b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png differ
diff --git a/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png
new file mode 100644
index 000000000..585065f94
Binary files /dev/null and b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png differ
diff --git a/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png
new file mode 100644
index 000000000..a450c421d
Binary files /dev/null and b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png differ
diff --git a/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png
new file mode 100644
index 000000000..457b6d94c
Binary files /dev/null and b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png differ
diff --git a/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png
new file mode 100644
index 000000000..fa5a6ac86
Binary files /dev/null and b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png differ
diff --git a/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png
new file mode 100644
index 000000000..94abcf70d
Binary files /dev/null and b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png differ
diff --git a/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png
new file mode 100644
index 000000000..2e71dd3a0
Binary files /dev/null and b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png differ
diff --git a/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png
new file mode 100644
index 000000000..4abc9ec50
Binary files /dev/null and b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png differ
diff --git a/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/Contents.json b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/Contents.json
new file mode 100644
index 000000000..da4a164c9
--- /dev/null
+++ b/e2e/single-page/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/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
new file mode 100644
index 000000000..4414bad08
--- /dev/null
+++ b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
@@ -0,0 +1,158 @@
+{
+ "images" : [
+ {
+ "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/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png b/e2e/single-page/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/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png differ
diff --git a/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png b/e2e/single-page/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/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png differ
diff --git a/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png b/e2e/single-page/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/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png differ
diff --git a/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png b/e2e/single-page/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/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png differ
diff --git a/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png b/e2e/single-page/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/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png differ
diff --git a/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png b/e2e/single-page/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/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png differ
diff --git a/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png b/e2e/single-page/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/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png differ
diff --git a/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png b/e2e/single-page/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/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png differ
diff --git a/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png
new file mode 100644
index 000000000..9f1f6ce3e
Binary files /dev/null and b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png differ
diff --git a/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png b/e2e/single-page/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/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png differ
diff --git a/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json
new file mode 100644
index 000000000..4f4e9c506
--- /dev/null
+++ b/e2e/single-page/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/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png b/e2e/single-page/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/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png differ
diff --git a/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png b/e2e/single-page/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/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png differ
diff --git a/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json b/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json
new file mode 100644
index 000000000..23c0ffd7a
--- /dev/null
+++ b/e2e/single-page/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/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png b/e2e/single-page/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/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png differ
diff --git a/e2e/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png b/e2e/single-page/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/single-page/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png differ
diff --git a/e2e/single-page/app/App_Resources/iOS/Info.plist b/e2e/single-page/app/App_Resources/iOS/Info.plist
new file mode 100644
index 000000000..ea3e3ea23
--- /dev/null
+++ b/e2e/single-page/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/single-page/app/App_Resources/iOS/LaunchScreen.storyboard b/e2e/single-page/app/App_Resources/iOS/LaunchScreen.storyboard
new file mode 100644
index 000000000..2ad9471e1
--- /dev/null
+++ b/e2e/single-page/app/App_Resources/iOS/LaunchScreen.storyboard
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/single-page/app/App_Resources/iOS/build.xcconfig b/e2e/single-page/app/App_Resources/iOS/build.xcconfig
new file mode 100644
index 000000000..4b0118490
--- /dev/null
+++ b/e2e/single-page/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/single-page/app/README.md b/e2e/single-page/app/README.md
new file mode 100644
index 000000000..ebe60c416
--- /dev/null
+++ b/e2e/single-page/app/README.md
@@ -0,0 +1,5 @@
+# NativeScript Tutorial Angular Template
+
+This repo serves as the starting point for NativeScript’s [Angular Getting Started Guide](https://docs.nativescript.org/angular/tutorial/ng-chapter-0).
+
+Please file any issues with this template on the [NativeScript/docs repository](https://github.com/nativescript/docs), which is where the tutorial content lives.
\ No newline at end of file
diff --git a/e2e/single-page/app/app-routing.module.ts b/e2e/single-page/app/app-routing.module.ts
new file mode 100644
index 000000000..a1528f7ff
--- /dev/null
+++ b/e2e/single-page/app/app-routing.module.ts
@@ -0,0 +1,32 @@
+import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
+import { NativeScriptRouterModule } from "@nativescript/angular/router";
+
+import { FirstComponent } from "./first/first.component"
+import { SecondComponent } from "./second/second.component"
+
+export const routes = [
+ {
+ path: "",
+ redirectTo: "/first",
+ pathMatch: "full"
+ },
+ {
+ path: "first",
+ component: FirstComponent,
+ },
+ {
+ path: "second/:id", component: SecondComponent,
+ },
+];
+
+export const navigatableComponents = [
+ FirstComponent,
+ SecondComponent,
+];
+
+@NgModule({
+ imports: [NativeScriptRouterModule.forRoot(routes)],
+ exports: [NativeScriptRouterModule],
+})
+export class AppRoutingModule { }
+
diff --git a/e2e/single-page/app/app.component.ts b/e2e/single-page/app/app.component.ts
new file mode 100644
index 000000000..7b9069e2c
--- /dev/null
+++ b/e2e/single-page/app/app.component.ts
@@ -0,0 +1,19 @@
+import { Component } from "@angular/core";
+
+@Component({
+ template: `
+
+
+
+
+
+
+
+
+
+
+ `
+})
+export class AppComponent { }
+
diff --git a/e2e/single-page/app/app.css b/e2e/single-page/app/app.css
new file mode 100644
index 000000000..6e4e9cee1
--- /dev/null
+++ b/e2e/single-page/app/app.css
@@ -0,0 +1,24 @@
+.title {
+ font-size: 15;
+ margin: 16;
+}
+
+.nav {
+ orientation: horizontal;
+ horizontal-align: stretch;
+ vertical-align: bottom;
+ padding: 4;
+ background-color: lightblue;
+ justify-content: space-around;
+ align-content: center;
+}
+
+.link {
+ margin: 10 30;
+ horizontal-align: center;
+}
+
+.active {
+ color: orangered;
+ background-color: lightgreen;
+}
diff --git a/e2e/single-page/app/app.module.ngfactory.d.ts b/e2e/single-page/app/app.module.ngfactory.d.ts
new file mode 100644
index 000000000..793157de3
--- /dev/null
+++ b/e2e/single-page/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/single-page/app/app.module.ts b/e2e/single-page/app/app.module.ts
new file mode 100644
index 000000000..cadf831d8
--- /dev/null
+++ b/e2e/single-page/app/app.module.ts
@@ -0,0 +1,39 @@
+import { NgModule, NgModuleFactoryLoader, NO_ERRORS_SCHEMA } from "@angular/core";
+import { NativeScriptModule } from "@nativescript/angular";
+import { NSModuleFactoryLoader } from "@nativescript/angular/router";
+ import { isNavigationButton } from "@nativescript/angular/directives/action-bar";
+
+import {
+ AppRoutingModule,
+ navigatableComponents,
+} from "./app-routing.module";
+
+import { AppComponent } from "./app.component";
+
+import { routeReuseStrategyTraceCategory, routerTraceCategory } from "@nativescript/angular/trace";
+import { setCategories, enable } from "@nativescript/core/trace";
+import { ModalComponent } from "./second/modal/modal.component";
+setCategories(routerTraceCategory + "," + routeReuseStrategyTraceCategory);
+enable();
+
+@NgModule({
+ declarations: [
+ AppComponent,
+ ModalComponent,
+ ...navigatableComponents,
+ ],
+ entryComponents:[
+ ModalComponent
+ ],
+ bootstrap: [AppComponent],
+ providers: [
+ { provide: NgModuleFactoryLoader, useClass: NSModuleFactoryLoader }
+ ],
+ imports: [
+ NativeScriptModule,
+ AppRoutingModule,
+ ],
+ schemas: [NO_ERRORS_SCHEMA],
+})
+export class AppModule { }
+
diff --git a/e2e/single-page/app/first/first.component.ts b/e2e/single-page/app/first/first.component.ts
new file mode 100644
index 000000000..e2d49e3a9
--- /dev/null
+++ b/e2e/single-page/app/first/first.component.ts
@@ -0,0 +1,27 @@
+import { Component, OnInit, OnDestroy } from "@angular/core";
+
+import { Page } from "@nativescript/core/ui/page";
+
+@Component({
+ selector: "first",
+ template: `
+
+
+
+
+
+ `
+})
+export class FirstComponent implements OnInit, OnDestroy {
+ constructor(page: Page) {
+ console.log("FirstComponent - constructor() page: " + page);
+ }
+
+ ngOnInit() {
+ console.log("FirstComponent - ngOnInit()");
+ }
+
+ ngOnDestroy() {
+ console.log("FirstComponent - ngOnDestroy()");
+ }
+}
diff --git a/e2e/single-page/app/main.ts b/e2e/single-page/app/main.ts
new file mode 100644
index 000000000..52e4bc5a2
--- /dev/null
+++ b/e2e/single-page/app/main.ts
@@ -0,0 +1,4 @@
+import { platformNativeScriptDynamic } from "@nativescript/angular/platform";
+import { AppModule } from "./app.module";
+
+platformNativeScriptDynamic({ createFrameOnBootstrap: true }).bootstrapModule(AppModule);
diff --git a/e2e/single-page/app/package.json b/e2e/single-page/app/package.json
new file mode 100644
index 000000000..706d1a31b
--- /dev/null
+++ b/e2e/single-page/app/package.json
@@ -0,0 +1,9 @@
+{
+ "main": "main.js",
+ "name": "nativescript-template-ng-tutorial",
+ "version": "3.1.0",
+ "android": {
+ "v8Flags": "--expose_gc",
+ "markingMode": "none"
+ }
+}
\ No newline at end of file
diff --git a/e2e/single-page/app/second/modal/modal.component.html b/e2e/single-page/app/second/modal/modal.component.html
new file mode 100644
index 000000000..99e8c4358
--- /dev/null
+++ b/e2e/single-page/app/second/modal/modal.component.html
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/e2e/single-page/app/second/modal/modal.component.ts b/e2e/single-page/app/second/modal/modal.component.ts
new file mode 100644
index 000000000..895f47679
--- /dev/null
+++ b/e2e/single-page/app/second/modal/modal.component.ts
@@ -0,0 +1,19 @@
+import { Component } from '@angular/core';
+import { ModalDialogParams } from "@nativescript/angular/modal-dialog";
+
+@Component({
+ moduleId: module.id,
+ selector: 'modal',
+ templateUrl: './modal.component.html'
+})
+
+export class ModalComponent {
+
+ constructor(private params: ModalDialogParams) {
+ }
+
+ public close() {
+ this.params.closeCallback();
+ }
+
+}
\ No newline at end of file
diff --git a/e2e/single-page/app/second/second.component.ts b/e2e/single-page/app/second/second.component.ts
new file mode 100644
index 000000000..e36fb3aec
--- /dev/null
+++ b/e2e/single-page/app/second/second.component.ts
@@ -0,0 +1,60 @@
+import { Component, OnInit, OnDestroy, ViewContainerRef } from "@angular/core";
+import { ActivatedRoute, Router, Route } from "@angular/router";
+
+import { ModalDialogService, ModalDialogOptions } from "@nativescript/angular";
+import { Page } from "@nativescript/core/ui/page";
+import { Observable } from "rxjs";
+import { map } from "rxjs/operators";
+import { RouterExtensions } from "@nativescript/angular/router";
+import { ModalComponent } from "./modal/modal.component";
+
+@Component({
+ selector: "second",
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+})
+export class SecondComponent implements OnInit, OnDestroy {
+ public id$: Observable;
+ constructor(route: ActivatedRoute,
+ private routerExtensions: RouterExtensions,
+ private viewContainerRef: ViewContainerRef,
+ private modalService: ModalDialogService) {
+ this.id$ = route.params.pipe(map(r => +r["id"]));
+ }
+
+ ngOnInit() {
+ console.log("SecondComponent - ngOnInit()");
+ }
+
+ ngOnDestroy() {
+ console.log("SecondComponent - ngOnDestroy()");
+ }
+
+ back() {
+ this.routerExtensions.back();
+ }
+
+ onShowModal() {
+ let options: ModalDialogOptions = {
+ viewContainerRef: this.viewContainerRef,
+ context: {
+ },
+ fullscreen: true
+ };
+
+ this.modalService.showModal(ModalComponent, options).then((dialogResult: string) => {
+ });
+ }
+}
\ No newline at end of file
diff --git a/e2e/single-page/e2e/setup.ts b/e2e/single-page/e2e/setup.ts
new file mode 100644
index 000000000..11b854fca
--- /dev/null
+++ b/e2e/single-page/e2e/setup.ts
@@ -0,0 +1,19 @@
+import { startServer, stopServer, ITestReporter, nsCapabilities, LogImageType } from "nativescript-dev-appium";
+const addContext = require('mochawesome/addContext');
+
+const testReporterContext = {};
+testReporterContext.name = "mochawesome";
+testReporterContext.reportDir = "mochawesome-report";
+testReporterContext.log = addContext;
+testReporterContext.logImageTypes = [LogImageType.screenshots];
+nsCapabilities.testReporter = testReporterContext;
+
+before("start server", async function () {
+ nsCapabilities.testReporter.context = this;
+ await startServer();
+});
+
+after("stop server", async function () {
+ nsCapabilities.testReporter.context = this;
+ await stopServer();
+});
diff --git a/e2e/single-page/e2e/tests.e2e-spec.ts b/e2e/single-page/e2e/tests.e2e-spec.ts
new file mode 100644
index 000000000..fb6d16d3c
--- /dev/null
+++ b/e2e/single-page/e2e/tests.e2e-spec.ts
@@ -0,0 +1,88 @@
+import {
+ AppiumDriver,
+ createDriver,
+ nsCapabilities,
+} from "nativescript-dev-appium";
+import { isSauceLab } from "nativescript-dev-appium/lib/parser";
+import { ImageOptions } from "nativescript-dev-appium/lib/image-options";
+
+const QUEUE_WAIT_TIME: number = 600000; // Sometimes SauceLabs threads are not available and the tests wait in a queue to start. Wait 10 min before timeout.
+
+describe("Single page app", async function () {
+ let driver: AppiumDriver;
+
+ before(async function () {
+ this.timeout(QUEUE_WAIT_TIME);
+ nsCapabilities.testReporter.context = this;
+ driver = await createDriver();
+ driver.imageHelper.defaultTolerance = 50;
+ driver.imageHelper.defaultToleranceType = ImageOptions.pixel;
+ });
+
+ after(async function () {
+ if (isSauceLab) {
+ driver.sessionId().then(function (sessionId) {
+ console.log("Report https://saucelabs.com/beta/tests/" + sessionId);
+ });
+ }
+ await driver.quit();
+ console.log("Quit driver!");
+ });
+
+ afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logTestArtifacts(this.currentTest.title);
+ }
+ });
+
+ it("should load first page", async function () {
+ await driver.findElementByAutomationText("First Component");
+
+ // ActionBar Title and item
+ await driver.findElementByAutomationText("First Title");
+ await driver.findElementByAutomationText("ACTION1");
+ });
+
+ it("should load second(1) page", async function () {
+ await findAndClick(driver, "SECOND(1)");
+
+ await driver.findElementByAutomationText("Second Component: 1");
+
+ // ActionBar Title and item
+ await driver.findElementByAutomationText("Second Title");
+ await driver.findElementByAutomationText("ACTION2");
+ });
+
+ it("should load second(2) page", async function () {
+ await findAndClick(driver, "SECOND(2)");
+
+ await driver.findElementByAutomationText("Second Component: 2");
+
+ // ActionBar Title and items
+ await driver.findElementByAutomationText("Second Title");
+ await driver.findElementByAutomationText("ACTION2");
+ await driver.findElementByAutomationText("ADD");
+ });
+
+ it("should open and close modal view", async function () {
+ await findAndClick(driver, "Show Modal");
+
+ await driver.findElementByAutomationText("Welcome to modal");
+ await findAndClick(driver, "Close Modal");
+
+ await driver.findElementByAutomationText("Second Component: 2");
+ });
+
+ it("should go back to second(1) and first", async function () {
+ await findAndClick(driver, "Back");
+ await driver.findElementByAutomationText("Second Component: 1");
+ await findAndClick(driver, "Back");
+ await driver.findElementByAutomationText("First Title");
+ await driver.findElementByAutomationText("ACTION1");
+ });
+});
+
+async function findAndClick(driver: AppiumDriver, text: string) {
+ const navigationButton = await driver.findElementByAutomationText(text);
+ await navigationButton.click();
+}
\ No newline at end of file
diff --git a/e2e/single-page/e2e/tsconfig.json b/e2e/single-page/e2e/tsconfig.json
new file mode 100644
index 000000000..18b6c4302
--- /dev/null
+++ b/e2e/single-page/e2e/tsconfig.json
@@ -0,0 +1,18 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es6",
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "importHelpers": false,
+ "types": [
+ "node",
+ "mocha",
+ "chai"
+ ],
+ "lib": [
+ "es6",
+ "dom"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/e2e/single-page/package.json b/e2e/single-page/package.json
new file mode 100644
index 000000000..e03922757
--- /dev/null
+++ b/e2e/single-page/package.json
@@ -0,0 +1,47 @@
+{
+ "description": "NativeScript Application",
+ "license": "SEE LICENSE IN ",
+ "readme": "NativeScript Application",
+ "repository": "",
+ "nativescript": {
+ "id": "org.nativescript.singlepage"
+ },
+ "dependencies": {
+ "@angular/animations": "~9.1.0",
+ "@angular/common": "~9.1.0",
+ "@angular/compiler": "~9.1.0",
+ "@angular/core": "~9.1.0",
+ "@angular/forms": "~9.1.0",
+ "@angular/platform-browser": "~9.1.0",
+ "@angular/platform-browser-dynamic": "~9.1.0",
+ "@angular/router": "~9.1.0",
+ "@nativescript/angular": "file:../../dist/nativescript-angular-scoped.tgz",
+ "reflect-metadata": "~0.1.8",
+ "rxjs": "~6.5.5",
+ "@nativescript/core": "next",
+ "zone.js": "^0.10.3"
+ },
+ "devDependencies": {
+ "@angular/compiler-cli": "~9.1.0",
+ "@ngtools/webpack": "~9.1.0",
+ "@types/chai": "~4.1.7",
+ "@types/mocha": "~5.2.5",
+ "@types/node": "~10.12.18",
+ "babel-traverse": "6.25.0",
+ "babel-types": "6.25.0",
+ "babylon": "6.17.4",
+ "chai-as-promised": "~7.1.1",
+ "colors": "^1.1.2",
+ "lazy": "1.0.11",
+ "mocha": "~5.2.0",
+ "mochawesome": "~3.1.2",
+ "nativescript-dev-appium": "^6.0.0",
+ "nativescript-dev-webpack": "next",
+ "tslib": "^1.7.1",
+ "typescript": "~3.8.3"
+ },
+ "scripts": {
+ "e2e": "tsc -p e2e && mocha --opts ../config/mocha.opts --recursive e2e --appiumCapsLocation ../config/appium.capabilities.json",
+ "compile-tests-w": "tsc -p e2e --watch"
+ }
+}
diff --git a/e2e/single-page/tsconfig.json b/e2e/single-page/tsconfig.json
new file mode 100644
index 000000000..a3ba8b51a
--- /dev/null
+++ b/e2e/single-page/tsconfig.json
@@ -0,0 +1,40 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es5",
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "noEmitHelpers": true,
+ "noEmitOnError": true,
+ "skipLibCheck": true,
+ "lib": [
+ "es2017",
+ "dom",
+ "es6"
+ ],
+ "baseUrl": ".",
+ "paths": {
+ "~/*": [
+ "app/*"
+ ],
+ "*": [
+ "./node_modules/*"
+ ]
+ }
+ },
+ "include": [
+ "../../nativescript-angular-package",
+ "../../nativescript-angular",
+ "**/*"
+ ],
+ "exclude": [
+ "../../nativescript-angular-package/node_modules",
+ "../../nativescript-angular-package/**/*.d.ts",
+ "../../nativescript-angular/node_modules",
+ "../../nativescript-angular/**/*.d.ts",
+ "node_modules",
+ "platforms",
+ "**/*.aot",
+ "e2e"
+ ]
+}
\ No newline at end of file
diff --git a/e2e/tests-app-ng/.DS_Store b/e2e/tests-app-ng/.DS_Store
new file mode 100644
index 000000000..1fbac29ca
Binary files /dev/null and b/e2e/tests-app-ng/.DS_Store differ
diff --git a/e2e/tests-app-ng/.gitignore b/e2e/tests-app-ng/.gitignore
new file mode 100644
index 000000000..ecb5e0009
--- /dev/null
+++ b/e2e/tests-app-ng/.gitignore
@@ -0,0 +1,15 @@
+hooks
+node_modules
+platforms
+lib
+tags
+report
+
+app/**/*.js
+app/**/*.map
+package-lock.json
+
+# Webpack files
+tsconfig.esm.json
+webpack.config.js
+
diff --git a/e2e/tests-app-ng/.vscode/settings.json b/e2e/tests-app-ng/.vscode/settings.json
new file mode 100644
index 000000000..b17e73fe9
--- /dev/null
+++ b/e2e/tests-app-ng/.vscode/settings.json
@@ -0,0 +1,9 @@
+// Place your settings in this file to overwrite default and user settings.
+{
+ "files.exclude": {
+ "**/*.js": {
+ "when": "$(basename).ts"
+ },
+ "**/*.js.map": true
+ }
+}
\ No newline at end of file
diff --git a/e2e/tests-app-ng/app/.DS_Store b/e2e/tests-app-ng/app/.DS_Store
new file mode 100644
index 000000000..7e319d2bf
Binary files /dev/null and b/e2e/tests-app-ng/app/.DS_Store differ
diff --git a/e2e/tests-app-ng/app/App_Resources/Android/app.gradle b/e2e/tests-app-ng/app/App_Resources/Android/app.gradle
new file mode 100644
index 000000000..725fb59b1
--- /dev/null
+++ b/e2e/tests-app-ng/app/App_Resources/Android/app.gradle
@@ -0,0 +1,15 @@
+// Add your native dependencies here:
+
+// Uncomment to add recyclerview-v7 dependency
+//dependencies {
+// compile 'com.android.support:recyclerview-v7:+'
+//}
+
+android {
+ defaultConfig {
+ generatedDensities = []
+ }
+ aaptOptions {
+ additionalParameters "--no-version-vectors"
+ }
+}
diff --git a/e2e/tests-app-ng/app/App_Resources/Android/src/main/AndroidManifest.xml b/e2e/tests-app-ng/app/App_Resources/Android/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..8d827dc8d
--- /dev/null
+++ b/e2e/tests-app-ng/app/App_Resources/Android/src/main/AndroidManifest.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/tests-app-ng/app/App_Resources/Android/src/main/res/drawable-hdpi/icon.png b/e2e/tests-app-ng/app/App_Resources/Android/src/main/res/drawable-hdpi/icon.png
new file mode 100755
index 000000000..1034356e2
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/Android/src/main/res/drawable-hdpi/icon.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/Android/src/main/res/drawable-ldpi/icon.png b/e2e/tests-app-ng/app/App_Resources/Android/src/main/res/drawable-ldpi/icon.png
new file mode 100755
index 000000000..ddfc17a71
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/Android/src/main/res/drawable-ldpi/icon.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/Android/src/main/res/drawable-mdpi/icon.png b/e2e/tests-app-ng/app/App_Resources/Android/src/main/res/drawable-mdpi/icon.png
new file mode 100755
index 000000000..486e41091
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/Android/src/main/res/drawable-mdpi/icon.png differ
diff --git a/ng-sample/src/App_Resources/Android/drawable-nodpi/splashscreen.9.png b/e2e/tests-app-ng/app/App_Resources/Android/src/main/res/drawable-nodpi/splashscreen.9.png
similarity index 100%
rename from ng-sample/src/App_Resources/Android/drawable-nodpi/splashscreen.9.png
rename to e2e/tests-app-ng/app/App_Resources/Android/src/main/res/drawable-nodpi/splashscreen.9.png
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000..5f5359340
--- /dev/null
+++ b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,128 @@
+{
+ "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" : "57x57",
+ "idiom" : "iphone",
+ "filename" : "icon-57.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "57x57",
+ "idiom" : "iphone",
+ "filename" : "icon-57@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "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" : "50x50",
+ "idiom" : "ipad",
+ "filename" : "icon-50.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "50x50",
+ "idiom" : "ipad",
+ "filename" : "icon-50@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "72x72",
+ "idiom" : "ipad",
+ "filename" : "icon-72.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "72x72",
+ "idiom" : "ipad",
+ "filename" : "icon-72@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"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png
new file mode 100644
index 000000000..9e15af09d
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png
new file mode 100644
index 000000000..7b9e55537
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png
new file mode 100644
index 000000000..76f61ec1f
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png
new file mode 100644
index 000000000..15b06db11
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png
new file mode 100644
index 000000000..585065f94
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png
new file mode 100644
index 000000000..a450c421d
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png differ
diff --git a/ng-sample/src/App_Resources/iOS/Icon-Small-50.png b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-50.png
similarity index 100%
rename from ng-sample/src/App_Resources/iOS/Icon-Small-50.png
rename to e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-50.png
diff --git a/ng-sample/src/App_Resources/iOS/Icon-Small-50@2x.png b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-50@2x.png
similarity index 100%
rename from ng-sample/src/App_Resources/iOS/Icon-Small-50@2x.png
rename to e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-50@2x.png
diff --git a/ng-sample/src/App_Resources/iOS/icon.png b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-57.png
similarity index 100%
rename from ng-sample/src/App_Resources/iOS/icon.png
rename to e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-57.png
diff --git a/ng-sample/src/App_Resources/iOS/icon@2x.png b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-57@2x.png
similarity index 100%
rename from ng-sample/src/App_Resources/iOS/icon@2x.png
rename to e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-57@2x.png
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png
new file mode 100644
index 000000000..457b6d94c
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png
new file mode 100644
index 000000000..fa5a6ac86
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png differ
diff --git a/ng-sample/src/App_Resources/iOS/icon-72.png b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-72.png
similarity index 100%
rename from ng-sample/src/App_Resources/iOS/icon-72.png
rename to e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-72.png
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png
new file mode 100755
index 000000000..4f69cb25b
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png
new file mode 100644
index 000000000..94abcf70d
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png
new file mode 100644
index 000000000..2e71dd3a0
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png
new file mode 100644
index 000000000..4abc9ec50
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/Contents.json b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/Contents.json
new file mode 100644
index 000000000..da4a164c9
--- /dev/null
+++ b/e2e/tests-app-ng/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/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
new file mode 100644
index 000000000..4414bad08
--- /dev/null
+++ b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
@@ -0,0 +1,158 @@
+{
+ "images" : [
+ {
+ "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/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png b/e2e/tests-app-ng/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/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png b/e2e/tests-app-ng/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/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png b/e2e/tests-app-ng/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/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png b/e2e/tests-app-ng/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/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png b/e2e/tests-app-ng/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/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png b/e2e/tests-app-ng/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/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png b/e2e/tests-app-ng/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/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png b/e2e/tests-app-ng/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/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png
new file mode 100644
index 000000000..9f1f6ce3e
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png b/e2e/tests-app-ng/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/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json
new file mode 100644
index 000000000..4f4e9c506
--- /dev/null
+++ b/e2e/tests-app-ng/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/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png b/e2e/tests-app-ng/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/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png b/e2e/tests-app-ng/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/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json b/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json
new file mode 100644
index 000000000..23c0ffd7a
--- /dev/null
+++ b/e2e/tests-app-ng/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/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png b/e2e/tests-app-ng/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/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png b/e2e/tests-app-ng/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/tests-app-ng/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Default-568h@2x.png b/e2e/tests-app-ng/app/App_Resources/iOS/Default-568h@2x.png
new file mode 100755
index 000000000..d7f17fcd2
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/iOS/Default-568h@2x.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Default-667h@2x.png b/e2e/tests-app-ng/app/App_Resources/iOS/Default-667h@2x.png
new file mode 100644
index 000000000..b88415405
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/iOS/Default-667h@2x.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Default-736h@3x.png b/e2e/tests-app-ng/app/App_Resources/iOS/Default-736h@3x.png
new file mode 100644
index 000000000..faab4b631
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/iOS/Default-736h@3x.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Default-Landscape-568h@2x.png b/e2e/tests-app-ng/app/App_Resources/iOS/Default-Landscape-568h@2x.png
new file mode 100644
index 000000000..d21bd292b
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/iOS/Default-Landscape-568h@2x.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Default-Landscape-667h@2x.png b/e2e/tests-app-ng/app/App_Resources/iOS/Default-Landscape-667h@2x.png
new file mode 100644
index 000000000..5305bbdf2
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/iOS/Default-Landscape-667h@2x.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Default-Landscape.png b/e2e/tests-app-ng/app/App_Resources/iOS/Default-Landscape.png
new file mode 100755
index 000000000..3365ba3cd
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/iOS/Default-Landscape.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Default-Landscape@2x.png b/e2e/tests-app-ng/app/App_Resources/iOS/Default-Landscape@2x.png
new file mode 100755
index 000000000..a44945c1a
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/iOS/Default-Landscape@2x.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Default-Landscape@3x.png b/e2e/tests-app-ng/app/App_Resources/iOS/Default-Landscape@3x.png
new file mode 100644
index 000000000..e6dca6269
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/iOS/Default-Landscape@3x.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Default-Portrait.png b/e2e/tests-app-ng/app/App_Resources/iOS/Default-Portrait.png
new file mode 100755
index 000000000..1a5007962
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/iOS/Default-Portrait.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Default-Portrait@2x.png b/e2e/tests-app-ng/app/App_Resources/iOS/Default-Portrait@2x.png
new file mode 100755
index 000000000..73d8b920f
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/iOS/Default-Portrait@2x.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Default.png b/e2e/tests-app-ng/app/App_Resources/iOS/Default.png
new file mode 100755
index 000000000..9f1f6ce3e
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/iOS/Default.png differ
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Default@2x.png b/e2e/tests-app-ng/app/App_Resources/iOS/Default@2x.png
new file mode 100755
index 000000000..514fc5cde
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/iOS/Default@2x.png differ
diff --git a/startup-test/app/App_Resources/iOS/Icon-Small-50.png b/e2e/tests-app-ng/app/App_Resources/iOS/Icon-Small-50.png
similarity index 100%
rename from startup-test/app/App_Resources/iOS/Icon-Small-50.png
rename to e2e/tests-app-ng/app/App_Resources/iOS/Icon-Small-50.png
diff --git a/startup-test/app/App_Resources/iOS/Icon-Small-50@2x.png b/e2e/tests-app-ng/app/App_Resources/iOS/Icon-Small-50@2x.png
similarity index 100%
rename from startup-test/app/App_Resources/iOS/Icon-Small-50@2x.png
rename to e2e/tests-app-ng/app/App_Resources/iOS/Icon-Small-50@2x.png
diff --git a/ng-sample/src/App_Resources/iOS/Icon-Small.png b/e2e/tests-app-ng/app/App_Resources/iOS/Icon-Small.png
similarity index 100%
rename from ng-sample/src/App_Resources/iOS/Icon-Small.png
rename to e2e/tests-app-ng/app/App_Resources/iOS/Icon-Small.png
diff --git a/ng-sample/src/App_Resources/iOS/Icon-Small@2x.png b/e2e/tests-app-ng/app/App_Resources/iOS/Icon-Small@2x.png
similarity index 100%
rename from ng-sample/src/App_Resources/iOS/Icon-Small@2x.png
rename to e2e/tests-app-ng/app/App_Resources/iOS/Icon-Small@2x.png
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/Info.plist b/e2e/tests-app-ng/app/App_Resources/iOS/Info.plist
new file mode 100644
index 000000000..ea3e3ea23
--- /dev/null
+++ b/e2e/tests-app-ng/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/tests-app-ng/app/App_Resources/iOS/LaunchScreen.storyboard b/e2e/tests-app-ng/app/App_Resources/iOS/LaunchScreen.storyboard
new file mode 100644
index 000000000..2ad9471e1
--- /dev/null
+++ b/e2e/tests-app-ng/app/App_Resources/iOS/LaunchScreen.storyboard
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/build.xcconfig b/e2e/tests-app-ng/app/App_Resources/iOS/build.xcconfig
new file mode 100644
index 000000000..9d738435d
--- /dev/null
+++ b/e2e/tests-app-ng/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/ng-sample/src/App_Resources/iOS/icon-40.png b/e2e/tests-app-ng/app/App_Resources/iOS/icon-40.png
similarity index 100%
rename from ng-sample/src/App_Resources/iOS/icon-40.png
rename to e2e/tests-app-ng/app/App_Resources/iOS/icon-40.png
diff --git a/ng-sample/src/App_Resources/iOS/icon-40@2x.png b/e2e/tests-app-ng/app/App_Resources/iOS/icon-40@2x.png
similarity index 100%
rename from ng-sample/src/App_Resources/iOS/icon-40@2x.png
rename to e2e/tests-app-ng/app/App_Resources/iOS/icon-40@2x.png
diff --git a/ng-sample/src/App_Resources/iOS/icon-60.png b/e2e/tests-app-ng/app/App_Resources/iOS/icon-60.png
similarity index 100%
rename from ng-sample/src/App_Resources/iOS/icon-60.png
rename to e2e/tests-app-ng/app/App_Resources/iOS/icon-60.png
diff --git a/ng-sample/src/App_Resources/iOS/icon-60@2x.png b/e2e/tests-app-ng/app/App_Resources/iOS/icon-60@2x.png
similarity index 100%
rename from ng-sample/src/App_Resources/iOS/icon-60@2x.png
rename to e2e/tests-app-ng/app/App_Resources/iOS/icon-60@2x.png
diff --git a/startup-test/app/App_Resources/iOS/icon-72.png b/e2e/tests-app-ng/app/App_Resources/iOS/icon-72.png
similarity index 100%
rename from startup-test/app/App_Resources/iOS/icon-72.png
rename to e2e/tests-app-ng/app/App_Resources/iOS/icon-72.png
diff --git a/e2e/tests-app-ng/app/App_Resources/iOS/icon-72@2x.png b/e2e/tests-app-ng/app/App_Resources/iOS/icon-72@2x.png
new file mode 100755
index 000000000..4f69cb25b
Binary files /dev/null and b/e2e/tests-app-ng/app/App_Resources/iOS/icon-72@2x.png differ
diff --git a/ng-sample/src/App_Resources/iOS/icon-76.png b/e2e/tests-app-ng/app/App_Resources/iOS/icon-76.png
similarity index 100%
rename from ng-sample/src/App_Resources/iOS/icon-76.png
rename to e2e/tests-app-ng/app/App_Resources/iOS/icon-76.png
diff --git a/ng-sample/src/App_Resources/iOS/icon-76@2x.png b/e2e/tests-app-ng/app/App_Resources/iOS/icon-76@2x.png
similarity index 100%
rename from ng-sample/src/App_Resources/iOS/icon-76@2x.png
rename to e2e/tests-app-ng/app/App_Resources/iOS/icon-76@2x.png
diff --git a/startup-test/app/App_Resources/iOS/icon.png b/e2e/tests-app-ng/app/App_Resources/iOS/icon.png
similarity index 100%
rename from startup-test/app/App_Resources/iOS/icon.png
rename to e2e/tests-app-ng/app/App_Resources/iOS/icon.png
diff --git a/startup-test/app/App_Resources/iOS/icon@2x.png b/e2e/tests-app-ng/app/App_Resources/iOS/icon@2x.png
similarity index 100%
rename from startup-test/app/App_Resources/iOS/icon@2x.png
rename to e2e/tests-app-ng/app/App_Resources/iOS/icon@2x.png
diff --git a/e2e/tests-app-ng/app/action-bar/action-bar-first.component.ts b/e2e/tests-app-ng/app/action-bar/action-bar-first.component.ts
new file mode 100644
index 000000000..2eaac6aed
--- /dev/null
+++ b/e2e/tests-app-ng/app/action-bar/action-bar-first.component.ts
@@ -0,0 +1,64 @@
+import { Component } from "@angular/core";
+
+@Component({
+ selector: "first-action-bar",
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+ `,
+})
+export class FirstActionBarComponent {
+
+ public counterShare: number = 0;
+ public counterDelete: number = 0;
+ public show: boolean = true;
+
+ public get messageShare(): string {
+ if (this.counterShare === 1) {
+ return this.counterShare + " share tap";
+ } else {
+ return this.counterShare + " share taps";
+ }
+ }
+
+ public get messageDelete(): string {
+ if (this.counterDelete === 1) {
+ return this.counterDelete + " delete tap";
+ } else {
+ return this.counterDelete + " delete taps";
+ }
+ }
+
+ public onShare() {
+ this.counterShare++;
+ console.log("Share button tapped!");
+ }
+
+ public onTap() {
+ console.log("FirstComponent.Tapped!");
+ }
+
+ public onDelete() {
+ this.counterDelete++;
+ console.log("Delete button tapped!");
+ }
+}
diff --git a/e2e/tests-app-ng/app/action-bar/action-bar-nested.component.ts b/e2e/tests-app-ng/app/action-bar/action-bar-nested.component.ts
new file mode 100644
index 000000000..6e1f9a317
--- /dev/null
+++ b/e2e/tests-app-ng/app/action-bar/action-bar-nested.component.ts
@@ -0,0 +1,35 @@
+import { Component } from "@angular/core";
+
+@Component({
+ selector: "nested-component",
+ template: `
+
+
+
+
+
+
+
+
+
+
+ `,
+})
+export class NestedComponent {
+
+ public counter: number = 0;
+ public show: boolean = true;
+
+ public get message(): string {
+ if (this.counter === 1) {
+ return this.counter + " custom tap";
+ } else {
+ return this.counter + " custom taps";
+ }
+ }
+
+ public onTap() {
+ this.counter++;
+ console.log("NestedComponent.Tapped!");
+ }
+}
diff --git a/e2e/tests-app-ng/app/action-bar/action-bar-second.component.ts b/e2e/tests-app-ng/app/action-bar/action-bar-second.component.ts
new file mode 100644
index 000000000..c3d9e873f
--- /dev/null
+++ b/e2e/tests-app-ng/app/action-bar/action-bar-second.component.ts
@@ -0,0 +1,33 @@
+import { Component } from "@angular/core";
+
+@Component({
+ selector: "second-action-bar",
+ template: `
+
+
+
+
+
+
+
+
+
+ `,
+})
+export class SecondActionBarComponent {
+
+ public counter: number = 0;
+
+ public get message(): string {
+ if (this.counter === 1) {
+ return this.counter + " tap";
+ } else {
+ return this.counter + " taps";
+ }
+ }
+
+ public onTap() {
+ this.counter++;
+ console.log("SecondComponent.Tapped!");
+ }
+}
diff --git a/e2e/tests-app-ng/app/app.css b/e2e/tests-app-ng/app/app.css
new file mode 100644
index 000000000..067c61478
--- /dev/null
+++ b/e2e/tests-app-ng/app/app.css
@@ -0,0 +1,19 @@
+@import '~nativescript-theme-core/css/core.light.css';
+
+button {
+ font-size: 13;
+}
+
+label {
+ text-align: center;
+}
+
+.tab-view-container {
+ font-family: 'Courier'; /* Enabling this results in the error and shows a blank TabView */
+}
+
+.btnIcon {
+ font-family: 'FontAwesome';
+ font-size: 30
+}
+
diff --git a/e2e/tests-app-ng/app/app.module.ngfactory.d.ts b/e2e/tests-app-ng/app/app.module.ngfactory.d.ts
new file mode 100644
index 000000000..4ead4ec18
--- /dev/null
+++ b/e2e/tests-app-ng/app/app.module.ngfactory.d.ts
@@ -0,0 +1,5 @@
+/*
+* A dynamically generated module when compiled with AoT.
+*/
+// tslint:disable-next-line
+export const AppModuleNgFactory: any;
diff --git a/e2e/tests-app-ng/app/app.module.ts b/e2e/tests-app-ng/app/app.module.ts
new file mode 100644
index 000000000..0b0e641bd
--- /dev/null
+++ b/e2e/tests-app-ng/app/app.module.ts
@@ -0,0 +1,25 @@
+import { NgModule, NgModuleFactoryLoader, NO_ERRORS_SCHEMA } from "@angular/core";
+import { NativeScriptModule, NativeScriptRouterModule, NativeScriptFormsModule } from "@nativescript/angular";
+
+import { NavigationMainPageRouterComponent } from "./main/main-page-router-outlet";
+import { routableComponents, routes } from "./app.routes";
+import { NestedComponent } from "./action-bar/action-bar-nested.component";
+import { CustomTemplateComponent } from "./list-view/list-view-item-template.component";
+
+@NgModule({
+ declarations: [
+ NavigationMainPageRouterComponent,
+ NestedComponent,
+ CustomTemplateComponent,
+ ...routableComponents,
+ ],
+ bootstrap: [NavigationMainPageRouterComponent],
+ imports: [
+ NativeScriptModule,
+ NativeScriptFormsModule,
+ NativeScriptRouterModule,
+ NativeScriptRouterModule.forRoot(routes),
+ ],
+ schemas: [NO_ERRORS_SCHEMA],
+})
+export class AppModule { }
diff --git a/e2e/tests-app-ng/app/app.routes.ts b/e2e/tests-app-ng/app/app.routes.ts
new file mode 100644
index 000000000..c3f9276b9
--- /dev/null
+++ b/e2e/tests-app-ng/app/app.routes.ts
@@ -0,0 +1,198 @@
+import { Component } from "@angular/core";
+
+import { FirstActionBarComponent } from "./action-bar/action-bar-first.component";
+import { SecondActionBarComponent } from "./action-bar/action-bar-second.component";
+
+import { AppComponent } from "./template/app.component";
+
+import { FirstComponent } from "./router/router-outlet/first.component";
+import { SecondComponent } from "./router/router-outlet/second.component";
+import { NavigationComponent, NAVIGATION_SUBROUTES } from "./router/router-outlet/navigation.component";
+import { LazyNavigationComponent } from "./router/lazy-module-navigation/lazy-navigation.component";
+
+import { BindingComponent } from "./binding/binding-page.component";
+import { BottomNavigation } from "./bottom-navigation/bottom-navigation.component"
+
+import { ListViewComponent } from "./list-view/list-view-page.component";
+import { ListViewControlComponent } from "./list-view/list-view-item-template.component";
+import { ListViewAsyncPipeComponent } from "./list-view/async-pipe-template.component";
+import { ListViewMainPageComponent } from "./list-view/list-view-main-page.component";
+import { ListViewSegmentedBarPageComponent } from "./list-view/list-view-nested-segmented-bar-page.component";
+import { ListViewWithNestedTemplateComponent } from "./list-view/list-view-nested-template.component";
+import { ListViewMultipleTemplatesComponent } from "./list-view/multiple-templates.component";
+
+import { ListPickerMainPageComponent } from "./list-picker/list-picker-main-page.component";
+import { ListPickerComponent } from "./list-picker/list-picker.component";
+
+import {
+ ModalTestComponent,
+ ModalTestWithPushStrategyComponent,
+ ModalContentComponent
+} from "./modal/modal-dialogs/modal-dialog.component";
+import { ModalViewMainPageComponent } from "./modal/modal-view-main-page.component";
+import { LazyLoadModalComponent } from "./modal/lazy/lazy-load-modal.component";
+
+import { TabViewComponent } from "./tab-view/tab-view.component";
+import { TabsComponent } from "./tabs/tabs.component";
+
+import { NavigationOptionsComponent } from "./navigation-options/navigation-options.component";
+import { NavigationInfoComponent } from "./navigation-options/navigation-info.component";
+
+import { SegmentedBarMainPageComponent } from "./segmented-bar/segmented-bar-main-page.component";
+import { SegmentedBarIssue649Component } from "./segmented-bar/issue-649.component";
+
+import { DatePickerMainPageComponent } from "./date-picker/date-picker-main-page.component";
+import { DatePickerIssue324Component } from "./date-picker/issue-324.component";
+import { ButtonMainPageComponent } from "./button/button-main-page.component";
+import { ButtonTextAlignmentComponent } from "./button/button-text-alignment.component";
+
+import { MainComponent } from "./main/main-page-router-outlet";
+
+export const routableComponents = [
+ MainComponent,
+ ModalContentComponent,
+ AppComponent,
+
+ NavigationComponent,
+ LazyNavigationComponent,
+
+ FirstComponent,
+ SecondComponent,
+
+ FirstActionBarComponent,
+ SecondActionBarComponent,
+
+ BindingComponent,
+ BottomNavigation,
+
+ ListViewMainPageComponent,
+ ListViewComponent,
+ ListViewControlComponent,
+ ListViewAsyncPipeComponent,
+ ListViewSegmentedBarPageComponent,
+ ListViewWithNestedTemplateComponent,
+ ListViewMultipleTemplatesComponent,
+
+ ListPickerComponent,
+ ListPickerMainPageComponent,
+
+ ModalViewMainPageComponent,
+ ModalTestComponent,
+ ModalTestWithPushStrategyComponent,
+ LazyLoadModalComponent,
+
+ TabViewComponent,
+ TabsComponent,
+
+ NavigationOptionsComponent,
+ NavigationInfoComponent,
+ SegmentedBarMainPageComponent,
+ SegmentedBarIssue649Component,
+ DatePickerMainPageComponent,
+ DatePickerIssue324Component,
+ ButtonMainPageComponent,
+ ButtonTextAlignmentComponent,
+];
+
+// Set `isNavigatable: true` if the page is a main page to other sub pages
+export const routes = [
+ { path: "", pathMatch: "full", redirectTo: "main" },
+ { path: "main", component: MainComponent, data: { title: "" } },
+ // { path: "", component: ModalContentComponent, data: { title: "" } },
+ { path: "template", component: AppComponent, data: { title: "Template", isNavigatable: true } },
+
+ {
+ path: "router",
+ component: NavigationComponent,
+ children: NAVIGATION_SUBROUTES,
+ data: { title: "Router", isNavigatable: true }
+ },
+ { path: "lazy-router", component: LazyNavigationComponent, data: { title: "Lazy Router", isNavigatable: true } },
+
+ { path: "first", component: FirstComponent, data: { title: "First", isNavigatable: true } },
+ { path: "second", component: SecondComponent, data: { title: "Second", isNavigatable: true } },
+
+ {
+ path: "first-action-bar",
+ component: FirstActionBarComponent,
+ data: { title: "ActionBar1", isNavigatable: true }
+ },
+ {
+ path: "second-action-bar",
+ component: SecondActionBarComponent,
+ data: { title: "ActionBar2", isNavigatable: true }
+ },
+
+ { path: "binding", component: BindingComponent, data: { title: "Binding", isNavigatable: true } },
+ { path: "bottom-navigation", component: BottomNavigation, data: { title: "BottomNavigation", isNavigatable: true } },
+
+ {
+ path: "ListViewExamples",
+ component: ListViewMainPageComponent,
+ data: { title: "ListViewExamples", isNavigatable: true }
+ },
+ { path: "ListViewExamples/commonTemplate", component: ListViewComponent, data: { title: "commonTemplate" } },
+ { path: "ListViewExamples/customTemplate", component: ListViewControlComponent, data: { title: "customTemplate" } },
+ { path: "listView/asyncPipeTemplate", component: ListViewAsyncPipeComponent, data: { title: "asyncPipeTemplate" } },
+ {
+ path: "ListViewExamples/segmentedBarTemplate",
+ component: ListViewSegmentedBarPageComponent,
+ data: { title: "segmentedBarTemplate" } },
+ {
+ path: "listView/nestedTemplate",
+ component: ListViewWithNestedTemplateComponent,
+ data: { title: "nestedTemplate" }
+ },
+ {
+ path: "listView/multiple-templates",
+ component: ListViewMultipleTemplatesComponent,
+ data: { title: "multipleTemplates" }
+ },
+ {
+ path: "listPicker",
+ component: ListPickerMainPageComponent,
+ data: { title: "ListPicker", isNavigatable: true }
+ },
+ {
+ path: "listPicker/list-picker",
+ component: ListPickerComponent,
+ data: { title: "ListPicker", isNavigatable: false }
+ },
+ { path: "modal", component: ModalViewMainPageComponent, data: { title: "Modals", isNavigatable: true } },
+ { path: "modal/modal-dialogs", component: ModalTestComponent, data: { title: "modal" } },
+ {
+ path: "modal/modal-dialogs-push",
+ component: ModalTestWithPushStrategyComponent,
+ data: { title: "modal(onPush)" }
+ },
+ { path: "modal/lazy", component: LazyLoadModalComponent, data: { title: "modal(lazy)" } },
+
+ { path: "tab-view", component: TabViewComponent, data: { title: "tab-view", isNavigatable: true } },
+ { path: "tabs", component: TabsComponent, data: { title: "Tabs", isNavigatable: true } },
+
+ { path: "nav-options", component: NavigationOptionsComponent, data: { title: "nav-options", isNavigatable: true } },
+ { path: "nav-info", component: NavigationInfoComponent, data: { title: "nav-info" } },
+
+ {
+ path: "segmented-bar",
+ component: SegmentedBarMainPageComponent,
+ data: { title: "SegmentedBar", isNavigatable: true }
+ },
+ { path: "segmented-bar/issue-649", component: SegmentedBarIssue649Component, data: { title: "issue-649" } },
+
+ { path: "date-picker", component: DatePickerMainPageComponent, data: { title: "DatePicker", isNavigatable: true } },
+ { path: "date-picker/issue-324", component: DatePickerIssue324Component, data: { title: "issue-324" } },
+
+ { path: "button", component: ButtonMainPageComponent, data: { title: "Button", isNavigatable: true } },
+ {
+ path: "button/button-text-alignment",
+ component: ButtonTextAlignmentComponent,
+ data: { title: "button-text-alignment" }
+ },
+
+ // Needed for AoT compilation
+ {
+ path: "lazy",
+ loadChildren: () => import("./lazy/lazy.module").then(m => m.LazyModule)
+ },
+];
diff --git a/e2e/tests-app-ng/app/binding/binding-page.component.ts b/e2e/tests-app-ng/app/binding/binding-page.component.ts
new file mode 100644
index 000000000..7cf7d526a
--- /dev/null
+++ b/e2e/tests-app-ng/app/binding/binding-page.component.ts
@@ -0,0 +1,99 @@
+import { Component } from "@angular/core";
+
+@Component({
+ selector: "binding",
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+})
+
+export class BindingComponent {
+ private _oneWayDataBinding: string;
+ private _twoWayDataBinding: string;
+ private _curlyBracket: string;
+ private _result: string;
+ public completedDate: Date = new Date(2016, 5, 3);
+ labelShow = true;
+
+ constructor() {
+ this.refresh();
+ }
+
+ get oneWayDataBinding() {
+ return this._oneWayDataBinding;
+ }
+
+ get twoWayDataBinding() {
+ return this._twoWayDataBinding;
+ }
+
+ set twoWayDataBinding(value: string) {
+ this._twoWayDataBinding = value;
+ }
+
+ get curlyBracket() {
+ return this._curlyBracket;
+ }
+
+ set curlyBracket(value: string) {
+ this._curlyBracket = value;
+ }
+
+ get results() {
+ return this._result;
+ }
+
+ toggle() {
+ this.labelShow = !this.labelShow;
+ console.log(this.labelShow);
+ }
+
+ changeValues() {
+ this._oneWayDataBinding = this.twoWayDataBinding;
+ this._curlyBracket = this.twoWayDataBinding;
+ this._twoWayDataBinding = this.twoWayDataBinding;
+ }
+
+ getValues() {
+ this._result = "";
+ this._result += "one-way value is " + this.oneWayDataBinding + "; \n";
+ this._result += "two-way value is " + this.twoWayDataBinding + "; \n";
+ this._result += "curly-bracket value is " + this.curlyBracket + "; \n";
+ }
+
+ private refresh() {
+ this._oneWayDataBinding = "1";
+ this._twoWayDataBinding = "2";
+ this._curlyBracket = "5";
+ this._result = "";
+ }
+}
diff --git a/e2e/tests-app-ng/app/bottom-navigation/bottom-navigation.component.ts b/e2e/tests-app-ng/app/bottom-navigation/bottom-navigation.component.ts
new file mode 100644
index 000000000..5c547d9f6
--- /dev/null
+++ b/e2e/tests-app-ng/app/bottom-navigation/bottom-navigation.component.ts
@@ -0,0 +1,68 @@
+import { Component, OnInit } from "@angular/core";
+
+@Component({
+ selector: "bottom-navigation-component",
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+})
+
+export class BottomNavigation implements OnInit {
+
+ public ngOnInit(): void { }
+
+ goTo(bottomNavigation: any, index: number) {
+ bottomNavigation.selectedIndex = index;
+ }
+
+}
diff --git a/e2e/tests-app-ng/app/button/button-main-page.component.ts b/e2e/tests-app-ng/app/button/button-main-page.component.ts
new file mode 100644
index 000000000..eba145679
--- /dev/null
+++ b/e2e/tests-app-ng/app/button/button-main-page.component.ts
@@ -0,0 +1,11 @@
+import { Component } from "@angular/core";
+
+@Component({
+ selector: "main-button",
+ template: `
+
+
+
+ `,
+})
+export class ButtonMainPageComponent { }
diff --git a/e2e/tests-app-ng/app/button/button-text-alignment.component.ts b/e2e/tests-app-ng/app/button/button-text-alignment.component.ts
new file mode 100644
index 000000000..ddd4ec7d6
--- /dev/null
+++ b/e2e/tests-app-ng/app/button/button-text-alignment.component.ts
@@ -0,0 +1,52 @@
+import { Observable as RxObservable } from "rxjs";
+import {
+ Component,
+ Input,
+ ChangeDetectionStrategy
+} from "@angular/core";
+
+@Component({
+ styles: [ ".odd { background-color : yellow } " +
+ ".even{ background-color : green } " +
+ ".test{ font-family:FontAwesome; font-size:70 }"],
+ template: `
+
+
+
+
+
+ `
+})
+export class ButtonTextAlignmentComponent {
+
+ public get test(): string {
+ return "\ntest";
+ }
+}
diff --git a/e2e/tests-app-ng/app/date-picker/date-picker-main-page.component.ts b/e2e/tests-app-ng/app/date-picker/date-picker-main-page.component.ts
new file mode 100644
index 000000000..1642196e2
--- /dev/null
+++ b/e2e/tests-app-ng/app/date-picker/date-picker-main-page.component.ts
@@ -0,0 +1,10 @@
+import { Component } from "@angular/core";
+
+@Component({
+ template: `
+
+
+
+ `,
+})
+export class DatePickerMainPageComponent { }
diff --git a/e2e/tests-app-ng/app/date-picker/issue-324.component.ts b/e2e/tests-app-ng/app/date-picker/issue-324.component.ts
new file mode 100644
index 000000000..70aa9192b
--- /dev/null
+++ b/e2e/tests-app-ng/app/date-picker/issue-324.component.ts
@@ -0,0 +1,23 @@
+import { Component } from "@angular/core";
+
+@Component({
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+})
+
+export class DatePickerIssue324Component {
+}
diff --git a/e2e/tests-app-ng/app/fonts/FontAwesome.ttf b/e2e/tests-app-ng/app/fonts/FontAwesome.ttf
new file mode 100644
index 000000000..35acda2fa
Binary files /dev/null and b/e2e/tests-app-ng/app/fonts/FontAwesome.ttf differ
diff --git a/e2e/tests-app-ng/app/fonts/Pacifico.ttf b/e2e/tests-app-ng/app/fonts/Pacifico.ttf
new file mode 100644
index 000000000..6d47cdc9a
Binary files /dev/null and b/e2e/tests-app-ng/app/fonts/Pacifico.ttf differ
diff --git a/e2e/tests-app-ng/app/fonts/Sofia.otf b/e2e/tests-app-ng/app/fonts/Sofia.otf
new file mode 100644
index 000000000..1f76d2d58
Binary files /dev/null and b/e2e/tests-app-ng/app/fonts/Sofia.otf differ
diff --git a/e2e/tests-app-ng/app/lazy/lazy.component.css b/e2e/tests-app-ng/app/lazy/lazy.component.css
new file mode 100644
index 000000000..eec6173f7
--- /dev/null
+++ b/e2e/tests-app-ng/app/lazy/lazy.component.css
@@ -0,0 +1,8 @@
+
+.system-background-color-stack-layout {
+ background-color: white;
+}
+
+.ns-dark .system-background-color-stack-layout {
+ background-color: gray;
+}
diff --git a/e2e/tests-app-ng/app/lazy/lazy.component.html b/e2e/tests-app-ng/app/lazy/lazy.component.html
new file mode 100644
index 000000000..82d691f92
--- /dev/null
+++ b/e2e/tests-app-ng/app/lazy/lazy.component.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
diff --git a/e2e/tests-app-ng/app/lazy/lazy.component.ts b/e2e/tests-app-ng/app/lazy/lazy.component.ts
new file mode 100644
index 000000000..bb8d0e01c
--- /dev/null
+++ b/e2e/tests-app-ng/app/lazy/lazy.component.ts
@@ -0,0 +1,30 @@
+import { Component } from "@angular/core";
+
+import { RouterExtensions, ModalDialogParams } from "@nativescript/angular";
+
+@Component({
+ selector: "ns-lazy",
+ moduleId: module.id,
+ templateUrl: "./lazy.component.html",
+ styleUrls: ["./lazy.component.css"]
+})
+export class LazyComponent {
+ public isModal: boolean;
+
+ constructor(
+ private router: RouterExtensions,
+ private params: ModalDialogParams
+ ) {
+ if (params.context.isModal) {
+ this.isModal = true;
+ }
+ }
+
+ public close() {
+ if (this.isModal) {
+ this.params.closeCallback();
+ } else {
+ this.router.back();
+ }
+ }
+}
diff --git a/e2e/tests-app-ng/app/lazy/lazy.module.ts b/e2e/tests-app-ng/app/lazy/lazy.module.ts
new file mode 100644
index 000000000..eea1ca005
--- /dev/null
+++ b/e2e/tests-app-ng/app/lazy/lazy.module.ts
@@ -0,0 +1,37 @@
+import { NativeScriptCommonModule, NativeScriptRouterModule, ModalDialogParams } from "@nativescript/angular";
+
+import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
+import { Routes } from "@angular/router";
+
+import { LazyComponent } from "./lazy.component";
+
+export function modalParamsFactory() {
+ return new ModalDialogParams({}, null);
+}
+
+const routes: Routes = [
+ {
+ path: "",
+ component: LazyComponent
+ }
+];
+
+@NgModule({
+ imports: [
+ NativeScriptCommonModule,
+ NativeScriptRouterModule.forChild(routes),
+ ],
+ declarations: [
+ LazyComponent
+ ],
+ entryComponents: [
+ LazyComponent
+ ],
+ providers: [
+ // allows same component to be routed to
+ // or lazily loaded via modal
+ { provide: ModalDialogParams, useFactory: modalParamsFactory }
+ ],
+ schemas: [NO_ERRORS_SCHEMA]
+})
+export class LazyModule { }
diff --git a/e2e/tests-app-ng/app/list-picker/list-picker-main-page.component.ts b/e2e/tests-app-ng/app/list-picker/list-picker-main-page.component.ts
new file mode 100644
index 000000000..27b3111e6
--- /dev/null
+++ b/e2e/tests-app-ng/app/list-picker/list-picker-main-page.component.ts
@@ -0,0 +1,11 @@
+import { Component } from "@angular/core";
+
+@Component({
+ selector: "main-list-picker",
+ template: `
+
+
+
+ `,
+})
+export class ListPickerMainPageComponent { }
diff --git a/e2e/tests-app-ng/app/list-picker/list-picker.component.ts b/e2e/tests-app-ng/app/list-picker/list-picker.component.ts
new file mode 100644
index 000000000..d6eff5efd
--- /dev/null
+++ b/e2e/tests-app-ng/app/list-picker/list-picker.component.ts
@@ -0,0 +1,42 @@
+import {
+ Component,
+ Input,
+ ChangeDetectionStrategy
+} from "@angular/core";
+
+@Component({
+ moduleId: module.id,
+ selector: "list",
+ styleUrls: ["./list-picker.css"],
+ template: `
+
+
+
+
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+
+export class ListPickerComponent {
+ public pokemons: Array;
+ public picked: string;
+ public selectedIndex: number;
+
+ private pokemonList = ["Bulbasaur", "Parasect", "Venonat", "Venomoth", "Diglett",
+ "Dugtrio", "Meowth", "Persian", "Psyduck", "Arcanine", "Poliwrath", "Machoke"];
+
+ constructor() {
+ this.pokemons = [];
+
+ for (let i = 0; i < this.pokemonList.length; i++) {
+ this.pokemons.push(this.pokemonList[i]);
+ }
+ }
+
+ public selectedIndexChanged(picker) {
+ console.log("picker selection: " + picker.selectedIndex);
+ this.picked = this.pokemons[picker.selectedIndex];
+ }
+}
diff --git a/e2e/tests-app-ng/app/list-picker/list-picker.css b/e2e/tests-app-ng/app/list-picker/list-picker.css
new file mode 100644
index 000000000..314c79fa9
--- /dev/null
+++ b/e2e/tests-app-ng/app/list-picker/list-picker.css
@@ -0,0 +1,4 @@
+.listPicker {
+ color: green;
+ background-color: black;
+}
diff --git a/e2e/tests-app-ng/app/list-view/async-pipe-template.component.ts b/e2e/tests-app-ng/app/list-view/async-pipe-template.component.ts
new file mode 100644
index 000000000..fab165dd4
--- /dev/null
+++ b/e2e/tests-app-ng/app/list-view/async-pipe-template.component.ts
@@ -0,0 +1,55 @@
+import { Observable as RxObservable } from "rxjs";
+import { Component, Input, ChangeDetectionStrategy } from "@angular/core";
+import { DataItem } from "./data-item";
+
+@Component({
+ selector: "list-test-async",
+ template: `
+
+
+
+
+
+
+
+
+
+
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class ListViewAsyncPipeComponent {
+ public myItems: RxObservable>;
+ public output: string;
+
+ constructor() {
+ let items = [];
+ for (let i = 0; i < 3; i++) {
+ items.push(new DataItem(i, "data item " + i));
+ }
+
+ let subscr;
+ this.myItems = RxObservable.create(subscriber => {
+ subscr = subscriber;
+ subscriber.next(items);
+ return function () {
+ console.log("Unsubscribe called!!!");
+ this.output = "Unsubscribe called!!!";
+ };
+ });
+
+ let counter = 2;
+ const intervalId = setInterval(() => {
+ counter++;
+ items.push(new DataItem(counter, "data item " + counter));
+ subscr.next(items);
+ if (counter == 11) {
+ clearInterval(intervalId);
+ }
+ }, 1000);
+
+ setTimeout(() => {
+ clearInterval(intervalId);
+ }, 11000);
+ }
+}
diff --git a/e2e/tests-app-ng/app/list-view/data-item.ts b/e2e/tests-app-ng/app/list-view/data-item.ts
new file mode 100644
index 000000000..f9774a293
--- /dev/null
+++ b/e2e/tests-app-ng/app/list-view/data-item.ts
@@ -0,0 +1,3 @@
+export class DataItem {
+ constructor(public id: number, public name: string) { }
+}
diff --git a/e2e/tests-app-ng/app/list-view/list-view-item-template.component.ts b/e2e/tests-app-ng/app/list-view/list-view-item-template.component.ts
new file mode 100644
index 000000000..e0e31713b
--- /dev/null
+++ b/e2e/tests-app-ng/app/list-view/list-view-item-template.component.ts
@@ -0,0 +1,38 @@
+import { Component, Input, ChangeDetectionStrategy } from "@angular/core";
+
+@Component({
+ selector: "custom-template",
+ template: `
+
+
+
+ `
+})
+export class CustomTemplateComponent {
+ @Input() data: any;
+}
+
+@Component({
+ selector: "list-test",
+ template: `
+
+
+
+
+
+
+
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class ListViewControlComponent {
+ public myItems: Array;
+ private counter: number;
+
+ constructor() {
+ const list = [{ "text": "a" }, { "text": "b" }];
+ const list1 = [{ "text": "c" }, { "text": "d" }];
+ this.myItems = [{ "list": list }, { "list": list1 }];
+ }
+}
diff --git a/e2e/tests-app-ng/app/list-view/list-view-main-page.component.ts b/e2e/tests-app-ng/app/list-view/list-view-main-page.component.ts
new file mode 100644
index 000000000..368c103da
--- /dev/null
+++ b/e2e/tests-app-ng/app/list-view/list-view-main-page.component.ts
@@ -0,0 +1,16 @@
+import { Component } from "@angular/core";
+
+@Component({
+ selector: "main-listview",
+ template: `
+
+
+
+
+
+
+
+
+ `,
+})
+export class ListViewMainPageComponent { }
diff --git a/e2e/tests-app-ng/app/list-view/list-view-nested-segmented-bar-page.component.ts b/e2e/tests-app-ng/app/list-view/list-view-nested-segmented-bar-page.component.ts
new file mode 100644
index 000000000..74e161d7b
--- /dev/null
+++ b/e2e/tests-app-ng/app/list-view/list-view-nested-segmented-bar-page.component.ts
@@ -0,0 +1,125 @@
+import { Component, ViewChild, ElementRef, OnInit } from "@angular/core";
+import { SegmentedBarItem, SegmentedBar, ListView, EventData } from "@nativescript/core";
+
+interface DataItem {
+ id: number;
+ name: string;
+ type: string;
+}
+
+@Component({
+ moduleId: module.id,
+ selector: "segmented-bar-list-test",
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+})
+export class ListViewSegmentedBarPageComponent implements OnInit {
+ public displayedItems: Array = [];
+ public items: Array;
+ public segmentedBarItems: SegmentedBarItem[] = this.createSegmentedBarItems();
+
+ @ViewChild("listViewTest", { static: false })
+ private listViewTest?: ElementRef;
+
+ constructor() {
+ this.items = [];
+
+ for (let i = 0; i < 20; i++) {
+ const type = "dataItemTemplate";
+
+ this.items.push({
+ id: i,
+ name: `data item ${i}`,
+ type: type,
+ });
+ }
+ }
+
+ public ngOnInit() {
+ this.displayedItems = this.updateItems(true);
+ }
+
+ public onButtonPress() {
+ // tslint:disable-next-line: no-unused-expression
+ new Promise((resolve) => {
+ setTimeout(() => {
+ if (this.listViewTest) {
+ console.log("Scrolling to the top of the list...");
+ const listView = this.listViewTest.nativeElement as ListView;
+ listView.scrollToIndex(0);
+ }
+ resolve();
+ }, 150);
+ });
+
+ this.displayedItems = this.updateItems(false);
+ }
+
+ public onSegmentedBarPress(args: EventData) {
+ if (args && args.object) {
+ const segmentBar = args.object as SegmentedBar;
+ const selectedOdd = segmentBar.selectedIndex === 0;
+ this.displayedItems = this.updateItems(selectedOdd);
+ }
+ }
+
+ public createSegmentedBarItems() {
+ const itemOdd = new SegmentedBarItem();
+ itemOdd.title = "Odd Items";
+ const itemEven = new SegmentedBarItem();
+ itemEven.title = "Even Items";
+ return [itemOdd, itemEven];
+ }
+
+ public templateSelector(item: DataItem): string {
+ return item.type;
+ }
+
+ private updateItems(odd: boolean) {
+ const items = [
+ {
+ id: -1,
+ name: "Segmented Bar",
+ type: "segmentedBarTemplate",
+ },
+ ...(odd
+ ? this.items.filter((item) => item.id % 2 === 1)
+ : this.items.filter((item) => item.id % 2 === 0)),
+ {
+ id: 999,
+ name: "Refresh test",
+ type: "buttonTemplate",
+ },
+ ];
+ return items;
+ }
+}
diff --git a/e2e/tests-app-ng/app/list-view/list-view-nested-template.component.ts b/e2e/tests-app-ng/app/list-view/list-view-nested-template.component.ts
new file mode 100644
index 000000000..a1d73e47f
--- /dev/null
+++ b/e2e/tests-app-ng/app/list-view/list-view-nested-template.component.ts
@@ -0,0 +1,22 @@
+import { Component, ChangeDetectionStrategy } from "@angular/core";
+
+@Component({
+ selector: "list-test-nested",
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class ListViewWithNestedTemplateComponent {
+ public myItems: string[] = ["one", "two"];
+}
diff --git a/e2e/tests-app-ng/app/list-view/list-view-page.component.ts b/e2e/tests-app-ng/app/list-view/list-view-page.component.ts
new file mode 100644
index 000000000..0a92cfdcc
--- /dev/null
+++ b/e2e/tests-app-ng/app/list-view/list-view-page.component.ts
@@ -0,0 +1,41 @@
+import { Component, Input, ChangeDetectionStrategy } from "@angular/core";
+import { DataItem } from "./data-item";
+
+@Component({
+ moduleId: module.id,
+ selector: "list-test",
+ styleUrls: ["./list-view-page.css"],
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+ `
+})
+
+export class ListViewComponent {
+ public myItems: Array;
+ public results: string;
+ private counter: number;
+
+ constructor() {
+ this.results = "";
+ this.myItems = [];
+ this.counter = 0;
+ for (let i = 0; i < 5; i++) {
+ this.myItems.push(new DataItem(i, "data item " + i));
+ this.counter = i;
+ }
+ }
+
+ public onItemTap(args) {
+ this.results += "ItemTapped: " + args.index + "; \n";
+ }
+}
diff --git a/e2e/tests-app-ng/app/list-view/list-view-page.css b/e2e/tests-app-ng/app/list-view/list-view-page.css
new file mode 100644
index 000000000..d29d67f85
--- /dev/null
+++ b/e2e/tests-app-ng/app/list-view/list-view-page.css
@@ -0,0 +1,7 @@
+.odd {
+ background-color: red;
+}
+
+.even {
+ background-color: blue;
+}
\ No newline at end of file
diff --git a/e2e/tests-app-ng/app/list-view/multiple-templates.component.ts b/e2e/tests-app-ng/app/list-view/multiple-templates.component.ts
new file mode 100644
index 000000000..00c92f288
--- /dev/null
+++ b/e2e/tests-app-ng/app/list-view/multiple-templates.component.ts
@@ -0,0 +1,36 @@
+import { Observable as RxObservable } from "rxjs";
+import { Component, Input, ChangeDetectionStrategy } from "@angular/core";
+import { DataItem } from "./data-item";
+
+@Component({
+ styles: [".odd { background-color : yellow } .even{ background-color : green }"],
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+})
+export class ListViewMultipleTemplatesComponent {
+ public myItems: Array = [];
+
+ constructor() {
+ for (let i = 0; i < 12; i++) {
+ this.myItems.push(new DataItem(i, "data item " + i));
+ }
+ }
+}
diff --git a/e2e/tests-app-ng/app/main.ts b/e2e/tests-app-ng/app/main.ts
new file mode 100644
index 000000000..60b027e0c
--- /dev/null
+++ b/e2e/tests-app-ng/app/main.ts
@@ -0,0 +1,8 @@
+import { platformNativeScriptDynamic } from "@nativescript/angular";
+
+import { AppModule } from "./app.module";
+import { Trace } from "@nativescript/core";
+
+Trace.enable();
+
+platformNativeScriptDynamic().bootstrapModule(AppModule);
diff --git a/e2e/tests-app-ng/app/main/main-page-router-outlet.ts b/e2e/tests-app-ng/app/main/main-page-router-outlet.ts
new file mode 100644
index 000000000..cf2d31525
--- /dev/null
+++ b/e2e/tests-app-ng/app/main/main-page-router-outlet.ts
@@ -0,0 +1,63 @@
+import { Component } from "@angular/core";
+import * as platform from "@nativescript/core/platform";
+
+@Component({
+ selector: "main-page",
+ styles: [
+ ".main-btn{" +
+ "margin-right:5; margin-bottom:5;" +
+ "padding-left:5; padding-right:5;" +
+ "background-color:#28a745;color:white;" +
+ "border-radius:5;}"],
+ template: `
+
+
+
+ `,
+})
+export class MainComponent {
+ private _pages = [];
+ private _routes = require("../app.routes").routes;
+ private _orientation: string = "vertical";
+
+ constructor() {
+ const navigatableRoutes = this._routes.filter((item) => {
+ return item.data && item.data.isNavigatable && item.path;
+ });
+
+ const examples = navigatableRoutes.sort((a, b) => {
+ if (a.data.title > b.data.title) {
+ return 1;
+ }
+
+ if (a.data.title < b.data.title) {
+ return -1;
+ }
+
+ return 0;
+ });
+
+ this._pages = examples;
+ if (platform.isAndroid) {
+ this._orientation = "horizontal";
+ }
+ }
+
+ get pages() {
+ return this._pages;
+ }
+
+ get orientation() {
+ return this._orientation;
+ }
+}
+
+@Component({
+ selector: "navigation-main",
+ template: ``
+})
+export class NavigationMainPageRouterComponent { }
diff --git a/e2e/tests-app-ng/app/modal/lazy/lazy-load-modal.component.ts b/e2e/tests-app-ng/app/modal/lazy/lazy-load-modal.component.ts
new file mode 100644
index 000000000..3571090ab
--- /dev/null
+++ b/e2e/tests-app-ng/app/modal/lazy/lazy-load-modal.component.ts
@@ -0,0 +1,37 @@
+import {
+ Component,
+ ComponentFactory,
+ NgModuleFactory,
+ NgModuleFactoryLoader,
+ ViewContainerRef,
+} from "@angular/core";
+
+import { NSModuleFactoryLoader, ModalDialogService } from "@nativescript/angular";
+
+import { LazyComponent } from "../../lazy/lazy.component";
+
+@Component({
+ template: `
+
+ `
+})
+export class LazyLoadModalComponent {
+ constructor(
+ private moduleLoader: NgModuleFactoryLoader,
+ private vcRef: ViewContainerRef,
+ private modalService: ModalDialogService
+ ) { }
+
+ public openModal() {
+ this.moduleLoader.load("./lazy/lazy.module#LazyModule")
+ .then((module: NgModuleFactory) => {
+ const moduleRef = module.create(this.vcRef.parentInjector);
+
+ this.modalService.showModal(LazyComponent, {
+ moduleRef,
+ viewContainerRef: this.vcRef,
+ context: { isModal: true }
+ });
+ });
+ }
+}
diff --git a/e2e/tests-app-ng/app/modal/modal-dialogs/modal-dialog.component.ts b/e2e/tests-app-ng/app/modal/modal-dialogs/modal-dialog.component.ts
new file mode 100644
index 000000000..eb2c0f84a
--- /dev/null
+++ b/e2e/tests-app-ng/app/modal/modal-dialogs/modal-dialog.component.ts
@@ -0,0 +1,109 @@
+import {
+ Component,
+ ChangeDetectionStrategy,
+ ViewContainerRef,
+ ChangeDetectorRef
+} from "@angular/core";
+import {
+ ModalDialogService,
+ ModalDialogOptions,
+ ModalDialogParams
+} from "@nativescript/angular";
+
+@Component({
+ selector: "modal-content",
+ template: `
+
+
+
+
+
+
+
+
+
+ `
+})
+export class ModalContentComponent {
+ public prompt: string;
+ constructor(private params: ModalDialogParams) {
+ this.prompt = params.context.message;
+ }
+
+ public close(res: string) {
+ this.params.closeCallback(res);
+ }
+}
+
+@Component({
+ selector: "modal-test",
+ template: `
+
+
+
+
+
+ `
+})
+export class ModalTestComponent {
+ public result: string = "---";
+
+ constructor(private modal: ModalDialogService, private vcRef: ViewContainerRef) { }
+
+ public showModal() {
+ const options: ModalDialogOptions = {
+ viewContainerRef: this.vcRef,
+ context: { message: "Hello from dialog!" },
+ fullscreen: true
+ };
+
+ this.modal.showModal(ModalContentComponent, options).then((res: string) => {
+ this.result = res || "empty result";
+ });
+ }
+
+ public showModalAsync() {
+ setTimeout(() => {
+ this.showModal();
+ }, 10);
+ }
+}
+
+@Component({
+ selector: "modal-test-on-push",
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ template: `
+
+
+
+
+
+ `
+})
+export class ModalTestWithPushStrategyComponent {
+ public result: string = "---";
+
+ constructor(
+ private modal: ModalDialogService,
+ private vcRef: ViewContainerRef,
+ private cdRef: ChangeDetectorRef) { }
+
+ public showModal() {
+ const options: ModalDialogOptions = {
+ viewContainerRef: this.vcRef,
+ context: { message: "Hello from dialog (onPush)!" },
+ fullscreen: true
+ };
+
+ this.modal.showModal(ModalContentComponent, options).then((res: string) => {
+ this.result = res || "empty result";
+ this.cdRef.markForCheck();
+ });
+ }
+
+ public showModalAsync() {
+ setTimeout(() => {
+ this.showModal();
+ }, 10);
+ }
+}
diff --git a/e2e/tests-app-ng/app/modal/modal-view-main-page.component.ts b/e2e/tests-app-ng/app/modal/modal-view-main-page.component.ts
new file mode 100644
index 000000000..79a125ee5
--- /dev/null
+++ b/e2e/tests-app-ng/app/modal/modal-view-main-page.component.ts
@@ -0,0 +1,13 @@
+import { Component } from "@angular/core";
+
+@Component({
+ selector: "main-modal",
+ template: `
+
+
+
+
+
+ `,
+})
+export class ModalViewMainPageComponent { }
diff --git a/e2e/tests-app-ng/app/navigation-options/navigation-info.component.ts b/e2e/tests-app-ng/app/navigation-options/navigation-info.component.ts
new file mode 100644
index 000000000..c949dac24
--- /dev/null
+++ b/e2e/tests-app-ng/app/navigation-options/navigation-info.component.ts
@@ -0,0 +1,28 @@
+import { Component } from "@angular/core";
+import { NSLocationStrategy } from "@nativescript/angular";
+import { Frame } from "@nativescript/core/ui/frame";
+
+@Component({
+ selector: "nav-info",
+ template: `
+
+
+
+
+
+
+
+ `
+})
+export class NavigationInfoComponent {
+ public frameStack: number = -1;
+ public locationStack: number = -1;
+
+ constructor(private frame: Frame, private strategy: NSLocationStrategy) { }
+
+ update() {
+ // If history is cleared: frameStack = 0, locationStack = 1
+ this.frameStack = this.frame.backStack.length;
+ this.locationStack = this.strategy.findOutlet("primary").states.length;
+ }
+}
diff --git a/e2e/tests-app-ng/app/navigation-options/navigation-options.component.ts b/e2e/tests-app-ng/app/navigation-options/navigation-options.component.ts
new file mode 100644
index 000000000..06bdc3368
--- /dev/null
+++ b/e2e/tests-app-ng/app/navigation-options/navigation-options.component.ts
@@ -0,0 +1,27 @@
+import { Component } from "@angular/core";
+import { RouterExtensions } from "@nativescript/angular";
+
+@Component({
+ selector: "nav-options",
+ template: `
+
+
+
+
+ `
+})
+
+export class NavigationOptionsComponent {
+
+ constructor(private routerExtensions: RouterExtensions) { }
+
+ flipToNextPage() {
+ this.routerExtensions.navigate(["/nav-info"], {
+ transition: {
+ name: "flip",
+ duration: 17000,
+ curve: "linear"
+ }
+ });
+ }
+}
diff --git a/e2e/tests-app-ng/app/router/lazy-module-navigation/lazy-navigation.component.ts b/e2e/tests-app-ng/app/router/lazy-module-navigation/lazy-navigation.component.ts
new file mode 100644
index 000000000..dde337885
--- /dev/null
+++ b/e2e/tests-app-ng/app/router/lazy-module-navigation/lazy-navigation.component.ts
@@ -0,0 +1,7 @@
+import { Component } from "@angular/core";
+
+@Component({
+ template: ``,
+})
+export class LazyNavigationComponent {
+}
diff --git a/e2e/tests-app-ng/app/router/router-outlet/first.component.ts b/e2e/tests-app-ng/app/router/router-outlet/first.component.ts
new file mode 100644
index 000000000..2e421dfcb
--- /dev/null
+++ b/e2e/tests-app-ng/app/router/router-outlet/first.component.ts
@@ -0,0 +1,11 @@
+import { Component } from "@angular/core";
+
+@Component({
+ selector: "first",
+ template: `
+
+
+
+ `
+})
+export class FirstComponent { }
diff --git a/e2e/tests-app-ng/app/router/router-outlet/navigation.component.css b/e2e/tests-app-ng/app/router/router-outlet/navigation.component.css
new file mode 100644
index 000000000..59cc35a5f
--- /dev/null
+++ b/e2e/tests-app-ng/app/router/router-outlet/navigation.component.css
@@ -0,0 +1,15 @@
+.title {
+ font-size: 30;
+ margin: 16;
+}
+
+.nav {
+ orientation: horizontal;
+ horizontal-align: stretch;
+ padding: 4;
+ background-color: lightblue;
+}
+
+.link {
+ margin: 10 30;
+}
\ No newline at end of file
diff --git a/e2e/tests-app-ng/app/router/router-outlet/navigation.component.ts b/e2e/tests-app-ng/app/router/router-outlet/navigation.component.ts
new file mode 100644
index 000000000..41b0b71d5
--- /dev/null
+++ b/e2e/tests-app-ng/app/router/router-outlet/navigation.component.ts
@@ -0,0 +1,28 @@
+import { Component } from "@angular/core";
+import { FirstComponent } from "./first.component";
+import { SecondComponent } from "./second.component";
+
+@Component({
+ moduleId: module.id,
+ selector: "navigation-test",
+ styleUrls: ["./navigation.component.css"],
+ template: `
+
+
+
+
+
+
+
+
+ `
+})
+export class NavigationComponent { }
+
+export const NAVIGATION_SUBROUTES = [
+ { path: "", redirectTo: "first", pathMatch: "full" },
+ { path: "first", component: FirstComponent },
+ { path: "second", component: SecondComponent },
+];
diff --git a/e2e/tests-app-ng/app/router/router-outlet/second.component.ts b/e2e/tests-app-ng/app/router/router-outlet/second.component.ts
new file mode 100644
index 000000000..8a8f4dfdb
--- /dev/null
+++ b/e2e/tests-app-ng/app/router/router-outlet/second.component.ts
@@ -0,0 +1,12 @@
+import { Component } from "@angular/core";
+
+@Component({
+ selector: "second",
+ template: `
+
+
+
+ `
+})
+export class SecondComponent { }
+
diff --git a/e2e/tests-app-ng/app/segmented-bar/issue-649.component.ts b/e2e/tests-app-ng/app/segmented-bar/issue-649.component.ts
new file mode 100644
index 000000000..2c2bd85e4
--- /dev/null
+++ b/e2e/tests-app-ng/app/segmented-bar/issue-649.component.ts
@@ -0,0 +1,36 @@
+import { Component } from "@angular/core";
+import { SegmentedBar, SegmentedBarItem } from "@nativescript/core/ui/segmented-bar";
+
+@Component({
+ styles: ["#second { margin: 5; color: blue;" +
+ "background-color: yellow;" +
+ "font-weight: bold; font-size: 20;" +
+ "font-style: italic; font-family: monospace;" +
+ "height: 72; border-width: 2; border-radius: 7;" +
+ "border-color:green; selected-background-color: red; }"],
+ template: `
+
+
+
+
+
+ `
+})
+export class SegmentedBarIssue649Component {
+ public firstSegmentedBarItems: Array = [];
+ public secondSegmentedBarItems: Array = [];
+
+ constructor() {
+ for (let i = 1; i < 4; i++) {
+ let segmentedBarItem = new SegmentedBarItem();
+ segmentedBarItem.title = "View " + i;
+ this.firstSegmentedBarItems.push(segmentedBarItem);
+ let segmentedBarItem1 = new SegmentedBarItem();
+ segmentedBarItem1.title = "View " + i * 2;
+ this.secondSegmentedBarItems.push(segmentedBarItem1);
+ }
+ }
+}
diff --git a/e2e/tests-app-ng/app/segmented-bar/segmented-bar-main-page.component.ts b/e2e/tests-app-ng/app/segmented-bar/segmented-bar-main-page.component.ts
new file mode 100644
index 000000000..08ebbf765
--- /dev/null
+++ b/e2e/tests-app-ng/app/segmented-bar/segmented-bar-main-page.component.ts
@@ -0,0 +1,10 @@
+import { Component } from "@angular/core";
+
+@Component({
+ template: `
+
+
+
+ `,
+})
+export class SegmentedBarMainPageComponent { }
diff --git a/e2e/tests-app-ng/app/tab-view/tab-view.component.ts b/e2e/tests-app-ng/app/tab-view/tab-view.component.ts
new file mode 100644
index 000000000..fad7b9204
--- /dev/null
+++ b/e2e/tests-app-ng/app/tab-view/tab-view.component.ts
@@ -0,0 +1,30 @@
+import { Component, OnInit } from "@angular/core";
+
+@Component({
+ selector: "tab-view-component",
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+})
+
+export class TabViewComponent implements OnInit {
+ public isLoading: boolean = true;
+
+ public ngOnInit(): void {
+ setTimeout(() => {
+ this.isLoading = false;
+ }, 500);
+ }
+}
diff --git a/e2e/tests-app-ng/app/tabs/tabs.component.ts b/e2e/tests-app-ng/app/tabs/tabs.component.ts
new file mode 100644
index 000000000..a0e6efe8f
--- /dev/null
+++ b/e2e/tests-app-ng/app/tabs/tabs.component.ts
@@ -0,0 +1,68 @@
+import { Component, OnInit } from "@angular/core";
+
+@Component({
+ selector: "tabs-component",
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+})
+
+export class TabsComponent implements OnInit {
+
+ public ngOnInit(): void { }
+
+ goTo(tabs: any, index: number) {
+ tabs.selectedIndex = index;
+ }
+
+}
diff --git a/e2e/tests-app-ng/app/template/.DS_Store b/e2e/tests-app-ng/app/template/.DS_Store
new file mode 100644
index 000000000..5008ddfcf
Binary files /dev/null and b/e2e/tests-app-ng/app/template/.DS_Store differ
diff --git a/e2e/tests-app-ng/app/template/app.component.css b/e2e/tests-app-ng/app/template/app.component.css
new file mode 100644
index 000000000..8d4dce63d
--- /dev/null
+++ b/e2e/tests-app-ng/app/template/app.component.css
@@ -0,0 +1,19 @@
+button, label, stack-layout {
+ horizontal-align: center;
+}
+
+button {
+ font-size: 36;
+}
+
+.title {
+ font-size: 30;
+ margin: 20;
+}
+
+.message {
+ font-size: 20;
+ color: #284848;
+ text-align: center;
+ margin: 0 20;
+}
\ No newline at end of file
diff --git a/e2e/tests-app-ng/app/template/app.component.ts b/e2e/tests-app-ng/app/template/app.component.ts
new file mode 100644
index 000000000..5f3969aba
--- /dev/null
+++ b/e2e/tests-app-ng/app/template/app.component.ts
@@ -0,0 +1,30 @@
+import { Component } from "@angular/core";
+
+@Component({
+ moduleId: module.id,
+ selector: "my-app",
+ styles: ["button {color: lime}"],
+ styleUrls: ["./app.component.css"],
+ template: `
+
+
+
+
+
+`,
+})
+export class AppComponent {
+ public counter: number = 16;
+
+ public get message(): string {
+ if (this.counter > 0) {
+ return this.counter + " taps left";
+ } else {
+ return "Hoorraaay! \nYou are ready to start building!";
+ }
+ }
+
+ public onTap() {
+ this.counter--;
+ }
+}
diff --git a/e2e/tests-app-ng/nativescript.config.ts b/e2e/tests-app-ng/nativescript.config.ts
new file mode 100644
index 000000000..89dc02904
--- /dev/null
+++ b/e2e/tests-app-ng/nativescript.config.ts
@@ -0,0 +1,11 @@
+import { NativeScriptConfig } from '@nativescript/core'
+
+export default {
+ id: 'org.nativescript.testsappng',
+ appResourcesPath: 'app/App_Resources',
+ android: {
+ v8Flags: '--expose_gc',
+ markingMode: 'none',
+ },
+ appPath: 'app',
+} as NativeScriptConfig
diff --git a/e2e/tests-app-ng/package.json b/e2e/tests-app-ng/package.json
new file mode 100644
index 000000000..98cee8a7c
--- /dev/null
+++ b/e2e/tests-app-ng/package.json
@@ -0,0 +1,47 @@
+{
+ "description": "NativeScript Application",
+ "license": "SEE LICENSE IN ",
+ "readme": "NativeScript Application",
+ "repository": "",
+ "dependencies": {
+ "@angular/animations": "~10.1.0",
+ "@angular/common": "~10.1.0",
+ "@angular/compiler": "~10.1.0",
+ "@angular/core": "~10.1.0",
+ "@angular/forms": "~10.1.0",
+ "@angular/platform-browser": "~10.1.0",
+ "@angular/platform-browser-dynamic": "~10.1.0",
+ "@angular/router": "~10.1.0",
+ "@nativescript/angular": "file:../../dist/nativescript-angular-scoped.tgz",
+ "nativescript-theme-core": "^1.0.4",
+ "reflect-metadata": "~0.1.8",
+ "rxjs": "~6.6.0",
+ "@nativescript/core": "~7.0.0",
+ "zone.js": "^0.11.1"
+ },
+ "devDependencies": {
+ "@angular/compiler-cli": "~10.1.0",
+ "@nativescript/ios": "7.0.0",
+ "@nativescript/webpack": "~3.0.0",
+ "@ngtools/webpack": "~10.1.0",
+ "babel-traverse": "6.24.1",
+ "babel-types": "6.24.1",
+ "babylon": "6.17.0",
+ "codelyzer": "^5.1.0",
+ "filewalker": "^0.1.3",
+ "lazy": "1.0.11",
+ "typescript": "~3.9.0"
+ },
+ "scripts": {
+ "clean": "npx rimraf hooks node_modules platforms package-lock.json webpack.config.js && npm i",
+ "setup": "cd ../../nativescript-angular && npm run prep.apps && cd ../e2e/tests-app-ng && npm run clean",
+ "u": "update-ns-webpack",
+ "e2e": "tsc -p e2e && mocha --opts ../config/mocha.opts --recursive e2e --appiumCapsLocation ../config/appium.capabilities.json",
+ "e2e-watch": "tsc -p e2e --watch",
+ "ns-verify-bundle": "ns-verify-bundle",
+ "update-ns-webpack": "update-ns-webpack",
+ "ios": "tns debug ios --emulator --no-hmr",
+ "android": "tns debug android --emulator --no-hmr"
+ },
+ "main": "main.js"
+}
diff --git a/e2e/tests-app-ng/references.d.ts b/e2e/tests-app-ng/references.d.ts
new file mode 100644
index 000000000..a5bb99810
--- /dev/null
+++ b/e2e/tests-app-ng/references.d.ts
@@ -0,0 +1 @@
+///
\ No newline at end of file
diff --git a/e2e/tests-app-ng/tsconfig.json b/e2e/tests-app-ng/tsconfig.json
new file mode 100644
index 000000000..29c1ff2d7
--- /dev/null
+++ b/e2e/tests-app-ng/tsconfig.json
@@ -0,0 +1,34 @@
+{
+ "compilerOptions": {
+ "module": "esnext",
+ "target": "es2017",
+ "moduleResolution": "node",
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "noEmitHelpers": true,
+ "noEmitOnError": true,
+ "skipLibCheck": true,
+ "lib": [
+ "es2017",
+ "dom",
+ "es6"
+ ],
+ "baseUrl": ".",
+ "paths": {
+ "~/*": [
+ "app/*"
+ ]
+ },
+ "removeComments": false
+ },
+ "files": [
+ "./references.d.ts",
+ "./app/main.ts"
+ ],
+ "exclude": [
+ "node_modules",
+ "platforms",
+ "**/*.aot",
+ "e2e"
+ ]
+}
\ No newline at end of file
diff --git a/gruntfile.js b/gruntfile.js
deleted file mode 100644
index 8957f2856..000000000
--- a/gruntfile.js
+++ /dev/null
@@ -1,174 +0,0 @@
-var path = require("path");
-var shelljs = require("shelljs");
-
-module.exports = function(grunt) {
- grunt.loadNpmTasks('grunt-ts');
- grunt.loadNpmTasks('grunt-shell');
- grunt.loadNpmTasks('grunt-contrib-copy');
- grunt.loadNpmTasks('grunt-contrib-clean');
- grunt.loadNpmTasks('grunt-env');
-
- var outDir = "bin/dist/modules";
- var moduleOutDir = path.join(outDir, "nativescript-angular");
- var nsDistPath = process.env.NSDIST || './deps/NativeScript/bin/dist';
-
- var ngSampleSubDir = {
- execOptions: {
- cwd: 'ng-sample',
- }
- };
-
- var nsSubDir = {
- execOptions: {
- cwd: 'deps/NativeScript',
- }
- };
-
- var angularSubDir = {
- execOptions: {
- cwd: 'deps/angular',
- }
- };
-
- grunt.initConfig({
- ts: {
- build: {
- src: [
- 'src/**/*.ts',
- 'node_modules/tns-core-modules/tns-core-modules.d.ts',
- ],
- outDir: outDir,
- options: {
- fast: 'never',
- module: "commonjs",
- target: "es5",
- sourceMap: true,
- experimentalDecorators: true,
- emitDecoratorMetadata: true,
- declaration: true,
- removeComments: false,
- compiler: "node_modules/typescript/bin/tsc",
- noEmitOnError: true
- },
- },
- },
- copy: {
- packageJson: {
- expand: true,
- src: 'package.json',
- dest: moduleOutDir
- },
- npmReadme: {
- expand: true,
- src: 'README.md',
- cwd: 'doc',
- dest: moduleOutDir
- },
- handCodedDefinitions: {
- src: '**/*.d.ts',
- cwd: 'src/nativescript-angular',
- expand: true,
- dest: moduleOutDir
- },
- },
- clean: {
- src: {
- expand: true,
- cwd: './src',
- src: [
- '*.ts',
- '!dependencies.d.ts',
- '!global.d.ts',
- 'angular2',
- ]
- },
- package: {
- src: 'nativescript-angular*.tgz'
- },
- packageDefinitions: {
- src: moduleOutDir + '/**/*.d.ts'
- }
- },
- shell: {
- depNSInit: {
- command: [
- 'npm install',
- 'grunt --test-app-only=true --no-runtslint',
- ].join('&&'),
- options: nsSubDir
- },
- localInstallModules: {
- command: "npm install \"<%= nsPackagePath %>\""
- },
- package: {
- command: "npm pack \"" + moduleOutDir + "\""
- },
- installAngularDependencies: {
- command: 'npm install',
- options: angularSubDir
- },
- compileAngular: {
- command: 'gulp build.js.cjs',
- options: angularSubDir
- },
- buildAngularPackage: {
- command: 'npm pack deps/angular/dist/js/cjs/angular2',
- },
- installAngularPackage: {
- command: 'npm install angular2-*.tgz',
- }
- },
- });
-
- grunt.registerTask("run", ['ts', 'shell:runApp']);
-
- grunt.registerTask("cleanAll", [
- 'clean:src',
- 'clean:package',
- ]);
-
- grunt.registerTask("package", [
- "clean:packageDefinitions",
- "copy:handCodedDefinitions",
- "copy:npmReadme",
- "shell:package",
- ]);
-
- grunt.registerTask("installAngular", [
- "shell:installAngularDependencies",
- "shell:compileAngular",
- "shell:buildAngularPackage",
- "shell:installAngularPackage",
- ]);
-
- grunt.registerTask("getNSPackage", function() {
- var packageFiles = grunt.file.expand({
- cwd: nsDistPath
- },[
- 'tns-core-modules*.tgz'
- ]);
- var nsPackagePath = path.join(nsDistPath, packageFiles[0]);
- grunt.config('nsPackagePath', nsPackagePath);
- });
-
- grunt.registerTask("installModules", [
- "shell:depNSInit",
- "getNSPackage",
- "shell:localInstallModules",
- ]);
-
- grunt.registerTask("prepare", [
- "cleanAll",
- "installAngular",
- "installModules",
- "build"
- ]);
-
- grunt.registerTask("build", [
- "ts:build",
- "copy:packageJson",
- "package"
- ]);
-
- grunt.registerTask("default", ["prepare"]);
-};
diff --git a/merge-guidance-schema.png b/merge-guidance-schema.png
new file mode 100644
index 000000000..dd882471e
Binary files /dev/null and b/merge-guidance-schema.png differ
diff --git a/nativescript-angular-package/.npmignore b/nativescript-angular-package/.npmignore
new file mode 100644
index 000000000..728e2a64d
--- /dev/null
+++ b/nativescript-angular-package/.npmignore
@@ -0,0 +1,13 @@
+*.tgz
+dist
+
+*.ts
+!*.d.ts
+
+*.js.map
+
+tsconfig.json
+global.d.ts
+.npmignore
+gulpfile.js
+tslint.json
diff --git a/nativescript-angular-package/animations/index.ts b/nativescript-angular-package/animations/index.ts
new file mode 100644
index 000000000..3add12158
--- /dev/null
+++ b/nativescript-angular-package/animations/index.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular/animations";
\ No newline at end of file
diff --git a/nativescript-angular-package/app-host-view.ts b/nativescript-angular-package/app-host-view.ts
new file mode 100644
index 000000000..2d2419d19
--- /dev/null
+++ b/nativescript-angular-package/app-host-view.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular";
\ No newline at end of file
diff --git a/nativescript-angular-package/common.ts b/nativescript-angular-package/common.ts
new file mode 100644
index 000000000..2d2419d19
--- /dev/null
+++ b/nativescript-angular-package/common.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular";
\ No newline at end of file
diff --git a/nativescript-angular-package/common/detached-loader.ts b/nativescript-angular-package/common/detached-loader.ts
new file mode 100644
index 000000000..1bf4ba6f3
--- /dev/null
+++ b/nativescript-angular-package/common/detached-loader.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular/common/detached-loader";
\ No newline at end of file
diff --git a/nativescript-angular-package/common/index.ts b/nativescript-angular-package/common/index.ts
new file mode 100644
index 000000000..c78d3aac7
--- /dev/null
+++ b/nativescript-angular-package/common/index.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular/common";
diff --git a/nativescript-angular-package/common/utils.ts b/nativescript-angular-package/common/utils.ts
new file mode 100644
index 000000000..54c798aed
--- /dev/null
+++ b/nativescript-angular-package/common/utils.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular/common/utils";
\ No newline at end of file
diff --git a/nativescript-angular-package/directives/action-bar.ts b/nativescript-angular-package/directives/action-bar.ts
new file mode 100644
index 000000000..5337b967b
--- /dev/null
+++ b/nativescript-angular-package/directives/action-bar.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular/directives/action-bar";
\ No newline at end of file
diff --git a/nativescript-angular-package/directives/dialogs.ts b/nativescript-angular-package/directives/dialogs.ts
new file mode 100644
index 000000000..f7aa4b95b
--- /dev/null
+++ b/nativescript-angular-package/directives/dialogs.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular/directives/dialogs";
\ No newline at end of file
diff --git a/nativescript-angular-package/directives/index.ts b/nativescript-angular-package/directives/index.ts
new file mode 100644
index 000000000..d32d1c9a8
--- /dev/null
+++ b/nativescript-angular-package/directives/index.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular/directives";
\ No newline at end of file
diff --git a/nativescript-angular-package/directives/templated-items-comp.ts b/nativescript-angular-package/directives/templated-items-comp.ts
new file mode 100644
index 000000000..cdb635ff4
--- /dev/null
+++ b/nativescript-angular-package/directives/templated-items-comp.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular/directives/templated-items-comp";
\ No newline at end of file
diff --git a/nativescript-angular-package/dom-adapter.ts b/nativescript-angular-package/dom-adapter.ts
new file mode 100644
index 000000000..2d2419d19
--- /dev/null
+++ b/nativescript-angular-package/dom-adapter.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular";
\ No newline at end of file
diff --git a/nativescript-angular-package/element-registry.ts b/nativescript-angular-package/element-registry.ts
new file mode 100644
index 000000000..2d2419d19
--- /dev/null
+++ b/nativescript-angular-package/element-registry.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular";
\ No newline at end of file
diff --git a/nativescript-angular-package/file-system/ns-file-system.ts b/nativescript-angular-package/file-system/ns-file-system.ts
new file mode 100644
index 000000000..e7b0b52dd
--- /dev/null
+++ b/nativescript-angular-package/file-system/ns-file-system.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular/file-system/ns-file-system";
\ No newline at end of file
diff --git a/nativescript-angular-package/forms/index.ts b/nativescript-angular-package/forms/index.ts
new file mode 100644
index 000000000..2d2419d19
--- /dev/null
+++ b/nativescript-angular-package/forms/index.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular";
\ No newline at end of file
diff --git a/nativescript-angular-package/forms/value-accessors/base-value-accessor.ts b/nativescript-angular-package/forms/value-accessors/base-value-accessor.ts
new file mode 100644
index 000000000..36c3a7f63
--- /dev/null
+++ b/nativescript-angular-package/forms/value-accessors/base-value-accessor.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular/forms/value-accessors/base-value-accessor";
\ No newline at end of file
diff --git a/nativescript-angular-package/forms/value-accessors/checked-value-accessor.ts b/nativescript-angular-package/forms/value-accessors/checked-value-accessor.ts
new file mode 100644
index 000000000..27f9059a7
--- /dev/null
+++ b/nativescript-angular-package/forms/value-accessors/checked-value-accessor.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular/forms/value-accessors/checked-value-accessor";
\ No newline at end of file
diff --git a/nativescript-angular-package/forms/value-accessors/date-value-accessor.ts b/nativescript-angular-package/forms/value-accessors/date-value-accessor.ts
new file mode 100644
index 000000000..f3640a34c
--- /dev/null
+++ b/nativescript-angular-package/forms/value-accessors/date-value-accessor.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular/forms/value-accessors/date-value-accessor";
\ No newline at end of file
diff --git a/nativescript-angular-package/forms/value-accessors/index.ts b/nativescript-angular-package/forms/value-accessors/index.ts
new file mode 100644
index 000000000..37a248724
--- /dev/null
+++ b/nativescript-angular-package/forms/value-accessors/index.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular/forms/value-accessors";
\ No newline at end of file
diff --git a/nativescript-angular-package/forms/value-accessors/number-value-accessor.ts b/nativescript-angular-package/forms/value-accessors/number-value-accessor.ts
new file mode 100644
index 000000000..5f9ff4ec8
--- /dev/null
+++ b/nativescript-angular-package/forms/value-accessors/number-value-accessor.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular/forms/value-accessors/number-value-accessor";
\ No newline at end of file
diff --git a/nativescript-angular-package/forms/value-accessors/selectedIndex-value-accessor.ts b/nativescript-angular-package/forms/value-accessors/selectedIndex-value-accessor.ts
new file mode 100644
index 000000000..8ae5a0419
--- /dev/null
+++ b/nativescript-angular-package/forms/value-accessors/selectedIndex-value-accessor.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular/forms/value-accessors/selectedIndex-value-accessor";
\ No newline at end of file
diff --git a/nativescript-angular-package/forms/value-accessors/text-value-accessor.ts b/nativescript-angular-package/forms/value-accessors/text-value-accessor.ts
new file mode 100644
index 000000000..81db7183d
--- /dev/null
+++ b/nativescript-angular-package/forms/value-accessors/text-value-accessor.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular/forms/value-accessors/text-value-accessor";
\ No newline at end of file
diff --git a/nativescript-angular-package/forms/value-accessors/time-value-accessor.ts b/nativescript-angular-package/forms/value-accessors/time-value-accessor.ts
new file mode 100644
index 000000000..98647ee2e
--- /dev/null
+++ b/nativescript-angular-package/forms/value-accessors/time-value-accessor.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular/forms/value-accessors/time-value-accessor";
\ No newline at end of file
diff --git a/nativescript-angular-package/http-client/index.ts b/nativescript-angular-package/http-client/index.ts
new file mode 100644
index 000000000..2d2419d19
--- /dev/null
+++ b/nativescript-angular-package/http-client/index.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular";
\ No newline at end of file
diff --git a/nativescript-angular-package/index.ts b/nativescript-angular-package/index.ts
new file mode 100644
index 000000000..053585d6a
--- /dev/null
+++ b/nativescript-angular-package/index.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular";
diff --git a/nativescript-angular-package/lang-facade.ts b/nativescript-angular-package/lang-facade.ts
new file mode 100644
index 000000000..2d2419d19
--- /dev/null
+++ b/nativescript-angular-package/lang-facade.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular";
\ No newline at end of file
diff --git a/nativescript-angular-package/modal-dialog.ts b/nativescript-angular-package/modal-dialog.ts
new file mode 100644
index 000000000..2d2419d19
--- /dev/null
+++ b/nativescript-angular-package/modal-dialog.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular";
\ No newline at end of file
diff --git a/nativescript-angular-package/nativescript.module.ts b/nativescript-angular-package/nativescript.module.ts
new file mode 100644
index 000000000..2d2419d19
--- /dev/null
+++ b/nativescript-angular-package/nativescript.module.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular";
\ No newline at end of file
diff --git a/nativescript-angular-package/package.json b/nativescript-angular-package/package.json
new file mode 100644
index 000000000..54867bcbb
--- /dev/null
+++ b/nativescript-angular-package/package.json
@@ -0,0 +1,61 @@
+{
+ "name": "nativescript-angular",
+ "version": "11.2.0",
+ "description": "Compatibility with old style nativescript-angular imports.",
+ "homepage": "https://www.nativescript.org/",
+ "bugs": "https://github.com/NativeScript/nativescript-angular/issues",
+ "author": {
+ "name": "NativeScript Team"
+ },
+ "nativescript": {
+ "platforms": {
+ "android": "6.0.0",
+ "ios": "6.0.0"
+ }
+ },
+ "keywords": [
+ "NativeScript",
+ "Angular"
+ ],
+ "license": "Apache-2.0",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/NativeScript/nativescript-angular.git"
+ },
+ "ngPackage": {
+ "lib": {
+ "entryFile": "index.ts",
+ "umdModuleIds": {
+ "@nativescript/core": "ns-core",
+ "@nativescript/angular": "ns-angular"
+ }
+ },
+ "allowedNonPeerDependencies": [
+ "."
+ ]
+ },
+ "devDependencies": {
+ "@angular/animations": "~11.0.0",
+ "@angular/common": "~11.0.0",
+ "@angular/compiler": "~11.0.0",
+ "@angular/compiler-cli": "~11.0.0",
+ "@angular/core": "~11.0.0",
+ "@angular/forms": "~11.0.0",
+ "@angular/platform-browser": "~11.0.0",
+ "@angular/platform-browser-dynamic": "~11.0.0",
+ "@angular/router": "~11.0.0",
+ "@nativescript/angular": "rc",
+ "@nativescript/core": "~8.0.0",
+ "ng-packagr": "^11.0.0",
+ "rxjs": "~6.6.0",
+ "typescript": "~4.0.0"
+ },
+ "scripts": {
+ "setup": "npx rimraf hooks node_modules package-lock.json && npm i",
+ "build": "ng-packagr -p package.json",
+ "build.pack": "npm run tsc && npm run build && cd dist && npm pack",
+ "ngc": "ngc -p tsconfig.json",
+ "tsc": "tsc",
+ "pack-with-scoped-version": "cd ../build/pack-scripts && npm i && npx ts-node pack-compat.ts"
+ }
+}
diff --git a/nativescript-angular-package/platform-common.ts b/nativescript-angular-package/platform-common.ts
new file mode 100644
index 000000000..2d2419d19
--- /dev/null
+++ b/nativescript-angular-package/platform-common.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular";
\ No newline at end of file
diff --git a/nativescript-angular-package/platform-providers.ts b/nativescript-angular-package/platform-providers.ts
new file mode 100644
index 000000000..2d2419d19
--- /dev/null
+++ b/nativescript-angular-package/platform-providers.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular";
\ No newline at end of file
diff --git a/nativescript-angular-package/platform.ts b/nativescript-angular-package/platform.ts
new file mode 100644
index 000000000..2d2419d19
--- /dev/null
+++ b/nativescript-angular-package/platform.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular";
\ No newline at end of file
diff --git a/nativescript-angular-package/renderer.ts b/nativescript-angular-package/renderer.ts
new file mode 100644
index 000000000..2d2419d19
--- /dev/null
+++ b/nativescript-angular-package/renderer.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular";
\ No newline at end of file
diff --git a/nativescript-angular-package/resource-loader.ts b/nativescript-angular-package/resource-loader.ts
new file mode 100644
index 000000000..2d2419d19
--- /dev/null
+++ b/nativescript-angular-package/resource-loader.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular";
\ No newline at end of file
diff --git a/nativescript-angular-package/router/index.ts b/nativescript-angular-package/router/index.ts
new file mode 100644
index 000000000..2d2419d19
--- /dev/null
+++ b/nativescript-angular-package/router/index.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular";
\ No newline at end of file
diff --git a/nativescript-angular-package/router/ns-location-strategy.ts b/nativescript-angular-package/router/ns-location-strategy.ts
new file mode 100644
index 000000000..3d94eb407
--- /dev/null
+++ b/nativescript-angular-package/router/ns-location-strategy.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular/router/ns-location-strategy";
\ No newline at end of file
diff --git a/nativescript-angular-package/router/ns-route-reuse-strategy.ts b/nativescript-angular-package/router/ns-route-reuse-strategy.ts
new file mode 100644
index 000000000..fa6306cfc
--- /dev/null
+++ b/nativescript-angular-package/router/ns-route-reuse-strategy.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular/router/ns-route-reuse-strategy";
\ No newline at end of file
diff --git a/nativescript-angular-package/router/ns-router-link.ts b/nativescript-angular-package/router/ns-router-link.ts
new file mode 100644
index 000000000..fa87694d9
--- /dev/null
+++ b/nativescript-angular-package/router/ns-router-link.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular/router/ns-router-link";
\ No newline at end of file
diff --git a/nativescript-angular-package/router/page-router-outlet.ts b/nativescript-angular-package/router/page-router-outlet.ts
new file mode 100644
index 000000000..f181cf615
--- /dev/null
+++ b/nativescript-angular-package/router/page-router-outlet.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular/router/page-router-outlet";
\ No newline at end of file
diff --git a/nativescript-angular-package/router/router-extensions.ts b/nativescript-angular-package/router/router-extensions.ts
new file mode 100644
index 000000000..f98a28146
--- /dev/null
+++ b/nativescript-angular-package/router/router-extensions.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular/router/router-extensions";
\ No newline at end of file
diff --git a/nativescript-angular-package/router/router.module.ts b/nativescript-angular-package/router/router.module.ts
new file mode 100644
index 000000000..50344ad36
--- /dev/null
+++ b/nativescript-angular-package/router/router.module.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular/router/router.module";
\ No newline at end of file
diff --git a/nativescript-angular-package/schema-registry.ts b/nativescript-angular-package/schema-registry.ts
new file mode 100644
index 000000000..2d2419d19
--- /dev/null
+++ b/nativescript-angular-package/schema-registry.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular";
\ No newline at end of file
diff --git a/nativescript-angular-package/testing/index.ts b/nativescript-angular-package/testing/index.ts
new file mode 100644
index 000000000..af69144d0
--- /dev/null
+++ b/nativescript-angular-package/testing/index.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular/testing";
\ No newline at end of file
diff --git a/nativescript-angular-package/testing/src/nativescript_test_component_renderer.ts b/nativescript-angular-package/testing/src/nativescript_test_component_renderer.ts
new file mode 100644
index 000000000..af69144d0
--- /dev/null
+++ b/nativescript-angular-package/testing/src/nativescript_test_component_renderer.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular/testing";
\ No newline at end of file
diff --git a/nativescript-angular-package/testing/src/util.ts b/nativescript-angular-package/testing/src/util.ts
new file mode 100644
index 000000000..af69144d0
--- /dev/null
+++ b/nativescript-angular-package/testing/src/util.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular/testing";
\ No newline at end of file
diff --git a/nativescript-angular-package/trace.ts b/nativescript-angular-package/trace.ts
new file mode 100644
index 000000000..2d2419d19
--- /dev/null
+++ b/nativescript-angular-package/trace.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular";
\ No newline at end of file
diff --git a/nativescript-angular-package/tsconfig.json b/nativescript-angular-package/tsconfig.json
new file mode 100644
index 000000000..85c737c2b
--- /dev/null
+++ b/nativescript-angular-package/tsconfig.json
@@ -0,0 +1,38 @@
+{
+ "compilerOptions": {
+ "target": "es2017",
+ "module": "esnext",
+ "moduleResolution": "node",
+ "sourceMap": true,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "noImplicitUseStrict": true,
+ "noEmitHelpers": true,
+ "declaration": true,
+ "removeComments": true,
+ "noEmitOnError": true,
+ "skipLibCheck": true,
+ "skipDefaultLibCheck": true,
+ "noImplicitAny": false,
+ "lib": [
+ "es2017",
+ "dom",
+ "es6"
+ ],
+ "baseUrl": "."
+ },
+ "angularCompilerOptions": {
+ "genDir": ".",
+ "skipMetadataEmit": false,
+ "skipTemplateCodegen": true,
+ "strictMetadataEmit": true,
+ "enableIvy": true
+ },
+ "include": [
+ "**/*.ts"
+ ],
+ "exclude": [
+ "node_modules",
+ "dist"
+ ]
+}
\ No newline at end of file
diff --git a/nativescript-angular-package/value-accessors/base-value-accessor.ts b/nativescript-angular-package/value-accessors/base-value-accessor.ts
new file mode 100644
index 000000000..aa3cfbdb5
--- /dev/null
+++ b/nativescript-angular-package/value-accessors/base-value-accessor.ts
@@ -0,0 +1,2 @@
+// This file is only for compatibility with pre 4.4.0 releases.
+export * from "@nativescript/angular";
diff --git a/nativescript-angular-package/view-util.ts b/nativescript-angular-package/view-util.ts
new file mode 100644
index 000000000..2d2419d19
--- /dev/null
+++ b/nativescript-angular-package/view-util.ts
@@ -0,0 +1 @@
+export * from "@nativescript/angular";
\ No newline at end of file
diff --git a/nativescript-angular-package/zone-js/README.md b/nativescript-angular-package/zone-js/README.md
new file mode 100644
index 000000000..38a0ebc26
--- /dev/null
+++ b/nativescript-angular-package/zone-js/README.md
@@ -0,0 +1,10 @@
+Zone.js for NativeScript
+---
+
+Zone.js is a library that aims to intercept all asynchronous API calls made in an environment, in order
+to wrap them into coherent execution contexts over time.
+
+NativeScript executes inside an environment that Zone.js is not designed to work in, so a custom Zone.js output
+must be created.
+
+Find out more about this in the [Upgrading Zone.js document](../../doc/upgrading-zonejs.md)
\ No newline at end of file
diff --git a/nativescript-angular-package/zone-js/testing.jasmine.ts b/nativescript-angular-package/zone-js/testing.jasmine.ts
new file mode 100644
index 000000000..b52f2e881
--- /dev/null
+++ b/nativescript-angular-package/zone-js/testing.jasmine.ts
@@ -0,0 +1,4 @@
+// Bootstrap helper module for jasmine spec tests
+import "@nativescript/angular/platform";
+// import "@nativescript/angular/zone-js/dist/zone-nativescript.jasmine.js";
+import '@nativescript/zone-js';
diff --git a/nativescript-angular-package/zone-js/testing.mocha.ts b/nativescript-angular-package/zone-js/testing.mocha.ts
new file mode 100644
index 000000000..ce59f0e7b
--- /dev/null
+++ b/nativescript-angular-package/zone-js/testing.mocha.ts
@@ -0,0 +1,3 @@
+import "@nativescript/angular/platform";
+// import "@nativescript/angular/zone-js/dist/zone-nativescript.mocha.js";
+import '@nativescript/zone-js';
diff --git a/nativescript-angular/.npmignore b/nativescript-angular/.npmignore
new file mode 100644
index 000000000..6f87c2653
--- /dev/null
+++ b/nativescript-angular/.npmignore
@@ -0,0 +1,11 @@
+*.tgz
+dist
+
+*.ts
+!*.d.ts
+
+tsconfig.json
+global.d.ts
+.npmignore
+gulpfile.js
+tslint.json
diff --git a/nativescript-angular/.prettierignore b/nativescript-angular/.prettierignore
new file mode 100644
index 000000000..3cd5b3e47
--- /dev/null
+++ b/nativescript-angular/.prettierignore
@@ -0,0 +1,14 @@
+.github
+*.yml
+.vscode
+build
+dist
+doc
+e2e
+tests
+coverage
+platforms
+temp
+*.md
+*.json
+*.js
\ No newline at end of file
diff --git a/nativescript-angular/.prettierrc.json b/nativescript-angular/.prettierrc.json
new file mode 100644
index 000000000..098f6bab3
--- /dev/null
+++ b/nativescript-angular/.prettierrc.json
@@ -0,0 +1,6 @@
+{
+ "useTabs": true,
+ "printWidth": 600,
+ "tabWidth": 2,
+ "singleQuote": true
+}
\ No newline at end of file
diff --git a/nativescript-angular/LICENSE b/nativescript-angular/LICENSE
new file mode 100755
index 000000000..ced13b45c
--- /dev/null
+++ b/nativescript-angular/LICENSE
@@ -0,0 +1,201 @@
+Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright (c) 2015-2018 Telerik AD
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/nativescript-angular/README.md b/nativescript-angular/README.md
new file mode 100644
index 000000000..b6d86f1a1
--- /dev/null
+++ b/nativescript-angular/README.md
@@ -0,0 +1,45 @@
+# NativeScript Angular
+[](https://travis-ci.org/NativeScript/nativescript-angular)
+
+This repository contains the code for integration of NativeScript with Angular.
+
+[NativeScript](https://www.nativescript.org/) is a framework which enables developers to write truly native mobile applications for Android and iOS using JavaScript and CSS. [Angular](https://angular.io/) is one of the most popular open source JavaScript frameworks for application development. We [worked closely with developers at Google](http://angularjs.blogspot.bg/2015/12/building-mobile-apps-with-angular-2-and.html) to make Angular in NativeScript a reality. The result is a software architecture that allows you to build mobile apps using the same framework—and in some cases the same code—that you use to build Angular web apps, with the performance you’d expect from native code. [Read more about building truly native mobile apps with NativeScript and Angular](https://docs.nativescript.org/tutorial/ng-chapter-0).
+
+
+
+
+- [NativeScript Angular](#nativescript-angular)
+ - [Watch the video explaining Angular and NativeScript](#watch-the-video-explaining-angular-and-nativescript)
+ - [Explore the examples](#explore-the-examples)
+ - [Contribute](#contribute)
+ - [Known issues](#known-issues)
+ - [Get Help](#get-help)
+
+
+
+
+## Watch the video explaining Angular and NativeScript
+[NativeScript session on AngularConnect conference](https://www.youtube.com/watch?v=4SbiiyRSIwo)
+
+## Explore the examples
+
+The `e2e` apps are meant for testing stuff. You can take a look at these additional sample apps that use the published builds from npm:
+
+* [Hello world starter](https://github.com/NativeScript/nativescript-app-templates/tree/master/packages/template-hello-world-ng)
+* [Master-detail template](https://github.com/NativeScript/nativescript-app-templates/tree/master/packages/template-master-detail-ng)
+* [Drawer navigation template](https://github.com/NativeScript/nativescript-app-templates/tree/master/packages/template-drawer-navigation-ng)
+* [TabView navigation template](https://github.com/NativeScript/nativescript-app-templates/tree/master/packages/template-tab-navigation-ng)
+* [NativeScript Angular SDK examples](https://github.com/NativeScript/nativescript-sdk-examples-ng)
+
+## Contribute
+We love PRs! Check out the [contributing guidelines](CONTRIBUTING.md) and [development workflow for local setup](DevelopmentWorkflow.md). If you want to contribute, but you are not sure where to start - look for [issues labeled `help wanted`](https://github.com/NativeScript/nativescript-angular/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22).
+
+## Known issues
+
+1. There are certain issues with the Parse5DomAdapter and we'll likely need to provide our own later on:
+ * Self-closing elements (``) get parsed wrong (in this case Button gets parsed as a Label child.
+
+## Get Help
+Please, use [github issues](https://github.com/NativeScript/nativescript-angular/issues) strictly for [reporting bugs](CONTRIBUTING.md#reporting-bugs) or [requesting features](CONTRIBUTING.md#requesting-new-features). For general questions and support, check out [Stack Overflow](https://stackoverflow.com/questions/tagged/nativescript) or ask our experts in [NativeScript community Slack channel](http://developer.telerik.com/wp-login.php?action=slack-invitation).
+
+
diff --git a/nativescript-angular/animations/animation-driver.ts b/nativescript-angular/animations/animation-driver.ts
new file mode 100644
index 000000000..b534ba950
--- /dev/null
+++ b/nativescript-angular/animations/animation-driver.ts
@@ -0,0 +1,159 @@
+import { AnimationPlayer } from '@angular/animations';
+import { AnimationDriver } from '@angular/animations/browser';
+import { ProxyViewContainer, eachDescendant, CssAnimationProperty, CSSHelper } from '@nativescript/core';
+
+import { NativeScriptAnimationPlayer } from './animation-player';
+import { Keyframe, dashCaseToCamelCase } from './utils';
+import { NgView, InvisibleNode } from '../element-registry';
+import { NativeScriptDebug } from '../trace';
+
+interface ViewMatchResult {
+ found: boolean;
+}
+
+interface ViewMatchParams {
+ originalView: NgView;
+}
+
+interface QueryParams {
+ selector: Selector;
+ multi: boolean;
+}
+
+interface QueryResult {
+ matches: NgView[];
+}
+
+class Selector {
+ private nsSelectors: Array;
+ private classSelectors: string[];
+
+ constructor(rawSelector: string) {
+ this.parse(rawSelector);
+ }
+
+ match(element: NgView): boolean {
+ return this.nsSelectorMatch(element) || this.classSelectorsMatch(element);
+ }
+
+ private parse(rawSelector: string) {
+ const selectors = rawSelector.split(',').map((s) => s.trim());
+
+ this.nsSelectors = selectors.map(CSSHelper.createSelector);
+ this.classSelectors = selectors.filter((s) => s.startsWith('.')).map((s) => s.substring(1));
+ }
+
+ private nsSelectorMatch(element: NgView) {
+ return this.nsSelectors.some((s) => s.match(element));
+ }
+
+ private classSelectorsMatch(element: NgView) {
+ return this.classSelectors.some((s) => this.hasClass(element, s));
+ }
+
+ // we're using that instead of match for classes
+ // that are dynamically added by the animation engine
+ // such as .ng-trigger, that's added for every :enter view
+ private hasClass(element: NgView, cls: string) {
+ return element && element['$$classes'] && element['$$classes'][cls];
+ }
+}
+
+export class NativeScriptAnimationDriver implements AnimationDriver {
+ private static validProperties = [...CssAnimationProperty._getPropertyNames(), 'transform'];
+
+ validateStyleProperty(property: string): boolean {
+ NativeScriptDebug.animationsLog(`CssAnimationProperty.validateStyleProperty: ${property}`);
+ return NativeScriptAnimationDriver.validProperties.indexOf(property) !== -1;
+ }
+
+ matchesElement(element: NgView, rawSelector: string): boolean {
+ NativeScriptDebug.animationsLog(`NativeScriptAnimationDriver.matchesElement ` + `element: ${element}, selector: ${rawSelector}`);
+
+ const selector = this.makeSelector(rawSelector);
+ return selector.match(element);
+ }
+
+ containsElement(elm1: NgView, elm2: NgView): boolean {
+ NativeScriptDebug.animationsLog(`NativeScriptAnimationDriver.containsElement ` + `element1: ${elm1}, element2: ${elm2}`);
+
+ // Checking if the parent is our fake body object
+ if (elm1['isOverride']) {
+ return true;
+ }
+
+ const params: ViewMatchParams = { originalView: elm2 };
+ const result: ViewMatchResult = this.visitDescendants(elm1, viewMatches, params);
+
+ return result.found;
+ }
+
+ query(element: NgView, rawSelector: string, multi: boolean): NgView[] {
+ NativeScriptDebug.animationsLog(`NativeScriptAnimationDriver.query ` + `element: ${element}, selector: ${rawSelector} ` + `multi: ${multi}`);
+
+ const selector = this.makeSelector(rawSelector);
+ const params: QueryParams = { selector, multi };
+ const result: QueryResult = this.visitDescendants(element, queryDescendants, params);
+
+ return result.matches || [];
+ }
+
+ computeStyle(element: NgView, prop: string): string {
+ NativeScriptDebug.animationsLog(`NativeScriptAnimationDriver.computeStyle ` + `element: ${element}, prop: ${prop}`);
+
+ const camelCaseProp = dashCaseToCamelCase(prop);
+ return element.style[camelCaseProp];
+ }
+
+ animate(element: NgView, keyframes: Keyframe[], duration: number, delay: number, easing: string): AnimationPlayer {
+ NativeScriptDebug.animationsLog(`NativeScriptAnimationDriver.animate ` + `element: ${element}, keyframes: ${keyframes} ` + `duration: ${duration}, delay: ${delay} ` + `easing: ${easing}`);
+
+ return new NativeScriptAnimationPlayer(element, keyframes, duration, delay, easing);
+ }
+
+ private makeSelector(rawSelector: string): Selector {
+ return new Selector(rawSelector);
+ }
+
+ private visitDescendants(element: NgView, cb: (child: NgView, result: any, params: any) => boolean, cbParams: any): any {
+ const result = {};
+ // fill the result obj with the result from the callback function
+ eachDescendant(element, (child: NgView) => cb(child, result, cbParams));
+
+ return result;
+ }
+}
+
+function viewMatches(element: NgView, result: ViewMatchResult, params: ViewMatchParams): boolean {
+ if (element === params.originalView) {
+ result.found = true;
+ }
+
+ return !result.found;
+}
+
+function queryDescendants(element: NgView, result: QueryResult, params: QueryParams): boolean {
+ if (!result.matches) {
+ result.matches = [];
+ }
+
+ const { selector, multi } = params;
+
+ // skip comment and text nodes
+ // because they are not actual Views
+ // and cannot be animated
+ if (element instanceof InvisibleNode || !selector.match(element)) {
+ return true;
+ }
+
+ if (element instanceof ProxyViewContainer) {
+ element.eachChild((child: NgView) => {
+ result.matches.push(child);
+ return true;
+ });
+ } else {
+ result.matches.push(element);
+ }
+
+ return multi;
+}
diff --git a/nativescript-angular/animations/animation-player.ts b/nativescript-angular/animations/animation-player.ts
new file mode 100644
index 000000000..d8fe447b7
--- /dev/null
+++ b/nativescript-angular/animations/animation-player.ts
@@ -0,0 +1,139 @@
+import { AnimationPlayer } from '@angular/animations';
+import { View, EventData, KeyframeAnimation } from '@nativescript/core';
+
+import { Keyframe, createKeyframeAnimation } from './utils';
+import { NgView } from '../element-registry';
+import { NativeScriptDebug } from '../trace';
+
+export class NativeScriptAnimationPlayer implements AnimationPlayer {
+ public parentPlayer: AnimationPlayer = null;
+
+ private _startSubscriptions: Function[] = [];
+ private _doneSubscriptions: Function[] = [];
+ private _finished = false;
+ private _started = false;
+ private animation: KeyframeAnimation;
+
+ constructor(private target: NgView, keyframes: Keyframe[], private duration: number, private delay: number, easing: string) {
+ this.initKeyframeAnimation(keyframes, duration, delay, easing);
+ }
+
+ get totalTime(): number {
+ return this.delay + this.duration;
+ }
+
+ init(): void {}
+
+ hasStarted(): boolean {
+ return this._started;
+ }
+
+ onStart(fn: Function): void {
+ this._startSubscriptions.push(fn);
+ }
+ onDone(fn: Function): void {
+ this._doneSubscriptions.push(fn);
+ }
+ onDestroy(fn: Function): void {
+ this._doneSubscriptions.push(fn);
+ }
+
+ play(): void {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.animationsLog(`NativeScriptAnimationPlayer.play`);
+ }
+
+ if (!this.animation) {
+ return;
+ }
+
+ if (!this._started) {
+ this._started = true;
+ this._startSubscriptions.forEach((fn) => fn());
+ this._startSubscriptions = [];
+ }
+
+ // When this issue https://github.com/NativeScript/NativeScript/issues/7984 is fixes in @nativescript/core
+ // we can change this fix and apply the one that is recommended in that issue.
+ if (this.target.isLoaded) {
+ this.playAnimation();
+ } else {
+ this.target.on(View.loadedEvent, this.onTargetLoaded.bind(this));
+ }
+ }
+
+ private onTargetLoaded(args: EventData) {
+ this.target.off(View.loadedEvent, this.onTargetLoaded);
+ this.playAnimation();
+ }
+
+ private playAnimation() {
+ this.animation
+ .play(this.target)
+ .then(() => this.onFinish())
+ .catch((_e) => {});
+ }
+
+ pause(): void {}
+
+ finish(): void {
+ this.onFinish();
+ }
+
+ reset(): void {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.animationsLog(`NativeScriptAnimationPlayer.reset`);
+ }
+
+ if (this.animation && this.animation.isPlaying) {
+ this.animation.cancel();
+ }
+ }
+
+ restart(): void {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.animationsLog(`NativeScriptAnimationPlayer.restart`);
+ }
+
+ this.reset();
+ this.play();
+ }
+
+ destroy(): void {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.animationsLog(`NativeScriptAnimationPlayer.destroy`);
+ }
+ this.onFinish();
+ }
+
+ setPosition(_p: any): void {
+ throw new Error('AnimationPlayer.setPosition method is not supported!');
+ }
+
+ getPosition(): number {
+ return 0;
+ }
+
+ private initKeyframeAnimation(keyframes: Keyframe[], duration: number, delay: number, easing: string) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.animationsLog(`NativeScriptAnimationPlayer.initKeyframeAnimation`);
+ }
+
+ this.animation = createKeyframeAnimation(keyframes, duration, delay, easing);
+ }
+
+ private onFinish() {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.animationsLog(`NativeScriptAnimationPlayer.onFinish`);
+ }
+
+ if (this._finished) {
+ return;
+ }
+
+ this._finished = true;
+ this._started = false;
+ this._doneSubscriptions.forEach((fn) => fn());
+ this._doneSubscriptions = [];
+ }
+}
diff --git a/nativescript-angular/animations/animations.module.ts b/nativescript-angular/animations/animations.module.ts
new file mode 100644
index 000000000..517773300
--- /dev/null
+++ b/nativescript-angular/animations/animations.module.ts
@@ -0,0 +1,59 @@
+import { NgModule, Injectable, Inject, NgZone, RendererFactory2, Optional, SkipSelf } from '@angular/core';
+import { DOCUMENT } from '@angular/common';
+import { AnimationBuilder } from '@angular/animations';
+
+import { AnimationDriver, ɵAnimationStyleNormalizer as AnimationStyleNormalizer, ɵWebAnimationsStyleNormalizer as WebAnimationsStyleNormalizer, ɵAnimationEngine as AnimationEngine } from '@angular/animations/browser';
+
+import { ɵAnimationRendererFactory as AnimationRendererFactory, ɵBrowserAnimationBuilder as BrowserAnimationBuilder } from '@angular/platform-browser/animations';
+
+// import { NativeScriptModule } from "../nativescript.module";
+import { NativeScriptRendererFactory } from '../renderer-factory';
+import { NativeScriptAnimationDriver } from './animation-driver';
+import { throwIfAlreadyLoaded } from '../common/utils';
+import { NativeScriptCommonModule } from '../common';
+
+@Injectable()
+export class InjectableAnimationEngine extends AnimationEngine {
+ constructor(@Inject(DOCUMENT) doc: any, driver: AnimationDriver, normalizer: AnimationStyleNormalizer) {
+ super(doc.body, driver, normalizer);
+ }
+}
+
+export function instantiateSupportedAnimationDriver() {
+ return new NativeScriptAnimationDriver();
+}
+
+export function instantiateRendererFactory(renderer: NativeScriptRendererFactory, engine: AnimationEngine, zone: NgZone) {
+ return new AnimationRendererFactory(renderer, engine, zone);
+}
+
+export function instantiateDefaultStyleNormalizer() {
+ return new WebAnimationsStyleNormalizer();
+}
+
+@NgModule({
+ imports: [NativeScriptCommonModule],
+ providers: [
+ {
+ provide: AnimationDriver,
+ useFactory: instantiateSupportedAnimationDriver,
+ },
+ { provide: AnimationBuilder, useClass: BrowserAnimationBuilder },
+ {
+ provide: AnimationStyleNormalizer,
+ useFactory: instantiateDefaultStyleNormalizer,
+ },
+ { provide: AnimationEngine, useClass: InjectableAnimationEngine },
+ {
+ provide: RendererFactory2,
+ useFactory: instantiateRendererFactory,
+ deps: [NativeScriptRendererFactory, AnimationEngine, NgZone],
+ },
+ ],
+})
+export class NativeScriptAnimationsModule {
+ constructor(@Optional() @SkipSelf() parentModule: NativeScriptAnimationsModule) {
+ // Prevents NativeScriptAnimationsModule from getting imported multiple times
+ throwIfAlreadyLoaded(parentModule, 'NativeScriptAnimationsModule');
+ }
+}
diff --git a/nativescript-angular/animations/index.ts b/nativescript-angular/animations/index.ts
new file mode 100644
index 000000000..04e987533
--- /dev/null
+++ b/nativescript-angular/animations/index.ts
@@ -0,0 +1,4 @@
+export * from './animations.module';
+export * from './animation-player';
+export * from './animation-driver';
+export * from './utils';
diff --git a/nativescript-angular/animations/utils.ts b/nativescript-angular/animations/utils.ts
new file mode 100644
index 000000000..4eecb6fe2
--- /dev/null
+++ b/nativescript-angular/animations/utils.ts
@@ -0,0 +1,39 @@
+import { KeyframeAnimation, KeyframeAnimationInfo, KeyframeDeclaration, KeyframeInfo, parseKeyframeDeclarations, animationTimingFunctionConverter } from '@nativescript/core';
+
+export interface Keyframe {
+ [key: string]: string | number;
+ offset: number;
+}
+
+const DASH_CASE_REGEXP = /-+([a-z0-9])/g;
+export function dashCaseToCamelCase(input: string): string {
+ return input.replace(DASH_CASE_REGEXP, (...m: any[]) => m[1].toUpperCase());
+}
+
+export function createKeyframeAnimation(styles: Keyframe[], duration: number, delay: number, easing: string): KeyframeAnimation {
+ const info = createKeyframeAnimationInfo(styles, duration, delay, easing);
+ return KeyframeAnimation.keyframeAnimationFromInfo(info);
+}
+
+const createKeyframeAnimationInfo = (styles: Keyframe[], duration: number, delay: number, easing: string): KeyframeAnimationInfo => ({
+ isForwards: true,
+ duration: duration || 0.01,
+ delay,
+ curve: getCurve(easing),
+ keyframes: styles.map(parseAnimationKeyframe),
+});
+
+const getCurve = (value: string) => animationTimingFunctionConverter(value);
+
+const parseAnimationKeyframe = (styles: Keyframe): KeyframeInfo => ({
+ duration: getKeyframeDuration(styles),
+ declarations: getDeclarations(styles),
+});
+
+const getKeyframeDuration = (styles: Keyframe): number => styles.offset;
+
+function getDeclarations(styles: Keyframe): KeyframeDeclaration[] {
+ const unparsedDeclarations: KeyframeDeclaration[] = Object.keys(styles).map((property) => ({ property, value: styles[property] }));
+
+ return parseKeyframeDeclarations(unparsedDeclarations);
+}
diff --git a/nativescript-angular/app-host-view.ts b/nativescript-angular/app-host-view.ts
new file mode 100644
index 000000000..3f14d684d
--- /dev/null
+++ b/nativescript-angular/app-host-view.ts
@@ -0,0 +1,60 @@
+import { ContentView, View, ProxyViewContainer, GridLayout, Color } from '@nativescript/core';
+
+export class AppHostView extends ContentView {
+ private _ngAppRoot: View;
+ private _content: View;
+
+ constructor(backgroundColor: Color) {
+ super();
+ this.backgroundColor = backgroundColor;
+ }
+
+ get ngAppRoot(): View {
+ return this._ngAppRoot;
+ }
+
+ set ngAppRoot(value: View) {
+ this._ngAppRoot = value;
+ }
+
+ // @ts-ignore
+ get content(): View {
+ return this._content;
+ }
+
+ set content(value: View) {
+ if (this._content) {
+ this._content.parentNode = undefined;
+ }
+
+ this._content = value;
+
+ if (value) {
+ this._content.parentNode = this;
+ }
+
+ this.ngAppRoot = value;
+
+ if (this._content instanceof ProxyViewContainer) {
+ const grid = new GridLayout();
+ grid.backgroundColor = this.backgroundColor;
+ grid.addChild(this._content);
+ this.ngAppRoot = grid;
+ }
+ }
+}
+
+export class AppHostAsyncView extends GridLayout {
+ constructor(backgroundColor: Color) {
+ super();
+ this.backgroundColor = backgroundColor;
+ }
+
+ get ngAppRoot(): View {
+ return this;
+ }
+
+ set ngAppRoot(value: View) {
+ // ignored
+ }
+}
diff --git a/nativescript-angular/common.ts b/nativescript-angular/common.ts
new file mode 100644
index 000000000..81f6dd6e2
--- /dev/null
+++ b/nativescript-angular/common.ts
@@ -0,0 +1,21 @@
+import { CommonModule } from '@angular/common';
+import { NO_ERRORS_SCHEMA, NgModule } from '@angular/core';
+
+import { ModalDialogService } from './directives/dialogs';
+import { defaultDeviceProvider, defaultFrameProvider, defaultPageProvider } from './platform-providers';
+import { ListViewComponent } from './directives/list-view-comp';
+import { TemplateKeyDirective } from './directives/templated-items-comp';
+import { TabViewDirective, TabViewItemDirective } from './directives/tab-view';
+import { ActionBarComponent, ActionBarScope, ActionItemDirective, NavigationButtonDirective } from './directives/action-bar';
+import { AndroidFilterComponent, IosFilterComponent } from './directives/platform-filters';
+
+@NgModule({
+ declarations: [ListViewComponent, TemplateKeyDirective, TabViewDirective, TabViewItemDirective, ActionBarComponent, ActionBarScope, ActionItemDirective, NavigationButtonDirective, AndroidFilterComponent, IosFilterComponent],
+ providers: [ModalDialogService, defaultDeviceProvider, defaultFrameProvider, defaultPageProvider],
+ imports: [CommonModule],
+ exports: [CommonModule, ListViewComponent, TemplateKeyDirective, TabViewDirective, TabViewItemDirective, ActionBarComponent, ActionBarScope, ActionItemDirective, NavigationButtonDirective, AndroidFilterComponent, IosFilterComponent],
+ schemas: [NO_ERRORS_SCHEMA],
+})
+export class NativeScriptCommonModule {}
+
+export * from './directives';
diff --git a/nativescript-angular/common/detached-loader.ts b/nativescript-angular/common/detached-loader.ts
new file mode 100644
index 000000000..2a433f7d3
--- /dev/null
+++ b/nativescript-angular/common/detached-loader.ts
@@ -0,0 +1,61 @@
+import { ComponentRef, ComponentFactory, ViewContainerRef, Component, Type, ComponentFactoryResolver, ChangeDetectorRef, ApplicationRef, OnDestroy } from '@angular/core';
+import { Trace } from '@nativescript/core';
+
+/**
+ * Wrapper component used for loading components when navigating
+ * It uses DetachedContainer as selector so that it is containerRef is not attached to
+ * the visual tree.
+ */
+@Component({
+ selector: 'DetachedContainer',
+ template: ``,
+})
+export class DetachedLoader implements OnDestroy {
+ private disposeFunctions: Array<() => void> = [];
+ // tslint:disable-line:component-class-suffix
+ constructor(private resolver: ComponentFactoryResolver, private changeDetector: ChangeDetectorRef, private containerRef: ViewContainerRef, private appRef: ApplicationRef) {}
+
+ private loadInLocation(componentType: Type): Promise> {
+ const factory = this.resolver.resolveComponentFactory(componentType);
+ const componentRef = factory.create(this.containerRef.injector);
+ this.appRef.attachView(componentRef.hostView);
+
+ this.disposeFunctions.push(() => {
+ this.appRef.detachView(componentRef.hostView);
+ componentRef.destroy();
+ });
+
+ // Component is created, built may not be checked if we are loading
+ // inside component with OnPush CD strategy. Mark us for check to be sure CD will reach us.
+ // We are inside a promise here so no need for setTimeout - CD should trigger
+ // after the promise.
+ Trace.write('DetachedLoader.loadInLocation component loaded -> markForCheck', 'detached-loader');
+
+ return Promise.resolve(componentRef);
+ }
+
+ public ngOnDestroy() {
+ this.disposeFunctions.forEach((fn) => fn());
+ }
+
+ public detectChanges() {
+ this.changeDetector.markForCheck();
+ }
+
+ // TODO: change this API -- async promises not needed here anymore.
+ public loadComponent(componentType: Type): Promise> {
+ Trace.write('DetachedLoader.loadComponent', 'detached-loader');
+ return this.loadInLocation(componentType);
+ }
+
+ public loadWithFactory(factory: ComponentFactory): ComponentRef {
+ const componentRef = factory.create(this.containerRef.injector);
+ this.appRef.attachView(componentRef.hostView);
+
+ this.disposeFunctions.push(() => {
+ this.appRef.detachView(componentRef.hostView);
+ componentRef.destroy();
+ });
+ return componentRef;
+ }
+}
diff --git a/nativescript-angular/common/utils.ts b/nativescript-angular/common/utils.ts
new file mode 100644
index 000000000..5525ca7da
--- /dev/null
+++ b/nativescript-angular/common/utils.ts
@@ -0,0 +1,27 @@
+/**
+ * Utility method to ensure a NgModule is only imported once in a codebase, otherwise will throw to help prevent accidental double importing
+ * @param parentModule Parent module name
+ * @param moduleName The module name
+ */
+export function throwIfAlreadyLoaded(parentModule: any, moduleName: string) {
+ if (parentModule) {
+ throw new Error(`${moduleName} has already been loaded. Import ${moduleName} in the AppModule only.`);
+ }
+}
+
+/**
+ * Utility method which will only fire the callback once ever
+ * @param fn callback to call only once
+ */
+export function once(fn: Function) {
+ let wasCalled = false;
+
+ return function wrapper() {
+ if (wasCalled) {
+ return;
+ }
+
+ wasCalled = true;
+ fn.apply(null, arguments);
+ };
+}
diff --git a/nativescript-angular/directives/action-bar.ts b/nativescript-angular/directives/action-bar.ts
new file mode 100644
index 000000000..a6e20e937
--- /dev/null
+++ b/nativescript-angular/directives/action-bar.ts
@@ -0,0 +1,160 @@
+import { Directive, Component, ElementRef, Optional, OnDestroy } from '@angular/core';
+import { ActionBar, ActionItem, ActionItems, NavigationButton, Page } from '@nativescript/core';
+
+import { isBlank } from '../lang-facade';
+import { NgView, ViewClassMeta, ViewExtensions, isInvisibleNode, isView, registerElement } from '../element-registry';
+
+export function isActionItem(view: any): view is ActionItem {
+ return view instanceof ActionItem;
+}
+
+export function isNavigationButton(view: any): view is NavigationButton {
+ return view instanceof NavigationButton;
+}
+
+type NgActionBar = ActionBar & ViewExtensions;
+
+const actionBarMeta: ViewClassMeta = {
+ skipAddToDom: true,
+ insertChild: (parent: NgActionBar, child: NgView, next: any) => {
+ if (isInvisibleNode(child)) {
+ return;
+ } else if (isNavigationButton(child)) {
+ parent.navigationButton = child;
+ child.parentNode = parent;
+ } else if (isActionItem(child)) {
+ addActionItem(parent, child, next);
+ child.parentNode = parent;
+ } else if (isView(child)) {
+ parent.titleView = child;
+ }
+ },
+ removeChild: (parent: NgActionBar, child: NgView) => {
+ if (isInvisibleNode(child)) {
+ return;
+ } else if (isNavigationButton(child)) {
+ if (parent.navigationButton === child) {
+ parent.navigationButton = null;
+ }
+
+ child.parentNode = null;
+ } else if (isActionItem(child)) {
+ parent.actionItems.removeItem(child);
+ child.parentNode = null;
+ } else if (isView(child) && parent.titleView && parent.titleView === child) {
+ parent.titleView = null;
+ }
+ },
+};
+
+const addActionItem = (bar: NgActionBar, item: ActionItem, next: ActionItem) => {
+ if (next) {
+ insertActionItemBefore(bar, item, next);
+ } else {
+ appendActionItem(bar, item);
+ }
+};
+
+const insertActionItemBefore = (bar: NgActionBar, item: ActionItem, next: ActionItem) => {
+ const actionItems: ActionItems = bar.actionItems;
+ const actionItemsCollection: ActionItem[] = actionItems.getItems();
+
+ const indexToInsert = actionItemsCollection.indexOf(next);
+ actionItemsCollection.splice(indexToInsert, 0, item);
+
+ (actionItems).setItems(actionItemsCollection);
+};
+
+const appendActionItem = (bar: NgActionBar, item: ActionItem) => {
+ bar.actionItems.addItem(item);
+};
+
+registerElement('ActionBar', () => ActionBar, actionBarMeta);
+registerElement('ActionItem', () => ActionItem);
+registerElement('NavigationButton', () => NavigationButton);
+
+@Component({
+ selector: 'ActionBar',
+ template: '',
+})
+export class ActionBarComponent {
+ constructor(public element: ElementRef, private page: Page) {
+ if (!this.page) {
+ throw new Error('Inside ActionBarComponent but no Page found in DI.');
+ }
+
+ if (isBlank(this.page.actionBarHidden)) {
+ this.page.actionBarHidden = false;
+ }
+ this.page.actionBar = this.element.nativeElement;
+ this.page.actionBar.update();
+ }
+}
+
+@Component({
+ selector: 'ActionBarExtension',
+ template: '',
+})
+export class ActionBarScope {
+ // tslint:disable-line:component-class-suffix
+ constructor(private page: Page) {
+ if (!this.page) {
+ throw new Error('Inside ActionBarScope but no Page found in DI.');
+ }
+ }
+
+ public onNavButtonInit(navBtn: NavigationButtonDirective) {
+ this.page.actionBar.navigationButton = navBtn.element.nativeElement;
+ }
+
+ public onNavButtonDestroy(navBtn: NavigationButtonDirective) {
+ const nav = navBtn.element.nativeElement;
+ if (nav && this.page.actionBar.navigationButton === nav) {
+ this.page.actionBar.navigationButton = null;
+ }
+ }
+
+ public onActionInit(item: ActionItemDirective) {
+ this.page.actionBar.actionItems.addItem(item.element.nativeElement);
+ }
+
+ public onActionDestroy(item: ActionItemDirective) {
+ if (item.element.nativeElement.actionBar) {
+ this.page.actionBar.actionItems.removeItem(item.element.nativeElement);
+ }
+ }
+}
+
+@Directive({
+ selector: 'ActionItem', // tslint:disable-line:directive-selector
+})
+export class ActionItemDirective implements OnDestroy {
+ constructor(public element: ElementRef, @Optional() private ownerScope: ActionBarScope) {
+ if (this.ownerScope) {
+ this.ownerScope.onActionInit(this);
+ }
+ }
+
+ ngOnDestroy() {
+ if (this.ownerScope) {
+ this.ownerScope.onActionDestroy(this);
+ }
+ }
+}
+
+@Directive({
+ selector: 'NavigationButton', // tslint:disable-line:directive-selector
+})
+export class NavigationButtonDirective implements OnDestroy {
+ constructor(public element: ElementRef, @Optional() private ownerScope: ActionBarScope) {
+ if (this.ownerScope) {
+ this.ownerScope.onNavButtonInit(this);
+ }
+ }
+
+ ngOnDestroy() {
+ if (this.ownerScope) {
+ this.ownerScope.onNavButtonDestroy(this);
+ }
+ }
+}
diff --git a/nativescript-angular/directives/dialogs.ts b/nativescript-angular/directives/dialogs.ts
new file mode 100644
index 000000000..7e5aa78bf
--- /dev/null
+++ b/nativescript-angular/directives/dialogs.ts
@@ -0,0 +1,134 @@
+import { ComponentFactoryResolver, ComponentRef, Injectable, Injector, NgModuleRef, NgZone, Type, ViewContainerRef } from '@angular/core';
+import { Frame, View, ViewBase, ProxyViewContainer, ShowModalOptions } from '@nativescript/core';
+
+import { NSLocationStrategy } from '../router/ns-location-strategy';
+import { AppHostView, AppHostAsyncView } from '../app-host-view';
+import { DetachedLoader } from '../common/detached-loader';
+import { PageFactory, PAGE_FACTORY } from '../platform-providers';
+import { once } from '../common/utils';
+
+export type BaseShowModalOptions = Pick>;
+
+export interface ModalDialogOptions extends BaseShowModalOptions {
+ context?: any;
+ viewContainerRef?: ViewContainerRef;
+ moduleRef?: NgModuleRef;
+ target?: View;
+}
+
+export interface ShowDialogOptions extends BaseShowModalOptions {
+ containerRef: ViewContainerRef;
+ context: any;
+ doneCallback;
+ pageFactory: PageFactory;
+ parentView: ViewBase;
+ resolver: ComponentFactoryResolver;
+ type: Type;
+}
+
+export class ModalDialogParams {
+ constructor(public context: any = {}, public closeCallback: (...args) => any) {}
+}
+
+@Injectable()
+export class ModalDialogService {
+ constructor(private location: NSLocationStrategy, private zone: NgZone) {}
+
+ public showModal(type: Type, options: ModalDialogOptions): Promise {
+ if (!options.viewContainerRef) {
+ throw new Error('No viewContainerRef: ' + 'Make sure you pass viewContainerRef in ModalDialogOptions.');
+ }
+
+ let parentView = options.viewContainerRef.element.nativeElement;
+ if (options.target) {
+ parentView = options.target;
+ }
+
+ if ((parentView instanceof AppHostView || parentView instanceof AppHostAsyncView) && parentView.ngAppRoot) {
+ parentView = parentView.ngAppRoot;
+ }
+
+ // _ngDialogRoot is the first child of the previously detached proxy.
+ // It should have 'viewController' (iOS) or '_dialogFragment' (Android) available for
+ // presenting future modal views.
+ if (parentView._ngDialogRoot) {
+ parentView = parentView._ngDialogRoot;
+ }
+
+ const pageFactory: PageFactory = options.viewContainerRef.injector.get(PAGE_FACTORY);
+
+ // resolve from particular module (moduleRef)
+ // or from same module as parentView (viewContainerRef)
+ const componentContainer = options.moduleRef || options.viewContainerRef;
+ const resolver = componentContainer.injector.get(ComponentFactoryResolver);
+
+ let frame = parentView;
+ if (!(parentView instanceof Frame)) {
+ frame = (parentView.page && parentView.page.frame) || Frame.topmost();
+ }
+
+ this.location._beginModalNavigation(frame);
+
+ return new Promise((resolve, reject) => {
+ setTimeout(() => {
+ try {
+ this._showDialog({
+ ...options,
+ containerRef: options.viewContainerRef,
+ context: options.context,
+ doneCallback: resolve,
+ pageFactory,
+ parentView,
+ resolver,
+ type,
+ });
+ } catch (err) {
+ reject(err);
+ }
+ }, 10);
+ });
+ }
+
+ private _showDialog(options: ShowDialogOptions): void {
+ let componentView: View;
+ let detachedLoaderRef: ComponentRef;
+
+ const closeCallback = once((...args) => {
+ options.doneCallback.apply(undefined, args);
+ if (componentView) {
+ componentView.closeModal();
+ this.location._closeModalNavigation();
+ this.zone.run(() => {
+ detachedLoaderRef.instance.detectChanges();
+ detachedLoaderRef.destroy();
+ });
+ }
+ });
+
+ const modalParams = new ModalDialogParams(options.context, closeCallback);
+
+ const childInjector = Injector.create({
+ providers: [{ provide: ModalDialogParams, useValue: modalParams }],
+ parent: options.containerRef.injector,
+ });
+ const detachedFactory = options.resolver.resolveComponentFactory(DetachedLoader);
+ detachedLoaderRef = options.containerRef.createComponent(detachedFactory, 0, childInjector, null);
+ this.zone.run(() => {
+ detachedLoaderRef.instance.loadComponent(options.type).then((compRef) => {
+ const detachedProxy = compRef.location.nativeElement;
+
+ if (detachedProxy.getChildrenCount() > 1) {
+ throw new Error('Modal content has more than one root view.');
+ }
+ componentView = detachedProxy.getChildAt(0);
+
+ if (componentView.parent) {
+ (componentView.parent)._ngDialogRoot = componentView;
+ (componentView.parent).removeChild(componentView);
+ }
+
+ options.parentView.showModal(componentView, { ...options, closeCallback });
+ });
+ });
+ }
+}
diff --git a/nativescript-angular/directives/index.ts b/nativescript-angular/directives/index.ts
new file mode 100644
index 000000000..29bef98f9
--- /dev/null
+++ b/nativescript-angular/directives/index.ts
@@ -0,0 +1,10 @@
+import { ListViewComponent } from './list-view-comp';
+import { TemplateKeyDirective, TemplatedItemsComponent, TEMPLATED_ITEMS_COMPONENT } from './templated-items-comp';
+import { TabViewDirective, TabViewItemDirective } from './tab-view';
+import { ActionBarComponent, ActionBarScope, ActionItemDirective, NavigationButtonDirective } from './action-bar';
+import { AndroidFilterComponent, IosFilterComponent } from './platform-filters';
+export { ModalDialogOptions, ModalDialogParams, ModalDialogService } from './dialogs';
+
+export const NS_DIRECTIVES = [ListViewComponent, TemplateKeyDirective, TabViewDirective, TabViewItemDirective, ActionBarComponent, ActionBarScope, ActionItemDirective, NavigationButtonDirective, AndroidFilterComponent, IosFilterComponent];
+
+export { ListViewComponent, TemplateKeyDirective, TemplatedItemsComponent, TabViewDirective, TabViewItemDirective, ActionBarComponent, ActionBarScope, ActionItemDirective, NavigationButtonDirective, AndroidFilterComponent, IosFilterComponent, TEMPLATED_ITEMS_COMPONENT };
diff --git a/nativescript-angular/directives/list-view-comp.ts b/nativescript-angular/directives/list-view-comp.ts
new file mode 100644
index 000000000..62e1eadfe
--- /dev/null
+++ b/nativescript-angular/directives/list-view-comp.ts
@@ -0,0 +1,23 @@
+import { ChangeDetectionStrategy, Component, ElementRef, IterableDiffers, forwardRef, NgZone } from '@angular/core';
+import { ListView } from '@nativescript/core';
+import { TEMPLATED_ITEMS_COMPONENT, TemplatedItemsComponent } from './templated-items-comp';
+
+@Component({
+ selector: 'ListView',
+ template: `
+
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ providers: [{ provide: TEMPLATED_ITEMS_COMPONENT, useExisting: forwardRef(() => ListViewComponent) }],
+})
+export class ListViewComponent extends TemplatedItemsComponent {
+ public get nativeElement(): ListView {
+ return this.templatedItemsView;
+ }
+
+ protected templatedItemsView: ListView;
+
+ constructor(_elementRef: ElementRef, _iterableDiffers: IterableDiffers, zone: NgZone) {
+ super(_elementRef, _iterableDiffers, zone);
+ }
+}
diff --git a/nativescript-angular/directives/platform-filters.ts b/nativescript-angular/directives/platform-filters.ts
new file mode 100644
index 000000000..55f5aac5b
--- /dev/null
+++ b/nativescript-angular/directives/platform-filters.ts
@@ -0,0 +1,24 @@
+import { Component, Inject } from '@angular/core';
+import { isIOS, isAndroid } from '@nativescript/core';
+
+@Component({
+ selector: 'android',
+ template: ``,
+})
+export class AndroidFilterComponent {
+ public show: boolean;
+ constructor() {
+ this.show = isAndroid;
+ }
+}
+
+@Component({
+ selector: 'ios',
+ template: ``,
+})
+export class IosFilterComponent {
+ public show: boolean;
+ constructor() {
+ this.show = isIOS;
+ }
+}
diff --git a/nativescript-angular/directives/tab-view.ts b/nativescript-angular/directives/tab-view.ts
new file mode 100644
index 000000000..2fa2a272d
--- /dev/null
+++ b/nativescript-angular/directives/tab-view.ts
@@ -0,0 +1,137 @@
+import { AfterViewInit, Directive, ElementRef, Input, OnInit, TemplateRef, ViewContainerRef } from '@angular/core';
+import { TabView, TabViewItem } from '@nativescript/core';
+
+import { InvisibleNode } from '../element-registry';
+import { NativeScriptDebug } from '../trace';
+import { isBlank } from '../lang-facade';
+
+type TextTransform = 'initial' | 'none' | 'capitalize' | 'uppercase' | 'lowercase';
+export interface TabViewItemDef {
+ title?: string;
+ iconSource?: string;
+ textTransform?: TextTransform;
+}
+
+@Directive({
+ selector: 'TabView', // tslint:disable-line:directive-selector
+})
+export class TabViewDirective implements AfterViewInit {
+ public tabView: TabView;
+ private _selectedIndex: number;
+ private viewInitialized: boolean;
+
+ @Input()
+ get selectedIndex(): number {
+ return this._selectedIndex;
+ }
+
+ set selectedIndex(value) {
+ this._selectedIndex = value;
+ if (this.viewInitialized) {
+ this.tabView.selectedIndex = this._selectedIndex;
+ }
+ }
+
+ constructor(element: ElementRef) {
+ this.tabView = element.nativeElement;
+ }
+
+ ngAfterViewInit() {
+ this.viewInitialized = true;
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog('this._selectedIndex: ' + this._selectedIndex);
+ }
+ if (!isBlank(this._selectedIndex)) {
+ this.tabView.selectedIndex = this._selectedIndex;
+ }
+ }
+}
+
+@Directive({
+ selector: '[tabItem]', // tslint:disable-line:directive-selector
+})
+export class TabViewItemDirective implements OnInit {
+ private item: TabViewItem;
+ private _config: TabViewItemDef;
+
+ constructor(private owner: TabViewDirective, private templateRef: TemplateRef, private viewContainer: ViewContainerRef) {}
+
+ @Input('tabItem')
+ set config(config: TabViewItemDef) {
+ if (!this._config || this._config.iconSource !== config.iconSource || this._config.title !== config.title || this._config.textTransform !== config.textTransform) {
+ this._config = config;
+ this.applyConfig();
+ }
+ }
+
+ get config(): TabViewItemDef {
+ // tslint:disable-line:no-input-rename
+ return this._config || {};
+ }
+
+ @Input()
+ set title(title: string) {
+ this.config = Object.assign(this.config, { title });
+ }
+
+ get title() {
+ return this.config.title;
+ }
+
+ @Input()
+ set iconSource(iconSource: string) {
+ this.config = Object.assign(this.config, { iconSource });
+ }
+
+ get iconSource() {
+ return this.config.iconSource;
+ }
+
+ @Input()
+ set textTransform(textTransform: TextTransform) {
+ this.config = Object.assign(this.config, { textTransform });
+ }
+
+ get textTransform() {
+ return this.config.textTransform;
+ }
+
+ private ensureItem() {
+ if (!this.item) {
+ this.item = new TabViewItem();
+ }
+ }
+
+ private applyConfig() {
+ this.ensureItem();
+
+ if (this.config.title) {
+ this.item.title = this.config.title;
+ }
+
+ if (this.config.iconSource) {
+ this.item.iconSource = this.config.iconSource;
+ }
+
+ // TabViewItem textTransform has a default value for Android that kick in
+ // only if no value (even a null value) is set.
+ if (this.config.textTransform) {
+ this.item.textTransform = this.config.textTransform;
+ }
+ }
+
+ ngOnInit() {
+ this.applyConfig();
+
+ const viewRef = this.viewContainer.createEmbeddedView(this.templateRef);
+ // Filter out text nodes and comments
+ const realViews = viewRef.rootNodes.filter((node) => !(node instanceof InvisibleNode));
+
+ if (realViews.length > 0) {
+ this.item.view = realViews[0];
+
+ const newItems = (this.owner.tabView.items || []).concat([this.item]);
+ this.owner.tabView.items = newItems;
+ }
+ }
+}
diff --git a/nativescript-angular/directives/templated-items-comp.ts b/nativescript-angular/directives/templated-items-comp.ts
new file mode 100644
index 000000000..5514ffa7b
--- /dev/null
+++ b/nativescript-angular/directives/templated-items-comp.ts
@@ -0,0 +1,239 @@
+import { AfterContentInit, ContentChild, Directive, DoCheck, ElementRef, EmbeddedViewRef, EventEmitter, Host, Inject, InjectionToken, Input, IterableDiffer, IterableDiffers, OnDestroy, Output, TemplateRef, ViewChild, ViewContainerRef, ɵisListLikeIterable as isListLikeIterable, Injectable, NgZone } from '@angular/core';
+import { ObservableArray, View, KeyedTemplate, LayoutBase, ItemEventData, TemplatedItemsView, profile } from '@nativescript/core';
+
+import { getSingleViewRecursive } from '../element-registry';
+import { NativeScriptDebug } from '../trace';
+
+const NG_VIEW = '_ngViewRef';
+
+export class ItemContext {
+ constructor(public $implicit?: any, public item?: any, public index?: number, public even?: boolean, public odd?: boolean) {}
+}
+
+export interface SetupItemViewArgs {
+ view: EmbeddedViewRef;
+ data: any;
+ index: number;
+ context: ItemContext;
+}
+
+@Directive()
+export abstract class TemplatedItemsComponent implements DoCheck, OnDestroy, AfterContentInit {
+ public abstract get nativeElement(): TemplatedItemsView;
+
+ protected templatedItemsView: TemplatedItemsView;
+ protected _items: any;
+ protected _differ: IterableDiffer;
+ protected _templateMap: Map;
+
+ @ViewChild('loader', { read: ViewContainerRef, static: false }) loader: ViewContainerRef;
+
+ @Output() public setupItemView = new EventEmitter();
+
+ @ContentChild(TemplateRef, { read: TemplateRef, static: false }) itemTemplateQuery: TemplateRef;
+
+ itemTemplate: TemplateRef;
+
+ @Input()
+ get items() {
+ return this._items;
+ }
+
+ set items(value: any) {
+ this._items = value;
+ let needDiffer = true;
+ if (value instanceof ObservableArray) {
+ needDiffer = false;
+ }
+ if (needDiffer && !this._differ && isListLikeIterable(value)) {
+ this._differ = this._iterableDiffers.find(this._items).create((_index, item) => {
+ return item;
+ });
+ }
+
+ this.templatedItemsView.items = this._items;
+ }
+
+ constructor(_elementRef: ElementRef, private _iterableDiffers: IterableDiffers, private zone: NgZone) {
+ this.templatedItemsView = _elementRef.nativeElement;
+
+ this.templatedItemsView.on('itemLoading', this.onItemLoading, this);
+ }
+
+ ngAfterContentInit() {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.listViewLog('TemplatedItemsView.ngAfterContentInit()');
+ }
+
+ this.setItemTemplates();
+ }
+
+ ngOnDestroy() {
+ this.templatedItemsView.off('itemLoading', this.onItemLoading, this);
+ this.templatedItemsView = null;
+
+ if (this._templateMap) {
+ this._templateMap.clear();
+ }
+ }
+
+ private setItemTemplates() {
+ // The itemTemplateQuery may be changed after list items are added that contain inside,
+ // so cache and use only the original template to avoid errors.
+ this.itemTemplate = this.itemTemplateQuery;
+
+ if (this._templateMap) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.listViewLog('Setting templates');
+ }
+
+ const templates: KeyedTemplate[] = [];
+ this._templateMap.forEach((value) => {
+ templates.push(value);
+ });
+ this.templatedItemsView.itemTemplates = templates;
+ }
+ }
+
+ public registerTemplate(key: string, template: TemplateRef) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.listViewLog(`registerTemplate for key: ${key}`);
+ }
+
+ if (!this._templateMap) {
+ this._templateMap = new Map();
+ }
+
+ const keyedTemplate = {
+ key,
+ createView: this.getItemTemplateViewFactory(template),
+ };
+
+ this._templateMap.set(key, keyedTemplate);
+ }
+
+ @profile
+ public onItemLoading(args: ItemEventData) {
+ if (!args.view && !this.itemTemplate) {
+ return;
+ }
+
+ const index = args.index;
+ const items = (args.object).items;
+ const currentItem = typeof items.getItem === 'function' ? items.getItem(index) : items[index];
+ let viewRef: EmbeddedViewRef;
+
+ if (args.view) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.listViewLog(`onItemLoading: ${index} - Reusing existing view`);
+ }
+
+ viewRef = args.view[NG_VIEW];
+
+ // Getting angular view from original element (in cases when ProxyViewContainer
+ // is used NativeScript internally wraps it in a StackLayout)
+ if (!viewRef && args.view instanceof LayoutBase && args.view.getChildrenCount() > 0) {
+ viewRef = args.view.getChildAt(0)[NG_VIEW];
+ }
+
+ if (!viewRef && NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.listViewError(`ViewReference not found for item ${index}. View recycling is not working`);
+ }
+
+ // No ng-template is setup, continue with 'defaultTemplate'
+ if (!viewRef) {
+ return;
+ }
+ }
+
+ if (!viewRef) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.listViewLog(`onItemLoading: ${index} - Creating view from template`);
+ }
+
+ viewRef = this.loader.createEmbeddedView(this.itemTemplate, new ItemContext(), 0);
+ args.view = getItemViewRoot(viewRef);
+ args.view[NG_VIEW] = viewRef;
+ }
+
+ this.setupViewRef(viewRef, currentItem, index);
+
+ this.detectChangesOnChild(viewRef, index);
+ }
+
+ public setupViewRef(viewRef: EmbeddedViewRef, data: any, index: number): void {
+ const context = viewRef.context;
+ context.$implicit = data;
+ context.item = data;
+ context.index = index;
+ context.even = index % 2 === 0;
+ context.odd = !context.even;
+
+ this.setupItemView.next({ view: viewRef, data: data, index: index, context: context });
+ }
+
+ protected getItemTemplateViewFactory(template: TemplateRef): () => View {
+ return () => {
+ const viewRef = this.loader.createEmbeddedView(template, new ItemContext(), 0);
+ const resultView = getItemViewRoot(viewRef);
+ resultView[NG_VIEW] = viewRef;
+
+ return resultView;
+ };
+ }
+
+ @profile
+ private detectChangesOnChild(viewRef: EmbeddedViewRef, index: number) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.listViewLog(`Manually detect changes in child: ${index}`);
+ }
+
+ this.zone.run(() => {
+ viewRef.markForCheck();
+ viewRef.detectChanges();
+ });
+ }
+
+ ngDoCheck() {
+ if (this._differ) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.listViewLog('ngDoCheck() - execute differ');
+ }
+
+ const changes = this._differ.diff(this._items);
+ if (changes) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.listViewLog('ngDoCheck() - refresh');
+ }
+
+ this.templatedItemsView.refresh();
+ }
+ }
+ }
+}
+
+export interface ComponentView {
+ rootNodes: Array;
+ destroy(): void;
+}
+
+export type RootLocator = (nodes: Array, nestLevel: number) => View;
+
+export function getItemViewRoot(viewRef: ComponentView, rootLocator: RootLocator = getSingleViewRecursive): View {
+ const rootView = rootLocator(viewRef.rootNodes, 0);
+ return rootView;
+}
+
+export const TEMPLATED_ITEMS_COMPONENT = new InjectionToken('TemplatedItemsComponent');
+
+@Directive({ selector: '[nsTemplateKey]' })
+export class TemplateKeyDirective {
+ constructor(private templateRef: TemplateRef, @Inject(TEMPLATED_ITEMS_COMPONENT) @Host() private comp: TemplatedItemsComponent) {}
+
+ @Input()
+ set nsTemplateKey(value: any) {
+ if (this.comp && this.templateRef) {
+ this.comp.registerTemplate(value, this.templateRef);
+ }
+ }
+}
diff --git a/nativescript-angular/dom-adapter.ts b/nativescript-angular/dom-adapter.ts
new file mode 100644
index 000000000..09f4bfecd
--- /dev/null
+++ b/nativescript-angular/dom-adapter.ts
@@ -0,0 +1,386 @@
+/* tslint:disable */
+import { Type } from '@angular/core';
+import { ɵDomAdapter, ɵsetRootDomAdapter } from '@angular/common';
+import { NativeScriptDebug } from './trace';
+
+export class NativeScriptDomAdapter implements ɵDomAdapter {
+ static makeCurrent() {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog('Setting root DOM adapter...');
+ }
+
+ ɵsetRootDomAdapter(new NativeScriptDomAdapter());
+ }
+
+ hasProperty(_element: any, _name: string) {
+ // TODO: actually check if the property exists.
+ return true;
+ }
+
+ log(arg: any): void {
+ console.log(arg);
+ }
+
+ logError(arg: any): void {
+ console.log(arg);
+ }
+
+ logGroup(arg: any): void {
+ console.log(arg);
+ }
+
+ logGroupEnd(): void {}
+
+ get attrToPropMap(): { [key: string]: string } {
+ throw new Error('Not implemented!');
+ }
+ set attrToPropMap(_value: { [key: string]: string }) {
+ throw new Error('Not implemented!');
+ }
+
+ public resourceLoaderType: Type = null;
+ setProperty(_el: Element, _name: string, _value: any): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ getProperty(_el: Element, _name: string): any {
+ throw new Error('Not implemented!');
+ }
+ invoke(_el: Element, _methodName: string, _args: any[]): any {
+ throw new Error('Not implemented!');
+ }
+
+ contains(_nodeA: any, _nodeB: any): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ parse(_templateHtml: string): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ query(_selector: string): any {
+ throw new Error('Not implemented!');
+ }
+ querySelector(_el: any /** TODO #9100 */, _selector: string): HTMLElement {
+ throw new Error('Not implemented!');
+ }
+ querySelectorAll(_el: any /** TODO #9100 */, _selector: string): any[] {
+ throw new Error('Not implemented!');
+ }
+ on(_el: any /** TODO #9100 */, _evt: any /** TODO #9100 */, _listener: any /** TODO #9100 */): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ onAndCancel(_el: any /** TODO #9100 */, _evt: any /** TODO #9100 */, _listener: any /** TODO #9100 */): Function {
+ throw new Error('Not implemented!');
+ }
+ dispatchEvent(_el: any /** TODO #9100 */, _evt: any /** TODO #9100 */): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ createMouseEvent(_eventType: any /** TODO #9100 */): any {
+ throw new Error('Not implemented!');
+ }
+ createEvent(_eventType: string): any {
+ throw new Error('Not implemented!');
+ }
+ preventDefault(_evt: any /** TODO #9100 */): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ isPrevented(_evt: any /** TODO #9100 */): boolean {
+ throw new Error('Not implemented!');
+ }
+ getInnerHTML(_el: any /** TODO #9100 */): string {
+ throw new Error('Not implemented!');
+ }
+
+ getTemplateContent(_el: any /** TODO #9100 */): any {
+ throw new Error('Not implemented!');
+ }
+ getOuterHTML(_el: any /** TODO #9100 */): string {
+ throw new Error('Not implemented!');
+ }
+ nodeName(_node: any /** TODO #9100 */): string {
+ throw new Error('Not implemented!');
+ }
+ nodeValue(_node: any /** TODO #9100 */): string {
+ throw new Error('Not implemented!');
+ }
+ type(_node: any /** TODO #9100 */): string {
+ throw new Error('Not implemented!');
+ }
+ content(_node: any /** TODO #9100 */): any {
+ throw new Error('Not implemented!');
+ }
+ firstChild(_el: any /** TODO #9100 */): Node {
+ throw new Error('Not implemented!');
+ }
+ nextSibling(_el: any /** TODO #9100 */): Node {
+ throw new Error('Not implemented!');
+ }
+ parentElement(_el: any /** TODO #9100 */): Node {
+ throw new Error('Not implemented!');
+ }
+ childNodes(_el: any /** TODO #9100 */): Node[] {
+ throw new Error('Not implemented!');
+ }
+ childNodesAsList(_el: any /** TODO #9100 */): Node[] {
+ throw new Error('Not implemented!');
+ }
+ clearNodes(_el: any /** TODO #9100 */): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ appendChild(_el: any /** TODO #9100 */, _node: any /** TODO #9100 */): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ removeChild(_el: any /** TODO #9100 */, _node: any /** TODO #9100 */): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ replaceChild(_el: any /** TODO #9100 */, _newNode: any /** TODO #9100 */, _oldNode: any /** TODO #9100 */): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ remove(_el: any /** TODO #9100 */): Node {
+ throw new Error('Not implemented!');
+ }
+ insertBefore(_el: any /** TODO #9100 */, _node: any /** TODO #9100 */): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ insertAllBefore(_el: any /** TODO #9100 */, _nodes: any /** TODO #9100 */): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ insertAfter(_el: any /** TODO #9100 */, _node: any /** TODO #9100 */): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ setInnerHTML(_el: any /** TODO #9100 */, _value: any /** TODO #9100 */): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ getText(_el: any /** TODO #9100 */): string {
+ throw new Error('Not implemented!');
+ }
+ setText(_el: any /** TODO #9100 */, _value: string): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ getValue(_el: any /** TODO #9100 */): string {
+ throw new Error('Not implemented!');
+ }
+ setValue(_el: any /** TODO #9100 */, _value: string): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ getChecked(_el: any /** TODO #9100 */): boolean {
+ throw new Error('Not implemented!');
+ }
+ setChecked(_el: any /** TODO #9100 */, _value: boolean): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ createComment(_text: string): any {
+ throw new Error('Not implemented!');
+ }
+ createTemplate(_html: any /** TODO #9100 */): HTMLElement {
+ throw new Error('Not implemented!');
+ }
+ createElement(_tagName: any /** TODO #9100 */, _doc?: any /** TODO #9100 */): HTMLElement {
+ throw new Error('Not implemented!');
+ }
+ createElementNS(_ns: string, _tagName: string, _doc?: any /** TODO #9100 */): Element {
+ throw new Error('Not implemented!');
+ }
+ createTextNode(_text: string, _doc?: any /** TODO #9100 */): Text {
+ throw new Error('Not implemented!');
+ }
+ createScriptTag(_attrName: string, _attrValue: string, _doc?: any /** TODO #9100 */): HTMLElement {
+ throw new Error('Not implemented!');
+ }
+ createStyleElement(_css: string, _doc?: any /** TODO #9100 */): HTMLStyleElement {
+ throw new Error('Not implemented!');
+ }
+ createShadowRoot(_el: any /** TODO #9100 */): any {
+ throw new Error('Not implemented!');
+ }
+ getShadowRoot(_el: any /** TODO #9100 */): any {
+ throw new Error('Not implemented!');
+ }
+ getHost(_el: any /** TODO #9100 */): any {
+ throw new Error('Not implemented!');
+ }
+ getDistributedNodes(_el: any /** TODO #9100 */): Node[] {
+ throw new Error('Not implemented!');
+ }
+ clone /**/(_node: Node /*T*/): Node /*T*/ {
+ throw new Error('Not implemented!');
+ }
+ getElementsByClassName(_element: any /** TODO #9100 */, _name: string): HTMLElement[] {
+ throw new Error('Not implemented!');
+ }
+ getElementsByTagName(_element: any /** TODO #9100 */, _name: string): HTMLElement[] {
+ throw new Error('Not implemented!');
+ }
+ classList(_element: any /** TODO #9100 */): any[] {
+ throw new Error('Not implemented!');
+ }
+ addClass(_element: any /** TODO #9100 */, _className: string): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ removeClass(_element: any /** TODO #9100 */, _className: string): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ hasClass(_element: any /** TODO #9100 */, _className: string): boolean {
+ throw new Error('Not implemented!');
+ }
+ setStyle(_element: any /** TODO #9100 */, _styleName: string, _styleValue: string): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ removeStyle(_element: any /** TODO #9100 */, _styleName: string): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ getStyle(_element: any /** TODO #9100 */, _styleName: string): string {
+ throw new Error('Not implemented!');
+ }
+ hasStyle(_element: any /** TODO #9100 */, _styleName: string, _styleValue?: string): boolean {
+ throw new Error('Not implemented!');
+ }
+ tagName(_element: any /** TODO #9100 */): string {
+ throw new Error('Not implemented!');
+ }
+ attributeMap(_element: any /** TODO #9100 */): Map {
+ throw new Error('Not implemented!');
+ }
+ hasAttribute(_element: any /** TODO #9100 */, _attribute: string): boolean {
+ throw new Error('Not implemented!');
+ }
+ hasAttributeNS(_element: any /** TODO #9100 */, _ns: string, _attribute: string): boolean {
+ throw new Error('Not implemented!');
+ }
+ getAttribute(_element: any /** TODO #9100 */, _attribute: string): string {
+ throw new Error('Not implemented!');
+ }
+ getAttributeNS(_element: any /** TODO #9100 */, _ns: string, _attribute: string): string {
+ throw new Error('Not implemented!');
+ }
+ setAttribute(_element: any /** TODO #9100 */, _name: string, _value: string): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ setAttributeNS(_element: any /** TODO #9100 */, _ns: string, _name: string, _value: string): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ removeAttribute(_element: any /** TODO #9100 */, _attribute: string): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ removeAttributeNS(_element: any /** TODO #9100 */, _ns: string, _attribute: string): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ templateAwareRoot(_el: any /** TODO #9100 */): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ createHtmlDocument(): HTMLDocument {
+ throw new Error('Not implemented!');
+ }
+ defaultDoc(): HTMLDocument {
+ throw new Error('Not implemented!');
+ }
+ getDefaultDocument(): Document {
+ throw new Error('Not implemented!');
+ }
+ getBoundingClientRect(_el: any /** TODO #9100 */): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ getTitle(): string {
+ throw new Error('Not implemented!');
+ }
+ setTitle(_doc: Document, _newTitle: string): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ elementMatches(_n: any /** TODO #9100 */, _selector: string): boolean {
+ throw new Error('Not implemented!');
+ }
+ isTemplateElement(_el: any): boolean {
+ throw new Error('Not implemented!');
+ }
+ isTextNode(_node: any /** TODO #9100 */): boolean {
+ throw new Error('Not implemented!');
+ }
+ isCommentNode(_node: any /** TODO #9100 */): boolean {
+ throw new Error('Not implemented!');
+ }
+ isElementNode(_node: any /** TODO #9100 */): boolean {
+ throw new Error('Not implemented!');
+ }
+ hasShadowRoot(_node: any /** TODO #9100 */): boolean {
+ throw new Error('Not implemented!');
+ }
+ isShadowRoot(_node: any /** TODO #9100 */): boolean {
+ throw new Error('Not implemented!');
+ }
+ importIntoDoc /**/(_node: Node /*T*/): Node /*T*/ {
+ throw new Error('Not implemented!');
+ }
+ adoptNode /**/(_node: Node /*T*/): Node /*T*/ {
+ throw new Error('Not implemented!');
+ }
+ getHref(_element: any /** TODO #9100 */): string {
+ throw new Error('Not implemented!');
+ }
+ getEventKey(_event: any /** TODO #9100 */): string {
+ throw new Error('Not implemented!');
+ }
+ resolveAndSetHref(_element: any /** TODO #9100 */, _baseUrl: string, _href: string): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ supportsDOMEvents(): boolean {
+ throw new Error('Not implemented!');
+ }
+ supportsNativeShadowDOM(): boolean {
+ throw new Error('Not implemented!');
+ }
+ getGlobalEventTarget(_doc: Document, _target: string): any {
+ throw new Error('Not implemented!');
+ }
+ getHistory(): History {
+ throw new Error('Not implemented!');
+ }
+ getLocation(): Location {
+ throw new Error('Not implemented!');
+ }
+ getBaseHref(): string {
+ throw new Error('Not implemented!');
+ }
+ resetBaseElement(): void {
+ throw new Error('Not implemented!');
+ }
+ getUserAgent(): string {
+ return 'Fake user agent';
+ }
+ setData(_element: any /** TODO #9100 */, _name: string, _value: string): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ getComputedStyle(_element: any /** TODO #9100 */): any {
+ throw new Error('Not implemented!');
+ }
+ getData(_element: any /** TODO #9100 */, _name: string): string {
+ throw new Error('Not implemented!');
+ }
+ setGlobalVar(_name: string, _value: any): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+ supportsWebAnimation(): boolean {
+ throw new Error('Not implemented!');
+ }
+ performanceNow(): number {
+ throw new Error('Not implemented!');
+ }
+ getAnimationPrefix(): string {
+ throw new Error('Not implemented!');
+ }
+ getTransitionEnd(): string {
+ throw new Error('Not implemented!');
+ }
+ supportsAnimation(): boolean {
+ throw new Error('Not implemented!');
+ }
+
+ supportsCookies(): boolean {
+ return false;
+ }
+ getCookie(_name: string): string {
+ throw new Error('Not implemented!');
+ }
+ setCookie(_name: string, _value: string): any /** TODO #9100 */ {
+ throw new Error('Not implemented!');
+ }
+}
+
+NativeScriptDomAdapter.makeCurrent();
diff --git a/nativescript-angular/element-registry.ts b/nativescript-angular/element-registry.ts
new file mode 100644
index 000000000..d42c91259
--- /dev/null
+++ b/nativescript-angular/element-registry.ts
@@ -0,0 +1,207 @@
+import { View, LayoutBase, Page, Frame, AbsoluteLayout, ActivityIndicator, Button, ContentView, DatePicker, DockLayout, GridLayout, HtmlView, Image, Label, ListPicker, ListView, Placeholder, Progress, ProxyViewContainer, Repeater, ScrollView, SearchBar, SegmentedBar, SegmentedBarItem, Slider, StackLayout, FlexboxLayout, Switch, TabView, TextField, TextView, TimePicker, WebView, WrapLayout, FormattedString, Span, RootLayout } from '@nativescript/core';
+
+export interface ViewClass {
+ new (): View;
+}
+export interface ViewExtensions {
+ meta: ViewClassMeta;
+ nodeType: number;
+ nodeName: string;
+ parentNode: NgView;
+ nextSibling: NgView;
+ previousSibling: NgView;
+ firstChild: NgView;
+ lastChild: NgView;
+ ngCssClasses: Map;
+}
+
+export type NgView = View & ViewExtensions;
+
+export abstract class InvisibleNode extends View implements NgView {
+ meta: { skipAddToDom: boolean };
+ nodeType: number;
+ nodeName: string;
+ parentNode: NgView;
+ nextSibling: NgView;
+ previousSibling: NgView;
+ firstChild: NgView;
+ lastChild: NgView;
+ ngCssClasses: Map;
+
+ constructor() {
+ super();
+
+ this.nodeType = 1;
+ this.nodeName = getClassName(this);
+ }
+
+ toString() {
+ return `${this.nodeName}(${this.id})`;
+ }
+}
+
+export class CommentNode extends InvisibleNode {
+ protected static id = 0;
+
+ constructor() {
+ super();
+
+ this.meta = {
+ skipAddToDom: true,
+ };
+ this.id = CommentNode.id.toString();
+ CommentNode.id += 1;
+ }
+}
+
+export class TextNode extends InvisibleNode {
+ protected static id = 0;
+
+ constructor() {
+ super();
+
+ this.meta = {
+ skipAddToDom: true,
+ };
+ this.id = TextNode.id.toString();
+ TextNode.id += 1;
+ }
+}
+
+const getClassName = (instance) => instance.constructor.name;
+
+export interface ViewClassMeta {
+ skipAddToDom?: boolean;
+ insertChild?: (parent: any, child: any, next?: any) => void;
+ removeChild?: (parent: any, child: any) => void;
+}
+
+export function isDetachedElement(element): boolean {
+ return element && element.meta && element.meta.skipAddToDom;
+}
+
+export function isView(view: any): view is NgView {
+ return view instanceof View;
+}
+
+export function isInvisibleNode(view: any): view is InvisibleNode {
+ return view instanceof InvisibleNode;
+}
+
+export type ViewResolver = () => any;
+
+const elementMap = new Map();
+const camelCaseSplit = /([a-z0-9])([A-Z])/g;
+const defaultViewMeta: ViewClassMeta = { skipAddToDom: false };
+
+export function registerElement(elementName: string, resolver: ViewResolver, meta?: ViewClassMeta): void {
+ const entry = { resolver, meta };
+ elementMap.set(elementName, entry);
+ elementMap.set(elementName.toLowerCase(), entry);
+ elementMap.set(elementName.replace(camelCaseSplit, '$1-$2').toLowerCase(), entry);
+}
+
+export function getViewClass(elementName: string): any {
+ const entry = elementMap.get(elementName) || elementMap.get(elementName.toLowerCase());
+ if (!entry) {
+ throw new TypeError(`No known component for element ${elementName}.`);
+ }
+
+ try {
+ return entry.resolver();
+ } catch (e) {
+ throw new TypeError(`Could not load view for: ${elementName}.${e}`);
+ }
+}
+
+export function getViewMeta(nodeName: string): ViewClassMeta {
+ const entry = elementMap.get(nodeName) || elementMap.get(nodeName.toLowerCase());
+ return (entry && entry.meta) || defaultViewMeta;
+}
+
+export function isKnownView(elementName: string): boolean {
+ return elementMap.has(elementName) || elementMap.has(elementName.toLowerCase());
+}
+
+export function getSingleViewRecursive(nodes: Array, nestLevel: number): View {
+ const actualNodes = nodes.filter((node) => !(node instanceof InvisibleNode));
+
+ if (actualNodes.length === 0) {
+ throw new Error(`No suitable views found in list template! ` + `Nesting level: ${nestLevel}`);
+ } else if (actualNodes.length > 1) {
+ throw new Error(`More than one view found in list template!` + `Nesting level: ${nestLevel}`);
+ }
+
+ const rootLayout = actualNodes[0];
+ if (!rootLayout) {
+ return getSingleViewRecursive(rootLayout.children, nestLevel + 1);
+ }
+
+ const parentLayout = rootLayout.parent;
+ if (parentLayout instanceof LayoutBase) {
+ let node = rootLayout.parentNode;
+ parentLayout.removeChild(rootLayout);
+ rootLayout.parentNode = node;
+ }
+
+ return rootLayout;
+}
+
+const frameMeta: ViewClassMeta = {
+ insertChild: (parent: Frame, child: NgView, next: any) => {
+ // Page cannot be added to Frame with _addChildFromBuilder (thwos "use defaultPage" error)
+ if (isInvisibleNode(child)) {
+ return;
+ } else if (child instanceof Page) {
+ parent.navigate({ create: () => child });
+ } else {
+ throw new Error('Only a Page can be a child of Frame');
+ }
+ },
+};
+
+// Register default NativeScript components
+// Note: ActionBar related components are registerd together with action-bar directives.
+registerElement('AbsoluteLayout', () => AbsoluteLayout);
+registerElement('ActivityIndicator', () => ActivityIndicator);
+registerElement('Button', () => Button);
+registerElement('ContentView', () => ContentView);
+registerElement('DatePicker', () => DatePicker);
+registerElement('DockLayout', () => DockLayout);
+registerElement('Frame', () => Frame, frameMeta);
+registerElement('GridLayout', () => GridLayout);
+registerElement('HtmlView', () => HtmlView);
+registerElement('Image', () => Image);
+// Parse5 changes tags to
. WTF!
+registerElement('img', () => Image);
+registerElement('Label', () => Label);
+registerElement('ListPicker', () => ListPicker);
+registerElement('ListView', () => ListView);
+registerElement('Page', () => Page);
+registerElement('Placeholder', () => Placeholder);
+registerElement('Progress', () => Progress);
+registerElement('ProxyViewContainer', () => ProxyViewContainer);
+registerElement('Repeater', () => Repeater);
+registerElement('RootLayout', () => RootLayout);
+registerElement('ScrollView', () => ScrollView);
+registerElement('SearchBar', () => SearchBar);
+registerElement('SegmentedBar', () => SegmentedBar);
+registerElement('SegmentedBarItem', () => SegmentedBarItem);
+registerElement('Slider', () => Slider);
+registerElement('StackLayout', () => StackLayout);
+registerElement('FlexboxLayout', () => FlexboxLayout);
+registerElement('Switch', () => Switch);
+registerElement('TabView', () => TabView);
+registerElement('TextField', () => TextField);
+registerElement('TextView', () => TextView);
+registerElement('TimePicker', () => TimePicker);
+registerElement('WebView', () => WebView);
+registerElement('WrapLayout', () => WrapLayout);
+registerElement('FormattedString', () => FormattedString);
+registerElement('Span', () => Span);
+
+registerElement('DetachedContainer', () => ProxyViewContainer, {
+ skipAddToDom: true,
+});
+
+registerElement('page-router-outlet', () => Frame);
diff --git a/nativescript-angular/file-system/index.ts b/nativescript-angular/file-system/index.ts
new file mode 100644
index 000000000..755af9427
--- /dev/null
+++ b/nativescript-angular/file-system/index.ts
@@ -0,0 +1 @@
+export * from './ns-file-system';
diff --git a/nativescript-angular/file-system/ns-file-system.ts b/nativescript-angular/file-system/ns-file-system.ts
new file mode 100644
index 000000000..a55b63f15
--- /dev/null
+++ b/nativescript-angular/file-system/ns-file-system.ts
@@ -0,0 +1,20 @@
+import { Injectable } from '@angular/core';
+import { knownFolders, Folder, File } from '@nativescript/core';
+
+// Allows greater flexibility with `file-system` and Angular
+// Also provides a way for `file-system` to be mocked for testing
+
+@Injectable()
+export class NSFileSystem {
+ public currentApp(): Folder {
+ return knownFolders.currentApp();
+ }
+
+ public fileFromPath(path: string): File {
+ return File.fromPath(path);
+ }
+
+ public fileExists(path: string): boolean {
+ return File.exists(path);
+ }
+}
diff --git a/nativescript-angular/forms/forms.module.ts b/nativescript-angular/forms/forms.module.ts
new file mode 100644
index 000000000..69ba95069
--- /dev/null
+++ b/nativescript-angular/forms/forms.module.ts
@@ -0,0 +1,13 @@
+import { NgModule } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { TextValueAccessor, CheckedValueAccessor, DateValueAccessor, TimeValueAccessor, NumberValueAccessor, SelectedIndexValueAccessor } from './value-accessors';
+
+export * from './value-accessors';
+
+@NgModule({
+ declarations: [TextValueAccessor, CheckedValueAccessor, DateValueAccessor, TimeValueAccessor, SelectedIndexValueAccessor, NumberValueAccessor],
+ providers: [],
+ imports: [FormsModule],
+ exports: [FormsModule, TextValueAccessor, CheckedValueAccessor, DateValueAccessor, TimeValueAccessor, SelectedIndexValueAccessor, NumberValueAccessor],
+})
+export class NativeScriptFormsModule {}
diff --git a/nativescript-angular/forms/index.ts b/nativescript-angular/forms/index.ts
new file mode 100644
index 000000000..c88691333
--- /dev/null
+++ b/nativescript-angular/forms/index.ts
@@ -0,0 +1 @@
+export * from './forms.module';
diff --git a/nativescript-angular/forms/value-accessors/base-value-accessor.ts b/nativescript-angular/forms/value-accessors/base-value-accessor.ts
new file mode 100644
index 000000000..abc3bf4a3
--- /dev/null
+++ b/nativescript-angular/forms/value-accessors/base-value-accessor.ts
@@ -0,0 +1,38 @@
+import { ControlValueAccessor } from '@angular/forms';
+import { View, unsetValue } from '@nativescript/core';
+
+import { isBlank } from '../../lang-facade';
+
+export class BaseValueAccessor implements ControlValueAccessor {
+ private pendingChangeNotification: any = 0;
+ onChange = (_) => {};
+ onTouched = () => {};
+
+ constructor(public view: TView) {}
+
+ registerOnChange(fn: (_: any) => void): void {
+ this.onChange = (arg) => {
+ if (this.pendingChangeNotification) {
+ clearTimeout(this.pendingChangeNotification);
+ }
+ this.pendingChangeNotification = setTimeout(() => {
+ this.pendingChangeNotification = 0;
+ fn(arg);
+ }, 20);
+ };
+ }
+
+ registerOnTouched(fn: () => void): void {
+ this.onTouched = fn;
+ }
+
+ setDisabledState(isDisabled: boolean): void {
+ this.view.isEnabled = !isDisabled;
+ }
+
+ writeValue(_: any) {}
+
+ protected normalizeValue(value: any): any {
+ return isBlank(value) ? unsetValue : value;
+ }
+}
diff --git a/nativescript-angular/forms/value-accessors/checked-value-accessor.ts b/nativescript-angular/forms/value-accessors/checked-value-accessor.ts
new file mode 100644
index 000000000..b394554d4
--- /dev/null
+++ b/nativescript-angular/forms/value-accessors/checked-value-accessor.ts
@@ -0,0 +1,38 @@
+import { Directive, ElementRef, forwardRef } from '@angular/core';
+import { NG_VALUE_ACCESSOR } from '@angular/forms';
+import { BaseValueAccessor } from './base-value-accessor';
+import { Switch } from '@nativescript/core';
+
+const CHECKED_VALUE_ACCESSOR = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => CheckedValueAccessor),
+ multi: true,
+};
+
+/**
+ * The accessor for setting a checked property and listening to changes that is used by the
+ * {@link NgModel} directives.
+ *
+ * ### Example
+ * ```
+ *
+ * ```
+ */
+@Directive({
+ selector: 'Switch[ngModel],Switch[formControlName],Switch[formControl],' + 'switch[ngModel],switch[formControlName],switch[formControl]',
+ providers: [CHECKED_VALUE_ACCESSOR],
+ host: {
+ '(checkedChange)': 'onChange($event.value)',
+ },
+})
+export class CheckedValueAccessor extends BaseValueAccessor {
+ // tslint:disable-line:directive-class-suffix
+ constructor(elementRef: ElementRef) {
+ super(elementRef.nativeElement);
+ }
+
+ writeValue(value: any): void {
+ const normalized = super.normalizeValue(value);
+ this.view.checked = normalized;
+ }
+}
diff --git a/nativescript-angular/forms/value-accessors/date-value-accessor.ts b/nativescript-angular/forms/value-accessors/date-value-accessor.ts
new file mode 100644
index 000000000..2417ef1b2
--- /dev/null
+++ b/nativescript-angular/forms/value-accessors/date-value-accessor.ts
@@ -0,0 +1,38 @@
+import { Directive, ElementRef, forwardRef } from '@angular/core';
+import { NG_VALUE_ACCESSOR } from '@angular/forms';
+import { BaseValueAccessor } from './base-value-accessor';
+import { DatePicker } from '@nativescript/core';
+
+const DATE_VALUE_ACCESSOR = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => DateValueAccessor),
+ multi: true,
+};
+
+/**
+ * The accessor for setting a date and listening to changes that is used by the
+ * {@link NgModel} directives.
+ *
+ * ### Example
+ * ```
+ *
+ * ```
+ */
+@Directive({
+ selector: 'DatePicker[ngModel],DatePicker[formControlName],DatePicker[formControl],' + 'datepicker[ngModel],datepicker[formControlName],datepicker[formControl],' + 'datePicker[ngModel],datePicker[formControlName],datePicker[formControl],' + 'date-picker[ngModel],date-picker[formControlName],date-picker[formControl]',
+ providers: [DATE_VALUE_ACCESSOR],
+ host: {
+ '(dateChange)': 'onChange($event.value)',
+ },
+})
+export class DateValueAccessor extends BaseValueAccessor {
+ // tslint:disable-line:directive-class-suffix
+ constructor(elementRef: ElementRef) {
+ super(elementRef.nativeElement);
+ }
+
+ writeValue(value: any): void {
+ const normalized = super.normalizeValue(value);
+ this.view.date = normalized;
+ }
+}
diff --git a/nativescript-angular/forms/value-accessors/index.ts b/nativescript-angular/forms/value-accessors/index.ts
new file mode 100644
index 000000000..c3b2a05a2
--- /dev/null
+++ b/nativescript-angular/forms/value-accessors/index.ts
@@ -0,0 +1,7 @@
+export { BaseValueAccessor } from './base-value-accessor';
+export { TextValueAccessor, TextView } from './text-value-accessor';
+export { CheckedValueAccessor } from './checked-value-accessor';
+export { DateValueAccessor } from './date-value-accessor';
+export { TimeValueAccessor } from './time-value-accessor';
+export { NumberValueAccessor } from './number-value-accessor';
+export { SelectedIndexValueAccessor, SelectableView } from './selectedIndex-value-accessor';
diff --git a/nativescript-angular/forms/value-accessors/number-value-accessor.ts b/nativescript-angular/forms/value-accessors/number-value-accessor.ts
new file mode 100644
index 000000000..8ec1481bf
--- /dev/null
+++ b/nativescript-angular/forms/value-accessors/number-value-accessor.ts
@@ -0,0 +1,38 @@
+import { Directive, ElementRef, forwardRef } from '@angular/core';
+import { NG_VALUE_ACCESSOR } from '@angular/forms';
+import { BaseValueAccessor } from './base-value-accessor';
+import { Slider } from '@nativescript/core';
+
+const NUMBER_VALUE_ACCESSOR = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => NumberValueAccessor),
+ multi: true,
+};
+
+/**
+ * The accessor for setting a value and listening to changes that is used by the
+ * {@link NgModel}
+ *
+ * ### Example
+ * ```
+ *
+ * ```
+ */
+@Directive({
+ selector: 'Slider[ngModel],Slider[formControlName],Slider[formControl],' + 'slider[ngModel],slider[formControlName],slider[formControl]',
+ providers: [NUMBER_VALUE_ACCESSOR],
+ host: {
+ '(valueChange)': 'onChange($event.value)',
+ },
+})
+export class NumberValueAccessor extends BaseValueAccessor {
+ // tslint:disable-line:directive-class-suffix
+ constructor(elementRef: ElementRef) {
+ super(elementRef.nativeElement);
+ }
+
+ writeValue(value: any): void {
+ const normalized = super.normalizeValue(value);
+ this.view.value = normalized;
+ }
+}
diff --git a/nativescript-angular/forms/value-accessors/selectedIndex-value-accessor.ts b/nativescript-angular/forms/value-accessors/selectedIndex-value-accessor.ts
new file mode 100644
index 000000000..9e98be412
--- /dev/null
+++ b/nativescript-angular/forms/value-accessors/selectedIndex-value-accessor.ts
@@ -0,0 +1,64 @@
+import { Directive, ElementRef, forwardRef, AfterViewInit } from '@angular/core';
+import { NG_VALUE_ACCESSOR } from '@angular/forms';
+import { BaseValueAccessor } from './base-value-accessor';
+import { View } from '@nativescript/core';
+
+const SELECTED_INDEX_VALUE_ACCESSOR = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => SelectedIndexValueAccessor),
+ multi: true,
+};
+
+export type SelectableView = { selectedIndex: number } & View;
+
+/**
+ * The accessor for setting a selectedIndex and listening to changes that is used by the
+ * {@link NgModel} directives.
+ *
+ * ### Example
+ * ```
+ *
+ * ```
+ */
+@Directive({
+ selector:
+ 'SegmentedBar[ngModel],SegmentedBar[formControlName],SegmentedBar[formControl],' +
+ 'segmentedBar[ngModel],segmentedBar[formControlName],segmentedBar[formControl],' +
+ 'segmentedbar[ngModel],segmentedbar[formControlName],segmentedbar[formControl],' +
+ 'segmented-bar[ngModel],segmented-bar[formControlName],segmented-bar[formControl],' +
+ 'ListPicker[ngModel],ListPicker[formControlName],ListPicker[formControl],' +
+ 'listPicker[ngModel],listPicker[formControlName],listPicker[formControl],' +
+ 'listpicker[ngModel],listpicker[formControlName],listpicker[formControl],' +
+ 'list-picker[ngModel],list-picker[formControlName],list-picker[formControl],' +
+ 'TabView[ngModel],TabView[formControlName],TabView[formControl],' +
+ 'tabView[ngModel],tabView[formControlName],tabView[formControl],' +
+ 'tabview[ngModel],tabview[formControlName],tabview[formControl],' +
+ 'tab-view[ngModel],tab-view[formControlName],tab-view[formControl]',
+ providers: [SELECTED_INDEX_VALUE_ACCESSOR],
+ host: {
+ '(selectedIndexChange)': 'onChange($event.value)',
+ },
+})
+export class SelectedIndexValueAccessor extends BaseValueAccessor implements AfterViewInit {
+ // tslint:disable-line:max-line-length directive-class-suffix
+ constructor(elementRef: ElementRef) {
+ super(elementRef.nativeElement);
+ }
+
+ private value: number;
+ private viewInitialized: boolean;
+
+ writeValue(value: any): void {
+ const normalized = super.normalizeValue(value);
+ this.value = normalized;
+
+ if (this.viewInitialized) {
+ this.view.selectedIndex = this.value;
+ }
+ }
+
+ ngAfterViewInit() {
+ this.viewInitialized = true;
+ this.view.selectedIndex = this.value;
+ }
+}
diff --git a/nativescript-angular/forms/value-accessors/text-value-accessor.ts b/nativescript-angular/forms/value-accessors/text-value-accessor.ts
new file mode 100644
index 000000000..c993e3490
--- /dev/null
+++ b/nativescript-angular/forms/value-accessors/text-value-accessor.ts
@@ -0,0 +1,53 @@
+import { Directive, ElementRef, forwardRef } from '@angular/core';
+import { NG_VALUE_ACCESSOR } from '@angular/forms';
+import { View } from '@nativescript/core';
+import { BaseValueAccessor } from './base-value-accessor';
+
+const TEXT_VALUE_ACCESSOR = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => TextValueAccessor),
+ multi: true,
+};
+
+export type TextView = { text: string } & View;
+
+/**
+ * The accessor for writing a text and listening to changes that is used by the
+ * {@link NgModel} directives.
+ *
+ * ### Example
+ * ```
+ *
+ * ```
+ */
+@Directive({
+ selector:
+ 'TextField[ngModel],TextField[formControlName],TextField[formControl],' +
+ 'textField[ngModel],textField[formControlName],textField[formControl],' +
+ 'textfield[ngModel],textfield[formControlName],textfield[formControl],' +
+ 'text-field[ngModel],text-field[formControlName],text-field[formControl],' +
+ 'TextView[ngModel],TextView[formControlName],TextView[formControl],' +
+ 'textView[ngModel],textView[formControlName],textView[formControl],' +
+ 'textview[ngModel],textview[formControlName],textview[formControl],' +
+ 'text-view[ngModel],text-view[formControlName],text-view[formControl],' +
+ 'SearchBar[ngModel],SearchBar[formControlName],SearchBar[formControl],' +
+ 'searchBar[ngModel],searchBar[formControlName],searchBar[formControl],' +
+ 'searchbar[ngModel],searchbar[formControlName],searchbar[formControl],' +
+ 'search-bar[ngModel], search-bar[formControlName],search-bar[formControl]',
+ providers: [TEXT_VALUE_ACCESSOR],
+ host: {
+ '(blur)': 'onTouched()',
+ '(textChange)': 'onChange($event.value)',
+ },
+})
+export class TextValueAccessor extends BaseValueAccessor {
+ // tslint:disable-line:directive-class-suffix
+ constructor(elementRef: ElementRef) {
+ super(elementRef.nativeElement);
+ }
+
+ writeValue(value: any): void {
+ const normalized = super.normalizeValue(value);
+ this.view.text = normalized;
+ }
+}
diff --git a/nativescript-angular/forms/value-accessors/time-value-accessor.ts b/nativescript-angular/forms/value-accessors/time-value-accessor.ts
new file mode 100644
index 000000000..b15aa6c34
--- /dev/null
+++ b/nativescript-angular/forms/value-accessors/time-value-accessor.ts
@@ -0,0 +1,38 @@
+import { Directive, ElementRef, forwardRef } from '@angular/core';
+import { NG_VALUE_ACCESSOR } from '@angular/forms';
+import { BaseValueAccessor } from './base-value-accessor';
+import { TimePicker } from '@nativescript/core';
+
+const TIME_VALUE_ACCESSOR = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => TimeValueAccessor),
+ multi: true,
+};
+
+/**
+ * The accessor for setting a time and listening to changes that is used by the
+ * {@link NgModel} directives.
+ *
+ * ### Example
+ * ```
+ *
+ * ```
+ */
+@Directive({
+ selector: 'TimePicker[ngModel],TimePicker[formControlName],TimePicker[formControl],' + 'timepicker[ngModel],timepicker[formControlName],timepicker[formControl],' + 'timePicker[ngModel],timePicker[formControlName],timePicker[formControl],' + 'time-picker[ngModel],time-picker[formControlName],time-picker[formControl]',
+ providers: [TIME_VALUE_ACCESSOR],
+ host: {
+ '(timeChange)': 'onChange($event.value)',
+ },
+})
+export class TimeValueAccessor extends BaseValueAccessor {
+ // tslint:disable-line:directive-class-suffix
+ constructor(elementRef: ElementRef) {
+ super(elementRef.nativeElement);
+ }
+
+ writeValue(value: any): void {
+ const normalized = super.normalizeValue(value);
+ this.view.time = normalized;
+ }
+}
diff --git a/nativescript-angular/frame.service.ts b/nativescript-angular/frame.service.ts
new file mode 100644
index 000000000..952a25d24
--- /dev/null
+++ b/nativescript-angular/frame.service.ts
@@ -0,0 +1,13 @@
+import { Injectable } from '@angular/core';
+import { Frame } from '@nativescript/core';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class FrameService {
+ // TODO: Add any methods that are needed to handle frame/page navigation
+ getFrame(): Frame {
+ let topmostFrame = Frame.topmost();
+ return topmostFrame;
+ }
+}
diff --git a/nativescript-angular/global.d.ts b/nativescript-angular/global.d.ts
new file mode 100644
index 000000000..6bb03980d
--- /dev/null
+++ b/nativescript-angular/global.d.ts
@@ -0,0 +1 @@
+///
diff --git a/nativescript-angular/http-client/http-client.module.ts b/nativescript-angular/http-client/http-client.module.ts
new file mode 100644
index 000000000..2f1c9a084
--- /dev/null
+++ b/nativescript-angular/http-client/http-client.module.ts
@@ -0,0 +1,13 @@
+import { NgModule } from '@angular/core';
+
+import { HttpClientModule, HttpBackend } from '@angular/common/http';
+
+import { NSFileSystem } from '../file-system/ns-file-system';
+import { NsHttpBackEnd } from './ns-http-backend';
+
+@NgModule({
+ providers: [NSFileSystem, NsHttpBackEnd, { provide: HttpBackend, useExisting: NsHttpBackEnd }],
+ imports: [HttpClientModule],
+ exports: [HttpClientModule],
+})
+export class NativeScriptHttpClientModule {}
diff --git a/nativescript-angular/http-client/http-utils.ts b/nativescript-angular/http-client/http-utils.ts
new file mode 100644
index 000000000..c719e4c2f
--- /dev/null
+++ b/nativescript-angular/http-client/http-utils.ts
@@ -0,0 +1,47 @@
+import { NSFileSystem } from '../file-system/ns-file-system';
+
+import { Observable, Observer } from 'rxjs';
+import { path } from '@nativescript/core';
+
+export type httpResponseFactory = (url: string, body: any, status: number) => T;
+export type httpErrorFactory = (url: string, body: any, status: number) => any;
+
+export function isLocalRequest(url: string): boolean {
+ return url.indexOf('~') === 0 || url.indexOf('/') === 0;
+}
+
+export function getAbsolutePath(url: string, nsFileSystem: NSFileSystem): string {
+ url = url.replace('~', '').replace('/', '');
+ url = path.join(nsFileSystem.currentApp().path, url);
+ return url;
+}
+
+export function processLocalFileRequest(url: string, nsFileSystem: NSFileSystem, successResponse: httpResponseFactory, errorResponse: httpErrorFactory): Observable {
+ url = getAbsolutePath(url, nsFileSystem);
+
+ // request from local app resources
+ return new Observable((observer: Observer) => {
+ if (nsFileSystem.fileExists(url)) {
+ const localFile = nsFileSystem.fileFromPath(url);
+ localFile.readText().then(
+ (data) => {
+ try {
+ const json = JSON.parse(data);
+ observer.next(successResponse(url, json, 200));
+ observer.complete();
+ } catch (error) {
+ // Even though the response status was 2xx, this is still an error.
+ // The parse error contains the text of the body that failed to parse.
+ const errorResult = { error, text: data };
+ observer.error(errorResponse(url, errorResult, 200));
+ }
+ },
+ (err: Object) => {
+ observer.error(errorResponse(url, err, 400));
+ }
+ );
+ } else {
+ observer.error(errorResponse(url, 'Not Found', 404));
+ }
+ });
+}
diff --git a/nativescript-angular/http-client/index.ts b/nativescript-angular/http-client/index.ts
new file mode 100644
index 000000000..f4174328b
--- /dev/null
+++ b/nativescript-angular/http-client/index.ts
@@ -0,0 +1,2 @@
+export * from './http-client.module';
+export * from './ns-http-backend';
diff --git a/nativescript-angular/http-client/ns-http-backend.ts b/nativescript-angular/http-client/ns-http-backend.ts
new file mode 100644
index 000000000..a4ef9bc2a
--- /dev/null
+++ b/nativescript-angular/http-client/ns-http-backend.ts
@@ -0,0 +1,47 @@
+import { Injectable } from '@angular/core';
+import { HttpRequest, HttpEvent, XhrFactory, HttpResponse, HttpErrorResponse, HttpXhrBackend } from '@angular/common/http';
+import { Observable } from 'rxjs';
+
+import { NSFileSystem } from '../file-system/ns-file-system';
+import { isLocalRequest, processLocalFileRequest } from './http-utils';
+
+@Injectable()
+export class NsHttpBackEnd extends HttpXhrBackend {
+ constructor(xhrFactory: XhrFactory, private nsFileSystem: NSFileSystem) {
+ super(xhrFactory);
+ }
+
+ handle(req: HttpRequest): Observable> {
+ let result: Observable>;
+
+ if (isLocalRequest(req.url)) {
+ result = this.handleLocalFileRequest(req.url);
+ } else {
+ result = super.handle(req);
+ }
+
+ return result;
+ }
+
+ private handleLocalFileRequest(url: string): Observable> {
+ return processLocalFileRequest(url, this.nsFileSystem, createSuccessResponse, createErrorResponse);
+ }
+}
+
+function createSuccessResponse(url: string, body: any, status: number): HttpEvent {
+ return new HttpResponse({
+ url,
+ body,
+ status,
+ statusText: 'OK',
+ });
+}
+
+function createErrorResponse(url: string, body: any, status: number): HttpErrorResponse {
+ return new HttpErrorResponse({
+ url,
+ error: body,
+ status,
+ statusText: 'ERROR',
+ });
+}
diff --git a/nativescript-angular/index.ts b/nativescript-angular/index.ts
new file mode 100644
index 000000000..f0e97a09c
--- /dev/null
+++ b/nativescript-angular/index.ts
@@ -0,0 +1,38 @@
+// Initial imports and polyfills
+import '@nativescript/core';
+import '@nativescript/zone-js';
+// TODO: migrate to standard zone.js if possible
+// investigate Ivy with templated-items-comp to allow standard zone below to be used instead of patched {N} zone above
+// import 'zone.js/dist/zone';
+import './dom-adapter';
+// import "./polyfills/array";
+import './polyfills/console';
+import 'nativescript-intl';
+
+export * from './platform-common';
+export * from './platform-providers';
+export * from './platform';
+export * from './resource-loader';
+
+export * from './nativescript.module';
+export * from './common';
+export * from './common/detached-loader';
+export * from './common/utils';
+
+export { NativeScriptAnimationsModule } from './animations';
+export * from './file-system';
+export * from './http-client';
+export * from './forms';
+export * from './directives';
+export * from './router';
+export * from './frame.service';
+
+export { NativeScriptRenderer } from './renderer';
+export { EmulatedRenderer } from './renderer-emulated';
+export { NativeScriptRendererFactory } from './renderer-factory';
+
+// utils
+export { NativeScriptDebug } from './trace';
+export * from './app-host-view';
+export { getViewClass, getViewMeta, isKnownView, registerElement, CommentNode, getSingleViewRecursive, isInvisibleNode, isView } from './element-registry';
+export type { NgView, ViewClass, ViewClassMeta, ViewResolver, ViewExtensions } from './element-registry';
diff --git a/nativescript-angular/lang-facade.ts b/nativescript-angular/lang-facade.ts
new file mode 100644
index 000000000..62b677fd3
--- /dev/null
+++ b/nativescript-angular/lang-facade.ts
@@ -0,0 +1,7 @@
+export function isPresent(obj: any): boolean {
+ return obj !== undefined && obj !== null;
+}
+
+export function isBlank(obj: any): boolean {
+ return obj === undefined || obj === null;
+}
diff --git a/nativescript-angular/nativescript.module.ts b/nativescript-angular/nativescript.module.ts
new file mode 100644
index 000000000..27f682d33
--- /dev/null
+++ b/nativescript-angular/nativescript.module.ts
@@ -0,0 +1,30 @@
+import { ApplicationModule, ErrorHandler, NO_ERRORS_SCHEMA, NgModule, RendererFactory2, SystemJsNgModuleLoader, Optional, SkipSelf, ɵINJECTOR_SCOPE } from '@angular/core';
+
+import { ViewportScroller, ɵNullViewportScroller as NullViewportScroller } from '@angular/common';
+
+import { NativeScriptCommonModule } from './common';
+import { NativeScriptRendererFactory } from './renderer-factory';
+import { DetachedLoader } from './common/detached-loader';
+import { throwIfAlreadyLoaded } from './common/utils';
+import { FrameService } from './frame.service';
+
+export function errorHandlerFactory() {
+ return new ErrorHandler();
+}
+
+export { DetachedLoader } from './common/detached-loader';
+
+@NgModule({
+ declarations: [DetachedLoader],
+ providers: [FrameService, NativeScriptRendererFactory, SystemJsNgModuleLoader, { provide: ɵINJECTOR_SCOPE, useValue: 'root' }, { provide: ErrorHandler, useFactory: errorHandlerFactory }, { provide: RendererFactory2, useExisting: NativeScriptRendererFactory }, { provide: ViewportScroller, useClass: NullViewportScroller }],
+ entryComponents: [DetachedLoader],
+ imports: [ApplicationModule, NativeScriptCommonModule],
+ exports: [ApplicationModule, NativeScriptCommonModule, DetachedLoader],
+ schemas: [NO_ERRORS_SCHEMA],
+})
+export class NativeScriptModule {
+ constructor(@Optional() @SkipSelf() parentModule: NativeScriptModule) {
+ // Prevents NativeScriptModule from getting imported multiple times
+ throwIfAlreadyLoaded(parentModule, 'NativeScriptModule');
+ }
+}
diff --git a/nativescript-angular/package.json b/nativescript-angular/package.json
new file mode 100644
index 000000000..574fab819
--- /dev/null
+++ b/nativescript-angular/package.json
@@ -0,0 +1,98 @@
+{
+ "name": "@nativescript/angular",
+ "version": "11.8.0",
+ "description": "An Angular renderer that lets you build mobile apps with NativeScript.",
+ "homepage": "https://www.nativescript.org/",
+ "bugs": "https://github.com/NativeScript/nativescript-angular/issues",
+ "main": "index.js",
+ "module": "index.js",
+ "types": "index.d.ts",
+ "author": {
+ "name": "NativeScript Team"
+ },
+ "nativescript": {
+ "platforms": {
+ "android": "6.0.0",
+ "ios": "6.0.0"
+ }
+ },
+ "keywords": [
+ "NativeScript",
+ "Angular"
+ ],
+ "license": "Apache-2.0",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/NativeScript/nativescript-angular.git"
+ },
+ "scripts": {
+ "setup": "npx rimraf hooks node_modules package-lock.json && npm i && npm run prep.apps",
+ "prep.apps": "npm run build.pack && cd ../build/pack-scripts && npm i && npx ts-node pack-scoped.ts",
+ "build": "ng-packagr -p package.json",
+ "build.pack": "npm run tsc && npm run build && cd dist && npm pack",
+ "format": "npx prettier --write .",
+ "format-check": "npx prettier --check .",
+ "tsc": "tsc -p tsconfig.json",
+ "tsc-w": "tsc -p tsconfig.json -w",
+ "ngc": "ngc",
+ "ngcc": "ngcc",
+ "ngcc-run": "npm run ngcc && ngc -p tsconfig.json",
+ "changelog": "conventional-changelog -p angular -i ../CHANGELOG.md -s",
+ "version": "rm -rf package-lock.json && npm run changelog && git add ../CHANGELOG.md",
+ "typedoc": "typedoc --tsconfig \"./tsconfig.typedoc.json\" --out ./bin/dist/ng-api-reference --includeDeclarations --name \"NativeScript Angular\" --theme ./node_modules/nativescript-typedoc-theme --excludeExternals --externalPattern \"**/+(tns-core-modules|module|declarations).d.ts\""
+ },
+ "sideEffects": false,
+ "ngPackage": {
+ "assets": [
+ "../README.md"
+ ],
+ "lib": {
+ "entryFile": "index.ts",
+ "umdModuleIds": {
+ "@nativescript/core": "ns-core"
+ }
+ },
+ "allowedNonPeerDependencies": [
+ "."
+ ]
+ },
+ "dependencies": {
+ "@nativescript/zone-js": "~1.0.0",
+ "nativescript-intl": "^4.0.0"
+ },
+ "devDependencies": {
+ "@angular/animations": "~11.2.0",
+ "@angular/common": "~11.2.0",
+ "@angular/compiler": "~11.2.0",
+ "@angular/compiler-cli": "~11.2.0",
+ "@angular/core": "~11.2.0",
+ "@angular/forms": "~11.2.0",
+ "@angular/platform-browser": "~11.2.0",
+ "@angular/platform-browser-dynamic": "~11.2.0",
+ "@angular/router": "~11.2.0",
+ "@nativescript/core": "~8.0.0",
+ "codelyzer": "^5.2.0",
+ "conventional-changelog-cli": "^2.0.34",
+ "husky": "^4.2.5",
+ "lint-staged": "^10.2.11",
+ "nativescript-typedoc-theme": "git://github.com/NativeScript/nativescript-typedoc-theme.git#master",
+ "ng-packagr": "~11.2.0",
+ "prettier": "^2.0.5",
+ "rxjs": "~6.6.0",
+ "tslint": "^6.1.2",
+ "tslint-config-prettier": "^1.18.0",
+ "typedoc": "~0.17.0",
+ "typescript": "~4.0.0",
+ "zone.js": "^0.11.1"
+ },
+ "husky": {
+ "hooks": {
+ "pre-commit": "lint-staged"
+ }
+ },
+ "lint-staged": {
+ ".": [
+ "prettier --write ."
+ ]
+ }
+}
diff --git a/nativescript-angular/platform-common.ts b/nativescript-angular/platform-common.ts
new file mode 100644
index 000000000..48ffde08f
--- /dev/null
+++ b/nativescript-angular/platform-common.ts
@@ -0,0 +1,344 @@
+import { Application, TextView, Color, View, Frame, GridLayout, LaunchEventData, ApplicationEventData, profile, profilingUptime, Page, LayoutBase } from '@nativescript/core';
+// import './dom-adapter';
+// import 'nativescript-intl';
+
+import { Type, Injector, CompilerOptions, PlatformRef, NgModuleFactory, NgModuleRef, EventEmitter, Sanitizer, InjectionToken, StaticProvider } from '@angular/core';
+import { DOCUMENT } from '@angular/common';
+
+import { NativeScriptDebug } from './trace';
+import { defaultPageFactoryProvider, setRootPage, PageFactory, PAGE_FACTORY, getRootPage } from './platform-providers';
+import { AppHostView, AppHostAsyncView } from './app-host-view';
+
+export const onBeforeLivesync = new EventEmitter>();
+export const onAfterLivesync = new EventEmitter<{
+ moduleRef?: NgModuleRef;
+ error?: Error;
+}>();
+let lastBootstrappedModule: WeakRef>;
+type BootstrapperAction = () => Promise>;
+
+// Work around a TS bug requiring an import of OpaqueToken without using it
+// if ((global).___TS_UNUSED) {
+// (() => {
+// return InjectionToken;
+// })();
+// }
+
+// tslint:disable:max-line-length
+/**
+ * Options to be passed when HMR is enabled
+ */
+export interface HmrOptions {
+ /**
+ * A factory function that returns either Module type or NgModuleFactory type.
+ * This needs to be a factory function as the types will change when modules are replaced.
+ */
+ moduleTypeFactory?: () => Type | NgModuleFactory;
+
+ /**
+ * A livesync callback that will be called instead of the original livesync.
+ * It gives the HMR a hook to apply the module replacement.
+ * @param bootstrapPlatform - A bootstrap callback to be called after HMR is done. It will bootstrap a new angular app within the exisiting platform, using the moduleTypeFactory to get the Module or NgModuleFactory to be used.
+ */
+ livesyncCallback: (bootstrapPlatform: () => void) => void;
+}
+// tslint:enable:max-line-length
+
+export interface AppLaunchView extends LayoutBase {
+ // called when the animation is to begin
+ startAnimation?: () => void;
+ // called when bootstrap is complete and cleanup can begin
+ // should resolve when animation is completely finished
+ cleanup?: () => Promise;
+}
+
+export interface AppOptions {
+ bootInExistingPage?: boolean;
+ cssFile?: string;
+ startPageActionBarHidden?: boolean;
+ hmrOptions?: HmrOptions;
+ /**
+ * Background color of the root view
+ */
+ backgroundColor?: string;
+ /**
+ * Use animated launch view (async by default)
+ */
+ launchView?: AppLaunchView;
+ /**
+ * When using Async APP_INITIALIZER, set this to `true`.
+ * (Not needed when using launchView)
+ */
+ async?: boolean;
+}
+
+export type PlatformFactory = (extraProviders?: StaticProvider[]) => PlatformRef;
+
+export class NativeScriptSanitizer extends Sanitizer {
+ sanitize(_context: any, value: string): string {
+ return value;
+ }
+}
+
+export class NativeScriptDocument {
+ // Required by the AnimationDriver
+ public body: any = {
+ isOverride: true,
+ };
+
+ createElement(tag: string) {
+ throw new Error('NativeScriptDocument is not DOM Document. There is no createElement() method.');
+ }
+}
+
+export const COMMON_PROVIDERS = [defaultPageFactoryProvider, { provide: Sanitizer, useClass: NativeScriptSanitizer, deps: [] }, { provide: DOCUMENT, useClass: NativeScriptDocument, deps: [] }];
+
+export class NativeScriptPlatformRef extends PlatformRef {
+ private _bootstrapper: BootstrapperAction;
+
+ constructor(private platform: PlatformRef, private appOptions: AppOptions = {}) {
+ super();
+ }
+
+ @profile
+ bootstrapModuleFactory(moduleFactory: NgModuleFactory): Promise> {
+ this._bootstrapper = () => {
+ let bootstrapFactory = moduleFactory;
+ if (this.appOptions.hmrOptions) {
+ bootstrapFactory = >this.appOptions.hmrOptions.moduleTypeFactory();
+ }
+
+ return this.platform.bootstrapModuleFactory(bootstrapFactory);
+ };
+
+ this.bootstrapApp();
+
+ return null; // Make the compiler happy
+ }
+
+ @profile
+ bootstrapModule(moduleType: Type, compilerOptions: CompilerOptions | CompilerOptions[] = []): Promise> {
+ this._bootstrapper = () => {
+ let bootstrapType = moduleType;
+ if (this.appOptions.hmrOptions) {
+ bootstrapType = >this.appOptions.hmrOptions.moduleTypeFactory();
+ }
+
+ return this.platform.bootstrapModule(bootstrapType, compilerOptions);
+ };
+ this.bootstrapApp();
+
+ return null; // Make the compiler happy
+ }
+
+ @profile
+ private bootstrapApp() {
+ (global).__onLiveSyncCore = () => {
+ if (this.appOptions.hmrOptions) {
+ const rootView = Application.getRootView();
+ if (rootView) {
+ rootView._closeAllModalViewsInternal();
+ }
+
+ this.appOptions.hmrOptions.livesyncCallback(() => this._livesync());
+ } else {
+ this._livesync();
+ }
+ };
+
+ if (this.appOptions && typeof this.appOptions.cssFile === 'string') {
+ Application.setCssFileName(this.appOptions.cssFile);
+ }
+
+ this.bootstrapNativeScriptApp();
+ }
+
+ onDestroy(callback: () => void): void {
+ this.platform.onDestroy(callback);
+ }
+
+ get injector(): Injector {
+ return this.platform.injector;
+ }
+
+ destroy(): void {
+ this.platform.destroy();
+ }
+
+ get destroyed(): boolean {
+ return this.platform.destroyed;
+ }
+
+ @profile
+ private bootstrapNativeScriptApp() {
+ let rootContent: View;
+
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.bootstrapLog('NativeScriptPlatform bootstrap started.');
+ }
+ const launchCallback = profile('@nativescript/angular/platform-common.launchCallback', (args: LaunchEventData) => {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.bootstrapLog('Application launch event fired');
+ }
+
+ // Create a temp page for root of the renderer
+ let tempAppHostView: AppHostView;
+ let tempAppHostAsyncView: AppHostAsyncView;
+ if (this.appOptions && (this.appOptions.async || this.appOptions.launchView)) {
+ tempAppHostAsyncView = new AppHostAsyncView(new Color(this.appOptions && this.appOptions.backgroundColor ? this.appOptions.backgroundColor : '#fff'));
+ if (this.appOptions.launchView) {
+ this.appOptions.launchView.style.zIndex = 1000;
+ tempAppHostAsyncView.addChild(this.appOptions.launchView);
+ }
+ rootContent = tempAppHostAsyncView.ngAppRoot;
+ setRootPage(tempAppHostAsyncView);
+ } else {
+ tempAppHostView = new AppHostView(new Color(this.appOptions && this.appOptions.backgroundColor ? this.appOptions.backgroundColor : '#fff'));
+ setRootPage(tempAppHostView);
+ }
+
+ let bootstrapPromiseCompleted = false;
+ const bootstrap = () => {
+ this._bootstrapper().then(
+ (moduleRef) => {
+ bootstrapPromiseCompleted = true;
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.bootstrapLog(`Angular bootstrap bootstrap done. uptime: ${profilingUptime()}`);
+ }
+
+ if (this.appOptions.launchView && this.appOptions.launchView.cleanup) {
+ this.appOptions.launchView.cleanup().then(() => {
+ // cleanup any custom launch views
+ tempAppHostAsyncView.removeChild(this.appOptions.launchView);
+ this.appOptions.launchView = null;
+ });
+ } else if (tempAppHostView) {
+ rootContent = tempAppHostView.content;
+ }
+
+ lastBootstrappedModule = new WeakRef(moduleRef);
+ },
+ (err) => {
+ bootstrapPromiseCompleted = true;
+ const errorMessage = err.message + '\n\n' + err.stack;
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.bootstrapLogError('ERROR BOOTSTRAPPING ANGULAR');
+ }
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.bootstrapLogError(errorMessage);
+ }
+
+ rootContent = this.createErrorUI(errorMessage);
+ }
+ );
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.bootstrapLog('bootstrapAction called, draining micro tasks queue. Root: ' + rootContent);
+ }
+ (global).Zone.drainMicroTaskQueue();
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.bootstrapLog('bootstrapAction called, draining micro tasks queue finished! Root: ' + rootContent);
+ }
+ };
+
+ if (this.appOptions && this.appOptions.launchView && this.appOptions.launchView.startAnimation) {
+ // start animations on next tick (after initial boot)
+ setTimeout(() => {
+ // ensure launch animation is executed after launchView added to view stack
+ this.appOptions.launchView.startAnimation();
+ });
+ }
+ bootstrap();
+ // if (!bootstrapPromiseCompleted) {
+ // const errorMessage = "Bootstrap promise didn't resolve";
+ // if (NativeScriptDebug.isLogEnabled()) {
+ // NativeScriptDebug.bootstrapLogError(errorMessage);
+ // }
+ // rootContent = this.createErrorUI(errorMessage);
+ // }
+ args.root = rootContent;
+ });
+ const exitCallback = profile('@nativescript/angular/platform-common.exitCallback', (args: ApplicationEventData) => {
+ const androidActivity = args.android;
+ if (androidActivity && !androidActivity.isFinishing()) {
+ // Exit event was triggered as a part of a restart of the app.
+ return;
+ }
+
+ const lastModuleRef = lastBootstrappedModule ? lastBootstrappedModule.get() : null;
+ if (lastModuleRef) {
+ // Make sure the module is only destroyed once
+ lastBootstrappedModule = null;
+
+ lastModuleRef.destroy();
+ }
+
+ rootContent = null;
+ });
+
+ Application.on(Application.launchEvent, launchCallback);
+ Application.on(Application.exitEvent, exitCallback);
+
+ Application.run();
+ }
+
+ @profile
+ public _livesync() {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.bootstrapLog('Angular livesync started.');
+ }
+
+ const lastModuleRef = lastBootstrappedModule ? lastBootstrappedModule.get() : null;
+ onBeforeLivesync.next(lastModuleRef);
+ if (lastModuleRef) {
+ lastModuleRef.destroy();
+ }
+
+ this._bootstrapper().then(
+ (moduleRef) => {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.bootstrapLog('Angular livesync done.');
+ }
+ onAfterLivesync.next({ moduleRef });
+
+ lastBootstrappedModule = new WeakRef(moduleRef);
+ Application.resetRootView({
+ create: () => (getRootPage() instanceof AppHostView ? ((getRootPage()) as AppHostView).ngAppRoot : getRootPage()),
+ });
+ },
+ (error) => {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.bootstrapLogError('ERROR LIVESYNC BOOTSTRAPPING ANGULAR');
+ }
+ const errorMessage = error.message + '\n\n' + error.stack;
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.bootstrapLogError(errorMessage);
+ }
+
+ Application.resetRootView({
+ create: () => this.createErrorUI(errorMessage),
+ });
+ onAfterLivesync.next({ error });
+ }
+ );
+ }
+
+ private createErrorUI(message: string): View {
+ const errorTextBox = new TextView();
+ errorTextBox.text = message;
+ errorTextBox.color = new Color('red');
+ return errorTextBox;
+ }
+
+ private createFrameAndPage(isLivesync: boolean) {
+ const frame = new Frame();
+ const pageFactory: PageFactory = this.platform.injector.get(PAGE_FACTORY);
+ const page = pageFactory({ isBootstrap: true, isLivesync });
+
+ frame.navigate({
+ create: () => {
+ return page;
+ },
+ });
+ return { page, frame };
+ }
+}
diff --git a/nativescript-angular/platform-providers.ts b/nativescript-angular/platform-providers.ts
new file mode 100644
index 000000000..a7ad87f2b
--- /dev/null
+++ b/nativescript-angular/platform-providers.ts
@@ -0,0 +1,64 @@
+import { InjectionToken, Injectable, OnDestroy } from '@angular/core';
+
+import { Frame, View, Page, IDevice, Device } from '@nativescript/core';
+
+export const APP_ROOT_VIEW = new InjectionToken('NativeScriptAppRootView');
+export const DeviceToken = new InjectionToken('NativeScriptPlatformDeviceToken');
+
+export type PageFactory = (options: PageFactoryOptions) => Page;
+export interface PageFactoryOptions {
+ isBootstrap?: boolean;
+ isLivesync?: boolean;
+ isModal?: boolean;
+ isNavigation?: boolean;
+ componentType?: any;
+}
+export const PAGE_FACTORY = new InjectionToken('NativeScriptPageFactory');
+export const defaultPageFactory: PageFactory = function (_opts: PageFactoryOptions) {
+ return new Page();
+};
+export const defaultPageFactoryProvider = { provide: PAGE_FACTORY, useValue: defaultPageFactory };
+
+let _rootPageRef: WeakRef;
+
+export function setRootPage(page: Page): void {
+ _rootPageRef = new WeakRef(page);
+}
+
+export function getRootPage(): Page {
+ return _rootPageRef && _rootPageRef.get();
+}
+
+// Use an exported function to make the AoT compiler happy.
+export function getDefaultPage(): Page {
+ const rootPage = getRootPage();
+ if (rootPage instanceof Page) {
+ return rootPage;
+ }
+ // if (rootPage) {
+ // return rootPage;
+ // }
+
+ const frame = Frame.topmost();
+ if (frame && frame.currentPage) {
+ return frame.currentPage;
+ }
+
+ return null;
+}
+
+export const defaultPageProvider = { provide: Page, useFactory: getDefaultPage };
+
+// Use an exported function to make the AoT compiler happy.
+export function getDefaultFrame(): Frame {
+ return Frame.topmost();
+}
+
+export const defaultFrameProvider = { provide: Frame, useFactory: getDefaultFrame };
+
+// Use an exported function to make the AoT compiler happy.
+export function getDefaultDevice(): IDevice {
+ return Device;
+}
+
+export const defaultDeviceProvider = { provide: DeviceToken, useFactory: getDefaultDevice };
diff --git a/nativescript-angular/platform.ts b/nativescript-angular/platform.ts
new file mode 100644
index 000000000..12e83ffbb
--- /dev/null
+++ b/nativescript-angular/platform.ts
@@ -0,0 +1,15 @@
+import { PlatformRef, platformCore, createPlatformFactory } from '@angular/core';
+
+import { NativeScriptPlatformRef, AppOptions, PlatformFactory, COMMON_PROVIDERS } from './platform-common';
+
+// "Static" platform
+const _platformNativeScript: PlatformFactory = createPlatformFactory(platformCore, 'nativeScript', [...COMMON_PROVIDERS]);
+
+export function platformNativeScriptDynamic(options?: AppOptions, extraProviders?: any[]): PlatformRef {
+ // Return raw platform to advanced users only if explicitly requested
+ if (options && options.bootInExistingPage === true) {
+ return _platformNativeScript(extraProviders);
+ } else {
+ return new NativeScriptPlatformRef(_platformNativeScript(extraProviders), options);
+ }
+}
diff --git a/nativescript-angular/polyfills/array.ts b/nativescript-angular/polyfills/array.ts
new file mode 100644
index 000000000..43e3e0b68
--- /dev/null
+++ b/nativescript-angular/polyfills/array.ts
@@ -0,0 +1,43 @@
+if (!(Array.prototype).fill) {
+ (Array.prototype).fill = function (value) {
+ let O = Object(this);
+ let len = parseInt(O.length, 10);
+ let start = arguments[1];
+ let relativeStart = parseInt(start, 10) || 0;
+ let k = relativeStart < 0 ? Math.max(len + relativeStart, 0) : Math.min(relativeStart, len);
+ let end = arguments[2];
+ let relativeEnd = end === undefined ? len : parseInt(end, 10) || 0;
+ let final = relativeEnd < 0 ? Math.max(len + relativeEnd, 0) : Math.min(relativeEnd, len);
+
+ for (; k < final; k++) {
+ O[k] = value;
+ }
+
+ return O;
+ };
+}
+
+if (!(Array).from) {
+ (Array).from = function (iterable, mapFn, thisArg) {
+ let results: Array = [];
+
+ if (iterable.next) {
+ // Iterator objects
+ for (let step = null; ; step = iterable.next()) {
+ if (step.done) {
+ break;
+ } else {
+ results.push(step.value);
+ }
+ }
+ } else {
+ // Array-like objects
+ results = [].slice.call(iterable);
+ }
+
+ if (mapFn) {
+ results = >(results.forEach(mapFn, thisArg));
+ }
+ return results;
+ };
+}
diff --git a/nativescript-angular/polyfills/console.ts b/nativescript-angular/polyfills/console.ts
new file mode 100644
index 000000000..80b8bbbad
--- /dev/null
+++ b/nativescript-angular/polyfills/console.ts
@@ -0,0 +1,7 @@
+if (!console.group) {
+ console.group = () => {};
+}
+
+if (!console.groupEnd) {
+ console.groupEnd = () => {};
+}
diff --git a/nativescript-angular/renderer-emulated.ts b/nativescript-angular/renderer-emulated.ts
new file mode 100644
index 000000000..d009fef27
--- /dev/null
+++ b/nativescript-angular/renderer-emulated.ts
@@ -0,0 +1,63 @@
+import { Inject, Injectable, RendererFactory2, Optional, NgZone, RendererType2 } from '@angular/core';
+import { View, getViewById, Application, profile } from '@nativescript/core';
+import { APP_ROOT_VIEW, getRootPage } from './platform-providers';
+import { ViewUtil } from './view-util';
+import { NgView, InvisibleNode } from './element-registry';
+import { NativeScriptDebug } from './trace';
+import { NativeScriptRenderer } from './renderer';
+
+// CONTENT_ATTR not exported from NativeScript_renderer - we need it for styles application.
+const COMPONENT_REGEX = /%COMP%/g;
+const ATTR_SANITIZER = /-/g;
+export const COMPONENT_VARIABLE = '%COMP%';
+export const HOST_ATTR = `_nghost-${COMPONENT_VARIABLE}`;
+export const CONTENT_ATTR = `_ngcontent-${COMPONENT_VARIABLE}`;
+
+const replaceNgAttribute = function (input: string, componentId: string): string {
+ return input.replace(COMPONENT_REGEX, componentId);
+};
+
+const addScopedStyleToCss = profile(`"renderer".addScopedStyleToCss`, function addScopedStyleToCss(style: string): void {
+ Application.addCss(style, true);
+});
+
+@Injectable()
+export class EmulatedRenderer extends NativeScriptRenderer {
+ private contentAttr: string;
+ private hostAttr: string;
+
+ constructor(component: RendererType2, rootView: NgView, zone: NgZone, viewUtil: ViewUtil) {
+ super(rootView, zone, viewUtil);
+
+ const componentId = component.id.replace(ATTR_SANITIZER, '_');
+ this.contentAttr = replaceNgAttribute(CONTENT_ATTR, componentId);
+ this.hostAttr = replaceNgAttribute(HOST_ATTR, componentId);
+ this.addStyles(component.styles, componentId);
+ }
+
+ applyToHost(view: NgView) {
+ super.setAttribute(view, this.hostAttr, '');
+ }
+
+ appendChild(parent: any, newChild: NgView): void {
+ super.appendChild(parent, newChild);
+ }
+
+ createElement(parent: any, name: string): NgView {
+ const view = super.createElement(parent, name);
+
+ // Set an attribute to the view to scope component-specific css.
+ // The property name is pre-generated by Angular.
+ super.setAttribute(view, this.contentAttr, '');
+
+ return view;
+ }
+
+ @profile
+ private addStyles(styles: (string | any[])[], componentId: string) {
+ styles
+ .map((s) => s.toString())
+ .map((s) => replaceNgAttribute(s, componentId))
+ .forEach(addScopedStyleToCss);
+ }
+}
diff --git a/nativescript-angular/renderer-factory.ts b/nativescript-angular/renderer-factory.ts
new file mode 100644
index 000000000..d77694b56
--- /dev/null
+++ b/nativescript-angular/renderer-factory.ts
@@ -0,0 +1,71 @@
+import { Inject, Injectable, RendererFactory2, Optional, NgZone, RendererType2, ViewEncapsulation } from '@angular/core';
+import { View, getViewById, Application, profile, Device } from '@nativescript/core';
+import { APP_ROOT_VIEW, getRootPage } from './platform-providers';
+import { ViewUtil } from './view-util';
+import { NgView, InvisibleNode } from './element-registry';
+import { NativeScriptDebug } from './trace';
+import { NativeScriptRenderer } from './renderer';
+import { EmulatedRenderer } from './renderer-emulated';
+
+const addStyleToCss = profile('"renderer".addStyleToCss', function addStyleToCss(style: string): void {
+ Application.addCss(style);
+});
+
+@Injectable()
+export class NativeScriptRendererFactory implements RendererFactory2 {
+ componentRenderers = new Map();
+ viewUtil: ViewUtil;
+ defaultRenderer: NativeScriptRenderer;
+ rootNgView: NgView;
+
+ constructor(@Optional() @Inject(APP_ROOT_VIEW) rootView: View, private zone: NgZone) {
+ this.viewUtil = new ViewUtil(Device);
+ this.setRootNgView(rootView);
+ this.defaultRenderer = new NativeScriptRenderer(this.rootNgView, zone, this.viewUtil);
+ }
+
+ private setRootNgView(rootView: any) {
+ if (!rootView) {
+ rootView = getRootPage();
+ }
+
+ rootView.nodeName = 'NONE';
+ this.rootNgView = rootView;
+ }
+
+ createRenderer(element: any, type: RendererType2): NativeScriptRenderer {
+ if (!element || !type) {
+ return this.defaultRenderer;
+ }
+
+ let renderer = this.componentRenderers.get(type.id);
+ if (renderer) {
+ if (renderer instanceof EmulatedRenderer) {
+ renderer.applyToHost(element);
+ }
+
+ return renderer;
+ }
+
+ if (type.encapsulation === ViewEncapsulation.None) {
+ type.styles.map((s) => s.toString()).forEach(addStyleToCss);
+ renderer = this.defaultRenderer;
+ } else {
+ renderer = new EmulatedRenderer(type, this.rootNgView, this.zone, this.viewUtil);
+ (renderer).applyToHost(element);
+ }
+
+ this.componentRenderers.set(type.id, renderer);
+ return renderer;
+ }
+
+ ngOnDestroy(): void {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog(`NativeScriptRendererFactory.ngOnDestroy()`);
+ }
+
+ while (this.rootNgView && this.rootNgView.firstChild) {
+ this.viewUtil.removeChild(this.rootNgView, this.rootNgView.firstChild);
+ }
+ }
+}
diff --git a/nativescript-angular/renderer.ts b/nativescript-angular/renderer.ts
new file mode 100644
index 000000000..99e5fd7b9
--- /dev/null
+++ b/nativescript-angular/renderer.ts
@@ -0,0 +1,227 @@
+import { Inject, Injectable, Optional, NgZone, Renderer2, RendererFactory2, RendererType2, RendererStyleFlags2, ViewEncapsulation } from '@angular/core';
+
+import { View, getViewById, profile } from '@nativescript/core';
+
+import { ViewUtil } from './view-util';
+import { NgView, InvisibleNode } from './element-registry';
+import { NativeScriptDebug } from './trace';
+
+@Injectable()
+export class NativeScriptRenderer extends Renderer2 {
+ data: { [key: string]: any } = Object.create(null);
+
+ constructor(private rootView: NgView, private zone: NgZone, private viewUtil: ViewUtil) {
+ super();
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog('NativeScriptRenderer created');
+ }
+ }
+
+ @profile
+ appendChild(parent: NgView, newChild: NgView): void {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog(`NativeScriptRenderer.appendChild child: ${newChild} parent: ${parent}`);
+ }
+ this.viewUtil.insertChild(parent, newChild);
+ }
+
+ @profile
+ insertBefore(parent: NgView, newChild: NgView, refChild: NgView): void {
+ let { previous, next } = refChild instanceof View ? { previous: refChild.previousSibling, next: refChild } : { previous: null, next: null };
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog(`NativeScriptRenderer.insertBefore child: ${newChild} ` + `parent: ${parent} previous: ${previous} next: ${next}`);
+ }
+ this.viewUtil.insertChild(parent, newChild, previous, next);
+ }
+
+ @profile
+ removeChild(parent: any, oldChild: NgView): void {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog(`NativeScriptRenderer.removeChild child: ${oldChild} parent: ${parent}`);
+ }
+ this.viewUtil.removeChild(parent, oldChild);
+ }
+
+ @profile
+ selectRootElement(selector: string): NgView {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog(`NativeScriptRenderer.selectRootElement: ${selector}`);
+ }
+ if (selector && selector[0] === '#') {
+ const result = getViewById(this.rootView, selector.slice(1));
+ return (result || this.rootView) as NgView;
+ }
+ return this.rootView;
+ }
+
+ @profile
+ parentNode(node: NgView): any {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog(`NativeScriptRenderer.parentNode for node: ${node} is ${node.parentNode}`);
+ }
+ return node.parentNode;
+ }
+
+ @profile
+ nextSibling(node: NgView): NgView {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog(`NativeScriptRenderer.nextSibling of ${node} is ${node.nextSibling}`);
+ }
+
+ return node.nextSibling;
+ }
+
+ @profile
+ createComment(_value: any): InvisibleNode {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog(`NativeScriptRenderer.createComment ${_value}`);
+ }
+ return this.viewUtil.createComment();
+ }
+
+ @profile
+ createElement(name: any, _namespace: string): NgView {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog(`NativeScriptRenderer.createElement: ${name}`);
+ }
+ return this.viewUtil.createView(name);
+ }
+
+ @profile
+ createText(_value: string): InvisibleNode {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog(`NativeScriptRenderer.createText ${_value}`);
+ }
+ return this.viewUtil.createText();
+ }
+
+ @profile
+ createViewRoot(hostElement: NgView): NgView {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog(`NativeScriptRenderer.createViewRoot ${hostElement.nodeName}`);
+ }
+ return hostElement;
+ }
+
+ @profile
+ projectNodes(parentElement: NgView, nodes: NgView[]): void {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog('NativeScriptRenderer.projectNodes');
+ }
+ nodes.forEach((node) => this.viewUtil.insertChild(parentElement, node));
+ }
+
+ @profile
+ destroy() {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog('NativeScriptRenderer.destroy');
+ }
+ // Seems to be called on component dispose only (router outlet)
+ // TODO: handle this when we resolve routing and navigation.
+ }
+
+ @profile
+ setAttribute(view: NgView, name: string, value: string, namespace?: string) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog(`NativeScriptRenderer.setAttribute ${view} : ${name} = ${value}, namespace: ${namespace}`);
+ }
+ return this.viewUtil.setProperty(view, name, value, namespace);
+ }
+
+ @profile
+ removeAttribute(_el: NgView, _name: string): void {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog(`NativeScriptRenderer.removeAttribute ${_el}: ${_name}`);
+ }
+ }
+
+ @profile
+ setProperty(view: any, name: string, value: any) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog(`NativeScriptRenderer.setProperty ${view} : ${name} = ${value}`);
+ }
+ return this.viewUtil.setProperty(view, name, value);
+ }
+
+ @profile
+ addClass(view: NgView, name: string): void {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog(`NativeScriptRenderer.addClass ${name}`);
+ }
+ this.viewUtil.addClass(view, name);
+ }
+
+ @profile
+ removeClass(view: NgView, name: string): void {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog(`NativeScriptRenderer.removeClass ${name}`);
+ }
+ this.viewUtil.removeClass(view, name);
+ }
+
+ @profile
+ setStyle(view: NgView, styleName: string, value: any, _flags?: RendererStyleFlags2): void {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog(`NativeScriptRenderer.setStyle: ${styleName} = ${value}`);
+ }
+ this.viewUtil.setStyle(view, styleName, value);
+ }
+
+ @profile
+ removeStyle(view: NgView, styleName: string, _flags?: RendererStyleFlags2): void {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog('NativeScriptRenderer.removeStyle: ${styleName}');
+ }
+ this.viewUtil.removeStyle(view, styleName);
+ }
+
+ // Used only in debug mode to serialize property changes to comment nodes,
+ // such as placeholders.
+ @profile
+ setBindingDebugInfo(renderElement: NgView, propertyName: string, propertyValue: string): void {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog(`NativeScriptRenderer.setBindingDebugInfo: ${renderElement}, ${propertyName} = ${propertyValue}`);
+ }
+ }
+
+ @profile
+ setElementDebugInfo(renderElement: any, _info: any /*RenderDebugInfo*/): void {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog(`NativeScriptRenderer.setElementDebugInfo: ${renderElement}`);
+ }
+ }
+
+ @profile
+ invokeElementMethod(_renderElement: NgView, methodName: string, args: Array) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog(`NativeScriptRenderer.invokeElementMethod ${methodName} ${args}`);
+ }
+ }
+
+ @profile
+ setValue(_renderNode: any, _value: string) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog(`NativeScriptRenderer.setValue renderNode: ${_renderNode}, value: ${_value}`);
+ }
+ }
+
+ @profile
+ listen(renderElement: any, eventName: string, callback: (event: any) => boolean): () => void {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.rendererLog(`NativeScriptRenderer.listen: ${eventName}`);
+ }
+ // Explicitly wrap in zone
+ let zonedCallback = (...args) => {
+ this.zone.run(() => {
+ callback.apply(undefined, args);
+ });
+ };
+
+ renderElement.on(eventName, zonedCallback);
+ if (eventName === View.loadedEvent && renderElement.isLoaded) {
+ const notifyData = { eventName: View.loadedEvent, object: renderElement };
+ zonedCallback(notifyData);
+ }
+ return () => renderElement.off(eventName, zonedCallback);
+ }
+}
diff --git a/nativescript-angular/resource-loader.ts b/nativescript-angular/resource-loader.ts
new file mode 100644
index 000000000..dec0228ce
--- /dev/null
+++ b/nativescript-angular/resource-loader.ts
@@ -0,0 +1,53 @@
+import { Injectable } from '@angular/core';
+import { ResourceLoader } from '@angular/compiler';
+import { path } from '@nativescript/core';
+
+import { NSFileSystem } from './file-system/ns-file-system';
+
+const sourceExtensionsMap = {
+ '.scss': '.css',
+ '.sass': '.css',
+ '.less': '.css',
+};
+
+@Injectable()
+export class FileSystemResourceLoader extends ResourceLoader {
+ constructor(private fs: NSFileSystem) {
+ super();
+ }
+
+ get(url: string): string {
+ const resolvedPath = this.resolve(url);
+
+ const templateFile = this.fs.fileFromPath(resolvedPath);
+
+ return templateFile.readTextSync();
+ }
+
+ resolve(url: string): string {
+ const normalizedSourceUrl = this.resolveRelativeUrls(url);
+ const normalizedCompiledFileUrl = normalizedSourceUrl.replace(/\.\w+$/, (ext) => sourceExtensionsMap[ext] || ext);
+ if (normalizedCompiledFileUrl !== normalizedSourceUrl && this.fs.fileExists(normalizedCompiledFileUrl)) {
+ return normalizedCompiledFileUrl;
+ }
+ if (this.fs.fileExists(normalizedSourceUrl)) {
+ return normalizedSourceUrl;
+ }
+
+ if (normalizedCompiledFileUrl === normalizedSourceUrl) {
+ throw new Error(`Could not resolve ${url}. Looked for: ${normalizedSourceUrl}.`);
+ } else {
+ throw new Error(`Could not resolve ${url}.` + `Looked for: ${normalizedCompiledFileUrl}, ${normalizedSourceUrl}.`);
+ }
+ }
+
+ private resolveRelativeUrls(url: string): string {
+ // Angular assembles absolute URLs and prefixes them with //
+ if (url.indexOf('/') !== 0) {
+ // Resolve relative URLs based on the app root.
+ return path.join(this.fs.currentApp().path, url);
+ } else {
+ return url;
+ }
+ }
+}
diff --git a/nativescript-angular/router/index.ts b/nativescript-angular/router/index.ts
new file mode 100644
index 000000000..321e53ae6
--- /dev/null
+++ b/nativescript-angular/router/index.ts
@@ -0,0 +1,3 @@
+export * from './ns-location-strategy';
+export * from './ns-route-reuse-strategy';
+export * from './router.module';
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..b94a643e2
--- /dev/null
+++ b/nativescript-angular/router/ns-empty-outlet.component.ts
@@ -0,0 +1,22 @@
+import { Component, ViewChild } from '@angular/core';
+import { Page } from '@nativescript/core';
+import { PageRouterOutlet } from './page-router-outlet';
+@Component({
+ // tslint:disable-next-line:component-selector
+ selector: 'ns-empty-outlet',
+ template: "",
+})
+export class NSEmptyOutletComponent {
+ @ViewChild(PageRouterOutlet, { read: PageRouterOutlet, static: false }) pageRouterOutlet: PageRouterOutlet;
+ constructor(private page: Page) {
+ if (this.page) {
+ this.page.actionBarHidden = true;
+
+ this.page.on('loaded', () => {
+ if (this.pageRouterOutlet && this.page.frame) {
+ this.pageRouterOutlet.setActionBarVisibility(this.page.frame.actionBarVisibility);
+ }
+ });
+ }
+ }
+}
diff --git a/nativescript-angular/router/ns-location-strategy.ts b/nativescript-angular/router/ns-location-strategy.ts
new file mode 100644
index 000000000..4c0088c8b
--- /dev/null
+++ b/nativescript-angular/router/ns-location-strategy.ts
@@ -0,0 +1,671 @@
+import { Injectable } from '@angular/core';
+import { LocationStrategy } from '@angular/common';
+import { DefaultUrlSerializer, UrlSegmentGroup, UrlTree, ActivatedRouteSnapshot, Params } from '@angular/router';
+import { Frame } from '@nativescript/core';
+import { NativeScriptDebug } from '../trace';
+import { isPresent } from '../lang-facade';
+import { FrameService } from '../frame.service';
+import { Outlet, NavigationOptions, LocationState, defaultNavOptions } from './ns-location-utils';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class NSLocationStrategy extends LocationStrategy {
+ private outlets: Array = [];
+ private currentOutlet: Outlet;
+
+ private popStateCallbacks = new Array<(_: any) => any>();
+ private _currentNavigationOptions: NavigationOptions;
+ private currentUrlTree: UrlTree;
+
+ public _modalNavigationDepth = 0;
+
+ constructor(private frameService: FrameService) {
+ super();
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('NSLocationStrategy.constructor()');
+ }
+ }
+
+ path(): string {
+ if (!this.currentUrlTree) {
+ return '/';
+ }
+
+ const state = this.currentOutlet && this.currentOutlet.peekState();
+ if (!state) {
+ return '/';
+ }
+
+ let tree = this.currentUrlTree;
+ let changedOutlet = this.getSegmentGroupByOutlet(this.currentOutlet);
+
+ // 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 if (changedOutlet) {
+ this.updateSegmentGroup(tree.root, changedOutlet, state.segmentGroup);
+ }
+
+ const urlSerializer = new DefaultUrlSerializer();
+ tree.queryParams = state.queryParams;
+ const url = urlSerializer.serialize(tree);
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('NSLocationStrategy.path(): ' + url);
+ }
+ return url;
+ }
+
+ prepareExternalUrl(internal: string): string {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('NSLocationStrategy.prepareExternalUrl() internal: ' + internal);
+ }
+ return internal;
+ }
+
+ pushState(state: any, title: string, url: string, queryParams: string): void {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('NSLocationStrategy.pushState state: ' + `${state}, title: ${title}, url: ${url}, queryParams: ${queryParams}`);
+ }
+ this.pushStateInternal(state, title, url, queryParams);
+ }
+
+ pushStateInternal(state: any, title: string, url: string, queryParams: string): void {
+ const urlSerializer = new DefaultUrlSerializer();
+ this.currentUrlTree = urlSerializer.parse(url);
+ const urlTreeRoot = this.currentUrlTree.root;
+
+ // 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(urlTreeRoot.children).length) {
+ const segmentGroup = this.currentUrlTree && this.currentUrlTree.root;
+ const outletKey = this.getOutletKey(this.getSegmentGroupFullPath(segmentGroup), 'primary');
+ const outlet = this.findOutlet(outletKey);
+
+ if (outlet && this.updateStates(outlet, segmentGroup, this.currentUrlTree.queryParams)) {
+ this.currentOutlet = outlet; // If states updated
+ } else if (!outlet) {
+ // tslint:disable-next-line:max-line-length
+ const rootOutlet = this.createOutlet('primary', null, segmentGroup, null, null, this.currentUrlTree.queryParams);
+ this.currentOutlet = rootOutlet;
+ }
+
+ this.currentOutlet.peekState().isRootSegmentGroup = true;
+ return;
+ }
+
+ const queue = [];
+ let currentTree = urlTreeRoot;
+
+ while (currentTree) {
+ Object.keys(currentTree.children).forEach((outletName) => {
+ const currentSegmentGroup = currentTree.children[outletName];
+ currentSegmentGroup.outlet = outletName;
+ currentSegmentGroup.root = urlTreeRoot;
+
+ const outletPath = this.getSegmentGroupFullPath(currentTree);
+ let outletKey = this.getOutletKey(outletPath, outletName);
+ let outlet = this.findOutlet(outletKey);
+
+ const parentOutletName = currentTree.outlet || '';
+ const parentOutletPath = this.getSegmentGroupFullPath(currentTree.parent);
+ const parentOutletKey = this.getOutletKey(parentOutletPath, parentOutletName);
+ const parentOutlet = this.findOutlet(parentOutletKey);
+
+ const containsLastState = outlet && outlet.containsTopState(currentSegmentGroup.toString());
+ if (!outlet) {
+ // tslint:disable-next-line:max-line-length
+ outlet = this.createOutlet(outletKey, outletPath, currentSegmentGroup, parentOutlet, this._modalNavigationDepth, this.currentUrlTree.queryParams);
+ this.currentOutlet = outlet;
+ } else if (this._modalNavigationDepth > 0 && outlet.showingModal && !containsLastState) {
+ // Navigation inside modal view.
+ this.upsertModalOutlet(outlet, currentSegmentGroup, this.currentUrlTree.queryParams);
+ } else {
+ outlet.parent = parentOutlet;
+
+ if (this.updateStates(outlet, currentSegmentGroup, this.currentUrlTree.queryParams)) {
+ this.currentOutlet = outlet; // If states updated
+ }
+ }
+
+ queue.push(currentSegmentGroup);
+ });
+
+ currentTree = queue.shift();
+ }
+ }
+
+ replaceState(state: any, title: string, url: string, queryParams: string): void {
+ const states = this.currentOutlet && this.currentOutlet.states;
+
+ if (states && states.length > 0) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('NSLocationStrategy.replaceState changing existing state: ' + `${state}, title: ${title}, url: ${url}, queryParams: ${queryParams}`);
+ }
+ } else {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('NSLocationStrategy.replaceState pushing new state: ' + `${state}, title: ${title}, url: ${url}, queryParams: ${queryParams}`);
+ }
+ this.pushStateInternal(state, title, url, queryParams);
+ }
+ }
+
+ forward(): void {
+ throw new Error('NSLocationStrategy.forward() - not implemented');
+ }
+
+ back(outlet?: Outlet, frame?: Frame): void {
+ this.currentOutlet = outlet || 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();
+ let count = 1;
+
+ if (frame) {
+ while (state.frame && state.frame !== frame) {
+ state = states.pop();
+ count++;
+ }
+ }
+
+ while (!state.isPageNavigation) {
+ state = states.pop();
+ count++;
+ }
+
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog(`NSLocationStrategy.back() while navigating back. States popped: ${count}`);
+ }
+ this.callPopState(state, true);
+ } else {
+ let state = this.currentOutlet.peekState();
+ if (state && state.isPageNavigation) {
+ // This was a page navigation - so navigate through frame.
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('NSLocationStrategy.back() while not navigating back but top' + ' state is page - will call frame.goBack()');
+ }
+
+ if (!outlet) {
+ const topmostFrame = this.frameService.getFrame();
+ this.currentOutlet = this.getOutletByFrame(topmostFrame) || this.currentOutlet;
+ }
+
+ const frameToBack: Frame = this.currentOutlet.getFrameToBack();
+ if (frameToBack) {
+ frameToBack.goBack();
+ }
+ } else {
+ // Nested navigation - just pop the state
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('NSLocationStrategy.back() while not navigating back but top' + ' state is not page - just pop');
+ }
+
+ this.callPopState(this.currentOutlet.states.pop(), true);
+ }
+ }
+ }
+
+ canGoBack(outlet?: Outlet) {
+ outlet = outlet || this.currentOutlet;
+ return outlet.states.length > 1;
+ }
+
+ onPopState(fn: (_: any) => any): void {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('NSLocationStrategy.onPopState');
+ }
+ this.popStateCallbacks.push(fn);
+ }
+
+ getBaseHref(): string {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('NSLocationStrategy.getBaseHref()');
+ }
+ return '';
+ }
+
+ private callPopState(state: LocationState, pop: boolean = true, outlet?: Outlet) {
+ outlet = outlet || this.currentOutlet;
+ const urlSerializer = new DefaultUrlSerializer();
+ let changedOutlet = this.getSegmentGroupByOutlet(outlet);
+
+ 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.
+ this.updateSegmentGroup(this.currentUrlTree.root, changedOutlet, null);
+ }
+
+ const url = urlSerializer.serialize(this.currentUrlTree);
+ const change = { url: url, pop: pop };
+ for (let fn of this.popStateCallbacks) {
+ fn(change);
+ }
+ }
+
+ public toString() {
+ let result = [];
+
+ this.outlets.forEach((outlet) => {
+ const outletStates = outlet.states;
+ const outletLog = outletStates
+ // tslint:disable-next-line:max-line-length
+ .map((v, i) => `${outlet.outletKeys}.${i}.[${v.isPageNavigation ? 'PAGE' : 'INTERNAL'}].[${outlet.modalNavigationDepth ? 'MODAL' : 'BASE'}] "${v.segmentGroup.toString()}"`)
+ .reverse();
+
+ result = result.concat(outletLog);
+ });
+
+ return result.join('\n');
+ }
+
+ // Methods for syncing with page navigation in PageRouterOutlet
+ public _beginBackPageNavigation(frame: Frame) {
+ const outlet: Outlet = this.getOutletByFrame(frame);
+
+ if (!outlet || outlet.isPageNavigationBack) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerError('Attempted to call startGoBack while going back.');
+ }
+ return;
+ }
+
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('NSLocationStrategy.startGoBack()');
+ }
+ outlet.isPageNavigationBack = true;
+
+ this.currentOutlet = outlet;
+ }
+
+ public _finishBackPageNavigation(frame: Frame) {
+ const outlet: Outlet = this.getOutletByFrame(frame);
+
+ if (!outlet || !outlet.isPageNavigationBack) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerError('Attempted to call endGoBack while not going back.');
+ }
+ return;
+ }
+
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('NSLocationStrategy.finishBackPageNavigation()');
+ }
+ outlet.isPageNavigationBack = false;
+ }
+
+ public _beginModalNavigation(frame: Frame): void {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('NSLocationStrategy._beginModalNavigation()');
+ }
+
+ this.currentOutlet = this.getOutletByFrame(frame) || this.currentOutlet;
+
+ // It is possible to have frame, but not corresponding Outlet, if
+ // showing modal dialog on app.component.ts ngOnInit() e.g. In that case
+ // the modal is treated as none modal navigation.
+ if (this.currentOutlet) {
+ this.currentOutlet.showingModal = true;
+ this._modalNavigationDepth++;
+ }
+ }
+
+ public _closeModalNavigation() {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('NSLocationStrategy.closeModalNavigation()');
+ }
+
+ const isShowingModal = this._modalNavigationDepth > 0;
+
+ if (isShowingModal) {
+ this._modalNavigationDepth--;
+ }
+
+ // currentOutlet should be the one that corresponds to the topmost frame
+ const topmostOutlet = this.getOutletByFrame(this.frameService.getFrame());
+ const outlet = this.findOutletByModal(this._modalNavigationDepth, isShowingModal) || topmostOutlet;
+
+ if (outlet) {
+ this.currentOutlet = outlet;
+ this.currentOutlet.showingModal = false;
+ this.callPopState(this.currentOutlet.peekState(), false);
+ }
+ }
+
+ public _beginPageNavigation(frame: Frame): NavigationOptions {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('NSLocationStrategy._beginPageNavigation()');
+ }
+
+ this.currentOutlet = this.getOutletByFrame(frame) || this.currentOutlet;
+ const lastState = this.currentOutlet.peekState();
+
+ if (lastState) {
+ lastState.isPageNavigation = true;
+ }
+
+ const navOptions = this._currentNavigationOptions || defaultNavOptions;
+ if (navOptions.clearHistory) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('NSLocationStrategy._beginPageNavigation clearing states history');
+ }
+ this.currentOutlet.states = [lastState];
+ }
+
+ this._currentNavigationOptions = undefined;
+ return navOptions;
+ }
+
+ public _setNavigationOptions(options: NavigationOptions) {
+ this._currentNavigationOptions = {
+ clearHistory: isPresent(options.clearHistory) ? options.clearHistory : false,
+ animated: isPresent(options.animated) ? options.animated : true,
+ transition: options.transition,
+ };
+
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('NSLocationStrategy._setNavigationOptions(' + `${JSON.stringify(this._currentNavigationOptions)})`);
+ }
+ }
+
+ public _getOutlets(): Array {
+ return this.outlets;
+ }
+
+ updateOutletFrame(outlet: Outlet, frame: Frame, isEmptyOutletFrame: boolean) {
+ const lastState = outlet.peekState();
+
+ if (lastState && !lastState.frame && !isEmptyOutletFrame) {
+ lastState.frame = frame;
+ }
+
+ if (!outlet.containsFrame(frame)) {
+ outlet.frames.push(frame);
+ }
+ this.currentOutlet = outlet;
+ }
+
+ clearOutlet(frame: Frame) {
+ this.outlets = this.outlets.filter((currentOutlet) => {
+ let isEqualToCurrent;
+
+ if (this.currentOutlet) {
+ isEqualToCurrent = currentOutlet.pathByOutlets === this.currentOutlet.pathByOutlets;
+ }
+
+ // Remove outlet from the url tree.
+ if (currentOutlet.containsFrame(frame) && !isEqualToCurrent) {
+ this.callPopState(null, true, currentOutlet);
+ }
+
+ // Skip frames filtering since currentOutlet is when no frames available.
+ if (currentOutlet.frames.length && !currentOutlet.isNSEmptyOutlet) {
+ currentOutlet.frames = currentOutlet.frames.filter((currentFrame) => currentFrame !== frame);
+ return currentOutlet.frames.length;
+ }
+
+ return !currentOutlet.containsFrame(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;
+ }
+
+ findOutlet(outletKey: string, activatedRouteSnapshot?: ActivatedRouteSnapshot): Outlet {
+ let outlet: Outlet = this.outlets.find((currentOutlet) => {
+ let equalModalDepth = currentOutlet.modalNavigationDepth === this._modalNavigationDepth;
+ return equalModalDepth && currentOutlet.outletKeys.indexOf(outletKey) > -1;
+ });
+
+ // No Outlet with the given outletKey could happen when using nested unnamed p-r-o
+ // primary -> primary -> prymary
+ if (!outlet && activatedRouteSnapshot) {
+ const pathByOutlets = this.getPathByOutlets(activatedRouteSnapshot);
+ outlet = this.outlets.find((currentOutlet) => {
+ let equalModalDepth = currentOutlet.modalNavigationDepth === this._modalNavigationDepth;
+ return equalModalDepth && currentOutlet.pathByOutlets === pathByOutlets;
+ });
+ }
+
+ return outlet;
+ }
+
+ private findOutletByModal(modalNavigation: number, isShowingModal?: boolean): Outlet {
+ return this.outlets.find((outlet) => {
+ let equalModalDepth = outlet.modalNavigationDepth === modalNavigation;
+ return isShowingModal ? equalModalDepth && outlet.showingModal : equalModalDepth;
+ });
+ }
+
+ private getOutletByFrame(frame: Frame): Outlet {
+ let outlet;
+
+ for (let index = 0; index < this.outlets.length; index++) {
+ const currentOutlet = this.outlets[index];
+
+ if (currentOutlet.containsFrame(frame)) {
+ outlet = currentOutlet;
+ break;
+ }
+ }
+
+ return outlet;
+ }
+
+ private updateStates(outlet: Outlet, currentSegmentGroup: UrlSegmentGroup, queryParams: Params): 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,
+ queryParams: { ...queryParams },
+ };
+
+ if (!lastState || !equalStateUrls) {
+ outlet.states.push(locationState);
+
+ // Update last state segmentGroup of parent Outlet.
+ 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;
+ }
+ }
+ }
+
+ // tslint:disable-next-line:max-line-length
+ private createOutlet(outletKey: string, path: string, segmentGroup: any, parent: Outlet, modalNavigation?: number, queryParams: Params = {}): Outlet {
+ const pathByOutlets = this.getPathByOutlets(segmentGroup);
+ const newOutlet = new Outlet(outletKey, path, pathByOutlets, modalNavigation);
+
+ const locationState: LocationState = {
+ segmentGroup: segmentGroup,
+ isRootSegmentGroup: false,
+ isPageNavigation: true, // It is a new OutletNode.
+ queryParams: { ...queryParams },
+ };
+
+ newOutlet.states = [locationState];
+ newOutlet.parent = parent;
+ this.outlets.push(newOutlet);
+
+ // Update last state segmentGroup of parent Outlet.
+ if (this._modalNavigationDepth === 0 && !newOutlet.showingModal) {
+ this.updateParentsStates(newOutlet, segmentGroup.parent);
+ }
+
+ return newOutlet;
+ }
+
+ private getSegmentGroupByOutlet(outlet: Outlet): UrlSegmentGroup {
+ const pathList = outlet.pathByOutlets.split('-');
+ let segmentGroup = this.currentUrlTree.root;
+ let pathToOutlet;
+
+ for (let index = 0; index < pathList.length; index++) {
+ const currentPath = pathList[index];
+ const childrenCount = Object.keys(segmentGroup.children).length;
+
+ if (childrenCount && segmentGroup.children[currentPath]) {
+ const url = segmentGroup.toString();
+ pathToOutlet = pathToOutlet ? pathToOutlet + '/' + url : url;
+ 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;
+ }
+ }
+
+ // Paths should also match since there could be another Outlet
+ // with the same pathByOutlets but different url path.
+ if (segmentGroup && outlet.path && pathToOutlet && outlet.path !== pathToOutlet) {
+ segmentGroup = null;
+ }
+
+ 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, queryParams: Params) {
+ 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 outletPath = parentOutlet.peekState().segmentGroup.toString();
+ const outletKey = this.getOutletKey(outletPath, outletName);
+ // tslint:disable-next-line:max-line-length
+ currentModalOutlet = this.createOutlet(outletKey, outletPath, segmentedGroup, parentOutlet, this._modalNavigationDepth, queryParams);
+ this.currentOutlet = currentModalOutlet;
+ } else if (this.updateStates(currentModalOutlet, segmentedGroup, queryParams)) {
+ this.currentOutlet = currentModalOutlet; // If states updated
+ }
+ }
+
+ private getOutletKey(path: string, outletName: string): string {
+ return path ? path + '-' + outletName : outletName;
+ }
+
+ ngOnDestroy() {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('NSLocationStrategy.ngOnDestroy()');
+ }
+
+ this.outlets = [];
+ this.currentOutlet = null;
+ }
+}
diff --git a/nativescript-angular/router/ns-location-utils.ts b/nativescript-angular/router/ns-location-utils.ts
new file mode 100644
index 000000000..aa5452820
--- /dev/null
+++ b/nativescript-angular/router/ns-location-utils.ts
@@ -0,0 +1,88 @@
+import { Frame, NavigationTransition } from '@nativescript/core';
+import { DefaultUrlSerializer, UrlSegmentGroup, UrlTree, ActivatedRouteSnapshot, Params } from '@angular/router';
+
+export interface LocationState {
+ queryParams: Params;
+ segmentGroup: UrlSegmentGroup;
+ isRootSegmentGroup: boolean;
+ isPageNavigation: boolean;
+ frame?: Frame;
+}
+
+export interface NavigationOptions {
+ clearHistory?: boolean;
+ animated?: boolean;
+ transition?: NavigationTransition;
+}
+
+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;
+
+ // More than one frame available when using NSEmptyOutletComponent component
+ // in module that lazy loads children (loadChildren) and has outlet name.
+ frames: Array = [];
+ // The url path to the Outlet.
+ // E.G: the path to Outlet3 that is nested Outlet1(home)->Outlet2(nested1)->Outlet3(nested2)
+ // will be 'home/nested1'
+ path: string;
+ pathByOutlets: string;
+ states: Array = [];
+ isNSEmptyOutlet: boolean;
+
+ // Used in reuse-strategy by its children to determine if they should be detached too.
+ shouldDetach: boolean = true;
+ constructor(outletKey: string, path: string, pathByOutlets: string, modalNavigationDepth?: number) {
+ this.outletKeys = [outletKey];
+ this.isPageNavigationBack = false;
+ this.showingModal = false;
+ this.modalNavigationDepth = modalNavigationDepth || 0;
+ this.pathByOutlets = pathByOutlets;
+ this.path = path;
+ }
+
+ containsFrame(frame: Frame): boolean {
+ return this.frames.indexOf(frame) > -1;
+ }
+
+ 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;
+ }
+
+ // Search for frame that can go back.
+ // Nested 'primary' outlets could result in Outlet with multiple navigatable frames.
+ getFrameToBack(): Frame {
+ let frame = this.frames[this.frames.length - 1];
+
+ if (!this.isNSEmptyOutlet) {
+ for (let index = this.frames.length - 1; index >= 0; index--) {
+ const currentFrame = this.frames[index];
+ if (currentFrame.canGoBack()) {
+ frame = currentFrame;
+ break;
+ }
+ }
+ }
+
+ return frame;
+ }
+}
+
+export const defaultNavOptions: NavigationOptions = {
+ clearHistory: false,
+ animated: true,
+};
diff --git a/nativescript-angular/router/ns-module-factory-loader.ts b/nativescript-angular/router/ns-module-factory-loader.ts
new file mode 100644
index 000000000..4d825ad02
--- /dev/null
+++ b/nativescript-angular/router/ns-module-factory-loader.ts
@@ -0,0 +1,9 @@
+import { Compiler, Injectable, Optional, SystemJsNgModuleLoader, SystemJsNgModuleLoaderConfig } from '@angular/core';
+
+@Injectable()
+export class NSModuleFactoryLoader extends SystemJsNgModuleLoader {
+ constructor(compiler: Compiler, @Optional() config?: SystemJsNgModuleLoaderConfig) {
+ super(compiler, config);
+ console.log(`NSModuleFactoryLoader is deprecated! ` + `You no longer need to provide it as a module loader.`);
+ }
+}
diff --git a/nativescript-angular/router/ns-platform-location.ts b/nativescript-angular/router/ns-platform-location.ts
new file mode 100644
index 000000000..bd2dbdca7
--- /dev/null
+++ b/nativescript-angular/router/ns-platform-location.ts
@@ -0,0 +1,62 @@
+import { NSLocationStrategy } from './ns-location-strategy';
+import { PlatformLocation, LocationChangeListener } from '@angular/common';
+import { Injectable } from '@angular/core';
+import { NativeScriptDebug } from '../trace';
+
+@Injectable()
+export class NativescriptPlatformLocation extends PlatformLocation {
+ constructor(private locationStrategy: NSLocationStrategy) {
+ super();
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('NativescriptPlatformLocation.constructor()');
+ }
+ }
+
+ getState(): any {
+ return undefined;
+ }
+
+ readonly hostname: string;
+ readonly href: string;
+ readonly port: string;
+ readonly protocol: string;
+
+ getBaseHrefFromDOM(): string {
+ return '/';
+ }
+
+ onPopState(fn: LocationChangeListener): void {
+ this.locationStrategy.onPopState(fn);
+ }
+
+ onHashChange(_fn: LocationChangeListener): void {}
+
+ get search(): string {
+ return '';
+ }
+ get hash(): string {
+ return '';
+ }
+ get pathname(): string {
+ return this.locationStrategy.path();
+ }
+ set pathname(_newPath: string) {
+ throw new Error('NativescriptPlatformLocation set pathname - not implemented');
+ }
+
+ pushState(state: any, title: string, url: string): void {
+ this.locationStrategy.pushState(state, title, url, null);
+ }
+
+ replaceState(state: any, title: string, url: string): void {
+ this.locationStrategy.replaceState(state, title, url, null);
+ }
+
+ forward(): void {
+ throw new Error('NativescriptPlatformLocation.forward() - not implemented');
+ }
+
+ back(): void {
+ this.locationStrategy.back();
+ }
+}
diff --git a/nativescript-angular/router/ns-route-reuse-strategy.ts b/nativescript-angular/router/ns-route-reuse-strategy.ts
new file mode 100644
index 000000000..e05179c4e
--- /dev/null
+++ b/nativescript-angular/router/ns-route-reuse-strategy.ts
@@ -0,0 +1,240 @@
+import { Injectable } from '@angular/core';
+import { RouteReuseStrategy, ActivatedRouteSnapshot, DetachedRouteHandle } from '@angular/router';
+
+import { NativeScriptDebug } from '../trace';
+import { NSLocationStrategy } from './ns-location-strategy';
+import { destroyComponentRef, findTopActivatedRouteNodeForOutlet, pageRouterActivatedSymbol } from './page-router-outlet-utils';
+
+interface CacheItem {
+ key: string;
+ state: DetachedRouteHandle;
+ isModal: boolean;
+}
+
+const getSnapshotKey = function (snapshot: ActivatedRouteSnapshot): string {
+ return snapshot.pathFromRoot.join('->');
+};
+
+/**
+ * Detached state cache
+ */
+class DetachedStateCache {
+ private cache = new Array();
+
+ public get length(): number {
+ return this.cache.length;
+ }
+
+ public push(cacheItem: CacheItem) {
+ this.cache.push(cacheItem);
+ }
+
+ public pop(): CacheItem {
+ return this.cache.pop();
+ }
+
+ public peek(): CacheItem {
+ return this.cache[this.cache.length - 1];
+ }
+
+ public clear() {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routeReuseStrategyLog(`DetachedStateCache.clear() ${this.cache.length} items will be destroyed`);
+ }
+
+ while (this.cache.length > 0) {
+ const state = this.cache.pop().state;
+ if (!state.componentRef) {
+ throw new Error('No componentRed found in DetachedRouteHandle');
+ }
+
+ destroyComponentRef(state.componentRef);
+ }
+ }
+
+ public clearModalCache() {
+ let removedItemsCount = 0;
+ const hasModalPages = this.cache.some((cacheItem) => {
+ return cacheItem.isModal;
+ });
+
+ if (hasModalPages) {
+ let modalCacheCleared = false;
+
+ while (!modalCacheCleared) {
+ let cacheItem = this.peek();
+ const state = cacheItem.state;
+
+ if (!state.componentRef) {
+ throw new Error('No componentRef found in DetachedRouteHandle');
+ }
+
+ destroyComponentRef(state.componentRef);
+ if (cacheItem.isModal) {
+ modalCacheCleared = true;
+ }
+
+ this.pop();
+ removedItemsCount++;
+ }
+ }
+
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routeReuseStrategyLog(`DetachedStateCache.clearModalCache() ${removedItemsCount} items will be destroyed`);
+ }
+ }
+}
+
+/**
+ * Detaches subtrees loaded inside PageRouterOutlet in forward navigation
+ * and reattaches them on back.
+ * Reuses routes as long as their route config is the same.
+ */
+@Injectable()
+export class NSRouteReuseStrategy implements RouteReuseStrategy {
+ private cacheByOutlet: { [key: string]: DetachedStateCache } = {};
+
+ constructor(private location: NSLocationStrategy) {}
+
+ shouldDetach(route: ActivatedRouteSnapshot): boolean {
+ route = findTopActivatedRouteNodeForOutlet(route);
+
+ const outletKey = this.location.getRouteFullPath(route);
+ const outlet = this.location.findOutlet(outletKey, route);
+ const key = getSnapshotKey(route);
+ const isPageActivated = route[pageRouterActivatedSymbol];
+ 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 (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routeReuseStrategyLog(`shouldDetach isBack: ${isBack} key: ${key} result: ${shouldDetach}`);
+ }
+
+ return shouldDetach;
+ }
+
+ shouldAttach(route: ActivatedRouteSnapshot): boolean {
+ route = findTopActivatedRouteNodeForOutlet(route);
+
+ const outletKey = this.location.getRouteFullPath(route);
+ const outlet = this.location.findOutlet(outletKey, route);
+ const cache = this.cacheByOutlet[outletKey];
+ if (!cache) {
+ return false;
+ }
+
+ const key = getSnapshotKey(route);
+ const isBack = outlet ? outlet.isPageNavigationBack : false;
+ const shouldAttach = isBack && cache.peek().key === key;
+
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routeReuseStrategyLog(`shouldAttach isBack: ${isBack} key: ${key} result: ${shouldAttach}`);
+ }
+
+ if (outlet) {
+ outlet.shouldDetach = true;
+ }
+
+ return shouldAttach;
+ }
+
+ store(route: ActivatedRouteSnapshot, state: DetachedRouteHandle): void {
+ route = findTopActivatedRouteNodeForOutlet(route);
+
+ const key = getSnapshotKey(route);
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routeReuseStrategyLog(`store key: ${key}, state: ${state}`);
+ }
+
+ 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._modalNavigationDepth > 0) {
+ isModal = true;
+ }
+
+ cache.push({ key, state, isModal });
+ } else {
+ 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}`);
+ }
+ }
+ }
+
+ retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
+ route = findTopActivatedRouteNodeForOutlet(route);
+
+ const outletKey = this.location.getRouteFullPath(route);
+ const outlet = this.location.findOutlet(outletKey, route);
+ const cache = this.cacheByOutlet[outletKey];
+ if (!cache) {
+ return null;
+ }
+
+ const key = getSnapshotKey(route);
+ const isBack = outlet ? outlet.isPageNavigationBack : false;
+ const cachedItem = cache.peek();
+
+ let state = null;
+ if (isBack && cachedItem && cachedItem.key === key) {
+ state = cachedItem.state;
+ }
+
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routeReuseStrategyLog(`retrieved isBack: ${isBack} key: ${key} state: ${state}`);
+ }
+
+ return state;
+ }
+
+ shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
+ const shouldReuse = future.routeConfig === curr.routeConfig;
+
+ if (shouldReuse && curr && curr[pageRouterActivatedSymbol]) {
+ // When reusing route - copy the pageRouterActivated to the new snapshot
+ // It's needed in shouldDetach to determine if the route should be detached.
+ future[pageRouterActivatedSymbol] = curr[pageRouterActivatedSymbol];
+ }
+
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routeReuseStrategyLog(`shouldReuseRoute result: ${shouldReuse}`);
+ }
+
+ return shouldReuse;
+ }
+
+ clearCache(outletKey: string) {
+ const cache = this.cacheByOutlet[outletKey];
+
+ if (cache) {
+ cache.clear();
+ }
+ }
+
+ clearModalCache(outletKey: string) {
+ const cache = this.cacheByOutlet[outletKey];
+
+ if (cache) {
+ cache.clearModalCache();
+ }
+ }
+}
diff --git a/nativescript-angular/router/ns-router-link-active.ts b/nativescript-angular/router/ns-router-link-active.ts
new file mode 100644
index 000000000..333cba56d
--- /dev/null
+++ b/nativescript-angular/router/ns-router-link-active.ts
@@ -0,0 +1,155 @@
+import { AfterContentInit, ChangeDetectorRef, ContentChildren, Directive, ElementRef, Input, OnChanges, OnDestroy, Optional, QueryList, Renderer2 } from '@angular/core';
+import { from, of, Subscription } from 'rxjs';
+
+import { NavigationEnd, Router, UrlTree } from '@angular/router';
+import { containsTree } from './private-imports/router-url-tree';
+
+import { NSRouterLink } from './ns-router-link';
+import { mergeAll } from 'rxjs/operators';
+
+/**
+ * The NSRouterLinkActive directive lets you add a CSS class to an element when the link"s route
+ * becomes active.
+ *
+ * Consider the following example:
+ *
+ * ```
+ * Bob
+ * ```
+ *
+ * When the url is either "/user" or "/user/bob", the active-link class will
+ * be added to the component. If the url changes, the class will be removed.
+ *
+ * You can set more than one class, as follows:
+ *
+ * ```
+ * Bob
+ * Bob
+ * ```
+ *
+ * You can configure NSRouterLinkActive by passing `exact: true`. This will add the
+ * classes only when the url matches the link exactly.
+ *
+ * ```
+ * Bob
+ * ```
+ *
+ * Finally, you can apply the NSRouterLinkActive directive to an ancestor of a RouterLink.
+ *
+ * ```
+ *
+ * ```
+ *
+ * This will set the active-link class on the div tag if the url is either "/user/jim" or
+ * "/user/bob".
+ *
+ * @stable
+ */
+@Directive({
+ selector: '[nsRouterLinkActive]',
+ exportAs: 'routerLinkActive',
+})
+export class NSRouterLinkActive implements OnChanges, OnDestroy, AfterContentInit {
+ // tslint:disable-line:max-line-length directive-class-suffix
+ @ContentChildren(NSRouterLink, { descendants: true }) links: QueryList;
+
+ private classes: string[] = [];
+ private routerEventsSubscription: Subscription;
+ private linkInputChangesSubscription?: Subscription;
+ private active: boolean = false;
+
+ @Input() nsRouterLinkActiveOptions: { exact: boolean } = { exact: false };
+
+ constructor(private router: Router, private element: ElementRef, private renderer: Renderer2, private readonly cdr: ChangeDetectorRef, @Optional() private link?: NSRouterLink) {
+ this.routerEventsSubscription = router.events.subscribe((s) => {
+ if (s instanceof NavigationEnd) {
+ this.update();
+ }
+ });
+ }
+
+ get isActive(): boolean {
+ return this.active;
+ }
+
+ ngAfterContentInit(): void {
+ // `of(null)` is used to force subscribe body to execute once immediately (like `startWith`).
+ from([this.links.changes, of(null)])
+ .pipe(mergeAll())
+ .subscribe((_) => {
+ this.update();
+ this.subscribeToEachLinkOnChanges();
+ });
+ }
+
+ private subscribeToEachLinkOnChanges() {
+ this.linkInputChangesSubscription?.unsubscribe();
+ const allLinkChanges = [...this.links.toArray(), this.link].filter((link): link is NSRouterLink => !!link).map((link) => link.onChanges);
+ this.linkInputChangesSubscription = from(allLinkChanges)
+ .pipe(mergeAll())
+ .subscribe((link) => {
+ if (this.isActive !== this.isLinkActive(this.router)(link)) {
+ this.update();
+ }
+ });
+ }
+
+ @Input('nsRouterLinkActive')
+ set nsRouterLinkActive(data: string[] | string) {
+ if (Array.isArray(data)) {
+ this.classes = data;
+ } else {
+ this.classes = data.split(' ');
+ }
+ }
+
+ ngOnChanges(_: {}): any {
+ this.update();
+ }
+ ngOnDestroy(): any {
+ this.routerEventsSubscription.unsubscribe();
+ this.linkInputChangesSubscription?.unsubscribe();
+ }
+
+ private update(): void {
+ if (!this.links) {
+ return;
+ }
+ Promise.resolve().then(() => {
+ const hasActiveLinks = this.hasActiveLinks();
+ if (this.active !== hasActiveLinks) {
+ this.active = hasActiveLinks;
+ const currentUrlTree = this.router.parseUrl(this.router.url);
+ const links = this.link ? [...this.links.toArray(), this.link] : this.links;
+ const isActiveLinks = this.reduceList(currentUrlTree, links);
+ this.cdr.markForCheck();
+ this.classes.forEach((c) => {
+ if (isActiveLinks) {
+ this.renderer.addClass(this.element.nativeElement, c);
+ } else {
+ this.renderer.removeClass(this.element.nativeElement, c);
+ }
+ });
+ }
+ });
+ }
+
+ private reduceList(currentUrlTree: UrlTree, q: QueryList | Array): boolean {
+ return q.reduce((res: boolean, link: NSRouterLink) => {
+ return res || containsTree(currentUrlTree, link.urlTree, this.nsRouterLinkActiveOptions.exact);
+ }, false);
+ }
+
+ private isLinkActive(router: Router): (link: NSRouterLink) => boolean {
+ return (link: NSRouterLink) => router.isActive(link.urlTree, this.nsRouterLinkActiveOptions.exact);
+ }
+
+ private hasActiveLinks(): boolean {
+ const isActiveCheckFn = this.isLinkActive(this.router);
+ return (this.link && isActiveCheckFn(this.link)) || this.links.some(isActiveCheckFn);
+ }
+}
diff --git a/nativescript-angular/router/ns-router-link.ts b/nativescript-angular/router/ns-router-link.ts
new file mode 100644
index 000000000..40c0b3a10
--- /dev/null
+++ b/nativescript-angular/router/ns-router-link.ts
@@ -0,0 +1,161 @@
+import { Directive, Input, ElementRef, NgZone, OnChanges, SimpleChanges } from '@angular/core';
+import { NavigationExtras } from '@angular/router';
+import { ActivatedRoute, Router, UrlTree } from '@angular/router';
+import { NavigationTransition } from '@nativescript/core';
+import { NativeScriptDebug } from '../trace';
+import { RouterExtensions } from './router-extensions';
+import { NavigationOptions } from './ns-location-utils';
+import { Subject } from 'rxjs';
+
+// Copied from "@angular/router/src/config"
+export type QueryParamsHandling = 'merge' | 'preserve' | '';
+
+/**
+ * The nsRouterLink directive lets you link to specific parts of your app.
+ *
+ * Consider the following route configuration:
+ * ```
+ * [{ path: "/user", component: UserCmp }]
+ * ```
+ *
+ * When linking to this `User` route, you can write:
+ *
+ * ```
+ * link to user component
+ * ```
+ *
+ * NSRouterLink expects the value to be an array of path segments, followed by the params
+ * for that level of routing. For instance `["/team", {teamId: 1}, "user", {userId: 2}]`
+ * means that we want to generate a link to `/team;teamId=1/user;userId=2`.
+ *
+ * The first segment name can be prepended with `/`, `./`, or `../`.
+ * If the segment begins with `/`, the router will look up the route from the root of the app.
+ * If the segment begins with `./`, or doesn"t begin with a slash, the router will
+ * instead look in the current component"s children for the route.
+ * And if the segment begins with `../`, the router will go up one level.
+ */
+@Directive({ selector: '[nsRouterLink]' })
+export class NSRouterLink implements OnChanges {
+ // tslint:disable-line:directive-class-suffix
+ @Input() target: string;
+ @Input() queryParams: { [k: string]: any };
+ @Input() fragment: string;
+
+ @Input() queryParamsHandling: QueryParamsHandling;
+ @Input() preserveQueryParams: boolean;
+ @Input() preserveFragment: boolean;
+ @Input() skipLocationChange: boolean;
+ @Input() replaceUrl: boolean;
+
+ @Input() clearHistory: boolean;
+ @Input() pageTransition: boolean | string | NavigationTransition = true;
+ @Input() pageTransitionDuration;
+
+ /** @internal */
+ onChanges = new Subject();
+
+ private commands: any[] = [];
+
+ constructor(private ngZone: NgZone, private router: Router, private navigator: RouterExtensions, private route: ActivatedRoute, private el: ElementRef) {}
+
+ ngAfterViewInit() {
+ this.el.nativeElement.on('tap', () => {
+ this.ngZone.run(() => {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog(`nsRouterLink.tapped: ${this.commands} ` + `clear: ${this.clearHistory} ` + `transition: ${JSON.stringify(this.pageTransition)} ` + `duration: ${this.pageTransitionDuration}`);
+ }
+
+ const extras = this.getExtras();
+ // this.navigator.navigateByUrl(this.urlTree, extras);
+ this.navigator.navigate(this.commands, {
+ ...extras,
+ relativeTo: this.route,
+ queryParams: this.queryParams,
+ fragment: this.fragment,
+ queryParamsHandling: this.queryParamsHandling,
+ preserveFragment: attrBoolValue(this.preserveFragment),
+ });
+ });
+ });
+ }
+
+ ngOnChanges(changes: SimpleChanges) {
+ // This is subscribed to by `RouterLinkActive` so that it knows to update when there are changes
+ // to the RouterLinks it's tracking.
+ this.onChanges.next(this);
+ }
+
+ @Input('nsRouterLink')
+ set params(data: any[] | string) {
+ if (Array.isArray(data)) {
+ this.commands = data;
+ } else {
+ this.commands = [data];
+ }
+ }
+
+ private getExtras(): NavigationExtras & NavigationOptions {
+ const transition = this.getTransition();
+ return {
+ skipLocationChange: attrBoolValue(this.skipLocationChange),
+ replaceUrl: attrBoolValue(this.replaceUrl),
+
+ clearHistory: this.convertClearHistory(this.clearHistory),
+ animated: transition.animated,
+ transition: transition.transition,
+ };
+ }
+
+ get urlTree(): UrlTree {
+ const urlTree = this.router.createUrlTree(this.commands, {
+ relativeTo: this.route,
+ queryParams: this.queryParams,
+ fragment: this.fragment,
+ queryParamsHandling: this.queryParamsHandling,
+ preserveFragment: attrBoolValue(this.preserveFragment),
+ });
+
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog(`nsRouterLink urlTree created: ${urlTree}`);
+ }
+
+ return urlTree;
+ }
+
+ private convertClearHistory(value: boolean | string): boolean {
+ return value === true || value === 'true';
+ }
+
+ private getTransition(): { animated: boolean; transition?: NavigationTransition } {
+ let transition: NavigationTransition;
+ let animated: boolean;
+
+ if (typeof this.pageTransition === 'boolean') {
+ animated = this.pageTransition;
+ } else if (typeof this.pageTransition === 'string') {
+ if (this.pageTransition === 'none' || this.pageTransition === 'false') {
+ animated = false;
+ } else {
+ animated = true;
+ transition = {
+ name: this.pageTransition,
+ };
+ }
+ } else {
+ animated = true;
+ transition = this.pageTransition;
+ }
+
+ let duration = +this.pageTransitionDuration;
+ if (!isNaN(duration)) {
+ transition = transition || {};
+ transition.duration = duration;
+ }
+
+ return { animated, transition };
+ }
+}
+
+function attrBoolValue(s: any): boolean {
+ return s === '' || !!s;
+}
diff --git a/nativescript-angular/router/page-router-outlet-utils.ts b/nativescript-angular/router/page-router-outlet-utils.ts
new file mode 100644
index 000000000..4b9db354c
--- /dev/null
+++ b/nativescript-angular/router/page-router-outlet-utils.ts
@@ -0,0 +1,43 @@
+import { ComponentRef } from '@angular/core';
+import { ActivatedRoute, ActivatedRouteSnapshot, ChildrenOutletContexts, PRIMARY_OUTLET } from '@angular/router';
+
+/**
+ * There are cases where multiple activatedRoute nodes should be associated/handled by the same PageRouterOutlet.
+ * We can gat additional ActivatedRoutes nodes when there is:
+ * - Lazy loading - there is an additional ActivatedRoute node for the RouteConfig with the `loadChildren` setup
+ * - Componentless routes - there is an additional ActivatedRoute node for the componentless RouteConfig
+ *
+ * Example:
+ * R <-- root
+ * |
+ * feature (lazy module) <-- RouteConfig: { path: "lazy", loadChildren: "./feature/feature.module#FeatureModule" }
+ * |
+ * module (componentless route) <-- RouteConfig: { path: "module", children: [...] } // Note: No 'component'
+ * |
+ * home <-- RouteConfig: { path: "module", component: MyComponent } - this is what we get as activatedRoute param
+ *
+ * In these cases we will mark the top-most node (feature). NSRouteReuseStrategy will detach the tree there and
+ * use this ActivateRoute as a kay for caching.
+ */
+export function findTopActivatedRouteNodeForOutlet(activatedRoute: ActivatedRouteSnapshot): ActivatedRouteSnapshot {
+ let outletActivatedRoute = activatedRoute;
+
+ while (outletActivatedRoute.parent && outletActivatedRoute.parent.routeConfig && !outletActivatedRoute.parent.routeConfig.component) {
+ outletActivatedRoute = outletActivatedRoute.parent;
+ }
+
+ return outletActivatedRoute;
+}
+
+export const pageRouterActivatedSymbol = Symbol('page-router-activated');
+export const loaderRefSymbol = Symbol('loader-ref');
+
+export function destroyComponentRef(componentRef: ComponentRef) {
+ if (componentRef) {
+ const loaderRef = componentRef[loaderRefSymbol];
+ if (loaderRef) {
+ loaderRef.destroy();
+ }
+ componentRef.destroy();
+ }
+}
diff --git a/nativescript-angular/router/page-router-outlet.ts b/nativescript-angular/router/page-router-outlet.ts
new file mode 100644
index 000000000..879378732
--- /dev/null
+++ b/nativescript-angular/router/page-router-outlet.ts
@@ -0,0 +1,401 @@
+import { Attribute, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, Directive, Inject, InjectionToken, Injector, OnDestroy, EventEmitter, Output, Type, ViewContainerRef, ElementRef, InjectFlags, NgZone } from '@angular/core';
+import { ActivatedRoute, ActivatedRouteSnapshot, ChildrenOutletContexts, PRIMARY_OUTLET } from '@angular/router';
+
+import { Frame, Page, NavigatedData, profile, Device } from '@nativescript/core';
+
+import { BehaviorSubject } from 'rxjs';
+
+import { PAGE_FACTORY, PageFactory } from '../platform-providers';
+import { NativeScriptDebug } from '../trace';
+import { DetachedLoader } from '../common/detached-loader';
+import { ViewUtil } from '../view-util';
+import { NSLocationStrategy } from './ns-location-strategy';
+import { Outlet } from './ns-location-utils';
+import { NSRouteReuseStrategy } from './ns-route-reuse-strategy';
+import { findTopActivatedRouteNodeForOutlet, pageRouterActivatedSymbol, loaderRefSymbol, destroyComponentRef } from './page-router-outlet-utils';
+
+export class PageRoute {
+ activatedRoute: BehaviorSubject;
+
+ constructor(startRoute: ActivatedRoute) {
+ this.activatedRoute = new BehaviorSubject(startRoute);
+ }
+}
+
+export class DestructibleInjector implements Injector {
+ private refs = new Set();
+ constructor(private destructableProviders: ProviderSet, private parent: Injector) {}
+ get(token: Type | InjectionToken, notFoundValue?: T, flags?: InjectFlags): T {
+ const ref = this.parent.get(token, notFoundValue, flags);
+ if (this.destructableProviders.has(token)) {
+ this.refs.add(ref);
+ }
+ return ref;
+ }
+ destroy() {
+ this.refs.forEach((ref) => {
+ if (ref.ngOnDestroy instanceof Function) {
+ ref.ngOnDestroy();
+ }
+ });
+ this.refs.clear();
+ }
+}
+
+type ProviderSet = Set | InjectionToken>;
+
+const routeToString = function (activatedRoute: ActivatedRoute | ActivatedRouteSnapshot): string {
+ return activatedRoute.pathFromRoot.join('->');
+};
+
+@Directive({ selector: 'page-router-outlet' }) // tslint:disable-line:directive-selector
+export class PageRouterOutlet implements OnDestroy {
+ // tslint:disable-line:directive-class-suffix
+ private activated: ComponentRef | null = null;
+ private _activatedRoute: ActivatedRoute | null = null;
+ private detachedLoaderFactory: ComponentFactory;
+
+ private outlet: Outlet;
+ private name: string;
+ private isEmptyOutlet: boolean;
+ private viewUtil: ViewUtil;
+ private frame: Frame;
+
+ @Output('activate') activateEvents = new EventEmitter(); // tslint:disable-line:no-output-rename
+ @Output('deactivate') deactivateEvents = new EventEmitter(); // tslint:disable-line:no-output-rename
+
+ /** @deprecated from Angular since v4 */
+ get locationInjector(): Injector {
+ return this.location.injector;
+ }
+ /** @deprecated from Angular since v4 */
+ get locationFactoryResolver(): ComponentFactoryResolver {
+ return this.resolver;
+ }
+
+ get isActivated(): boolean {
+ return !!this.activated;
+ }
+
+ get component(): Object {
+ if (!this.activated) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('Outlet is not activated');
+ }
+ return;
+ }
+
+ return this.activated.instance;
+ }
+ get activatedRoute(): ActivatedRoute {
+ if (!this.activated) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('Outlet is not activated');
+ }
+ return;
+ }
+
+ return this._activatedRoute;
+ }
+
+ constructor(private parentContexts: ChildrenOutletContexts, private location: ViewContainerRef, @Attribute('name') name: string, @Attribute('actionBarVisibility') actionBarVisibility: string, @Attribute('isEmptyOutlet') isEmptyOutlet: boolean, private locationStrategy: NSLocationStrategy, private componentFactoryResolver: ComponentFactoryResolver, private resolver: ComponentFactoryResolver, private changeDetector: ChangeDetectorRef, @Inject(PAGE_FACTORY) private pageFactory: PageFactory, private routeReuseStrategy: NSRouteReuseStrategy, private ngZone: NgZone, elRef: ElementRef) {
+ this.isEmptyOutlet = isEmptyOutlet;
+ this.frame = elRef.nativeElement;
+ this.setActionBarVisibility(actionBarVisibility);
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog(`PageRouterOutlet.constructor frame: ${this.frame}`);
+ }
+
+ this.name = name || PRIMARY_OUTLET;
+ parentContexts.onChildOutletCreated(this.name, this);
+
+ this.viewUtil = new ViewUtil(Device);
+ this.detachedLoaderFactory = resolver.resolveComponentFactory(DetachedLoader);
+ }
+
+ setActionBarVisibility(actionBarVisibility: string): void {
+ switch (actionBarVisibility) {
+ case 'always':
+ case 'never':
+ this.frame.actionBarVisibility = actionBarVisibility;
+ return;
+
+ default:
+ this.frame.actionBarVisibility = 'auto';
+ }
+ }
+
+ ngOnDestroy(): void {
+ // Clear accumulated modal view page cache when page-router-outlet
+ // destroyed on modal view closing
+ this.parentContexts.onChildOutletDestroyed(this.name);
+
+ if (this.outlet) {
+ this.outlet.outletKeys.forEach((key) => {
+ this.routeReuseStrategy.clearModalCache(key);
+ });
+ this.locationStrategy.clearOutlet(this.frame);
+ } else {
+ NativeScriptDebug.routerLog('PageRouterOutlet.ngOnDestroy: no outlet available for page-router-outlet');
+ }
+
+ if (this.isActivated) {
+ const c = this.activated.instance;
+ this.activated.hostView.detach();
+ destroyComponentRef(this.activated);
+
+ this.deactivateEvents.emit(c);
+ this.activated = null;
+ }
+ }
+
+ deactivate(): void {
+ if (!this.outlet || !this.outlet.isPageNavigationBack) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('Currently not in page back navigation - component should be detached instead of deactivated.');
+ }
+ return;
+ }
+
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('PageRouterOutlet.deactivate() while going back - should destroy');
+ }
+
+ if (!this.isActivated) {
+ return;
+ }
+
+ const c = this.activated.instance;
+ destroyComponentRef(this.activated);
+
+ this.activated = null;
+ this._activatedRoute = null;
+
+ this.deactivateEvents.emit(c);
+ }
+
+ /**
+ * Called when the `RouteReuseStrategy` instructs to detach the subtree
+ */
+ detach(): ComponentRef {
+ if (!this.isActivated) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('Outlet is not activated');
+ }
+ return;
+ }
+
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog(`PageRouterOutlet.detach() - ${routeToString(this._activatedRoute)}`);
+ }
+
+ // Detach from ChangeDetection
+ this.activated.hostView.detach();
+
+ const component = this.activated;
+ this.activated = null;
+ this._activatedRoute = null;
+ return component;
+ }
+
+ /**
+ * Called when the `RouteReuseStrategy` instructs to re-attach a previously detached subtree
+ */
+ attach(ref: ComponentRef, activatedRoute: ActivatedRoute) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog(`PageRouterOutlet.attach() - ${routeToString(activatedRoute)}`);
+ }
+
+ this.activated = ref;
+
+ // reattach to ChangeDetection
+ this.activated.hostView.markForCheck();
+ this.activated.hostView.reattach();
+ this._activatedRoute = activatedRoute;
+ this.markActivatedRoute(activatedRoute);
+
+ this.locationStrategy._finishBackPageNavigation(this.frame);
+ }
+
+ /**
+ * Called by the Router to instantiate a new component during the commit phase of a navigation.
+ * This method in turn is responsible for calling the `routerOnActivate` hook of its child.
+ */
+ @profile
+ activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver | null): void {
+ this.outlet = this.outlet || this.getOutlet(activatedRoute.snapshot);
+ if (!this.outlet) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerError('No outlet found relative to activated route');
+ }
+ return;
+ }
+
+ this.outlet.isNSEmptyOutlet = this.isEmptyOutlet;
+ this.locationStrategy.updateOutletFrame(this.outlet, this.frame, this.isEmptyOutlet);
+
+ if (this.outlet && this.outlet.isPageNavigationBack) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('Currently in page back navigation - component should be reattached instead of activated.');
+ }
+ this.locationStrategy._finishBackPageNavigation(this.frame);
+ }
+
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog(`PageRouterOutlet.activateWith() - ${routeToString(activatedRoute)}`);
+ }
+
+ this._activatedRoute = activatedRoute;
+
+ this.markActivatedRoute(activatedRoute);
+
+ resolver = resolver || this.resolver;
+
+ this.activateOnGoForward(activatedRoute, resolver);
+ this.activateEvents.emit(this.activated.instance);
+ }
+
+ private activateOnGoForward(activatedRoute: ActivatedRoute, loadedResolver: ComponentFactoryResolver): void {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('PageRouterOutlet.activate() forward navigation - ' + 'create detached loader in the loader container');
+ }
+
+ const factory = this.getComponentFactory(activatedRoute, loadedResolver);
+ const page = this.pageFactory({
+ isNavigation: true,
+ componentType: factory.componentType,
+ });
+
+ const destructables = new Set([]);
+ const injector = Injector.create({
+ providers: [
+ { provide: Page, useValue: page },
+ { provide: Frame, useValue: this.frame },
+ { provide: PageRoute, useValue: new PageRoute(activatedRoute) },
+ { provide: ActivatedRoute, useValue: activatedRoute },
+ { provide: ChildrenOutletContexts, useValue: this.parentContexts.getOrCreateContext(this.name).children },
+ ],
+ parent: this.location.injector,
+ });
+
+ const childInjector = new DestructibleInjector(destructables, injector);
+ const loaderRef = this.location.createComponent(this.detachedLoaderFactory, this.location.length, childInjector, []);
+ loaderRef.onDestroy(() => childInjector.destroy());
+ this.changeDetector.markForCheck();
+
+ this.activated = loaderRef.instance.loadWithFactory(factory);
+ this.loadComponentInPage(page, this.activated, { activatedRoute });
+
+ this.activated[loaderRefSymbol] = loaderRef;
+ }
+
+ @profile
+ private loadComponentInPage(page: Page, componentRef: ComponentRef, navigationContext): void {
+ // Component loaded. Find its root native view.
+ const componentView = componentRef.location.nativeElement;
+ // Remove it from original native parent.
+ this.viewUtil.removeChild(componentView.parent, componentView);
+ // Add it to the new page
+ this.viewUtil.insertChild(page, componentView);
+
+ const navigatedFromCallback = (global).Zone.current.wrap((args: NavigatedData) => {
+ if (args.isBackNavigation) {
+ this.locationStrategy._beginBackPageNavigation(this.frame);
+ this.locationStrategy.back(null, this.frame);
+ }
+ });
+ // TODO: experiment with using NgZone instead of global above
+ // const navigatedFromCallback = (args: NavigatedData) => {
+ // if (args.isBackNavigation) {
+ // this.ngZone.run(() => {
+ // this.locationStrategy._beginBackPageNavigation(this.frame);
+ // this.locationStrategy.back(null, this.frame);
+ // });
+ // }
+ // };
+
+ page.on(Page.navigatedFromEvent, navigatedFromCallback);
+ componentRef.onDestroy(() => {
+ if (page) {
+ page.off(Page.navigatedFromEvent, navigatedFromCallback);
+ page = null;
+ }
+ });
+
+ const navOptions = this.locationStrategy._beginPageNavigation(this.frame);
+
+ // Clear refCache if navigation with clearHistory
+ if (navOptions.clearHistory) {
+ const clearCallback = () =>
+ setTimeout(() => {
+ if (this.outlet) {
+ this.routeReuseStrategy.clearCache(this.outlet.outletKeys[0]);
+ }
+ });
+
+ page.once(Page.navigatedToEvent, clearCallback);
+ }
+
+ this.frame.navigate({
+ create() {
+ return page;
+ },
+ context: navigationContext,
+ clearHistory: navOptions.clearHistory,
+ animated: navOptions.animated,
+ transition: navOptions.transition,
+ });
+ }
+
+ // 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 queue = [];
+ queue.push(activatedRoute.snapshot);
+ let currentRoute = queue.shift();
+
+ while (currentRoute) {
+ currentRoute.children.forEach((childRoute) => {
+ queue.push(childRoute);
+ });
+
+ const topActivatedRoute = findTopActivatedRouteNodeForOutlet(currentRoute);
+ let outletKey = this.locationStrategy.getRouteFullPath(topActivatedRoute);
+ let outlet = this.locationStrategy.findOutlet(outletKey, topActivatedRoute);
+
+ if (outlet && outlet.frames.length) {
+ topActivatedRoute[pageRouterActivatedSymbol] = true;
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.routerLog('Activated route marked as page: ' + routeToString(topActivatedRoute));
+ }
+ }
+
+ currentRoute = queue.shift();
+ }
+ }
+
+ private getComponentFactory(activatedRoute: ActivatedRoute, loadedResolver: ComponentFactoryResolver): ComponentFactory {
+ const { component } = activatedRoute.routeConfig;
+
+ return loadedResolver ? loadedResolver.resolveComponentFactory(component) : this.componentFactoryResolver.resolveComponentFactory(component);
+ }
+
+ private getOutlet(activatedRouteSnapshot: ActivatedRouteSnapshot): Outlet {
+ const topActivatedRoute = findTopActivatedRouteNodeForOutlet(activatedRouteSnapshot);
+ const outletKey = this.locationStrategy.getRouteFullPath(topActivatedRoute);
+ let outlet = this.locationStrategy.findOutlet(outletKey, topActivatedRoute);
+
+ // Named lazy loaded outlet.
+ if (!outlet && this.isEmptyOutlet) {
+ const parentOutletKey = this.locationStrategy.getRouteFullPath(topActivatedRoute.parent);
+ outlet = this.locationStrategy.findOutlet(parentOutletKey, topActivatedRoute.parent);
+
+ if (outlet) {
+ outlet.outletKeys.push(outletKey);
+ }
+ }
+
+ return outlet;
+ }
+}
diff --git a/nativescript-angular/router/private-imports/router-url-tree.ts b/nativescript-angular/router/private-imports/router-url-tree.ts
new file mode 100644
index 000000000..b36c657d5
--- /dev/null
+++ b/nativescript-angular/router/private-imports/router-url-tree.ts
@@ -0,0 +1,83 @@
+/* tslint:disable:forin */
+// Copied unexported functions from @angular/router/src/url_tree
+import { UrlTree, UrlSegment, PRIMARY_OUTLET } from '@angular/router';
+// UrlSegmentGroup not exported, just use any.
+type UrlSegmentGroup = any;
+
+export function containsTree(container: UrlTree, containee: UrlTree, exact: boolean): boolean {
+ if (exact) {
+ return equalSegmentGroups(container.root, containee.root);
+ } else {
+ return containsSegmentGroup(container.root, containee.root);
+ }
+}
+
+function equalSegmentGroups(container: UrlSegmentGroup, containee: UrlSegmentGroup): boolean {
+ if (!equalPath(container.segments, containee.segments)) {
+ return false;
+ }
+ if (container.numberOfChildren !== containee.numberOfChildren) {
+ return false;
+ }
+ for (let c in containee.children) {
+ if (!container.children[c]) {
+ return false;
+ }
+ if (!equalSegmentGroups(container.children[c], containee.children[c])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function containsSegmentGroup(container: UrlSegmentGroup, containee: UrlSegmentGroup): boolean {
+ return containsSegmentGroupHelper(container, containee, containee.segments);
+}
+
+function containsSegmentGroupHelper(container: UrlSegmentGroup, containee: UrlSegmentGroup, containeePaths: UrlSegment[]): boolean {
+ if (container.segments.length > containeePaths.length) {
+ const current = container.segments.slice(0, containeePaths.length);
+ if (!equalPath(current, containeePaths)) {
+ return false;
+ }
+ if (containee.hasChildren()) {
+ return false;
+ }
+ return true;
+ } else if (container.segments.length === containeePaths.length) {
+ if (!equalPath(container.segments, containeePaths)) {
+ return false;
+ }
+ for (let c in containee.children) {
+ if (!container.children[c]) {
+ return false;
+ }
+ if (!containsSegmentGroup(container.children[c], containee.children[c])) {
+ return false;
+ }
+ }
+ return true;
+ } else {
+ const current = containeePaths.slice(0, container.segments.length);
+ const next = containeePaths.slice(container.segments.length);
+ if (!equalPath(container.segments, current)) {
+ return false;
+ }
+ if (!container.children[PRIMARY_OUTLET]) {
+ return false;
+ }
+ return containsSegmentGroupHelper(container.children[PRIMARY_OUTLET], containee, next);
+ }
+}
+
+export function equalPath(a: UrlSegment[], b: UrlSegment[]): boolean {
+ if (a.length !== b.length) {
+ return false;
+ }
+ for (let i = 0; i < a.length; ++i) {
+ if (a[i].path !== b[i].path) {
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/nativescript-angular/router/router-extensions.ts b/nativescript-angular/router/router-extensions.ts
new file mode 100644
index 000000000..da30472e8
--- /dev/null
+++ b/nativescript-angular/router/router-extensions.ts
@@ -0,0 +1,131 @@
+import { Injectable } from '@angular/core';
+import { Router, UrlTree, NavigationExtras, ActivatedRoute } from '@angular/router';
+import { NSLocationStrategy } from './ns-location-strategy';
+import { NavigationOptions, Outlet } from './ns-location-utils';
+import { FrameService } from '../frame.service';
+import { NativeScriptDebug } from '../trace';
+import { findTopActivatedRouteNodeForOutlet } from './page-router-outlet-utils';
+
+export type ExtendedNavigationExtras = NavigationExtras & NavigationOptions;
+
+export interface BackNavigationOptions {
+ outlets?: Array;
+ relativeTo?: ActivatedRoute | null;
+}
+
+@Injectable({
+ providedIn: 'root',
+})
+export class RouterExtensions {
+ constructor(public router: Router, public locationStrategy: NSLocationStrategy, public frameService: FrameService) {}
+
+ public navigate(commands: any[], extras?: ExtendedNavigationExtras): Promise {
+ if (extras) {
+ this.locationStrategy._setNavigationOptions(extras);
+ }
+ return this.router.navigate(commands, extras);
+ }
+
+ public navigateByUrl(url: string | UrlTree, options?: NavigationOptions): Promise {
+ if (options) {
+ this.locationStrategy._setNavigationOptions(options);
+ }
+ return this.router.navigateByUrl(url);
+ }
+
+ public back(backNavigationOptions?: BackNavigationOptions) {
+ if (backNavigationOptions) {
+ this.backOutlets(backNavigationOptions);
+ } else {
+ this.locationStrategy.back();
+ }
+ }
+
+ public canGoBack(backNavigationOptions?: BackNavigationOptions) {
+ let canGoBack = true;
+ if (backNavigationOptions) {
+ const { outletsToBack, outlets } = this.findOutletsToBack(backNavigationOptions);
+
+ if (outletsToBack.length !== outlets.length) {
+ NativeScriptDebug.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() {
+ this.frameService.getFrame().goBack();
+ }
+
+ public canGoBackToPreviousPage(): boolean {
+ return this.frameService.getFrame().canGoBack();
+ }
+
+ private backOutlets(options: BackNavigationOptions) {
+ const { outletsToBack, outlets } = this.findOutletsToBack(options);
+
+ if (outletsToBack.length !== outlets.length) {
+ NativeScriptDebug.routerError('No outlet found relative to activated route');
+ } else {
+ outletsToBack.forEach((outletToBack) => {
+ if (outletToBack.isPageNavigationBack) {
+ NativeScriptDebug.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 || rootRoute;
+
+ const relativeRouteOutlet = this.findOutletByRoute(relativeRoute);
+ const isNSEmptyOutlet = relativeRouteOutlet && relativeRouteOutlet.isNSEmptyOutlet;
+
+ // Lazy named outlet has added 'primary' inner NSEmptyOutlet child.
+ // Take parent route when `relativeTo` option points to the outer named outlet.
+ if (isNSEmptyOutlet && relativeRoute.outlet !== 'primary') {
+ relativeRoute = relativeRoute.parent || relativeRoute;
+ }
+
+ const routesToMatch = outlets ? relativeRoute.children : [relativeRoute];
+ outlets = outlets || [relativeRoute.outlet];
+
+ for (let index = 0; index < routesToMatch.length; index++) {
+ const currentRoute = routesToMatch[index];
+ if (outlets.some((currentOutlet) => currentOutlet === currentRoute.outlet)) {
+ const outlet = this.findOutletByRoute(currentRoute);
+
+ if (outlet) {
+ outletsToBack.push(outlet);
+ }
+ }
+ }
+
+ return { outletsToBack: outletsToBack, outlets: outlets };
+ }
+
+ private findOutletByRoute(currentRoute: ActivatedRoute): Outlet {
+ let outlet;
+
+ const currentRouteSnapshop = findTopActivatedRouteNodeForOutlet(currentRoute.snapshot);
+ const outletKey = this.locationStrategy.getRouteFullPath(currentRouteSnapshop);
+ outlet = this.locationStrategy.findOutlet(outletKey, currentRouteSnapshop);
+
+ return outlet;
+ }
+}
diff --git a/nativescript-angular/router/router.module.ts b/nativescript-angular/router/router.module.ts
new file mode 100644
index 000000000..005a3af98
--- /dev/null
+++ b/nativescript-angular/router/router.module.ts
@@ -0,0 +1,60 @@
+import { NgModule, ModuleWithProviders, NO_ERRORS_SCHEMA, Optional, SkipSelf } from '@angular/core';
+import { RouterModule, Routes, ExtraOptions, RouteReuseStrategy } from '@angular/router';
+import { LocationStrategy, PlatformLocation } from '@angular/common';
+import { NSRouterLink } from './ns-router-link';
+import { NSRouterLinkActive } from './ns-router-link-active';
+import { PageRouterOutlet } from './page-router-outlet';
+import { NSLocationStrategy } from './ns-location-strategy';
+import { NativescriptPlatformLocation } from './ns-platform-location';
+import { NSRouteReuseStrategy } from './ns-route-reuse-strategy';
+import { RouterExtensions } from './router-extensions';
+import { NativeScriptCommonModule } from '../common';
+import { FrameService } from '../frame.service';
+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 { Outlet, NavigationOptions, LocationState, defaultNavOptions } from './ns-location-utils';
+export { NSRouterLink } from './ns-router-link';
+export { NSRouterLinkActive } from './ns-router-link-active';
+export { PageRouterOutlet } from './page-router-outlet';
+export { NSLocationStrategy } from './ns-location-strategy';
+export { NSEmptyOutletComponent } from './ns-empty-outlet.component';
+
+export function provideLocationStrategy(locationStrategy: NSLocationStrategy, frameService: FrameService): NSLocationStrategy {
+ return locationStrategy ? locationStrategy : new NSLocationStrategy(frameService);
+}
+
+@NgModule({
+ declarations: [NSRouterLink, NSRouterLinkActive, PageRouterOutlet, NSEmptyOutletComponent],
+ entryComponents: [NSEmptyOutletComponent],
+ imports: [RouterModule, NativeScriptCommonModule],
+ exports: [RouterModule, NSRouterLink, NSRouterLinkActive, PageRouterOutlet, NSEmptyOutletComponent],
+ schemas: [NO_ERRORS_SCHEMA],
+})
+export class NativeScriptRouterModule {
+ static forRoot(routes: Routes, config?: ExtraOptions): ModuleWithProviders {
+ return {
+ ngModule: NativeScriptRouterModule,
+ providers: [
+ ...RouterModule.forRoot(routes, config).providers,
+ {
+ provide: NSLocationStrategy,
+ useFactory: provideLocationStrategy,
+ deps: [[NSLocationStrategy, new Optional(), new SkipSelf()], FrameService],
+ },
+ { provide: LocationStrategy, useExisting: NSLocationStrategy },
+ NativescriptPlatformLocation,
+ { provide: PlatformLocation, useExisting: NativescriptPlatformLocation },
+ RouterExtensions,
+ NSRouteReuseStrategy,
+ { provide: RouteReuseStrategy, useExisting: NSRouteReuseStrategy },
+ ],
+ };
+ }
+
+ static forChild(routes: Routes): ModuleWithProviders {
+ return { ngModule: NativeScriptRouterModule, providers: RouterModule.forChild(routes).providers };
+ }
+}
diff --git a/nativescript-angular/schema-registry.ts b/nativescript-angular/schema-registry.ts
new file mode 100644
index 000000000..db11252a8
--- /dev/null
+++ b/nativescript-angular/schema-registry.ts
@@ -0,0 +1,53 @@
+import { ElementSchemaRegistry } from '@angular/compiler';
+import { SchemaMetadata } from '@angular/core';
+
+export enum SecurityContext {
+ NONE,
+ HTML,
+ STYLE,
+ SCRIPT,
+ URL,
+ RESOURCE_URL,
+}
+
+export class NativeScriptElementSchemaRegistry extends ElementSchemaRegistry {
+ hasProperty(_tagName: string, _propName: string): boolean {
+ return true;
+ }
+
+ hasElement(_tagName: string, _schemaMetas: SchemaMetadata[]): boolean {
+ return true;
+ }
+
+ getMappedPropName(propName: string): string {
+ return propName;
+ }
+
+ getDefaultComponentElementName(): string {
+ return 'ng-component';
+ }
+
+ securityContext(_tagName: string, _propName: string): any {
+ return SecurityContext.NONE;
+ }
+
+ validateProperty(_name: string): { error: boolean; msg?: string } {
+ return { error: false };
+ }
+
+ validateAttribute(_name: string): { error: boolean; msg?: string } {
+ return { error: false };
+ }
+
+ allKnownElementNames(): string[] {
+ return [];
+ }
+
+ normalizeAnimationStyleProperty(propName: string): string {
+ return propName;
+ }
+
+ normalizeAnimationStyleValue(_camelCaseProp: string, _userProvidedProp: string, val: string | number): { error: string; value: string } {
+ return { error: null, value: val.toString() };
+ }
+}
diff --git a/nativescript-angular/testing/package.json b/nativescript-angular/testing/package.json
new file mode 100644
index 000000000..e0ecb51a5
--- /dev/null
+++ b/nativescript-angular/testing/package.json
@@ -0,0 +1,17 @@
+{
+ "ngPackage": {
+ "lib": {
+ "entryFile": "./src/public_api.ts",
+ "umdModuleIds": {
+ "@nativescript/core": "ns-core"
+ }
+ },
+ "allowedNonPeerDependencies": [
+ "."
+ ]
+ },
+ "devDependencies": {
+ "@nativescript/angular": "latest",
+ "@nativescript/core": "~8.0.0"
+ }
+}
diff --git a/nativescript-angular/testing/src/nativescript-testing.module.ts b/nativescript-angular/testing/src/nativescript-testing.module.ts
new file mode 100644
index 000000000..9e1acde4f
--- /dev/null
+++ b/nativescript-angular/testing/src/nativescript-testing.module.ts
@@ -0,0 +1,22 @@
+import { NgModule } from '@angular/core';
+import { TestComponentRenderer } from '@angular/core/testing';
+import { COMMON_PROVIDERS, APP_ROOT_VIEW } from '@nativescript/angular';
+import { NativeScriptTestComponentRenderer } from './nativescript_test_component_renderer';
+import { testingRootView } from './test-root-view';
+
+/**
+ * Providers array is exported for cases where a custom module has to be constructed
+ * to test a particular piece of code. This can happen, for example, if you are trying
+ * to test dynamic component loading and need to specify an entryComponent for the testing
+ * module.
+ */
+export const NATIVESCRIPT_TESTING_PROVIDERS: any[] = [COMMON_PROVIDERS, { provide: APP_ROOT_VIEW, useFactory: testingRootView }, { provide: TestComponentRenderer, useClass: NativeScriptTestComponentRenderer }];
+
+/**
+ * NativeScript testing support module. Enables use of TestBed for angular components, directives,
+ * pipes, and services.
+ */
+@NgModule({
+ providers: NATIVESCRIPT_TESTING_PROVIDERS,
+})
+export class NativeScriptTestingModule {}
diff --git a/nativescript-angular/testing/src/nativescript_test_component_renderer.ts b/nativescript-angular/testing/src/nativescript_test_component_renderer.ts
new file mode 100644
index 000000000..be83085f8
--- /dev/null
+++ b/nativescript-angular/testing/src/nativescript_test_component_renderer.ts
@@ -0,0 +1,18 @@
+import { Injectable } from '@angular/core';
+import { TestComponentRenderer } from '@angular/core/testing';
+import { ProxyViewContainer } from '@nativescript/core';
+import { testingRootView } from './test-root-view';
+
+/**
+ * A NativeScript based implementation of the TestComponentRenderer.
+ */
+@Injectable()
+export class NativeScriptTestComponentRenderer extends TestComponentRenderer {
+ insertRootElement(rootElId: string) {
+ const layout = new ProxyViewContainer();
+ layout.id = rootElId;
+
+ const rootLayout = testingRootView();
+ rootLayout.addChild(layout);
+ }
+}
diff --git a/nativescript-angular/testing/src/polyfills.ts b/nativescript-angular/testing/src/polyfills.ts
new file mode 100644
index 000000000..fb0686283
--- /dev/null
+++ b/nativescript-angular/testing/src/polyfills.ts
@@ -0,0 +1,4 @@
+if (typeof Node === 'undefined' && !global.Node) {
+ class DummyNode {}
+ global.Node = DummyNode as any;
+}
diff --git a/nativescript-angular/testing/src/public_api.ts b/nativescript-angular/testing/src/public_api.ts
new file mode 100644
index 000000000..66d4aa5f3
--- /dev/null
+++ b/nativescript-angular/testing/src/public_api.ts
@@ -0,0 +1,5 @@
+import './polyfills';
+export * from './util';
+export * from './test-root-view';
+export * from './nativescript_test_component_renderer';
+export * from './nativescript-testing.module';
diff --git a/nativescript-angular/testing/src/test-root-view.ts b/nativescript-angular/testing/src/test-root-view.ts
new file mode 100644
index 000000000..4fa5da9db
--- /dev/null
+++ b/nativescript-angular/testing/src/test-root-view.ts
@@ -0,0 +1,29 @@
+import { LayoutBase, GridLayout, Frame } from '@nativescript/core';
+
+const TESTING_ROOT_ID = '__testing_container';
+
+/**
+ * Get a reference to the fixtures container.
+ */
+export function testingRootView(): LayoutBase {
+ const rootPageLayout = Frame.topmost().currentPage.content as LayoutBase;
+
+ let testingRoot: LayoutBase;
+ rootPageLayout.eachChild((child) => {
+ if (child.id === TESTING_ROOT_ID) {
+ testingRoot = child as LayoutBase;
+ return false;
+ }
+ return true;
+ });
+
+ if (!testingRoot) {
+ testingRoot = new GridLayout();
+ testingRoot.id = TESTING_ROOT_ID;
+ GridLayout.setColumnSpan(testingRoot, 100);
+ GridLayout.setRowSpan(testingRoot, 100);
+ rootPageLayout.addChild(testingRoot);
+ }
+
+ return testingRoot;
+}
diff --git a/nativescript-angular/testing/src/util.ts b/nativescript-angular/testing/src/util.ts
new file mode 100644
index 000000000..1a044b1e7
--- /dev/null
+++ b/nativescript-angular/testing/src/util.ts
@@ -0,0 +1,152 @@
+import { View, Frame, LayoutBase, GridLayout } from '@nativescript/core';
+import { NgModule, Type } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { CommonModule } from '@angular/common';
+import { NativeScriptModule } from '@nativescript/angular';
+import { platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
+// import { NS_COMPILER_PROVIDERS } from "../../platform";
+import { NATIVESCRIPT_TESTING_PROVIDERS, NativeScriptTestingModule } from './nativescript-testing.module';
+import { testingRootView } from './test-root-view';
+
+/**
+ * Declared test contexts. When the suite is done this map should be empty if all lifecycle
+ * calls have happened as expected.
+ * @private
+ */
+const activeTestFixtures: ComponentFixture[][] = [];
+
+/**
+ * Return a promise that resolves after (durationMs) milliseconds
+ */
+export function promiseWait(durationMs: number) {
+ return () => new Promise((resolve) => setTimeout(() => resolve(), durationMs));
+}
+
+/**
+ * Perform basic TestBed environment initialization. Call this once in the main entry point to your tests.
+ */
+export function nsTestBedInit() {
+ TestBed.initTestEnvironment(
+ NativeScriptTestingModule,
+ platformBrowserDynamicTesting() // NS_COMPILER_PROVIDERS)
+ );
+}
+
+/**
+ * Helper for configuring a TestBed instance for rendering components for test. Ideally this
+ * would not be needed, and in truth it's just a wrapper to eliminate some boilerplate. It
+ * exists because when you need to specify `entryComponents` for a test the setup becomes quite
+ * a bit more complex than if you're just doing a basic component test.
+ *
+ * More about entryComponents complexity: https://github.com/angular/angular/issues/12079
+ *
+ * Use:
+ * ```
+ * beforeEach(nsTestBedBeforeEach([MyComponent,MyFailComponent]));
+ * ```
+ *
+ * **NOTE*** Remember to pair with {@see nsTestBedAfterEach}
+ *
+ * @param components Any components that you will create during the test
+ * @param providers Any services your tests depend on
+ * @param imports Any module imports your tests depend on
+ * @param entryComponents Any entry components that your tests depend on
+ */
+export function nsTestBedBeforeEach(components: any[], providers: any[] = [], imports: any[] = [], entryComponents: any[] = []) {
+ return (done) => {
+ activeTestFixtures.push([]);
+ // If there are no entry components we can take the simple path.
+ if (entryComponents.length === 0) {
+ TestBed.configureTestingModule({
+ declarations: [...components],
+ providers: [...providers],
+ imports: [NativeScriptModule, ...imports],
+ });
+ } else {
+ // If there are entry components, we have to reset the testing platform.
+ //
+ // There's got to be a better way... (o_O)
+ // TestBed.resetTestEnvironment();
+ // @NgModule({
+ // declarations: entryComponents,
+ // exports: entryComponents,
+ // entryComponents: entryComponents
+ // })
+ // class EntryComponentsTestModule {
+ // }
+ // TestBed.initTestEnvironment(
+ // EntryComponentsTestModule,
+ // platformBrowserDynamicTesting(NS_COMPILER_PROVIDERS)
+ // );
+ // TestBed.configureTestingModule({
+ // declarations: components,
+ // imports: [
+ // NativeScriptModule, NativeScriptTestingModule, CommonModule,
+ // ...imports
+ // ],
+ // providers: [...providers, ...NATIVESCRIPT_TESTING_PROVIDERS],
+ // });
+ }
+ TestBed.compileComponents()
+ .then(() => done())
+ .catch((e) => {
+ console.log(`Failed to instantiate test component with error: ${e}`);
+ console.log(e.stack);
+ done();
+ });
+ };
+}
+
+/**
+ * Helper for a basic component TestBed clean up.
+ * @param resetEnv When true the testing environment will be reset
+ * @param resetFn When resetting the environment, use this init function
+ */
+export function nsTestBedAfterEach(resetEnv = true, resetFn = nsTestBedInit) {
+ return () => {
+ if (activeTestFixtures.length === 0) {
+ throw new Error(`There are no more declared fixtures.` + `Did you call "nsTestBedBeforeEach" and "nsTestBedAfterEach" an equal number of times?`);
+ }
+ const root = testingRootView() as LayoutBase;
+ const fixtures = activeTestFixtures.pop();
+ fixtures.forEach((fixture) => {
+ const fixtureView = fixture.nativeElement;
+ if (fixtureView.parent === root) {
+ root.removeChild(fixtureView);
+ }
+ fixture.destroy();
+ });
+ TestBed.resetTestingModule();
+ if (resetEnv) {
+ TestBed.resetTestEnvironment();
+ resetFn();
+ }
+ };
+}
+
+/**
+ * Render a component using the TestBed helper, and return a promise that resolves when the
+ * ComponentFixture is fully initialized.
+ */
+export function nsTestBedRender(componentType: Type): Promise> {
+ const fixture = TestBed.createComponent(componentType);
+ fixture.detectChanges();
+ return (
+ fixture
+ .whenRenderingDone()
+ // TODO(jd): it seems that the whenStable and whenRenderingDone utilities of ComponentFixture
+ // do not work as expected. I looked at how to fix it and it's not clear how to provide
+ // a {N} specific subclass, because ComponentFixture is newed directly rather than injected
+ // What to do about it? Maybe fakeAsync can help? For now just setTimeout for 100ms (x_X)
+ .then(promiseWait(100))
+ .then(() => {
+ const list = activeTestFixtures[activeTestFixtures.length - 1];
+ if (!list) {
+ console.warn('nsTestBedRender called without nsTestBedBeforeEach/nsTestBedAfter each. ' + "You are responsible for calling 'fixture.destroy()' when your test is done " + 'in order to clean up the components that are created.');
+ } else {
+ list.push(fixture);
+ }
+ return fixture;
+ })
+ );
+}
diff --git a/nativescript-angular/trace.ts b/nativescript-angular/trace.ts
new file mode 100644
index 000000000..321893b5c
--- /dev/null
+++ b/nativescript-angular/trace.ts
@@ -0,0 +1,65 @@
+import { Trace } from '@nativescript/core';
+
+export namespace NativeScriptDebug {
+ export const animationsTraceCategory = 'ns-animations';
+ export const rendererTraceCategory = 'ns-renderer';
+ export const viewUtilCategory = 'ns-view-util';
+ export const routerTraceCategory = 'ns-router';
+ export const routeReuseStrategyTraceCategory = 'ns-route-reuse-strategy';
+ export const listViewTraceCategory = 'ns-list-view';
+ export const bootstrapCategory = 'bootstrap';
+ // TODO: migrate all usage to this - avoids extraneous method executions
+ export const enabled = Trace.isEnabled();
+
+ export function isLogEnabled() {
+ return Trace.isEnabled();
+ }
+
+ export function animationsLog(message: string): void {
+ Trace.write(message, animationsTraceCategory);
+ }
+
+ export function rendererLog(msg): void {
+ Trace.write(msg, rendererTraceCategory);
+ }
+
+ export function rendererError(message: string): void {
+ Trace.write(message, rendererTraceCategory, Trace.messageType.error);
+ }
+
+ export function viewUtilLog(msg): void {
+ Trace.write(msg, viewUtilCategory);
+ }
+
+ export function routerLog(message: string): void {
+ Trace.write(message, routerTraceCategory);
+ }
+
+ export function routerError(message: string): void {
+ Trace.write(message, routerTraceCategory, Trace.messageType.error);
+ }
+
+ export function routeReuseStrategyLog(message: string): void {
+ Trace.write(message, routeReuseStrategyTraceCategory);
+ }
+
+ export function styleError(message: string): void {
+ Trace.write(message, Trace.categories.Style, Trace.messageType.error);
+ }
+
+ export function listViewLog(message: string): void {
+ Trace.write(message, listViewTraceCategory);
+ }
+
+ export function listViewError(message: string): void {
+ Trace.write(message, listViewTraceCategory, Trace.messageType.error);
+ }
+
+ export function bootstrapLog(message: string): void {
+ Trace.write(message, bootstrapCategory);
+ }
+
+ export function bootstrapLogError(message: string): void {
+ Trace.write(message, bootstrapCategory, Trace.messageType.error);
+ }
+}
diff --git a/nativescript-angular/tsconfig.json b/nativescript-angular/tsconfig.json
new file mode 100644
index 000000000..a5a311bc6
--- /dev/null
+++ b/nativescript-angular/tsconfig.json
@@ -0,0 +1,36 @@
+{
+ "compilerOptions": {
+ "target": "es2017",
+ "module": "esnext",
+ "moduleResolution": "node",
+ "sourceMap": true,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "noImplicitUseStrict": true,
+ "noEmitHelpers": true,
+ "declaration": true,
+ "removeComments": true,
+ "noEmitOnError": true,
+ "skipLibCheck": true,
+ "noImplicitAny": false,
+ "lib": [
+ "es2017",
+ "dom",
+ "es6"
+ ],
+ "baseUrl": "."
+ },
+ "angularCompilerOptions": {
+ "genDir": ".",
+ "skipMetadataEmit": false,
+ "skipTemplateCodegen": true,
+ "strictMetadataEmit": true,
+ "enableIvy": true
+ },
+ "include": [
+ "**/*.ts"
+ ],
+ "exclude": [
+ "dist"
+ ]
+}
\ No newline at end of file
diff --git a/nativescript-angular/tsconfig.typedoc.json b/nativescript-angular/tsconfig.typedoc.json
new file mode 100644
index 000000000..8c0e4efa5
--- /dev/null
+++ b/nativescript-angular/tsconfig.typedoc.json
@@ -0,0 +1,60 @@
+{"compilerOptions": {
+ "target": "es2015",
+ "module": "esnext",
+ "moduleResolution": "node",
+ "sourceMap": true,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "noImplicitUseStrict": true,
+ "noEmitHelpers": true,
+ "declaration": true,
+ "removeComments": false,
+ "noEmitOnError": true,
+ "skipLibCheck": true,
+ "noImplicitAny": false,
+ "lib": [
+ "dom",
+ "es6",
+ "es2015.iterable"
+ ],
+ "baseUrl": ".",
+ "paths": {
+ "*": [
+ "./node_modules/*"
+ ]
+ }
+ },
+ "angularCompilerOptions": {
+ "genDir": ".",
+ "skipMetadataEmit": false,
+ "skipTemplateCodegen": true,
+ "strictMetadataEmit": true
+ },
+"exclude": [
+ "./node_modules",
+ "@nativescript/core/references.d.ts",
+ "@nativescript/core/node_modules",
+ "@nativescript/core/ui/frame/transition-definitions.android.d.ts",
+ "./zone-js",
+ "./index.ts",
+ "./bin",
+ "./index.d.ts",
+ "./testing",
+ "./animations/index.ts",
+ "./animations/utils.ts",
+ "./app-host-view.ts",
+ "./common/utils.ts",
+ "./dom-adapter.ts",
+ "./file-system",
+ "./forms/index.ts",
+ "./forms/value-accessors/index.ts",
+ "./lang-facade.ts",
+ "./polyfills",
+ "./router/private-imports",
+ "./schema-registry.ts",
+ "./http-client/index.ts",
+ "./router/index.ts",
+ ""
+
+ ]
+}
\ No newline at end of file
diff --git a/nativescript-angular/tslint.json b/nativescript-angular/tslint.json
new file mode 100644
index 000000000..7d0e4cc7a
--- /dev/null
+++ b/nativescript-angular/tslint.json
@@ -0,0 +1,138 @@
+{
+ "rulesDirectory": [
+ "node_modules/codelyzer"
+ ],
+ "linterOptions": {
+ "exclude": [
+ "**/node_modules",
+ "**/__test__",
+ "**/*.d.ts",
+ "**/zone-js/dist/*.ts"
+ ]
+ },
+ "rules":{
+ "directive-selector": [true, "attribute", "ns", "camelCase"],
+ "component-selector": [true, "element", "", "camelCase"],
+ "no-inputs-metadata-property": true,
+ "no-outputs-metadata-property": true,
+ "no-input-rename": true,
+ "no-output-rename": true,
+ "use-pipe-transform-interface": true,
+ "component-class-suffix": true,
+ "directive-class-suffix": true,
+ "member-access": false,
+ "no-any": false,
+ "no-inferrable-types": false,
+ "no-internal-module": true,
+ "no-var-requires": false,
+ "typedef": false,
+ "typedef-whitespace": [
+ true,
+ {
+ "call-signature": "nospace",
+ "index-signature": "nospace",
+ "parameter": "nospace",
+ "property-declaration": "nospace",
+ "variable-declaration": "nospace"
+ },
+ {
+ "call-signature": "space",
+ "index-signature": "space",
+ "parameter": "space",
+ "property-declaration": "space",
+ "variable-declaration": "space"
+ }
+ ],
+
+ "ban": false,
+ "curly": true,
+ "no-arg": true,
+ "no-bitwise": true,
+ "no-conditional-assignment": true,
+ "no-console": [
+ true,
+ "debug",
+ "info",
+ "time",
+ "timeEnd",
+ "trace"
+ ],
+ "no-construct": true,
+ "no-debugger": true,
+ "no-duplicate-variable": true,
+ "no-empty": false,
+ "no-eval": true,
+ "no-null-keyword": false,
+ "no-shadowed-variable": true,
+ "no-string-literal": false,
+ "no-switch-case-fall-through": true,
+ "no-unused-expression": true,
+ "no-var-keyword": true,
+ "radix": false,
+ "switch-default": true,
+ "triple-equals": [
+ true,
+ "allow-null-check"
+ ],
+ "eofline": true,
+ "indent": [
+ true,
+ "spaces"
+ ],
+ "max-line-length": [
+ true,
+ 600
+ ],
+ "no-require-imports": false,
+ "no-trailing-whitespace": true,
+ "object-literal-sort-keys": false,
+ "trailing-comma": [
+ true,
+ {
+ "multiline": false,
+ "singleline": "never"
+ }
+ ],
+
+ "align": false,
+ "class-name": true,
+ "comment-format": [
+ true,
+ "check-space"
+ ],
+ "interface-name": false,
+ "jsdoc-format": false,
+ "no-consecutive-blank-lines": [
+ true, 2
+ ],
+ "one-line": [
+ true,
+ "check-open-brace",
+ "check-catch",
+ "check-else",
+ "check-finally",
+ "check-whitespace"
+ ],
+ "quotemark": [
+ true,
+ "single",
+ "avoid-escape"
+ ],
+ "semicolon": [true, "always"],
+ "variable-name": [
+ true,
+ "check-format",
+ "allow-leading-underscore",
+ "ban-keywords"
+ ],
+ "whitespace": [
+ true,
+ "check-branch",
+ "check-decl",
+ "check-module",
+ "check-operator",
+ "check-separator",
+ "check-type"
+ ]
+ }
+}
diff --git a/nativescript-angular/value-accessors/base-value-accessor.ts b/nativescript-angular/value-accessors/base-value-accessor.ts
new file mode 100644
index 000000000..f19da818f
--- /dev/null
+++ b/nativescript-angular/value-accessors/base-value-accessor.ts
@@ -0,0 +1,3 @@
+// This file is only for compatibility with pre 4.4.0 releases.
+// Please use "nativescript-angular/forms/value-accessors/base-value-accessor"
+export * from '../forms/value-accessors/base-value-accessor';
diff --git a/nativescript-angular/view-util.ts b/nativescript-angular/view-util.ts
new file mode 100644
index 000000000..d254b8fd6
--- /dev/null
+++ b/nativescript-angular/view-util.ts
@@ -0,0 +1,439 @@
+import { View, unsetValue, Placeholder, ContentView, LayoutBase, platformNames, Device } from '@nativescript/core';
+import { CommentNode, InvisibleNode, NgView, TextNode, ViewExtensions, getViewClass, getViewMeta, isDetachedElement, isInvisibleNode, isKnownView, isView } from './element-registry';
+
+import { NativeScriptDebug } from './trace';
+
+const ELEMENT_NODE_TYPE = 1;
+const XML_ATTRIBUTES = Object.freeze(['style', 'rows', 'columns', 'fontAttributes']);
+const whiteSpaceSplitter = /\s+/;
+
+// export type ViewExtensions = ViewExtensions;
+// export type NgView = NgView;
+export type NgLayoutBase = LayoutBase & ViewExtensions;
+export type NgContentView = ContentView & ViewExtensions;
+export type NgPlaceholder = Placeholder & ViewExtensions;
+export type BeforeAttachAction = (view: View) => void;
+
+export function isLayout(view: any): view is NgLayoutBase {
+ return view instanceof LayoutBase;
+}
+
+export function isContentView(view: any): view is NgContentView {
+ return view instanceof ContentView;
+}
+
+const propertyMaps: Map> = new Map>();
+
+export class ViewUtil {
+ private isIos: boolean;
+ private isAndroid: boolean;
+
+ constructor(device: typeof Device) {
+ this.isIos = device.os === platformNames.ios;
+ this.isAndroid = device.os === platformNames.android;
+ }
+
+ public insertChild(parent: View, child: View, previous?: NgView, next?: NgView) {
+ if (!parent) {
+ return;
+ }
+
+ const extendedParent = this.ensureNgViewExtensions(parent);
+ const extendedChild = this.ensureNgViewExtensions(child);
+
+ // the element should be between previous and next
+ // if there's next but no previous, it's the first element
+ // if there's previous but no next, it's the last element
+ // elements that have no previous/next elements must be appended
+ if (!previous && !next) {
+ previous = extendedParent.lastChild;
+ }
+ this.addToQueue(extendedParent, extendedChild, previous, next);
+
+ if (isInvisibleNode(child)) {
+ extendedChild.parentNode = extendedParent;
+ }
+
+ if (!isDetachedElement(child)) {
+ const nextVisual = this.findNextVisual(next);
+ this.addToVisualTree(extendedParent, extendedChild, nextVisual);
+ }
+ }
+
+ private addToQueue(parent: NgView, child: NgView, previous: NgView, next: NgView): void {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.viewUtilLog(`ViewUtil.addToQueue parent: ${parent}, view: ${child}, ` + `previous: ${previous}, next: ${next}`);
+ }
+
+ if (previous) {
+ previous.nextSibling = child;
+ child.previousSibling = previous;
+ } else {
+ parent.firstChild = child;
+ }
+
+ if (next) {
+ child.nextSibling = next;
+ next.previousSibling = child;
+ } else {
+ this.appendToQueue(parent, child);
+ }
+ }
+
+ private appendToQueue(parent: NgView, view: NgView) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.viewUtilLog(`ViewUtil.appendToQueue parent: ${parent} view: ${view}`);
+ }
+
+ if (parent.lastChild) {
+ parent.lastChild.nextSibling = view;
+ view.previousSibling = parent.lastChild;
+ }
+
+ parent.lastChild = view;
+ }
+
+ private addToVisualTree(parent: NgView, child: NgView, next: NgView): void {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.viewUtilLog(`ViewUtil.addToVisualTree parent: ${parent}, view: ${child}, next: ${next}`);
+ }
+
+ if (parent.meta && parent.meta.insertChild) {
+ parent.meta.insertChild(parent, child, next);
+ } else if (isLayout(parent)) {
+ this.insertToLayout(parent, child, next);
+ } else if (isContentView(parent)) {
+ parent.content = child;
+ } else if (parent && (parent)._addChildFromBuilder) {
+ (parent)._addChildFromBuilder(child.nodeName, child);
+ }
+ }
+
+ private insertToLayout(parent: NgLayoutBase, child: NgView, next: NgView): void {
+ if (child.parent === parent) {
+ this.removeLayoutChild(parent, child);
+ }
+
+ const nextVisual = this.findNextVisual(next);
+ if (nextVisual) {
+ const index = parent.getChildIndex(nextVisual);
+ parent.insertChild(child, index);
+ } else {
+ parent.addChild(child);
+ }
+ }
+
+ private findNextVisual(view: NgView): NgView {
+ let next = view;
+ while (next && isDetachedElement(next)) {
+ next = next.nextSibling;
+ }
+
+ return next;
+ }
+
+ public removeChild(parent: View, child: View) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.viewUtilLog(`ViewUtil.removeChild parent: ${parent} child: ${child}`);
+ }
+
+ if (!parent) {
+ return;
+ }
+
+ const extendedParent = this.ensureNgViewExtensions(parent);
+ const extendedChild = this.ensureNgViewExtensions(child);
+
+ this.removeFromQueue(extendedParent, extendedChild);
+ if (!isDetachedElement(extendedChild)) {
+ this.removeFromVisualTree(extendedParent, extendedChild);
+ }
+ }
+
+ private removeFromQueue(parent: NgView, child: NgView) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.viewUtilLog(`ViewUtil.removeFromQueue parent: ${parent} child: ${child}`);
+ }
+
+ if (parent.firstChild === child && parent.lastChild === child) {
+ parent.firstChild = null;
+ parent.lastChild = null;
+ child.nextSibling = null;
+ child.previousSibling = null;
+ return;
+ }
+
+ if (parent.firstChild === child) {
+ parent.firstChild = child.nextSibling;
+ }
+
+ const previous = child.previousSibling;
+ if (parent.lastChild === child) {
+ parent.lastChild = previous;
+ }
+
+ if (previous) {
+ previous.nextSibling = child.nextSibling;
+ if (child.nextSibling) {
+ child.nextSibling.previousSibling = previous;
+ }
+ }
+
+ child.nextSibling = null;
+ child.previousSibling = null;
+ }
+
+ // NOTE: This one is O(n) - use carefully
+ private findPreviousElement(parent: NgView, child: NgView): NgView {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.viewUtilLog(`ViewUtil.findPreviousElement parent: ${parent} child: ${child}`);
+ }
+
+ let previousVisual;
+ if (isLayout(parent)) {
+ previousVisual = this.getPreviousVisualElement(parent, child);
+ }
+
+ let previous = previousVisual || parent.firstChild;
+
+ // since detached elements are not added to the visual tree,
+ // we need to find the actual previous sibling of the view,
+ // which may as well be an invisible node
+ while (previous && previous !== child && previous.nextSibling !== child) {
+ previous = previous.nextSibling;
+ }
+
+ return previous;
+ }
+
+ private getPreviousVisualElement(parent: NgLayoutBase, child: NgView): NgView {
+ const elementIndex = parent.getChildIndex(child);
+
+ if (elementIndex > 0) {
+ return parent.getChildAt(elementIndex - 1) as NgView;
+ }
+ }
+
+ // NOTE: This one is O(n) - use carefully
+ public getChildIndex(parent: any, child: NgView) {
+ if (isLayout(parent)) {
+ return parent.getChildIndex(child);
+ } else if (isContentView(parent)) {
+ return child === parent.content ? 0 : -1;
+ }
+ }
+
+ private removeFromVisualTree(parent: NgView, child: NgView) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.viewUtilLog(`ViewUtil.removeFromVisualTree parent: ${parent} child: ${child}`);
+ }
+
+ if (parent.meta && parent.meta.removeChild) {
+ parent.meta.removeChild(parent, child);
+ } else if (isLayout(parent)) {
+ this.removeLayoutChild(parent, child);
+ } else if (isContentView(parent) && parent.content === child) {
+ parent.content = null;
+ } else if (isView(parent)) {
+ parent._removeView(child);
+ }
+ }
+
+ private removeLayoutChild(parent: NgLayoutBase, child: NgView): void {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.viewUtilLog(`ViewUtil.removeLayoutChild parent: ${parent} child: ${child}`);
+ }
+
+ const index = parent.getChildIndex(child);
+
+ if (index !== -1) {
+ parent.removeChild(child);
+ }
+ }
+
+ public createComment(): InvisibleNode {
+ return new CommentNode();
+ }
+
+ public createText(): InvisibleNode {
+ return new TextNode();
+ }
+
+ public createView(name: string): NgView {
+ const originalName = name;
+ if (!isKnownView(name)) {
+ name = 'ProxyViewContainer';
+ }
+
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.viewUtilLog(`Creating view: ${originalName} ${name}`);
+ }
+
+ const viewClass = getViewClass(name);
+ const view = new viewClass();
+ const ngView = this.setNgViewExtensions(view, name);
+
+ return ngView;
+ }
+
+ private ensureNgViewExtensions(view: View): NgView {
+ if (view.hasOwnProperty('meta')) {
+ return view as NgView;
+ } else {
+ const name = view.cssType;
+ const ngView = this.setNgViewExtensions(view, name);
+
+ return ngView;
+ }
+ }
+
+ private setNgViewExtensions(view: View, name: string): NgView {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.viewUtilLog(`Make into a NgView view: ${view} name: "${name}"`);
+ }
+
+ const ngView = view as NgView;
+ ngView.nodeName = name;
+ ngView.meta = getViewMeta(name);
+
+ // we're setting the node type of the view
+ // to 'element' because of checks done in the
+ // dom animation engine
+ ngView.nodeType = ELEMENT_NODE_TYPE;
+
+ return ngView;
+ }
+
+ public setProperty(view: NgView, attributeName: string, value: any, namespace?: string): void {
+ if (!view || (namespace && !this.runsIn(namespace))) {
+ return;
+ }
+
+ if (attributeName.indexOf('.') !== -1) {
+ // Handle nested properties
+ const properties = attributeName.split('.');
+ attributeName = properties[properties.length - 1];
+
+ let propMap = this.getProperties(view);
+ let i = 0;
+ while (i < properties.length - 1 && typeof view !== 'undefined') {
+ let prop = properties[i];
+ if (propMap.has(prop)) {
+ prop = propMap.get(prop);
+ }
+
+ view = view[prop];
+ propMap = this.getProperties(view);
+ i++;
+ }
+ }
+
+ if (typeof view !== 'undefined') {
+ this.setPropertyInternal(view, attributeName, value);
+ }
+ }
+
+ private runsIn(platform: string): boolean {
+ return (platform === 'ios' && this.isIos) || (platform === 'android' && this.isAndroid);
+ }
+
+ private setPropertyInternal(view: NgView, attributeName: string, value: any): void {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.viewUtilLog(`Setting attribute: ${attributeName}=${value} to ${view}`);
+ }
+
+ if (attributeName === 'class') {
+ this.setClasses(view, value);
+ return;
+ }
+
+ if (XML_ATTRIBUTES.indexOf(attributeName) !== -1) {
+ view[attributeName] = value;
+ return;
+ }
+
+ const propMap = this.getProperties(view);
+ const propertyName = propMap.get(attributeName);
+
+ // Ensure the children of a collection currently have no parent set.
+ if (Array.isArray(value)) {
+ this.removeParentReferencesFromItems(value);
+ }
+
+ if (propertyName) {
+ // We have a lower-upper case mapped property.
+ view[propertyName] = value;
+ return;
+ }
+
+ // Unknown attribute value -- just set it to our object as is.
+ view[attributeName] = value;
+ }
+
+ private removeParentReferencesFromItems(items: any[]): void {
+ for (const item of items) {
+ if (item.parent && item.parentNode) {
+ if (NativeScriptDebug.isLogEnabled()) {
+ NativeScriptDebug.viewUtilLog(`Unassigning parent ${item.parentNode} on value: ${item}`);
+ }
+ item.parent = undefined;
+ item.parentNode = undefined;
+ }
+ }
+ }
+
+ private getProperties(instance: any): Map {
+ const type = instance && instance.constructor;
+ if (!type) {
+ return new Map();
+ }
+
+ if (!propertyMaps.has(type)) {
+ let propMap = new Map();
+ for (let propName in instance) {
+ // tslint:disable:forin
+ propMap.set(propName.toLowerCase(), propName);
+ }
+ propertyMaps.set(type, propMap);
+ }
+
+ return propertyMaps.get(type);
+ }
+
+ private cssClasses(view: NgView) {
+ if (!view.ngCssClasses) {
+ view.ngCssClasses = new Map();
+ }
+ return view.ngCssClasses;
+ }
+
+ public addClass(view: NgView, className: string): void {
+ this.cssClasses(view).set(className, true);
+ this.syncClasses(view);
+ }
+
+ public removeClass(view: NgView, className: string): void {
+ this.cssClasses(view).delete(className);
+ this.syncClasses(view);
+ }
+
+ private setClasses(view: NgView, classesValue: string): void {
+ let classes = classesValue.split(whiteSpaceSplitter);
+ this.cssClasses(view).clear();
+ classes.forEach((className) => this.cssClasses(view).set(className, true));
+ this.syncClasses(view);
+ }
+
+ private syncClasses(view: NgView): void {
+ let classValue = (