diff --git a/.gitignore b/.gitignore index a854d346598..b402969b355 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,6 @@ coverage/ tests/diffs/ tests/generated/ -# in-memory blobs written to disk as the expected image for image compariosn tests -tests/Resources/ios/snapshots/1[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9].png -tests/Resources/android/snapshots/1[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9].png +# in-memory blobs written to disk as the expected image for image comparison tests +tests/Resources/ios/snapshots/1[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]_*.png +tests/Resources/android/snapshots/1[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]_*.png diff --git a/Jenkinsfile b/Jenkinsfile index 86ab2bdfda8..bd4ec0b3a7b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -10,15 +10,17 @@ def isMainlineBranch = (env.BRANCH_NAME ==~ MAINLINE_BRANCH_REGEXP) def buildProperties = [buildDiscarder(logRotator(numToKeepStr: '30', artifactNumToKeepStr: '3'))] // For mainline branches, notify Teams channel of failures/success/not built/etc if (isMainlineBranch) { - buildProperties << office365ConnectorWebhooks([[ - notifyBackToNormal: true, - notifyFailure: true, - notifyNotBuilt: true, - notifyUnstable: true, - notifySuccess: true, - notifyRepeatedFailure: true, - url: 'https://outlook.office.com/webhook/ba1960f7-fcca-4b2c-a5f3-095ff9c87b22@300f59df-78e6-436f-9b27-b64973e34f7d/JenkinsCI/95439e5a0bef45539af8023b563dd345/72931ee3-e99d-4daf-84d2-1427168af2d9' - ]]) + withCredentials([string(credentialsId: 'titanium_mobile_ms_teams_webhook', variable: 'WEBHOOK_URL')]) { + buildProperties << office365ConnectorWebhooks([[ + notifyBackToNormal: true, + notifyFailure: true, + notifyNotBuilt: true, + notifyUnstable: true, + notifySuccess: true, + notifyRepeatedFailure: true, + url: "${WEBHOOK_URL}" + ]]) + } } properties(buildProperties) diff --git a/android/app/src/main/assets/Resources/app.js b/android/app/src/main/assets/Resources/app.js index b1a307f07a4..d7076400235 100644 --- a/android/app/src/main/assets/Resources/app.js +++ b/android/app/src/main/assets/Resources/app.js @@ -1,25 +1,25 @@ 'use strict'; // this sets the background color of the master UIView (when there are no windows/tab groups on it) -Titanium.UI.setBackgroundColor('#000'); +Titanium.UI.backgroundColor = '#000'; // create tab group -var tabGroup = Titanium.UI.createTabGroup(); +const tabGroup = Titanium.UI.createTabGroup(); // // create base UI tab and root window // -var win1 = Titanium.UI.createWindow({ +const win1 = Titanium.UI.createWindow({ title: 'Tab 1', backgroundColor: '#fff' }); -var tab1 = Titanium.UI.createTab({ +const tab1 = Titanium.UI.createTab({ icon: 'KS_nav_views.png', title: 'Tab 1', window: win1 }); -var label1 = Titanium.UI.createLabel({ +const label1 = Titanium.UI.createLabel({ color: '#999', text: 'I am Window 1', font: { fontSize: 20, fontFamily: 'Helvetica Neue' }, @@ -32,11 +32,11 @@ win1.add(label1); // // create controls tab and root window // -var win2 = Titanium.UI.createWindow({ +const win2 = Titanium.UI.createWindow({ title: 'Tab 2', backgroundColor: '#fff' }); -var tab2 = Titanium.UI.createTab({ +const tab2 = Titanium.UI.createTab({ icon: 'KS_nav_ui.png', title: 'Tab 2', window: win2 diff --git a/android/kroll-apt/build.gradle b/android/kroll-apt/build.gradle index 8fb4524f749..0c1c92864a3 100644 --- a/android/kroll-apt/build.gradle +++ b/android/kroll-apt/build.gradle @@ -72,6 +72,6 @@ jar { dependencies { implementation 'com.googlecode.json-simple:json-simple:1.1' - implementation 'org.freemarker:freemarker:2.3.14' + implementation 'org.freemarker:freemarker:2.3.30' implementation 'log4j:log4j:1.2.17' } diff --git a/android/kroll-apt/lib/freemarker.jar b/android/kroll-apt/lib/freemarker.jar deleted file mode 100755 index 968c1d31494..00000000000 Binary files a/android/kroll-apt/lib/freemarker.jar and /dev/null differ diff --git a/android/kroll-apt/lib/json_simple-1.1.jar b/android/kroll-apt/lib/json_simple-1.1.jar deleted file mode 100644 index f395f41471a..00000000000 Binary files a/android/kroll-apt/lib/json_simple-1.1.jar and /dev/null differ diff --git a/android/kroll-apt/src/main/resources/org/appcelerator/kroll/annotations/generator/ProxyBinding.fm b/android/kroll-apt/src/main/resources/org/appcelerator/kroll/annotations/generator/ProxyBinding.fm index 945a7ca9681..ad3389b05d5 100644 --- a/android/kroll-apt/src/main/resources/org/appcelerator/kroll/annotations/generator/ProxyBinding.fm +++ b/android/kroll-apt/src/main/resources/org/appcelerator/kroll/annotations/generator/ProxyBinding.fm @@ -217,6 +217,18 @@ namespace ${ns?lower_case} { <#assign typeInfo = { + "org.appcelerator.kroll.KrollPromise":{ + "jsType":"Object", + "jsConvertType":"Value", + "jsToJavaConverter":"jsObjectToJavaPromise", + "javaToJsConverter":"javaObjectToJsPromise", + "jvalue":"l", + "javaCallMethodType":"Object", + "javaReturnType":"jobject", + "javaValidation":true, + "typeValidation":false, + "defaultValue": "null" + }, "org.appcelerator.kroll.KrollProxy":{ "jsType":"Object", "jsConvertType":"Value", @@ -436,6 +448,10 @@ namespace ${ns?lower_case} { <#return typeInfo[type]> + <#if type?starts_with("org.appcelerator.kroll.KrollPromise")> + <#return typeInfo["org.appcelerator.kroll.KrollPromise"]> + + <#if type?ends_with("Proxy")> <#return typeInfo["org.appcelerator.kroll.KrollProxy"]> @@ -458,6 +474,10 @@ namespace ${ns?lower_case} { <#-- Look up the type's signature. If it does not exist, this type is a Java object. Object signatures take a special format which has the full class name in it (ex: Ljava.lang.Object;) --> + <#if type?starts_with("org.appcelerator.kroll.KrollPromise")> + <#local type = "org.appcelerator.kroll.KrollPromise"> + + <#-- FIXME: We should ideally have a general case check for removing generic type erasures from signatures here --> <#if typeInfo?keys?seq_contains(type) && typeInfo[type]?keys?seq_contains("signature")> <#return typeInfo[type].signature> <#else> @@ -643,7 +663,6 @@ fails, a JS exception is returned. <@Proxy.listMethodArguments args=args ; index, info, type, isOptional> - <#if type != "org.appcelerator.kroll.KrollInvocation"> <#if index = varArgsIndex> <@Proxy.convertToVarArgs args=args start=index/> @@ -697,11 +716,10 @@ beprovided to handle the result. <#if hasResult> ${resultExpr}env->${callExpr}(${argExpr}); - <#local jsType = info.jsType> - <#if info?keys?seq_contains("jsConvertType")> - <#local jsType = info.jsConvertType> - - + <#local jsType = info.jsType> + <#if info?keys?seq_contains("jsConvertType")> + <#local jsType = info.jsConvertType> + <#else> env->${callExpr}(${argExpr}); diff --git a/android/modules/android/src/java/ti/modules/titanium/android/AndroidModule.java b/android/modules/android/src/java/ti/modules/titanium/android/AndroidModule.java index 646dcdb69fd..77513809bc5 100644 --- a/android/modules/android/src/java/ti/modules/titanium/android/AndroidModule.java +++ b/android/modules/android/src/java/ti/modules/titanium/android/AndroidModule.java @@ -17,6 +17,8 @@ import org.appcelerator.kroll.KrollDict; import org.appcelerator.kroll.KrollFunction; import org.appcelerator.kroll.KrollModule; +import org.appcelerator.kroll.KrollObject; +import org.appcelerator.kroll.KrollPromise; import org.appcelerator.kroll.KrollRuntime; import org.appcelerator.kroll.annotations.Kroll; import org.appcelerator.kroll.common.Log; @@ -684,40 +686,48 @@ public boolean hasPermission(Object permissionObject) } @Kroll.method - public void requestPermissions(Object permissionObject, - @Kroll.argument(optional = true) KrollFunction permissionCallback) + public KrollPromise requestPermissions(final Object permissionObject, + @Kroll.argument(optional = true) final KrollFunction permissionCallback) { - if (Build.VERSION.SDK_INT >= 23) { - ArrayList permissions = new ArrayList(); - if (permissionObject instanceof String) { - permissions.add((String) permissionObject); - } else if (permissionObject instanceof Object[]) { - for (Object permission : (Object[]) permissionObject) { - if (permission instanceof String) { - permissions.add((String) permission); + // TODO: Create a subclass of Promise that takes in KrollFunction callback and "this" KrollObject + // to fire the callback when we resolve/reject? + final KrollObject callbackThisObject = getKrollObject(); + return KrollPromise.create((promise) -> { + if (Build.VERSION.SDK_INT >= 23) { + List permissions = new ArrayList(); + if (permissionObject instanceof String) { + permissions.add((String) permissionObject); + } else if (permissionObject instanceof Object[]) { + for (Object permission : (Object[]) permissionObject) { + if (permission instanceof String) { + permissions.add((String) permission); + } } } - } - Activity currentActivity = TiApplication.getInstance().getCurrentActivity(); - ArrayList filteredPermissions = new ArrayList(); - for (String permission : permissions) { - if (currentActivity.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) { - continue; + Activity currentActivity = TiApplication.getInstance().getCurrentActivity(); + List filteredPermissions = new ArrayList(); + for (String permission : permissions) { + if (currentActivity.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) { + continue; + } + filteredPermissions.add(permission); + } + if (filteredPermissions.size() > 0) { + TiBaseActivity.registerPermissionRequestCallback(REQUEST_CODE, permissionCallback, + callbackThisObject, promise); + currentActivity.requestPermissions(filteredPermissions.toArray( + new String[filteredPermissions.size()]), REQUEST_CODE); + return; } - filteredPermissions.add(permission); } - if (filteredPermissions.size() > 0) { - TiBaseActivity.registerPermissionRequestCallback(REQUEST_CODE, permissionCallback, getKrollObject()); - currentActivity.requestPermissions(filteredPermissions.toArray(new String[filteredPermissions.size()]), - REQUEST_CODE); - return; + // FIXME: If we're not on API level 23+, shouldn't we reject/error? + KrollDict response = new KrollDict(); + response.putCodeAndMessage(0, null); + if (permissionCallback != null) { + permissionCallback.callAsync(callbackThisObject, response); } - } - KrollDict response = new KrollDict(); - response.putCodeAndMessage(0, null); - if (permissionCallback != null) { - permissionCallback.callAsync(getKrollObject(), response); - } + promise.resolve(response); + }); } @Kroll.method diff --git a/android/modules/calendar/src/java/ti/modules/titanium/calendar/CalendarModule.java b/android/modules/calendar/src/java/ti/modules/titanium/calendar/CalendarModule.java index 8582fac3523..22800f6e14c 100644 --- a/android/modules/calendar/src/java/ti/modules/titanium/calendar/CalendarModule.java +++ b/android/modules/calendar/src/java/ti/modules/titanium/calendar/CalendarModule.java @@ -14,6 +14,8 @@ import org.appcelerator.kroll.KrollDict; import org.appcelerator.kroll.KrollFunction; import org.appcelerator.kroll.KrollModule; +import org.appcelerator.kroll.KrollObject; +import org.appcelerator.kroll.KrollPromise; import org.appcelerator.kroll.annotations.Kroll; import org.appcelerator.titanium.TiApplication; import org.appcelerator.titanium.TiBaseActivity; @@ -126,21 +128,26 @@ public boolean hasCalendarPermissions() } @Kroll.method - public void requestCalendarPermissions(@Kroll.argument(optional = true) KrollFunction permissionCallback) + public KrollPromise requestCalendarPermissions( + @Kroll.argument(optional = true) final KrollFunction permissionCallback) { - if (hasCalendarPermissions()) { - KrollDict response = new KrollDict(); - response.putCodeAndMessage(0, null); - permissionCallback.callAsync(getKrollObject(), response); - return; - } - - TiBaseActivity.registerPermissionRequestCallback(TiC.PERMISSION_CODE_CALENDAR, permissionCallback, - getKrollObject()); - Activity currentActivity = TiApplication.getInstance().getCurrentActivity(); - currentActivity.requestPermissions( - new String[] { Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR }, - TiC.PERMISSION_CODE_CALENDAR); + final KrollObject callbackThisObject = getKrollObject(); + return KrollPromise.create((promise) -> { + if (hasCalendarPermissions()) { + KrollDict response = new KrollDict(); + response.putCodeAndMessage(0, null); + permissionCallback.callAsync(callbackThisObject, response); + promise.resolve(response); + return; + } + + TiBaseActivity.registerPermissionRequestCallback(TiC.PERMISSION_CODE_CALENDAR, permissionCallback, + callbackThisObject, promise); + Activity currentActivity = TiApplication.getInstance().getCurrentActivity(); + currentActivity.requestPermissions( + new String[] { Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR }, + TiC.PERMISSION_CODE_CALENDAR); + }); } @Kroll.method diff --git a/android/modules/contacts/src/java/ti/modules/titanium/contacts/ContactsModule.java b/android/modules/contacts/src/java/ti/modules/titanium/contacts/ContactsModule.java index c31226daad8..49fd7be3cbd 100644 --- a/android/modules/contacts/src/java/ti/modules/titanium/contacts/ContactsModule.java +++ b/android/modules/contacts/src/java/ti/modules/titanium/contacts/ContactsModule.java @@ -14,6 +14,8 @@ import org.appcelerator.kroll.KrollDict; import org.appcelerator.kroll.KrollFunction; import org.appcelerator.kroll.KrollModule; +import org.appcelerator.kroll.KrollObject; +import org.appcelerator.kroll.KrollPromise; import org.appcelerator.kroll.KrollProxy; import org.appcelerator.kroll.annotations.Kroll; import org.appcelerator.kroll.common.Log; @@ -76,21 +78,26 @@ public boolean hasContactsPermissions() } @Kroll.method - public void requestContactsPermissions(@Kroll.argument(optional = true) KrollFunction permissionCallback) + public KrollPromise requestContactsPermissions( + @Kroll.argument(optional = true) final KrollFunction permissionCallback) { - if (hasContactsPermissions()) { - KrollDict response = new KrollDict(); - response.putCodeAndMessage(0, null); - permissionCallback.callAsync(getKrollObject(), response); - return; - } + final KrollObject callbackThisObject = getKrollObject(); + return KrollPromise.create((promise) -> { + if (hasContactsPermissions()) { + KrollDict response = new KrollDict(); + response.putCodeAndMessage(0, null); + permissionCallback.callAsync(callbackThisObject, response); + promise.resolve(response); + return; + } - TiBaseActivity.registerPermissionRequestCallback(TiC.PERMISSION_CODE_CONTACTS, permissionCallback, - getKrollObject()); - Activity currentActivity = TiApplication.getInstance().getCurrentActivity(); - currentActivity.requestPermissions( - new String[] { Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS }, - TiC.PERMISSION_CODE_CONTACTS); + TiBaseActivity.registerPermissionRequestCallback(TiC.PERMISSION_CODE_CONTACTS, permissionCallback, + callbackThisObject, promise); + Activity currentActivity = TiApplication.getInstance().getCurrentActivity(); + currentActivity.requestPermissions( + new String[] { Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS }, + TiC.PERMISSION_CODE_CONTACTS); + }); } @Kroll.method diff --git a/android/modules/filesystem/src/java/ti/modules/titanium/filesystem/FilesystemModule.java b/android/modules/filesystem/src/java/ti/modules/titanium/filesystem/FilesystemModule.java index 2c9e87a123a..83526d6b258 100644 --- a/android/modules/filesystem/src/java/ti/modules/titanium/filesystem/FilesystemModule.java +++ b/android/modules/filesystem/src/java/ti/modules/titanium/filesystem/FilesystemModule.java @@ -18,6 +18,8 @@ import org.appcelerator.kroll.KrollFunction; import org.appcelerator.kroll.KrollInvocation; import org.appcelerator.kroll.KrollModule; +import org.appcelerator.kroll.KrollObject; +import org.appcelerator.kroll.KrollPromise; import org.appcelerator.kroll.annotations.Kroll; import org.appcelerator.kroll.common.Log; import org.appcelerator.titanium.io.TiFileFactory; @@ -113,23 +115,28 @@ private boolean hasStoragePermissions() } @Kroll.method - public void requestStoragePermissions(@Kroll.argument(optional = true) KrollFunction permissionCallback) + public KrollPromise requestStoragePermissions( + @Kroll.argument(optional = true) final KrollFunction permissionCallback) { - if (hasStoragePermissions()) { - KrollDict response = new KrollDict(); - response.putCodeAndMessage(0, null); - permissionCallback.callAsync(getKrollObject(), response); - return; - } + final KrollObject callbackThisObject = getKrollObject(); + return KrollPromise.create((promise) -> { + if (hasStoragePermissions()) { + KrollDict response = new KrollDict(); + response.putCodeAndMessage(0, null); + permissionCallback.callAsync(callbackThisObject, response); + promise.resolve(response); + return; + } - String[] permissions = new String[] { - android.Manifest.permission.READ_EXTERNAL_STORAGE, - android.Manifest.permission.WRITE_EXTERNAL_STORAGE - }; - Activity currentActivity = TiApplication.getInstance().getCurrentActivity(); - TiBaseActivity.registerPermissionRequestCallback(TiC.PERMISSION_CODE_EXTERNAL_STORAGE, permissionCallback, - getKrollObject()); - currentActivity.requestPermissions(permissions, TiC.PERMISSION_CODE_EXTERNAL_STORAGE); + String[] permissions = new String[] { + android.Manifest.permission.READ_EXTERNAL_STORAGE, + android.Manifest.permission.WRITE_EXTERNAL_STORAGE + }; + Activity currentActivity = TiApplication.getInstance().getCurrentActivity(); + TiBaseActivity.registerPermissionRequestCallback(TiC.PERMISSION_CODE_EXTERNAL_STORAGE, + permissionCallback, callbackThisObject, promise); + currentActivity.requestPermissions(permissions, TiC.PERMISSION_CODE_EXTERNAL_STORAGE); + }); } @Kroll.method diff --git a/android/modules/geolocation/src/java/ti/modules/titanium/geolocation/GeolocationModule.java b/android/modules/geolocation/src/java/ti/modules/titanium/geolocation/GeolocationModule.java index b3c888fc2a3..2d02e349e95 100644 --- a/android/modules/geolocation/src/java/ti/modules/titanium/geolocation/GeolocationModule.java +++ b/android/modules/geolocation/src/java/ti/modules/titanium/geolocation/GeolocationModule.java @@ -13,6 +13,8 @@ import org.appcelerator.kroll.KrollDict; import org.appcelerator.kroll.KrollFunction; import org.appcelerator.kroll.KrollModule; +import org.appcelerator.kroll.KrollObject; +import org.appcelerator.kroll.KrollPromise; import org.appcelerator.kroll.KrollProxy; import org.appcelerator.kroll.KrollRuntime; import org.appcelerator.kroll.annotations.Kroll; @@ -508,28 +510,35 @@ public boolean hasLocationPermissions() @SuppressLint("NewApi") @Kroll.method - public void requestLocationPermissions(@Kroll.argument(optional = true) Object type, - @Kroll.argument(optional = true) KrollFunction permissionCallback) - { - KrollFunction permissionCB; - if (type instanceof KrollFunction && permissionCallback == null) { - permissionCB = (KrollFunction) type; - } else { - permissionCB = permissionCallback; - } + public KrollPromise requestLocationPermissions(@Kroll.argument(optional = true) final Object type, + @Kroll.argument(optional = true) final KrollFunction permissionCallback) + { + final KrollObject callbackThisObject = getKrollObject(); + return KrollPromise.create((promise) -> { + KrollFunction permissionCB; + if (type instanceof KrollFunction && permissionCallback == null) { + permissionCB = (KrollFunction) type; + } else { + permissionCB = permissionCallback; + } - // already have permissions, fall through - if (hasLocationPermissions()) { - KrollDict response = new KrollDict(); - response.putCodeAndMessage(0, null); - permissionCB.callAsync(getKrollObject(), response); - return; - } + // already have permissions, fall through + if (hasLocationPermissions()) { + KrollDict response = new KrollDict(); + response.putCodeAndMessage(0, null); + if (permissionCB != null) { + permissionCB.callAsync(callbackThisObject, response); + } + promise.resolve(response); + return; + } - TiBaseActivity.registerPermissionRequestCallback(TiC.PERMISSION_CODE_LOCATION, permissionCB, getKrollObject()); - Activity currentActivity = TiApplication.getInstance().getCurrentActivity(); - currentActivity.requestPermissions(new String[] { Manifest.permission.ACCESS_FINE_LOCATION }, - TiC.PERMISSION_CODE_LOCATION); + TiBaseActivity.registerPermissionRequestCallback(TiC.PERMISSION_CODE_LOCATION, permissionCB, + callbackThisObject, promise); + Activity currentActivity = TiApplication.getInstance().getCurrentActivity(); + currentActivity.requestPermissions(new String[] { Manifest.permission.ACCESS_FINE_LOCATION }, + TiC.PERMISSION_CODE_LOCATION); + }); } /** @@ -710,9 +719,12 @@ public void getCurrentPosition(KrollFunction callback) * for the specified address if available */ @Kroll.method - public void forwardGeocoder(String address, KrollFunction callback) + public KrollPromise forwardGeocoder(final String address, + @Kroll.argument(optional = true) final KrollFunction callback) { - tiLocation.forwardGeocode(address, createGeocodeResponseHandler(callback)); + return KrollPromise.create((promise) -> { + tiLocation.forwardGeocode(address, createGeocodeResponseHandler(callback, promise)); + }); } /** @@ -725,9 +737,12 @@ public void forwardGeocoder(String address, KrollFunction callback) * for the specified latitude and longitude if available */ @Kroll.method - public void reverseGeocoder(double latitude, double longitude, KrollFunction callback) + public KrollPromise reverseGeocoder(double latitude, double longitude, + @Kroll.argument(optional = true) final KrollFunction callback) { - tiLocation.reverseGeocode(latitude, longitude, createGeocodeResponseHandler(callback)); + return KrollPromise.create((promise) -> { + tiLocation.reverseGeocode(latitude, longitude, createGeocodeResponseHandler(callback, promise)); + }); } /** @@ -738,7 +753,8 @@ public void reverseGeocoder(double latitude, double longitude, KrollFunction cal * once the geocode response is ready * @return the geocode response handler */ - private GeocodeResponseHandler createGeocodeResponseHandler(final KrollFunction callback) + private GeocodeResponseHandler createGeocodeResponseHandler(final KrollFunction callback, + final KrollPromise promise) { final GeolocationModule geolocationModule = this; @@ -747,6 +763,10 @@ private GeocodeResponseHandler createGeocodeResponseHandler(final KrollFunction public void handleGeocodeResponse(KrollDict geocodeResponse) { geocodeResponse.put(TiC.EVENT_PROPERTY_SOURCE, geolocationModule); + promise.resolve(geocodeResponse); + if (callback == null) { + return; + } callback.call(getKrollObject(), new Object[] { geocodeResponse }); } }; diff --git a/android/modules/media/src/java/ti/modules/titanium/media/MediaModule.java b/android/modules/media/src/java/ti/modules/titanium/media/MediaModule.java index 7ab67061c67..76f54803606 100644 --- a/android/modules/media/src/java/ti/modules/titanium/media/MediaModule.java +++ b/android/modules/media/src/java/ti/modules/titanium/media/MediaModule.java @@ -19,6 +19,8 @@ import org.appcelerator.kroll.KrollDict; import org.appcelerator.kroll.KrollFunction; import org.appcelerator.kroll.KrollModule; +import org.appcelerator.kroll.KrollObject; +import org.appcelerator.kroll.KrollPromise; import org.appcelerator.kroll.annotations.Kroll; import org.appcelerator.kroll.common.Log; import org.appcelerator.titanium.ContextSpecific; @@ -532,103 +534,121 @@ public void showCamera(@SuppressWarnings("rawtypes") HashMap options) } @Kroll.method - public void requestCameraPermissions(@Kroll.argument(optional = true) KrollFunction permissionCallback) + public KrollPromise requestCameraPermissions( + @Kroll.argument(optional = true) KrollFunction permissionCallback) { - // Do not continue if we already have permission. - if (hasCameraPermissions()) { - if (permissionCallback != null) { + final KrollObject callbackThisObject = getKrollObject(); + return KrollPromise.create((promise) -> { + // Do not continue if we already have permission. + if (hasCameraPermissions()) { KrollDict response = new KrollDict(); response.putCodeAndMessage(0, null); - permissionCallback.callAsync(getKrollObject(), response); + if (permissionCallback != null) { + permissionCallback.callAsync(callbackThisObject, response); + } + promise.resolve(response); + return; } - return; - } - // Do not continue if there is no activity to host the request dialog. - Activity activity = TiApplication.getInstance().getCurrentActivity(); - if (activity == null) { - if (permissionCallback != null) { + // Do not continue if there is no activity to host the request dialog. + Activity activity = TiApplication.getInstance().getCurrentActivity(); + if (activity == null) { KrollDict response = new KrollDict(); response.putCodeAndMessage(-1, "There are no activities to host the camera request dialog."); - permissionCallback.callAsync(getKrollObject(), response); + if (permissionCallback != null) { + permissionCallback.callAsync(callbackThisObject, response); + } + promise.reject(response); + return; } - return; - } - // Create the permission list. On Android 10+, we don't need external storage permission anymore. - ArrayList permissionList = new ArrayList<>(); - permissionList.add(Manifest.permission.CAMERA); - if (Build.VERSION.SDK_INT < 29) { - permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); - } + // Create the permission list. On Android 10+, we don't need external storage permission anymore. + ArrayList permissionList = new ArrayList<>(); + permissionList.add(Manifest.permission.CAMERA); + if (Build.VERSION.SDK_INT < 29) { + permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); + } - // Show dialog requesting permission. - TiBaseActivity.registerPermissionRequestCallback( - TiC.PERMISSION_CODE_CAMERA, permissionCallback, getKrollObject()); - activity.requestPermissions(permissionList.toArray(new String[0]), TiC.PERMISSION_CODE_CAMERA); + // Show dialog requesting permission. + TiBaseActivity.registerPermissionRequestCallback( + TiC.PERMISSION_CODE_CAMERA, permissionCallback, callbackThisObject, promise); + activity.requestPermissions(permissionList.toArray(new String[0]), TiC.PERMISSION_CODE_CAMERA); + }); } @Kroll.method - public void requestAudioRecorderPermissions(@Kroll.argument(optional = true) KrollFunction permissionCallback) + public KrollPromise requestAudioRecorderPermissions( + @Kroll.argument(optional = true) final KrollFunction permissionCallback) { - // Do not continue if we already have permission. - if (hasAudioRecorderPermissions()) { - if (permissionCallback != null) { + final KrollObject callbackThisObject = getKrollObject(); + return KrollPromise.create((promise) -> { + // Do not continue if we already have permission. + if (hasAudioRecorderPermissions()) { KrollDict response = new KrollDict(); response.putCodeAndMessage(0, null); - permissionCallback.callAsync(getKrollObject(), response); + if (permissionCallback != null) { + permissionCallback.callAsync(callbackThisObject, response); + } + promise.resolve(response); + return; } - return; - } - // Do not continue if there is no activity to host the request dialog. - Activity activity = TiApplication.getInstance().getCurrentActivity(); - if (activity == null) { - if (permissionCallback != null) { + // Do not continue if there is no activity to host the request dialog. + Activity activity = TiApplication.getInstance().getCurrentActivity(); + if (activity == null) { KrollDict response = new KrollDict(); response.putCodeAndMessage(-1, "There are no activities to host the camera request dialog."); - permissionCallback.callAsync(getKrollObject(), response); + if (permissionCallback != null) { + permissionCallback.callAsync(callbackThisObject, response); + } + promise.reject(response); + return; } - return; - } - // Show dialog requesting permission. - TiBaseActivity.registerPermissionRequestCallback( - TiC.PERMISSION_CODE_MICROPHONE, permissionCallback, getKrollObject()); - activity.requestPermissions( - new String[] { Manifest.permission.RECORD_AUDIO }, TiC.PERMISSION_CODE_MICROPHONE); + // Show dialog requesting permission. + TiBaseActivity.registerPermissionRequestCallback( + TiC.PERMISSION_CODE_MICROPHONE, permissionCallback, callbackThisObject, promise); + activity.requestPermissions( + new String[] { Manifest.permission.RECORD_AUDIO }, TiC.PERMISSION_CODE_MICROPHONE); + }); } @Kroll.method - public void requestPhotoGalleryPermissions(@Kroll.argument(optional = true) KrollFunction permissionCallback) + public KrollPromise requestPhotoGalleryPermissions( + @Kroll.argument(optional = true) KrollFunction permissionCallback) { - // Do not continue if we already have permission. - if (hasPhotoGalleryPermissions()) { - if (permissionCallback != null) { + final KrollObject callbackThisObject = getKrollObject(); + return KrollPromise.create((promise) -> { + // Do not continue if we already have permission. + if (hasPhotoGalleryPermissions()) { KrollDict response = new KrollDict(); response.putCodeAndMessage(0, null); - permissionCallback.callAsync(getKrollObject(), response); + if (permissionCallback != null) { + permissionCallback.callAsync(callbackThisObject, response); + } + promise.resolve(response); + return; } - return; - } - // Do not continue if there is no activity to host the request dialog. - Activity activity = TiApplication.getInstance().getCurrentActivity(); - if (activity == null) { - if (permissionCallback != null) { + // Do not continue if there is no activity to host the request dialog. + Activity activity = TiApplication.getInstance().getCurrentActivity(); + if (activity == null) { KrollDict response = new KrollDict(); response.putCodeAndMessage( -1, "There are no activities to host the external storage permission request dialog."); - permissionCallback.callAsync(getKrollObject(), response); + if (permissionCallback != null) { + permissionCallback.callAsync(callbackThisObject, response); + } + promise.reject(response); + return; } - return; - } - // Show dialog requesting permission. - TiBaseActivity.registerPermissionRequestCallback( - TiC.PERMISSION_CODE_EXTERNAL_STORAGE, permissionCallback, getKrollObject()); - activity.requestPermissions( - new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, TiC.PERMISSION_CODE_EXTERNAL_STORAGE); + // Show dialog requesting permission. + TiBaseActivity.registerPermissionRequestCallback( + TiC.PERMISSION_CODE_EXTERNAL_STORAGE, permissionCallback, callbackThisObject, promise); + activity.requestPermissions( + new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, TiC.PERMISSION_CODE_EXTERNAL_STORAGE); + }); } @SuppressWarnings({ "unchecked", "rawtypes" }) diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/TabProxy.java b/android/modules/ui/src/java/ti/modules/titanium/ui/TabProxy.java index 53f4ef500e3..532c632f57a 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/TabProxy.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/TabProxy.java @@ -21,13 +21,15 @@ @Kroll.proxy(creatableInModule = UIModule.class, propertyAccessors = { + TiC.PROPERTY_ACTIVE_TINT_COLOR, TiC.PROPERTY_ACTIVE_TITLE_COLOR, + TiC.PROPERTY_BADGE, + TiC.PROPERTY_BADGE_COLOR, TiC.PROPERTY_ICON, + TiC.PROPERTY_TINT_COLOR, TiC.PROPERTY_TITLE, TiC.PROPERTY_TITLE_COLOR, - TiC.PROPERTY_TITLEID, - TiC.PROPERTY_BADGE, - TiC.PROPERTY_BADGE_COLOR + TiC.PROPERTY_TITLEID }) public class TabProxy extends TiViewProxy { diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/TiUIListView.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/TiUIListView.java index a8713458231..b5515db5fe6 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/TiUIListView.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/TiUIListView.java @@ -105,7 +105,7 @@ private void processProperty(String name, Object value) ((TiUISearchView) search).setOnSearchChangeListener(listView); } - final View searchView = search.getNativeView(); + final View searchView = search.getOuterView(); final ViewGroup searchViewParent = (ViewGroup) searchView.getParent(); final ViewGroup listViewParent = (ViewGroup) listView.getParent(); searchView.setId(SEARCHVIEW_ID); diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/TiUITableView.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/TiUITableView.java index 0c18398d09c..18db34a24e9 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/TiUITableView.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/TiUITableView.java @@ -115,7 +115,7 @@ private void processProperty(String name, Object value) } if (searchAsChild) { - final View searchView = search.getNativeView(); + final View searchView = search.getOuterView(); final ViewGroup searchViewParent = (ViewGroup) searchView.getParent(); final ViewGroup tableViewParent = (ViewGroup) tableView.getParent(); searchView.setId(SEARCHVIEW_ID); diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/listview/ListViewHolder.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/listview/ListViewHolder.java index 4e21e245b0f..0a0f48d2351 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/listview/ListViewHolder.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/listview/ListViewHolder.java @@ -59,10 +59,10 @@ public class ListViewHolder extends RecyclerView.ViewHolder private final ViewGroup container; private final TiCompositeLayout content; // Bottom - private final ViewGroup footer; + private final TiCompositeLayout footer; private final TextView footerTitle; // Top - private final ViewGroup header; + private final TiCompositeLayout header; private final TextView headerTitle; private final ImageView rightImage; @@ -250,7 +250,7 @@ public void bind(final ListItemProxy proxy, final boolean selected) if (section == null) { // Handle `header` and `footer` for rows without a parent section. - setHeaderFooter(properties, true, true); + setHeaderFooter(listViewProxy, properties, true, true); } else { @@ -263,14 +263,14 @@ public void bind(final ListItemProxy proxy, final boolean selected) if (indexInSection == 0 || filteredIndex == 0 || proxy.isPlaceholder()) { // Only set header on first row in section. - setHeaderFooter(sectionProperties, true, false); + setHeaderFooter(listViewProxy, sectionProperties, true, false); } if ((indexInSection >= section.getItems().length - 1) || (filteredIndex >= section.getFilteredItemCount() - 1) || proxy.isPlaceholder()) { // Only set footer on last row in section. - setHeaderFooter(sectionProperties, false, true); + setHeaderFooter(listViewProxy, sectionProperties, false, true); } } @@ -367,12 +367,25 @@ private void reset() /** * Set header and footer views of holder. * + * @param listViewProxy ListView proxy. * @param properties Properties containing header and footer entires. * @param updateHeader Boolean to determine if the header should be updated. * @param updateFooter Boolean to determine if the footer should be updated. */ - private void setHeaderFooter(KrollDict properties, boolean updateHeader, boolean updateFooter) + private void setHeaderFooter(TiViewProxy listViewProxy, + KrollDict properties, + boolean updateHeader, + boolean updateFooter) { + if (listViewProxy == null) { + return; + } + + final View nativeListView = listViewProxy.getOrCreateView().getNativeView(); + if (nativeListView == null) { + return; + } + // Handle `header` and `footer`. if (updateHeader) { if (properties.containsKeyAndNotNull(TiC.PROPERTY_HEADER_TITLE)) { @@ -393,6 +406,10 @@ private void setHeaderFooter(KrollDict properties, boolean updateHeader, boolean if (parent != null) { parent.removeView(headerView); } + + // Amend maximum size for header to parent ListView measured height. + this.header.setChildFillHeight(nativeListView.getMeasuredHeight()); + this.header.addView(headerView, view.getLayoutParams()); this.header.setVisibility(View.VISIBLE); } @@ -418,6 +435,10 @@ private void setHeaderFooter(KrollDict properties, boolean updateHeader, boolean if (parent != null) { parent.removeView(footerView); } + + // Amend maximum size for footer to parent ListView measured height. + this.footer.setChildFillHeight(nativeListView.getMeasuredHeight()); + this.footer.addView(footerView, view.getLayoutParams()); this.footer.setVisibility(View.VISIBLE); } diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tableview/TableViewHolder.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tableview/TableViewHolder.java index 76275d21f7c..e5a8e06743c 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tableview/TableViewHolder.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tableview/TableViewHolder.java @@ -61,7 +61,7 @@ public class TableViewHolder extends RecyclerView.ViewHolder private static ColorStateList defaultTextColors = null; // Top - private final ViewGroup header; + private final TiCompositeLayout header; private final TextView headerTitle; // Middle @@ -73,7 +73,7 @@ public class TableViewHolder extends RecyclerView.ViewHolder private final ImageView rightImage; // Bottom - private final ViewGroup footer; + private final TiCompositeLayout footer; private final TextView footerTitle; private WeakReference proxy; @@ -395,7 +395,7 @@ public void bind(final TableViewRowProxy proxy, final boolean selected) } // Handle `header` and `footer` for rows. - setHeaderFooter(properties, true, true); + setHeaderFooter(tableViewProxy, properties, true, true); if (section != null) { @@ -408,14 +408,14 @@ public void bind(final TableViewRowProxy proxy, final boolean selected) if (indexInSection == 0 || filteredIndex == 0 || proxy.isPlaceholder()) { // Only set header on first row in section. - setHeaderFooter(sectionProperties, true, false); + setHeaderFooter(tableViewProxy, sectionProperties, true, false); } if ((indexInSection >= section.getRowCount() - 1) || (filteredIndex >= section.getFilteredRowCount() - 1) || proxy.isPlaceholder()) { // Only set footer on last row in section. - setHeaderFooter(sectionProperties, false, true); + setHeaderFooter(tableViewProxy, sectionProperties, false, true); } } @@ -579,12 +579,25 @@ private void setTitleAttributes(final String prefix, final Context context, fina /** * Set header and footer views/title for row. * + * @param tableViewProxy TableView proxy. * @param properties Row proxy holding header/footer. * @param updateHeader Boolean determine if header should be updated. * @param updateFooter Boolean determine if footer should be updated. */ - private void setHeaderFooter(KrollDict properties, boolean updateHeader, boolean updateFooter) + private void setHeaderFooter(TiViewProxy tableViewProxy, + KrollDict properties, + boolean updateHeader, + boolean updateFooter) { + if (tableViewProxy == null) { + return; + } + + final View nativeTableView = tableViewProxy.getOrCreateView().getNativeView(); + if (nativeTableView == null) { + return; + } + // Handle `header` and `footer`. if (updateHeader) { @@ -616,6 +629,9 @@ private void setHeaderFooter(KrollDict properties, boolean updateHeader, boolean // TODO: Do not override fill behaviour, allow child to control fill. view.getLayoutParams().autoFillsWidth = true; + // Amend maximum size for header to parent TableView measured height. + this.header.setChildFillHeight(nativeTableView.getMeasuredHeight()); + this.header.addView(headerView, view.getLayoutParams()); this.header.setVisibility(View.VISIBLE); } @@ -651,6 +667,9 @@ private void setHeaderFooter(KrollDict properties, boolean updateHeader, boolean // TODO: Do not override fill behaviour, allow child to control fill. view.getLayoutParams().autoFillsWidth = true; + // Amend maximum size for footer to parent TableView measured height. + this.footer.setChildFillHeight(nativeTableView.getMeasuredHeight()); + this.footer.addView(footerView, view.getLayoutParams()); this.footer.setVisibility(View.VISIBLE); } diff --git a/android/runtime/common/src/java/org/appcelerator/kroll/KrollPromise.java b/android/runtime/common/src/java/org/appcelerator/kroll/KrollPromise.java new file mode 100644 index 00000000000..a875b333fc1 --- /dev/null +++ b/android/runtime/common/src/java/org/appcelerator/kroll/KrollPromise.java @@ -0,0 +1,31 @@ +package org.appcelerator.kroll; + +import org.appcelerator.kroll.common.TiMessenger; + +public interface KrollPromise { + + interface OnExecuteCallback { + void onExecute(KrollPromise promise); + } + + void resolve(V value); + + void reject(Object value); + + static KrollPromise create(OnExecuteCallback callback) + { + final KrollPromise promise = KrollRuntime.getInstance().createPromise(); + TiMessenger.postOnRuntime(() -> { + callback.onExecute(promise); + }); + return promise; + } + + static class NullPromise implements KrollPromise + { + @Override + public void resolve(Object value) {} + @Override + public void reject(Object value) {} + } +} diff --git a/android/runtime/common/src/java/org/appcelerator/kroll/KrollRuntime.java b/android/runtime/common/src/java/org/appcelerator/kroll/KrollRuntime.java index f9de64ea781..8c2ebec3464 100644 --- a/android/runtime/common/src/java/org/appcelerator/kroll/KrollRuntime.java +++ b/android/runtime/common/src/java/org/appcelerator/kroll/KrollRuntime.java @@ -19,6 +19,8 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; + +import androidx.annotation.NonNull; import androidx.annotation.Nullable; /** @@ -264,7 +266,7 @@ public void runModule(String source, String filename, KrollProxySupport activity * Equivalent to
evalString(source, SOURCE_ANONYMOUS)
* @see #evalString(String, String) * @param source A string containing Javascript source - * @return The Java representation of the return value of {@link source}, as long as Kroll supports the return value + * @return The Java representation of the return value of {@code source}, as long as Kroll supports the return value */ public Object evalString(String source) { @@ -284,8 +286,8 @@ public Object evalString(String source) *
  • Any Proxy type that extends {@link org.appcelerator.kroll.KrollProxy}
  • * * @param source A string containing Javascript source - * @param filename The name of the filename represented by {@link source} - * @return The Java representation of the return value of {@link source}, as long as Kroll supports the return value + * @param filename The name of the filename represented by {@code source} + * @return The Java representation of the return value of {@code source}, as long as Kroll supports the return value */ public Object evalString(String source, String filename) { @@ -608,4 +610,6 @@ private static void onDisposing(KrollRuntime runtime) public abstract String getRuntimeName(); public abstract void initRuntime(); public abstract void initObject(KrollProxySupport proxy); + @NonNull + public abstract KrollPromise createPromise(); } diff --git a/android/runtime/v8/src/java/org/appcelerator/kroll/runtime/v8/V8Function.java b/android/runtime/v8/src/java/org/appcelerator/kroll/runtime/v8/V8Function.java index 6dcf5bb7342..e620ae4c947 100644 --- a/android/runtime/v8/src/java/org/appcelerator/kroll/runtime/v8/V8Function.java +++ b/android/runtime/v8/src/java/org/appcelerator/kroll/runtime/v8/V8Function.java @@ -86,25 +86,8 @@ public boolean handleMessage(Message message) return super.handleMessage(message); } - @Override - public void doRelease() - { - long functionPointer = getPointer(); - if (functionPointer == 0) { - return; - } - - nativeRelease(functionPointer); - } - - @Override - protected void finalize() throws Throwable - { - super.finalize(); - } - // JNI method prototypes private native Object nativeInvoke(long thisPointer, long functionPointer, Object[] functionArgs); - private static native void nativeRelease(long functionPointer); + protected native boolean nativeRelease(long functionPointer); } diff --git a/android/runtime/v8/src/java/org/appcelerator/kroll/runtime/v8/V8Object.java b/android/runtime/v8/src/java/org/appcelerator/kroll/runtime/v8/V8Object.java index c1aff75ddec..05e379b6085 100644 --- a/android/runtime/v8/src/java/org/appcelerator/kroll/runtime/v8/V8Object.java +++ b/android/runtime/v8/src/java/org/appcelerator/kroll/runtime/v8/V8Object.java @@ -106,7 +106,7 @@ protected void finalize() throws Throwable // JNI method prototypes protected static native void nativeInitObject(Class proxyClass, Object proxyObject); - private static native boolean nativeRelease(long ptr); + protected native boolean nativeRelease(long ptr); private native Object nativeCallProperty(long ptr, String propertyName, Object[] args); diff --git a/android/runtime/v8/src/java/org/appcelerator/kroll/runtime/v8/V8Promise.java b/android/runtime/v8/src/java/org/appcelerator/kroll/runtime/v8/V8Promise.java new file mode 100644 index 00000000000..a1400a70817 --- /dev/null +++ b/android/runtime/v8/src/java/org/appcelerator/kroll/runtime/v8/V8Promise.java @@ -0,0 +1,78 @@ +package org.appcelerator.kroll.runtime.v8; + +import android.os.Message; + +import org.appcelerator.kroll.KrollPromise; +import org.appcelerator.kroll.KrollRuntime; +import org.appcelerator.kroll.common.AsyncResult; +import org.appcelerator.kroll.common.TiMessenger; + +/** + * This class wraps an underlying v8::Promise::Resolver which holds + * the Promise but allows us to call resolve/reject on it. + */ +public class V8Promise extends V8Object implements KrollPromise +{ + private static final String TAG = "V8Promise"; + + protected static final int MSG_RESOLVE = V8Object.MSG_LAST_ID + 100; + protected static final int MSG_REJECT = V8Object.MSG_LAST_ID + 101; + protected static final int MSG_LAST_ID = MSG_REJECT; + + public V8Promise() + { + super(nativeCreate()); + } + + @Override + public void resolve(V value) + { + if (KrollRuntime.getInstance().isRuntimeThread()) { + nativeResolve(getPointer(), value); + } else { + TiMessenger.sendBlockingRuntimeMessage(handler.obtainMessage(MSG_RESOLVE), value); + } + } + + @Override + public void reject(Object value) + { + if (KrollRuntime.getInstance().isRuntimeThread()) { + nativeReject(getPointer(), value); + } else { + TiMessenger.sendBlockingRuntimeMessage(handler.obtainMessage(MSG_REJECT), value); + } + } + + @Override + public boolean handleMessage(Message message) + { + switch (message.what) { + case MSG_RESOLVE: { + AsyncResult asyncResult = ((AsyncResult) message.obj); + Object value = asyncResult.getArg(); + nativeResolve(getPointer(), value); + asyncResult.setResult(null); + return true; + } + case MSG_REJECT: { + AsyncResult asyncResult = ((AsyncResult) message.obj); + Object value = asyncResult.getArg(); + nativeReject(getPointer(), value); + asyncResult.setResult(null); + return true; + } + } + + return super.handleMessage(message); + } + + // JNI method prototypes + private static native long nativeCreate(); + + private native void nativeResolve(long resolver, Object value); + + private native void nativeReject(long resolver, Object value); + + protected native boolean nativeRelease(long resolver); +} diff --git a/android/runtime/v8/src/java/org/appcelerator/kroll/runtime/v8/V8Runtime.java b/android/runtime/v8/src/java/org/appcelerator/kroll/runtime/v8/V8Runtime.java index d809b70d090..85c7a81a58e 100644 --- a/android/runtime/v8/src/java/org/appcelerator/kroll/runtime/v8/V8Runtime.java +++ b/android/runtime/v8/src/java/org/appcelerator/kroll/runtime/v8/V8Runtime.java @@ -15,6 +15,7 @@ import org.appcelerator.kroll.KrollApplication; import org.appcelerator.kroll.KrollExternalModule; +import org.appcelerator.kroll.KrollPromise; import org.appcelerator.kroll.KrollProxySupport; import org.appcelerator.kroll.KrollRuntime; import org.appcelerator.kroll.common.KrollSourceCodeProvider; @@ -26,6 +27,8 @@ import android.os.Looper; import android.os.MessageQueue.IdleHandler; +import androidx.annotation.NonNull; + public final class V8Runtime extends KrollRuntime implements Handler.Callback { private static final String TAG = "KrollV8Runtime"; @@ -212,6 +215,16 @@ public void setGCFlag() shouldGC.set(true); } + @Override + @NonNull + public KrollPromise createPromise() + { + if (KrollRuntime.isDisposed()) { + return new KrollPromise.NullPromise(); + } + return new V8Promise(); + } + // JNI method prototypes private native void nativeInit(JSDebugger jsDebugger, boolean DBG, boolean profilerEnabled); diff --git a/android/runtime/v8/src/native/CMakeLists.txt b/android/runtime/v8/src/native/CMakeLists.txt index 260e9769828..1135e8f3869 100644 --- a/android/runtime/v8/src/native/CMakeLists.txt +++ b/android/runtime/v8/src/native/CMakeLists.txt @@ -72,6 +72,7 @@ add_library( TypeConverter.cpp V8Function.cpp V8Object.cpp + V8Promise.cpp V8Runtime.cpp V8Util.cpp modules/APIModule.cpp diff --git a/android/runtime/v8/src/native/JNIUtil.cpp b/android/runtime/v8/src/native/JNIUtil.cpp index afde2798d38..3c9cd0fc26f 100644 --- a/android/runtime/v8/src/native/JNIUtil.cpp +++ b/android/runtime/v8/src/native/JNIUtil.cpp @@ -46,6 +46,7 @@ jclass JNIUtil::nullPointerException = NULL; jclass JNIUtil::throwableClass = NULL; jclass JNIUtil::stackTraceElementClass = NULL; +jclass JNIUtil::v8PromiseClass = NULL; jclass JNIUtil::v8ObjectClass = NULL; jclass JNIUtil::v8FunctionClass = NULL; jclass JNIUtil::krollRuntimeClass = NULL; @@ -89,6 +90,7 @@ jmethodID JNIUtil::throwableGetStackTraceMethod = NULL; jmethodID JNIUtil::stackTraceElementToStringMethod = NULL; jfieldID JNIUtil::v8ObjectPtrField = NULL; +jmethodID JNIUtil::v8PromiseInitMethod = NULL; jmethodID JNIUtil::v8ObjectInitMethod = NULL; jmethodID JNIUtil::v8FunctionInitMethod = NULL; @@ -331,6 +333,7 @@ void JNIUtil::initCache() throwableClass = findClass("java/lang/Throwable"); stackTraceElementClass = findClass("java/lang/StackTraceElement"); + v8PromiseClass = findClass("org/appcelerator/kroll/runtime/v8/V8Promise"); v8ObjectClass = findClass("org/appcelerator/kroll/runtime/v8/V8Object"); v8FunctionClass = findClass("org/appcelerator/kroll/runtime/v8/V8Function"); krollRuntimeClass = findClass("org/appcelerator/kroll/KrollRuntime"); @@ -373,6 +376,7 @@ void JNIUtil::initCache() stackTraceElementToStringMethod = getMethodID(stackTraceElementClass, "toString", "()Ljava/lang/String;", false); v8ObjectPtrField = getFieldID(v8ObjectClass, "ptr", "J"); + v8PromiseInitMethod = getMethodID(v8PromiseClass, "", "(J)V", false); v8ObjectInitMethod = getMethodID(v8ObjectClass, "", "(J)V", false); v8FunctionInitMethod = getMethodID(v8FunctionClass, "", "(J)V", false); diff --git a/android/runtime/v8/src/native/JNIUtil.h b/android/runtime/v8/src/native/JNIUtil.h index 7539a4a6562..8fe5d4538ae 100644 --- a/android/runtime/v8/src/native/JNIUtil.h +++ b/android/runtime/v8/src/native/JNIUtil.h @@ -96,6 +96,7 @@ class JNIUtil static jclass nullPointerException; // Titanium classes + static jclass v8PromiseClass; static jclass v8ObjectClass; static jclass v8FunctionClass; static jclass krollRuntimeClass; @@ -136,6 +137,7 @@ class JNIUtil // Titanium methods and fields static jfieldID v8ObjectPtrField; + static jmethodID v8PromiseInitMethod; static jmethodID v8ObjectInitMethod; static jmethodID v8FunctionInitMethod; diff --git a/android/runtime/v8/src/native/TypeConverter.cpp b/android/runtime/v8/src/native/TypeConverter.cpp index 16bf58f2143..9f8f89388b0 100644 --- a/android/runtime/v8/src/native/TypeConverter.cpp +++ b/android/runtime/v8/src/native/TypeConverter.cpp @@ -30,6 +30,9 @@ int64_t TypeConverter::functionIndex = std::numeric_limits::min(); // The global map to hold persistent functions. We use the index as our "pointer" to store and retrieve the function std::map>> TypeConverter::functions; +int64_t TypeConverter::resolverIndex = std::numeric_limits::min(); +std::map>> TypeConverter::resolvers; + /****************************** public methods ******************************/ jshort TypeConverter::jsNumberToJavaShort(Local jsNumber) { @@ -240,7 +243,6 @@ jobject TypeConverter::jsObjectToJavaFunction(Isolate* isolate, JNIEnv *env, Loc { Local func = jsObject.As(); Persistent> jsFunction(isolate, func); - // jsFunction.MarkIndependent(); // Method has been removed! // Place the persistent into some global table with incrementing index, use the index as the "ptr" here // Then when we re-construct, use the ptr value as index into the table to grab the persistent! @@ -271,6 +273,49 @@ Local TypeConverter::javaObjectToJsFunction(Isolate* isolate, JNIEnv * return persistentV8Object.Get(isolate); } +jobject TypeConverter::jsObjectToJavaPromise(Isolate* isolate, Local jsObject) +{ + JNIEnv *env = JNIScope::getEnv(); + if (!env) { + return NULL; + } + return TypeConverter::jsObjectToJavaPromise(isolate, env, jsObject); +} + +jobject TypeConverter::jsObjectToJavaPromise(Isolate* isolate, JNIEnv *env, Local jsObject) +{ + Local resolver = jsObject.As(); + Persistent> persistent(isolate, resolver); + + // Place the persistent into some global table with incrementing index, use the index as the "ptr" here + // Then when we re-construct, use the ptr value as index into the table to grab the persistent! + jlong ptr = (jlong) resolverIndex; // jlong is signed 64-bit, so int64_t should match up + TypeConverter::resolvers[resolverIndex] = persistent; + resolverIndex++; + // Java code assumes 0 is null pointer. So we need to skip it. TODO fix this so we don't need to perform this special check? + if (resolverIndex == 0) { + resolverIndex++; + } + + return env->NewObject(JNIUtil::v8PromiseClass, JNIUtil::v8PromiseInitMethod, ptr); +} + +Local TypeConverter::javaObjectToJsPromise(Isolate* isolate, jobject javaObject) +{ + JNIEnv *env = JNIScope::getEnv(); + if (!env) { + return Local(); + } + return TypeConverter::javaObjectToJsPromise(isolate, env, javaObject); +} + +Local TypeConverter::javaObjectToJsPromise(Isolate* isolate, JNIEnv *env, jobject javaObject) +{ + jlong v8ObjectPointer = env->GetLongField(javaObject, JNIUtil::v8ObjectPtrField); + Persistent> persistentV8Object = TypeConverter::resolvers.at(v8ObjectPointer); + return persistentV8Object.Get(isolate)->GetPromise(); +} + jobjectArray TypeConverter::jsArgumentsToJavaArray(const FunctionCallbackInfo& args) { JNIEnv *env = JNIScope::getEnv(); diff --git a/android/runtime/v8/src/native/TypeConverter.h b/android/runtime/v8/src/native/TypeConverter.h index d18925fe38c..b1c1f768c4f 100644 --- a/android/runtime/v8/src/native/TypeConverter.h +++ b/android/runtime/v8/src/native/TypeConverter.h @@ -23,6 +23,11 @@ class TypeConverter // The incrementing key to store the persistent functions static int64_t functionIndex; + // Our global map of "pointers" to persistent functions + static std::map>> resolvers; + // The incrementing key to store the persistent functions + static int64_t resolverIndex; + // short convert methods static jshort jsNumberToJavaShort(v8::Local jsNumber); static v8::Local javaShortToJsNumber(v8::Isolate* isolate, jshort javaShort); @@ -124,6 +129,13 @@ class TypeConverter static jobject jsObjectToJavaFunction(v8::Isolate* isolate, JNIEnv *env, v8::Local jsObject); static v8::Local javaObjectToJsFunction(v8::Isolate* isolate, JNIEnv *env, jobject javaObject); + // promise resolvers convert methods + static jobject jsObjectToJavaPromise(v8::Isolate* isolate, v8::Local jsObject); + static jobject jsObjectToJavaPromise(v8::Isolate* isolate, JNIEnv *env, v8::Local jsObject); + + static v8::Local javaObjectToJsPromise(v8::Isolate* isolate, jobject javaObject); + static v8::Local javaObjectToJsPromise(v8::Isolate* isolate, JNIEnv *env, jobject javaObject); + // arguments conversion static jobjectArray jsArgumentsToJavaArray(const v8::FunctionCallbackInfo& args); diff --git a/android/runtime/v8/src/native/V8Function.cpp b/android/runtime/v8/src/native/V8Function.cpp index 2908d8c7c9b..c51b9bbab5a 100644 --- a/android/runtime/v8/src/native/V8Function.cpp +++ b/android/runtime/v8/src/native/V8Function.cpp @@ -79,9 +79,9 @@ JNIEXPORT jobject JNICALL Java_org_appcelerator_kroll_runtime_v8_V8Function_nati return TypeConverter::jsValueToJavaObject(V8Runtime::v8_isolate, env, object.ToLocalChecked(), &isNew); } -JNIEXPORT void JNICALL +JNIEXPORT jboolean JNICALL Java_org_appcelerator_kroll_runtime_v8_V8Function_nativeRelease - (JNIEnv *env, jclass clazz, jlong ptr) + (JNIEnv *env, jobject self, jlong ptr) { // Release the JS function so it can be collected. // We guard against "bad" pointers by searching by index before releasing @@ -90,7 +90,9 @@ Java_org_appcelerator_kroll_runtime_v8_V8Function_nativeRelease auto jsFunction = it->second; jsFunction.Reset(); TypeConverter::functions.erase(it); + return JNI_TRUE; } + return JNI_FALSE; } #ifdef __cplusplus diff --git a/android/runtime/v8/src/native/V8Object.cpp b/android/runtime/v8/src/native/V8Object.cpp index a1f89fb0715..e99b3f9dc84 100644 --- a/android/runtime/v8/src/native/V8Object.cpp +++ b/android/runtime/v8/src/native/V8Object.cpp @@ -216,7 +216,7 @@ Java_org_appcelerator_kroll_runtime_v8_V8Object_nativeCallProperty JNIEXPORT jboolean JNICALL Java_org_appcelerator_kroll_runtime_v8_V8Object_nativeRelease - (JNIEnv *env, jclass clazz, jlong refPointer) + (JNIEnv *env, jobject self, jlong refPointer) { LOGD(TAG, "V8Object::nativeRelease"); HandleScope scope(V8Runtime::v8_isolate); diff --git a/android/runtime/v8/src/native/V8Promise.cpp b/android/runtime/v8/src/native/V8Promise.cpp new file mode 100644 index 00000000000..cdd69c14031 --- /dev/null +++ b/android/runtime/v8/src/native/V8Promise.cpp @@ -0,0 +1,110 @@ +/** + * Appcelerator Titanium Mobile + * Copyright (c) 2011-2018 by Appcelerator, Inc. All Rights Reserved. + * Licensed under the terms of the Apache Public License + * Please see the LICENSE included with this distribution for details. + */ + +#include +#include + +#include "JNIUtil.h" +#include "Proxy.h" +#include "TypeConverter.h" +#include "V8Runtime.h" +#include "V8Util.h" + +#define TAG "V8Promise" + +using namespace titanium; +using namespace v8; + +extern "C" { + +JNIEXPORT jlong JNICALL Java_org_appcelerator_kroll_runtime_v8_V8Promise_nativeCreate + (JNIEnv *env, jclass clazz) +{ + LOGD(TAG, "V8Promise::nativeCreate"); + HandleScope scope(V8Runtime::v8_isolate); + JNIScope jniScope(env); + + Local context = V8Runtime::v8_isolate->GetCurrentContext(); + TryCatch tryCatch(V8Runtime::v8_isolate); + MaybeLocal maybeResolver = v8::Promise::Resolver::New(context); + Local resolver; + if (!maybeResolver.ToLocal(&resolver)) { + titanium::V8Util::fatalException(V8Runtime::v8_isolate, tryCatch); + return 0; + } + Persistent> persistent(V8Runtime::v8_isolate, resolver); + + // Place the persistent into some global table with incrementing index, use the index as the "ptr" here + // Then when we re-construct, use the ptr value as index into the table to grab the persistent! + jlong ptr = (jlong) TypeConverter::resolverIndex; // jlong is signed 64-bit, so int64_t should match up + TypeConverter::resolvers[TypeConverter::resolverIndex] = persistent; + TypeConverter::resolverIndex++; + // Java code assumes 0 is null pointer. So we need to skip it. TODO fix this so we don't need to perform this special check? + if (TypeConverter::resolverIndex == 0) { + TypeConverter::resolverIndex++; + } + + return ptr; +} + +JNIEXPORT void JNICALL Java_org_appcelerator_kroll_runtime_v8_V8Promise_nativeResolve( + JNIEnv *env, jobject self, jlong ptr, jobject arg) +{ + LOGD(TAG, "Promise nativeResolve"); + HandleScope scope(V8Runtime::v8_isolate); + titanium::JNIScope jniScope(env); + + auto it = TypeConverter::resolvers.find(ptr); + if (it == TypeConverter::resolvers.end()) { + LOGE(TAG, "!!!Received a bad 'pointer' to the Promise::Resolver, unable to find an entry for it."); + return; + } + + auto persistent = TypeConverter::resolvers.at(ptr); + auto resolver = persistent.Get(V8Runtime::v8_isolate); + + Maybe b = resolver->Resolve(V8Runtime::v8_isolate->GetCurrentContext(), TypeConverter::javaObjectToJsValue(V8Runtime::v8_isolate, arg)); + LOGD(TAG, "Promise nativeReject resolver->Resolve %s", b.FromMaybe(false) ? "true" : "false"); +} + +JNIEXPORT void JNICALL Java_org_appcelerator_kroll_runtime_v8_V8Promise_nativeReject( + JNIEnv *env, jobject self, jlong ptr, jobject arg) +{ + LOGD(TAG, "Promise nativeReject"); + HandleScope scope(V8Runtime::v8_isolate); + titanium::JNIScope jniScope(env); + + auto it = TypeConverter::resolvers.find(ptr); + if (it == TypeConverter::resolvers.end()) { + LOGE(TAG, "!!!Received a bad 'pointer' to the Promise::Resolver, unable to find an entry for it."); + return; + } + + auto persistent = TypeConverter::resolvers.at(ptr); + auto resolver = persistent.Get(V8Runtime::v8_isolate); + + Maybe b = resolver->Reject(V8Runtime::v8_isolate->GetCurrentContext(), TypeConverter::javaObjectToJsValue(V8Runtime::v8_isolate, arg)); + LOGD(TAG, "Promise nativeReject resolver->Reject %s", b.FromMaybe(false) ? "true" : "false"); +} + +JNIEXPORT jboolean JNICALL +Java_org_appcelerator_kroll_runtime_v8_V8Promise_nativeRelease + (JNIEnv *env, jobject self, jlong ptr) +{ + // Release the JS function so it can be collected. + // We guard against "bad" pointers by searching by index before releasing + auto it = TypeConverter::resolvers.find(ptr); + if (it != TypeConverter::resolvers.end()) { + auto persistent = it->second; + persistent.Reset(); + TypeConverter::resolvers.erase(it); + return JNI_TRUE; + } + return JNI_FALSE; +} + +} // extern "C" diff --git a/android/titanium/lib/aps-analytics.jar b/android/titanium/lib/aps-analytics.jar index 276fc97c94b..1b18d03cbaa 100644 Binary files a/android/titanium/lib/aps-analytics.jar and b/android/titanium/lib/aps-analytics.jar differ diff --git a/android/titanium/src/java/org/appcelerator/titanium/TiApplication.java b/android/titanium/src/java/org/appcelerator/titanium/TiApplication.java index 321f732b9c0..52bd3d3ca1f 100644 --- a/android/titanium/src/java/org/appcelerator/titanium/TiApplication.java +++ b/android/titanium/src/java/org/appcelerator/titanium/TiApplication.java @@ -380,6 +380,12 @@ public void onDisposing(KrollRuntime runtime) // Delete all Titanium temp files. deleteTiTempFiles(); + + if (isAnalyticsEnabled()) { + + // Force send `session.end` event. + APSAnalytics.getInstance().sendSessionEndEvent(true); + } } }); } diff --git a/android/titanium/src/java/org/appcelerator/titanium/TiBaseActivity.java b/android/titanium/src/java/org/appcelerator/titanium/TiBaseActivity.java index 7acac9f2dca..3d6d62279e0 100644 --- a/android/titanium/src/java/org/appcelerator/titanium/TiBaseActivity.java +++ b/android/titanium/src/java/org/appcelerator/titanium/TiBaseActivity.java @@ -14,6 +14,7 @@ import org.appcelerator.kroll.KrollDict; import org.appcelerator.kroll.KrollFunction; import org.appcelerator.kroll.KrollObject; +import org.appcelerator.kroll.KrollPromise; import org.appcelerator.kroll.KrollProxy; import org.appcelerator.kroll.KrollRuntime; import org.appcelerator.kroll.common.Log; @@ -430,6 +431,24 @@ public static void registerPermissionRequestCallback( */ public static void registerPermissionRequestCallback( Integer requestCode, final KrollFunction callback, final KrollObject context) + { + TiBaseActivity.registerPermissionRequestCallback(requestCode, callback, context, null); + } + + /** + * Registers a global KrollCallback to be invoked when onRequestPermissionsResult() is called for the given + * request code. Indicates if permissions were granted from a requestPermissions() method call. + *

    + * The registered callback can be removed via the TiBaseActivity.unregisterPermissionRequestCallback() method. + * @param requestCode Unique 8-bit integer ID to be used by the requestPermissions() method. + * @param callback + * Callback to be invoked with KrollDict properties "success", "code", and an optional "message". Can be null. + * @param context KrollObject providing the JavaScript context needed to invoke a JS callback. + * @param promise Promise to be invoked. Can be null. + */ + public static void registerPermissionRequestCallback( + Integer requestCode, final KrollFunction callback, final KrollObject context, + final KrollPromise promise) { if (requestCode == null) { return; @@ -441,7 +460,7 @@ public void onRequestPermissionsResult( @NonNull TiBaseActivity activity, int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - if (callback == null) { + if (callback == null && promise == null) { return; } @@ -465,7 +484,16 @@ public void onRequestPermissionsResult( if (context == null) { Log.w(TAG, "Permission callback context object is null"); } - callback.callAsync(context, response); + if (callback != null) { + callback.callAsync(context, response); + } + if (promise != null) { + if (deniedPermissions.isEmpty()) { + promise.resolve(response); + } else { + promise.reject(response); + } + } } }); } diff --git a/apidoc/Titanium/Android/Android.yml b/apidoc/Titanium/Android/Android.yml index 9eecc703ce4..b317eaeb90b 100644 --- a/apidoc/Titanium/Android/Android.yml +++ b/apidoc/Titanium/Android/Android.yml @@ -231,9 +231,14 @@ methods: Since Titanium 6.1.0, the method will also accept a String. Any requests where permissions are already granted will yield a successful callback. type: [String, Array] - name: callback - summary: Function to call upon user decision to grant access. - optional: true + summary: | + Function to call upon user decision to grant access. + Optional on SDK 10, as this method will return a `Promise`, which may be used to handle the result. type: Callback + optional: true + returns: + summary: On SDK 10+, this method will return a `Promise` whose resolved value is equivalent to that passed to the optional callback argument. + type: Promise since: "5.4.0" - name: isServiceRunning diff --git a/apidoc/Titanium/Calendar/Calendar.yml b/apidoc/Titanium/Calendar/Calendar.yml index 1c131a253f3..9fb8a6bd07a 100644 --- a/apidoc/Titanium/Calendar/Calendar.yml +++ b/apidoc/Titanium/Calendar/Calendar.yml @@ -61,8 +61,14 @@ methods: will crash if your app does not include the key. Check the [Apple docs](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html) for more information. parameters: - name: callback - summary: Function to call upon user decision to grant calendar access. + summary: | + Function to call upon user decision to grant calendar access. + Optional on SDK 10, as this method will return a `Promise`, which may be used to handle the result. type: Callback + optional: true + returns: + summary: On SDK 10+, this method will return a `Promise` whose resolved value is equivalent to that passed to the optional callback argument. + type: Promise platforms: [iphone, ipad, android, macos] since: "5.1.0" diff --git a/apidoc/Titanium/Contacts/Contacts.yml b/apidoc/Titanium/Contacts/Contacts.yml index c75d5afc757..53beb20b866 100644 --- a/apidoc/Titanium/Contacts/Contacts.yml +++ b/apidoc/Titanium/Contacts/Contacts.yml @@ -218,11 +218,16 @@ methods: In iOS 6, Apple introduced the Info.plist key `NSContactsUsageDescription` that is used to display an own description while authorizing contacts permissions. In iOS 10, this key is mandatory and the application will crash if your app does not include the key. Check the [Apple docs](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html) for more information. - parameters: - name: callback - summary: Function to call upon user decision to grant contacts access. + summary: | + Function to call upon user decision to grant contacts access. + Optional on SDK 10, as this method will return a `Promise`, which may be used to handle the result. type: Callback + optional: true + returns: + summary: On SDK 10+, this method will return a `Promise` whose resolved value is equivalent to that passed to the optional callback argument. + type: Promise since: {android: "5.1.0", iphone: "5.1.0", ipad: "5.1.0", macos: "9.2.0"} - name: requestAuthorization diff --git a/apidoc/Titanium/Filesystem/Filesystem.yml b/apidoc/Titanium/Filesystem/Filesystem.yml index 5404bb3f5eb..00891064b62 100644 --- a/apidoc/Titanium/Filesystem/Filesystem.yml +++ b/apidoc/Titanium/Filesystem/Filesystem.yml @@ -145,8 +145,14 @@ methods: If you require other permissions, you can also use . parameters: - name: callback - summary: Function to call upon user decision to grant storage access + summary: | + Function to call upon user decision to grant storage access. + Optional on SDK 10, as this method will return a `Promise`, which may be used to handle the result. type: Callback + optional: true + returns: + summary: On SDK 10+, this method will return a `Promise` whose resolved value is equivalent to that passed to the optional callback argument. + type: Promise platforms: [android] since: "5.4.0" diff --git a/apidoc/Titanium/Geolocation/Geolocation.yml b/apidoc/Titanium/Geolocation/Geolocation.yml index 3f61f7fa7f3..ba034c2e058 100644 --- a/apidoc/Titanium/Geolocation/Geolocation.yml +++ b/apidoc/Titanium/Geolocation/Geolocation.yml @@ -207,8 +207,14 @@ methods: summary: address to resolve. type: String - name: callback - summary: Function to invoke on success or failure. + summary: | + Function to invoke on success or failure. + Optional on SDK 10, as this method will return a `Promise`, which may be used to handle the result. type: Callback + optional: true + returns: + summary: On SDK 10+, this method will return a `Promise` whose resolved value is equivalent to that passed to the optional callback argument. + type: Promise platforms: [android, iphone, ipad, macos] - name: getCurrentHeading @@ -277,8 +283,14 @@ methods: constants: [Titanium.Geolocation.AUTHORIZATION_ALWAYS,Titanium.Geolocation.AUTHORIZATION_WHEN_IN_USE] type: Number - name: callback - summary: Function to call upon user decision to grant location access. + summary: | + Function to call upon user decision to grant location access. + Optional on SDK 10, as this method will return a `Promise`, which may be used to handle the result. type: Callback + optional: true + returns: + summary: On SDK 10+, this method will return a `Promise` whose resolved value is equivalent to that passed to the optional callback argument. + type: Promise platforms: [iphone, ipad, android, macos] since: "5.1.0" @@ -355,12 +367,17 @@ methods: type: Number - name: callback - summary: Function to invoke on success or failure. + summary: | + Function to invoke on success or failure. + Optional on SDK 10, as this method will return a `Promise`, which may be used to handle the result. type: Callback + optional: true + returns: + summary: On SDK 10+, this method will return a `Promise` whose resolved value is equivalent to that passed to the optional callback argument. + type: Promise platforms: [android, iphone, ipad, macos] events: - - name: calibration summary: Fired when the device detects interface and requires calibration. description: | diff --git a/apidoc/Titanium/Media/Media.yml b/apidoc/Titanium/Media/Media.yml index 1dd66bb0dcb..f6c71bb8bf6 100644 --- a/apidoc/Titanium/Media/Media.yml +++ b/apidoc/Titanium/Media/Media.yml @@ -259,6 +259,7 @@ methods: type: Boolean platforms: [iphone, ipad, android, macos] since: "5.1.0" + - name: requestCameraPermissions summary: Requests for camera access. description: | @@ -278,8 +279,14 @@ methods: change it in the device settings. parameters: - name: callback - summary: Function to call indicating if permission was granted or denied. + summary: | + Function to call indicating if permission was granted or denied. + Optional on SDK 10, as this method will return a `Promise`, which may be used to handle the result. type: Callback + optional: true + returns: + summary: On SDK 10+, this method will return a `Promise` whose resolved value is equivalent to that passed to the optional callback argument. + type: Promise platforms: [iphone, ipad, android, macos] since: "5.1.0" osver: {ios: {min: "7.0"}} @@ -306,8 +313,14 @@ methods: iOS Apps that do not include this method, will present the system-dialog while the dialog is opened with . parameters: - name: callback - summary: Function to call indicating if permission was granted or denied. + summary: | + Function to call indicating if permission was granted or denied. + Optional on SDK 10, as this method will return a `Promise`, which may be used to handle the result. type: Callback + optional: true + returns: + summary: On SDK 10+, this method will return a `Promise` whose resolved value is equivalent to that passed to the optional callback argument. + type: Promise platforms: [android, iphone, ipad, macos] since: android: "9.3.0" @@ -333,6 +346,7 @@ methods: platforms: [iphone, ipad] since: "4.0.0" osver: {ios: {min: "7.0"}} + - name: takeScreenshot summary: Takes a screen shot of the visible UI on the device. description: | @@ -344,6 +358,7 @@ methods: summary: Function to call upon capture. type: Callback platforms: [iphone, ipad, android, macos] + - name: vibrate summary: Makes the device vibrate. description: | @@ -473,8 +488,14 @@ methods: ipad: "6.1.0" parameters: - name: callback - summary: Callback function to execute when the users responds to the authorization alert. + summary: | + Callback function to execute when the users responds to the authorization alert. + Optional on SDK 10, as this method will return a `Promise`, which may be used to handle the result. type: Callback + optional: true + returns: + summary: On SDK 10+, this method will return a `Promise` whose resolved value is equivalent to that passed to the optional callback argument. + type: Promise events: - name: cameraready diff --git a/apidoc/Titanium/UI/ProgressBar.yml b/apidoc/Titanium/UI/ProgressBar.yml index 87374e79425..741dea0d77b 100644 --- a/apidoc/Titanium/UI/ProgressBar.yml +++ b/apidoc/Titanium/UI/ProgressBar.yml @@ -41,6 +41,7 @@ properties: - name: font summary: Font for the progress bar text. type: Font + exclude-platforms: [android] - name: max summary: Maximum value of the progress bar. diff --git a/iphone/Classes/GeolocationModule.h b/iphone/Classes/GeolocationModule.h index a45120d5862..a1d7c17ac73 100644 --- a/iphone/Classes/GeolocationModule.h +++ b/iphone/Classes/GeolocationModule.h @@ -16,6 +16,8 @@ NSString *const kTiGeolocationUsageDescriptionAlways = @"NSLocationAlwaysUsageDe NSString *const kTiGeolocationUsageDescriptionAlwaysAndWhenInUse = @"NSLocationAlwaysAndWhenInUseUsageDescription"; NSString *const kTiGeolocationTemporaryUsageDescriptionDictionary = @"NSLocationTemporaryUsageDescriptionDictionary"; +@class KrollPromise; + @protocol GeolocationExports // accuracy constants @@ -78,18 +80,18 @@ PROPERTY(bool, trackSignificantLocationChange, TrackSignificantLocationChange); // methods JSExportAs(forwardGeocoder, - -(void)forwardGeocoder + -(JSValue *)forwardGeocoder : (NSString *)address withCallback : (JSValue *)callback); - (void)getCurrentHeading:(JSValue *)callback; - (void)getCurrentPosition:(JSValue *)callback; - (bool)hasLocationPermissions:(CLAuthorizationStatus)authorizationType; JSExportAs(requestLocationPermissions, - -(void)requestLocationPermissions + -(JSValue *)requestLocationPermissions : (CLAuthorizationStatus)authorizationType withCallback : (JSValue *)callback); JSExportAs(reverseGeocoder, - -(void)reverseGeocoder + -(JSValue *)reverseGeocoder : (double)latitude longitude : (double)longitude withCallback : (JSValue *)callback); @@ -119,6 +121,7 @@ JSExportAs(requestTemporaryFullAccuracyAuthorization, bool allowsBackgroundLocationUpdates; BOOL showBackgroundLocationIndicator; JSManagedValue *authorizationCallback; + KrollPromise *authorizationPromise; CLAuthorizationStatus requestedAuthorizationStatus; CLActivityType activityType; diff --git a/iphone/Classes/GeolocationModule.m b/iphone/Classes/GeolocationModule.m index e72aa0a0730..3e1cd07a995 100644 --- a/iphone/Classes/GeolocationModule.m +++ b/iphone/Classes/GeolocationModule.m @@ -8,6 +8,7 @@ #import "GeolocationModule.h" #import +#import #import #import @@ -17,18 +18,22 @@ @interface GeolocationCallback : NSObject { JSValue *callback; + KrollPromise *promise; } -- (id)initWithCallback:(JSValue *)callback; +- (id)initWithCallback:(JSValue *)callback andPromise:(KrollPromise *)promise; @end @implementation GeolocationCallback -- (id)initWithCallback:(JSValue *)callback_ +- (id)initWithCallback:(JSValue *)callback_ andPromise:(KrollPromise *)promise_ { //Ignore analyzer warning here. Delegate will call autorelease onLoad or onError. if (self = [super init]) { // FIXME Use JSManagedValue here? - callback = [callback_ retain]; + if (![callback_ isUndefined]) { // guard against user not supplying a callback function! + callback = [callback_ retain]; + } + promise = [promise_ retain]; } return self; } @@ -36,6 +41,7 @@ - (id)initWithCallback:(JSValue *)callback_ - (void)dealloc { RELEASE_TO_NIL(callback); + RELEASE_TO_NIL(promise); [super dealloc]; } @@ -74,7 +80,10 @@ - (void)requestSuccess:(NSString *)data - (void)requestError:(NSError *)error { NSDictionary *event = [TiUtils dictionaryWithCode:[error code] message:[TiUtils messageFromError:error]]; - [callback callWithArguments:@[ event ]]; + if (callback != nil) { + [callback callWithArguments:@[ event ]]; + } + [promise reject:@[ event ]]; } - (void)request:(APSHTTPRequest *)request onLoad:(APSHTTPResponse *)response @@ -126,7 +135,10 @@ - (void)requestSuccess:(NSString *)locationString event = [TiUtils dictionaryWithCode:-1 message:@"error obtaining geolocation"]; } - [callback callWithArguments:@[ event ]]; + if (callback != nil) { + [callback callWithArguments:@[ event ]]; + } + [promise resolve:@[ event ]]; } @end @@ -150,7 +162,10 @@ - (void)requestSuccess:(NSString *)locationString dict[@"countryCode"] = dict[@"country_code"]; [dict removeObjectForKey:@"country_code"]; } - [callback callWithArguments:@[ revisedEvent ]]; + if (callback != nil) { + [callback callWithArguments:@[ revisedEvent ]]; + } + [promise resolve:@[ revisedEvent ]]; } } @@ -492,21 +507,26 @@ - (void)performGeo:(NSString *)direction address:(NSString *)address callback:(G [params setValue:countryCode forKey:@"c"]; } [callback start:params]; + RELEASE_TO_NIL(params); } -- (void)reverseGeocoder:(double)latitude longitude:(double)longitude withCallback:(JSValue *)callback +- (JSValue *)reverseGeocoder:(double)latitude longitude:(double)longitude withCallback:(JSValue *)callback { #ifndef __clang_analyzer__ // Ignore static analyzer error here, memory will be released. See TIMOB-19444 - ReverseGeoCallback *rcb = [[ReverseGeoCallback alloc] initWithCallback:callback]; + KrollPromise *promise = [[[KrollPromise alloc] initInContext:[self currentContext]] autorelease]; + ReverseGeoCallback *rcb = [[ReverseGeoCallback alloc] initWithCallback:callback andPromise:promise]; [self performGeo:@"r" address:[NSString stringWithFormat:@"%f,%f", latitude, longitude] callback:rcb]; + return promise.JSValue; #endif } -- (void)forwardGeocoder:(NSString *)address withCallback:(JSValue *)callback +- (JSValue *)forwardGeocoder:(NSString *)address withCallback:(JSValue *)callback { #ifndef __clang_analyzer__ // Ignore static analyzer error here, memory will be released. See TIMOB-19444 - ForwardGeoCallback *fcb = [[ForwardGeoCallback alloc] initWithCallback:callback]; + KrollPromise *promise = [[[KrollPromise alloc] initInContext:[self currentContext]] autorelease]; + ForwardGeoCallback *fcb = [[ForwardGeoCallback alloc] initWithCallback:callback andPromise:promise]; [self performGeo:@"f" address:address callback:fcb]; + return promise.JSValue; #endif } @@ -780,7 +800,7 @@ - (bool)hasLocationPermissions:(CLAuthorizationStatus)authorizationType return locationServicesEnabled && currentPermissionLevel == requestedPermissionLevel; } -- (void)requestLocationPermissions:(CLAuthorizationStatus)authorizationType withCallback:(JSValue *)callback +- (JSValue *)requestLocationPermissions:(CLAuthorizationStatus)authorizationType withCallback:(JSValue *)callback { // Store the authorization callback for later usage if (callback != nil) { @@ -794,6 +814,14 @@ - (void)requestLocationPermissions:(CLAuthorizationStatus)authorizationType with [callback.context.virtualMachine addManagedReference:authorizationCallback withOwner:self]; } + // Promise to return + KrollPromise *promise = [[[KrollPromise alloc] initInContext:[self currentContext]] autorelease]; + if (authorizationPromise != nil) { + [authorizationPromise release]; + authorizationPromise = nil; + } + authorizationPromise = [promise retain]; + requestedAuthorizationStatus = authorizationType; CLAuthorizationStatus currentPermissionLevel = [CLLocationManager authorizationStatus]; BOOL permissionsGranted = currentPermissionLevel == requestedAuthorizationStatus; @@ -801,11 +829,11 @@ - (void)requestLocationPermissions:(CLAuthorizationStatus)authorizationType with // For iOS < 11, already granted permissions will return with success immediately if (permissionsGranted) { [self executeAndReleaseCallbackWithCode:0 andMessage:nil]; - return; + return promise.JSValue; } else if (currentPermissionLevel == kCLAuthorizationStatusDenied) { NSString *message = @"The user denied access to use location services."; [self executeAndReleaseCallbackWithCode:1 andMessage:message]; - return; + return promise.JSValue; } NSString *errorMessage = nil; @@ -851,6 +879,7 @@ - (void)requestLocationPermissions:(CLAuthorizationStatus)authorizationType with [self executeAndReleaseCallbackWithCode:(errorMessage == nil) ? 0 : 1 andMessage:errorMessage]; RELEASE_TO_NIL(errorMessage); } + return promise.JSValue; } #if IS_SDK_IOS_14 @@ -909,23 +938,37 @@ + (BOOL)hasWhenInUsePermissionKeys return [[NSBundle mainBundle] objectForInfoDictionaryKey:kTiGeolocationUsageDescriptionWhenInUse] != nil; } -- (void)executeAndReleaseCallbackWithCode:(NSInteger)code andMessage:(NSString *)message +- (void)executeAndReleaseCallbackWithCode:(NSInteger)code andMessage:(NSString *)message withDict:(NSDictionary *)dict { - if (authorizationCallback == nil) { - return; + NSMutableDictionary *fullDict = [TiUtils dictionaryWithCode:code message:message]; + if (dict != nil) { + [fullDict setDictionary:dict]; } + NSArray *invocationArray = @[ fullDict ]; - NSMutableDictionary *propertiesDict = [TiUtils dictionaryWithCode:code message:message]; - NSArray *invocationArray = [[NSArray alloc] initWithObjects:&propertiesDict count:1]; + if (authorizationPromise != nil) { + if (code == 0) { + [authorizationPromise resolve:invocationArray]; + } else { + [authorizationPromise reject:invocationArray]; + } + [authorizationPromise release]; + authorizationPromise = nil; + } - JSValue *actualCallback = [authorizationCallback value]; - [actualCallback callWithArguments:invocationArray]; - [invocationArray release]; + if (authorizationCallback != nil) { + JSValue *actualCallback = [authorizationCallback value]; + [actualCallback callWithArguments:invocationArray]; + // release the stored callback + [actualCallback.context.virtualMachine removeManagedReference:authorizationCallback withOwner:self]; + [authorizationCallback release]; + authorizationCallback = nil; + } +} - // release the stored callback - [actualCallback.context.virtualMachine removeManagedReference:authorizationCallback withOwner:self]; - [authorizationCallback release]; - authorizationCallback = nil; +- (void)executeAndReleaseCallbackWithCode:(NSInteger)code andMessage:(NSString *)message +{ + [self executeAndReleaseCallbackWithCode:code andMessage:message withDict:nil]; } - (NSDictionary *)locationDictionary:(CLLocation *)newLocation; @@ -1092,13 +1135,13 @@ - (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatu TiThreadPerformOnMainThread( ^{ - NSMutableDictionary *propertiesDict = [TiUtils dictionaryWithCode:code message:errorStr]; - [propertiesDict setObject:NUMINT([CLLocationManager authorizationStatus]) forKey:@"authorizationStatus"]; - [[authorizationCallback value] callWithArguments:@[ propertiesDict ]]; + [self executeAndReleaseCallbackWithCode:code + andMessage:errorStr + withDict:@{ + @"authorizationStatus" : NUMINT([CLLocationManager authorizationStatus]) + }]; }, YES); - [[authorizationCallback value].context.virtualMachine removeManagedReference:authorizationCallback withOwner:self]; - RELEASE_TO_NIL(authorizationCallback); RELEASE_TO_NIL(errorStr); } } diff --git a/iphone/Classes/MediaModule.m b/iphone/Classes/MediaModule.m index 66779dafdf9..190f88c1560 100644 --- a/iphone/Classes/MediaModule.m +++ b/iphone/Classes/MediaModule.m @@ -1791,6 +1791,8 @@ - (void)showPHPicker:(NSDictionary *)args [_phPicker setDelegate:self]; [self displayModalPicker:_phPicker settings:args]; + + RELEASE_TO_NIL(configuration); } #pragma mark PHPickerViewControllerDelegate diff --git a/iphone/Classes/TiAppiOSProxy.m b/iphone/Classes/TiAppiOSProxy.m index 3a1414fea29..074d7c5231d 100644 --- a/iphone/Classes/TiAppiOSProxy.m +++ b/iphone/Classes/TiAppiOSProxy.m @@ -500,6 +500,7 @@ - (void)registerUserNotificationSettings:(id)args // Assign the granted categories [event setValue:((categories != nil) ? categories : @{}) forKey:@"categories"]; [self fireEvent:@"usernotificationsettings" withObject:event]; + RELEASE_TO_NIL(event); }]; } } @@ -846,6 +847,10 @@ - (void)assignUserInfo:(NSDictionary *)userInfo toContent:(id)content ensureIden } else if ([content isKindOfClass:UILocalNotification.class]) { ((UILocalNotification *)content).userInfo = userInfoWithId; } + + if (userInfo != nil) { + RELEASE_TO_NIL(userInfoWithId); + } } - (void)cancelAllLocalNotifications:(id)unused diff --git a/iphone/Classes/TiAppiOSUserNotificationCenterProxy.m b/iphone/Classes/TiAppiOSUserNotificationCenterProxy.m index 8255274a1cf..1afc9ef1f57 100644 --- a/iphone/Classes/TiAppiOSUserNotificationCenterProxy.m +++ b/iphone/Classes/TiAppiOSUserNotificationCenterProxy.m @@ -85,6 +85,7 @@ - (void)removePendingNotifications:(id)args if (identifiers.count > 0) { [center removePendingNotificationRequestsWithIdentifiers:identifiers]; } + RELEASE_TO_NIL(identifiers); }]; } @@ -113,6 +114,7 @@ - (void)removeDeliveredNotifications:(id)args if (identifiers.count > 0) { [center removeDeliveredNotificationsWithIdentifiers:identifiers]; } + RELEASE_TO_NIL(identifiers); }]; } diff --git a/iphone/Classes/TiDOMNodeProxy.m b/iphone/Classes/TiDOMNodeProxy.m index 0ddb7b9e1d4..1b7a7ea45ad 100644 --- a/iphone/Classes/TiDOMNodeProxy.m +++ b/iphone/Classes/TiDOMNodeProxy.m @@ -24,7 +24,7 @@ #import "TiDOMValidator.h" #import #include - +#import /* * The nodeRegistry is used to map xmlNodePtr objects to TiDOMNodeProxies * Ensures that there is only one active proxy for a give xmlNodePtr. @@ -32,7 +32,7 @@ * Values are removed when the proxies are freed */ static CFMutableDictionaryRef nodeRegistry = NULL; -OSSpinLock nodeRegistryLock = OS_SPINLOCK_INIT; +os_unfair_lock nodeRegistryLock = OS_UNFAIR_LOCK_INIT; @implementation TiDOMNodeProxy @synthesize document, node; @@ -62,11 +62,11 @@ - (NSString *)XMLString + (id)nodeForXMLNode:(xmlNodePtr)nodePtr { id result = nil; - OSSpinLockLock(&nodeRegistryLock); + os_unfair_lock_lock(&nodeRegistryLock); if (nodeRegistry != NULL) { result = CFDictionaryGetValue(nodeRegistry, nodePtr); } - OSSpinLockUnlock(&nodeRegistryLock); + os_unfair_lock_unlock(&nodeRegistryLock); return result; } @@ -85,7 +85,7 @@ + (void)setNode:(id)node forXMLNode:(xmlNodePtr)nodePtr if ((node == nil) || (nodePtr == NULL)) { return; } - OSSpinLockLock(&nodeRegistryLock); + os_unfair_lock_lock(&nodeRegistryLock); if (nodeRegistry == NULL) { CFDictionaryKeyCallBacks keyCallbacks = kCFTypeDictionaryKeyCallBacks; CFDictionaryValueCallBacks callbacks = kCFTypeDictionaryValueCallBacks; @@ -98,16 +98,16 @@ + (void)setNode:(id)node forXMLNode:(xmlNodePtr)nodePtr nodeRegistry = CFDictionaryCreateMutable(nil, 0, &keyCallbacks, &callbacks); } CFDictionarySetValue(nodeRegistry, (void *)nodePtr, node); - OSSpinLockUnlock(&nodeRegistryLock); + os_unfair_lock_unlock(&nodeRegistryLock); } + (void)removeNodeForXMLNode:(xmlNodePtr)nodePtr { - OSSpinLockLock(&nodeRegistryLock); + os_unfair_lock_lock(&nodeRegistryLock); if (nodeRegistry == NULL) return; CFDictionaryRemoveValue(nodeRegistry, nodePtr); - OSSpinLockUnlock(&nodeRegistryLock); + os_unfair_lock_unlock(&nodeRegistryLock); } - (id)makeNodeListProxyFromArray:(NSArray *)nodes context:(id)context diff --git a/iphone/Classes/TiUIListView.m b/iphone/Classes/TiUIListView.m index 549dc1eb814..6aecf323e73 100644 --- a/iphone/Classes/TiUIListView.m +++ b/iphone/Classes/TiUIListView.m @@ -1177,6 +1177,9 @@ - (void)tableViewDidEndMultipleSelectionInteraction:(UITableView *)tableView [startingItem setDictionary:eventObject]; } [selectedItems addObject:eventObject]; + + RELEASE_TO_NIL(eventObject); + RELEASE_TO_NIL(theSection); } [self.proxy fireEvent:@"itemsselected" withObject:@{ @"selectedItems" : selectedItems, @"startingItem" : startingItem }]; } diff --git a/iphone/Classes/TiUIShortcutProxy.h b/iphone/Classes/TiUIShortcutProxy.h index 87e28c141ea..e7f96704a6f 100644 --- a/iphone/Classes/TiUIShortcutProxy.h +++ b/iphone/Classes/TiUIShortcutProxy.h @@ -17,7 +17,7 @@ NS_ASSUME_NONNULL_BEGIN READONLY_PROPERTY(NSArray *, items, Items); READONLY_PROPERTY(NSArray *, staticItems, StaticItems); -- (TiUIShortcutItemProxy *)getById:(NSString *)identifier; +- (TiUIShortcutItemProxy *_Nullable)getById:(NSString *)identifier; - (void)remove:(TiUIShortcutItemProxy *)shortcut; diff --git a/iphone/Classes/TiUIShortcutProxy.m b/iphone/Classes/TiUIShortcutProxy.m index e417ef15c07..09784b097c6 100644 --- a/iphone/Classes/TiUIShortcutProxy.m +++ b/iphone/Classes/TiUIShortcutProxy.m @@ -120,6 +120,7 @@ - (void)remove:(TiUIShortcutItemProxy *)shortcut } } UIApplication.sharedApplication.shortcutItems = shortcutsCopy; + RELEASE_TO_NIL(shortcutsCopy); } } diff --git a/iphone/Classes/TiUITabGroup.m b/iphone/Classes/TiUITabGroup.m index 23a32a4341a..431f50b3ecf 100644 --- a/iphone/Classes/TiUITabGroup.m +++ b/iphone/Classes/TiUITabGroup.m @@ -619,7 +619,7 @@ - (NSDictionary *)focusEvent index = [tabArray indexOfObject:[(TiUITabProxy *)focusedTabProxy controller]]; } return @{ - @"tab" : focusedTabProxy, + @"tab" : NULL_IF_NIL(focusedTabProxy), @"index" : NUMINTEGER(index), @"previousIndex" : NUMINT(-1), @"previousTab" : [NSNull null] diff --git a/iphone/Classes/TiUITableView.m b/iphone/Classes/TiUITableView.m index 25bd569aaae..393101e841c 100644 --- a/iphone/Classes/TiUITableView.m +++ b/iphone/Classes/TiUITableView.m @@ -2403,6 +2403,7 @@ - (void)viewGetFocus if (!controller.navigationItem.searchController) { controller.navigationItem.searchController = searchController; } + RELEASE_TO_NIL(controller); } if (!hideOnSearch && isSearched && self.searchedString && ![searchController isActive]) { isSearched = NO; diff --git a/iphone/Classes/TiUIiOSPreviewContextProxy.m b/iphone/Classes/TiUIiOSPreviewContextProxy.m index 40297010b57..803fd308747 100644 --- a/iphone/Classes/TiUIiOSPreviewContextProxy.m +++ b/iphone/Classes/TiUIiOSPreviewContextProxy.m @@ -54,7 +54,6 @@ - (void)dealloc - (void)connectToDelegate { -#ifndef __clang_analyzer__ UIView *nativeSourceView = nil; #ifdef USE_TI_UILISTVIEW @@ -79,9 +78,12 @@ - (void)connectToDelegate nativeSourceView = [_sourceView view]; } UIViewController *controller = [[[TiApp app] controller] topPresentedController]; - [controller registerForPreviewingWithDelegate:[[TiPreviewingDelegate alloc] initWithPreviewContext:self] + TiPreviewingDelegate *previewingDelegate = [[TiPreviewingDelegate alloc] initWithPreviewContext:self]; + + [controller registerForPreviewingWithDelegate:previewingDelegate sourceView:nativeSourceView]; -#endif + + RELEASE_TO_NIL(previewingDelegate); } @end diff --git a/iphone/Classes/TiUIiOSProxy.m b/iphone/Classes/TiUIiOSProxy.m index 0c30b2719de..de7c9705fee 100644 --- a/iphone/Classes/TiUIiOSProxy.m +++ b/iphone/Classes/TiUIiOSProxy.m @@ -470,7 +470,7 @@ - (TiBlob *)systemImage:(id)arg return nil; } ENSURE_SINGLE_ARG_OR_NIL(arg, NSString); - TiBlob *blob = [[TiBlob alloc] initWithSystemImage:arg]; + TiBlob *blob = [[[TiBlob alloc] initWithSystemImage:arg] autorelease]; return blob; } #endif @@ -912,9 +912,9 @@ - (TiColor *)fetchSemanticColor:(id)color DEPRECATED_REPLACED(@"UI.iOS.fetchSemanticColor", @"9.1.0", @"UI.fetchSemanticColor"); if ([TiUtils isIOSVersionOrGreater:@"11.0"]) { - return [[TiColor alloc] initWithColor:[UIColor colorNamed:color] name:nil]; + return [[[TiColor alloc] initWithColor:[UIColor colorNamed:color] name:nil] autorelease]; } - return [[TiColor alloc] initWithColor:UIColor.blackColor name:@"black"]; + return [[[TiColor alloc] initWithColor:UIColor.blackColor name:@"black"] autorelease]; } @end diff --git a/iphone/Resources/app.js b/iphone/Resources/app.js index 9559c132285..f118c72ef68 100644 --- a/iphone/Resources/app.js +++ b/iphone/Resources/app.js @@ -6,15 +6,15 @@ * to trigger a log that is displayed in the Xcode console. */ -var win = Ti.UI.createWindow({ +const win = Ti.UI.createWindow({ backgroundColor: '#fff' }); -var btn = Ti.UI.createButton({ +const btn = Ti.UI.createButton({ title: 'Trigger' }); -btn.addEventListener('click', function() { +btn.addEventListener('click', () => { Ti.API.info(L('hello_world')); }); diff --git a/iphone/TitaniumKit/TitaniumKit.xcodeproj/project.pbxproj b/iphone/TitaniumKit/TitaniumKit.xcodeproj/project.pbxproj index 24cf9984f5b..f005d9ab13f 100644 --- a/iphone/TitaniumKit/TitaniumKit.xcodeproj/project.pbxproj +++ b/iphone/TitaniumKit/TitaniumKit.xcodeproj/project.pbxproj @@ -27,6 +27,8 @@ B106A6FE24535CB800B1305E /* JSValue+Addons.m in Sources */ = {isa = PBXBuildFile; fileRef = B106A6FC24535CB800B1305E /* JSValue+Addons.m */; }; B10E8D502408559300578E8F /* APSHTTPClient.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = B10E8D4F2408559300578E8F /* APSHTTPClient.xcframework */; }; B10E8D5824085E1E00578E8F /* APSAnalytics.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = B10E8D5724085E1E00578E8F /* APSAnalytics.xcframework */; }; + B70ABB8424B79C9C0007D07A /* KrollPromise.h in Headers */ = {isa = PBXBuildFile; fileRef = B70ABB8324B79C9C0007D07A /* KrollPromise.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B70ABB8624B79DCF0007D07A /* KrollPromise.m in Sources */ = {isa = PBXBuildFile; fileRef = B70ABB8524B79DCF0007D07A /* KrollPromise.m */; }; B7624B6C216663C7000C365A /* ObjcProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = B7624B6A216663C7000C365A /* ObjcProxy.h */; settings = {ATTRIBUTES = (Public, ); }; }; B7624B6D216663C7000C365A /* ObjcProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = B7624B6B216663C7000C365A /* ObjcProxy.m */; }; DB1596592075389000292B19 /* APIModule.m in Sources */ = {isa = PBXBuildFile; fileRef = DB1596572075389000292B19 /* APIModule.m */; }; @@ -185,6 +187,8 @@ B106A6FC24535CB800B1305E /* JSValue+Addons.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "JSValue+Addons.m"; sourceTree = ""; }; B10E8D4F2408559300578E8F /* APSHTTPClient.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = APSHTTPClient.xcframework; path = TitaniumKit/Libraries/APSHTTPClient/APSHTTPClient.xcframework; sourceTree = ""; }; B10E8D5724085E1E00578E8F /* APSAnalytics.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = APSAnalytics.xcframework; path = TitaniumKit/Libraries/APSAnalytics/APSAnalytics.xcframework; sourceTree = ""; }; + B70ABB8324B79C9C0007D07A /* KrollPromise.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KrollPromise.h; sourceTree = ""; }; + B70ABB8524B79DCF0007D07A /* KrollPromise.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KrollPromise.m; sourceTree = ""; }; B7624B6A216663C7000C365A /* ObjcProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjcProxy.h; sourceTree = ""; }; B7624B6B216663C7000C365A /* ObjcProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ObjcProxy.m; sourceTree = ""; }; DB1596572075389000292B19 /* APIModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = APIModule.m; sourceTree = ""; }; @@ -417,6 +421,8 @@ DB258CDA1F0975F7000D0D8D /* Kroll */ = { isa = PBXGroup; children = ( + B70ABB8324B79C9C0007D07A /* KrollPromise.h */, + B70ABB8524B79DCF0007D07A /* KrollPromise.m */, DB258D1B1F097680000D0D8D /* KrollBridge.h */, DB258D1C1F097680000D0D8D /* KrollBridge.m */, DB15FC231F0A833100A82C45 /* KrollCallback.h */, @@ -666,6 +672,7 @@ DB15FD0C1F0A894000A82C45 /* TiWindowProxy.h in Headers */, DB258DB81F09770C000D0D8D /* TiUtils.h in Headers */, DB15FC351F0A833100A82C45 /* KrollContext.h in Headers */, + B70ABB8424B79C9C0007D07A /* KrollPromise.h in Headers */, DB258D9D1F097680000D0D8D /* TiUIView.h in Headers */, DB15FD2C1F0A93EF00A82C45 /* TiStreamProxy.h in Headers */, DB15FD241F0A8A7B00A82C45 /* TiBindingEvent.h in Headers */, @@ -819,6 +826,7 @@ DB15FC9D1F0A862D00A82C45 /* ListenerEntry.m in Sources */, DB15FC3C1F0A833100A82C45 /* KrollMethodDelegate.m in Sources */, DB15FD291F0A93A900A82C45 /* TiFilesystemFileStreamProxy.m in Sources */, + B70ABB8624B79DCF0007D07A /* KrollPromise.m in Sources */, DB15FC5C1F0A84C000A82C45 /* TiHost.m in Sources */, DB258D621F097680000D0D8D /* Bridge.m in Sources */, DB15FD071F0A892500A82C45 /* Ti2DMatrix.m in Sources */, diff --git a/iphone/TitaniumKit/TitaniumKit/Sources/API/KrollBridge.h b/iphone/TitaniumKit/TitaniumKit/Sources/API/KrollBridge.h index 05d2f87d44a..9d28e6d2f71 100644 --- a/iphone/TitaniumKit/TitaniumKit/Sources/API/KrollBridge.h +++ b/iphone/TitaniumKit/TitaniumKit/Sources/API/KrollBridge.h @@ -11,6 +11,7 @@ #import "TiEvaluator.h" #import "TiModule.h" #import "TiProxy.h" +#import @import Foundation; @import JavaScriptCore; @@ -34,7 +35,7 @@ extern NSString *TitaniumModuleRequireFormat; //CFMutableDictionaryRefs only retain keys, which lets them work with proxies properly. CFMutableDictionaryRef registeredProxies; NSCondition *shutdownCondition; - OSSpinLock proxyLock; + os_unfair_lock proxyLock; } - (void)boot:(id)callback url:(NSURL *)url_ preload:(NSDictionary *)preload_; - (void)evalJSWithoutResult:(NSString *)code; diff --git a/iphone/TitaniumKit/TitaniumKit/Sources/API/KrollBridge.m b/iphone/TitaniumKit/TitaniumKit/Sources/API/KrollBridge.m index e295d0d8633..c19e17f950b 100644 --- a/iphone/TitaniumKit/TitaniumKit/Sources/API/KrollBridge.m +++ b/iphone/TitaniumKit/TitaniumKit/Sources/API/KrollBridge.m @@ -65,7 +65,7 @@ - (void)dealloc } @end -OSSpinLock krollBridgeRegistryLock = OS_SPINLOCK_INIT; +os_unfair_lock krollBridgeRegistryLock = OS_UNFAIR_LOCK_INIT; CFMutableSetRef krollBridgeRegistry = nil; @implementation KrollBridge @@ -104,10 +104,10 @@ - (id)init #endif modules = [[NSMutableDictionary alloc] init]; packageJSONMainCache = [[NSMutableDictionary alloc] init]; - proxyLock = OS_SPINLOCK_INIT; - OSSpinLockLock(&krollBridgeRegistryLock); + proxyLock = OS_UNFAIR_LOCK_INIT; + os_unfair_lock_lock(&krollBridgeRegistryLock); CFSetAddValue(krollBridgeRegistry, self); - OSSpinLockUnlock(&krollBridgeRegistryLock); + os_unfair_lock_unlock(&krollBridgeRegistryLock); TiThreadPerformOnMainThread( ^{ [self registerForMemoryWarning]; @@ -119,16 +119,16 @@ - (id)init - (void)didReceiveMemoryWarning:(NSNotification *)notification { - OSSpinLockLock(&proxyLock); + os_unfair_lock_lock(&proxyLock); if (registeredProxies == NULL) { - OSSpinLockUnlock(&proxyLock); + os_unfair_lock_unlock(&proxyLock); [self gc]; return; } BOOL keepWarning = YES; signed long proxiesCount = CFDictionaryGetCount(registeredProxies); - OSSpinLockUnlock(&proxyLock); + os_unfair_lock_unlock(&proxyLock); // During a memory panic, we may not get the chance to copy proxies. while (keepWarning) { @@ -137,14 +137,14 @@ - (void)didReceiveMemoryWarning:(NSNotification *)notification for (id proxy in [(NSDictionary *)registeredProxies allKeys]) { [proxy didReceiveMemoryWarning:notification]; - OSSpinLockLock(&proxyLock); + os_unfair_lock_lock(&proxyLock); if (registeredProxies == NULL) { - OSSpinLockUnlock(&proxyLock); + os_unfair_lock_unlock(&proxyLock); break; } signed long newCount = CFDictionaryGetCount(registeredProxies); - OSSpinLockUnlock(&proxyLock); + os_unfair_lock_unlock(&proxyLock); if (newCount != proxiesCount) { proxiesCount = newCount; @@ -172,10 +172,10 @@ - (oneway void)release - (void)removeProxies { - OSSpinLockLock(&proxyLock); + os_unfair_lock_lock(&proxyLock); CFDictionaryRef oldProxies = registeredProxies; registeredProxies = NULL; - OSSpinLockUnlock(&proxyLock); + os_unfair_lock_unlock(&proxyLock); for (id thisProxy in (NSDictionary *)oldProxies) { KrollObject *thisKrollObject = (id)CFDictionaryGetValue(oldProxies, thisProxy); @@ -205,9 +205,9 @@ - (void)dealloc [self removeProxies]; RELEASE_TO_NIL(preload); RELEASE_TO_NIL(context); - OSSpinLockLock(&krollBridgeRegistryLock); + os_unfair_lock_lock(&krollBridgeRegistryLock); CFSetRemoveValue(krollBridgeRegistry, self); - OSSpinLockUnlock(&krollBridgeRegistryLock); + os_unfair_lock_unlock(&krollBridgeRegistryLock); [super dealloc]; } @@ -462,17 +462,17 @@ - (void)didStartNewContext:(KrollContext *)kroll TiModule *mod = [host moduleNamed:name context:self]; if (mod != nil) { KrollObject *ko = [self registerProxy:mod]; - result = [JSValue valueWithJSValueRef:[ko jsobject] inContext:[JSContext currentContext]]; + result = [JSValue valueWithJSValueRef:[ko jsobject] inContext:JSContext.currentContext]; } else { - result = [JSValue valueWithUndefinedInContext:[JSContext currentContext]]; + result = [JSValue valueWithUndefinedInContext:JSContext.currentContext]; } - [[JSContext currentThis] defineProperty:name - descriptor:@{ - JSPropertyDescriptorValueKey : result, - JSPropertyDescriptorWritableKey : @NO, - JSPropertyDescriptorEnumerableKey : @NO, - JSPropertyDescriptorConfigurableKey : @NO - }]; + [JSContext.currentThis defineProperty:name + descriptor:@{ + JSPropertyDescriptorValueKey : result, + JSPropertyDescriptorWritableKey : @NO, + JSPropertyDescriptorEnumerableKey : @NO, + JSPropertyDescriptorConfigurableKey : @NO + }]; return result; }; [titanium defineProperty:name @@ -492,17 +492,17 @@ - (void)didStartNewContext:(KrollContext *)kroll JSValue *result; Class moduleClass = NSClassFromString([NSString stringWithFormat:@"%@Module", name]); if (moduleClass != nil) { - result = [JSValue valueWithObject:[[moduleClass alloc] init] inContext:[JSContext currentContext]]; + result = [JSValue valueWithObject:[[moduleClass alloc] init] inContext:JSContext.currentContext]; } else { - result = [JSValue valueWithUndefinedInContext:[JSContext currentContext]]; + result = [JSValue valueWithUndefinedInContext:JSContext.currentContext]; } - [[JSContext currentThis] defineProperty:name - descriptor:@{ - JSPropertyDescriptorValueKey : result, - JSPropertyDescriptorWritableKey : @NO, - JSPropertyDescriptorEnumerableKey : @NO, - JSPropertyDescriptorConfigurableKey : @NO - }]; + [JSContext.currentThis defineProperty:name + descriptor:@{ + JSPropertyDescriptorValueKey : result, + JSPropertyDescriptorWritableKey : @NO, + JSPropertyDescriptorEnumerableKey : @NO, + JSPropertyDescriptorConfigurableKey : @NO + }]; return result; }; [titanium defineProperty:name @@ -604,7 +604,7 @@ - (void)didStopNewContext:(KrollContext *)kroll - (void)registerProxy:(id)proxy krollObject:(KrollObject *)ourKrollObject { - OSSpinLockLock(&proxyLock); + os_unfair_lock_lock(&proxyLock); if (registeredProxies == NULL) { registeredProxies = CFDictionaryCreateMutable(NULL, 10, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); } @@ -612,7 +612,7 @@ - (void)registerProxy:(id)proxy krollObject:(KrollObject *)ourKrollObject //CFMutableDictionaryRefs only retain keys, which lets them work with proxies properly. CFDictionaryAddValue(registeredProxies, proxy, ourKrollObject); - OSSpinLockUnlock(&proxyLock); + os_unfair_lock_unlock(&proxyLock); [proxy boundBridge:self withKrollObject:ourKrollObject]; } @@ -638,12 +638,12 @@ - (id)registerProxy:(id)proxy - (void)unregisterProxy:(id)proxy { - OSSpinLockLock(&proxyLock); + os_unfair_lock_lock(&proxyLock); if (registeredProxies != NULL) { CFDictionaryRemoveValue(registeredProxies, proxy); //Don't bother with removing the empty registry. It's small and leaves on dealloc anyways. } - OSSpinLockUnlock(&proxyLock); + os_unfair_lock_unlock(&proxyLock); [proxy unboundBridge:self]; } @@ -653,23 +653,23 @@ - (BOOL)usesProxy:(id)proxy return NO; } BOOL result = NO; - OSSpinLockLock(&proxyLock); + os_unfair_lock_lock(&proxyLock); if (registeredProxies != NULL) { result = (CFDictionaryGetCountOfKey(registeredProxies, proxy) != 0); } - OSSpinLockUnlock(&proxyLock); + os_unfair_lock_unlock(&proxyLock); return result; } - (id)krollObjectForProxy:(id)proxy { id result = nil; - OSSpinLockLock(&proxyLock); + os_unfair_lock_lock(&proxyLock); if (registeredProxies != NULL) { result = (id)CFDictionaryGetValue(registeredProxies, proxy); } - OSSpinLockUnlock(&proxyLock); + os_unfair_lock_unlock(&proxyLock); return result; } @@ -1351,7 +1351,7 @@ + (NSArray *)krollBridgesUsingProxy:(id)proxy { NSMutableArray *results = nil; - OSSpinLockLock(&krollBridgeRegistryLock); + os_unfair_lock_lock(&krollBridgeRegistryLock); signed long bridgeCount = CFSetGetCount(krollBridgeRegistry); KrollBridge *registryObjects[bridgeCount]; CFSetGetValues(krollBridgeRegistry, (const void **)registryObjects); @@ -1370,13 +1370,13 @@ + (NSArray *)krollBridgesUsingProxy:(id)proxy //Why do we wait so long? In case someone tries to dealloc the krollBridge while we're looking at it. //registryObjects nor the registry does a retain here! - OSSpinLockUnlock(&krollBridgeRegistryLock); + os_unfair_lock_unlock(&krollBridgeRegistryLock); return results; } + (NSArray *)krollContexts { - OSSpinLockLock(&krollBridgeRegistryLock); + os_unfair_lock_lock(&krollBridgeRegistryLock); signed long bridgeCount = CFSetGetCount(krollBridgeRegistry); KrollBridge *registryObjects[bridgeCount]; CFSetGetValues(krollBridgeRegistry, (const void **)registryObjects); @@ -1387,7 +1387,7 @@ + (NSArray *)krollContexts [results addObject:bridge.krollContext]; } - OSSpinLockUnlock(&krollBridgeRegistryLock); + os_unfair_lock_unlock(&krollBridgeRegistryLock); return [results autorelease]; } @@ -1398,7 +1398,7 @@ + (BOOL)krollBridgeExists:(KrollBridge *)bridge } bool result = NO; - OSSpinLockLock(&krollBridgeRegistryLock); + os_unfair_lock_lock(&krollBridgeRegistryLock); signed long bridgeCount = CFSetGetCount(krollBridgeRegistry); KrollBridge *registryObjects[bridgeCount]; CFSetGetValues(krollBridgeRegistry, (const void **)registryObjects); @@ -1411,7 +1411,7 @@ + (BOOL)krollBridgeExists:(KrollBridge *)bridge } //Why not CFSetContainsValue? Because bridge may not be a valid pointer, and SetContainsValue //will ask it for a hash! - OSSpinLockUnlock(&krollBridgeRegistryLock); + os_unfair_lock_unlock(&krollBridgeRegistryLock); return result; } @@ -1423,13 +1423,13 @@ + (KrollBridge *)krollBridgeForThreadName:(NSString *)threadName; } KrollBridge *result = nil; - OSSpinLockLock(&krollBridgeRegistryLock); + os_unfair_lock_lock(&krollBridgeRegistryLock); signed long bridgeCount = CFSetGetCount(krollBridgeRegistry); KrollBridge *registryObjects[bridgeCount]; CFSetGetValues(krollBridgeRegistry, (const void **)registryObjects); for (int currentBridgeIndex = 0; currentBridgeIndex < bridgeCount; currentBridgeIndex++) { } - OSSpinLockUnlock(&krollBridgeRegistryLock); + os_unfair_lock_unlock(&krollBridgeRegistryLock); return result; } diff --git a/iphone/TitaniumKit/TitaniumKit/Sources/API/ObjcProxy.h b/iphone/TitaniumKit/TitaniumKit/Sources/API/ObjcProxy.h index 4953f5df1f5..0f8bc76e473 100644 --- a/iphone/TitaniumKit/TitaniumKit/Sources/API/ObjcProxy.h +++ b/iphone/TitaniumKit/TitaniumKit/Sources/API/ObjcProxy.h @@ -161,4 +161,10 @@ JSExportAs(fireEvent, * the "page context" in their initializers. **/ - (id)executionContext; + +/** + * Due to mix and match of new and old C API, JSContext.currentContext (and currentThis) may be null + * If we get called back through the old C API functionc allback, it will be, so we need to hack. + */ +- (JSContext *)currentContext; @end diff --git a/iphone/TitaniumKit/TitaniumKit/Sources/API/ObjcProxy.m b/iphone/TitaniumKit/TitaniumKit/Sources/API/ObjcProxy.m index b571d2dfb75..b2130b73e16 100644 --- a/iphone/TitaniumKit/TitaniumKit/Sources/API/ObjcProxy.m +++ b/iphone/TitaniumKit/TitaniumKit/Sources/API/ObjcProxy.m @@ -56,14 +56,14 @@ - (JSValue *)createError:(NSString *)reason subreason:(NSString *)subreason loca - (void)throwException:(NSString *)reason subreason:(NSString *)subreason location:(NSString *)location { - JSContext *context = [JSContext currentContext]; + JSContext *context = JSContext.currentContext; JSValue *error = [self createError:reason subreason:subreason location:location inContext:context]; [context setException:error]; } + (void)throwException:(NSString *)reason subreason:(NSString *)subreason location:(NSString *)location { - JSContext *context = [JSContext currentContext]; + JSContext *context = JSContext.currentContext; JSValue *error = [ObjcProxy createError:reason subreason:subreason location:location inContext:context]; [context setException:error]; } @@ -320,8 +320,23 @@ - (void)_configure - (id)executionContext { - KrollContext *context = GetKrollContext([[JSContext currentContext] JSGlobalContextRef]); + KrollContext *context = GetKrollContext([self currentContext].JSGlobalContextRef); return (KrollBridge *)[context delegate]; } +- (JSContext *)currentContext +{ + JSContext *cur = JSContext.currentContext; + if (cur != nil) { + return cur; + } + KrollBridge *bridge = [KrollBridge krollBridgeForThreadName:NSThread.currentThread.name]; + if (bridge != nil) { + KrollContext *krollContext = bridge.krollContext; + JSGlobalContextRef globalRef = krollContext.context; + return [JSContext contextWithJSGlobalContextRef:globalRef]; + } + return nil; +} + @end diff --git a/iphone/TitaniumKit/TitaniumKit/Sources/API/TiExceptionHandler.m b/iphone/TitaniumKit/TitaniumKit/Sources/API/TiExceptionHandler.m index c4099464ee3..56ba396d6fa 100644 --- a/iphone/TitaniumKit/TitaniumKit/Sources/API/TiExceptionHandler.m +++ b/iphone/TitaniumKit/TitaniumKit/Sources/API/TiExceptionHandler.m @@ -46,7 +46,7 @@ + (TiExceptionHandler *)defaultExceptionHandler - (void)reportException:(NSException *)exception { // attempt to generate a script error, which includes JS stack information - JSContext *context = [JSContext currentContext]; + JSContext *context = JSContext.currentContext; JSValue *jsError = [JSValue valueWithNewErrorFromMessage:[exception reason] inContext:context]; @try { TiScriptError *error = [TiUtils scriptErrorValue:@{ diff --git a/iphone/TitaniumKit/TitaniumKit/Sources/API/TiProxy.m b/iphone/TitaniumKit/TitaniumKit/Sources/API/TiProxy.m index 3aab676313d..d43ba4f3f81 100644 --- a/iphone/TitaniumKit/TitaniumKit/Sources/API/TiProxy.m +++ b/iphone/TitaniumKit/TitaniumKit/Sources/API/TiProxy.m @@ -900,10 +900,17 @@ - (void)fireEvent:(NSString *)type withObject:(id)obj propagate:(BOOL)propagate return; } + __weak TiProxy *weakSelf = self; + TiThreadPerformOnMainThread( ^{ + TiProxy *strongSelf = weakSelf; + if (strongSelf == nil) { + return; + } + TiBindingEvent ourEvent; - ourEvent = TiBindingEventCreateWithNSObjects(self, self, type, obj); + ourEvent = TiBindingEventCreateWithNSObjects(strongSelf, strongSelf, type, obj); if (report || (code != 0)) { TiBindingEventSetErrorCode(ourEvent, code); } diff --git a/iphone/TitaniumKit/TitaniumKit/Sources/API/TopTiModule.m b/iphone/TitaniumKit/TitaniumKit/Sources/API/TopTiModule.m index 304803cdcef..cc3f4c8f183 100644 --- a/iphone/TitaniumKit/TitaniumKit/Sources/API/TopTiModule.m +++ b/iphone/TitaniumKit/TitaniumKit/Sources/API/TopTiModule.m @@ -68,7 +68,7 @@ - (JSValue *)createBuffer:(id)arg ENSURE_INT_OR_NIL_FOR_KEY(byteOrder, arg, @"byteOrder", hasByteOrder); // Hack to get our KrollBridge - JSContext *objcJsContext = [JSContext currentContext]; + JSContext *objcJsContext = JSContext.currentContext; JSGlobalContextRef contextRef = [objcJsContext JSGlobalContextRef]; KrollContext *context = GetKrollContext(contextRef); KrollBridge *ourBridge = (KrollBridge *)[context delegate]; diff --git a/iphone/TitaniumKit/TitaniumKit/Sources/Kroll/KrollPromise.h b/iphone/TitaniumKit/TitaniumKit/Sources/Kroll/KrollPromise.h new file mode 100644 index 00000000000..33cf236a8b3 --- /dev/null +++ b/iphone/TitaniumKit/TitaniumKit/Sources/Kroll/KrollPromise.h @@ -0,0 +1,30 @@ +/** + * Appcelerator Titanium Mobile + * Copyright (c) 2020-Present by Appcelerator, Inc. All Rights Reserved. + * Licensed under the terms of the Apache Public License + * Please see the LICENSE included with this distribution for details. +*/ +#ifndef KrollPromise_h +#define KrollPromise_h + +#import + +@interface KrollPromise : NSObject { + @private + JSValue *resolveFunc; + JSValue *rejectFunc; +} + +@property (readonly) JSValue *JSValue; + +- (void)resolve:(NSArray *)arguments; +- (void)reject:(NSArray *)arguments; + +- (KrollPromise *)initInContext:(JSContext *)context; + ++ (JSValue *)resolved:(NSArray *)arguments inContext:(JSContext *)context; ++ (JSValue *)rejected:(NSArray *)arguments inContext:(JSContext *)context; + +@end + +#endif /* KrollPromise_h */ diff --git a/iphone/TitaniumKit/TitaniumKit/Sources/Kroll/KrollPromise.m b/iphone/TitaniumKit/TitaniumKit/Sources/Kroll/KrollPromise.m new file mode 100644 index 00000000000..86edd795e9d --- /dev/null +++ b/iphone/TitaniumKit/TitaniumKit/Sources/Kroll/KrollPromise.m @@ -0,0 +1,73 @@ +#import "KrollPromise.h" +#import "TiExceptionHandler.h" + +@implementation KrollPromise + +- (KrollPromise *)initInContext:(JSContext *)context +{ + if (self = [super init]) { + if (@available(iOS 13, *)) { + // Use iOS 13 APIs. + JSObjectRef resolve; + JSObjectRef reject; + JSValueRef exception = NULL; + _JSValue = [JSValue valueWithJSValueRef:JSObjectMakeDeferredPromise(context.JSGlobalContextRef, &resolve, &reject, &exception) inContext:context]; + if (exception) { + // report exception + [TiExceptionHandler.defaultExceptionHandler reportScriptError:[JSValue valueWithJSValueRef:exception inContext:context] inJSContext:context]; + } + resolveFunc = [[JSValue valueWithJSValueRef:resolve inContext:context] retain]; + rejectFunc = [[JSValue valueWithJSValueRef:reject inContext:context] retain]; + } else { + // Alternative code for earlier versions of iOS. We hack it by evaluating JS + // TODO: I assume this is pretty slow. Can we re-use eval'd values here? + JSValue *executor = [context evaluateScript:@"function executor(resolve, reject) { executor.resolve = resolve; executor.reject = reject; }\nexecutor;"]; + JSValue *exception = context.exception; + if (exception != nil) { + [TiExceptionHandler.defaultExceptionHandler reportScriptError:exception inJSContext:context]; + } + JSValue *createPromise = [context evaluateScript:@"function createPromise(executor) { return new Promise(executor); }\ncreatePromise;"]; + exception = context.exception; + if (exception != nil) { + [TiExceptionHandler.defaultExceptionHandler reportScriptError:exception inJSContext:context]; + } + _JSValue = [createPromise callWithArguments:@[ executor ]]; + resolveFunc = [executor[@"resolve"] retain]; + rejectFunc = [executor[@"reject"] retain]; + } + } + return self; +} + ++ (JSValue *)resolved:(NSArray *)arguments inContext:(JSContext *)context +{ + KrollPromise *promise = [[[KrollPromise alloc] initInContext:context] autorelease]; + [promise resolve:arguments]; + return promise.JSValue; +} + ++ (JSValue *)rejected:(NSArray *)arguments inContext:(JSContext *)context +{ + KrollPromise *promise = [[[KrollPromise alloc] initInContext:context] autorelease]; + [promise reject:arguments]; + return promise.JSValue; +} + +- (void)resolve:(NSArray *)arguments +{ + [resolveFunc callWithArguments:arguments]; +} + +- (void)reject:(NSArray *)arguments +{ + [rejectFunc callWithArguments:arguments]; +} + +- (void)dealloc +{ + [resolveFunc release]; + [rejectFunc release]; + [super dealloc]; +} + +@end diff --git a/package-lock.json b/package-lock.json index 36b7db16548..98a68d56fb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3520,9 +3520,9 @@ "optional": true }, "@eslint/eslintrc": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.1.tgz", - "integrity": "sha512-XRUeBZ5zBWLYgSANMpThFddrZZkEbGHgUdt5UJjZfnlN9BGCiUBrf+nvbRupSjMvqzwnQN0qwCmOxITt1cfywA==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.2.tgz", + "integrity": "sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -6805,9 +6805,9 @@ "dev": true }, "core-js": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.0.tgz", - "integrity": "sha512-W2VYNB0nwQQE7tKS7HzXd7r2y/y2SVJl4ga6oH/dnaLFzM0o2lB2P3zCkWj5Wc/zyMYjtgd5Hmhk0ObkQFZOIA==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.1.tgz", + "integrity": "sha512-9Id2xHY1W7m8hCl8NkhQn5CufmF/WuR30BTRewvCXc1aZd3kMECwNZ69ndLbekKfakw9Rf2Xyc+QR6E7Gg+obg==", "dev": true }, "core-js-compat": { @@ -7057,9 +7057,9 @@ "dev": true }, "dateformat": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.0.0.tgz", - "integrity": "sha512-zpKyDYpeePyYGJp2HhRxLHlA+jZQNjt+MwmcVmLxCIECeC4Ks3TI3yk/CSMKylbnCJ5htonfOugYtRRTpyoHow==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.3.1.tgz", + "integrity": "sha512-xhq1wI5BQ0TMJDvio0BLP8lNeYlhAvmh/7H52H9n6kfzqSmRpIhH5KEIjJ7onFEAh5CQVrAP2MAG8wZ6j0BKzQ==", "dev": true }, "debug": { @@ -7382,13 +7382,13 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.14.0.tgz", - "integrity": "sha512-5YubdnPXrlrYAFCKybPuHIAH++PINe1pmKNc5wQRB9HSbqIK1ywAnntE3Wwua4giKu0bjligf1gLF6qxMGOYRA==", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.15.0.tgz", + "integrity": "sha512-Vr64xFDT8w30wFll643e7cGrIkPEU50yIiI36OdSIDoSGguIeaLzBo0vpGvzo9RECUqq7htURfwEtKqwytkqzA==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@eslint/eslintrc": "^0.2.1", + "@eslint/eslintrc": "^0.2.2", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -7398,10 +7398,10 @@ "eslint-scope": "^5.1.1", "eslint-utils": "^2.1.0", "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.0", + "espree": "^7.3.1", "esquery": "^1.2.0", "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", + "file-entry-cache": "^6.0.0", "functional-red-black-tree": "^1.0.1", "glob-parent": "^5.0.0", "globals": "^12.1.0", @@ -7774,13 +7774,13 @@ "dev": true }, "espree": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz", - "integrity": "sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", "dev": true, "requires": { "acorn": "^7.4.0", - "acorn-jsx": "^5.2.0", + "acorn-jsx": "^5.3.1", "eslint-visitor-keys": "^1.3.0" }, "dependencies": { @@ -8076,12 +8076,12 @@ } }, "file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.0.tgz", + "integrity": "sha512-fqoO76jZ3ZnYrXLDRxBR1YvOvc0k844kcOg40bgsPrE25LAb/PDqTY+ho64Xh2c8ZXgIKldchCFHczG2UVRcWA==", "dev": true, "requires": { - "flat-cache": "^2.0.1" + "flat-cache": "^3.0.4" } }, "file-state-monitor": { @@ -8257,20 +8257,19 @@ "dev": true }, "flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, "requires": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" + "flatted": "^3.1.0", + "rimraf": "^3.0.2" }, "dependencies": { "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { "glob": "^7.1.3" @@ -8279,9 +8278,9 @@ } }, "flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.0.tgz", + "integrity": "sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==", "dev": true }, "folder-hash": { @@ -9323,9 +9322,9 @@ "dev": true }, "husky": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/husky/-/husky-4.3.0.tgz", - "integrity": "sha512-tTMeLCLqSBqnflBZnlVDhpaIMucSGaYyX6855jM4AguGeWCeSzNdb1mfyWduTZ3pe3SJVvVWGL0jO1iKZVPfTA==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/husky/-/husky-4.3.5.tgz", + "integrity": "sha512-E5S/1HMoDDaqsH8kDF5zeKEQbYqe3wL9zJDyqyYqc8I4vHBtAoxkDBGXox0lZ9RI+k5GyB728vZdmnM4bYap+g==", "dev": true, "requires": { "chalk": "^4.0.0", @@ -9341,12 +9340,11 @@ }, "dependencies": { "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -10371,9 +10369,9 @@ } }, "lint-staged": { - "version": "10.5.2", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.5.2.tgz", - "integrity": "sha512-e8AYR1TDlzwB8VVd38Xu2lXDZf6BcshVqKVuBQThDJRaJLobqKnpbm4dkwJ2puypQNbLr9KF/9mfA649mAGvjA==", + "version": "10.5.3", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.5.3.tgz", + "integrity": "sha512-TanwFfuqUBLufxCc3RUtFEkFraSPNR3WzWcGF39R3f2J7S9+iF9W0KTVLfSy09lYGmZS5NDCxjNvhGMSJyFCWg==", "dev": true, "requires": { "chalk": "^4.1.0", @@ -10600,9 +10598,9 @@ } }, "listr2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.2.2.tgz", - "integrity": "sha512-AajqcZEUikF2ioph6PfH3dIuxJclhr3i3kHgTOP0xeXdWQohrvJAAmqVcV43/GI987HFY/vzT73jYXoa4esDHg==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.2.3.tgz", + "integrity": "sha512-vUb80S2dSUi8YxXahO8/I/s29GqnOL8ozgHVLjfWQXa03BNEeS1TpBLjh2ruaqq5ufx46BRGvfymdBSuoXET5w==", "dev": true, "requires": { "chalk": "^4.1.0", @@ -14593,9 +14591,9 @@ } }, "rollup": { - "version": "2.34.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.34.0.tgz", - "integrity": "sha512-dW5iLvttZzdVehjEuNJ1bWvuMEJjOWGmnuFS82WeKHTGXDkRHQeq/ExdifkSyJv9dLcR86ysKRmrIDyR6O0X8g==", + "version": "2.34.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.34.2.tgz", + "integrity": "sha512-mvtQLqu3cNeoctS+kZ09iOPxrc1P1/Bt1z15enuQ5feyKOdM3MJAVFjjsygurDpSWn530xB4AlA83TWIzRstXA==", "dev": true, "requires": { "fsevents": "~2.1.2" @@ -16138,15 +16136,6 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, - "write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - }, "write-file-atomic": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", diff --git a/package.json b/package.json index 1e731782fb5..391768de189 100644 --- a/package.json +++ b/package.json @@ -134,24 +134,24 @@ "commander": "^6.2.0", "commitizen": "^4.2.2", "conventional-changelog-cli": "^2.1.1", - "core-js": "^3.8.0", + "core-js": "^3.8.1", "cz-conventional-changelog": "^3.3.0", "danger": "^10.5.4", - "dateformat": "^4.0.0", - "eslint": "^7.14.0", + "dateformat": "^4.3.1", + "eslint": "^7.15.0", "eslint-config-axway": "^5.0.0", "eslint-plugin-mocha": "^8.0.0", "folder-hash": "^4.0.0", "glob": "^7.1.6", - "husky": "^4.3.0", - "lint-staged": "^10.5.2", + "husky": "^4.3.5", + "lint-staged": "^10.5.3", "lockfile-lint": "^4.3.7", "mocha": "^8.2.1", "mocha-jenkins-reporter": "^0.4.5", "npm-run-all": "^4.1.5", "nyc": "^15.1.0", "request-promise-native": "^1.0.9", - "rollup": "^2.34.0", + "rollup": "^2.34.2", "ssri": "^8.0.0", "stream-splitter": "^0.3.2", "strip-ansi": "^6.0.0", diff --git a/support/module/packaged/modules.json b/support/module/packaged/modules.json index 2c87c24e294..aaa32a4e1dc 100644 --- a/support/module/packaged/modules.json +++ b/support/module/packaged/modules.json @@ -21,8 +21,8 @@ "integrity": "sha512-FknsuivFl+0IcPTQnrc7OAdAv9yy0lJ7emgcX3U0xbaF7vBHUnz3TTLRb7IIguLfWmCBXmZnpcthEdyfKlQT3A==" }, "ti.identity": { - "url": "https://github.com/appcelerator-modules/titanium-identity/releases/download/v2.0.0-iphone/ti.identity-iphone-2.0.0.zip", - "integrity": "sha512-jZ/RgyWFprQlVpdNvboZYL90tghgBgKIKeQGyABPLqWBsqsvoCjYqcRMKbD1CJRCFH4amQ3yMkTY9tKdsyMz3g==" + "url": "https://github.com/appcelerator-modules/titanium-identity/releases/download/v3.0.0-iphone/ti.identity-iphone-3.0.0.zip", + "integrity": "sha512-SK4LhosC3Y+h0KX+yyXvF/8ocQTbKzOlgJ9t8XnNzIBg0bWShREimbnunkCXWlFk/fgP7Vntx4m9LFm0p+vAJw==" }, "ti.applesignin": { "url": "https://github.com/appcelerator-modules/titanium-apple-sign-in/releases/download/v2.0.0/ti.applesignin-iphone-2.0.0.zip", diff --git a/tests/Resources/app.js b/tests/Resources/app.js index 5d7a962fb04..fc59a42b8fe 100644 --- a/tests/Resources/app.js +++ b/tests/Resources/app.js @@ -163,6 +163,7 @@ function loadTests() { require('./ti.ui.ios.previewcontext.test'); require('./ti.ui.ios.splitwindow.test'); require('./ti.ui.ios.statusbar.test'); + require('./ti.ui.ios.stepper.test'); require('./ti.ui.ios.tabbedbar.test'); require('./ti.ui.ios.tableviewstyle.test'); require('./ti.ui.ios.webviewconfiguration.test'); diff --git a/tests/Resources/ti.analytics.test.js b/tests/Resources/ti.analytics.test.js index 6d7c7a30cba..eb36a417434 100644 --- a/tests/Resources/ti.analytics.test.js +++ b/tests/Resources/ti.analytics.test.js @@ -7,78 +7,82 @@ /* eslint-env mocha */ /* eslint no-unused-expressions: "off" */ 'use strict'; -var should = require('./utilities/assertions'), - utilities = require('./utilities/utilities'); +const should = require('./utilities/assertions'); -describe('Titanium.Analytics', function () { +describe('Titanium.Analytics', () => { - it('apiName', function () { - should(Ti.Analytics).have.a.readOnlyProperty('apiName').which.is.a.String(); - should(Ti.Analytics.apiName).be.eql('Ti.Analytics'); - }); + describe('properties', () => { + describe('.apiName', () => { + it('is a read-only String', () => { + should(Ti.Analytics).have.readOnlyProperty('apiName').which.is.a.String(); + }); - // FIXME: this is an invalid test as lastEvent can return null or undefined if an event is not queued - it.allBroken('lastEvent', function () { - should(Ti.Analytics.lastEvent).not.be.undefined(); - // FIXME: iOS and Android return a JSON string value here, while Windows has an Object! - if (utilities.isWindows()) { - should(Ti.Analytics.lastEvent).be.a.Object(); - } else { - should(Ti.Analytics.lastEvent).be.a.String(); - } - }); + it('equals \'Ti.Analytics\'', () => { + should(Ti.Analytics.apiName).be.eql('Ti.Analytics'); + }); - it('#getLastEvent()', function () { - should(Ti.Analytics.getLastEvent).not.be.undefined(); - should(Ti.Analytics.getLastEvent).be.a.Function(); - }); + it('has a getter', () => { + should(Ti.Analytics).have.a.getter('apiName'); + }); + }); - it('#featureEvent()', function () { - should(Ti.Analytics.featureEvent).not.be.undefined(); - should(Ti.Analytics.featureEvent).be.a.Function(); - }); + describe('.lastEvent', () => { + // FIXME: this is an invalid test as lastEvent can return null or undefined if an event is not queued + it.allBroken('is a read-only String', () => { + should(Ti.Analytics).have.a.readOnlyProperty('lastEvent').which.is.a.String(); + }); - // TODO: implement Titanium.Analytics.filterEvents on Windows? - it.windowsMissing('#filterEvents()', function () { - should(Ti.Analytics.filterEvents).not.be.undefined(); - should(Ti.Analytics.filterEvents).be.a.Function(); - }); + it('has a getter', () => { + should(Ti.Analytics).have.a.getter('lastEvent'); + }); + }); - it('#navEvent()', function () { - should(Ti.Analytics.navEvent).not.be.undefined(); - should(Ti.Analytics.navEvent).be.a.Function(); - }); + describe('.optedOut', () => { + it('is a Boolean', () => { + should(Ti.Analytics).have.a.property('optedOut').which.is.a.Boolean(); + }); - it('#featureEvent() validate limitations', function () { - var payloads = require('./analytics/featureEventPayload.json'), - tests = { - largeInvalid: -1, - complexInvalid: -1, - complexValid: 0, - maxKeysInvalid: -1 - }, - t; - for (t in tests) { - should(Ti.Analytics.featureEvent(t, payloads[t])).be.eql(tests[t]); - } - }); + it('defaults to false', () => { + should(Ti.Analytics.optedOut).be.false(); + }); - it.androidMissing('.optedOut', function () { - should(Ti.Analytics.optedOut).be.a.Boolean(); - should(Ti.Analytics.setOptedOut).be.a.Function(); - should(Ti.Analytics.getOptedOut).be.a.Function(); + it('can be assigned a Boolean value', () => { + Ti.Analytics.optedOut = true; + should(Ti.Analytics.optedOut).be.true(); + }); - should(Ti.Analytics.optedOut).be.false(); - should(Ti.Analytics.getOptedOut()).be.false(); + it('has accessors', () => { + should(Ti.Analytics).have.accessors('optedOut'); + }); + }); + }); - Ti.Analytics.optedOut = true; + describe('methods', () => { + describe('#featureEvent()', () => { + it('is a Function', () => { + should(Ti.Analytics).have.a.property('featureEvent').which.is.a.Function(); + }); - should(Ti.Analytics.optedOut).be.true(); - should(Ti.Analytics.getOptedOut()).be.true(); + it('validate limitations', () => { + const payloads = require('./analytics/featureEventPayload.json'); + const tests = { + largeInvalid: -1, + complexInvalid: -1, + complexValid: 0, + maxKeysInvalid: -1 + }; + for (const t in tests) { + should(Ti.Analytics.featureEvent(t, payloads[t])).be.eql(tests[t]); + } + }); + }); - Ti.Analytics.setOptedOut(false); + it.windowsMissing('#filterEvents() is a Function', () => { + should(Ti.Analytics).have.a.property('filterEvents').which.is.a.Function(); + }); - should(Ti.Analytics.optedOut).be.false(); - should(Ti.Analytics.getOptedOut()).be.false(); + it('#navEvent() is a Function', () => { + should(Ti.Analytics).have.a.property('navEvent').which.is.a.Function(); + }); }); }); diff --git a/tests/Resources/ti.app.test.js b/tests/Resources/ti.app.test.js index dc4e5238ec4..ed19ff97b8c 100644 --- a/tests/Resources/ti.app.test.js +++ b/tests/Resources/ti.app.test.js @@ -6,213 +6,258 @@ */ /* eslint-env mocha */ /* eslint no-unused-expressions: "off" */ +/* eslint mocha/no-identical-title: "off" */ 'use strict'; -var should = require('./utilities/assertions'); +const should = require('./utilities/assertions'); -describe('Titanium.App', function () { +describe('Titanium.App', () => { - it('EVENT_ACCESSIBILITY_ANNOUNCEMENT', function () { - should(Ti.App).have.constant('EVENT_ACCESSIBILITY_ANNOUNCEMENT').which.is.eql('accessibilityannouncement'); - }); + describe('constants', () => { + it('EVENT_ACCESSIBILITY_ANNOUNCEMENT', () => { + should(Ti.App).have.constant('EVENT_ACCESSIBILITY_ANNOUNCEMENT').which.is.eql('accessibilityannouncement'); + }); - it('EVENT_ACCESSIBILITY_CHANGED', function () { - should(Ti.App).have.constant('EVENT_ACCESSIBILITY_CHANGED').which.is.eql('accessibilitychanged'); + it('EVENT_ACCESSIBILITY_CHANGED', () => { + should(Ti.App).have.constant('EVENT_ACCESSIBILITY_CHANGED').which.is.eql('accessibilitychanged'); + }); }); - // TODO Add tests for set* methods! + describe('properties', () => { + describe('.apiName', () => { + it('is a read-only String', () => { + should(Ti.App).have.readOnlyProperty('apiName').which.is.a.String(); + }); - it('apiName', function () { - should(Ti.App.apiName).be.eql('Ti.App'); - should(Ti.App).have.readOnlyProperty('apiName').which.is.a.String(); - }); + it('equals \'Ti.App\'', () => { + should(Ti.App.apiName).be.eql('Ti.App'); + }); - it('accessibilityEnabled', function () { - should(Ti.App).have.readOnlyProperty('accessibilityEnabled').which.is.a.Boolean(); - }); + it('has a getter', () => { + should(Ti.App).have.a.getter('apiName'); + }); + }); - it('#getAccessibilityEnabled()', function () { - should(Ti.App.getAccessibilityEnabled).be.a.Function(); - should(Ti.App.getAccessibilityEnabled()).be.a.Boolean(); - }); + describe('.accessibilityEnabled', () => { + it('is a read-only Boolean', () => { + should(Ti.App).have.a.readOnlyProperty('accessibilityEnabled').which.is.a.Boolean(); + }); - it('analytics', function () { - should(Ti.App).have.readOnlyProperty('analytics').which.is.a.Boolean(); - }); + it('has a getter', () => { + should(Ti.App).have.a.getter('accessibilityEnabled'); + }); + }); - it('#getAnalytics()', function () { - should(Ti.App.getAnalytics).be.a.Function(); - should(Ti.App.getAnalytics()).be.a.Boolean(); - }); + describe('.analytics', () => { + it('is a read-only Boolean', () => { + should(Ti.App).have.a.readOnlyProperty('analytics').which.is.a.Boolean(); + }); - it('copyright', function () { - should(Ti.App).have.readOnlyProperty('copyright').which.is.a.String(); - }); + it('has a getter', () => { + should(Ti.App).have.a.getter('analytics'); + }); + }); - it('#getCopyright()', function () { - should(Ti.App.getCopyright).be.a.Function(); - should(Ti.App.getCopyright()).be.a.String(); - }); + describe('.copyright', () => { + it('is a read-only String', () => { + should(Ti.App).have.a.readOnlyProperty('copyright').which.is.a.String(); + }); - it('deployType', function () { - should(Ti.App).have.readOnlyProperty('deployType').which.is.a.String(); - }); + it('has a getter', () => { + should(Ti.App).have.a.getter('copyright'); + }); + }); - it('#getDeployType()', function () { - should(Ti.App.getDeployType).be.a.Function(); - should(Ti.App.getDeployType()).be.a.String(); - }); + describe('.deployType', () => { + it('is a read-only String', () => { + should(Ti.App).have.a.readOnlyProperty('deployType').which.is.a.String(); + }); - it('description', function () { - should(Ti.App).have.readOnlyProperty('description').which.is.a.String(); - }); + it('has a getter', () => { + should(Ti.App).have.a.getter('deployType'); + }); + }); - it('#getDescription()', function () { - should(Ti.App.getDescription).be.a.Function(); - should(Ti.App.getDescription()).be.a.String(); - }); + describe('.description', () => { + it('is a read-only String', () => { + should(Ti.App).have.a.readOnlyProperty('description').which.is.a.String(); + }); - it.ios('disableNetworkActivityIndicator', function () { - should(Ti.App.disableNetworkActivityIndicator).be.a.Boolean(); - }); + it('has a getter', () => { + should(Ti.App).have.a.getter('description'); + }); + }); - it.ios('#getDisableNetworkActivityIndicator()', function () { - should(Ti.App.getDisableNetworkActivityIndicator).be.a.Function(); - should(Ti.App.getDisableNetworkActivityIndicator()).be.a.Boolean(); - }); + describe.ios('.disableNetworkActivityIndicator', () => { + it('is a Boolean', () => { + should(Ti.App).have.a.property('disableNetworkActivityIndicator').which.is.a.Boolean(); + }); - it.ios('forceSplashAsSnapshot', function () { - should(Ti.App.forceSplashAsSnapshot).be.a.Boolean(); - should(Ti.App.forceSplashAsSnapshot).be.false(); + it('defaults to false', () => { + should(Ti.App.disableNetworkActivityIndicator).be.false(); + }); - Ti.App.forceSplashAsSnapshot = true; - should(Ti.App.forceSplashAsSnapshot).be.true(); + it('can be assigned a Boolean value', () => { + Ti.App.disableNetworkActivityIndicator = true; + should(Ti.App.disableNetworkActivityIndicator).be.true(); + }); - Ti.App.forceSplashAsSnapshot = false; - should(Ti.App.forceSplashAsSnapshot).be.false(); - }); + it('has accessors', () => { + should(Ti.App).have.accessors('disableNetworkActivityIndicator'); + }); + }); - it.ios('#getForceSplashAsSnapshot()', function () { - should(Ti.App.getForceSplashAsSnapshot).be.a.Function(); - should(Ti.App.getForceSplashAsSnapshot()).be.a.Boolean(); - }); + describe.ios('.forceSplashAsSnapshot', () => { + it('is a Boolean', () => { + should(Ti.App).have.a.property('forceSplashAsSnapshot').which.is.a.Boolean(); + }); - it('guid', function () { - should(Ti.App).have.readOnlyProperty('guid').which.is.a.String(); - }); + it('can be assigned a Boolean value', () => { + Ti.App.forceSplashAsSnapshot = true; + should(Ti.App.forceSplashAsSnapshot).be.true(); + }); - it('#getGuid()', function () { - should(Ti.App.getGuid).be.a.Function(); - should(Ti.App.getGuid()).be.a.String(); - }); + it('has accessors', () => { + should(Ti.App).have.accessors('forceSplashAsSnapshot'); + }); + }); - it('id', function () { - should(Ti.App).have.readOnlyProperty('id').which.is.a.String(); - }); + describe('.guid', () => { + it('is a read-only String', () => { + should(Ti.App).have.a.readOnlyProperty('guid').which.is.a.String(); + }); - it('getId()', function () { - should(Ti.App.getId).be.a.Function(); - should(Ti.App.getId()).be.a.String(); - }); + it('has a getter', () => { + should(Ti.App).have.a.getter('guid'); + }); + }); - it.ios('idleTimerDisabled', function () { - // FIXME Windows has this property and getter below and defaults to false, but you can't change it - should(Ti.App.idleTimerDisabled).be.a.Boolean(); - should(Ti.App.idleTimerDisabled).be.false(); - }); + describe('.id', () => { + it('is a read-only String', () => { + should(Ti.App).have.a.readOnlyProperty('id').which.is.a.String(); + }); - it.ios('#getIdleTimerDisabled()', function () { - should(Ti.App.getIdleTimerDisabled).be.a.Function(); - should(Ti.App.getIdleTimerDisabled()).be.a.Boolean(); - should(Ti.App.getIdleTimerDisabled()).be.false(); - }); + it('has a getter', () => { + should(Ti.App).have.a.getter('id'); + }); + }); - // TODO Add to Android API? - it.androidMissing('installId', function () { - should(Ti.App).have.readOnlyProperty('installId').which.is.a.String(); - }); + describe.ios('.idleTimerDisabled', () => { + it('is a Boolean', () => { + should(Ti.App).have.a.property('idleTimerDisabled').which.is.a.Boolean(); + }); - // TODO Add to Android API? - it.androidMissing('#getInstallId()', function () { - should(Ti.App.getInstallId).be.a.Function(); - should(Ti.App.getInstallId()).be.a.String(); - }); + it('can be assigned a Boolean value', () => { + Ti.App.idleTimerDisabled = true; + should(Ti.App.idleTimerDisabled).be.true(); + }); - // TODO Add to Android API? - it.androidMissing('keyboardVisible', function () { - should(Ti.App).have.readOnlyProperty('keyboardVisible').which.is.a.Boolean(); - }); + it('has accessors', () => { + should(Ti.App).have.accessors('idleTimerDisabled'); + }); + }); - // TODO Add to Android API? - it.androidMissing('#getKeyboardVisible()', function () { - should(Ti.App.getKeyboardVisible).be.a.Function(); - should(Ti.App.getKeyboardVisible()).be.a.Boolean(); - }); + describe.ios('.installId', () => { + it('is a read-only String', () => { + should(Ti.App).have.a.readOnlyProperty('installId').which.is.a.String(); + }); - it('name', function () { - should(Ti.App).have.readOnlyProperty('name').which.is.a.String(); - }); + it('has a getter', () => { + should(Ti.App).have.a.getter('installId'); + }); + }); - it('#getName()', function () { - should(Ti.App.getName).be.a.Function(); - should(Ti.App.getName()).be.a.String(); - }); + describe.ios('.keyboardVisible', () => { + it('is a read-only Boolean', () => { + should(Ti.App).have.a.readOnlyProperty('keyboardVisible').which.is.a.Boolean(); + }); - it('proximityDetection', function () { - should(Ti.App.proximityDetection).be.a.Boolean(); - }); + it('has a getter', () => { + should(Ti.App).have.a.getter('keyboardVisible'); + }); + }); - it('#getProximityDetection()', function () { - should(Ti.App.getProximityDetection).be.a.Function(); - should(Ti.App.getProximityDetection()).be.a.Boolean(); - }); + describe('.name', () => { + it('is a read-only String', () => { + should(Ti.App).have.a.readOnlyProperty('name').which.is.a.String(); + }); - it('proximityState', function () { - should(Ti.App.proximityState).be.a.Boolean(); - }); + it('has a getter', () => { + should(Ti.App).have.a.getter('name'); + }); + }); - it('#getProximityState()', function () { - should(Ti.App.getProximityState).be.a.Function(); - should(Ti.App.getProximityState()).be.a.Boolean(); - }); + describe('.proximityDetection', () => { + it('is a Boolean', () => { + should(Ti.App).have.a.property('proximityDetection').which.is.a.Boolean(); + }); - it('publisher', function () { - should(Ti.App).have.readOnlyProperty('publisher').which.is.a.String(); - }); + it('defaults to false', () => { + should(Ti.App.proximityDetection).be.false(); + }); - it('#getPublisher()', function () { - should(Ti.App.getPublisher).be.a.Function(); - should(Ti.App.getPublisher()).be.a.String(); - }); + it.iosBroken('can be assigned a Boolean value', () => { // iOS does it async? I don't know + Ti.App.proximityDetection = true; + should(Ti.App.proximityDetection).be.true(); + }); - it('sessionId', function () { - should(Ti.App).have.readOnlyProperty('sessionId').which.is.a.String(); - }); + it('has accessors', () => { + should(Ti.App).have.accessors('proximityDetection'); + }); + }); - it('#getSessionId()', function () { - should(Ti.App.getSessionId).be.a.Function(); - should(Ti.App.getSessionId()).be.a.String(); - }); + describe('.proximityState', () => { + it('is a read-only Boolean', () => { + should(Ti.App).have.a.readOnlyProperty('proximityState').which.is.a.Boolean(); + }); - it('url', function () { - should(Ti.App).have.readOnlyProperty('url').which.is.a.String(); - }); + it('has a getter', () => { + should(Ti.App).have.a.getter('proximityState'); + }); + }); - it('#getUrl()', function () { - should(Ti.App.getUrl).be.a.Function(); - should(Ti.App.getUrl()).be.a.String(); - }); + describe('.publisher', () => { + it('is a read-only String', () => { + should(Ti.App).have.a.readOnlyProperty('publisher').which.is.a.String(); + }); - it('version', function () { - should(Ti.App).have.readOnlyProperty('version').which.is.a.String(); - }); + it('has a getter', () => { + should(Ti.App).have.a.getter('publisher'); + }); + }); - it('#getVersion()', function () { - should(Ti.App.getVersion).be.a.Function(); - should(Ti.App.getVersion()).be.a.String(); + describe('.sessionId', () => { + it('is a read-only String', () => { + should(Ti.App).have.a.readOnlyProperty('sessionId').which.is.a.String(); + }); + + it('has a getter', () => { + should(Ti.App).have.a.getter('sessionId'); + }); + }); + + describe('.url', () => { + it('is a read-only String', () => { + should(Ti.App).have.a.readOnlyProperty('url').which.is.a.String(); + }); + + it('has a getter', () => { + should(Ti.App).have.a.getter('url'); + }); + }); + + describe('.version', () => { + it('is a read-only String', () => { + should(Ti.App).have.a.readOnlyProperty('version').which.is.a.String(); + }); + + it('has a getter', () => { + should(Ti.App).have.a.getter('version'); + }); + }); }); // TIMOB-23542 test searchQuery - it.ios('searchQuery', function () { + it.ios('searchQuery', () => { var searchQuery; should(Ti.App.iOS.createSearchQuery).not.be.undefined(); should(Ti.App.iOS.createSearchQuery).be.a.Function(); diff --git a/tests/Resources/ti.contacts.test.js b/tests/Resources/ti.contacts.test.js index 28e5807182c..b9e7866f597 100644 --- a/tests/Resources/ti.contacts.test.js +++ b/tests/Resources/ti.contacts.test.js @@ -11,48 +11,48 @@ const should = require('./utilities/assertions'); const utilities = require('./utilities/utilities'); // FIXME: Need to move from AddressBook framework to Contacts -describe.macMissing('Titanium.Contacts', function () { - it('apiName', function () { - should(Ti.Contacts.apiName).be.eql('Ti.Contacts'); +describe.macMissing('Titanium.Contacts', () => { + it('apiName', () => { should(Ti.Contacts).have.a.readOnlyProperty('apiName').which.is.a.String(); + should(Ti.Contacts.apiName).be.eql('Ti.Contacts'); }); - it('AUTHORIZATION_AUTHORIZED', function () { + it('AUTHORIZATION_AUTHORIZED', () => { should(Ti.Contacts).have.constant('AUTHORIZATION_AUTHORIZED').which.is.a.Number(); }); - it('AUTHORIZATION_DENIED', function () { + it('AUTHORIZATION_DENIED', () => { should(Ti.Contacts).have.constant('AUTHORIZATION_DENIED').which.is.a.Number(); }); - it('AUTHORIZATION_RESTRICTED', function () { + it('AUTHORIZATION_RESTRICTED', () => { should(Ti.Contacts).have.constant('AUTHORIZATION_RESTRICTED').which.is.a.Number(); }); - it('AUTHORIZATION_UNKNOWN', function () { + it('AUTHORIZATION_UNKNOWN', () => { should(Ti.Contacts).have.constant('AUTHORIZATION_UNKNOWN').which.is.a.Number(); }); // FIXME Get working for iOS - it.iosBroken('CONTACTS_KIND_ORGANIZATION', function () { + it.iosBroken('CONTACTS_KIND_ORGANIZATION', () => { should(Ti.Contacts).have.constant('CONTACTS_KIND_ORGANIZATION').which.is.a.Number(); }); // FIXME Get working for iOS - it.iosBroken('CONTACTS_KIND_PERSON', function () { + it.iosBroken('CONTACTS_KIND_PERSON', () => { should(Ti.Contacts).have.constant('CONTACTS_KIND_PERSON').which.is.a.Number(); }); - it('CONTACTS_SORT_FIRST_NAME', function () { + it('CONTACTS_SORT_FIRST_NAME', () => { should(Ti.Contacts).have.constant('CONTACTS_SORT_FIRST_NAME').which.is.a.Number(); }); - it('CONTACTS_SORT_LAST_NAME', function () { + it('CONTACTS_SORT_LAST_NAME', () => { should(Ti.Contacts).have.constant('CONTACTS_SORT_LAST_NAME').which.is.a.Number(); }); - it('contactsAuthorization', function () { - should(function () { + it('contactsAuthorization', () => { + should(() => { should(Ti.Contacts.contactsAuthorization).not.be.undefined(); should(Ti.Contacts.contactsAuthorization).be.a.Number(); // should be one of the authorization contants @@ -66,19 +66,19 @@ describe.macMissing('Titanium.Contacts', function () { }); // Intentionally skip on Android, this methods doesn't exist - it.androidMissing('createGroup()', function () { + it.androidMissing('createGroup()', () => { should(Ti.Contacts.createGroup).be.a.Function(); // exercising Ti.Contacts.Group creation is done in ti.contacts.group.test.js }); - it('createPerson()', function () { + it('createPerson()', () => { should(Ti.Contacts.createPerson).be.a.Function(); // exercising Ti.Contacts.Person creation is done in ti.contacts.person.test.js }); // FIXME This holds for permission prompt on iOS & Windows and hangs the tests. How can we "click OK" for user? // Intentionally skip on Android, this methods doesn't exist it.androidMissing - it.allBroken('getAllGroups()', function () { + it.allBroken('getAllGroups()', () => { var groups, i; should(Ti.Contacts.getAllGroups).be.a.Function(); @@ -92,7 +92,7 @@ describe.macMissing('Titanium.Contacts', function () { // FIXME This holds for permission prompt on iOS & Windows and hangs the tests. How can we "click OK" for user? it.iosBroken // FIXME Android says "Contacts permissions missing" it.androidBroken - it.allBroken('getAllPeople()', function () { + it.allBroken('getAllPeople()', () => { var people, i; should(Ti.Contacts.getAllPeople).be.a.Function(); @@ -104,15 +104,9 @@ describe.macMissing('Titanium.Contacts', function () { } }); - // Intentionally skip on Android, these methods don't exist - it.androidMissing('getGroupByID()', function () { - should(Ti.Contacts.getGroupByID).be.a.Function(); - // deprecated, do no more for now - }); - // FIXME This holds for permission prompt on iOS and hangs the tests. How can we "click OK" for user? it.iosBroken // Intentionally skip on Android, these methods don't exist it.androidMissing - ((utilities.isIOS() || utilities.isAndroid()) ? it.skip : it)('getGroupByIdentifier()', function () { + ((utilities.isIOS() || utilities.isAndroid()) ? it.skip : it)('getGroupByIdentifier()', () => { var noGroup; should(Ti.Contacts.getGroupByIdentifier).be.a.Function(); noGroup = Ti.Contacts.getGroupByIdentifier('doesntexist'); @@ -121,7 +115,7 @@ describe.macMissing('Titanium.Contacts', function () { // FIXME This holds for permission prompt on iOS & Windows and hangs the tests. How can we "click OK" for user? // Intentionally skip on Android, these methods don't exist it.androidMissing - it.allBroken('Group add/remove', function () { + it.allBroken('Group add/remove', () => { // Look for existing group and remove it first before we try to create dupe (which fails) var allGroups = Ti.Contacts.getAllGroups(), group, @@ -154,7 +148,7 @@ describe.macMissing('Titanium.Contacts', function () { // FIXME This holds for permission prompt on iOS & Windows and hangs the tests. How can we "click OK" for user? // FIXME Android says "Contacts permissions missing" - it.allBroken('#getPeopleWithName()', function () { + it.allBroken('#getPeopleWithName()', () => { should(Ti.Contacts.getPeopleWithName).be.a.Function(); const smiths = Ti.Contacts.getPeopleWithName('smith'); should(smiths).be.an.Array(); @@ -162,7 +156,7 @@ describe.macMissing('Titanium.Contacts', function () { // FIXME This holds for permission prompt on iOS & Windows and hangs the tests. How can we "click OK" for user? // FIXME Android says property is undefined, not a function - it.allBroken('#getPersonByIdentifier()', function () { + it.allBroken('#getPersonByIdentifier()', () => { should(Ti.Contacts.getPersonByIdentifier).be.a.Function(); // check for a person by bad identifier const noPerson = Ti.Contacts.getPersonByIdentifier('doesntexist'); @@ -171,7 +165,7 @@ describe.macMissing('Titanium.Contacts', function () { // FIXME This holds for permission prompt on iOS & Windows and hangs the tests. How can we "click OK" for user? // FIXME Android says "Contacts permissions missing" - it.allBroken('Person add/remove', function () { + it.allBroken('Person add/remove', () => { // TODO Remove Arthur first if he already exists! // create a person @@ -199,33 +193,33 @@ describe.macMissing('Titanium.Contacts', function () { }); // Intentionally skip method that doesn't exist on Android - it.androidMissing('removeGroup()', function () { + it.androidMissing('removeGroup()', () => { should(Ti.Contacts.removeGroup).be.a.Function(); // We exercise removal in Group add/remove }); - it('removePerson()', function () { + it('removePerson()', () => { should(Ti.Contacts.removePerson).be.a.Function(); // We exercise removal in Person add/remove }); - it('requestContactsPermissions()', function () { + it('requestContactsPermissions()', () => { should(Ti.Contacts.requestContactsPermissions).be.a.Function(); // TODO Test the method }); // Intentionally skip method that doesn't exist on Android - it.androidMissing('revert()', function () { + it.androidMissing('revert()', () => { should(Ti.Contacts.revert).be.a.Function(); // TODO Test the method }); - it('save()', function () { + it('save()', () => { should(Ti.Contacts.save).be.a.Function(); // We exercise save above when we test adding/removing groups and person }); - it('showContacts()', function () { + it('showContacts()', () => { should(Ti.Contacts.showContacts).be.a.Function(); // TODO Test the method }); diff --git a/tests/Resources/ti.filesystem.file.test.js b/tests/Resources/ti.filesystem.file.test.js index e988b284520..ae7c79c7556 100644 --- a/tests/Resources/ti.filesystem.file.test.js +++ b/tests/Resources/ti.filesystem.file.test.js @@ -772,15 +772,22 @@ describe('Titanium.Filesystem.File', function () { should(dir.exists()).be.false(); }); - // TIMOB-14364 - // FIXME: This pops a prompt dialog for permission to Documents folder on macOS - it.ios('#setRemoteBackup()', function () { - if (utilities.isMacOS()) { - return; - } - should(function () { - Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory).setRemoteBackup(false); - }).not.throw(); + describe.ios('.remoteBackup', () => { + // TIMOB-14364 + // FIXME: This pops a prompt dialog for permission to Documents folder on macOS + it('assigning Boolean value doesn\'t throw', () => { + if (utilities.isMacOS()) { + return; + } + should(function () { + Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory).remoteBackup = false; + }).not.throw(); + }); + + it('has accessors', () => { + const file = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory); + should(file).have.accessors('remoteBackup'); + }); }); it.android('externalCacheDirectory read/write', () => { diff --git a/tests/Resources/ti.geolocation.test.js b/tests/Resources/ti.geolocation.test.js index 1db799a5ae0..35339ee5486 100644 --- a/tests/Resources/ti.geolocation.test.js +++ b/tests/Resources/ti.geolocation.test.js @@ -7,6 +7,7 @@ /* globals OS_VERSION_MAJOR */ /* eslint-env mocha */ /* eslint no-unused-expressions: "off" */ +/* eslint mocha/no-identical-title: "off" */ 'use strict'; const should = require('./utilities/assertions'); const isMacOS = Ti.Platform.name === 'Mac OS X'; @@ -14,350 +15,421 @@ const isMacOS = Ti.Platform.name === 'Mac OS X'; // FIXME This pops a prompt on Windows 10 and will hang tests. We can log on and allow manually... // Skip on Windows 10 Mobile device family due to prompt, // however we might be able to run some tests? -describe.windowsBroken('Titanium.Geolocation', function () { +describe.windowsBroken('Titanium.Geolocation', () => { - it('.apiName', function () { - should(Ti.Geolocation).have.readOnlyProperty('apiName').which.is.a.String(); - should(Ti.Geolocation.apiName).be.eql('Ti.Geolocation'); - }); + describe('constants', () => { + it.ios('.ACCURACY_BEST', () => { + should(Ti.Geolocation).have.constant('ACCURACY_BEST').which.is.a.Number(); + }); - it.ios('.ACCURACY_BEST', function () { - should(Ti.Geolocation).have.constant('ACCURACY_BEST').which.is.a.Number(); - }); + it.ios('.ACCURACY_BEST_FOR_NAVIGATION', () => { + should(Ti.Geolocation).have.constant('ACCURACY_BEST_FOR_NAVIGATION').which.is.a.Number(); + }); - it.ios('.ACCURACY_BEST_FOR_NAVIGATION', function () { - should(Ti.Geolocation).have.constant('ACCURACY_BEST_FOR_NAVIGATION').which.is.a.Number(); - }); + it('.ACCURACY_HIGH', () => { + should(Ti.Geolocation).have.constant('ACCURACY_HIGH').which.is.a.Number(); + }); - it('.ACCURACY_HIGH', function () { - should(Ti.Geolocation).have.constant('ACCURACY_HIGH').which.is.a.Number(); - }); + it.ios('.ACCURACY_HUNDRED_METERS', () => { + should(Ti.Geolocation).have.constant('ACCURACY_HUNDRED_METERS').which.is.a.Number(); + }); - it.ios('.ACCURACY_HUNDRED_METERS', function () { - should(Ti.Geolocation).have.constant('ACCURACY_HUNDRED_METERS').which.is.a.Number(); - }); + it.ios('.ACCURACY_KILOMETER', () => { + should(Ti.Geolocation).have.constant('ACCURACY_KILOMETER').which.is.a.Number(); + }); - it.ios('.ACCURACY_KILOMETER', function () { - should(Ti.Geolocation).have.constant('ACCURACY_KILOMETER').which.is.a.Number(); - }); + it('.ACCURACY_LOW', () => { + should(Ti.Geolocation).have.constant('ACCURACY_LOW').which.is.a.Number(); + }); - it('.ACCURACY_LOW', function () { - should(Ti.Geolocation).have.constant('ACCURACY_LOW').which.is.a.Number(); - }); + it.ios('.ACCURACY_NEAREST_TEN_METERS', () => { + should(Ti.Geolocation).have.constant('ACCURACY_NEAREST_TEN_METERS').which.is.a.Number(); + }); - it.ios('.ACCURACY_NEAREST_TEN_METERS', function () { - should(Ti.Geolocation).have.constant('ACCURACY_NEAREST_TEN_METERS').which.is.a.Number(); - }); + it.ios('.ACCURACY_THREE_KILOMETERS', () => { + should(Ti.Geolocation).have.constant('ACCURACY_THREE_KILOMETERS').which.is.a.Number(); + }); - it.ios('.ACCURACY_THREE_KILOMETERS', function () { - should(Ti.Geolocation).have.constant('ACCURACY_THREE_KILOMETERS').which.is.a.Number(); - }); + it.androidMissing('.ACTIVITYTYPE_*', () => { + should(Ti.Geolocation).have.enumeration('Number', [ 'ACTIVITYTYPE_AUTOMOTIVE_NAVIGATION', 'ACTIVITYTYPE_FITNESS', 'ACTIVITYTYPE_OTHER', 'ACTIVITYTYPE_OTHER_NAVIGATION' ]); + }); - it.androidMissing('.ACTIVITYTYPE_*', function () { - should(Ti.Geolocation).have.enumeration('Number', [ 'ACTIVITYTYPE_AUTOMOTIVE_NAVIGATION', 'ACTIVITYTYPE_FITNESS', 'ACTIVITYTYPE_OTHER', 'ACTIVITYTYPE_OTHER_NAVIGATION' ]); - }); + it.androidMissing('.AUTHORIZATION_*', () => { + should(Ti.Geolocation).have.enumeration('Number', [ 'AUTHORIZATION_ALWAYS', 'AUTHORIZATION_DENIED', 'AUTHORIZATION_RESTRICTED', 'AUTHORIZATION_UNKNOWN', 'AUTHORIZATION_WHEN_IN_USE' ]); + }); - it.androidMissing('.AUTHORIZATION_*', function () { - should(Ti.Geolocation).have.enumeration('Number', [ 'AUTHORIZATION_ALWAYS', 'AUTHORIZATION_DENIED', 'AUTHORIZATION_RESTRICTED', 'AUTHORIZATION_UNKNOWN', 'AUTHORIZATION_WHEN_IN_USE' ]); - }); + it.androidMissing('.ERROR_*', () => { + should(Ti.Geolocation).have.enumeration('Number', [ 'ERROR_DENIED', 'ERROR_HEADING_FAILURE', 'ERROR_LOCATION_UNKNOWN', 'ERROR_NETWORK', 'ERROR_REGION_MONITORING_DELAYED', 'ERROR_REGION_MONITORING_DENIED', 'ERROR_REGION_MONITORING_FAILURE' ]); + }); - it.androidMissing('.ERROR_*', function () { - should(Ti.Geolocation).have.enumeration('Number', [ 'ERROR_DENIED', 'ERROR_HEADING_FAILURE', 'ERROR_LOCATION_UNKNOWN', 'ERROR_NETWORK', 'ERROR_REGION_MONITORING_DELAYED', 'ERROR_REGION_MONITORING_DENIED', 'ERROR_REGION_MONITORING_FAILURE' ]); - }); + it.ios('.ACCURACY_REDUCED', () => { + if (OS_VERSION_MAJOR >= 14) { + should(Ti.Geolocation).have.constant('ACCURACY_REDUCED').which.is.a.Number(); + } + }); - // FIXME Get working on Android - it.androidBroken('.accuracy', function () { - should(Ti.Geolocation).have.a.property('accuracy').which.is.a.Number(); - }); + it.ios('.ACCURACY_AUTHORIZATION_FULL', () => { + if (OS_VERSION_MAJOR >= 14) { + should(Ti.Geolocation).have.constant('ACCURACY_AUTHORIZATION_FULL').which.is.a.Number(); + } + }); - it.androidBroken('#getAccuracy()', function () { - should(Ti.Geolocation).have.a.property('getAccuracy').which.is.a.Function(); - should(Ti.Geolocation.getAccuracy()).be.a.Number(); + it.ios('.ACCURACY_AUTHORIZATION_REDUCED', () => { + if (OS_VERSION_MAJOR >= 14) { + should(Ti.Geolocation).have.constant('ACCURACY_AUTHORIZATION_REDUCED').which.is.a.Number(); + } + }); }); - it.androidBroken('#setAccuracy()', function () { - should(Ti.Geolocation).have.a.property('setAccuracy').which.is.a.Function(); - Ti.Geolocation.setAccuracy(Ti.Geolocation.ACCURACY_BEST); - should(Ti.Geolocation.accuracy).be.eql(Ti.Geolocation.ACCURACY_BEST); - }); + describe('properties', () => { + describe('.apiName', () => { + it('is a String', () => { + should(Ti.Geolocation).have.readOnlyProperty('apiName').which.is.a.String(); + }); - it.ios('.activityType', function () { - should(Ti.Geolocation).have.a.property('activityType').which.is.a.Number(); - }); + it('equals Ti.Geolocation', () => { + should(Ti.Geolocation.apiName).be.eql('Ti.Geolocation'); + }); + }); - it.ios('#getActivityType()', function () { - should(Ti.Geolocation).have.a.property('getActivityType').which.is.a.Function(); - should(Ti.Geolocation.getActivityType()).be.a.Number(); - }); + // FIXME Get working on Android + describe.androidBroken('.accuracy', () => { + it('is a Number', () => { + should(Ti.Geolocation).have.a.property('accuracy').which.is.a.Number(); + }); - it.ios('#setActivityType()', function () { - should(Ti.Geolocation).have.a.property('setActivityType').which.is.a.Function(); - Ti.Geolocation.setActivityType(Ti.Geolocation.ACTIVITYTYPE_FITNESS); - should(Ti.Geolocation.activityType).be.eql(Ti.Geolocation.ACTIVITYTYPE_FITNESS); - }); + it('can be assigned value', () => { + Ti.Geolocation.accuracy = Ti.Geolocation.ACCURACY_BEST; + should(Ti.Geolocation.accuracy).eql(Ti.Geolocation.ACCURACY_BEST); + }); - it.ios('.allowsBackgroundLocationUpdates', function () { - should(Ti.Geolocation).have.a.property('allowsBackgroundLocationUpdates').which.is.a.Boolean(); - should(Ti.Geolocation.allowsBackgroundLocationUpdates).be.be.false(); // defaults to false (unless a special tiapp property is set, see docs) - }); + it('has accessors', () => { + should(Ti.Geolocation).have.accessors('accuracy'); + }); + }); - it.ios('#getAllowsBackgroundLocationUpdates()', function () { - should(Ti.Geolocation).have.a.property('getAllowsBackgroundLocationUpdates').which.is.a.Function(); - should(Ti.Geolocation.getAllowsBackgroundLocationUpdates()).be.a.Boolean(); - }); + describe.androidBroken('.activityType', () => { + it('is a Number', () => { + should(Ti.Geolocation).have.a.property('activityType').which.is.a.Number(); + }); - it.ios('#setAllowsBackgroundLocationUpdates()', function () { - should(Ti.Geolocation).have.a.property('setAllowsBackgroundLocationUpdates').which.is.a.Function(); - Ti.Geolocation.setAllowsBackgroundLocationUpdates(true); // defaults to false, set to true - should(Ti.Geolocation.allowsBackgroundLocationUpdates).be.be.true(); - }); + it('can be assigned value', () => { + Ti.Geolocation.activityType = Ti.Geolocation.ACTIVITYTYPE_FITNESS; + should(Ti.Geolocation.activityType).eql(Ti.Geolocation.ACTIVITYTYPE_FITNESS); + }); - // Intentionally skip for Android, doesn't exist - it.androidMissing('.distanceFilter', function () { - should(Ti.Geolocation).have.a.property('distanceFilter').which.is.a.Number(); - }); + it('has accessors', () => { + should(Ti.Geolocation).have.accessors('activityType'); + }); + }); - it.androidMissing('#getDistanceFilter()', function () { - should(Ti.Geolocation).have.a.property('getDistanceFilter').which.is.a.Function(); - should(Ti.Geolocation.getDistanceFilter()).be.a.Number(); - }); + describe.ios('.allowsBackgroundLocationUpdates', () => { + it('is a Boolean', () => { + should(Ti.Geolocation).have.a.property('allowsBackgroundLocationUpdates').which.is.a.Boolean(); + }); - it.androidMissing('#setDistanceFilter()', function () { - should(Ti.Geolocation).have.a.property('setDistanceFilter').which.is.a.Function(); - Ti.Geolocation.setDistanceFilter(1000); - should(Ti.Geolocation.distanceFilter).eql(1000); - }); + it('defaults to false', () => { + should(Ti.Geolocation.allowsBackgroundLocationUpdates).be.false(); // defaults to false (unless a special tiapp property is set, see docs) + }); - it.ios('.hasCompass', function () { - should(Ti.Geolocation).have.a.property('hasCompass').which.is.a.Boolean(); - }); + it('can be assigned Boolean value', () => { + Ti.Geolocation.allowsBackgroundLocationUpdates = true; + should(Ti.Geolocation.allowsBackgroundLocationUpdates).be.true(); + }); - it.ios('#getHasCompass()', function () { - should(Ti.Geolocation).have.a.property('getHasCompass').which.is.a.Function(); - should(Ti.Geolocation.getHasCompass()).be.a.Boolean(); - }); + it('has accessors', () => { + should(Ti.Geolocation).have.accessors('allowsBackgroundLocationUpdates'); + }); + }); - // Intentionally skip for Android, doesn't exist - it.androidMissing('.headingFilter', function () { - should(Ti.Geolocation).have.a.property('headingFilter').which.is.a.Number(); - }); + // Intentionally skip for Android, doesn't exist + describe.androidMissing('.distanceFilter', () => { + it('is a Number', () => { + should(Ti.Geolocation).have.a.property('distanceFilter').which.is.a.Number(); + }); - it.androidMissing('#getHeadingFilter()', function () { - should(Ti.Geolocation).have.a.property('getHeadingFilter').which.is.a.Function(); - should(Ti.Geolocation.getHeadingFilter()).be.a.Number(); - }); + it('can be assigned integer value', () => { + Ti.Geolocation.distanceFilter = 1000; + should(Ti.Geolocation.distanceFilter).eql(1000); + }); - it.androidMissing('#setHeadingFilter', function () { - should(Ti.Geolocation).have.a.property('setHeadingFilter').which.is.a.Function(); - Ti.Geolocation.setHeadingFilter(90); - should(Ti.Geolocation.headingFilter).eql(90); - }); + it('has accessors', () => { + should(Ti.Geolocation).have.accessors('distanceFilter'); + }); + }); - // https://jira.appcelerator.org/browse/TIMOB-26452 - it.iosBroken('.lastGeolocation', function () { - should(Ti.Geolocation).have.a.property('lastGeolocation'); // TODO: which is a String/null/undefined? - }); + describe.ios('.hasCompass', () => { + it('is a Boolean', () => { + should(Ti.Geolocation).have.a.property('hasCompass').which.is.a.Boolean(); + }); - it('#getLastGeolocation()', function () { - should(Ti.Geolocation).have.a.property('getLastGeolocation').which.is.a.Function(); - Ti.Geolocation.getLastGeolocation(); - // const returnValue = Ti.Geolocation.getLastGeolocation(); - // should(returnValue).be.a.Object(); // FIXME How do we test return type? Docs say String. May be null or undefined, as well! - }); + it('has no getter', () => { + should(Ti.Geolocation).have.a.getter('hasCompass'); + }); + }); - it.androidMissing('.locationServicesAuthorization', function () { - should(Ti.Geolocation).have.a.property('locationServicesAuthorization').which.is.a.Number(); - }); + // Intentionally skip for Android, doesn't exist + describe.androidMissing('.headingFilter', () => { + it('is a Number', () => { + should(Ti.Geolocation).have.a.property('headingFilter').which.is.a.Number(); + }); - it.androidMissing('#getLocationServicesAuthorization()', function () { - should(Ti.Geolocation).have.a.property('getLocationServicesAuthorization').which.is.a.Function(); - should(Ti.Geolocation.getLocationServicesAuthorization()).be.a.Number(); - }); + it('can be assigned integer value', () => { + Ti.Geolocation.headingFilter = 90; + should(Ti.Geolocation.headingFilter).eql(90); + }); - it('.locationServicesEnabled', function () { - should(Ti.Geolocation).have.a.property('locationServicesEnabled').which.is.a.Boolean(); - }); + it('has accessors', () => { + should(Ti.Geolocation).have.accessors('headingFilter'); + }); + }); - it('#getLocationServicesEnabled()', function () { - should(Ti.Geolocation).have.a.property('getLocationServicesEnabled').which.is.a.Function(); - should(Ti.Geolocation.getLocationServicesEnabled()).be.a.Boolean(); - }); + describe('.lastGeolocation', () => { + // https://jira.appcelerator.org/browse/TIMOB-26452 + it.iosBroken('is a property', () => { + should(Ti.Geolocation).have.a.property('lastGeolocation'); // TODO: which is a String/null/undefined? + }); - it.ios('.pauseLocationUpdateAutomatically', function () { - should(Ti.Geolocation).have.a.property('pauseLocationUpdateAutomatically').which.is.a.Boolean(); - should(Ti.Geolocation.pauseLocationUpdateAutomatically).be.false(); // defaults to false - }); + it('has no getter', () => { + should(Ti.Geolocation).have.a.getter('lastGeolocation'); + }); + }); - it.ios('#getPauseLocationUpdateAutomatically()', function () { - should(Ti.Geolocation).have.a.property('getPauseLocationUpdateAutomatically').which.is.a.Function(); - should(Ti.Geolocation.getPauseLocationUpdateAutomatically()).be.a.Boolean(); - }); + it.ios('.locationAccuracyAuthorization', () => { + if (OS_VERSION_MAJOR >= 14) { + should(Ti.Geolocation).have.a.property('locationAccuracyAuthorization').which.is.a.Number(); + } + }); - it.ios('#setPauseLocationUpdateAutomatically()', function () { - should(Ti.Geolocation).have.a.property('setPauseLocationUpdateAutomatically').which.is.a.Function(); - Ti.Geolocation.setPauseLocationUpdateAutomatically(true); // defaults to false - should(Ti.Geolocation.pauseLocationUpdateAutomatically).be.true(); - }); + describe.androidMissing('.locationServicesAuthorization', () => { + it('is a Number', () => { + should(Ti.Geolocation).have.a.property('locationServicesAuthorization').which.is.a.Number(); + }); - it.ios('.showBackgroundLocationIndicator', function () { - if (isMacOS) { - return; // FIXME: How can we limit to ios only, and skip on macos? - } - should(Ti.Geolocation).have.a.property('showBackgroundLocationIndicator').which.is.a.Boolean(); - should(Ti.Geolocation.showBackgroundLocationIndicator).be.false(); // defaults to false - }); + it('has no getter', () => { + should(Ti.Geolocation).have.a.getter('locationServicesAuthorization'); + }); + }); - it.ios('#getShowBackgroundLocationIndicator()', function () { - if (isMacOS) { - return; // FIXME: How can we limit to ios only, and skip on macos? - } - should(Ti.Geolocation).have.a.property('getShowBackgroundLocationIndicator').which.is.a.Function(); - should(Ti.Geolocation.getShowBackgroundLocationIndicator()).be.a.Boolean(); - }); + describe('.locationServicesEnabled', () => { + it('is a Boolean', () => { + should(Ti.Geolocation).have.a.property('locationServicesEnabled').which.is.a.Boolean(); + }); - it.ios('#setShowBackgroundLocationIndicator()', function () { - if (isMacOS) { - return; // FIXME: How can we limit to ios only, and skip on macos? - } - should(Ti.Geolocation).have.a.property('setShowBackgroundLocationIndicator').which.is.a.Function(); - Ti.Geolocation.setShowBackgroundLocationIndicator(true); // defaults to false - should(Ti.Geolocation.showBackgroundLocationIndicator).be.true(); - }); + it('has no getter', () => { + should(Ti.Geolocation).have.a.getter('locationServicesEnabled'); + }); + }); - it.ios('.showCalibration', function () { - should(Ti.Geolocation).have.a.property('showCalibration').which.is.a.Boolean(); - should(Ti.Geolocation.showCalibration).be.true(); // defaults to true - }); + describe.ios('.pauseLocationUpdateAutomatically', () => { + it('is a Boolean', () => { + should(Ti.Geolocation).have.a.property('pauseLocationUpdateAutomatically').which.is.a.Boolean(); + }); - it.ios('#getShowCalibration()', function () { - should(Ti.Geolocation).have.a.property('getShowCalibration').which.is.a.Function(); - should(Ti.Geolocation.getShowCalibration()).be.a.Boolean(); - }); + it('defaults to false', () => { + should(Ti.Geolocation.pauseLocationUpdateAutomatically).be.false(); + }); - it.ios('#setShowCalibration()', function () { - should(Ti.Geolocation).have.a.property('setShowCalibration').which.is.a.Function(); - Ti.Geolocation.setShowCalibration(false); // defaults to true - should(Ti.Geolocation.showCalibration).be.false(); - }); + it('can be assigned a Boolean', () => { + Ti.Geolocation.pauseLocationUpdateAutomatically = true; + should(Ti.Geolocation.pauseLocationUpdateAutomatically).be.true(); + }); - it.ios('.trackSignificantLocationChange', function () { - should(Ti.Geolocation).have.a.property('trackSignificantLocationChange').which.is.a.Boolean(); - should(Ti.Geolocation.trackSignificantLocationChange).be.false(); // defaults to false - }); + it('has accessors', () => { + should(Ti.Geolocation).have.accessors('pauseLocationUpdateAutomatically'); + }); + }); - it.ios('#getTrackSignificantLocationChange()', function () { - should(Ti.Geolocation).have.a.property('getTrackSignificantLocationChange').which.is.a.Function(); - should(Ti.Geolocation.getTrackSignificantLocationChange()).be.a.Boolean(); - }); + describe.ios('.showBackgroundLocationIndicator', () => { + if (isMacOS) { + return; // FIXME: How can we limit to ios only, and skip on macos? + } - it.ios('#setTrackSignificantLocationChange()', function () { - should(Ti.Geolocation).have.a.property('setTrackSignificantLocationChange').which.is.a.Function(); - Ti.Geolocation.setTrackSignificantLocationChange(true); // defaults to false - should(Ti.Geolocation.trackSignificantLocationChange).be.true(); - }); + it('is a Boolean', () => { + should(Ti.Geolocation).have.a.property('showBackgroundLocationIndicator').which.is.a.Boolean(); + }); - it.ios('.ACCURACY_REDUCED', function () { - if (OS_VERSION_MAJOR >= 14) { - should(Ti.Geolocation).have.constant('ACCURACY_REDUCED').which.is.a.Number(); - } - }); + it('defaults to false', () => { + should(Ti.Geolocation.showBackgroundLocationIndicator).be.false(); + }); - it.ios('.ACCURACY_AUTHORIZATION_FULL', function () { - if (OS_VERSION_MAJOR >= 14) { - should(Ti.Geolocation).have.constant('ACCURACY_AUTHORIZATION_FULL').which.is.a.Number(); - } - }); + it('can be assigned a Boolean', () => { + Ti.Geolocation.showBackgroundLocationIndicator = true; + should(Ti.Geolocation.showBackgroundLocationIndicator).be.true(); + }); - it.ios('.ACCURACY_AUTHORIZATION_REDUCED', function () { - if (OS_VERSION_MAJOR >= 14) { - should(Ti.Geolocation).have.constant('ACCURACY_AUTHORIZATION_REDUCED').which.is.a.Number(); - } - }); + it('has accessors', () => { + should(Ti.Geolocation).have.accessors('showBackgroundLocationIndicator'); + }); + }); - it.ios('.locationAccuracyAuthorization', function () { - if (OS_VERSION_MAJOR >= 14) { - should(Ti.Geolocation).have.a.property('locationAccuracyAuthorization').which.is.a.Number(); - } - }); + describe.ios('.showCalibration', () => { + it('is a Boolean', () => { + should(Ti.Geolocation).have.a.property('showCalibration').which.is.a.Boolean(); + }); - // Methods - it.ios('#requestTemporaryFullAccuracyAuthorization()', function (finish) { - this.timeout(6e4); // 60 sec - if (OS_VERSION_MAJOR < 14) { - return finish(); - } - - should(Ti.Geolocation.requestTemporaryFullAccuracyAuthorization).be.a.Function(); - Ti.Geolocation.requestTemporaryFullAccuracyAuthorization('purposekey', function (e) { - try { - // It will always give error because 'purposekey' is not in tiapp.xml. - should(e).have.property('success').which.is.a.Boolean(); - should(e.success).be.false(); - should(e).have.property('code').which.is.a.Number(); - should(e.code).be.eql(1); - should(e).have.property('error').which.is.a.String(); - finish(); - } catch (err) { - return finish(err); - } + it('defaults to true', () => { + should(Ti.Geolocation.showCalibration).be.true(); + }); + + it('can be assigned Boolean value', () => { + Ti.Geolocation.showCalibration = false; + should(Ti.Geolocation.showCalibration).be.false(); + }); + + it('has accessors', () => { + should(Ti.Geolocation).have.accessors('showCalibration'); + }); }); - }); - it('#forwardGeocoder()', function (finish) { - this.timeout(6e4); // 60 sec - - should(Ti.Geolocation.forwardGeocoder).be.a.Function(); - Ti.Geolocation.forwardGeocoder('440 N Bernardo Ave, Mountain View', function (data) { - try { - should(data).have.property('success').which.is.a.Boolean(); - should(data.success).be.be.true(); - should(data).have.property('code').which.is.a.Number(); - should(data.code).be.eql(0); - should(data.latitude).be.approximately(37.387, 0.004); // iOS: 37.38605, Windows: 37.3883645, Android: 37.3909049 - should(data.longitude).be.approximately(-122.065, 0.02); // Windows: -122.0512682, iOS: -122.08385, Android: -122.0472468 - finish(); - } catch (err) { - finish(err); - } + describe.ios('.trackSignificantLocationChange', () => { + it('is a Boolean', () => { + should(Ti.Geolocation).have.a.property('trackSignificantLocationChange').which.is.a.Boolean(); + }); + + it('defaults to false', () => { + should(Ti.Geolocation.trackSignificantLocationChange).be.false(); + }); + + it('can be assigned Boolean value', () => { + Ti.Geolocation.trackSignificantLocationChange = true; + should(Ti.Geolocation.trackSignificantLocationChange).be.true(); + }); + + it('has accessors', () => { + should(Ti.Geolocation).have.accessors('trackSignificantLocationChange'); + }); }); }); - // FIXME The address object is different from platform to platform! https://jira.appcelerator.org/browse/TIMOB-23496 - it('#reverseGeocoder()', function (finish) { - this.timeout(6e4); // 60 sec - - should(Ti.Geolocation.reverseGeocoder).be.a.Function(); - Ti.Geolocation.reverseGeocoder(37.3883645, -122.0512682, function (data) { - try { - should(data).have.property('success').which.is.a.Boolean(); - should(data.success).be.be.true(); - should(data).have.property('code').which.is.a.Number(); - should(data.code).be.eql(0); - // FIXME error property is missing altogether on success for iOS... - // should(data).have.property('error'); // undefined on success, holds error message as String otherwise. - should(data).have.property('places').which.is.an.Array(); - - should(data.places[0].postalCode).be.eql('94043'); - should(data.places[0]).have.property('latitude').which.is.a.Number(); - should(data.places[0]).have.property('longitude').which.is.a.Number(); - should(data.places[0].country).be.oneOf('USA', 'United States of America'); - should(data.places[0].state).be.eql('California'); - should(data.places[0].countryCode).be.eql('US'); - should(data.places[0]).have.property('city').which.is.a.String(); - should(data.places[0]).have.property('address').which.is.a.String(); - - finish(); - } catch (err) { - finish(err); - } + describe('methods', () => { + describe('#getCurrentHeading()', () => { + it('is a Function', () => { + should(Ti.Geolocation).have.a.property('getCurrentHeading').which.is.a.Function(); + }); }); - }); - it('#getCurrentPosition()', function () { - should(Ti.Geolocation).have.a.property('getCurrentPosition').which.is.a.Function(); - }); + describe('#getCurrentPosition()', () => { + it('is a Function', () => { + should(Ti.Geolocation).have.a.property('getCurrentPosition').which.is.a.Function(); + }); + }); + + describe('#forwardGeocoder()', () => { + it('is a Function', () => should(Ti.Geolocation.forwardGeocoder).be.a.Function()); + + it('works via callback argument', function (finish) { + this.timeout(6e4); // 60 sec + + Ti.Geolocation.forwardGeocoder('440 N Bernardo Ave, Mountain View', function (data) { + try { + should(data).have.property('success').which.is.a.Boolean(); + should(data.success).be.be.true(); + should(data).have.property('code').which.is.a.Number(); + should(data.code).be.eql(0); + should(data.latitude).be.approximately(37.387, 0.005); // iOS: 37.38605, Windows: 37.3883645, Android: 37.3910366 + should(data.longitude).be.approximately(-122.065, 0.02); // Windows: -122.0512682, iOS: -122.08385, Android: -122.0472468 + } catch (err) { + return finish(err); + } + finish(); + }); + }); + + it('works via Promise return value', function (finish) { + this.timeout(6e4); // 60 sec + + const result = Ti.Geolocation.forwardGeocoder('440 N Bernardo Ave, Mountain View'); + result.should.be.a.Promise(); + result.then(data => { + should(data).have.property('success').which.is.a.Boolean(); + should(data.success).be.eql(true); + should(data).have.property('code').which.is.a.Number(); + should(data.code).be.eql(0); + should(data.latitude).be.approximately(37.387, 0.005); // iOS: 37.38605, Windows: 37.3883645, Android: 37.3910366 + should(data.longitude).be.approximately(-122.065, 0.02); // Windows: -122.0512682, iOS: -122.08385, Android: -122. + return finish(); + }).catch(e => finish(e)); + }); + }); + + it.ios('#requestTemporaryFullAccuracyAuthorization()', function (finish) { + this.timeout(6e4); // 60 sec + if (OS_VERSION_MAJOR < 14) { + return finish(); + } - it('#getCurrentHeading()', function () { - should(Ti.Geolocation).have.a.property('getCurrentHeading').which.is.a.Function(); + should(Ti.Geolocation.requestTemporaryFullAccuracyAuthorization).be.a.Function(); + Ti.Geolocation.requestTemporaryFullAccuracyAuthorization('purposekey', function (e) { + try { + // It will always give error because 'purposekey' is not in tiapp.xml. + should(e).have.property('success').which.is.a.Boolean(); + should(e.success).be.false(); + should(e).have.property('code').which.is.a.Number(); + should(e.code).be.eql(1); + should(e).have.property('error').which.is.a.String(); + finish(); + } catch (err) { + return finish(err); + } + }); + }); + + // FIXME The address object is different from platform to platform! https://jira.appcelerator.org/browse/TIMOB-23496 + describe('#reverseGeocoder()', () => { + it('is a Function', () => should(Ti.Geolocation.reverseGeocoder).be.a.Function()); + + it('works via function callback', function (finish) { + this.timeout(6e4); // 60 sec + + Ti.Geolocation.reverseGeocoder(37.3883645, -122.0512682, function (data) { + try { + should(data).have.property('success').which.is.a.Boolean(); + should(data.success).be.be.true(); + should(data).have.property('code').which.is.a.Number(); + should(data.code).be.eql(0); + // FIXME error property is missing altogether on success for iOS... + // should(data).have.property('error'); // undefined on success, holds error message as String otherwise. + should(data).have.property('places').which.is.an.Array(); + + should(data.places[0].postalCode).be.eql('94043'); + should(data.places[0]).have.property('latitude').which.is.a.Number(); + should(data.places[0]).have.property('longitude').which.is.a.Number(); + should(data.places[0].country).be.oneOf('USA', 'United States of America'); + should(data.places[0].state).be.eql('California'); + should(data.places[0].countryCode).be.eql('US'); + should(data.places[0]).have.property('city').which.is.a.String(); + should(data.places[0]).have.property('address').which.is.a.String(); + } catch (err) { + return finish(err); + } + finish(); + }); + }); + + it('works via Promise return value', function (finish) { + const result = Ti.Geolocation.reverseGeocoder(37.3883645, -122.0512682); + result.should.be.a.Promise(); + result.then(data => { + should(data).have.property('success').which.is.a.Boolean(); + should(data.success).be.eql(true); + should(data).have.property('code').which.is.a.Number(); + should(data.code).be.eql(0); + // FIXME error property is missing altogether on success for iOS... + // should(data).have.property('error'); // undefined on success, holds error message as String otherwise. + should(data).have.property('places').which.is.an.Array(); + + should(data.places[0].postalCode).be.eql('94043'); + should(data.places[0]).have.property('latitude').which.is.a.Number(); + should(data.places[0]).have.property('longitude').which.is.a.Number(); + should(data.places[0].country).be.oneOf('USA', 'United States of America'); + should(data.places[0].state).be.eql('California'); + should(data.places[0].countryCode).be.eql('US'); + should(data.places[0]).have.property('city').which.is.a.String(); + should(data.places[0]).have.property('address').which.is.a.String(); + return finish(); + }).catch(e => finish(e)); + }); + }); }); }); diff --git a/tests/Resources/ti.gesture.test.js b/tests/Resources/ti.gesture.test.js index ebf4ccac029..b9e2b22ed9d 100644 --- a/tests/Resources/ti.gesture.test.js +++ b/tests/Resources/ti.gesture.test.js @@ -7,67 +7,62 @@ /* eslint-env mocha */ /* eslint no-unused-expressions: "off" */ 'use strict'; -var should = require('./utilities/assertions'); +const should = require('./utilities/assertions'); -describe('Titanium.Gesture', function () { - it('apiName', function () { - should(Ti.Gesture).have.a.readOnlyProperty('apiName').which.is.a.String(); - should(Ti.Gesture.apiName).be.eql('Ti.Gesture'); - }); - - it('Ti.Gesture', function () { +describe('Titanium.Gesture', () => { + it('namespace/module exists', () => { should(Ti.Gesture).not.be.undefined(); should(Ti.Gesture.addEventListener).be.a.Function(); should(Ti.Gesture.removeEventListener).be.a.Function(); }); - it('landscape', function () { - should(Ti.Gesture).have.a.readOnlyProperty('landscape').which.is.a.Boolean(); - }); + describe('properties', () => { + describe('.apiName', () => { + it('is a String', () => { + should(Ti.Gesture).have.a.readOnlyProperty('apiName').which.is.a.String(); + }); - it('orientation', function () { - should(Ti.Gesture).have.a.readOnlyProperty('orientation').which.is.a.Number(); - }); + it('equals Ti.Gesture', () => { + should(Ti.Gesture.apiName).be.eql('Ti.Gesture'); + }); + }); - it('portrait', function () { - should(Ti.Gesture).have.a.readOnlyProperty('portrait').which.is.a.Boolean(); - }); + describe('.landscape', () => { + it('is a Boolean', () => { + should(Ti.Gesture).have.a.readOnlyProperty('landscape').which.is.a.Boolean(); + }); - it('getLandscape()', function () { - should(Ti.Gesture.getLandscape).not.be.undefined(); - should(Ti.Gesture.getLandscape).be.a.Function(); - should(Ti.Gesture.getLandscape()).be.a.Boolean(); - }); + it('has no getter', () => { + should(Ti.Gesture).have.a.getter('landscape'); + }); + }); - it('getPortrait()', function () { - should(Ti.Gesture.getPortrait).not.be.undefined(); - should(Ti.Gesture.getPortrait).be.a.Function(); - should(Ti.Gesture.getPortrait()).be.a.Boolean(); - }); + describe('.orientation', () => { + it('is a Number', () => { + should(Ti.Gesture).have.a.readOnlyProperty('orientation').which.is.a.Number(); + }); - // FIXME Seems like only Windows has this? Was it deprecated/removed? - it.windows('isFaceDown()', function () { - should(Ti.Gesture.isFaceDown).not.be.undefined(); - should(Ti.Gesture.isFaceDown).be.a.Function(); - should(Ti.Gesture.isFaceDown()).be.a.Boolean(); - }); + it('has no getter', () => { + should(Ti.Gesture).have.a.getter('orientation'); + }); + }); - // FIXME Seems like only Windows has this? Was it deprecated/removed? - it.windows('isFaceUp()', function () { - should(Ti.Gesture.isFaceUp).not.be.undefined(); - should(Ti.Gesture.isFaceUp).be.a.Function(); - should(Ti.Gesture.isFaceUp()).be.a.Boolean(); - }); + describe('.portrait', () => { + it('is a Boolean', () => { + should(Ti.Gesture).have.a.readOnlyProperty('portrait').which.is.a.Boolean(); + }); - it('getOrientation()', function () { - should(Ti.Gesture.getOrientation).not.be.undefined(); - should(Ti.Gesture.getOrientation).be.a.Function(); - should(Ti.Gesture.getOrientation()).be.a.Number(); + it('has no getter', () => { + should(Ti.Gesture).have.a.getter('portrait'); + }); + }); }); - it.windowsMissing('orientationchange', function () { - function listener () {} - Ti.Gesture.addEventListener('orientationchange', listener); - Ti.Gesture.removeEventListener('orientationchange', listener); + describe('events', () => { + it.windowsMissing('orientationchange', () => { + function listener () {} + Ti.Gesture.addEventListener('orientationchange', listener); + Ti.Gesture.removeEventListener('orientationchange', listener); + }); }); }); diff --git a/tests/Resources/ti.locale.test.js b/tests/Resources/ti.locale.test.js index 87a2d6af53d..4fb8db30d8f 100644 --- a/tests/Resources/ti.locale.test.js +++ b/tests/Resources/ti.locale.test.js @@ -7,284 +7,296 @@ /* eslint-env mocha */ /* eslint no-unused-expressions: "off" */ 'use strict'; -var should = require('./utilities/assertions'); +const should = require('./utilities/assertions'); -describe('Global', function () { - it('L', function () { - should(L).be.a.Function(); - // should(L).eql(Ti.Locale.getString); - }); +describe('Global', () => { + it('#L() is a Function', () => should(L).be.a.Function()); }); -describe('Titanium.Locale', function () { +describe('Titanium.Locale', () => { // reset back to US english when done - after(function () { - Ti.Locale.setLanguage('en-US'); - }); + after(() => Ti.Locale.setLanguage('en-US')); - it('apiName', function () { - should(Ti.Locale).have.a.readOnlyProperty('apiName').which.is.a.String(); - should(Ti.Locale.apiName).be.eql('Ti.Locale'); - }); - - it('exists', function () { + it('exists', () => { should(Ti.Locale).not.be.undefined(); should(Ti.Locale).not.be.null(); should(Ti.Locale).be.an.Object(); }); - it('#getString()', function () { - should(Ti.Locale.getString).be.a.Function(); - }); - - it('#getCurrentCountry()', function () { - should(Ti.Locale.getCurrentCountry).be.a.Function(); - should(Ti.Locale.getCurrentCountry()).eql('US'); - }); - - it('#getCurrentLanguage()', function () { - should(Ti.Locale.getCurrentLanguage).be.a.Function(); - should(Ti.Locale.getCurrentLanguage()).eql('en'); - }); - - it('#getLocaleCurrencySymbol', function () { - should(Ti.Locale.getLocaleCurrencySymbol).be.a.Function(); - should(Ti.Locale.getLocaleCurrencySymbol('en-US')).eql('$'); - }); - - // FIXME Get working on iOS - // FIXME Get working properly cross-platform. JPY gives us ¥ on Windows and Android, JP¥ on iOS. CNY gives us ¥ on Windows, CN¥ on Android - it.androidAndIosBroken('#getCurrencySymbol(String)', function () { - should(Ti.Locale.getCurrencySymbol).be.a.Function(); - should(Ti.Locale.getCurrencySymbol('USD')).eql('$'); - should(Ti.Locale.getCurrencySymbol('JPY')).eql('¥'); // 'JP¥' on iOS - should(Ti.Locale.getCurrencySymbol('CNY')).eql('¥'); // 'CN¥' on Android - should(Ti.Locale.getCurrencySymbol('TWD')).eql('NT$'); - }); - - it('#getCurrencyCode(String)', function () { - should(Ti.Locale.getCurrencyCode).be.a.Function(); - should(Ti.Locale.getCurrencyCode('en-US')).eql('USD'); - should(Ti.Locale.getCurrencyCode('ja-JP')).eql('JPY'); - should(Ti.Locale.getCurrencyCode('zh-CN')).eql('CNY'); - should(Ti.Locale.getCurrencyCode('zh-TW')).eql('TWD'); - }); - - // TODO Support Ti.Locale.formatTelephoneNumber on other platforms? - it.android('#formatTelephoneNumber(String)', function () { - should(Ti.Locale.formatTelephoneNumber).be.a.Function(); - // TODO Actually check inputs/outputs! - }); - - it('currentCountry', function () { - should(Ti.Locale.currentCountry).be.a.String(); - should(Ti.Locale.currentCountry).eql('US'); - }); + describe('properties', () => { + it('.apiName', () => { + should(Ti.Locale).have.a.readOnlyProperty('apiName').which.is.a.String(); + should(Ti.Locale.apiName).be.eql('Ti.Locale'); + }); - it('currentLanguage', function () { - should(Ti.Locale.currentLanguage).be.a.String(); - should(Ti.Locale.currentLanguage).eql('en'); - }); + describe('.currentCountry', () => { + it('is a String', () => { + should(Ti.Locale).have.a.readOnlyProperty('currentCountry').which.is.a.String(); + }); - it('currentLocale', function () { - should(Ti.Locale.currentLocale).be.a.String(); - should(Ti.Locale.currentLocale).eql('en-US'); - }); + it('defaults to \'US\'', () => { + should(Ti.Locale.currentCountry).eql('US'); + }); - describe('#setLanguage(String)', function () { - it('is a Function', function () { - should(Ti.Locale.setLanguage).be.a.Function(); + it('has no getter', () => { + should(Ti.Locale).have.a.getter('currentCountry'); + }); }); - it('changes currentLanguage', function () { - Ti.Locale.setLanguage('fr'); - should(Ti.Locale.currentLanguage).eql('fr'); - }); + describe('.currentLanguage', () => { + it('is a String', () => { + should(Ti.Locale).have.a.readOnlyProperty('currentLanguage').which.is.a.String(); + }); - // FIXME Get working on iOS, setLangauge doesn't seem to affect currentLocale - it.iosBroken('changes currentLocale', function () { - Ti.Locale.setLanguage('en-GB'); - should(Ti.Locale.currentLocale).eql('en-GB'); // iOS returns 'en-US' - Ti.Locale.setLanguage('fr'); - should(Ti.Locale.currentLocale).eql('fr'); - }); + it('defaults to \'en\'', () => { + should(Ti.Locale.currentLanguage).eql('en'); + }); - // TODO test if it changes the currentCountry? - }); - - describe('#getString()', function () { - it('is a Function', function () { - should(Ti.Locale.getString).be.a.Function(); + it('has no getter', () => { + should(Ti.Locale).have.a.getter('currentLanguage'); + }); }); - beforeEach(function () { - Ti.Locale.setLanguage('en-US'); - }); + describe('.currentLocale', () => { + it('is a String', () => { + should(Ti.Locale).have.a.readOnlyProperty('currentLocale').which.is.a.String(); + }); - it('returns stored value for found key', function () { - should(Ti.Locale.getString('this_is_my_key')).eql('this is my value'); - should(L('this_is_my_key')).eql('this is my value'); - }); + it('defaults to \'en-US\'', () => { + should(Ti.Locale.currentLocale).eql('en-US'); + }); - it('returns key if not found and no default specified', function () { - should(Ti.Locale.getString('this_should_not_be_found')).eql('this_should_not_be_found'); - should(L('this_should_not_be_found')).eql('this_should_not_be_found'); + it('has no getter', () => { + should(Ti.Locale).have.a.getter('currentLocale'); + }); }); + }); - it('returns supplied default if key not found', function () { - should(Ti.Locale.getString('this_should_not_be_found', 'this is the default value')).eql('this is the default value'); - should(L('this_should_not_be_found', 'this is the default value')).eql('this is the default value'); + describe('methods', () => { + // TODO Support Ti.Locale.formatTelephoneNumber on other platforms? + it.android('#formatTelephoneNumber(String)', () => { + should(Ti.Locale.formatTelephoneNumber).be.a.Function(); + // TODO Actually check inputs/outputs! }); - // FIXME: returns null - we can fix this in a cross-platform way via same extension we used for Android to fix issue - it('returns key if supplied default is not a String and key/value pair not found', function () { - should(Ti.Locale.getString('this_should_not_be_found', null)).eql('this_should_not_be_found'); - should(L('this_should_not_be_found', null)).eql('this_should_not_be_found'); - should(Ti.Locale.getString('this_should_not_be_found', 123)).eql('this_should_not_be_found'); - should(L('this_should_not_be_found', 123)).eql('this_should_not_be_found'); + it('#getCurrencyCode(String)', () => { + should(Ti.Locale.getCurrencyCode).be.a.Function(); + should(Ti.Locale.getCurrencyCode('en-US')).eql('USD'); + should(Ti.Locale.getCurrencyCode('ja-JP')).eql('JPY'); + should(Ti.Locale.getCurrencyCode('zh-CN')).eql('CNY'); + should(Ti.Locale.getCurrencyCode('zh-TW')).eql('TWD'); }); - // https://jira.appcelerator.org/browse/TIMOB-26651 - it('handles locale/country specific languages (i.e. en-GB vs en-US)', function () { - Ti.Locale.setLanguage('en-GB'); - should(Ti.Locale.getString('this_is_my_key')).eql('this is my en-GB value'); // This fails on Windows, gives 'this is my value' - should(L('this_is_my_key')).eql('this is my en-GB value'); // This fails on Windows, gives 'this is my value' + // FIXME Get working on iOS + // FIXME Get working properly cross-platform. JPY gives us ¥ on Windows and Android, JP¥ on iOS. CNY gives us ¥ on Windows, CN¥ on Android + it.androidAndIosBroken('#getCurrencySymbol(String)', () => { + should(Ti.Locale.getCurrencySymbol).be.a.Function(); + should(Ti.Locale.getCurrencySymbol('USD')).eql('$'); + should(Ti.Locale.getCurrencySymbol('JPY')).eql('¥'); // 'JP¥' on iOS + should(Ti.Locale.getCurrencySymbol('CNY')).eql('¥'); // 'CN¥' on Android + should(Ti.Locale.getCurrencySymbol('TWD')).eql('NT$'); }); - // and then this one fails because it's using en-GB strings after we tell it to be ja... - it('handles single segment language (i.e. ja)', function () { - Ti.Locale.setLanguage('ja'); - should(Ti.Locale.getString('this_is_my_key')).eql('これは私の値です'); - should(L('this_is_my_key')).eql('これは私の値です'); + it('#getLocaleCurrencySymbol()', () => { + should(Ti.Locale.getLocaleCurrencySymbol).be.a.Function(); + should(Ti.Locale.getLocaleCurrencySymbol('en-US')).eql('$'); }); - // ...and this one is now using ja strings, but langauge value is en-US! - // FIXME iOS seems to ignore position info on the format string. - // We're trying to force the 1st argument into the second slot, and vice versa here. iOS handles the %2$s syntax, but ignores position - it.iosAndWindowsBroken('usage with String.format()', function () { - var i18nMissingMsg = ''; - var string1 = 'You say ' + Ti.Locale.getString('signoff', i18nMissingMsg) + ' and I say ' + Ti.Locale.getString('greeting', i18nMissingMsg) + '!'; - var string2 = String.format(L('phrase'), L('greeting', i18nMissingMsg), L('signoff', i18nMissingMsg)); - - should(string1).eql(string2); - if (Ti.Locale.currentLanguage === 'en') { - should(string1).eql('You say goodbye and I say hello!'); - should(string2).eql('You say goodbye and I say hello!'); - } else if (Ti.Locale.currentLanguage === 'ja') { - should(string1).eql('You say さようなら and I say こんにちは!'); - should(string2).eql('You say さようなら and I say こんにちは!'); - } + describe('#getString()', () => { + it('is a Function', () => { + should(Ti.Locale.getString).be.a.Function(); + }); + + beforeEach(() => { + Ti.Locale.setLanguage('en-US'); + }); + + it('returns stored value for found key', () => { + should(Ti.Locale.getString('this_is_my_key')).eql('this is my value'); + should(L('this_is_my_key')).eql('this is my value'); + }); + + it('returns key if not found and no default specified', () => { + should(Ti.Locale.getString('this_should_not_be_found')).eql('this_should_not_be_found'); + should(L('this_should_not_be_found')).eql('this_should_not_be_found'); + }); + + it('returns supplied default if key not found', () => { + should(Ti.Locale.getString('this_should_not_be_found', 'this is the default value')).eql('this is the default value'); + should(L('this_should_not_be_found', 'this is the default value')).eql('this is the default value'); + }); + + // FIXME: returns null - we can fix this in a cross-platform way via same extension we used for Android to fix issue + it('returns key if supplied default is not a String and key/value pair not found', () => { + should(Ti.Locale.getString('this_should_not_be_found', null)).eql('this_should_not_be_found'); + should(L('this_should_not_be_found', null)).eql('this_should_not_be_found'); + should(Ti.Locale.getString('this_should_not_be_found', 123)).eql('this_should_not_be_found'); + should(L('this_should_not_be_found', 123)).eql('this_should_not_be_found'); + }); + + // https://jira.appcelerator.org/browse/TIMOB-26651 + it('handles locale/country specific languages (i.e. en-GB vs en-US)', () => { + Ti.Locale.setLanguage('en-GB'); + should(Ti.Locale.getString('this_is_my_key')).eql('this is my en-GB value'); // This fails on Windows, gives 'this is my value' + should(L('this_is_my_key')).eql('this is my en-GB value'); // This fails on Windows, gives 'this is my value' + }); + + // and then this one fails because it's using en-GB strings after we tell it to be ja... + it('handles single segment language (i.e. ja)', () => { + Ti.Locale.setLanguage('ja'); + should(Ti.Locale.getString('this_is_my_key')).eql('これは私の値です'); + should(L('this_is_my_key')).eql('これは私の値です'); + }); + + // ...and this one is now using ja strings, but langauge value is en-US! + // FIXME iOS seems to ignore position info on the format string. + // We're trying to force the 1st argument into the second slot, and vice versa here. iOS handles the %2$s syntax, but ignores position + it.iosAndWindowsBroken('usage with String.format()', () => { + var i18nMissingMsg = ''; + var string1 = 'You say ' + Ti.Locale.getString('signoff', i18nMissingMsg) + ' and I say ' + Ti.Locale.getString('greeting', i18nMissingMsg) + '!'; + var string2 = String.format(L('phrase'), L('greeting', i18nMissingMsg), L('signoff', i18nMissingMsg)); + + should(string1).eql(string2); + if (Ti.Locale.currentLanguage === 'en') { + should(string1).eql('You say goodbye and I say hello!'); + should(string2).eql('You say goodbye and I say hello!'); + } else if (Ti.Locale.currentLanguage === 'ja') { + should(string1).eql('You say さようなら and I say こんにちは!'); + should(string2).eql('You say さようなら and I say こんにちは!'); + } + }); }); - }); - describe('#parseDecimal()', () => { - it('compared with String.formatDecimal()', () => { - should(Ti.Locale.parseDecimal).be.a.Function(); + describe('#parseDecimal()', () => { + it('compared with String.formatDecimal()', () => { + should(Ti.Locale.parseDecimal).be.a.Function(); - const numericValue = 1234567.8; + const numericValue = 1234567.8; - let numericString = String.formatDecimal(numericValue); - let parsedValue = Ti.Locale.parseDecimal(numericString); - should(parsedValue).be.a.Number(); - should(Math.abs(parsedValue - numericValue)).be.lessThan(Number.EPSILON); + let numericString = String.formatDecimal(numericValue); + let parsedValue = Ti.Locale.parseDecimal(numericString); + should(parsedValue).be.a.Number(); + should(Math.abs(parsedValue - numericValue)).be.lessThan(Number.EPSILON); - numericString = String.formatDecimal(numericValue, 'de-DE'); - parsedValue = Ti.Locale.parseDecimal(numericString, 'de-DE'); - should(Math.abs(parsedValue - numericValue)).be.lessThan(Number.EPSILON); + numericString = String.formatDecimal(numericValue, 'de-DE'); + parsedValue = Ti.Locale.parseDecimal(numericString, 'de-DE'); + should(Math.abs(parsedValue - numericValue)).be.lessThan(Number.EPSILON); - numericString = String.formatDecimal(numericValue, 'fr-FR'); - parsedValue = Ti.Locale.parseDecimal(numericString, 'fr-FR'); - should(Math.abs(parsedValue - numericValue)).be.lessThan(Number.EPSILON); + numericString = String.formatDecimal(numericValue, 'fr-FR'); + parsedValue = Ti.Locale.parseDecimal(numericString, 'fr-FR'); + should(Math.abs(parsedValue - numericValue)).be.lessThan(Number.EPSILON); - numericString = String.formatDecimal(numericValue, 'ar-EG'); - parsedValue = Ti.Locale.parseDecimal(numericString, 'ar-EG'); - should(Math.abs(parsedValue - numericValue)).be.lessThan(Number.EPSILON); - }); + numericString = String.formatDecimal(numericValue, 'ar-EG'); + parsedValue = Ti.Locale.parseDecimal(numericString, 'ar-EG'); + should(Math.abs(parsedValue - numericValue)).be.lessThan(Number.EPSILON); + }); - it('localized values', () => { - let result = Ti.Locale.parseDecimal('1,234,567.8', 'en-US'); - should(Math.abs(result - 1234567.8)).be.lessThan(Number.EPSILON); + it('localized values', () => { + let result = Ti.Locale.parseDecimal('1,234,567.8', 'en-US'); + should(Math.abs(result - 1234567.8)).be.lessThan(Number.EPSILON); - result = Ti.Locale.parseDecimal('1.234.567,8', 'de-DE'); - should(Math.abs(result - 1234567.8)).be.lessThan(Number.EPSILON); + result = Ti.Locale.parseDecimal('1.234.567,8', 'de-DE'); + should(Math.abs(result - 1234567.8)).be.lessThan(Number.EPSILON); - // France uses non-breaking unicode spaces for thousands separator. - result = Ti.Locale.parseDecimal('1\u00A0234\u00A0567,8', 'fr-FR'); - should(Math.abs(result - 1234567.8)).be.lessThan(Number.EPSILON); + // France uses non-breaking unicode spaces for thousands separator. + result = Ti.Locale.parseDecimal('1\u00A0234\u00A0567,8', 'fr-FR'); + should(Math.abs(result - 1234567.8)).be.lessThan(Number.EPSILON); - // But it was then changed to narrow non-breaking spaces! - result = Ti.Locale.parseDecimal('1\u202F234\u202F567,8', 'fr-FR'); - should(Math.abs(result - 1234567.8)).be.lessThan(Number.EPSILON); - }); + // But it was then changed to narrow non-breaking spaces! + result = Ti.Locale.parseDecimal('1\u202F234\u202F567,8', 'fr-FR'); + should(Math.abs(result - 1234567.8)).be.lessThan(Number.EPSILON); + }); - it('various values', () => { - let result = Ti.Locale.parseDecimal('0', 'en-US'); - should(Math.abs(result)).be.lessThan(Number.EPSILON); + it('various values', () => { + let result = Ti.Locale.parseDecimal('0', 'en-US'); + should(Math.abs(result)).be.lessThan(Number.EPSILON); - result = Ti.Locale.parseDecimal('0.', 'en-US'); - should(Math.abs(result)).be.lessThan(Number.EPSILON); + result = Ti.Locale.parseDecimal('0.', 'en-US'); + should(Math.abs(result)).be.lessThan(Number.EPSILON); - result = Ti.Locale.parseDecimal('.0', 'en-US'); - should(Math.abs(result)).be.lessThan(Number.EPSILON); + result = Ti.Locale.parseDecimal('.0', 'en-US'); + should(Math.abs(result)).be.lessThan(Number.EPSILON); - result = Ti.Locale.parseDecimal('00.00123', 'en-US'); - should(Math.abs(result - 0.00123)).be.lessThan(Number.EPSILON); + result = Ti.Locale.parseDecimal('00.00123', 'en-US'); + should(Math.abs(result - 0.00123)).be.lessThan(Number.EPSILON); - result = Ti.Locale.parseDecimal('+0', 'en-US'); - should(Math.abs(result)).be.lessThan(Number.EPSILON); + result = Ti.Locale.parseDecimal('+0', 'en-US'); + should(Math.abs(result)).be.lessThan(Number.EPSILON); - result = Ti.Locale.parseDecimal('-0', 'en-US'); - should(Math.abs(result)).be.lessThan(Number.EPSILON); + result = Ti.Locale.parseDecimal('-0', 'en-US'); + should(Math.abs(result)).be.lessThan(Number.EPSILON); - result = Ti.Locale.parseDecimal('+1234.5', 'en-US'); - should(Math.abs(result - 1234.5)).be.lessThan(Number.EPSILON); + result = Ti.Locale.parseDecimal('+1234.5', 'en-US'); + should(Math.abs(result - 1234.5)).be.lessThan(Number.EPSILON); - result = Ti.Locale.parseDecimal('-1234.5', 'en-US'); - should(Math.abs(result + 1234.5)).be.lessThan(Number.EPSILON); - }); + result = Ti.Locale.parseDecimal('-1234.5', 'en-US'); + should(Math.abs(result + 1234.5)).be.lessThan(Number.EPSILON); + }); - it('scientific values', () => { - let result = Ti.Locale.parseDecimal('1.2E+3', 'en-US'); - should(Math.abs(result - 1200.0)).be.lessThan(Number.EPSILON); + it('scientific values', () => { + let result = Ti.Locale.parseDecimal('1.2E+3', 'en-US'); + should(Math.abs(result - 1200.0)).be.lessThan(Number.EPSILON); - result = Ti.Locale.parseDecimal('-1.2E+3', 'en-US'); - should(Math.abs(result + 1200.0)).be.lessThan(Number.EPSILON); + result = Ti.Locale.parseDecimal('-1.2E+3', 'en-US'); + should(Math.abs(result + 1200.0)).be.lessThan(Number.EPSILON); - result = Ti.Locale.parseDecimal('1.2E-3', 'en-US'); - should(Math.abs(result - 0.0012)).be.lessThan(Number.EPSILON); + result = Ti.Locale.parseDecimal('1.2E-3', 'en-US'); + should(Math.abs(result - 0.0012)).be.lessThan(Number.EPSILON); - result = Ti.Locale.parseDecimal('-1.2E-3', 'en-US'); - should(Math.abs(result + 0.0012)).be.lessThan(Number.EPSILON); - }); + result = Ti.Locale.parseDecimal('-1.2E-3', 'en-US'); + should(Math.abs(result + 0.0012)).be.lessThan(Number.EPSILON); + }); - it('padded with spaces', () => { - let result = Ti.Locale.parseDecimal(' 123 ', 'en-US'); - should(Math.abs(result - 123)).be.lessThan(Number.EPSILON); + it('padded with spaces', () => { + let result = Ti.Locale.parseDecimal(' 123 ', 'en-US'); + should(Math.abs(result - 123)).be.lessThan(Number.EPSILON); - result = Ti.Locale.parseDecimal(' +123 ', 'en-US'); - should(Math.abs(result - 123)).be.lessThan(Number.EPSILON); + result = Ti.Locale.parseDecimal(' +123 ', 'en-US'); + should(Math.abs(result - 123)).be.lessThan(Number.EPSILON); - result = Ti.Locale.parseDecimal(' -123 ', 'en-US'); - should(Math.abs(result + 123)).be.lessThan(Number.EPSILON); - }); + result = Ti.Locale.parseDecimal(' -123 ', 'en-US'); + should(Math.abs(result + 123)).be.lessThan(Number.EPSILON); + }); - it('NaN', () => { - let result = Ti.Locale.parseDecimal('ThisShouldFail'); - should(result).be.a.Number(); - should(result).be.eql(Number.NaN); + it('NaN', () => { + let result = Ti.Locale.parseDecimal('ThisShouldFail'); + should(result).be.a.Number(); + should(result).be.eql(Number.NaN); - result = Ti.Locale.parseDecimal(''); - should(result).be.eql(Number.NaN); + result = Ti.Locale.parseDecimal(''); + should(result).be.eql(Number.NaN); - result = Ti.Locale.parseDecimal(' '); - should(result).be.eql(Number.NaN); + result = Ti.Locale.parseDecimal(' '); + should(result).be.eql(Number.NaN); - result = Ti.Locale.parseDecimal(null); - should(result).be.eql(Number.NaN); + result = Ti.Locale.parseDecimal(null); + should(result).be.eql(Number.NaN); + + result = Ti.Locale.parseDecimal(undefined); + should(result).be.eql(Number.NaN); + }); + }); - result = Ti.Locale.parseDecimal(undefined); - should(result).be.eql(Number.NaN); + describe('#setLanguage(String)', () => { + it('is a Function', () => { + should(Ti.Locale.setLanguage).be.a.Function(); + }); + + it('changes .currentLanguage', () => { + Ti.Locale.setLanguage('fr'); + should(Ti.Locale.currentLanguage).eql('fr'); + }); + + // FIXME Get working on iOS, setLangauge doesn't seem to affect currentLocale + it.iosBroken('changes .currentLocale', () => { + Ti.Locale.setLanguage('en-GB'); + should(Ti.Locale.currentLocale).eql('en-GB'); // iOS returns 'en-US' + Ti.Locale.setLanguage('fr'); + should(Ti.Locale.currentLocale).eql('fr'); + }); + + // TODO test if it changes the currentCountry? }); }); }); diff --git a/tests/Resources/ti.media.audioplayer.test.js b/tests/Resources/ti.media.audioplayer.test.js index 7eba3f2ced1..7d2bfc811f4 100644 --- a/tests/Resources/ti.media.audioplayer.test.js +++ b/tests/Resources/ti.media.audioplayer.test.js @@ -18,11 +18,11 @@ describe('Titanium.Media.AudioPlayer', function () { this.timeout(5000); - beforeEach(function () { + beforeEach(() => { audioPlayer = Ti.Media.createAudioPlayer({ url: '/sample.mp3' }); }); - afterEach(function () { + afterEach(() => { // FIXME: calling release() on iOS is broken if (audioPlayer && Ti.App.Android) { audioPlayer.release(); @@ -35,20 +35,19 @@ describe('Titanium.Media.AudioPlayer', function () { should(audioPlayer.apiName).be.eql('Ti.Media.AudioPlayer'); }); - // Updated existing test-case, please replace with old one it('.url', function (finish) { should(audioPlayer.url).be.a.String(); - should(audioPlayer.getUrl).be.a.Function(); - should(audioPlayer.setUrl).be.a.Function(); - should(audioPlayer.url).eql(audioPlayer.getUrl()); + // getter/setters no longer exist! + should(audioPlayer).have.accessors('url'); + // should(audioPlayer.url).eql(audioPlayer.getUrl()); // Re-set URL to test TIMOB-26334, this should not crash try { audioPlayer.url = '/sample.mp3'; - finish(); } catch (e) { - finish(e); + return finish(e); } + finish(); }); it('#start, #stop', function (finish) { @@ -60,10 +59,10 @@ describe('Titanium.Media.AudioPlayer', function () { setTimeout(function () { try { audioPlayer.stop(); - finish(); } catch (e) { - finish(e); + return finish(e); } + finish(); }, 1000); }); @@ -75,10 +74,10 @@ describe('Titanium.Media.AudioPlayer', function () { setTimeout(function () { try { audioPlayer.pause(); - finish(); } catch (e) { - finish(e); + return finish(e); } + finish(); }, 1000); }); @@ -91,10 +90,10 @@ describe('Titanium.Media.AudioPlayer', function () { try { audioPlayer.restart(); audioPlayer.stop(); - finish(); } catch (e) { - finish(e); + return finish(e); } + finish(); }, 1000); }); @@ -106,10 +105,10 @@ describe('Titanium.Media.AudioPlayer', function () { should(audioPlayer.duration).be.a.Number(); // give a tiny bit of fudge room here. iOS and Android differ by 5ms on this file should(audioPlayer.duration).be.within(45250, 45500); // 45 seconds. iOS gives us 45322, Android gives 45327 - finish(); } catch (e) { - finish(e); + return finish(e); } + finish(); }, 1000); }); @@ -120,9 +119,9 @@ describe('Titanium.Media.AudioPlayer', function () { try { audioPlayer.addEventListener('progress', function () {}); - finish(); } catch (e) { - finish(e); + return finish(e); } + finish(); }); }); diff --git a/tests/Resources/ti.network.httpclient.test.js b/tests/Resources/ti.network.httpclient.test.js index 7e1915ec12d..8cc724de93c 100644 --- a/tests/Resources/ti.network.httpclient.test.js +++ b/tests/Resources/ti.network.httpclient.test.js @@ -48,20 +48,22 @@ describe('Titanium.Network.HTTPClient', function () { }); // Test for TIMOB-4513 - it('secureValidateProperty', function () { - const xhr = Ti.Network.createHTTPClient(); - should(xhr).be.an.Object(); - - should(xhr.validatesSecureCertificate).not.be.ok(); // FIXME: undefined on iOS, false on Android! - xhr.validatesSecureCertificate = true; - should(xhr.validatesSecureCertificate).be.true(); - xhr.validatesSecureCertificate = false; - should(xhr.validatesSecureCertificate).be.false(); - - xhr.setValidatesSecureCertificate(true); - should(xhr.getValidatesSecureCertificate()).be.true(); - xhr.setValidatesSecureCertificate(false); - should(xhr.getValidatesSecureCertificate()).be.false(); + describe('.validatesSecureCertificate', () => { + it('can be assigned Boolean', () => { + const xhr = Ti.Network.createHTTPClient(); + should(xhr).be.an.Object(); + + should(xhr.validatesSecureCertificate).not.be.ok(); // FIXME: undefined on iOS, false on Android! + xhr.validatesSecureCertificate = true; + should(xhr.validatesSecureCertificate).be.true(); + xhr.validatesSecureCertificate = false; + should(xhr.validatesSecureCertificate).be.false(); + }); + + it('has accessors', () => { + const xhr = Ti.Network.createHTTPClient(); + should(xhr).have.accessors('validatesSecureCertificate'); + }); }); it('downloadLargeFile', function (finish) { @@ -208,6 +210,11 @@ describe('Titanium.Network.HTTPClient', function () { xhr.send(); }); + it('#getAllResponseHeaders() exists (to match XMLHTTPRequest)', () => { + const xhr = Ti.Network.createHTTPClient(); + should(xhr.getAllResponseHeaders).be.a.Function(); + }); + // https://appcelerator.lighthouseapp.com/projects/32238/tickets/2339 it('responseHeadersBug', function (finish) { const xhr = Ti.Network.createHTTPClient({ diff --git a/tests/Resources/ti.platform.displaycaps.test.js b/tests/Resources/ti.platform.displaycaps.test.js index 4177aba1a7f..5f29bc10e9f 100644 --- a/tests/Resources/ti.platform.displaycaps.test.js +++ b/tests/Resources/ti.platform.displaycaps.test.js @@ -6,87 +6,126 @@ */ /* eslint-env mocha */ /* eslint no-unused-expressions: "off" */ +/* eslint mocha/no-identical-title: "off" */ 'use strict'; -var should = require('./utilities/assertions'); +const should = require('./utilities/assertions'); -describe('Titanium.Platform.DisplayCaps', function () { - it('apiName', function () { - should(Ti.Platform.displayCaps).have.readOnlyProperty('apiName').which.is.a.String(); - should(Ti.Platform.displayCaps.apiName).be.eql('Ti.Platform.DisplayCaps'); - }); +describe('Titanium.Platform.DisplayCaps', () => { + describe('properties', () => { + describe('.apiName', () => { + it('is a read-only String', () => { + should(Ti.Platform.displayCaps).have.readOnlyProperty('apiName').which.is.a.String(); + }); - // FIXME Get working on IOS // on iOS property is configurable - it.iosBroken('density', function () { - should(Ti.Platform.displayCaps).have.readOnlyProperty('density').which.is.a.String(); - // TODO Test for known range of values? - // Android: "high", "medium", "xhigh", "xxhigh", "xxxhigh", "low", "medium" - // iOS: "xhigh", "high", "medium" - }); + it('equals Ti.Platform.DisplayCaps', () => { + should(Ti.Platform.displayCaps.apiName).be.eql('Ti.Platform.DisplayCaps'); + }); + }); - it('getDensity()', function () { - should(Ti.Platform.displayCaps.getDensity).be.a.Function(); - should(Ti.Platform.displayCaps.getDensity()).be.a.String(); - }); + describe('.density', () => { + it('is a read-only String', () => { + should(Ti.Platform.displayCaps).have.readOnlyProperty('density').which.is.a.String(); + }); - // FIXME Get working on IOS - it.iosBroken('dpi', function () { - should(Ti.Platform.displayCaps).have.readOnlyProperty('dpi').which.is.a.Number(); - should(Ti.Platform.displayCaps.dpi).be.above(0); - }); + it('is one of known values', () => { + should([ + 'xxxhigh', // Android 4x 560+ dpi (note that android's constant is for 640 == xxxhdpi) + 'xxhigh', // Android 3x 400+ dpi (note that android's constant is for 480 == xxhdpi) + 'xhigh', // iOS 3x, Android 280+ dpi (note their constant is for 320dpi == xhdpi!) + 'high', // 2x on iOS, Android 240 dpi (hdpi) + 'tvdpi', // Android 213 dpi (720p TV screen) + 'medium', // 1x on iOS, Android 160 dpi (mdpi) + 'low', // Android < 160 dpi + ]).containEql(Ti.Platform.displayCaps.density); + }); - it('getDpi()', function () { - should(Ti.Platform.displayCaps.getDpi).be.a.Function(); - should(Ti.Platform.displayCaps.getDpi()).be.a.Number(); - }); + it('has no getter', () => { + should(Ti.Platform.displayCaps).have.a.getter('density'); + }); + }); - // FIXME Get working on IOS - it.iosBroken('logicalDensityFactor', function () { - should(Ti.Platform.displayCaps).have.readOnlyProperty('logicalDensityFactor').which.is.a.Number(); - should(Ti.Platform.displayCaps.logicalDensityFactor).be.above(0); - }); + describe('.dpi', () => { + it('is a read-only Number', () => { + should(Ti.Platform.displayCaps).have.readOnlyProperty('dpi').which.is.a.Number(); + }); - it('getLogicalDensityFactor()', function () { - should(Ti.Platform.displayCaps.getLogicalDensityFactor).be.a.Function(); - should(Ti.Platform.displayCaps.getLogicalDensityFactor()).be.a.Number(); - }); + it('is above 0', () => { + should(Ti.Platform.displayCaps.dpi).be.above(0); + }); - it('platformHeight', function () { - should(Ti.Platform.displayCaps.platformHeight).be.a.Number(); - should(Ti.Platform.displayCaps.platformHeight).be.above(0); - }); + it('has no getter', () => { + should(Ti.Platform.displayCaps).have.a.getter('dpi'); + }); + }); - it('getPlatformHeight()', function () { - should(Ti.Platform.displayCaps.getPlatformHeight).be.a.Function(); - should(Ti.Platform.displayCaps.getPlatformHeight()).be.a.Number(); - }); + describe('.logicalDensityFactor', () => { + it('is a read-only Number', () => { + should(Ti.Platform.displayCaps).have.readOnlyProperty('logicalDensityFactor').which.is.a.Number(); + }); - it('platformWidth', function () { - should(Ti.Platform.displayCaps.platformWidth).be.a.Number(); - should(Ti.Platform.displayCaps.platformWidth).be.above(0); - }); + it('is above 0', () => { + should(Ti.Platform.displayCaps.logicalDensityFactor).be.above(0); + }); - it('getPlatformWidth()', function () { - should(Ti.Platform.displayCaps.getPlatformWidth).be.a.Function(); - should(Ti.Platform.displayCaps.getPlatformWidth()).be.a.Number(); - }); + it('has no getter', () => { + should(Ti.Platform.displayCaps).have.a.getter('logicalDensityFactor'); + }); + }); - it.iosMissingAndWindowsDesktopBroken('xdpi', function () { - should(Ti.Platform.displayCaps).have.readOnlyProperty('xdpi').which.is.a.Number(); - should(Ti.Platform.displayCaps.xdpi).be.above(0); // Windows Desktop gives 0 - }); + describe('.platformHeight', () => { + it('is a read-only Number', () => { + should(Ti.Platform.displayCaps).have.readOnlyProperty('platformHeight').which.is.a.Number(); + }); - it.iosMissing('getXdpi()', function () { - should(Ti.Platform.displayCaps.getXdpi).be.a.Function(); - should(Ti.Platform.displayCaps.getXdpi()).be.a.Number(); - }); + it('is above 0', () => { + should(Ti.Platform.displayCaps.platformHeight).be.above(0); + }); - it.iosMissingAndWindowsDesktopBroken('ydpi', function () { - should(Ti.Platform.displayCaps).have.readOnlyProperty('ydpi').which.is.a.Number(); - should(Ti.Platform.displayCaps.ydpi).be.above(0); // Windows Desktop gives 0 - }); + it('has no getter', () => { + should(Ti.Platform.displayCaps).have.a.getter('platformHeight'); + }); + }); + + describe('.platformWidth', () => { + it('is a read-only Number', () => { + should(Ti.Platform.displayCaps).have.readOnlyProperty('platformWidth').which.is.a.Number(); + }); + + it('is above 0', () => { + should(Ti.Platform.displayCaps.platformWidth).be.above(0); + }); + + it('has no getter', () => { + should(Ti.Platform.displayCaps).have.a.getter('platformWidth'); + }); + }); + + describe.android('.xdpi', () => { + it('is a read-only Number', () => { + should(Ti.Platform.displayCaps).have.readOnlyProperty('xdpi').which.is.a.Number(); + }); + + it('is above 0', () => { + should(Ti.Platform.displayCaps.xdpi).be.above(0); + }); + + it('has no getter', () => { + should(Ti.Platform.displayCaps).have.a.getter('xdpi'); + }); + }); + + describe.android('.ydpi', () => { + it('is a read-only Number', () => { + should(Ti.Platform.displayCaps).have.readOnlyProperty('ydpi').which.is.a.Number(); + }); + + it('is above 0', () => { + should(Ti.Platform.displayCaps.ydpi).be.above(0); + }); - it.iosMissing('getYdpi()', function () { - should(Ti.Platform.displayCaps.getYdpi).be.a.Function(); - should(Ti.Platform.displayCaps.getYdpi()).be.a.Number(); + it('has no getter', () => { + should(Ti.Platform.displayCaps).have.a.getter('ydpi'); + }); + }); }); }); diff --git a/tests/Resources/ti.platform.test.js b/tests/Resources/ti.platform.test.js index dc218f536cf..c282d9e6a2b 100644 --- a/tests/Resources/ti.platform.test.js +++ b/tests/Resources/ti.platform.test.js @@ -1,344 +1,499 @@ /* * Appcelerator Titanium Mobile - * Copyright (c) 2011-Present by Appcelerator, Inc. All Rights Reserved. + * Copyright (c) 2020-Present by Axway, Inc. All Rights Reserved. * Licensed under the terms of the Apache Public License * Please see the LICENSE included with this distribution for details. */ -/* global OS_VERSION_MAJOR, OS_VERSION_MINOR, OS_VERSION_PATCH, OS_IOS */ /* eslint-env mocha */ /* eslint no-unused-expressions: "off" */ +/* eslint no-undef: "off" */ +/* eslint mocha/no-identical-title: "off" */ 'use strict'; + const should = require('./utilities/assertions'); -const utilities = require('./utilities/utilities'); -describe('Titanium.Platform', function () { +describe('Titanium.Platform', () => { - it('apiName', () => { - should(Ti.Platform).have.readOnlyProperty('apiName').which.is.a.String(); - should(Ti.Platform.apiName).be.eql('Ti.Platform'); - }); + describe('properties', () => { + describe('.address', () => { + it('is a String', () => { + // may be undefined on ios sim! + if (OS_IOS && Ti.Platform.model.includes('(Simulator)')) { + return; + } + should(Ti.Platform).have.a.readOnlyProperty('address').which.is.a.String(); + }); - it('canOpenURL()', () => { - should(Ti.Platform.canOpenURL).be.a.Function(); - should(Ti.Platform.canOpenURL('http://www.appcelerator.com/')).be.true(); - should(Ti.Platform.canOpenURL('mocha://')).be.true(); - }); + it('matches IP address format if defined', () => { + // may be undefined on ios sim! + if (OS_IOS && Ti.Platform.model.includes('(Simulator)')) { + return; + } + should(Ti.Platform.address).match(/\d+\.\d+\.\d+\.\d+/); + // TODO Verify the format of the String. Should be an IP address, so like: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/ + }); + }); - it('#createUUID()', () => { - should(Ti.Platform.createUUID).be.a.Function(); + describe('.apiName', () => { + it('is a String', () => { + should(Ti.Platform).have.a.readOnlyProperty('apiName').which.is.a.String(); + }); - const result = Ti.Platform.createUUID(); - should(result).be.a.String(); - should(result.length).eql(36); - // Verify format using regexp! - should(result.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i)).not.eql(null); - should(result.charAt(0)).not.eql('{'); - should(result.charAt(result.length - 1)).not.eql('}'); - }); + it('equals Ti.Platform', () => { + should(Ti.Platform.apiName).eql('Ti.Platform'); + }); + }); - describe('#openURL', () => { - // Checks if openURL() successfully opened this app with its own "mocha://" custom URL scheme. - function handleUrl(url, finish) { - if (utilities.isAndroid()) { - Ti.Android.rootActivity.addEventListener('newintent', function listener(e) { - try { - Ti.Android.rootActivity.removeEventListener('newintent', listener); - should(e.intent.data).be.eql(url); - } catch (err) { - return finish(err); - } - finish(); - }); - } else if (utilities.isIOS()) { - Ti.App.iOS.addEventListener('handleurl', function listener(e) { - try { - Ti.App.iOS.removeEventListener('handleurl', listener); - should(e.launchOptions.url).be.eql(url); - } catch (err) { - return finish(err); - } - finish(); - }); - } else { - finish(new Error('This test is not supported on this platform.')); - } - } - - it('(url)', (finish) => { - const url = 'mocha://test1'; - handleUrl(url, finish); - should(Ti.Platform.openURL).be.a.Function(); - const wasOpened = Ti.Platform.openURL(url); - if (utilities.isIOS()) { - should(wasOpened).be.a.Boolean(); - } else { - should(wasOpened).be.true(); - } + describe('.architecture', () => { + it('is a String', () => { + should(Ti.Platform).have.a.readOnlyProperty('architecture').which.is.a.String(); + }); }); - it('(url, callback)', (finish) => { - const url = 'mocha://test2'; - let wasCallbackInvoked = false; - let wasUrlReceived = false; + describe('.availableMemory', () => { + it('is a Number', () => { + should(Ti.Platform).have.a.readOnlyProperty('availableMemory').which.is.a.Number(); + }); + }); - handleUrl(url, (err) => { - wasUrlReceived = true; - if (err) { - return finish(err); - } - if (wasCallbackInvoked) { - finish(); - } + describe('.batteryLevel', () => { + it('is a Number', () => { + should(Ti.Platform).have.a.readOnlyProperty('batteryLevel').which.is.a.Number(); }); - const wasOpened = Ti.Platform.openURL(url, (e) => { - try { - wasCallbackInvoked = true; - should(e.success).be.true(); - } catch (err) { - return finish(err); - } - if (wasUrlReceived) { - finish(); - } + }); + + describe('.batteryMonitoring', () => { + it('is a Boolean', () => { + should(Ti.Platform).have.a.property('batteryMonitoring').which.is.a.Boolean(); + }); + + it('defaults to false', () => { + should(Ti.Platform.batteryMonitoring).be.false(); }); - if (utilities.isIOS()) { - should(wasOpened).be.a.Boolean; - } else { - should(wasOpened).be.true(); - } }); - it('(url, options, callback)', (finish) => { - const url = 'mocha://test3'; - let wasCallbackInvoked = false; - let wasUrlReceived = false; + describe('.batteryState', () => { + it('is a Number', () => { + should(Ti.Platform).have.a.readOnlyProperty('batteryState').which.is.a.Number(); + }); - handleUrl(url, (err) => { - wasUrlReceived = true; - if (err) { - return finish(err); - } - if (wasCallbackInvoked) { - finish(); + it('is one of Ti.Platform.BATTERY_STATE_*', () => { + should([ + Ti.Platform.BATTERY_STATE_CHARGING, + Ti.Platform.BATTERY_STATE_FULL, + Ti.Platform.BATTERY_STATE_UNKNOWN, + Ti.Platform.BATTERY_STATE_UNPLUGGED, + ]).containEql(Ti.Platform.batteryState); + }); + }); + + describe('.displayCaps', () => { + it('is a Titanium.Platform.DisplayCaps', () => { + should(Ti.Platform).have.a.readOnlyProperty('displayCaps').which.is.an.Object(); + should(Ti.Platform.displayCaps).have.a.readOnlyProperty('apiName').which.eql('Ti.Platform.DisplayCaps'); + }); + }); + + describe('.id', () => { + it('is a String', () => { + should(Ti.Platform).have.a.readOnlyProperty('id').which.is.a.String(); + }); + + it.ios('is a 36-character String matching guid format', () => { + const platformId = Ti.Platform.id; + should(platformId).be.a.String(); + should(platformId.length).eql(36); + // Verify format using regexp! + platformId.should.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i); + }); + }); + + describe.ios('.identifierForAdvertising', () => { + it('is a String', () => { + should(Ti.Platform).have.a.property('identifierForAdvertising').which.is.a.String(); + }); + }); + + describe.ios('.identifierForVendor', () => { + it('is a String', () => { + should(Ti.Platform).have.a.readOnlyProperty('identifierForVendor').which.is.a.String(); + }); + }); + + describe.ios('.isAdvertisingTrackingEnabled', () => { + it('is a Boolean', () => { + should(Ti.Platform).have.a.readOnlyProperty('isAdvertisingTrackingEnabled').which.is.a.Boolean(); + }); + }); + + describe('.locale', () => { + it('is a String', () => { + should(Ti.Platform).have.a.readOnlyProperty('locale').which.is.a.String(); + }); + }); + + describe('.macaddress', () => { + it('is a String', () => { + should(Ti.Platform).have.a.readOnlyProperty('macaddress').which.is.a.String(); + }); + + it.ios('returns a 36-character guid format String', () => { + const macaddress = Ti.Platform.macaddress; + should(macaddress).be.a.String(); + should(macaddress.length).eql(36); + // Verify format using regexp! + macaddress.should.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i); + }); + }); + + describe('.manufacturer', () => { + it('is a String', () => { + should(Ti.Platform).have.a.readOnlyProperty('manufacturer').which.is.a.String(); + }); + }); + + describe('.model', () => { + it('is a String', () => { + should(Ti.Platform).have.a.readOnlyProperty('model').which.is.a.String(); + }); + }); + + describe('.name', () => { + it('is a String', () => { + should(Ti.Platform).have.a.readOnlyProperty('name').which.is.a.String(); + }); + + it('is one of known constant String values', () => { + should(Ti.Platform.name).be.equalOneOf([ 'android', 'iOS', 'windows', 'mobileweb', 'Mac OS X' ]); + // TODO match with osname! + }); + }); + + describe('.netmask', () => { + it('is a String', () => { + // may be undefined on ios sim! + if (OS_IOS && Ti.Platform.model.includes('(Simulator)')) { + return; } + should(Ti.Platform).have.a.readOnlyProperty('netmask').which.is.a.String(); }); - const options = { - UIApplicationOpenURLOptionsOpenInPlaceKey: true - }; - const wasOpened = Ti.Platform.openURL(url, options, (e) => { - try { - wasCallbackInvoked = true; - should(e.success).be.true(); - } catch (err) { - return finish(err); + + it('matches IP address format if defined', () => { + // may be undefined on ios sim! + if (OS_IOS && Ti.Platform.model.includes('(Simulator)')) { + return; } - if (wasUrlReceived) { - finish(); + should(Ti.Platform.netmask).match(/\d+\.\d+\.\d+\.\d+/); + }); + }); + + describe('.osname', () => { + it('is a String', () => { + should(Ti.Platform).have.a.readOnlyProperty('osname').which.is.a.String(); + }); + + it('is one of known constant String values', () => { + should(Ti.Platform.osname).be.equalOneOf([ 'android', 'iphone', 'ipad', 'windowsphone', 'windowsstore', 'mobileweb' ]); + // TODO match up Ti.Platform.name? + }); + }); + + describe('.ostype', () => { + it('is a String', () => { + should(Ti.Platform).have.a.readOnlyProperty('ostype').which.is.a.String(); + }); + + it('is one of known String constant values', () => { + // Verify it's one of the known values + should(Ti.Platform.ostype).be.equalOneOf([ '64bit', '32bit', 'unknown' ]); + }); + }); + + describe('.processorCount', () => { + it('is a Number', () => { + should(Ti.Platform).have.a.readOnlyProperty('processorCount').which.is.a.Number(); + }); + }); + + describe('.runtime', () => { + it('is a String', () => { + should(Ti.Platform).have.a.readOnlyProperty('runtime').which.is.a.String(); + }); + + it('is one of known conatant String values', () => { + if (OS_ANDROID) { + should(Ti.Platform.runtime).eql('v8'); + } else { + should(Ti.Platform.runtime).eql('javascriptcore'); } }); - if (utilities.isIOS()) { - should(wasOpened).be.a.Boolean; - } else { - should(wasOpened).be.be.true(); - } }); - }); - it('#is24HourTimeFormat()', () => { - should(Ti.Platform.is24HourTimeFormat).be.a.Function(); - should(Ti.Platform.is24HourTimeFormat()).be.a.Boolean(); - }); + describe('.totalMemory', () => { + it('is a Number', () => { + should(Ti.Platform).have.a.readOnlyProperty('totalMemory').which.is.a.Number(); + }); + }); - it('.BATTERY_STATE_CHARGING', () => { - should(Ti.Platform).have.constant('BATTERY_STATE_CHARGING').which.is.a.Number(); - }); + describe('.uptime', () => { + it('is a Number', () => { + should(Ti.Platform).have.a.readOnlyProperty('uptime').which.is.a.Number(); + }); + }); - it('.BATTERY_STATE_FULL', () => { - should(Ti.Platform).have.constant('BATTERY_STATE_FULL').which.is.a.Number(); - }); + describe('.username', () => { + it('is a String', () => { + should(Ti.Platform).have.a.readOnlyProperty('username').which.is.a.String(); + }); + }); - it('.BATTERY_STATE_UNKNOWN', () => { - should(Ti.Platform).have.constant('BATTERY_STATE_UNKNOWN').which.is.a.Number(); - }); + describe('.version', () => { + it('is a String', () => { + should(Ti.Platform).have.a.readOnlyProperty('version').which.is.a.String(); + }); + }); - it('.BATTERY_STATE_UNPLUGGED', () => { - should(Ti.Platform).have.constant('BATTERY_STATE_UNPLUGGED').which.is.a.Number(); - }); + describe('.versionMajor', () => { + it('is a Number', () => { + should(Ti.Platform).have.a.readOnlyProperty('versionMajor').which.is.a.Number(); + }); - // TODO Add tests for getters! - it('.address', () => { - should(Ti.Platform).have.readOnlyProperty('address'); - if (Ti.Platform.address) { // we typically get undefined on iOS sim - should(Ti.Platform.address).match(/\d+\.\d+\.\d+\.\d+/); - } - // TODO Verify the format of the String. Should be an IP address, so like: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/ - }); + it('equals OS_VERSION_MAJOR value', () => { + should(Ti.Platform.versionMajor).be.eql(OS_VERSION_MAJOR); + }); - it('.architecture', () => { - should(Ti.Platform).have.readOnlyProperty('architecture').which.is.a.String(); - }); + it('equals first segment of Ti.Platform.version as Integer', () => { + should(Ti.Platform.versionMajor).be.eql(parseInt(Ti.Platform.version.split('.')[0])); + }); + }); - it('.availableMemory', () => { - should(Ti.Platform).have.readOnlyProperty('availableMemory').which.is.a.Number(); - }); + describe('.versionMinor', () => { + it('is a Number', () => { + should(Ti.Platform).have.a.readOnlyProperty('versionMinor').which.is.a.Number(); + }); - it('.batteryLevel', () => { - // batteryLevel should be a number and only accessible from phone - should(Ti.Platform).have.readOnlyProperty('batteryLevel').which.is.a.Number(); - }); + it('equals OS_VERSION_MINOR value', () => { + should(Ti.Platform.versionMinor).be.eql(OS_VERSION_MINOR); + }); - it('.batteryMonitoring', () => { - should(Ti.Platform.batteryMonitoring).be.a.Boolean(); - // Note: Windows 10 Mobile doesn't support battery monitoring - if (utilities.isWindowsPhone() && !/^10\./.test(Ti.Platform.version)) { - should(Ti.Platform.batteryMonitoring).be.true(); - } else if (utilities.isWindowsDesktop()) { - should(Ti.Platform.batteryMonitoring).be.false(); - } - }); + it('equals second segment of Ti.Platform.version as Integer', () => { + const versionComponents = Ti.Platform.version.split('.'); + const versionMinor = (versionComponents.length >= 2) ? parseInt(versionComponents[1]) : 0; + should(Ti.Platform.versionMinor).be.eql(versionMinor); + }); + }); - it('.batteryState', () => { - should(Ti.Platform).have.readOnlyProperty('batteryState').which.is.a.Number(); - // Must be one of the constant values - should(Ti.Platform.batteryState).be.equalOneOf([ - Ti.Platform.BATTERY_STATE_CHARGING, - Ti.Platform.BATTERY_STATE_FULL, - Ti.Platform.BATTERY_STATE_UNKNOWN, - Ti.Platform.BATTERY_STATE_UNPLUGGED ]); - }); + describe('.versionPatch', () => { + it('is a Number', () => { + should(Ti.Platform).have.a.readOnlyProperty('versionPatch').which.is.a.Number(); + }); - it('.displayCaps', () => { - should(Ti.Platform.displayCaps).be.an.Object(); - should(Ti.Platform.displayCaps).not.be.null(); - should(Ti.Platform.displayCaps.apiName).eql('Ti.Platform.DisplayCaps'); - }); + it('equals OS_VERSION_PATCH value', () => { + should(Ti.Platform.versionPatch).be.eql(OS_VERSION_PATCH); + }); - it('.id', () => { - should(Ti.Platform).have.readOnlyProperty('id').which.is.a.String(); - if (OS_IOS) { - const platformId = Ti.Platform.id; - should(platformId).be.a.String(); - should(platformId.length).eql(36); - // Verify format using regexp! - platformId.should.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i); - } - }); + it('equals third segment of Ti.Platform.version as Integer', () => { + const versionComponents = Ti.Platform.version.split('.'); + const versionPatch = (versionComponents.length >= 3) ? parseInt(versionComponents[2]) : 0; + should(Ti.Platform.versionPatch).be.eql(versionPatch); + }); + }); - it('.locale', () => { - should(Ti.Platform).have.readOnlyProperty('locale').which.is.a.String(); - // TODO Verify format of the string, i.e. 'en-US', 'en-GB' typically a 2-letter or two 2-letter segments combined with hyphen }); - it('.macaddress', () => { - should(Ti.Platform).have.readOnlyProperty('macaddress').which.is.a.String(); - if (OS_IOS) { - const macaddress = Ti.Platform.macaddress; - should(macaddress).be.a.String(); - should(macaddress.length).eql(36); - // Verify format using regexp! - macaddress.should.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i); - } - }); + describe('methods', () => { + describe('#canOpenURL()', () => { + it('is a Function', () => { + should(Ti.Platform.canOpenURL).be.a.Function(); + }); - it('.manufacturer', () => { - should(Ti.Platform).have.readOnlyProperty('manufacturer').which.is.a.String(); - }); + it('returns true for typical http URL', () => { + should(Ti.Platform.canOpenURL('http://www.appcelerator.com/')).be.true(); + }); - it('.model', () => { - should(Ti.Platform).have.readOnlyProperty('model').which.is.a.String(); - }); + it('returns true for app-sepcific URI scheme', () => { + should(Ti.Platform.canOpenURL('mocha://')).be.true(); + }); + }); - it('.name', () => { - should(Ti.Platform).have.readOnlyProperty('name').which.is.a.String(); - should(Ti.Platform.name).be.equalOneOf([ 'android', 'iOS', 'windows', 'mobileweb', 'Mac OS X' ]); - // TODO match with osname! - }); + describe.android('#cpus()', () => { + it('is a Function', () => { + should(Ti.Platform.cpus).be.a.Function(); + }); + }); - it('.netmask', () => { - should(Ti.Platform).have.readOnlyProperty('netmask'); - if (Ti.Platform.netmask) { // undefined on iOS sim - should(Ti.Platform.netmask).match(/\d+\.\d+\.\d+\.\d+/); - } - }); + describe('#createUUID()', () => { + it('is a Function', () => { + should(Ti.Platform.createUUID).be.a.Function(); + }); - it('.osname', () => { - should(Ti.Platform).have.readOnlyProperty('osname').which.is.a.String(); - // Must be one of the known platforms! - should(Ti.Platform.osname).be.equalOneOf([ 'android', 'iphone', 'ipad', 'windowsphone', 'windowsstore', 'mobileweb' ]); - // TODO match up Ti.Platform.name? - }); + it('returns a 36-character String matching guid format', () => { + const result = Ti.Platform.createUUID(); + should(result).be.a.String(); + should(result.length).eql(36); + // Verify format using regexp! + should(result.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i)).not.eql(null); + should(result.charAt(0)).not.eql('{'); + should(result.charAt(result.length - 1)).not.eql('}'); + }); + }); - it('.ostype', () => { - should(Ti.Platform).have.readOnlyProperty('ostype').which.is.a.String(); - // Verify it's one of the known values - should(Ti.Platform.ostype).be.equalOneOf([ '64bit', '32bit', 'unknown' ]); - }); + describe('#is24HourTimeFormat()', () => { + it('is a Function', () => { + should(Ti.Platform.is24HourTimeFormat).be.a.Function(); + }); - it('.processorCount', () => { - should(Ti.Platform).have.readOnlyProperty('processorCount').which.is.a.Number(); - }); + it('returns a Boolean', () => { + should(Ti.Platform.is24HourTimeFormat()).be.a.Boolean(); + }); + }); - it('.runtime', () => { - should(Ti.Platform).have.readOnlyProperty('runtime').which.is.a.String(); - if (utilities.isAndroid()) { - should(Ti.Platform.runtime).eql('v8'); - } else if (utilities.isIOS() || utilities.isWindows()) { - should(Ti.Platform.runtime).eql('javascriptcore'); - } else { - should(Ti.Platform.runtime.length).be.greaterThan(0); - } - }); + describe('#openURL', () => { + it('is a Function', () => { + should(Ti.Platform.openURL).be.a.Function(); + }); - it('.version', () => { - should(Ti.Platform).have.readOnlyProperty('version').which.is.a.String(); - }); + // Checks if openURL() successfully opened this app with its own "mocha://" custom URL scheme. + function handleUrl(url, finish) { + if (OS_ANDROID) { + Ti.Android.rootActivity.addEventListener('newintent', function listener(e) { + try { + Ti.Android.rootActivity.removeEventListener('newintent', listener); + should(e.intent.data).be.eql(url); + } catch (err) { + return finish(err); + } + finish(); + }); + } else if (OS_IOS) { + Ti.App.iOS.addEventListener('handleurl', function listener(e) { + try { + Ti.App.iOS.removeEventListener('handleurl', listener); + should(e.launchOptions.url).be.eql(url); + } catch (err) { + return finish(err); + } + finish(); + }); + } else { + finish(new Error('This test is not supported on this platform.')); + } + } - it('.versionMajor', () => { - should(Ti.Platform).have.readOnlyProperty('versionMajor').which.is.a.Number(); - should(Ti.Platform.versionMajor).be.eql(OS_VERSION_MAJOR); - should(Ti.Platform.versionMajor).be.eql(parseInt(Ti.Platform.version.split('.')[0])); - }); + it('(url)', finish => { + const url = 'mocha://test1'; + handleUrl(url, finish); + should(Ti.Platform.openURL).be.a.Function(); + const wasOpened = Ti.Platform.openURL(url); + if (OS_IOS) { + should(wasOpened).be.a.Boolean(); + } else { + should(wasOpened).be.true(); + } + }); - it('.versionMinor', () => { - should(Ti.Platform).have.readOnlyProperty('versionMinor').which.is.a.Number(); - should(Ti.Platform.versionMinor).be.eql(OS_VERSION_MINOR); + it('(url, callback)', finish => { + const url = 'mocha://test2'; + let wasCallbackInvoked = false; + let wasUrlReceived = false; - const versionComponents = Ti.Platform.version.split('.'); - const versionMinor = (versionComponents.length >= 2) ? parseInt(versionComponents[1]) : 0; - should(Ti.Platform.versionMinor).be.eql(versionMinor); - }); + handleUrl(url, (err) => { + wasUrlReceived = true; + if (err) { + return finish(err); + } + if (wasCallbackInvoked) { + finish(); + } + }); + const wasOpened = Ti.Platform.openURL(url, (e) => { + try { + wasCallbackInvoked = true; + should(e.success).be.true(); + } catch (err) { + return finish(err); + } + if (wasUrlReceived) { + finish(); + } + }); + if (OS_IOS) { + should(wasOpened).be.a.Boolean; + } else { + should(wasOpened).be.true(); + } + }); - it('.versionPatch', () => { - should(Ti.Platform).have.readOnlyProperty('versionPatch').which.is.a.Number(); - should(Ti.Platform.versionPatch).be.eql(OS_VERSION_PATCH); + it('(url, options, callback)', finish => { + const url = 'mocha://test3'; + let wasCallbackInvoked = false; + let wasUrlReceived = false; - const versionComponents = Ti.Platform.version.split('.'); - const versionPatch = (versionComponents.length >= 3) ? parseInt(versionComponents[2]) : 0; - should(Ti.Platform.versionPatch).be.eql(versionPatch); - }); + handleUrl(url, (err) => { + wasUrlReceived = true; + if (err) { + return finish(err); + } + if (wasCallbackInvoked) { + finish(); + } + }); + const options = { + UIApplicationOpenURLOptionsOpenInPlaceKey: true + }; + const wasOpened = Ti.Platform.openURL(url, options, (e) => { + try { + wasCallbackInvoked = true; + should(e.success).be.true(); + } catch (err) { + return finish(err); + } + if (wasUrlReceived) { + finish(); + } + }); + if (OS_IOS) { + should(wasOpened).be.a.Boolean; + } else { + should(wasOpened).be.true(); + } + }); - it.ios('.identifierForVendor', () => { - should(Ti.Platform.identifierForVendor).be.a.String(); - should(Ti.Platform.getIdentifierForVendor).be.a.Function(); - should(Ti.Platform.identifierForVendor).eql(Ti.Platform.getIdentifierForVendor()); - }); + // FIXME: macOS pops dialogs about no application set to open this url scheme + it.ios('(url, callback) with unhandled scheme passes Error to callback', finish => { + Ti.Platform.openURL('randomapp://', _e => finish()); + }); - it.ios('.identifierForAdvertising', () => { - should(Ti.Platform.identifierForAdvertising).be.a.String(); - should(Ti.Platform.getIdentifierForAdvertising).be.a.Function(); - should(Ti.Platform.identifierForAdvertising).eql(Ti.Platform.getIdentifierForAdvertising()); + it.ios('#openURL(url, options, callback) with unhandled scheme passes Error to callback', finish => { + Ti.Platform.openURL('randomapp://', {}, _e => finish()); + }); + }); }); - it.ios('.isAdvertisingTrackingEnabled', () => { - should(Ti.Platform.isAdvertisingTrackingEnabled).be.a.Boolean(); - }); + describe('constants', () => { + describe('.BATTERY_STATE_CHARGING', () => { + it('is a Number', () => { + should(Ti.Platform).have.a.constant('BATTERY_STATE_CHARGING').which.is.a.Number(); + }); + }); - // FIXME: macOS pops dialogs abotu no application set to open this url scheme - it.ios('#openURL(url, callback)', function (finish) { - Ti.Platform.openURL('randomapp://', _e => finish()); - }); + describe('.BATTERY_STATE_FULL', () => { + it('is a Number', () => { + should(Ti.Platform).have.a.constant('BATTERY_STATE_FULL').which.is.a.Number(); + }); + }); + + describe('.BATTERY_STATE_UNKNOWN', () => { + it('is a Number', () => { + should(Ti.Platform).have.a.constant('BATTERY_STATE_UNKNOWN').which.is.a.Number(); + }); + }); + + describe('.BATTERY_STATE_UNPLUGGED', () => { + it('is a Number', () => { + should(Ti.Platform).have.a.constant('BATTERY_STATE_UNPLUGGED').which.is.a.Number(); + }); + }); - it.ios('#openURL(url, options, callback)', function (finish) { - Ti.Platform.openURL('randomapp://', {}, _e => finish()); }); }); diff --git a/tests/Resources/ti.test.js b/tests/Resources/ti.test.js index aa51932374d..1e95885609d 100644 --- a/tests/Resources/ti.test.js +++ b/tests/Resources/ti.test.js @@ -10,88 +10,105 @@ const should = require('./utilities/assertions'); describe('Titanium', () => { - it('apiName', () => { - should(Ti).have.readOnlyProperty('apiName').which.is.a.String(); - should(Ti.apiName).be.eql('Ti'); - }); - it('version', () => { - should(Ti).have.readOnlyProperty('version').which.is.a.String(); - should(Ti.version).not.eql('__VERSION__'); // This is the placeholder value used in iOS, let's ensure we replaced it! - should(Ti.version).match(/\d+\.\d+\.\d+/); // i.e. '9.0.0' (the short version string, no timestamp qualifier) - // Build plugin "mocha.test.support" stores SDK version to app properties. - should(Ti.version).eql(Ti.App.Properties.getString('Ti.version')); - }); + describe('properties', () => { + describe('.apiName', () => { + it('is a read-only String', () => { + should(Ti).have.readOnlyProperty('apiName').which.is.a.String(); + }); - it('#getVersion()', () => { - should(Ti.getVersion).be.a.Function(); - should(Ti.getVersion()).eql(Ti.version); - }); + it('equals \'Ti\'', () => { + should(Ti.apiName).be.eql('Ti'); + }); - it('buildDate', () => { - should(Ti).have.readOnlyProperty('buildDate').which.is.a.String(); - should(Ti.buildDate).not.eql('__TIMESTAMP__'); // This is the placeholder value used in iOS, let's ensure we replaced it! - should(Ti.buildDate).match(/[01]?\d\/[0123]?\d\/20\d{2} \d{2}:\d{2}/); // i.e. '4/14/2020 18:48' - }); + it('has a getter', () => { + should(Ti).have.a.getter('apiName'); + }); + }); - it('#getBuildDate()', () => { - should(Ti.getBuildDate).be.a.Function(); - should(Ti.getBuildDate()).eql(Ti.buildDate); - }); + describe('.version', () => { + it('is a read-only String', () => { + should(Ti).have.readOnlyProperty('version').which.is.a.String(); + }); - it('buildHash', () => { - should(Ti).have.readOnlyProperty('buildHash').which.is.a.String(); - should(Ti.buildHash).not.eql('__GITHASH__'); // This is the placeholder value used in iOS, let's ensure we replaced it! - should(Ti.buildHash).match(/[a-f0-9]{10}/); // 10-character git sha - }); + it('matches expected format/value', () => { + should(Ti.version).not.eql('__VERSION__'); // This is the placeholder value used in iOS, let's ensure we replaced it! + should(Ti.version).match(/\d+\.\d+\.\d+/); // i.e. '9.0.0' (the short version string, no timestamp qualifier) + // Build plugin "mocha.test.support" stores SDK version to app properties. + should(Ti.version).eql(Ti.App.Properties.getString('Ti.version')); + }); - it('#getBuildHash()', () => { - should(Ti.getBuildHash).be.a.Function(); - should(Ti.getBuildHash()).eql(Ti.buildHash); - }); + it('has a getter', () => { + should(Ti).have.a.getter('version'); + }); + }); - // FIXME File a ticket in JIRA. Updating V8 fixes the property read/write issues, but exposes bug in that we set userAgent as read-only on Android and it shouldn't be - it.androidBroken('userAgent', function () { - should(Ti.userAgent).be.a.String(); + describe('.buildDate', () => { + it('is a read-only String', () => { + should(Ti).have.readOnlyProperty('buildDate').which.is.a.String(); + }); - const save = Ti.userAgent; - Ti.userAgent = 'Titanium_Mocha_Test'; - should(Ti.userAgent).be.eql('Titanium_Mocha_Test'); - Ti.userAgent = save; - should(Ti.userAgent).be.eql(save); - }); + it('matches expected format/value', () => { + should(Ti.buildDate).not.eql('__TIMESTAMP__'); // This is the placeholder value used in iOS, let's ensure we replaced it! + should(Ti.buildDate).match(/[01]?\d\/[0123]?\d\/20\d{2} \d{2}:\d{2}/); // i.e. '4/14/2020 18:48' + }); - it('#getUserAgent()', () => { - should(Ti.getUserAgent).be.a.Function(); - should(Ti.getUserAgent()).eql(Ti.userAgent); - }); + it('has a getter', () => { + should(Ti).have.a.getter('buildDate'); + }); + }); - // FIXME Get working on IOS/Android! - it.androidAndIosBroken('#setUserAgent()', function () { - should(Ti.setUserAgent).be.a.Function(); - var save = Ti.getUserAgent(); - Ti.setUserAgent('Titanium_Mocha_Test'); - should(Ti.getUserAgent()).be.eql('Titanium_Mocha_Test'); - should(Ti.userAgent).be.eql('Titanium_Mocha_Test'); - Ti.setUserAgent(save); - should(Ti.getUserAgent()).be.eql(save); - should(Ti.userAgent).be.eql(save); - }); + describe('.buildHash', () => { + it('is a read-only String', () => { + should(Ti).have.readOnlyProperty('buildHash').which.is.a.String(); + }); + + it('matches expected format/value', () => { + should(Ti.buildHash).not.eql('__GITHASH__'); // This is the placeholder value used in iOS, let's ensure we replaced it! + should(Ti.buildHash).match(/[a-f0-9]{10}/); // 10-character git sha + }); + + it('has a getter', () => { + should(Ti).have.a.getter('buildHash'); + }); + }); - it('#addEventListener()', () => should(Ti.addEventListener).be.a.Function()); + describe('.userAgent', () => { + it('is a String', () => { + should(Ti).have.property('userAgent').which.is.a.String(); + }); - it('#removeEventListener()', () => should(Ti.removeEventListener).be.a.Function()); + // FIXME File a ticket in JIRA. Updating V8 fixes the property read/write issues, but exposes bug in that we set userAgent as read-only on Android and it shouldn't be + it.androidBroken('can be assigned a String value', () => { + const save = Ti.userAgent; + Ti.userAgent = 'Titanium_Mocha_Test'; + should(Ti.userAgent).be.eql('Titanium_Mocha_Test'); + Ti.userAgent = save; + should(Ti.userAgent).be.eql(save); + }); - // FIXME Get working on IOS/Android! - it.androidAndIosBroken('#applyProperties()', function () { - should(Ti.applyProperties).be.a.Function(); - Ti.mocha_test = undefined; - should(Ti.applyProperties({ mocha_test: 'mocha_test_value' })); - should(Ti.mocha_test !== undefined); - should(Ti.mocha_test).be.eql('mocha_test_value'); - Ti.mocha_test = undefined; + it.androidBroken('has accessors', () => { // Cannot read property '_hasJavaListener' of undefined + should(Ti).have.accessors('userAgent'); + }); + }); }); - it('#createBuffer()', () => should(Ti.createBuffer).be.a.Function()); - it('#fireEvent()', () => should(Ti.fireEvent).be.a.Function()); + describe('methods', () => { + it('#addEventListener()', () => should(Ti.addEventListener).be.a.Function()); + + it('#removeEventListener()', () => should(Ti.removeEventListener).be.a.Function()); + + // FIXME Get working on IOS/Android! + it.androidAndIosBroken('#applyProperties()', function () { + should(Ti.applyProperties).be.a.Function(); + Ti.mocha_test = undefined; + should(Ti.applyProperties({ mocha_test: 'mocha_test_value' })); + should(Ti.mocha_test !== undefined); + should(Ti.mocha_test).be.eql('mocha_test_value'); + Ti.mocha_test = undefined; + }); + + it('#createBuffer()', () => should(Ti.createBuffer).be.a.Function()); + it('#fireEvent()', () => should(Ti.fireEvent).be.a.Function()); + }); }); diff --git a/tests/Resources/ti.ui.activityindicator.test.js b/tests/Resources/ti.ui.activityindicator.test.js index 0bdcdf68320..c36f8fee432 100644 --- a/tests/Resources/ti.ui.activityindicator.test.js +++ b/tests/Resources/ti.ui.activityindicator.test.js @@ -10,81 +10,116 @@ const should = require('./utilities/assertions'); -describe('Titanium.UI.ActivityIndicator', function () { +describe('Titanium.UI.ActivityIndicator', () => { it('.apiName', () => { const activityIndicator = Ti.UI.createActivityIndicator(); should(activityIndicator).have.readOnlyProperty('apiName').which.is.a.String(); should(activityIndicator.apiName).be.eql('Ti.UI.ActivityIndicator'); }); - it('.color', function () { - const activityIndicator = Ti.UI.createActivityIndicator({ - color: '#fff' + describe('.color', () => { + it('can be assigned a String value', () => { + const activityIndicator = Ti.UI.createActivityIndicator({ + color: '#fff' + }); + should(activityIndicator.color).be.a.String(); + should(activityIndicator.color).eql('#fff'); + activityIndicator.color = '#000'; + should(activityIndicator.color).eql('#000'); + }); + + it('has accessors', () => { + const activityIndicator = Ti.UI.createActivityIndicator({ + color: '#fff' + }); + should(activityIndicator).have.accessors('color'); }); - should(activityIndicator.color).be.a.String(); - should(activityIndicator.getColor).be.a.Function(); - should(activityIndicator.color).eql('#fff'); - should(activityIndicator.getColor()).eql('#fff'); - activityIndicator.color = '#000'; - should(activityIndicator.color).eql('#000'); - should(activityIndicator.getColor()).eql('#000'); }); - it('.font', function () { - const activityIndicator = Ti.UI.createActivityIndicator({ - font: { - fontSize: 24, - fontFamily: 'Segoe UI' - } + describe('.font', () => { + it('can be assigned Font object', () => { + const activityIndicator = Ti.UI.createActivityIndicator({ + font: { + fontSize: 24, + fontFamily: 'Segoe UI' + } + }); + should(activityIndicator.font).be.a.Object(); + should(activityIndicator.font.fontSize).eql(24); + should(activityIndicator.font.fontFamily).eql('Segoe UI'); + activityIndicator.font = { + fontSize: 11, + fontFamily: 'Segoe UI Semilight' + }; + should(activityIndicator.font.fontSize).eql(11); + should(activityIndicator.font.fontFamily).eql('Segoe UI Semilight'); + }); + + it('has accessors', () => { + const activityIndicator = Ti.UI.createActivityIndicator({ + font: { + fontSize: 24, + fontFamily: 'Segoe UI' + } + }); + should(activityIndicator).have.accessors('font'); }); - should(activityIndicator.font).be.a.Object(); - should(activityIndicator.getFont).be.a.Function(); - should(activityIndicator.font.fontSize).eql(24); - should(activityIndicator.getFont().fontFamily).eql('Segoe UI'); - activityIndicator.font = { - fontSize: 11, - fontFamily: 'Segoe UI Semilight' - }; - should(activityIndicator.font.fontSize).eql(11); - should(activityIndicator.getFont().fontFamily).eql('Segoe UI Semilight'); }); - it('.message', function () { - const activityIndicator = Ti.UI.createActivityIndicator({ - message: 'this is some text' + describe('.message', () => { + it('can be assigned a String value', () => { + const activityIndicator = Ti.UI.createActivityIndicator({ + message: 'this is some text' + }); + should(activityIndicator.message).be.a.String(); + should(activityIndicator.message).eql('this is some text'); + activityIndicator.message = 'other text'; + should(activityIndicator.message).eql('other text'); + }); + + it('has accessors', () => { + const activityIndicator = Ti.UI.createActivityIndicator({ + message: 'this is some text' + }); + should(activityIndicator).have.accessors('message'); }); - should(activityIndicator.message).be.a.String(); - should(activityIndicator.getMessage).be.a.Function(); - should(activityIndicator.message).eql('this is some text'); - should(activityIndicator.getMessage()).eql('this is some text'); - activityIndicator.message = 'other text'; - should(activityIndicator.message).eql('other text'); - should(activityIndicator.getMessage()).eql('other text'); }); - it('.style', function () { - const activityIndicator = Ti.UI.createActivityIndicator({ - style: Ti.UI.ActivityIndicatorStyle.BIG + describe('.style', () => { + it('can be assigned one of Numeric constants', () => { + const activityIndicator = Ti.UI.createActivityIndicator({ + style: Ti.UI.ActivityIndicatorStyle.BIG + }); + should(activityIndicator.style).be.a.Number(); + should(activityIndicator.style).eql(Ti.UI.ActivityIndicatorStyle.BIG); + activityIndicator.style = Ti.UI.ActivityIndicatorStyle.DARK; + should(activityIndicator.style).eql(Ti.UI.ActivityIndicatorStyle.DARK); + }); + + it('has accessors', () => { + const activityIndicator = Ti.UI.createActivityIndicator({ + style: Ti.UI.ActivityIndicatorStyle.BIG + }); + should(activityIndicator).have.accessors('style'); }); - should(activityIndicator.style).be.a.Number(); - should(activityIndicator.getStyle).be.a.Function(); - should(activityIndicator.style).eql(Ti.UI.ActivityIndicatorStyle.BIG); - should(activityIndicator.getStyle()).eql(Ti.UI.ActivityIndicatorStyle.BIG); - activityIndicator.style = Ti.UI.ActivityIndicatorStyle.DARK; - should(activityIndicator.style).eql(Ti.UI.ActivityIndicatorStyle.DARK); - should(activityIndicator.getStyle()).eql(Ti.UI.ActivityIndicatorStyle.DARK); }); - it('.indicatorColor', function () { - const activityIndicator = Ti.UI.createActivityIndicator({ - indicatorColor: '#fff' + describe('.indicatorColor', () => { + it('can be assigned/read a String', () => { + const activityIndicator = Ti.UI.createActivityIndicator({ + indicatorColor: '#fff' + }); + should(activityIndicator.indicatorColor).be.a.String(); + should(activityIndicator.indicatorColor).eql('#fff'); + activityIndicator.indicatorColor = '#000'; + should(activityIndicator.indicatorColor).eql('#000'); + }); + + it('has accessors', () => { + const activityIndicator = Ti.UI.createActivityIndicator({ + indicatorColor: '#fff' + }); + should(activityIndicator).have.accessors('indicatorColor'); }); - should(activityIndicator.indicatorColor).be.a.String(); - should(activityIndicator.getIndicatorColor).be.a.Function(); - should(activityIndicator.indicatorColor).eql('#fff'); - should(activityIndicator.getIndicatorColor()).eql('#fff'); - activityIndicator.indicatorColor = '#000'; - should(activityIndicator.indicatorColor).eql('#000'); - should(activityIndicator.getIndicatorColor()).eql('#000'); }); }); diff --git a/tests/Resources/ti.ui.alertdialog.test.js b/tests/Resources/ti.ui.alertdialog.test.js index 3e3b9236070..df319601791 100644 --- a/tests/Resources/ti.ui.alertdialog.test.js +++ b/tests/Resources/ti.ui.alertdialog.test.js @@ -9,100 +9,125 @@ 'use strict'; const should = require('./utilities/assertions'); -describe('Titanium.UI.AlertDialog', function () { - it('.apiName', function () { +describe('Titanium.UI.AlertDialog', () => { + it('.apiName', () => { const dialog = Ti.UI.createAlertDialog(); should(dialog).have.readOnlyProperty('apiName').which.is.a.String(); should(dialog.apiName).be.eql('Ti.UI.AlertDialog'); }); - it('.title', function () { - const bar = Ti.UI.createAlertDialog({ - title: 'this is some text' + describe('.title', () => { + it('is a String', () => { + const bar = Ti.UI.createAlertDialog({ + title: 'this is some text' + }); + should(bar.title).be.a.String(); + should(bar.title).eql('this is some text'); + bar.title = 'other text'; + should(bar.title).eql('other text'); }); - should(bar.title).be.a.String(); - should(bar.getTitle).be.a.Function(); - should(bar.title).eql('this is some text'); - should(bar.getTitle()).eql('this is some text'); - bar.title = 'other text'; - should(bar.title).eql('other text'); - should(bar.getTitle()).eql('other text'); - }); - // FIXME titleid doesn't seem to set title on iOS? - it.iosBroken('.titleid', function () { - const bar = Ti.UI.createAlertDialog({ - titleid: 'this_is_my_key' + it('has accessors', () => { + const bar = Ti.UI.createAlertDialog({ + title: 'this is some text' + }); + should(bar).have.accessors('title'); }); - should(bar.titleid).be.a.String(); - should(bar.getTitleid).be.a.Function(); - should(bar.titleid).eql('this_is_my_key'); - should(bar.getTitleid()).eql('this_is_my_key'); - should(bar.title).eql('this is my value'); // fails on iOS, gives undefined - bar.titleid = 'other text'; - should(bar.titleid).eql('other text'); - should(bar.getTitleid()).eql('other text'); - should(bar.title).eql('this is my value'); // retains old value if key not found: https://jira.appcelerator.org/browse/TIMOB-23498 }); - it('.message', function () { - const bar = Ti.UI.createAlertDialog({ - message: 'this is some text' + describe('.titleid', () => { + // FIXME titleid doesn't seem to set title on iOS? + it.iosBroken('is a String', () => { + const bar = Ti.UI.createAlertDialog({ + titleid: 'this_is_my_key' + }); + should(bar.titleid).be.a.String(); + should(bar.titleid).eql('this_is_my_key'); + should(bar.title).eql('this is my value'); // fails on iOS, gives undefined + bar.titleid = 'other text'; + should(bar.titleid).eql('other text'); + should(bar.title).eql('this is my value'); // retains old value if key not found: https://jira.appcelerator.org/browse/TIMOB-23498 + }); + + it('has accessors', () => { + const bar = Ti.UI.createAlertDialog({ + title: 'this is some text' + }); + should(bar).have.accessors('titleid'); }); - should(bar.message).be.a.String(); - should(bar.getMessage).be.a.Function(); - should(bar.message).eql('this is some text'); - should(bar.getMessage()).eql('this is some text'); - bar.message = 'other text'; - should(bar.message).eql('other text'); - should(bar.getMessage()).eql('other text'); }); - // FIXME Get working on iOS - defaults to undefined, should be ['OK'] - // FIXME Get working on Android - defaults to undefined, should be ['OK'] - it.androidAndIosBroken('.buttonNames', function () { - const bar = Ti.UI.createAlertDialog({}); - should(bar.buttonNames).be.an.Array(); // undefined on iOS and Android - should(bar.getButtonNames).be.a.Function(); - should(bar.buttonNames).be.empty; - should(bar.getButtonNames()).be.empty; - bar.buttonNames = [ 'this', 'other' ]; - should(bar.buttonNames.length).eql(2); - should(bar.getButtonNames().length).eql(2); + describe('.message', () => { + it('is a String', () => { + const bar = Ti.UI.createAlertDialog({ + message: 'this is some text' + }); + should(bar.message).be.a.String(); + should(bar.message).eql('this is some text'); + bar.message = 'other text'; + should(bar.message).eql('other text'); + }); + + it('has accessors', () => { + const bar = Ti.UI.createAlertDialog({ + message: 'this is some text' + }); + should(bar).have.accessors('message'); + }); }); - // FIXME Get working on iOS - defaults to undefined, should be -1 - // FIXME Get working on Android - defaults to undefined, should be -1 - it.androidAndIosBroken('.cancel', function () { - const bar = Ti.UI.createAlertDialog({}); - should(bar.cancel).be.a.Number(); // undefined on iOS and Android - should(bar.getCancel).be.a.Function(); - bar.cancel = 1; - should(bar.cancel).eql(1); - should(bar.getCancel()).eql(1); + describe('.buttonNames', () => { + // FIXME Get working on iOS - defaults to undefined, should be ['OK'] + // FIXME Get working on Android - defaults to undefined, should be ['OK'] + it.androidAndIosBroken('is a string[]', () => { + const bar = Ti.UI.createAlertDialog({}); + should(bar.buttonNames).be.an.Array(); // undefined on iOS and Android + should(bar.buttonNames).be.empty(); + bar.buttonNames = [ 'this', 'other' ]; + should(bar.buttonNames.length).eql(2); + }); + + it('has accessors', () => { + const bar = Ti.UI.createAlertDialog({}); + should(bar).have.accessors('buttonNames'); + }); }); - it.ios('.tintColor', function () { - const bar = Ti.UI.createAlertDialog({ - tintColor: 'red' + describe('.cancel', () => { + // FIXME Get working on iOS - defaults to undefined, should be -1 + // FIXME Get working on Android - defaults to undefined, should be -1 + it.androidAndIosBroken('is a Number', () => { + const bar = Ti.UI.createAlertDialog({}); + should(bar.cancel).be.a.Number(); // undefined on iOS and Android + bar.cancel = 1; + should(bar.cancel).eql(1); + }); + + it('has accessors', () => { + const bar = Ti.UI.createAlertDialog({}); + should(bar).have.accessors('cancel'); }); + }); - // Check getter - should(bar.tintColor).be.a.String(); - should(bar.getTintColor).be.a.Function(); - should(bar.tintColor).eql('red'); - should(bar.getTintColor()).eql('red'); + describe.ios('.tintColor', () => { + it('accepts a String color', () => { + const bar = Ti.UI.createAlertDialog({ + tintColor: 'red' + }); - // Set new value - bar.tintColor = '#f00'; + should(bar.tintColor).be.a.String(); + should(bar.tintColor).eql('red'); - // Check getter again - should(bar.tintColor).eql('#f00'); - should(bar.getTintColor()).eql('#f00'); + bar.tintColor = '#f00'; - // Check setter - should(bar.setTintColor).be.a.Function(); - bar.setTintColor('#0f0'); - should(bar.tintColor).eql('#0f0'); + should(bar.tintColor).eql('#f00'); + }); + + it('has accessors', () => { + const bar = Ti.UI.createAlertDialog({ + tintColor: 'red' + }); + should(bar).have.accessors('tintColor'); + }); }); }); diff --git a/tests/Resources/ti.ui.android.drawerlayout.test.js b/tests/Resources/ti.ui.android.drawerlayout.test.js index 98244e6fe44..91179c87a68 100644 --- a/tests/Resources/ti.ui.android.drawerlayout.test.js +++ b/tests/Resources/ti.ui.android.drawerlayout.test.js @@ -10,140 +10,140 @@ const should = require('./utilities/assertions'); -describe.android('Titanium.UI.Android', function () { - it('#createDrawerLayout', function () { +describe.android('Titanium.UI.Android', () => { + it('#createDrawerLayout', () => { should(Titanium.UI.Android.createDrawerLayout).be.a.Function(); const drawerLayout = Titanium.UI.Android.createDrawerLayout(); should(drawerLayout).be.a.Object(); }); }); -describe.android('Titanium.UI.Android.DrawerLayout', function () { - // constants - it('.LOCK_MODE_LOCKED_CLOSED', function () { - should(Titanium.UI.Android.DrawerLayout).have.constant('LOCK_MODE_LOCKED_CLOSED').which.is.a.Number(); - }); - it('.LOCK_MODE_LOCKED_OPEN', function () { - should(Titanium.UI.Android.DrawerLayout).have.constant('LOCK_MODE_LOCKED_OPEN').which.is.a.Number(); - }); - it('.LOCK_MODE_UNLOCKED', function () { - should(Titanium.UI.Android.DrawerLayout).have.constant('LOCK_MODE_UNLOCKED').which.is.a.Number(); - }); - it('.LOCK_MODE_UNDEFINED', function () { - should(Titanium.UI.Android.DrawerLayout).have.constant('LOCK_MODE_UNDEFINED').which.is.a.Number(); +describe.android('Titanium.UI.Android.DrawerLayout', () => { + let drawerLayout; + afterEach(() => { + drawerLayout = null; }); - // properties - it('.isLeftOpen', function () { - const drawerLayout = Titanium.UI.Android.createDrawerLayout(); - should(drawerLayout.isLeftOpen).be.a.Boolean(); - should(drawerLayout.isLeftOpen).be.false(); // default value + describe('constants', () => { + it('.LOCK_MODE_LOCKED_CLOSED', () => { + should(Titanium.UI.Android.DrawerLayout).have.constant('LOCK_MODE_LOCKED_CLOSED').which.is.a.Number(); + }); + it('.LOCK_MODE_LOCKED_OPEN', () => { + should(Titanium.UI.Android.DrawerLayout).have.constant('LOCK_MODE_LOCKED_OPEN').which.is.a.Number(); + }); + it('.LOCK_MODE_UNLOCKED', () => { + should(Titanium.UI.Android.DrawerLayout).have.constant('LOCK_MODE_UNLOCKED').which.is.a.Number(); + }); + it('.LOCK_MODE_UNDEFINED', () => { + should(Titanium.UI.Android.DrawerLayout).have.constant('LOCK_MODE_UNDEFINED').which.is.a.Number(); + }); }); - it('.isRightOpen', function () { - const drawerLayout = Titanium.UI.Android.createDrawerLayout(); - should(drawerLayout.isRightOpen).be.a.Boolean(); - should(drawerLayout.isRightOpen).be.false(); // default value - }); + describe('properties', () => { + beforeEach(() => { + drawerLayout = Titanium.UI.Android.createDrawerLayout(); + }); - it('.isLeftVisible', function () { - const drawerLayout = Titanium.UI.Android.createDrawerLayout(); - should(drawerLayout.isLeftVisible).be.a.Boolean(); - should(drawerLayout.isLeftVisible).be.false(); // default value - }); + it('.centerView', () => { + // should(drawerLayout.centerView).be.a.Object(); + // FIXME Default value is undefined, can't verify it's supposed to be an object unless we've set a value + should(drawerLayout.centerView).be.undefined(); + }); - it('isRightVisible', function () { - const drawerLayout = Titanium.UI.Android.createDrawerLayout(); - should(drawerLayout.isRightVisible).be.a.Boolean(); - should(drawerLayout.isRightVisible).be.false(); // default value - }); + it('.drawerIndicatorEnabled', () => { + should(drawerLayout.drawerIndicatorEnabled).be.a.Boolean(); + should(drawerLayout.drawerIndicatorEnabled).be.true(); // default value + }); - it('.leftWidth', function () { - const drawerLayout = Titanium.UI.Android.createDrawerLayout(); - // should(drawerLayout.leftWidth).be.a.Number(); - // FIXME Default value is undefined, can't verify it's supposed to be a number unless we've opened the left drawer - should(drawerLayout.leftWidth).be.undefined(); - }); + it('.drawerLockMode', () => { + should(drawerLayout.drawerLockMode).be.a.Number(); + should(drawerLayout.drawerLockMode).eql(Titanium.UI.Android.DrawerLayout.LOCK_MODE_UNDEFINED); // default value + // TODO Add tests that we enforce lock mode must be one of the constants defined! + }); - it('.rightWidth', function () { - const drawerLayout = Titanium.UI.Android.createDrawerLayout(); - // should(drawerLayout.rightWidth).be.a.Number(); - // FIXME Default value is undefined, can't verify it's supposed to be a number unless we've opened the right drawer - should(drawerLayout.rightWidth).be.undefined(); - }); + it('.isLeftOpen', () => { + should(drawerLayout.isLeftOpen).be.a.Boolean(); + should(drawerLayout.isLeftOpen).be.false(); // default value + }); - it('.leftView', function () { - const drawerLayout = Titanium.UI.Android.createDrawerLayout(); - // should(drawerLayout.leftView).be.a.Object(); - // FIXME Default value is undefined, can't verify it's supposed to be an object unless we've set a value - should(drawerLayout.leftView).be.undefined(); - }); + it('.isLeftVisible', () => { + should(drawerLayout.isLeftVisible).be.a.Boolean(); + should(drawerLayout.isLeftVisible).be.false(); // default value + }); - it('.rightView', function () { - const drawerLayout = Titanium.UI.Android.createDrawerLayout(); - // should(drawerLayout.rightView).be.a.Object(); - // FIXME Default value is undefined, can't verify it's supposed to be an object unless we've set a value - should(drawerLayout.rightView).be.undefined(); - }); + it('.isRightOpen', () => { + should(drawerLayout.isRightOpen).be.a.Boolean(); + should(drawerLayout.isRightOpen).be.false(); // default value + }); - it('.centerView', function () { - const drawerLayout = Titanium.UI.Android.createDrawerLayout(); - // should(drawerLayout.centerView).be.a.Object(); - // FIXME Default value is undefined, can't verify it's supposed to be an object unless we've set a value - should(drawerLayout.centerView).be.undefined(); - }); + it('isRightVisible', () => { + should(drawerLayout.isRightVisible).be.a.Boolean(); + should(drawerLayout.isRightVisible).be.false(); // default value + }); - it('.drawerIndicatorEnabled', function () { - const drawerLayout = Titanium.UI.Android.createDrawerLayout(); - should(drawerLayout.drawerIndicatorEnabled).be.a.Boolean(); - should(drawerLayout.drawerIndicatorEnabled).be.true(); // default value - }); + it('.leftView', () => { + // should(drawerLayout.leftView).be.a.Object(); + // FIXME Default value is undefined, can't verify it's supposed to be an object unless we've set a value + should(drawerLayout.leftView).be.undefined(); + }); - it('.drawerLockMode', function () { - const drawerLayout = Titanium.UI.Android.createDrawerLayout(); - should(drawerLayout.drawerLockMode).be.a.Number(); - should(drawerLayout.drawerLockMode).eql(Titanium.UI.Android.DrawerLayout.LOCK_MODE_UNDEFINED); // default value - // TODO Add tests that we enforce lock mode must be one of the constants defined! - }); + it('.leftWidth', () => { + // should(drawerLayout.leftWidth).be.a.Number(); + // FIXME Default value is undefined, can't verify it's supposed to be a number unless we've opened the left drawer + should(drawerLayout.leftWidth).be.undefined(); + }); - it('.toolbarEnabled', function () { - const drawerLayout = Titanium.UI.Android.createDrawerLayout(); - should(drawerLayout.toolbarEnabled).be.a.Boolean(); - should(drawerLayout.toolbarEnabled).be.true(); // default value - }); + it('.rightView', () => { + // should(drawerLayout.rightView).be.a.Object(); + // FIXME Default value is undefined, can't verify it's supposed to be an object unless we've set a value + should(drawerLayout.rightView).be.undefined(); + }); - // Test for theme with disabled default ActionBar - it.android('toolbarEnabled for Theme.Titanium.NoTitleBar', function () { - const window = Ti.UI.createWindow({ theme: 'Theme.Titanium.NoTitleBar' }); - const drawerLayout = Titanium.UI.Android.createDrawerLayout(); - window.add(drawerLayout); - should(drawerLayout.toolbarEnabled).be.a.Boolean(); - should(drawerLayout.toolbarEnabled).be.true(); // default value - drawerLayout.toolbarEnabled = false; - should(drawerLayout.toolbarEnabled).be.a.Boolean(); - should(drawerLayout.toolbarEnabled).be.false(); - drawerLayout.toolbarEnabled = true; - should(drawerLayout.toolbarEnabled).be.a.Boolean(); - should(drawerLayout.toolbarEnabled).be.true(); - }); + it('.rightWidth', () => { + // should(drawerLayout.rightWidth).be.a.Number(); + // FIXME Default value is undefined, can't verify it's supposed to be a number unless we've opened the right drawer + should(drawerLayout.rightWidth).be.undefined(); + }); - it.android('Toolbar used as toolbar', function (finish) { - const window = Ti.UI.createWindow({ theme: 'Theme.Titanium.NoTitleBar' }); - const toolbar = Ti.UI.createToolbar({ titleTextColor: 'red', backgroundColor: 'cyan' }); - const drawerLayout = Ti.UI.Android.createDrawerLayout({ toolbar: toolbar }); - window.add(drawerLayout); - window.addEventListener('open', function () { - try { - should(drawerLayout.toolbar.getTitleTextColor()).be.a.String(); - should(drawerLayout.toolbar.getTitleTextColor()).eql('red'); - } catch (err) { - return finish(err); - } finally { - window.close(); - } - finish(); - }); - window.open(); - }); + it('.toolbar', finish => { + const window = Ti.UI.createWindow({ theme: 'Theme.Titanium.NoTitleBar' }); + const toolbar = Ti.UI.createToolbar({ titleTextColor: 'red', backgroundColor: 'cyan' }); + drawerLayout = Ti.UI.Android.createDrawerLayout({ toolbar: toolbar }); + window.add(drawerLayout); + window.addEventListener('open', () => { + try { + should(drawerLayout.toolbar.titleTextColor).be.a.String(); + should(drawerLayout.toolbar.titleTextColor).eql('red'); + } catch (err) { + return finish(err); + } finally { + window.close(); + } + finish(); + }); + window.open(); + }); + describe('.toolbarEnabled', () => { + it('is a Boolean', () => { + should(drawerLayout.toolbarEnabled).be.a.Boolean(); + }); + + it('defaults to true', () => { + should(drawerLayout.toolbarEnabled).be.true(); // default value + }); + + // Test for theme with disabled default ActionBar + it('for Theme.Titanium.NoTitleBar', () => { + const window = Ti.UI.createWindow({ theme: 'Theme.Titanium.NoTitleBar' }); + drawerLayout = Titanium.UI.Android.createDrawerLayout(); + window.add(drawerLayout); + should(drawerLayout.toolbarEnabled).be.true(); // default value + drawerLayout.toolbarEnabled = false; + should(drawerLayout.toolbarEnabled).be.false(); + drawerLayout.toolbarEnabled = true; + should(drawerLayout.toolbarEnabled).be.true(); + }); + }); + }); }); diff --git a/tests/Resources/ti.ui.button.test.js b/tests/Resources/ti.ui.button.test.js index 520c41c13df..020ad55e94b 100644 --- a/tests/Resources/ti.ui.button.test.js +++ b/tests/Resources/ti.ui.button.test.js @@ -27,7 +27,7 @@ describe('Titanium.UI.Button', function () { } }); - it('apiName', () => { + it('.apiName', () => { const button = Ti.UI.createButton({ title: 'this is some text' }); @@ -35,36 +35,48 @@ describe('Titanium.UI.Button', function () { should(button.apiName).be.eql('Ti.UI.Button'); }); - it('title', function () { - const bar = Ti.UI.createButton({ - title: 'this is some text' + describe('.title', () => { + it('is a String', () => { + const button = Ti.UI.createButton({ + title: 'this is some text' + }); + should(button.title).be.a.String(); + should(button.title).eql('this is some text'); + button.title = 'other text'; + should(button.title).eql('other text'); + }); + + it('has accessors', () => { + const button = Ti.UI.createButton({ + title: 'this is some text' + }); + should(button).have.accessors('title'); }); - should(bar.title).be.a.String(); - should(bar.getTitle).be.a.Function(); - should(bar.title).eql('this is some text'); - should(bar.getTitle()).eql('this is some text'); - bar.title = 'other text'; - should(bar.title).eql('other text'); - should(bar.getTitle()).eql('other text'); }); // FIXME Parity issue - iOS and Android retains old title if titleid can't be found, Windows uses key - it('titleid', function () { - const bar = Ti.UI.createButton({ - titleid: 'this_is_my_key' + describe('.titleid', () => { + it('is a String', () => { + const bar = Ti.UI.createButton({ + titleid: 'this_is_my_key' + }); + should(bar.titleid).be.a.String(); + should(bar.titleid).eql('this_is_my_key'); + should(bar.title).eql('this is my value'); + bar.titleid = 'other text'; // key won't get found! + should(bar.titleid).eql('other text'); + should(bar.title).eql('this is my value'); // should retain old value if can't find key! https://jira.appcelerator.org/browse/TIMOB-23498 + }); + + it('has accessors', () => { + const button = Ti.UI.createButton({ + titleid: 'this_is_my_key' + }); + should(button).have.accessors('titleid'); }); - should(bar.titleid).be.a.String(); - should(bar.getTitleid).be.a.Function(); - should(bar.titleid).eql('this_is_my_key'); - should(bar.getTitleid()).eql('this_is_my_key'); - should(bar.title).eql('this is my value'); - bar.titleid = 'other text'; // key won't get found! - should(bar.titleid).eql('other text'); - should(bar.getTitleid()).eql('other text'); - should(bar.title).eql('this is my value'); // should retain old value if can't find key! https://jira.appcelerator.org/browse/TIMOB-23498 }); - it('image(String)', function (finish) { + it('image(String)', finish => { win = Ti.UI.createWindow({ backgroundColor: 'blue' }); diff --git a/tests/Resources/ti.ui.emaildialog.test.js b/tests/Resources/ti.ui.emaildialog.test.js index f07731b3b7f..94b9d92696c 100644 --- a/tests/Resources/ti.ui.emaildialog.test.js +++ b/tests/Resources/ti.ui.emaildialog.test.js @@ -6,12 +6,13 @@ */ /* eslint-env mocha */ /* eslint no-unused-expressions: "off" */ +/* eslint mocha/no-identical-title: "off" */ 'use strict'; const should = require('./utilities/assertions'); const utilities = require('./utilities/utilities'); -describe('Titanium.UI.EmailDialog', function () { - it('apiName', function () { +describe('Titanium.UI.EmailDialog', () => { + it('.apiName', () => { const emailDialog = Ti.UI.createEmailDialog({ subject: 'this is some text' }); @@ -20,98 +21,126 @@ describe('Titanium.UI.EmailDialog', function () { }); // FIXME constant may hang on instances for iOS and Android? But I think we should enforce being able to reference them as Ti.UI.EmailDialog.FAILED - ((utilities.isIOS() || utilities.isAndroid()) ? it.skip : it)('FAILED', function () { + ((utilities.isIOS() || utilities.isAndroid()) ? it.skip : it)('FAILED', () => { should(Ti.UI.EmailDialog).have.constant('FAILED').which.is.a.Number(); }); // FIXME constant may hang on instances for iOS and Android? But I think we should enforce being able to reference them as Ti.UI.EmailDialog.SENT - ((utilities.isIOS() || utilities.isAndroid()) ? it.skip : it)('SENT', function () { + ((utilities.isIOS() || utilities.isAndroid()) ? it.skip : it)('SENT', () => { should(Ti.UI.EmailDialog).have.constant('SENT').which.is.a.Number(); }); // FIXME constant may hang on instances for iOS and Android? But I think we should enforce being able to reference them as Ti.UI.EmailDialog.SAVED - ((utilities.isIOS() || utilities.isAndroid()) ? it.skip : it)('SAVED', function () { + ((utilities.isIOS() || utilities.isAndroid()) ? it.skip : it)('SAVED', () => { should(Ti.UI.EmailDialog).have.constant('SAVED').which.is.a.Number(); }); // FIXME constant may hang on instances for iOS and Android? But I think we should enforce being able to reference them as Ti.UI.EmailDialog.CANCELLED - ((utilities.isIOS() || utilities.isAndroid()) ? it.skip : it)('CANCELLED', function () { + ((utilities.isIOS() || utilities.isAndroid()) ? it.skip : it)('CANCELLED', () => { should(Ti.UI.EmailDialog).have.constant('CANCELLED').which.is.a.Number(); }); - (utilities.isWindowsDesktop() ? it.skip : it)('subject', function () { - var email = Ti.UI.createEmailDialog({ - subject: 'this is some text' + it.windowsDesktopBroken('.subject', () => { + it('is a String', () => { + const email = Ti.UI.createEmailDialog({ + subject: 'this is some text' + }); + should(email.subject).be.a.String(); + should(email.subject).eql('this is some text'); + email.subject = 'other text'; + should(email.subject).eql('other text'); + }); + + it('has accessors', () => { + const email = Ti.UI.createEmailDialog({ + subject: 'this is some text' + }); + should(email).have.accessors('subject'); }); - should(email.subject).be.a.String(); - should(email.getSubject).be.a.Function(); - should(email.subject).eql('this is some text'); - should(email.getSubject()).eql('this is some text'); - email.subject = 'other text'; - should(email.subject).eql('other text'); - should(email.getSubject()).eql('other text'); }); - (utilities.isWindowsDesktop() ? it.skip : it)('messageBody', function () { - var email = Ti.UI.createEmailDialog({ - messageBody: 'this is some text' + it.windowsDesktopBroken('.messageBody', () => { + it('is a String', () => { + const email = Ti.UI.createEmailDialog({ + messageBody: 'this is some text' + }); + should(email.messageBody).be.a.String(); + should(email.messageBody).eql('this is some text'); + email.messageBody = 'other text'; + should(email.messageBody).eql('other text'); + }); + + it('has accessors', () => { + const email = Ti.UI.createEmailDialog({ + messageBody: 'this is some text' + }); + should(email).have.accessors('messageBody'); }); - should(email.messageBody).be.a.String(); - should(email.getMessageBody).be.a.Function(); - should(email.messageBody).eql('this is some text'); - should(email.getMessageBody()).eql('this is some text'); - email.messageBody = 'other text'; - should(email.messageBody).eql('other text'); - should(email.getMessageBody()).eql('other text'); }); - (utilities.isWindowsDesktop() ? it.skip : it)('toRecipients', function () { - var email = Ti.UI.createEmailDialog({ - toRecipients: [ 'me@example.com' ] + it.windowsDesktopBroken('.toRecipients', () => { + it('is a string[]', () => { + const email = Ti.UI.createEmailDialog({ + toRecipients: [ 'me@example.com' ] + }); + should(email.toRecipients).be.an.Array(); + should(email.toRecipients).eql([ 'me@example.com' ]); + email.toRecipients = [ 'other@example.com' ]; + should(email.toRecipients).eql([ 'other@example.com' ]); + }); + + it('has accessors', () => { + const email = Ti.UI.createEmailDialog({ + toRecipients: [ 'me@example.com' ] + }); + should(email).have.accessors('toRecipients'); }); - should(email.toRecipients).be.an.Array(); - should(email.getToRecipients).be.a.Function(); - should(email.toRecipients).eql([ 'me@example.com' ]); - should(email.getToRecipients()).eql([ 'me@example.com' ]); - email.toRecipients = [ 'other@example.com' ]; - should(email.toRecipients).eql([ 'other@example.com' ]); - should(email.getToRecipients()).eql([ 'other@example.com' ]); }); - (utilities.isWindowsDesktop() ? it.skip : it)('ccRecipients', function () { - var email = Ti.UI.createEmailDialog({ - ccRecipients: [ 'me@example.com' ] + it.windowsDesktopBroken('.ccRecipients', () => { + it('is a string[]', () => { + const email = Ti.UI.createEmailDialog({ + ccRecipients: [ 'me@example.com' ] + }); + should(email.ccRecipients).be.an.Array(); + should(email.ccRecipients).eql([ 'me@example.com' ]); + email.ccRecipients = [ 'other@example.com' ]; + should(email.ccRecipients).eql([ 'other@example.com' ]); + }); + + it('has accessors', () => { + const email = Ti.UI.createEmailDialog({ + ccRecipients: [ 'me@example.com' ] + }); + should(email).have.accessors('ccRecipients'); }); - should(email.ccRecipients).be.an.Array(); - should(email.getCcRecipients).be.a.Function(); - should(email.ccRecipients).eql([ 'me@example.com' ]); - should(email.getCcRecipients()).eql([ 'me@example.com' ]); - email.ccRecipients = [ 'other@example.com' ]; - should(email.ccRecipients).eql([ 'other@example.com' ]); - should(email.getCcRecipients()).eql([ 'other@example.com' ]); }); - (utilities.isWindowsDesktop() ? it.skip : it)('bccRecipients', function () { - var email = Ti.UI.createEmailDialog({ - bccRecipients: [ 'me@example.com' ] + it.windowsDesktopBroken('.bccRecipients', () => { + it('is a string[]', () => { + const email = Ti.UI.createEmailDialog({ + bccRecipients: [ 'me@example.com' ] + }); + should(email.bccRecipients).be.an.Array(); + should(email.bccRecipients).eql([ 'me@example.com' ]); + email.bccRecipients = [ 'other@example.com' ]; + should(email.bccRecipients).eql([ 'other@example.com' ]); + }); + + it('has accessors', () => { + const email = Ti.UI.createEmailDialog({ + bccRecipients: [ 'me@example.com' ] + }); + should(email).have.accessors('bccRecipients'); }); - should(email.bccRecipients).be.an.Array(); - should(email.getBccRecipients).be.a.Function(); - should(email.bccRecipients).eql([ 'me@example.com' ]); - should(email.getBccRecipients()).eql([ 'me@example.com' ]); - email.bccRecipients = [ 'other@example.com' ]; - should(email.bccRecipients).eql([ 'other@example.com' ]); - should(email.getBccRecipients()).eql([ 'other@example.com' ]); }); // FIXME: macOS pops a permission prompt for Documents folder - it.macAndWindowsDesktopBroken('addAttachment', function () { - var file = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory, 'File.txt'), - blob, - email; + it.macAndWindowsDesktopBroken('#addAttachment()', () => { + const file = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory, 'File.txt'); file.write('File test.'); - blob = Ti.createBuffer({ value: 'Blob test.' }).toBlob(); - email = Ti.UI.createEmailDialog(); + const blob = Ti.createBuffer({ value: 'Blob test.' }).toBlob(); + const email = Ti.UI.createEmailDialog(); email.addAttachment(file); email.addAttachment(blob); }); diff --git a/tests/Resources/ti.ui.imageview.test.js b/tests/Resources/ti.ui.imageview.test.js index 7127455e457..90eb8a25a57 100644 --- a/tests/Resources/ti.ui.imageview.test.js +++ b/tests/Resources/ti.ui.imageview.test.js @@ -28,164 +28,232 @@ describe('Titanium.UI.ImageView', function () { } }); - it('apiName', () => { + it('.apiName', () => { const imageView = Ti.UI.createImageView(); should(imageView).have.readOnlyProperty('apiName').which.is.a.String(); should(imageView.apiName).be.eql('Ti.UI.ImageView'); }); - it('image (URL)', () => { - const imageView = Ti.UI.createImageView({ - image: 'https://www.google.com/images/srpr/logo11w.png' - }); - should(imageView.image).be.a.String(); - should(imageView.getImage).be.a.Function(); - should(imageView.image).eql('https://www.google.com/images/srpr/logo11w.png'); - should(imageView.getImage()).eql('https://www.google.com/images/srpr/logo11w.png'); - imageView.image = 'path/to/logo.png'; - should(imageView.image).eql('path/to/logo.png'); - should(imageView.getImage()).eql('path/to/logo.png'); - }); - - // FIXME Android and iOS don't fire the 'load' event! Seems liek android only fires load if image isn't in cache - it.androidAndIosBroken('image (local path)', function (finish) { - const imageView = Ti.UI.createImageView(); - imageView.addEventListener('load', function () { - try { - should(imageView.image).be.a.String(); - should(imageView.image).eql(Ti.Filesystem.resourcesDirectory + 'Logo.png'); - finish(); - } catch (err) { - finish(err); - } + describe('.image', () => { + it('has accessors', () => { + const imageView = Ti.UI.createImageView({}); + should(imageView).have.accessors('image'); }); - imageView.image = Ti.Filesystem.resourcesDirectory + 'Logo.png'; - }); - // FIXME Android and iOS don't fire the 'load' event! Seems liek android only fires load if image isn't in cache - it.androidAndIosBroken('image (local path with separator)', function (finish) { - const imageView = Ti.UI.createImageView(); - imageView.addEventListener('load', function () { - try { - should(imageView.image).be.a.String(); - should(imageView.image).eql(Ti.Filesystem.resourcesDirectory + Ti.Filesystem.separator + 'Logo.png'); + it('with an URL', () => { + const imageView = Ti.UI.createImageView({ + image: 'https://www.google.com/images/srpr/logo11w.png' + }); + should(imageView.image).be.a.String(); + should(imageView.image).eql('https://www.google.com/images/srpr/logo11w.png'); + imageView.image = 'path/to/logo.png'; + should(imageView.image).eql('path/to/logo.png'); + }); + + // FIXME Android and iOS don't fire the 'load' event! Seems like android only fires load if image isn't in cache + it.androidAndIosBroken('with a local file path', finish => { + const imageView = Ti.UI.createImageView(); + imageView.addEventListener('load', function () { + try { + should(imageView.image).be.a.String(); + should(imageView.image).eql(Ti.Filesystem.resourcesDirectory + 'Logo.png'); + } catch (err) { + return finish(err); + } finish(); - } catch (err) { - finish(err); - } - }); - // Try appending separator - // It's not quite clear if we need separator, but people often do this - imageView.image = Ti.Filesystem.resourcesDirectory + Ti.Filesystem.separator + 'Logo.png'; - }); - - // FIXME Android and iOS don't fire the 'load' event! Seems liek android only fires load if image isn't in cache - it.androidAndIosBroken('image (local path with /)', function (finish) { - const imageView = Ti.UI.createImageView(); - imageView.addEventListener('load', function () { - try { - should(imageView.image).be.a.String(); - should(imageView.image).eql(Ti.Filesystem.resourcesDirectory + '/Logo.png'); + }); + imageView.image = Ti.Filesystem.resourcesDirectory + 'Logo.png'; + }); + + // FIXME Android and iOS don't fire the 'load' event! Seems like android only fires load if image isn't in cache + it.androidAndIosBroken('with a local path with separator', finish => { + const imageView = Ti.UI.createImageView(); + imageView.addEventListener('load', function () { + try { + should(imageView.image).be.a.String(); + should(imageView.image).eql(Ti.Filesystem.resourcesDirectory + Ti.Filesystem.separator + 'Logo.png'); + } catch (err) { + return finish(err); + } finish(); - } catch (err) { - finish(err); - } + }); + // Try appending separator + // It's not quite clear if we need separator, but people often do this + imageView.image = Ti.Filesystem.resourcesDirectory + Ti.Filesystem.separator + 'Logo.png'; + }); + + // FIXME Android and iOS don't fire the 'load' event! Seems like android only fires load if image isn't in cache + it.androidAndIosBroken('with local path with /', finish => { + const imageView = Ti.UI.createImageView(); + imageView.addEventListener('load', function () { + try { + should(imageView.image).be.a.String(); + should(imageView.image).eql(Ti.Filesystem.resourcesDirectory + '/Logo.png'); + } catch (err) { + return finish(err); + } + finish(); + }); + // Try appending '/' for the separator + // Technically this is not right on Windows, but people often do this + imageView.image = Ti.Filesystem.resourcesDirectory + '/Logo.png'; + }); + + // FIXME Android and iOS don't fire the 'load' event! Seems like android only fires load if image isn't in cache + it.androidAndIosBroken('with Ti.Filesystem.File.nativePath value', finish => { + const fromFile = Ti.Filesystem.getFile(Ti.Filesystem.resourcesDirectory, 'Logo.png'); + const imageView = Ti.UI.createImageView(); + imageView.addEventListener('load', function () { + try { + should(imageView.image).be.a.String(); + should(imageView.image).eql(Ti.Filesystem.resourcesDirectory + 'Logo.png'); + } catch (err) { + return finish(err); + } + finish(); + }); + imageView.image = fromFile.nativePath; + }); + + it.windows('with ms-appx:// URL', finish => { + const imageView = Ti.UI.createImageView(); + imageView.addEventListener('load', function () { + try { + should(imageView.image).be.a.String(); + should(imageView.image).eql('ms-appx:///Logo.png'); + } catch (err) { + return finish(err); + } + finish(); + }); + imageView.image = 'ms-appx:///Logo.png'; + }); + + it.windows('with ms-appdata:// URL', finish => { + const fromFile = Ti.Filesystem.getFile(Ti.Filesystem.resourcesDirectory, 'Logo.png'); + const toFile = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory + 'TIMOB-20609.png'); + toFile.write(fromFile.read()); + + const imageView = Ti.UI.createImageView(); + imageView.addEventListener('load', function () { + try { + should(imageView.image).be.a.String(); + should(imageView.image).eql('ms-appdata:///local/TIMOB-20609.png'); + } catch (err) { + return finish(err); + } finally { + toFile.deleteFile(); + } + finish(); + }); + + imageView.image = 'ms-appdata:///local/TIMOB-20609.png'; }); - // Try appending '/' for the separator - // Technically this is not right on Windows, but people often do this - imageView.image = Ti.Filesystem.resourcesDirectory + '/Logo.png'; - }); - // FIXME Android and iOS don't fire the 'load' event! Seems liek android only fires load if image isn't in cache - it.androidAndIosBroken('image (nativePath)', function (finish) { - var fromFile = Ti.Filesystem.getFile(Ti.Filesystem.resourcesDirectory, 'Logo.png'); - var imageView = Ti.UI.createImageView(); - imageView.addEventListener('load', function () { - try { - should(imageView.image).be.a.String(); - should(imageView.image).eql(Ti.Filesystem.resourcesDirectory + 'Logo.png'); + // Windows: TIMOB-24985 + // FIXME Android and iOS don't fire the 'load' event! Seems like android only fires load if image isn't in cache + it.allBroken('with Ti.Fielsystem.File', finish => { + const fromFile = Ti.Filesystem.getFile(Ti.Filesystem.resourcesDirectory, 'Logo.png'); + + const imageView = Ti.UI.createImageView(); + imageView.addEventListener('load', function () { + try { + should(imageView.image).be.an.Object(); + should(imageView.image).eql(fromFile); + } catch (err) { + return finish(err); + } finish(); - } catch (err) { - finish(err); - } - }); - imageView.image = fromFile.nativePath; - }); + }); - it.windows('image (ms-appx)', function (finish) { - var imageView = Ti.UI.createImageView(); - imageView.addEventListener('load', function () { - try { - should(imageView.image).be.a.String(); - should(imageView.image).eql('ms-appx:///Logo.png'); + imageView.image = fromFile; + }); + + // Windows: TIMOB-24985 + // FIXME Android and iOS don't fire the 'load' event! Seems like android only fires load if image isn't in cache + it.allBroken('with Ti.Blob', finish => { + const fromFile = Ti.Filesystem.getFile(Ti.Filesystem.resourcesDirectory, 'Logo.png'); + const blob = fromFile.read(); + const imageView = Ti.UI.createImageView(); + imageView.addEventListener('load', function () { + try { + should(imageView.image).be.an.Object(); + should(imageView.toBlob()).eql(blob); + } catch (err) { + return finish(err); + } finish(); - } catch (err) { - finish(err); - } + }); + + imageView.image = blob; }); - imageView.image = 'ms-appx:///Logo.png'; - }); - it.windows('image (ms-appdata)', function (finish) { - var fromFile = Ti.Filesystem.getFile(Ti.Filesystem.resourcesDirectory, 'Logo.png'), - toFile = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory + 'TIMOB-20609.png'), - imageView; - toFile.write(fromFile.read()); + it.windowsBroken('with redirected URL and autorotate set to true', function (finish) { + this.slow(8000); + this.timeout(10000); - imageView = Ti.UI.createImageView(); - imageView.addEventListener('load', function () { - try { - should(imageView.image).be.a.String(); - should(imageView.image).eql('ms-appdata:///local/TIMOB-20609.png'); - finish(); - } catch (err) { - finish(err); - } finally { - toFile.deleteFile(); - } + win = Ti.UI.createWindow(); + const imageView = Ti.UI.createImageView({ + image: 'http://raw.githubusercontent.com/recurser/exif-orientation-examples/master/Portrait_3.jpg', + autorotate: true + }); + imageView.addEventListener('load', () => finish()); + win.add(imageView); + win.open(); }); - imageView.image = 'ms-appdata:///local/TIMOB-20609.png'; - }); - - // Windows: TIMOB-24985 - // FIXME Android and iOS don't fire the 'load' event! Seems liek android only fires load if image isn't in cache - it.allBroken('image (File)', function (finish) { - var fromFile = Ti.Filesystem.getFile(Ti.Filesystem.resourcesDirectory, 'Logo.png'); + // On Android, paths are relative to JS file. + // On iOS and Windows, paths are relative to app's "Resources" directory. + // The below works on all platforms because this JS file is in the "Resources" directory. + it('with root-relative path', function (finish) { + this.slow(8000); + this.timeout(10000); - var imageView = Ti.UI.createImageView(); - imageView.addEventListener('load', function () { - try { - should(imageView.image).be.an.Object(); - should(imageView.image).eql(fromFile); - finish(); - } catch (err) { - finish(err); - } + win = Ti.UI.createWindow(); + const imageView = Ti.UI.createImageView({ + autorotate: true + }); + imageView.addEventListener('load', () => finish()); + win.add(imageView); + imageView.image = 'Logo.png'; + win.open(); }); - imageView.image = fromFile; - }); + it('with image from folder', function (finish) { + this.timeout(10000); - // Windows: TIMOB-24985 - // FIXME Android and iOS don't fire the 'load' event! Seems liek android only fires load if image isn't in cache - it.allBroken('image (Blob)', function (finish) { - var fromFile = Ti.Filesystem.getFile(Ti.Filesystem.resourcesDirectory, 'Logo.png'), - blob = fromFile.read(); - var imageView = Ti.UI.createImageView(); - imageView.addEventListener('load', function () { - try { - should(imageView.image).be.an.Object(); - should(imageView.toBlob()).eql(blob); - finish(); - } catch (err) { - finish(err); - } + let loadCount = 0; + win = Ti.UI.createWindow(); + const imageView = Ti.UI.createImageView({ + image: 'Logo.png' + }); + imageView.addEventListener('load', function () { + loadCount++; + if (loadCount > 1) { + finish(); + } else { + imageView.image = '/image folder/Logo.png'; + } + }); + win.add(imageView); + win.open(); }); - imageView.image = blob; + it('fires error event for URL pointing at resource that does not exist', function (finish) { + if (OS_IOS) { + this.timeout(21000); // default timeout of underlying request is 20 seconds, so let's wait one extra + } + win = Ti.UI.createWindow(); + + const img = Ti.UI.createImageView({}); + img.addEventListener('error', () => finish()); + img.image = 'https://invalid.host.com/image.jpg'; + win.add(img); + win.open(); + }); }); + // TODO: Combine all tests for 'images' property into one suite // TODO Make this test cross-platform. We're using ms-appx urls here it.windows('images', function (finish) { var imageView, @@ -337,47 +405,12 @@ describe('Titanium.UI.ImageView', function () { win.open(); }); - it.windowsBroken('.image (URL-redirect-autorotate)', function (finish) { - this.slow(8000); - this.timeout(10000); - - win = Ti.UI.createWindow(); - let imageView = Ti.UI.createImageView({ - image: 'http://raw.githubusercontent.com/recurser/exif-orientation-examples/master/Portrait_3.jpg', - autorotate: true - }); - imageView.addEventListener('load', function () { - finish(); - }); - win.add(imageView); - win.open(); - }); - - // On Android, paths are relative to JS file. - // On iOS and Windows, paths are relative to app's "Resources" directory. - // The below works on all platforms because this JS file is in the "Resources" directory. - it('.image (root-relative-path)', function (finish) { - this.slow(8000); - this.timeout(10000); - - win = Ti.UI.createWindow(); - let imageView = Ti.UI.createImageView({ - autorotate: true - }); - imageView.addEventListener('load', function () { - finish(); - }); - win.add(imageView); - imageView.image = 'Logo.png'; - win.open(); - }); - it('should handle file URLs from applicationDataDirectory - TIMOB-18262', function (finish) { - var imageView = Ti.UI.createImageView({ + const imageView = Ti.UI.createImageView({ top: 10 }); - var icon = Ti.Filesystem.getFile(Ti.Filesystem.resourcesDirectory, 'Logo.png'); - var dest = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory, 'Logo.png'); + const icon = Ti.Filesystem.getFile(Ti.Filesystem.resourcesDirectory, 'Logo.png'); + const dest = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory, 'Logo.png'); should(icon.exists()).be.true(); dest.write(icon.read()); @@ -390,10 +423,10 @@ describe('Titanium.UI.ImageView', function () { imageView.addEventListener('load', function (e) { try { should(e.state).eql('images'); // Windows doesn't set this property! - finish(); } catch (err) { - finish(err); + return finish(err); } + finish(); }); win = Ti.UI.createWindow({ @@ -407,7 +440,7 @@ describe('Titanium.UI.ImageView', function () { }); it('should handle absolute-looking paths by resolving relative to resource dir', function (finish) { - var imageView = Ti.UI.createImageView({ + const imageView = Ti.UI.createImageView({ top: 10 }); @@ -418,10 +451,10 @@ describe('Titanium.UI.ImageView', function () { imageView.addEventListener('load', function (e) { try { should(e.state).eql('images'); // Windows doesn't set this property! - finish(); } catch (err) { - finish(err); + return finish(err); } + finish(); }); win = Ti.UI.createWindow({ @@ -433,37 +466,4 @@ describe('Titanium.UI.ImageView', function () { imageView.images = [ '/Logo.png' ]; }); - - it('Load Image from folder', function (finish) { - this.timeout(10000); - - var loadCount = 0; - win = Ti.UI.createWindow(); - let imageView = Ti.UI.createImageView({ - image: 'Logo.png' - }); - imageView.addEventListener('load', function () { - loadCount++; - if (loadCount > 1) { - finish(); - } else { - imageView.image = '/image folder/Logo.png'; - } - }); - win.add(imageView); - win.open(); - }); - - it('image error event', function (finish) { - if (OS_IOS) { - this.timeout(21000); // default timeout of underlying request is 20 seconds, so let's wait one extra - } - win = Ti.UI.createWindow(); - - const img = Ti.UI.createImageView({}); - img.addEventListener('error', () => finish()); - img.image = 'https://invalid.host.com/image.jpg'; - win.add(img); - win.open(); - }); }); diff --git a/tests/Resources/ti.ui.ios.stepper.test.js b/tests/Resources/ti.ui.ios.stepper.test.js new file mode 100644 index 00000000000..07209b05bd8 --- /dev/null +++ b/tests/Resources/ti.ui.ios.stepper.test.js @@ -0,0 +1,54 @@ +/* + * Appcelerator Titanium Mobile + * Copyright (c) 2020-Present by Appcelerator, Inc. All Rights Reserved. + * Licensed under the terms of the Apache Public License + * Please see the LICENSE included with this distribution for details. + */ +/* eslint-env mocha */ +/* eslint no-unused-expressions: "off" */ +'use strict'; +const should = require('./utilities/assertions'); + +describe.ios('Titanium.UI.iOS', () => { + it('#createStepper() is a Function', () => { + should(Ti.UI.iOS.createStepper).not.be.undefined(); + should(Ti.UI.iOS.createStepper).be.a.Function(); + }); +}); + +describe.ios('Titanium.UI.iOS.Stepper', () => { + it('.apiName', () => { + const stepper = Ti.UI.iOS.createStepper({ + steps: 3, + maximum: 30, + minimum: 0, + value: 20 + }); + should(stepper).have.readOnlyProperty('apiName').which.is.a.String(); + should(stepper.apiName).be.eql('Ti.UI.iOS.Stepper'); + }); + + describe('.value', () => { + it('is a Number', () => { + const stepper = Ti.UI.iOS.createStepper({ + steps: 3, + maximum: 30, + minimum: 0, + value: 20 + }); + should(stepper.value).be.eql(20); + stepper.value = 30; + should(stepper.value).be.eql(30); + }); + + it('has accessors', () => { + const stepper = Ti.UI.iOS.createStepper({ + steps: 3, + maximum: 30, + minimum: 0, + value: 20 + }); + should(stepper).have.accessors('value'); + }); + }); +}); diff --git a/tests/Resources/ti.ui.ios.tabbedbar.test.js b/tests/Resources/ti.ui.ios.tabbedbar.test.js index 73e83125f47..290114414cd 100644 --- a/tests/Resources/ti.ui.ios.tabbedbar.test.js +++ b/tests/Resources/ti.ui.ios.tabbedbar.test.js @@ -9,37 +9,49 @@ 'use strict'; const should = require('./utilities/assertions'); -describe.ios('Titanium.UI.iOS', function () { - it('#createTabbedBar()', function () { +describe.ios('Titanium.UI.iOS', () => { + it('#createTabbedBar() is a Function', () => { should(Ti.UI.iOS.createTabbedBar).not.be.undefined(); should(Ti.UI.iOS.createTabbedBar).be.a.Function(); }); }); -describe.ios('Titanium.UI.iOS.TabbedBar', function () { +describe.ios('Titanium.UI.iOS.TabbedBar', () => { + describe('.labels', () => { + let tabbedBar; + beforeEach(() => { + tabbedBar = Ti.UI.iOS.createTabbedBar({ + labels: [ 'One', 'Two', 'Three' ], + }); + }); + + it('is an Array', () => { + should(tabbedBar.labels).be.an.Array(); + should(tabbedBar.labels.length).be.eql(3); + }); - it('#labels', function () { - const tabbedBar = Ti.UI.iOS.createTabbedBar({ - labels: [ 'One', 'Two', 'Three' ], + it('can be assigned an Array', () => { + tabbedBar.labels = [ 'Four', 'Five' ]; + should(tabbedBar.labels.length).be.eql(2); }); - should(tabbedBar.labels).be.an.Array(); - should(tabbedBar.getLabels).be.a.Function(); - should(tabbedBar.labels.length).be.eql(3); - should(tabbedBar.getLabels().length).eql(3); - tabbedBar.labels = [ 'Four', 'Five' ]; - should(tabbedBar.labels.length).be.eql(2); }); - it('#index', function () { - const tabbedBar = Ti.UI.iOS.createTabbedBar({ - labels: [ 'One', 'Two', 'Three' ], - index: 1 + it('.index', () => { + let tabbedBar; + beforeEach(() => { + tabbedBar = Ti.UI.iOS.createTabbedBar({ + labels: [ 'One', 'Two', 'Three' ], + index: 1 + }); + }); + + it('is a Number', () => { + should(tabbedBar.index).be.a.Number(); + }); + + it('can be assigned a Number', () => { + tabbedBar.index = 2; + should(tabbedBar.index).be.eql(2); }); - should(tabbedBar.index).be.a.Number(); - should(tabbedBar.getIndex).be.a.Function(); - should(tabbedBar.getIndex()).be.eql(1); - should(tabbedBar.index).eql(1); - tabbedBar.index = 2; - should(tabbedBar.index).be.eql(2); }); }); diff --git a/tests/Resources/ti.ui.ios.test.js b/tests/Resources/ti.ui.ios.test.js index 1360e2be0c8..5437d8d80ea 100644 --- a/tests/Resources/ti.ui.ios.test.js +++ b/tests/Resources/ti.ui.ios.test.js @@ -130,21 +130,6 @@ describe.ios('Titanium.UI.iOS', function () { should(Ti.UI.iOS.INJECTION_TIME_DOCUMENT_END).be.a.Number(); }); - it('#createStepper()', function () { - var stepper; - should(Ti.UI.iOS.createStepper).not.be.undefined(); - should(Ti.UI.iOS.createStepper).be.a.Function(); - stepper = Ti.UI.iOS.createStepper({ - steps: 3, - maximum: 30, - minimum: 0, - value: 20 - }); - should(stepper.value).be.eql(20); - stepper.setValue(30); - should(stepper.value).be.eql(30); - }); - it('#systemImage()', function () { if (OS_VERSION_MAJOR >= 13) { should(Ti.UI.iOS.systemImage).not.be.undefined(); diff --git a/tests/Resources/ti.ui.label.test.js b/tests/Resources/ti.ui.label.test.js index 22c7ea54724..700e7399010 100644 --- a/tests/Resources/ti.ui.label.test.js +++ b/tests/Resources/ti.ui.label.test.js @@ -7,8 +7,8 @@ /* eslint-env mocha */ /* eslint no-unused-expressions: "off" */ 'use strict'; -var should = require('./utilities/assertions'), - utilities = require('./utilities/utilities'); +const should = require('./utilities/assertions'); +const utilities = require('./utilities/utilities'); describe('Titanium.UI.Label', function () { let win; @@ -26,7 +26,7 @@ describe('Titanium.UI.Label', function () { } }); - it('apiName', function () { + it('.apiName', () => { const label = Ti.UI.createLabel({ text: 'this is some text' }); @@ -34,133 +34,175 @@ describe('Titanium.UI.Label', function () { should(label.apiName).be.eql('Ti.UI.Label'); }); - it('maxLines', function () { - const label = Ti.UI.createLabel({ - text: 'This is a label with propably more than three lines of text. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.', - maxLines: 2 + describe('.maxLines', () => { + it('is a Number', () => { + const label = Ti.UI.createLabel({ + text: 'This is a label with propably more than three lines of text. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.', + maxLines: 2 + }); + should(label.maxLines).be.a.Number(); + should(label.maxLines).eql(2); + label.maxLines = 1; + should(label.maxLines).eql(1); }); - should(label.maxLines).be.a.Number(); - should(label.getMaxLines).be.a.Function(); // Windows gives undefined - should(label.maxLines).eql(2); - should(label.getMaxLines()).eql(2); - label.maxLines = 1; - should(label.maxLines).eql(1); - should(label.getMaxLines()).eql(1); - }); - // Tests if "maxLines" correctly truncates strings with '\n' characters. - it('maxLines-newline', function (finish) { - this.slow(1000); - this.timeout(5000); - - win = Ti.UI.createWindow({ - layout: 'vertical', + it('has accessors', () => { + const label = Ti.UI.createLabel({ + text: 'This is a label with propably more than three lines of text. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.', + maxLines: 2 + }); + should(label).have.accessors('maxLines'); }); - const label1 = Ti.UI.createLabel({ - // This label is 1 line tall. - text: 'Line 1', + + // Tests if "maxLines" correctly truncates strings with '\n' characters. + it('truncates strings with newline characters', function (finish) { + this.slow(1000); + this.timeout(5000); + + win = Ti.UI.createWindow({ + layout: 'vertical', + }); + const label1 = Ti.UI.createLabel({ + // This label is 1 line tall. + text: 'Line 1', + }); + win.add(label1); + const label2 = Ti.UI.createLabel({ + // The label should be 1 line tall since 'maxLines' is set to 1. + text: 'Line 1\nLine2', + maxLines: 1, + }); + win.add(label2); + win.addEventListener('postlayout', function listener() { + win.removeEventListener('postlayout', listener); + + try { + // Both labels are expected to be 1 line tall. + should(label1.size.height).be.approximately(label2.size.height, 1); + } catch (err) { + return finish(err); + } + finish(); + }); + win.open(); }); - win.add(label1); - const label2 = Ti.UI.createLabel({ - // The label should be 1 line tall since 'maxLines' is set to 1. - text: 'Line 1\nLine2', - maxLines: 1, + }); + + describe('.text', () => { + it('is a String', () => { + const label = Ti.UI.createLabel({ + text: 'this is some text' + }); + should(label.text).be.a.String(); + should(label.text).eql('this is some text'); + label.text = 'other text'; + should(label.text).eql('other text'); }); - win.add(label2); - win.addEventListener('postlayout', function listener() { - win.removeEventListener('postlayout', listener); - // Both labels are expected to be 1 line tall. - should(label1.size.height).be.approximately(label2.size.height, 1); - finish(); + it('has accessors', () => { + const label = Ti.UI.createLabel({ + text: 'this is some text' + }); + should(label).have.accessors('text'); }); - win.open(); }); - it('text', function () { - const label = Ti.UI.createLabel({ - text: 'this is some text' + describe('.textid', () => { + it('is a String', () => { + const label = Ti.UI.createLabel({ + textid: 'this_is_my_key' + }); + should(label.textid).be.a.String(); + should(label.textid).eql('this_is_my_key'); + should(label.text).eql('this is my value'); + label.textid = 'other text'; + should(label.textid).eql('other text'); + should(label.text).eql('this is my value'); // Windows issue }); - should(label.text).be.a.String(); - should(label.getText).be.a.Function(); - should(label.text).eql('this is some text'); - should(label.getText()).eql('this is some text'); - label.text = 'other text'; - should(label.text).eql('other text'); - should(label.getText()).eql('other text'); - }); - it('textid', function () { - const label = Ti.UI.createLabel({ - textid: 'this_is_my_key' + it('has accessors', () => { + const label = Ti.UI.createLabel({ + textid: 'this_is_my_key' + }); + should(label).have.accessors('textid'); }); - should(label.textid).be.a.String(); - should(label.getTextid).be.a.Function(); - should(label.textid).eql('this_is_my_key'); - should(label.getTextid()).eql('this_is_my_key'); - should(label.text).eql('this is my value'); - label.textid = 'other text'; - should(label.textid).eql('other text'); - should(label.getTextid()).eql('other text'); - should(label.text).eql('this is my value'); // Windows issue }); - it('textAlign', function () { - const label = Ti.UI.createLabel({ - text: 'this is some text', - textAlign: Ti.UI.TEXT_ALIGNMENT_CENTER + describe('.textAlign', () => { + it('is a String/Number', () => { + const label = Ti.UI.createLabel({ + text: 'this is some text', + textAlign: Ti.UI.TEXT_ALIGNMENT_CENTER + }); + if (utilities.isAndroid()) { + should(label.textAlign).be.a.String(); + } else { + should(label.textAlign).be.a.Number(); + } + should(label.textAlign).eql(Ti.UI.TEXT_ALIGNMENT_CENTER); + label.textAlign = Ti.UI.TEXT_ALIGNMENT_RIGHT; + should(label.textAlign).eql(Ti.UI.TEXT_ALIGNMENT_RIGHT); + + // TIMOB-3408 + if (utilities.isIOS()) { + label.textAlign = Ti.UI.TEXT_ALIGNMENT_JUSTIFY; + should(label.textAlign).eql(Ti.UI.TEXT_ALIGNMENT_JUSTIFY); + } + }); + + it('has accessors', () => { + const label = Ti.UI.createLabel({ + text: 'this is some text', + textAlign: Ti.UI.TEXT_ALIGNMENT_CENTER + }); + should(label).have.accessors('textAlign'); }); - if (utilities.isAndroid()) { - should(label.textAlign).be.a.String(); - } else { - should(label.textAlign).be.a.Number(); - } - should(label.getTextAlign).be.a.Function(); - should(label.textAlign).eql(Ti.UI.TEXT_ALIGNMENT_CENTER); - should(label.getTextAlign()).eql(Ti.UI.TEXT_ALIGNMENT_CENTER); - label.textAlign = Ti.UI.TEXT_ALIGNMENT_RIGHT; - should(label.textAlign).eql(Ti.UI.TEXT_ALIGNMENT_RIGHT); - should(label.getTextAlign()).eql(Ti.UI.TEXT_ALIGNMENT_RIGHT); - - // TIMOB-3408 - if (utilities.isIOS()) { - label.textAlign = Ti.UI.TEXT_ALIGNMENT_JUSTIFY; - should(label.textAlign).eql(Ti.UI.TEXT_ALIGNMENT_JUSTIFY); - should(label.getTextAlign()).eql(Ti.UI.TEXT_ALIGNMENT_JUSTIFY); - } }); - it('verticalAlign', function () { - const label = Ti.UI.createLabel({ - text: 'this is some text', - verticalAlign: Ti.UI.TEXT_VERTICAL_ALIGNMENT_BOTTOM + describe('.verticalAlign', () => { + it('is a String/Number', () => { + const label = Ti.UI.createLabel({ + text: 'this is some text', + verticalAlign: Ti.UI.TEXT_VERTICAL_ALIGNMENT_BOTTOM + }); + if (utilities.isAndroid()) { + should(label.verticalAlign).be.a.String(); + } else { + should(label.verticalAlign).be.a.Number(); + } + should(label.verticalAlign).eql(Ti.UI.TEXT_VERTICAL_ALIGNMENT_BOTTOM); + label.verticalAlign = Ti.UI.TEXT_VERTICAL_ALIGNMENT_TOP; + should(label.verticalAlign).eql(Ti.UI.TEXT_VERTICAL_ALIGNMENT_TOP); + }); + + it('has accessors', () => { + const label = Ti.UI.createLabel({ + text: 'this is some text', + verticalAlign: Ti.UI.TEXT_VERTICAL_ALIGNMENT_BOTTOM + }); + should(label).have.accessors('verticalAlign'); }); - if (utilities.isAndroid()) { - should(label.verticalAlign).be.a.String(); - } else { - should(label.verticalAlign).be.a.Number(); - } - should(label.getVerticalAlign).be.a.Function(); - should(label.verticalAlign).eql(Ti.UI.TEXT_VERTICAL_ALIGNMENT_BOTTOM); - should(label.getVerticalAlign()).eql(Ti.UI.TEXT_VERTICAL_ALIGNMENT_BOTTOM); - label.verticalAlign = Ti.UI.TEXT_VERTICAL_ALIGNMENT_TOP; - should(label.verticalAlign).eql(Ti.UI.TEXT_VERTICAL_ALIGNMENT_TOP); - should(label.getVerticalAlign()).eql(Ti.UI.TEXT_VERTICAL_ALIGNMENT_TOP); }); - // set ellipsize in the label - // Default: Ti.UI.TEXT_ELLIPSIZE_TRUNCATE_END - it('ellipsize', function () { - const label = Ti.UI.createLabel({ - text: 'this is some text' + describe('.ellipsize', () => { + // set ellipsize in the label + // Default: Ti.UI.TEXT_ELLIPSIZE_TRUNCATE_END + it('is a Number', () => { + const label = Ti.UI.createLabel({ + text: 'this is some text' + }); + should(label.ellipsize).be.a.Number(); // Windows gives false! + should(label.ellipsize).eql(Ti.UI.TEXT_ELLIPSIZE_TRUNCATE_END); + label.ellipsize = Ti.UI.TEXT_ELLIPSIZE_TRUNCATE_MIDDLE; + should(label.ellipsize).eql(Ti.UI.TEXT_ELLIPSIZE_TRUNCATE_MIDDLE); + }); + + it('has accessors', () => { + const label = Ti.UI.createLabel({ + text: 'this is some text' + }); + should(label).have.accessors('ellipsize'); }); - should(label.ellipsize).be.a.Number(); // Windows gives false! - should(label.getEllipsize).be.a.Function(); - should(label.ellipsize).eql(Ti.UI.TEXT_ELLIPSIZE_TRUNCATE_END); - should(label.getEllipsize()).eql(Ti.UI.TEXT_ELLIPSIZE_TRUNCATE_END); - label.ellipsize = Ti.UI.TEXT_ELLIPSIZE_TRUNCATE_MIDDLE; - should(label.getEllipsize()).eql(Ti.UI.TEXT_ELLIPSIZE_TRUNCATE_MIDDLE); - should(label.ellipsize).eql(Ti.UI.TEXT_ELLIPSIZE_TRUNCATE_MIDDLE); }); // FIXME Can't rely on Ti.UI.Window.postlayout event firing because neither platform fires it for that type (only maybe bubbles up from label) @@ -251,26 +293,41 @@ describe('Titanium.UI.Label', function () { win.open(); }); - it.ios('minimumFontSize', function () { - const label = Ti.UI.createLabel({ - text: 'this is some text', - textAlign: 'left', - font: { - fontSize: 36 - }, - color: 'black', - wordWrap: false, - ellipsize: false, - minimumFontSize: 28, - height: 50 + describe.ios('.minimumFontSize', () => { + it('is a Number', () => { + const label = Ti.UI.createLabel({ + text: 'this is some text', + textAlign: 'left', + font: { + fontSize: 36 + }, + color: 'black', + wordWrap: false, + ellipsize: false, + minimumFontSize: 28, + height: 50 + }); + should(label.minimumFontSize).be.a.Number(); + should(label.minimumFontSize).eql(28); + label.minimumFontSize = 22; + should(label.minimumFontSize).eql(22); + }); + + it('has accessors', () => { + const label = Ti.UI.createLabel({ + text: 'this is some text', + textAlign: 'left', + font: { + fontSize: 36 + }, + color: 'black', + wordWrap: false, + ellipsize: false, + minimumFontSize: 28, + height: 50 + }); + should(label).have.accessors('minimumFontSize'); }); - should(label.minimumFontSize).be.a.Number(); - should(label.getMinimumFontSize).be.a.Function(); - should(label.minimumFontSize).eql(28); - should(label.getMinimumFontSize()).eql(28); - label.minimumFontSize = 22; - should(label.minimumFontSize).eql(22); - should(label.getMinimumFontSize()).eql(22); }); it('animate font color', function (finish) { diff --git a/tests/Resources/ti.ui.listview.test.js b/tests/Resources/ti.ui.listview.test.js index 7dda79c9d50..7cb21c12643 100644 --- a/tests/Resources/ti.ui.listview.test.js +++ b/tests/Resources/ti.ui.listview.test.js @@ -13,43 +13,8 @@ const utilities = require('./utilities/utilities'); const isCI = Ti.App.Properties.getBool('isCI', false); -describe('Titanium.UI.ListView', function () { - this.timeout(5000); - - let win; - afterEach(done => { // fires after every test in sub-suites too... - if (win && !win.closed) { - win.addEventListener('close', function listener () { - win.removeEventListener('close', listener); - win = null; - done(); - }); - win.close(); - } else { - win = null; - done(); - } - }); - - it.iosBroken('Ti.UI.ListView', () => { // Should this be defined? - should(Ti.UI.ListView).not.be.undefined(); - }); - - it('.apiName', () => { - const listView = Ti.UI.createListView(); - should(listView).have.readOnlyProperty('apiName').which.is.a.String(); - should(listView.apiName).be.eql('Ti.UI.ListView'); - }); - - it('.canScroll', () => { - const listView = Ti.UI.createListView({ canScroll: false }); - should(listView.canScroll).be.be.false(); - listView.canScroll = !listView.canScroll; - should(listView.canScroll).be.be.true(); - }); - - it('createListView', () => { - +describe('Titanium.UI', () => { + it('#createListView()', () => { // Validate createListView() factory. should(Ti.UI.createListView).not.be.undefined(); should(Ti.UI.createListView).be.a.Function(); @@ -99,434 +64,541 @@ describe('Titanium.UI.ListView', function () { // Validate listView section count. should(listView.sectionCount).be.eql(2); }); +}); - // - // Making sure setting header & footer doesn't throw exception - // - it('Basic ListSection header and footer', finish => { - const listView = Ti.UI.createListView(); - const ukHeaderView = Ti.UI.createView({ - backgroundColor: 'black', - height: 42 - }); - const ukFooterView = Ti.UI.createView({ - backgroundColor: 'black', - height: 42 +describe('Titanium.UI.ListView', function () { + this.timeout(5000); + + let win; + afterEach(done => { // fires after every test in sub-suites too... + if (win && !win.closed) { + win.addEventListener('close', function listener () { + win.removeEventListener('close', listener); + win = null; + done(); + }); + win.close(); + } else { + win = null; + done(); + } + }); + + it.iosBroken('nsamespace exists', () => { // Should this be defined? + should(Ti.UI.ListView).not.be.undefined(); + }); + + describe('properties', () => { + + describe.ios('.allowsMultipleSelectionDuringEditing', () => { + let list; + + beforeEach(() => { + list = Ti.UI.createListView({ + allowsMultipleSelectionDuringEditing: true, + sections: [ Ti.UI.createListSection({ + items: [ { + properties: { + title: 'My Title 1', + } + }, { + properties: { + title: 'My Title 2', + } + } ] + }) ] + }); + }); + + it('is a Boolean', () => { + should(list).have.a.property('allowsMultipleSelectionDuringEditing').which.is.a.Boolean(); + }); + + it('equals value passed to factory method', () => { + should(list.allowsMultipleSelectionDuringEditing).be.true(); + }); + + it('can be assigned a Boolean value', () => { + list.allowsMultipleSelectionDuringEditing = false; + should(list.allowsMultipleSelectionDuringEditing).be.false(); + }); + + it('has accessors', () => { + should(list).have.accessors('allowsMultipleSelectionDuringEditing'); + }); + + it('lifecycle', function (finish) { + win = Ti.UI.createWindow(); + win.addEventListener('open', function () { + try { + should(list.allowsMultipleSelectionDuringEditing).be.true(); + + list.allowsMultipleSelectionDuringEditing = false; + should(list.allowsMultipleSelectionDuringEditing).be.false(); + } catch (err) { + return finish(err); + } + finish(); + }); + win.add(list); + win.open(); + }); }); - const ukSection = Ti.UI.createListSection({ - headerView: ukHeaderView, - footerView: ukFooterView + + describe('.apiName', () => { + it('is a read-only String', () => { + const listView = Ti.UI.createListView(); + should(listView).have.readOnlyProperty('apiName').which.is.a.String(); + }); + + it('equals \'Ti.UI.ListView\'', () => { + const listView = Ti.UI.createListView(); + should(listView.apiName).be.eql('Ti.UI.ListView'); + }); }); - const usSection = Ti.UI.createListSection({ - headerTitle: 'English US Header', - footerTitle: 'English US Footer' + + it('.canScroll', () => { + const listView = Ti.UI.createListView({ canScroll: false }); + should(listView.canScroll).be.be.false(); + listView.canScroll = !listView.canScroll; + should(listView.canScroll).be.be.true(); }); - win = Ti.UI.createWindow({ backgroundColor: 'green' }); + it.android('.fastScroll', () => { + const listView = Ti.UI.createListView(); + should(listView.fastScroll).be.false(); + }); - ukHeaderView.add(Ti.UI.createLabel({ text: 'English UK Header', color: 'white' })); - ukFooterView.add(Ti.UI.createLabel({ text: 'English UK Footer', color: 'white' })); + /** + * Validate ListSection header and footer properties. + */ + it('.headerView', finish => { + const listView = Ti.UI.createListView(); + const sections = []; + const fruitSection = Ti.UI.createListSection({ headerTitle: 'Fruits' }); + const fruitDataSet = [ + { properties: { title: 'Apple' } }, + { properties: { title: 'Banana' } }, + ]; + const vegSection = Ti.UI.createListSection({ headerTitle: 'Vegetables' }); + const vegDataSet = [ + { properties: { title: 'Carrots' } }, + { properties: { title: 'Potatoes' } }, + ]; + + win = Ti.UI.createWindow({ backgroundColor: 'green' }); + + // Set section items. + fruitSection.setItems(fruitDataSet); + vegSection.setItems(vegDataSet); + + // Set header and footer views for first section. + fruitSection.headerView = Ti.UI.createView({ backgroundColor: 'black', height: 42 }); + fruitSection.footerView = Ti.UI.createView({ backgroundColor: 'black', height: 42 }); + sections.push(fruitSection); + + // Set header and footer views for second section. + vegSection.headerView = Ti.UI.createView({ backgroundColor: 'black', height: 42 }); + vegSection.footerView = Ti.UI.createView({ backgroundColor: 'black', height: 42 }); + sections.push(vegSection); + + // NOTE: Since a headerTitle has already been defined, headerTitle will have priority. + + // Set ListView sections. + listView.sections = sections; + + win.addEventListener('open', () => { + try { - ukSection.setItems([ - { properties: { title: 'Lift', color: 'black' } }, - { properties: { title: 'Lorry', color: 'black' } }, - { properties: { title: 'Motorway', color: 'black' } } - ]); - listView.appendSection(ukSection); + // Validate section count. + should(listView.sectionCount).be.eql(2); - usSection.setItems([ - { properties: { title: 'Elevator', color: 'black' } }, - { properties: { title: 'Truck', color: 'black' } }, - { properties: { title: 'Freeway', color: 'black' } } - ]); - listView.appendSection(usSection); + // Validate first section count. + should(listView.sections[0].items.length).be.eql(2); - win.addEventListener('open', () => { - try { + // Validate first section items. + should(listView.sections[0].items[0].properties.title).be.eql('Apple'); + should(listView.sections[0].items[1].properties.title).be.eql('Banana'); - // Validate section count. - should(listView.sectionCount).be.eql(2); + // Validate second section count. + should(listView.sections[1].items.length).be.eql(2); - // Validate first section item count. - should(listView.sections[0].items.length).be.eql(3); + // Validate second section items. + should(listView.sections[1].items[0].properties.title).be.eql('Carrots'); + should(listView.sections[1].items[1].properties.title).be.eql('Potatoes'); + } catch (err) { + return finish(err); + } + finish(); + }); - // Validate first section items. - should(listView.sections[0].items[0].properties.title).be.eql('Lift'); - should(listView.sections[0].items[1].properties.title).be.eql('Lorry'); - should(listView.sections[0].items[2].properties.title).be.eql('Motorway'); + win.add(listView); + win.open(); + }); - // Validate second sectiion count. - should(listView.sections[1].items.length).be.eql(3); + it.ios('.selectedItems', function (finish) { + const list = Ti.UI.createListView({ + sections: [ Ti.UI.createListSection({ + items: [ { + properties: { + title: 'My Title 1', + } + }, { + properties: { + title: 'My Title 2', + } + } ] + }) ] + }); - // Validate second section items. - should(listView.sections[1].items[0].properties.title).be.eql('Elevator'); - should(listView.sections[1].items[1].properties.title).be.eql('Truck'); - should(listView.sections[1].items[2].properties.title).be.eql('Freeway'); - } catch (err) { - return finish(err); - } - finish(); + win = Ti.UI.createWindow(); + win.addEventListener('open', function () { + try { + list.selectItem(0, 1); + should(list.selectedItems[0].section).be.eql(list.sections[0]); + should(list.selectedItems[0].sectionIndex).be.eql(0); + should(list.selectedItems[0].itemIndex).be.eql(1); + list.selectItem(0, 0); + should(list.selectedItems[0].section).be.eql(list.sections[0]); + should(list.selectedItems[0].sectionIndex).be.eql(0); + should(list.selectedItems[0].itemIndex).be.eql(0); + } catch (err) { + return finish(err); + } + finish(); + }); + win.add(list); + win.open(); }); - - win.add(listView); - win.open(); }); - /** - * Validate ListSection header and footer properties. - */ - it('headerView', finish => { - const listView = Ti.UI.createListView(); - const sections = []; - const fruitSection = Ti.UI.createListSection({ headerTitle: 'Fruits' }); - const fruitDataSet = [ - { properties: { title: 'Apple' } }, - { properties: { title: 'Banana' } }, - ]; - const vegSection = Ti.UI.createListSection({ headerTitle: 'Vegetables' }); - const vegDataSet = [ - { properties: { title: 'Carrots' } }, - { properties: { title: 'Potatoes' } }, - ]; + describe('methods', () => { + it('#appendSection()', finish => { + const listView = Ti.UI.createListView(); - win = Ti.UI.createWindow({ backgroundColor: 'green' }); + win = Ti.UI.createWindow({ backgroundColor: 'green' }); - // Set section items. - fruitSection.setItems(fruitDataSet); - vegSection.setItems(vegDataSet); + const fruitSection = Ti.UI.createListSection({ headerTitle: 'Fruits' }); + fruitSection.setItems([ + { properties: { title: 'Apple' } }, + { properties: { title: 'Banana' } }, + ]); - // Set header and footer views for first section. - fruitSection.headerView = Ti.UI.createView({ backgroundColor: 'black', height: 42 }); - fruitSection.footerView = Ti.UI.createView({ backgroundColor: 'black', height: 42 }); - sections.push(fruitSection); + const vegSection = Ti.UI.createListSection({ headerTitle: 'Vegetables' }); + vegSection.setItems([ + { properties: { title: 'Carrots' } }, + { properties: { title: 'Potatoes' } }, + ]); - // Set header and footer views for second section. - vegSection.headerView = Ti.UI.createView({ backgroundColor: 'black', height: 42 }); - vegSection.footerView = Ti.UI.createView({ backgroundColor: 'black', height: 42 }); - sections.push(vegSection); + const fishSection = Ti.UI.createListSection({ headerTitle: 'Fish' }); + fishSection.setItems([ + { properties: { title: 'Cod' } }, + { properties: { title: 'Haddock' } }, + ]); - // NOTE: Since a headerTitle has already been defined, headerTitle will have priority. + listView.sections = [ fruitSection ]; - // Set ListView sections. - listView.sections = sections; + win.addEventListener('open', () => { + try { - win.addEventListener('open', () => { - try { + // Validate section count. + should(listView.sectionCount).be.eql(1); - // Validate section count. - should(listView.sectionCount).be.eql(2); + // Validate first section item count. + should(listView.sections[0].items.length).be.eql(2); - // Validate first section count. - should(listView.sections[0].items.length).be.eql(2); + // Validate first section items. + should(listView.sections[0].items[0].properties.title).be.eql('Apple'); + should(listView.sections[0].items[1].properties.title).be.eql('Banana'); - // Validate first section items. - should(listView.sections[0].items[0].properties.title).be.eql('Apple'); - should(listView.sections[0].items[1].properties.title).be.eql('Banana'); + // Append second section. + listView.appendSection(vegSection); - // Validate second section count. - should(listView.sections[1].items.length).be.eql(2); + // Validate new section count. + should(listView.sectionCount).be.eql(2); - // Validate second section items. - should(listView.sections[1].items[0].properties.title).be.eql('Carrots'); - should(listView.sections[1].items[1].properties.title).be.eql('Potatoes'); - } catch (err) { - return finish(err); - } - finish(); - }); + // Validate second section item count. + should(listView.sections[1].items.length).be.eql(2); - win.add(listView); - win.open(); - }); + // Validate second section items. + should(listView.sections[1].items[0].properties.title).be.eql('Carrots'); + should(listView.sections[1].items[1].properties.title).be.eql('Potatoes'); - /** - * Basic custom template test. - */ - it('template', finish => { - const listView = Ti.UI.createListView({ - templates: { - template: { - childTemplates: [ - { - type: 'Ti.UI.ImageView', - bindId: 'pic', - properties: { - width: 50, - height: 50, - left: 0 - } - }, - { - type: 'Ti.UI.Label', - bindId: 'info', - properties: { - color: 'black', - font: { - fontSize: 20, - fontWeight: 'bold' - }, - left: 60, - top: 0, - } - }, - { - type: 'Ti.UI.Label', - bindId: 'es_info', - properties: { - color: 'gray', - font: { fontSize: 14 }, - left: 60, - top: 25, - } - } - ] + // Append last section using an array. + listView.appendSection([ fishSection ]); + + // Validate new section count. + should(listView.sectionCount).be.eql(3); + + // Validate last section item count. + should(listView.sections[2].items.length).be.eql(2); + + // Validate last section items. + should(listView.sections[2].items[0].properties.title).be.eql('Cod'); + should(listView.sections[2].items[1].properties.title).be.eql('Haddock'); + } catch (err) { + return finish(err); } - }, - defaultItemTemplate: 'template' + finish(); + }); + + win.add(listView); + win.open(); }); - const sections = []; - win = Ti.UI.createWindow({ backgroundColor: 'green' }); + it('#insertSectionAt()', finish => { + const listView = Ti.UI.createListView(); - const fruitSection = Ti.UI.createListSection({ headerTitle: 'Fruits / Frutas' }); - fruitSection.setItems([ - { info: { text: 'Apple' }, es_info: { text: 'Manzana' }, pic: { image: 'Logo.png' } }, - { info: { text: 'Banana' }, es_info: { text: 'Banana' }, pic: { image: 'Logo.png' } } - ]); - sections.push(fruitSection); + win = Ti.UI.createWindow({ backgroundColor: 'green' }); - const vegSection = Ti.UI.createListSection({ headerTitle: 'Vegetables / Verduras' }); - vegSection.setItems([ - { info: { text: 'Carrot' }, es_info: { text: 'Zanahoria' }, pic: { image: 'Logo.png' } }, - { info: { text: 'Potato' }, es_info: { text: 'Patata' }, pic: { image: 'Logo.png' } } - ]); - sections.push(vegSection); + const fruitSection = Ti.UI.createListSection({ headerTitle: 'Fruits' }); + fruitSection.setItems([ + { properties: { title: 'Apple' } }, + { properties: { title: 'Banana' } }, + ]); - const grainSection = Ti.UI.createListSection({ headerTitle: 'Grains / Granos' }); - grainSection.setItems([ - { info: { text: 'Corn' }, es_info: { text: 'Maiz' }, pic: { image: 'Logo.png' } }, - { info: { text: 'Rice' }, es_info: { text: 'Arroz' }, pic: { image: 'Logo.png' } } - ]); - sections.push(grainSection); + const vegSection = Ti.UI.createListSection({ headerTitle: 'Vegetables' }); + vegSection.setItems([ + { properties: { title: 'Carrots' } }, + { properties: { title: 'Potatoes' } }, + ]); - listView.sections = sections; + const fishSection = Ti.UI.createListSection({ headerTitle: 'Fish' }); + fishSection.setItems([ + { properties: { title: 'Cod' } }, + { properties: { title: 'Haddock' } }, + ]); - win.addEventListener('open', () => { - try { + listView.sections = [ fruitSection, fishSection ]; - // Validate section count. - should(listView.sectionCount).be.eql(3); + win.addEventListener('open', () => { + try { - // Validate first section item count. - should(listView.sections[0].items.length).be.eql(2); + // Validate section count. + should(listView.sectionCount).be.eql(2); - // Validate first section items. - should(listView.sections[0].items[0].info.text).be.eql('Apple'); - should(listView.sections[0].items[1].info.text).be.eql('Banana'); + // Validate first section item count. + should(listView.sections[0].items.length).be.eql(2); - // Validate second section item count. - should(listView.sections[1].items.length).be.eql(2); + // Validate first section items. + should(listView.sections[0].items[0].properties.title).be.eql('Apple'); + should(listView.sections[0].items[1].properties.title).be.eql('Banana'); - // Validate second section items. - should(listView.sections[1].items[0].info.text).be.eql('Carrot'); - should(listView.sections[1].items[1].info.text).be.eql('Potato'); + // Validate second section item count. + should(listView.sections[1].items.length).be.eql(2); - // Validate last section item count. - should(listView.sections[2].items.length).be.eql(2); + // Validate second section items. + should(listView.sections[1].items[0].properties.title).be.eql('Cod'); + should(listView.sections[1].items[1].properties.title).be.eql('Haddock'); - // Validate last section items. - should(listView.sections[2].items[0].info.text).be.eql('Corn'); - should(listView.sections[2].items[1].info.text).be.eql('Rice'); - } catch (err) { - return finish(err); - } - finish(); - }); + // Append new section. + listView.insertSectionAt(1, vegSection); - win.add(listView); - win.open(); - }); + // Validate new section count. + should(listView.sectionCount).be.eql(3); - /** - * Basic appendSection test. - */ - it('appendSection', finish => { - const listView = Ti.UI.createListView(); + // Validate second section item count. + should(listView.sections[1].items.length).be.eql(2); - win = Ti.UI.createWindow({ backgroundColor: 'green' }); + // Validate second section items. + should(listView.sections[1].items[0].properties.title).be.eql('Carrots'); + should(listView.sections[1].items[1].properties.title).be.eql('Potatoes'); - const fruitSection = Ti.UI.createListSection({ headerTitle: 'Fruits' }); - fruitSection.setItems([ - { properties: { title: 'Apple' } }, - { properties: { title: 'Banana' } }, - ]); + // Validate last section item count. + should(listView.sections[2].items.length).be.eql(2); - const vegSection = Ti.UI.createListSection({ headerTitle: 'Vegetables' }); - vegSection.setItems([ - { properties: { title: 'Carrots' } }, - { properties: { title: 'Potatoes' } }, - ]); + // Validate last section items. + should(listView.sections[2].items[0].properties.title).be.eql('Cod'); + should(listView.sections[2].items[1].properties.title).be.eql('Haddock'); + } catch (err) { + return finish(err); + } + finish(); + }); - const fishSection = Ti.UI.createListSection({ headerTitle: 'Fish' }); - fishSection.setItems([ - { properties: { title: 'Cod' } }, - { properties: { title: 'Haddock' } }, - ]); + win.add(listView); + win.open(); + }); - listView.sections = [ fruitSection ]; + it('#replaceSectionAt()', finish => { + const listView = Ti.UI.createListView(); - win.addEventListener('open', () => { - try { + win = Ti.UI.createWindow({ backgroundColor: 'green' }); - // Validate section count. - should(listView.sectionCount).be.eql(1); + const fruitSection = Ti.UI.createListSection({ headerTitle: 'Fruits' }); + fruitSection.setItems([ + { properties: { title: 'Apple' } }, + { properties: { title: 'Banana' } }, + ]); - // Validate first section item count. - should(listView.sections[0].items.length).be.eql(2); + const vegSection = Ti.UI.createListSection({ headerTitle: 'Vegetables' }); + vegSection.setItems([ + { properties: { title: 'Carrots' } }, + { properties: { title: 'Potatoes' } }, + ]); - // Validate first section items. - should(listView.sections[0].items[0].properties.title).be.eql('Apple'); - should(listView.sections[0].items[1].properties.title).be.eql('Banana'); + const fishSection = Ti.UI.createListSection({ headerTitle: 'Fish' }); + fishSection.setItems([ + { properties: { title: 'Cod' } }, + { properties: { title: 'Haddock' } }, + ]); - // Append second section. - listView.appendSection(vegSection); + listView.sections = [ fruitSection, fishSection ]; - // Validate new section count. - should(listView.sectionCount).be.eql(2); + win.addEventListener('open', () => { + try { - // Validate second section item count. - should(listView.sections[1].items.length).be.eql(2); + // Validate section count. + should(listView.sectionCount).be.eql(2); - // Validate second section items. - should(listView.sections[1].items[0].properties.title).be.eql('Carrots'); - should(listView.sections[1].items[1].properties.title).be.eql('Potatoes'); + // Validate first section item count. + should(listView.sections[0].items.length).be.eql(2); - // Append last section using an array. - listView.appendSection([ fishSection ]); + // Validate first section items. + should(listView.sections[0].items[0].properties.title).be.eql('Apple'); + should(listView.sections[0].items[1].properties.title).be.eql('Banana'); - // Validate new section count. - should(listView.sectionCount).be.eql(3); + // Validate second section item count. + should(listView.sections[1].items.length).be.eql(2); - // Validate last section item count. - should(listView.sections[2].items.length).be.eql(2); + // Validate second section items. + should(listView.sections[1].items[0].properties.title).be.eql('Cod'); + should(listView.sections[1].items[1].properties.title).be.eql('Haddock'); - // Validate last section items. - should(listView.sections[2].items[0].properties.title).be.eql('Cod'); - should(listView.sections[2].items[1].properties.title).be.eql('Haddock'); - } catch (err) { - return finish(err); - } - finish(); - }); + // Append new section. + listView.replaceSectionAt(1, vegSection); - win.add(listView); - win.open(); - }); + // Validate section count. + should(listView.sectionCount).be.eql(2); - /** - * Basic insertSectionAt test. - */ - it('insertSectionAt', finish => { - const listView = Ti.UI.createListView(); + // Validate second section item count. + should(listView.sections[1].items.length).be.eql(2); - win = Ti.UI.createWindow({ backgroundColor: 'green' }); + // Validate second section items. + should(listView.sections[1].items[0].properties.title).be.eql('Carrots'); + should(listView.sections[1].items[1].properties.title).be.eql('Potatoes'); + } catch (err) { + return finish(err); + } + finish(); + }); - const fruitSection = Ti.UI.createListSection({ headerTitle: 'Fruits' }); - fruitSection.setItems([ - { properties: { title: 'Apple' } }, - { properties: { title: 'Banana' } }, - ]); + win.add(listView); + win.open(); + }); - const vegSection = Ti.UI.createListSection({ headerTitle: 'Vegetables' }); - vegSection.setItems([ - { properties: { title: 'Carrots' } }, - { properties: { title: 'Potatoes' } }, - ]); + it('#deleteSectionAt()', function (finish) { + const listView = Ti.UI.createListView(); + + win = Ti.UI.createWindow({ backgroundColor: 'green' }); + + const fruitSection = Ti.UI.createListSection({ headerTitle: 'Fruits' }); + fruitSection.setItems([ + { properties: { title: 'Apple' } }, + { properties: { title: 'Banana' } }, + ]); - const fishSection = Ti.UI.createListSection({ headerTitle: 'Fish' }); - fishSection.setItems([ - { properties: { title: 'Cod' } }, - { properties: { title: 'Haddock' } }, - ]); + const vegSection = Ti.UI.createListSection({ headerTitle: 'Vegetables' }); + vegSection.setItems([ + { properties: { title: 'Carrots' } }, + { properties: { title: 'Potatoes' } }, + ]); - listView.sections = [ fruitSection, fishSection ]; + const fishSection = Ti.UI.createListSection({ headerTitle: 'Fish' }); + fishSection.setItems([ + { properties: { title: 'Cod' } }, + { properties: { title: 'Haddock' } }, + ]); - win.addEventListener('open', () => { - try { + listView.sections = [ fruitSection, vegSection, fishSection ]; - // Validate section count. - should(listView.sectionCount).be.eql(2); + win.addEventListener('open', () => { + try { - // Validate first section item count. - should(listView.sections[0].items.length).be.eql(2); + // Validate section count. + should(listView.sectionCount).be.eql(3); - // Validate first section items. - should(listView.sections[0].items[0].properties.title).be.eql('Apple'); - should(listView.sections[0].items[1].properties.title).be.eql('Banana'); + // Validate first section item count. + should(listView.sections[0].items.length).be.eql(2); - // Validate second section item count. - should(listView.sections[1].items.length).be.eql(2); + // Validate first section items. + should(listView.sections[0].items[0].properties.title).be.eql('Apple'); + should(listView.sections[0].items[1].properties.title).be.eql('Banana'); - // Validate second section items. - should(listView.sections[1].items[0].properties.title).be.eql('Cod'); - should(listView.sections[1].items[1].properties.title).be.eql('Haddock'); + // Validate second section item count. + should(listView.sections[1].items.length).be.eql(2); - // Append new section. - listView.insertSectionAt(1, vegSection); + // Validate second section items. + should(listView.sections[1].items[0].properties.title).be.eql('Carrots'); + should(listView.sections[1].items[1].properties.title).be.eql('Potatoes'); - // Validate new section count. - should(listView.sectionCount).be.eql(3); + // Validate last section item count. + should(listView.sections[2].items.length).be.eql(2); - // Validate second section item count. - should(listView.sections[1].items.length).be.eql(2); + // Validate last section items. + should(listView.sections[2].items[0].properties.title).be.eql('Cod'); + should(listView.sections[2].items[1].properties.title).be.eql('Haddock'); - // Validate second section items. - should(listView.sections[1].items[0].properties.title).be.eql('Carrots'); - should(listView.sections[1].items[1].properties.title).be.eql('Potatoes'); + // Delete second section. + listView.deleteSectionAt(1); - // Validate last section item count. - should(listView.sections[2].items.length).be.eql(2); + // Validate new second section item count. + should(listView.sections[1].items.length).be.eql(2); - // Validate last section items. - should(listView.sections[2].items[0].properties.title).be.eql('Cod'); - should(listView.sections[2].items[1].properties.title).be.eql('Haddock'); - } catch (err) { - return finish(err); - } - finish(); - }); + // Validate new second section items. + should(listView.sections[1].items[0].properties.title).be.eql('Cod'); + should(listView.sections[1].items[1].properties.title).be.eql('Haddock'); + } catch (err) { + return finish(err); + } + finish(); + }); - win.add(listView); - win.open(); + win.add(listView); + win.open(); + }); }); - /** - * Basic replaceSectionAt test. - */ - it('replaceSectionAt', finish => { + // + // Making sure setting header & footer doesn't throw exception + // + it('Basic ListSection header and footer', finish => { const listView = Ti.UI.createListView(); + const ukHeaderView = Ti.UI.createView({ + backgroundColor: 'black', + height: 42 + }); + const ukFooterView = Ti.UI.createView({ + backgroundColor: 'black', + height: 42 + }); + const ukSection = Ti.UI.createListSection({ + headerView: ukHeaderView, + footerView: ukFooterView + }); + const usSection = Ti.UI.createListSection({ + headerTitle: 'English US Header', + footerTitle: 'English US Footer' + }); win = Ti.UI.createWindow({ backgroundColor: 'green' }); - const fruitSection = Ti.UI.createListSection({ headerTitle: 'Fruits' }); - fruitSection.setItems([ - { properties: { title: 'Apple' } }, - { properties: { title: 'Banana' } }, - ]); + ukHeaderView.add(Ti.UI.createLabel({ text: 'English UK Header', color: 'white' })); + ukFooterView.add(Ti.UI.createLabel({ text: 'English UK Footer', color: 'white' })); - const vegSection = Ti.UI.createListSection({ headerTitle: 'Vegetables' }); - vegSection.setItems([ - { properties: { title: 'Carrots' } }, - { properties: { title: 'Potatoes' } }, + ukSection.setItems([ + { properties: { title: 'Lift', color: 'black' } }, + { properties: { title: 'Lorry', color: 'black' } }, + { properties: { title: 'Motorway', color: 'black' } } ]); + listView.appendSection(ukSection); - const fishSection = Ti.UI.createListSection({ headerTitle: 'Fish' }); - fishSection.setItems([ - { properties: { title: 'Cod' } }, - { properties: { title: 'Haddock' } }, + usSection.setItems([ + { properties: { title: 'Elevator', color: 'black' } }, + { properties: { title: 'Truck', color: 'black' } }, + { properties: { title: 'Freeway', color: 'black' } } ]); - - listView.sections = [ fruitSection, fishSection ]; + listView.appendSection(usSection); win.addEventListener('open', () => { try { @@ -535,31 +607,20 @@ describe('Titanium.UI.ListView', function () { should(listView.sectionCount).be.eql(2); // Validate first section item count. - should(listView.sections[0].items.length).be.eql(2); + should(listView.sections[0].items.length).be.eql(3); // Validate first section items. - should(listView.sections[0].items[0].properties.title).be.eql('Apple'); - should(listView.sections[0].items[1].properties.title).be.eql('Banana'); - - // Validate second section item count. - should(listView.sections[1].items.length).be.eql(2); - - // Validate second section items. - should(listView.sections[1].items[0].properties.title).be.eql('Cod'); - should(listView.sections[1].items[1].properties.title).be.eql('Haddock'); - - // Append new section. - listView.replaceSectionAt(1, vegSection); - - // Validate section count. - should(listView.sectionCount).be.eql(2); + should(listView.sections[0].items[0].properties.title).be.eql('Lift'); + should(listView.sections[0].items[1].properties.title).be.eql('Lorry'); + should(listView.sections[0].items[2].properties.title).be.eql('Motorway'); - // Validate second section item count. - should(listView.sections[1].items.length).be.eql(2); + // Validate second sectiion count. + should(listView.sections[1].items.length).be.eql(3); // Validate second section items. - should(listView.sections[1].items[0].properties.title).be.eql('Carrots'); - should(listView.sections[1].items[1].properties.title).be.eql('Potatoes'); + should(listView.sections[1].items[0].properties.title).be.eql('Elevator'); + should(listView.sections[1].items[1].properties.title).be.eql('Truck'); + should(listView.sections[1].items[2].properties.title).be.eql('Freeway'); } catch (err) { return finish(err); } @@ -570,30 +631,77 @@ describe('Titanium.UI.ListView', function () { win.open(); }); - it('deleteSectionAt', function (finish) { - const listView = Ti.UI.createListView(); + /** + * Basic custom template test. + */ + it('template', finish => { + const listView = Ti.UI.createListView({ + templates: { + template: { + childTemplates: [ + { + type: 'Ti.UI.ImageView', + bindId: 'pic', + properties: { + width: 50, + height: 50, + left: 0 + } + }, + { + type: 'Ti.UI.Label', + bindId: 'info', + properties: { + color: 'black', + font: { + fontSize: 20, + fontWeight: 'bold' + }, + left: 60, + top: 0, + } + }, + { + type: 'Ti.UI.Label', + bindId: 'es_info', + properties: { + color: 'gray', + font: { fontSize: 14 }, + left: 60, + top: 25, + } + } + ] + } + }, + defaultItemTemplate: 'template' + }); + const sections = []; win = Ti.UI.createWindow({ backgroundColor: 'green' }); - const fruitSection = Ti.UI.createListSection({ headerTitle: 'Fruits' }); + const fruitSection = Ti.UI.createListSection({ headerTitle: 'Fruits / Frutas' }); fruitSection.setItems([ - { properties: { title: 'Apple' } }, - { properties: { title: 'Banana' } }, + { info: { text: 'Apple' }, es_info: { text: 'Manzana' }, pic: { image: 'Logo.png' } }, + { info: { text: 'Banana' }, es_info: { text: 'Banana' }, pic: { image: 'Logo.png' } } ]); + sections.push(fruitSection); - const vegSection = Ti.UI.createListSection({ headerTitle: 'Vegetables' }); + const vegSection = Ti.UI.createListSection({ headerTitle: 'Vegetables / Verduras' }); vegSection.setItems([ - { properties: { title: 'Carrots' } }, - { properties: { title: 'Potatoes' } }, + { info: { text: 'Carrot' }, es_info: { text: 'Zanahoria' }, pic: { image: 'Logo.png' } }, + { info: { text: 'Potato' }, es_info: { text: 'Patata' }, pic: { image: 'Logo.png' } } ]); + sections.push(vegSection); - const fishSection = Ti.UI.createListSection({ headerTitle: 'Fish' }); - fishSection.setItems([ - { properties: { title: 'Cod' } }, - { properties: { title: 'Haddock' } }, + const grainSection = Ti.UI.createListSection({ headerTitle: 'Grains / Granos' }); + grainSection.setItems([ + { info: { text: 'Corn' }, es_info: { text: 'Maiz' }, pic: { image: 'Logo.png' } }, + { info: { text: 'Rice' }, es_info: { text: 'Arroz' }, pic: { image: 'Logo.png' } } ]); + sections.push(grainSection); - listView.sections = [ fruitSection, vegSection, fishSection ]; + listView.sections = sections; win.addEventListener('open', () => { try { @@ -605,32 +713,22 @@ describe('Titanium.UI.ListView', function () { should(listView.sections[0].items.length).be.eql(2); // Validate first section items. - should(listView.sections[0].items[0].properties.title).be.eql('Apple'); - should(listView.sections[0].items[1].properties.title).be.eql('Banana'); + should(listView.sections[0].items[0].info.text).be.eql('Apple'); + should(listView.sections[0].items[1].info.text).be.eql('Banana'); // Validate second section item count. should(listView.sections[1].items.length).be.eql(2); // Validate second section items. - should(listView.sections[1].items[0].properties.title).be.eql('Carrots'); - should(listView.sections[1].items[1].properties.title).be.eql('Potatoes'); + should(listView.sections[1].items[0].info.text).be.eql('Carrot'); + should(listView.sections[1].items[1].info.text).be.eql('Potato'); // Validate last section item count. should(listView.sections[2].items.length).be.eql(2); // Validate last section items. - should(listView.sections[2].items[0].properties.title).be.eql('Cod'); - should(listView.sections[2].items[1].properties.title).be.eql('Haddock'); - - // Delete second section. - listView.deleteSectionAt(1); - - // Validate new second section item count. - should(listView.sections[1].items.length).be.eql(2); - - // Validate new second section items. - should(listView.sections[1].items[0].properties.title).be.eql('Cod'); - should(listView.sections[1].items[1].properties.title).be.eql('Haddock'); + should(listView.sections[2].items[0].info.text).be.eql('Corn'); + should(listView.sections[2].items[1].info.text).be.eql('Rice'); } catch (err) { return finish(err); } @@ -856,128 +954,58 @@ describe('Titanium.UI.ListView', function () { win.addEventListener('open', () => { - // Validate section count. - should(listView.sectionCount).be.eql(2); - - // Validate first section item count. - should(listView.sections[0].items.length).be.eql(2); - - // Validate first section items. - should(listView.sections[0].items[0].properties.title).be.eql('Apple'); - should(listView.sections[0].items[1].properties.title).be.eql('Banana'); + try { + // Validate section count. + should(listView.sectionCount).be.eql(2); - // Validate second section item count. - should(listView.sections[1].items.length).be.eql(2); + // Validate first section item count. + should(listView.sections[0].items.length).be.eql(2); - // Validate section items. - should(listView.sections[1].items[0].properties.title).be.eql('Carrots'); - should(listView.sections[1].items[1].properties.title).be.eql('Potatoes'); + // Validate first section items. + should(listView.sections[0].items[0].properties.title).be.eql('Apple'); + should(listView.sections[0].items[1].properties.title).be.eql('Banana'); - // Filter to show 'Apple' and 'Potatoes'. - listView.searchText = 'p'; + // Validate second section item count. + should(listView.sections[1].items.length).be.eql(2); - // Validate original section items still exist. - setTimeout(() => { - try { + // Validate section items. + should(listView.sections[1].items[0].properties.title).be.eql('Carrots'); + should(listView.sections[1].items[1].properties.title).be.eql('Potatoes'); - // Validate section count. - should(listView.sectionCount).be.eql(2); + // Filter to show 'Apple' and 'Potatoes'. + listView.searchText = 'p'; - // Validate first section item count. - should(listView.sections[0].items.length).be.eql(2); + // Validate original section items still exist. + setTimeout(() => { + try { - // Validate first section items. - should(listView.sections[0].items[0].properties.title).be.eql('Apple'); - should(listView.sections[0].items[1].properties.title).be.eql('Banana'); + // Validate section count. + should(listView.sectionCount).be.eql(2); - // Validate second section item count. - should(listView.sections[1].items.length).be.eql(2); + // Validate first section item count. + should(listView.sections[0].items.length).be.eql(2); - // Validate section items. - should(listView.sections[1].items[0].properties.title).be.eql('Carrots'); - should(listView.sections[1].items[1].properties.title).be.eql('Potatoes'); - } catch (err) { - return finish(err); - } - finish(); - }, 2000); - }); + // Validate first section items. + should(listView.sections[0].items[0].properties.title).be.eql('Apple'); + should(listView.sections[0].items[1].properties.title).be.eql('Banana'); - win.add(listView); - win.open(); - }); + // Validate second section item count. + should(listView.sections[1].items.length).be.eql(2); - // iOS-only properties - it.ios('ListView.getSelectedRows', function (finish) { - const list = Ti.UI.createListView({ - sections: [ Ti.UI.createListSection({ - items: [ { - properties: { - title: 'My Title 1', + // Validate section items. + should(listView.sections[1].items[0].properties.title).be.eql('Carrots'); + should(listView.sections[1].items[1].properties.title).be.eql('Potatoes'); + } catch (err) { + return finish(err); } - }, { - properties: { - title: 'My Title 2', - } - } ] - }) ] - }); - - win = Ti.UI.createWindow(); - win.addEventListener('open', function () { - try { - list.selectItem(0, 1); - should(list.selectedItems[0].section).be.eql(list.sections[0]); - should(list.selectedItems[0].sectionIndex).be.eql(0); - should(list.selectedItems[0].itemIndex).be.eql(1); - list.selectItem(0, 0); - should(list.selectedItems[0].section).be.eql(list.sections[0]); - should(list.selectedItems[0].sectionIndex).be.eql(0); - should(list.selectedItems[0].itemIndex).be.eql(0); + finish(); + }, 2000); } catch (err) { return finish(err); } - finish(); - }); - win.add(list); - win.open(); - }); - - // iOS-only properties - it.ios('ListView.getSelectedRows', function (finish) { - const list = Ti.UI.createListView({ - allowsMultipleSelectionDuringEditing: true, - sections: [ Ti.UI.createListSection({ - items: [ { - properties: { - title: 'My Title 1', - } - }, { - properties: { - title: 'My Title 2', - } - } ] - }) ] }); - win = Ti.UI.createWindow(); - win.addEventListener('open', function () { - try { - should(list.allowsMultipleSelectionDuringEditing).be.be.true(); - should(list.getAllowsMultipleSelectionDuringEditing()).be.be.true(); - - list.allowsMultipleSelectionDuringEditing = false; - should(list.allowsMultipleSelectionDuringEditing).be.be.false(); - should(list.getAllowsMultipleSelectionDuringEditing()).be.be.false(); - list.setAllowsMultipleSelectionDuringEditing(true); - should(list.allowsMultipleSelectionDuringEditing).be.be.true(); - should(list.getAllowsMultipleSelectionDuringEditing()).be.be.true(); - } catch (err) { - return finish(err); - } - finish(); - }); - win.add(list); + win.add(listView); win.open(); }); @@ -1096,11 +1124,6 @@ describe('Titanium.UI.ListView', function () { win.open(); }); - it.android('.fastScroll', () => { - const listView = Ti.UI.createListView(); - should(listView.fastScroll).be.be.false(); - }); - it('ListViewItem scaling (percent)', () => { // FIXME: Does not honour scale correctly on macOS. if (isCI && utilities.isMacOS()) { diff --git a/tests/Resources/ti.ui.optiondialog.test.js b/tests/Resources/ti.ui.optiondialog.test.js index 18d7a1bc805..58b10620663 100644 --- a/tests/Resources/ti.ui.optiondialog.test.js +++ b/tests/Resources/ti.ui.optiondialog.test.js @@ -6,106 +6,210 @@ */ /* eslint-env mocha */ /* eslint no-unused-expressions: "off" */ +/* eslint mocha/no-identical-title: "off" */ 'use strict'; const should = require('./utilities/assertions'); -describe('Titanium.UI.OptionDialog', function () { +describe('Titanium.UI.OptionDialog', () => { - it('apiName', function () { - const optionDialog = Ti.UI.createOptionDialog({ - title: 'this is some text' + describe('properties', () => { + describe('.apiName', () => { + it('is a read-only String', () => { + const dialog = Ti.UI.createOptionDialog(); + should(dialog).have.readOnlyProperty('apiName').which.is.a.String(); + }); + it('equals Ti.UI.OptionDialog', () => { + const dialog = Ti.UI.createOptionDialog(); + should(dialog.apiName).be.eql('Ti.UI.OptionDialog'); + }); }); - should(optionDialog).have.readOnlyProperty('apiName').which.is.a.String(); - should(optionDialog.apiName).be.eql('Ti.UI.OptionDialog'); - }); - it('title', function () { - const bar = Ti.UI.createOptionDialog({ - title: 'this is some text' + describe.android('.buttonNames', () => { + let dialog; + beforeEach(() => { + dialog = Ti.UI.createOptionDialog({}); + }); + + it.androidBroken('is an Array', () => { // defaults to undefined + should(dialog.buttonNames).be.an.Array(); + }); + + it.androidBroken('is empty', () => { // defaults to undefined + should(dialog.buttonNames).be.empty(); + }); + + it('can be assigned string[]', () => { + dialog.buttonNames = [ 'this', 'other' ]; + should(dialog.buttonNames.length).eql(2); + }); + + it('has accessors', () => { + should(dialog).have.accessors('buttonNames'); + }); }); - should(bar.title).be.a.String(); - should(bar.getTitle).be.a.Function(); - should(bar.title).eql('this is some text'); - should(bar.getTitle()).eql('this is some text'); - bar.title = 'other text'; - should(bar.title).eql('other text'); - should(bar.getTitle()).eql('other text'); - }); - // FIXME Get working on iOS. Looks like it doesn't look up titleid keys?! - it.iosBroken('titleid', function () { - const bar = Ti.UI.createOptionDialog({ - titleid: 'this_is_my_key' + describe('.cancel', () => { + let dialog; + beforeEach(() => { + dialog = Ti.UI.createOptionDialog({}); + }); + + it.allBroken('is a Number', () => { // defaults to undefined + should(dialog.cancel).be.a.Number(); + }); + + it.allBroken('defaults to -1', () => { // defaults to undefined + should(dialog.cancel).eql(-1); + }); + + it('can be assigned new Number value', () => { + dialog.cancel = 1; + should(dialog.cancel).eql(1); + }); + + it('has accessors', () => { + should(dialog).have.accessors('cancel'); + }); }); - should(bar.titleid).be.a.String(); - should(bar.getTitleid).be.a.Function(); - should(bar.titleid).eql('this_is_my_key'); - should(bar.getTitleid()).eql('this_is_my_key'); - should(bar.title).eql('this is my value'); // iOS returns undefined! - bar.titleid = 'other text'; - should(bar.titleid).eql('other text'); - should(bar.getTitleid()).eql('other text'); - should(bar.title).eql('this is my value'); // FIXME Windows: https://jira.appcelerator.org/browse/TIMOB-23498 - }); - // Intentionally skip for iOS. buttonNames property isn't on iOS. TODO Add it for parity? - // FIXME defaults to undefined on Android, empty array on Windows. - it.androidBrokenAndIosMissing('buttonNames', function () { - const bar = Ti.UI.createOptionDialog({}); - should(bar.buttonNames).be.an.Array(); // undefined on Android - should(bar.getButtonNames).be.a.Function(); - should(bar.buttonNames).be.empty; - should(bar.getButtonNames()).be.empty; - bar.buttonNames = [ 'this', 'other' ]; - should(bar.buttonNames.length).eql(2); - should(bar.getButtonNames().length).eql(2); - }); + describe('.options', () => { + let dialog; + beforeEach(() => { + dialog = Ti.UI.createOptionDialog({}); + }); - // FIXME Get working on iOS and Android. options is defaulting to undefined, where for Windows we do empty array - it.androidAndIosBroken('options', function () { - const bar = Ti.UI.createOptionDialog({}); - should(bar.options).be.an.Array(); // undefined on iOS and Android - should(bar.getOptions).be.a.Function(); - should(bar.options).be.empty; - should(bar.getOptions()).be.empty; - bar.options = [ 'this', 'other' ]; - should(bar.options.length).eql(2); - should(bar.getOptions().length).eql(2); - }); + // it('is an Array', () => { + // should(dialog.options).be.an.Array(); // undefined on Android + // }); - // FIXME Get working on iOS and Android. cancel is defaulting to undefined? Docs say should be -1 - it.androidAndIosBroken('cancel', function () { - const bar = Ti.UI.createOptionDialog({}); - should(bar.cancel).be.a.Number(); // undefined on iOS and Android - should(bar.getCancel).be.a.Function(); - bar.cancel = 1; - should(bar.cancel).eql(1); - should(bar.getCancel()).eql(1); - }); + // it('is empty', () => { + // should(dialog.options).be.empty(); + // }); + + it('defaults to undefined', () => { + should(dialog.options).be.undefined(); + }); + + it('can be assigned string[]', () => { + dialog.options = [ 'this', 'other' ]; + should(dialog.options.length).eql(2); + }); + + it('has accessors', () => { + should(dialog).have.accessors('options'); + }); + }); + + // FIXME Get working on iOS and Android. persistent is defaulting to undefined? Docs say should be true + describe('.persistent', () => { + let dialog; + beforeEach(() => { + dialog = Ti.UI.createOptionDialog({ + title: 'this is some text' + }); + }); + + it.allBroken('is a Boolean', () => { // defaults to undefined + should(dialog.persistent).be.a.Boolean(); + }); + + it.allBroken('defaults to true', () => { // defaults to undefined + should(dialog.persistent).be.true(); + }); - // FIXME Get working on iOS and Android. persistent is defaulting to undefined? Docs say should be true - it.androidAndIosBroken('persistent', function () { - const bar = Ti.UI.createOptionDialog({}); - should(bar.persistent).be.a.Boolean(); // undefined on iOS and Android - should(bar.getPersistent).be.a.Function(); - should(bar.persistent).be.true(); - should(bar.getPersistent()).be.true(); - bar.persistent = false; - should(bar.persistent).be.false(); - should(bar.getPersistent()).be.false(); + it('can be assigned new Boolean value', () => { + dialog.persistent = false; + should(dialog.persistent).be.false(); + }); + + it('has accessors', () => { + should(dialog).have.accessors('persistent'); + }); + }); + + // FIXME Get working on Android, defaults to undefined on Android + describe.iosMissing('.selectedIndex', () => { + let dialog; + beforeEach(() => { + dialog = Ti.UI.createOptionDialog({ + title: 'this is some text' + }); + }); + + it.androidBroken('is a Number', () => { // defaults to undefined + should(dialog.selectedIndex).be.a.Number(); + }); + + it('can be assigned new value', () => { + dialog.selectedIndex = 1; + should(dialog.selectedIndex).eql(1); + }); + + it('has accessors', () => { + should(dialog).have.accessors('selectedIndex'); + }); + }); + + describe('.title', () => { + let dialog; + beforeEach(() => { + dialog = Ti.UI.createOptionDialog({ + title: 'this is some text' + }); + }); + + it('is a String', () => { + should(dialog.title).be.a.String(); + }); + + it('equal to value set in factory method dictionary', () => { + should(dialog.title).eql('this is some text'); + }); + + it('can be assigned new value', () => { + dialog.title = 'other text'; + should(dialog.title).eql('other text'); + }); + + it('has accessors', () => { + should(dialog).have.accessors('title'); + }); + }); + + describe('.titleid', () => { + let dialog; + beforeEach(() => { + dialog = Ti.UI.createOptionDialog({ + titleid: 'this_is_my_key' + }); + }); + + it('is a String', () => { + should(dialog.titleid).be.a.String(); + }); + + it('equal to value set in factory method dictionary', () => { + should(dialog.titleid).eql('this_is_my_key'); + }); + + it.iosBroken('modifies .title property value', () => { + should(dialog.title).eql('this is my value'); + }); + + it('can be assigned new value', () => { + dialog.titleid = 'other text'; + should(dialog.titleid).eql('other text'); + // should(dialog.title).eql('this is my value'); // broken on iOS + }); + + it('has accessors', () => { + should(dialog).have.accessors('titleid'); + }); + }); }); - // Intentionally skip. property not on iOS - // FIXME Get working on Android, defaults to undefined on Android, Windows has Number - it.androidBrokenAndIosMissing('selectedIndex', function () { - const bar = Ti.UI.createOptionDialog({}); - should(bar.selectedIndex).be.a.Number(); // undefined on Android - should(bar.getSelectedIndex).be.a.Function(); - should(bar.selectedIndex).eql(0); - should(bar.getSelectedIndex()).eql(0); - bar.selectedIndex = 1; - should(bar.selectedIndex).eql(1); - should(bar.getSelectedIndex()).eql(1); + describe('methods', () => { + }); }); diff --git a/tests/Resources/ti.ui.picker.test.js b/tests/Resources/ti.ui.picker.test.js index fd739195c21..656da5e9765 100644 --- a/tests/Resources/ti.ui.picker.test.js +++ b/tests/Resources/ti.ui.picker.test.js @@ -30,327 +30,380 @@ describe('Titanium.UI.Picker', function () { } }); - it('DatePicker', function (finish) { - const date = new Date(); - const picker = Ti.UI.createPicker({ - type: Ti.UI.PICKER_TYPE_DATE, - value: date - }); + describe('.type is PICKER_TYPE_DATE', () => { + it('lifecycle', function (finish) { + const date = new Date(); + const picker = Ti.UI.createPicker({ + type: Ti.UI.PICKER_TYPE_DATE, + value: date + }); - win = Ti.UI.createWindow({ - backgroundColor: '#000' - }); - win.add(picker); - win.addEventListener('open', function () { - try { - should(picker.getValue()).be.eql(date); - } catch (err) { - return finish(err); - } - finish(); + win = Ti.UI.createWindow({ + backgroundColor: '#000' + }); + win.add(picker); + win.addEventListener('open', function () { + try { + should(picker.value).be.eql(date); + } catch (err) { + return finish(err); + } + finish(); + }); + win.open(); }); - win.open(); - }); - it('TimePicker', function (finish) { - const date = new Date(); - const picker = Ti.UI.createPicker({ - type: Ti.UI.PICKER_TYPE_TIME, - value: date - }); + it.ios('.dateTimeColor (invalid "type" - TIMOB-28181)', function (finish) { + const dp = Ti.UI.createPicker({ + type: Ti.UI.PICKER_TYPE_PLAIN, + dateTimeColor: 'red' + }); - win = Ti.UI.createWindow({ - backgroundColor: '#000' - }); - win.add(picker); - win.addEventListener('open', function () { - try { - should(picker.getValue().getHours()).be.eql(date.getHours()); - should(picker.getValue().getMinutes()).be.eql(date.getMinutes()); - } catch (err) { - return finish(err); - } - finish(); + win = Ti.UI.createWindow(); + win.addEventListener('open', function () { + try { + should(dp.dateTimeColor).be.eql('red'); + } catch (err) { + return finish(err); + } + finish(); + }); + win.add(dp); + win.open(); }); - win.open(); - }); - it('PlainPicker', function (finish) { - const picker = Ti.UI.createPicker({ - type: Ti.UI.PICKER_TYPE_PLAIN - }); + it.ios('.dateTimeColor (valid "type" + "datePickerStyle" - TIMOB-28181)', function (finish) { + const dp = Ti.UI.createPicker({ + type: Ti.UI.PICKER_TYPE_DATE, + datePickerStyle: Ti.UI.iOS.DATE_PICKER_STYLE_WHEELS, + dateTimeColor: 'red' + }); - win = Ti.UI.createWindow({ - backgroundColor: '#000' - }); - win.add(picker); - win.addEventListener('open', function () { - try { - should(picker).be.an.Object(); - picker.getValue(); - } catch (err) { - return finish(err); - } - finish(); + win = Ti.UI.createWindow(); + win.addEventListener('open', function () { + try { + should(dp.dateTimeColor).be.eql('red'); + finish(); + } catch (err) { + return finish(err); + } + }); + win.add(dp); + win.open(); }); - win.open(); - }); - it('PlainPicker.add(PickerColumn)', function (finish) { - const picker = Ti.UI.createPicker({ - type: Ti.UI.PICKER_TYPE_PLAIN - }); + it('.minDate', function (finish) { + const dp = Ti.UI.createPicker({ + type: Ti.UI.PICKER_TYPE_DATE + }); + const date = new Date(2018, 1, 1); + dp.minDate = date; - win = Ti.UI.createWindow({ - backgroundColor: '#000' - }); - win.add(picker); - win.addEventListener('open', function () { - try { - const column = Ti.UI.createPickerColumn(); - for (let i = 0, ilen = fruit.length; i < ilen; i++) { - const row = Ti.UI.createPickerRow({ - title: fruit[i], color: color[i], font: { fontSize: 24 }, - }); - column.addRow(row); + win = Ti.UI.createWindow({ title: 'Form' }); + win.addEventListener('open', function () { + try { + should(dp.minDate).be.eql(date); + } catch (err) { + return finish(err); } - picker.add(column); - - should(picker.columns.length).be.eql(1); - should(picker.columns[0]).be.an.Object(); - should(picker.columns[0].rows).be.an.Array(); - should(picker.columns[0].rows.length).be.eql(fruit.length); - } catch (err) { - return finish(err); - } - finish(); + finish(); + }); + win.add(dp); + win.open(); }); - win.open(); - }); - it('PlainPicker.add(multiple PickerColumn)', function (finish) { - const picker = Ti.UI.createPicker({ - type: Ti.UI.PICKER_TYPE_PLAIN - }); + it('.maxDate', function (finish) { + const dp = Ti.UI.createPicker({ + type: Ti.UI.PICKER_TYPE_DATE + }); + const date = new Date(2020, 1, 20); + dp.maxDate = date; - win = Ti.UI.createWindow({ - backgroundColor: '#000' - }); - win.add(picker); - win.addEventListener('open', function () { - try { - const column1 = Ti.UI.createPickerColumn(); - for (let i = 0, ilen = fruit.length; i < ilen; i++) { - const row = Ti.UI.createPickerRow({ - title: fruit[i], color: color[i], font: { fontSize: 24 }, - }); - column1.addRow(row); + win = Ti.UI.createWindow({ title: 'Form' }); + win.addEventListener('open', function () { + try { + should(dp.maxDate).be.eql(date); + } catch (err) { + return finish(err); } + finish(); + }); + win.add(dp); + win.open(); + }); + + it('.minDate/maxDate - change after open', (finish) => { + let minDate = new Date(2020, 4, 1); + let maxDate = new Date(2020, 6, 31); + const picker = Ti.UI.createPicker({ + type: Ti.UI.PICKER_TYPE_DATE, + minDate: minDate, + maxDate: maxDate, + value: new Date(2020, 5, 1) + }); - const column2 = Ti.UI.createPickerColumn(); - for (let i = 0, ilen = color.length; i < ilen; i++) { - const row = Ti.UI.createPickerRow({ - title: color[i] - }); - column2.addRow(row); + win = Ti.UI.createWindow(); + win.addEventListener('open', () => { + try { + should(picker.minDate).be.eql(minDate); + should(picker.maxDate).be.eql(maxDate); + should(picker.value.getTime()).be.aboveOrEqual(minDate.getTime()); + should(picker.value.getTime()).be.belowOrEqual(maxDate.getTime()); + minDate = new Date(2018, 0, 1); + maxDate = new Date(2018, 2, 31); + picker.minDate = minDate; + picker.maxDate = maxDate; + picker.value = new Date(2018, 1, 1); // Used to crash Android after changing range. + } catch (err) { + return finish(err); } - picker.add([ column1, column2 ]); - - should(picker.columns.length).be.eql(2); - should(picker.columns[0]).be.an.Object(); - should(picker.columns[0].rows).be.an.Array(); - should(picker.columns[0].rows.length).be.eql(fruit.length); - - should(picker.columns[1]).be.an.Object(); - should(picker.columns[1].rows).be.an.Array(); - should(picker.columns[1].rows.length).be.eql(color.length); - } catch (err) { - return finish(err); - } - finish(); + setTimeout(() => { + try { + should(picker.minDate).be.eql(minDate); + should(picker.maxDate).be.eql(maxDate); + should(picker.value.getTime()).be.aboveOrEqual(minDate.getTime()); + should(picker.value.getTime()).be.belowOrEqual(maxDate.getTime()); + } catch (err) { + return finish(err); + } + + finish(); + }, 1); + }); + win.add(picker); + win.open(); + }); + + describe('events', () => { + it('postlayout', function (finish) { + const dp = Ti.UI.createPicker({ + type: Ti.UI.PICKER_TYPE_DATE + }); + + win = Ti.UI.createWindow({ title: 'Form' }); + dp.addEventListener('postlayout', function postlayout() { + dp.removeEventListener('postlayout', postlayout); + finish(); + }); + win.add(dp); + win.open(); + }); }); - win.open(); }); - it('PlainPicker.add (PickerRow)', function (finish) { - const picker = Ti.UI.createPicker({ - type: Ti.UI.PICKER_TYPE_PLAIN - }); + describe('.type is PICKER_TYPE_TIME', () => { + it('TimePicker', function (finish) { + const date = new Date(); + const picker = Ti.UI.createPicker({ + type: Ti.UI.PICKER_TYPE_TIME, + value: date + }); - win = Ti.UI.createWindow({ - backgroundColor: '#000' - }); - win.add(picker); - win.addEventListener('open', function () { - try { - const rows = []; - for (let i = 0, ilen = fruit.length; i < ilen; i++) { - rows.push(Ti.UI.createPickerRow({ - title: fruit[i], color: color[i], font: { fontSize: 24 }, - })); + win = Ti.UI.createWindow({ + backgroundColor: '#000' + }); + win.add(picker); + win.addEventListener('open', function () { + try { + should(picker.value.getHours()).be.eql(date.getHours()); + should(picker.value.getMinutes()).be.eql(date.getMinutes()); + } catch (err) { + return finish(err); } - picker.add(rows); - } catch (err) { - return finish(err); - } - finish(); + finish(); + }); + win.open(); }); - win.open(); }); - it('PlainPicker.removeRow', function (finish) { - const picker = Ti.UI.createPicker({ - type: Ti.UI.PICKER_TYPE_PLAIN - }); - const column = Ti.UI.createPickerColumn(); - for (let i = 0, ilen = fruit.length; i < ilen; i++) { - const row = Ti.UI.createPickerRow({ - title: fruit[i], color: color[i], font: { fontSize: 24 }, + describe('.type is PICKER_TYPE_PLAIN', () => { + it('open and log value', function (finish) { + const picker = Ti.UI.createPicker({ + type: Ti.UI.PICKER_TYPE_PLAIN }); - column.addRow(row); - } - picker.add(column); - win = Ti.UI.createWindow({ - backgroundColor: '#000' - }); - win.add(picker); - win.addEventListener('open', function () { - try { - should(picker.columns.length).be.eql(1); - should(picker.columns[0]).be.an.Object(); - should(picker.columns[0].rows).be.an.Array(); - should(picker.columns[0].rows.length).be.eql(fruit.length); - - picker.columns[0].removeRow(picker.columns[0].rows[0]); - - should(picker.columns.length).be.eql(1); - should(picker.columns[0]).be.an.Object(); - should(picker.columns[0].rows).be.an.Array(); - should(picker.columns[0].rows.length).be.eql(fruit.length - 1); - } catch (err) { - return finish(err); - } - finish(); + win = Ti.UI.createWindow({ + backgroundColor: '#000' + }); + win.add(picker); + win.addEventListener('open', function () { + try { + should(picker).be.an.Object(); + console.log(picker.value); + } catch (err) { + return finish(err); + } + finish(); + }); + win.open(); }); - win.open(); - }); - - it('PlainPicker change event', function (finish) { - const pickerType = Ti.UI.createPicker(); - const type2 = [ - Ti.UI.createPickerRow({ title: 'Row 1' }), - Ti.UI.createPickerRow({ title: 'Row 2' }), - ]; + it('#add(PickerColumn)', function (finish) { + const picker = Ti.UI.createPicker({ + type: Ti.UI.PICKER_TYPE_PLAIN + }); - win = Ti.UI.createWindow(); - win.add(pickerType); - pickerType.addEventListener('postlayout', loadTypes); - pickerType.selectionIndicator = true; - pickerType.addEventListener('change', () => finish()); - - function loadTypes() { - pickerType.removeEventListener('postlayout', loadTypes); - pickerType.add(type2); - pickerType.setSelectedRow(0, 1); - } + win = Ti.UI.createWindow({ + backgroundColor: '#000' + }); + win.add(picker); + win.addEventListener('open', function () { + try { + const column = Ti.UI.createPickerColumn(); + for (let i = 0, ilen = fruit.length; i < ilen; i++) { + const row = Ti.UI.createPickerRow({ + title: fruit[i], color: color[i], font: { fontSize: 24 }, + }); + column.addRow(row); + } + picker.add(column); + + should(picker.columns.length).be.eql(1); + should(picker.columns[0]).be.an.Object(); + should(picker.columns[0].rows).be.an.Array(); + should(picker.columns[0].rows.length).be.eql(fruit.length); + } catch (err) { + return finish(err); + } + finish(); + }); + win.open(); + }); - win.open(); - }); + it('#add(multiple PickerColumn)', function (finish) { + const picker = Ti.UI.createPicker({ + type: Ti.UI.PICKER_TYPE_PLAIN + }); - it('DatePicker minDate', function (finish) { - const dp = Ti.UI.createPicker({ - type: Ti.UI.PICKER_TYPE_DATE - }); - const date = new Date(2018, 1, 1); - dp.setMinDate(date); - - win = Ti.UI.createWindow({ title: 'Form' }); - win.addEventListener('open', function () { - try { - should(dp.minDate).be.eql(date); - } catch (err) { - return finish(err); - } - finish(); + win = Ti.UI.createWindow({ + backgroundColor: '#000' + }); + win.add(picker); + win.addEventListener('open', function () { + try { + const column1 = Ti.UI.createPickerColumn(); + for (let i = 0, ilen = fruit.length; i < ilen; i++) { + const row = Ti.UI.createPickerRow({ + title: fruit[i], color: color[i], font: { fontSize: 24 }, + }); + column1.addRow(row); + } + + const column2 = Ti.UI.createPickerColumn(); + for (let i = 0, ilen = color.length; i < ilen; i++) { + const row = Ti.UI.createPickerRow({ + title: color[i] + }); + column2.addRow(row); + } + + picker.add([ column1, column2 ]); + + should(picker.columns.length).be.eql(2); + should(picker.columns[0]).be.an.Object(); + should(picker.columns[0].rows).be.an.Array(); + should(picker.columns[0].rows.length).be.eql(fruit.length); + + should(picker.columns[1]).be.an.Object(); + should(picker.columns[1].rows).be.an.Array(); + should(picker.columns[1].rows.length).be.eql(color.length); + } catch (err) { + return finish(err); + } + finish(); + }); + win.open(); }); - win.add(dp); - win.open(); - }); - it('DatePicker maxDate', function (finish) { - const dp = Ti.UI.createPicker({ - type: Ti.UI.PICKER_TYPE_DATE - }); - const date = new Date(2020, 1, 20); - dp.setMaxDate(date); - - win = Ti.UI.createWindow({ title: 'Form' }); - win.addEventListener('open', function () { - try { - should(dp.maxDate).be.eql(date); - } catch (err) { - return finish(err); - } - finish(); - }); - win.add(dp); - win.open(); - }); + it('#add(PickerRow)', function (finish) { + const picker = Ti.UI.createPicker({ + type: Ti.UI.PICKER_TYPE_PLAIN + }); - it('DatePicker dateTimeColor (invalid "type" - TIMOB-28181)', function (finish) { - const dp = Ti.UI.createPicker({ - type: Ti.UI.PICKER_TYPE_PLAIN, - dateTimeColor: 'red' + win = Ti.UI.createWindow({ + backgroundColor: '#000' + }); + win.add(picker); + win.addEventListener('open', function () { + try { + const rows = []; + for (let i = 0, ilen = fruit.length; i < ilen; i++) { + rows.push(Ti.UI.createPickerRow({ + title: fruit[i], color: color[i], font: { fontSize: 24 }, + })); + } + picker.add(rows); + } catch (err) { + return finish(err); + } + finish(); + }); + win.open(); }); - win = Ti.UI.createWindow(); - win.addEventListener('open', function () { - try { - should(dp.dateTimeColor).be.eql('red'); - finish(); - } catch (err) { - return finish(err); + it('#removeRow()', function (finish) { + const picker = Ti.UI.createPicker({ + type: Ti.UI.PICKER_TYPE_PLAIN + }); + const column = Ti.UI.createPickerColumn(); + for (let i = 0, ilen = fruit.length; i < ilen; i++) { + const row = Ti.UI.createPickerRow({ + title: fruit[i], color: color[i], font: { fontSize: 24 }, + }); + column.addRow(row); } - }); - win.add(dp); - win.open(); - }); + picker.add(column); - it.ios('DatePicker dateTimeColor (valid "type" + "datePickerStyle" - TIMOB-28181)', function (finish) { - const dp = Ti.UI.createPicker({ - type: Ti.UI.PICKER_TYPE_DATE, - datePickerStyle: Ti.UI.iOS.DATE_PICKER_STYLE_WHEELS, - dateTimeColor: 'red' - }); + win = Ti.UI.createWindow({ + backgroundColor: '#000' + }); + win.add(picker); + win.addEventListener('open', function () { + try { + should(picker.columns.length).be.eql(1); + should(picker.columns[0]).be.an.Object(); + should(picker.columns[0].rows).be.an.Array(); + should(picker.columns[0].rows.length).be.eql(fruit.length); - win = Ti.UI.createWindow(); - win.addEventListener('open', function () { - try { - should(dp.dateTimeColor).be.eql('red'); + picker.columns[0].removeRow(picker.columns[0].rows[0]); + + should(picker.columns.length).be.eql(1); + should(picker.columns[0]).be.an.Object(); + should(picker.columns[0].rows).be.an.Array(); + should(picker.columns[0].rows.length).be.eql(fruit.length - 1); + } catch (err) { + return finish(err); + } finish(); - } catch (err) { - return finish(err); - } + }); + win.open(); }); - win.add(dp); - win.open(); - }); - it('DatePicker postlayout event', function (finish) { - const dp = Ti.UI.createPicker({ - type: Ti.UI.PICKER_TYPE_DATE - }); + describe('events', () => { + it('change', function (finish) { + const pickerType = Ti.UI.createPicker(); + + const type2 = [ + Ti.UI.createPickerRow({ title: 'Row 1' }), + Ti.UI.createPickerRow({ title: 'Row 2' }), + ]; + + win = Ti.UI.createWindow(); + win.add(pickerType); + pickerType.addEventListener('postlayout', loadTypes); + pickerType.selectionIndicator = true; + pickerType.addEventListener('change', () => finish()); + + function loadTypes() { + pickerType.removeEventListener('postlayout', loadTypes); + pickerType.add(type2); + pickerType.setSelectedRow(0, 1); + } - win = Ti.UI.createWindow({ title: 'Form' }); - dp.addEventListener('postlayout', function postlayout() { - dp.removeEventListener('postlayout', postlayout); - finish(); + win.open(); + }); }); - win.add(dp); - win.open(); }); it.android('Selected index persistance', function (finish) { @@ -391,47 +444,4 @@ describe('Titanium.UI.Picker', function () { } } }); - - it('minDate/maxDate - change after open', (finish) => { - let minDate = new Date(2020, 4, 1); - let maxDate = new Date(2020, 6, 31); - const picker = Ti.UI.createPicker({ - type: Ti.UI.PICKER_TYPE_DATE, - minDate: minDate, - maxDate: maxDate, - value: new Date(2020, 5, 1) - }); - - win = Ti.UI.createWindow(); - win.addEventListener('open', () => { - try { - should(picker.minDate).be.eql(minDate); - should(picker.maxDate).be.eql(maxDate); - should(picker.value.getTime()).be.aboveOrEqual(minDate.getTime()); - should(picker.value.getTime()).be.belowOrEqual(maxDate.getTime()); - minDate = new Date(2018, 0, 1); - maxDate = new Date(2018, 2, 31); - picker.minDate = minDate; - picker.maxDate = maxDate; - picker.value = new Date(2018, 1, 1); // Used to crash Android after changing range. - } catch (err) { - return finish(err); - } - - setTimeout(() => { - try { - should(picker.minDate).be.eql(minDate); - should(picker.maxDate).be.eql(maxDate); - should(picker.value.getTime()).be.aboveOrEqual(minDate.getTime()); - should(picker.value.getTime()).be.belowOrEqual(maxDate.getTime()); - } catch (err) { - return finish(err); - } - - finish(); - }, 1); - }); - win.add(picker); - win.open(); - }); }); diff --git a/tests/Resources/ti.ui.progressbar.test.js b/tests/Resources/ti.ui.progressbar.test.js index 0cc88251976..0c96139b8a1 100644 --- a/tests/Resources/ti.ui.progressbar.test.js +++ b/tests/Resources/ti.ui.progressbar.test.js @@ -1,74 +1,246 @@ /* * Appcelerator Titanium Mobile - * Copyright (c) 2011-2016 by Appcelerator, Inc. All Rights Reserved. + * Copyright (c) 2020-Present by Axway, Inc. All Rights Reserved. * Licensed under the terms of the Apache Public License * Please see the LICENSE included with this distribution for details. */ /* eslint-env mocha */ /* eslint no-unused-expressions: "off" */ +/* eslint no-undef: "off" */ +/* eslint mocha/no-identical-title: "off" */ 'use strict'; const should = require('./utilities/assertions'); -describe('Titanium.UI.ProgressBar', function () { - it('apiName', () => { - const progressBar = Ti.UI.createProgressBar({ - message: 'this is some text' - }); - should(progressBar).have.readOnlyProperty('apiName').which.is.a.String(); - should(progressBar.apiName).be.eql('Ti.UI.ProgressBar'); +describe('Titanium.UI.ProgressBar', () => { + let bar; + afterEach(() => { + bar = null; }); - it('message', () => { - const bar = Ti.UI.createProgressBar({ - message: 'this is some text' + describe('properties', () => { + describe('.apiName', () => { + beforeEach(() => { + bar = Ti.UI.createProgressBar(); + }); + + it('is a String', () => { + should(bar).have.a.readOnlyProperty('apiName').which.is.a.String(); + }); + + it('equals Ti.UI.ProgressBar', () => { + should(bar.apiName).eql('Ti.UI.ProgressBar'); + }); }); - should(bar.message).be.a.String(); - should(bar.getMessage).be.a.Function(); - should(bar.message).eql('this is some text'); - should(bar.getMessage()).eql('this is some text'); - bar.message = 'other text'; - should(bar.message).eql('other text'); - should(bar.getMessage()).eql('other text'); - }); - it('min', () => { - const bar = Ti.UI.createProgressBar({ - min: 0 + describe('.color', () => { + beforeEach(() => { + bar = Ti.UI.createProgressBar({ color: 'red' }); + }); + + it('is a String', () => { + should(bar).have.property('color').which.is.a.String(); + }); + + it('equals value passed to factory method', () => { + should(bar.color).eql('red'); + }); + + it('can be assigned a String value', () => { + bar.color = 'blue'; + should(bar.color).eql('blue'); + }); + + it('has accessors', () => { + should(bar).have.accessors('color'); + }); }); - should(bar.min).be.a.Number(); - should(bar.getMin).be.a.Function(); - should(bar.min).eql(0); - should(bar.getMin()).eql(0); - bar.min = 100; - should(bar.min).eql(100); - should(bar.getMin()).eql(100); - }); - it('max', () => { - const bar = Ti.UI.createProgressBar({ - max: 0 + // FIXME: Add android support for the font property + describe.ios('.font', () => { + beforeEach(() => { + bar = Ti.UI.createProgressBar({ + font: { + fontSize: 24, + fontFamily: 'Segoe UI' + } + }); + }); + + it('is an Object', () => { + should(bar).have.a.property('font').which.is.an.Object(); + }); + + it('has accessors', () => { + should(bar).have.accessors('font'); + }); }); - should(bar.max).be.a.Number(); - should(bar.getMax).be.a.Function(); - should(bar.max).eql(0); - should(bar.getMax()).eql(0); - bar.max = 100; - should(bar.max).eql(100); - should(bar.getMax()).eql(100); - }); - it('value', () => { - const bar = Ti.UI.createProgressBar({ - value: 0 + describe('.max', () => { + beforeEach(() => { + bar = Ti.UI.createProgressBar({ + max: 0 + }); + }); + + it('is a Number', () => { + should(bar).have.a.property('max').which.is.a.Number(); + }); + + it('equals value passed to factory method', () => { + should(bar.max).eql(0); + }); + + it('can be assigned an Integer value', () => { + bar.max = 100; + should(bar.max).eql(100); + }); + + it('has accessors', () => { + should(bar).have.accessors('max'); + }); + }); + + describe('.message', () => { + beforeEach(() => { + bar = Ti.UI.createProgressBar({ + message: 'this is some text' + }); + }); + + it('is a String', () => { + should(bar.message).be.a.String(); + }); + + it('equals value passed to factory method', () => { + should(bar.message).eql('this is some text'); + }); + + it('can be assigned a String value', () => { + bar.message = 'other text'; + should(bar.message).eql('other text'); + }); + + it('has accessors', () => { + should(bar).have.accessors('message'); + }); + }); + + describe('.min', () => { + beforeEach(() => { + bar = Ti.UI.createProgressBar({ + min: 0 + }); + }); + + it('is a Number', () => { + should(bar).have.a.property('min').which.is.a.Number(); + }); + + it('equals value passed to factory method', () => { + should(bar.min).eql(0); + }); + + it('can be assigned an Integer value', () => { + bar.min = 100; + should(bar.min).eql(100); + }); + + it('has accessors', () => { + should(bar).have.accessors('min'); + }); + }); + + describe.ios('.style', () => { + beforeEach(() => { + bar = Ti.UI.createProgressBar({ style: Ti.UI.iOS.ProgressBarStyle.BAR }); + }); + + it('is a Number', () => { + should(bar).have.a.property('style').which.is.a.Number(); + }); + + it('equals value passed to factory method', () => { + should(bar.style).eql(Ti.UI.iOS.ProgressBarStyle.BAR); + }); + + it('can be assigned a constant value', () => { + bar.style = Ti.UI.iOS.ProgressBarStyle.PLAIN; + should(bar.style).eql(Ti.UI.iOS.ProgressBarStyle.PLAIN); + }); + + it('has accessors', () => { + should(bar).have.accessors('style'); + }); + }); + + describe('.tintColor', () => { + beforeEach(() => { + bar = Ti.UI.createProgressBar({ tintColor: 'red' }); + }); + + it('is a String', () => { + should(bar).have.property('tintColor').which.is.a.String(); + }); + + it('equals value passed to factory method', () => { + should(bar.tintColor).eql('red'); + }); + + it('can be assigned a String value', () => { + bar.tintColor = 'blue'; + should(bar.tintColor).eql('blue'); + }); + + it('has accessors', () => { + should(bar).have.accessors('tintColor'); + }); + }); + + describe('.trackTintColor', () => { + beforeEach(() => { + bar = Ti.UI.createProgressBar({ trackTintColor: 'red' }); + }); + + it('is a String', () => { + should(bar).have.property('trackTintColor').which.is.a.String(); + }); + + it('equals value passed to factory method', () => { + should(bar.trackTintColor).eql('red'); + }); + + it('can be assigned a String value', () => { + bar.trackTintColor = 'blue'; + should(bar.trackTintColor).eql('blue'); + }); + + it('has accessors', () => { + should(bar).have.accessors('trackTintColor'); + }); + }); + + describe('.value', () => { + beforeEach(() => { + bar = Ti.UI.createProgressBar({ value: 0 }); + }); + + it('is a Number', () => { + should(bar).have.a.property('value').which.is.a.Number(); + }); + + it('equals value passed to factory method', () => { + should(bar.value).eql(0); + }); + + it('can be assigned an Integer value', () => { + bar.value = 100; + should(bar.value).eql(100); + }); + + it('has accessors', () => { + should(bar).have.accessors('value'); + }); }); - should(bar.value).be.a.Number(); - should(bar.getValue).be.a.Function(); - should(bar.value).eql(0); - should(bar.getValue()).eql(0); - bar.value = 100; - should(bar.value).eql(100); - should(bar.getValue()).eql(100); }); - // TODO Add tests for style, color and font? }); diff --git a/tests/Resources/ti.ui.scrollableview.test.js b/tests/Resources/ti.ui.scrollableview.test.js index 0b2a0af3efe..903136df7d7 100644 --- a/tests/Resources/ti.ui.scrollableview.test.js +++ b/tests/Resources/ti.ui.scrollableview.test.js @@ -7,13 +7,14 @@ /* globals OS_VERSION_MAJOR */ /* eslint-env mocha */ /* eslint no-unused-expressions: "off" */ +/* eslint mocha/no-identical-title: "off" */ 'use strict'; const should = require('./utilities/assertions'); -describe('Titanium.UI.ScrollableView', function () { - this.timeout(5000); +describe('Titanium.UI.ScrollableView', () => { let win; + let scrollableView; afterEach(done => { // fires after every test in sub-suites too... if (win && !win.closed) { win.addEventListener('close', function listener () { @@ -28,209 +29,295 @@ describe('Titanium.UI.ScrollableView', function () { } }); - it('apiName', () => { - const scrollableView = Ti.UI.createScrollableView(); - should(scrollableView).have.readOnlyProperty('apiName').which.is.a.String(); - should(scrollableView.apiName).be.eql('Ti.UI.ScrollableView'); - }); - - it('views', () => { - const bar = Ti.UI.createScrollableView(); - should(bar.views).be.an.Array(); // iOS returns undefined - should(bar.getViews).be.a.Function(); - should(bar.views).be.empty; - should(bar.getViews()).be.empty; - bar.views = [ Ti.UI.createView(), Ti.UI.createView() ]; - should(bar.views.length).eql(2); - should(bar.getViews().length).eql(2); - }); + describe('properties', () => { + describe('.apiName', () => { + beforeEach(() => { + scrollableView = Ti.UI.createScrollableView(); + }); - it.windowsMissing('clipViews', function (finish) { - this.slow(5000); - this.timeout(5000); + it('is a read-only String', () => { + should(scrollableView).have.readOnlyProperty('apiName').which.is.a.String(); + }); - const scrollableView = Ti.UI.createScrollableView({ - clipViews: true + it('equals \'Ti.UI.ScrollableView\'', () => { + should(scrollableView.apiName).be.eql('Ti.UI.ScrollableView'); + }); }); - win = Ti.UI.createWindow(); - win.addEventListener('open', function () { - should(scrollableView.clipViews).be.true(); - finish(); - }); + describe.windowsMissing('.clipViews', () => { + beforeEach(() => { + scrollableView = Ti.UI.createScrollableView({ + clipViews: true + }); + }); - const view1 = Ti.UI.createView({ id: 'view1', backgroundColor: '#836' }); - const view2 = Ti.UI.createView({ id: 'view2', backgroundColor: '#246' }); - const view3 = Ti.UI.createView({ id: 'view3', backgroundColor: '#48b' }); + it('is a Boolean', () => { + should(scrollableView).have.property('clipViews').which.is.a.Boolean(); + }); - scrollableView.setViews([ view1, view2, view3 ]); + it('equal to the value passed in creation dictionary', () => { + should(scrollableView.clipViews).be.true(); + }); - win.add(scrollableView); - win.open(); - }); + it('can be assigned a Boolean value', () => { + scrollableView.clipViews = false; + should(scrollableView.clipViews).be.false(); + }); + + it('has accessors', () => { + should(scrollableView).have.accessors('clipViews'); + }); + + it('lifecycle', function (finish) { + this.timeout(5000); + + win = Ti.UI.createWindow(); + win.addEventListener('open', () => { + try { + should(scrollableView.clipViews).be.true(); + } catch (err) { + return finish(err); + } + finish(); + }); + + const view1 = Ti.UI.createView({ id: 'view1', backgroundColor: '#836' }); + const view2 = Ti.UI.createView({ id: 'view2', backgroundColor: '#246' }); + const view3 = Ti.UI.createView({ id: 'view3', backgroundColor: '#48b' }); - // TODO: Add parity? - it.android('padding', function (finish) { - this.slow(5000); - this.timeout(5000); + scrollableView.views = [ view1, view2, view3 ]; - const scrollableView = Ti.UI.createScrollableView({ - padding: { left: 20, right: 20 } + win.add(scrollableView); + win.open(); + }); }); - win = Ti.UI.createWindow(); - win.addEventListener('open', function () { - should(scrollableView.padding).be.an.Object(); - should(scrollableView.padding.left).eql(20); - should(scrollableView.padding.right).eql(20); - finish(); + describe('.currentPage', () => { + beforeEach(() => { + scrollableView = Ti.UI.createScrollableView({}); + }); + + it('is a Number', () => { + should(scrollableView).have.property('currentPage').which.is.a.Number(); + }); + + it('defaults to 0', () => { + should(scrollableView.currentPage).eql(0); + }); + + // FIXME explicitly setting currentPage doesn't seem to update value on Android + it.androidBroken('can be assigned an Integer value', () => { + scrollableView.views = [ Ti.UI.createView(), Ti.UI.createView() ]; + scrollableView.currentPage = 1; + should(scrollableView.currentPage).eql(1); // Android gives 0 + }); + + it('has accessors', () => { + should(scrollableView).have.accessors('currentPage'); + }); }); - const view1 = Ti.UI.createView({ id: 'view1', backgroundColor: '#836' }); - const view2 = Ti.UI.createView({ id: 'view2', backgroundColor: '#246' }); - const view3 = Ti.UI.createView({ id: 'view3', backgroundColor: '#48b' }); + // TODO: Add parity? + describe.android('.padding', () => { + beforeEach(() => { + scrollableView = Ti.UI.createScrollableView({ + padding: { left: 20, right: 20 } + }); + }); - scrollableView.setViews([ view1, view2, view3 ]); + it('is an Object', () => { + should(scrollableView).have.property('padding').which.is.an.Object(); + }); - win.add(scrollableView); - win.open(); - }); + it('equal to value passed in creation dictionary', () => { + should(scrollableView.padding).eql({ left: 20, right: 20 }); + }); - // FIXME explicitly setting currentPage doesn't seem to update value on Android - it.androidBroken('currentPage', function () { - const bar = Ti.UI.createScrollableView({}); - should(bar.currentPage).be.a.Number(); - should(bar.getCurrentPage).be.a.Function(); - should(bar.currentPage).eql(0); - should(bar.getCurrentPage()).eql(0); - bar.views = [ Ti.UI.createView(), Ti.UI.createView() ]; - bar.currentPage = 1; - should(bar.currentPage).eql(1); // Android gives 0 - should(bar.getCurrentPage()).eql(1); - }); + it('can be assigned an Object value', () => { + scrollableView.padding = { left: 10, right: 10 }; + should(scrollableView.padding).eql({ left: 10, right: 10 }); + }); - it.androidAndWindowsBroken('moveX-scrollTo', function (finish) { - var testName = null, - nextPageIndex = 0, - bar = null; - this.slow(5000); - this.timeout(20000); - function doNextTest() { - try { - if (!testName) { - testName = 'moveNext'; - Ti.API.info('Testing ScrollableView.moveNext()'); - nextPageIndex = bar.currentPage + 1; - should(bar.moveNext).be.a.Function(); - bar.moveNext(); - } else if (testName === 'moveNext') { - testName = 'movePrevious'; - Ti.API.info('Testing ScrollableView.movePrevious()'); - nextPageIndex = bar.currentPage - 1; - should(bar.movePrevious).be.a.Function(); - bar.movePrevious(); - } else if (testName === 'movePrevious') { - testName = 'scrollToView'; - Ti.API.info('Testing ScrollableView.scrollToView()'); - nextPageIndex = 2; - should(bar.scrollToView).be.a.Function(); - bar.scrollToView(nextPageIndex); - } else if (testName === 'scrollToView') { + it('has accessors', () => { + should(scrollableView).have.accessors('padding'); + }); + + it('lifecycle', function (finish) { + this.timeout(5000); + + win = Ti.UI.createWindow(); + win.addEventListener('open', () => { + try { + should(scrollableView.padding).be.an.Object(); + should(scrollableView.padding.left).eql(20); + should(scrollableView.padding.right).eql(20); + } catch (err) { + return finish(err); + } finish(); - } - } catch (err) { - finish(err); - } - } - win = Ti.UI.createWindow(); - bar = Ti.UI.createScrollableView(); - bar.views = [ Ti.UI.createView(), Ti.UI.createView(), Ti.UI.createView() ]; - bar.addEventListener('scrollend', function (e) { - try { - should(e.currentPage).eql(nextPageIndex); - should(bar.currentPage).eql(nextPageIndex); - should(bar.getCurrentPage()).eql(nextPageIndex); - doNextTest(); - } catch (err) { - finish(err); - } - }); - win.add(bar); - win.addEventListener('postlayout', function () { - if (!testName) { - doNextTest(); - } - }); - win.open(); - }); + }); - it.ios('preferredIndicatorImage', function (finish) { - if (OS_VERSION_MAJOR < 14) { - return finish(); - } + const view1 = Ti.UI.createView({ id: 'view1', backgroundColor: '#836' }); + const view2 = Ti.UI.createView({ id: 'view2', backgroundColor: '#246' }); + const view3 = Ti.UI.createView({ id: 'view3', backgroundColor: '#48b' }); - const view1 = Ti.UI.createView({ id: 'view1', backgroundColor: '#836' }); - const view2 = Ti.UI.createView({ id: 'view2', backgroundColor: '#246' }); - const backwardImage = Ti.UI.iOS.systemImage('backward'); - const forwardImage = Ti.UI.iOS.systemImage('forward'); - const scrollableView = Ti.UI.createScrollableView({ - preferredIndicatorImage: backwardImage, - views: [ view1, view2 ], - showPagingControl: true + scrollableView.views = [ view1, view2, view3 ]; + + win.add(scrollableView); + win.open(); + }); }); - // must set a bg color so don't have full alpha, or else image compare doesn't work as intended - win = Ti.UI.createWindow({ extendSafeArea: false, backgroundColor: 'orange' }); - win.addEventListener('postlayout', function listener () { - win.removeEventListener('postlayout', listener); - try { - const preferredBackwardImage = win.toImage(); - scrollableView.preferredIndicatorImage = forwardImage; - should(win).not.matchImage(preferredBackwardImage, { threshold: 0 }); - - scrollableView.preferredIndicatorImage = backwardImage; - should(win).matchImage(preferredBackwardImage, { threshold: 0 }); - } catch (error) { - return finish(error); + it.ios('.preferredIndicatorImage', function (finish) { + if (OS_VERSION_MAJOR < 14) { + return finish(); } + this.timeout(5000); + + const view1 = Ti.UI.createView({ id: 'view1', backgroundColor: '#836' }); + const view2 = Ti.UI.createView({ id: 'view2', backgroundColor: '#246' }); + const backwardImage = Ti.UI.iOS.systemImage('backward'); + const forwardImage = Ti.UI.iOS.systemImage('forward'); + scrollableView = Ti.UI.createScrollableView({ + preferredIndicatorImage: backwardImage, + views: [ view1, view2 ], + showPagingControl: true + }); + + // must set a bg color so don't have full alpha, or else image compare doesn't work as intended + win = Ti.UI.createWindow({ extendSafeArea: false, backgroundColor: 'orange' }); + win.addEventListener('postlayout', function listener () { + win.removeEventListener('postlayout', listener); + try { + const preferredBackwardImage = win.toImage(); + scrollableView.preferredIndicatorImage = forwardImage; + should(win).not.matchImage(preferredBackwardImage, { threshold: 0 }); - finish(); + scrollableView.preferredIndicatorImage = backwardImage; + should(win).matchImage(preferredBackwardImage, { threshold: 0 }); + } catch (error) { + return finish(error); + } + + finish(); + }); + + win.add(scrollableView); + win.open(); }); - win.add(scrollableView); - win.open(); - }); + describe('.views', () => { + beforeEach(() => { + scrollableView = Ti.UI.createScrollableView(); + }); - it.ios('setIndicatorImageForPage', function (finish) { - if (OS_VERSION_MAJOR < 14) { - return finish(); - } - const view1 = Ti.UI.createView({ id: 'view1', backgroundColor: '#836' }); - const view2 = Ti.UI.createView({ id: 'view2', backgroundColor: '#246' }); - const image = Ti.UI.iOS.systemImage('backward'); - const scrollableView = Ti.UI.createScrollableView({ - views: [ view1, view2 ], - showPagingControl: true, + it('is an Array', () => { + should(scrollableView).have.property('views').which.is.an.Array(); + }); + + it('defaults to empty Array', () => { + should(scrollableView.views).be.empty(); + }); + + it('can be assigned an Array of Ti.UI.Views', () => { + scrollableView.views = [ Ti.UI.createView(), Ti.UI.createView() ]; + should(scrollableView.views.length).eql(2); + }); + + it('has accessors', () => { + should(scrollableView).have.accessors('views'); + }); }); + }); - // must set a bg color so don't have full alpha, or else image compare doesn't work as intended - win = Ti.UI.createWindow({ extendSafeArea: false, backgroundColor: 'orange' }); - win.addEventListener('postlayout', function listener () { - win.removeEventListener('postlayout', listener); - try { - const defaultImage = win.toImage(); - scrollableView.setIndicatorImageForPage(image, 1); - should(win).not.matchImage(defaultImage, { threshold: 0 }); - - scrollableView.setIndicatorImageForPage(null, 1); // null will change to default - should(win).matchImage(defaultImage, { threshold: 0 }); - } catch (error) { - return finish(error); + describe('methods', () => { + it.androidAndWindowsBroken('#moveX()/#scrollToView()', function (finish) { + var testName = null, + nextPageIndex = 0, + bar = null; + this.slow(5000); + this.timeout(20000); + function doNextTest() { + try { + if (!testName) { + testName = 'moveNext'; + Ti.API.info('Testing ScrollableView.moveNext()'); + nextPageIndex = bar.currentPage + 1; + should(bar.moveNext).be.a.Function(); + bar.moveNext(); + } else if (testName === 'moveNext') { + testName = 'movePrevious'; + Ti.API.info('Testing ScrollableView.movePrevious()'); + nextPageIndex = bar.currentPage - 1; + should(bar.movePrevious).be.a.Function(); + bar.movePrevious(); + } else if (testName === 'movePrevious') { + testName = 'scrollToView'; + Ti.API.info('Testing ScrollableView.scrollToView()'); + nextPageIndex = 2; + should(bar.scrollToView).be.a.Function(); + bar.scrollToView(nextPageIndex); + } else if (testName === 'scrollToView') { + finish(); + } + } catch (err) { + finish(err); + } } - finish(); + win = Ti.UI.createWindow(); + bar = Ti.UI.createScrollableView(); + bar.views = [ Ti.UI.createView(), Ti.UI.createView(), Ti.UI.createView() ]; + bar.addEventListener('scrollend', function (e) { + try { + should(e.currentPage).eql(nextPageIndex); + should(bar.currentPage).eql(nextPageIndex); + doNextTest(); + } catch (err) { + finish(err); + } + }); + win.add(bar); + win.addEventListener('postlayout', function () { + if (!testName) { + doNextTest(); + } + }); + win.open(); }); - win.add(scrollableView); - win.open(); + it.ios('#setIndicatorImageForPage()', function (finish) { + if (OS_VERSION_MAJOR < 14) { + return finish(); + } + this.timeout(5000); + + const view1 = Ti.UI.createView({ id: 'view1', backgroundColor: '#836' }); + const view2 = Ti.UI.createView({ id: 'view2', backgroundColor: '#246' }); + const image = Ti.UI.iOS.systemImage('backward'); + scrollableView = Ti.UI.createScrollableView({ + views: [ view1, view2 ], + showPagingControl: true, + }); + + // must set a bg color so don't have full alpha, or else image compare doesn't work as intended + win = Ti.UI.createWindow({ extendSafeArea: false, backgroundColor: 'orange' }); + win.addEventListener('postlayout', function listener () { + win.removeEventListener('postlayout', listener); + try { + const defaultImage = win.toImage(); + scrollableView.setIndicatorImageForPage(image, 1); + should(win).not.matchImage(defaultImage, { threshold: 0 }); + + scrollableView.setIndicatorImageForPage(null, 1); // null will change to default + should(win).matchImage(defaultImage, { threshold: 0 }); + } catch (error) { + return finish(error); + } + finish(); + }); + + win.add(scrollableView); + win.open(); + }); }); }); diff --git a/tests/Resources/ti.ui.searchbar.test.js b/tests/Resources/ti.ui.searchbar.test.js index d2698884e72..3e0c4c246ad 100644 --- a/tests/Resources/ti.ui.searchbar.test.js +++ b/tests/Resources/ti.ui.searchbar.test.js @@ -6,10 +6,11 @@ */ /* eslint-env mocha */ /* eslint no-unused-expressions: "off" */ +/* eslint mocha/no-identical-title: "off" */ 'use strict'; const should = require('./utilities/assertions'); -describe('Titanium.UI.SearchBar', function () { +describe('Titanium.UI.SearchBar', () => { let win; afterEach(done => { // fires after every test in sub-suites too... if (win && !win.closed) { @@ -25,148 +26,361 @@ describe('Titanium.UI.SearchBar', function () { } }); - it.ios('.showBookmark', function () { - var searchBar = Ti.UI.createSearchBar({ - showBookmark: true + describe('properties', () => { + describe.ios('.autocapitalization', () => { + let searchBar; + beforeEach(() => { + searchBar = Ti.UI.createSearchBar({ + autocapitalization: Ti.UI.TEXT_AUTOCAPITALIZATION_ALL + }); + }); + + it('is a Number', () => { + should(searchBar.autocapitalization).be.a.Number(); + }); + + it('equals value passed to factory method', () => { + should(searchBar.autocapitalization).eql(Ti.UI.TEXT_AUTOCAPITALIZATION_ALL); + }); + + it('can be assigned a constant value', () => { + searchBar.autocapitalization = Ti.UI.TEXT_AUTOCAPITALIZATION_SENTENCES; + should(searchBar.autocapitalization).eql(Ti.UI.TEXT_AUTOCAPITALIZATION_SENTENCES); + }); + + it('has accessors', () => { + should(searchBar).have.accessors('autocapitalization'); + }); }); - should(searchBar.getShowBookmark).be.a.Function(); - should(searchBar.showBookmark).be.true(); - should(searchBar.getShowBookmark()).be.true(); - searchBar.showBookmark = false; - should(searchBar.showBookmark).be.false(); - should(searchBar.getShowBookmark()).be.fasle; - }); - it.ios('.keyboardType', function () { - var searchBar = Ti.UI.createSearchBar({ - keyboardType: Ti.UI.KEYBOARD_TYPE_NUMBER_PAD + describe.ios('.autocorrect', () => { + let searchBar; + beforeEach(() => { + searchBar = Ti.UI.createSearchBar({ + autocorrect: true + }); + }); + + it('is a Boolean', () => { + should(searchBar.autocorrect).be.a.Boolean(); + }); + + it('equals value passed to factory method', () => { + should(searchBar.autocorrect).be.true(); + }); + + it('can be assigned a Boolean value', () => { + searchBar.autocorrect = false; + should(searchBar.autocorrect).be.false(); + }); + + it('has accessors', () => { + should(searchBar).have.accessors('autocorrect'); + }); }); - should(searchBar.getKeyboardType).be.a.Function(); - should(searchBar.keyboardType).eql(Ti.UI.KEYBOARD_TYPE_NUMBER_PAD); - should(searchBar.getKeyboardType()).eql(Ti.UI.KEYBOARD_TYPE_NUMBER_PAD); - searchBar.keyboardType = Ti.UI.KEYBOARD_TYPE_EMAIL; - should(searchBar.keyboardType).eql(Ti.UI.KEYBOARD_TYPE_EMAIL); - should(searchBar.getKeyboardType()).eql(Ti.UI.KEYBOARD_TYPE_EMAIL); - }); - it.ios('.autocorrect', function () { - var searchBar = Ti.UI.createSearchBar({ - autocorrect: true + describe.windowsMissing('.color', () => { + let searchBar; + beforeEach(() => { + searchBar = Ti.UI.createSearchBar({ + color: 'red' + }); + }); + + it('is a String', () => { + should(searchBar.color).be.a.String(); + }); + + it('equals value passed to factory method', () => { + should(searchBar.color).eql('red'); + }); + + it('can be assigned a String value', () => { + searchBar.color = 'blue'; + should(searchBar.color).eql('blue'); + }); + + it('has accessors', () => { + should(searchBar).have.accessors('color'); + }); }); - should(searchBar.getAutocorrect).be.a.Function(); - should(searchBar.autocorrect).be.true(); - should(searchBar.getAutocorrect()).be.true(); - searchBar.autocorrect = false; - should(searchBar.autocorrect).be.false(); - should(searchBar.getAutocorrect()).be.false(); - }); - it.ios('.autocapitalization', function () { - var searchBar = Ti.UI.createSearchBar({ - autocapitalization: Ti.UI.TEXT_AUTOCAPITALIZATION_ALL + it.ios('.fieldBackgroundImage and .fieldBackgroundDisabledImage', () => { + const backgroundView = Ti.UI.createView({ + height: 36, + width: Ti.Platform.displayCaps.platformWidth - 20, + backgroundColor: '#268E8E93', + borderRadius: 12 + }); + + const searchBar = Ti.UI.createSearchBar({ + fieldBackgroundImage: backgroundView.toImage(), + fieldBackgroundDisabledImage: backgroundView.toImage() + }); + + should(searchBar.fieldBackgroundImage.apiName).eql('Ti.Blob'); + should(searchBar.fieldBackgroundDisabledImage.apiName).eql('Ti.Blob'); }); - should(searchBar.getAutocapitalization).be.a.Function(); - should(searchBar.autocapitalization).eql(Ti.UI.TEXT_AUTOCAPITALIZATION_ALL); - should(searchBar.getAutocapitalization()).eql(Ti.UI.TEXT_AUTOCAPITALIZATION_ALL); - searchBar.autocapitalization = Ti.UI.TEXT_AUTOCAPITALIZATION_SENTENCES; - should(searchBar.autocapitalization).eql(Ti.UI.TEXT_AUTOCAPITALIZATION_SENTENCES); - should(searchBar.getAutocapitalization()).eql(Ti.UI.TEXT_AUTOCAPITALIZATION_SENTENCES); - }); - it.ios('.keyboardAppearance', function () { - var searchBar = Ti.UI.createSearchBar({ - keyboardAppearance: Ti.UI.KEYBOARD_APPEARANCE_LIGHT + it('.focused', function (done) { + this.slow(1000); + this.timeout(5000); + + win = Ti.UI.createWindow({ backgroundColor: '#fff' }); + const searchbar = Ti.UI.createSearchBar({ + backgroundColor: '#fafafa', + color: 'green', + width: 250, + height: 40 + }); + win.add(searchbar); + try { + searchbar.should.have.a.property('focused').which.is.a.Boolean(); + searchbar.focused.should.be.false(); // haven't opened it yet, so shouldn't be focused + searchbar.addEventListener('focus', () => { + try { + searchbar.focused.should.be.true(); + } catch (e) { + return done(e); + } + win.close(); + }); + win.addEventListener('open', () => { + searchbar.focus(); // force focus! + }); + win.addEventListener('close', () => { + try { + // we've been closed (or are closing?) so hopefully shouldn't say that we're focused + searchbar.focused.should.be.false(); + } catch (e) { + return done(e); + } + done(); + }); + win.open(); + } catch (e) { + return done(e); + } }); - should(searchBar.getKeyboardAppearance).be.a.Function(); - should(searchBar.keyboardAppearance).eql(Ti.UI.KEYBOARD_APPEARANCE_LIGHT); - should(searchBar.getKeyboardAppearance()).eql(Ti.UI.KEYBOARD_APPEARANCE_LIGHT); - searchBar.keyboardAppearance = Ti.UI.KEYBOARD_APPEARANCE_DARK; - should(searchBar.keyboardAppearance).eql(Ti.UI.KEYBOARD_APPEARANCE_DARK); - should(searchBar.getKeyboardAppearance()).eql(Ti.UI.KEYBOARD_APPEARANCE_DARK); - }); - it.ios('.style', function () { - var searchBar = Ti.UI.createSearchBar({ - style: Ti.UI.iOS.SEARCH_BAR_STYLE_PROMINENT + describe('.hintText', () => { + let searchBar; + beforeEach(() => { + searchBar = Ti.UI.createSearchBar({ + hintText: 'Search' + }); + }); + + it('is a String', () => { + should(searchBar.hintText).be.a.String(); + }); + + it('equals value passed to factory method', () => { + should(searchBar.hintText).eql('Search'); + }); + + it('can be assigned a String value', () => { + searchBar.hintText = 'Updated search'; + should(searchBar.hintText).eql('Updated search'); + }); + + it('has accessors', () => { + should(searchBar).have.accessors('hintText'); + }); + + it('change dynamically', function (finish) { + this.timeout(5000); + + const OLD_HINT_TEXT = 'Old Hint Text'; + const NEW_HINT_TEXT = 'New Hint Text'; + searchBar = Ti.UI.createSearchBar({ + hintText: OLD_HINT_TEXT, + }); + should(searchBar.hintText).eql(OLD_HINT_TEXT); + win = Ti.UI.createWindow(); + win.add(searchBar); + win.addEventListener('open', function () { + try { + should(searchBar.hintText).eql(OLD_HINT_TEXT); + searchBar.hintText = NEW_HINT_TEXT; + should(searchBar.hintText).eql(NEW_HINT_TEXT); + } catch (err) { + return finish(err); + } + setTimeout(function () { + try { + should(searchBar.hintText).eql(NEW_HINT_TEXT); + } catch (err) { + return finish(err); + } + finish(); + }, 100); + }); + win.open(); + }); }); - should(searchBar.getStyle).be.a.Function(); - should(searchBar.style).eql(Ti.UI.iOS.SEARCH_BAR_STYLE_PROMINENT); - should(searchBar.getStyle()).eql(Ti.UI.iOS.SEARCH_BAR_STYLE_PROMINENT); - searchBar.style = Ti.UI.iOS.SEARCH_BAR_STYLE_MINIMAL; - should(searchBar.style).eql(Ti.UI.iOS.SEARCH_BAR_STYLE_MINIMAL); - should(searchBar.getStyle()).eql(Ti.UI.iOS.SEARCH_BAR_STYLE_MINIMAL); - }); - it.ios('.prompt', function () { - var searchBar = Ti.UI.createSearchBar({ - prompt: 'value' + // We have in in Ti.UI.Android.SearchView for Android, but need more parity here + describe.windowsMissing('.hintTextColor', () => { + let searchBar; + beforeEach(() => { + searchBar = Ti.UI.createSearchBar({ + hintText: 'Enter E-Mail ...', + hintTextColor: 'red' + }); + }); + + it('is a String', () => { + should(searchBar.hintTextColor).be.a.String(); + }); + + it('equals value passed to factory method', () => { + should(searchBar.hintTextColor).eql('red'); + }); + + it('can be assigned a String value', () => { + searchBar.hintTextColor = 'blue'; + should(searchBar.hintTextColor).eql('blue'); + }); + + it('has accessors', () => { + should(searchBar).have.accessors('hintTextColor'); + }); }); - should(searchBar.getStyle).be.a.Function(); - should(searchBar.prompt).eql('value'); - should(searchBar.getPrompt()).eql('value'); - searchBar.prompt = 'another value'; - should(searchBar.prompt).eql('another value'); - should(searchBar.getPrompt()).eql('another value'); - }); - // TODO: Expose Windows as well - // We have in in Ti.UI.Android.SearchView for Android, but need more parity here - it.windowsMissing('.hintTextColor', function () { - var searchBar = Ti.UI.createSearchBar({ - hintText: 'Enter E-Mail ...', - hintTextColor: 'red' + describe.ios('.keyboardAppearance', () => { + let searchBar; + beforeEach(() => { + searchBar = Ti.UI.createSearchBar({ + keyboardAppearance: Ti.UI.KEYBOARD_APPEARANCE_LIGHT + }); + }); + + it('is a Number', () => { + should(searchBar.keyboardAppearance).be.a.Number(); + }); + + it('equals value passed to factory method', () => { + should(searchBar.keyboardAppearance).eql(Ti.UI.KEYBOARD_APPEARANCE_LIGHT); + }); + + it('can be assigned a constant value', () => { + searchBar.keyboardAppearance = Ti.UI.KEYBOARD_APPEARANCE_DARK; + should(searchBar.keyboardAppearance).eql(Ti.UI.KEYBOARD_APPEARANCE_DARK); + }); + + it('has accessors', () => { + should(searchBar).have.accessors('keyboardAppearance'); + }); }); - should(searchBar.getHintTextColor).be.a.Function(); - should(searchBar.hintTextColor).eql('red'); - should(searchBar.getHintTextColor()).eql('red'); - searchBar.hintTextColor = 'blue'; - should(searchBar.hintTextColor).eql('blue'); - should(searchBar.getHintTextColor()).eql('blue'); - }); - // TODO: Expose Windows as well - it.windowsMissing('.color', function () { - var searchBar = Ti.UI.createSearchBar({ - color: 'red' + describe.ios('.keyboardType', () => { + let searchBar; + beforeEach(() => { + searchBar = Ti.UI.createSearchBar({ + keyboardType: Ti.UI.KEYBOARD_TYPE_NUMBER_PAD + }); + }); + + it('is a Number', () => { + should(searchBar.keyboardType).be.a.Number(); + }); + + it('equals value passed to factory method', () => { + should(searchBar.keyboardType).eql(Ti.UI.KEYBOARD_TYPE_NUMBER_PAD); + }); + + it('can be assigned a constant value', () => { + searchBar.keyboardType = Ti.UI.KEYBOARD_TYPE_EMAIL; + should(searchBar.keyboardType).eql(Ti.UI.KEYBOARD_TYPE_EMAIL); + }); + + it('has accessors', () => { + should(searchBar).have.accessors('keyboardType'); + }); }); - should(searchBar.getColor).be.a.Function(); - should(searchBar.color).eql('red'); - should(searchBar.getColor()).eql('red'); - searchBar.color = 'blue'; - should(searchBar.color).eql('blue'); - should(searchBar.getColor()).eql('blue'); - }); - it.ios('Should be able to set/get the background image of the textfield', function () { - var backgroundView = Ti.UI.createView({ - height: 36, - width: Ti.Platform.displayCaps.platformWidth - 20, - backgroundColor: '#268E8E93', - borderRadius: 12 + describe.ios('.prompt', () => { + let searchBar; + beforeEach(() => { + searchBar = Ti.UI.createSearchBar({ + prompt: 'value' + }); + }); + + it('is a String', () => { + should(searchBar.prompt).be.a.String(); + }); + + it('equals value passed to factory method', () => { + should(searchBar.prompt).eql('value'); + }); + + it('can be assigned a String value', () => { + searchBar.prompt = 'another value'; + should(searchBar.prompt).eql('another value'); + }); + + it('has accessors', () => { + should(searchBar).have.accessors('prompt'); + }); }); - var searchBar = Ti.UI.createSearchBar({ - fieldBackgroundImage: backgroundView.toImage(), - fieldBackgroundDisabledImage: backgroundView.toImage() + describe.ios('.showBookmark', () => { + let searchBar; + beforeEach(() => { + searchBar = Ti.UI.createSearchBar({ + showBookmark: true + }); + }); + + it('is a Boolean', () => { + should(searchBar.showBookmark).be.a.Boolean(); + }); + + it('equals value passed to factory method', () => { + should(searchBar.showBookmark).be.true(); + }); + + it('can be assigned a Boolean value', () => { + searchBar.showBookmark = false; + should(searchBar.showBookmark).be.false(); + }); + + it('has accessors', () => { + should(searchBar).have.accessors('showBookmark'); + }); }); - should(searchBar.fieldBackgroundImage.apiName).eql('Ti.Blob'); - should(searchBar.fieldBackgroundDisabledImage.apiName).eql('Ti.Blob'); - }); + describe.ios('.style', () => { + let searchBar; + beforeEach(() => { + searchBar = Ti.UI.createSearchBar({ + style: Ti.UI.iOS.SEARCH_BAR_STYLE_PROMINENT + }); + }); + + it('is a Number', () => { + should(searchBar.style).be.a.Number(); + }); + + it('equals value passed to factory method', () => { + should(searchBar.style).eql(Ti.UI.iOS.SEARCH_BAR_STYLE_PROMINENT); + }); - it('Should be able to get/set hintText', function () { - var search = Ti.UI.createSearchBar({ - hintText: 'Search' + it('can be assigned a constant value', () => { + searchBar.style = Ti.UI.iOS.SEARCH_BAR_STYLE_MINIMAL; + should(searchBar.style).eql(Ti.UI.iOS.SEARCH_BAR_STYLE_MINIMAL); + }); + + it('has accessors', () => { + should(searchBar).have.accessors('style'); + }); }); - should(search.hintText).eql('Search'); - should(search.getHintText()).eql('Search'); - should(function () { - search.setHintText('Updated search'); - }).not.throw(); - should(search.hintText).eql('Updated search'); - should(search.getHintText()).eql('Updated search'); }); + describe('methods', () => {}); + it.ios('Should work with absolute-positioned search-bars (ListView)', function (finish) { var data = [ { properties: { title: 'Bashful', hasDetail: true } } ], searchBar, @@ -174,12 +388,16 @@ describe('Titanium.UI.SearchBar', function () { win = Ti.UI.createWindow({ backgroundColor: 'white' }); win.addEventListener('open', function () { - should(listView.top).eql(50); - should(listView.bottom).eql(50); - should(listView.left).eql(40); - should(listView.right).eql(40); + try { + should(listView.top).eql(50); + should(listView.bottom).eql(50); + should(listView.left).eql(40); + should(listView.right).eql(40); - should(searchBar.getWidth()).eql(150); + should(searchBar.width).eql(150); + } catch (err) { + return finish(err); + } finish(); }); @@ -218,10 +436,10 @@ describe('Titanium.UI.SearchBar', function () { win.addEventListener('open', function () { try { table.search = sb; - finish(); } catch (err) { - finish(err); + return finish(err); } + finish(); }); win.add(table); win.open(); @@ -247,10 +465,10 @@ describe('Titanium.UI.SearchBar', function () { win.addEventListener('open', function () { try { listview.searchView = sb; - finish(); } catch (err) { - finish(err); + return finish(err); } + finish(); }); win.add(listview); win.open(); @@ -286,87 +504,14 @@ describe('Titanium.UI.SearchBar', function () { win.remove(table); win.add(table); - should(sb.getHeight()).eql(44); - should(sb.getShowCancel()).be.false(); - should(sb.getBarColor()).eql('blue'); - finish(); - } catch (err) { - finish(err); - } - }); - win.open(); - }); - - it('Change hintText dynamically', function (finish) { - this.timeout(5000); - - const OLD_HINT_TEXT = 'Old Hint Text'; - const NEW_HINT_TEXT = 'New Hint Text'; - const searchBar = Ti.UI.createSearchBar({ - hintText: OLD_HINT_TEXT, - }); - should(searchBar.hintText).eql(OLD_HINT_TEXT); - win = Ti.UI.createWindow(); - win.add(searchBar); - win.addEventListener('open', function () { - try { - should(searchBar.hintText).eql(OLD_HINT_TEXT); - searchBar.hintText = NEW_HINT_TEXT; - should(searchBar.hintText).eql(NEW_HINT_TEXT); + should(sb.height).eql(44); + should(sb.showCancel).be.false(); + should(sb.barColor).eql('blue'); } catch (err) { - finish(err); - return; + return finish(err); } - setTimeout(function () { - try { - should(searchBar.hintText).eql(NEW_HINT_TEXT); - finish(); - } catch (err) { - finish(err); - } - }, 100); + finish(); }); win.open(); }); - - it('.focused', function (done) { - this.slow(1000); - this.timeout(5000); - - win = Ti.UI.createWindow({ backgroundColor: '#fff' }); - const searchbar = Ti.UI.createSearchBar({ - backgroundColor: '#fafafa', - color: 'green', - width: 250, - height: 40 - }); - win.add(searchbar); - try { - searchbar.should.have.a.property('focused').which.is.a.Boolean(); - searchbar.focused.should.be.false(); // haven't opened it yet, so shouldn't be focused - searchbar.addEventListener('focus', () => { - try { - searchbar.focused.should.be.true(); - } catch (e) { - return done(e); - } - win.close(); - }); - win.addEventListener('open', () => { - searchbar.focus(); // force focus! - }); - win.addEventListener('close', () => { - try { - // we've been closed (or are closing?) so hopefully shouldn't say that we're focused - searchbar.focused.should.be.false(); - } catch (e) { - return done(e); - } - done(); - }); - win.open(); - } catch (e) { - return done(e); - } - }); }); diff --git a/tests/Resources/ti.ui.tab.test.js b/tests/Resources/ti.ui.tab.test.js index 861f27435ba..b4e037cce2a 100644 --- a/tests/Resources/ti.ui.tab.test.js +++ b/tests/Resources/ti.ui.tab.test.js @@ -6,100 +6,238 @@ */ /* eslint-env mocha */ /* eslint no-unused-expressions: "off" */ +/* eslint mocha/no-identical-title: "off" */ 'use strict'; const should = require('./utilities/assertions'); describe('Titanium.UI.Tab', () => { - it('.apiName', () => { - const tab = Ti.UI.createTab({ - text: 'this is some text' - }); - should(tab).have.readOnlyProperty('apiName').which.is.a.String(); - should(tab.apiName).be.eql('Ti.UI.Tab'); - }); + describe('properties', () => { + describe('.activeTintColor', () => { + let tab; + beforeEach(() => { + tab = Ti.UI.createTab({ + activeTintColor: 'red' + }); + }); + + it('is a String', () => { + should(tab).have.property('activeTintColor').which.is.a.String(); + }); + + it('equals value passed to factory method', () => { + should(tab.activeTintColor).eql('red'); + }); + + it('can be assigned a String value', () => { + tab.activeTintColor = 'blue'; + should(tab.activeTintColor).eql('blue'); + }); - it('.title', () => { - const tab = Ti.UI.createTab({ - title: 'this is some text' + it('has accessors', () => { + should(tab).have.accessors('activeTintColor'); + }); }); - should(tab.title).be.a.String(); - should(tab.getTitle).be.a.Function(); - should(tab.title).eql('this is some text'); - should(tab.getTitle()).eql('this is some text'); - tab.title = 'other text'; - should(tab.title).eql('other text'); - should(tab.getTitle()).eql('other text'); - }); - it('.titleid', () => { - const bar = Ti.UI.createTab({ - titleid: 'this_is_my_key' + describe('.activeTintColor', () => { + let tab; + beforeEach(() => { + tab = Ti.UI.createTab({ + activeTitleColor: 'red' + }); + }); + + it('is a String', () => { + should(tab).have.property('activeTitleColor').which.is.a.String(); + }); + + it('equals value passed to factory method', () => { + should(tab.activeTitleColor).eql('red'); + }); + + it('can be assigned a String value', () => { + tab.activeTitleColor = 'blue'; + should(tab.activeTitleColor).eql('blue'); + }); + + it('has accessors', () => { + should(tab).have.accessors('activeTitleColor'); + }); }); - should(bar.titleid).be.a.String(); - should(bar.getTitleid).be.a.Function(); - should(bar.titleid).eql('this_is_my_key'); - should(bar.getTitleid()).eql('this_is_my_key'); - should(bar.title).eql('this is my value'); - bar.titleid = 'other text'; - should(bar.titleid).eql('other text'); - should(bar.getTitleid()).eql('other text'); - should(bar.title).eql('this is my value'); // FIXME Windows: https://jira.appcelerator.org/browse/TIMOB-23498 - }); - it('.titleColor', () => { - const tab = Ti.UI.createTab({ - titleColor: 'red' + describe('.apiName', () => { + it('is a String', () => { + const tab = Ti.UI.createTab(); + should(tab).have.readOnlyProperty('apiName').which.is.a.String(); + }); + + it('equals \'Ti.UI.Tab\'', () => { + const tab = Ti.UI.createTab(); + should(tab.apiName).be.eql('Ti.UI.Tab'); + }); }); - should(tab.titleColor).be.a.String(); - should(tab.titleColor).eql('red'); - }); + // NOTE: These badge tests require a custom theme extending Material theme on Android + // We do that in our tiapp.xml + describe('.badge', () => { + let tab; + beforeEach(() => { + tab = Ti.UI.createTab({ + badge: 3 + }); + }); + + it('is a Number', () => { + should(tab.badge).be.a.Number(); + }); + + it('equals value passed to factory method', () => { + should(tab.badge).eql(3); + }); - it('.activeTitleColor', () => { - const tab = Ti.UI.createTab({ - activeTitleColor: 'red' + it('can be assigned an Integer value', () => { + tab.badge = 2; + should(tab.badge).eql(2); + }); + + it('has accessors', () => { + should(tab).have.accessors('badge'); + }); }); - should(tab.activeTitleColor).be.a.String(); - should(tab.activeTitleColor).eql('red'); - }); + describe('.badgeColor', () => { + let tab; + beforeEach(() => { + tab = Ti.UI.createTab({ + badge: 3, + badgeColor: '#123' + }); + }); + + it('is a String', () => { + should(tab).have.property('badgeColor').which.is.a.String(); + }); + + it('equals value passed to factory method', () => { + should(tab.badgeColor).eql('#123'); + }); - it('.tintColor', () => { - const tab = Ti.UI.createTab({ - tintColor: 'red' + it('can be assigned a String value', () => { + tab.badgeColor = 'blue'; + should(tab.badgeColor).eql('blue'); + }); + + it('has accessors', () => { + should(tab).have.accessors('badgeColor'); + }); }); - should(tab.tintColor).be.a.String(); - should(tab.tintColor).eql('red'); - }); + describe('.tintColor', () => { + let tab; + beforeEach(() => { + tab = Ti.UI.createTab({ + tintColor: 'red' + }); + }); + + it('is a String', () => { + should(tab).have.property('tintColor').which.is.a.String(); + }); + + it('equals value passed to factory method', () => { + should(tab.tintColor).eql('red'); + }); + + it('can be assigned a String value', () => { + tab.tintColor = 'blue'; + should(tab.tintColor).eql('blue'); + }); - it('.activeTintColor', () => { - const tab = Ti.UI.createTab({ - activeTintColor: 'red' + it('has accessors', () => { + should(tab).have.accessors('tintColor'); + }); }); - should(tab.activeTintColor).be.a.String(); - should(tab.activeTintColor).eql('red'); - }); + describe('.title', () => { + let tab; + beforeEach(() => { + tab = Ti.UI.createTab({ + title: 'this is some text' + }); + }); + + it('is a String', () => { + should(tab.title).be.a.String(); + }); + + it('equal to value set in factory method dictionary', () => { + should(tab.title).eql('this is some text'); + }); - // NOTE: These badge tests require a custom theme extending Material theme on Android - // We do that in our tiapp.xml - it('.badge', () => { - const tab = Ti.UI.createTab({ - badge: 3 + it('can be assigned new value', () => { + tab.title = 'other text'; + should(tab.title).eql('other text'); + }); + + it('has accessors', () => { + should(tab).have.accessors('title'); + }); }); - should(tab.badge).be.a.Number(); - should(tab.badge).eql(3); - }); + describe('.titleid', () => { + let tab; + beforeEach(() => { + tab = Ti.UI.createTab({ + titleid: 'this_is_my_key' + }); + }); - it('.badgeColor', () => { - const tab = Ti.UI.createTab({ - badge: 3, - badgeColor: '#123' + it('is a String', () => { + should(tab.titleid).be.a.String(); + }); + + it('equal to value set in factory method dictionary', () => { + should(tab.titleid).eql('this_is_my_key'); + }); + + it('modifies .title property value', () => { + should(tab.title).eql('this is my value'); + }); + + it('can be assigned new value', () => { + tab.titleid = 'other text'; + should(tab.titleid).eql('other text'); + should(tab.title).eql('this is my value'); + }); + + it('has accessors', () => { + should(tab).have.accessors('titleid'); + }); }); - should(tab.badgeColor).be.a.String(); - should(tab.badgeColor).eql('#123'); + describe('.titleColor', () => { + let tab; + beforeEach(() => { + tab = Ti.UI.createTab({ + titleColor: 'red' + }); + }); + + it('is a String', () => { + should(tab).have.property('titleColor').which.is.a.String(); + }); + + it('equals value passed to factory method', () => { + should(tab.titleColor).eql('red'); + }); + + it('can be assigned a String value', () => { + tab.titleColor = 'blue'; + should(tab.titleColor).eql('blue'); + }); + + it('has accessors', () => { + should(tab).have.accessors('titleColor'); + }); + }); }); }); diff --git a/tests/Resources/ti.ui.tabbedbar.test.js b/tests/Resources/ti.ui.tabbedbar.test.js index 7f6f772719b..25c7453d3c4 100644 --- a/tests/Resources/ti.ui.tabbedbar.test.js +++ b/tests/Resources/ti.ui.tabbedbar.test.js @@ -7,7 +7,7 @@ /* eslint-env mocha */ /* eslint no-unused-expressions: "off" */ 'use strict'; -var should = require('./utilities/assertions'); +const should = require('./utilities/assertions'); describe.windowsMissing('Titanium.UI.TabbedBar', function () { let win; @@ -31,150 +31,122 @@ describe.windowsMissing('Titanium.UI.TabbedBar', function () { } }); - it('apiName', () => { - const tabbedBar = Ti.UI.createTabbedBar(); - should(tabbedBar).have.readOnlyProperty('apiName').which.is.a.String(); - should(tabbedBar.apiName).be.eql('Ti.UI.TabbedBar'); - }); - - it('Labels from Strings', finish => { - const tabbedBar = Ti.UI.createTabbedBar({ - labels: [ 'A', 'B', 'C' ] - }); - function postlayout() { - tabbedBar.removeEventListener('postlayout', postlayout); - finish(); - } - tabbedBar.addEventListener('postlayout', postlayout); - win.add(tabbedBar); - win.open(); - }); + describe('properties', () => { + describe('.apiName', () => { + it('is a read-only String', () => { + const tabbedBar = Ti.UI.createTabbedBar(); + should(tabbedBar).have.readOnlyProperty('apiName').which.is.a.String(); + }); - it('Labels from BarItemType', finish => { - const tabbedBar = Ti.UI.createTabbedBar({ - labels: [ - { title: 'A' }, - { title: 'B' }, - { title: 'C' } - ] + it('equals Ti.UI.TabbedBar', () => { + const tabbedBar = Ti.UI.createTabbedBar(); + should(tabbedBar.apiName).be.eql('Ti.UI.TabbedBar'); + }); }); - function postlayout() { - tabbedBar.removeEventListener('postlayout', postlayout); - finish(); - } - tabbedBar.addEventListener('postlayout', postlayout); - win.add(tabbedBar); - win.open(); - }); - it('Labels update', finish => { - const tabbedBar = Ti.UI.createTabbedBar({ - labels: [ 'A', 'B', 'C' ] - }); - function postlayout() { - tabbedBar.removeEventListener('postlayout', postlayout); - try { - tabbedBar.labels = [ 'D', 'E', 'F' ]; - should(tabbedBar.labels[1]).be.eql('E'); - finish(); - } catch (err) { - finish(err); - } - } - tabbedBar.addEventListener('postlayout', postlayout); - win.add(tabbedBar); - win.open(); - }); + describe('.labels', () => { + it('from string[]', finish => { + const tabbedBar = Ti.UI.createTabbedBar({ + labels: [ 'A', 'B', 'C' ] + }); + function postlayout() { + tabbedBar.removeEventListener('postlayout', postlayout); + finish(); + } + tabbedBar.addEventListener('postlayout', postlayout); + win.add(tabbedBar); + win.open(); + }); - it('Index - direct change', finish => { - var tabbedBar = Ti.UI.createTabbedBar({ - labels: [ 'A', 'B', 'C' ], - index: 1 - }); - win.add(tabbedBar); - function postlayout() { - tabbedBar.removeEventListener('postlayout', postlayout); - try { - tabbedBar.index = 2; - should(tabbedBar.index).be.eql(2); - finish(); - } catch (err) { - finish(err); - } - } - tabbedBar.addEventListener('postlayout', postlayout); - win.open(); - }); + it('from BarItemType[]', finish => { + const tabbedBar = Ti.UI.createTabbedBar({ + labels: [ + { title: 'A' }, + { title: 'B' }, + { title: 'C' } + ] + }); + function postlayout() { + tabbedBar.removeEventListener('postlayout', postlayout); + finish(); + } + tabbedBar.addEventListener('postlayout', postlayout); + win.add(tabbedBar); + win.open(); + }); - it('Index - setter change', finish => { - const tabbedBar = Ti.UI.createTabbedBar({ - labels: [ 'A', 'B', 'C' ], - index: 1 - }); - win.add(tabbedBar); - function postlayout() { - tabbedBar.removeEventListener('postlayout', postlayout); - try { - tabbedBar.setIndex(2); - should(tabbedBar.index).be.eql(2); - finish(); - } catch (err) { - finish(err); - } - } - tabbedBar.addEventListener('postlayout', postlayout); - win.open(); - }); + it('update', finish => { + const tabbedBar = Ti.UI.createTabbedBar({ + labels: [ 'A', 'B', 'C' ] + }); + function postlayout() { + tabbedBar.removeEventListener('postlayout', postlayout); + try { + tabbedBar.labels = [ 'D', 'E', 'F' ]; + should(tabbedBar.labels[1]).be.eql('E'); + } catch (err) { + return finish(err); + } + finish(); + } + tabbedBar.addEventListener('postlayout', postlayout); + win.add(tabbedBar); + win.open(); + }); - it('Index - getter read', finish => { - const tabbedBar = Ti.UI.createTabbedBar({ - labels: [ 'A', 'B', 'C' ], - index: 1 + it('update - before window.open()', finish => { + const tabbedBar = Ti.UI.createTabbedBar(); + tabbedBar.labels = [ 'A', 'B', 'C' ]; + win.add(tabbedBar); + win.addEventListener('open', () => { + try { + should(tabbedBar.labels[1]).be.eql('B'); + } catch (err) { + return finish(err); + } + finish(); + }); + win.open(); + }); }); - win.add(tabbedBar); - function postlayout() { - tabbedBar.removeEventListener('postlayout', postlayout); - try { - tabbedBar.setIndex(2); - should(tabbedBar.getIndex()).be.eql(2); - finish(); - } catch (err) { - finish(err); - } - } - tabbedBar.addEventListener('postlayout', postlayout); - win.open(); - }); - it('Labels update - before window.open()', finish => { - const tabbedBar = Ti.UI.createTabbedBar(); - tabbedBar.labels = [ 'A', 'B', 'C' ]; - win.add(tabbedBar); - win.addEventListener('open', () => { - try { - should(tabbedBar.labels[1]).be.eql('B'); - finish(); - } catch (err) { - finish(err); - } - }); - win.open(); - }); + describe('.index', () => { + it('direct change', finish => { + const tabbedBar = Ti.UI.createTabbedBar({ + labels: [ 'A', 'B', 'C' ], + index: 1 + }); + win.add(tabbedBar); + function postlayout() { + tabbedBar.removeEventListener('postlayout', postlayout); + try { + tabbedBar.index = 2; + should(tabbedBar.index).eql(2); + } catch (err) { + return finish(err); + } + finish(); + } + tabbedBar.addEventListener('postlayout', postlayout); + win.open(); + }); - it('Index update - before window.open()', finish => { - var tabbedBar = Ti.UI.createTabbedBar({ - labels: [ 'A', 'B', 'C' ] - }); - tabbedBar.index = 2; - win.add(tabbedBar); - win.addEventListener('open', () => { - try { - should(tabbedBar.index).be.eql(2); - finish(); - } catch (err) { - finish(err); - } + it('update - before window.open()', finish => { + var tabbedBar = Ti.UI.createTabbedBar({ + labels: [ 'A', 'B', 'C' ] + }); + tabbedBar.index = 2; + win.add(tabbedBar); + win.addEventListener('open', () => { + try { + should(tabbedBar.index).be.eql(2); + } catch (err) { + return finish(err); + } + finish(); + }); + win.open(); + }); }); - win.open(); }); }); diff --git a/tests/Resources/ti.ui.tabgroup.test.js b/tests/Resources/ti.ui.tabgroup.test.js index 4093530fb4d..b2023faed0d 100644 --- a/tests/Resources/ti.ui.tabgroup.test.js +++ b/tests/Resources/ti.ui.tabgroup.test.js @@ -6,6 +6,7 @@ */ /* eslint-env mocha */ /* eslint no-unused-expressions: "off" */ +/* eslint mocha/no-identical-title: "off" */ 'use strict'; const should = require('./utilities/assertions'); // eslint-disable-line no-unused-vars const utilities = require('./utilities/utilities'); @@ -30,207 +31,382 @@ describe('Titanium.UI.TabGroup', function () { } }); - it('.barColor', () => { - tabGroup = Ti.UI.createTabGroup({ - title: 'TabGroup', - barColor: 'red' - }); + describe('properties', () => { + describe('.activeTab', () => { + it.windowsBroken('assign before open event', finish => { + const winA = Ti.UI.createWindow(), + tabA = Ti.UI.createTab({ + title: 'Tab A', + window: winA + }), + winB = Ti.UI.createWindow(), + tabB = Ti.UI.createTab({ + title: 'Tab B', + window: winB + }); + tabGroup = Ti.UI.createTabGroup(); + tabGroup.activeTab = 1; + // Does windows fire this event? + // Can we test this without even opening tab group? + tabGroup.addEventListener('open', () => { + try { + should(tabGroup.activeTab.title).be.a.String(); + should(tabGroup.activeTab.title).eql('Tab B'); + } catch (err) { + return finish(err); + } finally { + tabGroup.removeTab(tabA); + tabGroup.removeTab(tabB); + } + finish(); + }); + + tabGroup.addTab(tabA); + tabGroup.addTab(tabB); + tabGroup.open(); + }); - should(tabGroup.barColor).be.a.String(); - should(tabGroup.barColor).eql('red'); - }); + it.windowsBroken('assign after open event', finish => { + const winA = Ti.UI.createWindow(), + tabA = Ti.UI.createTab({ + title: 'Tab A', + window: winA + }), + winB = Ti.UI.createWindow(), + tabB = Ti.UI.createTab({ + title: 'Tab B', + window: winB + }); + tabGroup = Ti.UI.createTabGroup(); + + // Does windows fire this event? + // Can we test this without even opening tab group? + tabGroup.addEventListener('open', () => { + try { + tabGroup.activeTab = tabB; + should(tabGroup.activeTab.title).be.a.String(); + should(tabGroup.activeTab.title).eql('Tab B'); + } catch (err) { + return finish(err); + } finally { + tabGroup.removeTab(tabA); + tabGroup.removeTab(tabB); + } + finish(); + }); + + tabGroup.addTab(tabA); + tabGroup.addTab(tabB); + tabGroup.open(); + }); - it('.tintColor', () => { - tabGroup = Ti.UI.createTabGroup({ - title: 'TabGroup', - tintColor: 'red' + it.androidBroken('assign in creation dictionary', finish => { + const winA = Ti.UI.createWindow(), + tabA = Ti.UI.createTab({ + title: 'Tab A', + window: winA + }), + winB = Ti.UI.createWindow(), + tabB = Ti.UI.createTab({ + title: 'Tab B', + window: winB + }); + tabGroup = Ti.UI.createTabGroup({ + activeTab: 1 + }); + // Does windows fire this event? + // Can we test this without even opening tab group? + tabGroup.addEventListener('open', () => { + try { + should(tabGroup.activeTab.title).be.a.String(); // undefined on Android! + should(tabGroup.activeTab.title).eql('Tab B'); + } catch (err) { + return finish(err); + } finally { + tabGroup.removeTab(tabA); + tabGroup.removeTab(tabB); + } + finish(); + }); + + tabGroup.addTab(tabA); + tabGroup.addTab(tabB); + tabGroup.open(); + }); }); - should(tabGroup.tintColor).be.a.String(); - should(tabGroup.tintColor).eql('red'); - }); + describe('.activeTintColor', () => { + beforeEach(() => { + tabGroup = Ti.UI.createTabGroup({ + title: 'TabGroup', + activeTintColor: 'red' + }); + }); - it('.activeTintColor', () => { - tabGroup = Ti.UI.createTabGroup({ - title: 'TabGroup', - activeTintColor: 'red' + it('is a String', () => { + should(tabGroup).have.property('activeTintColor').which.is.a.String(); + }); + + it('equals value passed to factory method', () => { + should(tabGroup.activeTintColor).eql('red'); + }); + + it('can be assigned a String value', () => { + tabGroup.activeTintColor = 'blue'; + should(tabGroup.activeTintColor).eql('blue'); + }); + + it.androidBroken('has accessors', () => { // Windows are created during open + should(tabGroup).have.accessors('activeTintColor'); + }); }); - should(tabGroup.activeTintColor).be.a.String(); - should(tabGroup.activeTintColor).eql('red'); - }); + describe.ios('.allowUserCustomization', () => { + beforeEach(() => { + tabGroup = Ti.UI.createTabGroup(); + }); - it.windowsBroken('add Map.View to TabGroup', function (finish) { - if (isCI && utilities.isMacOS()) { // FIXME: On macOS CI (maybe < 10.15.6?), the mapView compelete event never fires! Does app need explicit focus added? - return finish(); // FIXME: skip when we move to official mocha package - } + it('is a Boolean', () => { + should(tabGroup.allowUserCustomization).be.a.Boolean(); + }); - this.slow(5000); - this.timeout(15000); + it('defaults to true', () => { + should(tabGroup.allowUserCustomization).be.true(); + }); - const map = require('ti.map'); - const mapView = map.createView({ top: 0, height: '80%' }); - mapView.addEventListener('complete', function listener () { - mapView.removeEventListener('complete', listener); - finish(); + it('can be assigned a Boolean value', () => { + tabGroup.allowUserCustomization = false; + should(tabGroup.allowUserCustomization).be.false(); + }); + + it('has accessors', () => { + should(tabGroup).have.accessors('allowUserCustomization'); + }); }); - const win = Ti.UI.createWindow(); - win.add(mapView); + describe('.barColor', () => { + beforeEach(() => { + tabGroup = Ti.UI.createTabGroup({ + title: 'TabGroup', + barColor: 'red' + }); + }); - tabGroup = Ti.UI.createTabGroup(); - const tab = Ti.UI.createTab({ - title: 'Tab', - window: win - }); + it('is a String', () => { + should(tabGroup).have.property('barColor').which.is.a.String(); + }); - tabGroup.addTab(tab); - tabGroup.open(); - }); + it('equals value passed to factory method', () => { + should(tabGroup.barColor).eql('red'); + }); - it.ios('.tabs', () => { - const win = Ti.UI.createWindow(); - tabGroup = Ti.UI.createTabGroup(); - const tab = Ti.UI.createTab({ - title: 'Tab', - window: win + it('can be assigned a String value', () => { + tabGroup.barColor = 'blue'; + should(tabGroup.barColor).eql('blue'); + }); + + it.androidBroken('has accessors', () => { // Windows are created during open + should(tabGroup).have.accessors('barColor'); + }); }); - tabGroup.addTab(tab); - should(tabGroup.tabs.length).eql(1); - tabGroup.removeTab(tab); - should(tabGroup.tabs.length).eql(0); - }); + describe('.tabs', () => { + beforeEach(() => { + tabGroup = Ti.UI.createTabGroup(); + }); - it.ios('.allowUserCustomization', () => { - const win = Ti.UI.createWindow(); - tabGroup = Ti.UI.createTabGroup({ - allowUserCustomization: true - }); - const tab = Ti.UI.createTab({ - title: 'Tab', - window: win - }); + it.iosBroken('is an Array', () => { // undfined on iOS? + should(tabGroup).have.property('tabs').which.is.an.Array(); + }); - tabGroup.addTab(tab); - should(tabGroup.allowUserCustomization).be.true(); - tabGroup.setAllowUserCustomization(false); - should(tabGroup.allowUserCustomization).be.false(); - }); + it('can be assigned an Array of Ti.UI.Tabs', () => { + const winA = Ti.UI.createWindow(), + tabA = Ti.UI.createTab({ + title: 'Tab A', + window: winA + }), + winB = Ti.UI.createWindow(), + tabB = Ti.UI.createTab({ + title: 'Tab B', + window: winB + }); + + tabGroup.tabs = [ tabA, tabB ]; + should(tabGroup.tabs).eql([ tabA, tabB ]); + }); - it.ios('.tabsTranslucent', () => { - const win = Ti.UI.createWindow(); - tabGroup = Ti.UI.createTabGroup({ - tabsTranslucent: true + it.androidBroken('has accessors', () => { // Windows are created during open + should(tabGroup).have.accessors('barColor'); + }); + + it('#addTab() and #removeTab() affect value', () => { + const win = Ti.UI.createWindow(); + const tab = Ti.UI.createTab({ + title: 'Tab', + window: win + }); + + tabGroup.addTab(tab); + should(tabGroup.tabs.length).eql(1); + tabGroup.removeTab(tab); + should(tabGroup.tabs.length).eql(0); + }); }); - const tab = Ti.UI.createTab({ - title: 'Tab', - window: win + + describe.ios('.tabsTranslucent', () => { + beforeEach(() => { + tabGroup = Ti.UI.createTabGroup(); + }); + + it.iosBroken('is a Boolean', () => { // defaults to undefined! + should(tabGroup.tabsTranslucent).be.a.Boolean(); + }); + + it.iosBroken('defaults to true', () => { // defaults to undefined! + should(tabGroup.tabsTranslucent).be.true(); + }); + + it('can be assigned a Boolean value', () => { + tabGroup.tabsTranslucent = false; + should(tabGroup.tabsTranslucent).be.false(); + }); + + it('has accessors', () => { + should(tabGroup).have.accessors('tabsTranslucent'); + }); }); - tabGroup.addTab(tab); - should(tabGroup.tabsTranslucent).be.true(); - tabGroup.setTabsTranslucent(false); - should(tabGroup.tabsTranslucent).be.false(); - }); + describe('.tintColor', () => { + beforeEach(() => { + tabGroup = Ti.UI.createTabGroup({ + title: 'TabGroup', + tintColor: 'red' + }); + }); - it.windowsBroken('#setTabs()', () => { - const winA = Ti.UI.createWindow(), - tabA = Ti.UI.createTab({ - title: 'Tab A', - window: winA - }), - winB = Ti.UI.createWindow(), - tabB = Ti.UI.createTab({ - title: 'Tab B', - window: winB + it('is a String', () => { + should(tabGroup).have.property('tintColor').which.is.a.String(); }); - tabGroup = Ti.UI.createTabGroup(); - tabGroup.setTabs([ tabA, tabB ]); - should(tabGroup.getTabs()).eql([ tabA, tabB ]); - }); + it('equals value passed to factory method', () => { + should(tabGroup.tintColor).eql('red'); + }); - it.windowsBroken('#setActiveTab()', finish => { - const winA = Ti.UI.createWindow(), - tabA = Ti.UI.createTab({ - title: 'Tab A', - window: winA - }), - winB = Ti.UI.createWindow(), - tabB = Ti.UI.createTab({ - title: 'Tab B', - window: winB + it('can be assigned a String value', () => { + tabGroup.tintColor = 'blue'; + should(tabGroup.tintColor).eql('blue'); }); - tabGroup = Ti.UI.createTabGroup(); - // Does windows fire this event? - // Can we test this without even opening tab group? - tabGroup.addEventListener('open', () => { - try { - tabGroup.setActiveTab(tabB); - should(tabGroup.getActiveTab().title).be.a.String(); - should(tabGroup.getActiveTab().title).eql('Tab B'); - finish(); - } catch (err) { - finish(err); - } finally { - tabGroup.removeTab(tabA); - tabGroup.removeTab(tabB); - } + it.androidBroken('has accessors', () => { // Windows are created during open + should(tabGroup).have.accessors('tintColor'); + }); }); - tabGroup.addTab(tabA); - tabGroup.addTab(tabB); - tabGroup.open(); + describe('.title', () => { + it.androidBroken('is a String', () => { + tabGroup = Ti.UI.createTabGroup({ + title: 'My title' + }); + should(tabGroup.title).be.a.String(); // null on Android! + }); + + it.androidBroken('equals value passed in to creation dictionary', () => { + tabGroup = Ti.UI.createTabGroup({ + title: 'My title' + }); + should(tabGroup.title).eql('My title'); // null on Android! + }); + + it('has accessors', () => { + tabGroup = Ti.UI.createTabGroup(); + should(tabGroup).have.accessors('title'); + }); + + it('assign after drawing the TabGroup', () => { + const winA = Ti.UI.createWindow(), + winB = Ti.UI.createWindow(), + tabA = Ti.UI.createTab({ title: 'titleA', window: winA }), + tabB = Ti.UI.createTab({ title: 'titleB', window: winB }); + tabGroup = Ti.UI.createTabGroup({ tabs: [ tabA, tabB ] }); + tabGroup.addEventListener('open', () => { + tabGroup.title = 'newTitle'; + tabGroup.activeTab = tabB; + }); + tabB.addEventListener('selected', () => { + should(tabGroup.title).be.a.String(); + should(tabGroup.title).eql('newTitle'); + }); + }); + }); }); - it.android('#disableTabNavigation()', function (finish) { - const winA = Ti.UI.createWindow(), - tabA = Ti.UI.createTab({ - title: 'Tab A', - window: winA - }), - winB = Ti.UI.createWindow(), - tabB = Ti.UI.createTab({ - title: 'Tab B', - window: winB + describe('methods', () => { + it.android('#disableTabNavigation()', function (finish) { + const winA = Ti.UI.createWindow(), + tabA = Ti.UI.createTab({ + title: 'Tab A', + window: winA + }), + winB = Ti.UI.createWindow(), + tabB = Ti.UI.createTab({ + title: 'Tab B', + window: winB + }); + this.timeout(5000); + tabGroup = Ti.UI.createTabGroup(); + + // does windows fire this event? + tabGroup.addEventListener('open', () => { + try { + tabGroup.disableTabNavigation(true); + tabGroup.activeTab = tabB; + should(tabGroup.activeTab.title).be.a.String(); + should(tabGroup.activeTab.title).eql('Tab A'); + tabGroup.disableTabNavigation(false); + tabGroup.activeTab = tabB; + should(tabGroup.activeTab.title).be.a.String(); + should(tabGroup.activeTab.title).eql('Tab B'); + } catch (err) { + return finish(err); + } finally { + tabGroup.removeTab(tabA); + tabGroup.removeTab(tabB); + } + finish(); }); - this.timeout(5000); - tabGroup = Ti.UI.createTabGroup(); - // does windows fire this event? - tabGroup.addEventListener('open', () => { - try { - tabGroup.disableTabNavigation(true); - tabGroup.setActiveTab(tabB); - should(tabGroup.getActiveTab().title).be.a.String(); - should(tabGroup.getActiveTab().title).eql('Tab A'); - tabGroup.disableTabNavigation(false); - tabGroup.setActiveTab(tabB); - should(tabGroup.getActiveTab().title).be.a.String(); - should(tabGroup.getActiveTab().title).eql('Tab B'); - } catch (err) { - return finish(err); - } finally { - tabGroup.removeTab(tabA); - tabGroup.removeTab(tabB); - } + tabGroup.addTab(tabA); + tabGroup.addTab(tabB); + tabGroup.open(); + }); + }); + + it.windowsBroken('add Map.View to TabGroup', function (finish) { + if (isCI && utilities.isMacOS()) { // FIXME: On macOS CI (maybe < 10.15.6?), the mapView compelete event never fires! Does app need explicit focus added? + return finish(); // FIXME: skip when we move to official mocha package + } + + this.slow(5000); + this.timeout(15000); + + const map = require('ti.map'); + const mapView = map.createView({ top: 0, height: '80%' }); + mapView.addEventListener('complete', function listener () { + mapView.removeEventListener('complete', listener); finish(); }); - tabGroup.addTab(tabA); - tabGroup.addTab(tabB); - tabGroup.open(); - }); + const win = Ti.UI.createWindow(); + win.add(mapView); - it('.title', () => { - tabGroup = Ti.UI.createTabGroup({ - title: 'My title' + tabGroup = Ti.UI.createTabGroup(); + const tab = Ti.UI.createTab({ + title: 'Tab', + window: win }); - should(tabGroup.getTitle()).be.a.String(); - should(tabGroup.getTitle()).eql('My title'); + tabGroup.addTab(tab); + tabGroup.open(); }); describe('events', function () { @@ -334,190 +510,6 @@ describe('Titanium.UI.TabGroup', function () { }); }); - it.windowsBroken('#setActiveTab()', finish => { - const winA = Ti.UI.createWindow(), - tabA = Ti.UI.createTab({ - title: 'Tab A', - window: winA - }), - winB = Ti.UI.createWindow(), - tabB = Ti.UI.createTab({ - title: 'Tab B', - window: winB - }); - tabGroup = Ti.UI.createTabGroup(); - - // Does windows fire this event? - // Can we test this without even opening tab group? - tabGroup.addEventListener('open', () => { - try { - tabGroup.setActiveTab(tabB); - should(tabGroup.getActiveTab().title).be.a.String(); - should(tabGroup.getActiveTab().title).eql('Tab B'); - } catch (err) { - return finish(err); - } finally { - tabGroup.removeTab(tabA); - tabGroup.removeTab(tabB); - } - finish(); - }); - - tabGroup.addTab(tabA); - tabGroup.addTab(tabB); - tabGroup.open(); - }); - - it.windowsBroken('#setActiveTab()_before_open', finish => { - const winA = Ti.UI.createWindow(), - tabA = Ti.UI.createTab({ - title: 'Tab A', - window: winA - }), - winB = Ti.UI.createWindow(), - tabB = Ti.UI.createTab({ - title: 'Tab B', - window: winB - }); - tabGroup = Ti.UI.createTabGroup(); - tabGroup.setActiveTab(1); - // Does windows fire this event? - // Can we test this without even opening tab group? - tabGroup.addEventListener('open', () => { - try { - should(tabGroup.getActiveTab().title).be.a.String(); - should(tabGroup.getActiveTab().title).eql('Tab B'); - } catch (err) { - return finish(err); - } finally { - tabGroup.removeTab(tabA); - tabGroup.removeTab(tabB); - } - finish(); - }); - - tabGroup.addTab(tabA); - tabGroup.addTab(tabB); - tabGroup.open(); - }); - - it.windowsBroken('#change_activeTab_property', finish => { - const winA = Ti.UI.createWindow(), - tabA = Ti.UI.createTab({ - title: 'Tab A', - window: winA - }), - winB = Ti.UI.createWindow(), - tabB = Ti.UI.createTab({ - title: 'Tab B', - window: winB - }); - tabGroup = Ti.UI.createTabGroup(); - - // Does windows fire this event? - // Can we test this without even opening tab group? - tabGroup.addEventListener('open', () => { - try { - tabGroup.activeTab = tabB; - should(tabGroup.getActiveTab().title).be.a.String(); - should(tabGroup.getActiveTab().title).eql('Tab B'); - } catch (err) { - return finish(err); - } finally { - tabGroup.removeTab(tabA); - tabGroup.removeTab(tabB); - } - finish(); - }); - - tabGroup.addTab(tabA); - tabGroup.addTab(tabB); - tabGroup.open(); - }); - - it.windowsBroken('#change_activeTab_property_before_open', finish => { - const winA = Ti.UI.createWindow(), - tabA = Ti.UI.createTab({ - title: 'Tab A', - window: winA - }), - winB = Ti.UI.createWindow(), - tabB = Ti.UI.createTab({ - title: 'Tab B', - window: winB - }); - tabGroup = Ti.UI.createTabGroup(); - tabGroup.activeTab = 1; - // Does windows fire this event? - // Can we test this without even opening tab group? - tabGroup.addEventListener('open', () => { - try { - should(tabGroup.getActiveTab().title).be.a.String(); - should(tabGroup.getActiveTab().title).eql('Tab B'); - } catch (err) { - return finish(err); - } finally { - tabGroup.removeTab(tabA); - tabGroup.removeTab(tabB); - } - finish(); - }); - - tabGroup.addTab(tabA); - tabGroup.addTab(tabB); - tabGroup.open(); - }); - - it.windowsBroken('#set_activeTab_in_creation_dictionary', finish => { - const winA = Ti.UI.createWindow(), - tabA = Ti.UI.createTab({ - title: 'Tab A', - window: winA - }), - winB = Ti.UI.createWindow(), - tabB = Ti.UI.createTab({ - title: 'Tab B', - window: winB - }); - tabGroup = Ti.UI.createTabGroup({ - activeTab: 1 - }); - // Does windows fire this event? - // Can we test this without even opening tab group? - tabGroup.addEventListener('open', () => { - try { - should(tabGroup.getActiveTab().title).be.a.String(); - should(tabGroup.getActiveTab().title).eql('Tab B'); - } catch (err) { - return finish(err); - } finally { - tabGroup.removeTab(tabA); - tabGroup.removeTab(tabB); - } - finish(); - }); - - tabGroup.addTab(tabA); - tabGroup.addTab(tabB); - tabGroup.open(); - }); - - it('title after drawing the TabGroup', () => { - const winA = Ti.UI.createWindow(), - winB = Ti.UI.createWindow(), - tabA = Ti.UI.createTab({ title: 'titleA', window: winA }), - tabB = Ti.UI.createTab({ title: 'titleB', window: winB }), - tabGroup = Ti.UI.createTabGroup({ tabs: [ tabA, tabB ] }); - tabGroup.addEventListener('open', () => { - tabGroup.title = 'newTitle'; - tabGroup.setActiveTab(tabB); - }); - tabB.addEventListener('selected', () => { - should(tabGroup.title).be.a.String(); - should(tabGroup.title).eql('newTitle'); - }); - }); - it('icon-only tabs - default style', finish => { this.timeout(5000); tabGroup = Ti.UI.createTabGroup({ diff --git a/tests/Resources/ti.ui.tableview.test.js b/tests/Resources/ti.ui.tableview.test.js index 02de6be48fb..f7587142808 100644 --- a/tests/Resources/ti.ui.tableview.test.js +++ b/tests/Resources/ti.ui.tableview.test.js @@ -1047,7 +1047,7 @@ describe('Titanium.UI.TableView', function () { isFocused = true; try { - searchBar.setValue('e'); + searchBar.value = 'e'; searchBar.focus(); should(tableView.sections[0].rowCount).be.eql(3); tableView.deleteRow(0); @@ -1457,6 +1457,7 @@ describe('Titanium.UI.TableView', function () { tableView.setData([ row ]); + // FIXME: Times out on ios? row.addEventListener('postlayout', () => { try { should(row.rect.height).be.eql(150); diff --git a/tests/Resources/ti.ui.textarea.test.js b/tests/Resources/ti.ui.textarea.test.js index 29a69bd988c..9ce4f777331 100644 --- a/tests/Resources/ti.ui.textarea.test.js +++ b/tests/Resources/ti.ui.textarea.test.js @@ -6,13 +6,14 @@ */ /* eslint-env titanium, mocha */ /* eslint no-unused-expressions: "off" */ +/* eslint mocha/no-identical-title: "off" */ 'use strict'; const should = require('./utilities/assertions'); const utilities = require('./utilities/utilities'); const isCI = Ti.App.Properties.getBool('isCI', false); -describe('Titanium.UI.TextArea', function () { +describe('Titanium.UI.TextArea', () => { let win; afterEach(done => { // fires after every test in sub-suites too... if (win && !win.closed) { @@ -28,77 +29,309 @@ describe('Titanium.UI.TextArea', function () { } }); - it('apiName', function () { - const textArea = Ti.UI.createTextArea({ - value: 'this is some text' + describe('properties', () => { + describe('.apiName', () => { + it('is a String', () => { + const textField = Ti.UI.createTextArea({}); + should(textField).have.readOnlyProperty('apiName').which.is.a.String(); + }); + + it('equals \'Ti.UI.TextField\'', () => { + const textField = Ti.UI.createTextArea({}); + should(textField.apiName).be.eql('Ti.UI.TextArea'); + }); }); - should(textArea).have.readOnlyProperty('apiName').which.is.a.String(); - should(textArea.apiName).be.eql('Ti.UI.TextArea'); - }); - it('value', function () { - const textArea = Ti.UI.createTextArea({ - value: 'this is some text' + describe('.backgroundColor', () => { + let textArea; + beforeEach(() => { + textArea = Ti.UI.createTextArea({ backgroundColor: 'red' }); + }); + + it('is a String', () => { + should(textArea).have.property('backgroundColor').which.is.a.String(); + }); + + it('equals value passed to factory method', () => { + should(textArea.backgroundColor).eql('red'); + }); + + it('can be assigned a String value', () => { + textArea.backgroundColor = 'blue'; + should(textArea.backgroundColor).eql('blue'); + }); + + it('has accessors', () => { + should(textArea).have.accessors('backgroundColor'); + }); }); - should(textArea.value).be.a.String(); - should(textArea.getValue).be.a.Function(); - should(textArea.value).eql('this is some text'); - should(textArea.getValue()).eql('this is some text'); - textArea.value = 'other text'; - should(textArea.value).eql('other text'); - should(textArea.getValue()).eql('other text'); - }); - it('editable', function () { - const textArea = Ti.UI.createTextArea(); - should(textArea.editable).be.a.Boolean(); - should(textArea.editable).be.true(); - textArea.setEditable(false); - should(textArea.editable).be.false(); - }); + describe('.editable', () => { + let textArea; + beforeEach(() => { + textArea = Ti.UI.createTextArea(); + }); - it.ios('scrollsToTop', function () { - const textArea = Ti.UI.createTextArea(); - should(textArea.scrollsToTop).be.a.Boolean(); - should(textArea.scrollsToTop).be.true(); - textArea.setScrollsToTop(false); - should(textArea.scrollsToTop).be.false(); - }); + it('is a Boolean', () => { + should(textArea.editable).be.a.Boolean(); + }); - it('backgroundColor', function () { - const textArea = Ti.UI.createTextArea({ backgroundColor: 'red' }); - should(textArea.backgroundColor).be.a.String(); - should(textArea.backgroundColor).eql('red'); - textArea.setBackgroundColor('white'); - should(textArea.backgroundColor).eql('white'); - }); + it('defaults to true', () => { + should(textArea.editable).be.true(); + }); + + it('can be assigned a Boolean value', () => { + textArea.editable = false; + should(textArea.editable).be.false(); + }); + + it('has accessors', () => { + should(textArea).have.accessors('editable'); + }); + }); + + it('.focused', function (done) { + this.slow(2000); + this.timeout(5000); - it.windowsMissing('padding', function () { - const textArea = Ti.UI.createTextArea({ - value: 'this is some text', - padding: { - left: 20, - right: 20 + win = Ti.UI.createWindow({ backgroundColor: '#fff' }); + const textarea = Ti.UI.createTextArea({ + backgroundColor: '#fafafa', + color: 'green', + width: 250, + height: 40 + }); + win.add(textarea); + try { + textarea.should.have.a.property('focused').which.is.a.Boolean(); + textarea.focused.should.be.false(); // haven't opened it yet, so shouldn't be focused + textarea.addEventListener('focus', () => { + try { + textarea.focused.should.be.true(); + } catch (e) { + return done(e); + } + win.close(); + }); + win.addEventListener('open', () => { + textarea.focus(); // force focus! + }); + win.addEventListener('close', () => { + try { + // we've been closed (or are closing?) so hopefully shouldn't say that we're focused + textarea.focused.should.be.false(); + } catch (e) { + return done(e); + } + done(); + }); + win.open(); + } catch (e) { + return done(e); } }); - should(textArea.padding).be.a.Object(); - should(textArea.getPadding).be.a.Function(); - should(textArea.setPadding).be.a.Function(); - - should(textArea.padding.left).eql(20); - should(textArea.padding.right).eql(20); - should(textArea.getPadding().left).eql(20); - should(textArea.getPadding().right).eql(20); - - textArea.setPadding({ - left: 10, - right: 10 + + describe.android('.lines', () => { + let textArea; + beforeEach(() => { + textArea = Ti.UI.createTextArea({ + lines: 1, + maxLines: 5 + }); + }); + + it('is a Number', () => { + should(textArea.lines).be.a.Number(); + }); + + it('equals value passed to factory method', () => { + should(textArea.lines).eql(1); + }); + + it('can be assigned an Integer value', () => { + textArea.lines = 2; + should(textArea.lines).eql(2); + }); + + it('has accessors', () => { + should(textArea).have.accessors('lines'); + }); + }); + + describe.android('.maxLines', () => { + let textArea; + beforeEach(() => { + textArea = Ti.UI.createTextArea({ + lines: 1, + maxLines: 5 + }); + }); + + it('is a Number', () => { + should(textArea.maxLines).be.a.Number(); + }); + + it('equals value passed to factory method', () => { + should(textArea.maxLines).eql(5); + }); + + it('can be assigned an Integer value', () => { + textArea.maxLines = 6; + should(textArea.maxLines).eql(6); + }); + + it('has accessors', () => { + should(textArea).have.accessors('maxLines'); + }); + }); + + describe.windowsMissing('.padding', () => { + let textArea; + beforeEach(() => { + textArea = Ti.UI.createTextArea({ + value: 'this is some text', + padding: { + left: 20, + right: 20 + } + }); + }); + + it('is an Object', () => { + should(textArea).have.property('padding').which.is.an.Object(); + }); + + it('equals value passed to factory method', () => { + should(textArea.padding.left).eql(20); + should(textArea.padding.right).eql(20); + }); + + it('can be assigned an Object value', () => { + textArea.padding = { + left: 10, + right: 10 + }; + + should(textArea.padding.left).eql(10); + should(textArea.padding.right).eql(10); + }); + + it('has accessors', () => { + should(textArea).have.accessors('padding'); + }); + }); + + describe.ios('.scrollsToTop', () => { + let textArea; + beforeEach(() => { + textArea = Ti.UI.createTextArea(); + }); + + it('is a Boolean', () => { + should(textArea.scrollsToTop).be.a.Boolean(); + }); + + it('defaults to true', () => { + should(textArea.scrollsToTop).be.true(); + }); + + it('can be assigned a Boolean value', () => { + textArea.scrollsToTop = false; + should(textArea.scrollsToTop).be.false(); + }); + + it('has accessors', () => { + should(textArea).have.accessors('scrollsToTop'); + }); + }); + + describe('.textAlign', () => { + let textArea; + beforeEach(() => { + textArea = Ti.UI.createTextArea({ + value: 'this is some text', + textAlign: Titanium.UI.TEXT_ALIGNMENT_CENTER + }); + }); + + // TODO: Check default is left! + + it('is a String on Android, Number on iOS', () => { + // FIXME Parity issue! + if (utilities.isAndroid()) { + should(textArea.textAlign).be.a.String(); + } else { + should(textArea.textAlign).be.a.Number(); + } + }); + + it('equals value passed to factory method', () => { + should(textArea.textAlign).eql(Titanium.UI.TEXT_ALIGNMENT_CENTER); + }); + + it('can be assigned a constant value', () => { + textArea.textAlign = Titanium.UI.TEXT_ALIGNMENT_RIGHT; + should(textArea.textAlign).eql(Titanium.UI.TEXT_ALIGNMENT_RIGHT); + }); + + it('has accessors', () => { + should(textArea).have.accessors('textAlign'); + }); + }); + + describe('.value', () => { + let textArea; + beforeEach(() => { + textArea = Ti.UI.createTextArea({ + value: 'this is some text' + }); + }); + + it('is a String', () => { + should(textArea.value).be.a.String(); + }); + + it('equals value passed to factory method', () => { + should(textArea.value).eql('this is some text'); + }); + + it('can be assigned a String value', () => { + textArea.value = 'other text'; + should(textArea.value).eql('other text'); + }); + + it('has accessors', () => { + should(textArea).have.accessors('value'); + }); }); - should(textArea.padding.left).eql(10); - should(textArea.padding.right).eql(10); - should(textArea.getPadding().left).eql(10); - should(textArea.getPadding().right).eql(10); + // FIXME: test was pegged to ios, but docs say Android-only! + describe.android('.verticalAlign', () => { + let textArea; + beforeEach(() => { + textArea = Ti.UI.createTextArea({ + value: 'this is some text', + verticalAlign: Ti.UI.TEXT_VERTICAL_ALIGNMENT_BOTTOM + }); + }); + + it('is a String', () => { + should(textArea.verticalAlign).be.a.String(); + }); + + it('equals value passed to factory method', () => { + should(textArea.verticalAlign).eql(Titanium.UI.TEXT_VERTICAL_ALIGNMENT_BOTTOM); + }); + + it('can be assigned a constant value', () => { + textArea.verticalAlign = Titanium.UI.TEXT_VERTICAL_ALIGNMENT_TOP; + should(textArea.verticalAlign).eql(Titanium.UI.TEXT_VERTICAL_ALIGNMENT_TOP); + }); + + it('has accessors', () => { + should(textArea).have.accessors('verticalAlign'); + }); + }); }); // Tests adding and removing a TextArea's focus. @@ -140,51 +373,6 @@ describe('Titanium.UI.TextArea', function () { win.open(); }); - it.ios('textAlign', function () { - const textArea = Ti.UI.createTextArea({ - value: 'this is some text', - textAlign: Titanium.UI.TEXT_ALIGNMENT_CENTER - }); - should(textArea.textAlign).be.a.Number(); - should(textArea.getTextAlign).be.a.Function(); - should(textArea.textAlign).eql(Titanium.UI.TEXT_ALIGNMENT_CENTER); - should(textArea.getTextAlign()).eql(Titanium.UI.TEXT_ALIGNMENT_CENTER); - textArea.textAlign = Titanium.UI.TEXT_ALIGNMENT_RIGHT; - should(textArea.textAlign).eql(Titanium.UI.TEXT_ALIGNMENT_RIGHT); - should(textArea.getTextAlign()).eql(Titanium.UI.TEXT_ALIGNMENT_RIGHT); - }); - - it.ios('verticalAlign', function () { - const textArea = Ti.UI.createTextArea({ - value: 'this is some text', - verticalAlign: Titanium.UI.TEXT_VERTICAL_ALIGNMENT_BOTTOM - }); - should(textArea.verticalAlign).be.a.Number(); - should(textArea.getVerticalAlign).be.a.Function(); - should(textArea.verticalAlign).eql(Titanium.UI.TEXT_VERTICAL_ALIGNMENT_BOTTOM); - should(textArea.getVerticalAlign()).eql(Titanium.UI.TEXT_VERTICAL_ALIGNMENT_BOTTOM); - textArea.verticalAlign = Titanium.UI.TEXT_VERTICAL_ALIGNMENT_TOP; - should(textArea.verticalAlign).eql(Titanium.UI.TEXT_VERTICAL_ALIGNMENT_TOP); - should(textArea.getVerticalAlign()).eql(Titanium.UI.TEXT_VERTICAL_ALIGNMENT_TOP); - }); - - it.android('lines', function () { - const textArea = Ti.UI.createTextArea({ - lines: 1, - maxLines: 5 - }); - should(textArea.lines).be.a.Number(); - should(textArea.getLines).be.a.Function(); - should(textArea.maxLines).be.a.Number(); - should(textArea.getMaxLines).be.a.Function(); - should(textArea.getLines()).eql(1); - should(textArea.maxLines).eql(5); - textArea.lines = 2; - textArea.setMaxLines(6); - should(textArea.lines).eql(2); - should(textArea.getMaxLines()).eql(6); - }); - // Tests adding and removing a TextArea's focus. it('textArea in tabGroup', function (finish) { if (isCI && utilities.isMacOS()) { // FIXME: On macOS CI (maybe < 10.15.6?), times out! Does app need explicit focus added? @@ -235,45 +423,4 @@ describe('Titanium.UI.TextArea', function () { tabGroup.open(); }); - - it('.focused', function (done) { - this.slow(2000); - this.timeout(5000); - - win = Ti.UI.createWindow({ backgroundColor: '#fff' }); - const textarea = Ti.UI.createTextArea({ - backgroundColor: '#fafafa', - color: 'green', - width: 250, - height: 40 - }); - win.add(textarea); - try { - textarea.should.have.a.property('focused').which.is.a.Boolean(); - textarea.focused.should.be.false(); // haven't opened it yet, so shouldn't be focused - textarea.addEventListener('focus', () => { - try { - textarea.focused.should.be.true(); - } catch (e) { - return done(e); - } - win.close(); - }); - win.addEventListener('open', () => { - textarea.focus(); // force focus! - }); - win.addEventListener('close', () => { - try { - // we've been closed (or are closing?) so hopefully shouldn't say that we're focused - textarea.focused.should.be.false(); - } catch (e) { - return done(e); - } - done(); - }); - win.open(); - } catch (e) { - return done(e); - } - }); }); diff --git a/tests/Resources/ti.ui.textfield.test.js b/tests/Resources/ti.ui.textfield.test.js index f3022fc680f..6bbf980a898 100644 --- a/tests/Resources/ti.ui.textfield.test.js +++ b/tests/Resources/ti.ui.textfield.test.js @@ -6,11 +6,12 @@ */ /* eslint-env titanium, mocha */ /* eslint no-unused-expressions: "off" */ +/* eslint mocha/no-identical-title: "off" */ 'use strict'; const should = require('./utilities/assertions'); const utilities = require('./utilities/utilities'); -describe('Titanium.UI.TextField', function () { +describe('Titanium.UI.TextField', () => { let win; afterEach(done => { // fires after every test in sub-suites too... if (win && !win.closed) { @@ -26,385 +27,484 @@ describe('Titanium.UI.TextField', function () { } }); - it('apiName', function () { - const textField = Ti.UI.createTextField({ - value: 'this is some text' - }); - should(textField).have.readOnlyProperty('apiName').which.is.a.String(); - should(textField.apiName).be.eql('Ti.UI.TextField'); - }); + describe('properties', () => { + describe('.apiName', () => { + it('is a String', () => { + const textField = Ti.UI.createTextField({}); + should(textField).have.readOnlyProperty('apiName').which.is.a.String(); + }); - it('value', function () { - const textfield = Ti.UI.createTextField({ - value: 'this is some text' + it('equals \'Ti.UI.TextField\'', () => { + const textField = Ti.UI.createTextField({}); + should(textField.apiName).be.eql('Ti.UI.TextField'); + }); }); - should(textfield.value).be.a.String(); - should(textfield.getValue).be.a.Function(); - should(textfield.value).eql('this is some text'); - should(textfield.getValue()).eql('this is some text'); - textfield.value = 'other text'; - should(textfield.value).eql('other text'); - should(textfield.getValue()).eql('other text'); - }); - // Skip on Windows Phone since not available, yet - it.windowsMissing('padding', function () { - const textfield = Ti.UI.createTextField({ - value: 'this is some text', - padding: { - left: 20, - right: 20 + it('.focused', done => { + win = Ti.UI.createWindow({ backgroundColor: '#fff' }); + const textField = Ti.UI.createTextField({ + backgroundColor: '#fafafa', + color: 'green', + width: 250, + height: 40 + }); + win.add(textField); + try { + textField.should.have.a.property('focused').which.is.a.Boolean(); + textField.focused.should.be.false(); // haven't opened it yet, so shouldn't be focused + textField.addEventListener('focus', () => { + try { + textField.focused.should.be.true(); + } catch (e) { + return done(e); + } + win.close(); + }); + win.addEventListener('open', () => { + textField.focus(); // force focus! + }); + win.addEventListener('close', () => { + try { + // we've been closed (or are closing?) so hopefully shouldn't say that we're focused + textField.focused.should.be.false(); + } catch (e) { + return done(e); + } + done(); + }); + win.open(); + } catch (e) { + return done(e); } }); - should(textfield.padding).be.a.Object(); - should(textfield.getPadding).be.a.Function(); - should(textfield.setPadding).be.a.Function(); - - should(textfield.padding.left).eql(20); - should(textfield.padding.right).eql(20); - should(textfield.getPadding().left).eql(20); - should(textfield.getPadding().right).eql(20); - - textfield.setPadding({ - left: 10, - right: 10 - }); - should(textfield.padding.left).eql(10); - should(textfield.padding.right).eql(10); - should(textfield.getPadding().left).eql(10); - should(textfield.getPadding().right).eql(10); - }); + // FIXME Intermittently failing on Android on build machine, I think due to test timeout + it.androidBroken('.height', function (finish) { + this.timeout(5000); + const textField = Ti.UI.createTextField({ + value: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec nec ullamcorper massa, eget tempor sapien. Phasellus nisi metus, tempus a magna nec, ultricies rutrum lacus. Aliquam sit amet augue suscipit, dignissim tellus eu, consectetur elit. Praesent ligula velit, blandit vel urna sit amet, suscipit euismod nunc.', + width: Ti.UI.SIZE, + height: Ti.UI.SIZE, + color: 'black' + }); + const bgView = Ti.UI.createView({ + width: 200, + height: 100, + backgroundColor: 'red' + }); + win = Ti.UI.createWindow({ + backgroundColor: '#eee' + }); + bgView.add(textField); + win.add(bgView); - it('textAlign', function () { - const textfield = Ti.UI.createTextField({ - value: 'this is some text', - textAlign: Titanium.UI.TEXT_ALIGNMENT_CENTER + win.addEventListener('focus', function () { + try { + should(bgView.height).be.eql(100); + should(textField.height).not.be.greaterThan(100); + } catch (err) { + return finish(err); + } + finish(); + }); + win.open(); }); - // FIXME Parity issue! - if (utilities.isAndroid()) { - should(textfield.textAlign).be.a.String(); - } else { - should(textfield.textAlign).be.a.Number(); - } - should(textfield.getTextAlign).be.a.Function(); - should(textfield.textAlign).eql(Titanium.UI.TEXT_ALIGNMENT_CENTER); - should(textfield.getTextAlign()).eql(Titanium.UI.TEXT_ALIGNMENT_CENTER); - textfield.textAlign = Titanium.UI.TEXT_ALIGNMENT_RIGHT; - should(textfield.textAlign).eql(Titanium.UI.TEXT_ALIGNMENT_RIGHT); - should(textfield.getTextAlign()).eql(Titanium.UI.TEXT_ALIGNMENT_RIGHT); - }); - it('verticalAlign', function () { - const textfield = Ti.UI.createTextField({ - value: 'this is some text', - verticalAlign: Titanium.UI.TEXT_VERTICAL_ALIGNMENT_BOTTOM - }); - // FIXME Parity issue! - if (utilities.isAndroid()) { - should(textfield.verticalAlign).be.a.String(); - } else { - should(textfield.verticalAlign).be.a.Number(); - } - should(textfield.getVerticalAlign).be.a.Function(); - should(textfield.verticalAlign).eql(Titanium.UI.TEXT_VERTICAL_ALIGNMENT_BOTTOM); - should(textfield.getVerticalAlign()).eql(Titanium.UI.TEXT_VERTICAL_ALIGNMENT_BOTTOM); - textfield.verticalAlign = Titanium.UI.TEXT_VERTICAL_ALIGNMENT_TOP; - should(textfield.verticalAlign).eql(Titanium.UI.TEXT_VERTICAL_ALIGNMENT_TOP); - should(textfield.getVerticalAlign()).eql(Titanium.UI.TEXT_VERTICAL_ALIGNMENT_TOP); - }); + describe('.hintText', () => { + let textField; + beforeEach(() => { + textField = Ti.UI.createTextField({ + hintText: 'Enter E-Mail ...' + }); + }); - // FIXME Defaults to undefined on Android. Docs say default is false - it.androidBroken('passwordMask', function () { - const text = 'this is some text', - textfield = Ti.UI.createTextField({ - value: text - }); - // passwordMask should default to false - should(textfield.passwordMask).be.false(); // undefined on Android - textfield.passwordMask = true; - should(textfield.passwordMask).be.true(); - // it should have same text before - should(textfield.value).be.eql(text); - }); + it('is a String', () => { + should(textField).have.property('hintText').which.is.a.String(); + }); - // TODO Add tests for: - // autocapitalize - // autocorrect - // borderStyle - // clearonEdit - // color - // editable - // enableReturnKey - // font - // keyboardType - // maxLength - // returnKeyType - // selection - // suppressReturn - - it('hintText', function () { - const textfield = Ti.UI.createTextField({ - hintText: 'Enter E-Mail ...' - }); - should(textfield.getHintText).be.a.Function(); - should(textfield.hintText).eql('Enter E-Mail ...'); - should(textfield.getHintText()).eql('Enter E-Mail ...'); - textfield.hintText = 'Enter Name ...'; - should(textfield.hintText).eql('Enter Name ...'); - should(textfield.getHintText()).eql('Enter Name ...'); - }); + it('equals value passed to factory method', () => { + should(textField.hintText).eql('Enter E-Mail ...'); + }); - it.windowsMissing('hintTextColor', function () { - const textfield = Ti.UI.createTextField({ - hintText: 'Enter E-Mail ...', - hintTextColor: 'red' - }); - should(textfield.getHintTextColor).be.a.Function(); - should(textfield.hintTextColor).eql('red'); - should(textfield.getHintTextColor()).eql('red'); - textfield.hintTextColor = 'blue'; - should(textfield.hintTextColor).eql('blue'); - should(textfield.getHintTextColor()).eql('blue'); - }); + it('can be assigned a String value', () => { + textField.hintText = 'Enter Name ...'; + should(textField.hintText).eql('Enter Name ...'); + }); - it.android('hintType', function () { - const textfield = Ti.UI.createTextField({ - hintText: 'Enter E-Mail ...', - hintType: Ti.UI.HINT_TYPE_ANIMATED + it('has accessors', () => { + should(textField).have.accessors('hintText'); + }); }); - should(textfield.getHintType).be.a.Function(); - should(textfield.hintType).eql(Ti.UI.HINT_TYPE_ANIMATED); - should(textfield.getHintType()).eql(Ti.UI.HINT_TYPE_ANIMATED); - textfield.hintType = Ti.UI.HINT_TYPE_STATIC; - should(textfield.hintType).eql(Ti.UI.HINT_TYPE_STATIC); - should(textfield.getHintType()).eql(Ti.UI.HINT_TYPE_STATIC); - }); - it('width', function (finish) { - this.timeout(5000); - const textfield = Ti.UI.createTextField({ - value: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec nec ullamcorper massa, eget tempor sapien. Phasellus nisi metus, tempus a magna nec, ultricies rutrum lacus. Aliquam sit amet augue suscipit, dignissim tellus eu, consectetur elit. Praesent ligula velit, blandit vel urna sit amet, suscipit euismod nunc.', - width: Ti.UI.SIZE - }); - win = Ti.UI.createWindow({ - backgroundColor: '#ddd' - }); - win.add(textfield); - win.addEventListener('postlayout', function listener () { - win.removeEventListener('postlayout', listener); - try { - should(win.rect.width).be.greaterThan(100); - should(textfield.rect.width).not.be.greaterThan(win.rect.width); - } catch (err) { - return finish(err); - } - finish(); - }); - win.open(); - }); + describe.windowsMissing('.hintTextColor', () => { + let textField; + beforeEach(() => { + textField = Ti.UI.createTextField({ + hintText: 'Enter E-Mail ...', + hintTextColor: 'red' + }); + }); - // FIXME Intermittently failing on Android on build machine, I think due to test timeout - it.androidBroken('height', function (finish) { - this.timeout(5000); - const textfield = Ti.UI.createTextField({ - value: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec nec ullamcorper massa, eget tempor sapien. Phasellus nisi metus, tempus a magna nec, ultricies rutrum lacus. Aliquam sit amet augue suscipit, dignissim tellus eu, consectetur elit. Praesent ligula velit, blandit vel urna sit amet, suscipit euismod nunc.', - width: Ti.UI.SIZE, - height: Ti.UI.SIZE, - color: 'black' - }); - const bgView = Ti.UI.createView({ - width: 200, - height: 100, - backgroundColor: 'red' - }); - win = Ti.UI.createWindow({ - backgroundColor: '#eee' - }); - bgView.add(textfield); - win.add(bgView); + it('is a String', () => { + should(textField).have.property('hintTextColor').which.is.a.String(); + }); - win.addEventListener('focus', function () { - try { - should(bgView.height).be.eql(100); - should(textfield.height).not.be.greaterThan(100); - } catch (err) { - return finish(err); - } - finish(); - }); - win.open(); - }); + it('equals value passed to factory method', () => { + should(textField.hintTextColor).eql('red'); + }); - // Tests adding and removing a TextField's focus. - it.ios('focus-blur', function (finish) { - this.timeout(5000); - win = Ti.UI.createWindow({ layout: 'vertical' }); + it('can be assigned a String value', () => { + textField.hintTextColor = 'blue'; + should(textField.hintTextColor).eql('blue'); + }); - // First TextField is needed to receive default focus on startup - // and to receive focus when second TextField has lost focus. - let textField = Ti.UI.createTextField({ - width: Ti.UI.FILL, - height: Ti.UI.SIZE, + it('has accessors', () => { + should(textField).have.accessors('hintTextColor'); + }); }); - win.add(textField); - // Second TextField is used to test focus/blur handling. - textField = Ti.UI.createTextField({ - width: Ti.UI.FILL, - height: Ti.UI.SIZE, + describe.android('.hintType', () => { + let textField; + beforeEach(() => { + textField = Ti.UI.createTextField({ + hintText: 'Enter E-Mail ...', + hintType: Ti.UI.HINT_TYPE_ANIMATED + }); + }); + + it('is a Number', () => { + should(textField).have.property('hintType').which.is.a.Number(); + }); + + it('equals value passed to factory method', () => { + should(textField.hintType).eql(Ti.UI.HINT_TYPE_ANIMATED); + }); + + it('can be assigned a constant value', () => { + textField.hintType = Ti.UI.HINT_TYPE_STATIC; + should(textField.hintType).eql(Ti.UI.HINT_TYPE_STATIC); + }); + + it('has accessors', () => { + should(textField).have.accessors('hintType'); + }); }); - textField.addEventListener('focus', function () { - // Focus has been received. Now test removing focus. - setTimeout(() => textField.blur(), 500); + + // Skip on Windows Phone since not available, yet + describe.windowsMissing('.padding', () => { + let textField; + beforeEach(() => { + textField = Ti.UI.createTextField({ + value: 'this is some text', + padding: { + left: 20, + right: 20 + } + }); + }); + + it('is an Object', () => { + should(textField).have.property('padding').which.is.an.Object(); + }); + + it('equals value passed to factory method', () => { + should(textField.padding.left).eql(20); + should(textField.padding.right).eql(20); + }); + + it('can be assigned an Object value', () => { + textField.padding = { + left: 10, + right: 10 + }; + + should(textField.padding.left).eql(10); + should(textField.padding.right).eql(10); + }); + + it('has accessors', () => { + should(textField).have.accessors('padding'); + }); }); - textField.addEventListener('blur', function () { - // Focus has been lost. The test was finished successfully. (Timeout means failure.) - finish(); + + // FIXME Defaults to undefined on Android. Docs say default is false + it.androidBroken('.passwordMask', function () { + const text = 'this is some text', + textField = Ti.UI.createTextField({ + value: text + }); + // passwordMask should default to false + should(textField.passwordMask).be.false(); // undefined on Android + textField.passwordMask = true; + should(textField.passwordMask).be.true(); + // it should have same text before + should(textField.value).be.eql(text); + }); + + it.ios('.passwordRules', () => { + const textField = Ti.UI.createTextField({ + passwordMask: true, + passwordRules: 'required: upper; required: lower; required: digit; max-consecutive: 2' + }); + should(textField.passwordRules).equal('required: upper; required: lower; required: digit; max-consecutive: 2'); }); - win.add(textField); - // Start the test when the window has been opened. - win.addEventListener('postlayout', function listener () { - win.removeEventListener('postlayout', listener); + describe('.textAlign', () => { + let textField; + beforeEach(() => { + textField = Ti.UI.createTextField({ + value: 'this is some text', + textAlign: Titanium.UI.TEXT_ALIGNMENT_CENTER + }); + }); - setTimeout(() => textField.focus(), 500); - }); - win.open(); - }); + it('is a String on Android, Number on iOS', () => { + // FIXME Parity issue! + if (utilities.isAndroid()) { + should(textField.textAlign).be.a.String(); + } else { + should(textField.textAlign).be.a.Number(); + } + }); + + it('equals value passed to factory method', () => { + should(textField.textAlign).eql(Titanium.UI.TEXT_ALIGNMENT_CENTER); + }); - // TextField must not receive focus by default upon opening a window. - it('focus-win-open', function (finish) { - this.timeout(5000); + it('can be assigned a constant value', () => { + textField.textAlign = Titanium.UI.TEXT_ALIGNMENT_RIGHT; + should(textField.textAlign).eql(Titanium.UI.TEXT_ALIGNMENT_RIGHT); + }); - win = Ti.UI.createWindow(); - const textField = Ti.UI.createTextField(); - textField.addEventListener('focus', function () { - // This should never happen. - finish(new Error('TextField wrongly received focus on open.')); + it('has accessors', () => { + should(textField).have.accessors('textAlign'); + }); }); - win.add(textField); - win.addEventListener('postlayout', function listener () { - win.removeEventListener('postlayout', listener); - // If we made it this far, assume TextField did not receive focus. - finish(); + + describe('.value', () => { + let textField; + beforeEach(() => { + textField = Ti.UI.createTextField({ + value: 'this is some text' + }); + }); + + it('is a String', () => { + should(textField.value).be.a.String(); + }); + + it('equals value passed to factory method', () => { + should(textField.value).eql('this is some text'); + }); + + it('can be assigned a constant value', () => { + textField.value = 'other text'; + should(textField.value).eql('other text'); + }); + + it('has accessors', () => { + should(textField).have.accessors('value'); + }); }); - win.open(); - }); - // The "focus" and "blur" events are not supposed to bubble up the view hierarchy. - // Windows ticket TIMOB-26177 - // Android intermittently fails (but quite often) - it.androidAndWindowsBroken('focus-blur-bubbles', function (finish) { - this.timeout(5000); + describe('.verticalAlign', () => { + let textField; + beforeEach(() => { + textField = Ti.UI.createTextField({ + value: 'this is some text', + verticalAlign: Titanium.UI.TEXT_VERTICAL_ALIGNMENT_BOTTOM + }); + }); - win = Ti.UI.createWindow(); - const textField = Ti.UI.createTextField(); - textField.addEventListener('focus', function (e) { - try { - should(e.bubbles).be.be.false(); - textField.blur(); - } catch (err) { - return finish(err); - } + it('is a String on Android, Number on iOS', () => { + // FIXME Parity issue! + if (utilities.isAndroid()) { + should(textField.verticalAlign).be.a.String(); + } else { + should(textField.verticalAlign).be.a.Number(); + } + }); + + it('equals value passed to factory method', () => { + should(textField.verticalAlign).eql(Titanium.UI.TEXT_VERTICAL_ALIGNMENT_BOTTOM); + }); + + it('can be assigned a constant value', () => { + textField.verticalAlign = Titanium.UI.TEXT_VERTICAL_ALIGNMENT_TOP; + should(textField.verticalAlign).eql(Titanium.UI.TEXT_VERTICAL_ALIGNMENT_TOP); + }); + + it('has accessors', () => { + should(textField).have.accessors('verticalAlign'); + }); }); - textField.addEventListener('blur', function (e) { - try { - should(e.bubbles).be.be.false(); - } catch (err) { - return finish(err); - } - finish(); + + // TODO Add tests for: + // autocapitalize + // autocorrect + // borderStyle + // clearonEdit + // color + // editable + // enableReturnKey + // font + // keyboardType + // maxLength + // returnKeyType + // selection + // suppressReturn + + it('.width', function (finish) { + this.timeout(5000); + const textField = Ti.UI.createTextField({ + value: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec nec ullamcorper massa, eget tempor sapien. Phasellus nisi metus, tempus a magna nec, ultricies rutrum lacus. Aliquam sit amet augue suscipit, dignissim tellus eu, consectetur elit. Praesent ligula velit, blandit vel urna sit amet, suscipit euismod nunc.', + width: Ti.UI.SIZE + }); + win = Ti.UI.createWindow({ + backgroundColor: '#ddd' + }); + win.add(textField); + win.addEventListener('postlayout', function listener () { + win.removeEventListener('postlayout', listener); + try { + should(win.rect.width).be.greaterThan(100); + should(textField.rect.width).not.be.greaterThan(win.rect.width); + } catch (err) { + return finish(err); + } + finish(); + }); + win.open(); }); - win.add(textField); - win.addEventListener('open', () => textField.focus()); - win.open(); }); - it.ios('.passwordRules', function () { - const textField = Ti.UI.createTextField({ - passwordMask: true, - passwordRules: 'required: upper; required: lower; required: digit; max-consecutive: 2' + describe('methods', () => { + it.ios('#hasText()', () => { + win = Ti.UI.createWindow(); + const textFieldA = Ti.UI.createTextField({ + top: '60dip', + value: 0 + }); + + win.add(textFieldA); + + const textFieldB = Ti.UI.createTextField({ + top: '120dip', + value: 0 + }); + + win.add(textFieldB); + + should(textFieldA.hasText()).be.true(); + should(textFieldB.hasText()).be.true(); }); - should(textField.passwordRules).equal('required: upper; required: lower; required: digit; max-consecutive: 2'); }); - it.ios('#hasText()', () => { - win = Ti.UI.createWindow(); - const textFieldA = Ti.UI.createTextField({ - top: '60dip', - value: 0 + describe('events', () => { + // TextField should not receive change event after setting value. + it.ios('change event should not fire after setting textField value', function (finish) { + this.timeout(5000); + + win = Ti.UI.createWindow(); + const textField = Ti.UI.createTextField({ + value: 123 + }); + textField.addEventListener('change', function () { + // This should never happen. + finish(new Error('TextField wrongly received change on setting value.')); + }); + win.add(textField); + win.addEventListener('postlayout', function listener () { + win.removeEventListener('postlayout', listener); + // If we made it this far, assume TextField did not receive change. + finish(); + }); + win.open(); }); - win.add(textFieldA); + // Tests adding and removing a TextField's focus. + it.ios('focus-blur', function (finish) { + this.timeout(5000); + win = Ti.UI.createWindow({ layout: 'vertical' }); - const textFieldB = Ti.UI.createTextField({ - top: '120dip', - value: 0 - }); + // First TextField is needed to receive default focus on startup + // and to receive focus when second TextField has lost focus. + let textField = Ti.UI.createTextField({ + width: Ti.UI.FILL, + height: Ti.UI.SIZE, + }); + win.add(textField); - win.add(textFieldB); + // Second TextField is used to test focus/blur handling. + textField = Ti.UI.createTextField({ + width: Ti.UI.FILL, + height: Ti.UI.SIZE, + }); + textField.addEventListener('focus', function () { + // Focus has been received. Now test removing focus. + setTimeout(() => textField.blur(), 500); + }); + textField.addEventListener('blur', function () { + // Focus has been lost. The test was finished successfully. (Timeout means failure.) + finish(); + }); + win.add(textField); - should(textFieldA.hasText()).be.true(); - should(textFieldB.hasText()).be.true(); - }); + // Start the test when the window has been opened. + win.addEventListener('postlayout', function listener () { + win.removeEventListener('postlayout', listener); - it('.focused', done => { - win = Ti.UI.createWindow({ backgroundColor: '#fff' }); - const textfield = Ti.UI.createTextField({ - backgroundColor: '#fafafa', - color: 'green', - width: 250, - height: 40 + setTimeout(() => textField.focus(), 500); + }); + win.open(); }); - win.add(textfield); - try { - textfield.should.have.a.property('focused').which.is.a.Boolean(); - textfield.focused.should.be.false(); // haven't opened it yet, so shouldn't be focused - textfield.addEventListener('focus', () => { + + // TextField must not receive focus by default upon opening a window. + it('focus-win-open', function (finish) { + this.timeout(5000); + + win = Ti.UI.createWindow(); + const textField = Ti.UI.createTextField(); + textField.addEventListener('focus', function () { + // This should never happen. + finish(new Error('TextField wrongly received focus on open.')); + }); + win.add(textField); + win.addEventListener('postlayout', function listener () { + win.removeEventListener('postlayout', listener); + // If we made it this far, assume TextField did not receive focus. + finish(); + }); + win.open(); + }); + + // The "focus" and "blur" events are not supposed to bubble up the view hierarchy. + // Windows ticket TIMOB-26177 + // Android intermittently fails (but quite often) + it.androidAndWindowsBroken('focus-blur-bubbles', function (finish) { + this.timeout(5000); + + win = Ti.UI.createWindow(); + const textField = Ti.UI.createTextField(); + textField.addEventListener('focus', function (e) { try { - textfield.focused.should.be.true(); - } catch (e) { - return done(e); + should(e.bubbles).be.be.false(); + textField.blur(); + } catch (err) { + return finish(err); } - win.close(); - }); - win.addEventListener('open', () => { - textfield.focus(); // force focus! }); - win.addEventListener('close', () => { + textField.addEventListener('blur', function (e) { try { - // we've been closed (or are closing?) so hopefully shouldn't say that we're focused - textfield.focused.should.be.false(); - } catch (e) { - return done(e); + should(e.bubbles).be.be.false(); + } catch (err) { + return finish(err); } - done(); + finish(); }); + win.add(textField); + win.addEventListener('open', () => textField.focus()); win.open(); - } catch (e) { - return done(e); - } - }); - - // TextField should not receive change event after setting value. - it.ios('change event should not fire after setting textField value', function (finish) { - this.timeout(5000); - - win = Ti.UI.createWindow(); - const textField = Ti.UI.createTextField({ - value: 123 - }); - textField.addEventListener('change', function () { - // This should never happen. - finish(new Error('TextField wrongly received change on setting value.')); - }); - win.add(textField); - win.addEventListener('postlayout', function listener () { - win.removeEventListener('postlayout', listener); - // If we made it this far, assume TextField did not receive change. - finish(); }); - win.open(); }); }); diff --git a/tests/Resources/ti.ui.view.test.js b/tests/Resources/ti.ui.view.test.js index aabd6d54e2a..a6a0dcf1c50 100644 --- a/tests/Resources/ti.ui.view.test.js +++ b/tests/Resources/ti.ui.view.test.js @@ -21,13 +21,13 @@ describe('Titanium.UI.View', function () { this.slow(2000); this.timeout(10000); - before(function (finish) { + before(finish => { rootWindow = Ti.UI.createWindow(); rootWindow.addEventListener('open', () => finish()); rootWindow.open(); }); - after(function (finish) { + after(finish => { rootWindow.addEventListener('close', () => finish()); rootWindow.close(); }); @@ -753,12 +753,9 @@ describe('Titanium.UI.View', function () { try { should(view.parent).be.an.Object(); should(view.parent).eql(win); - should(view.getParent).be.a.Function(); - should(view.setParent).be.a.Function(); - should(view.getParent()).eql(win); // parent is not read-only - view.setParent(null); + view.parent = null; should.not.exist(view.parent); } catch (err) { return finish(err); @@ -994,7 +991,7 @@ describe('Titanium.UI.View', function () { should(label.accessibilityHint).eql('Hint'); should(label.accessibilityHidden).be.true(); - label.setAccessibilityLabel('New Text'); + label.accessibilityLabel = 'New Text'; label.accessibilityValue = 'New Value'; label.accessibilityHint = 'New Hint'; label.accessibilityHidden = false; diff --git a/tests/Resources/ti.ui.webview.test.js b/tests/Resources/ti.ui.webview.test.js index 29c9e75e768..e0af41f7bab 100644 --- a/tests/Resources/ti.ui.webview.test.js +++ b/tests/Resources/ti.ui.webview.test.js @@ -94,7 +94,7 @@ describe('Titanium.UI.WebView', function () { win.open(); }); - it.ios('keyboardDisplayRequiresUserAction', function (finish) { + it.ios('.keyboardDisplayRequiresUserAction', function (finish) { win = Ti.UI.createWindow(); const webView = Ti.UI.createWebView(); @@ -103,16 +103,12 @@ describe('Titanium.UI.WebView', function () { webView.keyboardDisplayRequiresUserAction = true; should(webView.keyboardDisplayRequiresUserAction).be.a.Boolean(); - should(webView.getKeyboardDisplayRequiresUserAction()).be.a.Boolean(); should(webView.keyboardDisplayRequiresUserAction).be.true(); - should(webView.getKeyboardDisplayRequiresUserAction()).be.true(); - webView.setKeyboardDisplayRequiresUserAction(false); + webView.keyboardDisplayRequiresUserAction = false; should(webView.keyboardDisplayRequiresUserAction).be.a.Boolean(); - should(webView.getKeyboardDisplayRequiresUserAction()).be.a.Boolean(); should(webView.keyboardDisplayRequiresUserAction).be.false(); - should(webView.getKeyboardDisplayRequiresUserAction()).be.false(); } catch (err) { return finish(err); } @@ -144,7 +140,7 @@ describe('Titanium.UI.WebView', function () { }); // TIMOB-23542 webview data test - it('data', function (finish) { + it('.data', function (finish) { win = Ti.UI.createWindow({ backgroundColor: 'blue' }); @@ -169,7 +165,7 @@ describe('Titanium.UI.WebView', function () { // FIXME Parity issue! Windows require second argument which is callback function. Other platforms return value sync! // FIXME Android returns null? // FIXME Sometimes times out on iOS. Not really sure why... - (((utilities.isWindows10() && utilities.isWindowsDesktop()) || utilities.isAndroid() || utilities.isIOS()) ? it.skip : it)('evalJS', function (finish) { + it.allBroken('evalJS', function (finish) { win = Ti.UI.createWindow({ backgroundColor: 'blue' }); diff --git a/tests/Resources/ti.ui.window.test.js b/tests/Resources/ti.ui.window.test.js index d391c0b60ba..45b6ad6727e 100644 --- a/tests/Resources/ti.ui.window.test.js +++ b/tests/Resources/ti.ui.window.test.js @@ -7,12 +7,11 @@ /* global OS_IOS */ /* eslint-env mocha */ /* eslint no-unused-expressions: "off" */ +/* eslint mocha/no-identical-title: "off" */ 'use strict'; const should = require('./utilities/assertions'); const utilities = require('./utilities/utilities'); -const isCI = Ti.App.Properties.getBool('isCI', false); - describe('Titanium.UI.Window', function () { this.timeout(5000); @@ -31,299 +30,689 @@ describe('Titanium.UI.Window', function () { } }); - it('.title', () => { - win = Ti.UI.createWindow({ - title: 'this is some text' + describe('properties', () => { + it.android('.barColor with disabled ActionBar', finish => { + win = Ti.UI.createWindow({ + barColor: 'blue', + title: 'My Title', + theme: 'Theme.Titanium.NoTitleBar', + }); + win.add(Ti.UI.createLabel({ text: 'Window Title Test' })); + win.open(); + win.addEventListener('open', () => finish()); }); - should(win.title).be.a.String(); - should(win.getTitle).be.a.Function(); - should(win.title).eql('this is some text'); - should(win.getTitle()).eql('this is some text'); - win.title = 'other text'; - should(win.title).eql('other text'); - should(win.getTitle()).eql('other text'); - }); - it('.titleid', () => { - win = Ti.UI.createWindow({ - titleid: 'this_is_my_key' + it('.closed', done => { + win = Ti.UI.createWindow({ + backgroundColor: '#0000ff' + }); + win.closed.should.be.true(); // it's not yet opened, so treat as closed + win.addEventListener('open', function openListener () { + win.removeEventListener('open', openListener); + try { + win.closed.should.be.false(); // we're being notified the window is open, so should report closed as false! + } catch (e) { + return done(e); + } + done(); + }); + win.open(); + win.closed.should.be.false(); // should be open now }); - should(win.titleid).be.a.String(); - should(win.getTitleid).be.a.Function(); - should(win.titleid).eql('this_is_my_key'); - should(win.getTitleid()).eql('this_is_my_key'); - should(win.title).eql('this is my value'); - win.titleid = 'other text'; - should(win.titleid).eql('other text'); - should(win.getTitleid()).eql('other text'); - should(win.title).eql('this is my value'); // FIXME Windows: https://jira.appcelerator.org/browse/TIMOB-23498 - }); - // TODO: Why not run this on iOS? Seems to fail, though. - // TODO: Also broken on Android, need to figure out why this test is unreliable. - describe.allBroken('.orientationModes', () => { - this.slow(5000); - this.timeout(20000); + it.ios('.extendSafeArea', function (finish) { + this.timeout(5000); + // TODO: Add more unit tests related to top, bottom, left, right margins of win.safeAreaView. + win = Ti.UI.createWindow({ + backgroundColor: 'gray', + extendSafeArea: false + }); + + win.addEventListener('open', function () { + try { + should(win.safeAreaView).be.a.Object(); + } catch (e) { + return finish(e); + } + finish(); + }); + + win.open(); + }); - function doOrientationModeTest(orientation, finish) { + it('.focused', done => { win = Ti.UI.createWindow({ - orientationModes: [ orientation ] + backgroundColor: '#0000ff' }); - win.addEventListener('open', () => { - setTimeout(() => { - try { - win.orientationModes.should.have.length(1); - win.orientationModes[0].should.eql(orientation); - win.orientation.should.eql(orientation); - } catch (e) { - return finish(e); - } - finish(); - }, 1000); + win.focused.should.be.false(); // haven't opened it yet, so shouldn't be focused + win.addEventListener('focus', function focusListener() { + win.removeEventListener('focus', focusListener); + try { + win.focused.should.be.true(); + win.close(); + } catch (e) { + return done(e); + } + }); + win.addEventListener('close', function closeListener () { + win.removeEventListener('close', closeListener); + try { + // we've been closed (or are closing?) so hopefully shouldn't say that we're focused + win.focused.should.be.false(); + } catch (e) { + return done(e); + } + done(); }); win.open(); - } + }); // For reference, Android fires open event and then focus event - it('PORTRAIT', finish => { - doOrientationModeTest(Ti.UI.PORTRAIT, finish); - }); + describe.ios('.hidesBackButton', () => { + beforeEach(() => { + win = Ti.UI.createWindow({}); + }); - it('LANDSCAPE_LEFT', finish => { - doOrientationModeTest(Ti.UI.LANDSCAPE_LEFT, finish); - }); + it('is a Boolean', () => { + should(win.hidesBackButton).be.a.Boolean(); + }); - it('LANDSCAPE_RIGHT', finish => { - doOrientationModeTest(Ti.UI.LANDSCAPE_RIGHT, finish); - }); - }); + it('defaults to false', () => { + should(win.hidesBackButton).be.false(); + }); - it.ios('.leftNavButtons and .rightNavButtons', finish => { - const rightButton1 = Ti.UI.createButton({ - title: 'Right1', - color: 'green', - }); + it('has accessors', () => { + should(win).have.accessors('hidesBackButton'); + }); - const rightButton2 = Ti.UI.createButton({ - title: 'Right2', - color: 'green', + // TODO: Add snapshot test }); - const leftButton = Ti.UI.createButton({ - title: 'Left', - color: 'blue' - }); + describe.ios('.homeIndicatorAutoHidden', () => { + beforeEach(() => { + win = Ti.UI.createWindow({ + title: 'this is some text' + }); + }); - const rootWindow = Ti.UI.createWindow({ - backgroundColor: 'white', - leftNavButtons: [ leftButton ], - rightNavButtons: [ rightButton1, rightButton2 ], - }); + it('is a Boolean', () => { + should(win.homeIndicatorAutoHidden).be.a.Boolean(); + }); + + it('defaults to false', () => { + should(win.homeIndicatorAutoHidden).be.false(); + }); + + it('can be assigned a Boolean value', () => { + win.homeIndicatorAutoHidden = true; + should(win.homeIndicatorAutoHidden).be.true(); + }); - win = Ti.UI.createNavigationWindow({ - window: rootWindow + it('has accessors', () => { + should(win).have.accessors('homeIndicatorAutoHidden'); + }); }); - win.open(); + describe.ios('.largeTitleEnabled', () => { + beforeEach(() => { + win = Ti.UI.createWindow({ + title: 'this is some text', + largeTitleEnabled: true + }); + }); - rootWindow.addEventListener('focus', function focus() { - rootWindow.removeEventListener('focus', focus); - try { - should(rootWindow.rightNavButtons).be.an.Array(); - should(rootWindow.rightNavButtons.length).be.eql(2); - rootWindow.rightNavButtons = [ rightButton1 ]; - should(rootWindow.rightNavButtons.length).be.eql(1); - - should(rootWindow.leftNavButtons).be.an.Array(); - should(rootWindow.leftNavButtons.length).be.eql(1); - } catch (e) { - return finish(e); - } - finish(); + it('is a Boolean', () => { + should(win.largeTitleEnabled).be.a.Boolean(); + }); + + it('has value given in factory method dictionary', () => { + should(win.largeTitleEnabled).be.true(); + }); + + it('can be assigned a Boolean value', () => { + win.largeTitleEnabled = false; + should(win.largeTitleEnabled).be.false(); + }); + + it('has accessors', () => { + should(win).have.accessors('largeTitleEnabled'); + }); }); - }); - it.ios('.leftNavButton with default color (no color value) and .rightNavButton with tintColor', finish => { - // TO DO: Snapshots for different iPads are different. Can not test with static image. - // Probably try with snapshot comparision (with and without color) at run time - if (utilities.isMacOS() || utilities.isIPad()) { - return finish(); // how to skip for iPad? - } + describe.ios('.largeTitleDisplayMode', () => { + beforeEach(() => { + win = Ti.UI.createWindow({ + title: 'this is some text', + largeTitleDisplayMode: Ti.UI.iOS.LARGE_TITLE_DISPLAY_MODE_ALWAYS + }); + }); + + it('is a Number', () => { + should(win.largeTitleDisplayMode).be.a.Number(); + }); + + it('has value given in factory method dictionary', () => { + should(win.largeTitleDisplayMode).eql(Ti.UI.iOS.LARGE_TITLE_DISPLAY_MODE_ALWAYS); + }); - const rightButton = Ti.UI.createButton({ - title: 'Right', - tintColor: 'green', + it('can be assigned a constant value', () => { + win.largeTitleDisplayMode = Ti.UI.iOS.LARGE_TITLE_DISPLAY_MODE_AUTOMATIC; + should(win.largeTitleDisplayMode).eql(Ti.UI.iOS.LARGE_TITLE_DISPLAY_MODE_AUTOMATIC); + }); + + it('has accessors', () => { + should(win).have.accessors('largeTitleDisplayMode'); + }); }); - const leftButton = Ti.UI.createButton({ - title: 'Left', + it.ios('.leftNavButtons and .rightNavButtons', finish => { + const rightButton1 = Ti.UI.createButton({ + title: 'Right1', + color: 'green', + }); + + const rightButton2 = Ti.UI.createButton({ + title: 'Right2', + color: 'green', + }); + + const leftButton = Ti.UI.createButton({ + title: 'Left', + color: 'blue' + }); + + const rootWindow = Ti.UI.createWindow({ + backgroundColor: 'white', + leftNavButtons: [ leftButton ], + rightNavButtons: [ rightButton1, rightButton2 ], + }); + + win = Ti.UI.createNavigationWindow({ + window: rootWindow + }); + + win.open(); + + rootWindow.addEventListener('focus', function focus() { + rootWindow.removeEventListener('focus', focus); + try { + should(rootWindow.rightNavButtons).be.an.Array(); + should(rootWindow.rightNavButtons.length).be.eql(2); + rootWindow.rightNavButtons = [ rightButton1 ]; + should(rootWindow.rightNavButtons.length).be.eql(1); + + should(rootWindow.leftNavButtons).be.an.Array(); + should(rootWindow.leftNavButtons.length).be.eql(1); + } catch (e) { + return finish(e); + } + finish(); + }); }); - const rootWindow = Ti.UI.createWindow({ - backgroundColor: 'white', - leftNavButton: leftButton, - rightNavButton: rightButton, + it.ios('.leftNavButton with default color (no color value) and .rightNavButton with tintColor', finish => { + // TO DO: Snapshots for different iPads are different. Can not test with static image. + // Probably try with snapshot comparision (with and without color) at run time + if (utilities.isMacOS() || utilities.isIPad()) { + return finish(); // how to skip for iPad? + } + + const rightButton = Ti.UI.createButton({ + title: 'Right', + tintColor: 'green', + }); + + const leftButton = Ti.UI.createButton({ + title: 'Left', + }); + + const rootWindow = Ti.UI.createWindow({ + backgroundColor: 'white', + leftNavButton: leftButton, + rightNavButton: rightButton, + }); + + win = Ti.UI.createNavigationWindow({ + height: '400px', + width: '400px', + window: rootWindow + }); + + win.open(); + + rootWindow.addEventListener('postlayout', function postlayout() { + rootWindow.removeEventListener('postlayout', postlayout); + setTimeout(function () { + try { + should(rootWindow.leftNavButton).be.an.Object(); + should(rootWindow.rightNavButton).be.an.Object(); + should(win).matchImage('snapshots/navButton_left_defaultColor_right_greenColor.png', { maxPixelMismatch: OS_IOS ? 27 : 0 }); // iphone XR differs by 27 pixels + } catch (e) { + return finish(e); + } + finish(); + }, 10); + }); }); - win = Ti.UI.createNavigationWindow({ - height: '400px', - width: '400px', - window: rootWindow + it.ios('.leftNavButton and .rightNavButton with color and tintColor', finish => { + // TO DO: Snapshots for different iPads are different. Can not test with static image. + // Probably try with snapshot comparision (with and without color) at run time + if (utilities.isMacOS() || utilities.isIPad()) { + return finish(); // how to skip for iPad? + } + + const rightButton = Ti.UI.createButton({ + title: 'Right', + tintColor: 'red', + color: 'green', // should have preference + }); + + const leftButton = Ti.UI.createButton({ + title: 'Left', + tintColor: 'red' + }); + + const rootWindow = Ti.UI.createWindow({ + backgroundColor: 'white', + leftNavButton: leftButton, + rightNavButton: rightButton, + }); + + win = Ti.UI.createNavigationWindow({ + height: '400px', + width: '400px', + window: rootWindow + }); + + win.open(); + + rootWindow.addEventListener('postlayout', function postlayout() { + rootWindow.removeEventListener('postlayout', postlayout); + setTimeout(function () { + try { + should(rootWindow.leftNavButton).be.an.Object(); + should(rootWindow.rightNavButton).be.an.Object(); + should(win).matchImage('snapshots/navButton_left_redColor_right_greenColor.png', { maxPixelMismatch: OS_IOS ? 27 : 0 }); // iphone XR differs by 27 pixels + } catch (e) { + return finish(e); + } + finish(); + }, 10); + }); }); - win.open(); + it.androidAndWindowsBroken('.rect is read-only', finish => { + win = Ti.UI.createWindow({ + backgroundColor: 'green', + left: 100, + right: 100 + }); + win.addEventListener('postlayout', function listener () { + win.removeEventListener('postlayout', listener); - rootWindow.addEventListener('postlayout', function postlayout() { - rootWindow.removeEventListener('postlayout', postlayout); - setTimeout(function () { try { - should(rootWindow.leftNavButton).be.an.Object(); - should(rootWindow.rightNavButton).be.an.Object(); - should(win).matchImage('snapshots/navButton_left_defaultColor_right_greenColor.png', { maxPixelMismatch: OS_IOS ? 27 : 0 }); // iphone XR differs by 27 pixels - } catch (e) { - return finish(e); + win.rect.x.should.eql(100); // FiXME: get 0 on Android + win.rect.y.should.eql(0); + const width = win.rect.width; + const height = win.rect.height; + + // try to change the rect + win.rect.x = 120; + win.rect.y = 5; + win.rect.width = 10; + win.rect.height = 5; + + // shouldn't actually change + win.rect.x.should.eql(100); + win.rect.y.should.eql(0); + win.rect.width.should.eql(width); + win.rect.height.should.eql(height); + } catch (err) { + return finish(err); } finish(); - }, 10); + }); + win.open(); }); - }); - it.ios('.leftNavButton and .rightNavButton with color and tintColor', finish => { - // TO DO: Snapshots for different iPads are different. Can not test with static image. - // Probably try with snapshot comparision (with and without color) at run time - if (utilities.isMacOS() || utilities.isIPad()) { - return finish(); // how to skip for iPad? - } + it('.safeAreaPadding with extendSafeArea false', function (finish) { + this.slow(5000); - const rightButton = Ti.UI.createButton({ - title: 'Right', - tintColor: 'red', - color: 'green', // should have preference + win = Ti.UI.createWindow({ + extendSafeArea: false, + }); + win.addEventListener('postlayout', function listener () { + win.removeEventListener('postlayout', listener); + + try { + const padding = win.safeAreaPadding; + should(padding).be.an.Object(); + should(padding.left).be.eql(0); + should(padding.top).be.eql(0); + should(padding.right).be.eql(0); + should(padding.bottom).be.eql(0); + } catch (err) { + return finish(err); + } + finish(); + }); + win.open(); }); - const leftButton = Ti.UI.createButton({ - title: 'Left', - tintColor: 'red' + // This test will only pass on Android 4.4 and higher since older versions do not support translucent bars. + it.android('.safeAreaPadding with extendSafeArea true', function (finish) { + this.slow(5000); + + win = Ti.UI.createWindow({ + extendSafeArea: true, + theme: 'Theme.Titanium.NoTitleBar', + orientationModes: [ Ti.UI.PORTRAIT ], + windowFlags: Ti.UI.Android.FLAG_TRANSLUCENT_STATUS | Ti.UI.Android.FLAG_TRANSLUCENT_NAVIGATION + }); + win.addEventListener('postlayout', function listener () { + win.removeEventListener('postlayout', listener); + + try { + const padding = win.safeAreaPadding; + should(padding).be.an.Object(); + should(padding.top).be.aboveOrEqual(0); + should(padding.bottom).be.aboveOrEqual(0); + should(padding.left).be.aboveOrEqual(0); + should(padding.right).be.aboveOrEqual(0); + } catch (err) { + return finish(err); + } + finish(); + }); + win.open(); }); - const rootWindow = Ti.UI.createWindow({ - backgroundColor: 'white', - leftNavButton: leftButton, - rightNavButton: rightButton, + it.ios('.safeAreaPadding for window inside navigation window with extendSafeArea true', finish => { + const window = Ti.UI.createWindow({ + extendSafeArea: true, + }); + win = Ti.UI.createNavigationWindow({ + window: window + }); + window.addEventListener('postlayout', function listener () { + window.removeEventListener('postlayout', listener); + + try { + const padding = window.safeAreaPadding; + should(padding).be.an.Object(); + // top padding should always be 0 when inside a navigation window, notch or not + should(padding.top).be.eql(0); + should(padding.left).be.eql(0); + should(padding.right).be.eql(0); + + if (hasPhysicalHomeButton()) { + should(padding.bottom).be.eql(0); + } else { + let bottom; + if (utilities.isMacOS()) { + bottom = 0; + } else if (utilities.isIPad()) { + // https://useyourloaf.com/blog/supporting-new-ipad-pro-models/ + // Top: 24 pts, bottom: 20 pts in Portrait *and* landscape + bottom = 20; + // https://stackoverflow.com/questions/46376860/what-is-the-safe-region-for-iphone-x-in-pixels-that-factors-the-top-notch-an/49174154 + } else if (Ti.Gesture.landscape) { + // Bottom: 21 pt, left/right: 44 pt for iPhones in Landscape + bottom = 21; + } else { + // Top: 44 pt, bottom: 34 pt for iPhones in Portrait + bottom = 34; + } + should(padding.bottom).be.eql(bottom); + } + } catch (err) { + return finish(err); + } + finish(); + }); + win.open(); }); - win = Ti.UI.createNavigationWindow({ - height: '400px', - width: '400px', - window: rootWindow + // FIXME Move these rect/size tests into Ti.UI.View! + it.windowsBroken('.size is read-only', finish => { + win = Ti.UI.createWindow({ + backgroundColor: 'blue', + width: 100, + height: 100 + }); + win.addEventListener('postlayout', function listener () { + win.removeEventListener('postlayout', listener); + + try { + win.size.width.should.eql(100); + win.size.height.should.eql(100); + // size just returns 0 for x/y + win.size.x.should.eql(0); + win.size.y.should.eql(0); + + // try to change the size + win.size.width = 120; + win.size.height = 120; + + // shouldn't actually change + win.size.width.should.eql(100); + win.size.height.should.eql(100); + win.size.x.should.eql(0); + win.size.y.should.eql(0); + } catch (err) { + return finish(err); + } + finish(); + }); + win.open(); }); - win.open(); + it.ios('.statusBarStyle', finish => { + win = Ti.UI.createWindow({ + title: 'This is status bar style test', + statusBarStyle: Ti.UI.iOS.StatusBar.LIGHT_CONTENT + }); - rootWindow.addEventListener('postlayout', function postlayout() { - rootWindow.removeEventListener('postlayout', postlayout); - setTimeout(function () { + win.addEventListener('open', () => { try { - should(rootWindow.leftNavButton).be.an.Object(); - should(rootWindow.rightNavButton).be.an.Object(); - should(win).matchImage('snapshots/navButton_left_redColor_right_greenColor.png', { maxPixelMismatch: OS_IOS ? 27 : 0 }); // iphone XR differs by 27 pixels - } catch (e) { - return finish(e); + should(win.statusBarStyle).be.a.Number(); + should(win.statusBarStyle).eql(Ti.UI.iOS.StatusBar.LIGHT_CONTENT); + win.setStatusBarStyle(Ti.UI.iOS.StatusBar.GRAY); + should(win.statusBarStyle).eql(Ti.UI.iOS.StatusBar.GRAY); + } catch (err) { + return finish(err); } finish(); - }, 10); + }); + win.open(); }); - }); - // FIXME Move these rect/size tests into Ti.UI.View! - it.windowsBroken('.size is read-only', finish => { - win = Ti.UI.createWindow({ - backgroundColor: 'blue', - width: 100, - height: 100 + describe('.title', () => { + beforeEach(() => { + win = Ti.UI.createWindow({ + title: 'this is some text' + }); + }); + + it('is a String', () => { + should(win.title).be.a.String(); + }); + + it('equal to value set in factory method dictionary', () => { + should(win.title).eql('this is some text'); + }); + + it('can be assigned new value', () => { + win.title = 'other text'; + should(win.title).eql('other text'); + }); + + it('has accessors', () => { + should(win).have.accessors('title'); + }); }); - win.addEventListener('postlayout', function listener () { - win.removeEventListener('postlayout', listener); - try { - win.size.width.should.eql(100); - win.size.height.should.eql(100); - // size just returns 0 for x/y - win.size.x.should.eql(0); - win.size.y.should.eql(0); - - // try to change the size - win.size.width = 120; - win.size.height = 120; - - // shouldn't actually change - win.size.width.should.eql(100); - win.size.height.should.eql(100); - win.size.x.should.eql(0); - win.size.y.should.eql(0); - } catch (err) { - return finish(err); + describe('.titleid', () => { + beforeEach(() => { + win = Ti.UI.createWindow({ + titleid: 'this_is_my_key' + }); + }); + + it('is a String', () => { + should(win.titleid).be.a.String(); + }); + + it('equal to value set in factory method dictionary', () => { + should(win.titleid).eql('this_is_my_key'); + }); + + it('modifies .title property value', () => { + should(win.title).eql('this is my value'); + }); + + it('can be assigned new value', () => { + win.titleid = 'other text'; + should(win.titleid).eql('other text'); + should(win.title).eql('this is my value'); // FIXME Windows: https://jira.appcelerator.org/browse/TIMOB-23498 + }); + + it('has accessors', () => { + should(win).have.accessors('titleid'); + }); + }); + + // TODO: Why not run this on iOS? Seems to fail, though. + // TODO: Also broken on Android, need to figure out why this test is unreliable. + describe.allBroken('.orientationModes', function () { + this.slow(5000); + this.timeout(20000); + + function doOrientationModeTest(orientation, finish) { + win = Ti.UI.createWindow({ + orientationModes: [ orientation ] + }); + win.addEventListener('open', () => { + setTimeout(() => { + try { + win.orientationModes.should.have.length(1); + win.orientationModes[0].should.eql(orientation); + win.orientation.should.eql(orientation); + } catch (e) { + return finish(e); + } + finish(); + }, 1000); + }); + win.open(); } - finish(); + + it('PORTRAIT', finish => { + doOrientationModeTest(Ti.UI.PORTRAIT, finish); + }); + + it('LANDSCAPE_LEFT', finish => { + doOrientationModeTest(Ti.UI.LANDSCAPE_LEFT, finish); + }); + + it('LANDSCAPE_RIGHT', finish => { + doOrientationModeTest(Ti.UI.LANDSCAPE_RIGHT, finish); + }); }); - win.open(); }); - it.androidAndWindowsBroken('.rect is read-only', finish => { - win = Ti.UI.createWindow({ - backgroundColor: 'green', - left: 100, - right: 100 + describe('methods', () => { + // Performs an Android shared-element transition animation between 2 windows. + // Labels from parent window will move to child window's label positions during open animation. + it.android('#addSharedElement()', function (finish) { + this.slow(5000); + this.timeout(10000); + + win = Ti.UI.createWindow({ + backgroundColor: 'blue' + }); + const sourceLabel1 = Ti.UI.createLabel({ + text: 'Transition Label 1', + top: '10dp', + left: '10dp' + }); + win.add(sourceLabel1); + const sourceLabel2 = Ti.UI.createLabel({ + text: 'Transition Label 2', + bottom: '10dp', + right: '10dp' + }); + win.add(sourceLabel2); + win.addEventListener('postlayout', function eventHandler() { + win.removeEventListener('postlayout', eventHandler); + const childWindow = Ti.UI.createWindow({ + backgroundColor: 'purple' + }); + const targetLabel1 = Ti.UI.createLabel({ + text: 'Transition Label 1', + transitionName: 'label1Transition', + bottom: '10dp', + left: '10dp' + }); + childWindow.add(targetLabel1); + childWindow.addSharedElement(sourceLabel1, targetLabel1.transitionName); + const targetLabel2 = Ti.UI.createLabel({ + text: 'Transition Label 2', + transitionName: 'label2Transition', + top: '10dp', + right: '10dp' + }); + childWindow.add(targetLabel2); + childWindow.addSharedElement(sourceLabel2, targetLabel2.transitionName); + childWindow.addEventListener('open', function () { + // Wait for transition animation to end before closing. (We don't have an event for this.) + setTimeout(function () { + childWindow.close(); + }, 750); + }); + childWindow.addEventListener('close', () => finish()); // The exit animation has finished. We're done. + childWindow.open(); + }); + win.open(); }); - win.addEventListener('postlayout', function listener () { - win.removeEventListener('postlayout', listener); - try { - win.rect.x.should.eql(100); // FiXME: get 0 on Android - win.rect.y.should.eql(0); - const width = win.rect.width; - const height = win.rect.height; - - // try to change the rect - win.rect.x = 120; - win.rect.y = 5; - win.rect.width = 10; - win.rect.height = 5; - - // shouldn't actually change - win.rect.x.should.eql(100); - win.rect.y.should.eql(0); - win.rect.width.should.eql(width); - win.rect.height.should.eql(height); - } catch (err) { - return finish(err); - } - finish(); + it('#applyProperties(Object)', () => { + win = Ti.UI.createWindow(); + should.not.exist(win.custom); + win.applyProperties({ custom: 1234 }); + should(win.custom).eql(1234); }); - win.open(); - }); - it('#remove(View)', function (finish) { - this.slow(1000); - this.timeout(20000); + it('#remove(View)', function (finish) { + this.slow(1000); + this.timeout(20000); - win = Ti.UI.createWindow({ - backgroundColor: 'gray' + win = Ti.UI.createWindow({ + backgroundColor: 'gray' + }); + const view = Ti.UI.createView(); + win.addEventListener('focus', function listener () { + win.removeEventListener('focus', listener); + + try { + should(win.children.length).be.eql(1); + win.remove(win.children[0]); + should(win.children.length).be.eql(0); + } catch (err) { + return finish(err); + } + finish(); + }); + win.add(view); + win.open(); }); - const view = Ti.UI.createView(); - win.addEventListener('focus', function listener () { - win.removeEventListener('focus', listener); - try { - should(win.children.length).be.eql(1); - win.remove(win.children[0]); - should(win.children.length).be.eql(0); - } catch (err) { - return finish(err); - } - finish(); + it.iosAndWindowsBroken('#toString()', () => { + win = Ti.UI.createWindow(); + should(win.toString()).be.eql('[object Window]'); // Windows: '[object class TitaniumWindows::UI::Window]', iOS: '[object TiUIWindow]' + should(win.apiName).be.a.String(); + should(win.apiName).be.eql('Ti.UI.Window'); }); - win.add(view); - win.open(); }); describe('events', function () { @@ -515,13 +904,6 @@ describe('Titanium.UI.Window', function () { win.open(); }); - it.iosAndWindowsBroken('#toString()', () => { - win = Ti.UI.createWindow(); - should(win.toString()).be.eql('[object Window]'); // Windows: '[object class TitaniumWindows::UI::Window]', iOS: '[object TiUIWindow]' - should(win.apiName).be.a.String(); - should(win.apiName).be.eql('Ti.UI.Window'); - }); - it('Stringify unopened Window', () => { win = Ti.UI.createWindow(); Ti.API.info(JSON.stringify(win)); @@ -617,140 +999,6 @@ describe('Titanium.UI.Window', function () { rootWindow.open(); }); - it('#applyProperties(Object)', () => { - win = Ti.UI.createWindow(); - should.not.exist(win.custom); - win.applyProperties({ custom: 1234 }); - should(win.custom).eql(1234); - }); - - it.ios('largeTitleEnabled', () => { - win = Ti.UI.createWindow({ - title: 'this is some text', - largeTitleEnabled: true - }); - - should(win.largeTitleEnabled).be.a.Boolean(); - should(win.getLargeTitleEnabled).be.a.Function(); - should(win.setLargeTitleEnabled).be.a.Function(); - - should(win.largeTitleEnabled).be.true(); - should(win.getLargeTitleEnabled()).be.true(); - - win.largeTitleEnabled = false; - should(win.largeTitleEnabled).be.false(); - should(win.getLargeTitleEnabled()).be.false(); - - win.setLargeTitleEnabled(true); - should(win.largeTitleEnabled).be.true(); - should(win.getLargeTitleEnabled()).be.true(); - }); - - it.ios('largeTitleDisplayMode', () => { - win = Ti.UI.createWindow({ - title: 'this is some text', - largeTitleDisplayMode: Ti.UI.iOS.LARGE_TITLE_DISPLAY_MODE_ALWAYS - }); - - should(win.largeTitleDisplayMode).be.a.Number(); - should(win.getLargeTitleDisplayMode).be.a.Function(); - should(win.setLargeTitleDisplayMode).be.a.Function(); - - should(win.largeTitleDisplayMode).eql(Ti.UI.iOS.LARGE_TITLE_DISPLAY_MODE_ALWAYS); - should(win.getLargeTitleDisplayMode()).eql(Ti.UI.iOS.LARGE_TITLE_DISPLAY_MODE_ALWAYS); - - win.largeTitleDisplayMode = Ti.UI.iOS.LARGE_TITLE_DISPLAY_MODE_AUTOMATIC; - should(win.largeTitleDisplayMode).eql(Ti.UI.iOS.LARGE_TITLE_DISPLAY_MODE_AUTOMATIC); - should(win.getLargeTitleDisplayMode()).eql(Ti.UI.iOS.LARGE_TITLE_DISPLAY_MODE_AUTOMATIC); - - win.setLargeTitleDisplayMode(Ti.UI.iOS.LARGE_TITLE_DISPLAY_MODE_NEVER); - should(win.largeTitleDisplayMode).eql(Ti.UI.iOS.LARGE_TITLE_DISPLAY_MODE_NEVER); - should(win.getLargeTitleDisplayMode()).eql(Ti.UI.iOS.LARGE_TITLE_DISPLAY_MODE_NEVER); - }); - - it.ios('.extendSafeArea', function (finish) { - this.timeout(5000); - // TODO: Add more unit tests related to top, bottom, left, right margins of win.safeAreaView. - win = Ti.UI.createWindow({ - backgroundColor: 'gray', - extendSafeArea: false - }); - - win.addEventListener('open', function () { - try { - should(win.safeAreaView).be.a.Object(); - } catch (e) { - return finish(e); - } - finish(); - }); - - win.open(); - }); - - it.ios('.homeIndicatorAutoHidden', finish => { - win = Ti.UI.createWindow({ - title: 'this is some text' - }); - - win.addEventListener('open', () => { - try { - should(win.homeIndicatorAutoHidden).be.a.Boolean(); - should(win.homeIndicatorAutoHidden).be.false(); - win.setHomeIndicatorAutoHidden(true); - should(win.homeIndicatorAutoHidden).be.true(); - } catch (e) { - return finish(e); - } - finish(); - }); - win.open(); - }); - - it.ios('.hidesBackButton', finish => { - if (isCI && utilities.isMacOS()) { // for whatever reaosn this fails on ci nodes, but not locally. Maybe issue with headless mac? - return finish(); // FIXME: skip when we move to official mocha package - } - const window1 = Ti.UI.createWindow({ - backgroundColor: 'red' - }); - - const window2 = Ti.UI.createWindow({ - hidesBackButton: true, - backgroundColor: 'yellow' - }); - - window1.addEventListener('focus', () => { // FIXME: On macOS CI (maybe < 10.15.6?), this event never fires! Does app need explicit focus added? - win.openWindow(window2, { animated: false }); - }); - window2.addEventListener('open', () => { - try { - should(window2.hidesBackButton).be.a.Boolean(); - - should(window2.getHidesBackButton).be.a.Function(); - should(window2.setHidesBackButton).be.a.Function(); - - should(window2.hidesBackButton).be.true(); - should(window2.getHidesBackButton()).be.true(); - - window2.hidesBackButton = false; - should(window2.hidesBackButton).be.false(); - should(window2.getHidesBackButton()).be.false(); - - window2.setHidesBackButton(true); - should(window2.hidesBackButton).be.true(); - should(window2.getHidesBackButton()).be.true(); - } catch (err) { - return finish(err); - } - finish(); - }); - win = Ti.UI.createNavigationWindow({ - window: window1 - }); - win.open({ modal: true, animated: false }); - }); - // As of Android 8.0, the OS will throw an exception if you apply a fixed orientation to a translucent window. // Verify that Titanium handles the issue and avoids a crash. it.android('TIMOB-26157', function (finish) { @@ -766,78 +1014,6 @@ describe('Titanium.UI.Window', function () { win.open(); }); - it.ios('.statusBarStyle', finish => { - win = Ti.UI.createWindow({ - title: 'This is status bar style test', - statusBarStyle: Ti.UI.iOS.StatusBar.LIGHT_CONTENT - }); - - win.addEventListener('open', () => { - try { - should(win.statusBarStyle).be.a.Number(); - should(win.statusBarStyle).eql(Ti.UI.iOS.StatusBar.LIGHT_CONTENT); - win.setStatusBarStyle(Ti.UI.iOS.StatusBar.GRAY); - should(win.statusBarStyle).eql(Ti.UI.iOS.StatusBar.GRAY); - } catch (err) { - return finish(err); - } - finish(); - }); - win.open(); - }); - - it('.safeAreaPadding with extendSafeArea false', function (finish) { - this.slow(5000); - - win = Ti.UI.createWindow({ - extendSafeArea: false, - }); - win.addEventListener('postlayout', function listener () { - win.removeEventListener('postlayout', listener); - - try { - const padding = win.safeAreaPadding; - should(padding).be.an.Object(); - should(padding.left).be.eql(0); - should(padding.top).be.eql(0); - should(padding.right).be.eql(0); - should(padding.bottom).be.eql(0); - } catch (err) { - return finish(err); - } - finish(); - }); - win.open(); - }); - - // This test will only pass on Android 4.4 and higher since older versions do not support translucent bars. - it.android('.safeAreaPadding with extendSafeArea true', function (finish) { - this.slow(5000); - - win = Ti.UI.createWindow({ - extendSafeArea: true, - theme: 'Theme.Titanium.NoTitleBar', - orientationModes: [ Ti.UI.PORTRAIT ], - windowFlags: Ti.UI.Android.FLAG_TRANSLUCENT_STATUS | Ti.UI.Android.FLAG_TRANSLUCENT_NAVIGATION - }); - win.addEventListener('postlayout', function listener () { - win.removeEventListener('postlayout', listener); - - try { - const padding = win.safeAreaPadding; - should(padding).be.an.Object(); - should(padding.top).be.aboveOrEqual(0); - should(padding.bottom).be.aboveOrEqual(0); - should(padding.left).be.aboveOrEqual(0); - should(padding.right).be.aboveOrEqual(0); - } catch (err) { - return finish(err); - } - finish(); - }); - win.open(); - }); - function hasPhysicalHomeButton() { const model = Ti.Platform.model; const trimmed = model.replace(' (Simulator)', '').trim(); @@ -875,106 +1051,6 @@ describe('Titanium.UI.Window', function () { return true; } - it.ios('.safeAreaPadding for window inside navigation window with extendSafeArea true', finish => { - const window = Ti.UI.createWindow({ - extendSafeArea: true, - }); - win = Ti.UI.createNavigationWindow({ - window: window - }); - window.addEventListener('postlayout', function listener () { - window.removeEventListener('postlayout', listener); - - try { - const padding = window.safeAreaPadding; - should(padding).be.an.Object(); - // top padding should always be 0 when inside a navigation window, notch or not - should(padding.top).be.eql(0); - should(padding.left).be.eql(0); - should(padding.right).be.eql(0); - - if (hasPhysicalHomeButton()) { - should(padding.bottom).be.eql(0); - } else { - let bottom; - if (utilities.isMacOS()) { - bottom = 0; - } else if (utilities.isIPad()) { - // https://useyourloaf.com/blog/supporting-new-ipad-pro-models/ - // Top: 24 pts, bottom: 20 pts in Portrait *and* landscape - bottom = 20; - // https://stackoverflow.com/questions/46376860/what-is-the-safe-region-for-iphone-x-in-pixels-that-factors-the-top-notch-an/49174154 - } else if (Ti.Gesture.landscape) { - // Bottom: 21 pt, left/right: 44 pt for iPhones in Landscape - bottom = 21; - } else { - // Top: 44 pt, bottom: 34 pt for iPhones in Portrait - bottom = 34; - } - should(padding.bottom).be.eql(bottom); - } - } catch (err) { - return finish(err); - } - finish(); - }); - win.open(); - }); - - // Performs an Android shared-element transition animation between 2 windows. - // Labels from parent window will move to child window's label positions during open animation. - it.android('#addSharedElement()', function (finish) { - this.slow(5000); - this.timeout(10000); - - win = Ti.UI.createWindow({ - backgroundColor: 'blue' - }); - const sourceLabel1 = Ti.UI.createLabel({ - text: 'Transition Label 1', - top: '10dp', - left: '10dp' - }); - win.add(sourceLabel1); - const sourceLabel2 = Ti.UI.createLabel({ - text: 'Transition Label 2', - bottom: '10dp', - right: '10dp' - }); - win.add(sourceLabel2); - win.addEventListener('postlayout', function eventHandler() { - win.removeEventListener('postlayout', eventHandler); - const childWindow = Ti.UI.createWindow({ - backgroundColor: 'purple' - }); - const targetLabel1 = Ti.UI.createLabel({ - text: 'Transition Label 1', - transitionName: 'label1Transition', - bottom: '10dp', - left: '10dp' - }); - childWindow.add(targetLabel1); - childWindow.addSharedElement(sourceLabel1, targetLabel1.transitionName); - const targetLabel2 = Ti.UI.createLabel({ - text: 'Transition Label 2', - transitionName: 'label2Transition', - top: '10dp', - right: '10dp' - }); - childWindow.add(targetLabel2); - childWindow.addSharedElement(sourceLabel2, targetLabel2.transitionName); - childWindow.addEventListener('open', function () { - // Wait for transition animation to end before closing. (We don't have an event for this.) - setTimeout(function () { - childWindow.close(); - }, 750); - }); - childWindow.addEventListener('close', () => finish()); // The exit animation has finished. We're done. - childWindow.open(); - }); - win.open(); - }); - describe.android('activity transitions', function () { this.slow(5000); this.timeout(10000); @@ -1032,17 +1108,6 @@ describe('Titanium.UI.Window', function () { }); }); - it.android('.barColor with disabled ActionBar', finish => { - win = Ti.UI.createWindow({ - barColor: 'blue', - title: 'My Title', - theme: 'Theme.Titanium.NoTitleBar', - }); - win.add(Ti.UI.createLabel({ text: 'Window Title Test' })); - win.open(); - win.addEventListener('open', () => finish()); - }); - it('TIMOB-27711 will not open if close() called immediately after', finish => { const win = Ti.UI.createWindow({ backgroundColor: '#0000ff' @@ -1060,49 +1125,4 @@ describe('Titanium.UI.Window', function () { // ios took 1ms repeatedly // so 1 second should be enough time. }); - - it('.closed', done => { - win = Ti.UI.createWindow({ - backgroundColor: '#0000ff' - }); - win.closed.should.be.true(); // it's not yet opened, so treat as closed - win.addEventListener('open', function openListener () { - win.removeEventListener('open', openListener); - try { - win.closed.should.be.false(); // we're being notified the window is open, so should report closed as false! - } catch (e) { - return done(e); - } - done(); - }); - win.open(); - win.closed.should.be.false(); // should be open now - }); - - it('.focused', done => { - win = Ti.UI.createWindow({ - backgroundColor: '#0000ff' - }); - win.focused.should.be.false(); // haven't opened it yet, so shouldn't be focused - win.addEventListener('focus', function focusListener() { - win.removeEventListener('focus', focusListener); - try { - win.focused.should.be.true(); - win.close(); - } catch (e) { - return done(e); - } - }); - win.addEventListener('close', function closeListener () { - win.removeEventListener('close', closeListener); - try { - // we've been closed (or are closing?) so hopefully shouldn't say that we're focused - win.focused.should.be.false(); - } catch (e) { - return done(e); - } - done(); - }); - win.open(); - }); // For reference, Android fires open event and then focus event }); diff --git a/tests/Resources/ti.xml.test.js b/tests/Resources/ti.xml.test.js index 37335a46abd..73a13ec00e8 100644 --- a/tests/Resources/ti.xml.test.js +++ b/tests/Resources/ti.xml.test.js @@ -510,19 +510,19 @@ describe.windowsBroken('Titanium.XML', function () { var doc = Ti.XML.parseString(testSource['nodes.xml']), textValue = 'this is some test', textNode, - getTextResults = null, + // getTextResults = null, getTextResults2; should(doc.createTextNode).be.a.Function(); textNode = doc.createTextNode(textValue); should(textNode.nodeValue).eql(textValue); - should(function () { - getTextResults = textNode.getText(); - }).not.throw(); - should(getTextResults).eql(textValue); - should(function () { - getTextResults = textNode.getTextContent(); - }).not.throw(); - should(getTextResults).eql(textValue); + // should(function () { + // getTextResults = textNode.getText(); + // }).not.throw(); + // should(getTextResults).eql(textValue); + // should(function () { + // getTextResults = textNode.getTextContent(); + // }).not.throw(); + // should(getTextResults).eql(textValue); should(function () { getTextResults2 = textNode.text; }).not.throw(); diff --git a/tests/Resources/utilities/assertions.js b/tests/Resources/utilities/assertions.js index 2e9b07a2aa7..e31f0779226 100644 --- a/tests/Resources/utilities/assertions.js +++ b/tests/Resources/utilities/assertions.js @@ -109,6 +109,31 @@ should.Assertion.add('enumeration', function (type, names) { } }, false); +should.Assertion.add('getter', function (propName) { + const upper = propName.slice(0, 1).toUpperCase(); + const getterName = `get${upper}${propName.slice(1)}`; + this.params = { operator: `to have a getter method named: ${getterName}` }; + if (this.obj.apiName) { + this.params.obj = this.obj.apiName; + } + should(this.obj).have.a.property(getterName).which.is.a.Function(); +}, false); + +should.Assertion.add('setter', function (propName) { + const upper = propName.slice(0, 1).toUpperCase(); + const setterName = `set${upper}${propName.slice(1)}`; + this.params = { operator: `to have a setter method named: ${setterName}` }; + if (this.obj.apiName) { + this.params.obj = this.obj.apiName; + } + should(this.obj).have.a.property(setterName).which.is.a.Function(); +}, false); + +should.Assertion.add('accessors', function (propName) { + should(this.obj).have.a.getter(propName); + should(this.obj).have.a.setter(propName); +}, false); + /** * @param {Ti.Blob} blob binary data to write * @param {string} imageFilePath relative file path to save image under