diff options
Diffstat (limited to 'SmartDeviceLinkTests/DevAPISpecs/SDLPreloadPresentChoicesOperationSpec.m')
-rw-r--r-- | SmartDeviceLinkTests/DevAPISpecs/SDLPreloadPresentChoicesOperationSpec.m | 1070 |
1 files changed, 1070 insertions, 0 deletions
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLPreloadPresentChoicesOperationSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLPreloadPresentChoicesOperationSpec.m new file mode 100644 index 000000000..45e4b7efc --- /dev/null +++ b/SmartDeviceLinkTests/DevAPISpecs/SDLPreloadPresentChoicesOperationSpec.m @@ -0,0 +1,1070 @@ +#import <Quick/Quick.h> +#import <Nimble/Nimble.h> +#import <OCMock/OCMock.h> + +#import <SmartDeviceLink/SmartDeviceLink.h> +#import "SDLError.h" +#import "SDLPreloadPresentChoicesOperation.h" + +#import "SDLGlobals.h" +#import "TestConnectionManager.h" + +@interface SDLPreloadPresentChoicesOperation() + +// Dependencies +@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager; +@property (weak, nonatomic) SDLFileManager *fileManager; +@property (strong, nonatomic) SDLWindowCapability *windowCapability; + +// Preload Dependencies +@property (strong, nonatomic) NSMutableOrderedSet<SDLChoiceCell *> *cellsToUpload; +@property (strong, nonatomic) NSString *displayName; +@property (assign, nonatomic, readwrite, getter=isVROptional) BOOL vrOptional; +@property (copy, nonatomic) SDLUploadChoicesCompletionHandler preloadCompletionHandler; + +// Present Dependencies +@property (strong, nonatomic) SDLChoiceSet *choiceSet; +@property (strong, nonatomic, nullable) SDLInteractionMode presentationMode; +@property (strong, nonatomic, nullable) SDLKeyboardProperties *originalKeyboardProperties; +@property (strong, nonatomic, nullable) SDLKeyboardProperties *customKeyboardProperties; +@property (weak, nonatomic, nullable) id<SDLKeyboardDelegate> keyboardDelegate; +@property (assign, nonatomic) UInt16 cancelId; + +// Internal operation properties +@property (strong, nonatomic) NSUUID *operationId; +@property (copy, nonatomic, nullable) NSError *internalError; + +// Mutable state +@property (strong, nonatomic) NSMutableSet<SDLChoiceCell *> *mutableLoadedCells; + +@end + +@interface SDLChoiceCell() + +@property (assign, nonatomic) UInt16 choiceId; +@property (assign, nonatomic) NSUInteger uniqueTextId; + +@end + +QuickSpecBegin(SDLPreloadPresentChoicesOperationSpec) + +describe(@"a preload choices operation", ^{ + __block TestConnectionManager *testConnectionManager = nil; + __block SDLFileManager *testFileManager = nil; + __block SDLPreloadPresentChoicesOperation *testOp = nil; + __block NSString *testDisplayName = @"SDL_GENERIC"; + __block SDLVersion *choiceSetUniquenessActiveVersion = [[SDLVersion alloc] initWithMajor:7 minor:1 patch:0]; + __block SDLVersion *choiceSetUniquenessInactiveVersion = [[SDLVersion alloc] initWithMajor:7 minor:0 patch:0]; + + __block SDLWindowCapability *enabledWindowCapability = nil; + __block SDLWindowCapability *disabledWindowCapability = nil; + __block SDLWindowCapability *primaryTextOnlyCapability = nil; + + __block NSSet<SDLChoiceCell *> *emptyLoadedCells = [NSSet set]; + __block NSArray<SDLChoiceCell *> *cellsWithArtwork = nil; + __block NSArray<SDLChoiceCell *> *cellsWithStaticIcon = nil; + __block NSArray<SDLChoiceCell *> *cellsWithoutArtwork = nil; + + __block NSData *cellArtData = [@"testart" dataUsingEncoding:NSUTF8StringEncoding]; + __block NSData *cellArtData2 = [@"testart2" dataUsingEncoding:NSUTF8StringEncoding]; + __block NSString *art1Name = @"Art1Name"; + __block NSString *art2Name = @"Art2Name"; + SDLArtwork *cell1Art = [[SDLArtwork alloc] initWithData:cellArtData name:art1Name fileExtension:@"png" persistent:NO]; + SDLArtwork *cell1Art2 = [[SDLArtwork alloc] initWithData:cellArtData2 name:art1Name fileExtension:@"png" persistent:NO]; + SDLArtwork *cell2Art = [[SDLArtwork alloc] initWithData:cellArtData name:art2Name fileExtension:@"png" persistent:NO]; + + __block SDLChoiceCell *cellBasic = nil; + __block SDLChoiceCell *cellBasicDuplicate = nil; + __block SDLChoiceCell *cellWithVR = nil; + __block SDLChoiceCell *cellWithAllText = nil; + + __block SDLCreateInteractionChoiceSetResponse *testBadResponse = nil; + __block SDLCreateInteractionChoiceSetResponse *testGoodResponse = nil; + + __block NSSet<SDLChoiceCell *> *resultChoices = nil; + __block NSError *resultPreloadError = nil; + + __block SDLChoiceSet *testChoiceSet = nil; + __block int testCancelID = 98; + __block SDLInteractionMode testInteractionMode = SDLInteractionModeBoth; + __block SDLKeyboardProperties *testKeyboardProperties = nil; + __block id<SDLKeyboardDelegate> testKeyboardDelegate = nil; + __block id<SDLChoiceSetDelegate> testChoiceDelegate = nil; + + beforeEach(^{ + resultPreloadError = nil; + resultChoices = nil; + + testConnectionManager = [[TestConnectionManager alloc] init]; + testFileManager = OCMClassMock([SDLFileManager class]); + OCMStub([testFileManager uploadArtworks:[OCMArg any] completionHandler:[OCMArg invokeBlock]]); + OCMStub([testFileManager fileNeedsUpload:[OCMArg isNotNil]]).andReturn(YES); + + enabledWindowCapability = [[SDLWindowCapability alloc] init]; + enabledWindowCapability.textFields = @[ + [[SDLTextField alloc] initWithName:SDLTextFieldNameMenuName characterSet:SDLCharacterSetUtf8 width:500 rows:1], + [[SDLTextField alloc] initWithName:SDLTextFieldNameSecondaryText characterSet:SDLCharacterSetUtf8 width:500 rows:1], + [[SDLTextField alloc] initWithName:SDLTextFieldNameTertiaryText characterSet:SDLCharacterSetUtf8 width:500 rows:1] + ]; + enabledWindowCapability.imageFields = @[ + [[SDLImageField alloc] initWithName:SDLImageFieldNameChoiceImage imageTypeSupported:@[SDLFileTypePNG] imageResolution:nil], + [[SDLImageField alloc] initWithName:SDLImageFieldNameChoiceSecondaryImage imageTypeSupported:@[SDLFileTypePNG] imageResolution:nil] + ]; + disabledWindowCapability = [[SDLWindowCapability alloc] init]; + disabledWindowCapability.textFields = @[]; + primaryTextOnlyCapability = [[SDLWindowCapability alloc] init]; + primaryTextOnlyCapability.textFields = @[ + [[SDLTextField alloc] initWithName:SDLTextFieldNameMenuName characterSet:SDLCharacterSetUtf8 width:500 rows:1], + ]; + + SDLChoiceCell *cell1WithArt = [[SDLChoiceCell alloc] initWithText:@"Cell1" artwork:cell1Art voiceCommands:nil]; + cell1WithArt.choiceId = 1; + SDLChoiceCell *cell2WithArtAndSecondary = [[SDLChoiceCell alloc] initWithText:@"Cell2" secondaryText:nil tertiaryText:nil voiceCommands:nil artwork:cell2Art secondaryArtwork:cell2Art]; + cell2WithArtAndSecondary.choiceId = 2; + + SDLArtwork *staticIconArt = [SDLArtwork artworkWithStaticIcon:SDLStaticIconNameDate]; + SDLChoiceCell *cellWithStaticIcon = [[SDLChoiceCell alloc] initWithText:@"Static Icon" secondaryText:nil tertiaryText:nil voiceCommands:nil artwork:staticIconArt secondaryArtwork:nil]; + cellWithStaticIcon.choiceId = 3; + + cellsWithArtwork = @[cell1WithArt, cell2WithArtAndSecondary]; + cellsWithStaticIcon = @[cellWithStaticIcon]; + + cellBasic = [[SDLChoiceCell alloc] initWithText:@"Cell1" artwork:nil voiceCommands:nil]; + cellBasic.choiceId = 4; + cellBasicDuplicate = [[SDLChoiceCell alloc] initWithText:@"Cell1" artwork:nil voiceCommands:nil]; + cellBasicDuplicate.choiceId = 5; + cellWithVR = [[SDLChoiceCell alloc] initWithText:@"Cell2" secondaryText:nil tertiaryText:nil voiceCommands:@[@"Cell2"] artwork:nil secondaryArtwork:nil]; + cellWithVR.choiceId = 6; + cellWithAllText = [[SDLChoiceCell alloc] initWithText:@"Cell2" secondaryText:@"Cell2" tertiaryText:@"Cell2" voiceCommands:nil artwork:nil secondaryArtwork:nil]; + cellWithAllText.choiceId = 7; + cellsWithoutArtwork = @[cellBasic, cellWithVR, cellWithAllText]; + + testBadResponse = [[SDLCreateInteractionChoiceSetResponse alloc] init]; + testBadResponse.success = @NO; + testBadResponse.resultCode = SDLResultRejected; + + testGoodResponse = [[SDLCreateInteractionChoiceSetResponse alloc] init]; + testGoodResponse.success = @YES; + testGoodResponse.resultCode = SDLResultSuccess; + + testChoiceDelegate = OCMProtocolMock(@protocol(SDLChoiceSetDelegate)); + testKeyboardDelegate = OCMProtocolMock(@protocol(SDLKeyboardDelegate)); + OCMStub([testKeyboardDelegate customKeyboardConfiguration]).andReturn(nil); + testKeyboardProperties = [[SDLKeyboardProperties alloc] initWithLanguage:SDLLanguageArSa keyboardLayout:SDLKeyboardLayoutAZERTY keypressMode:SDLKeypressModeResendCurrentEntry limitedCharacterList:nil autoCompleteList:nil maskInputCharacters:nil customKeys:nil]; + testChoiceSet = [[SDLChoiceSet alloc] initWithTitle:@"Choice Set" delegate:testChoiceDelegate layout:SDLChoiceSetLayoutTiles timeout:8.0 initialPromptString:@"Initial Prompt" timeoutPromptString:@"Timeout Prompt" helpPromptString:@"Help Prompt" vrHelpList:nil choices:cellsWithoutArtwork]; + }); + + it(@"should have a priority of 'normal'", ^{ + testOp = [[SDLPreloadPresentChoicesOperation alloc] init]; + + expect(@(testOp.queuePriority)).to(equal(@(NSOperationQueuePriorityNormal))); + }); + + context(@"running a preload only operation", ^{ + describe(@"updating cells for uniqueness", ^{ + beforeEach(^{ + testOp = [[SDLPreloadPresentChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:enabledWindowCapability isVROptional:YES cellsToPreload:@[cellWithVR] loadedCells:[NSSet setWithArray:@[cellWithAllText]] preloadCompletionHandler:^(NSSet<SDLChoiceCell *> * _Nonnull updatedLoadedCells, NSError * _Nullable error) {}]; + }); + + context(@"when some choices are already uploaded with duplicate titles version >= 7.1.0", ^{ + beforeEach(^{ + [SDLGlobals sharedGlobals].rpcVersion = choiceSetUniquenessActiveVersion; + }); + + context(@"if there are duplicate cells once you strip unused cell properties", ^{ + beforeEach(^{ + SDLChoiceCell *loadedCell1 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Loaded 1" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil]; + SDLChoiceCell *loadedCell2 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Loaded 2" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil]; + loadedCell2.uniqueTextId = 3; + SDLChoiceCell *loadedCell3 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Loaded 3" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil]; + loadedCell3.uniqueTextId = 5; + + SDLChoiceCell *cellToUpload1 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Unique 1" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil]; + SDLChoiceCell *cellToUpload2 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Unique 2" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil]; + SDLChoiceCell *cellToUpload3 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Unique 3" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil]; + SDLChoiceCell *cellToUpload4 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Unique 4" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil]; + + testOp.windowCapability = primaryTextOnlyCapability; + testOp.loadedCells = [NSSet setWithArray:@[loadedCell1, loadedCell2, loadedCell3]]; + testOp.cellsToUpload = [NSMutableOrderedSet orderedSetWithArray:@[cellToUpload1, cellToUpload2, cellToUpload3, cellToUpload4]]; + [testOp start]; + }); + + it(@"should properly assign unique text", ^{ + expect(testOp.cellsToUpload[0].uniqueText).to(equal(@"Cell 2 (2)")); + expect(testOp.cellsToUpload[1].uniqueText).to(equal(@"Cell 2 (4)")); + expect(testOp.cellsToUpload[2].uniqueText).to(equal(@"Cell 2 (6)")); + expect(testOp.cellsToUpload[3].uniqueText).to(equal(@"Cell 2 (7)")); + expect(testOp.cellsToUpload[0].secondaryText).toNot(beNil()); + }); + }); + + context(@"if all cell properties are used", ^{ + beforeEach(^{ + testOp.windowCapability = enabledWindowCapability; + [testOp start]; + }); + + it(@"should not update the choiceCells' unique title", ^{ + expect(testOp.cellsToUpload[0].uniqueText).to(equal("Cell2")); + expect(testOp.cellsToUpload.count).to(equal(1)); + }); + }); + }); + + context(@"when some choices are already uploaded with duplicate titles version <= 7.1.0", ^{ + beforeEach(^{ + [SDLGlobals sharedGlobals].rpcVersion = choiceSetUniquenessInactiveVersion; + }); + + context(@"if all cell properties are used", ^{ + beforeEach(^{ + testOp.windowCapability = enabledWindowCapability; + [testOp start]; + }); + + it(@"should update the choiceCells' unique title", ^{ + expect(testOp.cellsToUpload[0].uniqueText).to(equal("Cell2 (2)")); + expect(testOp.cellsToUpload.count).to(equal(1)); + }); + }); + }); + }); + + context(@"with artworks", ^{ + context(@"only primary text allowed", ^{ + it(@"should skip loading artworks to preloading cells", ^{ + testOp = [[SDLPreloadPresentChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:primaryTextOnlyCapability isVROptional:YES cellsToPreload:cellsWithArtwork loadedCells:emptyLoadedCells preloadCompletionHandler:^(NSSet<SDLChoiceCell *> * _Nonnull updatedLoadedCells, NSError * _Nullable error) { + resultPreloadError = error; + resultChoices = updatedLoadedCells; + }]; + [testOp start]; + + for (SDLRPCRequest *request in testConnectionManager.receivedRequests) { + expect(request).toNot(beAnInstanceOf(SDLPutFile.class)); + } + expect(testConnectionManager.receivedRequests).to(haveCount(2)); + }); + }); + + context(@"all text and image display capabilities", ^{ + beforeEach(^{ + testOp = [[SDLPreloadPresentChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:enabledWindowCapability isVROptional:YES cellsToPreload:cellsWithArtwork loadedCells:emptyLoadedCells preloadCompletionHandler:^(NSSet<SDLChoiceCell *> * _Nonnull updatedLoadedCells, NSError * _Nullable error) { + resultPreloadError = error; + resultChoices = updatedLoadedCells; + }]; + }); + + context(@"when artworks are already on the system", ^{ + beforeEach(^{ + OCMStub([testFileManager hasUploadedFile:[OCMArg isNotNil]]).andReturn(YES); + }); + + it(@"should not upload artworks", ^{ + OCMReject([testFileManager uploadArtworks:[OCMArg checkWithBlock:^BOOL(id obj) { + NSArray<SDLArtwork *> *artworks = (NSArray<SDLArtwork *> *)obj; + return (artworks.count == 2); + }] completionHandler:[OCMArg any]]); + + [testOp start]; + + OCMVerifyAll(testFileManager); + }); + + it(@"should properly overwrite artwork", ^{ + cell1Art2.overwrite = YES; + SDLChoiceCell *cellOverwriteArt = [[SDLChoiceCell alloc] initWithText:@"Cell1" artwork:cell1Art2 voiceCommands:nil]; + + testOp.cellsToUpload = [NSMutableOrderedSet orderedSetWithArray:@[cellOverwriteArt]]; + [testOp start]; + + OCMVerify([testFileManager uploadArtworks:[OCMArg isNotNil] completionHandler:[OCMArg any]]); + }); + }); + + context(@"when artworks are static icons", ^{ + beforeEach(^{ + testOp.cellsToUpload = [NSMutableOrderedSet orderedSetWithArray:cellsWithStaticIcon]; + [testOp start]; + }); + + it(@"should skip uploading artwork", ^{ + OCMReject([testFileManager uploadArtwork:[OCMArg any] completionHandler:[OCMArg any]]); + }); + }); + + fcontext(@"when artworks are not already on the system", ^{ + beforeEach(^{ + OCMStub([testFileManager hasUploadedFile:[OCMArg isNotNil]]).andReturn(NO); + }); + + context(@"when there's more than one of the same artwork", ^{ + beforeEach(^{ + testOp.cellsToUpload = [NSMutableOrderedSet orderedSetWithArray:@[ + [[SDLChoiceCell alloc] initWithText:@"Cell 1" artwork:cell1Art voiceCommands:nil], + [[SDLChoiceCell alloc] initWithText:@"Cell 2" artwork:cell1Art voiceCommands:nil], + [[SDLChoiceCell alloc] initWithText:@"Cell 3" artwork:cell1Art voiceCommands:nil], + ]]; + testOp.loadedCells = [NSSet set]; + }); + + it(@"should only attempt to upload one of each art", ^{ + [testOp start]; + OCMVerify([testFileManager uploadArtworks:[OCMArg checkWithBlock:^BOOL(id obj) { + NSArray<SDLArtwork *> *artworks = (NSArray<SDLArtwork *> *)obj; + return (artworks.count == 1); + }] completionHandler:[OCMArg any]]); + }); + }); + + context(@"when uploading unique art", ^{ + beforeEach(^{ + testOp.cellsToUpload = [NSMutableOrderedSet orderedSetWithArray:cellsWithArtwork]; + testOp.loadedCells = [NSSet set]; + }); + + it(@"should upload artworks", ^{ + [testOp start]; + OCMVerify([testFileManager uploadArtworks:[OCMArg checkWithBlock:^BOOL(id obj) { + NSArray<SDLArtwork *> *artworks = (NSArray<SDLArtwork *> *)obj; + return (artworks.count == 2); + }] completionHandler:[OCMArg any]]); + }); + }); + }); + }); + }); + + context(@"without artworks", ^{ + describe(@"only main text capabilities", ^{ + it(@"should skip loading artworks to preloading cells", ^{ + testOp = [[SDLPreloadPresentChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:primaryTextOnlyCapability isVROptional:YES cellsToPreload:cellsWithArtwork loadedCells:emptyLoadedCells preloadCompletionHandler:^(NSSet<SDLChoiceCell *> * _Nonnull updatedLoadedCells, NSError * _Nullable error) { + resultPreloadError = error; + resultChoices = updatedLoadedCells; + }]; + [testOp start]; + + for (SDLRPCRequest *request in testConnectionManager.receivedRequests) { + expect(request).toNot(beAnInstanceOf(SDLPutFile.class)); + } + expect(testConnectionManager.receivedRequests).to(haveCount(2)); + }); + }); + + describe(@"assembling choices", ^{ + beforeEach(^{ + testOp = [[SDLPreloadPresentChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:enabledWindowCapability isVROptional:YES cellsToPreload:cellsWithoutArtwork loadedCells:emptyLoadedCells preloadCompletionHandler:^(NSSet<SDLChoiceCell *> * _Nonnull updatedLoadedCells, NSError * _Nullable error) { + resultChoices = updatedLoadedCells; + resultPreloadError = error; + }]; + }); + + it(@"should skip preloading the choices if all choice items have already been uploaded", ^{ + testOp.loadedCells = [NSSet setWithArray:cellsWithoutArtwork]; + [testOp start]; + + expect(testConnectionManager.receivedRequests).to(haveCount(0)); + }); + + it(@"should not send any requests if all items are disabled", ^{ + testOp.windowCapability = disabledWindowCapability; + [testOp start]; + + expect(testConnectionManager.receivedRequests).to(haveCount(0)); + }); + + it(@"should be correct with only primary text", ^{ + testOp.windowCapability = primaryTextOnlyCapability; + [testOp start]; + + NSArray<SDLCreateInteractionChoiceSet *> *receivedRequests = (NSArray<SDLCreateInteractionChoiceSet *> *)testConnectionManager.receivedRequests; + + expect(receivedRequests).to(haveCount(3)); + + SDLChoice *representativeItem = receivedRequests.lastObject.choiceSet.firstObject; + expect(representativeItem.menuName).toNot(beNil()); + expect(representativeItem.secondaryText).to(beNil()); + expect(representativeItem.tertiaryText).to(beNil()); + }); + + it(@"should be correct with all text", ^{ + SDLWindowCapability *allTextCapability = [enabledWindowCapability copy]; + allTextCapability.imageFields = @[]; + testOp.windowCapability = allTextCapability; + [testOp start]; + + NSArray<SDLCreateInteractionChoiceSet *> *receivedRequests = (NSArray<SDLCreateInteractionChoiceSet *> *)testConnectionManager.receivedRequests; + + expect(receivedRequests).to(haveCount(3)); + + SDLChoice *representativeItem = receivedRequests.lastObject.choiceSet.firstObject; + expect(representativeItem.menuName).toNot(beNil()); + expect(representativeItem.secondaryText).toNot(beNil()); + expect(representativeItem.tertiaryText).toNot(beNil()); + }); + + it(@"should be correct with VR required", ^{ + testOp.vrOptional = NO; + [testOp start]; + + NSArray<SDLCreateInteractionChoiceSet *> *receivedRequests = (NSArray<SDLCreateInteractionChoiceSet *> *)testConnectionManager.receivedRequests; + + expect(receivedRequests).to(haveCount(3)); + + // The last item has no VR + SDLChoice *representativeItem = receivedRequests.lastObject.choiceSet.firstObject; + expect(representativeItem.vrCommands).toNot(beNil()); + }); + + it(@"should be correct with VR Optional", ^{ + testOp.vrOptional = YES; + [testOp start]; + + NSArray<SDLCreateInteractionChoiceSet *> *receivedRequests = (NSArray<SDLCreateInteractionChoiceSet *> *)testConnectionManager.receivedRequests; + + expect(receivedRequests).to(haveCount(3)); + + // The middle item is the one with VR + SDLChoice *representativeItem = receivedRequests.lastObject.choiceSet.firstObject; + expect(representativeItem.vrCommands).to(beNil()); + }); + }); + }); + + describe(@"the module's response to choice uploads", ^{ + context(@"when a bad response comes back", ^{ + beforeEach(^{ + testOp = [[SDLPreloadPresentChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:primaryTextOnlyCapability isVROptional:YES cellsToPreload:cellsWithoutArtwork loadedCells:emptyLoadedCells preloadCompletionHandler:^(NSSet<SDLChoiceCell *> * _Nonnull updatedLoadedCells, NSError * _Nullable error) { + resultChoices = updatedLoadedCells; + resultPreloadError = error; + }]; + }); + + it(@"should not add the item to the list of loaded cells", ^{ + [testOp start]; + + NSArray<SDLCreateInteractionChoiceSet *> *receivedRequests = (NSArray<SDLCreateInteractionChoiceSet *> *)testConnectionManager.receivedRequests; + + expect(receivedRequests).to(haveCount(3)); + expect(receivedRequests[0].choiceSet[0].menuName).to(equal(cellsWithoutArtwork[0].uniqueText)); + expect(receivedRequests[1].choiceSet[0].menuName).to(equal(cellsWithoutArtwork[1].uniqueText)); + expect(receivedRequests[2].choiceSet[0].menuName).to(equal(cellsWithoutArtwork[2].uniqueText)); + + [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:0 error:nil]; + [testConnectionManager respondToRequestWithResponse:testBadResponse requestNumber:1 error:[NSError errorWithDomain:SDLErrorDomainChoiceSetManager code:SDLChoiceSetManagerErrorUploadFailed userInfo:nil]]; + [testConnectionManager respondToLastMultipleRequestsWithSuccess:NO]; + + expect(testOp.loadedCells).to(haveCount(1)); + expect(testOp.loadedCells).to(contain(cellsWithoutArtwork[0])); + expect(testOp.loadedCells).toNot(contain(cellsWithoutArtwork[1])); + expect(testOp.error).toNot(beNil()); + expect(resultChoices).toNot(beNil()); + expect(resultPreloadError).toNot(beNil()); + }); + }); + + context(@"when only good responses comes back", ^{ + beforeEach(^{ + testOp = [[SDLPreloadPresentChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:primaryTextOnlyCapability isVROptional:YES cellsToPreload:cellsWithoutArtwork loadedCells:emptyLoadedCells preloadCompletionHandler:^(NSSet<SDLChoiceCell *> * _Nonnull updatedLoadedCells, NSError * _Nullable error) { + resultChoices = updatedLoadedCells; + resultPreloadError = error; + }]; + }); + + it(@"should add all the items to the list of loaded cells", ^{ + [testOp start]; + + NSArray<SDLCreateInteractionChoiceSet *> *receivedRequests = (NSArray<SDLCreateInteractionChoiceSet *> *)testConnectionManager.receivedRequests; + + expect(receivedRequests).to(haveCount(3)); + expect(receivedRequests[0].choiceSet[0].menuName).to(equal(cellsWithoutArtwork[0].uniqueText)); + expect(receivedRequests[1].choiceSet[0].menuName).to(equal(cellsWithoutArtwork[1].uniqueText)); + expect(receivedRequests[2].choiceSet[0].menuName).to(equal(cellsWithoutArtwork[2].uniqueText)); + + [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:0 error:nil]; + [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:1 error:nil]; + [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:2 error:nil]; + [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + + expect(testOp.loadedCells).to(haveCount(3)); + expect(testOp.loadedCells).to(contain(cellsWithoutArtwork[0])); + expect(testOp.loadedCells).to(contain(cellsWithoutArtwork[1])); + expect(testOp.loadedCells).to(contain(cellsWithoutArtwork[2])); + expect(resultPreloadError).to(beNil()); + expect(resultChoices).to(haveCount(3)); + }); + }); + }); + }); + + context(@"running a preload and present operation", ^{ + beforeEach(^{ + testOp = [[SDLPreloadPresentChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager choiceSet:testChoiceSet mode:testInteractionMode keyboardProperties:testKeyboardProperties keyboardDelegate:testKeyboardDelegate cancelID:testCancelID displayName:testDisplayName windowCapability:enabledWindowCapability isVROptional:YES loadedCells:emptyLoadedCells preloadCompletionHandler:^(NSSet<SDLChoiceCell *> * _Nonnull updatedLoadedCells, NSError * _Nullable error) { + resultChoices = updatedLoadedCells; + resultPreloadError = error; + }]; + }); + + describe(@"updating cells for uniqueness", ^{ + beforeEach(^{ + testOp = [[SDLPreloadPresentChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager choiceSet:[[SDLChoiceSet alloc] initWithTitle:@"Test Choice Set" delegate:testChoiceDelegate choices:@[cellWithVR]] mode:testInteractionMode keyboardProperties:testKeyboardProperties keyboardDelegate:testKeyboardDelegate cancelID:testCancelID displayName:testDisplayName windowCapability:enabledWindowCapability isVROptional:YES loadedCells:[NSSet setWithArray:@[cellWithAllText]] preloadCompletionHandler:^(NSSet<SDLChoiceCell *> * _Nonnull updatedLoadedCells, NSError * _Nullable error) { + resultChoices = updatedLoadedCells; + resultPreloadError = error; + }]; + }); + + context(@"when some choices are already uploaded with duplicate titles version >= 7.1.0", ^{ + beforeEach(^{ + [SDLGlobals sharedGlobals].rpcVersion = choiceSetUniquenessActiveVersion; + }); + + context(@"if there are duplicate cells once you strip unused cell properties", ^{ + beforeEach(^{ + SDLChoiceCell *loadedCell1 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Loaded 1" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil]; + SDLChoiceCell *loadedCell2 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Loaded 2" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil]; + loadedCell2.uniqueTextId = 3; + SDLChoiceCell *loadedCell3 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Loaded 3" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil]; + loadedCell3.uniqueTextId = 5; + + SDLChoiceCell *cellToUpload1 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Unique 1" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil]; + SDLChoiceCell *cellToUpload2 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Unique 2" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil]; + SDLChoiceCell *cellToUpload3 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Unique 3" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil]; + SDLChoiceCell *cellToUpload4 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Unique 4" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil]; + + testOp.windowCapability = primaryTextOnlyCapability; + testOp.loadedCells = [NSSet setWithArray:@[loadedCell1, loadedCell2, loadedCell3]]; + testOp.choiceSet.choices = @[cellToUpload1, cellToUpload2, cellToUpload3, cellToUpload4]; + testOp.cellsToUpload = [[NSMutableOrderedSet alloc] initWithArray:@[cellToUpload1, cellToUpload2, cellToUpload3, cellToUpload4]]; + [testOp start]; + }); + + it(@"should properly assign unique text", ^{ + expect(testOp.cellsToUpload[0].uniqueText).to(equal(@"Cell 2 (2)")); + expect(testOp.cellsToUpload[1].uniqueText).to(equal(@"Cell 2 (4)")); + expect(testOp.cellsToUpload[2].uniqueText).to(equal(@"Cell 2 (6)")); + expect(testOp.cellsToUpload[3].uniqueText).to(equal(@"Cell 2 (7)")); + expect(testOp.cellsToUpload[0].secondaryText).toNot(beNil()); + expect(testOp.choiceSet.choices[0].uniqueText).to(equal(@"Cell 2 (2)")); + }); + }); + + context(@"if all cell properties are used", ^{ + beforeEach(^{ + testOp.windowCapability = enabledWindowCapability; + [testOp start]; + }); + + it(@"should not update the choiceCells' unique title", ^{ + expect(testOp.cellsToUpload[0].uniqueText).to(equal(@"Cell2")); + expect(testOp.cellsToUpload.count).to(equal(1)); + expect(testOp.choiceSet.choices[0].uniqueText).to(equal(@"Cell2")); + }); + }); + }); + + context(@"when some choices are already uploaded with duplicate titles version <= 7.1.0", ^{ + beforeEach(^{ + [SDLGlobals sharedGlobals].rpcVersion = choiceSetUniquenessInactiveVersion; + }); + + context(@"if all cell properties are used", ^{ + beforeEach(^{ + testOp.windowCapability = enabledWindowCapability; + [testOp start]; + }); + + it(@"should update the choiceCells' unique title", ^{ + expect(testOp.cellsToUpload[0].uniqueText).to(equal("Cell2 (2)")); + expect(testOp.cellsToUpload.count).to(equal(1)); + expect(testOp.choiceSet.choices[0].uniqueText).to(equal(@"Cell2 (2)")); + }); + }); + }); + }); + + describe(@"running a non-searchable choice set operation", ^{ + beforeEach(^{ + testOp.keyboardDelegate = nil; + [testOp start]; + + // Move us past the preload + [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:0 error:nil]; + [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:1 error:nil]; + [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:2 error:nil]; + [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + }); + + it(@"should not update global keyboard properties", ^{ + for (SDLRPCRequest *req in testConnectionManager.receivedRequests) { + expect(req).toNot(beAnInstanceOf([SDLSetGlobalProperties class])); + } + }); + + it(@"should send the perform interaction", ^{ + SDLPerformInteraction *request = testConnectionManager.receivedRequests.lastObject; + expect(request).to(beAnInstanceOf([SDLPerformInteraction class])); + + expect(request.initialText).to(equal(testChoiceSet.title)); + expect(request.initialPrompt).to(equal(testChoiceSet.initialPrompt)); + expect(request.interactionMode).to(equal(testInteractionMode)); + expect(request.interactionLayout).to(equal(SDLLayoutModeIconOnly)); + expect(request.timeoutPrompt).to(equal(testChoiceSet.timeoutPrompt)); + expect(request.helpPrompt).to(equal(testChoiceSet.helpPrompt)); + expect(request.timeout).to(equal(testChoiceSet.timeout * 1000)); + expect(request.vrHelp).to(beNil()); + expect(request.interactionChoiceSetIDList).to(equal(@[@(cellsWithoutArtwork[0].choiceId), @(cellsWithoutArtwork[1].choiceId), @(cellsWithoutArtwork[2].choiceId)])); + expect(request.cancelID).to(equal(testCancelID)); + }); + + describe(@"after a perform interaction response", ^{ + __block UInt16 responseChoiceId = UINT16_MAX; + __block SDLTriggerSource responseTriggerSource = SDLTriggerSourceMenu; + + beforeEach(^{ + SDLPerformInteractionResponse *response = [[SDLPerformInteractionResponse alloc] init]; + response.success = @YES; + response.choiceID = @(responseChoiceId); + response.triggerSource = responseTriggerSource; + + [testConnectionManager respondToLastRequestWithResponse:response]; + }); + + it(@"should not reset the keyboard properties and should be finished", ^{ + expect(testConnectionManager.receivedRequests.lastObject).toNot(beAnInstanceOf([SDLSetGlobalProperties class])); + expect(testOp.isFinished).to(beTrue()); + }); + }); + }); + + describe(@"running a searchable choice set operation", ^{ + beforeEach(^{ + [testOp start]; + + // Move us past the preload + [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:0 error:nil]; + [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:1 error:nil]; + [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:2 error:nil]; + [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + }); + + it(@"should ask for custom properties", ^{ + OCMVerify([testKeyboardDelegate customKeyboardConfiguration]); + + expect(testConnectionManager.receivedRequests.lastObject).to(beAnInstanceOf([SDLSetGlobalProperties class])); + }); + + describe(@"presenting the keyboard", ^{ + beforeEach(^{ + SDLSetGlobalPropertiesResponse *response = [[SDLSetGlobalPropertiesResponse alloc] init]; + response.success = @YES; + [testConnectionManager respondToLastRequestWithResponse:response]; + }); + + it(@"should send the perform interaction", ^{ + expect(testConnectionManager.receivedRequests.lastObject).to(beAnInstanceOf([SDLPerformInteraction class])); + SDLPerformInteraction *request = testConnectionManager.receivedRequests.lastObject; + expect(request.initialText).to(equal(testChoiceSet.title)); + expect(request.initialPrompt).to(equal(testChoiceSet.initialPrompt)); + expect(request.interactionMode).to(equal(testInteractionMode)); + expect(request.interactionLayout).to(equal(SDLLayoutModeIconWithSearch)); + expect(request.timeoutPrompt).to(equal(testChoiceSet.timeoutPrompt)); + expect(request.helpPrompt).to(equal(testChoiceSet.helpPrompt)); + expect(request.timeout).to(equal(testChoiceSet.timeout * 1000)); + expect(request.vrHelp).to(beNil()); + expect(request.interactionChoiceSetIDList).to(equal(@[@(testChoiceSet.choices[0].choiceId), @(testChoiceSet.choices[1].choiceId), @(testChoiceSet.choices[2].choiceId)])); + expect(request.cancelID).to(equal(testCancelID)); + }); + + it(@"should respond to submitted notifications", ^{ + NSString *inputData = @"Test"; + SDLRPCNotificationNotification *notification = nil; + + // Submit notification + SDLOnKeyboardInput *input = [[SDLOnKeyboardInput alloc] init]; + input.event = SDLKeyboardEventSubmitted; + input.data = inputData; + notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveKeyboardInputNotification object:nil rpcNotification:input]; + + [[NSNotificationCenter defaultCenter] postNotification:notification]; + + OCMVerify([testKeyboardDelegate keyboardDidSendEvent:[OCMArg checkWithBlock:^BOOL(id obj) { + return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventSubmitted]; + }] text:[OCMArg checkWithBlock:^BOOL(id obj) { + return [(NSString *)obj isEqualToString:inputData]; + }]]); + + OCMVerify([testKeyboardDelegate userDidSubmitInput:[OCMArg checkWithBlock:^BOOL(id obj) { + return [(NSString *)obj isEqualToString:inputData]; + }] withEvent:[OCMArg checkWithBlock:^BOOL(id obj) { + return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventSubmitted]; + }]]); + }); + + it(@"should respond to voice request notifications", ^{ + SDLRPCNotificationNotification *notification = nil; + + // Submit notification + SDLOnKeyboardInput *input = [[SDLOnKeyboardInput alloc] init]; + input.event = SDLKeyboardEventVoice; + notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveKeyboardInputNotification object:nil rpcNotification:input]; + + [[NSNotificationCenter defaultCenter] postNotification:notification]; + + OCMVerify([testKeyboardDelegate keyboardDidSendEvent:[OCMArg checkWithBlock:^BOOL(id obj) { + return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventVoice]; + }] text:[OCMArg isNil]]); + + OCMVerify([testKeyboardDelegate userDidSubmitInput:[OCMArg isNil] withEvent:[OCMArg checkWithBlock:^BOOL(id obj) { + return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventVoice]; + }]]); + }); + + it(@"should respond to abort notifications", ^{ + SDLRPCNotificationNotification *notification = nil; + + // Submit notification + SDLOnKeyboardInput *input = [[SDLOnKeyboardInput alloc] init]; + input.event = SDLKeyboardEventAborted; + notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveKeyboardInputNotification object:nil rpcNotification:input]; + + [[NSNotificationCenter defaultCenter] postNotification:notification]; + + OCMVerify([testKeyboardDelegate keyboardDidSendEvent:[OCMArg checkWithBlock:^BOOL(id obj) { + return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventAborted]; + }] text:[OCMArg isNil]]); + + OCMVerify([testKeyboardDelegate keyboardDidAbortWithReason:[OCMArg checkWithBlock:^BOOL(id obj) { + return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventAborted]; + }]]); + }); + + it(@"should respond to enabled keyboard event", ^{ + SDLRPCNotificationNotification *notification = nil; + + // Submit notification + SDLOnKeyboardInput *input = [[SDLOnKeyboardInput alloc] init]; + input.event = SDLKeyboardEventInputKeyMaskEnabled; + notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveKeyboardInputNotification object:nil rpcNotification:input]; + + [[NSNotificationCenter defaultCenter] postNotification:notification]; + + OCMVerify([testKeyboardDelegate keyboardDidSendEvent:[OCMArg checkWithBlock:^BOOL(id obj) { + return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventInputKeyMaskEnabled]; + }] text:[OCMArg isNil]]); + + OCMVerify([testKeyboardDelegate keyboardDidUpdateInputMask:[OCMArg checkWithBlock:^BOOL(id obj) { + return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventInputKeyMaskEnabled]; + }]]); + }); + + it(@"should respond to cancellation notifications", ^{ + SDLRPCNotificationNotification *notification = nil; + + // Submit notification + SDLOnKeyboardInput *input = [[SDLOnKeyboardInput alloc] init]; + input.event = SDLKeyboardEventCancelled; + notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveKeyboardInputNotification object:nil rpcNotification:input]; + + [[NSNotificationCenter defaultCenter] postNotification:notification]; + + OCMVerify([testKeyboardDelegate keyboardDidSendEvent:[OCMArg checkWithBlock:^BOOL(id obj) { + return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventCancelled]; + }] text:[OCMArg isNil]]); + + OCMVerify([testKeyboardDelegate keyboardDidAbortWithReason:[OCMArg checkWithBlock:^BOOL(id obj) { + return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventCancelled]; + }]]); + }); + + it(@"should respond to text input notification with autocomplete", ^{ + NSString *inputData = @"Test"; + SDLRPCNotificationNotification *notification = nil; + + OCMStub([testKeyboardDelegate updateAutocompleteWithInput:[OCMArg any] autoCompleteResultsHandler:([OCMArg invokeBlockWithArgs:@[inputData], nil])]); + + // Submit notification + SDLOnKeyboardInput *input = [[SDLOnKeyboardInput alloc] init]; + input.event = SDLKeyboardEventKeypress; + input.data = inputData; + notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveKeyboardInputNotification object:nil rpcNotification:input]; + + [[NSNotificationCenter defaultCenter] postNotification:notification]; + + OCMVerify([testKeyboardDelegate keyboardDidSendEvent:[OCMArg checkWithBlock:^BOOL(id obj) { + return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventKeypress]; + }] text:[OCMArg checkWithBlock:^BOOL(id obj) { + return [(NSString *)obj isEqualToString:inputData]; + }]]); + + OCMVerify([testKeyboardDelegate updateAutocompleteWithInput:[OCMArg checkWithBlock:^BOOL(id obj) { + return [(NSString *)obj isEqualToString:inputData]; + }] autoCompleteResultsHandler:[OCMArg any]]); + + expect(testConnectionManager.receivedRequests.lastObject).to(beAnInstanceOf([SDLSetGlobalProperties class])); + + SDLSetGlobalProperties *setProperties = testConnectionManager.receivedRequests.lastObject; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + expect(setProperties.keyboardProperties.autoCompleteText).to(equal(inputData)); +#pragma clang diagnostic pop + }); + + it(@"should respond to text input notification with character set", ^{ + NSString *inputData = @"Test"; + SDLRPCNotificationNotification *notification = nil; + + OCMStub([testKeyboardDelegate updateCharacterSetWithInput:[OCMArg any] completionHandler:([OCMArg invokeBlockWithArgs:@[inputData], nil])]); + + // Submit notification + SDLOnKeyboardInput *input = [[SDLOnKeyboardInput alloc] init]; + input.event = SDLKeyboardEventKeypress; + input.data = inputData; + notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveKeyboardInputNotification object:nil rpcNotification:input]; + + [[NSNotificationCenter defaultCenter] postNotification:notification]; + + OCMVerify([testKeyboardDelegate keyboardDidSendEvent:[OCMArg checkWithBlock:^BOOL(id obj) { + return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventKeypress]; + }] text:[OCMArg checkWithBlock:^BOOL(id obj) { + return [(NSString *)obj isEqualToString:inputData]; + }]]); + + OCMVerify([testKeyboardDelegate updateCharacterSetWithInput:[OCMArg checkWithBlock:^BOOL(id obj) { + return [(NSString *)obj isEqualToString:inputData]; + }] completionHandler:[OCMArg any]]); + + expect(testConnectionManager.receivedRequests.lastObject).to(beAnInstanceOf([SDLSetGlobalProperties class])); + + SDLSetGlobalProperties *setProperties = testConnectionManager.receivedRequests.lastObject; + expect(setProperties.keyboardProperties.limitedCharacterList).to(equal(@[inputData])); + }); + + describe(@"after a perform interaction response", ^{ + beforeEach(^{ + SDLPerformInteractionResponse *response = [[SDLPerformInteractionResponse alloc] init]; + response.success = @YES; + response.choiceID = @65535; + response.triggerSource = SDLTriggerSourceVoiceRecognition; + + [testConnectionManager respondToLastRequestWithResponse:response]; + }); + + it(@"should reset the keyboard properties", ^{ + expect(testConnectionManager.receivedRequests.lastObject).to(beAnInstanceOf([SDLSetGlobalProperties class])); + }); + + describe(@"after the reset response", ^{ + __block SDLSetGlobalPropertiesResponse *response = [[SDLSetGlobalPropertiesResponse alloc] init]; + beforeEach(^{ + response.success = @YES; + }); + + it(@"should be finished", ^{ + OCMExpect([testChoiceDelegate choiceSet:[OCMArg isEqual:testChoiceSet] didSelectChoice:[OCMArg isNotNil] withSource:[OCMArg isEqual:SDLTriggerSourceVoiceRecognition] atRowIndex:0]); + OCMReject([testChoiceDelegate choiceSet:[OCMArg any] didReceiveError:[OCMArg any]]); + + [testConnectionManager respondToLastRequestWithResponse:response]; + + expect(testOp.isFinished).to(beTrue()); + }); + }); + }); + }); + }); + + describe(@"canceling the choice set", ^{ + context(@"if the head unit supports the `CancelInteraction` RPC", ^{ + beforeEach(^{ + [SDLGlobals sharedGlobals].rpcVersion = [SDLVersion versionWithMajor:6 minor:0 patch:0]; + }); + + context(@"if the operation is executing", ^{ + beforeEach(^{ + [testOp start]; + }); + + context(@"before the present is sent", ^{ + it(@"should cancel without a CancelInteraction", ^{ + expect(testOp.isExecuting).to(beTrue()); + expect(testOp.isFinished).to(beFalse()); + expect(testOp.isCancelled).to(beFalse()); + + [testChoiceSet cancel]; + + expect(testConnectionManager.receivedRequests.lastObject).toNot(beAnInstanceOf([SDLCancelInteraction class])); + + expect(testOp.isExecuting).to(beTrue()); + expect(testOp.isFinished).to(beFalse()); + expect(testOp.isCancelled).to(beTrue()); + }); + }); + + context(@"if the present is in progress", ^{ + beforeEach(^{ + // Move us past the preload + [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:0 error:nil]; + [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:1 error:nil]; + [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:2 error:nil]; + [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + + // Move us past the SetGlobalProperties + SDLSetGlobalPropertiesResponse *sgpr = [[SDLSetGlobalPropertiesResponse alloc] init]; + sgpr.success = @YES; + sgpr.resultCode = SDLResultSuccess; + [testConnectionManager respondToLastRequestWithResponse:sgpr]; + }); + + it(@"should attempt to send a cancel interaction", ^{ + expect(testOp.isExecuting).to(beTrue()); + expect(testOp.isFinished).to(beFalse()); + expect(testOp.isCancelled).to(beFalse()); + + [testChoiceSet cancel]; + + SDLCancelInteraction *lastRequest = testConnectionManager.receivedRequests.lastObject; + expect(lastRequest).to(beAnInstanceOf([SDLCancelInteraction class])); + expect(lastRequest.cancelID).to(equal(testCancelID)); + expect(lastRequest.functionID).to(equal([SDLFunctionID.sharedInstance functionIdForName:SDLRPCFunctionNamePerformInteraction])); + }); + + context(@"If the cancel interaction was successful", ^{ + __block SDLCancelInteractionResponse *testCancelInteractionResponse = [[SDLCancelInteractionResponse alloc] init]; + beforeEach(^{ + testCancelInteractionResponse.success = @YES; + testCancelInteractionResponse.resultCode = SDLResultSuccess; + [testChoiceSet cancel]; + }); + + it(@"should finish with an error", ^{ + // Respond to the cancel interaction, then the perform interaction + [testConnectionManager respondToLastRequestWithResponse:testCancelInteractionResponse]; + + SDLPerformInteractionResponse *pir = [[SDLPerformInteractionResponse alloc] init]; + pir.success = @NO; + pir.resultCode = SDLResultAborted; + [testConnectionManager respondToRequestWithResponse:pir requestNumber:4 error:[NSError sdl_choiceSetManager_cancelled]]; + + // Try to reset the keyboard + expect(testConnectionManager.receivedRequests.lastObject).to(beAnInstanceOf([SDLSetGlobalProperties class])); + + SDLSetGlobalPropertiesResponse *sgpr = [[SDLSetGlobalPropertiesResponse alloc] init]; + sgpr.success = @YES; + sgpr.resultCode = SDLResultSuccess; + [testConnectionManager respondToLastRequestWithResponse:sgpr]; + + OCMReject([testChoiceDelegate choiceSet:[OCMArg isNotNil] didSelectChoice:[OCMArg isNotNil] withSource:[OCMArg any] atRowIndex:0]); + OCMVerify([testChoiceDelegate choiceSet:[OCMArg isEqual:testChoiceSet] didReceiveError:[OCMArg isNotNil]]); + }); + }); + + context(@"If the cancel interaction was not successful", ^{ + __block NSError *testError = [NSError sdl_lifecycle_notConnectedError]; + __block SDLCancelInteractionResponse *testCancelInteractionResponse = [[SDLCancelInteractionResponse alloc] init]; + + beforeEach(^{ + testCancelInteractionResponse.success = @NO; + }); + + it(@"should error", ^{ + OCMExpect([testChoiceDelegate choiceSet:[OCMArg any] didReceiveError:[OCMArg any]]); + OCMReject([testChoiceDelegate choiceSet:[OCMArg isEqual:testChoiceSet] didSelectChoice:[OCMArg isNotNil] withSource:[OCMArg isEqual:SDLTriggerSourceVoiceRecognition] atRowIndex:0]); + [testConnectionManager respondToLastRequestWithResponse:testCancelInteractionResponse error:testError]; + }); + }); + }); + }); + + context(@"if the operation has already finished", ^{ + it(@"should not attempt to send a cancel interaction", ^{ + [testOp finishOperation]; + + expect(testOp.isExecuting).to(beFalse()); + expect(testOp.isFinished).to(beTrue()); + expect(testOp.isCancelled).to(beFalse()); + + [testChoiceSet cancel]; + + SDLCancelInteraction *lastRequest = testConnectionManager.receivedRequests.lastObject; + expect(lastRequest).to(beNil()); + }); + }); + + context(@"if the operation has not started", ^{ + beforeEach(^{ + expect(testOp.isExecuting).to(beFalse()); + expect(testOp.isFinished).to(beFalse()); + expect(testOp.isCancelled).to(beFalse()); + + [testChoiceSet cancel]; + }); + + it(@"should not attempt to send a cancel interaction", ^{ + expect(testOp.isExecuting).to(beFalse()); + expect(testOp.isFinished).to(beFalse()); + expect(testOp.isCancelled).to(beTrue()); + + SDLCancelInteraction *lastRequest = testConnectionManager.receivedRequests.lastObject; + expect(lastRequest).to(beNil()); + }); + + context(@"once the operation has started", ^{ + beforeEach(^{ + [testOp start]; + }); + + it(@"immediately finish", ^{ + expect(testConnectionManager.receivedRequests).to(haveCount(0)); + expect(testOp.isExecuting).to(beFalse()); + expect(testOp.isFinished).to(beTrue()); + expect(testOp.isCancelled).to(beTrue()); + }); + + it(@"should finish", ^{ + expect(testOp.isExecuting).toEventually(beFalse()); + expect(testOp.isFinished).toEventually(beTrue()); + expect(testOp.isCancelled).toEventually(beTrue()); + }); + }); + }); + }); + + context(@"Head unit does not support the `CancelInteraction` RPC", ^{ + beforeEach(^{ + SDLVersion *unsupportedVersion = [SDLVersion versionWithMajor:5 minor:1 patch:0]; + id globalMock = OCMPartialMock([SDLGlobals sharedGlobals]); + OCMStub([globalMock rpcVersion]).andReturn(unsupportedVersion); + }); + + it(@"should not attempt to send a cancel interaction if the operation is executing", ^{ + [testOp start]; + + expect(testOp.isExecuting).to(beTrue()); + expect(testOp.isFinished).to(beFalse()); + expect(testOp.isCancelled).to(beFalse()); + + [testChoiceSet cancel]; + + SDLCancelInteraction *lastRequest = testConnectionManager.receivedRequests.lastObject; + expect(lastRequest).toNot(beAnInstanceOf([SDLCancelInteraction class])); + }); + + it(@"should cancel the operation if it has not yet been run", ^{ + expect(testOp.isExecuting).to(beFalse()); + expect(testOp.isFinished).to(beFalse()); + expect(testOp.isCancelled).to(beFalse()); + + [testChoiceSet cancel]; + + SDLCancelInteraction *lastRequest = testConnectionManager.receivedRequests.lastObject; + expect(lastRequest).to(beNil()); + + expect(testOp.isExecuting).to(beFalse()); + expect(testOp.isFinished).to(beFalse()); + expect(testOp.isCancelled).to(beTrue()); + }); + }); + }); + }); +}); + +QuickSpecEnd |