diff --git a/apps/app/package.json b/apps/app/package.json
index 275d90e72..711929cac 100644
--- a/apps/app/package.json
+++ b/apps/app/package.json
@@ -1,4 +1,4 @@
{
"name": "tns-samples-apps",
- "main": "perf-app/app.js"
+ "main": "ui-tests-app/app.js"
}
diff --git a/e2e/file-qualifiers/.gitignore b/e2e/file-qualifiers/.gitignore
new file mode 100644
index 000000000..32172cda8
--- /dev/null
+++ b/e2e/file-qualifiers/.gitignore
@@ -0,0 +1,35 @@
+mochawesome-report
+
+# NativeScript
+hooks/
+node_modules/
+platforms/
+
+# NativeScript Template
+*.js.map
+*.js
+!webpack.config.js
+
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+.idea
+.cloud
+.project
+tmp/
+typings/
+
+# Visual Studio Code
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
diff --git a/e2e/file-qualifiers/README.md b/e2e/file-qualifiers/README.md
new file mode 100644
index 000000000..ece7545b1
--- /dev/null
+++ b/e2e/file-qualifiers/README.md
@@ -0,0 +1,4 @@
+Execute Tests
+=============
+
+TODO
\ No newline at end of file
diff --git a/e2e/file-qualifiers/app/App_Resources/Android/AndroidManifest.xml b/e2e/file-qualifiers/app/App_Resources/Android/AndroidManifest.xml
new file mode 100644
index 000000000..9db832151
--- /dev/null
+++ b/e2e/file-qualifiers/app/App_Resources/Android/AndroidManifest.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/file-qualifiers/app/App_Resources/Android/app.gradle b/e2e/file-qualifiers/app/App_Resources/Android/app.gradle
new file mode 100644
index 000000000..ad050c828
--- /dev/null
+++ b/e2e/file-qualifiers/app/App_Resources/Android/app.gradle
@@ -0,0 +1,16 @@
+// Add your native dependencies here:
+
+// Uncomment to add recyclerview-v7 dependency
+//dependencies {
+// compile 'com.android.support:recyclerview-v7:+'
+//}
+
+android {
+ defaultConfig {
+ generatedDensities = []
+ applicationId = "org.nativescript.animation"
+ }
+ aaptOptions {
+ additionalParameters "--no-version-vectors"
+ }
+}
diff --git a/e2e/file-qualifiers/app/App_Resources/Android/drawable-hdpi/background.png b/e2e/file-qualifiers/app/App_Resources/Android/drawable-hdpi/background.png
new file mode 100644
index 000000000..eb381c258
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/Android/drawable-hdpi/background.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/Android/drawable-hdpi/icon.png b/e2e/file-qualifiers/app/App_Resources/Android/drawable-hdpi/icon.png
new file mode 100755
index 000000000..9cde84cd5
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/Android/drawable-hdpi/icon.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/Android/drawable-hdpi/logo.png b/e2e/file-qualifiers/app/App_Resources/Android/drawable-hdpi/logo.png
new file mode 100644
index 000000000..5218f4c90
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/Android/drawable-hdpi/logo.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/Android/drawable-ldpi/background.png b/e2e/file-qualifiers/app/App_Resources/Android/drawable-ldpi/background.png
new file mode 100644
index 000000000..748b2adf5
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/Android/drawable-ldpi/background.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/Android/drawable-ldpi/icon.png b/e2e/file-qualifiers/app/App_Resources/Android/drawable-ldpi/icon.png
new file mode 100755
index 000000000..4d6a674b3
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/Android/drawable-ldpi/icon.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/Android/drawable-ldpi/logo.png b/e2e/file-qualifiers/app/App_Resources/Android/drawable-ldpi/logo.png
new file mode 100644
index 000000000..b9e102a76
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/Android/drawable-ldpi/logo.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/Android/drawable-mdpi/background.png b/e2e/file-qualifiers/app/App_Resources/Android/drawable-mdpi/background.png
new file mode 100644
index 000000000..efeaf2907
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/Android/drawable-mdpi/background.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/Android/drawable-mdpi/icon.png b/e2e/file-qualifiers/app/App_Resources/Android/drawable-mdpi/icon.png
new file mode 100755
index 000000000..92ccc85a6
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/Android/drawable-mdpi/icon.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/Android/drawable-mdpi/logo.png b/e2e/file-qualifiers/app/App_Resources/Android/drawable-mdpi/logo.png
new file mode 100644
index 000000000..626338766
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/Android/drawable-mdpi/logo.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/Android/drawable-nodpi/splash_screen.xml b/e2e/file-qualifiers/app/App_Resources/Android/drawable-nodpi/splash_screen.xml
new file mode 100644
index 000000000..ada77f92c
--- /dev/null
+++ b/e2e/file-qualifiers/app/App_Resources/Android/drawable-nodpi/splash_screen.xml
@@ -0,0 +1,8 @@
+
+ -
+
+
+ -
+
+
+
\ No newline at end of file
diff --git a/e2e/file-qualifiers/app/App_Resources/Android/drawable-xhdpi/background.png b/e2e/file-qualifiers/app/App_Resources/Android/drawable-xhdpi/background.png
new file mode 100644
index 000000000..612bbd072
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/Android/drawable-xhdpi/background.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/Android/drawable-xhdpi/icon.png b/e2e/file-qualifiers/app/App_Resources/Android/drawable-xhdpi/icon.png
new file mode 100644
index 000000000..8bcde6277
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/Android/drawable-xhdpi/icon.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/Android/drawable-xhdpi/logo.png b/e2e/file-qualifiers/app/App_Resources/Android/drawable-xhdpi/logo.png
new file mode 100644
index 000000000..ad8ee2f4b
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/Android/drawable-xhdpi/logo.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/Android/drawable-xxhdpi/background.png b/e2e/file-qualifiers/app/App_Resources/Android/drawable-xxhdpi/background.png
new file mode 100644
index 000000000..0fa88e235
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/Android/drawable-xxhdpi/background.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/Android/drawable-xxhdpi/icon.png b/e2e/file-qualifiers/app/App_Resources/Android/drawable-xxhdpi/icon.png
new file mode 100644
index 000000000..9d81c85dc
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/Android/drawable-xxhdpi/icon.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/Android/drawable-xxhdpi/logo.png b/e2e/file-qualifiers/app/App_Resources/Android/drawable-xxhdpi/logo.png
new file mode 100644
index 000000000..668327832
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/Android/drawable-xxhdpi/logo.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/Android/drawable-xxxhdpi/background.png b/e2e/file-qualifiers/app/App_Resources/Android/drawable-xxxhdpi/background.png
new file mode 100644
index 000000000..c650f6438
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/Android/drawable-xxxhdpi/background.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/Android/drawable-xxxhdpi/icon.png b/e2e/file-qualifiers/app/App_Resources/Android/drawable-xxxhdpi/icon.png
new file mode 100644
index 000000000..9a34d0d43
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/Android/drawable-xxxhdpi/icon.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/Android/drawable-xxxhdpi/logo.png b/e2e/file-qualifiers/app/App_Resources/Android/drawable-xxxhdpi/logo.png
new file mode 100644
index 000000000..fa6331c8d
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/Android/drawable-xxxhdpi/logo.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/Android/values-v21/colors.xml b/e2e/file-qualifiers/app/App_Resources/Android/values-v21/colors.xml
new file mode 100644
index 000000000..a64641a9d
--- /dev/null
+++ b/e2e/file-qualifiers/app/App_Resources/Android/values-v21/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #3d5afe
+
\ No newline at end of file
diff --git a/e2e/file-qualifiers/app/App_Resources/Android/values-v21/styles.xml b/e2e/file-qualifiers/app/App_Resources/Android/values-v21/styles.xml
new file mode 100644
index 000000000..dac8727c8
--- /dev/null
+++ b/e2e/file-qualifiers/app/App_Resources/Android/values-v21/styles.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/file-qualifiers/app/App_Resources/Android/values/colors.xml b/e2e/file-qualifiers/app/App_Resources/Android/values/colors.xml
new file mode 100644
index 000000000..74ad8829c
--- /dev/null
+++ b/e2e/file-qualifiers/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/file-qualifiers/app/App_Resources/Android/values/styles.xml b/e2e/file-qualifiers/app/App_Resources/Android/values/styles.xml
new file mode 100644
index 000000000..c793e6d4c
--- /dev/null
+++ b/e2e/file-qualifiers/app/App_Resources/Android/values/styles.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000..4034b76e6
--- /dev/null
+++ b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,98 @@
+{
+ "images" : [
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "icon-29.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "icon-29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "icon-29@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "icon-40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "icon-40@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "icon-60@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "icon-60@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "icon-29.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "icon-29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "icon-40.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "icon-40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "icon-76.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "icon-76@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "83.5x83.5",
+ "idiom" : "ipad",
+ "filename" : "icon-83.5@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "1024x1024",
+ "idiom" : "ios-marketing",
+ "filename" : "icon-1024.png",
+ "scale" : "1x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png
new file mode 100644
index 000000000..a1d7eb479
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png
new file mode 100644
index 000000000..bb9b9e86d
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png
new file mode 100644
index 000000000..ec609dcf3
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png
new file mode 100644
index 000000000..a97180800
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png
new file mode 100644
index 000000000..214800ee6
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png
new file mode 100644
index 000000000..8554b88a8
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png
new file mode 100644
index 000000000..a22626bae
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png
new file mode 100644
index 000000000..a22626bae
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png
new file mode 100644
index 000000000..492c9c8e8
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png
new file mode 100644
index 000000000..9208113cf
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png
new file mode 100644
index 000000000..24415e5a3
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png
new file mode 100644
index 000000000..b3ef1bf0c
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/Contents.json b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/Contents.json
new file mode 100644
index 000000000..da4a164c9
--- /dev/null
+++ b/e2e/file-qualifiers/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/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
new file mode 100644
index 000000000..11bfcf55c
--- /dev/null
+++ b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
@@ -0,0 +1,176 @@
+{
+ "images" : [
+ {
+ "extent" : "full-screen",
+ "idiom" : "iphone",
+ "subtype" : "2436h",
+ "filename" : "Default-1125h.png",
+ "minimum-system-version" : "11.0",
+ "orientation" : "portrait",
+ "scale" : "3x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "iphone",
+ "extent" : "full-screen",
+ "filename" : "Default-Landscape-X.png",
+ "minimum-system-version" : "11.0",
+ "subtype" : "2436h",
+ "scale" : "3x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "iphone",
+ "subtype" : "736h",
+ "filename" : "Default-736h@3x.png",
+ "minimum-system-version" : "8.0",
+ "orientation" : "portrait",
+ "scale" : "3x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "iphone",
+ "subtype" : "736h",
+ "filename" : "Default-Landscape@3x.png",
+ "minimum-system-version" : "8.0",
+ "orientation" : "landscape",
+ "scale" : "3x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "iphone",
+ "subtype" : "667h",
+ "filename" : "Default-667h@2x.png",
+ "minimum-system-version" : "8.0",
+ "orientation" : "portrait",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "iphone",
+ "filename" : "Default@2x.png",
+ "extent" : "full-screen",
+ "minimum-system-version" : "7.0",
+ "scale" : "2x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "iphone",
+ "subtype" : "retina4",
+ "filename" : "Default-568h@2x.png",
+ "minimum-system-version" : "7.0",
+ "orientation" : "portrait",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "ipad",
+ "filename" : "Default-Portrait.png",
+ "extent" : "full-screen",
+ "minimum-system-version" : "7.0",
+ "scale" : "1x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "ipad",
+ "filename" : "Default-Landscape.png",
+ "extent" : "full-screen",
+ "minimum-system-version" : "7.0",
+ "scale" : "1x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "ipad",
+ "filename" : "Default-Portrait@2x.png",
+ "extent" : "full-screen",
+ "minimum-system-version" : "7.0",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "ipad",
+ "filename" : "Default-Landscape@2x.png",
+ "extent" : "full-screen",
+ "minimum-system-version" : "7.0",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "iphone",
+ "filename" : "Default.png",
+ "extent" : "full-screen",
+ "scale" : "1x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "iphone",
+ "filename" : "Default@2x.png",
+ "extent" : "full-screen",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "iphone",
+ "filename" : "Default-568h@2x.png",
+ "extent" : "full-screen",
+ "subtype" : "retina4",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "ipad",
+ "extent" : "to-status-bar",
+ "scale" : "1x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "ipad",
+ "filename" : "Default-Portrait.png",
+ "extent" : "full-screen",
+ "scale" : "1x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "ipad",
+ "extent" : "to-status-bar",
+ "scale" : "1x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "ipad",
+ "filename" : "Default-Landscape.png",
+ "extent" : "full-screen",
+ "scale" : "1x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "ipad",
+ "extent" : "to-status-bar",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "ipad",
+ "filename" : "Default-Portrait@2x.png",
+ "extent" : "full-screen",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "ipad",
+ "extent" : "to-status-bar",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "ipad",
+ "filename" : "Default-Landscape@2x.png",
+ "extent" : "full-screen",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-1125h.png b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-1125h.png
new file mode 100644
index 000000000..2913f85d9
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-1125h.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png b/e2e/file-qualifiers/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/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png b/e2e/file-qualifiers/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/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png b/e2e/file-qualifiers/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/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape-X.png b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape-X.png
new file mode 100644
index 000000000..cd94a3ac2
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape-X.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png b/e2e/file-qualifiers/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/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png b/e2e/file-qualifiers/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/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png b/e2e/file-qualifiers/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/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png b/e2e/file-qualifiers/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/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png b/e2e/file-qualifiers/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/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png
new file mode 100644
index 000000000..9f1f6ce3e
Binary files /dev/null and b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png b/e2e/file-qualifiers/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/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json
new file mode 100644
index 000000000..4f4e9c506
--- /dev/null
+++ b/e2e/file-qualifiers/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/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png b/e2e/file-qualifiers/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/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png b/e2e/file-qualifiers/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/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json b/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json
new file mode 100644
index 000000000..23c0ffd7a
--- /dev/null
+++ b/e2e/file-qualifiers/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/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png b/e2e/file-qualifiers/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/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png b/e2e/file-qualifiers/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/file-qualifiers/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png differ
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/Info.plist b/e2e/file-qualifiers/app/App_Resources/iOS/Info.plist
new file mode 100644
index 000000000..ea3e3ea23
--- /dev/null
+++ b/e2e/file-qualifiers/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/file-qualifiers/app/App_Resources/iOS/LaunchScreen.storyboard b/e2e/file-qualifiers/app/App_Resources/iOS/LaunchScreen.storyboard
new file mode 100644
index 000000000..c4e5a3f39
--- /dev/null
+++ b/e2e/file-qualifiers/app/App_Resources/iOS/LaunchScreen.storyboard
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/file-qualifiers/app/App_Resources/iOS/build.xcconfig b/e2e/file-qualifiers/app/App_Resources/iOS/build.xcconfig
new file mode 100644
index 000000000..9d738435d
--- /dev/null
+++ b/e2e/file-qualifiers/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/file-qualifiers/app/app-root.land.xml b/e2e/file-qualifiers/app/app-root.land.xml
new file mode 100644
index 000000000..197d26ea6
--- /dev/null
+++ b/e2e/file-qualifiers/app/app-root.land.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/e2e/file-qualifiers/app/app-root.xml b/e2e/file-qualifiers/app/app-root.xml
new file mode 100644
index 000000000..bfe288782
--- /dev/null
+++ b/e2e/file-qualifiers/app/app-root.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/e2e/file-qualifiers/app/app.css b/e2e/file-qualifiers/app/app.css
new file mode 100644
index 000000000..923f47830
--- /dev/null
+++ b/e2e/file-qualifiers/app/app.css
@@ -0,0 +1,5 @@
+.root-footer {
+ horizontal-align: middle;
+ color: black;
+ background-color: lightgreen
+}
\ No newline at end of file
diff --git a/e2e/file-qualifiers/app/app.land.css b/e2e/file-qualifiers/app/app.land.css
new file mode 100644
index 000000000..ac82d3c20
--- /dev/null
+++ b/e2e/file-qualifiers/app/app.land.css
@@ -0,0 +1,5 @@
+.root-footer {
+ horizontal-align: middle;
+ color: black;
+ background-color: lightpink;
+}
\ No newline at end of file
diff --git a/e2e/file-qualifiers/app/app.ts b/e2e/file-qualifiers/app/app.ts
new file mode 100644
index 000000000..0c5afc47e
--- /dev/null
+++ b/e2e/file-qualifiers/app/app.ts
@@ -0,0 +1,3 @@
+import * as application from "tns-core-modules/application";
+
+application.run({ moduleName: "app-root" });
diff --git a/e2e/file-qualifiers/app/main/main-page.css b/e2e/file-qualifiers/app/main/main-page.css
new file mode 100644
index 000000000..cdd87009f
--- /dev/null
+++ b/e2e/file-qualifiers/app/main/main-page.css
@@ -0,0 +1,4 @@
+.page-content {
+ padding: 20;
+ background-color: lightblue;
+}
\ No newline at end of file
diff --git a/e2e/file-qualifiers/app/main/main-page.land.css b/e2e/file-qualifiers/app/main/main-page.land.css
new file mode 100644
index 000000000..7d856c923
--- /dev/null
+++ b/e2e/file-qualifiers/app/main/main-page.land.css
@@ -0,0 +1,4 @@
+.page-content {
+ padding: 20;
+ background-color: lightcoral;
+}
\ No newline at end of file
diff --git a/e2e/file-qualifiers/app/main/main-page.land.ts b/e2e/file-qualifiers/app/main/main-page.land.ts
new file mode 100644
index 000000000..4edf0d0de
--- /dev/null
+++ b/e2e/file-qualifiers/app/main/main-page.land.ts
@@ -0,0 +1,7 @@
+export function onNavigatedTo(args) {
+ console.log("---> [LANDSCAPE] onNavigatedTo");
+}
+
+export function navigate(args) {
+ console.log("---> [LANDSCAPE] navigate");
+}
diff --git a/e2e/file-qualifiers/app/main/main-page.land.xml b/e2e/file-qualifiers/app/main/main-page.land.xml
new file mode 100644
index 000000000..a9cc65e4c
--- /dev/null
+++ b/e2e/file-qualifiers/app/main/main-page.land.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/e2e/file-qualifiers/app/main/main-page.ts b/e2e/file-qualifiers/app/main/main-page.ts
new file mode 100644
index 000000000..277e8a4a3
--- /dev/null
+++ b/e2e/file-qualifiers/app/main/main-page.ts
@@ -0,0 +1,7 @@
+export function onNavigatedTo(args) {
+ console.log("---> [DEFAULT] onNavigatedTo");
+}
+
+export function navigate(args) {
+ console.log("---> [DEFAULT] navigate");
+}
diff --git a/e2e/file-qualifiers/app/main/main-page.xml b/e2e/file-qualifiers/app/main/main-page.xml
new file mode 100644
index 000000000..239e35495
--- /dev/null
+++ b/e2e/file-qualifiers/app/main/main-page.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/e2e/file-qualifiers/app/package.json b/e2e/file-qualifiers/app/package.json
new file mode 100644
index 000000000..0d6fdb754
--- /dev/null
+++ b/e2e/file-qualifiers/app/package.json
@@ -0,0 +1,8 @@
+{
+ "android": {
+ "v8Flags": "--expose_gc"
+ },
+ "main": "app.js",
+ "name": "tns-template-hello-world-ts",
+ "version": "3.4.0"
+}
\ No newline at end of file
diff --git a/e2e/file-qualifiers/nsconfig.json b/e2e/file-qualifiers/nsconfig.json
new file mode 100644
index 000000000..a6d75472c
--- /dev/null
+++ b/e2e/file-qualifiers/nsconfig.json
@@ -0,0 +1,3 @@
+{
+ "useLegacyWorkflow": false
+}
\ No newline at end of file
diff --git a/e2e/file-qualifiers/package.json b/e2e/file-qualifiers/package.json
new file mode 100644
index 000000000..a10f98556
--- /dev/null
+++ b/e2e/file-qualifiers/package.json
@@ -0,0 +1,23 @@
+{
+ "description": "NativeScript Application",
+ "license": "SEE LICENSE IN ",
+ "readme": "NativeScript Application",
+ "repository": "",
+ "nativescript": {
+ "id": "org.nativescript.filequalifiers",
+ "tns-ios": {
+ "version": "6.0.0-2019-06-19-084246-01"
+ },
+ "tns-android": {
+ "version": "6.0.0-2019-06-24-113546-01"
+ }
+ },
+ "dependencies": {
+ "tns-core-modules": "file:../../tns-core-modules"
+ },
+ "devDependencies": {
+ "nativescript-dev-webpack": "next",
+ "tns-platform-declarations": "next",
+ "typescript": "3.4.1"
+ }
+}
diff --git a/e2e/file-qualifiers/tsconfig.json b/e2e/file-qualifiers/tsconfig.json
new file mode 100644
index 000000000..15a5ce832
--- /dev/null
+++ b/e2e/file-qualifiers/tsconfig.json
@@ -0,0 +1,29 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es5",
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "noEmitHelpers": true,
+ "noEmitOnError": true,
+ "lib": [
+ "es6",
+ "dom"
+ ],
+ "baseUrl": ".",
+ "paths": {
+ "*": [
+ "./node_modules/tns-core-modules/*",
+ "./node_modules/*"
+ ],
+ "~/*": [
+ "app/*"
+ ]
+ }
+ },
+ "exclude": [
+ "node_modules",
+ "platforms",
+ "e2e"
+ ]
+}
\ No newline at end of file
diff --git a/e2e/file-qualifiers/webpack.config.js b/e2e/file-qualifiers/webpack.config.js
new file mode 100644
index 000000000..9ec6edf7c
--- /dev/null
+++ b/e2e/file-qualifiers/webpack.config.js
@@ -0,0 +1,300 @@
+const { join, relative, resolve, sep } = require("path");
+
+const webpack = require("webpack");
+const nsWebpack = require("nativescript-dev-webpack");
+const nativescriptTarget = require("nativescript-dev-webpack/nativescript-target");
+const CleanWebpackPlugin = require("clean-webpack-plugin");
+const CopyWebpackPlugin = require("copy-webpack-plugin");
+const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
+const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
+const { NativeScriptWorkerPlugin } = require("nativescript-worker-loader/NativeScriptWorkerPlugin");
+const TerserPlugin = require("terser-webpack-plugin");
+const hashSalt = Date.now().toString();
+
+module.exports = env => {
+ // Add your custom Activities, Services and other Android app components here.
+ const appComponents = [
+ "tns-core-modules/ui/frame",
+ "tns-core-modules/ui/frame/activity",
+ ];
+
+ const platform = env && (env.android && "android" || env.ios && "ios");
+ if (!platform) {
+ throw new Error("You need to provide a target platform!");
+ }
+
+ const platforms = ["ios", "android"];
+ const projectRoot = __dirname;
+
+ // Default destination inside platforms//...
+ const dist = resolve(projectRoot, nsWebpack.getAppPath(platform, projectRoot));
+ const appResourcesPlatformDir = platform === "android" ? "Android" : "iOS";
+
+ const {
+ // The 'appPath' and 'appResourcesPath' values are fetched from
+ // the nsconfig.json configuration file
+ // when bundling with `tns run android|ios --bundle`.
+ appPath = "app",
+ appResourcesPath = "app/App_Resources",
+
+ // You can provide the following flags when running 'tns run android|ios'
+ snapshot, // --env.snapshot
+ production, // --env.production
+ uglify, // --env.uglify
+ report, // --env.report
+ sourceMap, // --env.sourceMap
+ hiddenSourceMap, // --env.hiddenSourceMap
+ hmr, // --env.hmr,
+ unitTesting, // --env.unitTesting,
+ verbose, // --env.verbose
+ } = env;
+ const isAnySourceMapEnabled = !!sourceMap || !!hiddenSourceMap;
+ const externals = nsWebpack.getConvertedExternals(env.externals);
+
+ const appFullPath = resolve(projectRoot, appPath);
+ const appResourcesFullPath = resolve(projectRoot, appResourcesPath);
+
+ const entryModule = nsWebpack.getEntryModule(appFullPath, platform);
+ const entryPath = `.${sep}${entryModule}.ts`;
+ const entries = { bundle: entryPath };
+
+ const tsConfigPath = resolve(projectRoot, "tsconfig.tns.json");
+
+ const areCoreModulesExternal = Array.isArray(env.externals) && env.externals.some(e => e.indexOf("tns-core-modules") > -1);
+ if (platform === "ios" && !areCoreModulesExternal) {
+ entries["tns_modules/tns-core-modules/inspector_modules"] = "inspector_modules";
+ };
+
+ let sourceMapFilename = nsWebpack.getSourceMapFilename(hiddenSourceMap, __dirname, dist);
+
+ const itemsToClean = [`${dist}/**/*`];
+ if (platform === "android") {
+ itemsToClean.push(`${join(projectRoot, "platforms", "android", "app", "src", "main", "assets", "snapshots")}`);
+ itemsToClean.push(`${join(projectRoot, "platforms", "android", "app", "build", "configurations", "nativescript-android-snapshot")}`);
+ }
+
+ const config = {
+ mode: production ? "production" : "development",
+ context: appFullPath,
+ externals,
+ watchOptions: {
+ ignored: [
+ appResourcesFullPath,
+ // Don't watch hidden files
+ "**/.*",
+ ]
+ },
+ target: nativescriptTarget,
+ entry: entries,
+ output: {
+ pathinfo: false,
+ path: dist,
+ sourceMapFilename,
+ libraryTarget: "commonjs2",
+ filename: "[name].js",
+ globalObject: "global",
+ hashSalt
+ },
+ resolve: {
+ extensions: [".ts", ".js", ".scss", ".css"],
+ // Resolve {N} system modules from tns-core-modules
+ modules: [
+ resolve(__dirname, "node_modules/tns-core-modules"),
+ resolve(__dirname, "node_modules"),
+ "node_modules/tns-core-modules",
+ "node_modules",
+ ],
+ alias: {
+ '~': appFullPath
+ },
+ // resolve symlinks to symlinked modules
+ symlinks: true
+ },
+ resolveLoader: {
+ // don't resolve symlinks to symlinked loaders
+ symlinks: false
+ },
+ node: {
+ // Disable node shims that conflict with NativeScript
+ "http": false,
+ "timers": false,
+ "setImmediate": false,
+ "fs": "empty",
+ "__dirname": false,
+ },
+ devtool: hiddenSourceMap ? "hidden-source-map" : (sourceMap ? "inline-source-map" : "none"),
+ optimization: {
+ runtimeChunk: "single",
+ splitChunks: {
+ cacheGroups: {
+ vendor: {
+ name: "vendor",
+ chunks: "all",
+ test: (module, chunks) => {
+ const moduleName = module.nameForCondition ? module.nameForCondition() : '';
+ return /[\\/]node_modules[\\/]/.test(moduleName) ||
+ appComponents.some(comp => comp === moduleName);
+
+ },
+ enforce: true,
+ },
+ }
+ },
+ minimize: !!uglify,
+ minimizer: [
+ new TerserPlugin({
+ parallel: true,
+ cache: true,
+ sourceMap: isAnySourceMapEnabled,
+ terserOptions: {
+ output: {
+ comments: false,
+ semicolons: !isAnySourceMapEnabled
+ },
+ compress: {
+ // The Android SBG has problems parsing the output
+ // when these options are enabled
+ 'collapse_vars': platform !== "android",
+ sequences: platform !== "android",
+ }
+ }
+ })
+ ],
+ },
+ module: {
+ rules: [
+ {
+ test: nsWebpack.getEntryPathRegExp(appFullPath, entryPath),
+ use: [
+ // Require all Android app components
+ platform === "android" && {
+ loader: "nativescript-dev-webpack/android-app-components-loader",
+ options: { modules: appComponents }
+ },
+
+ {
+ loader: "nativescript-dev-webpack/bundle-config-loader",
+ options: {
+ loadCss: !snapshot, // load the application css if in debug mode
+ unitTesting,
+ appFullPath,
+ projectRoot,
+ registerModules: /(root|page|app)(\.(land|port|phone|tablet|minH\d+|minW\d+|minWH\d+))?\.(xml|css|js|ts|scss)$/ // NB: MODIFIED
+ }
+ },
+ ].filter(loader => !!loader)
+ },
+
+ {
+ test: /(page|app)(\.(land|port|phone|tablet|minH\d+|minW\d+|minWH\d+))?\.ts$/,
+ use: "nativescript-dev-webpack/script-hot-loader"
+ },
+
+ {
+ test: /\.(css|scss)$/,
+ use: "nativescript-dev-webpack/style-hot-loader"
+ },
+
+ {
+ test: /\.(html|xml)$/,
+ use: "nativescript-dev-webpack/markup-hot-loader"
+ },
+
+ { test: /\.(html|xml)$/, use: "nativescript-dev-webpack/xml-namespace-loader" },
+
+ {
+ test: /\.css$/,
+ use: { loader: "css-loader", options: { url: false } }
+ },
+
+ {
+ test: /\.scss$/,
+ use: [
+ { loader: "css-loader", options: { url: false } },
+ "sass-loader"
+ ]
+ },
+
+ {
+ test: /\.ts$/,
+ use: {
+ loader: "ts-loader",
+ options: {
+ configFile: tsConfigPath,
+ // https://github.com/TypeStrong/ts-loader/blob/ea2fcf925ec158d0a536d1e766adfec6567f5fb4/README.md#faster-builds
+ // https://github.com/TypeStrong/ts-loader/blob/ea2fcf925ec158d0a536d1e766adfec6567f5fb4/README.md#hot-module-replacement
+ transpileOnly: true,
+ allowTsInNodeModules: true,
+ compilerOptions: {
+ sourceMap: isAnySourceMapEnabled,
+ declaration: false
+ }
+ },
+ }
+ },
+ ]
+ },
+ plugins: [
+ // Define useful constants like TNS_WEBPACK
+ new webpack.DefinePlugin({
+ "global.TNS_WEBPACK": "true",
+ "process": undefined,
+ }),
+ // Remove all files from the out dir.
+ new CleanWebpackPlugin(itemsToClean, { verbose: !!verbose }),
+ // Copy assets to out dir. Add your own globs as needed.
+ new CopyWebpackPlugin([
+ { from: { glob: "fonts/**" } },
+ { from: { glob: "**/*.jpg" } },
+ { from: { glob: "**/*.png" } },
+ ], { ignore: [`${relative(appPath, appResourcesFullPath)}/**`] }),
+ new nsWebpack.GenerateNativeScriptEntryPointsPlugin("bundle"),
+ // For instructions on how to set up workers with webpack
+ // check out https://github.com/nativescript/worker-loader
+ new NativeScriptWorkerPlugin(),
+ new nsWebpack.PlatformFSPlugin({
+ platform,
+ platforms,
+ }),
+ // Does IPC communication with the {N} CLI to notify events when running in watch mode.
+ new nsWebpack.WatchStateLoggerPlugin(),
+ // https://github.com/TypeStrong/ts-loader/blob/ea2fcf925ec158d0a536d1e766adfec6567f5fb4/README.md#faster-builds
+ // https://github.com/TypeStrong/ts-loader/blob/ea2fcf925ec158d0a536d1e766adfec6567f5fb4/README.md#hot-module-replacement
+ new ForkTsCheckerWebpackPlugin({
+ tsconfig: tsConfigPath,
+ async: false,
+ useTypescriptIncrementalApi: true,
+ memoryLimit: 4096
+ })
+ ],
+ };
+
+ if (report) {
+ // Generate report files for bundles content
+ config.plugins.push(new BundleAnalyzerPlugin({
+ analyzerMode: "static",
+ openAnalyzer: false,
+ generateStatsFile: true,
+ reportFilename: resolve(projectRoot, "report", `report.html`),
+ statsFilename: resolve(projectRoot, "report", `stats.json`),
+ }));
+ }
+
+ if (snapshot) {
+ config.plugins.push(new nsWebpack.NativeScriptSnapshotPlugin({
+ chunk: "vendor",
+ requireModules: [
+ "tns-core-modules/bundle-entry-points",
+ ],
+ projectRoot,
+ webpackConfig: config,
+ }));
+ }
+
+ if (hmr) {
+ config.plugins.push(new webpack.HotModuleReplacementPlugin());
+ }
+
+
+ return config;
+};
diff --git a/e2e/ui-tests-app/app/tab-view/tab-view-android-swipe-page.xml b/e2e/ui-tests-app/app/tab-view/tab-view-android-swipe-page.xml
index e4d492348..b59aa523e 100644
--- a/e2e/ui-tests-app/app/tab-view/tab-view-android-swipe-page.xml
+++ b/e2e/ui-tests-app/app/tab-view/tab-view-android-swipe-page.xml
@@ -1,5 +1,5 @@
-
-
+
+
diff --git a/e2e/ui-tests-app/app/tab-view/tab-view-more-page.xml b/e2e/ui-tests-app/app/tab-view/tab-view-more-page.xml
index c8f08cd59..6ffd2eb52 100644
--- a/e2e/ui-tests-app/app/tab-view/tab-view-more-page.xml
+++ b/e2e/ui-tests-app/app/tab-view/tab-view-more-page.xml
@@ -1,4 +1,4 @@
-
+
diff --git a/tests/app/name-resolvers-tests/file-name-resolver-tests.ts b/tests/app/name-resolvers-tests/file-name-resolver-tests.ts
new file mode 100644
index 000000000..548c713af
--- /dev/null
+++ b/tests/app/name-resolvers-tests/file-name-resolver-tests.ts
@@ -0,0 +1,42 @@
+import * as TKUnit from "../tk-unit";
+import * as fs from "tns-core-modules/file-system";
+import { FileNameResolver } from "tns-core-modules/file-system/file-name-resolver";
+import {
+ androidPhonePortraitContext,
+ androidPhoneLandscapeContext,
+ androidTabletPortraitContext,
+ iPhoneLandscapeContext,
+ iPhonePortraitContext
+} from "./qualifier-matcher-tests";
+
+const testFilePath = "~/name-resolvers-tests/files/test".replace("~", fs.knownFolders.currentApp().path);
+
+export function test_file_resolver_with_android_phone_portrait() {
+ const fileResolver = new FileNameResolver(androidPhonePortraitContext);
+ const result = fileResolver.resolveFileName(testFilePath, "xml");
+ TKUnit.assertEqual(result, testFilePath + ".xml", "File path");
+}
+
+export function test_file_resolver_with_android_phone_landscape() {
+ const fileResolver = new FileNameResolver(androidPhoneLandscapeContext);
+ const result = fileResolver.resolveFileName(testFilePath, "xml");
+ TKUnit.assertEqual(result, testFilePath + ".land.xml", "File path");
+}
+
+export function test_file_resolver_with_android_tablet_portrait() {
+ const fileResolver = new FileNameResolver(androidTabletPortraitContext);
+ const result = fileResolver.resolveFileName(testFilePath, "xml");
+ TKUnit.assertEqual(result, testFilePath + ".minWH600.xml", "File path");
+}
+
+export function test_file_resolver_with_ios_phone_landscape() {
+ const fileResolver = new FileNameResolver(iPhoneLandscapeContext);
+ const result = fileResolver.resolveFileName(testFilePath, "xml");
+ TKUnit.assertEqual(result, testFilePath + ".land.xml", "File path");
+}
+
+export function test_file_resolver_with_ios_phone_portrait() {
+ const fileResolver = new FileNameResolver(iPhonePortraitContext);
+ const result = fileResolver.resolveFileName(testFilePath, "xml");
+ TKUnit.assertEqual(result, testFilePath + ".xml", "File path");
+}
diff --git a/tests/app/file-name-resolver-tests/files/other.xml b/tests/app/name-resolvers-tests/files/other.xml
similarity index 100%
rename from tests/app/file-name-resolver-tests/files/other.xml
rename to tests/app/name-resolvers-tests/files/other.xml
diff --git a/tests/app/file-name-resolver-tests/files/test.land.xml b/tests/app/name-resolvers-tests/files/test.land.xml
similarity index 100%
rename from tests/app/file-name-resolver-tests/files/test.land.xml
rename to tests/app/name-resolvers-tests/files/test.land.xml
diff --git a/tests/app/file-name-resolver-tests/files/test.minWH600.xml b/tests/app/name-resolvers-tests/files/test.minWH600.xml
similarity index 100%
rename from tests/app/file-name-resolver-tests/files/test.minWH600.xml
rename to tests/app/name-resolvers-tests/files/test.minWH600.xml
diff --git a/tests/app/file-name-resolver-tests/files/test.xml b/tests/app/name-resolvers-tests/files/test.xml
similarity index 100%
rename from tests/app/file-name-resolver-tests/files/test.xml
rename to tests/app/name-resolvers-tests/files/test.xml
diff --git a/tests/app/name-resolvers-tests/module-name-resolver-tests.ts b/tests/app/name-resolvers-tests/module-name-resolver-tests.ts
new file mode 100644
index 000000000..c84305021
--- /dev/null
+++ b/tests/app/name-resolvers-tests/module-name-resolver-tests.ts
@@ -0,0 +1,50 @@
+import * as TKUnit from "../tk-unit";
+import { ModuleNameResolver, ModuleListProvider } from "tns-core-modules/module-name-resolver";
+
+import {
+ androidPhonePortraitContext,
+ androidPhoneLandscapeContext,
+ androidTabletPortraitContext,
+ iPhoneLandscapeContext,
+ iPhonePortraitContext
+} from "./qualifier-matcher-tests";
+
+const testModule = "name-resolvers-tests/files/test";
+const moduleProvider: ModuleListProvider = () => {
+ return [
+ "name-resolvers-tests/files/other.xml",
+ "name-resolvers-tests/files/test.land.xml",
+ "name-resolvers-tests/files/test.minWH600.xml",
+ "name-resolvers-tests/files/test.xml",
+ ];
+};
+
+export function test_module_name_resolver_with_android_phone_portrait() {
+ const moduleResolver = new ModuleNameResolver(androidPhonePortraitContext, moduleProvider);
+ const result = moduleResolver.resolveModuleName(testModule, "xml");
+ TKUnit.assertEqual(result, testModule + ".xml");
+}
+
+export function test_module_name_resolver_with_android_phone_landscape() {
+ const moduleResolver = new ModuleNameResolver(androidPhoneLandscapeContext, moduleProvider);
+ const result = moduleResolver.resolveModuleName(testModule, "xml");
+ TKUnit.assertEqual(result, testModule + ".land.xml");
+}
+
+export function test_module_name_resolver_with_android_tablet_portrait() {
+ const moduleResolver = new ModuleNameResolver(androidTabletPortraitContext, moduleProvider);
+ const result = moduleResolver.resolveModuleName(testModule, "xml");
+ TKUnit.assertEqual(result, testModule + ".minWH600.xml");
+}
+
+export function test_module_name_resolver_with_ios_phone_landscape() {
+ const moduleResolver = new ModuleNameResolver(iPhoneLandscapeContext, moduleProvider);
+ const result = moduleResolver.resolveModuleName(testModule, "xml");
+ TKUnit.assertEqual(result, testModule + ".land.xml");
+}
+
+export function test_module_name_resolver_with_ios_phone_portrait() {
+ const moduleResolver = new ModuleNameResolver(iPhonePortraitContext, moduleProvider);
+ const result = moduleResolver.resolveModuleName(testModule, "xml");
+ TKUnit.assertEqual(result, testModule + ".xml");
+}
diff --git a/tests/app/file-name-resolver-tests/file-name-resolver-tests.ts b/tests/app/name-resolvers-tests/qualifier-matcher-tests.ts
similarity index 50%
rename from tests/app/file-name-resolver-tests/file-name-resolver-tests.ts
rename to tests/app/name-resolvers-tests/qualifier-matcher-tests.ts
index ca812a8f7..6e95230e2 100644
--- a/tests/app/file-name-resolver-tests/file-name-resolver-tests.ts
+++ b/tests/app/name-resolvers-tests/qualifier-matcher-tests.ts
@@ -1,37 +1,36 @@
import * as TKUnit from "../tk-unit";
-import * as fs from "tns-core-modules/file-system";
import * as enums from "tns-core-modules/ui/enums";
-import * as resolver from "tns-core-modules/file-system/file-name-resolver";
+import { findMatch, PlatformContext } from "tns-core-modules/module-name-resolver/qualifier-matcher";
-var androidPhonePortraitContext: resolver.PlatformContext = {
+export const androidPhonePortraitContext: PlatformContext = {
width: 360,
height: 640,
deviceType: enums.DeviceType.Phone,
os: "android"
};
-var androidPhoneLandsacpeContext: resolver.PlatformContext = {
+export const androidPhoneLandscapeContext: PlatformContext = {
width: 640,
height: 360,
deviceType: enums.DeviceType.Phone,
os: "android"
};
-var androidTabletPortraitContext: resolver.PlatformContext = {
+export const androidTabletPortraitContext: PlatformContext = {
width: 600,
height: 960,
deviceType: enums.DeviceType.Tablet,
os: "android"
};
-var iPhonePortraitContext: resolver.PlatformContext = {
+export const iPhonePortraitContext: PlatformContext = {
width: 320,
height: 480,
deviceType: enums.DeviceType.Phone,
os: "ios"
};
-var iPhoneLandscapeContext: resolver.PlatformContext = {
+export const iPhoneLandscapeContext: PlatformContext = {
width: 480,
height: 320,
deviceType: enums.DeviceType.Phone,
@@ -45,7 +44,7 @@ export function test_findFileMatch_fileName() {
"other.xml"
];
- testTemplate(candidates, androidPhonePortraitContext, "test.xml");
+ findMatchTemplate(candidates, androidPhonePortraitContext, "test.xml");
}
export function test_findFileMatch_os_android() {
@@ -56,7 +55,7 @@ export function test_findFileMatch_os_android() {
"other.xml"
];
- testTemplate(candidates, androidPhonePortraitContext, "test.android.xml");
+ findMatchTemplate(candidates, androidPhonePortraitContext, "test.android.xml");
}
export function test_findFileMatch_os_ios() {
@@ -67,7 +66,7 @@ export function test_findFileMatch_os_ios() {
"other.xml"
];
- testTemplate(candidates, iPhonePortraitContext, "test.ios.xml");
+ findMatchTemplate(candidates, iPhonePortraitContext, "test.ios.xml");
}
export function test_findFileMatch_os_fallback() {
@@ -77,7 +76,7 @@ export function test_findFileMatch_os_fallback() {
"other.xml"
];
- testTemplate(candidates, androidPhonePortraitContext, "test.xml");
+ findMatchTemplate(candidates, androidPhonePortraitContext, "test.xml");
}
export function test_findFileMatch_minWH_fallback() {
@@ -87,7 +86,7 @@ export function test_findFileMatch_minWH_fallback() {
"other.xml"
];
- testTemplate(candidates, androidPhonePortraitContext, "test.xml");
+ findMatchTemplate(candidates, androidPhonePortraitContext, "test.xml");
}
export function test_findFileMatch_minWH_best_value() {
@@ -100,7 +99,7 @@ export function test_findFileMatch_minWH_best_value() {
"other.xml"
];
- testTemplate(candidates, androidTabletPortraitContext, "test.minWH600.xml");
+ findMatchTemplate(candidates, androidTabletPortraitContext, "test.minWH600.xml");
}
export function test_findFileMatch_minW_fallback() {
@@ -110,7 +109,7 @@ export function test_findFileMatch_minW_fallback() {
"other.xml"
];
- testTemplate(candidates, androidPhonePortraitContext, "test.xml");
+ findMatchTemplate(candidates, androidPhonePortraitContext, "test.xml");
}
export function test_findFileMatch_minW_best_value() {
@@ -123,7 +122,7 @@ export function test_findFileMatch_minW_best_value() {
"other.xml"
];
- testTemplate(candidates, androidTabletPortraitContext, "test.minW600.xml");
+ findMatchTemplate(candidates, androidTabletPortraitContext, "test.minW600.xml");
}
export function test_findFileMatch_minH_fallback() {
@@ -133,7 +132,7 @@ export function test_findFileMatch_minH_fallback() {
"other.xml"
];
- testTemplate(candidates, androidPhoneLandsacpeContext, "test.xml");
+ findMatchTemplate(candidates, androidPhoneLandscapeContext, "test.xml");
}
export function test_findFileMatch_minH_best_value() {
@@ -146,20 +145,20 @@ export function test_findFileMatch_minH_best_value() {
"other.xml"
];
- testTemplate(candidates, androidPhonePortraitContext, "test.minH600.xml");
+ findMatchTemplate(candidates, androidPhonePortraitContext, "test.minH600.xml");
}
-export function test_findFileMatch_orienation_fallback() {
+export function test_findFileMatch_orientation_fallback() {
var candidates: Array = [
"test.xml",
"test.land.xml",
"other.xml"
];
- testTemplate(candidates, androidTabletPortraitContext, "test.xml");
+ findMatchTemplate(candidates, androidTabletPortraitContext, "test.xml");
}
-export function test_findFileMatch_orienation_portrait() {
+export function test_findFileMatch_orientation_portrait() {
var candidates: Array = [
"test.xml",
"test.land.xml",
@@ -167,10 +166,10 @@ export function test_findFileMatch_orienation_portrait() {
"other.xml"
];
- testTemplate(candidates, androidTabletPortraitContext, "test.port.xml");
+ findMatchTemplate(candidates, androidTabletPortraitContext, "test.port.xml");
}
-export function test_findFileMatch_orienation_landscape() {
+export function test_findFileMatch_orientation_landscape() {
var candidates: Array = [
"test.xml",
"test.land.xml",
@@ -178,7 +177,7 @@ export function test_findFileMatch_orienation_landscape() {
"other.xml"
];
- testTemplate(candidates, androidPhoneLandsacpeContext, "test.land.xml");
+ findMatchTemplate(candidates, androidPhoneLandscapeContext, "test.land.xml");
}
export function test_findFileMatch_choose_most_specific_file() {
@@ -189,7 +188,7 @@ export function test_findFileMatch_choose_most_specific_file() {
"other.xml"
];
- testTemplate(candidates, androidPhonePortraitContext, "test.android.port.xml");
+ findMatchTemplate(candidates, androidPhonePortraitContext, "test.android.port.xml");
}
export function test_findFileMatch_with_multiple_matches_loads_by_priority() {
@@ -204,42 +203,30 @@ export function test_findFileMatch_with_multiple_matches_loads_by_priority() {
"other.xml"
];
- testTemplate(candidates, androidTabletPortraitContext, "test.minWH600.xml");
+ findMatchTemplate(candidates, androidTabletPortraitContext, "test.minWH600.xml");
}
-function testTemplate(candidates: Array, context: resolver.PlatformContext, expected: string) {
- var result = resolver._findFileMatch("test", ".xml", candidates, context);
- TKUnit.assertEqual(result, expected, "File path");
+function findMatchTemplate(candidates: Array, context: PlatformContext, expected: string) {
+ var result = findMatch("test", ".xml", candidates, context);
+ TKUnit.assertEqual(result, expected, "module name");
}
-var testFilePath = "~/file-name-resolver-tests/files/test".replace("~", fs.knownFolders.currentApp().path);
+export function test_findFileMatch_with_empty_extension() {
+ var candidates: Array = [
+ "test",
+ "other"
+ ];
-export function test_file_resolver_with_andorid_phone_portratit() {
- var fileResolver = new resolver.FileNameResolver(androidPhonePortraitContext);
- var result = fileResolver.resolveFileName(testFilePath, "xml");
- TKUnit.assertEqual(result, testFilePath + ".xml", "File path");
+ var result = findMatch("test", "", candidates, androidTabletPortraitContext);
+ TKUnit.assertEqual(result, "test", "module name");
}
-export function test_file_resolver_with_andorid_phone_landscape() {
- var fileResolver = new resolver.FileNameResolver(androidPhoneLandsacpeContext);
- var result = fileResolver.resolveFileName(testFilePath, "xml");
- TKUnit.assertEqual(result, testFilePath + ".land.xml", "File path");
-}
+export function test_findFileMatch_with_null_extension() {
+ var candidates: Array = [
+ "test",
+ "other"
+ ];
-export function test_file_resolver_with_andorid_tablet_portrait() {
- var fileResolver = new resolver.FileNameResolver(androidTabletPortraitContext);
- var result = fileResolver.resolveFileName(testFilePath, "xml");
- TKUnit.assertEqual(result, testFilePath + ".minWH600.xml", "File path");
-}
-
-export function test_file_resolver_with_ios_phone_landscape() {
- var fileResolver = new resolver.FileNameResolver(iPhoneLandscapeContext);
- var result = fileResolver.resolveFileName(testFilePath, "xml");
- TKUnit.assertEqual(result, testFilePath + ".land.xml", "File path");
-}
-
-export function test_file_resolver_with_ios_phone_portrait() {
- var fileResolver = new resolver.FileNameResolver(iPhonePortraitContext);
- var result = fileResolver.resolveFileName(testFilePath, "xml");
- TKUnit.assertEqual(result, testFilePath + ".xml", "File path");
+ var result = findMatch("test", null, candidates, androidTabletPortraitContext);
+ TKUnit.assertEqual(result, "test", "module name");
}
diff --git a/tests/app/test-runner.ts b/tests/app/test-runner.ts
index 3e979c0e1..53fd98d5e 100644
--- a/tests/app/test-runner.ts
+++ b/tests/app/test-runner.ts
@@ -91,9 +91,15 @@ allTests["FORMATTEDSTRING"] = formattedStringTests;
import * as fileSystemAccessTests from "./file-system-access-tests/file-system-access-tests";
allTests["FILE-SYSTEM-ACCESS"] = fileSystemAccessTests;
-import * as fileNameResolverTests from "./file-name-resolver-tests/file-name-resolver-tests";
+import * as qualifierMatcherTests from "./name-resolvers-tests/qualifier-matcher-tests";
+allTests["QUALIFIER-MATCHER"] = qualifierMatcherTests;
+
+import * as fileNameResolverTests from "./name-resolvers-tests/file-name-resolver-tests";
allTests["FILE-NAME-RESOLVER"] = fileNameResolverTests;
+import * as moduleNameResolverTests from "./name-resolvers-tests/module-name-resolver-tests";
+allTests["MODULE-NAME-RESOLVER"] = moduleNameResolverTests;
+
import * as weakEventsTests from "./ui/core/weak-event-listener/weak-event-listener-tests";
allTests["WEAK-EVENTS"] = weakEventsTests;
@@ -112,10 +118,7 @@ allTests["SCROLL-VIEW"] = scrollViewTests;
import * as actionBarTests from "./ui/action-bar/action-bar-tests";
allTests["ACTION-BAR"] = actionBarTests;
-//TODO: 13 tests commented out. Reasons:
-// 1. Loading module without XML and bundle is not supported at this moment
-// 2. Using CODEFILE/IMPORT/CSSFILE with bundle is not supported at this moment
-// 3. Attempted to assign to readonly property
+//TODO: 1 test commented out: test_EventInCodelessFragment
import * as xmlDeclarationTests from "./xml-declaration/xml-declaration-tests";
allTests["XML-DECLARATION"] = xmlDeclarationTests;
@@ -178,6 +181,9 @@ allTests["BORDER"] = borderTests;
import * as builderTests from "./ui/builder/builder-tests";
allTests["BUILDER"] = builderTests;
+import * as builderFileQualifierTests from "./ui/builder/builder-file-qualifiers-tests";
+allTests["BUILDER-QUALIFIERS"] = builderFileQualifierTests;
+
import * as buttonTests from "./ui/button/button-tests";
allTests["BUTTON"] = buttonTests;
@@ -220,8 +226,6 @@ allTests["PROGRESS"] = progressTests;
import * as placeholderTests from "./ui/placeholder/placeholder-tests";
allTests["PLACEHOLDER"] = placeholderTests;
-//TODO: 1 test commented out in page-tests-common. Reason:
-// 1. CSS is not applied when creating Label from code
import * as pageTests from "./ui/page/page-tests";
allTests["PAGE"] = pageTests;
@@ -293,8 +297,6 @@ allTests["BOTTOM-NAVIGATION-ROOT"] = bottomNavigationRootTests;
import * as resetRootViewTests from "./ui/root-view/reset-root-view-tests";
allTests["RESET-ROOT-VIEW"] = resetRootViewTests;
-//TODO: 2 test commented out. Reason:
-// 1.Loading module without XML and bundle is not supported at this moment
import * as rootViewTests from "./ui/root-view/root-view-tests";
allTests["ROOT-VIEW"] = rootViewTests;
diff --git a/tests/app/ui/builder/builder-file-qualifiers-tests.ts b/tests/app/ui/builder/builder-file-qualifiers-tests.ts
new file mode 100644
index 000000000..1e572ab51
--- /dev/null
+++ b/tests/app/ui/builder/builder-file-qualifiers-tests.ts
@@ -0,0 +1,468 @@
+import { createViewFromEntry } from "tns-core-modules/ui/builder";
+import { assertEqual, assertNull, assertThrows, assertNotNull, assert } from "../../tk-unit";
+import { _setResolver, ModuleNameResolver, PlatformContext } from "tns-core-modules/module-name-resolver";
+import { Button } from "tns-core-modules/ui/button";
+import { navigate } from "../../ui-helper";
+import { Page, Color } from "tns-core-modules/ui/page";
+
+const testPrefix = "bundle-file-qualifiers-tests";
+let modulesToCleanup: string[] = [];
+function registerTestModule(name, loader: (name: string) => void) {
+ modulesToCleanup.push(name);
+ global.registerModule(name, loader);
+}
+
+export function setUp() {
+ _setResolver(new ModuleNameResolver({
+ width: 640,
+ height: 360,
+ os: "android",
+ deviceType: "phone"
+ }));
+}
+
+export function tearDown() {
+ modulesToCleanup.forEach(global._unregisterModule);
+ modulesToCleanup = [];
+ _setResolver(undefined);
+}
+
+function createViewFromEntryAndNavigate(): Page {
+ const page = createViewFromEntry({
+ moduleName: `${testPrefix}/test`
+ });
+
+ navigate(() => page);
+
+ return page;
+}
+
+function loadRightCss() {
+ return `.test-class { color: green }`;
+}
+
+function loadRightXml() {
+ return `
+
+
+ `;
+}
+
+let testEventCalled = false;
+function loadRightJS() {
+ return {
+ testEvent: () => {
+ testEventCalled = true;
+ }
+ };
+}
+
+function loadRightJS_CreatePage() {
+ return {
+ createPage: () => {
+ const page = new Page();
+ const btn = new Button();
+ btn.id = "test-id";
+ btn.className = "test-class";
+ btn.text = "right";
+ btn.on("tap", () => { testEventCalled = true; });
+ page.content = btn;
+
+ return page;
+ }
+ };
+}
+
+function loadWrongCss(name: string) {
+ throw new Error(`Loading wrong CSS module: ${name}`);
+}
+
+function loadWrongXml(name: string) {
+ throw new Error(`Loading wrong XML module: ${name}`);
+}
+
+function loadWrongJS(name: string) {
+ throw new Error(`Loading wrong JS module: ${name}`);
+}
+
+function assertXmlCss(btn: Button) {
+ assertNotNull(btn, "Test button not found");
+
+ assertEqual(btn.text, "right", "Wrong XML loaded");
+
+ assertNotNull(btn.style.color, "Wrong CSS loaded");
+ assertEqual(btn.style.color.hex, new Color("green").hex, "Wrong CSS loaded");
+}
+
+function assertXmlCssJs(btn: Button) {
+ assertXmlCss(btn);
+
+ testEventCalled = false;
+ btn._emit("tap");
+ assert(testEventCalled, "Wrong JS loaded");
+}
+
+export function testPageWithXmlCssJs_NoQualifiers() {
+ // Arrange
+ registerTestModule(`${testPrefix}/test.xml`, loadRightXml);
+ registerTestModule(`${testPrefix}/test.css`, loadRightCss);
+ registerTestModule(`${testPrefix}/test`, loadRightJS);
+
+ // Act
+ const page = createViewFromEntryAndNavigate();
+
+ // Assert
+ assertXmlCssJs(page.getViewById("test-id"));
+}
+
+export function testPageWithJsCss_NoXML_NoQualifiers() {
+ // Arrange
+ registerTestModule(`${testPrefix}/test`, loadRightJS_CreatePage);
+ registerTestModule(`${testPrefix}/test.css`, loadRightCss);
+
+ // Act
+ const page = createViewFromEntryAndNavigate();
+
+ // Assert
+ assertXmlCssJs(page.getViewById("test-id"));
+}
+
+export function testPageWithXmlCss_NoJS_NoQualifiers() {
+ // Arrange
+ registerTestModule(`${testPrefix}/test.xml`, loadRightXml);
+ registerTestModule(`${testPrefix}/test.css`, loadRightCss);
+
+ // Act
+ const page = createViewFromEntryAndNavigate();
+
+ // Assert
+ assertXmlCss(page.getViewById("test-id"));
+}
+
+export function testPageWithXmlCssJs_CssQualifier() {
+ // Arrange
+ registerTestModule(`${testPrefix}/test.xml`, loadRightXml);
+ registerTestModule(`${testPrefix}/test.css`, loadWrongCss);
+ registerTestModule(`${testPrefix}/test.land.css`, loadRightCss);
+ registerTestModule(`${testPrefix}/test`, loadRightJS);
+
+ // Act
+ const page = createViewFromEntryAndNavigate();
+
+ // Assert
+ assertXmlCssJs(page.getViewById("test-id"));
+}
+
+export function testPageWithXmlCssJs_XmlQualifier() {
+ // Arrange
+ registerTestModule(`${testPrefix}/test.xml`, loadWrongXml);
+ registerTestModule(`${testPrefix}/test.land.xml`, loadRightXml);
+ registerTestModule(`${testPrefix}/test.css`, loadRightCss);
+ registerTestModule(`${testPrefix}/test`, loadRightJS);
+
+ // Act
+ const page = createViewFromEntryAndNavigate();
+
+ // Assert
+ assertXmlCssJs(page.getViewById("test-id"));
+}
+
+export function testPageWithXmlCssJs_JsQualifier() {
+ // Arrange
+ registerTestModule(`${testPrefix}/test.xml`, loadRightXml);
+ registerTestModule(`${testPrefix}/test.css`, loadRightCss);
+ registerTestModule(`${testPrefix}/test`, loadWrongJS);
+ registerTestModule(`${testPrefix}/test.land`, loadRightJS);
+
+ // Act
+ const page = createViewFromEntryAndNavigate();
+
+ // Assert
+ assertXmlCssJs(page.getViewById("test-id"));
+}
+
+export function testPageWithXmlCssJs_XmlCssJsQualifiers() {
+ // Arrange
+ registerTestModule(`${testPrefix}/test.xml`, loadWrongXml);
+ registerTestModule(`${testPrefix}/test.land.xml`, loadRightXml);
+ registerTestModule(`${testPrefix}/test.css`, loadWrongCss);
+ registerTestModule(`${testPrefix}/test.land.css`, loadRightCss);
+ registerTestModule(`${testPrefix}/test`, loadWrongJS);
+ registerTestModule(`${testPrefix}/test.land`, loadRightJS);
+
+ // Act
+ const page = createViewFromEntryAndNavigate();
+
+ // Assert
+ assertXmlCssJs(page.getViewById("test-id"));
+}
+
+export function testPageWithXmlCss_NoJS_XmlQualifier() {
+ // Arrange
+ registerTestModule(`${testPrefix}/test.xml`, loadWrongXml);
+ registerTestModule(`${testPrefix}/test.land.xml`, loadRightXml);
+ registerTestModule(`${testPrefix}/test.css`, loadRightCss);
+
+ // Act
+ const page = createViewFromEntryAndNavigate();
+
+ // Assert
+ assertXmlCss(page.getViewById("test-id"));
+}
+
+export function testPageWithJsCss_NoXML_JsQualifier() {
+ // Arrange
+ registerTestModule(`${testPrefix}/test`, loadWrongJS);
+ registerTestModule(`${testPrefix}/test.land`, loadRightJS_CreatePage);
+ registerTestModule(`${testPrefix}/test.css`, loadRightCss);
+
+ // Act
+ const page = createViewFromEntryAndNavigate();
+
+ // Assert
+ assertXmlCssJs(page.getViewById("test-id"));
+}
+
+function loadPageXmlWithCssFile() {
+ return `
+
+
+ `;
+}
+
+export function testPageWithXmlCssJs_WithCssFile_NoQualifiers() {
+ // Arrange
+ registerTestModule(`${testPrefix}/test.xml`, loadPageXmlWithCssFile);
+ registerTestModule(`${testPrefix}/test.css`, loadWrongCss);
+ registerTestModule(`${testPrefix}/custom-test.css`, loadRightCss);
+ registerTestModule(`${testPrefix}/test`, loadRightJS);
+
+ // Act
+ const page = createViewFromEntryAndNavigate();
+
+ // Assert
+ assertXmlCssJs(page.getViewById("test-id"));
+}
+
+export function testPageWithXmlCssJs_WithCssFile_WithQualifiers() {
+ // Arrange
+ registerTestModule(`${testPrefix}/test.xml`, loadPageXmlWithCssFile);
+ registerTestModule(`${testPrefix}/test.css`, loadWrongCss);
+ registerTestModule(`${testPrefix}/custom-test.css`, loadWrongCss);
+ registerTestModule(`${testPrefix}/custom-test.land.css`, loadRightCss);
+ registerTestModule(`${testPrefix}/test`, loadRightJS);
+
+ // Act
+ const page = createViewFromEntryAndNavigate();
+
+ // Assert
+ assertXmlCssJs(page.getViewById("test-id"));
+}
+
+function loadPageXmlWithCodeFile() {
+ return `
+
+
+ `;
+}
+
+export function testPageWithXmlCssJs_WithCodeFile_NoQualifiers() {
+ // Arrange
+ registerTestModule(`${testPrefix}/test.xml`, loadPageXmlWithCodeFile);
+ registerTestModule(`${testPrefix}/test.css`, loadRightCss);
+ registerTestModule(`${testPrefix}/custom-test`, loadRightJS);
+
+ // Act
+ const page = createViewFromEntryAndNavigate();
+
+ // Assert
+ assertXmlCssJs(page.getViewById("test-id"));
+}
+
+export function testPageWithXmlCssJs_WithCodeFile_WithQualifiers() {
+ // Arrange
+ registerTestModule(`${testPrefix}/test.xml`, loadPageXmlWithCodeFile);
+ registerTestModule(`${testPrefix}/test.css`, loadRightCss);
+ registerTestModule(`${testPrefix}/custom-test`, loadWrongJS);
+ registerTestModule(`${testPrefix}/custom-test.land`, loadRightJS);
+
+ // Act
+ const page = createViewFromEntryAndNavigate();
+
+ // Assert
+ assertXmlCssJs(page.getViewById("test-id"));
+}
+
+// --------------------------------------------------------------
+// ----------------- Custom Component Tests ---------------------
+// --------------------------------------------------------------
+
+function loadPageXmlWithComponent() {
+ return `
+
+
+ `;
+}
+
+function loadRightComponentXml() {
+ return ``;
+}
+
+export function testCustomComponentWithXmlCssJs_NoQualifiers() {
+ // Arrange
+ registerTestModule(`${testPrefix}/test.xml`, loadPageXmlWithComponent);
+ registerTestModule(`${testPrefix}/component/MyComponent.xml`, loadRightComponentXml);
+ registerTestModule(`${testPrefix}/component/MyComponent.css`, loadRightCss);
+ registerTestModule(`${testPrefix}/component/MyComponent`, loadRightJS);
+
+ // Act
+ const page = createViewFromEntryAndNavigate();
+
+ // Assert
+ assertXmlCssJs(page.getViewById("test-id"));
+}
+
+export function testCustomComponentWithXmlCssJs_JsQualifiers() {
+ // Arrange
+ registerTestModule(`${testPrefix}/test.xml`, loadPageXmlWithComponent);
+ registerTestModule(`${testPrefix}/component/MyComponent.xml`, loadRightComponentXml);
+ registerTestModule(`${testPrefix}/component/MyComponent.css`, loadRightCss);
+ registerTestModule(`${testPrefix}/component/MyComponent`, loadWrongJS);
+ registerTestModule(`${testPrefix}/component/MyComponent.land`, loadRightJS);
+
+ // Act
+ const page = createViewFromEntryAndNavigate();
+
+ // Assert
+ assertXmlCssJs(page.getViewById("test-id"));
+}
+
+export function testCustomComponentWithXmlCssJs_CssQualifiers() {
+ // Arrange
+ registerTestModule(`${testPrefix}/test.xml`, loadPageXmlWithComponent);
+ registerTestModule(`${testPrefix}/component/MyComponent.xml`, loadRightComponentXml);
+ registerTestModule(`${testPrefix}/component/MyComponent.css`, loadWrongCss);
+ registerTestModule(`${testPrefix}/component/MyComponent.land.css`, loadRightCss);
+ registerTestModule(`${testPrefix}/component/MyComponent`, loadRightJS);
+
+ // Act
+ const page = createViewFromEntryAndNavigate();
+
+ // Assert
+ assertXmlCssJs(page.getViewById("test-id"));
+}
+
+export function testCustomComponentWithXmlCssJs_XmlQualifiers() {
+ // Arrange
+ registerTestModule(`${testPrefix}/test.xml`, loadPageXmlWithComponent);
+ registerTestModule(`${testPrefix}/component/MyComponent.xml`, loadWrongXml);
+ registerTestModule(`${testPrefix}/component/MyComponent.land.xml`, loadRightComponentXml);
+ registerTestModule(`${testPrefix}/component/MyComponent.css`, loadRightCss);
+ registerTestModule(`${testPrefix}/component/MyComponent`, loadRightJS);
+
+ // Act
+ const page = createViewFromEntryAndNavigate();
+
+ // Assert
+ assertXmlCssJs(page.getViewById("test-id"));
+}
+
+export function testCustomComponentWithXmlCssJs_XmlCssJsQualifiers() {
+ // Arrange
+ registerTestModule(`${testPrefix}/test.xml`, loadPageXmlWithComponent);
+ registerTestModule(`${testPrefix}/component/MyComponent.xml`, loadWrongXml);
+ registerTestModule(`${testPrefix}/component/MyComponent.css`, loadWrongCss);
+ registerTestModule(`${testPrefix}/component/MyComponent`, loadWrongJS);
+ registerTestModule(`${testPrefix}/component/MyComponent.land.xml`, loadRightComponentXml);
+ registerTestModule(`${testPrefix}/component/MyComponent.land.css`, loadRightCss);
+ registerTestModule(`${testPrefix}/component/MyComponent.land`, loadRightJS);
+
+ // Act
+ const page = createViewFromEntryAndNavigate();
+
+ // Assert
+ assertXmlCssJs(page.getViewById("test-id"));
+}
+
+function loadCodeCustomComponentJS() {
+ class MyComponent extends Button {
+ constructor() {
+ super();
+ this.id = "test-id";
+ this.className = "test-class";
+ this.text = "right";
+ this.on("tap", () => { testEventCalled = true; });
+ }
+ }
+
+ return { MyComponent };
+}
+
+export function testCustomComponentWithCode_NoQualifiers() {
+ // Arrange
+ registerTestModule(`${testPrefix}/test.xml`, loadPageXmlWithComponent);
+ registerTestModule(`${testPrefix}/component`, loadCodeCustomComponentJS);
+ registerTestModule(`${testPrefix}/component.css`, loadRightCss);
+
+ // Act
+ const page = createViewFromEntryAndNavigate();
+
+ // Assert
+ assertXmlCssJs(page.getViewById("test-id"));
+}
+
+export function testCustomComponentWithCode_JsCssQualifiers() {
+ // Arrange
+ registerTestModule(`${testPrefix}/test.xml`, loadPageXmlWithComponent);
+ registerTestModule(`${testPrefix}/component`, loadWrongJS);
+ registerTestModule(`${testPrefix}/component.land`, loadCodeCustomComponentJS);
+ registerTestModule(`${testPrefix}/component.css`, loadWrongCss);
+ registerTestModule(`${testPrefix}/component.land.css`, loadRightCss);
+
+ // Act
+ const page = createViewFromEntryAndNavigate();
+
+ // Assert
+ assertXmlCssJs(page.getViewById("test-id"));
+}
+
+function loadRightComponentXmlWithCodeFileAndCssFile() {
+ return ``;
+}
+
+export function testCustomComponent_WithCodeFileAndCssFile_NoQualifiers() {
+ // Arrange
+ registerTestModule(`${testPrefix}/test.xml`, loadPageXmlWithComponent);
+ registerTestModule(`${testPrefix}/component/MyComponent.xml`, loadRightComponentXmlWithCodeFileAndCssFile);
+
+ registerTestModule(`${testPrefix}/component/MyComponent.css`, loadWrongCss);
+ registerTestModule(`${testPrefix}/component/custom-code-component`, loadRightJS);
+ registerTestModule(`${testPrefix}/component/custom-code-component.css`, loadRightCss);
+
+ // Act
+ const page = createViewFromEntryAndNavigate();
+
+ // Assert
+ assertXmlCssJs(page.getViewById("test-id"));
+}
+
+export function testCustomComponent_WithCodeFileAndCssFile_WithQualifiers() {
+ // Arrange
+ registerTestModule(`${testPrefix}/test.xml`, loadPageXmlWithComponent);
+ registerTestModule(`${testPrefix}/component/MyComponent.xml`, loadRightComponentXmlWithCodeFileAndCssFile);
+
+ registerTestModule(`${testPrefix}/component/MyComponent.css`, loadWrongCss);
+ registerTestModule(`${testPrefix}/component/custom-code-component`, loadWrongJS);
+ registerTestModule(`${testPrefix}/component/custom-code-component.css`, loadWrongCss);
+
+ registerTestModule(`${testPrefix}/component/custom-code-component.land`, loadRightJS);
+ registerTestModule(`${testPrefix}/component/custom-code-component.land.css`, loadRightCss);
+ // Act
+ const page = createViewFromEntryAndNavigate();
+
+ // Assert
+ assertXmlCssJs(page.getViewById("test-id"));
+}
diff --git a/tests/app/ui/builder/builder-tests.ts b/tests/app/ui/builder/builder-tests.ts
index 88a00740e..48bd27f80 100644
--- a/tests/app/ui/builder/builder-tests.ts
+++ b/tests/app/ui/builder/builder-tests.ts
@@ -1,18 +1,12 @@
-import { path } from "tns-core-modules/file-system";
-import { _loadPage } from "tns-core-modules/ui/builder";
+import { createViewFromEntry } from "tns-core-modules/ui/builder";
import { assertEqual, assertNull, assertThrows } from "../../tk-unit";
-const COMPONENT_MODULE = "component-module";
-const MISSING_MODULE = "missing-module";
-const LABEL = "label";
+const COMPONENT_MODULE = "ui/builder/component-module";
+const MISSING_MODULE = "ui/builder/missing-module";
+const labelId = "label";
-const testDir = "ui/builder";
-function getViewComponent(componentModule: string) {
- const moduleNamePath = path.join(testDir, componentModule);
- const fileName = path.join(testDir, `${componentModule}.xml`);
- const view = _loadPage(moduleNamePath, fileName);
-
- return view;
+function getViewComponent(moduleName: string) {
+ return createViewFromEntry({ moduleName });
}
export function test_view_is_module_root_component() {
@@ -23,7 +17,7 @@ export function test_view_is_module_root_component() {
export function test_view_is_NOT_module_root_component() {
const view = getViewComponent(COMPONENT_MODULE);
- const nestedView = view.getViewById(`${LABEL}`);
+ const nestedView = view.getViewById(`${labelId}`);
const undefinedModule = nestedView._moduleName;
assertNull(undefinedModule, `View<${nestedView}> should NOT be a root component of a module.`);
}
diff --git a/tests/app/ui/root-view/root-view-tests.ts b/tests/app/ui/root-view/root-view-tests.ts
index 098a0ebbc..ec564717a 100644
--- a/tests/app/ui/root-view/root-view-tests.ts
+++ b/tests/app/ui/root-view/root-view-tests.ts
@@ -22,24 +22,24 @@ function createTestFrameRootEntry() {
};
}
-// Loading module without XML and bundle is not supported at this moment
-// export function test_custom_component_rootview_css_applied() {
-// var entry = {
-// moduleName: "ui/root-view/root-modules/custom-component-root"
-// };
+export function test_custom_component_rootview_css_applied() {
+ var entry = {
+ moduleName: "ui/root-view/root-modules/custom-component-root"
+ };
-// _resetRootView(entry);
+ _resetRootView(entry);
-// var rootView = getRootView();
-// TKUnit.waitUntilReady(() => rootView.isLoaded);
+ var rootView = getRootView();
+ TKUnit.waitUntilReady(() => rootView.isLoaded);
-// TKUnit.assert(rootView instanceof myCustomControlWithoutXml.MyControl);
-// helper.assertViewBackgroundColor(rootView, "#0000FF");
-// };
+ TKUnit.assertNotNull(rootView);
+ TKUnit.assertEqual(rootView.className, "MyStackLayoutRoot", "Expected result custom control is created");
+ helper.assertViewBackgroundColor(rootView, "#0000FF");
+}
-// export function test_custom_component_rootview_layout_updates() {
-// layout_invalidate_test("./ui/root-view/root-modules/custom-component-root");
-// }
+export function test_custom_component_rootview_layout_updates() {
+ layout_invalidate_test("./ui/root-view/root-modules/custom-component-root");
+}
export function test_tabview_rootview_css_applied() {
var entry = {
diff --git a/tests/app/xml-declaration/custom-code-file.ts b/tests/app/xml-declaration/custom-code-file-page.ts
similarity index 100%
rename from tests/app/xml-declaration/custom-code-file.ts
rename to tests/app/xml-declaration/custom-code-file-page.ts
diff --git a/tests/app/xml-declaration/custom-css-file.css b/tests/app/xml-declaration/custom-css-file-page.css
similarity index 100%
rename from tests/app/xml-declaration/custom-css-file.css
rename to tests/app/xml-declaration/custom-css-file-page.css
diff --git a/tests/app/xml-declaration/template-builder-tests/event-in-codeless-fragment.ts b/tests/app/xml-declaration/template-builder-tests/event-in-codeless-fragment.ts
index 7fc17f985..c4b3cf7b0 100644
--- a/tests/app/xml-declaration/template-builder-tests/event-in-codeless-fragment.ts
+++ b/tests/app/xml-declaration/template-builder-tests/event-in-codeless-fragment.ts
@@ -1,2 +1,11 @@
-// Our unit test will set this function and expect it to be set as a handler on a View in the XML.
-export var test: (args: any) => void;
\ No newline at end of file
+
+let callback: (args: any) => void;
+export function test(args: any) {
+ if (callback) {
+ callback(args);
+ }
+}
+
+export function setCallback(cb: (args: any) => void) {
+ callback = cb;
+}
diff --git a/tests/app/xml-declaration/xml-declaration-tests.ts b/tests/app/xml-declaration/xml-declaration-tests.ts
index cbde71ef1..3507c40c5 100644
--- a/tests/app/xml-declaration/xml-declaration-tests.ts
+++ b/tests/app/xml-declaration/xml-declaration-tests.ts
@@ -43,30 +43,29 @@ export function test_load_ShouldNotCrashWithoutExports() {
TKUnit.assertTrue(v instanceof view.View, "Expected result: View; Actual result: " + v + ";");
}
-// Loading module without XML and bundle is not supported at this moment
-// export function test_loadWithOptionsNoXML() {
-// var v = builder.load({
-// path: "~/xml-declaration/mymodule",
-// name: "MyControl",
-// exports: exports,
-// page: new Page()
-// });
+export function test_loadWithOptionsNoXML() {
+ var v = builder.load({
+ path: "~/xml-declaration/mymodule",
+ name: "MyControl",
+ exports: exports,
+ page: new Page()
+ });
-// TKUnit.assertTrue(v instanceof view.View, "Expected result: View; Actual result: " + v + ";");
-// };
+ TKUnit.assertTrue(v instanceof view.View, "Expected result: View; Actual result: " + v + ";");
+}
-// export function test_loadWithOptionsNoXML_CSSIsApplied() {
-// let newPage = helper.getCurrentPage();
-// newPage.content = builder.load({
-// path: "~/xml-declaration/mymodule",
-// name: "MyControl",
-// exports: exports,
-// page: newPage
-// });
+export function test_loadWithOptionsNoXML_CSSIsApplied() {
+ let newPage = helper.getCurrentPage();
+ newPage.content = builder.load({
+ path: "~/xml-declaration/mymodule",
+ name: "MyControl",
+ exports: exports,
+ page: newPage
+ });
-// TKUnit.assert(newPage.isLoaded, "The page should be loaded here.");
-// helper.assertViewBackgroundColor(newPage.content, "#FF0000");
-// };
+ TKUnit.assert(newPage.isLoaded, "The page should be loaded here.");
+ helper.assertViewBackgroundColor(newPage.content, "#FF0000");
+}
export function test_loadInheritedPageAndResolveFromChild() {
var basePath = "xml-declaration/";
@@ -160,98 +159,104 @@ export function test_parse_ShouldNotCrashWithoutExports() {
TKUnit.assert(v instanceof view.View, "Expected result: View; Actual result: " + v + ";");
}
-// component-builder do not support [CODEFILE/IMPORT] with bundle at this point
+export function test_parse_ShouldResolveExportsFromCodeFile() {
+ var page = builder.parse("");
+ page._emit("loaded");
-// export function test_parse_ShouldResolveExportsFromCodeFile() {
-// var page = builder.parse("");
-// page._emit("loaded");
+ TKUnit.assert((page).customCodeLoaded, "Parse should resolve exports from custom code file.");
+}
-// TKUnit.assert((page).customCodeLoaded, "Parse should resolve exports from custom code file.");
-// }
+export function test_parse_ShouldResolveExportsFromImport() {
+ var page = builder.parse("");
+ page._emit("loaded");
-// export function test_parse_ShouldResolveExportsFromImport() {
-// var page = builder.parse("");
-// page._emit("loaded");
+ TKUnit.assert((page).customCodeLoaded, "Parse should resolve exports from import.");
+}
-// TKUnit.assert((page).customCodeLoaded, "Parse should resolve exports from import.");
-// }
+export function test_parse_ShouldResolveExportsFromCodeFileForPageContent() {
+ var page = builder.parse("");
+ (page).content._emit("loaded");
-// export function test_parse_ShouldResolveExportsFromCodeFileForPageContent() {
-// var page = builder.parse("");
-// (page).content._emit("loaded");
+ TKUnit.assert((page).content.customCodeLoaded, "Parse should resolve exports from custom code file for page content.");
+}
-// TKUnit.assert((page).content.customCodeLoaded, "Parse should resolve exports from custom code file for page content.");
-// }
+export function test_parse_ShouldThrowErrorWhenInvalidCodeFileIsSpecified() {
+ var e: Error;
+ try {
+ builder.parse("");
+ } catch (ex) {
+ e = ex;
+ }
-// export function test_parse_ShouldThrowErrorWhenInvalidCodeFileIsSpecified() {
-// var e: Error;
-// try {
-// builder.parse("");
-// } catch (ex) {
-// e = ex;
-// }
+ TKUnit.assert(e, "Expected result: Error; Actual result: " + e);
+}
-// TKUnit.assert(e, "Expected result: Error; Actual result: " + e);
-// };
+export function test_parse_ShouldResolveExportsFromCodeFileForTemplates() {
+ var p = builder.parse(`
+
+
+
+
+
+
+ `);
+ function testAction(views: Array) {
+ var ctrl;
-// export function test_parse_ShouldResolveExportsFromCodeFileForTemplates() {
-// var p = builder.parse("");
+ var obj = new observable.Observable();
+ obj.set("items", [1]);
+ obj.set("itemLoading", function (args: listViewModule.ItemEventData) {
+ ctrl = args.view;
+ });
+ p.bindingContext = obj;
-// function testAction(views: Array) {
-// var ctrl;
+ TKUnit.waitUntilReady(() => !!ctrl);
+ TKUnit.assert((ctrl).customCodeLoaded, "Parse should resolve exports for templates from custom code file.");
+ }
-// var obj = new observable.Observable();
-// obj.set("items", [1]);
-// obj.set("itemLoading", function (args: listViewModule.ItemEventData) {
-// ctrl = args.view
-// });
-// p.bindingContext = obj;
+ helper.navigate(function () { return p; });
+ testAction([p.content, p]);
+}
-// TKUnit.waitUntilReady(() => ctrl);
-// TKUnit.assert((ctrl).customCodeLoaded, "Parse should resolve exports for templates from custom code file.");
-// };
+export function test_parse_css_is_applied_by_type_to_lower_case_dashed_components() {
+ var newPage: Page;
+ var pageFactory = function (): Page {
+ newPage = builder.parse("");
-// helper.navigate(function () { return p; });
-// testAction([p.content, p]);
-// }
+ return newPage;
+ };
-// component-builder do not support [CSSFILE] with bundle at this point
-// export function test_parse_css_is_applied_by_type_to_lower_case_dashed_components() {
-// var newPage: Page;
-// var pageFactory = function (): Page {
-// newPage = builder.parse("");
-// return newPage;
-// };
+ helper.navigate(pageFactory);
+ TKUnit.assert(newPage.isLoaded, "The page should be loaded here.");
+ helper.assertViewBackgroundColor(newPage.content, "#008000");
+}
-// helper.navigate(pageFactory);
-// TKUnit.assert(newPage.isLoaded, "The page should be loaded here.");
-// helper.assertViewBackgroundColor(newPage.content, "#008000");
-// };
+export function test_parse_ShouldApplyCssFromCssFile() {
+ var newPage: Page;
+ var pageFactory = function (): Page {
+ newPage = builder.parse("");
-// export function test_parse_ShouldApplyCssFromCssFile() {
-// var newPage: Page;
-// var pageFactory = function (): Page {
-// newPage = builder.parse("");
-// return newPage;
-// };
+ return newPage;
+ };
-// helper.navigate(pageFactory);
-// TKUnit.assert(newPage.isLoaded, "The page should be loaded here.");
-// helper.assertViewBackgroundColor(newPage.content, "#008000");
-// };
+ helper.navigate(pageFactory);
+ TKUnit.assert(newPage.isLoaded, "The page should be loaded here.");
+ helper.assertViewBackgroundColor(newPage.content, "#008000");
+}
-// export function test_parse_ShouldResolveExportsFromCodeFileAndApplyCssFile() {
-// var newPage: Page;
-// var pageFactory = function (): Page {
-// newPage = builder.parse("");
-// return newPage;
-// };
+export function test_parse_ShouldResolveExportsFromCodeFileAndApplyCssFile() {
+ var newPage: Page;
+ var pageFactory = function (): Page {
+ newPage = builder.parse("");
-// helper.navigate(pageFactory);
-// TKUnit.assert(newPage.isLoaded, "The page should be loaded here.");
-// TKUnit.assert((newPage).customCodeLoaded, "Parse should resolve exports from custom code file.");
-// helper.assertViewBackgroundColor(newPage.content, "#008000");
-// };
+ return newPage;
+ };
+
+ helper.navigate(pageFactory);
+ TKUnit.assert(newPage.isLoaded, "The page should be loaded here.");
+ TKUnit.assert((newPage).customCodeLoaded, "Parse should resolve exports from custom code file.");
+ helper.assertViewBackgroundColor(newPage.content, "#008000");
+}
export function test_parse_ShouldFindEventHandlersInExports() {
var loaded;
@@ -511,13 +516,12 @@ export function test_parse_ShouldParseLowerCaseDashedComponentDeclaration() {
TKUnit.assert(ctrl.getChildAt(1) instanceof segmentedBar.SegmentedBar, "Expected result: Label!; Actual result: " + ctrl.getChildAt(0));
}
-// Loading module without XML and bundle is not supported at this moment
-// adding /MyControl will work
-// export function test_parse_ShouldParseCustomComponentWithoutXml() {
-// var p = builder.parse("");
-// var ctrl = p.content;
-// TKUnit.assert(ctrl instanceof myCustomControlWithoutXml.MyControl, "Expected result: custom control is defined!; Actual result: " + ctrl);
-// };
+export function test_parse_ShouldParseCustomComponentWithoutXml() {
+ var p = builder.parse("");
+ var ctrl = p.content;
+ TKUnit.assertNotNull(ctrl);
+ TKUnit.assertEqual(ctrl.className, "MyStackLayout", "Expected result custom control is created");
+}
export function test_parse_ShouldParseCustomComponentWithoutXmlFromTNSModules() {
var p = builder.parse("");
@@ -577,25 +581,25 @@ export function test_parse_ShouldParseCustomComponentWithXmlNoJS_WithCustomAttri
TKUnit.assertEqual(panel["myProperty"], "myValue", "customControl.myProperty");
}
-// Loading module without XML and bundle is not supported at this moment
-// export function test_parse_ShouldParseCustomComponentWithoutXmlInListViewTemplate() {
-// var p = builder.parse("");
+export function test_parse_ShouldParseCustomComponentWithoutXmlInListViewTemplate() {
+ var p = builder.parse("");
-// function testAction(views: Array) {
-// let ctrl;
-// let obj = new observable.Observable();
-// obj.set("items", [1]);
-// obj.set("itemLoading", function (args: listViewModule.ItemEventData) {
-// ctrl = args.view
-// });
-// p.bindingContext = obj;
-// TKUnit.waitUntilReady(() => ctrl);
-// TKUnit.assert(ctrl instanceof myCustomControlWithoutXml.MyControl, "Expected result: custom control is defined!; Actual result: " + ctrl);
-// };
+ function testAction(views: Array) {
+ let ctrl;
+ let obj = new observable.Observable();
+ obj.set("items", [1]);
+ obj.set("itemLoading", function (args: listViewModule.ItemEventData) {
+ ctrl = args.view;
+ });
+ p.bindingContext = obj;
+ TKUnit.waitUntilReady(() => !!ctrl);
+ TKUnit.assertNotNull(ctrl);
+ TKUnit.assertEqual(ctrl.className, "MyStackLayout", "Expected result custom control is created");
+ }
-// helper.navigate(function () { return p; });
-// testAction([p.content, p]);
-// }
+ helper.navigate(function () { return p; });
+ testAction([p.content, p]);
+}
export function test_parse_ShouldParseNestedListViewInListViewTemplate() {
var p = builder.parse("");
@@ -608,7 +612,7 @@ export function test_parse_ShouldParseNestedListViewInListViewTemplate() {
ctrl = args.view;
});
p.bindingContext = obj;
- TKUnit.waitUntilReady(() => ctrl);
+ TKUnit.waitUntilReady(() => !!ctrl);
TKUnit.assert(ctrl instanceof listViewModule.ListView, "Expected result: ListView!; Actual result: " + ctrl);
}
@@ -643,7 +647,7 @@ export function test_parse_ShouldEvaluateEventBindingExpressionInListViewTemplat
});
p.bindingContext = obj;
- TKUnit.waitUntilReady(() => ctrl !== null);
+ TKUnit.waitUntilReady(() => !!ctrl);
ctrl.selectedIndex = 1;
TKUnit.assert(changed, "Expected result: true!; Actual result: " + changed);
@@ -849,30 +853,29 @@ export function test_EventInTemplate() {
TKUnit.assert(notified, "Expected the child to raise the test event.");
}
-//[XML-DECLARATION.test_EventInCodelessFragment] FAILED: Attempted to assign to readonly property., Stack: test_EventInCodelessFragment
-// export function test_EventInCodelessFragment() {
-// var pageCode = global.loadModule("./xml-declaration/template-builder-tests/event-in-codeless-fragment");
+export function test_EventInCodelessFragment() {
+ var pageCode = global.loadModule("./xml-declaration/template-builder-tests/event-in-codeless-fragment");
-// var notified = false;
-// pageCode.test = (args) => {
-// notified = true;
-// }
+ var notified = false;
+ pageCode.setCallback((args) => {
+ notified = true;
+ });
-// var page = builder.load("./xml-declaration/template-builder-tests/event-in-codeless-fragment.xml", pageCode);
-// TKUnit.assert(view, "Expected the xml to generate a page");
-// var templateView = page.getViewById("template-view");
-// TKUnit.assert(templateView, "Expected the page to have a TemplateView with 'temaplte-view' id.");
-// templateView.parseTemplate();
-// TKUnit.assertEqual(templateView.getChildrenCount(), 1, "Expected TemplateView initially to have 1 child.");
-// var childTemplateView = templateView.getChildAt(0);
-// TKUnit.assert(childTemplateView, "Expected the TemplateView's template to create a child TemplateView.");
-// childTemplateView.notify({
-// eventName: "test",
-// object: childTemplateView
-// });
+ var page = builder.load("./xml-declaration/template-builder-tests/event-in-codeless-fragment.xml", pageCode);
+ TKUnit.assert(view, "Expected the xml to generate a page");
+ var templateView = page.getViewById("template-view");
+ TKUnit.assert(templateView, "Expected the page to have a TemplateView with 'temaplte-view' id.");
+ templateView.parseTemplate();
+ TKUnit.assertEqual(templateView.getChildrenCount(), 1, "Expected TemplateView initially to have 1 child.");
+ var childTemplateView = templateView.getChildAt(0);
+ TKUnit.assert(childTemplateView, "Expected the TemplateView's template to create a child TemplateView.");
+ childTemplateView.notify({
+ eventName: "test",
+ object: childTemplateView
+ });
-// TKUnit.assert(notified, "Expected the child to raise the test event.");
-// }
+ TKUnit.assert(notified, "Expected the child to raise the test event.");
+}
export function test_tabview_selectedindex_will_work_from_xml() {
var p = builder.parse(
diff --git a/tests/webpack.config.js b/tests/webpack.config.js
index 6a6925baa..c07a19285 100644
--- a/tests/webpack.config.js
+++ b/tests/webpack.config.js
@@ -249,7 +249,7 @@ module.exports = env => {
{ from: { glob: "**/*.jpg" } },
{ from: { glob: "**/*.png" } },
{ from: { glob: "file-system-access-tests/**/*.expected" } }, // NB: MODIFIED
- { from: { glob: "file-name-resolver-tests/files/*" } }, // NB: MODIFIED
+ { from: { glob: "name-resolvers-tests/files/*" } }, // NB: MODIFIED
{ from: { glob: "ui/web-view/*.html" } }, // NB: MODIFIED
], { ignore: [`${relative(appPath, appResourcesFullPath)}/**`] }),
new nsWebpack.GenerateNativeScriptEntryPointsPlugin("bundle"),
diff --git a/tns-core-modules/file-system/file-name-resolver/file-name-resolver.d.ts b/tns-core-modules/file-system/file-name-resolver/file-name-resolver.d.ts
index 8cbd7b0b4..c8488a95b 100644
--- a/tns-core-modules/file-system/file-name-resolver/file-name-resolver.d.ts
+++ b/tns-core-modules/file-system/file-name-resolver/file-name-resolver.d.ts
@@ -3,14 +3,10 @@
* @module "file-system/file-name-resolver"
*/ /** */
-export interface PlatformContext {
- width: number;
- height: number;
- os: string;
- deviceType: string;
-}
+ import { PlatformContext } from "../../module-name-resolver/qualifier-matcher";
+ export { PlatformContext } from "../../module-name-resolver/qualifier-matcher";
-export class FileNameResolver {
+ export class FileNameResolver {
constructor(context: PlatformContext);
resolveFileName(path: string, ext: string): string;
clearCache(): void;
@@ -18,4 +14,3 @@ export class FileNameResolver {
export function resolveFileName(path: string, ext: string): string;
export function clearCache(): void;
-export function _findFileMatch(path: string, ext: string, candidates: Array, context: PlatformContext): string;
diff --git a/tns-core-modules/file-system/file-name-resolver/file-name-resolver.ts b/tns-core-modules/file-system/file-name-resolver/file-name-resolver.ts
index f24082222..608e275d0 100644
--- a/tns-core-modules/file-system/file-name-resolver/file-name-resolver.ts
+++ b/tns-core-modules/file-system/file-name-resolver/file-name-resolver.ts
@@ -4,114 +4,16 @@ import { path as fsPath, Folder, File } from "../file-system";
import * as trace from "../../trace";
import * as appCommonModule from "../../application/application-common";
-const MIN_WH: string = "minWH";
-const MIN_W: string = "minW";
-const MIN_H: string = "minH";
-const PRIORITY_STEP = 10000;
-
-interface QualifierSpec {
- isMatch(value: string): boolean;
- getMatchValue(value: string, context: PlatformContext): number;
-}
-
-const minWidthHeightQualifier: QualifierSpec = {
- isMatch: function (value: string): boolean {
- return value.indexOf(MIN_WH) === 0;
-
- },
- getMatchValue(value: string, context: PlatformContext): number {
- const numVal = parseInt(value.substr(MIN_WH.length));
- if (isNaN(numVal)) {
- return -1;
- }
-
- const actualLength = Math.min(context.width, context.height);
- if (actualLength < numVal) {
- return -1;
- }
-
- return PRIORITY_STEP - (actualLength - numVal);
- }
-};
-
-const minWidthQualifier: QualifierSpec = {
- isMatch: function (value: string): boolean {
- return value.indexOf(MIN_W) === 0 && value.indexOf(MIN_WH) < 0;
-
- },
- getMatchValue(value: string, context: PlatformContext): number {
- const numVal = parseInt(value.substr(MIN_W.length));
- if (isNaN(numVal)) {
- return -1;
- }
-
- const actualWidth = context.width;
- if (actualWidth < numVal) {
- return -1;
- }
-
- return PRIORITY_STEP - (actualWidth - numVal);
- }
-};
-
-const minHeightQualifier: QualifierSpec = {
- isMatch: function (value: string): boolean {
- return value.indexOf(MIN_H) === 0 && value.indexOf(MIN_WH) < 0;
-
- },
- getMatchValue(value: string, context: PlatformContext): number {
- const numVal = parseInt(value.substr(MIN_H.length));
- if (isNaN(numVal)) {
- return -1;
- }
-
- const actualHeight = context.height;
- if (actualHeight < numVal) {
- return -1;
- }
-
- return PRIORITY_STEP - (actualHeight - numVal);
- }
-};
-
-const platformQualifier: QualifierSpec = {
- isMatch: function (value: string): boolean {
- return value === "android" ||
- value === "ios";
-
- },
- getMatchValue(value: string, context: PlatformContext): number {
- return value === context.os.toLowerCase() ? 1 : -1;
- }
-};
-
-const orientationQualifier: QualifierSpec = {
- isMatch: function (value: string): boolean {
- return value === "land" ||
- value === "port";
-
- },
- getMatchValue(value: string, context: PlatformContext): number {
- const isLandscape: number = (context.width > context.height) ? 1 : -1;
-
- return (value === "land") ? isLandscape : -isLandscape;
- }
-};
-
-// List of supported qualifiers ordered by priority
-const supportedQualifiers: Array = [
- minWidthHeightQualifier,
- minWidthQualifier,
- minHeightQualifier,
- orientationQualifier,
- platformQualifier
-];
+import { findMatch } from "../../module-name-resolver/qualifier-matcher/qualifier-matcher";
+@Deprecated
export class FileNameResolver implements FileNameResolverDefinition {
private _context: PlatformContext;
private _cache = {};
constructor(context: PlatformContext) {
+ console.log("FileNameResolver is deprecated; use ModuleNameResolver instead");
+
this._context = context;
}
@@ -136,7 +38,7 @@ export class FileNameResolver implements FileNameResolverDefinition {
ext = "." + ext;
const candidates = this.getFileCandidatesFromFolder(path, ext);
- result = _findFileMatch(path, ext, candidates, this._context);
+ result = findMatch(path, ext, candidates, this._context);
return result;
}
@@ -168,61 +70,6 @@ export class FileNameResolver implements FileNameResolverDefinition {
}
}
-export function _findFileMatch(path: string, ext: string, candidates: Array, context: PlatformContext): string {
- let bestValue = -1;
- let result: string = null;
-
- for (let i = 0; i < candidates.length; i++) {
- const filePath = candidates[i];
- const qualifiersStr: string = filePath.substr(path.length, filePath.length - path.length - ext.length);
-
- const qualifiers = qualifiersStr.split(".");
-
- const value = checkQualifiers(qualifiers, context);
-
- if (value >= 0 && value > bestValue) {
- bestValue = value;
- result = candidates[i];
- }
- }
-
- return result;
-}
-
-function checkQualifiers(qualifiers: Array, context: PlatformContext): number {
- let result = 0;
- let value: number;
- for (let i = 0; i < qualifiers.length; i++) {
- if (qualifiers[i]) {
- value = checkQualifier(qualifiers[i], context);
- if (value < 0) {
- // Non of the supported qualifiers matched this or the match was not satisified
- return -1;
- }
-
- result += value;
- }
- }
-
- return result;
-}
-
-function checkQualifier(value: string, context: PlatformContext) {
- let result: number;
- for (let i = 0; i < supportedQualifiers.length; i++) {
- if (supportedQualifiers[i].isMatch(value)) {
- result = supportedQualifiers[i].getMatchValue(value, context);
- if (result > 0) {
- result += (supportedQualifiers.length - i) * PRIORITY_STEP;
- }
-
- return result;
- }
- }
-
- return -1;
-}
-
let resolverInstance: FileNameResolver;
export function resolveFileName(path: string, ext: string): string {
@@ -237,6 +84,11 @@ export function resolveFileName(path: string, ext: string): string {
return resolverInstance.resolveFileName(path, ext);
}
+export function clearCache() {
+ if (resolverInstance) {
+ resolverInstance.clearCache();
+ }
+}
appCommonModule.on("cssChanged", args => resolverInstance = undefined);
-appCommonModule.on("livesync", args => resolverInstance && resolverInstance.clearCache());
\ No newline at end of file
+appCommonModule.on("livesync", args => clearCache());
diff --git a/tns-core-modules/globals/globals.ts b/tns-core-modules/globals/globals.ts
index e523063aa..1725e4c44 100644
--- a/tns-core-modules/globals/globals.ts
+++ b/tns-core-modules/globals/globals.ts
@@ -18,9 +18,15 @@ const modules: Map = new Map();
(global).moduleResolvers = [global.require];
global.registerModule = function (name: string, loader: ModuleLoader): void {
+ // console.log("[global.registerModule]", name);
modules.set(name, loader);
};
+global._unregisterModule = function (name: string): void {
+ // console.log("[global._unregisterModule]", name);
+ modules.delete(name);
+};
+
interface Context {
keys(): string[];
(key: string): any;
@@ -29,7 +35,16 @@ interface ExtensionMap {
[originalFileExtension: string]: string;
}
-const defaultExtensionMap = { ".js": ".js", ".ts": ".js", ".css": ".css", ".scss": ".css", ".xml": ".xml", ".less": ".css", ".sass": ".css" };
+const defaultExtensionMap = {
+ ".js": ".js",
+ ".ts": ".js",
+ ".css": ".css",
+ ".scss": ".css",
+ ".less": ".css",
+ ".sass": ".css",
+ ".xml": ".xml"
+};
+
global.registerWebpackModules = function registerWebpackModules(context: Context, extensionMap: ExtensionMap = {}) {
context.keys().forEach(key => {
const extDotIndex = key.lastIndexOf(".");
@@ -84,8 +99,9 @@ global.moduleExists = function (name: string): boolean {
global.loadModule = function (name: string): any {
const loader = modules.get(name);
if (loader) {
- return loader();
+ return loader(name);
}
+
for (let resolver of (global).moduleResolvers) {
const result = resolver(name);
if (result) {
@@ -96,6 +112,10 @@ global.loadModule = function (name: string): any {
}
};
+global.getRegisteredModules = function (): string[] {
+ return Array.from(modules.keys());
+};
+
global.zonedCallback = function (callback: Function): Function {
if ((global).zone) {
// Zone v0.5.* style callback wrapping
diff --git a/tns-core-modules/module-name-resolver/module-name-resolver.d.ts b/tns-core-modules/module-name-resolver/module-name-resolver.d.ts
new file mode 100644
index 000000000..170b47bbc
--- /dev/null
+++ b/tns-core-modules/module-name-resolver/module-name-resolver.d.ts
@@ -0,0 +1,20 @@
+/**
+ * Provides ModuleNameResolver class used for loading files based on device capabilities.
+ * @module "module-name-resolver"
+ */ /** */
+
+import { PlatformContext } from "./qualifier-matcher";
+export { PlatformContext } from "./qualifier-matcher";
+
+export type ModuleListProvider = () => string[];
+
+export class ModuleNameResolver {
+ constructor(context: PlatformContext, moduleListProvider?: ModuleListProvider);
+ resolveModuleName(path: string, ext: string): string;
+ clearCache(): void;
+}
+
+export function resolveModuleName(path: string, ext: string): string;
+export function clearCache(): void;
+
+export function _setResolver(resolver: ModuleNameResolver);
diff --git a/tns-core-modules/module-name-resolver/module-name-resolver.ts b/tns-core-modules/module-name-resolver/module-name-resolver.ts
new file mode 100644
index 000000000..2fd68863f
--- /dev/null
+++ b/tns-core-modules/module-name-resolver/module-name-resolver.ts
@@ -0,0 +1,70 @@
+import { ModuleNameResolver as ModuleNameResolverDefinition, ModuleListProvider } from "./";
+import { screen, device } from "../platform/platform";
+import * as appCommonModule from "../application/application-common";
+import { PlatformContext, findMatch } from "./qualifier-matcher";
+
+export class ModuleNameResolver implements ModuleNameResolverDefinition {
+ private _cache = {};
+
+ constructor(private context: PlatformContext, private moduleListProvider: ModuleListProvider = global.getRegisteredModules) {
+ }
+
+ public resolveModuleName(path: string, ext: string): string {
+ const key = path + ext;
+ let result: string = this._cache[key];
+ if (result === undefined) {
+ result = this.resolveModuleNameImpl(path, ext);
+ this._cache[key] = result;
+ }
+
+ return result;
+ }
+
+ public clearCache(): void {
+ this._cache = {};
+ }
+
+ private resolveModuleNameImpl(path: string, ext: string): string {
+ let result: string = null;
+ ext = ext ? "." + ext : "";
+
+ const candidates = this.getCandidates(path, ext);
+ result = findMatch(path, ext, candidates, this.context);
+
+ return result;
+ }
+
+ private getCandidates(path: string, ext: string): Array {
+ const candidates = this.moduleListProvider()
+ .filter((moduleName) => moduleName.startsWith(path) && (!ext || moduleName.endsWith(ext)));
+
+ return candidates;
+ }
+}
+
+let resolverInstance: ModuleNameResolver;
+
+export function resolveModuleName(path: string, ext: string): string {
+ if (!resolverInstance) {
+ resolverInstance = new ModuleNameResolver({
+ width: screen.mainScreen.widthDIPs,
+ height: screen.mainScreen.heightDIPs,
+ os: device.os,
+ deviceType: device.deviceType
+ });
+ }
+
+ return resolverInstance.resolveModuleName(path, ext);
+}
+
+export function clearCache() {
+ if (resolverInstance) {
+ resolverInstance.clearCache();
+ }
+}
+
+export function _setResolver(resolver: ModuleNameResolver) {
+ resolverInstance = resolver;
+}
+
+appCommonModule.on("livesync", args => clearCache());
diff --git a/tns-core-modules/module-name-resolver/package.json b/tns-core-modules/module-name-resolver/package.json
new file mode 100644
index 000000000..0fa0dbca8
--- /dev/null
+++ b/tns-core-modules/module-name-resolver/package.json
@@ -0,0 +1,5 @@
+{
+ "name" : "module-name-resolver",
+ "main" : "module-name-resolver",
+ "types" : "module-name-resolver.d.ts"
+}
diff --git a/tns-core-modules/module-name-resolver/qualifier-matcher/package.json b/tns-core-modules/module-name-resolver/qualifier-matcher/package.json
new file mode 100644
index 000000000..b837fdc6e
--- /dev/null
+++ b/tns-core-modules/module-name-resolver/qualifier-matcher/package.json
@@ -0,0 +1,5 @@
+{
+ "name" : "qualifier-matcher",
+ "main" : "qualifier-matcher",
+ "types" : "qualifier-matcher.d.ts"
+}
diff --git a/tns-core-modules/module-name-resolver/qualifier-matcher/qualifier-matcher.d.ts b/tns-core-modules/module-name-resolver/qualifier-matcher/qualifier-matcher.d.ts
new file mode 100644
index 000000000..46ff19d9f
--- /dev/null
+++ b/tns-core-modules/module-name-resolver/qualifier-matcher/qualifier-matcher.d.ts
@@ -0,0 +1,13 @@
+/**
+ * Provides ModuleNameResolver class used for loading files based on device capabilities.
+ * @module "module-name-resolver/qualifier-matcher"
+ */ /** */
+
+export interface PlatformContext {
+ width: number;
+ height: number;
+ os: string;
+ deviceType: string;
+}
+
+export function findMatch(path: string, ext: string, candidates: Array, context: PlatformContext): string;
diff --git a/tns-core-modules/module-name-resolver/qualifier-matcher/qualifier-matcher.ts b/tns-core-modules/module-name-resolver/qualifier-matcher/qualifier-matcher.ts
new file mode 100644
index 000000000..e2268c9dd
--- /dev/null
+++ b/tns-core-modules/module-name-resolver/qualifier-matcher/qualifier-matcher.ts
@@ -0,0 +1,158 @@
+import { PlatformContext } from "./";
+const MIN_WH: string = "minWH";
+const MIN_W: string = "minW";
+const MIN_H: string = "minH";
+const PRIORITY_STEP = 10000;
+
+interface QualifierSpec {
+ isMatch(value: string): boolean;
+ getMatchValue(value: string, context: PlatformContext): number;
+}
+
+const minWidthHeightQualifier: QualifierSpec = {
+ isMatch: function (value: string): boolean {
+ return value.indexOf(MIN_WH) === 0;
+
+ },
+ getMatchValue(value: string, context: PlatformContext): number {
+ const numVal = parseInt(value.substr(MIN_WH.length));
+ if (isNaN(numVal)) {
+ return -1;
+ }
+
+ const actualLength = Math.min(context.width, context.height);
+ if (actualLength < numVal) {
+ return -1;
+ }
+
+ return PRIORITY_STEP - (actualLength - numVal);
+ }
+};
+
+const minWidthQualifier: QualifierSpec = {
+ isMatch: function (value: string): boolean {
+ return value.indexOf(MIN_W) === 0 && value.indexOf(MIN_WH) < 0;
+
+ },
+ getMatchValue(value: string, context: PlatformContext): number {
+ const numVal = parseInt(value.substr(MIN_W.length));
+ if (isNaN(numVal)) {
+ return -1;
+ }
+
+ const actualWidth = context.width;
+ if (actualWidth < numVal) {
+ return -1;
+ }
+
+ return PRIORITY_STEP - (actualWidth - numVal);
+ }
+};
+
+const minHeightQualifier: QualifierSpec = {
+ isMatch: function (value: string): boolean {
+ return value.indexOf(MIN_H) === 0 && value.indexOf(MIN_WH) < 0;
+
+ },
+ getMatchValue(value: string, context: PlatformContext): number {
+ const numVal = parseInt(value.substr(MIN_H.length));
+ if (isNaN(numVal)) {
+ return -1;
+ }
+
+ const actualHeight = context.height;
+ if (actualHeight < numVal) {
+ return -1;
+ }
+
+ return PRIORITY_STEP - (actualHeight - numVal);
+ }
+};
+
+const platformQualifier: QualifierSpec = {
+ isMatch: function (value: string): boolean {
+ return value === "android" ||
+ value === "ios";
+
+ },
+ getMatchValue(value: string, context: PlatformContext): number {
+ return value === context.os.toLowerCase() ? 1 : -1;
+ }
+};
+
+const orientationQualifier: QualifierSpec = {
+ isMatch: function (value: string): boolean {
+ return value === "land" ||
+ value === "port";
+
+ },
+ getMatchValue(value: string, context: PlatformContext): number {
+ const isLandscape: number = (context.width > context.height) ? 1 : -1;
+
+ return (value === "land") ? isLandscape : -isLandscape;
+ }
+};
+
+// List of supported qualifiers ordered by priority
+const supportedQualifiers: Array = [
+ minWidthHeightQualifier,
+ minWidthQualifier,
+ minHeightQualifier,
+ orientationQualifier,
+ platformQualifier
+];
+
+function checkQualifiers(qualifiers: Array, context: PlatformContext): number {
+ let result = 0;
+ let value: number;
+ for (let i = 0; i < qualifiers.length; i++) {
+ if (qualifiers[i]) {
+ value = checkQualifier(qualifiers[i], context);
+ if (value < 0) {
+ // Non of the supported qualifiers matched this or the match was not satisfied
+ return -1;
+ }
+
+ result += value;
+ }
+ }
+
+ return result;
+}
+
+function checkQualifier(value: string, context: PlatformContext) {
+ let result: number;
+ for (let i = 0; i < supportedQualifiers.length; i++) {
+ if (supportedQualifiers[i].isMatch(value)) {
+ result = supportedQualifiers[i].getMatchValue(value, context);
+ if (result > 0) {
+ result += (supportedQualifiers.length - i) * PRIORITY_STEP;
+ }
+
+ return result;
+ }
+ }
+
+ return -1;
+}
+
+export function findMatch(path: string, ext: string, candidates: Array, context: PlatformContext): string {
+ let bestValue = -1;
+ let result: string = null;
+
+ for (let i = 0; i < candidates.length; i++) {
+ const filePath = candidates[i];
+ const qualifiersStr: string = filePath.substr(path.length, filePath.length - path.length - (ext ? ext.length : 0));
+
+ const qualifiers = qualifiersStr.split(".");
+
+ const value = checkQualifiers(qualifiers, context);
+
+ if (value >= 0 && value > bestValue) {
+ bestValue = value;
+ result = candidates[i];
+ }
+ }
+
+ return result;
+}
\ No newline at end of file
diff --git a/tns-core-modules/module.d.ts b/tns-core-modules/module.d.ts
index d94213fd0..5649d472f 100644
--- a/tns-core-modules/module.d.ts
+++ b/tns-core-modules/module.d.ts
@@ -15,6 +15,7 @@ declare namespace NodeJS {
android?: any;
require(id: string): any;
registerModule(name: string, loader: ((name: string) => any)): void;
+ _unregisterModule(name: string): void;
/**
* Register all modules from a webpack context.
* The context is one created using the following webpack utility:
@@ -45,6 +46,7 @@ declare namespace NodeJS {
loadModule(name: string): any;
moduleExists(name: string): boolean;
moduleMerge(sourceExports: any, destExports: any): void;
+ getRegisteredModules(): string[];
onGlobalLayoutListener: any;
zonedCallback(callback: Function): Function;
Reflect?: any;
diff --git a/tns-core-modules/ui/builder/builder.d.ts b/tns-core-modules/ui/builder/builder.d.ts
index f70c06a2d..d3e01402b 100644
--- a/tns-core-modules/ui/builder/builder.d.ts
+++ b/tns-core-modules/ui/builder/builder.d.ts
@@ -6,23 +6,6 @@ import { View, Template, KeyedTemplate } from "../core/view";
import { Page } from "../page";
import { NavigationEntry } from "../frame";
-//@private
-/**
- * @private
- */
-export function _loadPage(moduleNamePath: string, fileName: string, moduleExports?: any): Page;
-//@endprivate
-
-export function createViewFromEntry(entry: NavigationEntry): View;
-
-export function load(fileName: string, exports?: any): View;
-
-export function load(options: LoadOptions): View;
-
-export function parse(value: string | Template, exports?: any): View;
-
-export function parseMultipleTemplates(value: string, exports?: any): Array;
-
export interface LoadOptions {
path: string;
name: string;
@@ -30,3 +13,10 @@ export interface LoadOptions {
exports?: any;
page?: Page;
}
+
+export function createViewFromEntry(entry: NavigationEntry): View;
+export function parse(value: string | Template, exports?: any): View;
+export function parseMultipleTemplates(value: string, exports?: any): Array;
+
+export function load(fileName: string, exports?: any): View;
+export function load(options: LoadOptions): View;
diff --git a/tns-core-modules/ui/builder/builder.ts b/tns-core-modules/ui/builder/builder.ts
index b19ded134..6a391a0c0 100644
--- a/tns-core-modules/ui/builder/builder.ts
+++ b/tns-core-modules/ui/builder/builder.ts
@@ -2,30 +2,22 @@
import { LoadOptions } from ".";
import { View, ViewBase, Template, KeyedTemplate } from "../core/view";
import { ViewEntry } from "../frame";
-import * as traceModule from "../../trace";
// Types.
import { debug, ScopeError, SourceError, Source } from "../../utils/debug";
import * as xml from "../../xml";
-import { File, path, knownFolders } from "../../file-system";
import { isString, isDefined } from "../../utils/types";
import { ComponentModule, setPropertyValue, getComponentModule } from "./component-builder";
import { platformNames, device } from "../../platform";
-import { resolveFileName } from "../../file-system/file-name-resolver";
import { profile } from "../../profiling";
+import { sanitizeModuleName } from "./module-name-sanitizer";
+import { resolveModuleName } from "../../module-name-resolver";
const ios = platformNames.ios.toLowerCase();
const android = platformNames.android.toLowerCase();
const defaultNameSpaceMatcher = /tns\.xsd$/i;
-let trace: typeof traceModule;
-function ensureTrace() {
- if (!trace) {
- trace = require("trace");
- }
-}
-
export function parse(value: string | Template, context: any): View {
if (typeof value === "function") {
return (value)();
@@ -45,147 +37,77 @@ export function parseMultipleTemplates(value: string, context: any): ArraypathOrOptions;
- componentModule = loadInternal(path, context);
+ componentModule = loadCustomComponent(pathOrOptions.path, pathOrOptions.name, pathOrOptions.attributes, pathOrOptions.exports, pathOrOptions.page, true);
}
return componentModule && componentModule.component;
}
-export function _loadPage(moduleNamePath: string, fileName: string, context?: any): View {
- const componentModule = loadInternal(fileName, context, moduleNamePath);
- const componentView = componentModule && componentModule.component;
- if (componentView && moduleNamePath) {
- markAsModuleRoot(componentView, moduleNamePath);
- }
-
- return componentView;
-}
-
-const loadModule = profile("loadModule", (moduleNamePath: string, entry: ViewEntry): ModuleExports => {
- // web-pack case where developers register their page JS file manually.
- if (global.moduleExists(entry.moduleName)) {
- return global.loadModule(entry.moduleName);
- } else {
- let moduleExportsResolvedPath = resolveFileName(moduleNamePath, "js");
- if (moduleExportsResolvedPath) {
- // Exclude extension when doing require.
- moduleExportsResolvedPath = moduleExportsResolvedPath.substr(0, moduleExportsResolvedPath.length - 3);
-
- return global.loadModule(moduleExportsResolvedPath);
- }
- }
-
- return null;
-});
-
-const viewFromBuilder = profile("viewFromBuilder", (moduleNamePath: string, moduleExports: any): View => {
- // Possible XML file path.
- const fileName = resolveFileName(moduleNamePath, "xml");
-
- // Attempts to implement https://github.com/NativeScript/NativeScript/issues/1311
- // if (page && fileName === `${moduleNamePath}.port.xml` || fileName === `${moduleNamePath}.land.xml`){
- // page["isBiOrientational"] = true;
- // };
-
- return _loadPage(moduleNamePath, fileName, moduleExports);
-});
-
export const createViewFromEntry = profile("createViewFromEntry", (entry: ViewEntry): View => {
if (entry.create) {
- return createView(entry);
- } else if (entry.moduleName) {
- // Current app full path.
- const currentAppPath = knownFolders.currentApp().path;
+ const view = entry.create();
+ if (!view) {
+ throw new Error("Failed to create View with entry.create() function.");
+ }
- // Full path of the module = current app full path + module name.
- const moduleNamePath = path.join(currentAppPath, entry.moduleName);
- const moduleExports = loadModule(moduleNamePath, entry);
+ return view;
+ } else if (entry.moduleName) {
+ const resolvedCodeModuleName = resolveModuleName(entry.moduleName, ""); //`${moduleName}.xml`;
+ let moduleExports = resolvedCodeModuleName ? global.loadModule(resolvedCodeModuleName) : null;
if (moduleExports && moduleExports.createPage) {
- return moduleCreateView(entry.moduleName, moduleNamePath, moduleExports);
+ // Exports has a createPage() method
+ const view = moduleExports.createPage();
+ const resolvedCssModuleName = resolveModuleName(entry.moduleName, "css"); //entry.moduleName + ".css";
+ if (resolvedCssModuleName) {
+ view.addCssFile(resolvedCssModuleName);
+ }
+
+ return view;
} else {
- // cssFileName is loaded inside pageFromBuilder->loadPage
- return viewFromBuilder(moduleNamePath, moduleExports);
+ const componentModule = loadInternal(entry.moduleName, moduleExports);
+ const componentView = componentModule && componentModule.component;
+
+ return componentView;
}
}
throw new Error("Failed to load page XML file for module: " + entry.moduleName);
});
-const createView = profile("entry.create", (entry: ViewEntry): View => {
- const view = entry.create();
- if (!view) {
- throw new Error("Failed to create Page with entry.create() function.");
- }
-
- return view;
-});
-
-interface ModuleExports {
- createPage?: () => View;
-}
-
-const moduleCreateView = profile("module.createView", (moduleName: string, modulePath: string, moduleExports: ModuleExports): View => {
- const view = moduleExports.createPage();
-
- const cssModuleName = moduleName + ".css";
- if (global.moduleExists(cssModuleName)) {
- view.addCssFile(cssModuleName);
- } else {
- const cssFileName = resolveFileName(modulePath, "css");
- if (cssFileName) {
- view.addCssFile(cssFileName);
- }
- }
-
- return view;
-});
-
-function markAsModuleRoot(componentView: View, moduleNamePath: string): void {
- const lastIndexOfSeparator = moduleNamePath.lastIndexOf(path.separator);
- const moduleName = moduleNamePath.substring(lastIndexOfSeparator + 1);
- componentView._moduleName = moduleName;
-}
-
-function loadInternal(fileName: string, context?: any, moduleNamePath?: string): ComponentModule {
+function loadInternal(moduleName: string, moduleExports: any): ComponentModule {
let componentModule: ComponentModule;
- const appPath = knownFolders.currentApp().path;
- const filePathRelativeToApp = (moduleNamePath && moduleNamePath.startsWith(appPath) ? "./" + moduleNamePath.substr(appPath.length + 1) : moduleNamePath) + ".xml";
+ const resolvedXmlModule = resolveModuleName(moduleName, "xml");
- if (global.moduleExists(filePathRelativeToApp)) {
- const text = global.loadModule(filePathRelativeToApp);
- componentModule = parseInternal(text, context, fileName, moduleNamePath);
- } else if (global.moduleExists(fileName)) {
- const text = global.loadModule(fileName);
- componentModule = parseInternal(text, context, fileName, moduleNamePath);
- } else if (fileName && File.exists(fileName)) {
- const file = File.fromPath(fileName);
- const text = file.readTextSync((error) => { throw new Error("Error loading file " + fileName + " :" + error.message); });
- componentModule = parseInternal(text, context, fileName, moduleNamePath);
+ if (resolvedXmlModule) {
+ const text = global.loadModule(resolvedXmlModule);
+ componentModule = parseInternal(text, moduleExports, resolvedXmlModule, moduleName);
}
- if (componentModule && componentModule.component) {
+ const componentView = componentModule && componentModule.component;
+ if (componentView) {
// Save exports to root component (will be used for templates).
- (componentModule.component).exports = context;
+ (componentView).exports = moduleExports;
+
+ // Save _moduleName - used for livesync
+ componentView._moduleName = moduleName;
}
if (!componentModule) {
- throw new Error("Failed to load component from module: " + filePathRelativeToApp + " or file: " + fileName);
+ throw new Error("Failed to load component from module: " + moduleName);
}
return componentModule;
+
}
-function loadCustomComponent(componentPath: string, componentName?: string, attributes?: Object, context?: Object, parentPage?: View, isRootComponent: boolean = true, moduleNamePath?: string): ComponentModule {
+function loadCustomComponent(componentNamespace: string, componentName?: string, attributes?: Object, context?: Object, parentPage?: View, isRootComponent: boolean = true, moduleNamePath?: string): ComponentModule {
if (!parentPage && context) {
// Read the parent page that was passed down below
// https://github.com/NativeScript/NativeScript/issues/1639
@@ -194,30 +116,19 @@ function loadCustomComponent(componentPath: string, componentName?: string, attr
}
let result: ComponentModule;
- componentPath = componentPath.replace("~/", "");
- const moduleName = `${componentPath}/${componentName}`;
- const xmlModuleName = `${moduleName}.xml`;
- let fullComponentPathFilePathWithoutExt = componentPath;
+ componentNamespace = sanitizeModuleName(componentNamespace);
+ const moduleName = `${componentNamespace}/${componentName}`;
+ const resolvedCodeModuleName = resolveModuleName(moduleName, "");
+ const resolvedXmlModuleName = resolveModuleName(moduleName, "xml");
+ let resolvedCssModuleName = resolveModuleName(moduleName, "css");
- if (!File.exists(componentPath) || componentPath === "." || componentPath === "./") {
- fullComponentPathFilePathWithoutExt = path.join(knownFolders.currentApp().path, componentPath, componentName);
- }
-
- const xmlFilePath = resolveFileName(fullComponentPathFilePathWithoutExt, "xml");
- if (xmlFilePath || global.moduleExists(xmlModuleName)) {
+ if (resolvedXmlModuleName) {
// Custom components with XML
-
let subExports = context;
- if (global.moduleExists(moduleName)) {
+ if (resolvedCodeModuleName) {
// Component has registered code module.
- subExports = global.loadModule(moduleName);
- } else {
- const jsFilePath = resolveFileName(fullComponentPathFilePathWithoutExt, "js");
- if (jsFilePath) {
- // Component has code file.
- subExports = global.loadModule(jsFilePath);
- }
+ subExports = global.loadModule(resolvedCodeModuleName);
}
// Pass the parent page down the chain in case of custom components nested on many levels. Use the context for piggybacking.
@@ -228,11 +139,9 @@ function loadCustomComponent(componentPath: string, componentName?: string, attr
subExports["_parentPage"] = parentPage;
- result = xmlFilePath ?
- loadInternal(xmlFilePath, subExports) :
- loadInternal(xmlFilePath, subExports, moduleName);
+ result = loadInternal(moduleName, subExports);
- // Attributes will be transfered to the custom component
+ // Attributes will be transferred to the custom component
if (isDefined(result) && isDefined(result.component) && isDefined(attributes)) {
for (let attr in attributes) {
setPropertyValue(result.component, subExports, context, attr, attributes[attr]);
@@ -240,32 +149,19 @@ function loadCustomComponent(componentPath: string, componentName?: string, attr
}
} else {
// Custom components without XML
- result = getComponentModule(componentName, componentPath, attributes, context, moduleNamePath, isRootComponent);
- }
+ result = getComponentModule(componentName, componentNamespace, attributes, context, moduleNamePath, isRootComponent);
- // webpack modules require paths to be relative to /app folder.
- let cssModulePath = fullComponentPathFilePathWithoutExt + ".css";
- if (cssModulePath.startsWith("/")) {
- const app = knownFolders.currentApp().path + "/";
- if (cssModulePath.startsWith(app)) {
- cssModulePath = "./" + cssModulePath.substr(app.length);
+ // The namespace is the JS module and the (componentName is the name of the class in the module)
+ // So if there is no componentNamespace/componentName.{qualifiers}.css we should also look for
+ // componentNamespace.{qualifiers}.css
+ if (!resolvedCssModuleName) {
+ resolvedCssModuleName = resolveModuleName(componentNamespace, "css");
}
}
// Add CSS from webpack module if exists.
- if (global.moduleExists(cssModulePath)) {
- (parentPage).addCssFile(cssModulePath);
- } else {
- const cssFilePath = resolveFileName(fullComponentPathFilePathWithoutExt, "css");
- // Add component CSS file if exists.
- if (cssFilePath) {
- if (parentPage && typeof (parentPage).addCssFile === "function") {
- (parentPage).addCssFile(cssFilePath);
- } else {
- ensureTrace();
- trace.write("CSS file found but no page specified. Please specify page in the options!", trace.categories.Error, trace.messageType.error);
- }
- }
+ if (parentPage && resolvedCssModuleName) {
+ parentPage.addCssFile(resolvedCssModuleName);
}
return result;
@@ -287,18 +183,18 @@ function getExports(instance: ViewBase): any {
return exportObject;
}
-function parseInternal(value: string, context: any, uri?: string, moduleNamePath?: string): ComponentModule {
+function parseInternal(value: string, context: any, xmlModule?: string, moduleName?: string): ComponentModule {
let start: xml2ui.XmlStringParser;
let ui: xml2ui.ComponentParser;
- const errorFormat = (debug && uri) ? xml2ui.SourceErrorFormat(uri) : xml2ui.PositionErrorFormat;
- const componentSourceTracker = (debug && uri) ? xml2ui.ComponentSourceTracker(uri) : () => {
+ const errorFormat = (debug && xmlModule) ? xml2ui.SourceErrorFormat(xmlModule) : xml2ui.PositionErrorFormat;
+ const componentSourceTracker = (debug && xmlModule) ? xml2ui.ComponentSourceTracker(xmlModule) : () => {
// no-op
};
(start = new xml2ui.XmlStringParser(errorFormat))
.pipe(new xml2ui.PlatformFilter())
- .pipe(new xml2ui.XmlStateParser(ui = new xml2ui.ComponentParser(context, errorFormat, componentSourceTracker, moduleNamePath)));
+ .pipe(new xml2ui.XmlStateParser(ui = new xml2ui.ComponentParser(context, errorFormat, componentSourceTracker, moduleName)));
start.parse(value);
@@ -634,7 +530,7 @@ namespace xml2ui {
private error: ErrorFormatter;
private sourceTracker: SourceTracker;
- constructor(context: any, errorFormat: ErrorFormatter, sourceTracker: SourceTracker, private moduleNamePath?: string) {
+ constructor(context: any, errorFormat: ErrorFormatter, sourceTracker: SourceTracker, private moduleName?: string) {
this.context = context;
this.error = errorFormat;
this.sourceTracker = sourceTracker;
@@ -644,7 +540,7 @@ namespace xml2ui {
private buildComponent(args: xml.ParserEvent): ComponentModule {
if (args.prefix && args.namespace) {
// Custom components
- return loadCustomComponent(args.namespace, args.elementName, args.attributes, this.context, this.currentRootView, !this.currentRootView, this.moduleNamePath);
+ return loadCustomComponent(args.namespace, args.elementName, args.attributes, this.context, this.currentRootView, !this.currentRootView, this.moduleName);
} else {
// Default components
let namespace = args.namespace;
@@ -653,7 +549,7 @@ namespace xml2ui {
namespace = undefined;
}
- return getComponentModule(args.elementName, namespace, args.attributes, this.context, this.moduleNamePath, !this.currentRootView);
+ return getComponentModule(args.elementName, namespace, args.attributes, this.context, this.moduleName, !this.currentRootView);
}
}
diff --git a/tns-core-modules/ui/builder/component-builder/component-builder.d.ts b/tns-core-modules/ui/builder/component-builder/component-builder.d.ts
index 51ef44e70..9f9855c1d 100644
--- a/tns-core-modules/ui/builder/component-builder/component-builder.d.ts
+++ b/tns-core-modules/ui/builder/component-builder/component-builder.d.ts
@@ -4,10 +4,10 @@
import { View } from "../../core/view";
-export function getComponentModule(elementName: string, namespace: string, attributes: Object, exports: Object, moduleNamePath?: string, isRootComponent?: boolean): ComponentModule;
-export function setPropertyValue(instance: View, instanceModuleExports: Object, pageExports: Object, propertyName: string, propertyValue: any): void;
-
export interface ComponentModule {
component: View;
exports: any;
}
+
+export function getComponentModule(elementName: string, namespace: string, attributes: Object, exports: Object, moduleNamePath?: string, isRootComponent?: boolean): ComponentModule;
+export function setPropertyValue(instance: View, instanceModuleExports: Object, pageExports: Object, propertyName: string, propertyValue: any): void;
diff --git a/tns-core-modules/ui/builder/component-builder/component-builder.ts b/tns-core-modules/ui/builder/component-builder/component-builder.ts
index 9aaef2f58..7e04c16d5 100644
--- a/tns-core-modules/ui/builder/component-builder/component-builder.ts
+++ b/tns-core-modules/ui/builder/component-builder/component-builder.ts
@@ -1,17 +1,15 @@
-// Deifinitions.
+// Definitions.
import { ComponentModule } from ".";
import { View } from "../../core/view";
// Types.
import { isEventOrGesture } from "../../core/bindable";
-import { File, path, knownFolders } from "../../../file-system";
import { getBindingOptions, bindingConstants } from "../binding-builder";
-import { resolveFileName } from "../../../file-system/file-name-resolver";
import { profile } from "../../../profiling";
import * as debugModule from "../../../utils/debug";
import * as platform from "../../../platform";
-
-import * as filesystem from "../../../file-system";
+import { sanitizeModuleName } from "../module-name-sanitizer";
+import { resolveModuleName } from "../../../module-name-resolver";
const UI_PATH = "ui/";
const MODULES = {
@@ -26,40 +24,26 @@ const MODULES = {
"SegmentedBarItem": "ui/segmented-bar",
};
-const CODEFILE = "codeFile";
-const CSSFILE = "cssFile";
+const CODE_FILE = "codeFile";
+const CSS_FILE = "cssFile";
const IMPORT = "import";
const createComponentInstance = profile("createComponentInstance", (elementName: string, namespace: string): { instance: View, instanceModule: Object } => {
let instance: View;
let instanceModule: Object;
// Get module id.
- let moduleId = MODULES[elementName] || UI_PATH +
- (elementName.toLowerCase().indexOf("layout") !== -1 ? "layouts/" : "") +
- elementName.split(/(?=[A-Z])/).join("-").toLowerCase();
-
+ let resolvedModuleName;
try {
if (typeof namespace === "string") {
- if (global.moduleExists(namespace)) {
- moduleId = namespace;
- } else {
- const pathInsideTNSModules = path.join(knownFolders.currentApp().path, "tns_modules", namespace);
-
- try {
- // module inside tns_modules
- instanceModule = global.require(pathInsideTNSModules);
- moduleId = pathInsideTNSModules;
- } catch (e) {
- // module at root level in the app.
- moduleId = path.join(knownFolders.currentApp().path, namespace);
- }
- }
+ resolvedModuleName = resolveModuleName(namespace, "");
+ } else {
+ // load module from tns-core-modules/ui or mapped paths
+ resolvedModuleName = MODULES[elementName] || UI_PATH +
+ (elementName.toLowerCase().indexOf("layout") !== -1 ? "layouts/" : "") +
+ elementName.split(/(?=[A-Z])/).join("-").toLowerCase();
}
- if (!instanceModule) {
- // Require module by module id.
- instanceModule = global.loadModule(moduleId);
- }
+ instanceModule = global.loadModule(resolvedModuleName);
// Get the component type from module.
const instanceType = instanceModule[elementName] || Object;
@@ -68,7 +52,7 @@ const createComponentInstance = profile("createComponentInstance", (elementName:
instance = new instanceType();
} catch (ex) {
const debug: typeof debugModule = require("utils/debug");
- throw new debug.ScopeError(ex, "Module '" + moduleId + "' not found for element '" + (namespace ? namespace + ":" : "") + elementName + "'.");
+ throw new debug.ScopeError(ex, "Module '" + resolvedModuleName + "' not found for element '" + (namespace ? namespace + ":" : "") + elementName + "'.");
}
return { instance, instanceModule };
@@ -76,29 +60,14 @@ const createComponentInstance = profile("createComponentInstance", (elementName:
const getComponentModuleExports = profile("getComponentModuleExports", (instance: View, moduleExports: Object, attributes: Object): Object => {
if (attributes) {
- if (attributes[IMPORT]) {
- let importPath = attributes[IMPORT].trim();
-
- if (importPath.indexOf("~/") === 0) {
- importPath = path.join(knownFolders.currentApp().path, importPath.replace("~/", ""));
- }
-
- moduleExports = global.loadModule(importPath);
- (instance).exports = moduleExports;
- }
-
- if (attributes[CODEFILE]) {
- let codeFilePath = attributes[CODEFILE].trim();
- if (codeFilePath.indexOf("~/") === 0) {
- codeFilePath = path.join(knownFolders.currentApp().path, codeFilePath.replace("~/", ""));
- }
-
- const codeFilePathWithExt = codeFilePath.indexOf(".js") !== -1 ? codeFilePath : `${codeFilePath}.js`;
- if (File.exists(codeFilePathWithExt)) {
- moduleExports = global.loadModule(codeFilePath);
+ const codeFileAttribute = attributes[CODE_FILE] || attributes[IMPORT];
+ if (codeFileAttribute) {
+ const resolvedCodeFileModule = resolveModuleName(sanitizeModuleName(codeFileAttribute), "");
+ if (resolvedCodeFileModule) {
+ moduleExports = global.loadModule(resolvedCodeFileModule);
(instance).exports = moduleExports;
} else {
- throw new Error(`Code file with path "${codeFilePathWithExt}" cannot be found!`);
+ throw new Error(`Code file with path "${codeFileAttribute}" cannot be found! Looking for webpack module with name "${resolvedCodeFileModule}"`);
}
}
}
@@ -106,37 +75,24 @@ const getComponentModuleExports = profile("getComponentModuleExports", (instance
return moduleExports;
});
-const applyComponentCss = profile("applyComponentCss", (instance: View, moduleNamePath: string, attributes: Object) => {
+const applyComponentCss = profile("applyComponentCss", (instance: View, moduleName: string, attributes: Object) => {
let cssApplied = false;
- if (attributes) {
- if (attributes[CSSFILE] && typeof (instance).addCssFile === "function") {
- let cssFilePath = attributes[CSSFILE].trim();
- if (cssFilePath.indexOf("~/") === 0) {
- cssFilePath = path.join(knownFolders.currentApp().path, cssFilePath.replace("~/", ""));
- }
- if (File.exists(cssFilePath)) {
- (instance).addCssFile(cssFilePath);
- cssApplied = true;
- } else {
- throw new Error(`Css file with path "${cssFilePath}" cannot be found!`);
- }
+
+ if (attributes && attributes[CSS_FILE]) {
+ let resolvedCssModuleName = resolveModuleName(sanitizeModuleName(attributes[CSS_FILE]), "css");
+
+ if (resolvedCssModuleName) {
+ instance.addCssFile(resolvedCssModuleName);
+ cssApplied = true;
+ } else {
+ throw new Error(`Css file with path "${attributes[CSS_FILE]}" cannot be found! Looking for webpack module with name "${resolvedCssModuleName}"`);
}
}
- if (typeof (instance).addCssFile === "function") {//instance instanceof Page) {
- if (moduleNamePath && !cssApplied) {
-
- const appPath = filesystem.knownFolders.currentApp().path;
- const cssPathRelativeToApp = (moduleNamePath.startsWith(appPath) ? "./" + moduleNamePath.substr(appPath.length + 1) : moduleNamePath) + ".css";
- if (global.moduleExists(cssPathRelativeToApp)) {
- (instance).addCssFile(cssPathRelativeToApp);
- }
-
- let cssFilePath = resolveFileName(moduleNamePath, "css");
- if (cssFilePath) {
- (instance).addCssFile(cssFilePath);
- cssApplied = true;
- }
+ if (moduleName && !cssApplied) {
+ let resolvedCssModuleName = resolveModuleName(moduleName, "css");
+ if (resolvedCssModuleName) {
+ instance.addCssFile(resolvedCssModuleName);
}
}
});
@@ -199,7 +155,7 @@ export function getComponentModule(elementName: string, namespace: string, attri
}
export function setPropertyValue(instance: View, instanceModule: Object, exports: Object, propertyName: string, propertyValue: any) {
- // Note: instanceModule can be null if we are loading custom compnenet with no code-behind.
+ // Note: instanceModule can be null if we are loading custom component with no code-behind.
if (isBinding(propertyValue) && instance.bind) {
const bindOptions = getBindingOptions(propertyName, getBindingExpressionFromAttribute(propertyValue));
instance.bind({
diff --git a/tns-core-modules/ui/builder/module-name-sanitizer.d.ts b/tns-core-modules/ui/builder/module-name-sanitizer.d.ts
new file mode 100644
index 000000000..725b3d57b
--- /dev/null
+++ b/tns-core-modules/ui/builder/module-name-sanitizer.d.ts
@@ -0,0 +1,5 @@
+/**
+ * @module "ui/builder/module-name-sanitizer"
+ */ /** */
+
+ export function sanitizeModuleName(moduleName: string, removeExtension: boolean): string;
diff --git a/tns-core-modules/ui/builder/module-name-sanitizer.ts b/tns-core-modules/ui/builder/module-name-sanitizer.ts
new file mode 100644
index 000000000..c3e777abd
--- /dev/null
+++ b/tns-core-modules/ui/builder/module-name-sanitizer.ts
@@ -0,0 +1,18 @@
+export function sanitizeModuleName(moduleName: string, removeExtension: boolean = true): string {
+ moduleName = moduleName.trim();
+
+ if (moduleName.startsWith("~/")) {
+ moduleName = moduleName.substring(2);
+ } else if (moduleName.startsWith("/")) {
+ moduleName = moduleName.substring(1);
+ }
+
+ if (removeExtension) {
+ const lastDot = moduleName.lastIndexOf(".");
+ if (lastDot > 0) {
+ moduleName = moduleName.substr(0, lastDot);
+ }
+ }
+
+ return moduleName;
+}
\ No newline at end of file
diff --git a/tns-core-modules/ui/core/view/view-common.ts b/tns-core-modules/ui/core/view/view-common.ts
index 74c70785e..791a8c4af 100644
--- a/tns-core-modules/ui/core/view/view-common.ts
+++ b/tns-core-modules/ui/core/view/view-common.ts
@@ -21,6 +21,7 @@ import {
} from "../../gestures";
import { createViewFromEntry } from "../../builder";
+import { sanitizeModuleName } from "../../builder/module-name-sanitizer";
import { StyleScope } from "../../styling/style-scope";
import { LinearGradient } from "../../styling/linear-gradient";
import { BackgroundRepeat } from "../../styling/style-properties";
@@ -183,7 +184,10 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
traceWrite(`Change Handled: Changing CSS for ${this}`, traceCategories.Livesync);
}
- this.changeCssFile(context.path);
+ // Always load styles with ".css" extension. Even when changes are in ".scss" ot ".less" files
+ const cssModuleName = `${sanitizeModuleName(context.path)}.css`;
+
+ this.changeCssFile(cssModuleName);
return true;
}
diff --git a/tns-core-modules/ui/utils.android.ts b/tns-core-modules/ui/utils.android.ts
new file mode 100644
index 000000000..b253eba9e
--- /dev/null
+++ b/tns-core-modules/ui/utils.android.ts
@@ -0,0 +1,13 @@
+export module ios {
+ export function getActualHeight(view: any): number {
+ throw new Error("Not implemented for Android");
+ }
+
+ export function getStatusBarHeight(viewController?: any): number {
+ throw new Error("Not implemented for Android");
+ }
+
+ export function _layoutRootView(rootView: any, parentBounds: any) {
+ throw new Error("Not implemented for Android");
+ }
+}
\ No newline at end of file