diff options
author | Joel Fischer <joeljfischer@gmail.com> | 2020-09-01 12:56:02 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-09-01 12:56:02 -0400 |
commit | 36a7790d9075c0a44c0f5902e33ac0a5eeaecc76 (patch) | |
tree | 7e44bc2438e65657e96fc12ae54c6fdb12f62115 | |
parent | 4376899c9d88ad441078143bcaad59001dbc7309 (diff) | |
parent | bc330333b611f841801aa3dadb07e63ace0a6158 (diff) | |
download | sdl_ios-36a7790d9075c0a44c0f5902e33ac0a5eeaecc76.tar.gz |
Merge pull request #1753 from smartdevicelink/bugfix/issue-1749-refactor-t-g-manager-queues
Refactor Text & Graphic Manager to use queues
-rw-r--r-- | SmartDeviceLink-iOS.xcodeproj/project.pbxproj | 36 | ||||
-rw-r--r-- | SmartDeviceLink/SDLLogFileModuleMap.m | 2 | ||||
-rw-r--r-- | SmartDeviceLink/SDLTextAndGraphicManager.m | 492 | ||||
-rw-r--r-- | SmartDeviceLink/SDLTextAndGraphicState.h | 39 | ||||
-rw-r--r-- | SmartDeviceLink/SDLTextAndGraphicState.m | 48 | ||||
-rw-r--r-- | SmartDeviceLink/SDLTextAndGraphicUpdateOperation.h | 42 | ||||
-rw-r--r-- | SmartDeviceLink/SDLTextAndGraphicUpdateOperation.m | 493 | ||||
-rw-r--r-- | SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicManagerSpec.m | 779 | ||||
-rw-r--r-- | SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicUpdateOperationSpec.m | 929 | ||||
-rw-r--r-- | SmartDeviceLinkTests/SDLScreenManagerSpec.m | 5 |
10 files changed, 1718 insertions, 1147 deletions
diff --git a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj index ae70e6fd9..3ede71e9a 100644 --- a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj +++ b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj @@ -441,6 +441,10 @@ 4A457DD924A5137100386CBA /* SDLLifecycleProtocolHandlerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A457DD824A5137100386CBA /* SDLLifecycleProtocolHandlerSpec.m */; }; 4A4AD8A424894260008FC414 /* TestOldConfigurationUpdateManagerDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A4AD8A324894260008FC414 /* TestOldConfigurationUpdateManagerDelegate.m */; }; 4A4AD8A724894270008FC414 /* TestNewConfigurationUpdateManagerDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A4AD8A624894270008FC414 /* TestNewConfigurationUpdateManagerDelegate.m */; }; + 4A89AE1824E589D60017EBDC /* SDLTextAndGraphicUpdateOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A89AE1624E589D60017EBDC /* SDLTextAndGraphicUpdateOperation.h */; }; + 4A89AE1924E589D60017EBDC /* SDLTextAndGraphicUpdateOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A89AE1724E589D60017EBDC /* SDLTextAndGraphicUpdateOperation.m */; }; + 4A89AE1C24E595410017EBDC /* SDLTextAndGraphicState.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A89AE1A24E595410017EBDC /* SDLTextAndGraphicState.h */; }; + 4A89AE1D24E595410017EBDC /* SDLTextAndGraphicState.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A89AE1B24E595410017EBDC /* SDLTextAndGraphicState.m */; }; 4A899D3624D31FEE007BDD9F /* SDLOnUpdateFile.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A899D3424D31FEE007BDD9F /* SDLOnUpdateFile.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4A899D3724D31FEE007BDD9F /* SDLOnUpdateFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A899D3524D31FEE007BDD9F /* SDLOnUpdateFile.m */; }; 4A899D3A24D32273007BDD9F /* SDLOnUpdateSubMenu.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A899D3824D32273007BDD9F /* SDLOnUpdateSubMenu.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -453,6 +457,7 @@ 4A99D0132475773C009B43E6 /* SDLImageField+ScreenManagerExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A99D0112475773C009B43E6 /* SDLImageField+ScreenManagerExtensions.m */; }; 4A9D02BE2497EED400FBE99B /* SDLLifecycleRPCAdapter.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9D02BC2497EED400FBE99B /* SDLLifecycleRPCAdapter.h */; }; 4A9D02BF2497EED400FBE99B /* SDLLifecycleRPCAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9D02BD2497EED400FBE99B /* SDLLifecycleRPCAdapter.m */; }; + 4AAA5AE324EC686C00837CC7 /* SDLTextAndGraphicUpdateOperationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AAA5AE224EC686C00837CC7 /* SDLTextAndGraphicUpdateOperationSpec.m */; }; 4AC68FBA24D33B7A0073FF67 /* SDLOnUpdateFileSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AC68FB924D33B7A0073FF67 /* SDLOnUpdateFileSpec.m */; }; 4AC68FBC24D33E5D0073FF67 /* SDLOnUpdateSubMenuSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AC68FBB24D33E5D0073FF67 /* SDLOnUpdateSubMenuSpec.m */; }; 4AC68FBE24D340400073FF67 /* SDLDynamicUpdateCapabilitiesSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AC68FBD24D340400073FF67 /* SDLDynamicUpdateCapabilitiesSpec.m */; }; @@ -2222,6 +2227,10 @@ 4A4AD8A324894260008FC414 /* TestOldConfigurationUpdateManagerDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = TestOldConfigurationUpdateManagerDelegate.m; path = DevAPISpecs/TestOldConfigurationUpdateManagerDelegate.m; sourceTree = "<group>"; }; 4A4AD8A524894270008FC414 /* TestNewConfigurationUpdateManagerDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TestNewConfigurationUpdateManagerDelegate.h; sourceTree = "<group>"; }; 4A4AD8A624894270008FC414 /* TestNewConfigurationUpdateManagerDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestNewConfigurationUpdateManagerDelegate.m; sourceTree = "<group>"; }; + 4A89AE1624E589D60017EBDC /* SDLTextAndGraphicUpdateOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDLTextAndGraphicUpdateOperation.h; sourceTree = "<group>"; }; + 4A89AE1724E589D60017EBDC /* SDLTextAndGraphicUpdateOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLTextAndGraphicUpdateOperation.m; sourceTree = "<group>"; }; + 4A89AE1A24E595410017EBDC /* SDLTextAndGraphicState.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDLTextAndGraphicState.h; sourceTree = "<group>"; }; + 4A89AE1B24E595410017EBDC /* SDLTextAndGraphicState.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLTextAndGraphicState.m; sourceTree = "<group>"; }; 4A899D3424D31FEE007BDD9F /* SDLOnUpdateFile.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDLOnUpdateFile.h; sourceTree = "<group>"; }; 4A899D3524D31FEE007BDD9F /* SDLOnUpdateFile.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLOnUpdateFile.m; sourceTree = "<group>"; }; 4A899D3824D32273007BDD9F /* SDLOnUpdateSubMenu.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDLOnUpdateSubMenu.h; sourceTree = "<group>"; }; @@ -2234,6 +2243,7 @@ 4A99D0112475773C009B43E6 /* SDLImageField+ScreenManagerExtensions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "SDLImageField+ScreenManagerExtensions.m"; sourceTree = "<group>"; }; 4A9D02BC2497EED400FBE99B /* SDLLifecycleRPCAdapter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDLLifecycleRPCAdapter.h; sourceTree = "<group>"; }; 4A9D02BD2497EED400FBE99B /* SDLLifecycleRPCAdapter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLLifecycleRPCAdapter.m; sourceTree = "<group>"; }; + 4AAA5AE224EC686C00837CC7 /* SDLTextAndGraphicUpdateOperationSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLTextAndGraphicUpdateOperationSpec.m; path = DevAPISpecs/SDLTextAndGraphicUpdateOperationSpec.m; sourceTree = "<group>"; }; 4AC68FB924D33B7A0073FF67 /* SDLOnUpdateFileSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLOnUpdateFileSpec.m; sourceTree = "<group>"; }; 4AC68FBB24D33E5D0073FF67 /* SDLOnUpdateSubMenuSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLOnUpdateSubMenuSpec.m; sourceTree = "<group>"; }; 4AC68FBD24D340400073FF67 /* SDLDynamicUpdateCapabilitiesSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLDynamicUpdateCapabilitiesSpec.m; sourceTree = "<group>"; }; @@ -4205,6 +4215,23 @@ name = "Status Manager"; sourceTree = "<group>"; }; + 4AAA5ADF24EC678A00837CC7 /* Operations */ = { + isa = PBXGroup; + children = ( + 4AAA5AE224EC686C00837CC7 /* SDLTextAndGraphicUpdateOperationSpec.m */, + ); + name = Operations; + sourceTree = "<group>"; + }; + 4AAA5AE424EC690700837CC7 /* Operations */ = { + isa = PBXGroup; + children = ( + 4A89AE1624E589D60017EBDC /* SDLTextAndGraphicUpdateOperation.h */, + 4A89AE1724E589D60017EBDC /* SDLTextAndGraphicUpdateOperation.m */, + ); + name = Operations; + sourceTree = "<group>"; + }; 5D0218EB1A8E795700D1BF62 /* UI */ = { isa = PBXGroup; children = ( @@ -4289,8 +4316,11 @@ 5D0A737D203F23B30001595D /* Text and Graphic */ = { isa = PBXGroup; children = ( + 4AAA5AE424EC690700837CC7 /* Operations */, 5D0A7372203F0C730001595D /* SDLTextAndGraphicManager.h */, 5D0A7373203F0C730001595D /* SDLTextAndGraphicManager.m */, + 4A89AE1A24E595410017EBDC /* SDLTextAndGraphicState.h */, + 4A89AE1B24E595410017EBDC /* SDLTextAndGraphicState.m */, ); name = "Text and Graphic"; sourceTree = "<group>"; @@ -6091,6 +6121,7 @@ 5DAD5F8320507DF30025624C /* Text and Graphic */ = { isa = PBXGroup; children = ( + 4AAA5ADF24EC678A00837CC7 /* Operations */, 5DAD5F86205087430025624C /* SDLTextAndGraphicManagerSpec.m */, ); name = "Text and Graphic"; @@ -6894,9 +6925,11 @@ 884E702321FBA952008D53BA /* SDLAppServiceType.h in Headers */, DAC572571D1067270004288B /* SDLTouchManager.h in Headers */, 5D61FE0D1A84238C00846EE7 /* SDLVrCapabilities.h in Headers */, + 4A89AE1824E589D60017EBDC /* SDLTextAndGraphicUpdateOperation.h in Headers */, EEB1932E205028B700A8940C /* SDLControlFramePayloadTransportEventUpdate.h in Headers */, EE798CA420561210008EDE8E /* SDLSecondaryTransportManager.h in Headers */, 5DBF06271E64A91D00A5CF03 /* SDLLogFileModule.h in Headers */, + 4A89AE1C24E595410017EBDC /* SDLTextAndGraphicState.h in Headers */, 5D61FC531A84238C00846EE7 /* SDLButtonEventMode.h in Headers */, 88E6F1AD220E19DF006156F9 /* SDLMediaServiceData.h in Headers */, 1FF7DAB61F75B27300B46C30 /* SDLFocusableItemLocatorType.h in Headers */, @@ -7915,6 +7948,7 @@ 9FE2471222D77AA400F8D2FC /* SDLCreateWindowResponse.m in Sources */, 5D61FDBC1A84238C00846EE7 /* SDLSystemAction.m in Sources */, 5D61FC381A84238C00846EE7 /* SDLAlert.m in Sources */, + 4A89AE1D24E595410017EBDC /* SDLTextAndGraphicState.m in Sources */, 88AAD4BD2211B76800F1E6D7 /* SDLMediaServiceManifest.m in Sources */, 884E701C21FB8D0F008D53BA /* SDLPublishAppService.m in Sources */, 8831FA49220235B000B8FFB7 /* SDLAppServicesCapabilities.m in Sources */, @@ -8019,6 +8053,7 @@ 88EED83F1F33C5A400E6C42E /* SDLSendHapticData.m in Sources */, 5D16545B1D3E7A1600554D93 /* SDLLifecycleManager.m in Sources */, E9C32B971AB20BA200F283AF /* SDLTimer.m in Sources */, + 4A89AE1924E589D60017EBDC /* SDLTextAndGraphicUpdateOperation.m in Sources */, 10893C172417D78300BA347E /* SDLIconArchiveFile.m in Sources */, 5D61FCB61A84238C00846EE7 /* SDLGetVehicleData.m in Sources */, 5DAB5F572098E5D100A020C8 /* SDLProtocolConstants.m in Sources */, @@ -8340,6 +8375,7 @@ 88B8657924A102AB003491AD /* TestSubscribeButtonObserver.m in Sources */, 5DAE06751BDEC6D600F9B498 /* SDLArtworkSpec.m in Sources */, 5DA23FF01F2FA0FF009C0313 /* SDLControlFramePayloadEndServiceSpec.m in Sources */, + 4AAA5AE324EC686C00837CC7 /* SDLTextAndGraphicUpdateOperationSpec.m in Sources */, 162E83591A9BDE8B00906325 /* SDLListFilesResponseSpec.m in Sources */, 162E832A1A9BDE8B00906325 /* SDLDeleteInteractionChoiceSetSpec.m in Sources */, EE460E0A2066B6E40006EDD3 /* SDLControlFramePayloadRegisterSecondaryTransportNakSpec.m in Sources */, diff --git a/SmartDeviceLink/SDLLogFileModuleMap.m b/SmartDeviceLink/SDLLogFileModuleMap.m index 3d25be5d9..403a82258 100644 --- a/SmartDeviceLink/SDLLogFileModuleMap.m +++ b/SmartDeviceLink/SDLLogFileModuleMap.m @@ -118,7 +118,7 @@ } + (SDLLogFileModule *)sdl_screenManagerTextAndGraphicModule { - return [SDLLogFileModule moduleWithName:@"Screen/TextAndGraphic" files:[NSSet setWithArray:@[@"SDLTextAndGraphicManager"]]]; + return [SDLLogFileModule moduleWithName:@"Screen/TextAndGraphic" files:[NSSet setWithArray:@[@"SDLTextAndGraphicManager", @"SDLTextAndGraphicUpdateOperation"]]]; } + (SDLLogFileModule *)sdl_screenManagerSoftButtonModule { diff --git a/SmartDeviceLink/SDLTextAndGraphicManager.m b/SmartDeviceLink/SDLTextAndGraphicManager.m index 6324e13e3..d7edb7b98 100644 --- a/SmartDeviceLink/SDLTextAndGraphicManager.m +++ b/SmartDeviceLink/SDLTextAndGraphicManager.m @@ -26,6 +26,8 @@ #import "SDLSystemCapability.h" #import "SDLSystemCapabilityManager.h" #import "SDLTextField.h" +#import "SDLTextAndGraphicUpdateOperation.h" +#import "SDLTextAndGraphicState.h" #import "SDLWindowCapability.h" #import "SDLWindowCapability+ScreenManagerExtensions.h" @@ -43,15 +45,7 @@ NS_ASSUME_NONNULL_BEGIN */ @property (strong, nonatomic) SDLShow *currentScreenData; -/** - This is the "full" update, including both text and image names, whether or not that will succeed at the moment (e.g. if images are in the process of uploading) - */ -@property (strong, nonatomic, nullable) SDLShow *inProgressUpdate; -@property (copy, nonatomic, nullable) SDLTextAndGraphicUpdateCompletionHandler inProgressHandler; - -@property (strong, nonatomic, nullable) SDLShow *queuedImageUpdate; -@property (assign, nonatomic) BOOL hasQueuedUpdate; -@property (copy, nonatomic, nullable) SDLTextAndGraphicUpdateCompletionHandler queuedUpdateHandler; +@property (strong, nonatomic) NSOperationQueue *transactionQueue; @property (strong, nonatomic, nullable) SDLWindowCapability *windowCapability; @property (strong, nonatomic, nullable) SDLHMILevel currentLevel; @@ -72,6 +66,7 @@ NS_ASSUME_NONNULL_BEGIN _connectionManager = connectionManager; _fileManager = fileManager; _systemCapabilityManager = systemCapabilityManager; + _transactionQueue = [self sdl_newTransactionQueue]; _alignment = SDLTextAlignmentCenter; @@ -118,11 +113,7 @@ NS_ASSUME_NONNULL_BEGIN _textField4Type = nil; _currentScreenData = [[SDLShow alloc] init]; - _inProgressUpdate = nil; - _inProgressHandler = nil; - _queuedImageUpdate = nil; - _hasQueuedUpdate = NO; - _queuedUpdateHandler = nil; + _transactionQueue = [self sdl_newTransactionQueue]; _windowCapability = nil; _currentLevel = SDLHMILevelNone; _blankArtwork = nil; @@ -130,18 +121,32 @@ NS_ASSUME_NONNULL_BEGIN _isDirty = NO; } -#pragma mark - Upload / Send +- (NSOperationQueue *)sdl_newTransactionQueue { + NSOperationQueue *queue = [[NSOperationQueue alloc] init]; + queue.name = @"SDLTextAndGraphicManager Transaction Queue"; + queue.maxConcurrentOperationCount = 1; + queue.qualityOfService = NSQualityOfServiceUserInitiated; + queue.suspended = YES; -- (void)updateWithCompletionHandler:(nullable SDLTextAndGraphicUpdateCompletionHandler)handler { - if (self.isBatchingUpdates) { return; } + return queue; +} - // Don't send if we're in HMI NONE - if (self.currentLevel == nil || [self.currentLevel isEqualToString:SDLHMILevelNone]) { - self.waitingOnHMILevelUpdateToUpdate = YES; - return; +/// Suspend the queue if the window capabilities are nil (we assume that text and graphics are not supported yet) +/// OR if the HMI level is NONE since we want to delay sending RPCs until we're in non-NONE +- (void)sdl_updateTransactionQueueSuspended { + if (self.windowCapability == nil || [self.currentLevel isEqualToEnum:SDLHMILevelNone]) { + SDLLogD(@"Suspending the transaction queue. Current HMI level is NONE: %@, window capabilities are nil: %@", ([self.currentLevel isEqualToEnum:SDLHMILevelNone] ? @"YES" : @"NO"), (self.windowCapability == nil ? @"YES" : @"NO")); + self.transactionQueue.suspended = YES; } else { - self.waitingOnHMILevelUpdateToUpdate = NO; + SDLLogD(@"Starting the transaction queue"); + self.transactionQueue.suspended = NO; } +} + +#pragma mark - Upload / Send + +- (void)updateWithCompletionHandler:(nullable SDLTextAndGraphicUpdateCompletionHandler)handler { + if (self.isBatchingUpdates) { return; } if (self.isDirty) { self.isDirty = NO; @@ -151,402 +156,43 @@ NS_ASSUME_NONNULL_BEGIN - (void)sdl_updateWithCompletionHandler:(nullable SDLTextAndGraphicUpdateCompletionHandler)handler { SDLLogD(@"Updating text and graphics"); - if (self.inProgressUpdate != nil) { - SDLLogV(@"In progress update exists, queueing update"); - // If we already have a pending update, we're going to tell the old handler that it was superseded by a new update and then return - if (self.queuedUpdateHandler != nil) { - SDLLogV(@"Queued update already exists, superseding previous queued update"); - self.queuedUpdateHandler([NSError sdl_textAndGraphicManager_pendingUpdateSuperseded]); - self.queuedUpdateHandler = nil; - } - - if (handler != nil) { - self.queuedUpdateHandler = handler; - } else { - self.hasQueuedUpdate = YES; - } - - return; + if (self.transactionQueue.operationCount > 0) { + SDLLogV(@"Transactions already exist, cancelling them"); + [self.transactionQueue cancelAllOperations]; } - SDLShow *fullShow = [[SDLShow alloc] init]; - fullShow.alignment = self.alignment; - fullShow.metadataTags = [[SDLMetadataTags alloc] init]; - fullShow = [self sdl_assembleShowText:fullShow]; - fullShow = [self sdl_assembleShowImages:fullShow]; - - self.inProgressHandler = handler; - - __weak typeof(self)weakSelf = self; - if (!([self sdl_shouldUpdatePrimaryImage] || [self sdl_shouldUpdateSecondaryImage])) { - SDLLogV(@"No images to send, sending text"); - // If there are no images to update, just send the text - self.inProgressUpdate = [self sdl_extractTextFromShow:fullShow]; - } else if (![self sdl_artworkNeedsUpload:self.primaryGraphic] && ![self sdl_artworkNeedsUpload:self.secondaryGraphic]) { - SDLLogV(@"Images already uploaded, sending full update"); - // The files to be updated are already uploaded, send the full show immediately - self.inProgressUpdate = fullShow; - } else { - SDLLogV(@"Images need to be uploaded, sending text and uploading images"); - - // We need to upload or queue the upload of the images - // Send the text immediately - self.inProgressUpdate = [self sdl_extractTextFromShow:fullShow]; - - // Start uploading the images - __block SDLShow *thisUpdate = fullShow; - [self sdl_uploadImagesWithCompletionHandler:^(NSError *_Nullable error) { - __strong typeof(weakSelf) strongSelf = weakSelf; - - if (error != nil) { - SDLShow *showWithGraphics = [self sdl_createImageOnlyShowWithPrimaryArtwork:self.primaryGraphic secondaryArtwork:self.secondaryGraphic]; - if (showWithGraphics != nil) { - SDLLogW(@"Some images failed to upload. Sending update with the successfully uploaded images"); - self.inProgressUpdate = showWithGraphics; - } else { - SDLLogE(@"All images failed to upload. No graphics to show, skipping update."); - self.inProgressUpdate = nil; - } - return; - } - - // Check if queued image update still matches our images (there could have been a new Show in the meantime) and send a new update if it does. Since the images will already be on the head unit, the whole show will be sent - // TODO: Send delete if it doesn't? - if ([strongSelf sdl_showImages:thisUpdate isEqualToShowImages:strongSelf.queuedImageUpdate]) { - SDLLogV(@"Queued image update matches the images we need, sending update"); - return [strongSelf sdl_updateWithCompletionHandler:strongSelf.inProgressHandler]; - } else { - SDLLogV(@"Queued image update does not match the images we need, skipping update"); - } - }]; - // When the images are done uploading, send another show with the images - self.queuedImageUpdate = fullShow; - } - - if (self.inProgressUpdate == nil) { return; } - - [self.connectionManager sendConnectionRequest:self.inProgressUpdate withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) { - __strong typeof(weakSelf) strongSelf = weakSelf; - SDLLogD(@"Text and Graphic update completed"); - - // TODO: Monitor and delete old images when space is low? - if (response.success) { - [strongSelf sdl_updateCurrentScreenDataFromShow:(SDLShow *)request]; - } - - strongSelf.inProgressUpdate = nil; - if (strongSelf.inProgressHandler != nil) { - strongSelf.inProgressHandler(error); - strongSelf.inProgressHandler = nil; - } + __weak typeof(self) weakSelf = self; + SDLTextAndGraphicUpdateOperation *updateOperation = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:self.connectionManager fileManager:self.fileManager currentCapabilities:self.windowCapability currentScreenData:self.currentScreenData newState:[self currentState] currentScreenDataUpdatedHandler:^(SDLShow * _Nonnull newScreenData) { + weakSelf.currentScreenData = newScreenData; + [weakSelf sdl_updatePendingOperationsWithNewScreenData:newScreenData]; + } updateCompletionHandler:handler]; - if (strongSelf.hasQueuedUpdate) { - SDLLogV(@"Queued update exists, sending another update"); - [strongSelf sdl_updateWithCompletionHandler:[strongSelf.queuedUpdateHandler copy]]; - strongSelf.queuedUpdateHandler = nil; - strongSelf.hasQueuedUpdate = NO; + __weak typeof(updateOperation) weakOp = updateOperation; + updateOperation.completionBlock = ^{ + if (weakOp.error != nil) { + SDLLogE(@"Update operation failed with error: %@", weakOp.error); } - }]; + }; + [self.transactionQueue addOperation:updateOperation]; } -- (void)sdl_uploadImagesWithCompletionHandler:(void (^)(NSError *_Nullable error))handler { - NSMutableArray<SDLArtwork *> *artworksToUpload = [NSMutableArray array]; - if ([self sdl_shouldUpdatePrimaryImage] && !self.primaryGraphic.isStaticIcon) { - [artworksToUpload addObject:self.primaryGraphic]; - } - if ([self sdl_shouldUpdateSecondaryImage] && !self.secondaryGraphic.isStaticIcon) { - [artworksToUpload addObject:self.secondaryGraphic]; - } +- (void)sdl_updatePendingOperationsWithNewScreenData:(SDLShow *)newScreenData { + for (NSOperation *operation in self.transactionQueue.operations) { + if (![operation isMemberOfClass:SDLTextAndGraphicUpdateOperation.class] || operation.isExecuting) { continue; } + SDLTextAndGraphicUpdateOperation *updateOp = (SDLTextAndGraphicUpdateOperation *)operation; - if (artworksToUpload.count == 0 - && (self.primaryGraphic.isStaticIcon || self.secondaryGraphic.isStaticIcon)) { - SDLLogD(@"Upload attempted on static icons, sending them without upload instead"); - handler(nil); + updateOp.currentScreenData = newScreenData; } - - [self.fileManager uploadArtworks:artworksToUpload completionHandler:^(NSArray<NSString *> * _Nonnull artworkNames, NSError * _Nullable error) { - if (error != nil) { - SDLLogW(@"Text and graphic manager artwork failed to upload with error: %@", error.localizedDescription); - } - - handler(error); - }]; } -#pragma mark - Assembly of Shows - -#pragma mark Images - -- (SDLShow *)sdl_assembleShowImages:(SDLShow *)show { - if (![self sdl_shouldUpdatePrimaryImage] && ![self sdl_shouldUpdateSecondaryImage]) { - return show; - } +#pragma mark - Convert to State - if ([self sdl_shouldUpdatePrimaryImage]) { - show.graphic = self.primaryGraphic.imageRPC; - } - if ([self sdl_shouldUpdateSecondaryImage]) { - show.secondaryGraphic = self.secondaryGraphic.imageRPC; - } - - return show; -} - -#pragma mark Text - -- (SDLShow *)sdl_assembleShowText:(SDLShow *)show { - [self sdl_setBlankTextFieldsWithShow:show]; - - if (self.mediaTrackTextField != nil && [self sdl_shouldUpdateMediaTextField]) { - show.mediaTrack = self.mediaTrackTextField; - } else { - show.mediaTrack = @""; - } - - if (self.title != nil && [self sdl_shouldUpdateTitleField]) { - show.templateTitle = self.title; - } else { - show.templateTitle = @""; - } - - NSArray *nonNilFields = [self sdl_findNonNilTextFields]; - if (nonNilFields.count == 0) { return show; } - - NSUInteger numberOfLines = self.windowCapability.maxNumberOfMainFieldLines; - if (numberOfLines == 1) { - show = [self sdl_assembleOneLineShowText:show withShowFields:nonNilFields]; - } else if (numberOfLines == 2) { - show = [self sdl_assembleTwoLineShowText:show]; - } else if (numberOfLines == 3) { - show = [self sdl_assembleThreeLineShowText:show]; - } else if (numberOfLines == 4) { - show = [self sdl_assembleFourLineShowText:show]; - } - - return show; -} - -- (SDLShow *)sdl_assembleOneLineShowText:(SDLShow *)show withShowFields:(NSArray<NSString *> *)fields { - NSMutableString *showString1 = [NSMutableString stringWithString:fields[0]]; - for (NSUInteger i = 1; i < fields.count; i++) { - [showString1 appendFormat:@" - %@", fields[i]]; - } - show.mainField1 = showString1.copy; - - SDLMetadataTags *tags = [[SDLMetadataTags alloc] init]; - NSMutableArray<SDLMetadataType> *metadataArray = [NSMutableArray array]; - self.textField1Type ? [metadataArray addObject:self.textField1Type] : nil; - self.textField2Type ? [metadataArray addObject:self.textField2Type] : nil; - self.textField3Type ? [metadataArray addObject:self.textField3Type] : nil; - self.textField4Type ? [metadataArray addObject:self.textField4Type] : nil; - tags.mainField1 = [metadataArray copy]; - show.metadataTags = tags; - - return show; -} - -- (SDLShow *)sdl_assembleTwoLineShowText:(SDLShow *)show { - NSMutableString *tempString = [NSMutableString string]; - if (self.textField1.length > 0) { - // If text 1 exists, put it in slot 1 - [tempString appendString:self.textField1]; - show.metadataTags.mainField1 = self.textField1Type.length > 0 ? @[self.textField1Type] : @[]; - } - - if (self.textField2.length > 0) { - if (!(self.textField3.length > 0 || self.textField4.length > 0)) { - // If text 3 & 4 do not exist, put it in slot 2 - show.mainField2 = self.textField2; - show.metadataTags.mainField2 = self.textField2Type.length > 0 ? @[self.textField2Type] : @[]; - } else if (self.textField1.length > 0) { - // If text 1 exists, put it in slot 1 formatted - [tempString appendFormat:@" - %@", self.textField2]; - show.metadataTags.mainField1 = self.textField2Type.length > 0 ? [show.metadataTags.mainField1 arrayByAddingObjectsFromArray:@[self.textField2Type]] : show.metadataTags.mainField1; - } else { - // If text 1 does not exist, put it in slot 1 unformatted - [tempString appendString:self.textField2]; - show.metadataTags.mainField1 = self.textField2Type.length > 0 ? @[self.textField2Type] : @[]; - } - } - - show.mainField1 = [tempString copy]; - - tempString = [NSMutableString string]; - if (self.textField3.length > 0) { - // If text 3 exists, put it in slot 2 - [tempString appendString:self.textField3]; - show.metadataTags.mainField2 = self.textField3Type.length > 0 ? @[self.textField3Type] : @[]; - } - - if (self.textField4.length > 0) { - if (self.textField3.length > 0) { - // If text 3 exists, put it in slot 2 formatted - [tempString appendFormat:@" - %@", self.textField4]; - show.metadataTags.mainField2 = self.textField4Type.length > 0 ? [show.metadataTags.mainField2 arrayByAddingObjectsFromArray:@[self.textField4Type]] : show.metadataTags.mainField2; - } else { - // If text 3 does not exist, put it in slot 3 unformatted - [tempString appendString:self.textField4]; - show.metadataTags.mainField2 = self.textField4Type.length > 0 ? @[self.textField4Type] : @[]; - } - } - - if (tempString.length > 0) { - show.mainField2 = [tempString copy]; - } - - return show; -} - -- (SDLShow *)sdl_assembleThreeLineShowText:(SDLShow *)show { - if (self.textField1.length > 0) { - show.mainField1 = self.textField1; - show.metadataTags.mainField1 = self.textField1Type.length > 0 ? @[self.textField1Type] : @[]; - } - - if (self.textField2.length > 0) { - show.mainField2 = self.textField2; - show.metadataTags.mainField2 = self.textField2Type.length > 0 ? @[self.textField2Type] : @[]; - } - - NSMutableString *tempString = [NSMutableString string]; - if (self.textField3.length > 0) { - [tempString appendString:self.textField3]; - show.metadataTags.mainField3 = self.textField3Type.length > 0 ? @[self.textField3Type] : @[]; - } - - if (self.textField4.length > 0) { - if (self.textField3.length > 0) { - // If text 3 exists, put it in slot 3 formatted - [tempString appendFormat:@" - %@", self.textField4]; - show.metadataTags.mainField3 = self.textField4Type.length > 0 ? [show.metadataTags.mainField3 arrayByAddingObjectsFromArray:@[self.textField4Type]] : show.metadataTags.mainField3; - } else { - // If text 3 does not exist, put it in slot 3 formatted - [tempString appendString:self.textField4]; - show.metadataTags.mainField3 = self.textField4Type.length > 0 ? @[self.textField4Type] : @[]; - } - } - - show.mainField3 = [tempString copy]; - - return show; -} - -- (SDLShow *)sdl_assembleFourLineShowText:(SDLShow *)show { - if (self.textField1.length > 0) { - show.mainField1 = self.textField1; - show.metadataTags.mainField1 = self.textField1Type.length > 0 ? @[self.textField1Type] : @[]; - } - - if (self.textField2.length > 0) { - show.mainField2 = self.textField2; - show.metadataTags.mainField2 = self.textField2Type.length > 0 ? @[self.textField2Type] : @[]; - } - - if (self.textField3.length > 0) { - show.mainField3 = self.textField3; - show.metadataTags.mainField3 = self.textField3Type.length > 0 ? @[self.textField3Type] : @[]; - } - - if (self.textField4.length > 0) { - show.mainField4 = self.textField4; - show.metadataTags.mainField4 = self.textField4Type.length > 0 ? @[self.textField4Type] : @[]; - } - - return show; -} - -- (SDLShow *)sdl_setBlankTextFieldsWithShow:(SDLShow *)show { - show.mainField1 = @""; - show.mainField2 = @""; - show.mainField3 = @""; - show.mainField4 = @""; - show.mediaTrack = @""; - show.templateTitle = @""; - - return show; -} - -#pragma mark - Extraction - -- (SDLShow *)sdl_extractTextFromShow:(SDLShow *)show { - SDLShow *newShow = [[SDLShow alloc] init]; - newShow.mainField1 = show.mainField1; - newShow.mainField2 = show.mainField2; - newShow.mainField3 = show.mainField3; - newShow.mainField4 = show.mainField4; - newShow.mediaTrack = show.mediaTrack; - newShow.templateTitle = show.templateTitle; - newShow.metadataTags = show.metadataTags; - - return newShow; -} - -- (SDLShow *)sdl_extractImageFromShow:(SDLShow *)show { - SDLShow *newShow = [[SDLShow alloc] init]; - newShow.graphic = show.graphic; - newShow.secondaryGraphic = show.secondaryGraphic; - - return newShow; -} - -- (nullable SDLShow *)sdl_createImageOnlyShowWithPrimaryArtwork:(nullable SDLArtwork *)primaryArtwork secondaryArtwork:(nullable SDLArtwork *)secondaryArtwork { - SDLShow *newShow = [[SDLShow alloc] init]; - newShow.graphic = ![self sdl_artworkNeedsUpload:primaryArtwork] ? primaryArtwork.imageRPC : nil; - newShow.secondaryGraphic = ![self sdl_artworkNeedsUpload:secondaryArtwork] ? secondaryArtwork.imageRPC : nil; - - if (newShow.graphic == nil && newShow.secondaryGraphic == nil) { - SDLLogV(@"No graphics to upload"); - return nil; - } - - return newShow; -} - -- (void)sdl_updateCurrentScreenDataFromShow:(SDLShow *)show { - // If the items are nil, they were not updated, so we can't just set it directly - self.currentScreenData.mainField1 = show.mainField1 ?: self.currentScreenData.mainField1; - self.currentScreenData.mainField2 = show.mainField2 ?: self.currentScreenData.mainField2; - self.currentScreenData.mainField3 = show.mainField3 ?: self.currentScreenData.mainField3; - self.currentScreenData.mainField4 = show.mainField4 ?: self.currentScreenData.mainField4; - self.currentScreenData.mediaTrack = show.mediaTrack ?: self.currentScreenData.mediaTrack; - self.currentScreenData.templateTitle = show.templateTitle ?: self.currentScreenData.templateTitle; - self.currentScreenData.metadataTags = show.metadataTags ?: self.currentScreenData.metadataTags; - self.currentScreenData.alignment = show.alignment ?: self.currentScreenData.alignment; - self.currentScreenData.graphic = show.graphic ?: self.currentScreenData.graphic; - self.currentScreenData.secondaryGraphic = show.secondaryGraphic ?: self.currentScreenData.secondaryGraphic; +- (SDLTextAndGraphicState *)currentState { + return [[SDLTextAndGraphicState alloc] initWithTextField1:_textField1 textField2:_textField2 textField3:_textField3 textField4:_textField4 mediaText:_mediaTrackTextField title:_title primaryGraphic:_primaryGraphic secondaryGraphic:_secondaryGraphic alignment:_alignment textField1Type:_textField1Type textField2Type:_textField2Type textField3Type:_textField3Type textField4Type:_textField4Type]; } #pragma mark - Helpers -- (BOOL)sdl_artworkNeedsUpload:(SDLArtwork *)artwork { - return (artwork != nil && ![self.fileManager hasUploadedFile:artwork] && !artwork.isStaticIcon); -} - -- (BOOL)sdl_shouldUpdatePrimaryImage { - BOOL templateSupportsPrimaryArtwork = [self.windowCapability hasImageFieldOfName:SDLImageFieldNameGraphic]; - BOOL graphicMatchesExisting = [self.currentScreenData.graphic.value isEqualToString:self.primaryGraphic.name]; - BOOL graphicExists = (self.primaryGraphic != nil); - - return (templateSupportsPrimaryArtwork && !graphicMatchesExisting && graphicExists); -} - -- (BOOL)sdl_shouldUpdateSecondaryImage { - BOOL templateSupportsSecondaryArtwork = [self.windowCapability hasImageFieldOfName:SDLImageFieldNameSecondaryGraphic]; - BOOL graphicMatchesExisting = [self.currentScreenData.secondaryGraphic.value isEqualToString:self.secondaryGraphic.name]; - BOOL graphicExists = (self.secondaryGraphic != nil); - - // Cannot detect if there is a secondary image, so we'll just try to detect if there's a primary image and allow it if there is. - return (templateSupportsSecondaryArtwork && !graphicMatchesExisting && graphicExists); -} - -- (BOOL)sdl_shouldUpdateMediaTextField { - return [self.windowCapability hasTextFieldOfName:SDLTextFieldNameMediaTrack]; -} - -- (BOOL)sdl_shouldUpdateTitleField { - return [self.windowCapability hasTextFieldOfName:SDLTextFieldNameTemplateTitle]; -} - - (NSArray<NSString *> *)sdl_findNonNilTextFields { NSMutableArray *array = [NSMutableArray array]; (self.textField1.length > 0) ? [array addObject:self.textField1] : nil; @@ -557,16 +203,6 @@ NS_ASSUME_NONNULL_BEGIN return [array copy]; } -- (NSArray<SDLMetadataType> *)sdl_findNonNilMetadataFields { - NSMutableArray *array = [NSMutableArray array]; - (self.textField1Type.length) > 0 ? [array addObject:self.textField1Type] : nil; - (self.textField2Type.length) > 0 ? [array addObject:self.textField2Type] : nil; - (self.textField3Type.length) > 0 ? [array addObject:self.textField3Type] : nil; - (self.textField4Type.length) > 0 ? [array addObject:self.textField4Type] : nil; - - return [array copy]; -} - - (BOOL)sdl_hasData { BOOL hasTextFields = ([self sdl_findNonNilTextFields].count > 0) || (self.title.length > 0) || (self.mediaTrackTextField.length > 0); BOOL hasImageFields = (self.primaryGraphic != nil) || (self.secondaryGraphic != nil); @@ -574,24 +210,8 @@ NS_ASSUME_NONNULL_BEGIN return hasTextFields || hasImageFields; } -#pragma mark - Equality - -- (BOOL)sdl_showImages:(SDLShow *)show isEqualToShowImages:(SDLShow *)show2 { - BOOL same = NO; - same = ((show.graphic.value == nil && show.graphic.value == nil) - || [show.graphic.value isEqualToString:show2.graphic.value]); - if (!same) { return NO; } - - same = ((show.secondaryGraphic.value == nil && show.secondaryGraphic.value == nil) - || [show.secondaryGraphic.value isEqualToString:show2.secondaryGraphic.value]); - - return same; -} - #pragma mark - Getters / Setters -#pragma mark - Setters - - (void)setTextField1:(nullable NSString *)textField1 { _textField1 = textField1; _isDirty = YES; @@ -697,10 +317,6 @@ NS_ASSUME_NONNULL_BEGIN } } -- (BOOL)hasQueuedUpdate { - return (_hasQueuedUpdate || _queuedUpdateHandler != nil); -} - - (nullable SDLArtwork *)blankArtwork { if (_blankArtwork != nil) { return _blankArtwork; @@ -720,10 +336,11 @@ NS_ASSUME_NONNULL_BEGIN - (void)sdl_displayCapabilityDidUpdate:(SDLSystemCapability *)systemCapability { // we won't use the object in the parameter but the convenience method of the system capability manager self.windowCapability = self.systemCapabilityManager.defaultMainWindowCapability; + [self sdl_updateTransactionQueueSuspended]; // Auto-send an updated show if ([self sdl_hasData]) { - [self updateWithCompletionHandler:nil]; + [self sdl_updateWithCompletionHandler:nil]; } } @@ -734,13 +351,8 @@ NS_ASSUME_NONNULL_BEGIN return; } - SDLHMILevel oldLevel = self.currentLevel; self.currentLevel = hmiStatus.hmiLevel; - - // Auto-send an updated show if we were in NONE and now we are not - if ([oldLevel isEqualToString:SDLHMILevelNone] && ![self.currentLevel isEqualToString:SDLHMILevelNone] && self.waitingOnHMILevelUpdateToUpdate) { - [self sdl_updateWithCompletionHandler:nil]; - } + [self sdl_updateTransactionQueueSuspended]; } @end diff --git a/SmartDeviceLink/SDLTextAndGraphicState.h b/SmartDeviceLink/SDLTextAndGraphicState.h new file mode 100644 index 000000000..3fd2b3f93 --- /dev/null +++ b/SmartDeviceLink/SDLTextAndGraphicState.h @@ -0,0 +1,39 @@ +// +// SDLTextAndGraphicState.h +// SmartDeviceLink +// +// Created by Joel Fischer on 8/13/20. +// Copyright © 2020 smartdevicelink. All rights reserved. +// + +#import <Foundation/Foundation.h> + +#import "SDLMetadataType.h" +#import "SDLTextAlignment.h" + +@class SDLArtwork; + +NS_ASSUME_NONNULL_BEGIN + +@interface SDLTextAndGraphicState : NSObject <NSCopying> + +@property (copy, nonatomic, nullable) NSString *textField1; +@property (copy, nonatomic, nullable) NSString *textField2; +@property (copy, nonatomic, nullable) NSString *textField3; +@property (copy, nonatomic, nullable) NSString *textField4; +@property (copy, nonatomic, nullable) NSString *mediaTrackTextField; +@property (copy, nonatomic, nullable) NSString *title; +@property (strong, nonatomic, nullable) SDLArtwork *primaryGraphic; +@property (strong, nonatomic, nullable) SDLArtwork *secondaryGraphic; + +@property (copy, nonatomic, nullable) SDLTextAlignment alignment; +@property (copy, nonatomic, nullable) SDLMetadataType textField1Type; +@property (copy, nonatomic, nullable) SDLMetadataType textField2Type; +@property (copy, nonatomic, nullable) SDLMetadataType textField3Type; +@property (copy, nonatomic, nullable) SDLMetadataType textField4Type; + +- (instancetype)initWithTextField1:(nullable NSString *)textField1 textField2:(nullable NSString *)textField2 textField3:(nullable NSString *)textField3 textField4:(nullable NSString *)textField4 mediaText:(nullable NSString *)mediaTrackTextField title:(nullable NSString *)title primaryGraphic:(nullable SDLArtwork *)primaryGraphic secondaryGraphic:(nullable SDLArtwork *)secondaryGraphic alignment:(nullable SDLTextAlignment)alignment textField1Type:(nullable SDLMetadataType)textField1Type textField2Type:(nullable SDLMetadataType)textField2Type textField3Type:(nullable SDLMetadataType)textField3Type textField4Type:(nullable SDLMetadataType)textField4Type; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SmartDeviceLink/SDLTextAndGraphicState.m b/SmartDeviceLink/SDLTextAndGraphicState.m new file mode 100644 index 000000000..a44b63810 --- /dev/null +++ b/SmartDeviceLink/SDLTextAndGraphicState.m @@ -0,0 +1,48 @@ +// +// SDLTextAndGraphicState.m +// SmartDeviceLink +// +// Created by Joel Fischer on 8/13/20. +// Copyright © 2020 smartdevicelink. All rights reserved. +// + +#import "SDLTextAndGraphicState.h" + +#import "SDLArtwork.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation SDLTextAndGraphicState + +- (instancetype)initWithTextField1:(nullable NSString *)textField1 textField2:(nullable NSString *)textField2 textField3:(nullable NSString *)textField3 textField4:(nullable NSString *)textField4 mediaText:(nullable NSString *)mediaTrackTextField title:(nullable NSString *)title primaryGraphic:(nullable SDLArtwork *)primaryGraphic secondaryGraphic:(nullable SDLArtwork *)secondaryGraphic alignment:(nullable SDLTextAlignment)alignment textField1Type:(nullable SDLMetadataType)textField1Type textField2Type:(nullable SDLMetadataType)textField2Type textField3Type:(nullable SDLMetadataType)textField3Type textField4Type:(nullable SDLMetadataType)textField4Type { + self = [self init]; + if (!self) { return nil; } + + _textField1 = textField1; + _textField2 = textField2; + _textField3 = textField3; + _textField4 = textField4; + _mediaTrackTextField = mediaTrackTextField; + _title = title; + _primaryGraphic = primaryGraphic; + _secondaryGraphic = secondaryGraphic; + _alignment = alignment; + _textField1Type = textField1Type; + _textField2Type = textField2Type; + _textField3Type = textField3Type; + _textField4Type = textField4Type; + + return self; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"Text Field 1: %@, 2: %@, 3: %@, 4: %@, media track: %@, title: %@, alignment: %@, text 1 type: %@, 2: %@, 3: %@, 4: %@, primary graphic: %@, secondary graphic: %@", _textField1, _textField2, _textField3, _textField4, _mediaTrackTextField, _title, _alignment, _textField1Type, _textField2Type, _textField3Type, _textField4Type, _primaryGraphic, _secondaryGraphic]; +} + +- (id)copyWithZone:(nullable NSZone *)zone { + return [[SDLTextAndGraphicState allocWithZone:zone] initWithTextField1:[_textField1 copy] textField2:[_textField2 copy] textField3:[_textField3 copy] textField4:[_textField4 copy] mediaText:[_mediaTrackTextField copy] title:[_title copy] primaryGraphic:[_primaryGraphic copy] secondaryGraphic:[_secondaryGraphic copy] alignment:[_alignment copy] textField1Type:[_textField1Type copy] textField2Type:[_textField2Type copy] textField3Type:[_textField3Type copy] textField4Type:[_textField4Type copy]]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SmartDeviceLink/SDLTextAndGraphicUpdateOperation.h b/SmartDeviceLink/SDLTextAndGraphicUpdateOperation.h new file mode 100644 index 000000000..cb563760d --- /dev/null +++ b/SmartDeviceLink/SDLTextAndGraphicUpdateOperation.h @@ -0,0 +1,42 @@ +// +// SDLTextAndGraphicUpdateOperation.h +// SmartDeviceLink +// +// Created by Joel Fischer on 8/13/20. +// Copyright © 2020 smartdevicelink. All rights reserved. +// + +#import "SDLAsynchronousOperation.h" + +@class SDLArtwork; +@class SDLFileManager; +@class SDLImageField; +@class SDLTextField; +@class SDLShow; +@class SDLTextAndGraphicState; +@class SDLWindowCapability; + +@protocol SDLConnectionManagerType; + +NS_ASSUME_NONNULL_BEGIN + +typedef void(^SDLTextAndGraphicUpdateCompletionHandler)(NSError *__nullable error); +typedef void(^CurrentDataUpdatedHandler)(SDLShow *newScreenData); + +@interface SDLTextAndGraphicUpdateOperation : SDLAsynchronousOperation + +/// The current state of the screen in Show form. This is passed as a dependency in the init but it may need to be updated if a previous operation updated the state of the screen. This will be updated with new screen data when this operation sends successful shows. +@property (strong, nonatomic) SDLShow *currentScreenData; + +/// Initialize the operation with its dependencies +/// @param connectionManager The connection manager to send RPCs +/// @param fileManager The file manager to upload artwork +/// @param currentCapabilities The current window capability describing whether or not image fields and text fields are supported +/// @param currentData The current show data to determine which text and image fields need to be sent +/// @param newState The new text and graphic manager state to be compared with currentData and sent in a Show update if needed. +/// @param updateCompletionHandler The handler potentially passed by the developer to be called when the update finishes +- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager currentCapabilities:(SDLWindowCapability *)currentCapabilities currentScreenData:(SDLShow *)currentData newState:(SDLTextAndGraphicState *)newState currentScreenDataUpdatedHandler:(nullable CurrentDataUpdatedHandler)currentDataUpdatedHandler updateCompletionHandler:(nullable SDLTextAndGraphicUpdateCompletionHandler)updateCompletionHandler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SmartDeviceLink/SDLTextAndGraphicUpdateOperation.m b/SmartDeviceLink/SDLTextAndGraphicUpdateOperation.m new file mode 100644 index 000000000..dd70bda0f --- /dev/null +++ b/SmartDeviceLink/SDLTextAndGraphicUpdateOperation.m @@ -0,0 +1,493 @@ +// +// SDLTextAndGraphicUpdateOperation.m +// SmartDeviceLink +// +// Created by Joel Fischer on 8/13/20. +// Copyright © 2020 smartdevicelink. All rights reserved. +// + +#import "SDLTextAndGraphicUpdateOperation.h" + +#import "SDLArtwork.h" +#import "SDLConnectionManagerType.h" +#import "SDLError.h" +#import "SDLFileManager.h" +#import "SDLGlobals.h" +#import "SDLImage.h" +#import "SDLLogMacros.h" +#import "SDLMetadataTags.h" +#import "SDLShow.h" +#import "SDLTextAndGraphicState.h" +#import "SDLVersion.h" +#import "SDLWindowCapability.h" +#import "SDLWindowCapability+ScreenManagerExtensions.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface SDLTextAndGraphicUpdateOperation () + +@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager; +@property (weak, nonatomic) SDLFileManager *fileManager; +@property (strong, nonatomic) SDLWindowCapability *currentCapabilities; +@property (strong, nonatomic) SDLTextAndGraphicState *updatedState; + +@property (copy, nonatomic, nullable) CurrentDataUpdatedHandler currentDataUpdatedHandler; +@property (copy, nonatomic, nullable) SDLTextAndGraphicUpdateCompletionHandler updateCompletionHandler; + +@property (copy, nonatomic, nullable) NSError *internalError; + +@end + +@implementation SDLTextAndGraphicUpdateOperation + +- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager currentCapabilities:(SDLWindowCapability *)currentCapabilities currentScreenData:(SDLShow *)currentData newState:(nonnull SDLTextAndGraphicState *)newState currentScreenDataUpdatedHandler:(nullable CurrentDataUpdatedHandler)currentDataUpdatedHandler updateCompletionHandler:(nullable SDLTextAndGraphicUpdateCompletionHandler)updateCompletionHandler { + self = [self init]; + if (!self) { return nil; } + + _connectionManager = connectionManager; + _fileManager = fileManager; + _currentCapabilities = currentCapabilities; + _currentScreenData = currentData; + _updatedState = newState; + _currentDataUpdatedHandler = currentDataUpdatedHandler; + _updateCompletionHandler = updateCompletionHandler; + + return self; +} + +- (void)start { + [super start]; + if (self.cancelled) { + // Make sure the update handler is called + self.internalError = [NSError sdl_textAndGraphicManager_pendingUpdateSuperseded]; + [self finishOperation]; + return; + } + + // Build a show with everything from `self.newState`, we'll pull things out later if we can. + SDLShow *fullShow = [[SDLShow alloc] init]; + fullShow.alignment = self.updatedState.alignment; + fullShow = [self sdl_assembleShowText:fullShow]; + fullShow = [self sdl_assembleShowImages:fullShow]; + + __weak typeof(self) weakSelf = self; + if (!([self sdl_shouldUpdatePrimaryImage] || [self sdl_shouldUpdateSecondaryImage])) { + SDLLogV(@"No images to send, sending text"); + // If there are no images to update, just send the text + [self sdl_sendShow:[self sdl_extractTextFromShow:fullShow] withHandler:^(NSError * _Nullable error) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (error != nil) { + strongSelf.internalError = error; + } + [strongSelf finishOperation]; + }]; + } else if (![self sdl_artworkNeedsUpload:self.updatedState.primaryGraphic] && ![self sdl_artworkNeedsUpload:self.updatedState.secondaryGraphic]) { + SDLLogV(@"Images already uploaded, sending full update"); + // The files to be updated are already uploaded, send the full show immediately + [self sdl_sendShow:fullShow withHandler:^(NSError * _Nullable error) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (error != nil) { + strongSelf.internalError = error; + } + [strongSelf finishOperation]; + }]; + } else { + SDLLogV(@"Images need to be uploaded, sending text and uploading images"); + + // We need to upload or queue the upload of the images + // Send the text immediately + [self sdl_sendShow:[self sdl_extractTextFromShow:fullShow] withHandler:^(NSError * _Nullable error) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (self.cancelled) { + [strongSelf finishOperation]; + } + + [strongSelf sdl_uploadImagesAndSendWhenDone:^(NSError * _Nullable error) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (error != nil) { + strongSelf.internalError = error; + } + [strongSelf finishOperation]; + }]; + }]; + } +} + +#pragma mark - Send Show + +- (void)sdl_sendShow:(SDLShow *)show withHandler:(void (^)(NSError *_Nullable error))handler { + __weak typeof(self)weakSelf = self; + [self.connectionManager sendConnectionRequest:show withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) { + __strong typeof(weakSelf) strongSelf = weakSelf; + SDLLogD(@"Text and Graphic update completed"); + + if (response.success) { + [strongSelf sdl_updateCurrentScreenDataFromShow:(SDLShow *)request]; + } + + handler(error); + }]; +} + +#pragma mark - Uploading Images + +- (void)sdl_uploadImagesAndSendWhenDone:(void (^)(NSError *_Nullable error))handler { + __weak typeof(self)weakSelf = self; + [self sdl_uploadImagesWithCompletionHandler:^(NSError *_Nullable error) { + __strong typeof(weakSelf) strongSelf = weakSelf; + + SDLShow *showWithGraphics = [self sdl_createImageOnlyShowWithPrimaryArtwork:self.updatedState.primaryGraphic secondaryArtwork:self.updatedState.secondaryGraphic]; + if (showWithGraphics != nil) { + SDLLogD(@"Sending update with the successfully uploaded images"); + [strongSelf sdl_sendShow:showWithGraphics withHandler:^(NSError * _Nullable error) { + return handler(error); + }]; + } else { + SDLLogW(@"All images failed to upload. No graphics to show, skipping update."); + return handler(error); + } + }]; +} + +- (void)sdl_uploadImagesWithCompletionHandler:(void (^)(NSError *_Nullable error))handler { + NSMutableArray<SDLArtwork *> *artworksToUpload = [NSMutableArray array]; + if ([self sdl_shouldUpdatePrimaryImage] && !self.updatedState.primaryGraphic.isStaticIcon) { + [artworksToUpload addObject:self.updatedState.primaryGraphic]; + } + if ([self sdl_shouldUpdateSecondaryImage] && !self.updatedState.secondaryGraphic.isStaticIcon) { + [artworksToUpload addObject:self.updatedState.secondaryGraphic]; + } + + if (artworksToUpload.count == 0) { + SDLLogD(@"No artworks need an upload, sending them without upload instead"); + return handler(nil); + } + + __weak typeof(self) weakSelf = self; + [self.fileManager uploadArtworks:artworksToUpload progressHandler:^BOOL(NSString * _Nonnull artworkName, float uploadPercentage, NSError * _Nullable error) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (strongSelf.isCancelled) { + [strongSelf finishOperation]; + return NO; + } + + return YES; + } completionHandler:^(NSArray<NSString *> * _Nonnull artworkNames, NSError * _Nullable error) { + if (error != nil) { + SDLLogW(@"Text and graphic manager artwork failed to upload with error: %@", error.localizedDescription); + } + + handler(error); + }]; +} + + +#pragma mark - Assembly of Shows + +#pragma mark Images + +- (SDLShow *)sdl_assembleShowImages:(SDLShow *)show { + if (![self sdl_shouldUpdatePrimaryImage] && ![self sdl_shouldUpdateSecondaryImage]) { + return show; + } + + if ([self sdl_shouldUpdatePrimaryImage]) { + show.graphic = self.updatedState.primaryGraphic.imageRPC; + } + if ([self sdl_shouldUpdateSecondaryImage]) { + show.secondaryGraphic = self.updatedState.secondaryGraphic.imageRPC; + } + + return show; +} + +- (nullable SDLShow *)sdl_createImageOnlyShowWithPrimaryArtwork:(nullable SDLArtwork *)primaryArtwork secondaryArtwork:(nullable SDLArtwork *)secondaryArtwork { + SDLShow *newShow = [[SDLShow alloc] init]; + newShow.graphic = ![self sdl_artworkNeedsUpload:primaryArtwork] ? primaryArtwork.imageRPC : nil; + newShow.secondaryGraphic = ![self sdl_artworkNeedsUpload:secondaryArtwork] ? secondaryArtwork.imageRPC : nil; + + if (newShow.graphic == nil && newShow.secondaryGraphic == nil) { + SDLLogV(@"No graphics to upload"); + return nil; + } + + return newShow; +} + +#pragma mark Text + +- (SDLShow *)sdl_assembleShowText:(SDLShow *)show { + [self sdl_setBlankTextFieldsWithShow:show]; + + if (self.updatedState.mediaTrackTextField != nil && [self sdl_shouldUpdateMediaTextField]) { + show.mediaTrack = self.updatedState.mediaTrackTextField; + } else { + show.mediaTrack = @""; + } + + if (self.updatedState.title != nil && [self sdl_shouldUpdateTitleField]) { + show.templateTitle = self.updatedState.title; + } else { + show.templateTitle = @""; + } + + NSArray *nonNilFields = [self sdl_findNonNilTextFields]; + if (nonNilFields.count == 0) { return show; } + + NSUInteger numberOfLines = self.currentCapabilities.maxNumberOfMainFieldLines; + if (numberOfLines == 1) { + show = [self sdl_assembleOneLineShowText:show withShowFields:nonNilFields]; + } else if (numberOfLines == 2) { + show = [self sdl_assembleTwoLineShowText:show]; + } else if (numberOfLines == 3) { + show = [self sdl_assembleThreeLineShowText:show]; + } else if (numberOfLines == 4) { + show = [self sdl_assembleFourLineShowText:show]; + } + + return show; +} + +- (SDLShow *)sdl_assembleOneLineShowText:(SDLShow *)show withShowFields:(NSArray<NSString *> *)fields { + NSMutableString *showString1 = [NSMutableString stringWithString:fields[0]]; + for (NSUInteger i = 1; i < fields.count; i++) { + [showString1 appendFormat:@" - %@", fields[i]]; + } + show.mainField1 = showString1.copy; + + NSMutableArray<SDLMetadataType> *metadataArray = [NSMutableArray array]; + self.updatedState.textField1Type ? [metadataArray addObject:self.updatedState.textField1Type] : nil; + self.updatedState.textField2Type ? [metadataArray addObject:self.updatedState.textField2Type] : nil; + self.updatedState.textField3Type ? [metadataArray addObject:self.updatedState.textField3Type] : nil; + self.updatedState.textField4Type ? [metadataArray addObject:self.updatedState.textField4Type] : nil; + show.metadataTags.mainField1 = [metadataArray copy]; + + return show; +} + +- (SDLShow *)sdl_assembleTwoLineShowText:(SDLShow *)show { + NSMutableString *tempString = [NSMutableString string]; + if (self.updatedState.textField1.length > 0) { + // If text 1 exists, put it in slot 1 + [tempString appendString:self.updatedState.textField1]; + show.metadataTags.mainField1 = self.updatedState.textField1Type.length > 0 ? @[self.updatedState.textField1Type] : @[]; + } + + if (self.updatedState.textField2.length > 0) { + if (!(self.updatedState.textField3.length > 0 || self.updatedState.textField4.length > 0)) { + // If text 3 & 4 do not exist, put it in slot 2 + show.mainField2 = self.updatedState.textField2; + show.metadataTags.mainField2 = self.updatedState.textField2Type.length > 0 ? @[self.updatedState.textField2Type] : @[]; + } else if (self.updatedState.textField1.length > 0) { + // If text 1 exists, put it in slot 1 formatted + [tempString appendFormat:@" - %@", self.updatedState.textField2]; + show.metadataTags.mainField1 = self.updatedState.textField2Type.length > 0 ? [show.metadataTags.mainField1 arrayByAddingObjectsFromArray:@[self.updatedState.textField2Type]] : show.metadataTags.mainField1; + } else { + // If text 1 does not exist, put it in slot 1 unformatted + [tempString appendString:self.updatedState.textField2]; + show.metadataTags.mainField1 = self.updatedState.textField2Type.length > 0 ? @[self.updatedState.textField2Type] : @[]; + } + } + + show.mainField1 = [tempString copy]; + + tempString = [NSMutableString string]; + if (self.updatedState.textField3.length > 0) { + // If text 3 exists, put it in slot 2 + [tempString appendString:self.updatedState.textField3]; + show.metadataTags.mainField2 = self.updatedState.textField3Type.length > 0 ? @[self.updatedState.textField3Type] : @[]; + } + + if (self.updatedState.textField4.length > 0) { + if (self.updatedState.textField3.length > 0) { + // If text 3 exists, put it in slot 2 formatted + [tempString appendFormat:@" - %@", self.updatedState.textField4]; + show.metadataTags.mainField2 = self.updatedState.textField4Type.length > 0 ? [show.metadataTags.mainField2 arrayByAddingObjectsFromArray:@[self.updatedState.textField4Type]] : show.metadataTags.mainField2; + } else { + // If text 3 does not exist, put it in slot 3 unformatted + [tempString appendString:self.updatedState.textField4]; + show.metadataTags.mainField2 = self.updatedState.textField4Type.length > 0 ? @[self.updatedState.textField4Type] : @[]; + } + } + + if (tempString.length > 0) { + show.mainField2 = [tempString copy]; + } + + return show; +} + +- (SDLShow *)sdl_assembleThreeLineShowText:(SDLShow *)show { + if (self.updatedState.textField1.length > 0) { + show.mainField1 = self.updatedState.textField1; + show.metadataTags.mainField1 = self.updatedState.textField1Type.length > 0 ? @[self.updatedState.textField1Type] : @[]; + } + + if (self.updatedState.textField2.length > 0) { + show.mainField2 = self.updatedState.textField2; + show.metadataTags.mainField2 = self.updatedState.textField2Type.length > 0 ? @[self.updatedState.textField2Type] : @[]; + } + + NSMutableString *tempString = [NSMutableString string]; + if (self.updatedState.textField3.length > 0) { + [tempString appendString:self.updatedState.textField3]; + show.metadataTags.mainField3 = self.updatedState.textField3Type.length > 0 ? @[self.updatedState.textField3Type] : @[]; + } + + if (self.updatedState.textField4.length > 0) { + if (self.updatedState.textField3.length > 0) { + // If text 3 exists, put it in slot 3 formatted + [tempString appendFormat:@" - %@", self.updatedState.textField4]; + show.metadataTags.mainField3 = self.updatedState.textField4Type.length > 0 ? [show.metadataTags.mainField3 arrayByAddingObjectsFromArray:@[self.updatedState.textField4Type]] : show.metadataTags.mainField3; + } else { + // If text 3 does not exist, put it in slot 3 formatted + [tempString appendString:self.updatedState.textField4]; + show.metadataTags.mainField3 = self.updatedState.textField4Type.length > 0 ? @[self.updatedState.textField4Type] : @[]; + } + } + + show.mainField3 = [tempString copy]; + + return show; +} + +- (SDLShow *)sdl_assembleFourLineShowText:(SDLShow *)show { + if (self.updatedState.textField1.length > 0) { + show.mainField1 = self.updatedState.textField1; + show.metadataTags.mainField1 = self.updatedState.textField1Type.length > 0 ? @[self.updatedState.textField1Type] : @[]; + } + + if (self.updatedState.textField2.length > 0) { + show.mainField2 = self.updatedState.textField2; + show.metadataTags.mainField2 = self.updatedState.textField2Type.length > 0 ? @[self.updatedState.textField2Type] : @[]; + } + + if (self.updatedState.textField3.length > 0) { + show.mainField3 = self.updatedState.textField3; + show.metadataTags.mainField3 = self.updatedState.textField3Type.length > 0 ? @[self.updatedState.textField3Type] : @[]; + } + + if (self.updatedState.textField4.length > 0) { + show.mainField4 = self.updatedState.textField4; + show.metadataTags.mainField4 = self.updatedState.textField4Type.length > 0 ? @[self.updatedState.textField4Type] : @[]; + } + + return show; +} + +- (SDLShow *)sdl_setBlankTextFieldsWithShow:(SDLShow *)show { + show.mainField1 = @""; + show.mainField2 = @""; + show.mainField3 = @""; + show.mainField4 = @""; + show.mediaTrack = @""; + show.templateTitle = @""; + show.metadataTags = [[SDLMetadataTags alloc] init]; + + return show; +} + +#pragma mark - Extraction + +- (SDLShow *)sdl_extractTextFromShow:(SDLShow *)show { + SDLShow *newShow = [[SDLShow alloc] init]; + newShow.mainField1 = show.mainField1; + newShow.mainField2 = show.mainField2; + newShow.mainField3 = show.mainField3; + newShow.mainField4 = show.mainField4; + newShow.mediaTrack = show.mediaTrack; + newShow.templateTitle = show.templateTitle; + newShow.metadataTags = show.metadataTags; + newShow.alignment = show.alignment; + + return newShow; +} + +- (void)sdl_updateCurrentScreenDataFromShow:(SDLShow *)show { + // If the items are nil, they were not updated, so we can't just set it directly + self.currentScreenData.mainField1 = show.mainField1 ?: self.currentScreenData.mainField1; + self.currentScreenData.mainField2 = show.mainField2 ?: self.currentScreenData.mainField2; + self.currentScreenData.mainField3 = show.mainField3 ?: self.currentScreenData.mainField3; + self.currentScreenData.mainField4 = show.mainField4 ?: self.currentScreenData.mainField4; + self.currentScreenData.mediaTrack = show.mediaTrack ?: self.currentScreenData.mediaTrack; + self.currentScreenData.templateTitle = show.templateTitle ?: self.currentScreenData.templateTitle; + self.currentScreenData.metadataTags = show.metadataTags ?: self.currentScreenData.metadataTags; + self.currentScreenData.alignment = show.alignment ?: self.currentScreenData.alignment; + self.currentScreenData.graphic = show.graphic ?: self.currentScreenData.graphic; + self.currentScreenData.secondaryGraphic = show.secondaryGraphic ?: self.currentScreenData.secondaryGraphic; + + if (self.currentDataUpdatedHandler != nil) { + self.currentDataUpdatedHandler(self.currentScreenData); + } +} + +#pragma mark - Should Update + +- (BOOL)sdl_artworkNeedsUpload:(SDLArtwork *)artwork { + return (artwork != nil && ![self.fileManager hasUploadedFile:artwork] && !artwork.isStaticIcon); +} + +- (BOOL)sdl_shouldUpdatePrimaryImage { + BOOL templateSupportsPrimaryArtwork = [self.currentCapabilities hasImageFieldOfName:SDLImageFieldNameGraphic]; + BOOL graphicMatchesExisting = [self.currentScreenData.graphic.value isEqualToString:self.updatedState.primaryGraphic.name]; + BOOL graphicExists = (self.updatedState.primaryGraphic != nil); + + return (templateSupportsPrimaryArtwork && !graphicMatchesExisting && graphicExists); +} + +- (BOOL)sdl_shouldUpdateSecondaryImage { + BOOL templateSupportsSecondaryArtwork = [self.currentCapabilities hasImageFieldOfName:SDLImageFieldNameSecondaryGraphic]; + BOOL graphicMatchesExisting = [self.currentScreenData.secondaryGraphic.value isEqualToString:self.updatedState.secondaryGraphic.name]; + BOOL graphicExists = (self.updatedState.secondaryGraphic != nil); + + // Cannot detect if there is a secondary image below v5.0, so we'll just try to detect if the primary image is allowed and allow the secondary image if it is. + if ([[SDLGlobals sharedGlobals].rpcVersion isGreaterThanOrEqualToVersion:[SDLVersion versionWithMajor:5 minor:0 patch:0]]) { + return (templateSupportsSecondaryArtwork && !graphicMatchesExisting && graphicExists); + } else { + return ([self.currentCapabilities hasImageFieldOfName:SDLImageFieldNameGraphic] && !graphicMatchesExisting && graphicExists); + } +} + +- (BOOL)sdl_shouldUpdateMediaTextField { + return [self.currentCapabilities hasTextFieldOfName:SDLTextFieldNameMediaTrack]; +} + +- (BOOL)sdl_shouldUpdateTitleField { + return [self.currentCapabilities hasTextFieldOfName:SDLTextFieldNameTemplateTitle]; +} + +- (NSArray<NSString *> *)sdl_findNonNilTextFields { + NSMutableArray *array = [NSMutableArray array]; + (self.updatedState.textField1.length > 0) ? [array addObject:self.updatedState.textField1] : nil; + (self.updatedState.textField2.length > 0) ? [array addObject:self.updatedState.textField2] : nil; + (self.updatedState.textField3.length > 0) ? [array addObject:self.updatedState.textField3] : nil; + (self.updatedState.textField4.length > 0) ? [array addObject:self.updatedState.textField4] : nil; + + return [array copy]; +} + +#pragma mark - Operation Overrides + +- (void)finishOperation { + SDLLogV(@"Finishing text and graphic update operation"); + if (self.updateCompletionHandler != nil) { + self.updateCompletionHandler(self.error); + } + [super finishOperation]; +} + +- (nullable NSString *)name { + return @"com.sdl.textandgraphic.update"; +} + +- (NSOperationQueuePriority)queuePriority { + return NSOperationQueuePriorityNormal; +} + +- (nullable NSError *)error { + return self.internalError; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicManagerSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicManagerSpec.m index 99aa9dbb5..b8b34e046 100644 --- a/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicManagerSpec.m +++ b/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicManagerSpec.m @@ -11,6 +11,7 @@ #import "SDLPutFileResponse.h" #import "SDLShow.h" #import "SDLTextAndGraphicManager.h" +#import "SDLTextAndGraphicUpdateOperation.h" #import "SDLTextField.h" #import "SDLSystemCapabilityManager.h" #import "SDLWindowCapability.h" @@ -21,15 +22,11 @@ // Dependencies @property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager; @property (weak, nonatomic) SDLFileManager *fileManager; +@property (weak, nonatomic) SDLSystemCapabilityManager *systemCapabilityManager; @property (strong, nonatomic) SDLShow *currentScreenData; -@property (strong, nonatomic, nullable) SDLShow *inProgressUpdate; -@property (copy, nonatomic, nullable) SDLTextAndGraphicUpdateCompletionHandler inProgressHandler; - -@property (strong, nonatomic, nullable) SDLShow *queuedImageUpdate; -@property (assign, nonatomic) BOOL hasQueuedUpdate; -@property (copy, nonatomic, nullable) SDLTextAndGraphicUpdateCompletionHandler queuedUpdateHandler; +@property (strong, nonatomic) NSOperationQueue *transactionQueue; @property (strong, nonatomic, nullable) SDLWindowCapability *windowCapability; @property (strong, nonatomic, nullable) SDLHMILevel currentLevel; @@ -53,7 +50,6 @@ describe(@"text and graphic manager", ^{ __block NSString *testString = @"some string"; __block NSString *testArtworkName = @"some artwork name"; __block SDLArtwork *testArtwork = [[SDLArtwork alloc] initWithData:[@"Test data" dataUsingEncoding:NSUTF8StringEncoding] name:testArtworkName fileExtension:@"png" persistent:NO]; - __block SDLArtwork *testStaticIcon = [SDLArtwork artworkWithStaticIcon:SDLStaticIconNameDate]; beforeEach(^{ mockFileManager = OCMClassMock([SDLFileManager class]); @@ -62,6 +58,7 @@ describe(@"text and graphic manager", ^{ [testManager start]; }); + // should instantiate correctly it(@"should instantiate correctly", ^{ expect(testManager.connectionManager).to(equal(mockConnectionManager)); expect(testManager.fileManager).to(equal(mockFileManager)); @@ -81,48 +78,66 @@ describe(@"text and graphic manager", ^{ expect(testManager.textField4Type).to(beNil()); expect(testManager.currentScreenData).to(equal([[SDLShow alloc] init])); - expect(testManager.inProgressUpdate).to(beNil()); - expect(testManager.queuedImageUpdate).to(beNil()); - expect(testManager.hasQueuedUpdate).to(beFalse()); + expect(testManager.transactionQueue).toNot(beNil()); expect(testManager.windowCapability).to(beNil()); expect(testManager.currentLevel).to(equal(SDLHMILevelNone)); expect(testManager.blankArtwork).toNot(beNil()); expect(testManager.isDirty).to(beFalse()); }); + // setting setters describe(@"setting setters", ^{ beforeEach(^{ testManager.currentLevel = SDLHMILevelFull; }); + // when in HMI NONE context(@"when in HMI NONE", ^{ beforeEach(^{ testManager.currentLevel = SDLHMILevelNone; }); - it(@"should not set text field 1", ^{ + it(@"should set text field 1 but be suspended", ^{ testManager.textField1 = testString; expect(testManager.textField1).to(equal(testString)); - expect(testManager.inProgressUpdate).to(beNil()); - expect(testManager.isDirty).to(beTrue()); + expect(testManager.transactionQueue.isSuspended).to(beTrue()); + expect(testManager.transactionQueue.operationCount).to(equal(1)); }); }); + // when no HMI level has been received context(@"when no HMI level has been received", ^{ beforeEach(^{ testManager.currentLevel = nil; }); - it(@"should not set text field 1", ^{ + it(@"should set text field 1 but be suspended", ^{ testManager.textField1 = testString; expect(testManager.textField1).to(equal(testString)); - expect(testManager.inProgressUpdate).to(beNil()); - expect(testManager.isDirty).to(beTrue()); + expect(testManager.transactionQueue.operationCount).to(equal(1)); + expect(testManager.transactionQueue.isSuspended).to(beTrue()); + }); + }); + + // when previous updates have bene cancelled + context(@"when previous updates have bene cancelled", ^{ + beforeEach(^{ + testManager.textField1 = @"Hello"; + + // This should cancel the first operation + testManager.textField2 = @"Goodbye"; + }); + + it(@"should properly queue the new update", ^{ + expect(testManager.transactionQueue.isSuspended).to(beTrue()); + expect(testManager.transactionQueue.operationCount).to(equal(2)); + expect(testManager.transactionQueue.operations[0].cancelled).to(beTrue()); }); }); + // while batching context(@"while batching", ^{ beforeEach(^{ testManager.batchUpdates = YES; @@ -132,7 +147,7 @@ describe(@"text and graphic manager", ^{ testManager.textField1 = testString; expect(testManager.textField1).to(equal(testString)); - expect(testManager.inProgressUpdate).to(beNil()); + expect(testManager.transactionQueue.operationCount).to(equal(0)); expect(testManager.isDirty).to(beTrue()); }); @@ -140,7 +155,7 @@ describe(@"text and graphic manager", ^{ testManager.textField2 = testString; expect(testManager.textField2).to(equal(testString)); - expect(testManager.inProgressUpdate).to(beNil()); + expect(testManager.transactionQueue.operationCount).to(equal(0)); expect(testManager.isDirty).to(beTrue()); }); @@ -148,7 +163,7 @@ describe(@"text and graphic manager", ^{ testManager.textField3 = testString; expect(testManager.textField3).to(equal(testString)); - expect(testManager.inProgressUpdate).to(beNil()); + expect(testManager.transactionQueue.operationCount).to(equal(0)); expect(testManager.isDirty).to(beTrue()); }); @@ -156,7 +171,7 @@ describe(@"text and graphic manager", ^{ testManager.textField4 = testString; expect(testManager.textField4).to(equal(testString)); - expect(testManager.inProgressUpdate).to(beNil()); + expect(testManager.transactionQueue.operationCount).to(equal(0)); expect(testManager.isDirty).to(beTrue()); }); @@ -164,7 +179,7 @@ describe(@"text and graphic manager", ^{ testManager.mediaTrackTextField = testString; expect(testManager.mediaTrackTextField).to(equal(testString)); - expect(testManager.inProgressUpdate).to(beNil()); + expect(testManager.transactionQueue.operationCount).to(equal(0)); expect(testManager.isDirty).to(beTrue()); }); @@ -172,7 +187,7 @@ describe(@"text and graphic manager", ^{ testManager.title = testString; expect(testManager.title).to(equal(testString)); - expect(testManager.inProgressUpdate).to(beNil()); + expect(testManager.transactionQueue.operationCount).to(equal(0)); expect(testManager.isDirty).to(beTrue()); }); @@ -180,7 +195,7 @@ describe(@"text and graphic manager", ^{ testManager.primaryGraphic = testArtwork; expect(testManager.primaryGraphic.name).to(equal(testArtworkName)); - expect(testManager.inProgressUpdate).to(beNil()); + expect(testManager.transactionQueue.operationCount).to(equal(0)); expect(testManager.isDirty).to(beTrue()); }); @@ -188,7 +203,7 @@ describe(@"text and graphic manager", ^{ testManager.secondaryGraphic = testArtwork; expect(testManager.secondaryGraphic.name).to(equal(testArtworkName)); - expect(testManager.inProgressUpdate).to(beNil()); + expect(testManager.transactionQueue.operationCount).to(equal(0)); expect(testManager.isDirty).to(beTrue()); }); @@ -196,7 +211,7 @@ describe(@"text and graphic manager", ^{ testManager.alignment = SDLTextAlignmentLeft; expect(testManager.alignment).to(equal(SDLTextAlignmentLeft)); - expect(testManager.inProgressUpdate).to(beNil()); + expect(testManager.transactionQueue.operationCount).to(equal(0)); expect(testManager.isDirty).to(beTrue()); }); @@ -204,7 +219,7 @@ describe(@"text and graphic manager", ^{ testManager.textField1Type = SDLMetadataTypeMediaAlbum; expect(testManager.textField1Type).to(equal(SDLMetadataTypeMediaAlbum)); - expect(testManager.inProgressUpdate).to(beNil()); + expect(testManager.transactionQueue.operationCount).to(equal(0)); expect(testManager.isDirty).to(beTrue()); }); @@ -212,7 +227,7 @@ describe(@"text and graphic manager", ^{ testManager.textField2Type = SDLMetadataTypeMediaAlbum; expect(testManager.textField2Type).to(equal(SDLMetadataTypeMediaAlbum)); - expect(testManager.inProgressUpdate).to(beNil()); + expect(testManager.transactionQueue.operationCount).to(equal(0)); expect(testManager.isDirty).to(beTrue()); }); @@ -220,7 +235,7 @@ describe(@"text and graphic manager", ^{ testManager.textField3Type = SDLMetadataTypeMediaAlbum; expect(testManager.textField3Type).to(equal(SDLMetadataTypeMediaAlbum)); - expect(testManager.inProgressUpdate).to(beNil()); + expect(testManager.transactionQueue.operationCount).to(equal(0)); expect(testManager.isDirty).to(beTrue()); }); @@ -228,11 +243,12 @@ describe(@"text and graphic manager", ^{ testManager.textField4Type = SDLMetadataTypeMediaAlbum; expect(testManager.textField4Type).to(equal(SDLMetadataTypeMediaAlbum)); - expect(testManager.inProgressUpdate).to(beNil()); + expect(testManager.transactionQueue.operationCount).to(equal(0)); expect(testManager.isDirty).to(beTrue()); }); }); + // while not batching context(@"while not batching", ^{ beforeEach(^{ testManager.batchUpdates = NO; @@ -242,7 +258,7 @@ describe(@"text and graphic manager", ^{ testManager.textField1 = testString; expect(testManager.textField1).to(equal(testString)); - expect(testManager.inProgressUpdate).toNot(beNil()); + expect(testManager.transactionQueue.operationCount).to(equal(1)); expect(testManager.isDirty).to(beFalse()); }); @@ -250,7 +266,7 @@ describe(@"text and graphic manager", ^{ testManager.textField2 = testString; expect(testManager.textField2).to(equal(testString)); - expect(testManager.inProgressUpdate).toNot(beNil()); + expect(testManager.transactionQueue.operationCount).to(equal(1)); expect(testManager.isDirty).to(beFalse()); }); @@ -258,7 +274,7 @@ describe(@"text and graphic manager", ^{ testManager.textField3 = testString; expect(testManager.textField3).to(equal(testString)); - expect(testManager.inProgressUpdate).toNot(beNil()); + expect(testManager.transactionQueue.operationCount).to(equal(1)); expect(testManager.isDirty).to(beFalse()); }); @@ -266,7 +282,7 @@ describe(@"text and graphic manager", ^{ testManager.textField4 = testString; expect(testManager.textField4).to(equal(testString)); - expect(testManager.inProgressUpdate).toNot(beNil()); + expect(testManager.transactionQueue.operationCount).to(equal(1)); expect(testManager.isDirty).to(beFalse()); }); @@ -274,7 +290,7 @@ describe(@"text and graphic manager", ^{ testManager.mediaTrackTextField = testString; expect(testManager.mediaTrackTextField).to(equal(testString)); - expect(testManager.inProgressUpdate).toNot(beNil()); + expect(testManager.transactionQueue.operationCount).to(equal(1)); expect(testManager.isDirty).to(beFalse()); }); @@ -282,7 +298,7 @@ describe(@"text and graphic manager", ^{ testManager.title = testString; expect(testManager.title).to(equal(testString)); - expect(testManager.inProgressUpdate).toNot(beNil()); + expect(testManager.transactionQueue.operationCount).to(equal(1)); expect(testManager.isDirty).to(beFalse()); }); @@ -290,7 +306,7 @@ describe(@"text and graphic manager", ^{ testManager.primaryGraphic = testArtwork; expect(testManager.primaryGraphic.name).to(equal(testArtworkName)); - expect(testManager.inProgressUpdate).toNot(beNil()); + expect(testManager.transactionQueue.operationCount).to(equal(1)); expect(testManager.isDirty).to(beFalse()); }); @@ -298,7 +314,7 @@ describe(@"text and graphic manager", ^{ testManager.secondaryGraphic = testArtwork; expect(testManager.secondaryGraphic.name).to(equal(testArtworkName)); - expect(testManager.inProgressUpdate).toNot(beNil()); + expect(testManager.transactionQueue.operationCount).to(equal(1)); expect(testManager.isDirty).to(beFalse()); }); @@ -306,7 +322,7 @@ describe(@"text and graphic manager", ^{ testManager.alignment = SDLTextAlignmentLeft; expect(testManager.alignment).to(equal(SDLTextAlignmentLeft)); - expect(testManager.inProgressUpdate).toNot(beNil()); + expect(testManager.transactionQueue.operationCount).to(equal(1)); expect(testManager.isDirty).to(beFalse()); }); @@ -314,7 +330,7 @@ describe(@"text and graphic manager", ^{ testManager.textField1Type = SDLMetadataTypeMediaAlbum; expect(testManager.textField1Type).to(equal(SDLMetadataTypeMediaAlbum)); - expect(testManager.inProgressUpdate).toNot(beNil()); + expect(testManager.transactionQueue.operationCount).to(equal(1)); expect(testManager.isDirty).to(beFalse()); }); @@ -322,7 +338,7 @@ describe(@"text and graphic manager", ^{ testManager.textField2Type = SDLMetadataTypeMediaAlbum; expect(testManager.textField2Type).to(equal(SDLMetadataTypeMediaAlbum)); - expect(testManager.inProgressUpdate).toNot(beNil()); + expect(testManager.transactionQueue.operationCount).to(equal(1)); expect(testManager.isDirty).to(beFalse()); }); @@ -330,7 +346,7 @@ describe(@"text and graphic manager", ^{ testManager.textField3Type = SDLMetadataTypeMediaAlbum; expect(testManager.textField3Type).to(equal(SDLMetadataTypeMediaAlbum)); - expect(testManager.inProgressUpdate).toNot(beNil()); + expect(testManager.transactionQueue.operationCount).to(equal(1)); expect(testManager.isDirty).to(beFalse()); }); @@ -338,12 +354,13 @@ describe(@"text and graphic manager", ^{ testManager.textField4Type = SDLMetadataTypeMediaAlbum; expect(testManager.textField4Type).to(equal(SDLMetadataTypeMediaAlbum)); - expect(testManager.inProgressUpdate).toNot(beNil()); + expect(testManager.transactionQueue.operationCount).to(equal(1)); expect(testManager.isDirty).to(beFalse()); }); }); }); + // batching an update describe(@"batching an update", ^{ NSString *textLine1 = @"line1"; NSString *textLine2 = @"line2"; @@ -361,670 +378,29 @@ describe(@"text and graphic manager", ^{ testManager.currentLevel = SDLHMILevelFull; testManager.batchUpdates = YES; - testManager.textField1 = nil; - testManager.textField2 = nil; - testManager.textField3 = nil; - testManager.textField4 = nil; - testManager.mediaTrackTextField = nil; - testManager.title = nil; - testManager.textField1Type = nil; - testManager.textField2Type = nil; - testManager.textField3Type = nil; - testManager.textField4Type = nil; - }); - - context(@"when textFields are nil", ^{ - beforeEach(^{ - testManager.windowCapability = [[SDLWindowCapability alloc] init]; - }); - - it(@"should send nothing", ^{ - testManager.mediaTrackTextField = textMediaTrack; - testManager.title = textTitle; - testManager.textField1 = textLine1; - testManager.textField2 = textLine2; - testManager.textField3 = textLine3; - testManager.textField4 = textLine4; - - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.mediaTrack).toNot(equal(textMediaTrack)); - expect(testManager.inProgressUpdate.templateTitle).toNot(equal(textTitle)); - expect(testManager.inProgressUpdate.mainField1).toNot(equal(textLine1)); - expect(testManager.inProgressUpdate.mainField2).toNot(equal(textLine2)); - expect(testManager.inProgressUpdate.mainField3).toNot(equal(textLine3)); - expect(testManager.inProgressUpdate.mainField4).toNot(equal(textLine4)); - }); - }); - - context(@"with one line available", ^{ - beforeEach(^{ - testManager.windowCapability = [[SDLWindowCapability alloc] init]; - SDLTextField *lineOneField = [[SDLTextField alloc] init]; - lineOneField.name = SDLTextFieldNameMainField1; - testManager.windowCapability.textFields = @[lineOneField]; - }); - - it(@"should not set mediatrack", ^{ - testManager.mediaTrackTextField = textMediaTrack; - - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.mediaTrack).toNot(equal(textMediaTrack)); - expect(testManager.inProgressUpdate.mainField1).to(beEmpty()); - expect(testManager.inProgressUpdate.metadataTags.mainField1).to(beNil()); - }); - - it(@"should not set title", ^{ - testManager.title = textTitle; - - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.templateTitle).toNot(equal(textTitle)); - expect(testManager.inProgressUpdate.mainField1).to(beEmpty()); - expect(testManager.inProgressUpdate.metadataTags.mainField1).to(beNil()); - }); - - it(@"should format a one line text and metadata update properly", ^{ - testManager.textField1 = textLine1; - testManager.textField1Type = line1Type; - - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.mainField1).to(equal(textLine1)); - expect(testManager.inProgressUpdate.metadataTags.mainField1.firstObject).to(equal(line1Type)); - expect(testManager.inProgressUpdate.mainField2).to(beEmpty()); - expect(testManager.inProgressUpdate.metadataTags.mainField2).to(beNil()); - }); - - it(@"should format a two line text and metadata update properly", ^{ - testManager.textField1 = textLine1; - testManager.textField2 = textLine2; - testManager.textField1Type = line1Type; - testManager.textField2Type = line2Type; - - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.mainField1).to(equal([NSString stringWithFormat:@"%@ - %@", textLine1, textLine2])); - expect(testManager.inProgressUpdate.metadataTags.mainField1[0]).to(equal(line1Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField1[1]).to(equal(line2Type)); - expect(testManager.inProgressUpdate.mainField2).to(beEmpty()); - expect(testManager.inProgressUpdate.metadataTags.mainField2).to(beNil()); - }); - - it(@"should format a three line text and metadata update properly", ^{ - testManager.textField1 = textLine1; - testManager.textField2 = textLine2; - testManager.textField3 = textLine3; - testManager.textField1Type = line1Type; - testManager.textField2Type = line2Type; - testManager.textField3Type = line3Type; - - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.mainField1).to(equal([NSString stringWithFormat:@"%@ - %@ - %@", textLine1, textLine2, textLine3])); - expect(testManager.inProgressUpdate.metadataTags.mainField1[0]).to(equal(line1Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField1[1]).to(equal(line2Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField1[2]).to(equal(line3Type)); - expect(testManager.inProgressUpdate.mainField2).to(beEmpty()); - expect(testManager.inProgressUpdate.metadataTags.mainField2).to(beNil()); - }); - - it(@"should format a four line text and metadata update properly", ^{ - testManager.textField1 = textLine1; - testManager.textField2 = textLine2; - testManager.textField3 = textLine3; - testManager.textField4 = textLine4; - testManager.textField1Type = line1Type; - testManager.textField2Type = line2Type; - testManager.textField3Type = line3Type; - testManager.textField4Type = line4Type; - - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.mainField1).to(equal([NSString stringWithFormat:@"%@ - %@ - %@ - %@", textLine1, textLine2, textLine3, textLine4])); - expect(testManager.inProgressUpdate.metadataTags.mainField1[0]).to(equal(line1Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField1[1]).to(equal(line2Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField1[2]).to(equal(line3Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField1[3]).to(equal(line4Type)); - expect(testManager.inProgressUpdate.mainField2).to(beEmpty()); - expect(testManager.inProgressUpdate.metadataTags.mainField2).to(beNil()); - }); - - context(@"when media track and title are available", ^{ - beforeEach(^{ - NSMutableArray<SDLTextField *> *existingFieldsMutable = [testManager.windowCapability.textFields mutableCopy]; - SDLTextField *mediaTrack = [[SDLTextField alloc] init]; - mediaTrack.name = SDLTextFieldNameMediaTrack; - [existingFieldsMutable addObject:mediaTrack]; - - SDLTextField *title = [[SDLTextField alloc] init]; - title.name = SDLTextFieldNameTemplateTitle; - [existingFieldsMutable addObject:title]; - testManager.windowCapability.textFields = [existingFieldsMutable copy]; - }); - - it(@"should set media track and title properly", ^{ - testManager.mediaTrackTextField = textMediaTrack; - testManager.title = textTitle; - - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.mediaTrack).to(equal(textMediaTrack)); - expect(testManager.inProgressUpdate.templateTitle).to(equal(textTitle)); - }); - }); - }); - - context(@"with two lines available", ^{ - beforeEach(^{ - testManager.windowCapability = [[SDLWindowCapability alloc] init]; - SDLTextField *lineTwoField = [[SDLTextField alloc] init]; - lineTwoField.name = SDLTextFieldNameMainField2; - testManager.windowCapability.textFields = @[lineTwoField]; - }); - - it(@"should not set mediatrack", ^{ - testManager.mediaTrackTextField = textMediaTrack; - - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.mediaTrack).toNot(equal(textMediaTrack)); - expect(testManager.inProgressUpdate.mainField1).to(beEmpty()); - expect(testManager.inProgressUpdate.metadataTags.mainField1).to(beNil()); - }); - - it(@"should not set title", ^{ - testManager.title = textTitle; - - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.templateTitle).toNot(equal(textTitle)); - expect(testManager.inProgressUpdate.mainField1).to(beEmpty()); - expect(testManager.inProgressUpdate.metadataTags.mainField1).to(beNil()); - }); - - it(@"should format a one line text and metadata update properly", ^{ - testManager.textField1 = textLine1; - testManager.textField1Type = line1Type; - - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.mainField1).to(equal(textLine1)); - expect(testManager.inProgressUpdate.metadataTags.mainField1.firstObject).to(equal(line1Type)); - expect(testManager.inProgressUpdate.mainField2).to(beEmpty()); - expect(testManager.inProgressUpdate.metadataTags.mainField2).to(beNil()); - }); - - it(@"should format a two line text and metadata update properly", ^{ - testManager.textField1 = textLine1; - testManager.textField2 = textLine2; - testManager.textField1Type = line1Type; - testManager.textField2Type = line2Type; - - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.mainField1).to(equal(textLine1)); - expect(testManager.inProgressUpdate.metadataTags.mainField1.firstObject).to(equal(line1Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField1).to(haveCount(1)); - expect(testManager.inProgressUpdate.mainField2).to(equal(textLine2)); - expect(testManager.inProgressUpdate.metadataTags.mainField2[0]).to(equal(line2Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField2).to(haveCount(1)); - expect(testManager.inProgressUpdate.mainField3).to(beEmpty()); - expect(testManager.inProgressUpdate.metadataTags.mainField3).to(beNil()); - }); - - it(@"should format a three line text and metadata update properly", ^{ - testManager.textField1 = textLine1; - testManager.textField2 = textLine2; - testManager.textField3 = textLine3; - testManager.textField1Type = line1Type; - testManager.textField2Type = line2Type; - testManager.textField3Type = line3Type; - - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.mainField1).to(equal([NSString stringWithFormat:@"%@ - %@", textLine1, textLine2])); - expect(testManager.inProgressUpdate.mainField2).to(equal(textLine3)); - expect(testManager.inProgressUpdate.metadataTags.mainField1[0]).to(equal(line1Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField1[1]).to(equal(line2Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField1).to(haveCount(2)); - expect(testManager.inProgressUpdate.metadataTags.mainField2[0]).to(equal(line3Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField2).to(haveCount(1)); - expect(testManager.inProgressUpdate.mainField3).to(beEmpty()); - expect(testManager.inProgressUpdate.metadataTags.mainField3).to(beNil()); - }); - - it(@"should format a four line text and metadata update properly", ^{ - testManager.textField1 = textLine1; - testManager.textField2 = textLine2; - testManager.textField3 = textLine3; - testManager.textField4 = textLine4; - testManager.textField1Type = line1Type; - testManager.textField2Type = line2Type; - testManager.textField3Type = line3Type; - testManager.textField4Type = line4Type; - - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.mainField1).to(equal([NSString stringWithFormat:@"%@ - %@", textLine1, textLine2])); - expect(testManager.inProgressUpdate.mainField2).to(equal([NSString stringWithFormat:@"%@ - %@", textLine3, textLine4])); - expect(testManager.inProgressUpdate.metadataTags.mainField1[0]).to(equal(line1Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField1[1]).to(equal(line2Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField1).to(haveCount(2)); - expect(testManager.inProgressUpdate.metadataTags.mainField2[0]).to(equal(line3Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField2[1]).to(equal(line4Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField2).to(haveCount(2)); - expect(testManager.inProgressUpdate.mainField3).to(beEmpty()); - expect(testManager.inProgressUpdate.metadataTags.mainField3).to(beNil()); - }); - }); - - context(@"with three lines available", ^{ - beforeEach(^{ - testManager.windowCapability = [[SDLWindowCapability alloc] init]; - SDLTextField *lineThreeField = [[SDLTextField alloc] init]; - lineThreeField.name = SDLTextFieldNameMainField3; - testManager.windowCapability.textFields = @[lineThreeField]; - }); - - it(@"should not set mediatrack", ^{ - testManager.mediaTrackTextField = textMediaTrack; - - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.mediaTrack).toNot(equal(textMediaTrack)); - expect(testManager.inProgressUpdate.mainField1).to(beEmpty()); - expect(testManager.inProgressUpdate.metadataTags.mainField1).to(beNil()); - }); - - it(@"should not set title", ^{ - testManager.title = textTitle; - - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.templateTitle).toNot(equal(textTitle)); - expect(testManager.inProgressUpdate.mainField1).to(beEmpty()); - expect(testManager.inProgressUpdate.metadataTags.mainField1).to(beNil()); - }); - - it(@"should format a one line text and metadata update properly", ^{ - testManager.textField1 = textLine1; - testManager.textField1Type = line1Type; - - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.mainField1).to(equal(textLine1)); - expect(testManager.inProgressUpdate.metadataTags.mainField1[0]).to(equal(line1Type)); - expect(testManager.inProgressUpdate.mainField2).to(beEmpty()); - expect(testManager.inProgressUpdate.metadataTags.mainField2).to(beNil()); - }); - - it(@"should format a two line text and metadata update properly", ^{ - testManager.textField1 = textLine1; - testManager.textField2 = textLine2; - testManager.textField1Type = line1Type; - testManager.textField2Type = line2Type; - - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.mainField1).to(equal(textLine1)); - expect(testManager.inProgressUpdate.metadataTags.mainField1.firstObject).to(equal(line1Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField1).to(haveCount(1)); - expect(testManager.inProgressUpdate.mainField2).to(equal(textLine2)); - expect(testManager.inProgressUpdate.metadataTags.mainField2[0]).to(equal(line2Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField2).to(haveCount(1)); - expect(testManager.inProgressUpdate.mainField3).to(beEmpty()); - expect(testManager.inProgressUpdate.metadataTags.mainField3).to(beNil()); - }); - - it(@"should format a three line text and metadata update properly", ^{ - testManager.textField1 = textLine1; - testManager.textField2 = textLine2; - testManager.textField3 = textLine3; - testManager.textField1Type = line1Type; - testManager.textField2Type = line2Type; - testManager.textField3Type = line3Type; - - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.mainField1).to(equal(textLine1)); - expect(testManager.inProgressUpdate.mainField2).to(equal(textLine2)); - expect(testManager.inProgressUpdate.mainField3).to(equal(textLine3)); - expect(testManager.inProgressUpdate.metadataTags.mainField1[0]).to(equal(line1Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField1).to(haveCount(1)); - expect(testManager.inProgressUpdate.metadataTags.mainField2[0]).to(equal(line2Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField2).to(haveCount(1)); - expect(testManager.inProgressUpdate.metadataTags.mainField3[0]).to(equal(line3Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField3).to(haveCount(1)); - expect(testManager.inProgressUpdate.mainField4).to(beEmpty()); - expect(testManager.inProgressUpdate.metadataTags.mainField4).to(beNil()); - }); - - it(@"should format a four line text and metadata update properly", ^{ - testManager.textField1 = textLine1; - testManager.textField2 = textLine2; - testManager.textField3 = textLine3; - testManager.textField4 = textLine4; - testManager.textField1Type = line1Type; - testManager.textField2Type = line2Type; - testManager.textField3Type = line3Type; - testManager.textField4Type = line4Type; - - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.mainField1).to(equal(textLine1)); - expect(testManager.inProgressUpdate.mainField2).to(equal(textLine2)); - expect(testManager.inProgressUpdate.mainField3).to(equal([NSString stringWithFormat:@"%@ - %@", textLine3, textLine4])); - expect(testManager.inProgressUpdate.metadataTags.mainField1[0]).to(equal(line1Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField1).to(haveCount(1)); - expect(testManager.inProgressUpdate.metadataTags.mainField2[0]).to(equal(line2Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField2).to(haveCount(1)); - expect(testManager.inProgressUpdate.metadataTags.mainField3[0]).to(equal(line3Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField3[1]).to(equal(line4Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField3).to(haveCount(2)); - expect(testManager.inProgressUpdate.mainField4).to(beEmpty()); - expect(testManager.inProgressUpdate.metadataTags.mainField4).to(beNil()); - }); - }); - - context(@"with four lines available", ^{ - beforeEach(^{ - testManager.windowCapability = [[SDLWindowCapability alloc] init]; - SDLTextField *lineFourField = [[SDLTextField alloc] init]; - lineFourField.name = SDLTextFieldNameMainField4; - testManager.windowCapability.textFields = @[lineFourField]; - }); - - it(@"should not set mediatrack", ^{ - testManager.mediaTrackTextField = textMediaTrack; - - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.mediaTrack).toNot(equal(textMediaTrack)); - expect(testManager.inProgressUpdate.mainField1).to(beEmpty()); - expect(testManager.inProgressUpdate.metadataTags.mainField1).to(beNil()); - }); - - it(@"should not set title", ^{ - testManager.title = textTitle; - - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.templateTitle).toNot(equal(textTitle)); - expect(testManager.inProgressUpdate.mainField1).to(beEmpty()); - expect(testManager.inProgressUpdate.metadataTags.mainField1).to(beNil()); - }); - - it(@"should format a one line text and metadata update properly", ^{ - testManager.textField1 = textLine1; - testManager.textField1Type = line1Type; - - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.mainField1).to(equal(textLine1)); - expect(testManager.inProgressUpdate.metadataTags.mainField1[0]).to(equal(line1Type)); - expect(testManager.inProgressUpdate.mainField2).to(beEmpty()); - expect(testManager.inProgressUpdate.metadataTags.mainField2).to(beNil()); - }); - - it(@"should format a two line text and metadata update properly", ^{ - testManager.textField1 = textLine1; - testManager.textField2 = textLine2; - testManager.textField1Type = line1Type; - testManager.textField2Type = line2Type; - - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.mainField1).to(equal(textLine1)); - expect(testManager.inProgressUpdate.metadataTags.mainField1.firstObject).to(equal(line1Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField1).to(haveCount(1)); - expect(testManager.inProgressUpdate.mainField2).to(equal(textLine2)); - expect(testManager.inProgressUpdate.metadataTags.mainField2[0]).to(equal(line2Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField2).to(haveCount(1)); - expect(testManager.inProgressUpdate.mainField3).to(beEmpty()); - expect(testManager.inProgressUpdate.metadataTags.mainField3).to(beNil()); - }); - - it(@"should format a three line text and metadata update properly", ^{ - testManager.textField1 = textLine1; - testManager.textField2 = textLine2; - testManager.textField3 = textLine3; - testManager.textField1Type = line1Type; - testManager.textField2Type = line2Type; - testManager.textField3Type = line3Type; - - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.mainField1).to(equal(textLine1)); - expect(testManager.inProgressUpdate.mainField2).to(equal(textLine2)); - expect(testManager.inProgressUpdate.mainField3).to(equal(textLine3)); - expect(testManager.inProgressUpdate.metadataTags.mainField1[0]).to(equal(line1Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField1).to(haveCount(1)); - expect(testManager.inProgressUpdate.metadataTags.mainField2[0]).to(equal(line2Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField2).to(haveCount(1)); - expect(testManager.inProgressUpdate.metadataTags.mainField3[0]).to(equal(line3Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField3).to(haveCount(1)); - expect(testManager.inProgressUpdate.mainField4).to(beEmpty()); - expect(testManager.inProgressUpdate.metadataTags.mainField4).to(beNil()); - }); - - it(@"should format a four line text and metadata update properly", ^{ - testManager.textField1 = textLine1; - testManager.textField2 = textLine2; - testManager.textField3 = textLine3; - testManager.textField4 = textLine4; - testManager.textField1Type = line1Type; - testManager.textField2Type = line2Type; - testManager.textField3Type = line3Type; - testManager.textField4Type = line4Type; - - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.mainField1).to(equal(textLine1)); - expect(testManager.inProgressUpdate.mainField2).to(equal(textLine2)); - expect(testManager.inProgressUpdate.mainField3).to(equal(textLine3)); - expect(testManager.inProgressUpdate.mainField4).to(equal(textLine4)); - expect(testManager.inProgressUpdate.metadataTags.mainField1[0]).to(equal(line1Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField1).to(haveCount(1)); - expect(testManager.inProgressUpdate.metadataTags.mainField2[0]).to(equal(line2Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField2).to(haveCount(1)); - expect(testManager.inProgressUpdate.metadataTags.mainField3[0]).to(equal(line3Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField3).to(haveCount(1)); - expect(testManager.inProgressUpdate.metadataTags.mainField4[0]).to(equal(line4Type)); - expect(testManager.inProgressUpdate.metadataTags.mainField4).to(haveCount(1)); - }); + testManager.textField1 = textLine1; + testManager.textField2 = textLine2; + testManager.textField3 = textLine3; + testManager.textField4 = textLine4; + testManager.mediaTrackTextField = textMediaTrack; + testManager.title = textTitle; + testManager.textField1Type = line1Type; + testManager.textField2Type = line2Type; + testManager.textField3Type = line3Type; + testManager.textField4Type = line4Type; }); - context(@"updating images", ^{ - __block NSString *testTextFieldText = @"mainFieldText"; - - beforeEach(^{ - testManager.windowCapability = [[SDLWindowCapability alloc] init]; - SDLImageField *primaryImageField = [[SDLImageField alloc] init]; - primaryImageField.name = SDLImageFieldNameGraphic; - SDLImageField *secondaryImageField = [[SDLImageField alloc] init]; - secondaryImageField.name = SDLImageFieldNameSecondaryGraphic; - testManager.windowCapability.imageFields = @[primaryImageField, secondaryImageField]; - - SDLTextField *lineOneField = [[SDLTextField alloc] init]; - lineOneField.name = SDLTextFieldNameMainField1; - testManager.windowCapability.textFields = @[lineOneField]; - - testManager.batchUpdates = YES; - testManager.textField1 = testTextFieldText; - }); - - context(@"when imageFields are nil", ^{ - beforeEach(^{ - testManager.windowCapability.imageFields = nil; - }); - - it(@"should send nothing", ^{ - testManager.primaryGraphic = testArtwork; - testManager.secondaryGraphic = testArtwork; - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.graphic).to(beNil()); - expect(testManager.inProgressUpdate.secondaryGraphic).to(beNil()); - expect(testManager.inProgressUpdate.mainField1).to(equal(testTextFieldText)); - }); - }); - - context(@"when the image is already on the head unit", ^{ - beforeEach(^{ - OCMStub([mockFileManager hasUploadedFile:[OCMArg isNotNil]]).andReturn(YES); - }); - - it(@"should immediately attempt to update", ^{ - testManager.primaryGraphic = testArtwork; - testManager.secondaryGraphic = testArtwork; - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.inProgressUpdate.graphic.value).to(equal(testArtworkName)); - expect(testManager.inProgressUpdate.secondaryGraphic.value).to(equal(testArtworkName)); - expect(testManager.inProgressUpdate.mainField1).to(equal(testTextFieldText)); - }); - }); - - context(@"when the image is a static icon", ^{ - beforeEach(^{ - testManager.primaryGraphic = testStaticIcon; - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - }); - - it(@"should immediately update without uploading the images", ^{ - OCMReject([mockFileManager uploadArtwork:[OCMArg any] completionHandler:[OCMArg any]]); - expect(testManager.inProgressUpdate.mainField1).to(equal(testTextFieldText)); - expect(testManager.inProgressUpdate.graphic.value).toNot(beNil()); - }); - }); + it(@"should wait until batching ends to create an update operation", ^{ + expect(testManager.transactionQueue.operationCount).to(equal(0)); - context(@"when the image is not on the head unit", ^{ - beforeEach(^{ - OCMStub([mockFileManager hasUploadedFile:[OCMArg isNotNil]]).andReturn(NO); - - testManager.primaryGraphic = testArtwork; - testManager.secondaryGraphic = testArtwork; - testManager.batchUpdates = NO; - [testManager updateWithCompletionHandler:nil]; - }); - - it(@"should immediately attempt to update without the images", ^{ - expect(testManager.inProgressUpdate.mainField1).to(equal(testTextFieldText)); - expect(testManager.inProgressUpdate.graphic.value).to(beNil()); - expect(testManager.inProgressUpdate.secondaryGraphic.value).to(beNil()); - expect(testManager.queuedImageUpdate.graphic.value).to(equal(testArtworkName)); - expect(testManager.queuedImageUpdate.secondaryGraphic.value).to(equal(testArtworkName)); - }); - }); - - describe(@"When an image fails to upload to the remote", ^{ - __block SDLArtwork *testArtwork1 = nil; - __block SDLArtwork *testArtwork2 = nil; - - beforeEach(^{ - testArtwork1 = [[SDLArtwork alloc] initWithData:[@"Test data 1" dataUsingEncoding:NSUTF8StringEncoding] name:@"Test data 1" fileExtension:@"png" persistent:NO]; - testArtwork2 = [[SDLArtwork alloc] initWithData:[@"Test data 2" dataUsingEncoding:NSUTF8StringEncoding] name:@"Test data 2" fileExtension:@"png" persistent:NO]; - }); - - context(@"If the images for the primary and secondary graphics fail the upload process", ^{ - it(@"Should skip sending an update", ^{ - testManager.primaryGraphic = testArtwork1; - testManager.secondaryGraphic = testArtwork2; - testManager.batchUpdates = NO; - - OCMStub([mockFileManager hasUploadedFile:[OCMArg isNotNil]]).andReturn(NO); - NSArray<NSString *> *testSuccessfulArtworks = @[]; - NSError *testError = [NSError errorWithDomain:@"errorDomain" - code:9 - userInfo:@{testArtwork1.name:@"error 1", testArtwork2.name:@"error 2"} - ]; - OCMStub([mockFileManager uploadArtworks:[OCMArg isNotNil] completionHandler:([OCMArg invokeBlockWithArgs:testSuccessfulArtworks, testError, nil])]); - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.textField1).to(equal(testTextFieldText)); - expect(testManager.inProgressUpdate).to(beNil()); - expect(testManager.queuedImageUpdate.graphic.value).to(equal(testArtwork1.name)); - expect(testManager.queuedImageUpdate.secondaryGraphic.value).to(equal(testArtwork2.name)); - }); - }); - - context(@"If only one of images for the primary and secondary graphics fails to upload", ^{ - it(@"Should show the primary graphic even if the secondary graphic upload fails", ^{ - testManager.primaryGraphic = testArtwork1; - testManager.secondaryGraphic = testArtwork2; - testManager.batchUpdates = NO; - - OCMStub([mockFileManager hasUploadedFile:testArtwork1]).andReturn(YES); - OCMStub([mockFileManager hasUploadedFile:testArtwork2]).andReturn(NO); - NSArray<NSString *> *testSuccessfulArtworks = @[testArtwork1.name]; - NSError *testError = [NSError errorWithDomain:@"errorDomain" code:9 userInfo:@{testArtwork2.name:@"error 2"}]; - OCMStub([mockFileManager uploadArtworks:[OCMArg isNotNil] completionHandler:([OCMArg invokeBlockWithArgs:testSuccessfulArtworks, testError, nil])]); - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.textField1).to(equal(testTextFieldText)); - expect(testManager.inProgressUpdate.graphic.value).to(equal(testArtwork1.name)); - expect(testManager.inProgressUpdate.secondaryGraphic).to(beNil()); - expect(testManager.inProgressUpdate.mainField1).to(beNil()); - expect(testManager.queuedImageUpdate.graphic.value).to(equal(testArtwork1.name)); - expect(testManager.queuedImageUpdate.secondaryGraphic.value).to(equal(testArtwork2.name)); - }); - - it(@"Should show the secondary graphic even if the primary graphic upload fails", ^{ - testManager.primaryGraphic = testArtwork1; - testManager.secondaryGraphic = testArtwork2; - testManager.batchUpdates = NO; - - OCMStub([mockFileManager hasUploadedFile:testArtwork1]).andReturn(NO); - OCMStub([mockFileManager hasUploadedFile:testArtwork2]).andReturn(YES); - NSArray<NSString *> *testSuccessfulArtworks = @[testArtwork2.name]; - NSError *testError = [NSError errorWithDomain:@"errorDomain" code:9 userInfo:@{testArtwork1.name:@"error 2"}]; - OCMStub([mockFileManager uploadArtworks:[OCMArg isNotNil] completionHandler:([OCMArg invokeBlockWithArgs:testSuccessfulArtworks, testError, nil])]); - [testManager updateWithCompletionHandler:nil]; - - expect(testManager.textField1).to(equal(testTextFieldText)); - expect(testManager.inProgressUpdate.graphic).to(beNil()); - expect(testManager.inProgressUpdate.secondaryGraphic.value).to(equal(testArtwork2.name)); - expect(testManager.inProgressUpdate.mainField1).to(beNil()); - expect(testManager.queuedImageUpdate.graphic.value).to(equal(testArtwork1.name)); - expect(testManager.queuedImageUpdate.secondaryGraphic.value).to(equal(testArtwork2.name)); - }); - }); - }); + testManager.batchUpdates = NO; + [testManager updateWithCompletionHandler:nil]; + expect(testManager.transactionQueue.operationCount).to(equal(1)); }); }); - context(@"On disconnects", ^{ + // on disconnect + context(@"on disconnect", ^{ beforeEach(^{ [testManager stop]; }); @@ -1047,9 +423,6 @@ describe(@"text and graphic manager", ^{ expect(testManager.textField4Type).to(beNil()); expect(testManager.currentScreenData).to(equal([[SDLShow alloc] init])); - expect(testManager.inProgressUpdate).to(beNil()); - expect(testManager.queuedImageUpdate).to(beNil()); - expect(testManager.hasQueuedUpdate).to(beFalse()); expect(testManager.windowCapability).to(beNil()); expect(testManager.currentLevel).to(equal(SDLHMILevelNone)); expect(testManager.blankArtwork).toNot(beNil()); diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicUpdateOperationSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicUpdateOperationSpec.m new file mode 100644 index 000000000..cf308aecf --- /dev/null +++ b/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicUpdateOperationSpec.m @@ -0,0 +1,929 @@ +// +// SDLTextAndGraphicUpdateOperationSpec.m +// SmartDeviceLinkTests +// +// Created by Joel Fischer on 8/18/20. +// Copyright © 2020 smartdevicelink. All rights reserved. +// + +#import <Foundation/Foundation.h> + +#import <Quick/Quick.h> +#import <Nimble/Nimble.h> +#import <OCMock/OCMock.h> + +#import "SDLFileManager.h" +#import "SDLImage.h" +#import "SDLImageField.h" +#import "SDLMetadataTags.h" +#import "SDLResult.h" +#import "SDLShow.h" +#import "SDLShowResponse.h" +#import "SDLTextAndGraphicState.h" +#import "SDLTextAndGraphicUpdateOperation.h" +#import "SDLTextField.h" +#import "SDLTextFieldName.h" +#import "SDLWindowCapability.h" +#import "TestConnectionManager.h" + +QuickSpecBegin(SDLTextAndGraphicUpdateOperationSpec) + +SDLTextField *fieldLine1 = [[SDLTextField alloc] initWithName:SDLTextFieldNameMainField1 characterSet:SDLCharacterSetUtf8 width:20 rows:20]; +SDLTextField *fieldLine2 = [[SDLTextField alloc] initWithName:SDLTextFieldNameMainField2 characterSet:SDLCharacterSetUtf8 width:20 rows:20]; +SDLTextField *fieldLine3 = [[SDLTextField alloc] initWithName:SDLTextFieldNameMainField3 characterSet:SDLCharacterSetUtf8 width:20 rows:20]; +SDLTextField *fieldLine4 = [[SDLTextField alloc] initWithName:SDLTextFieldNameMainField4 characterSet:SDLCharacterSetUtf8 width:20 rows:20]; +SDLImageField *fieldGraphic = [[SDLImageField alloc] initWithName:SDLImageFieldNameGraphic imageTypeSupported:@[SDLFileTypePNG] imageResolution:nil]; +SDLImageField *fieldSecondaryGraphic = [[SDLImageField alloc] initWithName:SDLImageFieldNameSecondaryGraphic imageTypeSupported:@[SDLFileTypePNG] imageResolution:nil]; + +NSString *field1String = @"Text Field 1"; +NSString *field2String = @"Text Field 2"; +NSString *field3String = @"Text Field 3"; +NSString *field4String = @"Text Field 4"; + +NSString *testArtworkName = @"some artwork name"; +SDLArtwork *testArtwork = [[SDLArtwork alloc] initWithData:[@"Test data" dataUsingEncoding:NSUTF8StringEncoding] name:testArtworkName fileExtension:@"png" persistent:NO]; +NSString *testArtworkName2 = @"some other artwork name"; +SDLArtwork *testArtwork2 = [[SDLArtwork alloc] initWithData:[@"Test data 2" dataUsingEncoding:NSUTF8StringEncoding] name:testArtworkName2 fileExtension:@"png" persistent:NO]; +SDLArtwork *testStaticIcon = [SDLArtwork artworkWithStaticIcon:SDLStaticIconNameDate]; + +describe(@"the text and graphic operation", ^{ + __block SDLTextAndGraphicUpdateOperation *testOp = nil; + + __block TestConnectionManager *testConnectionManager = nil; + __block SDLFileManager *mockFileManager = nil; + __block SDLWindowCapability *windowCapability = nil; + __block SDLTextAndGraphicState *updatedState = nil; + + __block SDLShowResponse *successShowResponse = [[SDLShowResponse alloc] init]; + __block SDLShow *emptyCurrentDataShow = nil; + + beforeEach(^{ + testConnectionManager = [[TestConnectionManager alloc] init]; + mockFileManager = OCMClassMock([SDLFileManager class]); + testOp = nil; + updatedState = nil; + + successShowResponse.success = @YES; + successShowResponse.resultCode = SDLResultSuccess; + emptyCurrentDataShow = [[SDLShow alloc] init]; + }); + + // updating text fields + describe(@"updating text fields", ^{ + // with textfields available == nil + context(@"with textfields available == nil", ^{ + beforeEach(^{ + windowCapability = [[SDLWindowCapability alloc] init]; + }); + + context(@"when sending four lines of text", ^{ + beforeEach(^{ + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.textField1 = field1String; + updatedState.textField2 = field2String; + updatedState.textField3 = field3String; + updatedState.textField4 = field4String; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:nil updateCompletionHandler:nil]; + [testOp start]; + + [testConnectionManager respondToLastRequestWithResponse:successShowResponse]; + }); + + it(@"should not send any text", ^{ + expect(testOp.isFinished).to(beTrue()); + expect(testOp.currentScreenData.mainField1).to(beEmpty()); + expect(testOp.currentScreenData.mainField2).to(beEmpty()); + expect(testOp.currentScreenData.mainField3).to(beEmpty()); + expect(testOp.currentScreenData.mainField4).to(beEmpty()); + expect(testOp.currentScreenData.templateTitle).to(beEmpty()); + expect(testOp.currentScreenData.alignment).to(beNil()); + expect(testOp.currentScreenData.mediaTrack).to(beEmpty()); + expect(testOp.currentScreenData.graphic).to(beNil()); + expect(testOp.currentScreenData.secondaryGraphic).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField1).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField2).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField3).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField4).to(beNil()); + }); + }); + }); + + // with one line available + context(@"with one line available", ^{ + beforeEach(^{ + windowCapability = [[SDLWindowCapability alloc] init]; + windowCapability.textFields = @[fieldLine1]; + }); + + context(@"when sending one line of text", ^{ + beforeEach(^{ + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.textField1 = field1String; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:nil updateCompletionHandler:nil]; + [testOp start]; + + [testConnectionManager respondToLastRequestWithResponse:successShowResponse]; + }); + + it(@"should only send one line of text", ^{ + expect(testOp.isFinished).to(beTrue()); + expect(testOp.currentScreenData.mainField1).to(equal([NSString stringWithFormat:@"%@", field1String])); + expect(testOp.currentScreenData.mainField2).to(beEmpty()); + expect(testOp.currentScreenData.mainField3).to(beEmpty()); + expect(testOp.currentScreenData.mainField4).to(beEmpty()); + expect(testOp.currentScreenData.templateTitle).to(beEmpty()); + expect(testOp.currentScreenData.alignment).to(beNil()); + expect(testOp.currentScreenData.mediaTrack).to(beEmpty()); + expect(testOp.currentScreenData.graphic).to(beNil()); + expect(testOp.currentScreenData.secondaryGraphic).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField1).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField2).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField3).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField4).to(beNil()); + }); + }); + + context(@"when sending two lines of text", ^{ + beforeEach(^{ + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.textField1 = field1String; + updatedState.textField2 = field2String; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:nil updateCompletionHandler:nil]; + [testOp start]; + + [testConnectionManager respondToLastRequestWithResponse:successShowResponse]; + }); + + it(@"should concatenate the strings into one line", ^{ + expect(testOp.isFinished).to(beTrue()); + expect(testOp.currentScreenData.mainField1).to(equal([NSString stringWithFormat:@"%@ - %@", field1String, field2String])); + expect(testOp.currentScreenData.mainField2).to(beEmpty()); + expect(testOp.currentScreenData.mainField3).to(beEmpty()); + expect(testOp.currentScreenData.mainField4).to(beEmpty()); + expect(testOp.currentScreenData.templateTitle).to(beEmpty()); + expect(testOp.currentScreenData.alignment).to(beNil()); + expect(testOp.currentScreenData.mediaTrack).to(beEmpty()); + expect(testOp.currentScreenData.graphic).to(beNil()); + expect(testOp.currentScreenData.secondaryGraphic).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField1).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField2).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField3).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField4).to(beNil()); + }); + }); + + context(@"when sending three lines of text", ^{ + beforeEach(^{ + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.textField1 = field1String; + updatedState.textField2 = field2String; + updatedState.textField3 = field3String; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:nil updateCompletionHandler:nil]; + [testOp start]; + + [testConnectionManager respondToLastRequestWithResponse:successShowResponse]; + }); + + it(@"should concatenate the strings into one line", ^{ + expect(testOp.isFinished).to(beTrue()); + expect(testOp.currentScreenData.mainField1).to(equal([NSString stringWithFormat:@"%@ - %@ - %@", field1String, field2String, field3String])); + expect(testOp.currentScreenData.mainField2).to(beEmpty()); + expect(testOp.currentScreenData.mainField3).to(beEmpty()); + expect(testOp.currentScreenData.mainField4).to(beEmpty()); + expect(testOp.currentScreenData.templateTitle).to(beEmpty()); + expect(testOp.currentScreenData.alignment).to(beNil()); + expect(testOp.currentScreenData.mediaTrack).to(beEmpty()); + expect(testOp.currentScreenData.graphic).to(beNil()); + expect(testOp.currentScreenData.secondaryGraphic).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField1).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField2).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField3).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField4).to(beNil()); + }); + }); + + context(@"when sending four lines of text", ^{ + beforeEach(^{ + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.textField1 = field1String; + updatedState.textField2 = field2String; + updatedState.textField3 = field3String; + updatedState.textField4 = field4String; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:nil updateCompletionHandler:nil]; + [testOp start]; + + [testConnectionManager respondToLastRequestWithResponse:successShowResponse]; + }); + + it(@"should concatenate the strings into one line", ^{ + expect(testOp.isFinished).to(beTrue()); + expect(testOp.currentScreenData.mainField1).to(equal([NSString stringWithFormat:@"%@ - %@ - %@ - %@", field1String, field2String, field3String, field4String])); + expect(testOp.currentScreenData.mainField2).to(beEmpty()); + expect(testOp.currentScreenData.mainField3).to(beEmpty()); + expect(testOp.currentScreenData.mainField4).to(beEmpty()); + expect(testOp.currentScreenData.templateTitle).to(beEmpty()); + expect(testOp.currentScreenData.alignment).to(beNil()); + expect(testOp.currentScreenData.mediaTrack).to(beEmpty()); + expect(testOp.currentScreenData.graphic).to(beNil()); + expect(testOp.currentScreenData.secondaryGraphic).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField1).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField2).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField3).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField4).to(beNil()); + }); + }); + }); + + // with two lines available + context(@"with two lines available", ^{ + beforeEach(^{ + windowCapability = [[SDLWindowCapability alloc] init]; + windowCapability.textFields = @[fieldLine1, fieldLine2]; + }); + + context(@"when sending one line of text", ^{ + beforeEach(^{ + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.textField1 = field1String; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:nil updateCompletionHandler:nil]; + [testOp start]; + + [testConnectionManager respondToLastRequestWithResponse:successShowResponse]; + }); + + it(@"should only send one line of text", ^{ + expect(testOp.isFinished).to(beTrue()); + expect(testOp.currentScreenData.mainField1).to(equal([NSString stringWithFormat:@"%@", field1String])); + expect(testOp.currentScreenData.mainField2).to(beEmpty()); + expect(testOp.currentScreenData.mainField3).to(beEmpty()); + expect(testOp.currentScreenData.mainField4).to(beEmpty()); + expect(testOp.currentScreenData.templateTitle).to(beEmpty()); + expect(testOp.currentScreenData.alignment).to(beNil()); + expect(testOp.currentScreenData.mediaTrack).to(beEmpty()); + expect(testOp.currentScreenData.graphic).to(beNil()); + expect(testOp.currentScreenData.secondaryGraphic).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField1).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField2).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField3).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField4).to(beNil()); + }); + }); + + context(@"when sending two lines of text", ^{ + beforeEach(^{ + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.textField1 = field1String; + updatedState.textField2 = field2String; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:nil updateCompletionHandler:nil]; + [testOp start]; + + [testConnectionManager respondToLastRequestWithResponse:successShowResponse]; + }); + + it(@"should send two lines of text", ^{ + expect(testOp.isFinished).to(beTrue()); + expect(testOp.currentScreenData.mainField1).to(equal([NSString stringWithFormat:@"%@", field1String])); + expect(testOp.currentScreenData.mainField2).to(equal([NSString stringWithFormat:@"%@", field2String])); + expect(testOp.currentScreenData.mainField3).to(beEmpty()); + expect(testOp.currentScreenData.mainField4).to(beEmpty()); + expect(testOp.currentScreenData.templateTitle).to(beEmpty()); + expect(testOp.currentScreenData.alignment).to(beNil()); + expect(testOp.currentScreenData.mediaTrack).to(beEmpty()); + expect(testOp.currentScreenData.graphic).to(beNil()); + expect(testOp.currentScreenData.secondaryGraphic).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField1).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField2).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField3).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField4).to(beNil()); + }); + }); + + context(@"when sending three lines of text", ^{ + beforeEach(^{ + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.textField1 = field1String; + updatedState.textField2 = field2String; + updatedState.textField3 = field3String; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:nil updateCompletionHandler:nil]; + [testOp start]; + + [testConnectionManager respondToLastRequestWithResponse:successShowResponse]; + }); + + it(@"should concatenate the strings into two lines", ^{ + expect(testOp.isFinished).to(beTrue()); + expect(testOp.currentScreenData.mainField1).to(equal([NSString stringWithFormat:@"%@ - %@", field1String, field2String])); + expect(testOp.currentScreenData.mainField2).to(equal([NSString stringWithFormat:@"%@", field3String])); + expect(testOp.currentScreenData.mainField3).to(beEmpty()); + expect(testOp.currentScreenData.mainField4).to(beEmpty()); + expect(testOp.currentScreenData.templateTitle).to(beEmpty()); + expect(testOp.currentScreenData.alignment).to(beNil()); + expect(testOp.currentScreenData.mediaTrack).to(beEmpty()); + expect(testOp.currentScreenData.graphic).to(beNil()); + expect(testOp.currentScreenData.secondaryGraphic).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField1).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField2).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField3).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField4).to(beNil()); + }); + }); + + context(@"when sending four lines of text", ^{ + beforeEach(^{ + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.textField1 = field1String; + updatedState.textField2 = field2String; + updatedState.textField3 = field3String; + updatedState.textField4 = field4String; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:nil updateCompletionHandler:nil]; + [testOp start]; + + [testConnectionManager respondToLastRequestWithResponse:successShowResponse]; + }); + + it(@"should concatenate the strings into two lines", ^{ + expect(testOp.isFinished).to(beTrue()); + expect(testOp.currentScreenData.mainField1).to(equal([NSString stringWithFormat:@"%@ - %@", field1String, field2String])); + expect(testOp.currentScreenData.mainField2).to(equal([NSString stringWithFormat:@"%@ - %@", field3String, field4String])); + expect(testOp.currentScreenData.mainField3).to(beEmpty()); + expect(testOp.currentScreenData.mainField4).to(beEmpty()); + expect(testOp.currentScreenData.templateTitle).to(beEmpty()); + expect(testOp.currentScreenData.alignment).to(beNil()); + expect(testOp.currentScreenData.mediaTrack).to(beEmpty()); + expect(testOp.currentScreenData.graphic).to(beNil()); + expect(testOp.currentScreenData.secondaryGraphic).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField1).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField2).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField3).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField4).to(beNil()); + }); + }); + }); + + // with three lines available + context(@"with three lines available", ^{ + beforeEach(^{ + windowCapability = [[SDLWindowCapability alloc] init]; + windowCapability.textFields = @[fieldLine1, fieldLine2, fieldLine3]; + }); + + context(@"when sending one line of text", ^{ + beforeEach(^{ + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.textField1 = field1String; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:nil updateCompletionHandler:nil]; + [testOp start]; + + [testConnectionManager respondToLastRequestWithResponse:successShowResponse]; + }); + + it(@"should only send one line of text", ^{ + expect(testOp.isFinished).to(beTrue()); + expect(testOp.currentScreenData.mainField1).to(equal([NSString stringWithFormat:@"%@", field1String])); + expect(testOp.currentScreenData.mainField2).to(beEmpty()); + expect(testOp.currentScreenData.mainField3).to(beEmpty()); + expect(testOp.currentScreenData.mainField4).to(beEmpty()); + expect(testOp.currentScreenData.templateTitle).to(beEmpty()); + expect(testOp.currentScreenData.alignment).to(beNil()); + expect(testOp.currentScreenData.mediaTrack).to(beEmpty()); + expect(testOp.currentScreenData.graphic).to(beNil()); + expect(testOp.currentScreenData.secondaryGraphic).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField1).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField2).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField3).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField4).to(beNil()); + }); + }); + + context(@"when sending two lines of text", ^{ + beforeEach(^{ + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.textField1 = field1String; + updatedState.textField2 = field2String; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:nil updateCompletionHandler:nil]; + [testOp start]; + + [testConnectionManager respondToLastRequestWithResponse:successShowResponse]; + }); + + it(@"should send two lines of text", ^{ + expect(testOp.isFinished).to(beTrue()); + expect(testOp.currentScreenData.mainField1).to(equal([NSString stringWithFormat:@"%@", field1String])); + expect(testOp.currentScreenData.mainField2).to(equal([NSString stringWithFormat:@"%@", field2String])); + expect(testOp.currentScreenData.mainField3).to(beEmpty()); + expect(testOp.currentScreenData.mainField4).to(beEmpty()); + expect(testOp.currentScreenData.templateTitle).to(beEmpty()); + expect(testOp.currentScreenData.alignment).to(beNil()); + expect(testOp.currentScreenData.mediaTrack).to(beEmpty()); + expect(testOp.currentScreenData.graphic).to(beNil()); + expect(testOp.currentScreenData.secondaryGraphic).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField1).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField2).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField3).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField4).to(beNil()); + }); + }); + + context(@"when sending three lines of text", ^{ + beforeEach(^{ + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.textField1 = field1String; + updatedState.textField2 = field2String; + updatedState.textField3 = field3String; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:nil updateCompletionHandler:nil]; + [testOp start]; + + [testConnectionManager respondToLastRequestWithResponse:successShowResponse]; + }); + + it(@"should send three lines of text", ^{ + expect(testOp.isFinished).to(beTrue()); + expect(testOp.currentScreenData.mainField1).to(equal([NSString stringWithFormat:@"%@", field1String])); + expect(testOp.currentScreenData.mainField2).to(equal([NSString stringWithFormat:@"%@", field2String])); + expect(testOp.currentScreenData.mainField3).to(equal([NSString stringWithFormat:@"%@", field3String])); + expect(testOp.currentScreenData.mainField4).to(beEmpty()); + expect(testOp.currentScreenData.templateTitle).to(beEmpty()); + expect(testOp.currentScreenData.alignment).to(beNil()); + expect(testOp.currentScreenData.mediaTrack).to(beEmpty()); + expect(testOp.currentScreenData.graphic).to(beNil()); + expect(testOp.currentScreenData.secondaryGraphic).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField1).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField2).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField3).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField4).to(beNil()); + }); + }); + + context(@"when sending four lines of text", ^{ + beforeEach(^{ + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.textField1 = field1String; + updatedState.textField2 = field2String; + updatedState.textField3 = field3String; + updatedState.textField4 = field4String; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:nil updateCompletionHandler:nil]; + [testOp start]; + + [testConnectionManager respondToLastRequestWithResponse:successShowResponse]; + }); + + it(@"should concatenate the strings into three lines", ^{ + expect(testOp.isFinished).to(beTrue()); + expect(testOp.currentScreenData.mainField1).to(equal([NSString stringWithFormat:@"%@", field1String])); + expect(testOp.currentScreenData.mainField2).to(equal([NSString stringWithFormat:@"%@", field2String])); + expect(testOp.currentScreenData.mainField3).to(equal([NSString stringWithFormat:@"%@ - %@", field3String, field4String])); + expect(testOp.currentScreenData.mainField4).to(beEmpty()); + expect(testOp.currentScreenData.templateTitle).to(beEmpty()); + expect(testOp.currentScreenData.alignment).to(beNil()); + expect(testOp.currentScreenData.mediaTrack).to(beEmpty()); + expect(testOp.currentScreenData.graphic).to(beNil()); + expect(testOp.currentScreenData.secondaryGraphic).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField1).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField2).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField3).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField4).to(beNil()); + }); + }); + }); + + // with four lines available + context(@"with four lines available", ^{ + beforeEach(^{ + windowCapability = [[SDLWindowCapability alloc] init]; + windowCapability.textFields = @[fieldLine1, fieldLine2, fieldLine3, fieldLine4]; + }); + + context(@"when sending one line of text", ^{ + beforeEach(^{ + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.textField1 = field1String; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:nil updateCompletionHandler:nil]; + [testOp start]; + + [testConnectionManager respondToLastRequestWithResponse:successShowResponse]; + }); + + it(@"should only send one line of text", ^{ + expect(testOp.isFinished).to(beTrue()); + expect(testOp.currentScreenData.mainField1).to(equal([NSString stringWithFormat:@"%@", field1String])); + expect(testOp.currentScreenData.mainField2).to(beEmpty()); + expect(testOp.currentScreenData.mainField3).to(beEmpty()); + expect(testOp.currentScreenData.mainField4).to(beEmpty()); + expect(testOp.currentScreenData.templateTitle).to(beEmpty()); + expect(testOp.currentScreenData.alignment).to(beNil()); + expect(testOp.currentScreenData.mediaTrack).to(beEmpty()); + expect(testOp.currentScreenData.graphic).to(beNil()); + expect(testOp.currentScreenData.secondaryGraphic).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField1).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField2).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField3).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField4).to(beNil()); + }); + }); + + context(@"when sending two lines of text", ^{ + beforeEach(^{ + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.textField1 = field1String; + updatedState.textField2 = field2String; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:nil updateCompletionHandler:nil]; + [testOp start]; + + [testConnectionManager respondToLastRequestWithResponse:successShowResponse]; + }); + + it(@"should send two lines of text", ^{ + expect(testOp.isFinished).to(beTrue()); + expect(testOp.currentScreenData.mainField1).to(equal([NSString stringWithFormat:@"%@", field1String])); + expect(testOp.currentScreenData.mainField2).to(equal([NSString stringWithFormat:@"%@", field2String])); + expect(testOp.currentScreenData.mainField3).to(beEmpty()); + expect(testOp.currentScreenData.mainField4).to(beEmpty()); + expect(testOp.currentScreenData.templateTitle).to(beEmpty()); + expect(testOp.currentScreenData.alignment).to(beNil()); + expect(testOp.currentScreenData.mediaTrack).to(beEmpty()); + expect(testOp.currentScreenData.graphic).to(beNil()); + expect(testOp.currentScreenData.secondaryGraphic).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField1).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField2).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField3).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField4).to(beNil()); + }); + }); + + context(@"when sending three lines of text", ^{ + beforeEach(^{ + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.textField1 = field1String; + updatedState.textField2 = field2String; + updatedState.textField3 = field3String; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:nil updateCompletionHandler:nil]; + [testOp start]; + + [testConnectionManager respondToLastRequestWithResponse:successShowResponse]; + }); + + it(@"should send three lines text", ^{ + expect(testOp.isFinished).to(beTrue()); + expect(testOp.currentScreenData.mainField1).to(equal([NSString stringWithFormat:@"%@", field1String])); + expect(testOp.currentScreenData.mainField2).to(equal([NSString stringWithFormat:@"%@", field2String])); + expect(testOp.currentScreenData.mainField3).to(equal([NSString stringWithFormat:@"%@", field3String])); + expect(testOp.currentScreenData.mainField4).to(beEmpty()); + expect(testOp.currentScreenData.templateTitle).to(beEmpty()); + expect(testOp.currentScreenData.alignment).to(beNil()); + expect(testOp.currentScreenData.mediaTrack).to(beEmpty()); + expect(testOp.currentScreenData.graphic).to(beNil()); + expect(testOp.currentScreenData.secondaryGraphic).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField1).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField2).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField3).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField4).to(beNil()); + }); + }); + + context(@"when sending four lines of text", ^{ + beforeEach(^{ + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.textField1 = field1String; + updatedState.textField2 = field2String; + updatedState.textField3 = field3String; + updatedState.textField4 = field4String; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:nil updateCompletionHandler:nil]; + [testOp start]; + + [testConnectionManager respondToLastRequestWithResponse:successShowResponse]; + }); + + it(@"should send four lines of text", ^{ + expect(testOp.isFinished).to(beTrue()); + expect(testOp.currentScreenData.mainField1).to(equal([NSString stringWithFormat:@"%@", field1String])); + expect(testOp.currentScreenData.mainField2).to(equal([NSString stringWithFormat:@"%@", field2String])); + expect(testOp.currentScreenData.mainField3).to(equal([NSString stringWithFormat:@"%@", field3String])); + expect(testOp.currentScreenData.mainField4).to(equal([NSString stringWithFormat:@"%@", field4String])); + expect(testOp.currentScreenData.templateTitle).to(beEmpty()); + expect(testOp.currentScreenData.alignment).to(beNil()); + expect(testOp.currentScreenData.mediaTrack).to(beEmpty()); + expect(testOp.currentScreenData.graphic).to(beNil()); + expect(testOp.currentScreenData.secondaryGraphic).to(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField1).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField2).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField3).toNot(beNil()); + expect(testOp.currentScreenData.metadataTags.mainField4).toNot(beNil()); + }); + }); + }); + + // should call the update handler when done + context(@"should call the update handler when done", ^{ + __block BOOL didCallHandler = NO; + beforeEach(^{ + windowCapability = [[SDLWindowCapability alloc] init]; + windowCapability.textFields = @[fieldLine1, fieldLine2, fieldLine3, fieldLine4]; + + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.textField1 = field1String; + updatedState.textField2 = field2String; + updatedState.textField3 = field3String; + updatedState.textField4 = field4String; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:nil updateCompletionHandler:^(NSError * _Nullable error) { + didCallHandler = YES; + }]; + [testOp start]; + + [testConnectionManager respondToLastRequestWithResponse:successShowResponse]; + }); + + it(@"should send the text and then call the update handler", ^{ + expect(didCallHandler).to(equal(YES)); + }); + }); + + context(@"should call the currentScreenDataUpdatedHandler when the screen data is updated", ^{ + __block SDLShow *updatedData = nil; + beforeEach(^{ + windowCapability = [[SDLWindowCapability alloc] init]; + windowCapability.textFields = @[fieldLine1, fieldLine2, fieldLine3, fieldLine4]; + + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.textField1 = field1String; + updatedState.textField2 = field2String; + updatedState.textField3 = field3String; + updatedState.textField4 = field4String; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:^(SDLShow * _Nonnull newScreenData) { + updatedData = newScreenData; + } updateCompletionHandler:nil]; + [testOp start]; + + [testConnectionManager respondToLastRequestWithResponse:successShowResponse]; + }); + + it(@"should send the text and then call the update handler", ^{ + expect(updatedData.mainField1).to(equal(field1String)); + expect(updatedData.mainField2).to(equal(field2String)); + expect(updatedData.mainField3).to(equal(field3String)); + expect(updatedData.mainField4).to(equal(field4String)); + }); + }); + }); + + // updating image fields + describe(@"updating image fields", ^{ + beforeEach(^{ + windowCapability = [[SDLWindowCapability alloc] init]; + windowCapability.textFields = @[fieldLine1, fieldLine2, fieldLine3, fieldLine4]; + windowCapability.imageFields = @[fieldGraphic, fieldSecondaryGraphic]; + }); + + // when the images are already available on the head unit + context(@"when the images are already available on the head unit", ^{ + beforeEach(^{ + OCMStub([mockFileManager hasUploadedFile:[OCMArg isNotNil]]).andReturn(YES); + }); + + // when only the primary graphic is supported + context(@"when only the primary graphic is supported", ^{ + beforeEach(^{ + windowCapability.imageFields = @[fieldGraphic]; + + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.textField1 = field1String; + updatedState.primaryGraphic = testArtwork; + updatedState.secondaryGraphic = testArtwork2; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:nil updateCompletionHandler:nil]; + [testOp start]; + }); + + it(@"should send a show and not upload any artworks", ^{ + expect(testConnectionManager.receivedRequests).to(haveCount(1)); + SDLShow *firstSentRequest = testConnectionManager.receivedRequests[0]; + expect(firstSentRequest.mainField1).to(equal(field1String)); + expect(firstSentRequest.mainField2).to(beEmpty()); + expect(firstSentRequest.graphic).toNot(beNil()); + expect(firstSentRequest.secondaryGraphic).to(beNil()); + }); + }); + + // when both image fields are supported + context(@"when both image fields are supported", ^{ + beforeEach(^{ + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.textField1 = field1String; + updatedState.primaryGraphic = testArtwork; + updatedState.secondaryGraphic = testArtwork2; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:nil updateCompletionHandler:nil]; + [testOp start]; + }); + + it(@"should send a show and not upload any artworks", ^{ + expect(testConnectionManager.receivedRequests).to(haveCount(1)); + SDLShow *firstSentRequest = testConnectionManager.receivedRequests[0]; + expect(firstSentRequest.mainField1).to(equal(field1String)); + expect(firstSentRequest.mainField2).to(beEmpty()); + expect(firstSentRequest.graphic).toNot(beNil()); + expect(firstSentRequest.secondaryGraphic).toNot(beNil()); + }); + }); + }); + + // when images are not on the head unit + context(@"when images are not on the head unit", ^{ + beforeEach(^{ + OCMStub([mockFileManager hasUploadedFile:[OCMArg isNotNil]]).andReturn(NO); + OCMStub([mockFileManager uploadArtworks:[OCMArg any] progressHandler:[OCMArg any] completionHandler:([OCMArg invokeBlockWithArgs:[NSNull null], [NSNull null], nil])]); + }); + + // when there is text to update as well + context(@"when there is text to update as well", ^{ + beforeEach(^{ + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.textField1 = field1String; + updatedState.primaryGraphic = testArtwork; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:nil updateCompletionHandler:nil]; + [testOp start]; + }); + + it(@"should send the text, then upload the images, then send the full show", ^{ + // First the text only show should be sent + expect(testConnectionManager.receivedRequests).to(haveCount(1)); + SDLShow *firstSentRequest = testConnectionManager.receivedRequests[0]; + expect(firstSentRequest.mainField1).to(equal(field1String)); + expect(firstSentRequest.graphic).to(beNil()); + [testConnectionManager respondToLastRequestWithResponse:successShowResponse]; + + // Then the images should be uploaded + OCMExpect([mockFileManager uploadArtworks:[OCMArg any] progressHandler:[OCMArg any] completionHandler:[OCMArg any]]); + + // Then the full show should be sent, this is currently not testable because the `mockFileManager hasUploadedFile` should change mid-call of `uploadArtworks` after the artwork is uploaded but before the final Show is sent in sdl_createImageOnlyShowWithPrimaryArtwork. +// expect(testConnectionManager.receivedRequests).to(haveCount(2)); +// SDLShow *secondSentRequest = testConnectionManager.receivedRequests[1]; +// expect(secondSentRequest.mainField1).to(beNil()); +// expect(secondSentRequest.graphic).toNot(beNil()); + }); + }); + + // when there is no text to update + context(@"when there is no text to update", ^{ + beforeEach(^{ + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.primaryGraphic = testArtwork; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:nil updateCompletionHandler:nil]; + [testOp start]; + }); + + it(@"should just upload the images, then send the full show", ^{ + // First the text only show should be sent + expect(testConnectionManager.receivedRequests).to(haveCount(1)); + SDLShow *firstSentRequest = testConnectionManager.receivedRequests[0]; + expect(firstSentRequest.mainField1).to(beEmpty()); + expect(firstSentRequest.graphic).to(beNil()); + [testConnectionManager respondToLastRequestWithResponse:successShowResponse]; + + // Then the images should be uploaded + OCMExpect([mockFileManager uploadArtworks:[OCMArg any] progressHandler:[OCMArg any] completionHandler:[OCMArg any]]); + + // Then the full show should be sent, this is currently not testable because the `mockFileManager hasUploadedFile` should change mid-call of `uploadArtworks` after the artwork is uploaded but before the final Show is sent in sdl_createImageOnlyShowWithPrimaryArtwork. +// expect(testConnectionManager.receivedRequests).to(haveCount(2)); +// SDLShow *secondSentRequest = testConnectionManager.receivedRequests[1]; +// expect(secondSentRequest.mainField1).to(beEmpty()); +// expect(secondSentRequest.graphic).toNot(beNil()); + }); + }); + + // when the image is a static icon + context(@"when the image is a static icon", ^{ + beforeEach(^{ + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.primaryGraphic = testStaticIcon; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:nil updateCompletionHandler:nil]; + [testOp start]; + }); + + it(@"should not upload the artwork", ^{ + // The full show should be sent immediately + expect(testConnectionManager.receivedRequests).to(haveCount(1)); + SDLShow *firstSentRequest = testConnectionManager.receivedRequests[0]; + expect(firstSentRequest.mainField1).to(beEmpty()); + expect(firstSentRequest.graphic).toNot(beNil()); + [testConnectionManager respondToLastRequestWithResponse:successShowResponse]; + + // Then the images should be uploaded + OCMReject([mockFileManager uploadArtworks:[OCMArg any] progressHandler:[OCMArg any] completionHandler:[OCMArg any]]); + }); + }); + }); + + // when an image fails to upload to the remote + describe(@"when an image fails to upload to the remote", ^{ + context(@"if the images for the primary and secondary graphics fail the upload process", ^{ + beforeEach(^{ + OCMStub([mockFileManager hasUploadedFile:[OCMArg isNotNil]]).andReturn(NO); + NSArray<NSString *> *testSuccessfulArtworks = @[]; + NSError *testError = [NSError errorWithDomain:@"errorDomain" + code:9 + userInfo:@{testArtwork.name:@"error 1", testArtwork2.name:@"error 2"} + ]; + OCMStub([mockFileManager uploadArtworks:[OCMArg isNotNil] completionHandler:([OCMArg invokeBlockWithArgs:testSuccessfulArtworks, testError, nil])]); + + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.primaryGraphic = testArtwork; + updatedState.secondaryGraphic = testArtwork2; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:nil updateCompletionHandler:nil]; + [testOp start]; + }); + + it(@"should skip sending an update", ^{ + // Just the empty text show + expect(testConnectionManager.receivedRequests).to(haveCount(1)); + + SDLShow *sentShow = testConnectionManager.receivedRequests[0]; + expect(sentShow.graphic).to(beNil()); + expect(sentShow.secondaryGraphic).to(beNil()); + }); + }); + + context(@"if only one of images for the primary and secondary graphics fails to upload", ^{ + it(@"should show the primary graphic even if the secondary graphic upload fails", ^{ + OCMStub([mockFileManager hasUploadedFile:testArtwork]).andReturn(YES); + OCMStub([mockFileManager hasUploadedFile:testArtwork2]).andReturn(NO); + NSArray<NSString *> *testSuccessfulArtworks = @[testArtwork.name]; + NSError *testError = [NSError errorWithDomain:@"errorDomain" code:9 userInfo:@{testArtwork2.name:@"error 2"}]; + OCMStub([mockFileManager uploadArtworks:[OCMArg isNotNil] progressHandler:[OCMArg isNotNil] completionHandler:([OCMArg invokeBlockWithArgs:testSuccessfulArtworks, testError, nil])]); + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.textField1 = field1String; + updatedState.primaryGraphic = testArtwork; + updatedState.secondaryGraphic = testArtwork2; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:nil updateCompletionHandler:nil]; + [testOp start]; + + expect(testConnectionManager.receivedRequests).to(haveCount(1)); + SDLShow *receivedShow = testConnectionManager.receivedRequests[0]; + expect(receivedShow.mainField1).to(equal(field1String)); + expect(receivedShow.graphic.value).to(beNil()); + expect(receivedShow.secondaryGraphic).to(beNil()); + + [testConnectionManager respondToLastRequestWithResponse:successShowResponse]; + expect(testConnectionManager.receivedRequests).to(haveCount(2)); + SDLShow *secondReceivedShow = testConnectionManager.receivedRequests[1]; + expect(secondReceivedShow.mainField1).to(beNil()); + expect(secondReceivedShow.graphic.value).to(equal(testArtwork.name)); + expect(secondReceivedShow.secondaryGraphic).to(beNil()); + }); + + it(@"Should show the secondary graphic even if the primary graphic upload fails", ^{ + OCMStub([mockFileManager hasUploadedFile:testArtwork]).andReturn(NO); + OCMStub([mockFileManager hasUploadedFile:testArtwork2]).andReturn(YES); + NSArray<NSString *> *testSuccessfulArtworks = @[testArtwork2.name]; + NSError *testError = [NSError errorWithDomain:@"errorDomain" code:9 userInfo:@{testArtwork.name:@"error 2"}]; + OCMStub([mockFileManager uploadArtworks:[OCMArg isNotNil] progressHandler:[OCMArg isNotNil] completionHandler:([OCMArg invokeBlockWithArgs:testSuccessfulArtworks, testError, nil])]); + updatedState = [[SDLTextAndGraphicState alloc] init]; + updatedState.textField1 = field1String; + updatedState.primaryGraphic = testArtwork; + updatedState.secondaryGraphic = testArtwork2; + + testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentDataShow newState:updatedState currentScreenDataUpdatedHandler:nil updateCompletionHandler:nil]; + [testOp start]; + + expect(testConnectionManager.receivedRequests).to(haveCount(1)); + SDLShow *receivedShow = testConnectionManager.receivedRequests[0]; + expect(receivedShow.mainField1).to(equal(field1String)); + expect(receivedShow.graphic).to(beNil()); + expect(receivedShow.secondaryGraphic).to(beNil()); + + [testConnectionManager respondToLastRequestWithResponse:successShowResponse]; + expect(testConnectionManager.receivedRequests).to(haveCount(2)); + SDLShow *secondReceivedShow = testConnectionManager.receivedRequests[1]; + expect(secondReceivedShow.mainField1).to(beNil()); + expect(secondReceivedShow.graphic).to(beNil()); + expect(secondReceivedShow.secondaryGraphic.value).to(equal(testArtwork2.name)); + }); + }); + }); + }); +}); + +QuickSpecEnd diff --git a/SmartDeviceLinkTests/SDLScreenManagerSpec.m b/SmartDeviceLinkTests/SDLScreenManagerSpec.m index b3f613256..da4f9f725 100644 --- a/SmartDeviceLinkTests/SDLScreenManagerSpec.m +++ b/SmartDeviceLinkTests/SDLScreenManagerSpec.m @@ -30,8 +30,7 @@ @property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager; @property (weak, nonatomic) SDLFileManager *fileManager; - -@property (strong, nonatomic, nullable) SDLShow *inProgressUpdate; +@property (strong, nonatomic) NSOperationQueue *transactionQueue; @property (copy, nonatomic, nullable) SDLHMILevel currentLevel; @end @@ -106,7 +105,7 @@ describe(@"screen manager", ^{ }); it(@"should have in progress updates", ^{ - expect(testScreenManager.textAndGraphicManager.inProgressUpdate).toNot(beNil()); + expect(testScreenManager.textAndGraphicManager.transactionQueue.operationCount).toNot(equal(0)); expect(testScreenManager.softButtonManager.transactionQueue.operationCount).to(equal(1)); expect(testScreenManager.textAndGraphicManager.batchUpdates).to(beFalse()); |