From 3271d0461c09938500547916c399fde819748d22 Mon Sep 17 00:00:00 2001 From: "Tanner W. Stokes" Date: Thu, 10 Mar 2022 16:15:37 -0500 Subject: [PATCH 01/11] Override the accessibility traits so that VoiceOver doesn't say 'Dimmed' --- WordPress/Classes/ViewRelated/Menus/MenuItemView.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WordPress/Classes/ViewRelated/Menus/MenuItemView.m b/WordPress/Classes/ViewRelated/Menus/MenuItemView.m index c81c14be6d26..163487a008cf 100644 --- a/WordPress/Classes/ViewRelated/Menus/MenuItemView.m +++ b/WordPress/Classes/ViewRelated/Menus/MenuItemView.m @@ -51,6 +51,8 @@ - (void)setupOrderingButton button.accessibilityLabel = NSLocalizedString(@"Move menu item", @"Screen reader text for button that will move the menu item"); button.accessibilityHint = NSLocalizedString(@"Double tap and hold to move this menu item up or down", @"Screen reader hint for button that will move the menu item"); button.userInteractionEnabled = NO; + // Override the accessibility traits so that VoiceOver doesn't read "Dimmed" due to userInteractionEnabled = NO + [button setAccessibilityTraits:UIAccessibilityTraitButton]; _orderingButton = button; } From 16efa1750161e6033164ebd26a6413008edf028d Mon Sep 17 00:00:00 2001 From: "Tanner W. Stokes" Date: Wed, 16 Mar 2022 14:17:12 -0400 Subject: [PATCH 02/11] Add a note to a11y hint about changing hierarchy. --- WordPress/Classes/ViewRelated/Menus/MenuItemView.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Menus/MenuItemView.m b/WordPress/Classes/ViewRelated/Menus/MenuItemView.m index 163487a008cf..c73d70eda20d 100644 --- a/WordPress/Classes/ViewRelated/Menus/MenuItemView.m +++ b/WordPress/Classes/ViewRelated/Menus/MenuItemView.m @@ -49,7 +49,7 @@ - (void)setupOrderingButton UIImage *image = [[UIImage imageNamed:@"menus-move-icon"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; UIButton *button = [self addAccessoryButtonIconViewWithImage:image]; button.accessibilityLabel = NSLocalizedString(@"Move menu item", @"Screen reader text for button that will move the menu item"); - button.accessibilityHint = NSLocalizedString(@"Double tap and hold to move this menu item up or down", @"Screen reader hint for button that will move the menu item"); + button.accessibilityHint = NSLocalizedString(@"Double tap and hold to move this menu item up or down. Move horizontally to change hierarchy.", @"Screen reader hint for button that will move the menu item"); button.userInteractionEnabled = NO; // Override the accessibility traits so that VoiceOver doesn't read "Dimmed" due to userInteractionEnabled = NO [button setAccessibilityTraits:UIAccessibilityTraitButton]; From c5c4d6ebdce2c18aafa3e823daa16871624a213a Mon Sep 17 00:00:00 2001 From: "Tanner W. Stokes" Date: Wed, 16 Mar 2022 20:30:06 -0400 Subject: [PATCH 03/11] Add MenuItem method for finding preceding siblings. --- WordPress/Classes/Models/MenuItem.h | 8 ++++++++ WordPress/Classes/Models/MenuItem.m | 14 ++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/WordPress/Classes/Models/MenuItem.h b/WordPress/Classes/Models/MenuItem.h index 81edfbb5b098..ef98b0cdde5d 100644 --- a/WordPress/Classes/Models/MenuItem.h +++ b/WordPress/Classes/Models/MenuItem.h @@ -112,6 +112,14 @@ extern NSString * const MenuItemLinkTargetBlank; */ - (BOOL)nameIsEmptyOrDefault; +/** + Search for a sibling that precedes self. + A sibling is a MenuItem that shares the same parent. + @param orderedItems an ordered set of items nested top-down as a parent followed by children. (as returned from the Menus API) + @returns MenuItem sibling that precedes self, or nil if there is not one. + */ +- (MenuItem *)precedingSiblingInOrderedItems:(NSOrderedSet *)orderedItems; + @end @interface MenuItem (CoreDataGeneratedAccessors) diff --git a/WordPress/Classes/Models/MenuItem.m b/WordPress/Classes/Models/MenuItem.m index 9213aa5d141f..b42b2ab8daf3 100644 --- a/WordPress/Classes/Models/MenuItem.m +++ b/WordPress/Classes/Models/MenuItem.m @@ -115,4 +115,18 @@ - (BOOL)nameIsEmptyOrDefault return self.name.length == 0 || [self.name isEqualToString:[MenuItem defaultItemNameLocalized]]; } +/** + Return a sibling that precedes self, or nil if one wasn't found. + */ +- (MenuItem *)precedingSiblingInOrderedItems:(NSOrderedSet *)orderedItems +{ + for (NSUInteger idx = [orderedItems indexOfObject:self]; idx > 0; idx--) { + MenuItem *previousItem = [orderedItems objectAtIndex:idx - 1]; + if (previousItem.parent == self.parent) { + return previousItem; + } + } + return nil; +} + @end From 12303f278242655f985f03cf35b1aa2413745db3 Mon Sep 17 00:00:00 2001 From: "Tanner W. Stokes" Date: Wed, 16 Mar 2022 21:06:38 -0400 Subject: [PATCH 04/11] Add method to refresh a11y labels. --- .../Classes/ViewRelated/Menus/MenuItemView.h | 5 +++++ .../Classes/ViewRelated/Menus/MenuItemView.m | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/WordPress/Classes/ViewRelated/Menus/MenuItemView.h b/WordPress/Classes/ViewRelated/Menus/MenuItemView.h index 9ee8661f0fed..cfdf1fe43f66 100644 --- a/WordPress/Classes/ViewRelated/Menus/MenuItemView.h +++ b/WordPress/Classes/ViewRelated/Menus/MenuItemView.h @@ -29,6 +29,11 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)refresh; +/** + Refresh the accessibility labels. + */ +- (void)refreshAccessibilityLabels; + /** The detectedable region of the view for allowing ordering. */ diff --git a/WordPress/Classes/ViewRelated/Menus/MenuItemView.m b/WordPress/Classes/ViewRelated/Menus/MenuItemView.m index c73d70eda20d..8c622100c4e3 100644 --- a/WordPress/Classes/ViewRelated/Menus/MenuItemView.m +++ b/WordPress/Classes/ViewRelated/Menus/MenuItemView.m @@ -111,6 +111,21 @@ - (void)refresh { self.iconView.image = [MenuItem iconImageForItemType:self.item.type]; self.textLabel.text = self.item.name; + [self refreshAccessibilityLabels]; +} + +- (void)refreshAccessibilityLabels +{ + NSString *levelString; + if (self.item.parent) { + levelString = NSLocalizedString(@"Child of %@", @"Screen reader text expressing the menu item is a child of another menu item. Argument is a name for another menu item."); + } else { + levelString = NSLocalizedString(@"Top level", @"Screen reader text expressing the menu item is at the top level."); + } + self.textLabel.accessibilityLabel = [NSString stringWithFormat:@"%@. %@.", self.textLabel.text, levelString]; + + NSString *labelString = NSLocalizedString(@"Move %@", @"Screen reader text for button that will move the menu item. Argument is menu item's name."); + self.orderingButton.accessibilityLabel = [NSString stringWithFormat:labelString, self.item.name]; } - (CGRect)orderingToggleRect From 5c4e4be81e19bc0f8ada7026a40ec110e37944d5 Mon Sep 17 00:00:00 2001 From: "Tanner W. Stokes" Date: Mon, 21 Mar 2022 16:49:40 -0400 Subject: [PATCH 05/11] Announce changes to menu ordering when VO is enabled. --- .../Classes/ViewRelated/Menus/MenuItemView.m | 8 +-- .../Menus/MenuItemsViewController.m | 60 +++++++++++++++++++ 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Menus/MenuItemView.m b/WordPress/Classes/ViewRelated/Menus/MenuItemView.m index 8c622100c4e3..23e9cd8b7743 100644 --- a/WordPress/Classes/ViewRelated/Menus/MenuItemView.m +++ b/WordPress/Classes/ViewRelated/Menus/MenuItemView.m @@ -116,13 +116,13 @@ - (void)refresh - (void)refreshAccessibilityLabels { - NSString *levelString; + NSString *parentString; if (self.item.parent) { - levelString = NSLocalizedString(@"Child of %@", @"Screen reader text expressing the menu item is a child of another menu item. Argument is a name for another menu item."); + parentString = [NSString stringWithFormat:NSLocalizedString(@"Child of %@", @"Screen reader text expressing the menu item is a child of another menu item. Argument is a name for another menu item."), self.item.parent.name]; } else { - levelString = NSLocalizedString(@"Top level", @"Screen reader text expressing the menu item is at the top level."); + parentString = NSLocalizedString(@"Top level", @"Screen reader text expressing the menu item is at the top level and has no parent."); } - self.textLabel.accessibilityLabel = [NSString stringWithFormat:@"%@. %@.", self.textLabel.text, levelString]; + self.textLabel.accessibilityLabel = [NSString stringWithFormat:@"%@. %@", self.textLabel.text, parentString]; NSString *labelString = NSLocalizedString(@"Move %@", @"Screen reader text for button that will move the menu item. Argument is menu item's name."); self.orderingButton.accessibilityLabel = [NSString stringWithFormat:labelString, self.item.name]; diff --git a/WordPress/Classes/ViewRelated/Menus/MenuItemsViewController.m b/WordPress/Classes/ViewRelated/Menus/MenuItemsViewController.m index 7c72b4f063f7..6f3b582b7ff7 100644 --- a/WordPress/Classes/ViewRelated/Menus/MenuItemsViewController.m +++ b/WordPress/Classes/ViewRelated/Menus/MenuItemsViewController.m @@ -162,6 +162,8 @@ - (void)updateParentChildIndentationForItemViews itemView.indentationLevel++; parentItem = parentItem.parent; } + + [itemView refreshAccessibilityLabels]; } } @@ -488,6 +490,9 @@ - (void)orderingTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)ev if (newParent) { selectedItem.parent = newParent; + MenuItem *precedingSibling = [selectedItem precedingSiblingInOrderedItems:orderedItems]; + [self announceOrderingChange:newParent parentChanged:YES before:nil after:precedingSibling]; + modelUpdated = YES; } @@ -500,6 +505,9 @@ - (void)orderingTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)ev // try to move up the parent tree MenuItem *parent = selectedItem.parent.parent; selectedItem.parent = parent; + MenuItem *precedingSibling = [selectedItem precedingSiblingInOrderedItems:orderedItems]; + [self announceOrderingChange:parent parentChanged:YES before:nil after:precedingSibling]; + modelUpdated = YES; } } @@ -606,6 +614,8 @@ - (BOOL)handleOrderingTouchForItemView:(MenuItemView *)itemView withOtherItemVie if ([self nextAvailableItemForOrderingAfterItem:item] == otherItem) { // take the parent of the otherItem, or nil item.parent = otherItem.parent; + [self announceOrderingChange:item.parent parentChanged:YES before:otherItem after:nil]; + updated = YES; } } @@ -616,9 +626,11 @@ - (BOOL)handleOrderingTouchForItemView:(MenuItemView *)itemView withOtherItemVie if (otherItem.children.count) { // if ordering after a parent, we need to become a child item.parent = otherItem; + [self announceOrderingChange:otherItem parentChanged:YES before:nil after:nil]; } else { // assuming the item will take the parent of the otherItem's parent, or nil item.parent = otherItem.parent; + [self announceOrderingChange:nil parentChanged:NO before:nil after:otherItem]; } moveItemAndDescendantsOrderingWithOtherItem(YES); @@ -631,6 +643,11 @@ - (BOOL)handleOrderingTouchForItemView:(MenuItemView *)itemView withOtherItemVie if (orderingTouchesBeforeOtherItem) { // trying to order the item before the otherItem + if (item.parent != otherItem.parent) { + [self announceOrderingChange:otherItem.parent parentChanged:YES before:otherItem after:nil]; + } else { + [self announceOrderingChange:nil parentChanged:NO before:otherItem after:nil]; + } // assuming the item will become the parent of the otherItem's parent, or nil item.parent = otherItem.parent; @@ -899,4 +916,47 @@ - (void)cleanUpOrderingFeedbackGenerator self.orderingFeedbackGenerator = nil; } +#pragma mark - VoiceOver + + +/// Used by VoiceOver to announce changes of the Menu UI. +/// @param parent A new parent of the current item. +/// @param parentChanged If the parent changed. Needed to infer "Top level" when parent is nil. +/// @param before A menu item that now precedes the current item. +/// @param after A menu item that now succeeds the current item. +- (void)announceOrderingChange:(MenuItem *)parent parentChanged:(BOOL)parentChanged before:(MenuItem *)before after:(MenuItem *)after { + if (!UIAccessibilityIsVoiceOverRunning()) { + return; + } + + NSMutableArray *stringArray = [[NSMutableArray alloc] init]; + + if (parentChanged) { + if (parent) { + NSString *parentString = NSLocalizedString(@"Child of %@", @"Screen reader text expressing the menu item is a child of another menu item. Argument is a name for another menu item."); + [stringArray addObject:[NSString stringWithFormat:parentString, parent.name]]; + } else { + NSString *parentString = NSLocalizedString(@"Top level", @"Screen reader text expressing the menu item is at the top level and has no parent."); + [stringArray addObject:parentString]; + } + } + + if (before) { + NSString *beforeString = NSLocalizedString(@"Before %@", @"Screen reader text expressing the menu item is before another menu item. Argument is a name for another menu item."); + [stringArray addObject:[NSString stringWithFormat:beforeString, before.name]]; + } + + if (after) { + NSString *afterString = NSLocalizedString(@"After %@", @"Screen reader text expressing the menu item is after another menu item. Argument is a name for another menu item."); + [stringArray addObject:[NSString stringWithFormat:afterString, after.name]]; + } + + if (!stringArray.count) { + return; + } + + NSString *announcement = [stringArray componentsJoinedByString:@". "]; + UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, announcement); +} + @end From fdfdf9a45f51be0da7a715eb17dab85fa82e5654 Mon Sep 17 00:00:00 2001 From: "Tanner W. Stokes" Date: Thu, 31 Mar 2022 13:20:29 -0400 Subject: [PATCH 06/11] Add tests for VoiceOver strings. --- .../System/WordPress-Bridging-Header.h | 2 + .../Menus/MenuItemsViewController.h | 2 + .../Menus/MenuItemsViewController.m | 41 ++++++----- WordPress/WordPress.xcodeproj/project.pbxproj | 20 ++++++ .../MenuItemsViewControllerTests.swift | 72 +++++++++++++++++++ 5 files changed, 118 insertions(+), 19 deletions(-) create mode 100644 WordPress/WordPressTest/Menus/Controllers/MenuItemsViewControllerTests.swift diff --git a/WordPress/Classes/System/WordPress-Bridging-Header.h b/WordPress/Classes/System/WordPress-Bridging-Header.h index 9e9ade5e18e7..c7515d1271ad 100644 --- a/WordPress/Classes/System/WordPress-Bridging-Header.h +++ b/WordPress/Classes/System/WordPress-Bridging-Header.h @@ -33,6 +33,8 @@ #import "MediaLibraryPickerDataSource.h" #import "MediaService.h" #import "MeHeaderView.h" +#import "MenuItem.h" +#import "MenuItemsViewController.h" #import "NavBarTitleDropdownButton.h" #import "NSObject+Helpers.h" diff --git a/WordPress/Classes/ViewRelated/Menus/MenuItemsViewController.h b/WordPress/Classes/ViewRelated/Menus/MenuItemsViewController.h index ebcb6e527f42..a61a10b3ea29 100644 --- a/WordPress/Classes/ViewRelated/Menus/MenuItemsViewController.h +++ b/WordPress/Classes/ViewRelated/Menus/MenuItemsViewController.h @@ -27,6 +27,8 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)removeItem:(MenuItem *)item; ++ (nullable NSString *)generateOrderingChangeVOString:(nullable MenuItem *)parent parentChanged:(BOOL)parentChanged before:(nullable MenuItem *)before after:(nullable MenuItem *)after; + @end @protocol MenuItemsViewControllerDelegate diff --git a/WordPress/Classes/ViewRelated/Menus/MenuItemsViewController.m b/WordPress/Classes/ViewRelated/Menus/MenuItemsViewController.m index 6f3b582b7ff7..d2b0eeddf056 100644 --- a/WordPress/Classes/ViewRelated/Menus/MenuItemsViewController.m +++ b/WordPress/Classes/ViewRelated/Menus/MenuItemsViewController.m @@ -918,17 +918,7 @@ - (void)cleanUpOrderingFeedbackGenerator #pragma mark - VoiceOver - -/// Used by VoiceOver to announce changes of the Menu UI. -/// @param parent A new parent of the current item. -/// @param parentChanged If the parent changed. Needed to infer "Top level" when parent is nil. -/// @param before A menu item that now precedes the current item. -/// @param after A menu item that now succeeds the current item. -- (void)announceOrderingChange:(MenuItem *)parent parentChanged:(BOOL)parentChanged before:(MenuItem *)before after:(MenuItem *)after { - if (!UIAccessibilityIsVoiceOverRunning()) { - return; - } - ++ (nullable NSString *)generateOrderingChangeVOString:(nullable MenuItem *)parent parentChanged:(BOOL)parentChanged before:(nullable MenuItem *)before after:(nullable MenuItem *)after { NSMutableArray *stringArray = [[NSMutableArray alloc] init]; if (parentChanged) { @@ -940,23 +930,36 @@ - (void)announceOrderingChange:(MenuItem *)parent parentChanged:(BOOL)parentChan [stringArray addObject:parentString]; } } - + if (after) { + NSString *afterString = NSLocalizedString(@"After %@", @"Screen reader text expressing the menu item is after another menu item. Argument is a name for another menu item."); + [stringArray addObject:[NSString stringWithFormat:afterString, after.name]]; + } if (before) { NSString *beforeString = NSLocalizedString(@"Before %@", @"Screen reader text expressing the menu item is before another menu item. Argument is a name for another menu item."); [stringArray addObject:[NSString stringWithFormat:beforeString, before.name]]; } - - if (after) { - NSString *afterString = NSLocalizedString(@"After %@", @"Screen reader text expressing the menu item is after another menu item. Argument is a name for another menu item."); - [stringArray addObject:[NSString stringWithFormat:afterString, after.name]]; + if (!stringArray.count) { + return nil; } - if (!stringArray.count) { + return [stringArray componentsJoinedByString:@". "]; +} + + +/// Used by VoiceOver to announce changes of the Menu UI. +/// @param parent A new parent of the current item. +/// @param parentChanged If the parent changed. Needed to infer "Top level" when parent is nil. +/// @param before A menu item that now precedes the current item. +/// @param after A menu item that now succeeds the current item. +- (void)announceOrderingChange:(MenuItem *)parent parentChanged:(BOOL)parentChanged before:(MenuItem *)before after:(MenuItem *)after { + if (!UIAccessibilityIsVoiceOverRunning()) { return; } - NSString *announcement = [stringArray componentsJoinedByString:@". "]; - UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, announcement); + NSString *announcement = [MenuItemsViewController generateOrderingChangeVOString:parent parentChanged:parentChanged before:before after:after]; + if (announcement) { + UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, announcement); + } } @end diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 9f98d8c6fc69..6a814fe75a7b 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -2154,6 +2154,7 @@ C3C39B0726F50D3900B1238D /* WordPressSupportSourceTag+Editor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C39B0626F50D3900B1238D /* WordPressSupportSourceTag+Editor.swift */; }; C3C39B0826F50D3900B1238D /* WordPressSupportSourceTag+Editor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C39B0626F50D3900B1238D /* WordPressSupportSourceTag+Editor.swift */; }; C3E2462926277B7700B99EA6 /* PostAuthorSelectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E2462826277B7700B99EA6 /* PostAuthorSelectorViewController.swift */; }; + C3E42AB027F4D30E00546706 /* MenuItemsViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E42AAF27F4D30E00546706 /* MenuItemsViewControllerTests.swift */; }; C533CF350E6D3ADA000C3DE8 /* CommentsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C533CF340E6D3ADA000C3DE8 /* CommentsViewController.m */; }; C545E0A21811B9880020844C /* ContextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C545E0A11811B9880020844C /* ContextManager.m */; }; C56636E91868D0CE00226AAB /* StatsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C56636E71868D0CE00226AAB /* StatsViewController.m */; }; @@ -6882,6 +6883,7 @@ C3ABE791263099F7009BD402 /* WordPress 121.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 121.xcdatamodel"; sourceTree = ""; }; C3C39B0626F50D3900B1238D /* WordPressSupportSourceTag+Editor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "WordPressSupportSourceTag+Editor.swift"; sourceTree = ""; }; C3E2462826277B7700B99EA6 /* PostAuthorSelectorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostAuthorSelectorViewController.swift; sourceTree = ""; }; + C3E42AAF27F4D30E00546706 /* MenuItemsViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuItemsViewControllerTests.swift; sourceTree = ""; }; C52812131832E071008931FD /* WordPress 13.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 13.xcdatamodel"; sourceTree = ""; }; C533CF330E6D3ADA000C3DE8 /* CommentsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommentsViewController.h; sourceTree = ""; }; C533CF340E6D3ADA000C3DE8 /* CommentsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CommentsViewController.m; sourceTree = ""; }; @@ -13343,6 +13345,7 @@ 325D3B3A23A8372500766DF6 /* Comments */, BEC8A3FD1B4BAA08001CB8C3 /* Blog */, E1B921BA1C0ED481003EA3CB /* Cells */, + C3E42AAD27F4D2CF00546706 /* Menus */, 8B7623352384372200AB3EE7 /* Pages */, 937E3AB41E3EBDC900CDA01A /* Post */, FE3D057C26C3D5A1002A51B0 /* Sharing */, @@ -13463,6 +13466,22 @@ path = Login; sourceTree = ""; }; + C3E42AAD27F4D2CF00546706 /* Menus */ = { + isa = PBXGroup; + children = ( + C3E42AAE27F4D2EC00546706 /* Controllers */, + ); + path = Menus; + sourceTree = ""; + }; + C3E42AAE27F4D2EC00546706 /* Controllers */ = { + isa = PBXGroup; + children = ( + C3E42AAF27F4D30E00546706 /* MenuItemsViewControllerTests.swift */, + ); + path = Controllers; + sourceTree = ""; + }; C533CF320E6D3AB3000C3DE8 /* Comments */ = { isa = PBXGroup; children = ( @@ -19506,6 +19525,7 @@ 321955C124BE4EBF00E3F316 /* ReaderSelectInterestsCoordinatorTests.swift in Sources */, 59B48B621B99E132008EBB84 /* JSONLoader.swift in Sources */, 3F82310F24564A870086E9B8 /* ReaderTabViewTests.swift in Sources */, + C3E42AB027F4D30E00546706 /* MenuItemsViewControllerTests.swift in Sources */, D842EA4021FABB1800210E96 /* SiteSegmentTests.swift in Sources */, 436D55F5211632B700CEAA33 /* RegisterDomainDetailsViewModelTests.swift in Sources */, E180BD4C1FB462FF00D0D781 /* CookieJarTests.swift in Sources */, diff --git a/WordPress/WordPressTest/Menus/Controllers/MenuItemsViewControllerTests.swift b/WordPress/WordPressTest/Menus/Controllers/MenuItemsViewControllerTests.swift new file mode 100644 index 000000000000..a102ad933d55 --- /dev/null +++ b/WordPress/WordPressTest/Menus/Controllers/MenuItemsViewControllerTests.swift @@ -0,0 +1,72 @@ +import UIKit +import XCTest + +@testable import WordPress + +class MenuItemsViewControllerTests: XCTestCase { + + private var context: NSManagedObjectContext! + + override func setUpWithError() throws { + context = TestContextManager().mainContext + try super.setUpWithError() + } + + override func tearDownWithError() throws { + context = nil + TestContextManager.overrideSharedInstance(nil) + try super.tearDownWithError() + } + + /// Tests that no string is provided when there is nothing to announce. + func testOrderingChangeVOStringNoChanges() { + let noChanges = MenuItemsViewController.generateOrderingChangeVOString(nil, parentChanged: false, before: nil, after: nil) + XCTAssertNil(noChanges) + + let newParent = MenuItem(context: context) + newParent.name = "New Parent" + + /// This is a programming error. parentChanged should always be set to true if the parent indeed changed. + let parentChangedFalse = MenuItemsViewController.generateOrderingChangeVOString(newParent, parentChanged: false, before: nil, after: nil) + XCTAssertNil(parentChangedFalse) + } + + /// Tests handling of changes to the menu item's parent. + func testOrderingChangeVOStringParentChanged() { + let newParent = MenuItem(context: context) + newParent.name = "New Parent" + + /// test when the parent changed but it was the top level (no parent) + let topLevelString = MenuItemsViewController.generateOrderingChangeVOString(nil, parentChanged: true, before: nil, after: nil) + XCTAssertEqual(topLevelString, "Top level") + + /// test when the parent changed and it was a menu item + let newParentString = MenuItemsViewController.generateOrderingChangeVOString(newParent, parentChanged: true, before: nil, after: nil) + XCTAssertEqual(newParentString, "Child of New Parent") + } + + /// Tests handling of changes to the menu item's parent and order. + func testOrderingChangeVOString() { + let newParent = MenuItem(context: context) + newParent.name = "New Parent" + + let afterItem = MenuItem(context: context) + afterItem.name = "Item A" + + let beforeItem = MenuItem(context: context) + beforeItem.name = "Item B" + + let beforeString = MenuItemsViewController.generateOrderingChangeVOString(nil, parentChanged: false, before: beforeItem, after: nil) + XCTAssertEqual(beforeString, "Before Item B") + + let afterString = MenuItemsViewController.generateOrderingChangeVOString(nil, parentChanged: false, before: nil, after: afterItem) + XCTAssertEqual(afterString, "After Item A") + + let parentAndBeforeString = MenuItemsViewController.generateOrderingChangeVOString(newParent, parentChanged: true, before: beforeItem, after: nil) + XCTAssertEqual(parentAndBeforeString, "Child of New Parent. Before Item B") + + let parentAndBeforeAndAfterString = MenuItemsViewController.generateOrderingChangeVOString(newParent, parentChanged: true, before: beforeItem, after: afterItem) + XCTAssertEqual(parentAndBeforeAndAfterString, "Child of New Parent. After Item A. Before Item B") + } + +} From 4db1267c550f528be9de76f7cc3480f101c286c9 Mon Sep 17 00:00:00 2001 From: "Tanner W. Stokes" Date: Thu, 31 Mar 2022 13:28:43 -0400 Subject: [PATCH 07/11] Touch up comments. --- .../Classes/ViewRelated/Menus/MenuItemsViewController.h | 6 ++++++ .../Classes/ViewRelated/Menus/MenuItemsViewController.m | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Menus/MenuItemsViewController.h b/WordPress/Classes/ViewRelated/Menus/MenuItemsViewController.h index a61a10b3ea29..670f476bf5e2 100644 --- a/WordPress/Classes/ViewRelated/Menus/MenuItemsViewController.h +++ b/WordPress/Classes/ViewRelated/Menus/MenuItemsViewController.h @@ -27,6 +27,12 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)removeItem:(MenuItem *)item; +/// Generates a string used by VoiceOver to announce menu ordering. +/// @param parent A new parent of the current item. +/// @param parentChanged If the parent changed. Needed to infer "Top level" when parent is nil. +/// @param before A menu item that now precedes the current item. +/// @param after A menu item that now succeeds the current item. +/// @return An NSString to announce, or nil. + (nullable NSString *)generateOrderingChangeVOString:(nullable MenuItem *)parent parentChanged:(BOOL)parentChanged before:(nullable MenuItem *)before after:(nullable MenuItem *)after; @end diff --git a/WordPress/Classes/ViewRelated/Menus/MenuItemsViewController.m b/WordPress/Classes/ViewRelated/Menus/MenuItemsViewController.m index d2b0eeddf056..88754e86ec56 100644 --- a/WordPress/Classes/ViewRelated/Menus/MenuItemsViewController.m +++ b/WordPress/Classes/ViewRelated/Menus/MenuItemsViewController.m @@ -945,7 +945,6 @@ + (nullable NSString *)generateOrderingChangeVOString:(nullable MenuItem *)paren return [stringArray componentsJoinedByString:@". "]; } - /// Used by VoiceOver to announce changes of the Menu UI. /// @param parent A new parent of the current item. /// @param parentChanged If the parent changed. Needed to infer "Top level" when parent is nil. From d3f0a7254fbe5222ad3d79aad439fca7ebbece6b Mon Sep 17 00:00:00 2001 From: "Tanner W. Stokes" Date: Thu, 31 Mar 2022 17:28:42 -0400 Subject: [PATCH 08/11] Add tests for menu items. --- WordPress/Classes/Models/MenuItem.h | 4 +- WordPress/Classes/Models/MenuItem.m | 4 +- WordPress/WordPress.xcodeproj/project.pbxproj | 4 + .../WordPressTest/Menus/MenuItemTests.swift | 115 ++++++++++++++++++ 4 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 WordPress/WordPressTest/Menus/MenuItemTests.swift diff --git a/WordPress/Classes/Models/MenuItem.h b/WordPress/Classes/Models/MenuItem.h index ef98b0cdde5d..0c0ab9943859 100644 --- a/WordPress/Classes/Models/MenuItem.h +++ b/WordPress/Classes/Models/MenuItem.h @@ -105,7 +105,7 @@ extern NSString * const MenuItemLinkTargetBlank; @param orderedItems an ordered set of items nested top-down as a parent followed by children. (as returned from the Menus API) @returns MenuItem the last child of self to occur in the ordered set. */ -- (MenuItem *)lastDescendantInOrderedItems:(NSOrderedSet *)orderedItems; +- (nullable MenuItem *)lastDescendantInOrderedItems:(NSOrderedSet *)orderedItems; /** The item's name is nil, empty, or the default string. @@ -118,7 +118,7 @@ extern NSString * const MenuItemLinkTargetBlank; @param orderedItems an ordered set of items nested top-down as a parent followed by children. (as returned from the Menus API) @returns MenuItem sibling that precedes self, or nil if there is not one. */ -- (MenuItem *)precedingSiblingInOrderedItems:(NSOrderedSet *)orderedItems; +- (nullable MenuItem *)precedingSiblingInOrderedItems:(NSOrderedSet *)orderedItems; @end diff --git a/WordPress/Classes/Models/MenuItem.m b/WordPress/Classes/Models/MenuItem.m index b42b2ab8daf3..d90055a2e6ff 100644 --- a/WordPress/Classes/Models/MenuItem.m +++ b/WordPress/Classes/Models/MenuItem.m @@ -94,7 +94,7 @@ - (BOOL)isDescendantOfItem:(MenuItem *)item /** Traverse the orderedItems for parent items equal to self or that are a descendant of self (a child of a child). */ -- (MenuItem *)lastDescendantInOrderedItems:(NSOrderedSet *)orderedItems +- (nullable MenuItem *)lastDescendantInOrderedItems:(NSOrderedSet *)orderedItems { MenuItem *lastChildItem = nil; NSUInteger parentIndex = [orderedItems indexOfObject:self]; @@ -118,7 +118,7 @@ - (BOOL)nameIsEmptyOrDefault /** Return a sibling that precedes self, or nil if one wasn't found. */ -- (MenuItem *)precedingSiblingInOrderedItems:(NSOrderedSet *)orderedItems +- (nullable MenuItem *)precedingSiblingInOrderedItems:(NSOrderedSet *)orderedItems { for (NSUInteger idx = [orderedItems indexOfObject:self]; idx > 0; idx--) { MenuItem *previousItem = [orderedItems objectAtIndex:idx - 1]; diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 6a814fe75a7b..2769805a8d39 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -2151,6 +2151,7 @@ C3234F5427EBBACA004ADB29 /* SiteIntentVertical.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3234F5327EBBACA004ADB29 /* SiteIntentVertical.swift */; }; C3234F5527EBBACA004ADB29 /* SiteIntentVertical.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3234F5327EBBACA004ADB29 /* SiteIntentVertical.swift */; }; C387B7A22638D66F00BDEF86 /* PostAuthorSelectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E2462826277B7700B99EA6 /* PostAuthorSelectorViewController.swift */; }; + C38C5D8127F61D2C002F517E /* MenuItemTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38C5D8027F61D2C002F517E /* MenuItemTests.swift */; }; C3C39B0726F50D3900B1238D /* WordPressSupportSourceTag+Editor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C39B0626F50D3900B1238D /* WordPressSupportSourceTag+Editor.swift */; }; C3C39B0826F50D3900B1238D /* WordPressSupportSourceTag+Editor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C39B0626F50D3900B1238D /* WordPressSupportSourceTag+Editor.swift */; }; C3E2462926277B7700B99EA6 /* PostAuthorSelectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E2462826277B7700B99EA6 /* PostAuthorSelectorViewController.swift */; }; @@ -6880,6 +6881,7 @@ C3234F5327EBBACA004ADB29 /* SiteIntentVertical.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteIntentVertical.swift; sourceTree = ""; }; C3302CC427EB67D0004229D3 /* IntentCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = IntentCell.xib; sourceTree = ""; }; C3302CC527EB67D0004229D3 /* IntentCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentCell.swift; sourceTree = ""; }; + C38C5D8027F61D2C002F517E /* MenuItemTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MenuItemTests.swift; path = Menus/MenuItemTests.swift; sourceTree = ""; }; C3ABE791263099F7009BD402 /* WordPress 121.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 121.xcdatamodel"; sourceTree = ""; }; C3C39B0626F50D3900B1238D /* WordPressSupportSourceTag+Editor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "WordPressSupportSourceTag+Editor.swift"; sourceTree = ""; }; C3E2462826277B7700B99EA6 /* PostAuthorSelectorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostAuthorSelectorViewController.swift; sourceTree = ""; }; @@ -10335,6 +10337,7 @@ 24C69AC12612467C00312D9A /* UserSettingsTestsObjc.m */, 2481B1E7260D4EAC00AE59DB /* WPAccount+LookupTests.swift */, 2481B20B260D8FED00AE59DB /* WPAccount+ObjCLookupTests.m */, + C38C5D8027F61D2C002F517E /* MenuItemTests.swift */, ); name = Models; sourceTree = ""; @@ -19377,6 +19380,7 @@ 8BE7C84123466927006EDE70 /* I18n.swift in Sources */, 806E53E427E01CFE0064315E /* DashboardStatsViewModelTests.swift in Sources */, D88A649E208D82D2008AE9BC /* XCTestCase+Wait.swift in Sources */, + C38C5D8127F61D2C002F517E /* MenuItemTests.swift in Sources */, E18549DB230FBFEF003C620E /* BlogServiceDeduplicationTests.swift in Sources */, 400A2C8F2217AD7F000A8A59 /* ClicksStatsRecordValueTests.swift in Sources */, 7320C8BD2190C9FC0082FED5 /* UITextView+SummaryTests.swift in Sources */, diff --git a/WordPress/WordPressTest/Menus/MenuItemTests.swift b/WordPress/WordPressTest/Menus/MenuItemTests.swift new file mode 100644 index 000000000000..6805f4c3e533 --- /dev/null +++ b/WordPress/WordPressTest/Menus/MenuItemTests.swift @@ -0,0 +1,115 @@ +import XCTest +import Foundation + +class MenuItemTests: XCTestCase { + + private var context: NSManagedObjectContext! + + override func setUpWithError() throws { + context = TestContextManager().mainContext + } + + override func tearDownWithError() throws { + TestContextManager.overrideSharedInstance(nil) + context.reset() + context = nil + } + + func testLastDescendantInOrderedItems() { + let itemA = newMenuItem(named: "Item A") + let itemB = newMenuItem(named: "Item B") + let itemC = newMenuItem(named: "Item C") + let itemD = newMenuItem(named: "Item D") + let itemE = newMenuItem(named: "Item E") + let itemF = newMenuItem(named: "Item F") + + let orderedItems = NSOrderedSet(array: [ + itemA, + itemB, + itemC, + itemD, + itemE, + itemF + ]) + + /* + Item A + -- Item B + ---- Item C + Item D + -- Item E + -- Item F + */ + + itemB.parent = itemA + itemC.parent = itemB + itemE.parent = itemD + itemF.parent = itemD + + /// Item B has a child, but is the last descendant of Item A + let lastDescendant = itemA.lastDescendant(inOrderedItems: orderedItems) + XCTAssertEqual(lastDescendant, itemB) + + /// Item C has no descendants + let descendantOfItemC = itemC.lastDescendant(inOrderedItems: orderedItems) + XCTAssertEqual(descendantOfItemC, nil) + + /// Item F should be the last descendant of Item D + let descendantOfItemD = itemD.lastDescendant(inOrderedItems: orderedItems) + XCTAssertEqual(descendantOfItemD, itemF) + } + + func testPrecedingSiblingInOrderedItems() { + let itemA = newMenuItem(named: "Item A") + let itemB = newMenuItem(named: "Item B") + let itemC = newMenuItem(named: "Item C") + let itemD = newMenuItem(named: "Item D") + let itemE = newMenuItem(named: "Item E") + let itemF = newMenuItem(named: "Item F") + + let orderedItems = NSOrderedSet(array: [ + itemA, + itemB, + itemC, + itemD, + itemE, + itemF + ]) + + /* + Item A + -- Item B + ---- Item C + Item D + -- Item E + -- Item F + */ + + itemB.parent = itemA + itemC.parent = itemB + itemE.parent = itemD + itemF.parent = itemD + + let precedingSiblingForItemA = itemA.precedingSibling(inOrderedItems: orderedItems) + XCTAssertNil(precedingSiblingForItemA) + + let precedingSiblingForItemD = itemD.precedingSibling(inOrderedItems: orderedItems) + XCTAssertEqual(precedingSiblingForItemD, itemA) + + let precedingSiblingForItemF = itemF.precedingSibling(inOrderedItems: orderedItems) + XCTAssertEqual(precedingSiblingForItemF, itemE) + } + + // MARK: - Private Helpers + + fileprivate func newMenuItem(named name: String) -> MenuItem { + let entityName = MenuItem.classNameWithoutNamespaces() + let entity = NSEntityDescription.insertNewObject(forEntityName: entityName, into: context) + + let menuItem = entity as! MenuItem + // set a name to make debugging easier + menuItem.name = name + + return menuItem + } +} From 23f609a1ffbdf57b0a3567aed579beb421a6b5a0 Mon Sep 17 00:00:00 2001 From: "Tanner W. Stokes" Date: Thu, 31 Mar 2022 17:32:41 -0400 Subject: [PATCH 09/11] Add descendant tests. --- .../WordPressTest/Menus/MenuItemTests.swift | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/WordPress/WordPressTest/Menus/MenuItemTests.swift b/WordPress/WordPressTest/Menus/MenuItemTests.swift index 6805f4c3e533..bd60c29ce9d5 100644 --- a/WordPress/WordPressTest/Menus/MenuItemTests.swift +++ b/WordPress/WordPressTest/Menus/MenuItemTests.swift @@ -15,6 +15,41 @@ class MenuItemTests: XCTestCase { context = nil } + func testIsDescendantOfItem() { + let itemA = newMenuItem(named: "Item A") + let itemB = newMenuItem(named: "Item B") + let itemC = newMenuItem(named: "Item C") + let itemD = newMenuItem(named: "Item D") + let itemE = newMenuItem(named: "Item E") + let itemF = newMenuItem(named: "Item F") + + /* + Item A + -- Item B + ---- Item C + Item D + -- Item E + -- Item F + */ + + itemB.parent = itemA + itemC.parent = itemB + itemE.parent = itemD + itemF.parent = itemD + + let bIsDescendantOfA = itemB.isDescendant(of: itemA) + XCTAssertTrue(bIsDescendantOfA) + + let cIsDescendantOfA = itemC.isDescendant(of: itemA) + XCTAssertTrue(cIsDescendantOfA) + + let fIsDescendantOfD = itemF.isDescendant(of: itemD) + XCTAssertTrue(fIsDescendantOfD) + + let fIsNotDescendantOfA = itemF.isDescendant(of: itemA) + XCTAssertFalse(fIsNotDescendantOfA) + } + func testLastDescendantInOrderedItems() { let itemA = newMenuItem(named: "Item A") let itemB = newMenuItem(named: "Item B") From 7cbfaf0c76b354bcaf776e326e21a0015bc5963a Mon Sep 17 00:00:00 2001 From: "Tanner W. Stokes" Date: Thu, 31 Mar 2022 17:37:04 -0400 Subject: [PATCH 10/11] Cleanup tests. --- .../MenuItemsViewControllerTests.swift | 94 +++++++++++++------ .../WordPressTest/Menus/MenuItemTests.swift | 10 +- 2 files changed, 72 insertions(+), 32 deletions(-) diff --git a/WordPress/WordPressTest/Menus/Controllers/MenuItemsViewControllerTests.swift b/WordPress/WordPressTest/Menus/Controllers/MenuItemsViewControllerTests.swift index a102ad933d55..366351356136 100644 --- a/WordPress/WordPressTest/Menus/Controllers/MenuItemsViewControllerTests.swift +++ b/WordPress/WordPressTest/Menus/Controllers/MenuItemsViewControllerTests.swift @@ -1,8 +1,5 @@ -import UIKit import XCTest -@testable import WordPress - class MenuItemsViewControllerTests: XCTestCase { private var context: NSManagedObjectContext! @@ -20,53 +17,96 @@ class MenuItemsViewControllerTests: XCTestCase { /// Tests that no string is provided when there is nothing to announce. func testOrderingChangeVOStringNoChanges() { - let noChanges = MenuItemsViewController.generateOrderingChangeVOString(nil, parentChanged: false, before: nil, after: nil) + let noChanges = MenuItemsViewController.generateOrderingChangeVOString( + nil, + parentChanged: false, + before: nil, + after: nil + ) XCTAssertNil(noChanges) - let newParent = MenuItem(context: context) - newParent.name = "New Parent" + let newParent = newMenuItem(named: "New Parent") /// This is a programming error. parentChanged should always be set to true if the parent indeed changed. - let parentChangedFalse = MenuItemsViewController.generateOrderingChangeVOString(newParent, parentChanged: false, before: nil, after: nil) + let parentChangedFalse = MenuItemsViewController.generateOrderingChangeVOString( + newParent, + parentChanged: false, + before: nil, + after: nil + ) XCTAssertNil(parentChangedFalse) } /// Tests handling of changes to the menu item's parent. func testOrderingChangeVOStringParentChanged() { - let newParent = MenuItem(context: context) - newParent.name = "New Parent" - - /// test when the parent changed but it was the top level (no parent) - let topLevelString = MenuItemsViewController.generateOrderingChangeVOString(nil, parentChanged: true, before: nil, after: nil) + let newParent = newMenuItem(named: "New Parent") + + let topLevelString = MenuItemsViewController.generateOrderingChangeVOString( + nil, + parentChanged: true, + before: nil, + after: nil + ) XCTAssertEqual(topLevelString, "Top level") - /// test when the parent changed and it was a menu item - let newParentString = MenuItemsViewController.generateOrderingChangeVOString(newParent, parentChanged: true, before: nil, after: nil) + let newParentString = MenuItemsViewController.generateOrderingChangeVOString( + newParent, + parentChanged: true, + before: nil, + after: nil + ) XCTAssertEqual(newParentString, "Child of New Parent") } /// Tests handling of changes to the menu item's parent and order. func testOrderingChangeVOString() { - let newParent = MenuItem(context: context) - newParent.name = "New Parent" - - let afterItem = MenuItem(context: context) - afterItem.name = "Item A" - - let beforeItem = MenuItem(context: context) - beforeItem.name = "Item B" - - let beforeString = MenuItemsViewController.generateOrderingChangeVOString(nil, parentChanged: false, before: beforeItem, after: nil) + let newParent = newMenuItem(named: "New Parent") + let afterItem = newMenuItem(named: "Item A") + let beforeItem = newMenuItem(named: "Item B") + + let beforeString = MenuItemsViewController.generateOrderingChangeVOString( + nil, + parentChanged: false, + before: beforeItem, + after: nil + ) XCTAssertEqual(beforeString, "Before Item B") - let afterString = MenuItemsViewController.generateOrderingChangeVOString(nil, parentChanged: false, before: nil, after: afterItem) + let afterString = MenuItemsViewController.generateOrderingChangeVOString( + nil, + parentChanged: false, + before: nil, + after: afterItem + ) XCTAssertEqual(afterString, "After Item A") - let parentAndBeforeString = MenuItemsViewController.generateOrderingChangeVOString(newParent, parentChanged: true, before: beforeItem, after: nil) + let parentAndBeforeString = MenuItemsViewController.generateOrderingChangeVOString( + newParent, + parentChanged: true, + before: beforeItem, + after: nil + ) XCTAssertEqual(parentAndBeforeString, "Child of New Parent. Before Item B") - let parentAndBeforeAndAfterString = MenuItemsViewController.generateOrderingChangeVOString(newParent, parentChanged: true, before: beforeItem, after: afterItem) + let parentAndBeforeAndAfterString = MenuItemsViewController.generateOrderingChangeVOString( + newParent, + parentChanged: true, + before: beforeItem, + after: afterItem + ) XCTAssertEqual(parentAndBeforeAndAfterString, "Child of New Parent. After Item A. Before Item B") } + // MARK: - Private Helpers + + fileprivate func newMenuItem(named name: String) -> MenuItem { + let entityName = MenuItem.classNameWithoutNamespaces() + let entity = NSEntityDescription.insertNewObject(forEntityName: entityName, into: context) + + let menuItem = entity as! MenuItem + // set a name to make debugging easier + menuItem.name = name + + return menuItem + } } diff --git a/WordPress/WordPressTest/Menus/MenuItemTests.swift b/WordPress/WordPressTest/Menus/MenuItemTests.swift index bd60c29ce9d5..33735f28dacb 100644 --- a/WordPress/WordPressTest/Menus/MenuItemTests.swift +++ b/WordPress/WordPressTest/Menus/MenuItemTests.swift @@ -15,6 +15,7 @@ class MenuItemTests: XCTestCase { context = nil } + /// Tests detection of descendants. func testIsDescendantOfItem() { let itemA = newMenuItem(named: "Item A") let itemB = newMenuItem(named: "Item B") @@ -46,10 +47,11 @@ class MenuItemTests: XCTestCase { let fIsDescendantOfD = itemF.isDescendant(of: itemD) XCTAssertTrue(fIsDescendantOfD) - let fIsNotDescendantOfA = itemF.isDescendant(of: itemA) - XCTAssertFalse(fIsNotDescendantOfA) + let fIsDescendantOfA = itemF.isDescendant(of: itemA) + XCTAssertFalse(fIsDescendantOfA) } + /// Tests that the last descendant of an item is found. func testLastDescendantInOrderedItems() { let itemA = newMenuItem(named: "Item A") let itemB = newMenuItem(named: "Item B") @@ -81,19 +83,17 @@ class MenuItemTests: XCTestCase { itemE.parent = itemD itemF.parent = itemD - /// Item B has a child, but is the last descendant of Item A let lastDescendant = itemA.lastDescendant(inOrderedItems: orderedItems) XCTAssertEqual(lastDescendant, itemB) - /// Item C has no descendants let descendantOfItemC = itemC.lastDescendant(inOrderedItems: orderedItems) XCTAssertEqual(descendantOfItemC, nil) - /// Item F should be the last descendant of Item D let descendantOfItemD = itemD.lastDescendant(inOrderedItems: orderedItems) XCTAssertEqual(descendantOfItemD, itemF) } + /// Tests that preceding siblings are found. func testPrecedingSiblingInOrderedItems() { let itemA = newMenuItem(named: "Item A") let itemB = newMenuItem(named: "Item B") From c41c600f7189eb7ea09226ab44eef605402a27cf Mon Sep 17 00:00:00 2001 From: "Tanner W. Stokes" Date: Tue, 5 Apr 2022 12:34:04 -0400 Subject: [PATCH 11/11] Add release note. --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 40792867bf6c..10c6d49fbfcf 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,5 +1,6 @@ 19.7 ----- +* [*] a11y: VoiceOver has been improved on the Menus view and now announces changes to ordering. [#18155] 19.6