diff options
author | Joel Fischer <joeljfischer@gmail.com> | 2021-09-27 09:54:30 -0400 |
---|---|---|
committer | Joel Fischer <joeljfischer@gmail.com> | 2021-09-27 09:54:30 -0400 |
commit | cbc318c64aadd04600d14bd70d04cb13717953c8 (patch) | |
tree | 7dc36e8f753e901a535aa950d29a81a8bdaa3606 /SmartDeviceLinkTests/DevAPISpecs | |
parent | 6685595ac5d8db73ccfd64542d8af0df61f95ef2 (diff) | |
parent | 6eaf543e6b6ecea478bd760be9cbf37564398681 (diff) | |
download | sdl_ios-cbc318c64aadd04600d14bd70d04cb13717953c8.tar.gz |
Merge branch 'develop' into bugfix/issue-2034-file-manager-multiple-uploads
# Conflicts:
# SmartDeviceLink/private/SDLDeleteFileOperation.m
# SmartDeviceLink/public/SDLFileManager.m
Diffstat (limited to 'SmartDeviceLinkTests/DevAPISpecs')
8 files changed, 1892 insertions, 712 deletions
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLMenuCellSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLMenuCellSpec.m index f6d982a57..4958e7674 100644 --- a/SmartDeviceLinkTests/DevAPISpecs/SDLMenuCellSpec.m +++ b/SmartDeviceLinkTests/DevAPISpecs/SDLMenuCellSpec.m @@ -108,6 +108,13 @@ describe(@"a menu cell", ^{ expect([testCell isEqual:testCell2]).to(beFalse()); }); + it(@"should compare cells and return false if one cell has subcells empty and another has subcells nil", ^{ + testCell = [[SDLMenuCell alloc] initWithTitle:someTitle secondaryText:someSecondaryTitle tertiaryText:someTertiaryTitle icon:nil secondaryArtwork:someSecondaryArtwork submenuLayout:testLayout subCells:nil]; + testCell2 = [[SDLMenuCell alloc] initWithTitle:someTitle secondaryText:someSecondaryTitle tertiaryText:someTertiaryTitle icon:nil secondaryArtwork:someSecondaryArtwork submenuLayout:testLayout subCells:@[]]; + + expect([testCell isEqual:testCell2]).to(beFalse()); + }); + it(@"should compare cells and return true if cells equal", ^{ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLMenuConfigurationUpdateOperationSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLMenuConfigurationUpdateOperationSpec.m new file mode 100644 index 000000000..3678d4ecc --- /dev/null +++ b/SmartDeviceLinkTests/DevAPISpecs/SDLMenuConfigurationUpdateOperationSpec.m @@ -0,0 +1,174 @@ +// +// SDLMenuConfigurationUpdateOperationSpec.m +// SmartDeviceLinkTests +// +// Created by Joel Fischer on 2/16/21. +// Copyright © 2021 smartdevicelink. All rights reserved. +// + +#import <Nimble/Nimble.h> +#import <OCMock/OCMock.h> +#import <Quick/Quick.h> + +#import <SmartDeviceLink/SmartDeviceLink.h> +#import "SDLMenuConfigurationUpdateOperation.h" +#import "TestConnectionManager.h" + +QuickSpecBegin(SDLMenuConfigurationUpdateOperationSpec) + +describe(@"a menu configuration update operation", ^{ + __block SDLMenuConfigurationUpdateOperation *testOp = nil; + + __block TestConnectionManager *testConnectionManager = nil; + __block SDLFileManager *testFileManager = nil; + __block SDLWindowCapability *testWindowCapability = nil; + SDLMenuConfiguration *testMenuConfiguration = [[SDLMenuConfiguration alloc] initWithMainMenuLayout:SDLMenuLayoutList defaultSubmenuLayout:SDLMenuLayoutTiles]; + + __block SDLMenuConfigurationUpdatedBlock testUpdatedBlock = nil; + __block SDLMenuConfiguration *resultMenuConfiguration = nil; + __block NSError *resultError = nil; + + beforeEach(^{ + testConnectionManager = [[TestConnectionManager alloc] init]; + testFileManager = OCMClassMock([SDLFileManager class]); + testWindowCapability = [[SDLWindowCapability alloc] initWithWindowID:@0 textFields:nil imageFields:nil imageTypeSupported:nil templatesAvailable:nil numCustomPresetsAvailable:nil buttonCapabilities:nil softButtonCapabilities:nil menuLayoutsAvailable:@[] dynamicUpdateCapabilities:nil keyboardCapabilities:nil]; + + resultMenuConfiguration = nil; + resultError = nil; + testUpdatedBlock = ^(SDLMenuConfiguration *newConfiguration, NSError *_Nullable error) { + resultMenuConfiguration = newConfiguration; + resultError = error; + }; + }); + + // when the layout check fails + describe(@"when the layout check fails", ^{ + // when there are no known menu layouts + context(@"when there are no known menu layouts", ^{ + it(@"should return an error and finish", ^{ + testOp = [[SDLMenuConfigurationUpdateOperation alloc] initWithConnectionManager:testConnectionManager windowCapability:testWindowCapability newMenuConfiguration:testMenuConfiguration configurationUpdatedHandler:testUpdatedBlock]; + [testOp start]; + + expect(testConnectionManager.receivedRequests).to(beEmpty()); + expect(testOp.isFinished).to(beTrue()); + expect(resultMenuConfiguration).to(beNil()); + expect(resultError).toNot(beNil()); + expect(testOp.error).toNot(beNil()); + }); + }); + + // when the set main menu layout is not available + context(@"when the set main menu layout is not available", ^{ + beforeEach(^{ + testWindowCapability.menuLayoutsAvailable = @[SDLMenuLayoutTiles]; + }); + + it(@"should return an error and finish", ^{ + testOp = [[SDLMenuConfigurationUpdateOperation alloc] initWithConnectionManager:testConnectionManager windowCapability:testWindowCapability newMenuConfiguration:testMenuConfiguration configurationUpdatedHandler:testUpdatedBlock]; + [testOp start]; + + expect(testConnectionManager.receivedRequests).to(beEmpty()); + expect(testOp.isFinished).to(beTrue()); + expect(resultMenuConfiguration).to(beNil()); + expect(resultError).toNot(beNil()); + expect(testOp.error).toNot(beNil()); + }); + }); + + // when the set default submenu layout is not available + context(@"when the set default submenu layout is not available", ^{ + beforeEach(^{ + testWindowCapability.menuLayoutsAvailable = @[SDLMenuLayoutList]; + }); + + it(@"should return an error and finish", ^{ + testOp = [[SDLMenuConfigurationUpdateOperation alloc] initWithConnectionManager:testConnectionManager windowCapability:testWindowCapability newMenuConfiguration:testMenuConfiguration configurationUpdatedHandler:testUpdatedBlock]; + [testOp start]; + + expect(testConnectionManager.receivedRequests).to(beEmpty()); + expect(testOp.isFinished).to(beTrue()); + expect(resultMenuConfiguration).to(beNil()); + expect(resultError).toNot(beNil()); + expect(testOp.error).toNot(beNil()); + }); + }); + }); + + // when the set layouts are available + describe(@"when the set layouts are available", ^{ + __block SDLSetGlobalPropertiesResponse *response = [[SDLSetGlobalPropertiesResponse alloc] init]; + + beforeEach(^{ + testWindowCapability.menuLayoutsAvailable = @[SDLMenuLayoutList, SDLMenuLayoutTiles]; + + testOp = [[SDLMenuConfigurationUpdateOperation alloc] initWithConnectionManager:testConnectionManager windowCapability:testWindowCapability newMenuConfiguration:testMenuConfiguration configurationUpdatedHandler:testUpdatedBlock]; + [testOp start]; + }); + + // should send the RPC + it(@"should send the RPC", ^{ + expect(testOp.error).to(beNil()); + expect(testConnectionManager.receivedRequests).toNot(beEmpty()); + expect(testOp.isFinished).to(beFalse()); + expect(resultMenuConfiguration).to(beNil()); + expect(resultError).to(beNil()); + + SDLSetGlobalProperties *receivedSGP = (SDLSetGlobalProperties *)testConnectionManager.receivedRequests[0]; + expect(receivedSGP.menuLayout).to(equal(testMenuConfiguration.mainMenuLayout)); + }); + + // if an error returned + context(@"if an error returned", ^{ + beforeEach(^{ + response.success = @NO; + response.resultCode = SDLResultRejected; + }); + + it(@"should return an error and finish", ^{ + [testConnectionManager respondToLastRequestWithResponse:response]; + + expect(testOp.error).toNot(beNil()); + expect(testConnectionManager.receivedRequests).toNot(beEmpty()); + expect(testOp.isFinished).to(beTrue()); + expect(resultMenuConfiguration).to(beNil()); + }); + }); + + // if it succeeded + context(@"if it succeeded", ^{ + beforeEach(^{ + response.success = @YES; + response.resultCode = SDLResultSuccess; + }); + + it(@"should not return an error and finish", ^{ + [testConnectionManager respondToLastRequestWithResponse:response]; + + expect(testOp.error).to(beNil()); + expect(testConnectionManager.receivedRequests).toNot(beEmpty()); + expect(testOp.isFinished).to(beTrue()); + expect(resultMenuConfiguration).to(equal(testMenuConfiguration)); + expect(resultError).to(beNil()); + }); + }); + }); + + describe(@"cancelling the operation before it starts", ^{ + testOp = [[SDLMenuConfigurationUpdateOperation alloc] initWithConnectionManager:testConnectionManager windowCapability:testWindowCapability newMenuConfiguration:testMenuConfiguration configurationUpdatedHandler:testUpdatedBlock]; + + beforeEach(^{ + [testOp cancel]; + [testOp start]; + }); + + it(@"should finish without any callbacks", ^{ + expect(testOp.error).to(beNil()); + expect(testConnectionManager.receivedRequests).to(beEmpty()); + expect(testOp.isFinished).to(beTrue()); + expect(resultMenuConfiguration).to(beNil()); + expect(resultError).to(beNil()); + }); + }); +}); + +QuickSpecEnd diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLMenuManagerSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLMenuManagerSpec.m index 4b20b08de..c663205e2 100644 --- a/SmartDeviceLinkTests/DevAPISpecs/SDLMenuManagerSpec.m +++ b/SmartDeviceLinkTests/DevAPISpecs/SDLMenuManagerSpec.m @@ -4,9 +4,10 @@ #import <SmartDeviceLink/SmartDeviceLink.h> +#import "SDLGlobals.h" #import "SDLMenuManager.h" +#import "SDLMenuReplaceOperation.h" #import "TestConnectionManager.h" -#import "SDLGlobals.h" @interface SDLMenuCell() @@ -22,20 +23,13 @@ @property (weak, nonatomic) SDLFileManager *fileManager; @property (weak, nonatomic) SDLSystemCapabilityManager *systemCapabilityManager; -@property (copy, nonatomic, nullable) SDLHMILevel currentHMILevel; -@property (copy, nonatomic, nullable) SDLSystemContext currentSystemContext; +@property (strong, nonatomic) NSOperationQueue *transactionQueue; @property (strong, nonatomic, nullable) SDLWindowCapability *windowCapability; -@property (strong, nonatomic, nullable) NSArray<SDLRPCRequest *> *inProgressUpdate; -@property (assign, nonatomic) BOOL hasQueuedUpdate; -@property (assign, nonatomic) BOOL waitingOnHMIUpdate; -@property (copy, nonatomic) NSArray<SDLMenuCell *> *waitingUpdateMenuCells; - -@property (assign, nonatomic) UInt32 lastMenuId; -@property (copy, nonatomic) NSArray<SDLMenuCell *> *oldMenuCells; - -- (BOOL)sdl_shouldRPCsIncludeImages:(NSArray<SDLMenuCell *> *)cells; -- (void)sdl_displayCapabilityDidUpdate; +@property (copy, nonatomic, nullable) SDLHMILevel currentHMILevel; +@property (copy, nonatomic, nullable) SDLSystemContext currentSystemContext; +@property (copy, nonatomic) NSArray<SDLMenuCell *> *currentMenuCells; +@property (strong, nonatomic, nullable) SDLMenuConfiguration *currentMenuConfiguration; @end @@ -46,40 +40,15 @@ describe(@"menu manager", ^{ __block TestConnectionManager *mockConnectionManager = nil; __block SDLFileManager *mockFileManager = nil; __block SDLSystemCapabilityManager *mockSystemCapabilityManager = nil; - __block SDLArtwork *testArtwork = nil; - __block SDLArtwork *testArtwork2 = nil; - __block SDLArtwork *testArtwork3 = nil; - - __block SDLMenuCell *textOnlyCell = nil; - __block SDLMenuCell *textOnlyCell2 = nil; - __block SDLMenuCell *textAndImageCell = nil; - __block SDLMenuCell *textAndImageCell2 = nil; - __block SDLMenuCell *submenuCell = nil; - __block SDLMenuCell *submenuCell2 = nil; - __block SDLMenuCell *submenuImageCell = nil; __block SDLMenuConfiguration *testMenuConfiguration = nil; - __block SDLImageField *commandIconField = nil; - __block SDLImageField *commandSecondaryArtworkField = nil; - __block SDLImageField *submenuIconField = nil; - __block SDLImageField *subMenuSecondaryArtworkField = nil; - - __block SDLVersion *menuUniquenessActiveVersion = nil; + __block SDLMenuCell *textOnlyCell = nil; + __block SDLMenuCell *submenuCell = nil; beforeEach(^{ - testArtwork = [[SDLArtwork alloc] initWithData:[@"Test data" dataUsingEncoding:NSUTF8StringEncoding] name:@"some artwork name" fileExtension:@"png" persistent:NO]; - testArtwork2 = [[SDLArtwork alloc] initWithData:[@"Test data 2" dataUsingEncoding:NSUTF8StringEncoding] name:@"some artwork name 2" fileExtension:@"png" persistent:NO]; - testArtwork3 = [[SDLArtwork alloc] initWithData:[@"Test data 3" dataUsingEncoding:NSUTF8StringEncoding] name:@"some artwork name" fileExtension:@"png" persistent:NO]; - testArtwork3.overwrite = YES; - textOnlyCell = [[SDLMenuCell alloc] initWithTitle:@"Test 1" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; - textAndImageCell = [[SDLMenuCell alloc] initWithTitle:@"Test 2" secondaryText:nil tertiaryText:nil icon:testArtwork secondaryArtwork:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; - textAndImageCell2 = [[SDLMenuCell alloc] initWithTitle:@"Test 2" secondaryText:nil tertiaryText:nil icon:testArtwork2 secondaryArtwork:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; - submenuCell = [[SDLMenuCell alloc] initWithTitle:@"Test 3" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil submenuLayout:nil subCells:@[textOnlyCell, textAndImageCell]]; - submenuCell2 = [[SDLMenuCell alloc] initWithTitle:@"Test 3" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil submenuLayout:nil subCells:@[textAndImageCell, textAndImageCell2]]; - submenuImageCell = [[SDLMenuCell alloc] initWithTitle:@"Test 4" secondaryText:nil tertiaryText:nil icon:testArtwork2 secondaryArtwork:nil submenuLayout:SDLMenuLayoutTiles subCells:@[textOnlyCell]]; - textOnlyCell2 = [[SDLMenuCell alloc] initWithTitle:@"Test 5" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; + submenuCell = [[SDLMenuCell alloc] initWithTitle:@"Test 3" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil submenuLayout:nil subCells:@[textOnlyCell]]; testMenuConfiguration = [[SDLMenuConfiguration alloc] initWithMainMenuLayout:SDLMenuLayoutTiles defaultSubmenuLayout:SDLMenuLayoutList]; @@ -88,620 +57,257 @@ describe(@"menu manager", ^{ mockSystemCapabilityManager = OCMClassMock([SDLSystemCapabilityManager class]); testManager = [[SDLMenuManager alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager]; - commandIconField = [[SDLImageField alloc] initWithName:SDLImageFieldNameCommandIcon imageTypeSupported:@[SDLFileTypePNG] imageResolution:nil]; - commandSecondaryArtworkField = [[SDLImageField alloc] initWithName:SDLImageFieldNameMenuCommandSecondaryImage imageTypeSupported:@[SDLFileTypePNG] imageResolution:nil]; - submenuIconField = [[SDLImageField alloc] initWithName:SDLImageFieldNameSubMenuIcon imageTypeSupported:@[SDLFileTypePNG] imageResolution:nil]; - subMenuSecondaryArtworkField = [[SDLImageField alloc] initWithName:SDLImageFieldNameMenuSubMenuSecondaryImage imageTypeSupported:@[SDLFileTypePNG] imageResolution:nil]; - SDLTextField *commandSecondaryTextField = [[SDLTextField alloc] initWithName:SDLTextFieldNameMenuCommandSecondaryText characterSet:SDLCharacterSetAscii width:100 rows:1]; - SDLTextField *commandTertiaryTextField = [[SDLTextField alloc] initWithName:SDLTextFieldNameMenuCommandTertiaryText characterSet:SDLCharacterSetAscii width:100 rows:1]; - SDLTextField *submenuSecondaryTextField = [[SDLTextField alloc] initWithName:SDLTextFieldNameMenuSubMenuSecondaryText characterSet:SDLCharacterSetAscii width:100 rows:1]; - SDLTextField *submenuTertiaryTextField = [[SDLTextField alloc] initWithName:SDLTextFieldNameMenuSubMenuTertiaryText characterSet:SDLCharacterSetAscii width:100 rows:1]; + SDLImageField *commandIconField = [[SDLImageField alloc] init]; + commandIconField.name = SDLImageFieldNameCommandIcon; SDLWindowCapability *windowCapability = [[SDLWindowCapability alloc] init]; windowCapability.windowID = @(SDLPredefinedWindowsDefaultWindow); - windowCapability.imageFields = @[commandIconField, commandSecondaryArtworkField, submenuIconField, subMenuSecondaryArtworkField]; - windowCapability.textFields = @[commandSecondaryTextField, commandTertiaryTextField, submenuSecondaryTextField, submenuTertiaryTextField]; + windowCapability.imageFields = @[commandIconField]; windowCapability.imageTypeSupported = @[SDLImageTypeDynamic, SDLImageTypeStatic]; windowCapability.menuLayoutsAvailable = @[SDLMenuLayoutList, SDLMenuLayoutTiles]; testManager.windowCapability = windowCapability; - menuUniquenessActiveVersion = [[SDLVersion alloc] initWithMajor:7 minor:1 patch:0]; }); - // should instantiate correctly it(@"should instantiate correctly", ^{ expect(testManager.menuCells).to(beEmpty()); + + expect(@(testManager.dynamicMenuUpdatesMode)).to(equal(@(SDLDynamicMenuUpdatesModeOnWithCompatibility))); expect(testManager.connectionManager).to(equal(mockConnectionManager)); expect(testManager.fileManager).to(equal(mockFileManager)); expect(testManager.systemCapabilityManager).to(equal(mockSystemCapabilityManager)); + expect(testManager.transactionQueue).toNot(beNil()); + expect(testManager.windowCapability).toNot(beNil()); expect(testManager.currentHMILevel).to(beNil()); - expect(testManager.inProgressUpdate).to(beNil()); - expect(testManager.hasQueuedUpdate).to(beFalse()); - expect(testManager.waitingOnHMIUpdate).to(beFalse()); - expect(testManager.lastMenuId).to(equal(1)); - expect(testManager.oldMenuCells).to(beEmpty()); - expect(testManager.waitingUpdateMenuCells).to(beNil()); - expect(testManager.menuConfiguration).toNot(beNil()); - }); - - // updating menu cells before HMI is ready - describe(@"updating menu cells before HMI is ready", ^{ - // when in HMI NONE - context(@"when in HMI NONE", ^{ - beforeEach(^{ - testManager.currentHMILevel = SDLHMILevelNone; - testManager.menuCells = @[textOnlyCell]; - }); - - it(@"should not update", ^{ - expect(mockConnectionManager.receivedRequests).to(beEmpty()); - }); - - describe(@"when entering the foreground", ^{ - beforeEach(^{ - SDLOnHMIStatus *onHMIStatus = [[SDLOnHMIStatus alloc] init]; - onHMIStatus.hmiLevel = SDLHMILevelFull; - onHMIStatus.systemContext = SDLSystemContextMain; - - SDLRPCNotificationNotification *testSystemContextNotification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidChangeHMIStatusNotification object:nil rpcNotification:onHMIStatus]; - [[NSNotificationCenter defaultCenter] postNotification:testSystemContextNotification]; - }); - - it(@"should update", ^{ - expect(mockConnectionManager.receivedRequests).toNot(beEmpty()); - }); - }); - }); - - // when no HMI level has been received - context(@"when no HMI level has been received", ^{ - beforeEach(^{ - testManager.currentHMILevel = nil; - }); - - it(@"should not update the menu configuration", ^{ - testManager.menuConfiguration = testMenuConfiguration; - expect(mockConnectionManager.receivedRequests).to(beEmpty()); - expect(testManager.menuConfiguration).toNot(equal(testMenuConfiguration)); - }); - - it(@"should not update the menu cells", ^{ - testManager.menuCells = @[textOnlyCell]; - expect(mockConnectionManager.receivedRequests).to(beEmpty()); - }); - }); - - // when in the menu - context(@"when in the menu", ^{ - beforeEach(^{ - [SDLGlobals sharedGlobals].rpcVersion = [SDLVersion versionWithString:@"6.0.0"]; - testManager.currentHMILevel = SDLHMILevelFull; - testManager.currentSystemContext = SDLSystemContextMenu; - }); - - it(@"should update the menu configuration", ^{ - testManager.menuConfiguration = testMenuConfiguration; - expect(mockConnectionManager.receivedRequests).toNot(beEmpty()); - expect(testManager.menuConfiguration).to(equal(testMenuConfiguration)); - }); - }); + expect(testManager.currentSystemContext).to(beNil()); + expect(testManager.currentMenuCells).to(beEmpty()); + expect(testManager.currentMenuConfiguration).to(beNil()); }); - // display capability updates - describe(@"display capability updates", ^{ + describe(@"when the manager stops", ^{ beforeEach(^{ - testManager.currentHMILevel = SDLHMILevelFull; - testManager.currentSystemContext = SDLSystemContextMain; + [testManager stop]; }); - it(@"should save the new window capability", ^{ - SDLWindowCapability *testWindowCapability = [[SDLWindowCapability alloc] init]; - testWindowCapability.textFields = @[[[SDLTextField alloc] initWithName:SDLTextFieldNameMenuName characterSet:SDLCharacterSetUtf8 width:500 rows:1]]; - OCMStub([mockSystemCapabilityManager defaultMainWindowCapability]).andReturn(testWindowCapability); - [testManager sdl_displayCapabilityDidUpdate]; + it(@"should reset correctly", ^{ + expect(testManager.menuCells).to(beEmpty()); - expect(testManager.windowCapability).to(equal(testWindowCapability)); + expect(@(testManager.dynamicMenuUpdatesMode)).to(equal(@(SDLDynamicMenuUpdatesModeOnWithCompatibility))); + expect(testManager.connectionManager).to(equal(mockConnectionManager)); + expect(testManager.fileManager).to(equal(mockFileManager)); + expect(testManager.systemCapabilityManager).to(equal(mockSystemCapabilityManager)); + expect(testManager.transactionQueue).toNot(beNil()); + expect(testManager.windowCapability).to(beNil()); + expect(testManager.currentHMILevel).to(beNil()); + expect(testManager.currentSystemContext).to(beNil()); + expect(testManager.currentMenuCells).to(beEmpty()); + expect(testManager.currentMenuConfiguration).to(beNil()); }); }); - // updating menu cells - describe(@"updating menu cells", ^{ + context(@"when in HMI NONE", ^{ beforeEach(^{ - testManager.currentHMILevel = SDLHMILevelFull; - testManager.currentSystemContext = SDLSystemContextMain; + SDLOnHMIStatus *noneStatus = [[SDLOnHMIStatus alloc] initWithHMILevel:SDLHMILevelNone systemContext:SDLSystemContextMain audioStreamingState:SDLAudioStreamingStateNotAudible videoStreamingState:nil windowID:nil]; + [[NSNotificationCenter defaultCenter] postNotification:[[SDLRPCNotificationNotification alloc] initWithName:SDLDidChangeHMIStatusNotification object:nil rpcNotification:noneStatus]]; }); - // HMI does not support a command secondary image - context(@"HMI does not support a command secondary image", ^{ - SDLArtwork *staticArtwork = [[SDLArtwork alloc] initWithStaticIcon:SDLStaticIconNameKey]; - - beforeEach(^{ - testManager.windowCapability.imageFields = @[commandIconField, submenuIconField, subMenuSecondaryArtworkField]; - textAndImageCell = [[SDLMenuCell alloc] initWithTitle:@"Test 2" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:staticArtwork voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; - textAndImageCell2 = [[SDLMenuCell alloc] initWithTitle:@"Test 3" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:staticArtwork submenuLayout:SDLMenuLayoutList subCells:@[textOnlyCell]]; - testManager.menuCells = @[textAndImageCell, textAndImageCell2]; - }); - - it(@"should not send secondaryArtwork in our request for addCommand but send it with addSubMenu", ^{ - SDLAddCommand *cellCommand = (SDLAddCommand *)testManager.inProgressUpdate.firstObject; - SDLAddSubMenu *cellSubMenu = (SDLAddSubMenu *)testManager.inProgressUpdate[1]; - expect(cellCommand.menuParams.menuName).to(equal(@"Test 2")); - expect(cellCommand.secondaryImage).to(beNil()); - expect(cellSubMenu.secondaryImage).toNot(beNil()); - }); + it(@"should not suspend the transaction queue", ^{ + expect(testManager.transactionQueue.isSuspended).to(beTrue()); }); - // HMI does not support a submenu secondary image - context(@"HMI does not support a submenu secondary image", ^{ - SDLArtwork *staticArtwork = [[SDLArtwork alloc] initWithStaticIcon:SDLStaticIconNameKey]; - + // when entering HMI FULL + describe(@"when entering HMI FULL", ^{ beforeEach(^{ - testManager.windowCapability.imageFields = @[commandIconField, submenuIconField, commandSecondaryArtworkField]; - textAndImageCell = [[SDLMenuCell alloc] initWithTitle:@"Test 2" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:staticArtwork submenuLayout:SDLMenuLayoutList subCells:@[textOnlyCell]]; - textAndImageCell2 = [[SDLMenuCell alloc] initWithTitle:@"Test 3" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:staticArtwork voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; - testManager.menuCells = @[textAndImageCell, textAndImageCell2]; - }); + SDLOnHMIStatus *onHMIStatus = [[SDLOnHMIStatus alloc] init]; + onHMIStatus.hmiLevel = SDLHMILevelFull; + onHMIStatus.systemContext = SDLSystemContextMain; - it(@"should not send secondaryArtwork in our request for addSubMenu but send it with addCommand", ^{ - SDLAddSubMenu *cellSubMenu = (SDLAddSubMenu *)testManager.inProgressUpdate.firstObject; - SDLAddCommand *cellCommand = (SDLAddCommand *)testManager.inProgressUpdate[1]; - expect(cellSubMenu.menuName).to(equal(@"Test 2")); - expect(cellSubMenu.secondaryImage).to(beNil()); - expect(cellCommand.secondaryImage).toNot(beNil()); - }); - }); - - // duplicate titles version >= 7.1.0 - context(@"duplicate titles version >= 7.1.0", ^{ - beforeEach(^{ - [SDLGlobals sharedGlobals].rpcVersion = menuUniquenessActiveVersion; - }); - - // if there are duplicate cells once you strip unused menu properties - context(@"if there are duplicate cells once you strip unused menu properties", ^{ - beforeEach(^{ - testManager.windowCapability.textFields = @[]; - testManager.windowCapability.imageFields = @[]; - }); - - it(@"should update the cells' unique title to include unique data", ^{ - testManager.menuCells = @[textAndImageCell, textAndImageCell2]; - expect(testManager.menuCells).toNot(beEmpty()); - expect(testManager.menuCells.firstObject.uniqueTitle).to(equal("Test 2")); - expect(testManager.menuCells.lastObject.uniqueTitle).to(equal("Test 2 (2)")); - }); - - it(@"should update subcells' unique title to include unique data", ^{ - testManager.menuCells = @[submenuCell2]; - expect(testManager.menuCells).toNot(beEmpty()); - expect(testManager.menuCells.firstObject.subCells.firstObject.uniqueTitle).to(equal("Test 2")); - expect(testManager.menuCells.firstObject.subCells.lastObject.uniqueTitle).to(equal("Test 2 (2)")); - }); + SDLRPCNotificationNotification *testSystemContextNotification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidChangeHMIStatusNotification object:nil rpcNotification:onHMIStatus]; + [[NSNotificationCenter defaultCenter] postNotification:testSystemContextNotification]; }); - // if there are no duplicate cells - context(@"if there are no duplicate cells", ^{ - it(@"should not update the cells' unique title", ^{ - testManager.menuCells = @[textAndImageCell, textAndImageCell2]; - expect(testManager.menuCells).toNot(beEmpty()); - expect(testManager.menuCells.firstObject.uniqueTitle).to(equal("Test 2")); - expect(testManager.menuCells.lastObject.uniqueTitle).to(equal("Test 2")); - }); - - it(@"should not update subcells' unique title", ^{ - testManager.menuCells = @[submenuCell2]; - expect(testManager.menuCells).toNot(beEmpty()); - expect(testManager.menuCells.firstObject.subCells.firstObject.uniqueTitle).to(equal("Test 2")); - expect(testManager.menuCells.firstObject.subCells.lastObject.uniqueTitle).to(equal("Test 2")); - }); + it(@"should run the transaction queue", ^{ + expect(testManager.transactionQueue.isSuspended).to(beFalse()); }); }); + }); - // duplicate titles version <= 7.1.0 - context(@"duplicate titles version <= 7.1.0", ^{ - beforeEach(^{ - [SDLGlobals sharedGlobals].rpcVersion = [[SDLVersion alloc] initWithMajor:7 minor:0 patch:0]; - }); - - it(@"append a number to the unique text for main menu cells", ^{ - testManager.menuCells = @[textAndImageCell, textAndImageCell2]; - expect(testManager.menuCells).toNot(beEmpty()); - expect(testManager.menuCells.firstObject.uniqueTitle).to(equal("Test 2")); - expect(testManager.menuCells.lastObject.uniqueTitle).to(equal("Test 2 (2)")); - }); - - it(@"should append a number to the unique text for subcells", ^{ - testManager.menuCells = @[submenuCell2]; - expect(testManager.menuCells).toNot(beEmpty()); - expect(testManager.menuCells.firstObject.subCells.firstObject.uniqueTitle).to(equal("Test 2")); - expect(testManager.menuCells.firstObject.subCells.lastObject.uniqueTitle).to(equal("Test 2 (2)")); - }); + context(@"when the HMI is ready", ^{ + beforeEach(^{ + testManager.currentHMILevel = SDLHMILevelFull; + testManager.currentSystemContext = SDLSystemContextMain; }); - // when there are complete duplicates - describe(@"when there are complete duplicates", ^{ - // when the cells contain duplicates - context(@"when the cells contain duplicates", ^{ - SDLMenuCell *textCell = [[SDLMenuCell alloc] initWithTitle:@"Test 1" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil voiceCommands:@[@"no", @"yes"] handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; - SDLMenuCell *textCell2 = [[SDLMenuCell alloc] initWithTitle:@"Test 1" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil voiceCommands:@[@"no", @"yes"] handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; - - it(@"should fail with duplicate cells", ^{ - testManager.menuCells = @[textCell, textCell2]; + describe(@"setting new menu cells", ^{ + context(@"containing duplicate titles", ^{ + it(@"should not start an operation", ^{ + testManager.menuCells = @[textOnlyCell, textOnlyCell]; expect(testManager.menuCells).to(beEmpty()); + expect(testManager.transactionQueue.operationCount).to(equal(0)); }); }); - // when cells contain duplicate subcells - context(@"when cells contain duplicate subcells", ^{ - SDLMenuCell *subCell1 = [[SDLMenuCell alloc] initWithTitle:@"subCell 1" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; - SDLMenuCell *subCell2 = [[SDLMenuCell alloc] initWithTitle:@"subCell 1" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; - SDLMenuCell *textCell = [[SDLMenuCell alloc] initWithTitle:@"Test 1" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil submenuLayout:nil subCells:@[subCell1, subCell2]]; + context(@"containing duplicate VR commands", ^{ + SDLMenuCell *textAndVRCell1 = [[SDLMenuCell alloc] initWithTitle:@"Test 1" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil voiceCommands:@[@"Cat", @"Turtle"] handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; + SDLMenuCell *textAndVRCell2 = [[SDLMenuCell alloc] initWithTitle:@"Test 3" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil voiceCommands:@[@"Cat", @"Dog"] handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; - it(@"should fail with duplicate cells", ^{ - testManager.menuCells = @[textCell]; + it(@"should not start an operation", ^{ + testManager.menuCells = @[textAndVRCell1, textAndVRCell2]; expect(testManager.menuCells).to(beEmpty()); + expect(testManager.transactionQueue.operationCount).to(equal(0)); }); }); - context(@"duplicate VR commands", ^{ - __block SDLMenuCell *textAndVRCell1 = [[SDLMenuCell alloc] initWithTitle:@"Test 1" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil voiceCommands:@[@"Cat", @"Turtle"] handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; - __block SDLMenuCell *textAndVRCell2 = [[SDLMenuCell alloc] initWithTitle:@"Test 3" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil voiceCommands:@[@"Cat", @"Dog"] handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; + context(@"if the new menu cells are identical to the old menu cells", ^{ + it(@"should queue two transactions and let the operation handle not updating", ^{ + testManager.menuCells = @[textOnlyCell]; + testManager.menuCells = @[textOnlyCell]; - it(@"should fail when menu items have duplicate vr commands", ^{ - testManager.menuCells = @[textAndVRCell1, textAndVRCell2]; - expect(testManager.menuCells).to(beEmpty()); + expect(testManager.menuCells).to(equal(@[textOnlyCell])); + expect(testManager.transactionQueue.operationCount).to(equal(2)); }); }); - context(@"when there are duplicate VR commands in subCells", ^{ - SDLMenuCell *textAndVRSubCell1 = [[SDLMenuCell alloc] initWithTitle:@"subCell 1" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil voiceCommands:@[@"Cat"] handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; - SDLMenuCell *textAndVRSubCell2 = [[SDLMenuCell alloc] initWithTitle:@"subCell 2" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; - SDLMenuCell *textAndVRCell1 = [[SDLMenuCell alloc] initWithTitle:@"Test 1" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil voiceCommands:@[@"Cat", @"Turtle"] handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; - SDLMenuCell *textAndVRCell2 = [[SDLMenuCell alloc] initWithTitle:@"Test 2" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil submenuLayout:nil subCells:@[textAndVRSubCell1, textAndVRSubCell2]]; + context(@"when a second menu cells update is queued before the first is done", ^{ + it(@"should cancel the first operation", ^{ + testManager.menuCells = @[textOnlyCell]; + testManager.menuCells = @[submenuCell]; - it(@"should fail when menu items have duplicate vr commands", ^{ - testManager.menuCells = @[textAndVRCell1, textAndVRCell2]; - expect(testManager.menuCells).to(beEmpty()); + expect(testManager.menuCells).to(equal(@[submenuCell])); + expect(testManager.transactionQueue.operationCount).to(equal(2)); + expect(testManager.transactionQueue.operations[0].isCancelled).to(beTrue()); }); }); - }); - // should check if all artworks are uploaded and return NO - it(@"should check if all artworks are uploaded and return NO", ^{ - textAndImageCell = [[SDLMenuCell alloc] initWithTitle:@"Test 2" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:testArtwork voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; - testManager.menuCells = @[textAndImageCell, textOnlyCell]; - OCMVerify([testManager sdl_shouldRPCsIncludeImages:testManager.menuCells]); - expect([testManager sdl_shouldRPCsIncludeImages:testManager.menuCells]).to(beFalse()); - }); + context(@"if cells are formed properly", ^{ + it(@"should properly prepare and queue the transaction", ^{ + testManager.menuCells = @[textOnlyCell]; - // should properly update a text cell - it(@"should properly update a text cell", ^{ - testManager.menuCells = @[textOnlyCell]; + expect(testManager.transactionQueue.operationCount).to(equal(1)); + expect(testManager.transactionQueue.operations[0]).to(beAnInstanceOf([SDLMenuReplaceOperation class])); - NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLDeleteCommand class]]; - NSArray *deletes = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate]; - expect(deletes).to(beEmpty()); + // Assign proper current menu + SDLMenuReplaceOperation *testOp = testManager.transactionQueue.operations[0]; + expect(testOp.currentMenu).to(haveCount(0)); - NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddCommand class]]; - NSArray *add = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; - expect(add).toNot(beEmpty()); + // Callback proper current menu + testOp.currentMenu = @[textOnlyCell]; + [testOp finishOperation]; + expect(testManager.currentMenuCells).to(haveCount(1)); + }); + }); }); - // should properly update with subcells - it(@"should properly update with subcells", ^{ - OCMStub([mockFileManager uploadArtworks:[OCMArg any] completionHandler:[OCMArg invokeBlock]]); - testManager.menuCells = @[submenuCell]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - - NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddCommand class]]; - NSArray *adds = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; - - NSPredicate *submenuCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddSubMenu class]]; - NSArray *submenus = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:submenuCommandPredicate]; - - expect(adds).to(haveCount(2)); - expect(submenus).to(haveCount(1)); - }); + describe(@"updating the menu configuration", ^{ + beforeEach(^{ + testManager.currentHMILevel = SDLHMILevelFull; + testManager.currentSystemContext = SDLSystemContextMain; + }); - // updating with an image - describe(@"updating with an image", ^{ - context(@"when the image is already on the head unit", ^{ + context(@"if the connection RPC version is less than 6.0.0", ^{ beforeEach(^{ - OCMStub([mockFileManager hasUploadedFile:[OCMArg isNotNil]]).andReturn(YES); - }); - - it(@"should check if all artworks are uploaded", ^{ - textAndImageCell = [[SDLMenuCell alloc] initWithTitle:@"Test 2" secondaryText:nil tertiaryText:nil icon:testArtwork3 secondaryArtwork:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; - testManager.menuCells = @[textAndImageCell, textOnlyCell]; - OCMVerify([testManager sdl_shouldRPCsIncludeImages:testManager.menuCells]); - expect([testManager sdl_shouldRPCsIncludeImages:testManager.menuCells]).to(beTrue()); + [SDLGlobals sharedGlobals].rpcVersion = [SDLVersion versionWithString:@"5.0.0"]; }); - it(@"should properly update an image cell", ^{ - testManager.menuCells = @[textAndImageCell, submenuImageCell]; + it(@"should not queue a menu configuration update", ^{ + testManager.menuConfiguration = testMenuConfiguration; - NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddCommand class]]; - NSArray *add = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; - SDLAddCommand *sentCommand = add.firstObject; - - NSPredicate *addSubmenuPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddSubMenu class]]; - NSArray *submenu = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addSubmenuPredicate]; - SDLAddSubMenu *sentSubmenu = submenu.firstObject; - - expect(add).to(haveCount(1)); - expect(submenu).to(haveCount(1)); - expect(sentCommand.cmdIcon.value).to(equal(testArtwork.name)); - expect(sentSubmenu.menuIcon.value).to(equal(testArtwork2.name)); - OCMReject([mockFileManager uploadArtworks:[OCMArg any] completionHandler:[OCMArg any]]); - }); - - it(@"should properly overwrite an image cell", ^{ - OCMStub([mockFileManager fileNeedsUpload:[OCMArg isNotNil]]).andReturn(YES); - textAndImageCell = [[SDLMenuCell alloc] initWithTitle:@"Test 2" secondaryText:nil tertiaryText:nil icon:testArtwork3 secondaryArtwork:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; - testManager.menuCells = @[textAndImageCell, submenuImageCell]; - OCMVerify([mockFileManager uploadArtworks:[OCMArg any] completionHandler:[OCMArg any]]); + expect(testManager.menuConfiguration).toNot(equal(testMenuConfiguration)); + expect(testManager.transactionQueue.operationCount).to(equal(0)); }); }); - // No longer a valid unit test - context(@"when the image is not on the head unit", ^{ + context(@"if the connection RPC version is greater than or equal to 6.0.0", ^{ beforeEach(^{ - testManager.dynamicMenuUpdatesMode = SDLDynamicMenuUpdatesModeForceOff; - OCMStub([mockFileManager uploadArtworks:[OCMArg any] completionHandler:[OCMArg invokeBlock]]); + [SDLGlobals sharedGlobals].rpcVersion = [SDLVersion versionWithString:@"6.0.0"]; }); - it(@"should wait till image is on head unit and attempt to update without the image", ^{ - testManager.menuCells = @[textAndImageCell, submenuImageCell]; - - NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddCommand class]]; - NSArray *add = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; - SDLAddCommand *sentCommand = add.firstObject; - - NSPredicate *addSubmenuPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddSubMenu class]]; - NSArray *submenu = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addSubmenuPredicate]; - SDLAddSubMenu *sentSubmenu = submenu.firstObject; - - expect(add).to(haveCount(1)); - expect(submenu).to(haveCount(1)); - expect(sentCommand.cmdIcon.value).to(beNil()); - expect(sentSubmenu.menuIcon.value).to(beNil()); - }); - }); - }); - - describe(@"updating when a menu already exists with dynamic updates on", ^{ - beforeEach(^{ - testManager.dynamicMenuUpdatesMode = SDLDynamicMenuUpdatesModeForceOn; - OCMStub([mockFileManager uploadArtworks:[OCMArg any] completionHandler:[OCMArg invokeBlock]]); - }); - - it(@"should send deletes first", ^{ - testManager.menuCells = @[textOnlyCell]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - - testManager.menuCells = @[textAndImageCell]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - - NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteCommand class]]; - NSArray *deletes = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate]; - - NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLAddCommand class]]; - NSArray *adds = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; - - expect(deletes).to(haveCount(1)); - expect(adds).to(haveCount(2)); - }); - - it(@"should send dynamic deletes first then dynamic adds case with 2 submenu cells", ^{ - testManager.menuCells = @[textOnlyCell, submenuCell, submenuImageCell]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - - testManager.menuCells = @[submenuCell, submenuImageCell, textOnlyCell]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - - NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteCommand class]]; - NSArray *deletes = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate]; - - NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLAddCommand class]]; - NSArray *adds = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; - - NSPredicate *addSubmenuPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddSubMenu class]]; - NSArray *submenu = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addSubmenuPredicate]; - - expect(deletes).to(haveCount(1)); - expect(adds).to(haveCount(5)); - expect(submenu).to(haveCount(2)); - }); - - it(@"should send dynamic deletes first then dynamic adds when removing one submenu cell", ^{ - testManager.menuCells = @[textOnlyCell, textAndImageCell, submenuCell, submenuImageCell]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - - testManager.menuCells = @[textOnlyCell, textAndImageCell, submenuCell]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - - NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteCommand class]]; - NSArray *deletes = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate]; - - NSPredicate *deleteSubCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteSubMenu class]]; - NSArray *subDeletes = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteSubCommandPredicate]; - - NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLAddCommand class]]; - NSArray *adds = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; - - NSPredicate *addSubmenuPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddSubMenu class]]; - NSArray *submenu = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addSubmenuPredicate]; - - expect(deletes).to(haveCount(0)); - expect(subDeletes).to(haveCount(1)); - expect(adds).to(haveCount(5)); - expect(submenu).to(haveCount(2)); - }); - - it(@"should send dynamic deletes first then dynamic adds when adding one new cell", ^{ - testManager.menuCells = @[textOnlyCell, textAndImageCell, submenuCell, submenuImageCell]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + it(@"should should queue a menu configuration update", ^{ + testManager.menuConfiguration = testMenuConfiguration; - testManager.menuCells = @[textOnlyCell, textAndImageCell, submenuCell, submenuImageCell, textOnlyCell2]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - - NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteCommand class]]; - NSArray *deletes = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate]; - - NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLAddCommand class]]; - NSArray *adds = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; - - NSPredicate *addSubmenuPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddSubMenu class]]; - NSArray *submenu = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addSubmenuPredicate]; - - expect(deletes).to(haveCount(0)); - expect(adds).to(haveCount(6)); - expect(submenu).to(haveCount(2)); - }); - - it(@"should send dynamic deletes first then dynamic adds when cells stay the same", ^{ - testManager.menuCells = @[textOnlyCell, textOnlyCell2, textAndImageCell]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - - testManager.menuCells = @[textOnlyCell, textOnlyCell2, textAndImageCell]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - - NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteCommand class]]; - NSArray *deletes = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate]; + expect(testManager.menuConfiguration).to(equal(testMenuConfiguration)); + expect(testManager.transactionQueue.operationCount).to(equal(1)); + }); - NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLAddCommand class]]; - NSArray *adds = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; + context(@"when queueing a second task after the first", ^{ + it(@"should cancel the first task", ^{ + testManager.menuConfiguration = testMenuConfiguration; + testManager.menuConfiguration = [[SDLMenuConfiguration alloc] initWithMainMenuLayout:SDLMenuLayoutList defaultSubmenuLayout:SDLMenuLayoutList]; - expect(deletes).to(haveCount(0)); - expect(adds).to(haveCount(3)); + expect(testManager.transactionQueue.operationCount).to(equal(2)); + expect(testManager.transactionQueue.operations[0].isCancelled).to(beTrue()); + }); + }); }); }); - describe(@"updating when a menu already exists with dynamic updates off", ^{ + describe(@"opening the menu", ^{ beforeEach(^{ - testManager.dynamicMenuUpdatesMode = SDLDynamicMenuUpdatesModeForceOff; - OCMStub([mockFileManager uploadArtworks:[OCMArg any] completionHandler:[OCMArg invokeBlock]]); - }); - - it(@"should send deletes first", ^{ - testManager.menuCells = @[textOnlyCell]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - - testManager.menuCells = @[textAndImageCell]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - - NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteCommand class]]; - NSArray *deletes = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate]; - - NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLAddCommand class]]; - NSArray *adds = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; - - expect(deletes).to(haveCount(1)); - expect(adds).to(haveCount(2)); - }); - - it(@"should deletes first case 2", ^{ - testManager.menuCells = @[textOnlyCell, textAndImageCell]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - - testManager.menuCells = @[textAndImageCell, textOnlyCell]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - - NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteCommand class]]; - NSArray *deletes = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate]; - - NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLAddCommand class]]; - NSArray *adds = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; - - expect(deletes).to(haveCount(2)); - expect(adds).to(haveCount(4)); + testManager.currentHMILevel = SDLHMILevelFull; + testManager.currentSystemContext = SDLSystemContextMain; }); - it(@"should send deletes first case 3", ^{ - testManager.menuCells = @[textOnlyCell, textAndImageCell, submenuCell, submenuImageCell]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + context(@"when open menu RPC can be sent", ^{ + beforeEach(^{ + SDLVersion *oldVersion = [SDLVersion versionWithMajor:6 minor:0 patch:0]; + id globalMock = OCMPartialMock([SDLGlobals sharedGlobals]); + OCMStub([globalMock rpcVersion]).andReturn(oldVersion); + }); - testManager.menuCells = @[textOnlyCell, textAndImageCell, submenuCell]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + // should queue an open menu operation for the main menu + it(@"should queue an open menu operation for the main menu", ^{ + BOOL canSendRPC = [testManager openMenu:nil]; - NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteCommand class]]; - NSArray *deletes = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate]; + expect(testManager.transactionQueue.operationCount).to(equal(1)); + expect(canSendRPC).to(equal(YES)); + }); - NSPredicate *deleteSubCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteSubMenu class]]; - NSArray *subDeletes = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteSubCommandPredicate]; + // should queue an open menu operation for a submenu cell + it(@"should queue an open menu operation for a submenu cell", ^ { + testManager.menuCells = @[submenuCell]; + BOOL canSendRPC = [testManager openMenu:submenuCell]; - NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLAddCommand class]]; - NSArray *adds = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; + expect(testManager.transactionQueue.operationCount).to(equal(2)); + expect(canSendRPC).to(equal(YES)); + }); - NSPredicate *addSubmenuPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddSubMenu class]]; - NSArray *submenu = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addSubmenuPredicate]; + it(@"should cancel the first task if a second is queued", ^{ + testManager.menuCells = @[submenuCell]; + [testManager openMenu:nil]; + [testManager openMenu:submenuCell]; - expect(deletes).to(haveCount(2)); - expect(subDeletes).to(haveCount(2)); - expect(adds).to(haveCount(9)); - expect(submenu).to(haveCount(3)); + expect(testManager.transactionQueue.operationCount).to(equal(3)); + expect(testManager.transactionQueue.operations[1].isCancelled).to(beTrue()); + }); }); - it(@"should send deletes first case 4", ^{ - testManager.menuCells = @[textOnlyCell, textAndImageCell, submenuCell]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - - testManager.menuCells = @[textOnlyCell, textAndImageCell, submenuCell, textOnlyCell2]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - - - NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteCommand class]]; - NSArray *deletes = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate]; - - NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLAddCommand class]]; - NSArray *adds = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; + context(@"when the open menu RPC can not be sent", ^{ + it(@"should not queue an open menu operation when cell has no subcells", ^ { + BOOL canSendRPC = [testManager openMenu:textOnlyCell]; - NSPredicate *addSubmenuPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddSubMenu class]]; - NSArray *submenu = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addSubmenuPredicate]; - - NSPredicate *deleteSubCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteSubMenu class]]; - NSArray *subDeletes = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteSubCommandPredicate]; + expect(testManager.transactionQueue.operationCount).to(equal(0)); + expect(canSendRPC).to(equal(NO)); + }); - expect(deletes).to(haveCount(2)); - expect(adds).to(haveCount(9)); - expect(submenu).to(haveCount(2)); - expect(subDeletes).to(haveCount(1)); - }); + it(@"should not queue an open menu operation when RPC version is not at least 6.0.0", ^ { + SDLVersion *oldVersion = [SDLVersion versionWithMajor:5 minor:0 patch:0]; + id globalMock = OCMPartialMock([SDLGlobals sharedGlobals]); + OCMStub([globalMock rpcVersion]).andReturn(oldVersion); - it(@"should deletes first case 5", ^{ - testManager.menuCells = @[textOnlyCell, textOnlyCell2, textAndImageCell]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + BOOL canSendRPC = [testManager openMenu:submenuCell]; - testManager.menuCells = @[textOnlyCell, textOnlyCell2, textAndImageCell]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + expect(testManager.transactionQueue.operationCount).to(equal(0)); + expect(canSendRPC).to(equal(NO)); + }); - NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteCommand class]]; - NSArray *deletes = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate]; + it(@"should not queue an open menu operation when the cell is not in the menu array", ^ { + SDLVersion *oldVersion = [SDLVersion versionWithMajor:6 minor:0 patch:0]; + id globalMock = OCMPartialMock([SDLGlobals sharedGlobals]); + OCMStub([globalMock rpcVersion]).andReturn(oldVersion); - NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLAddCommand class]]; - NSArray *adds = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; + BOOL canSendRPC = [testManager openMenu:submenuCell]; - expect(deletes).to(haveCount(3)); - expect(adds).to(haveCount(6)); + expect(testManager.transactionQueue.operationCount).to(equal(0)); + expect(canSendRPC).to(equal(NO)); + }); }); }); }); @@ -725,8 +331,9 @@ describe(@"menu manager", ^{ cellCalled = YES; testTriggerSource = triggerSource; }]; + cellWithHandler.cellId = 1; - testManager.menuCells = @[cellWithHandler]; + testManager.currentMenuCells = @[cellWithHandler]; }); it(@"should call the cell handler", ^{ @@ -748,10 +355,14 @@ describe(@"menu manager", ^{ cellCalled = YES; testTriggerSource = triggerSource; }]; + cellWithHandler.cellId = 2; SDLMenuCell *submenuCell = [[SDLMenuCell alloc] initWithTitle:@"Submenu" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil submenuLayout:SDLMenuLayoutTiles subCells:@[cellWithHandler]]; + submenuCell.cellId = 1; + + cellWithHandler.parentCellId = 1; - testManager.menuCells = @[submenuCell]; + testManager.currentMenuCells = @[submenuCell]; }); it(@"should call the cell handler", ^{ @@ -767,154 +378,6 @@ describe(@"menu manager", ^{ }); }); }); - - describe(@"updating the menu configuration", ^{ - beforeEach(^{ - testManager.currentHMILevel = SDLHMILevelFull; - testManager.currentSystemContext = SDLSystemContextMain; - }); - - context(@"if the connection RPC version is less than 6.0.0", ^{ - beforeEach(^{ - [SDLGlobals sharedGlobals].rpcVersion = [SDLVersion versionWithString:@"5.0.0"]; - }); - - it(@"should fail to send a SetGlobalProperties RPC update", ^{ - testManager.menuConfiguration = testMenuConfiguration; - - expect(testManager.menuConfiguration).toNot(equal(testMenuConfiguration)); - expect(mockConnectionManager.receivedRequests).to(haveCount(0)); - }); - }); - - context(@"if the connection RPC version is greater than or equal to 6.0.0", ^{ - beforeEach(^{ - [SDLGlobals sharedGlobals].rpcVersion = [SDLVersion versionWithString:@"6.0.0"]; - }); - - it(@"should send a SetGlobalProperties RPC update", ^{ - testManager.menuConfiguration = testMenuConfiguration; - - expect(testManager.menuConfiguration).to(equal(testMenuConfiguration)); - expect(mockConnectionManager.receivedRequests).to(haveCount(1)); - - SDLSetGlobalPropertiesResponse *response = [[SDLSetGlobalPropertiesResponse alloc] init]; - response.success = @YES; - [mockConnectionManager respondToLastRequestWithResponse:response]; - - expect(testManager.menuConfiguration).to(equal(testMenuConfiguration)); - }); - }); - }); - - context(@"when the manager stops", ^{ - beforeEach(^{ - [testManager stop]; - }); - - it(@"should reset correctly", ^{ - expect(testManager.connectionManager).to(equal(mockConnectionManager)); - expect(testManager.fileManager).to(equal(mockFileManager)); - - expect(testManager.menuCells).to(beEmpty()); - expect(testManager.currentHMILevel).to(beNil()); - expect(testManager.inProgressUpdate).to(beNil()); - expect(testManager.hasQueuedUpdate).to(beFalse()); - expect(testManager.waitingOnHMIUpdate).to(beFalse()); - expect(testManager.lastMenuId).to(equal(1)); - expect(testManager.oldMenuCells).to(beEmpty()); - expect(testManager.waitingUpdateMenuCells).to(beEmpty()); - expect(testManager.menuConfiguration).toNot(beNil()); - }); - }); - - describe(@"ShowMenu RPC", ^{ - beforeEach(^{ - testManager.currentHMILevel = SDLHMILevelFull; - testManager.currentSystemContext = SDLSystemContextMain; - }); - - context(@"when open menu RPC can be sent", ^{ - beforeEach(^{ - SDLVersion *oldVersion = [SDLVersion versionWithMajor:6 minor:0 patch:0]; - id globalMock = OCMPartialMock([SDLGlobals sharedGlobals]); - OCMStub([globalMock rpcVersion]).andReturn(oldVersion); - }); - - it(@"should send showAppMenu RPC", ^{ - BOOL canSendRPC = [testManager openMenu]; - - NSPredicate *showMenu = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLShowAppMenu class]]; - NSArray *openMenu = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:showMenu]; - - expect(mockConnectionManager.receivedRequests).toNot(beEmpty()); - expect(openMenu).to(haveCount(1)); - expect(canSendRPC).to(equal(YES)); - }); - - it(@"should send showAppMenu RPC with cellID", ^ { - testManager.menuCells = @[submenuCell]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; - - BOOL canSendRPC = [testManager openSubmenu:submenuCell]; - - NSPredicate *addSubmenuPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLShowAppMenu class]]; - NSArray *openMenu = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addSubmenuPredicate]; - - expect(mockConnectionManager.receivedRequests).toNot(beEmpty()); - expect(openMenu).to(haveCount(1)); - expect(canSendRPC).to(equal(YES)); - }); - }); - - context(@"when open menu RPC can not be sent", ^{ - it(@"should not send a showAppMenu RPC when cell has no subcells", ^ { - BOOL canSendRPC = [testManager openSubmenu:textOnlyCell]; - - NSPredicate *addSubmenuPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLShowAppMenu class]]; - NSArray *openMenu = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addSubmenuPredicate]; - - expect(mockConnectionManager.receivedRequests).to(beEmpty()); - expect(openMenu).to(haveCount(0)); - expect(canSendRPC).to(equal(NO)); - }); - - it(@"should not send a showAppMenu RPC when RPC verison is not at least 6.0.0", ^ { - SDLVersion *oldVersion = [SDLVersion versionWithMajor:5 minor:0 patch:0]; - id globalMock = OCMPartialMock([SDLGlobals sharedGlobals]); - OCMStub([globalMock rpcVersion]).andReturn(oldVersion); - - BOOL canSendRPC = [testManager openSubmenu:submenuCell]; - - NSPredicate *addSubmenuPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLShowAppMenu class]]; - NSArray *openMenu = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addSubmenuPredicate]; - - expect(mockConnectionManager.receivedRequests).to(beEmpty()); - expect(openMenu).to(haveCount(0)); - expect(canSendRPC).to(equal(NO)); - }); - - it(@"should not send a showAppMenu RPC when the cell is not in the menu array", ^ { - SDLVersion *oldVersion = [SDLVersion versionWithMajor:6 minor:0 patch:0]; - id globalMock = OCMPartialMock([SDLGlobals sharedGlobals]); - OCMStub([globalMock rpcVersion]).andReturn(oldVersion); - - BOOL canSendRPC = [testManager openSubmenu:submenuCell]; - - NSPredicate *addSubmenuPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLShowAppMenu class]]; - NSArray *openMenu = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addSubmenuPredicate]; - - expect(mockConnectionManager.receivedRequests).to(beEmpty()); - expect(openMenu).to(haveCount(0)); - expect(canSendRPC).to(equal(NO)); - }); - }); - }); - - afterEach(^{ - testManager = nil; - }); }); QuickSpecEnd diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLMenuReplaceOperationSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLMenuReplaceOperationSpec.m new file mode 100644 index 000000000..bb3c662bb --- /dev/null +++ b/SmartDeviceLinkTests/DevAPISpecs/SDLMenuReplaceOperationSpec.m @@ -0,0 +1,745 @@ +// +// SDLMenuReplaceOperationSpec.m +// SmartDeviceLinkTests +// +// Created by Joel Fischer on 2/16/21. +// Copyright © 2021 smartdevicelink. All rights reserved. +// + +#import <Nimble/Nimble.h> +#import <OCMock/OCMock.h> +#import <Quick/Quick.h> + +#import <SmartDeviceLink/SmartDeviceLink.h> +#import "SDLGlobals.h" +#import "SDLMenuReplaceOperation.h" +#import "SDLMenuManagerPrivateConstants.h" +#import "SDLMenuReplaceUtilities.h" +#import "TestConnectionManager.h" + +@interface SDLMenuCell () + +@property (assign, nonatomic) UInt32 parentCellId; +@property (assign, nonatomic) UInt32 cellId; +@property (strong, nonatomic, readwrite) NSString *uniqueTitle; + +@property (copy, nonatomic, readwrite) NSString *title; +@property (strong, nonatomic, readwrite, nullable) SDLArtwork *icon; +@property (copy, nonatomic, readwrite, nullable) NSArray<NSString *> *voiceCommands; +@property (copy, nonatomic, readwrite, nullable) NSString *secondaryText; +@property (copy, nonatomic, readwrite, nullable) NSString *tertiaryText; +@property (strong, nonatomic, readwrite, nullable) SDLArtwork *secondaryArtwork; +@property (copy, nonatomic, readwrite, nullable) NSArray<SDLMenuCell *> *subCells; +@property (copy, nonatomic, readwrite, nullable) SDLMenuCellSelectionHandler handler; + +@end + +QuickSpecBegin(SDLMenuReplaceOperationSpec) + +describe(@"a menu replace operation", ^{ + __block SDLMenuReplaceOperation *testOp = nil; + + __block TestConnectionManager *testConnectionManager = nil; + __block SDLFileManager *testFileManager = nil; + __block SDLMenuConfiguration *testMenuConfiguration = nil; + __block NSArray<SDLMenuCell *> *testCurrentMenu = nil; + __block NSArray<SDLMenuCell *> *testNewMenu = nil; + + SDLTextField *commandSecondaryTextField = [[SDLTextField alloc] initWithName:SDLTextFieldNameMenuCommandSecondaryText characterSet:SDLCharacterSetUtf8 width:200 rows:1]; + SDLTextField *commandTertiaryTextField = [[SDLTextField alloc] initWithName:SDLTextFieldNameMenuCommandTertiaryText characterSet:SDLCharacterSetUtf8 width:200 rows:1]; + SDLTextField *submenuSecondaryTextField = [[SDLTextField alloc] initWithName:SDLTextFieldNameMenuSubMenuSecondaryText characterSet:SDLCharacterSetUtf8 width:200 rows:1]; + SDLTextField *submenuTertiaryTextField = [[SDLTextField alloc] initWithName:SDLTextFieldNameMenuSubMenuTertiaryText characterSet:SDLCharacterSetUtf8 width:200 rows:1]; + SDLImageField *commandImageField = [[SDLImageField alloc] initWithName:SDLImageFieldNameCommandIcon imageTypeSupported:@[SDLFileTypePNG] imageResolution:nil]; + SDLImageField *submenuImageField = [[SDLImageField alloc] initWithName:SDLImageFieldNameSubMenuIcon imageTypeSupported:@[SDLFileTypePNG] imageResolution:nil]; + __block SDLWindowCapability *testWindowCapability = [[SDLWindowCapability alloc] initWithWindowID:@0 textFields:@[commandSecondaryTextField, commandTertiaryTextField, submenuSecondaryTextField, submenuTertiaryTextField] imageFields:@[commandImageField, submenuImageField] imageTypeSupported:nil templatesAvailable:nil numCustomPresetsAvailable:nil buttonCapabilities:nil softButtonCapabilities:nil menuLayoutsAvailable:nil dynamicUpdateCapabilities:nil keyboardCapabilities:nil]; + __block SDLWindowCapability *testTitleOnlyWindowCapability = [[SDLWindowCapability alloc] initWithWindowID:@0 textFields:@[] imageFields:@[commandImageField, submenuImageField] imageTypeSupported:nil templatesAvailable:nil numCustomPresetsAvailable:nil buttonCapabilities:nil softButtonCapabilities:nil menuLayoutsAvailable:nil dynamicUpdateCapabilities:nil keyboardCapabilities:nil]; + + __block SDLArtwork *testArtwork = nil; + __block SDLArtwork *testArtwork2 = nil; + __block SDLArtwork *testArtwork3 = nil; + + __block SDLMenuCell *textOnlyCell = nil; + __block SDLMenuCell *textOnlyCell2 = nil; + __block SDLMenuCell *textAndImageCell = nil; + __block SDLMenuCell *submenuCell = nil; + __block SDLMenuCell *submenuCellReversed = nil; + __block SDLMenuCell *submenuImageCell = nil; + + __block SDLAddCommandResponse *addCommandSuccessResponse = nil; + __block SDLAddSubMenuResponse *addSubMenuSuccessResponse = nil; + __block SDLDeleteCommandResponse *deleteCommandSuccessResponse = nil; + __block SDLDeleteSubMenuResponse *deleteSubMenuSuccessResponse = nil; + + __block NSMutableArray<SDLMenuCell *> *basicCellArray = [NSMutableArray array]; + + __block NSArray<SDLMenuCell *> *resultMenuCells = nil; + __block NSError *resultError = nil; + __block SDLCurrentMenuUpdatedBlock testCurrentMenuUpdatedBlock = nil; + + __block SDLMenuReplaceUtilities *mockReplaceUtilities = nil; + + beforeSuite(^{ + for (int i = 0; i < 50; i++) { + NSString *cellTitle = [NSString stringWithFormat:@"Cell %@", @(i)]; + [basicCellArray addObject:[[SDLMenuCell alloc] initWithTitle:cellTitle secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil voiceCommands:@[cellTitle] handler:^(SDLTriggerSource _Nonnull triggerSource) { + NSLog(@"%@ pressed", cellTitle); + }]]; + } + }); + + beforeEach(^{ + [SDLGlobals sharedGlobals].rpcVersion = [SDLVersion versionWithMajor:7 minor:1 patch:0]; + + testArtwork = [[SDLArtwork alloc] initWithData:[@"Test data" dataUsingEncoding:NSUTF8StringEncoding] name:@"some artwork name" fileExtension:@"png" persistent:NO]; + testArtwork2 = [[SDLArtwork alloc] initWithData:[@"Test data 2" dataUsingEncoding:NSUTF8StringEncoding] name:@"some artwork name 2" fileExtension:@"png" persistent:NO]; + testArtwork3 = [[SDLArtwork alloc] initWithData:[@"Test data 3" dataUsingEncoding:NSUTF8StringEncoding] name:@"some artwork name" fileExtension:@"png" persistent:NO]; + testArtwork3.overwrite = YES; + + textOnlyCell = [[SDLMenuCell alloc] initWithTitle:@"Test 1" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; + textOnlyCell2 = [[SDLMenuCell alloc] initWithTitle:@"Test 5" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; + textAndImageCell = [[SDLMenuCell alloc] initWithTitle:@"Test 2" secondaryText:nil tertiaryText:nil icon:testArtwork secondaryArtwork:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; + submenuCell = [[SDLMenuCell alloc] initWithTitle:@"Cell with Subcells" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil submenuLayout:nil subCells:basicCellArray]; + submenuCellReversed = [[SDLMenuCell alloc] initWithTitle:@"Cell with Subcells" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil submenuLayout:nil subCells:basicCellArray.reverseObjectEnumerator.allObjects]; + submenuImageCell = [[SDLMenuCell alloc] initWithTitle:@"Cell with Image and Subcell" secondaryText:nil tertiaryText:nil icon:testArtwork2 secondaryArtwork:nil submenuLayout:SDLMenuLayoutTiles subCells:@[textOnlyCell]]; + + addCommandSuccessResponse = [[SDLAddCommandResponse alloc] init]; + addCommandSuccessResponse.success = @YES; + addCommandSuccessResponse.resultCode = SDLResultSuccess; + addSubMenuSuccessResponse = [[SDLAddSubMenuResponse alloc] init]; + addSubMenuSuccessResponse.success = @YES; + addSubMenuSuccessResponse.resultCode = SDLResultSuccess; + deleteCommandSuccessResponse = [[SDLDeleteCommandResponse alloc] init]; + deleteCommandSuccessResponse.success = @YES; + deleteCommandSuccessResponse.resultCode = SDLResultSuccess; + deleteSubMenuSuccessResponse = [[SDLDeleteSubMenuResponse alloc] init]; + deleteSubMenuSuccessResponse.success = @YES; + deleteSubMenuSuccessResponse.resultCode = SDLResultSuccess; + + testOp = nil; + testConnectionManager = [[TestConnectionManager alloc] init]; + testFileManager = OCMClassMock([SDLFileManager class]); + + testMenuConfiguration = [[SDLMenuConfiguration alloc] initWithMainMenuLayout:SDLMenuLayoutList defaultSubmenuLayout:SDLMenuLayoutList]; + testCurrentMenu = @[]; + testNewMenu = nil; + + resultMenuCells = nil; + resultError = nil; + testCurrentMenuUpdatedBlock = ^(NSArray<SDLMenuCell *> *currentMenuCells, NSError *error) { + resultMenuCells = currentMenuCells; + resultError = error; + }; + + mockReplaceUtilities = OCMClassMock([SDLMenuReplaceUtilities class]); + }); + + context(@"sending initial batch of cells", ^{ + context(@"when setting no cells", ^{ + it(@"should finish without doing anything", ^{ + testOp = [[SDLMenuReplaceOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager windowCapability:testWindowCapability menuConfiguration:testMenuConfiguration currentMenu:testCurrentMenu updatedMenu:testNewMenu compatibilityModeEnabled:YES currentMenuUpdatedHandler:testCurrentMenuUpdatedBlock]; + [testOp start]; + + expect(testConnectionManager.receivedRequests).to(beEmpty()); + expect(testOp.isFinished).to(beTrue()); + expect(resultError).to(beNil()); + expect(resultMenuCells).to(beEmpty()); + }); + }); + + context(@"when starting while cancelled", ^{ + it(@"should finish without doing anything", ^{ + testOp = [[SDLMenuReplaceOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager windowCapability:testWindowCapability menuConfiguration:testMenuConfiguration currentMenu:testCurrentMenu updatedMenu:testNewMenu compatibilityModeEnabled:YES currentMenuUpdatedHandler:testCurrentMenuUpdatedBlock]; + [testOp cancel]; + [testOp start]; + + expect(testConnectionManager.receivedRequests).to(beEmpty()); + expect(testOp.isFinished).to(beTrue()); + expect(resultError).to(beNil()); + expect(resultMenuCells).to(beNil()); + }); + }); + + context(@"when uploading a text-only cell", ^{ + beforeEach(^{ + testNewMenu = @[textOnlyCell]; + OCMStub([testFileManager fileNeedsUpload:[OCMArg any]]).andReturn(NO); + }); + + it(@"should properly send the RPCs and finish the operation", ^{ + testOp = [[SDLMenuReplaceOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager windowCapability:testWindowCapability menuConfiguration:testMenuConfiguration currentMenu:testCurrentMenu updatedMenu:testNewMenu compatibilityModeEnabled:YES currentMenuUpdatedHandler:testCurrentMenuUpdatedBlock]; + [testOp start]; + + OCMReject([testFileManager uploadArtworks:[OCMArg any] progressHandler:[OCMArg any] completionHandler:[OCMArg any]]); + + NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLDeleteCommand class]]; + NSArray *deletes = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate]; + expect(deletes).to(beEmpty()); + + NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddCommand class]]; + NSArray *add = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; + expect(add).to(haveCount(1)); + + [testConnectionManager respondToLastRequestWithResponse:addCommandSuccessResponse]; + [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + + expect(testOp.isFinished).to(beTrue()); + expect(resultError).to(beNil()); + expect(resultMenuCells).to(haveCount(1)); + }); + }); + + context(@"when uploading text and image cell", ^{ + beforeEach(^{ + testNewMenu = @[textAndImageCell]; + + OCMStub([testFileManager uploadArtworks:[OCMArg any] progressHandler:([OCMArg invokeBlockWithArgs:textAndImageCell.icon.name, @1.0, [NSNull null], nil]) completionHandler:([OCMArg invokeBlockWithArgs: @[textAndImageCell.icon.name], [NSNull null], nil])]); + }); + + // when the image is already on the head unit + context(@"when the image is already on the head unit", ^{ + beforeEach(^{ + OCMStub([testFileManager hasUploadedFile:[OCMArg isNotNil]]).andReturn(YES); + OCMStub([testFileManager fileNeedsUpload:[OCMArg isNotNil]]).andReturn(NO); + }); + + it(@"should properly update an image cell", ^{ + OCMReject([testFileManager uploadArtworks:[OCMArg any] progressHandler:[OCMArg any] completionHandler:[OCMArg any]]); + + + testOp = [[SDLMenuReplaceOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager windowCapability:testWindowCapability menuConfiguration:testMenuConfiguration currentMenu:testCurrentMenu updatedMenu:testNewMenu compatibilityModeEnabled:YES currentMenuUpdatedHandler:testCurrentMenuUpdatedBlock]; + [testOp start]; + + NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddCommand class]]; + NSArray *add = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; + SDLAddCommand *sentCommand = add.firstObject; + + expect(add).to(haveCount(1)); + expect(sentCommand.cmdIcon.value).to(equal(testArtwork.name)); + + [testConnectionManager respondToLastRequestWithResponse:addCommandSuccessResponse]; + [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + + expect(testOp.isFinished).to(beTrue()); + expect(resultError).to(beNil()); + expect(resultMenuCells).to(haveCount(1)); + }); + }); + + // when the image is not on the head unit + context(@"when the image is not on the head unit", ^{ + beforeEach(^{ + OCMStub([testFileManager hasUploadedFile:[OCMArg isNotNil]]).andReturn(NO); + OCMStub([testFileManager fileNeedsUpload:[OCMArg isNotNil]]).andReturn(YES); + }); + + it(@"should attempt to upload artworks then send the add", ^{ + testOp = [[SDLMenuReplaceOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager windowCapability:testWindowCapability menuConfiguration:testMenuConfiguration currentMenu:testCurrentMenu updatedMenu:testNewMenu compatibilityModeEnabled:YES currentMenuUpdatedHandler:testCurrentMenuUpdatedBlock]; + [testOp start]; + + OCMVerify([testFileManager uploadArtworks:[OCMArg any] progressHandler:[OCMArg any] completionHandler:[OCMArg any]]); + + NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLDeleteCommand class]]; + NSArray *deletesArray = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate]; + expect(deletesArray).to(beEmpty()); + + NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddCommand class]]; + NSArray *addsArray = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; + expect(addsArray).toNot(beEmpty()); + + [testConnectionManager respondToLastRequestWithResponse:addCommandSuccessResponse]; + [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + + expect(testOp.isFinished).to(beTrue()); + expect(resultError).to(beNil()); + expect(resultMenuCells).to(haveCount(1)); + }); + }); + }); + + context(@"when uploading a cell with subcells", ^{ + beforeEach(^{ + testNewMenu = @[submenuCell]; + }); + + it(@"should send an appropriate number of AddSubmenu and AddCommandRequests", ^{ + testOp = [[SDLMenuReplaceOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager windowCapability:testWindowCapability menuConfiguration:testMenuConfiguration currentMenu:testCurrentMenu updatedMenu:testNewMenu compatibilityModeEnabled:YES currentMenuUpdatedHandler:testCurrentMenuUpdatedBlock]; + [testOp start]; + + [testConnectionManager respondToLastRequestWithResponse:addSubMenuSuccessResponse]; + [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + + NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddCommand class]]; + NSArray *adds = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; + + NSPredicate *submenuCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddSubMenu class]]; + NSArray *submenus = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:submenuCommandPredicate]; + + expect(adds).to(haveCount(50)); + expect(submenus).to(haveCount(1)); + + [testConnectionManager respondToRequestWithResponse:addCommandSuccessResponse requestNumber:1 error:nil]; + [testConnectionManager respondToRequestWithResponse:addCommandSuccessResponse requestNumber:2 error:nil]; + [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + + expect(testOp.isFinished).to(beTrue()); + expect(resultError).to(beNil()); + expect(resultMenuCells).to(haveCount(1)); + expect(resultMenuCells[0].subCells).to(haveCount(2)); + }); + }); + }); + + context(@"updating a menu without dynamic updates", ^{ + describe(@"basic cell updates", ^{ + context(@"adding a text cell", ^{ + beforeEach(^{ + testCurrentMenu = [[NSArray alloc] initWithArray:@[textOnlyCell] copyItems:YES]; + [SDLMenuReplaceUtilities addIdsToMenuCells:testCurrentMenu parentId:ParentIdNotFound]; + + testNewMenu = [[NSArray alloc] initWithArray:@[textOnlyCell, textOnlyCell2] copyItems:YES]; + }); + + it(@"should send a delete and two adds", ^{ + testOp = [[SDLMenuReplaceOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager windowCapability:testWindowCapability menuConfiguration:testMenuConfiguration currentMenu:testCurrentMenu updatedMenu:testNewMenu compatibilityModeEnabled:YES currentMenuUpdatedHandler:testCurrentMenuUpdatedBlock]; + [testOp start]; + + [testConnectionManager respondToLastRequestWithResponse:deleteCommandSuccessResponse]; + [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + + [testConnectionManager respondToRequestWithResponse:addCommandSuccessResponse requestNumber:1 error:nil]; + [testConnectionManager respondToRequestWithResponse:addCommandSuccessResponse requestNumber:2 error:nil]; + [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + + NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteCommand class]]; + NSArray *deletes = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate]; + + NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLAddCommand class]]; + NSArray *adds = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; + + expect(deletes).to(haveCount(1)); + expect(adds).to(haveCount(2)); + + expect(testOp.isFinished).to(beTrue()); + expect(resultError).to(beNil()); + expect(resultMenuCells).to(haveCount(2)); + expect(resultMenuCells[0]).to(equal(textOnlyCell)); + expect(resultMenuCells[1]).to(equal(textOnlyCell2)); + }); + }); + + context(@"when all cells remain the same", ^{ + beforeEach(^{ + testCurrentMenu = [[NSArray alloc] initWithArray:@[textOnlyCell, textOnlyCell2, textAndImageCell] copyItems:YES]; + [SDLMenuReplaceUtilities addIdsToMenuCells:testCurrentMenu parentId:ParentIdNotFound]; + + testNewMenu = [[NSArray alloc] initWithArray:@[textOnlyCell, textOnlyCell2, textAndImageCell] copyItems:YES]; + }); + + it(@"should delete all cells and add the new ones", ^{ + testOp = [[SDLMenuReplaceOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager windowCapability:testWindowCapability menuConfiguration:testMenuConfiguration currentMenu:testCurrentMenu updatedMenu:testNewMenu compatibilityModeEnabled:YES currentMenuUpdatedHandler:testCurrentMenuUpdatedBlock]; + [testOp start]; + + [testConnectionManager respondToRequestWithResponse:deleteCommandSuccessResponse requestNumber:0 error:nil]; + [testConnectionManager respondToRequestWithResponse:deleteCommandSuccessResponse requestNumber:1 error:nil]; + [testConnectionManager respondToRequestWithResponse:deleteCommandSuccessResponse requestNumber:2 error:nil]; + [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + + [testConnectionManager respondToRequestWithResponse:addCommandSuccessResponse requestNumber:3 error:nil]; + [testConnectionManager respondToRequestWithResponse:addCommandSuccessResponse requestNumber:4 error:nil]; + [testConnectionManager respondToRequestWithResponse:addCommandSuccessResponse requestNumber:5 error:nil]; + [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + + NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteCommand class]]; + NSArray *deletes = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate]; + + NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLAddCommand class]]; + NSArray *adds = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; + + expect(deletes).to(haveCount(3)); + expect(adds).to(haveCount(3)); + + expect(testOp.isFinished).to(beTrue()); + expect(resultError).to(beNil()); + expect(resultMenuCells).to(haveCount(3)); + }); + }); + }); + + describe(@"unique cell updates", ^{ + context(@"with cell uniqueness", ^{ + beforeEach(^{ + [SDLGlobals sharedGlobals].rpcVersion = [SDLVersion versionWithMajor:7 minor:1 patch:0]; + }); + + context(@"when cells have the same title but are unique", ^{ + beforeEach(^{ + testCurrentMenu = @[]; + + SDLMenuCell *textOnlyCellDupe = [textOnlyCell copy]; + textOnlyCellDupe.secondaryText = @"Secondary Text"; + + testNewMenu = @[textOnlyCell, textOnlyCellDupe]; + }); + + it(@"should send the cells unchanged", ^{ + testOp = [[SDLMenuReplaceOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager windowCapability:testWindowCapability menuConfiguration:testMenuConfiguration currentMenu:testCurrentMenu updatedMenu:testNewMenu compatibilityModeEnabled:NO currentMenuUpdatedHandler:testCurrentMenuUpdatedBlock]; + [testOp start]; + + NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteCommand class]]; + NSArray *deletes = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate]; + + NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLAddCommand class]]; + NSArray *adds = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; + + expect(deletes).to(haveCount(0)); + expect(adds).to(haveCount(2)); + + [testConnectionManager respondToRequestWithResponse:addCommandSuccessResponse requestNumber:0 error:nil]; + [testConnectionManager respondToRequestWithResponse:addCommandSuccessResponse requestNumber:1 error:nil]; + [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + + expect(testOp.isFinished).to(beTrue()); + expect(resultError).to(beNil()); + expect(resultMenuCells).to(haveCount(2)); + expect(resultMenuCells[0].uniqueTitle).to(equal(textOnlyCell.uniqueTitle)); + expect(resultMenuCells[0].secondaryText).to(beNil()); + expect(resultMenuCells[1].uniqueTitle).to(equal(textOnlyCell.uniqueTitle)); + expect(resultMenuCells[1].secondaryText).toNot(beNil()); + }); + }); + + context(@"when cells are unique but are identical when stripped", ^{ + beforeEach(^{ + testCurrentMenu = @[]; + + SDLMenuCell *textOnlyCellDupe = [textOnlyCell copy]; + textOnlyCellDupe.secondaryText = @"Secondary Text"; + + testNewMenu = [[NSArray alloc] initWithArray:@[textOnlyCell, textOnlyCellDupe] copyItems:YES]; + }); + + it(@"should change the second cell's title", ^{ + testOp = [[SDLMenuReplaceOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager windowCapability:testTitleOnlyWindowCapability menuConfiguration:testMenuConfiguration currentMenu:testCurrentMenu updatedMenu:testNewMenu compatibilityModeEnabled:NO currentMenuUpdatedHandler:testCurrentMenuUpdatedBlock]; + [testOp start]; + + NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteCommand class]]; + NSArray *deletes = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate]; + + NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLAddCommand class]]; + NSArray *adds = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; + + expect(deletes).to(haveCount(0)); + expect(adds).to(haveCount(2)); + + [testConnectionManager respondToRequestWithResponse:addCommandSuccessResponse requestNumber:0 error:nil]; + [testConnectionManager respondToRequestWithResponse:addCommandSuccessResponse requestNumber:1 error:nil]; + [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + + expect(testOp.isFinished).to(beTrue()); + expect(resultError).to(beNil()); + expect(resultMenuCells).to(haveCount(2)); + expect(resultMenuCells[0].uniqueTitle).to(equal(textOnlyCell.uniqueTitle)); + expect(resultMenuCells[0].secondaryText).to(beNil()); + expect(resultMenuCells[1].uniqueTitle).toNot(equal(textOnlyCell.uniqueTitle)); + expect(resultMenuCells[1].secondaryText).toNot(beNil()); + }); + }); + }); + + context(@"without cell uniqueness", ^{ + beforeEach(^{ + [SDLGlobals sharedGlobals].rpcVersion = [SDLVersion versionWithMajor:7 minor:0 patch:0]; + }); + + context(@"when cells have the same title but are unique", ^{ + beforeEach(^{ + testCurrentMenu = @[]; + + SDLMenuCell *textOnlyCellDupe = [textOnlyCell copy]; + textOnlyCellDupe.secondaryText = @"Secondary Text"; + + testNewMenu = @[textOnlyCell, textOnlyCellDupe]; + }); + + it(@"should change the second cell's title", ^{ + testOp = [[SDLMenuReplaceOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager windowCapability:testWindowCapability menuConfiguration:testMenuConfiguration currentMenu:testCurrentMenu updatedMenu:testNewMenu compatibilityModeEnabled:NO currentMenuUpdatedHandler:testCurrentMenuUpdatedBlock]; + [testOp start]; + + NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteCommand class]]; + NSArray *deletes = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate]; + + NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLAddCommand class]]; + NSArray *adds = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; + + expect(deletes).to(haveCount(0)); + expect(adds).to(haveCount(2)); + + [testConnectionManager respondToRequestWithResponse:addCommandSuccessResponse requestNumber:0 error:nil]; + [testConnectionManager respondToRequestWithResponse:addCommandSuccessResponse requestNumber:1 error:nil]; + [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + + expect(testOp.isFinished).to(beTrue()); + expect(resultError).to(beNil()); + expect(resultMenuCells).to(haveCount(2)); + expect(resultMenuCells[0].uniqueTitle).to(equal(textOnlyCell.uniqueTitle)); + expect(resultMenuCells[0].secondaryText).to(beNil()); + expect(resultMenuCells[1].uniqueTitle).toNot(equal(textOnlyCell.uniqueTitle)); + expect(resultMenuCells[1].secondaryText).toNot(beNil()); + }); + }); + + context(@"when cells are unique but are identical when stripped", ^{ + beforeEach(^{ + testCurrentMenu = @[]; + + SDLMenuCell *textOnlyCellDupe = [textOnlyCell copy]; + textOnlyCellDupe.secondaryText = @"Secondary Text"; + + testNewMenu = [[NSArray alloc] initWithArray:@[textOnlyCell, textOnlyCellDupe] copyItems:YES]; + }); + + it(@"should change the second cell's title", ^{ + testOp = [[SDLMenuReplaceOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager windowCapability:testTitleOnlyWindowCapability menuConfiguration:testMenuConfiguration currentMenu:testCurrentMenu updatedMenu:testNewMenu compatibilityModeEnabled:NO currentMenuUpdatedHandler:testCurrentMenuUpdatedBlock]; + [testOp start]; + + NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteCommand class]]; + NSArray *deletes = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate]; + + NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLAddCommand class]]; + NSArray *adds = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; + + expect(deletes).to(haveCount(0)); + expect(adds).to(haveCount(2)); + + [testConnectionManager respondToRequestWithResponse:addCommandSuccessResponse requestNumber:0 error:nil]; + [testConnectionManager respondToRequestWithResponse:addCommandSuccessResponse requestNumber:1 error:nil]; + [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + + expect(testOp.isFinished).to(beTrue()); + expect(resultError).to(beNil()); + expect(resultMenuCells).to(haveCount(2)); + expect(resultMenuCells[0].uniqueTitle).to(equal(textOnlyCell.uniqueTitle)); + expect(resultMenuCells[0].secondaryText).to(beNil()); + expect(resultMenuCells[1].uniqueTitle).toNot(equal(textOnlyCell.uniqueTitle)); + expect(resultMenuCells[1].secondaryText).toNot(beNil()); + }); + }); + }); + }); + }); + + context(@"updating a menu with dynamic updates", ^{ + context(@"adding a text cell", ^{ + beforeEach(^{ + testCurrentMenu = @[textOnlyCell]; + testNewMenu = @[textOnlyCell, textOnlyCell2]; + }); + + it(@"should only send an add", ^{ + testOp = [[SDLMenuReplaceOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager windowCapability:testWindowCapability menuConfiguration:testMenuConfiguration currentMenu:testCurrentMenu updatedMenu:testNewMenu compatibilityModeEnabled:NO currentMenuUpdatedHandler:testCurrentMenuUpdatedBlock]; + [testOp start]; + + NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteCommand class]]; + NSArray *deletes = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate]; + + NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLAddCommand class]]; + NSArray *adds = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; + + expect(deletes).to(haveCount(0)); + expect(adds).to(haveCount(1)); + + [testConnectionManager respondToLastRequestWithResponse:addCommandSuccessResponse]; + [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + + expect(testOp.isFinished).to(beTrue()); + expect(resultError).to(beNil()); + expect(resultMenuCells).to(haveCount(2)); + }); + }); + + context(@"rearranging cells with subcells", ^{ + beforeEach(^{ + testCurrentMenu = [[NSArray alloc] initWithArray:@[textOnlyCell, submenuCell, submenuImageCell] copyItems:YES]; + [SDLMenuReplaceUtilities addIdsToMenuCells:testCurrentMenu parentId:ParentIdNotFound]; + + testNewMenu = [[NSArray alloc] initWithArray:@[submenuCell, submenuImageCell, textOnlyCell] copyItems:YES]; + + OCMStub([testFileManager uploadArtworks:[OCMArg any] progressHandler:([OCMArg invokeBlockWithArgs:textAndImageCell.icon.name, @1.0, [NSNull null], nil]) completionHandler:([OCMArg invokeBlockWithArgs: @[textAndImageCell.icon.name], [NSNull null], nil])]); + }); + + it(@"should send dynamic deletes first then dynamic adds case with 2 submenu cells", ^{ + testOp = [[SDLMenuReplaceOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager windowCapability:testWindowCapability menuConfiguration:testMenuConfiguration currentMenu:testCurrentMenu updatedMenu:testNewMenu compatibilityModeEnabled:NO currentMenuUpdatedHandler:testCurrentMenuUpdatedBlock]; + [testOp start]; + + // Delete textOnlyCell + [testConnectionManager respondToLastRequestWithResponse:deleteCommandSuccessResponse]; + [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + expect(testOp.currentMenu).toNot(contain(textOnlyCell)); + + // Add textOnlyCell + [testConnectionManager respondToLastRequestWithResponse:addCommandSuccessResponse]; + [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + + NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteCommand class]]; + NSArray *deletes = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate]; + + NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLAddCommand class]]; + NSArray *adds = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; + + NSPredicate *addSubmenuPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddSubMenu class]]; + NSArray *submenu = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addSubmenuPredicate]; + + expect(deletes).to(haveCount(1)); + expect(adds).to(haveCount(1)); + expect(submenu).to(haveCount(0)); + + expect(testOp.isFinished).to(beTrue()); + expect(resultError).to(beNil()); + expect(resultMenuCells).to(haveCount(3)); + }); + }); + + context(@"rearranging cells and their subcells", ^{ + beforeEach(^{ + testCurrentMenu = [[NSArray alloc] initWithArray:@[textOnlyCell, textAndImageCell, submenuCell] copyItems:YES]; + [SDLMenuReplaceUtilities addIdsToMenuCells:testCurrentMenu parentId:ParentIdNotFound]; + + testNewMenu = [[NSArray alloc] initWithArray:@[submenuCellReversed, textAndImageCell, textOnlyCell] copyItems:YES]; + + OCMStub([testFileManager uploadArtworks:[OCMArg any] progressHandler:([OCMArg invokeBlockWithArgs:textAndImageCell.icon.name, @1.0, [NSNull null], nil]) completionHandler:([OCMArg invokeBlockWithArgs: @[textAndImageCell.icon.name], [NSNull null], nil])]); + }); + + it(@"should sent the correct deletions and additions", ^{ + testOp = [[SDLMenuReplaceOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager windowCapability:testWindowCapability menuConfiguration:testMenuConfiguration currentMenu:testCurrentMenu updatedMenu:testNewMenu compatibilityModeEnabled:NO currentMenuUpdatedHandler:testCurrentMenuUpdatedBlock]; + [testOp start]; + + // Delete textOnlyCell and submenuCell + expect(testConnectionManager.receivedRequests).to(haveCount(2)); + expect(testConnectionManager.receivedRequests[0]).to(beAnInstanceOf(SDLDeleteCommand.class)); + expect(testConnectionManager.receivedRequests[1]).to(beAnInstanceOf(SDLDeleteSubMenu.class)); + + [testConnectionManager respondToRequestWithResponse:deleteCommandSuccessResponse requestNumber:0 error:nil]; + [testConnectionManager respondToRequestWithResponse:deleteSubMenuSuccessResponse requestNumber:1 error:nil]; + [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + expect(testOp.currentMenu).to(haveCount(1)); + + // Main Menu Add Command / Add Submenu + expect(testConnectionManager.receivedRequests).to(haveCount(4)); + + [testConnectionManager respondToRequestWithResponse:addSubMenuSuccessResponse requestNumber:2 error:nil]; + [testConnectionManager respondToRequestWithResponse:addCommandSuccessResponse requestNumber:3 error:nil]; + [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + expect(testOp.currentMenu).to(haveCount(3)); + + NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteCommand class]]; + NSArray *deletes = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate]; + + NSPredicate *deleteSubCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteSubMenu class]]; + NSArray *subDeletes = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteSubCommandPredicate]; + + NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLAddCommand class]]; + NSArray *adds = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; + + NSPredicate *addSubmenuPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddSubMenu class]]; + NSArray *submenu = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addSubmenuPredicate]; + + // Submenu add commands sent + expect(deletes).to(haveCount(1)); + expect(subDeletes).to(haveCount(1)); + expect(adds).to(haveCount(51)); + expect(submenu).to(haveCount(1)); + + // Respond to all 50 submenu add commands + for (NSUInteger i = 0; i < 50; i++) { + [testConnectionManager respondToRequestWithResponse:addCommandSuccessResponse requestNumber:(i + 4) error:nil]; + } + [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + + expect(testOp.isFinished).to(beTrue()); + expect(resultError).to(beNil()); + expect(resultMenuCells).to(haveCount(3)); + }); + }); + + context(@"removing a cell with subcells", ^{ + beforeEach(^{ + testCurrentMenu = [[NSArray alloc] initWithArray:@[textOnlyCell, textAndImageCell, submenuCell, submenuImageCell] copyItems:YES]; + [SDLMenuReplaceUtilities addIdsToMenuCells:testCurrentMenu parentId:ParentIdNotFound]; + + testNewMenu = [[NSArray alloc] initWithArray:@[textOnlyCell, textAndImageCell, submenuCell] copyItems:YES]; + + OCMStub([testFileManager uploadArtworks:[OCMArg any] progressHandler:([OCMArg invokeBlockWithArgs:textAndImageCell.icon.name, @1.0, [NSNull null], nil]) completionHandler:([OCMArg invokeBlockWithArgs: @[textAndImageCell.icon.name], [NSNull null], nil])]); + }); + + it(@"should send one deletion", ^{ + testOp = [[SDLMenuReplaceOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager windowCapability:testWindowCapability menuConfiguration:testMenuConfiguration currentMenu:testCurrentMenu updatedMenu:testNewMenu compatibilityModeEnabled:NO currentMenuUpdatedHandler:testCurrentMenuUpdatedBlock]; + [testOp start]; + + // Delete submenuImageCell + [testConnectionManager respondToLastRequestWithResponse:deleteSubMenuSuccessResponse]; + [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; + expect(testOp.currentMenu).toNot(contain(submenuImageCell)); + + NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteCommand class]]; + NSArray *deletes = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate]; + + NSPredicate *deleteSubCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteSubMenu class]]; + NSArray *subDeletes = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteSubCommandPredicate]; + + NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLAddCommand class]]; + NSArray *adds = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; + + NSPredicate *addSubmenuPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddSubMenu class]]; + NSArray *submenu = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addSubmenuPredicate]; + + expect(deletes).to(haveCount(0)); + expect(subDeletes).to(haveCount(1)); + expect(adds).to(haveCount(0)); + expect(submenu).to(haveCount(0)); + + expect(testOp.isFinished).to(beTrue()); + expect(resultError).to(beNil()); + expect(resultMenuCells).to(haveCount(3)); + }); + }); + + context(@"when cells remain the same", ^{ + __block BOOL secondHandlerCalled = NO; + + beforeEach(^{ + secondHandlerCalled = NO; + + testCurrentMenu = [[NSArray alloc] initWithArray:@[textOnlyCell, textOnlyCell2, textAndImageCell] copyItems:YES]; + [SDLMenuReplaceUtilities addIdsToMenuCells:testCurrentMenu parentId:ParentIdNotFound]; + + textOnlyCell.handler = ^(SDLTriggerSource triggerSource) { + secondHandlerCalled = YES; + }; + testNewMenu = [[NSArray alloc] initWithArray:@[textOnlyCell, textOnlyCell2, textAndImageCell] copyItems:YES]; + }); + + it(@"should not send deletes or adds, but should transfer handlers", ^{ + testOp = [[SDLMenuReplaceOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager windowCapability:testWindowCapability menuConfiguration:testMenuConfiguration currentMenu:testCurrentMenu updatedMenu:testNewMenu compatibilityModeEnabled:NO currentMenuUpdatedHandler:testCurrentMenuUpdatedBlock]; + [testOp start]; + + NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteCommand class]]; + NSArray *deletes = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate]; + + NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLAddCommand class]]; + NSArray *adds = [[testConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate]; + + expect(deletes).to(haveCount(0)); + expect(adds).to(haveCount(0)); + + expect(testOp.isFinished).to(beTrue()); + expect(resultError).to(beNil()); + expect(resultMenuCells).to(haveCount(3)); + + resultMenuCells[0].handler(SDLTriggerSourceMenu); + expect(secondHandlerCalled).to(beTrue()); + }); + }); + }); +}); + +QuickSpecEnd diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLMenuReplaceUtilitiesSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLMenuReplaceUtilitiesSpec.m new file mode 100644 index 000000000..51f6bca6a --- /dev/null +++ b/SmartDeviceLinkTests/DevAPISpecs/SDLMenuReplaceUtilitiesSpec.m @@ -0,0 +1,532 @@ +#import <Quick/Quick.h> +#import <Nimble/Nimble.h> +#import <OCMock/OCMock.h> + +#import "SDLMenuReplaceUtilities.h" + +#import "SDLFileManager.h" +#import "SDLMenuCell.h" +#import "SDLMenuReplaceUtilitiesSpecHelpers.h" +#import "SDLMenuManagerPrivateConstants.h" +#import "SDLWindowCapability.h" +#import "TestConnectionManager.h" + +@interface SDLMenuCell() + +@property (assign, nonatomic) UInt32 parentCellId; +@property (assign, nonatomic) UInt32 cellId; +@property (copy, nonatomic, readwrite, nullable) NSArray<SDLMenuCell *> *subCells; + +@end + +@interface SDLMenuReplaceUtilities () + +@property (class, assign, nonatomic) UInt32 nextMenuId; + +@end + +QuickSpecBegin(SDLMenuReplaceUtilitiesSpec) + +__block NSMutableArray<SDLMenuCell *> *testMenuCells = nil; +__block SDLFileManager *mockFileManager = nil; +__block SDLWindowCapability *testWindowCapability = nil; +__block NSArray<SDLTextField *> *allSupportedTextFields = @[ + [[SDLTextField alloc] initWithName:SDLTextFieldNameMenuCommandSecondaryText characterSet:SDLCharacterSetUtf8 width:100 rows:1], + [[SDLTextField alloc] initWithName:SDLTextFieldNameMenuCommandTertiaryText characterSet:SDLCharacterSetUtf8 width:100 rows:1], + [[SDLTextField alloc] initWithName:SDLTextFieldNameMenuSubMenuSecondaryText characterSet:SDLCharacterSetUtf8 width:100 rows:1], + [[SDLTextField alloc] initWithName:SDLTextFieldNameMenuSubMenuTertiaryText characterSet:SDLCharacterSetUtf8 width:100 rows:1] +]; +__block NSArray<SDLImageField *> *allSupportedImageFields = @[ + [[SDLImageField alloc] initWithName:SDLImageFieldNameCommandIcon imageTypeSupported:@[SDLImageTypeDynamic] imageResolution:nil], + [[SDLImageField alloc] initWithName:SDLImageFieldNameMenuCommandSecondaryImage imageTypeSupported:@[SDLImageTypeDynamic] imageResolution:nil], + [[SDLImageField alloc] initWithName:SDLImageFieldNameSubMenuIcon imageTypeSupported:@[SDLImageTypeDynamic] imageResolution:nil], + [[SDLImageField alloc] initWithName:SDLImageFieldNameMenuSubMenuSecondaryImage imageTypeSupported:@[SDLImageTypeDynamic] imageResolution:nil] +]; + +describe(@"adding ids", ^{ + it(@"should properly add ids", ^{ + SDLMenuReplaceUtilities.nextMenuId = 0; + testMenuCells = SDLMenuReplaceUtilitiesSpecHelpers.deepMenu; + + [SDLMenuReplaceUtilities addIdsToMenuCells:testMenuCells parentId:ParentIdNotFound]; + + expect(testMenuCells[0].cellId).to(equal(1)); + expect(testMenuCells[1].cellId).to(equal(6)); + expect(testMenuCells[2].cellId).to(equal(7)); + + NSArray<SDLMenuCell *> *subCellList1 = testMenuCells[0].subCells; + expect(subCellList1[0].cellId).to(equal(2)); + expect(subCellList1[0].parentCellId).to(equal(1)); + expect(subCellList1[1].cellId).to(equal(5)); + expect(subCellList1[1].parentCellId).to(equal(1)); + + NSArray<SDLMenuCell *> *subCell1SubCellList1 = subCellList1[0].subCells; + expect(subCell1SubCellList1[0].cellId).to(equal(3)); + expect(subCell1SubCellList1[0].parentCellId).to(equal(2)); + expect(subCell1SubCellList1[1].cellId).to(equal(4)); + expect(subCell1SubCellList1[1].parentCellId).to(equal(2)); + + NSArray<SDLMenuCell *> *subCellList2 = testMenuCells[2].subCells; + expect(subCellList2[0].cellId).to(equal(8)); + expect(subCellList2[0].parentCellId).to(equal(7)); + expect(subCellList2[1].cellId).to(equal(9)); + expect(subCellList2[1].parentCellId).to(equal(7)); + }); +}); + +describe(@"transferring cell ids", ^{ + it(@"should properly transfer ids and set parent ids", ^{ + testMenuCells = [[NSMutableArray alloc] initWithArray:SDLMenuReplaceUtilitiesSpecHelpers.deepMenu copyItems:YES]; + [SDLMenuReplaceUtilities addIdsToMenuCells:testMenuCells parentId:ParentIdNotFound]; + + NSArray<SDLMenuCell *> *toCells = [[NSArray alloc] initWithArray:SDLMenuReplaceUtilitiesSpecHelpers.deepMenu copyItems:YES]; + [SDLMenuReplaceUtilities transferCellIDsFromCells:testMenuCells toCells:toCells]; + + // Top-level cells should have same cell ids + for (NSUInteger i = 0; i < testMenuCells.count; i++) { + expect(toCells[i].cellId).to(equal(testMenuCells[i].cellId)); + } + + // Sub-cells should _not_ have the same cell ids + for (NSUInteger i = 0; i < testMenuCells[0].subCells.count; i++) { + expect(toCells[0].subCells[i].cellId).toNot(equal(testMenuCells[0].subCells[i].cellId)); + } + + // Sub-cells should have proper parent ids + for (NSUInteger i = 0; i < testMenuCells[0].subCells.count; i++) { + expect(toCells[0].subCells[i].parentCellId).to(equal(toCells[0].cellId)); + } + }); +}); + +describe(@"transferring cell handlers", ^{ + __block BOOL cell1HandlerTriggered = NO; + __block BOOL cell2HandlerTriggered = NO; + beforeEach(^{ + cell1HandlerTriggered = NO; + cell2HandlerTriggered = NO; + }); + + it(@"should properly transfer cell handlers", ^{ + SDLMenuCell *cell1 = [[SDLMenuCell alloc] initWithTitle:@"Cell1" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) { + cell1HandlerTriggered = YES; + }]; + SDLMenuCell *cell2 = [[SDLMenuCell alloc] initWithTitle:@"Cell1" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) { + cell2HandlerTriggered = YES; + }]; + + [SDLMenuReplaceUtilities transferCellHandlersFromCells:@[cell1] toCells:@[cell2]]; + cell2.handler(SDLTriggerSourceMenu); + + expect(cell1HandlerTriggered).to(beTrue()); + expect(cell2HandlerTriggered).to(beFalse()); + }); +}); + +describe(@"finding all artworks from cells", ^{ + beforeEach(^{ + mockFileManager = OCMClassMock([SDLFileManager class]); + testWindowCapability = [[SDLWindowCapability alloc] init]; + }); + + context(@"when all the files need to be uploaded", ^{ + beforeEach(^{ + OCMStub([mockFileManager fileNeedsUpload:[OCMArg any]]).andReturn(YES); + }); + + context(@"when the window capability doesn't support the primary image", ^{ + beforeEach(^{ + testWindowCapability.textFields = allSupportedTextFields; + }); + + it(@"should return an empty list of artworks to upload", ^{ + NSArray<SDLArtwork *> *artworksToUpload = [SDLMenuReplaceUtilities findAllArtworksToBeUploadedFromCells:SDLMenuReplaceUtilitiesSpecHelpers.topLevelOnlyMenu fileManager:mockFileManager windowCapability:testWindowCapability]; + + expect(artworksToUpload).to(beEmpty()); + }); + }); + + context(@"when the window capability supports primary but not secondary image", ^{ + beforeEach(^{ + testWindowCapability.textFields = allSupportedTextFields; + testWindowCapability.imageFields = @[allSupportedImageFields[0], allSupportedImageFields[2]]; + }); + + it(@"should only return primary images to upload", ^{ + NSArray<SDLArtwork *> *artworksToUpload = [SDLMenuReplaceUtilities findAllArtworksToBeUploadedFromCells:SDLMenuReplaceUtilitiesSpecHelpers.topLevelOnlyMenu fileManager:mockFileManager windowCapability:testWindowCapability]; + + expect(artworksToUpload).to(haveCount(2)); + }); + }); + + context(@"when the window capability supports both images", ^{ + beforeEach(^{ + testWindowCapability.textFields = allSupportedTextFields; + testWindowCapability.imageFields = allSupportedImageFields; + }); + + context(@"with a shallow menu", ^{ + it(@"should only return all images to upload", ^{ + NSArray<SDLArtwork *> *artworksToUpload = [SDLMenuReplaceUtilities findAllArtworksToBeUploadedFromCells:SDLMenuReplaceUtilitiesSpecHelpers.topLevelOnlyMenu fileManager:mockFileManager windowCapability:testWindowCapability]; + + expect(artworksToUpload).to(haveCount(2)); + }); + }); + + context(@"with a deep menu", ^{ + it(@"should only return all images to upload", ^{ + NSArray<SDLArtwork *> *artworksToUpload = [SDLMenuReplaceUtilities findAllArtworksToBeUploadedFromCells:SDLMenuReplaceUtilitiesSpecHelpers.deepMenu fileManager:mockFileManager windowCapability:testWindowCapability]; + + expect(artworksToUpload).to(haveCount(4)); + }); + }); + }); + }); + + context(@"when no files need to be uploaded", ^{ + beforeEach(^{ + OCMStub([mockFileManager fileNeedsUpload:[OCMArg any]]).andReturn(NO); + }); + + context(@"when the window capability supports both images", ^{ + beforeEach(^{ + testWindowCapability.textFields = allSupportedTextFields; + testWindowCapability.imageFields = allSupportedImageFields; + }); + + it(@"should not return any images to upload", ^{ + NSArray<SDLArtwork *> *artworksToUpload = [SDLMenuReplaceUtilities findAllArtworksToBeUploadedFromCells:SDLMenuReplaceUtilitiesSpecHelpers.topLevelOnlyMenu fileManager:mockFileManager windowCapability:testWindowCapability]; + + expect(artworksToUpload).to(beEmpty()); + }); + }); + }); +}); + +describe(@"retrieving a commandId", ^{ + context(@"with an AddCommand", ^{ + it(@"should return the command id", ^{ + SDLAddCommand *rpc = [[SDLAddCommand alloc] init]; + rpc.cmdID = @12345; + expect([SDLMenuReplaceUtilities commandIdForRPCRequest:rpc]).to(equal(12345)); + }); + }); + + context(@"with an AddSubMenu", ^{ + it(@"should return the command id", ^{ + SDLAddSubMenu *rpc = [[SDLAddSubMenu alloc] init]; + rpc.menuID = @12345; + expect([SDLMenuReplaceUtilities commandIdForRPCRequest:rpc]).to(equal(12345)); + }); + }); + + context(@"with a DeleteCommand", ^{ + it(@"should return the command id", ^{ + SDLDeleteCommand *rpc = [[SDLDeleteCommand alloc] init]; + rpc.cmdID = @12345; + expect([SDLMenuReplaceUtilities commandIdForRPCRequest:rpc]).to(equal(12345)); + }); + }); + + context(@"with a DeleteSubMenu", ^{ + it(@"should return the command id", ^{ + SDLDeleteSubMenu *rpc = [[SDLDeleteSubMenu alloc] init]; + rpc.menuID = @12345; + expect([SDLMenuReplaceUtilities commandIdForRPCRequest:rpc]).to(equal(12345)); + }); + }); + + context(@"with an Alert", ^{ + it(@"should return 0", ^{ + SDLAlert *rpc = [[SDLAlert alloc] init]; + expect([SDLMenuReplaceUtilities commandIdForRPCRequest:rpc]).to(equal(0)); + }); + }); +}); + +describe(@"retrieving a position", ^{ + context(@"with an AddCommand", ^{ + it(@"should return the position", ^{ + SDLAddCommand *rpc = [[SDLAddCommand alloc] init]; + rpc.menuParams = [[SDLMenuParams alloc] init]; + rpc.menuParams.position = @123; + expect(@([SDLMenuReplaceUtilities positionForRPCRequest:rpc])).to(equal(@123)); + }); + }); + + context(@"with an AddSubMenu", ^{ + it(@"should return the command id", ^{ + SDLAddSubMenu *rpc = [[SDLAddSubMenu alloc] init]; + rpc.position = @123; + expect(@([SDLMenuReplaceUtilities positionForRPCRequest:rpc])).to(equal(@123)); + }); + }); +}); + +describe(@"generating RPCs", ^{ + __block SDLMenuLayout testMenuLayout = SDLMenuLayoutList; + + beforeEach(^{ + mockFileManager = OCMClassMock([SDLFileManager class]); + testWindowCapability = [[SDLWindowCapability alloc] init]; + }); + + context(@"delete commands", ^{ + context(@"shallow menu", ^{ + beforeEach(^{ + testMenuCells = SDLMenuReplaceUtilitiesSpecHelpers.topLevelOnlyMenu; + }); + + it(@"should generate the correct RPCs", ^{ + NSArray<SDLRPCRequest *> *requests = [SDLMenuReplaceUtilities deleteCommandsForCells:testMenuCells]; + expect(requests).to(haveCount(3)); + expect(requests[0]).to(beAnInstanceOf(SDLDeleteCommand.class)); + expect(requests[1]).to(beAnInstanceOf(SDLDeleteCommand.class)); + expect(requests[2]).to(beAnInstanceOf(SDLDeleteCommand.class)); + }); + }); + + context(@"deep menu", ^{ + beforeEach(^{ + testMenuCells = SDLMenuReplaceUtilitiesSpecHelpers.deepMenu; + }); + + it(@"should generate the correct RPCs", ^{ + NSArray<SDLRPCRequest *> *requests = [SDLMenuReplaceUtilities deleteCommandsForCells:testMenuCells]; + expect(requests).to(haveCount(3)); + expect(requests[0]).to(beAnInstanceOf(SDLDeleteSubMenu.class)); + expect(requests[1]).to(beAnInstanceOf(SDLDeleteCommand.class)); + expect(requests[2]).to(beAnInstanceOf(SDLDeleteSubMenu.class)); + }); + }); + }); + + context(@"main menu commands", ^{ + context(@"shallow menu", ^{ + beforeEach(^{ + testMenuCells = SDLMenuReplaceUtilitiesSpecHelpers.topLevelOnlyMenu; + }); + + it(@"should generate the correct RPCs", ^{ + NSArray<SDLRPCRequest *> *requests = [SDLMenuReplaceUtilities mainMenuCommandsForCells:testMenuCells fileManager:mockFileManager usingPositionsFromFullMenu:testMenuCells windowCapability:testWindowCapability defaultSubmenuLayout:testMenuLayout]; + expect(requests).to(haveCount(3)); + expect(requests[0]).to(beAnInstanceOf(SDLAddCommand.class)); + expect(requests[1]).to(beAnInstanceOf(SDLAddCommand.class)); + expect(requests[2]).to(beAnInstanceOf(SDLAddCommand.class)); + }); + }); + + context(@"deep menu", ^{ + beforeEach(^{ + testMenuCells = SDLMenuReplaceUtilitiesSpecHelpers.deepMenu; + }); + + it(@"should generate the correct RPCs", ^{ + NSArray<SDLRPCRequest *> *requests = [SDLMenuReplaceUtilities mainMenuCommandsForCells:testMenuCells fileManager:mockFileManager usingPositionsFromFullMenu:testMenuCells windowCapability:testWindowCapability defaultSubmenuLayout:testMenuLayout]; + expect(requests).to(haveCount(3)); + expect(requests[0]).to(beAnInstanceOf(SDLAddSubMenu.class)); + expect(requests[1]).to(beAnInstanceOf(SDLAddCommand.class)); + expect(requests[2]).to(beAnInstanceOf(SDLAddSubMenu.class)); + }); + }); + }); + + context(@"sub menu commands", ^{ + context(@"shallow menu", ^{ + beforeEach(^{ + testMenuCells = SDLMenuReplaceUtilitiesSpecHelpers.topLevelOnlyMenu; + }); + }); + + context(@"deep menu", ^{ + beforeEach(^{ + testMenuCells = SDLMenuReplaceUtilitiesSpecHelpers.deepMenu; + }); + }); + }); +}); + +// updating menu cells +describe(@"updating menu cell lists", ^{ + __block UInt32 testCommandId = 0; + + describe(@"removing commands", ^{ + context(@"from a shallow list", ^{ + beforeEach(^{ + testMenuCells = SDLMenuReplaceUtilitiesSpecHelpers.topLevelOnlyMenu; + [SDLMenuReplaceUtilities addIdsToMenuCells:testMenuCells parentId:ParentIdNotFound]; + }); + + context(@"when the cell is in the menu", ^{ + beforeEach(^{ + testCommandId = testMenuCells[1].cellId; + }); + + it(@"should return the menu without the cell and return YES", ^{ + NSMutableArray<SDLMenuCell *> *testMutableMenuCells = [testMenuCells mutableCopy]; + BOOL foundItem = [SDLMenuReplaceUtilities removeCellFromList:testMutableMenuCells withCellId:testCommandId]; + + expect(foundItem).to(beTrue()); + expect(testMutableMenuCells).to(haveCount(2)); + expect(testMutableMenuCells[0]).to(equal(testMenuCells[0])); + expect(testMutableMenuCells[1]).to(equal(testMenuCells[2])); + }); + }); + + context(@"when the cell is not in the menu", ^{ + beforeEach(^{ + testCommandId = 100; + }); + + it(@"should return the menu with all cells and return NO", ^{ + NSMutableArray<SDLMenuCell *> *testMutableMenuCells = [testMenuCells mutableCopy]; + BOOL foundItem = [SDLMenuReplaceUtilities removeCellFromList:testMutableMenuCells withCellId:testCommandId]; + + expect(foundItem).to(beFalse()); + expect(testMutableMenuCells).to(haveCount(3)); + }); + }); + }); + + context(@"from a deep list", ^{ + beforeEach(^{ + testMenuCells = SDLMenuReplaceUtilitiesSpecHelpers.deepMenu; + [SDLMenuReplaceUtilities addIdsToMenuCells:testMenuCells parentId:ParentIdNotFound]; + }); + + context(@"when the cell is in the top menu", ^{ + beforeEach(^{ + testCommandId = testMenuCells[1].cellId; + }); + + it(@"should return the menu without the cell and return YES", ^{ + NSMutableArray<SDLMenuCell *> *testMutableMenuCells = [testMenuCells mutableCopy]; + BOOL foundItem = [SDLMenuReplaceUtilities removeCellFromList:testMutableMenuCells withCellId:testCommandId]; + + expect(foundItem).to(beTrue()); + expect(testMutableMenuCells).to(haveCount(2)); + expect(testMutableMenuCells[0]).to(equal(testMenuCells[0])); + expect(testMutableMenuCells[1]).to(equal(testMenuCells[2])); + }); + }); + + context(@"when the cell is in the submenu", ^{ + beforeEach(^{ + testCommandId = testMenuCells[0].subCells[0].cellId; + }); + + it(@"should return the menu without the cell and return YES", ^{ + NSMutableArray<SDLMenuCell *> *testMutableMenuCells = [testMenuCells mutableCopy]; + BOOL foundItem = [SDLMenuReplaceUtilities removeCellFromList:testMutableMenuCells withCellId:testCommandId]; + + expect(foundItem).to(beTrue()); + expect(testMutableMenuCells).to(haveCount(3)); + expect(testMutableMenuCells[0].subCells).to(haveCount(1)); + }); + }); + + context(@"when the cell is not in the menu", ^{ + beforeEach(^{ + testCommandId = 100; + }); + + it(@"should return the menu with all cells and return NO", ^{ + NSMutableArray<SDLMenuCell *> *testMutableMenuCells = [testMenuCells mutableCopy]; + BOOL foundItem = [SDLMenuReplaceUtilities removeCellFromList:testMutableMenuCells withCellId:testCommandId]; + + expect(foundItem).to(beFalse()); + expect(testMutableMenuCells).to(haveCount(3)); + expect(testMutableMenuCells[0].subCells).to(haveCount(2)); + expect(testMutableMenuCells[2].subCells).to(haveCount(2)); + }); + }); + }); + }); + + describe(@"add commands to the main list", ^{ + __block NSMutableArray<SDLMenuCell *> *newCellList = nil; + + context(@"from a shallow list", ^{ + beforeEach(^{ + testMenuCells = SDLMenuReplaceUtilitiesSpecHelpers.topLevelOnlyMenu; + [SDLMenuReplaceUtilities addIdsToMenuCells:testMenuCells parentId:ParentIdNotFound]; + + SDLMenuCell *newCell = [[SDLMenuCell alloc] initWithTitle:@"New Cell" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; + newCell.cellId = 99; + newCellList = [@[newCell] mutableCopy]; + }); + + describe(@"if the cell is not in the cell list", ^{ + beforeEach(^{ + newCellList = [[NSMutableArray alloc] init]; + }); + + it(@"should return NO", ^{ + BOOL didAddCell = [SDLMenuReplaceUtilities addCellWithCellId:99 position:0 fromNewMenuList:newCellList toMainMenuList:testMenuCells]; + + expect(didAddCell).to(beFalse()); + }); + }); + + context(@"at the beginning", ^{ + it(@"should return YES and the cell should be included", ^{ + BOOL didAddCell = [SDLMenuReplaceUtilities addCellWithCellId:newCellList[0].cellId position:0 fromNewMenuList:newCellList toMainMenuList:testMenuCells]; + + expect(didAddCell).to(beTrue()); + expect(testMenuCells).to(haveCount(4)); + expect(testMenuCells[0]).to(equal(newCellList[0])); + }); + }); + + context(@"in the middle", ^{ + it(@"should return YES and the cell should be included", ^{ + BOOL didAddCell = [SDLMenuReplaceUtilities addCellWithCellId:newCellList[0].cellId position:1 fromNewMenuList:newCellList toMainMenuList:testMenuCells]; + + expect(didAddCell).to(beTrue()); + expect(testMenuCells).to(haveCount(4)); + expect(testMenuCells[1]).to(equal(newCellList[0])); + }); + }); + + context(@"at the end", ^{ + it(@"should return YES and the cell should be included", ^{ + BOOL didAddCell = [SDLMenuReplaceUtilities addCellWithCellId:newCellList[0].cellId position:3 fromNewMenuList:newCellList toMainMenuList:testMenuCells]; + + expect(didAddCell).to(beTrue()); + expect(testMenuCells).to(haveCount(4)); + expect(testMenuCells[3]).to(equal(newCellList[0])); + }); + }); + }); + + context(@"from a deep list", ^{ + __block SDLMenuCell *subCell = nil; + __block NSMutableArray<SDLMenuCell *> *newMenu = nil; + + beforeEach(^{ + testMenuCells = SDLMenuReplaceUtilitiesSpecHelpers.deepMenu.copy; + [SDLMenuReplaceUtilities addIdsToMenuCells:testMenuCells parentId:ParentIdNotFound]; + + newMenu = [[NSMutableArray alloc] initWithArray:testMenuCells copyItems:YES]; + NSMutableArray<SDLMenuCell *> *subMenuToUpdate = newMenu[0].subCells.mutableCopy; + + subCell = [[SDLMenuCell alloc] initWithTitle:@"New SubCell" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; + subCell.cellId = 98; + subCell.parentCellId = newMenu[0].cellId; + [subMenuToUpdate insertObject:subCell atIndex:0]; + newMenu[0].subCells = subMenuToUpdate.copy; + }); + + it(@"should properly add the subcell to the list", ^{ + BOOL didAddCell = [SDLMenuReplaceUtilities addCellWithCellId:newMenu[0].subCells[0].cellId position:0 fromNewMenuList:newMenu toMainMenuList:testMenuCells]; + + expect(didAddCell).to(beTrue()); + expect(testMenuCells).to(haveCount(3)); + expect(testMenuCells[0].subCells).to(haveCount(3)); + expect(testMenuCells[0].subCells[0]).to(equal(subCell)); + }); + }); + }); +}); + +QuickSpecEnd diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLMenuReplaceUtilitiesSpecHelpers.h b/SmartDeviceLinkTests/DevAPISpecs/SDLMenuReplaceUtilitiesSpecHelpers.h new file mode 100644 index 000000000..7106cb567 --- /dev/null +++ b/SmartDeviceLinkTests/DevAPISpecs/SDLMenuReplaceUtilitiesSpecHelpers.h @@ -0,0 +1,22 @@ +// +// SDLMenuReplaceUtilitiesSpecHelpers.h +// SmartDeviceLinkTests +// +// Created by Joel Fischer on 1/29/21. +// Copyright © 2021 smartdevicelink. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class SDLMenuCell; + +NS_ASSUME_NONNULL_BEGIN + +@interface SDLMenuReplaceUtilitiesSpecHelpers : NSObject + +@property (class, nonatomic, readonly) NSMutableArray<SDLMenuCell *> *topLevelOnlyMenu; +@property (class, nonatomic, readonly) NSMutableArray<SDLMenuCell *> *deepMenu; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLMenuReplaceUtilitiesSpecHelpers.m b/SmartDeviceLinkTests/DevAPISpecs/SDLMenuReplaceUtilitiesSpecHelpers.m new file mode 100644 index 000000000..c6069d315 --- /dev/null +++ b/SmartDeviceLinkTests/DevAPISpecs/SDLMenuReplaceUtilitiesSpecHelpers.m @@ -0,0 +1,70 @@ +// +// SDLMenuReplaceUtilitiesSpecHelpers.m +// SmartDeviceLinkTests +// +// Created by Joel Fischer on 1/29/21. +// Copyright © 2021 smartdevicelink. All rights reserved. +// + +#import "SDLMenuReplaceUtilitiesSpecHelpers.h" + +#import "SDLArtwork.h" +#import "SDLMenuCell.h" + +@interface SDLMenuCell() + +@property (assign, nonatomic) UInt32 parentCellId; +@property (assign, nonatomic) UInt32 cellId; + +@end + +@implementation SDLMenuReplaceUtilitiesSpecHelpers + ++ (NSMutableArray<SDLMenuCell *> *)topLevelOnlyMenu { + NSData *cellArtData = [@"testart" dataUsingEncoding:NSUTF8StringEncoding]; + NSData *cellArtData2 = [@"testart2" dataUsingEncoding:NSUTF8StringEncoding]; + SDLArtwork *artwork1 = [[SDLArtwork alloc] initWithData:cellArtData name:@"Test Art 1" fileExtension:@"png" persistent:NO]; + SDLArtwork *artwork2 = [[SDLArtwork alloc] initWithData:cellArtData2 name:@"Test Art 2" fileExtension:@"png" persistent:NO]; + + SDLMenuCell *cell1 = [[SDLMenuCell alloc] initWithTitle:@"Item 1" secondaryText:nil tertiaryText:nil icon:artwork1 secondaryArtwork:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; + cell1.cellId = 1; + + SDLMenuCell *cell2 = [[SDLMenuCell alloc] initWithTitle:@"Item 2" secondaryText:nil tertiaryText:nil icon:artwork1 secondaryArtwork:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; + cell2.cellId = 2; + + SDLMenuCell *cell3 = [[SDLMenuCell alloc] initWithTitle:@"Item 3" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:artwork2 voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; + cell3.cellId = 3; + + return [@[cell1, cell2, cell3] mutableCopy]; +} + ++ (NSMutableArray<SDLMenuCell *> *)deepMenu { + NSData *cellArtData = [@"testart" dataUsingEncoding:NSUTF8StringEncoding]; + NSData *cellArtData2 = [@"testart2" dataUsingEncoding:NSUTF8StringEncoding]; + NSData *cellArtData3 = [@"testart3" dataUsingEncoding:NSUTF8StringEncoding]; + NSData *cellArtData4 = [@"testart4" dataUsingEncoding:NSUTF8StringEncoding]; + SDLArtwork *artwork1 = [[SDLArtwork alloc] initWithData:cellArtData name:@"Test Art 1" fileExtension:@"png" persistent:NO]; + SDLArtwork *artwork2 = [[SDLArtwork alloc] initWithData:cellArtData2 name:@"Test Art 2" fileExtension:@"png" persistent:NO]; + SDLArtwork *artwork3 = [[SDLArtwork alloc] initWithData:cellArtData3 name:@"Test Art 3" fileExtension:@"png" persistent:NO]; + SDLArtwork *artwork4 = [[SDLArtwork alloc] initWithData:cellArtData4 name:@"Test Art 4" fileExtension:@"png" persistent:NO]; + + SDLMenuCell *subList1SubList1Cell1 = [[SDLMenuCell alloc] initWithTitle:@"Item 1" secondaryText:@"SubItem 1" tertiaryText:@"Sub-SubItem 1" icon:nil secondaryArtwork:artwork3 voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; + SDLMenuCell *subList1SubList1Cell2 = [[SDLMenuCell alloc] initWithTitle:@"Item 1" secondaryText:@"SubItem 1" tertiaryText:@"Sub-SubItem 2" icon:artwork1 secondaryArtwork:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; + NSArray<SDLMenuCell *> *subList1SubList1 = @[subList1SubList1Cell1, subList1SubList1Cell2]; + + SDLMenuCell *subList1Cell1 = [[SDLMenuCell alloc] initWithTitle:@"Item 1" secondaryText:@"SubItem 1" tertiaryText:nil icon:artwork4 secondaryArtwork:nil submenuLayout:nil subCells:subList1SubList1]; + SDLMenuCell *subList1Cell2 = [[SDLMenuCell alloc] initWithTitle:@"Item 1" secondaryText:@"SubItem 2" tertiaryText:nil icon:artwork2 secondaryArtwork:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; + NSArray<SDLMenuCell *> *subList1 = @[subList1Cell1, subList1Cell2]; + + SDLMenuCell *subList2Cell1 = [[SDLMenuCell alloc] initWithTitle:@"Item 3" secondaryText:@"SubItem 1" tertiaryText:nil icon:artwork1 secondaryArtwork:artwork4 voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; + SDLMenuCell *subList2Cell2 = [[SDLMenuCell alloc] initWithTitle:@"Item 3" secondaryText:@"SubItem 2" tertiaryText:nil icon:artwork1 secondaryArtwork:artwork2 voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; + NSArray<SDLMenuCell *> *subList2 = @[subList2Cell1, subList2Cell2]; + + SDLMenuCell *topListCell1 = [[SDLMenuCell alloc] initWithTitle:@"Item 1" secondaryText:nil tertiaryText:nil icon:artwork1 secondaryArtwork:nil submenuLayout:nil subCells:subList1]; + SDLMenuCell *topListCell2 = [[SDLMenuCell alloc] initWithTitle:@"Item 2" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}]; + SDLMenuCell *topListCell3 = [[SDLMenuCell alloc] initWithTitle:@"Item 3" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil submenuLayout:nil subCells:subList2]; + + return @[topListCell1, topListCell2, topListCell3].mutableCopy; +} + +@end diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLMenuShowOperationSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLMenuShowOperationSpec.m new file mode 100644 index 000000000..d79f2b90f --- /dev/null +++ b/SmartDeviceLinkTests/DevAPISpecs/SDLMenuShowOperationSpec.m @@ -0,0 +1,167 @@ +// +// SDLMenuShowOperationSpec.m +// SmartDeviceLinkTests +// +// Created by Joel Fischer on 2/16/21. +// Copyright © 2021 smartdevicelink. All rights reserved. +// + +#import <Nimble/Nimble.h> +#import <OCMock/OCMock.h> +#import <Quick/Quick.h> + +#import <SmartDeviceLink/SmartDeviceLink.h> +#import "SDLMenuShowOperation.h" +#import "TestConnectionManager.h" + +@interface SDLMenuShowOperation () + +@property (strong, nonatomic, nullable) SDLMenuCell *submenuCell; + +@end + +QuickSpecBegin(SDLMenuShowOperationSpec) + +describe(@"the show menu operation", ^{ + __block SDLMenuShowOperation *testOp = nil; + __block TestConnectionManager *testConnectionManager = nil; + __block NSError *resultError = nil; + __block BOOL callbackCalled = NO; + + beforeEach(^{ + testConnectionManager = [[TestConnectionManager alloc] init]; + testOp = [[SDLMenuShowOperation alloc] initWithConnectionManager:testConnectionManager toMenuCell:nil completionHandler:^(NSError * _Nullable error) { + resultError = error; + callbackCalled = YES; + }]; + resultError = nil; + callbackCalled = NO; + }); + + // opening to the main menu + context(@"opening to the main menu", ^{ + beforeEach(^{ + [testOp start]; + }); + + it(@"should send the RPC request", ^{ + expect(testConnectionManager.receivedRequests).to(haveCount(1)); + }); + + // when the response is not SUCCESS or WARNINGS + context(@"when the response is not SUCCESS or WARNINGS", ^{ + beforeEach(^{ + SDLShowAppMenuResponse *response = [[SDLShowAppMenuResponse alloc] init]; + response.success = @NO; + response.resultCode = SDLResultRejected; + + [testConnectionManager respondToLastRequestWithResponse:response]; + }); + + it(@"should set the error and finish", ^{ + expect(testOp.isFinished).to(beTrue()); + expect(resultError).toNot(beNil()); + expect(callbackCalled).to(beTrue()); + }); + }); + + // when the response is SUCCESS + context(@"when the response is SUCCESS", ^{ + beforeEach(^{ + SDLShowAppMenuResponse *response = [[SDLShowAppMenuResponse alloc] init]; + response.success = @YES; + response.resultCode = SDLResultSuccess; + + [testConnectionManager respondToLastRequestWithResponse:response]; + }); + + it(@"should not set the error and finish", ^{ + expect(testOp.isFinished).to(beTrue()); + expect(resultError).to(beNil()); + expect(callbackCalled).to(beTrue()); + }); + }); + + // when the response is WARNINGS + context(@"when the response is WARNINGS", ^{ + beforeEach(^{ + SDLShowAppMenuResponse *response = [[SDLShowAppMenuResponse alloc] init]; + response.success = @YES; + response.resultCode = SDLResultWarnings; + + [testConnectionManager respondToLastRequestWithResponse:response]; + }); + + it(@"should not set the error and finish", ^{ + expect(testOp.isFinished).to(beTrue()); + expect(resultError).to(beNil()); + expect(callbackCalled).to(beTrue()); + }); + }); + }); + + // opening to an inner menu + context(@"opening to an inner menu", ^{ + __block SDLMenuCell *openToCell = nil; + __block SDLMenuCell *subcell = nil; + beforeEach(^{ + subcell = [[SDLMenuCell alloc] initWithTitle:@"Subcell" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) { }]; + openToCell = [[SDLMenuCell alloc] initWithTitle:@"Test submenu" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil submenuLayout:nil subCells:@[subcell]]; + testOp.submenuCell = openToCell; + [testOp start]; + }); + + // when the response is not SUCCESS or WARNINGS + context(@"when the response is not SUCCESS or WARNINGS", ^{ + beforeEach(^{ + SDLShowAppMenuResponse *response = [[SDLShowAppMenuResponse alloc] init]; + response.success = @NO; + response.resultCode = SDLResultRejected; + + [testConnectionManager respondToLastRequestWithResponse:response]; + }); + + it(@"should set the error and finish", ^{ + expect(resultError).toNot(beNil()); + expect(callbackCalled).to(beTrue()); + expect(testOp.isFinished).to(beTrue()); + }); + }); + + // when the response is SUCCESS + context(@"when the response is SUCCESS", ^{ + beforeEach(^{ + SDLShowAppMenuResponse *response = [[SDLShowAppMenuResponse alloc] init]; + response.success = @YES; + response.resultCode = SDLResultSuccess; + + [testConnectionManager respondToLastRequestWithResponse:response]; + }); + + it(@"should not set the error and finish", ^{ + expect(resultError).to(beNil()); + expect(callbackCalled).to(beTrue()); + expect(testOp.isFinished).to(beTrue()); + }); + }); + + // when the response is WARNINGS + context(@"when the response is WARNINGS", ^{ + beforeEach(^{ + SDLShowAppMenuResponse *response = [[SDLShowAppMenuResponse alloc] init]; + response.success = @YES; + response.resultCode = SDLResultWarnings; + + [testConnectionManager respondToLastRequestWithResponse:response]; + }); + + it(@"should not set the error and finish", ^{ + expect(resultError).to(beNil()); + expect(callbackCalled).to(beTrue()); + expect(testOp.isFinished).to(beTrue()); + }); + }); + }); +}); + +QuickSpecEnd |