From c352cb8c01f14d43e4f79c743afcfaa6f9d8ff18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20Kn=C3=B6chel?= Date: Mon, 30 Jul 2018 07:35:05 +0200 Subject: [PATCH] [TIMOB-26103] iOS 12: Expose new UserNotifications API's (#10091) * [TIMOB-26103] First stage of adding the new UserNotifications API's * Fix method access for two methods, fix 7.2.0 -> 7.3.0, fix docs (#10093) * Add docs * Update docs, fix typo * Add Ti.App.iOS unit tests * Update ti.app.ios.searchquery.test.js * Update ti.app.ios.test.js * feat: expose "threadIdentifier", fix: "timezone" property in notifications * Some final improvements --- .../App/iOS/UserNotificationCategory.yml | 8 + .../App/iOS/UserNotificationCenter.yml | 16 ++ apidoc/Titanium/App/iOS/iOS.yml | 60 ++++- iphone/Classes/TiApp.m | 8 + iphone/Classes/TiAppiOSProxy.m | 139 ++++++++-- .../TiAppiOSUserNotificationCategoryProxy.m | 27 +- .../TiAppiOSUserNotificationCenterProxy.m | 6 + iphone/Classes/TiUtils.m | 4 + .../Resources/ti.app.ios.searchquery.test.js | 48 ++++ tests/Resources/ti.app.ios.test.js | 240 ++++++++++++++++++ 10 files changed, 530 insertions(+), 26 deletions(-) create mode 100644 tests/Resources/ti.app.ios.searchquery.test.js create mode 100644 tests/Resources/ti.app.ios.test.js diff --git a/apidoc/Titanium/App/iOS/UserNotificationCategory.yml b/apidoc/Titanium/App/iOS/UserNotificationCategory.yml index 4a2a9d0502e..b3b61380911 100644 --- a/apidoc/Titanium/App/iOS/UserNotificationCategory.yml +++ b/apidoc/Titanium/App/iOS/UserNotificationCategory.yml @@ -42,6 +42,14 @@ properties: osver: {ios: {max: 9.3}} availability: creation + - name: categorySummaryFormat + summary: A format string for the summary description used when the system groups the category's notifications. + type: String + accessors: false + osver: { ios: { min: 12.0 } } + availability: creation + since: "7.4.0" + - name: identifier summary: Identifier for this category. description: | diff --git a/apidoc/Titanium/App/iOS/UserNotificationCenter.yml b/apidoc/Titanium/App/iOS/UserNotificationCenter.yml index 2c5b1c72ad7..1eb37069fc2 100644 --- a/apidoc/Titanium/App/iOS/UserNotificationCenter.yml +++ b/apidoc/Titanium/App/iOS/UserNotificationCenter.yml @@ -209,6 +209,22 @@ properties: constants: [Titanium.App.iOS.USER_NOTIFICATION_SETTING_*] osver: {ios: {min: "10.0"}} + - name: criticalAlertSetting + summary: | + The authorization status to play sounds for critical alerts. + Available in Titanium SDK 7.4.0 and later. + type: Number + constants: [Titanium.App.iOS.USER_NOTIFICATION_SETTING_*] + osver: { ios: { min: "12.0" } } + + - name: providesAppNotificationSettings + summary: | + A Boolean value indicating the system displays a button for in-app notification settings. + Available in Titanium SDK 7.4.0 and later. + type: Number + constants: [Titanium.App.iOS.USER_NOTIFICATION_SETTING_*] + osver: { ios: { min: "12.0" } } + - name: alertStyle summary: The current alert style used to display notifications. type: Number diff --git a/apidoc/Titanium/App/iOS/iOS.yml b/apidoc/Titanium/App/iOS/iOS.yml index 22ed399a010..fc4d2653e7b 100644 --- a/apidoc/Titanium/App/iOS/iOS.yml +++ b/apidoc/Titanium/App/iOS/iOS.yml @@ -272,6 +272,33 @@ properties: osver: {ios: {min: "8.0"}} since: "3.4.0" + - name: USER_NOTIFICATION_TYPE_CRITICAL_ALERT + summary: | + The ability to play sounds for critical alerts. + Use with the [types](UserNotificationSettings.types) property. + type: Number + permission: read-only + osver: {ios: {min: "12.0"}} + since: "7.4.0" + + - name: USER_NOTIFICATION_TYPE_PROVISIONAL + summary: | + The ability to post non-interrupting notifications provisionally to the Notification Center. + Use with the [types](UserNotificationSettings.types) property. + type: Number + permission: read-only + osver: {ios: {min: "12.0"}} + since: "7.4.0" + + - name: USER_NOTIFICATION_TYPE_PROVIDES_APP_NOTIFICATION_SETTINGS + summary: | + An option indicating the system should display a button for in-app notification settings. + Use with the [types](UserNotificationSettings.types) property. + type: Number + permission: read-only + osver: {ios: {min: "12.0"}} + since: "7.4.0" + - name: USER_NOTIFICATION_ACTIVATION_MODE_BACKGROUND summary: | The action will execute in background. Use with the @@ -345,6 +372,17 @@ properties: osver: {ios: {min: "10.0"}} since: "7.3.0" + + - name: USER_NOTIFICATION_AUTHORIZATION_STATUS_PROVISIONAL + summary: The application is provisionally authorized to post non-interruptive user notifications. + description: | + Used to check the `authorizationStatus` attribute received in + . + type: Number + permission: read-only + osver: { ios: { min: "12.0" } } + since: "7.4.0" + - name: USER_NOTIFICATION_SETTING_NOT_SUPPORTED summary: The application does not support this notification type. description: | @@ -796,7 +834,7 @@ events: type: String - name: timezone - summary: Timezone of the date when the notification was configured to fire. + summary: Timezone of the date when the notification was configured to fire. Available in Titanium SDK 7.4.0+. type: String - name: userInfo @@ -804,9 +842,15 @@ events: type: Dictionary - name: inBackground - summary: Boolean indicating if notification was received while app was in background (since Titanium SDK 6.2.0). + summary: Boolean indicating if notification was received while app was in background. Available in Titanium SDK 6.2.0+. type: Boolean + - name: threadIdentifier + summary: | + The unique identifier for the thread or conversation related to this notification request. + It will be used to visually group notifications together. Available in Titanium SDK 7.4.0+ and iOS 12+. + type: String + - name: localnotificationaction summary: Fired when a user selects an action for an interactive local notification. properties: @@ -1394,6 +1438,18 @@ properties: osver: {ios: {min: "8.0"}} since: "7.3.0" + - name: summaryArgument + summary: The string the notification adds to the category's summary format string. + type: String + osver: { ios: { min: "12.0" } } + since: "7.4.0" + + - name: summaryArgumentCount + summary: The number of items the notification adds to the category's summary format string. + type: String + osver: { ios: { min: "12.0" } } + since: "7.4.0" + --- name: UserNotificationAttachment summary: | diff --git a/iphone/Classes/TiApp.m b/iphone/Classes/TiApp.m index 4ebaa937089..2e69df86d1b 100644 --- a/iphone/Classes/TiApp.m +++ b/iphone/Classes/TiApp.m @@ -592,12 +592,20 @@ - (void)application:(UIApplication *)application didRegisterUserNotificationSett userInfo:@{ @"userNotificationSettings" : notificationSettings }]; } +// iOS 12+ +- (void)userNotificationCenter:(UNUserNotificationCenter *)center openSettingsForNotification:(UNNotification *)notification +{ + // Unused so far, may expose as an event in the future? +} + +// iOS 10+ - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler { // TODO: Get desired options from notification? completionHandler(UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionSound); } +// iOS 10+ - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler { diff --git a/iphone/Classes/TiAppiOSProxy.m b/iphone/Classes/TiAppiOSProxy.m index 1628f85e269..0ee3461bfed 100644 --- a/iphone/Classes/TiAppiOSProxy.m +++ b/iphone/Classes/TiAppiOSProxy.m @@ -408,6 +408,7 @@ - (void)registerUserNotificationSettings:(id)args if (typesRequested != nil) { for (id thisTypeRequested in typesRequested) { + // Handle basic iOS 12+ enums if ([TiUtils isIOS10OrGreater]) { switch ([TiUtils intValue:thisTypeRequested]) { case UNAuthorizationOptionBadge: // USER_NOTIFICATION_TYPE_BADGE @@ -431,6 +432,28 @@ - (void)registerUserNotificationSettings:(id)args break; } } +#if IS_XCODE_10 + // Handle additional iOS 12+ enums + if ([TiUtils isIOSVersionOrGreater:@"12.0"]) { + switch ([TiUtils intValue:thisTypeRequested]) { + case UNAuthorizationOptionCriticalAlert: // USER_NOTIFICATION_TYPE_CRITICAL_ALERT + { + types |= UNAuthorizationOptionCriticalAlert; + break; + } + case UNAuthorizationOptionProvisional: // USER_NOTIFICATION_TYPE_PROVISIONAL + { + types |= UNAuthorizationOptionProvisional; + break; + } + case UNAuthorizationOptionProvidesAppNotificationSettings: // USER_NOTIFICATION_TYPE_PROVIDES_APP_NOTIFICATION_SETTINGS + { + types |= UNAuthorizationOptionProvidesAppNotificationSettings; + break; + } + } + } +#endif } else { switch ([TiUtils intValue:thisTypeRequested]) { case UIUserNotificationTypeBadge: // USER_NOTIFICATION_TYPE_BADGE @@ -618,6 +641,7 @@ - (id)scheduleLocalNotification:(id)args { ENSURE_SINGLE_ARG(args, NSDictionary); + // Prepare valid properties inside our arguments id repeat = [args objectForKey:@"repeat"]; id date = [args objectForKey:@"date"]; id region = [args objectForKey:@"region"]; @@ -627,9 +651,15 @@ - (id)scheduleLocalNotification:(id)args id badge = [args objectForKey:@"badge"]; id userInfo = [args objectForKey:@"userInfo"]; id sound = [args objectForKey:@"sound"]; + id summaryArgument = [args objectForKey:@"summaryArgument"]; + id summaryArgumentCount = [args objectForKey:@"summaryArgumentCount"]; + id threadIdentifier = [args objectForKey:@"threadIdentifier"]; + id timezone = [args objectForKey:@"timezone"]; - TiAppiOSLocalNotificationProxy *lp = [[[TiAppiOSLocalNotificationProxy alloc] _initWithPageContext:[self executionContext]] autorelease]; + // Construct a new local notification proxy from our current context + TiAppiOSLocalNotificationProxy *notification = [[[TiAppiOSLocalNotificationProxy alloc] _initWithPageContext:[self executionContext]] autorelease]; + // For iOS 10+, use the UserNotifications framework to manage notification if ([TiUtils isIOS10OrGreater]) { id identifier = [args objectForKey:@"identifier"]; id alertSubtitle = [args objectForKey:@"alertSubtitle"]; @@ -638,13 +668,18 @@ - (id)scheduleLocalNotification:(id)args UNNotificationTrigger *trigger; - if (date) { + // Configure a date-based trigger + if (date != nil) { // Handle time intervals as well (backwards compatibility) if ([date isKindOfClass:[NSNumber class]]) { date = [NSDate dateWithTimeIntervalSince1970:[TiUtils doubleValue:date] / 1000]; } NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; + if (timezone != nil) { + calendar.timeZone = [NSTimeZone timeZoneWithName:[TiUtils stringValue:timezone]]; + } + // Per default, use all components and don't repeat NSCalendarUnit components = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond; @@ -666,7 +701,9 @@ - (id)scheduleLocalNotification:(id)args fromDate:date] repeats:(repeat != nil)]; RELEASE_TO_NIL(calendar); - } else if (region) { + + // Configure a location-based trigger + } else if (region != nil) { BOOL triggersOnce = [TiUtils boolValue:[region valueForKey:@"triggersOnce"] def:YES]; double latitude = [TiUtils doubleValue:[region valueForKey:@"latitude"] def:0]; double longitude = [TiUtils doubleValue:[region valueForKey:@"longitude"] def:0]; @@ -684,30 +721,35 @@ - (id)scheduleLocalNotification:(id)args return; } + // Instantiate a new mutable notification content UNMutableNotificationContent *content = [UNMutableNotificationContent new]; - if (alertTitle) { + // Set badge. No nil-check required, since nil will be converted to 0, which resets the badge + [content setBadge:[TiUtils numberFromObject:badge]]; + + // Set the title of the notification + if (alertTitle != nil) { [content setTitle:[TiUtils stringValue:alertTitle]]; } - if (alertSubtitle) { + // Set the subtitle of the notification + if (alertSubtitle != nil) { [content setSubtitle:[TiUtils stringValue:alertSubtitle]]; } - if (alertBody) { + // Set the content (body) of the notification + if (alertBody != nil) { [content setBody:[TiUtils stringValue:alertBody]]; } - if (alertLaunchImage) { + // Set the launch image of the notification, e.g an image- or storyboard name + if (alertLaunchImage != nil) { [content setLaunchImageName:[TiUtils stringValue:alertLaunchImage]]; } - if (badge) { - [content setBadge:[TiUtils numberFromObject:badge]]; - } - - if (userInfo) { - if (identifier != nil && ![userInfo objectForKey:@"id"]) { + // Set additional user-info + if (userInfo != nil) { + if (identifier != nil && [userInfo objectForKey:@"id"] == nil) { NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:userInfo]; [dict setObject:identifier forKey:@"id"]; [content setUserInfo:dict]; @@ -716,7 +758,8 @@ - (id)scheduleLocalNotification:(id)args } } - if (attachments) { + // Set attachments + if (attachments != nil) { NSMutableArray *_attachments = [NSMutableArray arrayWithCapacity:[attachments count]]; for (id attachment in attachments) { NSString *_identifier; @@ -741,20 +784,43 @@ - (id)scheduleLocalNotification:(id)args [content setAttachments:_attachments]; } - if (sound) { + // Set notification sound + if (sound != nil) { + // Set a default sound if ([sound isEqual:@"default"]) { [content setSound:[UNNotificationSound defaultSound]]; } else { + // Set a custom sound name [content setSound:[UNNotificationSound soundNamed:sound]]; } } + // Set category identifier if (category != nil && [category isKindOfClass:[TiAppiOSUserNotificationCategoryProxy class]]) { [content setCategoryIdentifier:[(TiAppiOSUserNotificationCategoryProxy *)category identifier]]; } else if (category != nil && [category isKindOfClass:[NSString class]]) { [content setCategoryIdentifier:[TiUtils stringValue:category]]; } +#if IS_XCODE_10 + // Add iOS 12+ API's to enable threading and notification groups + if ([TiUtils isIOSVersionOrGreater:@"12.0"]) { + // Set the string the notification adds to the category’s summary format string. + if (summaryArgument != nil) { + [content setSummaryArgument:summaryArgument]; + } + // Set a number that indicates how many items in the summary are represented in the summary. + if (summaryArgumentCount != nil) { + [content setSummaryArgumentCount:[TiUtils intValue:summaryArgumentCount]]; + } + // Set the thread identifier to enable grouped notifications + if (threadIdentifier != nil) { + [content setThreadIdentifier:threadIdentifier]; + } + } +#endif + + // Construct a new notiication request using our content and trigger (e.g. date or location) UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:[TiUtils stringValue:identifier] ?: @"notification" content:content trigger:trigger]; @@ -769,17 +835,21 @@ - (id)scheduleLocalNotification:(id)args }, NO); - lp.notification = content; + notification.notification = content; [content release]; - return lp; + return notification; } else { UILocalNotification *content = [UILocalNotification new]; id alertAction = [args objectForKey:@"alertAction"]; if (date != nil) { content.fireDate = date; - content.timeZone = [NSTimeZone defaultTimeZone]; + if (timezone != nil) { + content.timeZone = [NSTimeZone timeZoneWithName:[TiUtils stringValue:timezone]]; + } else { + content.timeZone = [NSTimeZone defaultTimeZone]; + } } if (repeat != nil) { @@ -861,10 +931,10 @@ - (id)scheduleLocalNotification:(id)args }, NO); - lp.notification = content; + notification.notification = content; [content release]; - return lp; + return notification; } } @@ -1081,6 +1151,32 @@ - (NSNumber *)USER_NOTIFICATION_TYPE_CAR_PLAY return NUMINT(0); } +#if IS_XCODE_10 +- (NSNumber *)USER_NOTIFICATION_TYPE_CRITICAL_ALERT +{ + if ([TiUtils isIOSVersionOrGreater:@"12.0"]) { + return NUMINT(UNAuthorizationOptionCriticalAlert); + } + return NUMINT(0); +} + +- (NSNumber *)USER_NOTIFICATION_TYPE_PROVISIONAL +{ + if ([TiUtils isIOSVersionOrGreater:@"12.0"]) { + return NUMINT(UNAuthorizationOptionProvisional); + } + return NUMINT(0); +} + +- (NSNumber *)USER_NOTIFICATION_TYPE_PROVIDES_APP_NOTIFICATION_SETTINGS +{ + if ([TiUtils isIOSVersionOrGreater:@"12.0"]) { + return NUMINT(UNAuthorizationOptionProvidesAppNotificationSettings); + } + return NUMINT(0); +} +#endif + - (NSNumber *)USER_NOTIFICATION_ACTIVATION_MODE_BACKGROUND { if ([TiUtils isIOS10OrGreater]) { @@ -1391,6 +1487,9 @@ - (NSString *)applicationOpenSettingsURL MAKE_SYSTEM_PROP(USER_NOTIFICATION_AUTHORIZATION_STATUS_DENIED, UNAuthorizationStatusDenied); MAKE_SYSTEM_PROP(USER_NOTIFICATION_AUTHORIZATION_STATUS_AUTHORIZED, UNAuthorizationStatusAuthorized); MAKE_SYSTEM_PROP(USER_NOTIFICATION_AUTHORIZATION_STATUS_NOT_DETERMINED, UNAuthorizationStatusNotDetermined); +#if IS_XCODE_10 +MAKE_SYSTEM_PROP(USER_NOTIFICATION_AUTHORIZATION_STATUS_PROVISIONAL, UNAuthorizationStatusProvisional); +#endif MAKE_SYSTEM_PROP(USER_NOTIFICATION_SETTING_ENABLED, UNNotificationSettingEnabled); MAKE_SYSTEM_PROP(USER_NOTIFICATION_SETTING_DISABLED, UNNotificationSettingDisabled); diff --git a/iphone/Classes/TiAppiOSUserNotificationCategoryProxy.m b/iphone/Classes/TiAppiOSUserNotificationCategoryProxy.m index cc4f0fbfada..e86be909f43 100644 --- a/iphone/Classes/TiAppiOSUserNotificationCategoryProxy.m +++ b/iphone/Classes/TiAppiOSUserNotificationCategoryProxy.m @@ -27,26 +27,29 @@ - (NSString *)apiName - (void)_initWithProperties:(NSDictionary *)properties { if (_notificationCategory == nil) { - NSString *identifier = [properties valueForKey:@"identifier"]; NSArray *actionsForDefaultContext = [properties valueForKey:@"actionsForDefaultContext"]; NSArray *actionsForMinimalContext = [properties valueForKey:@"actionsForMinimalContext"]; NSArray *intentIdentifiers = [properties valueForKey:@"intentIdentifiers"] ?: @[]; NSString *hiddenPreviewsBodyPlaceholder = [properties valueForKey:@"hiddenPreviewsBodyPlaceholder"]; + NSString *categorySummaryFormat = [properties valueForKey:@"categorySummaryFormat"]; UNNotificationCategoryOptions options = [self categoryOptionsFromArray:[properties valueForKey:@"options"] ?: @[]]; NSMutableArray *defaultActions = [NSMutableArray new]; NSMutableArray *minimalActions = [NSMutableArray new]; + // Prepare default actions for (TiAppiOSUserNotificationActionProxy *action in actionsForDefaultContext) { [defaultActions addObject:[action notificationAction]]; } + // Prepare minimal actions (iOS < 10 only) for (TiAppiOSUserNotificationActionProxy *action in actionsForMinimalContext) { [minimalActions addObject:[action notificationAction]]; } - if (intentIdentifiers) { + // Pre-validate intent identifiers to be a String + if (intentIdentifiers != nil) { for (id intentIdentifier in intentIdentifiers) { if (![intentIdentifier isKindOfClass:[NSString class]]) { DebugLog(@"[ERROR] All elements in \"intentIdentifiers\" must be a String, \"%@\" is not!", intentIdentifier); @@ -56,13 +59,29 @@ - (void)_initWithProperties:(NSDictionary *)properties // For iOS 10+, use the UserNotifications framerwork if ([TiUtils isIOS10OrGreater]) { - // For iOS 11+, use the "hiddenPreviewsBodyPlaceholder" constructor - if (hiddenPreviewsBodyPlaceholder != nil && [TiUtils isIOSVersionOrGreater:@"11.0"]) { + // For iOS 11+, offer new constructors + if ([TiUtils isIOSVersionOrGreater:@"11.0"]) { +#if IS_XCODE_10 + // For iOS 12+, use the "hiddenPreviewsBodyPlaceholder" and "categorySummaryFormat" constructor + if ([TiUtils isIOSVersionOrGreater:@"12.0"]) { + _notificationCategory = [[UNNotificationCategory categoryWithIdentifier:identifier + actions:defaultActions + intentIdentifiers:intentIdentifiers + hiddenPreviewsBodyPlaceholder:hiddenPreviewsBodyPlaceholder + categorySummaryFormat:categorySummaryFormat + options:options] retain]; + } else { +#else + // For iOS 11, use the "hiddenPreviewsBodyPlaceholder" constructor _notificationCategory = [UNNotificationCategory categoryWithIdentifier:identifier actions:defaultActions intentIdentifiers:intentIdentifiers hiddenPreviewsBodyPlaceholder:hiddenPreviewsBodyPlaceholder options:options]; +#endif +#if IS_XCODE_10 + } +#endif } else { // For iOS < 11, use the default constructor _notificationCategory = [[UNNotificationCategory categoryWithIdentifier:identifier diff --git a/iphone/Classes/TiAppiOSUserNotificationCenterProxy.m b/iphone/Classes/TiAppiOSUserNotificationCenterProxy.m index 2d6e14c03d2..5ad703b4426 100644 --- a/iphone/Classes/TiAppiOSUserNotificationCenterProxy.m +++ b/iphone/Classes/TiAppiOSUserNotificationCenterProxy.m @@ -187,6 +187,12 @@ - (void)requestUserNotificationSettings:(id)callback if ([TiUtils isIOSVersionOrGreater:@"11.0"]) { propertiesDict[@"showPreviewsSetting"] = @([settings showPreviewsSetting]); } +#endif +#if IS_XCODE_10 + if ([TiUtils isIOSVersionOrGreater:@"12.0"]) { + propertiesDict[@"criticalAlertSetting"] = @([settings criticalAlertSetting]); + propertiesDict[@"providesAppNotificationSettings"] = @([settings providesAppNotificationSettings]); + } #endif NSArray *invocationArray = [[NSArray alloc] initWithObjects:&propertiesDict count:1]; diff --git a/iphone/Classes/TiUtils.m b/iphone/Classes/TiUtils.m index 7f6df9d1f2b..1df528a7032 100644 --- a/iphone/Classes/TiUtils.m +++ b/iphone/Classes/TiUtils.m @@ -464,6 +464,10 @@ + (CGPoint)pointValue:(id)value bounds:(CGRect)bounds defaultOffset:(CGPoint)def + (NSNumber *)numberFromObject:(id)obj { + if (obj == nil) { + return nil; + } + if ([obj isKindOfClass:[NSNumber class]]) { return obj; } diff --git a/tests/Resources/ti.app.ios.searchquery.test.js b/tests/Resources/ti.app.ios.searchquery.test.js new file mode 100644 index 00000000000..46fab614996 --- /dev/null +++ b/tests/Resources/ti.app.ios.searchquery.test.js @@ -0,0 +1,48 @@ +/* + * Appcelerator Titanium Mobile + * Copyright (c) 2017-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 */ +/* global Ti */ +/* eslint no-unused-expressions: "off" */ +'use strict'; +var should = require('./utilities/assertions'); + +describe.ios('Titanium.App.iOS.SearchQuery', function () { + + var searchQuery; + + before(function () { + searchQuery = Ti.App.iOS.createSearchQuery({ + queryString: 'title == "Titanium*"', + attributes: [ 'title', 'displayName', 'keywords', 'contentType' ] + }); + }); + + after(function () { + searchQuery = null; + }); + + it('constructor', function () { + should(searchQuery).have.readOnlyProperty('apiName').which.is.a.String; + should(searchQuery.apiName).be.eql('Ti.App.iOS.SearchQuery'); + should(searchQuery.attributes).be.an.Array; + should(searchQuery.attributes.length).be.eql(4); + should(searchQuery.queryString).be.eql('title == "Titanium*"'); + }); + + it('#start()', function () { + should(searchQuery.start).be.a.Function; + }); + + it('#cancel()', function () { + should(searchQuery.cancel).be.a.Function; + }); + + it('#isCancelled()', function () { + should(searchQuery.isCancelled).be.a.Function; + should(searchQuery.isCancelled()).be.a.Boolean; + }); +}); diff --git a/tests/Resources/ti.app.ios.test.js b/tests/Resources/ti.app.ios.test.js new file mode 100644 index 00000000000..1ff98aad3bd --- /dev/null +++ b/tests/Resources/ti.app.ios.test.js @@ -0,0 +1,240 @@ +/* + * Appcelerator Titanium Mobile + * Copyright (c) 2018-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 */ +/* global Ti */ +/* eslint no-unused-expressions: "off" */ +'use strict'; +var should = require('./utilities/assertions'); + +describe.ios('Titanium.App.iOS', function () { + + // --- properties --- + + it('apiName', function () { + should(Ti.App.iOS).have.readOnlyProperty('apiName').which.is.a.String; + should(Ti.App.iOS.apiName).eql('Ti.App.iOS'); + }); + + it('applicationOpenSettingsURL', function () { + should(Ti.App.iOS.applicationOpenSettingsURL).be.a.String; + }); + + it('supportedUserActivityTypes', function () { + should(Ti.App.iOS.supportedUserActivityTypes).be.null; // Only non-null if set via Info.plist NSUserActivityTypes key + }); + + // --- methods --- + + it('#cancelLocalNotification(id)', function () { + should(Ti.App.iOS.cancelLocalNotification).be.a.Function; + // TODO: Add more tests + }); + + it('#canceAlllLocalNotifications()', function () { + should(Ti.App.iOS.canceAlllLocalNotifications).be.a.Function; + // TODO: Add more tests + }); + + it('#createSearchQuery(args)', function () { + should(Ti.App.iOS.createSearchQuery).be.a.Function; // More tests can be found in own suite + }); + + it('#createSearchableIndex(args)', function () { + should(Ti.App.iOS.createSearchableIndex).be.a.Function; + + var searchableIndex = Ti.App.iOS.createSearchableIndex(); + + should(searchableIndex).be.an.Object; + should(searchableIndex.apiName).eql('Ti.App.iOS.SearchableIndex'); + should(searchableIndex.addToDefaultSearchableIndex).be.a.Function; + should(searchableIndex.deleteAllSearchableItemByDomainIdenifiers).be.a.Function; + should(searchableIndex.deleteAllSearchableItems).be.a.Function; + should(searchableIndex.deleteAllSearchableItems).be.a.Function; + should(searchableIndex.deleteSearchableItemsByIdentifiers).be.a.Function; + should(searchableIndex.isSupported).be.a.Function; + }); + + it('#createSearchableItem(args)', function () { + should(Ti.App.iOS.createSearchableItem).be.a.Function; + + var itemAttr = Ti.App.iOS.createSearchableItemAttributeSet({ + itemContentType: Ti.App.iOS.UTTYPE_IMAGE, + title: 'Titanium Core Spotlight Tutorial', + contentDescription: 'Tech Example \nOn: ' + String.formatDate(new Date(), 'short'), + keywords: [ 'Mobile', 'Appcelerator', 'Titanium' ] + }); + + var searchableItem = Ti.App.iOS.createSearchableItem({ + uniqueIndentifier: 'my-id', + domainIdentifier: 'com.mydomain', + attributeSet: itemAttr + }); + + should(searchableItem).be.an.Object; + should(searchableItem.apiName).eql('Ti.App.iOS.SearchableItem'); + }); + + it('#createSearchableItemAttributeSet(args)', function () { + should(Ti.App.iOS.createSearchableItemAttributeSet).be.a.Function; + + var itemAttr = Ti.App.iOS.createSearchableItemAttributeSet({ + itemContentType: Ti.App.iOS.UTTYPE_IMAGE, + title: 'Titanium Core Spotlight Tutorial', + contentDescription: 'Tech Example \nOn: ' + String.formatDate(new Date(), 'short'), + keywords: [ 'Mobile', 'Appcelerator', 'Titanium' ] + }); + + should(itemAttr).be.an.Object; + should(itemAttr.apiName).eql('Ti.App.iOS.SearchableItemAttributeSet'); + + // TODO: Move to own test suite, test all 90+ properties + }); + + it('#createUserActivity(args)', function () { + should(Ti.App.iOS.createUserActivity).be.a.Function; + + var userActivity = Ti.App.iOS.createUserActivity({ + activityType: 'com.setdirection.home', + title: 'activity 1', + userInfo: { + msg: 'hello world' + } + }); + + should(userActivity).be.an.Object; + should(userActivity.apiName).eql('Ti.App.iOS.UserActivity'); + should(userActivity.addContentAttributeSet).be.a.Function; + should(userActivity.becomeCurrent).be.a.Function; + should(userActivity.resignCurrent).be.a.Function; + should(userActivity.invalidate).be.a.Function; + should(userActivity.isSupported).be.a.Function; + }); + + it('#createUserDefaults(args)', function (finish) { + this.timeout = 5000; + + should(Ti.App.iOS.createUserDefaults).be.a.Function; + + var userDefaults = Ti.App.iOS.createUserDefaults({ + suiteName: 'group.mySuite' + }); + + userDefaults.addEventListener('change', finish); + + should(userDefaults).be.an.Object; + should(userDefaults.apiName).eql('Ti.App.iOS.UserDefaults'); + should(userDefaults.suiteName).eql('group.mySuite'); + should(userDefaults.getInt).be.a.Function; + should(userDefaults.setInt).be.a.Function; + should(userDefaults.getBool).be.a.Function; + should(userDefaults.setBool).be.a.Function; + should(userDefaults.getDouble).be.a.Function; + should(userDefaults.setDouble).be.a.Function; + should(userDefaults.getList).be.a.Function; + should(userDefaults.setList).be.a.Function; + should(userDefaults.getObject).be.a.Function; + should(userDefaults.setObject).be.a.Function; + should(userDefaults.getString).be.a.Function; + should(userDefaults.setString).be.a.Function; + should(userDefaults.hasProperty).be.a.Function; + should(userDefaults.listProperties).be.a.Function; + should(userDefaults.removeProperty).be.a.Function; + should(userDefaults.removeAllProperties).be.a.Function; + should(userDefaults.setString).be.a.Function; + + // Trigger change + userDefaults.setString('test', 'tirocks'); + }); + + it('#createUserNotificationAction(args)', function () { + should(Ti.App.iOS.createUserNotificationAction).be.a.Function; + // TODO: Add more tests + }); + + it('#createUserNotificationCategory(args)', function () { + should(Ti.App.iOS.createUserNotificationCategory).be.a.Function; + // TODO: Add more tests + }); + + it('UserNotificationCenter', function () { + should(Ti.App.iOS.UserNotificationCenter).be.an.Object; + should(Ti.App.iOS.UserNotificationCenter.apiName).eql('Ti.App.iOS.UserNotificationCenter'); + should(Ti.App.iOS.UserNotificationCenter.getDeliveredNotifications).be.a.Function; + should(Ti.App.iOS.UserNotificationCenter.getPendingNotifications).be.a.Function; + should(Ti.App.iOS.UserNotificationCenter.removeDeliveredNotifications).be.a.Function; + should(Ti.App.iOS.UserNotificationCenter.removePendingNotifications).be.a.Function; + should(Ti.App.iOS.UserNotificationCenter.requestUserNotificationSettings).be.a.Function; + }); + + it('#constants', function () { + should(Ti.App.iOS.BACKGROUNDFETCHINTERVAL_MIN).be.a.Number; + should(Ti.App.iOS.BACKGROUNDFETCHINTERVAL_NEVER).be.a.Number; + + should(Ti.App.iOS.EVENT_ACCESSIBILITY_LAYOUT_CHANGED).be.a.Number; + should(Ti.App.iOS.EVENT_ACCESSIBILITY_SCREEN_CHANGED).be.a.Number; + + should(Ti.App.iOS.USER_NOTIFICATION_ACTIVATION_MODE_BACKGROUND).be.a.Number; + should(Ti.App.iOS.USER_NOTIFICATION_ACTIVATION_MODE_FOREGROUND).be.a.Number; + + should(Ti.App.iOS.USER_NOTIFICATION_ALERT_STYLE_ALERT).be.a.Number; + should(Ti.App.iOS.USER_NOTIFICATION_ALERT_STYLE_BANNER).be.a.Number; + should(Ti.App.iOS.USER_NOTIFICATION_ALERT_STYLE_NONE).be.a.Number; + + should(Ti.App.iOS.USER_NOTIFICATION_AUTHORIZATION_STATUS_AUTHORIZED).be.a.Number; + should(Ti.App.iOS.USER_NOTIFICATION_AUTHORIZATION_STATUS_DENIED).be.a.Number; + should(Ti.App.iOS.USER_NOTIFICATION_AUTHORIZATION_STATUS_NOT_DETERMINED).be.a.Number; + + should(Ti.App.iOS.USER_NOTIFICATION_BEHAVIOR_DEFAULT).be.a.Number; + should(Ti.App.iOS.USER_NOTIFICATION_BEHAVIOR_TEXTINPUT).be.a.Number; + + should(Ti.App.iOS.USER_NOTIFICATION_CATEGORY_OPTION_CUSTOM_DISMISS_ACTION).be.a.Number; + should(Ti.App.iOS.USER_NOTIFICATION_CATEGORY_OPTION_HIDDEN_PREVIEWS_SHOW_TITLE).be.a.Number; + should(Ti.App.iOS.USER_NOTIFICATION_CATEGORY_OPTION_HIDDEN_PREVIEWS_SHOW_SUBTITLE).be.a.Number; + should(Ti.App.iOS.USER_NOTIFICATION_CATEGORY_OPTION_NONE).be.a.Number; + + should(Ti.App.iOS.USER_NOTIFICATION_SETTING_DISABLED).be.a.Number; + should(Ti.App.iOS.USER_NOTIFICATION_SETTING_ENABLED).be.a.Number; + should(Ti.App.iOS.USER_NOTIFICATION_SETTING_NOT_SUPPORTED).be.a.Number; + + should(Ti.App.iOS.USER_NOTIFICATION_TYPE_ALERT).be.a.Number; + should(Ti.App.iOS.USER_NOTIFICATION_TYPE_BADGE).be.a.Number; + should(Ti.App.iOS.USER_NOTIFICATION_TYPE_NONE).be.a.Number; + should(Ti.App.iOS.USER_NOTIFICATION_TYPE_SOUND).be.a.Number; + + should(Ti.App.iOS.UTTYPE_AUDIO).be.a.String; + should(Ti.App.iOS.UTTYPE_BMP).be.a.String; + should(Ti.App.iOS.UTTYPE_FLAT_RTFD).be.a.String; + should(Ti.App.iOS.UTTYPE_GID).be.a.String; + should(Ti.App.iOS.UTTYPE_HTML).be.a.String; + should(Ti.App.iOS.UTTYPE_ICO).be.a.String; + should(Ti.App.iOS.UTTYPE_IMAGE).be.a.String; + should(Ti.App.iOS.UTTYPE_JPEG).be.a.String; + should(Ti.App.iOS.UTTYPE_JPEG2000).be.a.String; + should(Ti.App.iOS.UTTYPE_MOVIE).be.a.String; + should(Ti.App.iOS.UTTYPE_MP3).be.a.String; + should(Ti.App.iOS.UTTYPE_MPEG).be.a.String; + should(Ti.App.iOS.UTTYPE_MPEG4).be.a.String; + should(Ti.App.iOS.UTTYPE_MPEG4_AUDIO).be.a.String; + should(Ti.App.iOS.UTTYPE_PDF).be.a.String; + should(Ti.App.iOS.UTTYPE_PICT).be.a.String; + should(Ti.App.iOS.UTTYPE_PLAIN_TEXT).be.a.String; + should(Ti.App.iOS.UTTYPE_PNG).be.a.String; + should(Ti.App.iOS.UTTYPE_QUICKTIME_IMAGE).be.a.String; + should(Ti.App.iOS.UTTYPE_QUICKTIME_MOVIE).be.a.String; + should(Ti.App.iOS.UTTYPE_RTF).be.a.String; + should(Ti.App.iOS.UTTYPE_RTFD).be.a.String; + should(Ti.App.iOS.UTTYPE_TEXT).be.a.String; + should(Ti.App.iOS.UTTYPE_TIFF).be.a.String; + should(Ti.App.iOS.UTTYPE_TXN_TEXT_AND_MULTIMEDIA_DATA).be.a.String; + should(Ti.App.iOS.UTTYPE_UTF16_EXTERNAL_PLAIN_TEXT).be.a.String; + should(Ti.App.iOS.UTTYPE_UTF16_PLAIN_TEXT).be.a.String; + should(Ti.App.iOS.UTTYPE_UTF8_PLAIN_TEXT).be.a.String; + should(Ti.App.iOS.UTTYPE_VIDEO).be.a.String; + should(Ti.App.iOS.UTTYPE_WEB_ARCHIVE).be.a.String; + should(Ti.App.iOS.UTTYPE_XML).be.a.String; + }); +});