diff --git a/.gitignore b/.gitignore
index 9995f912d..7b21aa525 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,6 +21,7 @@ tags
.DS_Store
npm-debug.log
nativescript-angular*.tgz
+package-lock.json
tests/app/**/*.js
tests/test-output.txt
diff --git a/.travis.yml b/.travis.yml
index 8fe66b1b1..273f0a9c1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,22 +2,24 @@ env:
global:
- NODE_VERSION=6.11.1
- EMULATOR_API_LEVEL=19
- - ANDROID_VERSION=23
- - ANDROID_BUILD_TOOLS_VERSION=23.0.1
+ - ANDROID_VERSION=25
+ - ANDROID_BUILD_TOOLS_VERSION=25.0.2
- ANDROID_ABI=armeabi-v7a
- - ANDROID_TAG=google_apis
- - APPIUM_VERSION=1.6.5
+ - APPIUM_VERSION=1.7.0
+ - EMULATOR_NAME=test
language: android
jdk:
- oraclejdk8
android:
components:
- - platform-tools
- tools
+ - platform-tools
- build-tools-$ANDROID_BUILD_TOOLS_VERSION
- android-$ANDROID_VERSION
+ - android-$EMULATOR_API_LEVEL
+ - extra-android-support
- extra-android-m2repository
- - sys-img-armeabi-v7a-android-$EMULATOR_API_LEVEL
+ - sys-img-$ANDROID_ABI-android-$EMULATOR_API_LEVEL
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
cache:
@@ -27,6 +29,7 @@ cache:
install:
- nvm install $NODE_VERSION
- npm install -g typings
+- echo y | $ANDROID_HOME/tools/android update sdk --filter tools --all --no-ui
- wget -O ./nativescript.tgz "https://s3.amazonaws.com/nativescript-ci/build_result/nativescript.tgz"
- npm install -g nativescript.tgz --ignore-scripts
- tns usage-reporting disable
@@ -36,12 +39,10 @@ install:
- npm run tslint
- cd ../tests
- npm install
-- npm i appium@$APPIUM_VERSION
before_script:
- - echo no | android create avd --force -n test -t android-$EMULATOR_API_LEVEL --abi $ANDROID_ABI
- - emulator -memory 1024 -avd test -no-audio -no-window &
+ - echo no | android create avd --force -n $EMULATOR_NAME -t android-$EMULATOR_API_LEVEL --abi $ANDROID_ABI -c 12M
+ - emulator -avd $EMULATOR_NAME -no-skin -no-audio -no-window &
- android-wait-for-emulator
script:
- tns build android
- android-wait-for-emulator
-- npm run appium --runtype=android19 --appium=$APPIUM_VERSION
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5bddf5fb4..abcff17b1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,27 @@
+
+# [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))
+
+
+### Reverts
+
+* **router:** page navigation with RouteReuseStrategy (#987) ([421978e](https://github.com/NativeScript/nativescript-angular/commit/421978e))
+
+
+
# [4.2.0](https://github.com/NativeScript/nativescript-angular/compare/3.1.3...4.2.0) (2017-08-09)
@@ -12,7 +36,7 @@
module (usually named `AppModule`).
All other NgModules in the app (both feature and lazy-loaded ones)
should import the `NativeScriptCommonModule` instead.
-The behaviour is alligned with `BrowserModule` and `CommonModule` in web
+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:
diff --git a/README.md b/README.md
index 44d7e2a50..2416a971a 100644
--- a/README.md
+++ b/README.md
@@ -53,7 +53,7 @@ $ tns test android --emulator
# Developer workflow:
## Setup:
-Use `npm link` to link `nativescript-angular` in `tests` and `ng-sample` progects:
+Use `npm link` to link `nativescript-angular` in `tests` and `ng-sample` projects:
```
cd nativescript-angular
diff --git a/e2e/renderer/.gitignore b/e2e/renderer/.gitignore
new file mode 100644
index 000000000..5a67ca289
--- /dev/null
+++ b/e2e/renderer/.gitignore
@@ -0,0 +1,9 @@
+platforms
+node_modules
+hooks
+
+app/**/*.js
+e2e/**/*.js
+test-results.xml
+e2e/reports
+
diff --git a/e2e/renderer/.vscode/launch.json b/e2e/renderer/.vscode/launch.json
new file mode 100644
index 000000000..cd10b18ce
--- /dev/null
+++ b/e2e/renderer/.vscode/launch.json
@@ -0,0 +1,23 @@
+{
+ "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",
+ "--reuseDevice",
+ "--runType",
+ "sim.iPhone7.iOS110",
+ "${workspaceRoot}/e2e"
+ ],
+ "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..699fb4408
--- /dev/null
+++ b/e2e/renderer/.vscode/settings.json
@@ -0,0 +1,19 @@
+{
+ "typescript.check.tscVersion": false,
+ "window.zoomLevel": 0,
+ "workbench.iconTheme": "vscode-icons",
+ "vsicons.dontShowNewVersionMessage": true,
+ "workbench.colorTheme": "Default Light+",
+ "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/AndroidManifest.xml b/e2e/renderer/app/App_Resources/Android/AndroidManifest.xml
new file mode 100644
index 000000000..9db832151
--- /dev/null
+++ b/e2e/renderer/app/App_Resources/Android/AndroidManifest.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/drawable-hdpi/background.png b/e2e/renderer/app/App_Resources/Android/drawable-hdpi/background.png
new file mode 100644
index 000000000..eb381c258
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/drawable-hdpi/background.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/drawable-hdpi/icon.png b/e2e/renderer/app/App_Resources/Android/drawable-hdpi/icon.png
new file mode 100755
index 000000000..1034356e2
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/drawable-hdpi/icon.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/drawable-hdpi/logo.png b/e2e/renderer/app/App_Resources/Android/drawable-hdpi/logo.png
new file mode 100644
index 000000000..5218f4c90
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/drawable-hdpi/logo.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/drawable-ldpi/background.png b/e2e/renderer/app/App_Resources/Android/drawable-ldpi/background.png
new file mode 100644
index 000000000..748b2adf5
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/drawable-ldpi/background.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/drawable-ldpi/icon.png b/e2e/renderer/app/App_Resources/Android/drawable-ldpi/icon.png
new file mode 100755
index 000000000..ddfc17a71
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/drawable-ldpi/icon.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/drawable-ldpi/logo.png b/e2e/renderer/app/App_Resources/Android/drawable-ldpi/logo.png
new file mode 100644
index 000000000..b9e102a76
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/drawable-ldpi/logo.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/drawable-mdpi/background.png b/e2e/renderer/app/App_Resources/Android/drawable-mdpi/background.png
new file mode 100644
index 000000000..efeaf2907
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/drawable-mdpi/background.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/drawable-mdpi/icon.png b/e2e/renderer/app/App_Resources/Android/drawable-mdpi/icon.png
new file mode 100755
index 000000000..486e41091
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/drawable-mdpi/icon.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/drawable-mdpi/logo.png b/e2e/renderer/app/App_Resources/Android/drawable-mdpi/logo.png
new file mode 100644
index 000000000..626338766
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/drawable-mdpi/logo.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/drawable-nodpi/splash_screen.xml b/e2e/renderer/app/App_Resources/Android/drawable-nodpi/splash_screen.xml
new file mode 100644
index 000000000..ada77f92c
--- /dev/null
+++ b/e2e/renderer/app/App_Resources/Android/drawable-nodpi/splash_screen.xml
@@ -0,0 +1,8 @@
+
+ -
+
+
+ -
+
+
+
\ No newline at end of file
diff --git a/e2e/renderer/app/App_Resources/Android/drawable-xhdpi/background.png b/e2e/renderer/app/App_Resources/Android/drawable-xhdpi/background.png
new file mode 100644
index 000000000..612bbd072
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/drawable-xhdpi/background.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/drawable-xhdpi/icon.png b/e2e/renderer/app/App_Resources/Android/drawable-xhdpi/icon.png
new file mode 100644
index 000000000..f29188209
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/drawable-xhdpi/icon.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/drawable-xhdpi/logo.png b/e2e/renderer/app/App_Resources/Android/drawable-xhdpi/logo.png
new file mode 100644
index 000000000..ad8ee2f4b
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/drawable-xhdpi/logo.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/drawable-xxhdpi/background.png b/e2e/renderer/app/App_Resources/Android/drawable-xxhdpi/background.png
new file mode 100644
index 000000000..0fa88e235
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/drawable-xxhdpi/background.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/drawable-xxhdpi/icon.png b/e2e/renderer/app/App_Resources/Android/drawable-xxhdpi/icon.png
new file mode 100644
index 000000000..4f69cb25b
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/drawable-xxhdpi/icon.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/drawable-xxhdpi/logo.png b/e2e/renderer/app/App_Resources/Android/drawable-xxhdpi/logo.png
new file mode 100644
index 000000000..668327832
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/drawable-xxhdpi/logo.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/drawable-xxxhdpi/background.png b/e2e/renderer/app/App_Resources/Android/drawable-xxxhdpi/background.png
new file mode 100644
index 000000000..c650f6438
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/drawable-xxxhdpi/background.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/drawable-xxxhdpi/icon.png b/e2e/renderer/app/App_Resources/Android/drawable-xxxhdpi/icon.png
new file mode 100644
index 000000000..50887a856
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/drawable-xxxhdpi/icon.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/drawable-xxxhdpi/logo.png b/e2e/renderer/app/App_Resources/Android/drawable-xxxhdpi/logo.png
new file mode 100644
index 000000000..fa6331c8d
Binary files /dev/null and b/e2e/renderer/app/App_Resources/Android/drawable-xxxhdpi/logo.png differ
diff --git a/e2e/renderer/app/App_Resources/Android/values-v21/colors.xml b/e2e/renderer/app/App_Resources/Android/values-v21/colors.xml
new file mode 100644
index 000000000..a64641a9d
--- /dev/null
+++ b/e2e/renderer/app/App_Resources/Android/values-v21/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #3d5afe
+
\ No newline at end of file
diff --git a/e2e/renderer/app/App_Resources/Android/values-v21/styles.xml b/e2e/renderer/app/App_Resources/Android/values-v21/styles.xml
new file mode 100644
index 000000000..dac8727c8
--- /dev/null
+++ b/e2e/renderer/app/App_Resources/Android/values-v21/styles.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/renderer/app/App_Resources/Android/values/colors.xml b/e2e/renderer/app/App_Resources/Android/values/colors.xml
new file mode 100644
index 000000000..74ad8829c
--- /dev/null
+++ b/e2e/renderer/app/App_Resources/Android/values/colors.xml
@@ -0,0 +1,7 @@
+
+
+ #F5F5F5
+ #757575
+ #33B5E5
+ #272734
+
\ No newline at end of file
diff --git a/e2e/renderer/app/App_Resources/Android/values/styles.xml b/e2e/renderer/app/App_Resources/Android/values/styles.xml
new file mode 100644
index 000000000..1e8c7f29b
--- /dev/null
+++ b/e2e/renderer/app/App_Resources/Android/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/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..a975d5be8
--- /dev/null
+++ b/e2e/renderer/app/app-routing.module.ts
@@ -0,0 +1,89 @@
+import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
+import { NativeScriptRouterModule } from "nativescript-angular/router";
+
+import { ActionBarDynamicItemsComponent } from "./action-bar/action-bar-dynamic-items.component";
+import { ActionBarExtensionComponent } from "./action-bar/action-bar-extension.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-dynamic",
+ component: ActionBarDynamicItemsComponent,
+ },
+ {
+ path: "action-bar-extension",
+ component: ActionBarExtensionComponent,
+ },
+ {
+ 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,
+
+ 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.ts b/e2e/renderer/app/app.module.ts
new file mode 100644
index 000000000..c5bf7e4dd
--- /dev/null
+++ b/e2e/renderer/app/app.module.ts
@@ -0,0 +1,33 @@
+import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
+import { NativeScriptModule } from "nativescript-angular/nativescript.module";
+
+import {
+ AppRoutingModule,
+ navigatableComponents,
+} from "./app-routing.module";
+
+import { AppComponent } from "./app.component";
+import { ItemsService } from "./items.service";
+
+import { rendererTraceCategory, viewUtilCategory } from "nativescript-angular/trace";
+import { setCategories, enable } from "trace";
+setCategories(rendererTraceCategory + "," + viewUtilCategory);
+enable();
+
+@NgModule({
+ declarations: [
+ AppComponent,
+ ...navigatableComponents,
+ ],
+ bootstrap: [AppComponent],
+ providers: [
+ ItemsService
+ ],
+ imports: [
+ NativeScriptModule,
+ AppRoutingModule,
+ ],
+ 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..58774bd5a
--- /dev/null
+++ b/e2e/renderer/app/items-accessor.ts
@@ -0,0 +1,18 @@
+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) {
+ 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..ddbaa8b95
--- /dev/null
+++ b/e2e/renderer/app/list.component.ts
@@ -0,0 +1,20 @@
+import { Component } from "@angular/core";
+
+@Component({
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+})
+export class ListComponent {
+}
diff --git a/e2e/renderer/app/main.aot.ts b/e2e/renderer/app/main.aot.ts
new file mode 100644
index 000000000..98bf134fc
--- /dev/null
+++ b/e2e/renderer/app/main.aot.ts
@@ -0,0 +1,4 @@
+import { platformNativeScript } from "nativescript-angular/platform-static";
+import { AppModuleNgFactory } from "./app.module.ngfactory";
+
+platformNativeScript().bootstrapModuleFactory(AppModuleNgFactory);
diff --git a/e2e/renderer/app/main.ts b/e2e/renderer/app/main.ts
new file mode 100644
index 000000000..639bfd513
--- /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..d5356c0cc
--- /dev/null
+++ b/e2e/renderer/app/package.json
@@ -0,0 +1,5 @@
+{
+ "main": "main.js",
+ "name": "nativescript-template-ng-tutorial",
+ "version": "3.1.0"
+}
\ No newline at end of file
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..312df8a19
--- /dev/null
+++ b/e2e/renderer/e2e/action-bar.e2e-spec.ts
@@ -0,0 +1,148 @@
+import {
+ AppiumDriver,
+ createDriver,
+ SearchOptions,
+ UIElement
+} from "nativescript-dev-appium";
+
+import { isOnTheLeft } from "./helpers/location";
+
+describe("Action Bar scenario", () => {
+ let driver: AppiumDriver;
+
+ describe("dynamically add/remove ActionItems", async () => {
+ let firstActionItem: UIElement;
+ let secondActionItem: UIElement;
+ let toggleFirstButton: UIElement;
+ let toggleSecondButton: UIElement;
+
+ before(async () => {
+ driver = await createDriver();
+ await driver.driver.resetApp();
+ });
+
+ it("should navigate to page", async () => {
+ const navigationButton =
+ await driver.findElementByText("ActionBar dynamic", SearchOptions.exact);
+ await navigationButton.click();
+
+ const actionBar =
+ await driver.findElementByText("Action Bar Dynamic Items", SearchOptions.exact);
+ });
+
+ it("should find elements", async () => {
+ firstActionItem = await driver.findElementByText("one");
+ secondActionItem = await driver.findElementByText("two");
+
+ toggleFirstButton = await driver.findElementByText("toggle 1");
+ toggleSecondButton = await driver.findElementByText("toggle 2");
+ });
+
+ it("should initially render the action items in the correct order", async () => {
+ await checkOrderIsCorrect();
+ });
+
+ it("should detach first element when its condition is false", done => {
+ (async () => {
+ await toggleFirst();
+
+ try {
+ await driver.findElementByText("one", SearchOptions.exact);
+ } catch (e) {
+ done();
+ }
+ })();
+ });
+
+ it("should attach first element in the correct position", async () => {
+ await toggleFirst();
+ await checkOrderIsCorrect();
+ });
+
+ it("should detach second element when its condition is false", done => {
+ (async () => {
+ await toggleSecond();
+
+ try {
+ await driver.findElementByText("two", SearchOptions.exact);
+ } catch (e) {
+ done();
+ }
+ })();
+ });
+
+ it("should attach second element in the correct position", async () => {
+ await toggleSecond();
+ await checkOrderIsCorrect();
+ });
+
+ it("should detach and then reattach both at correct places", async () => {
+ await toggleFirst();
+ await toggleSecond();
+
+ await toggleFirst();
+ await toggleSecond();
+
+ await checkOrderIsCorrect();
+ });
+
+ const checkOrderIsCorrect = async () => {
+ await isOnTheLeft(firstActionItem, secondActionItem);
+ };
+
+ const toggleFirst = async () => {
+ await toggleFirstButton.click();
+ };
+
+ const toggleSecond = async () => {
+ await toggleSecondButton.click();
+ };
+
+ });
+
+ describe("Action Bar extension with dynamic ActionItem", async () => {
+ let toggleButton: UIElement;
+ let conditional: UIElement;
+
+ before(async () => {
+ driver = await createDriver();
+ await driver.driver.resetApp();
+ });
+
+ it("should navigate to page", async () => {
+ const navigationButton =
+ await driver.findElementByText("ActionBarExtension", SearchOptions.exact);
+ await navigationButton.click();
+ });
+
+ it("should find elements", async () => {
+ toggleButton = await driver.findElementByText("toggle");
+ conditional = await driver.findElementByText("conditional");
+ });
+
+ it("should detach conditional action item when its condition is false", done => {
+ (async () => {
+ await toggle();
+
+ try {
+ await driver.findElementByText("conditional", SearchOptions.exact);
+ } catch (e) {
+ done();
+ }
+ })();
+ });
+
+ it("should reattach conditional action item at correct place", async () => {
+ await toggle();
+ await checkOrderIsCorrect();
+ });
+
+ const checkOrderIsCorrect = async () => {
+ await isOnTheLeft(toggleButton, conditional);
+ };
+
+ const toggle = async () => {
+ await toggleButton.click();
+ };
+ });
+});
diff --git a/e2e/renderer/e2e/config/appium.capabilities.json b/e2e/renderer/e2e/config/appium.capabilities.json
new file mode 100644
index 000000000..4f1b4407d
--- /dev/null
+++ b/e2e/renderer/e2e/config/appium.capabilities.json
@@ -0,0 +1,100 @@
+{
+ "nexus5": {
+ "browserName": "",
+ "platformName": "Android",
+ "platformVersion": "6.0",
+ "deviceName": "device",
+ "udid": "077e4a47003b7698",
+ "appActivity": "com.tns.NativeScriptActivity",
+ "app": ""
+ },
+ "android19": {
+ "platformName": "Android",
+ "platformVersion": "4.4",
+ "deviceName": "Emulator-Api19-Default",
+ "avd": "Emulator-Api19-Default",
+ "lt": 60000,
+ "appActivity": "com.tns.NativeScriptActivity",
+ "newCommandTimeout": 720,
+ "noReset": false,
+ "fullReset": false,
+ "app": ""
+ },
+ "android21": {
+ "platformName": "Android",
+ "platformVersion": "5.0",
+ "deviceName": "Emulator-Api21-Default",
+ "avd": "Emulator-Api21-Default",
+ "lt": 60000,
+ "appActivity": "com.tns.NativeScriptActivity",
+ "newCommandTimeout": 720,
+ "noReset": false,
+ "fullReset": false,
+ "app": ""
+ },
+ "android23": {
+ "platformName": "Android",
+ "platformVersion": "6.0",
+ "deviceName": "Emulator-Api23-Default",
+ "avd": "Emulator-Api23-Default",
+ "lt": 60000,
+ "appActivity": "com.tns.NativeScriptActivity",
+ "newCommandTimeout": 720,
+ "noReset": false,
+ "fullReset": false,
+ "app": ""
+ },
+ "android24": {
+ "platformName": "Android",
+ "platformVersion": "7.0",
+ "deviceName": "Emulator-Api24-Default",
+ "avd": "Emulator-Api24-Default",
+ "lt": 60000,
+ "appActivity": "com.tns.NativeScriptActivity",
+ "newCommandTimeout": 720,
+ "noReset": false,
+ "fullReset": false,
+ "app": ""
+ },
+ "android25": {
+ "platformName": "Android",
+ "platformVersion": "7.1",
+ "deviceName": "Emulator-Api25-Google",
+ "avd": "Emulator-Api25-Google",
+ "lt": 60000,
+ "appActivity": "com.tns.NativeScriptActivity",
+ "newCommandTimeout": 720,
+ "noReset": false,
+ "fullReset": false,
+ "app": ""
+ },
+ "android26": {
+ "platformName": "Android",
+ "platformVersion": "8.0",
+ "deviceName": "Emulator-Api26-Google",
+ "avd": "Emulator-Api26-Google",
+ "lt": 60000,
+ "automationName": "UIAutomator2",
+ "appActivity": "com.tns.NativeScriptActivity",
+ "newCommandTimeout": 720,
+ "noReset": false,
+ "fullReset": false,
+ "app": ""
+ },
+ "sim.iPhone7.iOS100": {
+ "platformName": "iOS",
+ "platformVersion": "10.0",
+ "deviceName": "iPhone 7 100",
+ "noReset": false,
+ "fullReset": false,
+ "app": ""
+ },
+ "sim.iPhone7.iOS110": {
+ "platformName": "iOS",
+ "platformVersion": "11.0",
+ "deviceName": "iPhone 7 110",
+ "noReset": false,
+ "fullReset": false,
+ "app": ""
+ }
+}
\ No newline at end of file
diff --git a/e2e/renderer/e2e/config/mocha.opts b/e2e/renderer/e2e/config/mocha.opts
new file mode 100644
index 000000000..ea635b0dc
--- /dev/null
+++ b/e2e/renderer/e2e/config/mocha.opts
@@ -0,0 +1,4 @@
+--timeout 140000
+--recursive e2e
+--reporter mocha-multi
+--reporter-options spec=-,mocha-junit-reporter=test-results.xml
\ No newline at end of file
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..ff0f0a162
--- /dev/null
+++ b/e2e/renderer/e2e/ngfor.e2e-spec.ts
@@ -0,0 +1,117 @@
+import {
+ AppiumDriver,
+ createDriver,
+ SearchOptions,
+ UIElement
+} from "nativescript-dev-appium";
+
+import { isAbove } from "./helpers/location";
+
+describe("ngFor scenario", () => {
+ let driver: AppiumDriver;
+ let addButton: UIElement;
+ let removeButton: UIElement;
+ let elements: UIElement[] = [];
+ let lastAddedElementId = 0;
+
+ before(async () => {
+ driver = await createDriver();
+ await driver.driver.resetApp();
+ });
+
+ it("should navigate to page", async () => {
+ const navigationButton =
+ await driver.findElementByText("NgFor", SearchOptions.exact);
+ await navigationButton.click();
+
+ const actionBar =
+ await driver.findElementByText("ngFor", SearchOptions.exact);
+ });
+
+ it("should find elements", async () => {
+ const first = await driver.findElementByText(
+ lastAddedElementId.toString(), SearchOptions.exact);
+ elements.push(first);
+
+ addButton = await driver.findElementByText("add", SearchOptions.exact);
+ removeButton = await driver.findElementByText("remove", SearchOptions.exact);
+
+ await isAbove(first, addButton);
+ });
+
+ it("should render elements in correct order", async () => {
+ await isAbove(elements[0], addButton);
+ await isAbove(addButton, removeButton);
+ });
+
+ it("should place new elements in the right places", async () => {
+ for (let i = 0; i < 5; i += 1) {
+ await addElement();
+ await checkAppendedCorrectly();
+ }
+ });
+
+ it("shouldn't reorder elements when last is removed", async () => {
+ while (elements.length) {
+ await removeElement();
+ await checkCorrectOrderAll();
+ }
+ });
+
+ it("should render new elements correctly after all old ones are removed", async () => {
+ for (let i = 0; i < 5; i += 1) {
+ await addElement();
+ await checkCorrectOrderAll();
+ }
+ });
+
+ it("shouldn't reorder elements when middle is removed", async () => {
+ const middleIndex = Math.floor(elements.length / 2);
+ await removeElement(middleIndex);
+ await checkCorrectOrderAll();
+ });
+
+ const addElement = async () => {
+ await addButton.click();
+
+ lastAddedElementId += 1;
+ const newElement = await driver.findElementByText(
+ lastAddedElementId.toString(), SearchOptions.exact);
+
+ 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 () => {
+ const lastAdded = await driver.findElementByText(
+ lastAddedElementId.toString(), SearchOptions.exact);
+
+ await isAbove(elements.slice(-2)[0], lastAdded);
+ await isAbove(lastAdded, addButton);
+ await isAbove(addButton, removeButton);
+ };
+
+ const checkCorrectOrderAll = async () => {
+ 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..23f6b0eee
--- /dev/null
+++ b/e2e/renderer/e2e/ngforof.e2e-spec.ts
@@ -0,0 +1,147 @@
+import {
+ AppiumDriver,
+ createDriver,
+ SearchOptions,
+ UIElement
+} from "nativescript-dev-appium";
+
+import { isAbove } from "./helpers/location";
+
+interface ElementTuple {
+ label: UIElement,
+ button: UIElement,
+}
+
+describe("ngForOf scenario", () => {
+ let driver: AppiumDriver;
+ let addButton: UIElement;
+ let removeButton: UIElement;
+ let elements: ElementTuple[] = [];
+ let lastAddedElementId = 0;
+
+ before(async () => {
+ driver = await createDriver();
+ await driver.driver.resetApp();
+ });
+
+ it("should navigate to page", async () => {
+ const navigationButton =
+ await driver.findElementByText("NgForOf", SearchOptions.exact);
+ await navigationButton.click();
+
+ const actionBar =
+ await driver.findElementByText("ngForOf", SearchOptions.exact);
+ });
+
+ it("should find elements", async () => {
+ const firstElement = await getElement(lastAddedElementId);
+ elements.push(firstElement);
+
+ addButton = await driver.findElementByText("add", SearchOptions.exact);
+ removeButton = await driver.findElementByText("remove", SearchOptions.exact);
+
+ await elementTupleCorrectlyRendered(firstElement);
+ await isAbove(firstElement.button, addButton);
+ });
+
+ it("should render elements in correct order", async () => {
+ await elementTupleCorrectlyRendered(elements[0]);
+ await isAbove(elements[0].button, addButton);
+ await isAbove(addButton, removeButton);
+ });
+
+
+ it("should place new elements in the right places", async () => {
+ for (let i = 0; i < 2; i += 1) {
+ await addElement();
+ await checkAppendedCorrectly();
+ }
+ });
+
+ it("shouldn't reorder elements when last is removed", async () => {
+ while (elements.length) {
+ await removeElement();
+ await checkCorrectOrderAll();
+ }
+ });
+
+ it("should render new elements correctly after all old ones are removed", async () => {
+ for (let i = 0; i < 5; i += 1) {
+ await addElement();
+ await checkCorrectOrderAll();
+ }
+ });
+
+ it("shouldn't reorder elements when middle is removed", async () => {
+ const middleIndex = Math.floor(elements.length / 2);
+ await removeElement(middleIndex);
+ await checkCorrectOrderAll();
+ });
+
+
+ const addElement = async () => {
+ 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;
+ await removeButton.click();
+ }
+
+ elements.splice(index, 1);
+ lastAddedElementId -= 1;
+ };
+
+ const checkAppendedCorrectly = async () => {
+ const lastAdded = await getElement(lastAddedElementId);
+
+ await elementIsAbove(elements.slice(-2)[0], lastAdded);
+ await isAbove(lastAdded.button, addButton);
+ await isAbove(addButton, removeButton);
+ };
+
+ const checkCorrectOrderAll = async () => {
+ 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) => {
+ const label = await driver.findElementByText(
+ "label: " + id.toString(), SearchOptions.exact);
+
+ const button = await driver.findElementByText(
+ id.toString(), SearchOptions.exact);
+
+ 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..4684fe4df
--- /dev/null
+++ b/e2e/renderer/e2e/ngif.e2e-spec.ts
@@ -0,0 +1,370 @@
+import {
+ AppiumDriver,
+ createDriver,
+ SearchOptions,
+ UIElement
+} from "nativescript-dev-appium";
+
+import { isAbove } from "./helpers/location";
+
+import { assert } from "chai";
+
+describe("ngIf scenario", () => {
+ let driver: AppiumDriver;
+ let toggleButton: UIElement;
+
+ describe("without layout", async () => {
+ before(async () => {
+ driver = await createDriver();
+ await driver.driver.resetApp();
+ });
+
+ it("should navigate to page", async () => {
+ const navigationButton =
+ await driver.findElementByText("NgIf no layout", SearchOptions.exact);
+ await navigationButton.click();
+
+ const actionBar =
+ await driver.findElementByText("ngIf - no layout", SearchOptions.exact);
+ });
+
+ it("should find elements", async () => {
+ await driver.findElementByText("false", SearchOptions.exact);
+ toggleButton = await driver.findElementByText("Toggle", SearchOptions.exact);
+ });
+
+ it("show 'true' button when show is true", async () => {
+ await toggleButton.click();
+
+ await driver.findElementByText("true", SearchOptions.exact);
+ });
+ });
+
+ describe("label inbetween", async () => {
+ let firstButton: UIElement;
+ let secondButton: UIElement;
+ let conditionalLabel: UIElement;
+ let toggle: UIElement;
+
+ before(async () => {
+ driver = await createDriver();
+ await driver.driver.resetApp();
+ });
+
+ it("should navigate to page", async () => {
+ const navigationButton =
+ await driver.findElementByText("NgIf inbetween", SearchOptions.exact);
+ await navigationButton.click();
+
+ const actionBar =
+ await driver.findElementByText("ngIf - inbetween", SearchOptions.exact);
+ });
+
+ it("should find elements", async () => {
+ firstButton = await driver.findElementByText("Button 1", SearchOptions.exact);
+ secondButton = await driver.findElementByText("Button 2", SearchOptions.exact);
+ toggleButton = await driver.findElementByText("Toggle", SearchOptions.exact);
+
+ conditionalLabel = await driver.findElementByText("Label", SearchOptions.exact);
+ const labelIsDisplayed = await conditionalLabel.isDisplayed();
+ assert.isTrue(labelIsDisplayed);
+ });
+
+ it("detach label when condition is false", done => {
+ (async () => {
+ await toggleButton.click();
+
+ try {
+ await driver.findElementByText("Label", SearchOptions.exact);
+ } catch (e) {
+ done();
+ }
+ })();
+ });
+ });
+
+ describe("with else template", async () => {
+ let ifButton: UIElement;
+ let elseButton: UIElement;
+ let toggle: UIElement;
+
+ before(async () => {
+ driver = await createDriver();
+ await driver.driver.resetApp();
+ });
+
+ it("should navigate to page", async () => {
+ const navigationButton =
+ await driver.findElementByText("NgIfElse", SearchOptions.exact);
+ await navigationButton.click();
+
+ const actionBar =
+ await driver.findElementByText("ngIfElse", SearchOptions.exact);
+ });
+
+ it("should find elements", async () => {
+ toggleButton = await driver.findElementByText("Toggle", SearchOptions.exact);
+ ifButton = await driver.findElementByText("If", SearchOptions.exact);
+ });
+
+ it("shouldn't render 'else' template when condition is true", done => {
+ driver.findElementByText("Else", SearchOptions.exact)
+ .then(_ => { throw new Error("Else template found!"); })
+ .catch(() => done());
+ });
+
+ it("should attach 'else' template when condition is changed to false", async () => {
+ await toggleButton.click();
+
+ elseButton = await driver.findElementByText("Else", SearchOptions.exact);
+ });
+
+ it("should detach 'if' template when condition is changed to false", done => {
+ driver.findElementByText("If", SearchOptions.exact)
+ .then(_ => { throw new Error("If template found!"); })
+ .catch(() => done());
+ });
+
+ it("should swap the content when condition is changed", done => {
+ (async () => {
+ await toggleButton.click();
+
+ try {
+ await driver.findElementByText("Else", SearchOptions.exact);
+ } catch (e) {
+ done();
+ }
+ })();
+ });
+ });
+
+ describe("with then-else template", async () => {
+ let thenButton: UIElement;
+ let elseButton: UIElement;
+ let toggle: UIElement;
+
+ before(async () => {
+ driver = await createDriver();
+ await driver.driver.resetApp();
+ });
+
+ it("should navigate to page", async () => {
+ const navigationButton =
+ await driver.findElementByText("NgIf Then Else", SearchOptions.exact);
+ await navigationButton.click();
+
+ const actionBar =
+ await driver.findElementByText("ngIf Then Else", SearchOptions.exact);
+ });
+
+ it("should find elements", async () => {
+ toggleButton = await driver.findElementByText("Toggle", SearchOptions.exact);
+ thenButton = await driver.findElementByText("Then", SearchOptions.exact);
+ });
+
+ it("shouldn't render 'else' template when condition is true", done => {
+ driver.findElementByText("Else", SearchOptions.exact)
+ .then(_ => { throw new Error("Else template found!"); })
+ .catch(() => done());
+ });
+
+ it("should attach 'else' template when condition is changed to false", async () => {
+ await toggleButton.click();
+
+ elseButton = await driver.findElementByText("Else", SearchOptions.exact);
+ });
+
+ it("should detach 'then' template when condition is changed to false", done => {
+ driver.findElementByText("Then", SearchOptions.exact)
+ .then(_ => { throw new Error("Then template found!"); })
+ .catch(() => done());
+ });
+
+ it("should swap the content when condition is changed", done => {
+ (async () => {
+ await toggleButton.click();
+
+ try {
+ await driver.findElementByText("Else", SearchOptions.exact);
+ } catch (e) {
+ done();
+ }
+ })();
+ });
+ });
+
+ describe("then-else templates inside content view", async () => {
+ let thenButton: UIElement;
+ let elseButton: UIElement;
+ let toggle: UIElement;
+
+ before(async () => {
+ driver = await createDriver();
+ await driver.driver.resetApp();
+ });
+
+ it("should navigate to page", async () => {
+ const navigationButton =
+ await driver.findElementByText("Content view", SearchOptions.exact);
+ await navigationButton.click();
+
+ const actionBar =
+ await driver.findElementByText("Content View", SearchOptions.exact);
+ });
+
+ it("should find elements", async () => {
+ toggleButton = await driver.findElementByText("Toggle", SearchOptions.exact);
+ thenButton = await driver.findElementByText("Then", SearchOptions.exact);
+ });
+
+ it("shouldn't render 'else' template when condition is true", done => {
+ driver.findElementByText("Else", SearchOptions.exact)
+ .then(_ => { throw new Error("Else template found!"); })
+ .catch(() => done());
+ });
+
+ it("should attach 'else' template when condition is changed to false", async () => {
+ await toggleButton.click();
+
+ elseButton = await driver.findElementByText("Else", SearchOptions.exact);
+ });
+
+ it("should detach 'then' template when condition is changed to false", done => {
+ driver.findElementByText("Then", SearchOptions.exact)
+ .then(_ => { throw new Error("Then template found!"); })
+ .catch(() => done());
+ });
+
+ it("should swap the content when condition is changed", done => {
+ (async () => {
+ await toggleButton.click();
+
+ try {
+ await driver.findElementByText("Else", SearchOptions.exact);
+ } catch (e) {
+ done();
+ }
+ })();
+ });
+ });
+
+ describe("subsequent ifs", async () => {
+ let firstButton: UIElement;
+ let secondButton: UIElement;
+ let firstLabel: UIElement;
+ let secondLabel: UIElement;
+
+ before(async () => {
+ driver = await createDriver();
+ await driver.driver.resetApp();
+ });
+
+ it("should navigate to page", async () => {
+ const navigationButton =
+ await driver.findElementByText("NgIf Subsequent Ifs", SearchOptions.exact);
+ await navigationButton.click();
+ });
+
+ it("should find elements", async () => {
+ firstButton = await driver.findElementByText("Toggle first", SearchOptions.exact);
+ secondButton = await driver.findElementByText("Toggle second", SearchOptions.exact);
+
+ firstLabel = await driver.findElementByText("== 1 ==", SearchOptions.exact);
+ secondLabel = await driver.findElementByText("== 2 ==", SearchOptions.exact);
+
+ assert.isDefined(firstButton);
+ assert.isDefined(secondButton);
+ assert.isDefined(firstLabel);
+ assert.isDefined(secondLabel);
+ });
+
+ it("should toggle on first view", async () => {
+ await firstButton.click();
+
+ let conditional = await driver.findElementByText("first", SearchOptions.exact);
+
+ await isAbove(firstLabel, conditional);
+ await isAbove(conditional, secondLabel);
+ });
+
+ it("should toggle off first view", done => {
+ (async () => {
+ await firstButton.click();
+
+ driver.findElementByText("first", SearchOptions.exact, 500)
+ .then(_ => { throw new Error("first label found!"); })
+ .catch(() => done());
+ })();
+ });
+
+ it("should toggle on second view", async () => {
+ await secondButton.click();
+
+ let conditional = await driver.findElementByText("second", SearchOptions.exact);
+ await isAbove(firstLabel, conditional);
+ await isAbove(conditional, secondLabel);
+ });
+
+ it("should toggle off second view", done => {
+ (async () => {
+ await secondButton.click();
+
+ driver.findElementByText("first", SearchOptions.exact, 500)
+ .then(_ => { throw new Error("first label found!"); })
+ .catch(() => done());
+ })();
+ });
+
+ it("should toggle on both views", async () => {
+ await firstButton.click();
+ await secondButton.click();
+
+ let conditional1 = await driver.findElementByText("first", SearchOptions.exact);
+ let conditional2 = await driver.findElementByText("second", SearchOptions.exact);
+ await isAbove(firstLabel, conditional1);
+ await isAbove(conditional1, conditional2);
+ await isAbove(conditional2, secondLabel);
+ });
+
+ it("should toggle off both views", done => {
+ (async () => {
+ await firstButton.click();
+ await secondButton.click();
+
+ driver.findElementByText("first", SearchOptions.exact, 500)
+ .then(_ => { throw new Error("first label found!"); })
+ .catch(() => {
+ driver.findElementByText("second", SearchOptions.exact, 500)
+ .then(_ => { throw new Error("second label found!"); })
+ .catch(() => done());
+ });
+ })();
+ });
+
+ it("should toggle on both views in reverse", async () => {
+ await secondButton.click();
+ await firstButton.click();
+
+ let conditional1 = await driver.findElementByText("first", SearchOptions.exact);
+ let conditional2 = await driver.findElementByText("second", SearchOptions.exact);
+ await isAbove(firstLabel, conditional1);
+ await isAbove(conditional1, conditional2);
+ await isAbove(conditional2, secondLabel);
+ });
+
+ it("should toggle off both views in reverse", done => {
+ (async () => {
+ await secondButton.click();
+ await firstButton.click();
+
+ driver.findElementByText("first", SearchOptions.exact, 500)
+ .then(_ => { throw new Error("first label found!"); })
+ .catch(() => {
+ driver.findElementByText("second", SearchOptions.exact, 500)
+ .then(_ => { throw new Error("second label found!"); })
+ .catch(() => done());
+ });
+ })();
+ });
+ });
+});
diff --git a/e2e/renderer/e2e/setup.ts b/e2e/renderer/e2e/setup.ts
new file mode 100644
index 000000000..549020395
--- /dev/null
+++ b/e2e/renderer/e2e/setup.ts
@@ -0,0 +1,19 @@
+import { startServer, stopServer, createDriver, AppiumDriver } from "nativescript-dev-appium";
+
+let driver: AppiumDriver;
+
+before("start server", async () => {
+ await startServer();
+ driver = await createDriver();
+});
+
+after("stop server", async () => {
+ await driver.quit();
+ await stopServer();
+});
+
+afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logScreenshot(this.currentTest.title);
+ }
+});
\ No newline at end of file
diff --git a/e2e/renderer/e2e/tsconfig.json b/e2e/renderer/e2e/tsconfig.json
new file mode 100644
index 000000000..18b6c4302
--- /dev/null
+++ b/e2e/renderer/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/renderer/package.json b/e2e/renderer/package.json
new file mode 100644
index 000000000..94403d63c
--- /dev/null
+++ b/e2e/renderer/package.json
@@ -0,0 +1,51 @@
+{
+ "description": "NativeScript Application",
+ "license": "SEE LICENSE IN ",
+ "readme": "NativeScript Application",
+ "repository": "",
+ "nativescript": {
+ "id": "org.nativescript.renderer",
+ "tns-android": {
+ "version": "next"
+ }
+ },
+ "dependencies": {
+ "@angular/animations": "~4.4.1",
+ "@angular/common": "~4.4.1",
+ "@angular/compiler": "~4.4.1",
+ "@angular/core": "~4.4.1",
+ "@angular/forms": "~4.4.1",
+ "@angular/http": "~4.4.1",
+ "@angular/platform-browser": "~4.4.1",
+ "@angular/router": "~4.4.1",
+ "nativescript-angular": "file:../../nativescript-angular",
+ "nativescript-intl": "^3.0.0",
+ "reflect-metadata": "~0.1.8",
+ "rxjs": "~5.3.0",
+ "tns-core-modules": "next",
+ "zone.js": "~0.8.2"
+ },
+ "devDependencies": {
+ "@types/chai": "^4.0.2",
+ "@types/mocha": "^2.2.41",
+ "@types/node": "^7.0.5",
+ "babel-traverse": "6.25.0",
+ "babel-types": "6.25.0",
+ "babylon": "6.17.4",
+ "chai": "~4.1.1",
+ "chai-as-promised": "~7.1.1",
+ "colors": "^1.1.2",
+ "lazy": "1.0.11",
+ "mocha": "~3.5.0",
+ "mocha-junit-reporter": "^1.13.0",
+ "mocha-multi": "^0.11.0",
+ "nativescript-dev-appium": "next",
+ "nativescript-dev-typescript": "~0.4.0",
+ "tslib": "^1.7.1",
+ "typescript": "~2.2.1"
+ },
+ "scripts": {
+ "e2e": "tsc -p e2e && mocha --opts ./e2e/config/mocha.opts",
+ "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..14205dd1b
--- /dev/null
+++ b/e2e/renderer/tsconfig.json
@@ -0,0 +1,28 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es5",
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "noEmitHelpers": true,
+ "noEmitOnError": true,
+ "lib": [
+ "es6",
+ "dom",
+ "es2015.iterable"
+ ],
+ "baseUrl": ".",
+ "paths": {
+ "*": [
+ "./node_modules/tns-core-modules/*",
+ "./node_modules/*"
+ ]
+ }
+ },
+ "exclude": [
+ "node_modules",
+ "platforms",
+ "**/*.aot.ts",
+ "e2e"
+ ]
+}
\ No newline at end of file
diff --git a/e2e/router/.gitignore b/e2e/router/.gitignore
new file mode 100644
index 000000000..8e824499e
--- /dev/null
+++ b/e2e/router/.gitignore
@@ -0,0 +1,10 @@
+.vscode
+
+platforms
+node_modules
+hooks
+
+/**/*.js
+/**/*.map
+e2e/reports
+test-results.xml
\ No newline at end of file
diff --git a/e2e/router/app/App_Resources/Android/AndroidManifest.xml b/e2e/router/app/App_Resources/Android/AndroidManifest.xml
new file mode 100644
index 000000000..9db832151
--- /dev/null
+++ b/e2e/router/app/App_Resources/Android/AndroidManifest.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/drawable-nodpi/background.png b/e2e/router/app/App_Resources/Android/drawable-nodpi/background.png
new file mode 100644
index 000000000..748b2adf5
Binary files /dev/null and b/e2e/router/app/App_Resources/Android/drawable-nodpi/background.png differ
diff --git a/e2e/router/app/App_Resources/Android/drawable-nodpi/icon.png b/e2e/router/app/App_Resources/Android/drawable-nodpi/icon.png
new file mode 100755
index 000000000..ddfc17a71
Binary files /dev/null and b/e2e/router/app/App_Resources/Android/drawable-nodpi/icon.png differ
diff --git a/e2e/router/app/App_Resources/Android/drawable-nodpi/logo.png b/e2e/router/app/App_Resources/Android/drawable-nodpi/logo.png
new file mode 100644
index 000000000..b9e102a76
Binary files /dev/null and b/e2e/router/app/App_Resources/Android/drawable-nodpi/logo.png differ
diff --git a/e2e/router/app/App_Resources/Android/drawable-nodpi/splash_screen.xml b/e2e/router/app/App_Resources/Android/drawable-nodpi/splash_screen.xml
new file mode 100644
index 000000000..ada77f92c
--- /dev/null
+++ b/e2e/router/app/App_Resources/Android/drawable-nodpi/splash_screen.xml
@@ -0,0 +1,8 @@
+
+ -
+
+
+ -
+
+
+
\ No newline at end of file
diff --git a/e2e/router/app/App_Resources/Android/values-v21/colors.xml b/e2e/router/app/App_Resources/Android/values-v21/colors.xml
new file mode 100644
index 000000000..a64641a9d
--- /dev/null
+++ b/e2e/router/app/App_Resources/Android/values-v21/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #3d5afe
+
\ No newline at end of file
diff --git a/e2e/router/app/App_Resources/Android/values-v21/styles.xml b/e2e/router/app/App_Resources/Android/values-v21/styles.xml
new file mode 100644
index 000000000..dac8727c8
--- /dev/null
+++ b/e2e/router/app/App_Resources/Android/values-v21/styles.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/router/app/App_Resources/Android/values/colors.xml b/e2e/router/app/App_Resources/Android/values/colors.xml
new file mode 100644
index 000000000..74ad8829c
--- /dev/null
+++ b/e2e/router/app/App_Resources/Android/values/colors.xml
@@ -0,0 +1,7 @@
+
+
+ #F5F5F5
+ #757575
+ #33B5E5
+ #272734
+
\ No newline at end of file
diff --git a/e2e/router/app/App_Resources/Android/values/styles.xml b/e2e/router/app/App_Resources/Android/values/styles.xml
new file mode 100644
index 000000000..1e8c7f29b
--- /dev/null
+++ b/e2e/router/app/App_Resources/Android/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..dcc881a54
--- /dev/null
+++ b/e2e/router/app/app-routing.module.ts
@@ -0,0 +1,57 @@
+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"
+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: "c-less",
+ children: [
+ {
+ path: "deep/:depth",
+ component: SecondComponent,
+ children: [
+ { path: "", component: MasterComponent },
+ { path: "detail/:id", component: DetailComponent }
+ ]
+ }
+ ]
+ },
+ {
+ path: "lazy",
+ loadChildren: "./lazy/lazy.module#LazyModule",
+ }
+];
+
+export const navigatableComponents = [
+ FirstComponent,
+ SecondComponent,
+ MasterComponent,
+ DetailComponent
+];
+
+@NgModule({
+ imports: [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..3890d9f3a
--- /dev/null
+++ b/e2e/router/app/app.css
@@ -0,0 +1,17 @@
+.header {
+ font-size: 32;
+ white-space: normal;
+}
+
+.nested-header {
+ font-size: 26;
+}
+
+.nested-outlet {
+ margin: 20;
+ background-color: lightgreen;
+}
+
+Label {
+ text-align: center;
+}
\ 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..5d7dcbdbc
--- /dev/null
+++ b/e2e/router/app/app.module.ts
@@ -0,0 +1,35 @@
+import { NgModule, NgModuleFactoryLoader, NO_ERRORS_SCHEMA } from "@angular/core";
+import { NativeScriptModule } from "nativescript-angular/nativescript.module";
+import { NSModuleFactoryLoader } from "nativescript-angular/router";
+
+import "./rxjs-operators";
+
+import {
+ AppRoutingModule,
+ navigatableComponents,
+} from "./app-routing.module";
+
+import { AppComponent } from "./app.component";
+
+import { rendererTraceCategory, viewUtilCategory, routeReuseStrategyTraceCategory, routerTraceCategory } from "nativescript-angular/trace";
+import { setCategories, enable } from "trace";
+setCategories(routerTraceCategory + "," + routeReuseStrategyTraceCategory);
+enable();
+
+@NgModule({
+ declarations: [
+ AppComponent,
+ ...navigatableComponents,
+ ],
+ bootstrap: [AppComponent],
+ providers: [
+ { provide: NgModuleFactoryLoader, useClass: NSModuleFactoryLoader }
+ ],
+ imports: [
+ NativeScriptModule,
+ AppRoutingModule,
+ ],
+ schemas: [NO_ERRORS_SCHEMA],
+})
+export class AppModule { }
+
diff --git a/e2e/router/app/first/first.component.ts b/e2e/router/app/first/first.component.ts
new file mode 100644
index 000000000..5748e7b2b
--- /dev/null
+++ b/e2e/router/app/first/first.component.ts
@@ -0,0 +1,48 @@
+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/router";
+
+import { Page } from "ui/page";
+import { Observable } from "rxjs/Observable";
+
+@Component({
+ selector: "first",
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+})
+export class FirstComponent implements OnInit, OnDestroy {
+ public message: string = "";
+ constructor(private routerExt: RouterExtensions, page: Page) {
+ console.log("FirstComponent - constructor() page: " + page);
+ }
+
+ ngOnInit() {
+ console.log("FirstComponent - ngOnInit()");
+ }
+
+ ngOnDestroy() {
+ console.log("FirstComponent - ngOnDestroy()");
+ }
+
+ goBack() {
+ this.message = "";
+ if (this.routerExt.canGoBack()) {
+ this.routerExt.back();
+ } else {
+ this.message = "canGoBack() - false"
+ }
+ }
+}
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..a3df10fd5
--- /dev/null
+++ b/e2e/router/app/lazy/lazy-componentless-route.component.ts
@@ -0,0 +1,44 @@
+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/router";
+
+import { Page } from "ui/page";
+import { Observable } from "rxjs/Observable";
+
+@Component({
+ selector: "lazy",
+ 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..94e3f72b6
--- /dev/null
+++ b/e2e/router/app/lazy/lazy.component.ts
@@ -0,0 +1,44 @@
+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/router";
+
+import { Page } from "ui/page";
+import { Observable } from "rxjs/Observable";
+
+@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..3f622e33a
--- /dev/null
+++ b/e2e/router/app/lazy/lazy.module.ts
@@ -0,0 +1,38 @@
+import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
+import { Route } from "@angular/router";
+
+import { NativeScriptModule } from "nativescript-angular/nativescript.module";
+import { NativeScriptRouterModule } from "nativescript-angular/router";
+
+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: [
+ NativeScriptModule,
+ NativeScriptRouterModule,
+ NativeScriptRouterModule.forChild(routes)
+ ],
+ declarations: [
+ LazyComponent,
+ LazyComponentlessRouteComponent
+ ]
+})
+export class LazyModule { }
\ No newline at end of file
diff --git a/e2e/router/app/main.aot.ts b/e2e/router/app/main.aot.ts
new file mode 100644
index 000000000..98bf134fc
--- /dev/null
+++ b/e2e/router/app/main.aot.ts
@@ -0,0 +1,4 @@
+import { platformNativeScript } from "nativescript-angular/platform-static";
+import { AppModuleNgFactory } from "./app.module.ngfactory";
+
+platformNativeScript().bootstrapModuleFactory(AppModuleNgFactory);
diff --git a/e2e/router/app/main.ts b/e2e/router/app/main.ts
new file mode 100644
index 000000000..639bfd513
--- /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..d5356c0cc
--- /dev/null
+++ b/e2e/router/app/package.json
@@ -0,0 +1,5 @@
+{
+ "main": "main.js",
+ "name": "nativescript-template-ng-tutorial",
+ "version": "3.1.0"
+}
\ No newline at end of file
diff --git a/e2e/router/app/rxjs-operators.ts b/e2e/router/app/rxjs-operators.ts
new file mode 100644
index 000000000..480bbe40f
--- /dev/null
+++ b/e2e/router/app/rxjs-operators.ts
@@ -0,0 +1 @@
+import "rxjs/add/operator/map";
\ 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..e005f6dee
--- /dev/null
+++ b/e2e/router/app/second/detail.component.ts
@@ -0,0 +1,32 @@
+import { Component, OnInit, OnDestroy } from "@angular/core";
+import { ActivatedRoute, Router, Route } from "@angular/router";
+import { Location } from "@angular/common";
+import { Page } from "ui/page";
+import { Observable } from "rxjs/Observable";
+
+@Component({
+ selector: "detail",
+ template: `
+
+
+
+
+
+ `
+})
+export class DetailComponent {
+ public id$: Observable;
+
+ constructor(private router: Router, private route: ActivatedRoute) {
+ console.log("DetailComponent - constructor()");
+ this.id$ = route.params.map(r => r["id"]);
+ }
+
+ ngOnInit() {
+ console.log("DetailComponent - ngOnInit()");
+ }
+
+ ngOnDestroy() {
+ console.log("DetailComponent - ngOnDestroy()");
+ }
+}
\ No newline at end of file
diff --git a/e2e/router/app/second/master.component.ts b/e2e/router/app/second/master.component.ts
new file mode 100644
index 000000000..08605d5e5
--- /dev/null
+++ b/e2e/router/app/second/master.component.ts
@@ -0,0 +1,27 @@
+import { Component, OnInit, OnDestroy } from "@angular/core";
+
+@Component({
+ selector: "master",
+ template: `
+
+
+
+
+
+ `
+})
+export class MasterComponent implements OnInit, OnDestroy {
+ public details: Array = [1, 2, 3];
+
+ constructor() {
+ 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..75acb847b
--- /dev/null
+++ b/e2e/router/app/second/second.component.ts
@@ -0,0 +1,46 @@
+import { Component, OnInit, OnDestroy } from "@angular/core";
+import { ActivatedRoute, Router, Route } from "@angular/router";
+
+import { RouterExtensions } from "nativescript-angular/router";
+import { Page } from "ui/page";
+import { Observable } from "rxjs/Observable";
+
+@Component({
+ selector: "second",
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+})
+export class SecondComponent implements OnInit, OnDestroy {
+ public depth$: Observable;
+ public nextDepth$: Observable;
+
+ constructor(private routerExt: RouterExtensions, route: ActivatedRoute, page: Page) {
+ console.log("SecondComponent - constructor() page: " + page);
+ this.depth$ = route.params.map(r => r["depth"]);
+ this.nextDepth$ = route.params.map(r => +r["depth"] + 1);
+ }
+
+ ngOnInit() {
+ console.log("SecondComponent - ngOnInit()");
+ }
+
+ ngOnDestroy() {
+ console.log("SecondComponent - ngOnDestroy()");
+ }
+
+ goBack() {
+ this.routerExt.back();
+ }
+}
\ No newline at end of file
diff --git a/e2e/router/e2e/config/appium.capabilities.json b/e2e/router/e2e/config/appium.capabilities.json
new file mode 100644
index 000000000..4f1b4407d
--- /dev/null
+++ b/e2e/router/e2e/config/appium.capabilities.json
@@ -0,0 +1,100 @@
+{
+ "nexus5": {
+ "browserName": "",
+ "platformName": "Android",
+ "platformVersion": "6.0",
+ "deviceName": "device",
+ "udid": "077e4a47003b7698",
+ "appActivity": "com.tns.NativeScriptActivity",
+ "app": ""
+ },
+ "android19": {
+ "platformName": "Android",
+ "platformVersion": "4.4",
+ "deviceName": "Emulator-Api19-Default",
+ "avd": "Emulator-Api19-Default",
+ "lt": 60000,
+ "appActivity": "com.tns.NativeScriptActivity",
+ "newCommandTimeout": 720,
+ "noReset": false,
+ "fullReset": false,
+ "app": ""
+ },
+ "android21": {
+ "platformName": "Android",
+ "platformVersion": "5.0",
+ "deviceName": "Emulator-Api21-Default",
+ "avd": "Emulator-Api21-Default",
+ "lt": 60000,
+ "appActivity": "com.tns.NativeScriptActivity",
+ "newCommandTimeout": 720,
+ "noReset": false,
+ "fullReset": false,
+ "app": ""
+ },
+ "android23": {
+ "platformName": "Android",
+ "platformVersion": "6.0",
+ "deviceName": "Emulator-Api23-Default",
+ "avd": "Emulator-Api23-Default",
+ "lt": 60000,
+ "appActivity": "com.tns.NativeScriptActivity",
+ "newCommandTimeout": 720,
+ "noReset": false,
+ "fullReset": false,
+ "app": ""
+ },
+ "android24": {
+ "platformName": "Android",
+ "platformVersion": "7.0",
+ "deviceName": "Emulator-Api24-Default",
+ "avd": "Emulator-Api24-Default",
+ "lt": 60000,
+ "appActivity": "com.tns.NativeScriptActivity",
+ "newCommandTimeout": 720,
+ "noReset": false,
+ "fullReset": false,
+ "app": ""
+ },
+ "android25": {
+ "platformName": "Android",
+ "platformVersion": "7.1",
+ "deviceName": "Emulator-Api25-Google",
+ "avd": "Emulator-Api25-Google",
+ "lt": 60000,
+ "appActivity": "com.tns.NativeScriptActivity",
+ "newCommandTimeout": 720,
+ "noReset": false,
+ "fullReset": false,
+ "app": ""
+ },
+ "android26": {
+ "platformName": "Android",
+ "platformVersion": "8.0",
+ "deviceName": "Emulator-Api26-Google",
+ "avd": "Emulator-Api26-Google",
+ "lt": 60000,
+ "automationName": "UIAutomator2",
+ "appActivity": "com.tns.NativeScriptActivity",
+ "newCommandTimeout": 720,
+ "noReset": false,
+ "fullReset": false,
+ "app": ""
+ },
+ "sim.iPhone7.iOS100": {
+ "platformName": "iOS",
+ "platformVersion": "10.0",
+ "deviceName": "iPhone 7 100",
+ "noReset": false,
+ "fullReset": false,
+ "app": ""
+ },
+ "sim.iPhone7.iOS110": {
+ "platformName": "iOS",
+ "platformVersion": "11.0",
+ "deviceName": "iPhone 7 110",
+ "noReset": false,
+ "fullReset": false,
+ "app": ""
+ }
+}
\ No newline at end of file
diff --git a/e2e/router/e2e/config/mocha.opts b/e2e/router/e2e/config/mocha.opts
new file mode 100644
index 000000000..796ec4724
--- /dev/null
+++ b/e2e/router/e2e/config/mocha.opts
@@ -0,0 +1,4 @@
+--timeout 80000
+--recursive e2e
+--reporter mocha-multi
+--reporter-options spec=-,mocha-junit-reporter=test-results.xml
\ No newline at end of file
diff --git a/e2e/router/e2e/router.e2e-spec.ts b/e2e/router/e2e/router.e2e-spec.ts
new file mode 100644
index 000000000..82dcda30c
--- /dev/null
+++ b/e2e/router/e2e/router.e2e-spec.ts
@@ -0,0 +1,398 @@
+import {
+ AppiumDriver,
+ UIElement,
+ createDriver,
+ SearchOptions,
+} from "nativescript-dev-appium";
+
+describe("Simple navigate and back", () => {
+ let driver: AppiumDriver;
+
+ before(async () => {
+ driver = await createDriver();
+ await driver.resetApp();
+ });
+
+ it("should find First", async () => {
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate to Second(1)/master", async () => {
+ await findAndClick(driver, "GO TO SECOND");
+
+ await assureSecondComponent(driver, 1);
+ await assureNestedMasterComponent(driver);
+ });
+
+ it("should navigate back to First", async () => {
+ await goBack(driver);
+ await assureFirstComponent(driver);
+ });
+});
+
+describe("Navigate inside nested outlet", () => {
+ let driver: AppiumDriver;
+
+ before(async () => {
+ driver = await createDriver();
+ await driver.resetApp();
+ });
+
+ it("should find First", async () => {
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate to Second(1)/master", async () => {
+ await findAndClick(driver, "GO TO SECOND");
+
+ await assureSecondComponent(driver, 1)
+ await assureNestedMasterComponent(driver);
+ });
+
+ it("should navigate to Second(1)/detail(1) and back", async () => {
+ const detailBtn = await driver.findElementByText("DETAIL 1", SearchOptions.exact);
+ 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 () => {
+ const detailBtn = await driver.findElementByText("DETAIL 2", SearchOptions.exact);
+ 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 () => {
+ await goBack(driver);
+ await assureFirstComponent(driver);
+ });
+});
+
+describe("Navigate to same component with different param", () => {
+ let driver: AppiumDriver;
+
+ before(async () => {
+ driver = await createDriver();
+ await driver.resetApp();
+ });
+
+ it("should find First", async () => {
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate to Second(1)/master", async () => {
+ await findAndClick(driver, "GO TO SECOND");
+
+ await assureSecondComponent(driver, 1)
+ await assureNestedMasterComponent(driver);
+ });
+
+ it("should navigate to Second(2)/master", async () => {
+ const navigationButton =
+ await driver.findElementByText("GO TO NEXT SECOND", SearchOptions.exact);
+ navigationButton.click();
+
+ await assureSecondComponent(driver, 2)
+ await assureNestedMasterComponent(driver);
+ });
+
+ it("should navigate back to Second(1)/master", async () => {
+ await goBack(driver);
+
+ await assureSecondComponent(driver, 1)
+ await assureNestedMasterComponent(driver);
+ });
+
+ it("should navigate back to First", async () => {
+ await goBack(driver);
+ await assureFirstComponent(driver);
+ });
+});
+
+describe("Nested navigation + page navigation", () => {
+ let driver: AppiumDriver;
+
+ before(async () => {
+ driver = await createDriver();
+ await driver.resetApp();
+ });
+
+ it("should find First", async () => {
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate to Second(1)/master", async () => {
+ await findAndClick(driver, "GO TO SECOND");
+
+ await assureSecondComponent(driver, 1)
+ await assureNestedMasterComponent(driver);
+ });
+
+ it("should navigate to Second(1)/detail(1)", async () => {
+ const detailBtn = await driver.findElementByText("DETAIL 1", SearchOptions.exact);
+ detailBtn.click();
+
+ await assureSecondComponent(driver, 1)
+ await assureNestedDetailComponent(driver, 1);
+ });
+
+ it("should navigate to Second(2)/master", async () => {
+ const navigationButton =
+ await driver.findElementByText("GO TO NEXT SECOND", SearchOptions.exact);
+ navigationButton.click();
+
+ await assureSecondComponent(driver, 2)
+ await assureNestedMasterComponent(driver);
+ });
+
+ it("should navigate to Second(2)/detail(2)", async () => {
+ const detailBtn = await driver.findElementByText("DETAIL 2", SearchOptions.exact);
+ detailBtn.click();
+
+ await assureSecondComponent(driver, 2)
+ await assureNestedDetailComponent(driver, 2);
+ });
+
+ it("should navigate to First", async () => {
+ await findAndClick(driver, "GO TO FIRST");
+
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate the whole stack", async () => {
+ 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("Shouldn't be able to navigate back on startup", () => {
+ let driver: AppiumDriver;
+
+ before(async () => {
+ driver = await createDriver();
+ await driver.resetApp();
+ });
+
+ it("should find First", async () => {
+ await assureFirstComponent(driver);
+ });
+
+ it("shouldn't be able to go back", async () => {
+ await goBack(driver);
+ await driver.findElementByText("canGoBack() - false", SearchOptions.exact);
+ });
+});
+
+describe("Shouldn't be able to navigate back after cleared history", () => {
+ let driver: AppiumDriver;
+
+ before(async () => {
+ driver = await createDriver();
+ await driver.resetApp();
+ });
+
+ it("should find First", async () => {
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate to Second(1)/master", async () => {
+ await findAndClick(driver, "GO TO SECOND");
+
+ await assureSecondComponent(driver, 1)
+ await assureNestedMasterComponent(driver);
+ });
+
+ it("should navigate to Second(1)/master", async () => {
+ await findAndClick(driver, "GO TO FIRST(CLEAR)");
+
+ await assureFirstComponent(driver);
+ });
+
+ it("shouldn't be able to go back", async () => {
+ await goBack(driver);
+ await driver.findElementByText("canGoBack() - false", SearchOptions.exact);
+ });
+});
+
+describe("Navigate to componentless route", () => {
+ let driver: AppiumDriver;
+
+ before(async () => {
+ driver = await createDriver();
+ await driver.resetApp();
+ });
+
+ it("should find First", async () => {
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate to ComponentlessSecond(100)/detail(200)", async () => {
+ const navigationButton =
+ await driver.findElementByText("GO TO C-LESS SECOND", SearchOptions.exact);
+ navigationButton.click();
+
+ await assureSecondComponent(driver, 100)
+ await assureNestedDetailComponent(driver, 200);
+ });
+
+ it("should navigate to First", async () => {
+ await findAndClick(driver, "GO TO FIRST");
+
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate the whole stack", async () => {
+ await goBack(driver);
+ await assureSecondComponent(driver, 100)
+ await assureNestedDetailComponent(driver, 200);
+
+ await goBack(driver);
+ await assureFirstComponent(driver);
+ });
+});
+
+describe("Navigate to lazy module", () => {
+ let driver: AppiumDriver;
+
+ before(async () => {
+ driver = await createDriver();
+ await driver.resetApp();
+ });
+
+ it("should find First", async () => {
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate to lazy/home", async () => {
+ await findAndClick(driver, "GO TO LAZY HOME");
+ await assureLazyComponent(driver);
+ });
+
+ it("should navigate to First", async () => {
+ await findAndClick(driver, "GO TO FIRST");
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate back to lazy/home", async () => {
+ await goBack(driver);
+ await assureLazyComponent(driver);
+ });
+
+ it("should navigate to First again", async () => {
+ await findAndClick(driver, "GO TO FIRST");
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate the whole stack", async () => {
+ await goBack(driver);
+ await assureLazyComponent(driver);
+
+ await goBack(driver);
+ await assureFirstComponent(driver);
+ });
+});
+
+describe("Navigate to componentless lazy module route", () => {
+ let driver: AppiumDriver;
+
+ before(async () => {
+ driver = await createDriver();
+ await driver.resetApp();
+ });
+
+ it("should find First", async () => {
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate to nest/more (componentless lazy route)", async () => {
+ await findAndClick(driver, "GO TO C-LESS LAZY");
+
+ await assureComponentlessLazyComponent(driver);
+ });
+
+ it("should navigate to lazy/home", async () => {
+ await findAndClick(driver, "GO TO LAZY HOME");
+
+ await assureLazyComponent(driver);
+ });
+
+ it("should navigate to First", async () => {
+ await findAndClick(driver, "GO TO FIRST");
+
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate the whole stack", async () => {
+ await goBack(driver);
+ await assureLazyComponent(driver);
+
+ await goBack(driver);
+ await assureComponentlessLazyComponent(driver);
+
+ await goBack(driver);
+ await assureFirstComponent(driver);
+ });
+});
+
+async function assureFirstComponent(driver: AppiumDriver) {
+ await driver.findElementByText("FirstComponent", SearchOptions.exact);
+}
+
+async function assureLazyComponent(driver: AppiumDriver) {
+ await driver.findElementByText("LazyComponent", SearchOptions.exact);
+}
+
+async function assureComponentlessLazyComponent(driver: AppiumDriver) {
+ await driver.findElementByText("Lazy Componentless Route", SearchOptions.exact);
+}
+
+async function assureSecondComponent(driver: AppiumDriver, param: number) {
+ await driver.findElementByText("SecondComponent", SearchOptions.exact);
+ await driver.findElementByText(`param: ${param}`, SearchOptions.exact);
+}
+
+async function assureNestedMasterComponent(driver: AppiumDriver) {
+ await driver.findElementByText("NestedMaster", SearchOptions.exact);
+}
+
+async function assureNestedDetailComponent(driver: AppiumDriver, param: number) {
+ await driver.findElementByText("NestedDetail", SearchOptions.exact);
+ await driver.findElementByText(`nested-param: ${param}`, SearchOptions.exact);
+}
+
+async function goBack(driver: AppiumDriver) {
+ const backButton = await driver.findElementByText("BACK", SearchOptions.exact);
+ await backButton.click();
+ //await driver.navBack();
+}
+
+async function findAndClick(driver: AppiumDriver, text: string) {
+ const navigationButton =
+ await driver.findElementByText(text, SearchOptions.exact);
+ 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..0c0add14a
--- /dev/null
+++ b/e2e/router/e2e/setup.ts
@@ -0,0 +1,19 @@
+import { startServer, stopServer , createDriver , AppiumDriver } from "nativescript-dev-appium";
+
+let driver: AppiumDriver;
+
+before("start server", async () => {
+ await startServer();
+ driver = await createDriver();
+});
+
+after("stop server", async () => {
+ await driver.quit();
+ await stopServer();
+});
+
+afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logScreenshot(this.currentTest.title);
+ }
+});
\ No newline at end of file
diff --git a/e2e/router/e2e/tsconfig.json b/e2e/router/e2e/tsconfig.json
new file mode 100644
index 000000000..18b6c4302
--- /dev/null
+++ b/e2e/router/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/router/package.json b/e2e/router/package.json
new file mode 100644
index 000000000..e99f48117
--- /dev/null
+++ b/e2e/router/package.json
@@ -0,0 +1,54 @@
+{
+ "description": "NativeScript Application",
+ "license": "SEE LICENSE IN ",
+ "readme": "NativeScript Application",
+ "repository": "",
+ "nativescript": {
+ "id": "org.nativescript.router",
+ "tns-android": {
+ "version": "3.2.0-2017-9-4-1"
+ },
+ "tns-ios": {
+ "version": "3.2.0"
+ }
+ },
+ "dependencies": {
+ "@angular/animations": "~4.4.1",
+ "@angular/common": "~4.4.1",
+ "@angular/compiler": "~4.4.1",
+ "@angular/core": "~4.4.1",
+ "@angular/forms": "~4.4.1",
+ "@angular/http": "~4.4.1",
+ "@angular/platform-browser": "~4.4.1",
+ "@angular/router": "~4.4.1",
+ "nativescript-angular": "file:../../nativescript-angular",
+ "nativescript-intl": "^3.0.0",
+ "reflect-metadata": "~0.1.8",
+ "rxjs": "~5.3.0",
+ "tns-core-modules": "next",
+ "zone.js": "~0.8.2"
+ },
+ "devDependencies": {
+ "@types/chai": "^4.0.2",
+ "@types/mocha": "^2.2.41",
+ "@types/node": "^7.0.5",
+ "babel-traverse": "6.25.0",
+ "babel-types": "6.25.0",
+ "babylon": "6.17.4",
+ "chai": "~4.1.1",
+ "chai-as-promised": "~7.1.1",
+ "colors": "^1.1.2",
+ "lazy": "1.0.11",
+ "mocha": "~3.5.0",
+ "mocha-junit-reporter": "^1.13.0",
+ "mocha-multi": "^0.11.0",
+ "nativescript-dev-appium": "next",
+ "nativescript-dev-typescript": "~0.4.0",
+ "tslib": "^1.7.1",
+ "typescript": "~2.2.1"
+ },
+ "scripts": {
+ "e2e": "tsc -p e2e && mocha --opts ./e2e/config/mocha.opts",
+ "compile-tests-w": "tsc -p e2e --watch"
+ }
+}
diff --git a/e2e/router/tsconfig.json b/e2e/router/tsconfig.json
new file mode 100644
index 000000000..14205dd1b
--- /dev/null
+++ b/e2e/router/tsconfig.json
@@ -0,0 +1,28 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es5",
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "noEmitHelpers": true,
+ "noEmitOnError": true,
+ "lib": [
+ "es6",
+ "dom",
+ "es2015.iterable"
+ ],
+ "baseUrl": ".",
+ "paths": {
+ "*": [
+ "./node_modules/tns-core-modules/*",
+ "./node_modules/*"
+ ]
+ }
+ },
+ "exclude": [
+ "node_modules",
+ "platforms",
+ "**/*.aot.ts",
+ "e2e"
+ ]
+}
\ No newline at end of file
diff --git a/nativescript-angular/animations.ts b/nativescript-angular/animations/animations.module.ts
similarity index 87%
rename from nativescript-angular/animations.ts
rename to nativescript-angular/animations/animations.module.ts
index 7b5f2d2f3..7f2b3adea 100644
--- a/nativescript-angular/animations.ts
+++ b/nativescript-angular/animations/animations.module.ts
@@ -13,10 +13,10 @@ import {
ɵBrowserAnimationBuilder as BrowserAnimationBuilder,
} from "@angular/platform-browser/animations";
-import { NativeScriptAnimationEngine } from "./animations/animation-engine";
-import { NativeScriptAnimationDriver } from "./animations/animation-driver";
-import { NativeScriptModule } from "./nativescript.module";
-import { NativeScriptRendererFactory } from "./renderer";
+import { NativeScriptAnimationEngine } from "./animation-engine";
+import { NativeScriptAnimationDriver } from "./animation-driver";
+import { NativeScriptModule } from "../nativescript.module";
+import { NativeScriptRendererFactory } from "../renderer";
@Injectable()
export class InjectableAnimationEngine extends NativeScriptAnimationEngine {
diff --git a/nativescript-angular/animations/index.ts b/nativescript-angular/animations/index.ts
new file mode 100644
index 000000000..d5754740b
--- /dev/null
+++ b/nativescript-angular/animations/index.ts
@@ -0,0 +1 @@
+export * from "./animations.module";
diff --git a/nativescript-angular/collection-facade.ts b/nativescript-angular/collection-facade.ts
deleted file mode 100644
index 08c2f6742..000000000
--- a/nativescript-angular/collection-facade.ts
+++ /dev/null
@@ -1,134 +0,0 @@
-/* tslint:disable */
-//Copied unexported functions from @angular/core/src/facade/collection
-import {
- isJsObject, isArray, getSymbolIterator,
- isPresent, isBlank
-} from "./lang-facade";
-
-export function isListLikeIterable(obj: any): boolean {
- if (!isJsObject(obj)) return false;
- return isArray(obj) ||
- (!(obj instanceof Map) && // JS Map are iterables but return entries as [k, v]
- getSymbolIterator() in obj); // JS Iterable have a Symbol.iterator prop
-}
-
-export class ListWrapper {
- // JS has no way to express a statically fixed size list, but dart does so we
- // keep both methods.
- static createFixedSize(size: number): any[] { return new Array(size); }
- static createGrowableSize(size: number): any[] { return new Array(size); }
- static clone(array: T[]): T[] { return array.slice(0); }
- static forEachWithIndex(array: T[], fn: (t: T, n: number) => void) {
- for (let i = 0; i < array.length; i++) {
- fn(array[i], i);
- }
- }
- static first(array: T[]): T {
- if (!array) return null;
- return array[0];
- }
- static last(array: T[]): T {
- if (!array || array.length == 0) return null;
- return array[array.length - 1];
- }
- static indexOf(array: T[], value: T, startIndex: number = 0): number {
- return array.indexOf(value, startIndex);
- }
- static contains(list: T[], el: T): boolean { return list.indexOf(el) !== -1; }
- static reversed(array: T[]): T[] {
- let a = ListWrapper.clone(array);
- return a.reverse();
- }
- static concat(a: any[], b: any[]): any[] { return a.concat(b); }
- static insert(list: T[], index: number, value: T) { list.splice(index, 0, value); }
- static removeAt(list: T[], index: number): T {
- let res = list[index];
- list.splice(index, 1);
- return res;
- }
- static removeAll(list: T[], items: T[]) {
- for (let i = 0; i < items.length; ++i) {
- let index = list.indexOf(items[i]);
- list.splice(index, 1);
- }
- }
- static remove(list: T[], el: T): boolean {
- let index = list.indexOf(el);
- if (index > -1) {
- list.splice(index, 1);
- return true;
- }
- return false;
- }
- static clear(list: any[]) { list.length = 0; }
- static isEmpty(list: any[]): boolean { return list.length == 0; }
- static fill(list: any[], value: any, start: number = 0, end: number = null) {
- (list).fill(value, start, end === null ? list.length : end);
- }
- static equals(a: any[], b: any[]): boolean {
- if (a.length != b.length) return false;
- for (let i = 0; i < a.length; ++i) {
- if (a[i] !== b[i]) return false;
- }
- return true;
- }
- static slice(l: T[], from: number = 0, to: number = null): T[] {
- return l.slice(from, to === null ? undefined : to);
- }
- static splice(l: T[], from: number, length: number): T[] { return l.splice(from, length); }
- static sort(l: T[], compareFn?: (a: T, b: T) => number) {
- if (isPresent(compareFn)) {
- l.sort(compareFn);
- } else {
- l.sort();
- }
- }
- static toString(l: T[]): string { return l.toString(); }
- static toJSON(l: T[]): string { return JSON.stringify(l); }
-
- static maximum(list: T[], predicate: (t: T) => number): T {
- if (list.length == 0) {
- return null;
- }
- let solution: any /** TODO #???? */ = null;
- let maxValue = -Infinity;
- for (let index = 0; index < list.length; index++) {
- let candidate = list[index];
- if (isBlank(candidate)) {
- continue;
- }
- let candidateValue = predicate(candidate);
- if (candidateValue > maxValue) {
- solution = candidate;
- maxValue = candidateValue;
- }
- }
- return solution;
- }
-
- static flatten(list: Array): T[] {
- let target: any[] = [];
- _flattenArray(list, target);
- return target;
- }
-
- static addAll(list: Array, source: Array): void {
- for (let i = 0; i < source.length; i++) {
- list.push(source[i]);
- }
- }
-}
-
-function _flattenArray(source: any[], target: any[]): any[] {
- if (isPresent(source)) {
- for (let i = 0; i < source.length; i++) {
- let item = source[i];
- if (isArray(item)) {
- _flattenArray(item, target);
- } else {
- target.push(item);
- }
- }
- }
- return target;
-}
diff --git a/nativescript-angular/directives.ts b/nativescript-angular/directives.ts
deleted file mode 100644
index 2e07c59d5..000000000
--- a/nativescript-angular/directives.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { ListViewComponent, TemplateKeyDirective } from "./directives/list-view-comp";
-import { TabViewDirective, TabViewItemDirective } from "./directives/tab-view";
-import {
- ActionBarComponent,
- ActionBarScope,
- ActionItemDirective,
- NavigationButtonDirective
-} from "./directives/action-bar";
-import { AndroidFilterComponent, IosFilterComponent } from "./directives/platform-filters";
-
-export const NS_DIRECTIVES = [
- ListViewComponent,
- TemplateKeyDirective,
- TabViewDirective,
- TabViewItemDirective,
- ActionBarComponent,
- ActionBarScope,
- ActionItemDirective,
- NavigationButtonDirective,
- AndroidFilterComponent,
- IosFilterComponent,
-];
-
-export {
- ListViewComponent,
- SetupItemViewArgs,
- TemplateKeyDirective
-} from "./directives/list-view-comp";
-export { TextValueAccessor } from "./value-accessors/text-value-accessor";
-export { CheckedValueAccessor } from "./value-accessors/checked-value-accessor";
-export { DateValueAccessor } from "./value-accessors/date-value-accessor";
-export { TimeValueAccessor } from "./value-accessors/time-value-accessor";
-export { NumberValueAccessor } from "./value-accessors/number-value-accessor";
-export { SelectedIndexValueAccessor } from "./value-accessors/selectedIndex-value-accessor";
-export { TabViewDirective, TabViewItemDirective } from "./directives/tab-view";
-export {
- ActionBarComponent,
- ActionBarScope,
- ActionItemDirective,
- NavigationButtonDirective
-} from "./directives/action-bar";
-export { AndroidFilterComponent, IosFilterComponent } from "./directives/platform-filters";
diff --git a/nativescript-angular/directives/action-bar.ts b/nativescript-angular/directives/action-bar.ts
index 5158a1532..1b9053dd2 100644
--- a/nativescript-angular/directives/action-bar.ts
+++ b/nativescript-angular/directives/action-bar.ts
@@ -1,54 +1,87 @@
import { Directive, Component, ElementRef, Optional, OnDestroy } from "@angular/core";
-import { ActionItem, ActionBar, NavigationButton } from "tns-core-modules/ui/action-bar";
+import {
+ ActionBar,
+ ActionItem,
+ ActionItems,
+ NavigationButton,
+} from "tns-core-modules/ui/action-bar";
import { Page } from "tns-core-modules/ui/page";
-import { View } from "tns-core-modules/ui/core/view";
import { isBlank } from "../lang-facade";
import {
- InvisibleNode,
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: NgView, child: NgView) => {
- const bar = (parent);
- const childView = child;
-
- if (child instanceof InvisibleNode) {
+ insertChild: (parent: NgActionBar, child: NgView, next: any) => {
+ if (isInvisibleNode(child)) {
return;
- } else if (child instanceof NavigationButton) {
- bar.navigationButton = childView;
- childView.parent = bar;
- } else if (child instanceof ActionItem) {
- bar.actionItems.addItem(childView);
- childView.parent = bar;
- } else if (child instanceof View) {
- bar.titleView = childView;
+ } else if (isNavigationButton(child)) {
+ parent.navigationButton = child;
+ child.templateParent = parent;
+ } else if (isActionItem(child)) {
+ addActionItem(parent, child, next);
+ child.templateParent = parent;
+ } else if (isView(child)) {
+ parent.titleView = child;
}
},
- removeChild: (parent: NgView, child: NgView) => {
- const bar = (parent);
- const childView = child;
-
- if (child instanceof InvisibleNode) {
+ removeChild: (parent: NgActionBar, child: NgView) => {
+ if (isInvisibleNode(child)) {
return;
- } else if (child instanceof NavigationButton) {
- if (bar.navigationButton === childView) {
- bar.navigationButton = null;
+ } else if (isNavigationButton(child)) {
+ if (parent.navigationButton === child) {
+ parent.navigationButton = null;
}
- childView.parent = null;
- } else if (child instanceof ActionItem) {
- bar.actionItems.removeItem(childView);
- childView.parent = null;
- } else if (child instanceof View && bar.titleView && bar.titleView === childView) {
- bar.titleView = null;
+
+ child.templateParent = null;
+ } else if (isActionItem(child)) {
+ parent.actionItems.removeItem(child);
+ child.templateParent = 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", () => require("ui/action-bar").ActionBar, actionBarMeta);
registerElement("ActionItem", () => require("ui/action-bar").ActionItem);
registerElement("NavigationButton", () => require("ui/action-bar").NavigationButton);
diff --git a/nativescript-angular/directives/index.ts b/nativescript-angular/directives/index.ts
new file mode 100644
index 000000000..9a8a6fd69
--- /dev/null
+++ b/nativescript-angular/directives/index.ts
@@ -0,0 +1,37 @@
+import { ListViewComponent, TemplateKeyDirective } from "./list-view-comp";
+import { TabViewDirective, TabViewItemDirective } from "./tab-view";
+import {
+ ActionBarComponent,
+ ActionBarScope,
+ ActionItemDirective,
+ NavigationButtonDirective
+} from "./action-bar";
+import { AndroidFilterComponent, IosFilterComponent } from "./platform-filters";
+
+export {
+ ListViewComponent,
+ SetupItemViewArgs,
+ TemplateKeyDirective
+} from "./list-view-comp";
+
+export { TabViewDirective, TabViewItemDirective } from "./tab-view";
+export {
+ ActionBarComponent,
+ ActionBarScope,
+ ActionItemDirective,
+ NavigationButtonDirective
+} from "./action-bar";
+export { AndroidFilterComponent, IosFilterComponent } from "./platform-filters";
+
+export const NS_DIRECTIVES = [
+ ListViewComponent,
+ TemplateKeyDirective,
+ TabViewDirective,
+ TabViewItemDirective,
+ ActionBarComponent,
+ ActionBarScope,
+ ActionItemDirective,
+ NavigationButtonDirective,
+ AndroidFilterComponent,
+ IosFilterComponent,
+];
diff --git a/nativescript-angular/directives/list-view-comp.ts b/nativescript-angular/directives/list-view-comp.ts
index f2237df90..351441cce 100644
--- a/nativescript-angular/directives/list-view-comp.ts
+++ b/nativescript-angular/directives/list-view-comp.ts
@@ -18,6 +18,7 @@ import {
TemplateRef,
ViewChild,
ViewContainerRef,
+ ɵisListLikeIterable as isListLikeIterable
} from "@angular/core";
import { ListView, ItemEventData } from "tns-core-modules/ui/list-view";
import { View, KeyedTemplate } from "tns-core-modules/ui/core/view";
@@ -26,7 +27,6 @@ import { ObservableArray } from "tns-core-modules/data/observable-array";
import { profile } from "tns-core-modules/profiling";
import { getSingleViewRecursive } from "../element-registry";
-import { isListLikeIterable } from "../collection-facade";
import { listViewLog, listViewError } from "../trace";
const NG_VIEW = "_ngViewRef";
diff --git a/nativescript-angular/dom-adapter.ts b/nativescript-angular/dom-adapter.ts
index f577c5ae0..e228f92a5 100644
--- a/nativescript-angular/dom-adapter.ts
+++ b/nativescript-angular/dom-adapter.ts
@@ -2,7 +2,6 @@
import { Type } from "@angular/core";
import { ɵDomAdapter } from "@angular/platform-browser";
import { rendererLog } from "./trace";
-import { print } from "./lang-facade";
export class NativeScriptDomAdapter implements ɵDomAdapter {
static makeCurrent() {
@@ -27,15 +26,15 @@ export class NativeScriptDomAdapter implements ɵDomAdapter {
}
log(arg: any): void {
- print(arg);
+ console.log(arg);
}
logError(arg: any): void {
- print(arg);
+ console.log(arg);
}
logGroup(arg: any): void {
- print(arg);
+ console.log(arg);
}
logGroupEnd(): void {
diff --git a/nativescript-angular/element-registry.ts b/nativescript-angular/element-registry.ts
index 9a0f044aa..35f111cf8 100644
--- a/nativescript-angular/element-registry.ts
+++ b/nativescript-angular/element-registry.ts
@@ -2,25 +2,30 @@ import { View } from "tns-core-modules/ui/core/view";
import { LayoutBase } from "tns-core-modules/ui/layouts/layout-base";
export type NgView = (View & ViewExtensions);
-export type NgElement = NgView | InvisibleNode;
export interface ViewExtensions {
+ meta: ViewClassMeta;
nodeType: number;
nodeName: string;
templateParent: NgView;
+ nextSibling: NgView;
+ firstChild: NgView;
+ lastChild: NgView;
ngCssClasses: Map;
- meta: ViewClassMeta;
}
export interface ViewClass {
new (): View;
}
-export abstract class InvisibleNode extends View implements ViewExtensions {
+export abstract class InvisibleNode extends View implements NgView {
meta: { skipAddToDom: boolean };
- templateParent: NgView;
nodeType: number;
nodeName: string;
+ templateParent: NgView;
+ nextSibling: NgView;
+ firstChild: NgView;
+ lastChild: NgView;
ngCssClasses: Map;
constructor() {
@@ -42,7 +47,7 @@ export class CommentNode extends InvisibleNode {
super();
this.meta = {
- skipAddToDom: false,
+ skipAddToDom: true,
};
this.id = CommentNode.id.toString();
CommentNode.id += 1;
@@ -67,14 +72,22 @@ const getClassName = instance => instance.constructor.name;
export interface ViewClassMeta {
skipAddToDom?: boolean;
- insertChild?: (parent: NgView, child: NgView, atIndex: number) => void;
- removeChild?: (parent: NgView, child: NgView) => void;
+ 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 = () => ViewClass;
const elementMap = new Map();
diff --git a/nativescript-angular/file-system/ns-file-system.ts b/nativescript-angular/file-system/ns-file-system.ts
index f8cf590d5..222a54c12 100644
--- a/nativescript-angular/file-system/ns-file-system.ts
+++ b/nativescript-angular/file-system/ns-file-system.ts
@@ -1,12 +1,20 @@
import { Injectable } from "@angular/core";
-import { knownFolders, Folder } from "tns-core-modules/file-system";
+import { knownFolders, Folder, File } from "tns-core-modules/file-system";
// 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 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.ts b/nativescript-angular/forms/forms.module.ts
similarity index 51%
rename from nativescript-angular/forms.ts
rename to nativescript-angular/forms/forms.module.ts
index e449fc79f..814b08727 100644
--- a/nativescript-angular/forms.ts
+++ b/nativescript-angular/forms/forms.module.ts
@@ -1,11 +1,13 @@
import { NgModule } from "@angular/core";
import { FormsModule } from "@angular/forms";
-import { TextValueAccessor } from "./value-accessors/text-value-accessor";
-import { CheckedValueAccessor } from "./value-accessors/checked-value-accessor";
-import { DateValueAccessor } from "./value-accessors/date-value-accessor";
-import { TimeValueAccessor } from "./value-accessors/time-value-accessor";
-import { NumberValueAccessor } from "./value-accessors/number-value-accessor";
-import { SelectedIndexValueAccessor } from "./value-accessors/selectedIndex-value-accessor";
+import {
+ TextValueAccessor,
+ CheckedValueAccessor,
+ DateValueAccessor,
+ TimeValueAccessor,
+ NumberValueAccessor,
+ SelectedIndexValueAccessor
+} from "./value-accessors";
export const FORMS_DIRECTIVES = [
TextValueAccessor,
diff --git a/nativescript-angular/forms/index.ts b/nativescript-angular/forms/index.ts
new file mode 100644
index 000000000..a95e3e129
--- /dev/null
+++ b/nativescript-angular/forms/index.ts
@@ -0,0 +1 @@
+export * from "./forms.module";
diff --git a/nativescript-angular/value-accessors/base-value-accessor.ts b/nativescript-angular/forms/value-accessors/base-value-accessor.ts
similarity index 77%
rename from nativescript-angular/value-accessors/base-value-accessor.ts
rename to nativescript-angular/forms/value-accessors/base-value-accessor.ts
index 25ac8aa7a..5f46ca98a 100644
--- a/nativescript-angular/value-accessors/base-value-accessor.ts
+++ b/nativescript-angular/forms/value-accessors/base-value-accessor.ts
@@ -1,5 +1,7 @@
import { ControlValueAccessor } from "@angular/forms";
-import { View } from "tns-core-modules/ui/core/view";
+import { View, unsetValue } from "tns-core-modules/ui/core/view";
+
+import { isBlank } from "../../lang-facade";
export class BaseValueAccessor implements ControlValueAccessor {
private pendingChangeNotification: any = 0;
@@ -28,5 +30,9 @@ export class BaseValueAccessor implements ControlValueAccess
this.view.isEnabled = !isDisabled;
}
- writeValue(_: any) { }
+ writeValue(_: any) {}
+
+ protected normalizeValue(value: any): any {
+ return isBlank(value) ? unsetValue : value;
+ }
}
diff --git a/nativescript-angular/value-accessors/checked-value-accessor.ts b/nativescript-angular/forms/value-accessors/checked-value-accessor.ts
similarity index 92%
rename from nativescript-angular/value-accessors/checked-value-accessor.ts
rename to nativescript-angular/forms/value-accessors/checked-value-accessor.ts
index af1a22289..6053ee700 100644
--- a/nativescript-angular/value-accessors/checked-value-accessor.ts
+++ b/nativescript-angular/forms/value-accessors/checked-value-accessor.ts
@@ -34,6 +34,7 @@ export class CheckedValueAccessor extends BaseValueAccessor { // tslint:
}
writeValue(value: any): void {
- this.view.checked = value;
+ const normalized = super.normalizeValue(value);
+ this.view.checked = normalized;
}
}
diff --git a/nativescript-angular/value-accessors/date-value-accessor.ts b/nativescript-angular/forms/value-accessors/date-value-accessor.ts
similarity index 93%
rename from nativescript-angular/value-accessors/date-value-accessor.ts
rename to nativescript-angular/forms/value-accessors/date-value-accessor.ts
index 9e66bbc92..c7f8419a9 100644
--- a/nativescript-angular/value-accessors/date-value-accessor.ts
+++ b/nativescript-angular/forms/value-accessors/date-value-accessor.ts
@@ -25,7 +25,6 @@ const DATE_VALUE_ACCESSOR = {
"date-picker[ngModel],date-picker[formControlName],date-picker[formControl]",
providers: [DATE_VALUE_ACCESSOR],
host: {
- "(touch)": "onTouched()",
"(dateChange)": "onChange($event.value)",
},
})
@@ -35,6 +34,7 @@ export class DateValueAccessor extends BaseValueAccessor { // tslint
}
writeValue(value: any): void {
- this.view.date = value;
+ 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..351151606
--- /dev/null
+++ b/nativescript-angular/forms/value-accessors/index.ts
@@ -0,0 +1,7 @@
+export * from "./base-value-accessor";
+export * from "./text-value-accessor";
+export * from "./checked-value-accessor";
+export * from "./date-value-accessor";
+export * from "./time-value-accessor";
+export * from "./number-value-accessor";
+export * from "./selectedIndex-value-accessor";
diff --git a/nativescript-angular/value-accessors/number-value-accessor.ts b/nativescript-angular/forms/value-accessors/number-value-accessor.ts
similarity index 92%
rename from nativescript-angular/value-accessors/number-value-accessor.ts
rename to nativescript-angular/forms/value-accessors/number-value-accessor.ts
index 44451f96e..32275d4ea 100644
--- a/nativescript-angular/value-accessors/number-value-accessor.ts
+++ b/nativescript-angular/forms/value-accessors/number-value-accessor.ts
@@ -24,7 +24,6 @@ const NUMBER_VALUE_ACCESSOR = {
"slider[ngModel],slider[formControlName],slider[formControl]",
providers: [NUMBER_VALUE_ACCESSOR],
host: {
- "(touch)": "onTouched()",
"(valueChange)": "onChange($event.value)",
},
})
@@ -34,6 +33,7 @@ export class NumberValueAccessor extends BaseValueAccessor { // tslint:d
}
writeValue(value: any): void {
- this.view.value = value;
+ const normalized = super.normalizeValue(value);
+ this.view.value = normalized;
}
}
diff --git a/nativescript-angular/value-accessors/selectedIndex-value-accessor.ts b/nativescript-angular/forms/value-accessors/selectedIndex-value-accessor.ts
similarity index 96%
rename from nativescript-angular/value-accessors/selectedIndex-value-accessor.ts
rename to nativescript-angular/forms/value-accessors/selectedIndex-value-accessor.ts
index 005a40f98..2c9a7ddf1 100644
--- a/nativescript-angular/value-accessors/selectedIndex-value-accessor.ts
+++ b/nativescript-angular/forms/value-accessors/selectedIndex-value-accessor.ts
@@ -38,7 +38,6 @@ export type SelectableView = {selectedIndex: number} & View;
"tab-view[ngModel],tab-view[formControlName],tab-view[formControl]",
providers: [SELECTED_INDEX_VALUE_ACCESSOR],
host: {
- "(touch)": "onTouched()",
"(selectedIndexChange)": "onChange($event.value)",
},
})
@@ -51,7 +50,8 @@ export class SelectedIndexValueAccessor extends BaseValueAccessor { // tslint:d
}
writeValue(value: any): void {
- this.view.text = value;
+ const normalized = super.normalizeValue(value);
+ this.view.text = normalized;
}
}
diff --git a/nativescript-angular/value-accessors/time-value-accessor.ts b/nativescript-angular/forms/value-accessors/time-value-accessor.ts
similarity index 93%
rename from nativescript-angular/value-accessors/time-value-accessor.ts
rename to nativescript-angular/forms/value-accessors/time-value-accessor.ts
index d62b170e7..6be6851ee 100644
--- a/nativescript-angular/value-accessors/time-value-accessor.ts
+++ b/nativescript-angular/forms/value-accessors/time-value-accessor.ts
@@ -36,6 +36,7 @@ export class TimeValueAccessor extends BaseValueAccessor { // tslint
}
writeValue(value: any): void {
- this.view.time = value;
+ const normalized = super.normalizeValue(value);
+ this.view.time = normalized;
}
}
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..ea6623d5b
--- /dev/null
+++ b/nativescript-angular/http-client/http-client.module.ts
@@ -0,0 +1,26 @@
+import { NgModule } from "@angular/core";
+
+// IMPORTant: Importing "@angular/common/http" for the first time overwrites the
+// global.__extends function.
+const cachedExtends = global.__extends;
+import { HttpClientModule, HttpBackend } from "@angular/common/http";
+global.__extends = cachedExtends;
+
+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..ae0ecc6f8
--- /dev/null
+++ b/nativescript-angular/http-client/http-utils.ts
@@ -0,0 +1,52 @@
+import { NSFileSystem } from "../file-system/ns-file-system";
+
+import { Observable } from "rxjs/Observable";
+import { Observer } from "rxjs/Observer";
+import { path } from "tns-core-modules/file-system/file-system";
+
+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..da2e04db0
--- /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..6cf2f2bf4
--- /dev/null
+++ b/nativescript-angular/http-client/ns-http-backend.ts
@@ -0,0 +1,62 @@
+import { Injectable } from "@angular/core";
+import {
+ HttpRequest, HttpEvent,
+ XhrFactory, HttpResponse,
+ HttpErrorResponse, HttpXhrBackend
+} from "@angular/common/http";
+import { Observable } from "rxjs/Observable";
+
+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/http.ts b/nativescript-angular/http.ts
deleted file mode 100644
index 5f6583a02..000000000
--- a/nativescript-angular/http.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import {
- Http, XHRBackend, RequestOptions
-} from "@angular/http";
-import { NSXSRFStrategy, NSHttp } from "./http/ns-http";
-import { NSFileSystem } from "./file-system/ns-file-system";
-
-import { NgModule } from "@angular/core";
-import { HttpModule, XSRFStrategy } from "@angular/http";
-
-export { NSHttp } from "./http/ns-http";
-
-export function nsHttpFactory(backend, options, nsFileSystem) {
- return new NSHttp(backend, options, nsFileSystem);
-}
-
-export function nsXSRFStrategyFactory() {
- return new NSXSRFStrategy();
-}
-
-@NgModule({
- providers: [
- {provide: XSRFStrategy, useFactory: nsXSRFStrategyFactory},
- NSFileSystem,
- {provide: Http, useFactory: nsHttpFactory,
- deps: [XHRBackend, RequestOptions, NSFileSystem]}
- ],
- imports: [
- HttpModule,
- ],
- exports: [
- HttpModule,
- ]
-})
-export class NativeScriptHttpModule {
-}
diff --git a/nativescript-angular/http/http.module.ts b/nativescript-angular/http/http.module.ts
new file mode 100644
index 000000000..b6d35172c
--- /dev/null
+++ b/nativescript-angular/http/http.module.ts
@@ -0,0 +1,34 @@
+import { NgModule } from "@angular/core";
+import { Http, XHRBackend, RequestOptions, HttpModule, XSRFStrategy } from "@angular/http";
+
+import { NSXSRFStrategy, NSHttp } from "./ns-http";
+import { NSFileSystem } from "../file-system/ns-file-system";
+
+export { NSHttp } from "./ns-http";
+
+export function nsHttpFactory(backend, options, nsFileSystem) {
+ return new NSHttp(backend, options, nsFileSystem);
+}
+
+export function nsXSRFStrategyFactory() {
+ return new NSXSRFStrategy();
+}
+
+@NgModule({
+ providers: [
+ { provide: XSRFStrategy, useFactory: nsXSRFStrategyFactory },
+ NSFileSystem,
+ {
+ provide: Http, useFactory: nsHttpFactory,
+ deps: [XHRBackend, RequestOptions, NSFileSystem]
+ }
+ ],
+ imports: [
+ HttpModule,
+ ],
+ exports: [
+ HttpModule,
+ ]
+})
+export class NativeScriptHttpModule {
+}
diff --git a/nativescript-angular/http/index.ts b/nativescript-angular/http/index.ts
new file mode 100644
index 000000000..720657322
--- /dev/null
+++ b/nativescript-angular/http/index.ts
@@ -0,0 +1 @@
+export * from "./http.module";
diff --git a/nativescript-angular/http/ns-http.ts b/nativescript-angular/http/ns-http.ts
index 6b6fff33e..e0f04b661 100644
--- a/nativescript-angular/http/ns-http.ts
+++ b/nativescript-angular/http/ns-http.ts
@@ -2,6 +2,7 @@ import { Injectable } from "@angular/core";
import {
Http,
ConnectionBackend,
+ Request,
RequestOptions,
RequestOptionsArgs,
ResponseOptions,
@@ -10,54 +11,62 @@ import {
} from "@angular/http";
import { Observable } from "rxjs/Observable";
import "rxjs/add/observable/fromPromise";
+
+import { isLocalRequest, processLocalFileRequest } from "../http-client/http-utils";
+
import { NSFileSystem } from "../file-system/ns-file-system";
export class NSXSRFStrategy {
- public configureRequest(_req: any) {
- // noop
- }
+ public configureRequest(_req: any) {
+ // noop
+ }
}
@Injectable()
export class NSHttp extends Http {
- constructor(backend: ConnectionBackend, defaultOptions: RequestOptions, private nsFileSystem: NSFileSystem) {
- super(backend, defaultOptions);
- }
-
- /**
- * Performs a request with `get` http method.
- * Uses a local file if `~/` resource is requested.
- */
- get(url: string, options?: RequestOptionsArgs): Observable {
- if (url.indexOf("~") === 0 || url.indexOf("/") === 0) {
- // normalize url
- url = url.replace("~", "").replace("/", "");
- // request from local app resources
- return Observable.fromPromise(new Promise((resolve, reject) => {
- let app = this.nsFileSystem.currentApp();
- let localFile = app.getFile(url);
- if (localFile) {
- localFile.readText().then((data) => {
- resolve(responseOptions(data, 200, url));
- }, (err: Object) => {
- reject(responseOptions(err, 400, url));
- });
+ constructor(backend: ConnectionBackend, defaultOptions: RequestOptions, private nsFileSystem: NSFileSystem) {
+ super(backend, defaultOptions);
+ }
+
+ /**
+ * Performs a request with `request` http method.
+ */
+ request(req: string | Request, options?: RequestOptionsArgs): Observable {
+ const urlString = typeof req === "string" ? req : req.url;
+ if (isLocalRequest(urlString)) {
+ return this.requestLocalFile(urlString);
} else {
- reject(responseOptions("Not Found", 404, url));
+ return super.request(req, options);
}
- }));
- } else {
- return super.get(url, options);
}
- }
+
+ /**
+ * Performs a request with `get` http method.
+ */
+ get(url: string, options?: RequestOptionsArgs): Observable {
+ if (isLocalRequest(url)) {
+ return this.requestLocalFile(url);
+ } else {
+ return super.get(url, options);
+ }
+ }
+
+ private requestLocalFile(url: string): Observable {
+ return processLocalFileRequest(
+ url,
+ this.nsFileSystem,
+ createResponse,
+ createResponse
+ );
+ }
}
-function responseOptions(body: string | Object, status: number, url: string): Response {
- return new Response(new ResponseOptions({
- body: body,
- status: status,
- statusText: "OK",
- type: status === 200 ? ResponseType.Default : ResponseType.Error,
- url: url
- }));
+function createResponse(url: string, body: string | Object, status: number): Response {
+ return new Response(new ResponseOptions({
+ body: body,
+ status: status,
+ statusText: "OK",
+ type: status === 200 ? ResponseType.Default : ResponseType.Error,
+ url: url
+ }));
}
diff --git a/nativescript-angular/index.ts b/nativescript-angular/index.ts
index 2270a8ac4..e5f4bfb8b 100644
--- a/nativescript-angular/index.ts
+++ b/nativescript-angular/index.ts
@@ -26,4 +26,4 @@ export {
registerElement,
} from "./element-registry";
-export * from "./value-accessors/base-value-accessor";
+export * from "./forms/value-accessors/base-value-accessor";
diff --git a/nativescript-angular/lang-facade.ts b/nativescript-angular/lang-facade.ts
index 424b1c64a..31b567065 100644
--- a/nativescript-angular/lang-facade.ts
+++ b/nativescript-angular/lang-facade.ts
@@ -1,8 +1,3 @@
-/* tslint:disable */
-//Copied unexported functions from @angular/core/src/facade/lang
-var globalScope = global;
-export var global = globalScope;
-
export function isPresent(obj: any): boolean {
return obj !== undefined && obj !== null;
}
@@ -10,58 +5,3 @@ export function isPresent(obj: any): boolean {
export function isBlank(obj: any): boolean {
return obj === undefined || obj === null;
}
-
-export function isDate(obj: any): obj is Date {
- return obj instanceof Date && !isNaN(obj.valueOf());
-}
-
-export function print(obj: Error | Object) {
- console.log(obj);
-}
-
-export function isJsObject(o: any): boolean {
- return o !== null && (typeof o === 'function' || typeof o === 'object');
-}
-
-export function isArray(obj: any): boolean {
- return Array.isArray(obj);
-}
-
-// When Symbol.iterator doesn't exist, retrieves the key used in es6-shim
-declare var Symbol: any;
-let _symbolIterator: any = null;
-export function getSymbolIterator(): string|symbol {
- if (isBlank(_symbolIterator)) {
- if (isPresent((globalScope).Symbol) && isPresent(Symbol.iterator)) {
- _symbolIterator = Symbol.iterator;
- } else {
- // es6-shim specific logic
- let keys = Object.getOwnPropertyNames(Map.prototype);
- for (let i = 0; i < keys.length; ++i) {
- let key = keys[i];
- if (key !== 'entries' && key !== 'size' &&
- (Map as any).prototype[key] === Map.prototype['entries']) {
- _symbolIterator = key;
- }
- }
- }
- }
- return _symbolIterator;
-}
-
-export function setValueOnPath(global: any, path: string, value: any) {
- let parts = path.split('.');
- let obj: any = global;
- while (parts.length > 1) {
- let name = parts.shift();
- if (obj.hasOwnProperty(name) && isPresent(obj[name])) {
- obj = obj[name];
- } else {
- obj = obj[name] = {};
- }
- }
- if (obj === undefined || obj === null) {
- obj = {};
- }
- obj[parts.shift()] = value;
-}
diff --git a/nativescript-angular/nativescript.module.ts b/nativescript-angular/nativescript.module.ts
index e5b34007e..305890ab4 100644
--- a/nativescript-angular/nativescript.module.ts
+++ b/nativescript-angular/nativescript.module.ts
@@ -21,7 +21,7 @@ import { NativeScriptRendererFactory } from "./renderer";
import { DetachedLoader } from "./common/detached-loader";
export function errorHandlerFactory() {
- return new ErrorHandler(true);
+ return new ErrorHandler();
}
@NgModule({
diff --git a/nativescript-angular/package.json b/nativescript-angular/package.json
index 2685c0fb6..673f98e03 100644
--- a/nativescript-angular/package.json
+++ b/nativescript-angular/package.json
@@ -1,7 +1,7 @@
{
"private": true,
"name": "nativescript-angular",
- "version": "4.2.0",
+ "version": "4.4.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",
@@ -43,32 +43,32 @@
"reflect-metadata": "^0.1.8"
},
"peerDependencies": {
- "@angular/common": "~4.2.5",
- "@angular/compiler": "~4.2.5",
- "@angular/core": "~4.2.5",
- "@angular/forms": "~4.2.5",
- "@angular/http": "~4.2.5",
- "@angular/platform-browser": "~4.2.5",
- "@angular/router": "~4.2.5",
+ "@angular/common": "~4.4.1",
+ "@angular/compiler": "~4.4.1",
+ "@angular/core": "~4.4.1",
+ "@angular/forms": "~4.4.1",
+ "@angular/http": "~4.4.1",
+ "@angular/platform-browser": "~4.4.1",
+ "@angular/router": "~4.4.1",
"rxjs": "^5.0.1",
- "tns-core-modules": "^3.1.0 || >3.2.0-",
+ "tns-core-modules": "^3.1.0 || >3.3.0-",
"zone.js": "^0.8.4"
},
"devDependencies": {
- "@angular/animations": "~4.2.5",
- "@angular/common": "~4.2.5",
- "@angular/compiler": "~4.2.5",
- "@angular/compiler-cli": "~4.2.5",
- "@angular/core": "~4.2.5",
- "@angular/forms": "~4.2.5",
- "@angular/http": "~4.2.5",
- "@angular/platform-browser": "~4.2.5",
- "@angular/router": "~4.2.5",
+ "@angular/animations": "~4.4.1",
+ "@angular/common": "~4.4.1",
+ "@angular/compiler": "~4.4.1",
+ "@angular/compiler-cli": "~4.4.1",
+ "@angular/core": "~4.4.1",
+ "@angular/forms": "~4.4.1",
+ "@angular/http": "~4.4.1",
+ "@angular/platform-browser": "~4.4.1",
+ "@angular/router": "~4.4.1",
"codelyzer": "^3.1.2",
"rxjs": "^5.4.2",
"tns-core-modules": "next",
"tslint": "^5.5.0",
- "typescript": "^2.4.1",
+ "typescript": "^2.5.1",
"zone.js": "^0.8.12"
}
}
diff --git a/nativescript-angular/platform-common.ts b/nativescript-angular/platform-common.ts
index 374d9276c..a5fbb67e1 100644
--- a/nativescript-angular/platform-common.ts
+++ b/nativescript-angular/platform-common.ts
@@ -18,18 +18,17 @@ import {
EventEmitter,
Provider,
Sanitizer,
- OpaqueToken
+ InjectionToken
} from "@angular/core";
-
-// Work around a TS bug requiring an import of OpaqueToken without using it
-if ((global).___TS_UNUSED) {
- (() => {
- return OpaqueToken;
- })();
-}
+import { DOCUMENT } from "@angular/common";
import { rendererLog, rendererError } from "./trace";
-import { PAGE_FACTORY, PageFactory, defaultPageFactoryProvider, setRootPage } from "./platform-providers";
+import {
+ PAGE_FACTORY,
+ PageFactory,
+ defaultPageFactoryProvider,
+ setRootPage
+} from "./platform-providers";
import { start, setCssFileName } from "tns-core-modules/application";
import { topmost, NavigationEntry } from "tns-core-modules/ui/frame";
@@ -43,6 +42,12 @@ export const onAfterLivesync = new EventEmitter>();
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;
+ })();
+}
export interface AppOptions {
bootInExistingPage?: boolean;
cssFile?: string;
@@ -57,9 +62,16 @@ export class NativeScriptSanitizer extends Sanitizer {
}
}
+export class NativeScriptDocument {
+ 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 },
+ { provide: DOCUMENT, useClass: NativeScriptDocument },
];
export class NativeScriptPlatformRef extends PlatformRef {
@@ -82,7 +94,7 @@ export class NativeScriptPlatformRef extends PlatformRef {
bootstrapModule(
moduleType: Type,
compilerOptions: CompilerOptions | CompilerOptions[] = []
- ): Promise> {
+ ): Promise> {
this._bootstrapper = () => this.platform.bootstrapModule(moduleType, compilerOptions);
this.bootstrapApp();
diff --git a/nativescript-angular/platform-providers.ts b/nativescript-angular/platform-providers.ts
index 466fa54c9..86d47e0a2 100644
--- a/nativescript-angular/platform-providers.ts
+++ b/nativescript-angular/platform-providers.ts
@@ -1,19 +1,13 @@
+import { InjectionToken } from "@angular/core";
+
import { topmost, Frame } from "tns-core-modules/ui/frame";
+import { View } from "tns-core-modules/ui/core/view";
import { Page } from "tns-core-modules/ui/page";
-import { OpaqueToken } from "@angular/core";
import { device, Device } from "tns-core-modules/platform";
-import * as platform from "tns-core-modules/platform";
-
-export const APP_ROOT_VIEW = new OpaqueToken("App Root View");
-export const DEVICE = new OpaqueToken("platfrom device");
-export const PAGE_FACTORY = new OpaqueToken("page factory");
-// Work around a TS bug requiring an import of platform.Device without using it
-if ((global).___TS_UNUSED) {
- (() => {
- return platform;
- })();
-}
+export const APP_ROOT_VIEW = new InjectionToken("App Root View");
+export const DEVICE = new InjectionToken("platform device");
+export const PAGE_FACTORY = new InjectionToken("page factory");
let _rootPageRef: WeakRef;
diff --git a/nativescript-angular/platform.ts b/nativescript-angular/platform.ts
index 1c418e794..f1a6ad80b 100644
--- a/nativescript-angular/platform.ts
+++ b/nativescript-angular/platform.ts
@@ -35,6 +35,7 @@ if ((global).___TS_UNUSED) {
import "./dom-adapter";
import { NativeScriptElementSchemaRegistry } from "./schema-registry";
+import { NSFileSystem } from "./file-system/ns-file-system";
import { FileSystemResourceLoader } from "./resource-loader";
export const NS_COMPILER_PROVIDERS = [
@@ -43,6 +44,7 @@ export const NS_COMPILER_PROVIDERS = [
provide: COMPILER_OPTIONS,
useValue: {
providers: [
+ NSFileSystem,
{ provide: ResourceLoader, useClass: FileSystemResourceLoader },
{ provide: ElementSchemaRegistry, useClass: NativeScriptElementSchemaRegistry },
]
diff --git a/nativescript-angular/renderer.ts b/nativescript-angular/renderer.ts
index ddf247c0b..cdf550fca 100644
--- a/nativescript-angular/renderer.ts
+++ b/nativescript-angular/renderer.ts
@@ -11,7 +11,6 @@ import { topmost } from "tns-core-modules/ui/frame";
import { profile } from "tns-core-modules/profiling";
import { APP_ROOT_VIEW, DEVICE, getRootPage } from "./platform-providers";
-import { isBlank } from "./lang-facade";
import { ViewUtil } from "./view-util";
import { NgView, InvisibleNode } from "./element-registry";
import { rendererLog as traceLog } from "./trace";
@@ -23,6 +22,11 @@ export const HOST_ATTR = `_nghost-${COMPONENT_VARIABLE}`;
export const CONTENT_ATTR = `_ngcontent-${COMPONENT_VARIABLE}`;
const ATTR_SANITIZER = /-/g;
+export interface ElementReference {
+ previous: NgView;
+ next: NgView;
+}
+
@Injectable()
export class NativeScriptRendererFactory implements RendererFactory2 {
private componentRenderers = new Map();
@@ -55,7 +59,7 @@ export class NativeScriptRendererFactory implements RendererFactory2 {
}
let renderer: NativeScriptRenderer = this.componentRenderers.get(type.id);
- if (!isBlank(renderer)) {
+ if (renderer) {
return renderer;
}
@@ -85,15 +89,16 @@ export class NativeScriptRenderer extends Renderer2 {
}
@profile
- appendChild(parent: any, newChild: NgView): void {
+ appendChild(parent: NgView, newChild: NgView): void {
traceLog(`NativeScriptRenderer.appendChild child: ${newChild} parent: ${parent}`);
this.viewUtil.insertChild(parent, newChild);
}
@profile
- insertBefore(parent: NgView, newChild: NgView, refChildIndex: number): void {
- traceLog(`NativeScriptRenderer.insertBefore child: ${newChild} parent: ${parent}`);
- this.viewUtil.insertChild(parent, newChild, refChildIndex);
+ insertBefore(parent: NgView, newChild: NgView, { previous, next }: ElementReference): void {
+ traceLog(`NativeScriptRenderer.insertBefore child: ${newChild} ` +
+ `parent: ${parent} previous: ${previous} next: ${next}`);
+ this.viewUtil.insertChild(parent, newChild, previous, next);
}
@profile
@@ -110,14 +115,18 @@ export class NativeScriptRenderer extends Renderer2 {
@profile
parentNode(node: NgView): any {
- traceLog("NativeScriptRenderer.parentNode for node: " + node);
+ traceLog(`NativeScriptRenderer.parentNode for node: ${node}`);
return node.parent || node.templateParent;
}
@profile
- nextSibling(node: NgView): number {
- traceLog(`NativeScriptRenderer.nextSibling ${node}`);
- return this.viewUtil.nextSiblingIndex(node);
+ nextSibling(node: NgView): ElementReference {
+ traceLog(`NativeScriptRenderer.nextSibling of ${node} is ${node.nextSibling}`);
+
+ return {
+ previous: node,
+ next: node.nextSibling,
+ };
}
@profile
diff --git a/nativescript-angular/resource-loader.ts b/nativescript-angular/resource-loader.ts
index e3c2ba4db..43a5091bc 100644
--- a/nativescript-angular/resource-loader.ts
+++ b/nativescript-angular/resource-loader.ts
@@ -1,25 +1,72 @@
-import { path, knownFolders, File } from "tns-core-modules/file-system";
+import { Injectable } from "@angular/core";
import { ResourceLoader } from "@angular/compiler";
+import { path } from "tns-core-modules/file-system";
+import { NSFileSystem } from "./file-system/ns-file-system";
+
+const extensionsFallbacks = [
+ [".scss", ".css"],
+ [".sass", ".css"],
+ [".less", ".css"]
+];
+
+@Injectable()
export class FileSystemResourceLoader extends ResourceLoader {
- resolve(url: string, baseUrl: string): string {
- // Angular assembles absolute URL's and prefixes them with //
+ constructor(private fs: NSFileSystem) {
+ super();
+ }
+
+ get(url: string): Promise {
+ const resolvedPath = this.resolve(url);
+
+ const templateFile = this.fs.fileFromPath(resolvedPath);
+
+ return templateFile.readText();
+ }
+
+ resolve(url: string): string {
+ const normalizedUrl = this.resolveRelativeUrls(url);
+
+ if (this.fs.fileExists(normalizedUrl)) {
+ return normalizedUrl;
+ }
+
+ const { candidates: fallbackCandidates, resource: fallbackResource } =
+ this.fallbackResolve(normalizedUrl);
+
+ if (fallbackResource) {
+ return fallbackResource;
+ }
+
+ throw new Error(`Could not resolve ${url}. Looked for: ${normalizedUrl}, ${fallbackCandidates}`);
+ }
+
+ private resolveRelativeUrls(url: string): string {
+ // Angular assembles absolute URLs and prefixes them with //
if (url.indexOf("/") !== 0) {
- // Resolve relative URL's based on the app root.
- return path.join(baseUrl, url);
+ // Resolve relative URLs based on the app root.
+ return path.join(this.fs.currentApp().path, url);
} else {
return url;
}
}
- get(url: string): Promise {
- const appDir = knownFolders.currentApp().path;
- const templatePath = this.resolve(url, appDir);
+ private fallbackResolve(url: string):
+ ({ resource: string, candidates: string[] }) {
- if (!File.exists(templatePath)) {
- throw new Error(`File ${templatePath} does not exist. Resolved from: ${url}.`);
- }
- let templateFile = File.fromPath(templatePath);
- return templateFile.readText();
+ const candidates = extensionsFallbacks
+ .filter(([extension]) => url.endsWith(extension))
+ .map(([extension, fallback]) =>
+ this.replaceExtension(url, extension, fallback));
+
+ const resource = candidates.find(candidate => this.fs.fileExists(candidate));
+
+ return { candidates, resource };
+ }
+
+ private replaceExtension(fileName: string, oldExtension: string, newExtension: string): string {
+ const baseName = fileName.substr(0, fileName.length - oldExtension.length);
+ return baseName + newExtension;
}
}
+
diff --git a/nativescript-angular/router/index.ts b/nativescript-angular/router/index.ts
new file mode 100644
index 000000000..d13492a9e
--- /dev/null
+++ b/nativescript-angular/router/index.ts
@@ -0,0 +1 @@
+export * from "./router.module";
diff --git a/nativescript-angular/router/ns-location-strategy.ts b/nativescript-angular/router/ns-location-strategy.ts
index 3a0f2a0b1..c474f2a24 100644
--- a/nativescript-angular/router/ns-location-strategy.ts
+++ b/nativescript-angular/router/ns-location-strategy.ts
@@ -67,7 +67,7 @@ export class NSLocationStrategy extends LocationStrategy {
replaceState(state: any, title: string, url: string, queryParams: string): void {
if (this.states.length > 0) {
- routerLog("NSLocationStrategy.replaceState changing exisitng state: " +
+ routerLog("NSLocationStrategy.replaceState changing existing state: " +
`${state}, title: ${title}, url: ${url}, queryParams: ${queryParams}`);
const topState = this.peekState();
topState.state = state;
@@ -104,7 +104,7 @@ export class NSLocationStrategy extends LocationStrategy {
if (state.isPageNavigation) {
// This was a page navigation - so navigate through frame.
routerLog("NSLocationStrategy.back() while not navigating back but top" +
- " state is page - will call frame.goback()");
+ " state is page - will call frame.goBack()");
this.frame.goBack();
} else {
// Nested navigation - just pop the state
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..d6b489180
--- /dev/null
+++ b/nativescript-angular/router/ns-route-reuse-strategy.ts
@@ -0,0 +1,146 @@
+import { Injectable } from "@angular/core";
+import { RouteReuseStrategy, ActivatedRouteSnapshot, DetachedRouteHandle } from "@angular/router";
+
+import { routeReuseStrategyLog as log } from "../trace";
+import { NSLocationStrategy } from "./ns-location-strategy";
+import {
+ destroyComponentRef,
+ findTopActivatedRouteNodeForOutlet,
+ pageRouterActivatedSymbol
+} from "./page-router-outlet";
+
+interface CacheItem {
+ key: string;
+ state: DetachedRouteHandle;
+}
+
+/**
+ * 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() {
+ log(`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);
+ }
+ }
+}
+
+/**
+ * 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 cache: DetachedStateCache = new DetachedStateCache();
+
+ constructor(private location: NSLocationStrategy) { }
+
+ shouldDetach(route: ActivatedRouteSnapshot): boolean {
+ route = findTopActivatedRouteNodeForOutlet(route);
+
+ const key = getSnapshotKey(route);
+ const isPageActivated = route[pageRouterActivatedSymbol];
+ const isBack = this.location._isPageNavigatingBack();
+ const shouldDetach = !isBack && isPageActivated;
+
+ log(`shouldDetach isBack: ${isBack} key: ${key} result: ${shouldDetach}`);
+
+ return shouldDetach;
+ }
+
+ shouldAttach(route: ActivatedRouteSnapshot): boolean {
+ route = findTopActivatedRouteNodeForOutlet(route);
+
+ const key = getSnapshotKey(route);
+ const isBack = this.location._isPageNavigatingBack();
+ const shouldAttach = isBack && this.cache.peek().key === key;
+
+ log(`shouldAttach isBack: ${isBack} key: ${key} result: ${shouldAttach}`);
+
+ return shouldAttach;
+ }
+
+ store(route: ActivatedRouteSnapshot, state: DetachedRouteHandle): void {
+ route = findTopActivatedRouteNodeForOutlet(route);
+
+ const key = getSnapshotKey(route);
+ log(`store key: ${key}, state: ${state}`);
+
+ if (state) {
+ this.cache.push({ key, state });
+ } else {
+ const topItem = this.cache.peek();
+ if (topItem.key === key) {
+ this.cache.pop();
+ } 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 key = getSnapshotKey(route);
+ const isBack = this.location._isPageNavigatingBack();
+ const cachedItem = this.cache.peek();
+
+ let state = null;
+ if (isBack && cachedItem && cachedItem.key === key) {
+ state = cachedItem.state;
+ }
+
+ log(`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];
+ }
+
+ log(`shouldReuseRoute result: ${shouldReuse}`);
+
+ return shouldReuse;
+ }
+
+ clearCache() {
+ this.cache.clear();
+ }
+}
+
+function getSnapshotKey(snapshot: ActivatedRouteSnapshot): string {
+ return snapshot.pathFromRoot.join("->");
+}
diff --git a/nativescript-angular/router/ns-router-link-active.ts b/nativescript-angular/router/ns-router-link-active.ts
index b47f4b9de..9d0210e74 100644
--- a/nativescript-angular/router/ns-router-link-active.ts
+++ b/nativescript-angular/router/ns-router-link-active.ts
@@ -6,7 +6,7 @@ import {
import { Subscription } from "rxjs/Subscription";
import { NavigationEnd, Router, UrlTree } from "@angular/router";
-import { containsTree } from "../router-url-tree";
+import { containsTree } from "./private-imports/router-url-tree";
import { NSRouterLink } from "./ns-router-link";
@@ -53,12 +53,16 @@ import { NSRouterLink } from "./ns-router-link";
*
* @stable
*/
-@Directive({ selector: "[nsRouterLinkActive]" })
+@Directive({
+ selector: "[nsRouterLinkActive]",
+ exportAs: "routerLinkActive",
+})
export class NSRouterLinkActive implements OnChanges, OnDestroy, AfterContentInit { // tslint:disable-line:max-line-length directive-class-suffix
@ContentChildren(NSRouterLink) links: QueryList;
private classes: string[] = [];
private subscription: Subscription;
+ private active: boolean = false;
@Input() nsRouterLinkActiveOptions: { exact: boolean } = { exact: false };
@@ -70,6 +74,10 @@ export class NSRouterLinkActive implements OnChanges, OnDestroy, AfterContentIni
});
}
+ get isActive(): boolean {
+ return this.active;
+ }
+
ngAfterContentInit(): void {
this.links.changes.subscribe(() => this.update());
this.update();
@@ -91,12 +99,16 @@ export class NSRouterLinkActive implements OnChanges, OnDestroy, AfterContentIni
if (!this.links) {
return;
}
-
- const currentUrlTree = this.router.parseUrl(this.router.url);
- const isActiveLinks = this.reduceList(currentUrlTree, this.links);
- this.classes.forEach(
- c => this.renderer.setElementClass(
- this.element.nativeElement, c, isActiveLinks));
+ const hasActiveLinks = this.hasActiveLinks();
+ // react only when status has changed to prevent unnecessary dom updates
+ if (this.active !== hasActiveLinks) {
+ const currentUrlTree = this.router.parseUrl(this.router.url);
+ const isActiveLinks = this.reduceList(currentUrlTree, this.links);
+ this.classes.forEach(
+ c => this.renderer.setElementClass(
+ this.element.nativeElement, c, isActiveLinks));
+ }
+ Promise.resolve(hasActiveLinks).then(active => this.active = active);
}
private reduceList(currentUrlTree: UrlTree, q: QueryList): boolean {
@@ -106,4 +118,13 @@ export class NSRouterLinkActive implements OnChanges, OnDestroy, AfterContentIni
this.nsRouterLinkActiveOptions.exact);
}, false);
}
+
+ private isLinkActive(router: Router): (link: NSRouterLink) => boolean {
+ return (link: NSRouterLink) =>
+ router.isActive(link.urlTree, this.nsRouterLinkActiveOptions.exact);
+ }
+
+ private hasActiveLinks(): boolean {
+ return this.links.some(this.isLinkActive(this.router));
+ }
}
diff --git a/nativescript-angular/router/page-router-outlet.ts b/nativescript-angular/router/page-router-outlet.ts
index 9f7541ec9..2695026c4 100644
--- a/nativescript-angular/router/page-router-outlet.ts
+++ b/nativescript-angular/router/page-router-outlet.ts
@@ -2,11 +2,12 @@ import {
Attribute, ChangeDetectorRef,
ComponentFactory, ComponentFactoryResolver, ComponentRef,
Directive, Inject, InjectionToken, Injector,
- OnDestroy, OnInit,
+ OnDestroy, OnInit, EventEmitter, Output,
Type, ViewContainerRef,
} from "@angular/core";
import {
ActivatedRoute,
+ ActivatedRouteSnapshot,
ChildrenOutletContexts,
PRIMARY_OUTLET,
} from "@angular/router";
@@ -18,12 +19,12 @@ import { profile } from "tns-core-modules/profiling";
import { BehaviorSubject } from "rxjs/BehaviorSubject";
-import { isPresent } from "../lang-facade";
import { DEVICE, PAGE_FACTORY, PageFactory } from "../platform-providers";
-import { routerLog } from "../trace";
+import { routerLog as log } from "../trace";
import { DetachedLoader } from "../common/detached-loader";
import { ViewUtil } from "../view-util";
import { NSLocationStrategy } from "./ns-location-strategy";
+import { NSRouteReuseStrategy } from "./ns-route-reuse-strategy";
export class PageRoute {
activatedRoute: BehaviorSubject;
@@ -33,79 +34,87 @@ export class PageRoute {
}
}
+// Used to "mark" ActivatedRoute snapshots that are handled in PageRouterOutlet
+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();
+ }
+}
+
class ChildInjector implements Injector {
constructor(
private providers: ProviderMap,
private parent: Injector
- ) {}
+ ) { }
- get(token: Type|InjectionToken, notFoundValue?: T): T {
- return this.providers.get(token) || this.parent.get(token, notFoundValue);
+ get(token: Type | InjectionToken, notFoundValue?: T): T {
+ let localValue = this.providers.get(token);
+ if (localValue) {
+ return localValue;
+ }
+
+ return this.parent.get(token, notFoundValue);
}
}
+type ProviderMap = Map | InjectionToken, any>;
+
/**
- * Reference Cache
+ * 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.
*/
-class RefCache {
- private cache = new Array();
+export function findTopActivatedRouteNodeForOutlet(activatedRoute: ActivatedRouteSnapshot): ActivatedRouteSnapshot {
+ let outletActivatedRoute = activatedRoute;
- public get length(): number {
- return this.cache.length;
- }
-
- public push(cacheItem: CacheItem) {
- this.cache.push(cacheItem);
- }
+ while (outletActivatedRoute.parent &&
+ outletActivatedRoute.parent.routeConfig &&
+ !outletActivatedRoute.parent.routeConfig.component) {
- public pop(): CacheItem {
- return this.cache.pop();
+ outletActivatedRoute = outletActivatedRoute.parent;
}
- public peek(): CacheItem {
- return this.cache[this.cache.length - 1];
- }
-
- public clear(): void {
- while (this.length) {
- RefCache.destroyItem(this.pop());
- }
- }
-
- public static destroyItem(item: CacheItem) {
- if (isPresent(item.componentRef)) {
- item.componentRef.destroy();
- }
-
- if (isPresent(item.loaderRef)) {
- item.loaderRef.destroy();
- }
- }
+ return outletActivatedRoute;
}
-interface CacheItem {
- componentRef: ComponentRef;
- reusedRoute: PageRoute;
- loaderRef?: ComponentRef;
+function routeToString(activatedRoute: ActivatedRoute | ActivatedRouteSnapshot): string {
+ return activatedRoute.pathFromRoot.join("->");
}
-
-type ProviderMap = Map|InjectionToken, any>;
-
-const log = (msg: string) => routerLog(msg);
-
@Directive({ selector: "page-router-outlet" }) // tslint:disable-line:directive-selector
export class PageRouterOutlet implements OnDestroy, OnInit { // tslint:disable-line:directive-class-suffix
- private activated: ComponentRef|null = null;
- private _activatedRoute: ActivatedRoute|null = null;
- private refCache: RefCache = new RefCache();
+ private activated: ComponentRef | null = null;
+ private _activatedRoute: ActivatedRoute | null = null;
+
private isInitialPage: boolean = true;
private detachedLoaderFactory: ComponentFactory;
- private itemsToDestroy: CacheItem[] = [];
private name: string;
private viewUtil: ViewUtil;
+ @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 */
@@ -140,7 +149,8 @@ export class PageRouterOutlet implements OnDestroy, OnInit { // tslint:disable-l
private frame: Frame,
private changeDetector: ChangeDetectorRef,
@Inject(DEVICE) device: Device,
- @Inject(PAGE_FACTORY) private pageFactory: PageFactory
+ @Inject(PAGE_FACTORY) private pageFactory: PageFactory,
+ private routeReuseStrategy: NSRouteReuseStrategy
) {
this.name = name || PRIMARY_OUTLET;
@@ -148,11 +158,6 @@ export class PageRouterOutlet implements OnDestroy, OnInit { // tslint:disable-l
this.viewUtil = new ViewUtil(device);
this.detachedLoaderFactory = resolver.resolveComponentFactory(DetachedLoader);
- log("DetachedLoaderFactory loaded");
- }
-
- ngOnDestroy(): void {
- this.parentContexts.onChildOutletDestroyed(this.name);
}
ngOnInit(): void {
@@ -176,41 +181,29 @@ export class PageRouterOutlet implements OnDestroy, OnInit { // tslint:disable-l
}
}
- deactivate(): void {
- if (this.locationStrategy._isPageNavigatingBack()) {
- log("PageRouterOutlet.deactivate() while going back - should destroy");
- if (!this.isActivated) {
- return;
- }
+ ngOnDestroy(): void {
+ this.parentContexts.onChildOutletDestroyed(this.name);
+ }
- const poppedItem = this.refCache.pop();
- const poppedRef = poppedItem.componentRef;
+ deactivate(): void {
+ if (!this.locationStrategy._isPageNavigatingBack()) {
+ throw new Error("Currently not in page back navigation" +
+ " - component should be detached instead of deactivated.");
+ }
- if (this.activated !== poppedRef) {
- throw new Error("Current componentRef is different for cached componentRef");
- }
+ log("PageRouterOutlet.deactivate() while going back - should destroy");
- RefCache.destroyItem(poppedItem);
- this.activated = null;
- } else {
- log("PageRouterOutlet.deactivate() while going forward - do nothing");
+ if (!this.isActivated) {
+ return;
}
- }
- private destroyQueuedCacheItems() {
- while (this.itemsToDestroy.length > 0) {
- this.destroyCacheItem(this.itemsToDestroy.pop());
- }
- }
+ const c = this.activated.instance;
+ destroyComponentRef(this.activated);
- private destroyCacheItem(poppedItem: CacheItem) {
- if (isPresent(poppedItem.componentRef)) {
- poppedItem.componentRef.destroy();
- }
+ this.activated = null;
+ this._activatedRoute = null;
- if (isPresent(poppedItem.loaderRef)) {
- poppedItem.loaderRef.destroy();
- }
+ this.deactivateEvents.emit(c);
}
/**
@@ -221,24 +214,26 @@ export class PageRouterOutlet implements OnDestroy, OnInit { // tslint:disable-l
throw new Error("Outlet is not activated");
}
- this.location.detach();
- const cmp = this.activated;
+ log("PageRouterOutlet.detach() - " + routeToString(this._activatedRoute));
+
+ const component = this.activated;
this.activated = null;
this._activatedRoute = null;
- return cmp;
+ return component;
}
/**
* Called when the `RouteReuseStrategy` instructs to re-attach a previously detached subtree
*/
attach(ref: ComponentRef, activatedRoute: ActivatedRoute) {
- log("PageRouterOutlet.attach()" +
- "when RouteReuseStrategy instructs to re-attach " +
- "previously detached subtree");
+ log("PageRouterOutlet.attach() - " + routeToString(activatedRoute));
this.activated = ref;
this._activatedRoute = activatedRoute;
- this.location.insert(ref.hostView);
+
+ this.markActivatedRoute(activatedRoute);
+
+ this.locationStrategy._finishBackPageNavigation();
}
/**
@@ -248,20 +243,22 @@ export class PageRouterOutlet implements OnDestroy, OnInit { // tslint:disable-l
@profile
activateWith(
activatedRoute: ActivatedRoute,
- resolver: ComponentFactoryResolver|null
- ): void {
+ resolver: ComponentFactoryResolver | null): void {
- log("PageRouterOutlet.activateWith() - " +
- "instanciating new component during commit phase of a navigation");
+ if (this.locationStrategy._isPageNavigatingBack()) {
+ throw new Error("Currently in page back navigation - component should be reattached instead of activated.");
+ }
+
+ log("PageRouterOutlet.activateWith() - " + routeToString(activatedRoute));
this._activatedRoute = activatedRoute;
+
+ this.markActivatedRoute(activatedRoute);
+
resolver = resolver || this.resolver;
- if (this.locationStrategy._isPageNavigatingBack()) {
- this.activateOnGoBack(activatedRoute);
- } else {
- this.activateOnGoForward(activatedRoute, resolver);
- }
+ this.activateOnGoForward(activatedRoute, resolver);
+ this.activateEvents.emit(this.activated.instance);
}
private activateOnGoForward(
@@ -271,6 +268,7 @@ export class PageRouterOutlet implements OnDestroy, OnInit { // tslint:disable-l
const pageRoute = new PageRoute(activatedRoute);
const providers = this.initProvidersMap(activatedRoute, pageRoute);
+
const childInjector = new ChildInjector(providers, this.location.injector);
const factory = this.getComponentFactory(activatedRoute, loadedResolver);
@@ -282,12 +280,6 @@ export class PageRouterOutlet implements OnDestroy, OnInit { // tslint:disable-l
this.activated = this.location.createComponent(
factory, this.location.length, childInjector, []);
this.changeDetector.markForCheck();
-
- this.refCache.push({
- componentRef: this.activated,
- reusedRoute: pageRoute,
- loaderRef: null,
- });
} else {
log("PageRouterOutlet.activate() forward navigation - " +
"create detached loader in the loader container");
@@ -306,11 +298,7 @@ export class PageRouterOutlet implements OnDestroy, OnInit { // tslint:disable-l
this.activated = loaderRef.instance.loadWithFactory(factory);
this.loadComponentInPage(page, this.activated);
- this.refCache.push({
- componentRef: this.activated,
- reusedRoute: pageRoute,
- loaderRef,
- });
+ this.activated[loaderRefSymbol] = loaderRef;
}
}
@@ -329,17 +317,6 @@ export class PageRouterOutlet implements OnDestroy, OnInit { // tslint:disable-l
return providers;
}
- private activateOnGoBack(activatedRoute: ActivatedRoute): void {
- log("PageRouterOutlet.activate() - Back navigation, so load from cache");
-
- this.locationStrategy._finishBackPageNavigation();
-
- const cacheItem = this.refCache.peek();
- cacheItem.reusedRoute.activatedRoute.next(activatedRoute);
-
- this.activated = cacheItem.componentRef;
- }
-
@profile
private loadComponentInPage(page: Page, componentRef: ComponentRef): void {
// Component loaded. Find its root native view.
@@ -349,9 +326,6 @@ export class PageRouterOutlet implements OnDestroy, OnInit { // tslint:disable-l
// Add it to the new page
page.content = componentView;
- page.on(Page.navigatedToEvent, () => setTimeout(() => {
- this.destroyQueuedCacheItems();
- }));
page.on(Page.navigatedFromEvent, (global).Zone.current.wrap((args: NavigatedData) => {
if (args.isBackNavigation) {
this.locationStrategy._beginBackPageNavigation();
@@ -360,17 +334,29 @@ export class PageRouterOutlet implements OnDestroy, OnInit { // tslint:disable-l
}));
const navOptions = this.locationStrategy._beginPageNavigation();
+
+ // Clear refCache if navigation with clearHistory
+ if (navOptions.clearHistory) {
+ const clearCallback = () => setTimeout(() => {
+ this.routeReuseStrategy.clearCache();
+ page.off(Page.navigatedToEvent, clearCallback);
+ });
+
+ page.on(Page.navigatedToEvent, clearCallback);
+ }
+
this.frame.navigate({
create: () => { return page; },
clearHistory: navOptions.clearHistory,
animated: navOptions.animated,
transition: navOptions.transition
});
+ }
- // Clear refCache if navigation with clearHistory
- if (navOptions.clearHistory) {
- this.refCache.clear();
- }
+ private markActivatedRoute(activatedRoute: ActivatedRoute) {
+ const nodeToMark = findTopActivatedRouteNodeForOutlet(activatedRoute.snapshot);
+ nodeToMark[pageRouterActivatedSymbol] = true;
+ log("Activated route marked as page: " + routeToString(nodeToMark));
}
// NOTE: Using private APIs - potential break point!
diff --git a/nativescript-angular/router-url-tree.ts b/nativescript-angular/router/private-imports/router-url-tree.ts
similarity index 100%
rename from nativescript-angular/router-url-tree.ts
rename to nativescript-angular/router/private-imports/router-url-tree.ts
diff --git a/nativescript-angular/router.ts b/nativescript-angular/router/router.module.ts
similarity index 64%
rename from nativescript-angular/router.ts
rename to nativescript-angular/router/router.module.ts
index d5494bcfb..066c437d0 100644
--- a/nativescript-angular/router.ts
+++ b/nativescript-angular/router/router.module.ts
@@ -5,20 +5,21 @@ import {
Optional,
SkipSelf,
} from "@angular/core";
-import { RouterModule, Routes, ExtraOptions } from "@angular/router";
+import { RouterModule, Routes, ExtraOptions, RouteReuseStrategy } from "@angular/router";
import { LocationStrategy, PlatformLocation } from "@angular/common";
import { Frame } from "tns-core-modules/ui/frame";
-import { NSRouterLink } from "./router/ns-router-link";
-import { NSRouterLinkActive } from "./router/ns-router-link-active";
-import { PageRouterOutlet } from "./router/page-router-outlet";
-import { NSLocationStrategy, LocationState } from "./router/ns-location-strategy";
-import { NativescriptPlatformLocation } from "./router/ns-platform-location";
-import { RouterExtensions } from "./router/router-extensions";
-import { NativeScriptCommonModule } from "./common";
+import { NSRouterLink } from "./ns-router-link";
+import { NSRouterLinkActive } from "./ns-router-link-active";
+import { PageRouterOutlet } from "./page-router-outlet";
+import { NSLocationStrategy, LocationState } 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";
-export { PageRoute } from "./router/page-router-outlet";
-export { RouterExtensions } from "./router/router-extensions";
-export { NSModuleFactoryLoader } from "./router/ns-module-factory-loader";
+export { PageRoute } from "./page-router-outlet";
+export { RouterExtensions } from "./router-extensions";
+export { NSModuleFactoryLoader } from "./ns-module-factory-loader";
export type LocationState = LocationState;
@NgModule({
@@ -37,6 +38,8 @@ export type LocationState = LocationState;
NativescriptPlatformLocation,
{ provide: PlatformLocation, useClass: NativescriptPlatformLocation },
RouterExtensions,
+ NSRouteReuseStrategy,
+ { provide: RouteReuseStrategy, useExisting: NSRouteReuseStrategy }
],
imports: [
RouterModule,
diff --git a/nativescript-angular/trace.ts b/nativescript-angular/trace.ts
index 800b96b22..8f81c0005 100644
--- a/nativescript-angular/trace.ts
+++ b/nativescript-angular/trace.ts
@@ -2,7 +2,9 @@ import { write, categories, messageType } from "tns-core-modules/trace";
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 function animationsLog(message: string): void {
@@ -17,10 +19,18 @@ export function rendererError(message: string): void {
write(message, rendererTraceCategory, messageType.error);
}
+export function viewUtilLog(msg): void {
+ write(msg, viewUtilCategory);
+}
+
export function routerLog(message: string): void {
write(message, routerTraceCategory);
}
+export function routeReuseStrategyLog(message: string): void {
+ write(message, routeReuseStrategyTraceCategory);
+}
+
export function styleError(message: string): void {
write(message, categories.Style, messageType.error);
}
diff --git a/nativescript-angular/view-util.ts b/nativescript-angular/view-util.ts
index 99b196197..f1075bac0 100644
--- a/nativescript-angular/view-util.ts
+++ b/nativescript-angular/view-util.ts
@@ -6,21 +6,22 @@ import { LayoutBase } from "tns-core-modules/ui/layouts/layout-base";
import {
CommentNode,
InvisibleNode,
- NgElement,
NgView,
TextNode,
ViewExtensions,
getViewClass,
getViewMeta,
isDetachedElement,
+ isInvisibleNode,
isKnownView,
+ isView,
} from "./element-registry";
import { platformNames, Device } from "tns-core-modules/platform";
-import { rendererLog as traceLog } from "./trace";
+import { viewUtilLog as traceLog } from "./trace";
-const XML_ATTRIBUTES = Object.freeze(["style", "rows", "columns", "fontAttributes"]);
const ELEMENT_NODE_TYPE = 1;
+const XML_ATTRIBUTES = Object.freeze(["style", "rows", "columns", "fontAttributes"]);
const whiteSpaceSplitter = /\s+/;
export type ViewExtensions = ViewExtensions;
@@ -30,10 +31,6 @@ export type NgContentView = ContentView & ViewExtensions;
export type NgPlaceholder = Placeholder & ViewExtensions;
export type BeforeAttachAction = (view: View) => void;
-export function isView(view: any): view is NgView {
- return view instanceof View;
-}
-
export function isLayout(view: any): view is NgLayoutBase {
return view instanceof LayoutBase;
}
@@ -53,54 +50,174 @@ export class ViewUtil {
this.isAndroid = device.os === platformNames.android;
}
- public insertChild(parent: any, child: NgElement, atIndex: number = -1) {
- if (child instanceof InvisibleNode) {
- child.templateParent = parent;
+ public insertChild(
+ parent: View,
+ child: View,
+ previous?: NgView,
+ next?: NgView
+ ) {
+ if (!parent) {
+ return;
}
- if (!parent || isDetachedElement(child)) {
- return;
+ const extendedParent = this.ensureNgViewExtensions(parent);
+ const extendedChild = this.ensureNgViewExtensions(child);
+
+ if (!previous) {
+ previous = extendedParent.lastChild;
+ }
+ this.addToQueue(extendedParent, extendedChild, previous, next);
+
+ if (isInvisibleNode(child)) {
+ extendedChild.templateParent = 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 {
+ traceLog(`ViewUtil.addToQueue parent: ${parent}, view: ${child}, ` +
+ `previous: ${previous}, next: ${next}`);
+
+ if (previous) {
+ previous.nextSibling = child;
+ } else {
+ parent.firstChild = child;
}
+ if (next) {
+ child.nextSibling = next;
+ } else {
+ this.appendToQueue(parent, child);
+ }
+ }
+
+ private appendToQueue(parent: NgView, view: NgView) {
+ traceLog(`ViewUtil.appendToQueue parent: ${parent} view: ${view}`);
+
+ if (parent.lastChild) {
+ parent.lastChild.nextSibling = view;
+ }
+
+ parent.lastChild = view;
+ }
+
+ private addToVisualTree(parent: NgView, child: NgView, next: NgView): void {
+ traceLog(`ViewUtil.addToVisualTree parent: ${parent}, view: ${child}, next: ${next}`);
+
if (parent.meta && parent.meta.insertChild) {
- parent.meta.insertChild(parent, child, atIndex);
+ parent.meta.insertChild(parent, child, next);
} else if (isLayout(parent)) {
- if (child.parent === parent) {
- const index = (parent).getChildIndex(child);
- if (index !== -1) {
- parent.removeChild(child);
- }
- }
- if (atIndex !== -1) {
- parent.insertChild(child, atIndex);
- } else {
- parent.addChild(child);
- }
+ this.insertToLayout(parent, child, next);
} else if (isContentView(parent)) {
parent.content = child;
- } else if (parent && parent._addChildFromBuilder) {
- parent._addChildFromBuilder(child.nodeName, child);
+ } else if (parent && (parent)._addChildFromBuilder) {
+ (parent)._addChildFromBuilder(child.nodeName, child);
}
}
- public removeChild(parent: any, child: NgElement) {
- if (!parent || isDetachedElement(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) {
+ traceLog(`ViewUtil.removeChild parent: ${parent} child: ${child}`);
+
+ if (!parent) {
return;
}
- if (parent.meta && parent.meta.removeChild) {
- parent.meta.removeChild(parent, child);
- } else if (isLayout(parent)) {
- parent.removeChild(child);
- } else if (isContentView(parent)) {
- if (parent.content === child) {
- parent.content = null;
- }
- } else if (isView(parent)) {
- parent._removeView(child);
+ const extendedParent = this.ensureNgViewExtensions(parent);
+ const extendedChild = this.ensureNgViewExtensions(child);
+
+ this.removeFromQueue(extendedParent, extendedChild);
+ this.removeFromVisualTree(extendedParent, extendedChild);
+ }
+
+ private removeFromQueue(parent: NgView, child: NgView) {
+ traceLog(`ViewUtil.removeFromQueue parent: ${parent} child: ${child}`);
+
+ if (parent.firstChild === child && parent.lastChild === child) {
+ parent.firstChild = null;
+ parent.lastChild = null;
+ return;
+ }
+
+ if (parent.firstChild === child) {
+ parent.firstChild = child.nextSibling;
+ }
+
+ const previous = this.findPreviousElement(parent, child);
+ if (parent.lastChild === child) {
+ parent.lastChild = previous;
+ }
+
+ if (previous) {
+ previous.nextSibling = child.nextSibling;
}
}
+ // NOTE: This one is O(n) - use carefully
+ private findPreviousElement(parent: NgView, child: NgView): NgView {
+ traceLog(`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);
@@ -109,6 +226,30 @@ export class ViewUtil {
}
}
+ private removeFromVisualTree(parent: NgView, child: NgView) {
+ traceLog(`ViewUtil.findPreviousElement 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;
+ parent.lastChild = null;
+ parent.firstChild = null;
+ } else if (isView(parent)) {
+ parent._removeView(child);
+ }
+ }
+
+ private removeLayoutChild(parent: NgLayoutBase, child: NgView): void {
+ const index = parent.getChildIndex(child);
+
+ if (index !== -1) {
+ parent.removeChild(child);
+ }
+ }
+
public createComment(): InvisibleNode {
return new CommentNode();
}
@@ -125,18 +266,34 @@ export class ViewUtil {
}
const viewClass = getViewClass(name);
- let view = new viewClass();
- view.nodeName = name;
- view.meta = getViewMeta(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.typeName;
+ const ngView = this.setNgViewExtensions(view, name);
+
+ return ngView;
+ }
+ }
+
+ private setNgViewExtensions(view: View, name: string): NgView {
+ 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:
- // tslint:disable-next-line:max-line-length
- // https://github.com/angular/angular/blob/master/packages/animations/browser/src/render/dom_animation_engine.ts#L70-L81
- view.nodeType = ELEMENT_NODE_TYPE;
+ // dom animation engine
+ ngView.nodeType = ELEMENT_NODE_TYPE;
- return view;
+ return ngView;
}
public setProperty(view: NgView, attributeName: string, value: any, namespace?: string): void {
@@ -168,34 +325,11 @@ export class ViewUtil {
}
}
- // finds the node in the parent's views and returns the next index
- // returns -1 if the node has no parent or next sibling
- public nextSiblingIndex(node: NgView): number {
- const parent = node.parent;
- if (!parent) {
- return -1;
- }
-
- let index = 0;
- let found = false;
- parent.eachChild(child => {
- if (child === node) {
- found = true;
- }
-
- index += 1;
- return !found;
- });
-
- return found ? index : -1;
- }
-
private runsIn(platform: string): boolean {
return (platform === "ios" && this.isIos) ||
(platform === "android" && this.isAndroid);
}
-
private setPropertyInternal(view: NgView, attributeName: string, value: any): void {
traceLog(`Setting attribute: ${attributeName}=${value} to ${view}`);
@@ -275,4 +409,3 @@ export class ViewUtil {
view.style[styleName] = unsetValue;
}
}
-
diff --git a/nativescript-angular/yarn.lock b/nativescript-angular/yarn.lock
deleted file mode 100644
index 2ade79c72..000000000
--- a/nativescript-angular/yarn.lock
+++ /dev/null
@@ -1,381 +0,0 @@
-# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
-# yarn lockfile v1
-
-
-"@angular/animations@~4.0.0 || ~4.1.0":
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-4.1.0.tgz#97b642aee01b5406e03ec65e499342ba91e2dd38"
-
-"@angular/common@~4.0.0 || ~4.1.0":
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/@angular/common/-/common-4.1.0.tgz#4370f569e51ddd99963b7f4aa58c1a5dcc5fea52"
-
-"@angular/compiler-cli@~4.0.0 || ~4.1.0":
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-4.1.0.tgz#727aaada8bfd94285e9818995925048f7fdf1200"
- dependencies:
- "@angular/tsc-wrapped" "4.1.0"
- minimist "^1.2.0"
- reflect-metadata "^0.1.2"
-
-"@angular/compiler@~4.0.0 || ~4.1.0":
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-4.1.0.tgz#be1ade5b6aec81f03c29d52bcb95925a28900dcb"
-
-"@angular/core@~4.0.0 || ~4.1.0":
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/@angular/core/-/core-4.1.0.tgz#72ec173316879571880c9c483ed6dfc0caab94b0"
-
-"@angular/forms@~4.0.0 || ~4.1.0":
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-4.1.0.tgz#8eae2a45c4ba064b377f9280e59c012b5dac6b80"
-
-"@angular/http@~4.0.0 || ~4.1.0":
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/@angular/http/-/http-4.1.0.tgz#7ba0c4d044dee964021b7cf19cb146a2c31577a5"
-
-"@angular/platform-browser@~4.0.0 || ~4.1.0":
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-4.1.0.tgz#b981386be1a36f2af7f0679447fd97b7267b25de"
-
-"@angular/router@~4.0.0 || ~4.1.0":
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/@angular/router/-/router-4.1.0.tgz#dd3563662f95ca3aa3dd9ff13c6ed4bea1d90b06"
-
-"@angular/tsc-wrapped@4.1.0":
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/@angular/tsc-wrapped/-/tsc-wrapped-4.1.0.tgz#07cbd61d91adde4c2daf9a41605152952b8832b3"
- dependencies:
- tsickle "^0.21.0"
-
-ansi-regex@^2.0.0:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
-
-ansi-styles@^2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
-
-app-root-path@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.0.1.tgz#cd62dcf8e4fd5a417efc664d2e5b10653c651b46"
-
-babel-code-frame@^6.22.0:
- version "6.22.0"
- resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4"
- dependencies:
- chalk "^1.1.0"
- esutils "^2.0.2"
- js-tokens "^3.0.0"
-
-balanced-match@^0.4.1:
- version "0.4.2"
- resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838"
-
-brace-expansion@^1.0.0:
- version "1.1.7"
- resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.7.tgz#3effc3c50e000531fb720eaff80f0ae8ef23cf59"
- dependencies:
- balanced-match "^0.4.1"
- concat-map "0.0.1"
-
-chalk@^1.1.0:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
- dependencies:
- ansi-styles "^2.2.1"
- escape-string-regexp "^1.0.2"
- has-ansi "^2.0.0"
- strip-ansi "^3.0.0"
- supports-color "^2.0.0"
-
-codelyzer@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/codelyzer/-/codelyzer-3.0.1.tgz#ba66b7b2aa564fe9f45d6004b4003ad2cf116828"
- dependencies:
- app-root-path "^2.0.1"
- css-selector-tokenizer "^0.7.0"
- cssauron "^1.4.0"
- semver-dsl "^1.0.1"
- source-map "^0.5.6"
- sprintf-js "^1.0.3"
-
-colors@^1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
-
-concat-map@0.0.1:
- version "0.0.1"
- resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
-
-css-selector-tokenizer@^0.7.0:
- version "0.7.0"
- resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz#e6988474ae8c953477bf5e7efecfceccd9cf4c86"
- dependencies:
- cssesc "^0.1.0"
- fastparse "^1.1.1"
- regexpu-core "^1.0.0"
-
-cssauron@^1.4.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/cssauron/-/cssauron-1.4.0.tgz#a6602dff7e04a8306dc0db9a551e92e8b5662ad8"
- dependencies:
- through X.X.X
-
-cssesc@^0.1.0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4"
-
-diff@^3.2.0:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9"
-
-escape-string-regexp@^1.0.2:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
-
-esutils@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
-
-fastparse@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8"
-
-findup-sync@~0.3.0:
- version "0.3.0"
- resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.3.0.tgz#37930aa5d816b777c03445e1966cc6790a4c0b16"
- dependencies:
- glob "~5.0.0"
-
-fs.realpath@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
-
-glob@^7.1.1:
- version "7.1.1"
- resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
- dependencies:
- fs.realpath "^1.0.0"
- inflight "^1.0.4"
- inherits "2"
- minimatch "^3.0.2"
- once "^1.3.0"
- path-is-absolute "^1.0.0"
-
-glob@~5.0.0:
- version "5.0.15"
- resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1"
- dependencies:
- inflight "^1.0.4"
- inherits "2"
- minimatch "2 || 3"
- once "^1.3.0"
- path-is-absolute "^1.0.0"
-
-has-ansi@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
- dependencies:
- ansi-regex "^2.0.0"
-
-inflight@^1.0.4:
- version "1.0.6"
- resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
- dependencies:
- once "^1.3.0"
- wrappy "1"
-
-inherits@2:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
-
-js-tokens@^3.0.0:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7"
-
-jsesc@~0.5.0:
- version "0.5.0"
- resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
-
-"minimatch@2 || 3", minimatch@^3.0.2:
- version "3.0.3"
- resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774"
- dependencies:
- brace-expansion "^1.0.0"
-
-minimist@0.0.8:
- version "0.0.8"
- resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
-
-minimist@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
-
-minimist@~0.0.1:
- version "0.0.10"
- resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
-
-mkdirp@^0.5.1:
- version "0.5.1"
- resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
- dependencies:
- minimist "0.0.8"
-
-nativescript-intl@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/nativescript-intl/-/nativescript-intl-3.0.0.tgz#82ee9be7d377172b3c4295734723037628e186a7"
-
-once@^1.3.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
- dependencies:
- wrappy "1"
-
-optimist@~0.6.0:
- version "0.6.1"
- resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
- dependencies:
- minimist "~0.0.1"
- wordwrap "~0.0.2"
-
-path-is-absolute@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
-
-path-parse@^1.0.5:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
-
-reflect-metadata@^0.1.2, reflect-metadata@^0.1.8:
- version "0.1.10"
- resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.10.tgz#b4f83704416acad89988c9b15635d47e03b9344a"
-
-regenerate@^1.2.1:
- version "1.3.2"
- resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260"
-
-regexpu-core@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b"
- dependencies:
- regenerate "^1.2.1"
- regjsgen "^0.2.0"
- regjsparser "^0.1.4"
-
-regjsgen@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7"
-
-regjsparser@^0.1.4:
- version "0.1.5"
- resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c"
- dependencies:
- jsesc "~0.5.0"
-
-resolve@^1.3.2:
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5"
- dependencies:
- path-parse "^1.0.5"
-
-rxjs@^5.0.1:
- version "5.3.0"
- resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.3.0.tgz#d88ccbdd46af290cbdb97d5d8055e52453fabe2d"
- dependencies:
- symbol-observable "^1.0.1"
-
-semver-dsl@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/semver-dsl/-/semver-dsl-1.0.1.tgz#d3678de5555e8a61f629eed025366ae5f27340a0"
- dependencies:
- semver "^5.3.0"
-
-semver@^5.3.0:
- version "5.3.0"
- resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
-
-source-map-support@^0.4.2:
- version "0.4.15"
- resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.15.tgz#03202df65c06d2bd8c7ec2362a193056fef8d3b1"
- dependencies:
- source-map "^0.5.6"
-
-source-map@^0.5.6:
- version "0.5.6"
- resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
-
-sprintf-js@^1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
-
-strip-ansi@^3.0.0:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
- dependencies:
- ansi-regex "^2.0.0"
-
-supports-color@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
-
-symbol-observable@^1.0.1:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d"
-
-through@X.X.X:
- version "2.3.8"
- resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
-
-tns-core-modules-widgets@internal-preview:
- version "3.1.0-2017-04-28-266"
- resolved "https://registry.yarnpkg.com/tns-core-modules-widgets/-/tns-core-modules-widgets-3.1.0-2017-04-28-266.tgz#56d82a9952604e064b76bbd232d56cad9a13337a"
-
-tns-core-modules@internal-preview:
- version "3.1.0-2017-04-26-6412"
- resolved "https://registry.yarnpkg.com/tns-core-modules/-/tns-core-modules-3.1.0-2017-04-26-6412.tgz#ae7717292d511e3e6fcac14330d0b0adc18e905d"
- dependencies:
- tns-core-modules-widgets internal-preview
-
-tsickle@^0.21.0:
- version "0.21.6"
- resolved "https://registry.yarnpkg.com/tsickle/-/tsickle-0.21.6.tgz#53b01b979c5c13fdb13afb3fb958177e5991588d"
- dependencies:
- minimist "^1.2.0"
- mkdirp "^0.5.1"
- source-map "^0.5.6"
- source-map-support "^0.4.2"
-
-tslint@^5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.1.0.tgz#51a47baeeb58956fcd617bd2cf00e2ef0eea2ed9"
- dependencies:
- babel-code-frame "^6.22.0"
- colors "^1.1.2"
- diff "^3.2.0"
- findup-sync "~0.3.0"
- glob "^7.1.1"
- optimist "~0.6.0"
- resolve "^1.3.2"
- semver "^5.3.0"
- tsutils "^1.4.0"
-
-tsutils@^1.4.0:
- version "1.8.0"
- resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-1.8.0.tgz#bf8118ed8e80cd5c9fc7d75728c7963d44ed2f52"
-
-typescript@^2.3.2:
- version "2.3.2"
- resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.3.2.tgz#f0f045e196f69a72f06b25fd3bd39d01c3ce9984"
-
-wordwrap@~0.0.2:
- version "0.0.3"
- resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
-
-wrappy@1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
-
-zone.js@^0.8.4:
- version "0.8.9"
- resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.8.9.tgz#34aaa9a3ec6d0e4acebd1b761adafa590473638b"
diff --git a/ng-sample/app/app.ts b/ng-sample/app/app.ts
index 2b0c85ed1..5e860f71c 100644
--- a/ng-sample/app/app.ts
+++ b/ng-sample/app/app.ts
@@ -3,25 +3,30 @@ import { platformNativeScriptDynamic } from "nativescript-angular/platform";
import { NativeScriptAnimationsModule } from "nativescript-angular/animations";
import { onAfterLivesync, onBeforeLivesync } from "nativescript-angular/platform-common";
import { NgModule } from "@angular/core";
+import { DOCUMENT } from '@angular/common';
import { Router } from "@angular/router";
import { NativeScriptRouterModule } from "nativescript-angular/router";
import { NativeScriptFormsModule } from "nativescript-angular/forms";
import { NativeScriptHttpModule } from "nativescript-angular/http";
+import { NativeScriptHttpClientModule } from "nativescript-angular/http-client";
+
import {
rendererTraceCategory,
routerTraceCategory,
listViewTraceCategory,
animationsTraceCategory,
+ routeReuseStrategyTraceCategory,
} from "nativescript-angular/trace";
import { PAGE_FACTORY, PageFactory, PageFactoryOptions } from "nativescript-angular/platform-providers";
import { Page } from "ui/page";
import { Color } from "color";
import { setCategories, enable } from "trace";
-setCategories(
- `${animationsTraceCategory},${rendererTraceCategory}`
-);
+// setCategories(
+// `${animationsTraceCategory},${rendererTraceCategory}`
+// );
// setCategories(routerTraceCategory);
// setCategories(listViewTraceCategory);
+setCategories(`${routeReuseStrategyTraceCategory}`);
enable();
import { RendererTest } from "./examples/renderer-test";
@@ -32,6 +37,7 @@ import { ListTemplateSelectorTest } from "./examples/list/template-selector";
import { ListTestAsync, ListTestFilterAsync } from "./examples/list/list-test-async";
import { ImageTest } from "./examples/image/image-test";
import { HttpTest } from "./examples/http/http-test";
+import { HttpClientTest } from "./examples/http-client/http-client-test";
import { ActionBarTest } from "./examples/action-bar/action-bar-test";
import { ModalTest } from "./examples/modal/modal-test";
import { PlatfromDirectivesTest } from "./examples/platform-directives/platform-directives-test";
@@ -51,6 +57,7 @@ import { AnimationNgClassTest } from "./examples/animation/animation-ngclass-tes
import { AnimationStatesTest } from "./examples/animation/animation-states-test";
import { AnimationStatesMultiTest } from "./examples/animation/animation-states-multi-test";
+
@NgModule({
declarations: [
],
@@ -58,12 +65,14 @@ import { AnimationStatesMultiTest } from "./examples/animation/animation-states-
NativeScriptModule,
NativeScriptFormsModule,
NativeScriptHttpModule,
+ NativeScriptHttpClientModule,
NativeScriptRouterModule,
],
exports: [
NativeScriptModule,
NativeScriptFormsModule,
NativeScriptHttpModule,
+ NativeScriptHttpClientModule,
NativeScriptRouterModule,
],
providers: []
@@ -128,6 +137,7 @@ const customPageFactoryProvider = {
// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(ImageTest));
// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(ModalTest));
// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(HttpTest));
+platformNativeScriptDynamic().bootstrapModule(makeExampleModule(HttpClientTest));
// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(PlatfromDirectivesTest));
// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(ActionBarTest));
@@ -139,7 +149,7 @@ const customPageFactoryProvider = {
// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(LoginAppComponent));
// animations
-platformNativeScriptDynamic().bootstrapModule(makeExampleModule(AnimationStatesTest));
+// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(AnimationStatesTest));
// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(AnimationStatesMultiTest));
// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(AnimationNgClassTest));
// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(AnimationKeyframesTest));
diff --git a/ng-sample/app/examples/http-client/data.json b/ng-sample/app/examples/http-client/data.json
new file mode 100644
index 000000000..52d4169d4
--- /dev/null
+++ b/ng-sample/app/examples/http-client/data.json
@@ -0,0 +1,8 @@
+{
+ "results": [
+ {
+ "title": "Test",
+ "description": "Testing Http local and remote."
+ }
+ ]
+}
diff --git a/ng-sample/app/examples/http-client/http-client-test.ts b/ng-sample/app/examples/http-client/http-client-test.ts
new file mode 100644
index 000000000..7af6fb9d6
--- /dev/null
+++ b/ng-sample/app/examples/http-client/http-client-test.ts
@@ -0,0 +1,122 @@
+import { Component, Inject, Injectable } from "@angular/core";
+import {
+ HttpClient, HTTP_INTERCEPTORS, HttpEventType, HttpErrorResponse,
+ HttpEvent, HttpInterceptor, HttpHandler, HttpRequest
+} from "@angular/common/http";
+import { Observable } from "rxjs/Observable";
+import "rxjs/add/operator/do";
+
+@Injectable()
+export class CustomInterceptor implements HttpInterceptor {
+ intercept(req: HttpRequest, next: HttpHandler): Observable> {
+ console.log(`[CustomInterceptor] intercept url: ${req.url}`);
+
+ return next.handle(req)
+ .do(event => {
+ console.log(`[CustomInterceptor] handled type: ${HttpEventType[event.type]} url: ${req.url}`);
+ });
+ }
+}
+
+interface DataResults {
+ results: Array;
+}
+
+interface LocalData {
+ title: string;
+ description: string;
+}
+
+interface RemoteData {
+ name: { first: string };
+ email: string;
+}
+
+@Component({
+ selector: "http-client-test",
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ styles: [`
+ #title { margin-top:20; }
+ Label { margin: 5 20; }
+ `],
+})
+export class HttpClientTest {
+ static providers = [
+ {
+ provide: HTTP_INTERCEPTORS,
+ useClass: CustomInterceptor,
+ multi: true,
+ }
+ ];
+
+ public title: string;
+ public description: string;
+ public error: string;
+
+ constructor(private http: HttpClient) {
+ }
+
+ public loadLocal() {
+ this.http.get>("~/examples/http/data.json")
+ .subscribe((response) => {
+ let user = response.results[0];
+ this.onSuccess(user.title, user.description);
+ }, (error) => {
+ this.onError(error);
+ });
+ }
+
+ public loadRemote() {
+ this.http.get>(`https://randomuser.me/api/?results=1&nat=us`)
+ .subscribe((response) => {
+ const user = response.results[0];
+ this.onSuccess(user.email, user.name.first);
+ }, (error) => {
+ this.onError(error);
+ });
+ }
+
+ public loadNonexistentLocal() {
+ this.http.get>("~/non/existent/app/folder/data.json")
+ .subscribe((response) => {
+ this.onSuccess("strange?!", "");
+ }, (error) => {
+ this.onError(error);
+ });
+ }
+
+ public loadNonexistentRemote() {
+ this.http.get>("https://google.com/non/existent/url/data.json")
+ .subscribe((response) => {
+ this.onSuccess("strange?!", "");
+ }, (error) => {
+ this.onError(error);
+ });
+ }
+
+ private onSuccess(title: string, description: string) {
+ this.title = title;
+ this.description = description;
+ this.error = "";
+ }
+
+ private onError(error: HttpErrorResponse) {
+ console.log("onError " + error);
+ console.dir(error);
+ this.title = "";
+ this.description = "";
+ this.error = error.message;
+ }
+}
diff --git a/ng-sample/app/examples/list/list-test-async.ts b/ng-sample/app/examples/list/list-test-async.ts
index 146ee9cba..ed635997c 100644
--- a/ng-sample/app/examples/list/list-test-async.ts
+++ b/ng-sample/app/examples/list/list-test-async.ts
@@ -15,11 +15,11 @@ import "rxjs/add/operator/combineLatest";
-
+
-
+
@@ -68,11 +68,11 @@ export class ListTestAsync {
-
+
-
+
diff --git a/ng-sample/app/examples/list/list-test.ts b/ng-sample/app/examples/list/list-test.ts
index ccec8a964..9390b6dfd 100644
--- a/ng-sample/app/examples/list/list-test.ts
+++ b/ng-sample/app/examples/list/list-test.ts
@@ -42,26 +42,26 @@ export class ItemComponent implements AfterViewChecked, DoCheck {
-
+
-
+
`
// TEMPLATE WITH COMPONENT
- //
+ //
//
- //
+ //
// IN-PLACE TEMPLATE
- //
+ //
//
//
//
//
- //
+ //
})
export class ListTest {
public myItems: Array;
diff --git a/ng-sample/app/examples/list/template-selector.ts b/ng-sample/app/examples/list/template-selector.ts
index 4557b7e9c..54c11b28e 100644
--- a/ng-sample/app/examples/list/template-selector.ts
+++ b/ng-sample/app/examples/list/template-selector.ts
@@ -43,13 +43,13 @@ export class HeaderComponent implements DoCheck {
row="1" margin="10"
[itemTemplateSelector]="templateSelector">
-
+
-
+
-
+
-
+
diff --git a/ng-sample/app/examples/renderer-test.ts b/ng-sample/app/examples/renderer-test.ts
index 7ceb33849..232aaed4a 100644
--- a/ng-sample/app/examples/renderer-test.ts
+++ b/ng-sample/app/examples/renderer-test.ts
@@ -1,7 +1,5 @@
import { Component, Directive, Host, ElementRef, Input } from "@angular/core";
import { Observable } from "data/observable";
-import { TextValueAccessor } from "nativescript-angular/value-accessors/text-value-accessor";
-import { CheckedValueAccessor } from "nativescript-angular/value-accessors/checked-value-accessor";
import { SegmentedBarItem } from "ui/segmented-bar";
diff --git a/ng-sample/app/examples/router/clear-history-test.ts b/ng-sample/app/examples/router/clear-history-test.ts
index c83b40936..dba5a8e86 100644
--- a/ng-sample/app/examples/router/clear-history-test.ts
+++ b/ng-sample/app/examples/router/clear-history-test.ts
@@ -40,10 +40,10 @@ class LocationLogService {
(service.routerEvents$ | async)"
margin="10">
-
+
-
+
diff --git a/ng-sample/app/examples/router/page-router-outlet-nested-test.ts b/ng-sample/app/examples/router/page-router-outlet-nested-test.ts
index 104278c94..5f5e07da5 100644
--- a/ng-sample/app/examples/router/page-router-outlet-nested-test.ts
+++ b/ng-sample/app/examples/router/page-router-outlet-nested-test.ts
@@ -1,5 +1,5 @@
import { Component, OnInit, OnDestroy } from "@angular/core";
-import { ActivatedRoute, Router } from "@angular/router";
+import { ActivatedRoute, Router, Route } from "@angular/router";
import { Location } from "@angular/common";
import { Page } from "ui/page";
import { Observable } from "rxjs/Observable";
@@ -12,8 +12,8 @@ import "rxjs/add/operator/map";
-
-
+
+
`
})
@@ -70,6 +70,14 @@ class DetailComponent {
console.log("DetailComponent.constructor()");
this.id$ = route.params.map(r => r["id"]);
}
+
+ ngOnInit() {
+ console.log("DetailComponent - ngOnInit()");
+ }
+
+ ngOnDestroy() {
+ console.log("DetailComponent - ngOnDestroy()");
+ }
}
@Component({
@@ -79,7 +87,7 @@ class DetailComponent {
-
+
@@ -121,8 +129,11 @@ class SecondComponent implements OnInit, OnDestroy {
template: ``
})
export class PageRouterOutletNestedAppComponent {
- static routes = [
- { path: "", component: FirstComponent },
+ static routes: Route[] = [
+ { path: "", redirectTo: "/second/1/detail/3", pathMatch: "full" },
+ {
+ path: "first", component: FirstComponent,
+ },
{
path: "second/:depth", component: SecondComponent,
children: [
diff --git a/ng-sample/app/examples/router/router-outlet-test.ts b/ng-sample/app/examples/router/router-outlet-test.ts
index fd9803c93..76fa8a8c0 100644
--- a/ng-sample/app/examples/router/router-outlet-test.ts
+++ b/ng-sample/app/examples/router/router-outlet-test.ts
@@ -49,14 +49,14 @@ class SecondComponent implements OnInit, OnDestroy {
template: `
-
-
+
+
-
+
`
diff --git a/ng-sample/app/examples/router/styles.css b/ng-sample/app/examples/router/styles.css
index f613be9a5..0182ca37a 100644
--- a/ng-sample/app/examples/router/styles.css
+++ b/ng-sample/app/examples/router/styles.css
@@ -5,8 +5,8 @@
}
.subtitle {
- font-size: 20;
- horizontal-align: center;
+ font-size: 20;
+ horizontal-align: center;
margin: 10;
}
@@ -53,17 +53,21 @@ button {
horizontal-align: center;
}
-.odd {
- background-color: white;
+.odd {
+ background-color: white;
margin: 2;
}
-.even {
- background-color: whitesmoke;
- margin: 2;
+.even {
+ background-color: whitesmoke;
+ margin: 2;
}
.stretch {
horizontal-align: stretch;
margin: 0 10;
-}
\ No newline at end of file
+}
+
+.rlaActive {
+ color:blue;
+}
diff --git a/ng-sample/app/examples/tab-view/tab-view-test.html b/ng-sample/app/examples/tab-view/tab-view-test.html
index f66474024..5caed39c8 100644
--- a/ng-sample/app/examples/tab-view/tab-view-test.html
+++ b/ng-sample/app/examples/tab-view/tab-view-test.html
@@ -1,9 +1,9 @@
-
+
-
+
diff --git a/ng-sample/package.json b/ng-sample/package.json
index 1d07d71b5..6009d22fd 100644
--- a/ng-sample/package.json
+++ b/ng-sample/package.json
@@ -32,15 +32,15 @@
},
"homepage": "https://github.com/NativeScript/template-hello-world",
"dependencies": {
- "@angular/animations": "~4.2.4",
- "@angular/common": "~4.2.4",
- "@angular/compiler": "~4.2.4",
- "@angular/core": "~4.2.4",
- "@angular/forms": "~4.2.4",
- "@angular/http": "~4.2.4",
- "@angular/platform-browser": "~4.2.4",
- "@angular/platform-browser-dynamic": "~4.2.4",
- "@angular/router": "~4.2.4",
+ "@angular/animations": "~4.4.1",
+ "@angular/common": "~4.4.1",
+ "@angular/compiler": "~4.4.1",
+ "@angular/core": "~4.4.1",
+ "@angular/forms": "~4.4.1",
+ "@angular/http": "~4.4.1",
+ "@angular/platform-browser": "~4.4.1",
+ "@angular/platform-browser-dynamic": "~4.4.1",
+ "@angular/router": "~4.4.1",
"nativescript-angular": "file:../nativescript-angular",
"rxjs": "^5.3.0",
"tns-core-modules": "next",
diff --git a/tests/.gitignore b/tests/.gitignore
index 9b0e2ef3f..df14611cc 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -6,6 +6,10 @@ typings
.tscache
app/**/*.js
+e2e/**/*.js
app/**/*.map
+e2e/**/*.map
+e2e/reports
+test-results.xml
platforms
lib
diff --git a/tests/app/app.component.ts b/tests/app/app.component.ts
index 55dc77724..e997d6eb0 100644
--- a/tests/app/app.component.ts
+++ b/tests/app/app.component.ts
@@ -1,6 +1,6 @@
-import {Component} from "@angular/core";
-import {SinglePageMain} from "./single-page-main.component";
-import {MultiPageMain} from "./multi-page-main.component";
+import { Component } from "@angular/core";
+import { SinglePageMain } from "./single-page-main.component";
+import { MultiPageMain } from "./multi-page-main.component";
@Component({
selector: "my-app",
diff --git a/tests/app/tests/http-client-ns-backend.ts b/tests/app/tests/http-client-ns-backend.ts
new file mode 100644
index 000000000..e5baf8d2c
--- /dev/null
+++ b/tests/app/tests/http-client-ns-backend.ts
@@ -0,0 +1,80 @@
+// make sure you import mocha-config before @angular/core
+import { assert } from "./test-config";
+import { NSFileSystem } from "nativescript-angular/file-system/ns-file-system";
+import { NsHttpBackEnd } from "nativescript-angular/http-client";
+
+import { XhrFactory, HttpRequest, HttpResponse, HttpErrorResponse } from "@angular/common/http";
+import { File } from "tns-core-modules/file-system";
+
+class NSFileSystemMock implements NSFileSystem {
+ public currentApp(): any {
+ return { path: "/app/dir" };
+ }
+
+ public fileFromPath(path: string): any {
+ if (path === "/app/dir/data.json") {
+ return {
+ readText: () => { return Promise.resolve(` { "result": "success" } `); }
+ };
+ }
+ throw new Error("Opening non-existing file");
+ }
+
+ public fileExists(path: string): boolean {
+ return path === "/app/dir/data.json";
+ }
+}
+class XhrFactoryMock implements XhrFactory {
+ build(): XMLHttpRequest {
+ throw new Error("Hi, from XhrFactoryMock!");
+ }
+}
+
+describe("NsHttpBackEnd ", () => {
+ let backend: NsHttpBackEnd;
+
+ before(() => {
+ backend = new NsHttpBackEnd(new XhrFactoryMock(), new NSFileSystemMock());
+ });
+
+ it("should work with local files prefixed with '~'", (done) => {
+ const req = new HttpRequest("GET", "~/data.json");
+ let nextCalled = false;
+ backend.handle(req).subscribe(
+ (response: HttpResponse<{ result: string }>) => {
+ assert.equal(response.body.result, "success");
+ nextCalled = true;
+ }, (error) => {
+ done(error);
+ }, () => {
+ assert.isTrue(nextCalled, "next callback should be called with result.");
+ done();
+ });
+ });
+
+ it("should return 404 for non-existing local files prefixed with '~'", (done) => {
+ const req = new HttpRequest("GET", "~/non/existing/file.json");
+ backend.handle(req).subscribe(
+ (response) => {
+ assert.fail("next callback should not be called for non existing file.");
+ }, (error: HttpErrorResponse) => {
+ assert.equal(error.status, 404);
+ done();
+ }, () => {
+ assert.fail("next callback should not be called for non existing file.");
+ });
+ });
+
+ it("should fallback to XHR backend when requesting remote files", (done) => {
+ const req = new HttpRequest("GET", "https://nativescript.org/");
+ backend.handle(req).subscribe(
+ (response) => {
+ assert.fail("next callback should not be called for non existing file.");
+ }, (error: Error) => {
+ assert.equal(error.message, "Hi, from XhrFactoryMock!");
+ done();
+ }, () => {
+ assert.fail("next callback should not be called for non existing file.");
+ });
+ });
+});
diff --git a/tests/app/tests/http.ts b/tests/app/tests/http.ts
index b9456f95f..e02af2ad0 100644
--- a/tests/app/tests/http.ts
+++ b/tests/app/tests/http.ts
@@ -1,16 +1,39 @@
// make sure you import mocha-config before @angular/core
-import {assert} from "./test-config";
+import { assert } from "./test-config";
import {
async,
inject,
} from "@angular/core/testing";
-import {ReflectiveInjector} from "@angular/core";
-import {BaseRequestOptions, ConnectionBackend, Http, Response, ResponseOptions} from "@angular/http";
+import { ReflectiveInjector, Injectable } from "@angular/core";
+import { Request, BaseRequestOptions, ConnectionBackend, Http, Response, ResponseOptions } from "@angular/http";
import "rxjs/add/operator/map";
-import {MockBackend} from "@angular/http/testing";
-import {NSHttp} from "nativescript-angular/http/ns-http";
-import {NSFileSystem} from "nativescript-angular/file-system/ns-file-system";
-import {NSFileSystemMock, FileResponses} from "./mocks/ns-file-system.mock";
+import { MockBackend } from "@angular/http/testing";
+import { NSHttp } from "nativescript-angular/http/ns-http";
+import { NSFileSystem } from "nativescript-angular/file-system/ns-file-system";
+
+const AWESOME_TEAM: string = '[{"name":"Alex"}, {"name":"Rosen"}, {"name":"Panayot"}]';
+
+// Filesystem mock
+@Injectable()
+export class NSFileSystemMock implements NSFileSystem {
+ public currentApp(): any {
+ return { path: "/app/dir" };
+ }
+
+ public fileFromPath(path: string): any {
+ if (path === "/app/dir/test.json") {
+ return {
+ readText: () => { return Promise.resolve(AWESOME_TEAM); }
+ };
+ }
+ throw new Error("Opening non-existing file");
+ }
+
+ public fileExists(path: string): boolean {
+ return path === "/app/dir/test.json";
+ }
+
+}
describe("Http", () => {
let http: Http;
@@ -22,15 +45,15 @@ describe("Http", () => {
MockBackend,
{ provide: NSFileSystem, useClass: NSFileSystemMock },
{
- provide: Http,
- useFactory: function (
- connectionBackend: ConnectionBackend,
- defaultOptions: BaseRequestOptions,
- nsFileSystem: NSFileSystem) {
+ provide: Http,
+ useFactory: function (
+ connectionBackend: ConnectionBackend,
+ defaultOptions: BaseRequestOptions,
+ nsFileSystem: NSFileSystem) {
// HACK: cast backend to any to work around an angular typings problem
return new NSHttp(connectionBackend, defaultOptions, nsFileSystem);
- },
- deps: [MockBackend, BaseRequestOptions, NSFileSystem]
+ },
+ deps: [MockBackend, BaseRequestOptions, NSFileSystem]
}
]);
@@ -45,6 +68,25 @@ describe("Http", () => {
});
});
+ it("request method should work with local files prefixed with '~'", () => {
+ http.request("~/test.json").map(res => res.json()).subscribe((response: any) => {
+ assert.strictEqual(3, response.length);
+ assert.strictEqual("Alex", response[0].name);
+ });
+ });
+
+ it("request method using Request type should work with local files prefixed with '~'", () => {
+ const url = "~/test.json";
+ const req = new Request({
+ method: 'GET',
+ url
+ });
+ http.request(req).map(res => res.json()).subscribe((response: any) => {
+ assert.strictEqual(3, response.length);
+ assert.strictEqual("Alex", response[0].name);
+ });
+ });
+
it("should work with local files prefixed with '/'", () => {
http.get("/test.json").map(res => res.json()).subscribe((response: any) => {
assert.strictEqual(3, response.length);
@@ -59,6 +101,6 @@ describe("Http", () => {
assert.strictEqual(3, response.length);
assert.strictEqual("Rosen", response[1].name);
});
- connection.mockRespond(new Response(new ResponseOptions({ body: FileResponses.AWESOME_TEAM })));
+ connection.mockRespond(new Response(new ResponseOptions({ body: AWESOME_TEAM })));
});
});
diff --git a/tests/app/tests/mocks/ns-file-system.mock.ts b/tests/app/tests/mocks/ns-file-system.mock.ts
deleted file mode 100644
index 0f841aff5..000000000
--- a/tests/app/tests/mocks/ns-file-system.mock.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import {Injectable} from "@angular/core";
-import {ResponseType, Response, ResponseOptions} from "@angular/http";
-
-export class FileResponses {
- public static AWESOME_TEAM: string = '[{"name":"Alex"}, {"name":"Rosen"}, {"name":"Panayot"}]';
-}
-
-// Folder mock
-class Folder {
- public getFile(url: string): any {
- let data;
- switch (url) {
- case "test.json":
- data = FileResponses.AWESOME_TEAM;
- break;
- default:
- throw (new Error("Unsupported file for the testing mock - ns-file-system-mock"));
- }
- return {
- readText: () => {
- return new Promise((resolve) => {
- resolve(data);
- });
- }
- };
- }
-}
-
-// Filesystem mock
-@Injectable()
-export class NSFileSystemMock {
- public currentApp(): Folder {
- return new Folder();
- }
-}
-
diff --git a/tests/app/tests/ns-location-strategy.ts b/tests/app/tests/ns-location-strategy.ts
index 3a89ff541..13fd898a9 100644
--- a/tests/app/tests/ns-location-strategy.ts
+++ b/tests/app/tests/ns-location-strategy.ts
@@ -19,7 +19,7 @@ class FakeFrame extends View implements Frame {
}
}
- navigate(entry: NavigationEntry) { }
+ navigate(entry: any) { }
constructor(private backCB?: () => void) {
super();
diff --git a/tests/app/tests/property-sets.ts b/tests/app/tests/property-sets.ts
index e62bb8c74..3b5c5253e 100644
--- a/tests/app/tests/property-sets.ts
+++ b/tests/app/tests/property-sets.ts
@@ -11,11 +11,14 @@ import {Red} from "color/known-colors";
import {device, platformNames} from "platform";
import {createDevice} from "./test-utils";
-class TestView extends View implements ViewExtensions {
- public nodeName: string = "TestView";
+class TestView extends View implements NgView {
+ public meta: ViewClassMeta = { skipAddToDom: false };
public nodeType: number = 1;
+ public nodeName: string = "TestView";
public templateParent: NgView = null;
- public meta: ViewClassMeta = { skipAddToDom: false };
+ public nextSibling: NgView;
+ public firstChild: NgView;
+ public lastChild: NgView;
public ngCssClasses: Map = new Map();
public stringValue: string = "";
@@ -43,10 +46,10 @@ describe("setting View properties", () => {
it("doesn\'t convert number values", () => {
let view = new TestView();
viewUtil.setProperty(view, "numValue", "42");
- assert.strictEqual("42", view.numValue);
+ assert.strictEqual(42, view.numValue);
viewUtil.setProperty(view, "numValue", "42.");
- assert.strictEqual("42.", view.numValue);
+ assert.strictEqual(42., view.numValue);
viewUtil.setProperty(view, "numValue", 0);
assert.strictEqual(0, view.numValue);
@@ -55,9 +58,9 @@ describe("setting View properties", () => {
it("doesn\'t convert boolean values", () => {
let view = new TestView();
viewUtil.setProperty(view, "boolValue", "true");
- assert.strictEqual("true", view.boolValue);
+ assert.strictEqual(true, view.boolValue);
viewUtil.setProperty(view, "boolValue", "false");
- assert.strictEqual("false", view.boolValue);
+ assert.strictEqual(false, view.boolValue);
});
it("sets style values", () => {
diff --git a/tests/app/tests/renderer-tests.ts b/tests/app/tests/renderer-tests.ts
index 6efc463c8..3c2ccd506 100644
--- a/tests/app/tests/renderer-tests.ts
+++ b/tests/app/tests/renderer-tests.ts
@@ -291,7 +291,7 @@ describe("Renderer E2E", () => {
it("ngIf hides component when false", () => {
return testApp.loadComponent(NgIfLabel).then((componentRef) => {
const componentRoot = componentRef.instance.elementRef.nativeElement;
- assert.equal("(ProxyViewContainer (CommentNode))", dumpView(componentRoot));
+ assert.equal("(ProxyViewContainer)", dumpView(componentRoot));
});
});
@@ -302,7 +302,7 @@ describe("Renderer E2E", () => {
component.show = true;
testApp.appRef.tick();
- assert.equal("(ProxyViewContainer (CommentNode), (Label))", dumpView(componentRoot));
+ assert.equal("(ProxyViewContainer (Label))", dumpView(componentRoot));
});
});
@@ -314,12 +314,11 @@ describe("Renderer E2E", () => {
component.show = true;
testApp.appRef.tick();
assert.equal(
- "(ProxyViewContainer (StackLayout (CommentNode), (Label), (Button)))",
+ "(ProxyViewContainer (StackLayout (Label), (Button)))",
dumpView(componentRoot));
});
});
-
it("ngIf shows elements in correct order when multiple are rendered and there's *ngIf", () => {
return testApp.loadComponent(NgIfMultiple).then((componentRef) => {
const component = componentRef.instance;
@@ -333,8 +332,7 @@ describe("Renderer E2E", () => {
"(Label[text=1]), " +
"(Label[text=2]), " +
"(Label[text=3]), " +
- "(CommentNode), " + // ng-reflect comment
- "(Label[text=4]), " + // the content to be displayed and its anchor
+ "(Label[text=4]), " + // the content to be conditionally displayed
"(Label[text=5])" +
")" +
")",
@@ -348,13 +346,15 @@ describe("Renderer E2E", () => {
const componentRoot = component.elementRef.nativeElement;
testApp.appRef.tick();
+
assert.equal(
"(ProxyViewContainer " +
"(StackLayout " +
- "(CommentNode), " + // ng-reflect comment
- "(Label[text=If]), (CommentNode)))", // the content to be displayed and its anchor
+ "(Label[text=If])" +
+ ")" +
+ ")",
- dumpView(componentRoot, true));
+ dumpView(componentRoot, true));
});
});
@@ -368,10 +368,11 @@ describe("Renderer E2E", () => {
assert.equal(
"(ProxyViewContainer " +
"(StackLayout " +
- "(CommentNode), " + // ng-reflect comment
- "(Label[text=Else]), (CommentNode)))", // the content to be displayed and its anchor
+ "(Label[text=Else])" +
+ ")" +
+ ")",
- dumpView(componentRoot, true));
+ dumpView(componentRoot, true));
});
});
@@ -384,11 +385,11 @@ describe("Renderer E2E", () => {
assert.equal(
"(ProxyViewContainer " +
"(StackLayout " +
- "(CommentNode), " + // ng-reflect comment
- "(Label[text=Then]), (CommentNode), " + // the content to be displayed and its anchor
- "(CommentNode)))", // the anchor for the else template
+ "(Label[text=Then])" +
+ ")" +
+ ")",
- dumpView(componentRoot, true));
+ dumpView(componentRoot, true));
});
});
@@ -403,11 +404,11 @@ describe("Renderer E2E", () => {
assert.equal(
"(ProxyViewContainer " +
"(StackLayout " +
- "(CommentNode), " + // the content to be displayed
- "(Label[text=Else]), (CommentNode), " + // the content to be displayed
- "(CommentNode)))", // the content to be displayed
+ "(Label[text=Else])" +
+ ")" +
+ ")",
- dumpView(componentRoot, true));
+ dumpView(componentRoot, true));
});
});
@@ -415,7 +416,7 @@ describe("Renderer E2E", () => {
return testApp.loadComponent(NgForLabel).then((componentRef) => {
const componentRoot = componentRef.instance.elementRef.nativeElement;
assert.equal(
- "(ProxyViewContainer (CommentNode), (Label[text=one]), (Label[text=two]), (Label[text=three]))",
+ "(ProxyViewContainer (Label[text=one]), (Label[text=two]), (Label[text=three]))",
dumpView(componentRoot, true));
});
});
@@ -429,7 +430,7 @@ describe("Renderer E2E", () => {
testApp.appRef.tick();
assert.equal(
- "(ProxyViewContainer (CommentNode), (Label[text=one]), (Label[text=three]))",
+ "(ProxyViewContainer (Label[text=one]), (Label[text=three]))",
dumpView(componentRoot, true));
});
});
@@ -443,7 +444,7 @@ describe("Renderer E2E", () => {
testApp.appRef.tick();
assert.equal(
- "(ProxyViewContainer (CommentNode), " +
+ "(ProxyViewContainer " +
"(Label[text=one]), (Label[text=new]), (Label[text=two]), (Label[text=three]))",
dumpView(componentRoot, true));
});
diff --git a/tests/app/tests/value-accessor-tests.ts b/tests/app/tests/value-accessor-tests.ts
index c1d6c53fc..57b7173ed 100644
--- a/tests/app/tests/value-accessor-tests.ts
+++ b/tests/app/tests/value-accessor-tests.ts
@@ -1,22 +1,24 @@
// make sure you import mocha-config before @angular/core
-import {assert} from "./test-config";
-import {View} from "ui/core/view";
-import {Slider} from "ui/slider";
-import {Switch} from "ui/switch";
-import {DatePicker} from "ui/date-picker";
-import {TimePicker} from "ui/time-picker";
-import {ListPicker} from "ui/list-picker";
-import {TextField} from "ui/text-field";
-import {NumberValueAccessor} from "nativescript-angular/value-accessors/number-value-accessor";
-import {CheckedValueAccessor} from "nativescript-angular/value-accessors/checked-value-accessor";
-import {DateValueAccessor} from "nativescript-angular/value-accessors/date-value-accessor";
-import {TimeValueAccessor} from "nativescript-angular/value-accessors/time-value-accessor";
-import {SelectedIndexValueAccessor} from "nativescript-angular/value-accessors/selectedIndex-value-accessor";
-import {TextValueAccessor} from "nativescript-angular/value-accessors/text-value-accessor";
-import {ElementRef} from "@angular/core";
+import { assert } from "./test-config";
+import { View } from "ui/core/view";
+import { Slider } from "ui/slider";
+import { Switch } from "ui/switch";
+import { DatePicker } from "ui/date-picker";
+import { TimePicker } from "ui/time-picker";
+import { ListPicker } from "ui/list-picker";
+import { TextField } from "ui/text-field";
+import {
+ NumberValueAccessor,
+ CheckedValueAccessor,
+ DateValueAccessor,
+ TimeValueAccessor,
+ SelectedIndexValueAccessor,
+ TextValueAccessor
+} from "nativescript-angular/forms/value-accessors";
+import { ElementRef } from "@angular/core";
class TestElementRef implements ElementRef {
- constructor(public nativeElement: View) {};
+ constructor(public nativeElement: View) { };
}
class TestNumberValueAccessor extends NumberValueAccessor {
@@ -138,7 +140,7 @@ describe("two-way binding via ng-model", () => {
accessor.writeValue("blah");
assert.equal("blah", accessor.view.text);
- accessor.writeValue({toString: () => "stringified"});
+ accessor.writeValue({ toString: () => "stringified" });
assert.equal("stringified", accessor.view.text);
});
});
diff --git a/tests/app/tests/xhr-paths.ts b/tests/app/tests/xhr-paths.ts
index 9a4927515..c3de6a6dc 100644
--- a/tests/app/tests/xhr-paths.ts
+++ b/tests/app/tests/xhr-paths.ts
@@ -1,22 +1,72 @@
// make sure you import mocha-config before @angular/core
-import {assert} from "./test-config";
-import {FileSystemResourceLoader} from "nativescript-angular/resource-loader";
+import { assert } from "./test-config";
+import { FileSystemResourceLoader } from "nativescript-angular/resource-loader";
+
+import { File } from "tns-core-modules/file-system";
+import { NSFileSystem } from "nativescript-angular/file-system/ns-file-system";
+
+class NSFileSystemMock {
+ public currentApp(): any {
+ return { path: "/app/dir" };
+ }
+
+ public fileFromPath(path: string): File {
+ return null;
+ }
+
+ public fileExists(path: string): boolean {
+ // mycomponent.html always exists
+ // mycomponent.css is the other file
+ return path.indexOf("mycomponent.html") >= 0 || path === "/app/dir/mycomponent.css";
+ }
+}
+const fsMock = new NSFileSystemMock();
describe("XHR name resolution", () => {
+ let resourceLoader: FileSystemResourceLoader;
+ before(() => {
+ resourceLoader = new FileSystemResourceLoader(new NSFileSystemMock());
+ });
+
it("resolves relative paths from app root", () => {
- const xhr = new FileSystemResourceLoader();
- assert.strictEqual("/app/dir/mydir/mycomponent.html", xhr.resolve("mydir/mycomponent.html", "/app/dir"));
+ assert.strictEqual("/app/dir/mydir/mycomponent.html", resourceLoader.resolve("mydir/mycomponent.html"));
});
it("resolves double-slashed absolute paths as is", () => {
- const xhr = new FileSystemResourceLoader();
- assert.strictEqual("//app/mydir/mycomponent.html", xhr.resolve("//app/mydir/mycomponent.html", "/app/dir"));
+ assert.strictEqual("//app/mydir/mycomponent.html", resourceLoader.resolve("//app/mydir/mycomponent.html"));
});
it("resolves single-slashed absolute paths as is", () => {
- const xhr = new FileSystemResourceLoader();
assert.strictEqual(
"/data/data/app/mydir/mycomponent.html",
- xhr.resolve("/data/data/app/mydir/mycomponent.html", "/app/dir"));
+ resourceLoader.resolve("/data/data/app/mydir/mycomponent.html"));
+ });
+
+ it("resolves existing CSS file", () => {
+ assert.strictEqual(
+ "/app/dir/mycomponent.css",
+ resourceLoader.resolve("mycomponent.css"));
+ });
+
+ it("resolves non-existing .scss file to existing .css file", () => {
+ assert.strictEqual(
+ "/app/dir/mycomponent.css",
+ resourceLoader.resolve("mycomponent.scss"));
+ });
+
+ it("resolves non-existing .sass file to existing .css file", () => {
+ assert.strictEqual(
+ "/app/dir/mycomponent.css",
+ resourceLoader.resolve("mycomponent.sass"));
+ });
+
+ it("resolves non-existing .less file to existing .css file", () => {
+ assert.strictEqual(
+ "/app/dir/mycomponent.css",
+ resourceLoader.resolve("mycomponent.less"));
+ });
+
+ it("throws for non-existing file that has no fallbacks", () => {
+ assert.throws(() => resourceLoader.resolve("does-not-exist.css"));
});
});
diff --git a/tests/e2e-tests/lazy-load-routing.js b/tests/e2e-tests/lazy-load-routing.js
deleted file mode 100644
index 044103081..000000000
--- a/tests/e2e-tests/lazy-load-routing.js
+++ /dev/null
@@ -1,66 +0,0 @@
-"use strict";
-var nsAppium = require("nativescript-dev-appium");
-
-describe("lazy load routing", function () {
- this.timeout(360000);
- var driver;
-
- before(function () {
- driver = nsAppium.createDriver();
- });
-
- after(function () {
- return driver
- .quit()
- .finally(function () {
- console.log("Driver quit successfully");
- });
- });
-
- it("loads default path", function () {
- return driver
- .waitForElementByAccessibilityId("first-lazy-load", 300000)
- .elementByAccessibilityId("first-lazy-load")
- .should.eventually.exist
- .text().should.eventually.equal("First: lazy-load")
- });
-
- it("navigates and returns", function () {
- var expectedHookLog = [
- "first.init", // <-- load
- "second.init", // <-- forward (first component is not destroyed)
- "second.destroy"] // <-- back (first component is reused)
- .join(",");
- return driver
- .waitForElementByAccessibilityId("first-navigate-lazy-load", 300000)
- .elementByAccessibilityId("first-navigate-lazy-load")
- .should.eventually.exist
- .tap()
- .elementByAccessibilityId("second-lazy-load")
- .should.eventually.exist
- .text().should.eventually.equal("Second: lazy-load")
- .elementByAccessibilityId("router-location-strategy-states-lazy-load")
- .text().should.eventually.equal("/first/lazy-load,/second/lazy-load")
- .elementByAccessibilityId("second-navigate-back-lazy-load")
- .should.eventually.exist
- .tap()
- .elementByAccessibilityId("first-lazy-load")
- .should.eventually.exist
- .text().should.eventually.equal("First: lazy-load")
- .elementByAccessibilityId("hooks-log-lazy-load")
- .text().should.eventually.equal(expectedHookLog)
- });
-
- it("navigates and clear history", function() {
- return driver
- .waitForElementByAccessibilityId("first-navigate-lazy-load", 300000)
- .elementByAccessibilityId("first-navigate-clear-history-lazy-load")
- .should.eventually.exist
- .tap()
- .elementByAccessibilityId("second-lazy-load")
- .should.eventually.exist
- .text().should.eventually.equal("Second: lazy-load")
- .elementByAccessibilityId("router-location-strategy-states-lazy-load")
- .text().should.eventually.equal("/second/lazy-load")
- });
-});
diff --git a/tests/e2e-tests/multi-page-routing.js b/tests/e2e-tests/multi-page-routing.js
deleted file mode 100644
index 161d58c05..000000000
--- a/tests/e2e-tests/multi-page-routing.js
+++ /dev/null
@@ -1,51 +0,0 @@
-"use strict";
-var nsAppium = require("nativescript-dev-appium");
-
-describe("multi page routing", function () {
- this.timeout(360000);
- var driver;
-
- before(function () {
- driver = nsAppium.createDriver();
- });
-
- after(function () {
- return driver
- .quit()
- .finally(function () {
- console.log("Driver quit successfully");
- });
- });
-
- it("loads default path", function () {
- return driver
- .waitForElementByAccessibilityId("first-multi-page", 300000)
- .elementByAccessibilityId("first-multi-page")
- .should.eventually.exist
- .text().should.eventually.equal("First: multi-page")
- });
-
- it("navigates and returns", function () {
- var expectedHookLog = [
- "first.init", // <-- load
- "second.init", // <-- forward (first component is not destroyed)
- "second.destroy"] // <-- back (first component is reused)
- .join(",");
- return driver
- .waitForElementByAccessibilityId("first-navigate-multi-page", 300000)
- .elementByAccessibilityId("first-navigate-multi-page")
- .should.eventually.exist
- .tap()
- .elementByAccessibilityId("second-multi-page")
- .should.eventually.exist
- .text().should.eventually.equal("Second: multi-page")
- .elementByAccessibilityId("second-navigate-back-multi-page")
- .should.eventually.exist
- .tap()
- .elementByAccessibilityId("first-multi-page")
- .should.eventually.exist
- .text().should.eventually.equal("First: multi-page")
- .elementByAccessibilityId("hooks-log-multi-page")
- .text().should.eventually.equal(expectedHookLog)
- });
-});
diff --git a/tests/e2e-tests/single-page-routing.js b/tests/e2e-tests/single-page-routing.js
deleted file mode 100644
index c839953da..000000000
--- a/tests/e2e-tests/single-page-routing.js
+++ /dev/null
@@ -1,53 +0,0 @@
-"use strict";
-var nsAppium = require("nativescript-dev-appium");
-
-describe("single page routing", function () {
- this.timeout(360000);
- var driver;
-
- before(function () {
- driver = nsAppium.createDriver();
- });
-
- after(function () {
- return driver
- .quit()
- .finally(function () {
- console.log("Driver quit successfully");
- });
- });
-
- it("loads default path", function () {
- return driver
- .waitForElementByAccessibilityId("first-single-page", 300000)
- .elementByAccessibilityId("first-single-page")
- .should.eventually.exist
- .text().should.eventually.equal("First: single-page")
- });
-
- it("navigates and returns", function () {
- var expectedHookLog = [
- "first.init", // <--load
- "first.destroy", // <--forward
- "second.init",
- "second.destroy", // <--back
- "first.init"].join(",");
-
- return driver
- .waitForElementByAccessibilityId("first-single-page", 300000)
- .elementByAccessibilityId("first-navigate-single-page")
- .should.eventually.exist
- .tap()
- .elementByAccessibilityId("second-single-page")
- .should.eventually.exist
- .text().should.eventually.equal("Second: single-page")
- .elementByAccessibilityId("second-navigate-back-single-page")
- .should.eventually.exist
- .tap()
- .elementByAccessibilityId("first-single-page")
- .should.eventually.exist
- .text().should.eventually.equal("First: single-page")
- .elementByAccessibilityId("hooks-log-single-page")
- .text().should.eventually.equal(expectedHookLog)
- });
-});
diff --git a/tests/e2e/config/appium.capabilities.json b/tests/e2e/config/appium.capabilities.json
new file mode 100644
index 000000000..ee7f5a177
--- /dev/null
+++ b/tests/e2e/config/appium.capabilities.json
@@ -0,0 +1,101 @@
+{
+ "travis-e2e-tests": {
+ "platformName": "Android",
+ "platformVersion": "4.4",
+ "deviceName": "test",
+ "lt": 60000,
+ "appActivity": "com.tns.NativeScriptActivity",
+ "newCommandTimeout": 720,
+ "noReset": true,
+ "fullReset": false,
+ "app": ""
+ },
+ "android19": {
+ "platformName": "Android",
+ "platformVersion": "4.4",
+ "deviceName": "Emulator-Api19-Default",
+ "avd": "Emulator-Api19-Default",
+ "lt": 60000,
+ "appActivity": "com.tns.NativeScriptActivity",
+ "newCommandTimeout": 720,
+ "noReset": true,
+ "fullReset": false,
+ "app": ""
+ },
+ "android21": {
+ "platformName": "Android",
+ "platformVersion": "5.0",
+ "deviceName": "Emulator-Api21-Default",
+ "avd": "Emulator-Api21-Default",
+ "lt": 60000,
+ "appActivity": "com.tns.NativeScriptActivity",
+ "newCommandTimeout": 720,
+ "noReset": true,
+ "fullReset": false,
+ "app": ""
+ },
+ "android23": {
+ "platformName": "Android",
+ "platformVersion": "6.0",
+ "deviceName": "Emulator-Api23-Default",
+ "avd": "Emulator-Api23-Default",
+ "lt": 60000,
+ "appActivity": "com.tns.NativeScriptActivity",
+ "newCommandTimeout": 720,
+ "noReset": true,
+ "fullReset": false,
+ "app": ""
+ },
+ "android24": {
+ "platformName": "Android",
+ "platformVersion": "7.0",
+ "deviceName": "Emulator-Api24-Default",
+ "avd": "Emulator-Api24-Default",
+ "lt": 60000,
+ "appActivity": "com.tns.NativeScriptActivity",
+ "newCommandTimeout": 720,
+ "noReset": true,
+ "fullReset": false,
+ "app": ""
+ },
+ "android25": {
+ "platformName": "Android",
+ "platformVersion": "7.1",
+ "deviceName": "Emulator-Api25-Google",
+ "avd": "Emulator-Api25-Google",
+ "lt": 60000,
+ "appActivity": "com.tns.NativeScriptActivity",
+ "newCommandTimeout": 720,
+ "noReset": true,
+ "fullReset": false,
+ "app": ""
+ },
+ "android26": {
+ "platformName": "Android",
+ "platformVersion": "8.0",
+ "deviceName": "Emulator-Api26-Google",
+ "avd": "Emulator-Api26-Google",
+ "lt": 60000,
+ "appActivity": "com.tns.NativeScriptActivity",
+ "newCommandTimeout": 720,
+ "noReset": true,
+ "fullReset": false,
+ "app": ""
+ },
+ "sim.iPhone7.iOS100": {
+ "platformName": "iOS",
+ "platformVersion": "10.0",
+ "deviceName": "iPhone 7 100",
+ "noReset": true,
+ "fullReset": false,
+ "app": ""
+ },
+ "sim.iPhone7.iOS110": {
+ "platformName": "iOS",
+ "platformVersion": "11.0",
+ "deviceName": "iPhone 7 110",
+ "noReset": true,
+ "fullReset": false,
+ "app": ""
+ }
+}
diff --git a/tests/e2e/config/mocha.opts b/tests/e2e/config/mocha.opts
new file mode 100644
index 000000000..ea635b0dc
--- /dev/null
+++ b/tests/e2e/config/mocha.opts
@@ -0,0 +1,4 @@
+--timeout 140000
+--recursive e2e
+--reporter mocha-multi
+--reporter-options spec=-,mocha-junit-reporter=test-results.xml
\ No newline at end of file
diff --git a/tests/e2e/const.js b/tests/e2e/const.js
new file mode 100644
index 000000000..9978b5dac
--- /dev/null
+++ b/tests/e2e/const.js
@@ -0,0 +1,4 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.initialDisplayName = "initialdisplay";
+//# sourceMappingURL=const.js.map
\ No newline at end of file
diff --git a/tests/e2e/helpers/image-helper.ts b/tests/e2e/helpers/image-helper.ts
new file mode 100644
index 000000000..ff82fb024
--- /dev/null
+++ b/tests/e2e/helpers/image-helper.ts
@@ -0,0 +1,22 @@
+import { AppiumDriver } from "nativescript-dev-appium";
+import { assert } from "chai";
+
+export class ImageHelper {
+ private _imagesResults = new Map();
+
+ constructor(private _driver: AppiumDriver) { }
+
+ public async compareScreen(imageName, retryCounts = 1, tollerance = 0.01) {
+ this._imagesResults.set(imageName, await this._driver.compareScreen(imageName, retryCounts, tollerance));
+ }
+
+ public assertImages() {
+ for (let key in this._imagesResults) {
+ assert.isTrue(this._imagesResults.get(key), `Image is not correct ${key}`);
+ }
+ }
+
+ public reset(){
+ this._imagesResults.clear();
+ }
+}
\ No newline at end of file
diff --git a/tests/e2e/lazy-load-routing.ts b/tests/e2e/lazy-load-routing.ts
new file mode 100644
index 000000000..5348e6879
--- /dev/null
+++ b/tests/e2e/lazy-load-routing.ts
@@ -0,0 +1,40 @@
+import { AppiumDriver, createDriver } from "nativescript-dev-appium";
+import { assert } from "chai";
+import { ImageHelper } from "./helpers/image-helper"
+
+describe("lazy load routing", async function () {
+ let driver: AppiumDriver;
+ let imageHelper: ImageHelper;
+ const lazyLoadedDisplay = "lazyLoadedDisplay";
+
+ before(async () => {
+ driver = await createDriver();
+ await driver.resetApp();
+ imageHelper = new ImageHelper(driver);
+ });
+
+ afterEach("clear image results", () => {
+ imageHelper.reset();
+ });
+
+ it("navigates and returns", async () => {
+ await (await driver.findElementByAccessibilityId("first-navigate-lazy-load")).tap();
+ imageHelper.compareScreen("first-navigate-lazy-load-screen");
+
+ const btn = await driver.findElementByAccessibilityId("second-navigate-back-lazy-load");
+ btn.tap();
+ imageHelper.compareScreen("second-navigate-back-lazy-load-screen");
+
+ imageHelper.assertImages();
+ });
+
+ it("navigates and clear history", async () => {
+ await (await driver.findElementByAccessibilityId("first-navigate-clear-history-lazy-load")).tap();
+ imageHelper.compareScreen("first-navigate-clear-history-lazy-load-screen");
+
+ await driver.navBack();
+ imageHelper.compareScreen("first-navigate-clear-history-lazy-load-nav-back-sceen");
+
+ imageHelper.assertImages();
+ });
+});
diff --git a/tests/e2e/multi-page-routing.ts b/tests/e2e/multi-page-routing.ts
new file mode 100644
index 000000000..db4a86719
--- /dev/null
+++ b/tests/e2e/multi-page-routing.ts
@@ -0,0 +1,29 @@
+import { AppiumDriver, createDriver } from "nativescript-dev-appium";
+import { assert } from "chai";
+import { ImageHelper } from "./helpers/image-helper";
+
+describe("multi page routing", async () => {
+ let driver: AppiumDriver;
+ let imageHelper: ImageHelper;
+
+ before(async () => {
+ driver = await createDriver();
+ await driver.resetApp();
+ imageHelper = new ImageHelper(driver);
+ });
+
+ afterEach(()=>{
+ imageHelper.reset();
+ })
+
+ it("navigates and returns", async () => {
+ let btn = await driver.findElementByAccessibilityId("first-navigate-multi-page");
+ await btn.tap();
+ await imageHelper.compareScreen("first-navigate-multi-page-screen", 3);
+
+ btn = await driver.findElementByAccessibilityId("second-navigate-back-multi-page");
+ await btn.tap();
+ await imageHelper.compareScreen("second-navigate-back-multi-page-screen", 1, 0.01);
+ imageHelper.assertImages();
+ });
+});
diff --git a/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/first-navigate-clear-history-lazy-load-nav-back-sceen.png b/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/first-navigate-clear-history-lazy-load-nav-back-sceen.png
new file mode 100644
index 000000000..4206d382b
Binary files /dev/null and b/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/first-navigate-clear-history-lazy-load-nav-back-sceen.png differ
diff --git a/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/first-navigate-clear-history-lazy-load-screen.png b/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/first-navigate-clear-history-lazy-load-screen.png
new file mode 100644
index 000000000..de5884ed5
Binary files /dev/null and b/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/first-navigate-clear-history-lazy-load-screen.png differ
diff --git a/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/first-navigate-clear-history-single-page-screen.png b/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/first-navigate-clear-history-single-page-screen.png
new file mode 100644
index 000000000..a0d4d1483
Binary files /dev/null and b/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/first-navigate-clear-history-single-page-screen.png differ
diff --git a/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/first-navigate-lazy-load-screen.png b/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/first-navigate-lazy-load-screen.png
new file mode 100644
index 000000000..2a469d51f
Binary files /dev/null and b/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/first-navigate-lazy-load-screen.png differ
diff --git a/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/first-navigate-multi-page-screen.png b/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/first-navigate-multi-page-screen.png
new file mode 100644
index 000000000..363719c75
Binary files /dev/null and b/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/first-navigate-multi-page-screen.png differ
diff --git a/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/first-single-page-screen.png b/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/first-single-page-screen.png
new file mode 100644
index 000000000..e37fdef33
Binary files /dev/null and b/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/first-single-page-screen.png differ
diff --git a/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/second-navigate-back-lazy-load-screen.png b/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/second-navigate-back-lazy-load-screen.png
new file mode 100644
index 000000000..1be280d92
Binary files /dev/null and b/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/second-navigate-back-lazy-load-screen.png differ
diff --git a/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/second-navigate-back-multi-page-screen.png b/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/second-navigate-back-multi-page-screen.png
new file mode 100644
index 000000000..88d27dfe7
Binary files /dev/null and b/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/second-navigate-back-multi-page-screen.png differ
diff --git a/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/second-navigate-back-single-page-screen.png b/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/second-navigate-back-single-page-screen.png
new file mode 100644
index 000000000..4d1f3be8b
Binary files /dev/null and b/tests/e2e/resources/images/ngtests/Emulator-Api19-Default/second-navigate-back-single-page-screen.png differ
diff --git a/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/first-navigate-clear-history-lazy-load-nav-back-sceen.png b/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/first-navigate-clear-history-lazy-load-nav-back-sceen.png
new file mode 100755
index 000000000..7c8653c11
Binary files /dev/null and b/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/first-navigate-clear-history-lazy-load-nav-back-sceen.png differ
diff --git a/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/first-navigate-clear-history-lazy-load-screen.png b/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/first-navigate-clear-history-lazy-load-screen.png
new file mode 100755
index 000000000..7a3014a67
Binary files /dev/null and b/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/first-navigate-clear-history-lazy-load-screen.png differ
diff --git a/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/first-navigate-clear-history-single-page-screen_actual.png b/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/first-navigate-clear-history-single-page-screen_actual.png
new file mode 100755
index 000000000..c76add36a
Binary files /dev/null and b/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/first-navigate-clear-history-single-page-screen_actual.png differ
diff --git a/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/first-navigate-clear-history-single-page.png b/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/first-navigate-clear-history-single-page.png
new file mode 100755
index 000000000..e484c2823
Binary files /dev/null and b/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/first-navigate-clear-history-single-page.png differ
diff --git a/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/first-navigate-lazy-load-screen.png b/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/first-navigate-lazy-load-screen.png
new file mode 100755
index 000000000..c08ba640d
Binary files /dev/null and b/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/first-navigate-lazy-load-screen.png differ
diff --git a/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/first-navigate-multi-page-screen.png b/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/first-navigate-multi-page-screen.png
new file mode 100755
index 000000000..c96828824
Binary files /dev/null and b/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/first-navigate-multi-page-screen.png differ
diff --git a/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/first-single-page-screen.png b/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/first-single-page-screen.png
new file mode 100755
index 000000000..191481588
Binary files /dev/null and b/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/first-single-page-screen.png differ
diff --git a/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/second-navigate-back-lazy-load-screen.png b/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/second-navigate-back-lazy-load-screen.png
new file mode 100755
index 000000000..9e40c0c96
Binary files /dev/null and b/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/second-navigate-back-lazy-load-screen.png differ
diff --git a/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/second-navigate-back-multi-page-screen.png b/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/second-navigate-back-multi-page-screen.png
new file mode 100755
index 000000000..84d8b7eeb
Binary files /dev/null and b/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/second-navigate-back-multi-page-screen.png differ
diff --git a/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/second-navigate-back-single-page-screen.png b/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/second-navigate-back-single-page-screen.png
new file mode 100755
index 000000000..c29c6e503
Binary files /dev/null and b/tests/e2e/resources/images/ngtests/Emulator-Api24-Default/second-navigate-back-single-page-screen.png differ
diff --git a/tests/e2e/setup.ts b/tests/e2e/setup.ts
new file mode 100644
index 000000000..64138b8d3
--- /dev/null
+++ b/tests/e2e/setup.ts
@@ -0,0 +1,20 @@
+import { startServer, stopServer, createDriver, AppiumDriver } from "nativescript-dev-appium";
+
+let driver: AppiumDriver;
+before("start server", async () => {
+ await startServer();
+ driver = await createDriver();
+});
+
+afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ const fullName = await this.currentTest.fullTitle();
+ await driver.logScreenshot(fullName);
+ await driver.logPageSource(fullName);
+ }
+});
+
+after("stop server", async () => {
+ await driver.quit();
+ await stopServer();
+});
diff --git a/tests/e2e/single-page-routing.ts b/tests/e2e/single-page-routing.ts
new file mode 100644
index 000000000..dc0a75694
--- /dev/null
+++ b/tests/e2e/single-page-routing.ts
@@ -0,0 +1,42 @@
+import { AppiumDriver, createDriver } from "nativescript-dev-appium";
+import { assert } from "chai";
+import { ImageHelper } from "./helpers/image-helper";
+
+
+describe("single page routing", function () {
+ let driver: AppiumDriver;
+ let imageHelper: ImageHelper;
+
+ before(async () => {
+ driver = await createDriver();
+ await driver.resetApp();
+ imageHelper = new ImageHelper(driver);
+ });
+
+ afterEach("clear image results", () => {
+ imageHelper.reset();
+ });
+
+ it("navigates and returns", async () => {
+ let btn = (await driver.findElementByAccessibilityId("first-navigate-single-page"));
+ await btn.tap();
+ await imageHelper.compareScreen("first-single-page-screen");
+
+ btn = await driver.findElementByAccessibilityId("second-navigate-back-single-page");
+ await btn.tap();
+ await imageHelper.compareScreen("second-navigate-back-single-page-screen");
+
+ imageHelper.assertImages();
+ });
+
+ it("navigates and returns with clear history", async () => {
+ let btn = (await driver.findElementByAccessibilityId("first-navigate-clear-history-single-page"));
+ await btn.click();
+ await imageHelper.compareScreen("first-navigate-clear-history-single-page-screen");
+
+ await driver.navBack();
+ await imageHelper.compareScreen("second-navigate-back-single-page-screen");
+
+ imageHelper.assertImages();
+ });
+});
diff --git a/tests/e2e/tsconfig.json b/tests/e2e/tsconfig.json
new file mode 100644
index 000000000..99f58b8e6
--- /dev/null
+++ b/tests/e2e/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es5",
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "importHelpers": true,
+ "sourceMap": true,
+ "types": [
+ "node",
+ "mocha",
+ "chai"
+ ],
+ "lib": [
+ "es6",
+ "dom"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/tests/hooks/before-livesync/nativescript-angular-sync.js b/tests/hooks/before-livesync/nativescript-angular-sync.js
deleted file mode 100644
index 5a8510cb2..000000000
--- a/tests/hooks/before-livesync/nativescript-angular-sync.js
+++ /dev/null
@@ -1 +0,0 @@
-module.exports = require("nativescript-angular/hooks/before-livesync");
diff --git a/tests/package.json b/tests/package.json
index d23bf511b..9cfb0bb66 100644
--- a/tests/package.json
+++ b/tests/package.json
@@ -26,15 +26,15 @@
],
"homepage": "http://nativescript.org",
"dependencies": {
- "@angular/animations": "~4.2.4",
- "@angular/common": "~4.2.4",
- "@angular/compiler": "~4.2.4",
- "@angular/core": "~4.2.4",
- "@angular/forms": "~4.2.4",
- "@angular/http": "~4.2.4",
- "@angular/platform-browser": "~4.2.4",
- "@angular/platform-browser-dynamic": "~4.2.4",
- "@angular/router": "~4.2.4",
+ "@angular/animations": "~4.4.1",
+ "@angular/common": "~4.4.1",
+ "@angular/compiler": "~4.4.1",
+ "@angular/core": "~4.4.1",
+ "@angular/forms": "~4.4.1",
+ "@angular/http": "~4.4.1",
+ "@angular/platform-browser": "~4.4.1",
+ "@angular/platform-browser-dynamic": "~4.4.1",
+ "@angular/router": "~4.4.1",
"nativescript-angular": "../nativescript-angular",
"nativescript-unit-test-runner": "^0.3.4",
"rxjs": "^5.2.0",
@@ -42,6 +42,9 @@
"zone.js": "^0.8.2"
},
"devDependencies": {
+ "@types/chai": "^4.0.2",
+ "@types/mocha": "^2.2.41",
+ "@types/node": "^7.0.5",
"babel-traverse": "6.8.0",
"babel-types": "6.8.1",
"babylon": "6.8.0",
@@ -55,16 +58,19 @@
"karma-mocha-reporter": "^1.2.0",
"karma-nativescript-launcher": "^0.4.0",
"lazy": "1.0.11",
- "mocha": "^2.4.5",
- "nativescript-dev-appium": "^0.4.0",
+ "mocha-junit-reporter": "^1.13.0",
+ "mocha-multi": "^0.11.0",
+ "nativescript-dev-appium": "next",
"nativescript-dev-typescript": "^0.4.0",
"socket.io": "1.4.8",
"socket.io-client": "1.4.8",
+ "tslib": "^1.7.1",
"tslint": "^4.5.1",
- "typescript": "~2.2.0",
- "wd": "^1.2.0"
+ "typescript": "~2.5.0",
+ "mocha": "~3.5.0"
},
"scripts": {
- "appium": "nativescript-dev-appium"
+ "e2e": "tsc -p e2e && mocha --opts ./e2e/config/mocha.opts",
+ "compile-tests-w": "tsc -p e2e --watch"
}
}
diff --git a/tests/references.d.ts b/tests/references.d.ts
new file mode 100644
index 000000000..b14f3837d
--- /dev/null
+++ b/tests/references.d.ts
@@ -0,0 +1 @@
+/// Needed for autocompletion and compilation.
\ No newline at end of file
diff --git a/tests/tsconfig.json b/tests/tsconfig.json
index 7193bf4a1..abbc8d5f9 100644
--- a/tests/tsconfig.json
+++ b/tests/tsconfig.json
@@ -22,6 +22,7 @@
"exclude": [
"node_modules",
"platforms",
- "**/*.aot"
+ "**/*.aot",
+ "e2e"
]
}
\ No newline at end of file
diff --git a/tests/typings/chai.d.ts b/tests/typings/chai.d.ts
deleted file mode 100644
index 113158d83..000000000
--- a/tests/typings/chai.d.ts
+++ /dev/null
@@ -1,283 +0,0 @@
-// Type definitions for chai 1.7.2
-// Project: http://chaijs.com/
-// Definitions by: Jed Hunsaker , Bart van der Schoor
-// Definitions: https://github.com/borisyankov/DefinitelyTyped
-
-declare module chai {
- export class AssertionError {
- constructor(message: string, _props?: any, ssf?: Function);
- name: string;
- message: string;
- showDiff: boolean;
- stack: string;
- }
-
- function expect(target: any, message?: string): Expect;
-
- export var assert: Assert;
- export var config: Config;
-
- export interface Config {
- includeStack: boolean;
- }
-
- // Provides a way to extend the internals of Chai
- function use(fn: (chai: any, utils: any) => void): any;
-
- interface ExpectStatic {
- (target: any): Expect;
- }
-
- interface Assertions {
- attr(name: string, value?: string): any;
- css(name: string, value?: string): any;
- data(name: string, value?: string): any;
- class(className: string): any;
- id(id: string): any;
- html(html: string): any;
- text(text: string): any;
- value(value: string): any;
- visible: any;
- hidden: any;
- selected: any;
- checked: any;
- disabled: any;
- empty: any;
- exist: any;
- }
-
- interface Expect extends LanguageChains, NumericComparison, TypeComparison, Assertions {
- not: Expect;
- deep: Deep;
- a: TypeComparison;
- an: TypeComparison;
- include: Include;
- contain: Include;
- ok: Expect;
- true: Expect;
- false: Expect;
- null: Expect;
- undefined: Expect;
- exist: Expect;
- empty: Expect;
- arguments: Expect;
- Arguments: Expect;
- equal: Equal;
- equals: Equal;
- eq: Equal;
- eql: Equal;
- eqls: Equal;
- property: Property;
- ownProperty: OwnProperty;
- haveOwnProperty: OwnProperty;
- length: Length;
- lengthOf: Length;
- match(RegularExpression: RegExp, message?: string): Expect;
- string(string: string, message?: string): Expect;
- keys: Keys;
- key(string: string): Expect;
- throw: Throw;
- throws: Throw;
- Throw: Throw;
- respondTo(method: string, message?: string): Expect;
- itself: Expect;
- satisfy(matcher: Function, message?: string): Expect;
- closeTo(expected: number, delta: number, message?: string): Expect;
- members: Members;
- }
-
- interface LanguageChains {
- to: Expect;
- be: Expect;
- been: Expect;
- is: Expect;
- that: Expect;
- and: Expect;
- have: Expect;
- with: Expect;
- at: Expect;
- of: Expect;
- same: Expect;
- }
-
- interface NumericComparison {
- above: NumberComparer;
- gt: NumberComparer;
- greaterThan: NumberComparer;
- least: NumberComparer;
- gte: NumberComparer;
- below: NumberComparer;
- lt: NumberComparer;
- lessThan: NumberComparer;
- most: NumberComparer;
- lte: NumberComparer;
- within(start: number, finish: number, message?: string): Expect;
- }
-
- interface NumberComparer {
- (value: number, message?: string): Expect;
- }
-
- interface TypeComparison {
- (type: string, message?: string): Expect;
- instanceof: InstanceOf;
- instanceOf: InstanceOf;
- }
-
- interface InstanceOf {
- (constructor: Object, message?: string): Expect;
- }
-
- interface Deep {
- equal: Equal;
- property: Property;
- }
-
- interface Equal {
- (value: any, message?: string): Expect;
- }
-
- interface Property {
- (name: string, value?: any, message?: string): Expect;
- }
-
- interface OwnProperty {
- (name: string, message?: string): Expect;
- }
-
- interface Length extends LanguageChains, NumericComparison {
- (length: number, message?: string): Expect;
- }
-
- interface Include {
- (value: Object, message?: string): Expect;
- (value: string, message?: string): Expect;
- (value: number, message?: string): Expect;
- keys: Keys;
- members: Members;
- }
-
- interface Keys {
- (...keys: string[]): Expect;
- (keys: any[]): Expect;
- }
-
- interface Members {
- (set: any[], message?: string): Expect;
- }
-
- interface Throw {
- (): Expect;
- (expected: string, message?: string): Expect;
- (expected: RegExp, message?: string): Expect;
- (constructor: Error, expected?: string, message?: string): Expect;
- (constructor: Error, expected?: RegExp, message?: string): Expect;
- (constructor: Function, expected?: string, message?: string): Expect;
- (constructor: Function, expected?: RegExp, message?: string): Expect;
- }
-
- export interface Assert {
- (express: any, msg?: string):void;
-
- fail(actual?: any, expected?: any, msg?: string, operator?: string):void;
-
- ok(val: any, msg?: string):void;
- notOk(val: any, msg?: string):void;
-
- equal(act: any, exp: any, msg?: string):void;
- notEqual(act: any, exp: any, msg?: string):void;
-
- strictEqual(act: any, exp: any, msg?: string):void;
- notStrictEqual(act: any, exp: any, msg?: string):void;
-
- deepEqual(act: any, exp: any, msg?: string):void;
- notDeepEqual(act: any, exp: any, msg?: string):void;
-
- isTrue(val: any, msg?: string):void;
- isFalse(val: any, msg?: string):void;
-
- isNull(val: any, msg?: string):void;
- isNotNull(val: any, msg?: string):void;
-
- isUndefined(val: any, msg?: string):void;
- isDefined(val: any, msg?: string):void;
-
- isFunction(val: any, msg?: string):void;
- isNotFunction(val: any, msg?: string):void;
-
- isObject(val: any, msg?: string):void;
- isNotObject(val: any, msg?: string):void;
-
- isArray(val: any, msg?: string):void;
- isNotArray(val: any, msg?: string):void;
-
- isString(val: any, msg?: string):void;
- isNotString(val: any, msg?: string):void;
-
- isNumber(val: any, msg?: string):void;
- isNotNumber(val: any, msg?: string):void;
-
- isBoolean(val: any, msg?: string):void;
- isNotBoolean(val: any, msg?: string):void;
-
- typeOf(val: any, type: string, msg?: string):void;
- notTypeOf(val: any, type: string, msg?: string):void;
-
- instanceOf(val: any, type: Function, msg?: string):void;
- notInstanceOf(val: any, type: Function, msg?: string):void;
-
- include(exp: string, inc: any, msg?: string):void;
- include(exp: any[], inc: any, msg?: string):void;
-
- notInclude(exp: string, inc: any, msg?: string):void;
- notInclude(exp: any[], inc: any, msg?: string):void;
-
- match(exp: any, re: RegExp, msg?: string):void;
- notMatch(exp: any, re: RegExp, msg?: string):void;
-
- property(obj: Object, prop: string, msg?: string):void;
- notProperty(obj: Object, prop: string, msg?: string):void;
- deepProperty(obj: Object, prop: string, msg?: string):void;
- notDeepProperty(obj: Object, prop: string, msg?: string):void;
-
- propertyVal(obj: Object, prop: string, val: any, msg?: string):void;
- propertyNotVal(obj: Object, prop: string, val: any, msg?: string):void;
-
- deepPropertyVal(obj: Object, prop: string, val: any, msg?: string):void;
- deepPropertyNotVal(obj: Object, prop: string, val: any, msg?: string):void;
-
- lengthOf(exp: any, len: number, msg?: string):void;
- //alias frenzy
- throw(fn: Function, msg?: string):void;
- throw(fn: Function, regExp: RegExp):void;
- throw(fn: Function, errType: Function, msg?: string):void;
- throw(fn: Function, errType: Function, regExp: RegExp):void;
-
- throws(fn: Function, msg?: string):void;
- throws(fn: Function, regExp: RegExp):void;
- throws(fn: Function, errType: Function, msg?: string):void;
- throws(fn: Function, errType: Function, regExp: RegExp):void;
-
- Throw(fn: Function, msg?: string):void;
- Throw(fn: Function, regExp: RegExp):void;
- Throw(fn: Function, errType: Function, msg?: string):void;
- Throw(fn: Function, errType: Function, regExp: RegExp):void;
-
- doesNotThrow(fn: Function, msg?: string):void;
- doesNotThrow(fn: Function, regExp: RegExp):void;
- doesNotThrow(fn: Function, errType: Function, msg?: string):void;
- doesNotThrow(fn: Function, errType: Function, regExp: RegExp):void;
-
- operator(val: any, operator: string, val2: any, msg?: string):void;
- closeTo(act: number, exp: number, delta: number, msg?: string):void;
-
- sameMembers(set1: any[], set2: any[], msg?: string):void;
- includeMembers(set1: any[], set2: any[], msg?: string):void;
-
- ifError(val: any, msg?: string):void;
- }
-}
-
-declare module "chai" {
-export = chai;
-}
diff --git a/tests/typings/mocha.d.ts b/tests/typings/mocha.d.ts
deleted file mode 100644
index 8d49749b7..000000000
--- a/tests/typings/mocha.d.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-// Type definitions for mocha 1.9.0
-// Project: http://visionmedia.github.io/mocha/
-// Definitions by: Kazi Manzur Rashid
-// DefinitelyTyped: https://github.com/borisyankov/DefinitelyTyped
-
-interface Mocha {
- // Setup mocha with the given setting options.
- setup(options: MochaSetupOptions): Mocha;
-
- //Run tests and invoke `fn()` when complete.
- run(callback?: () => void): void;
-
- // Set reporter as function
- reporter(reporter: () => void): Mocha;
-
- // Set reporter, defaults to "dot"
- reporter(reporter: string): Mocha;
-
- // Enable growl support.
- growl(): Mocha
-}
-
-interface MochaSetupOptions {
- //milliseconds to wait before considering a test slow
- slow?: number;
-
- // timeout in milliseconds
- timeout?: number;
-
- // ui name "bdd", "tdd", "exports" etc
- ui?: string;
-
- //array of accepted globals
- globals?: any[];
-
- // reporter instance (function or string), defaults to `mocha.reporters.Dot`
- reporter?: any;
-
- // bail on the first test failure
- bail?: Boolean;
-
- // ignore global leaks
- ignoreLeaks?: Boolean;
-
- // grep string or regexp to filter tests with
- grep?: any;
-}
-
-declare module mocha {
- interface Done {
- (error?: Error): void;
- }
-}
-
-declare var describe : {
- (description: string, spec: () => void): void;
- only(description: string, spec: () => void): void;
- skip(description: string, spec: () => void): void;
- timeout(ms: number): void;
-}
-
-declare var it: {
- (expectation: string, assertion?: () => void): void;
- (expectation: string, assertion?: (done: mocha.Done) => void): void;
- only(expectation: string, assertion?: () => void): void;
- only(expectation: string, assertion?: (done: mocha.Done) => void): void;
- skip(expectation: string, assertion?: () => void): void;
- skip(expectation: string, assertion?: (done: mocha.Done) => void): void;
- timeout(ms: number): void;
-};
-
-declare function before(action: () => void): void;
-
-declare function before(action: (done: mocha.Done) => void): void;
-
-declare function after(action: () => void): void;
-
-declare function after(action: (done: mocha.Done) => void): void;
-
-declare function beforeEach(action: () => void): void;
-
-declare function beforeEach(action: (done: mocha.Done) => void): void;
-
-declare function afterEach(action: () => void): void;
-
-declare function afterEach(action: (done: mocha.Done) => void): void;