blob: 48fa21ca41ad9386df2c7f93d0871f9965cd7505 [file] [log] [blame] [edit]
// Copyright 2017-present the Material Components for iOS authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <XCTest/XCTest.h>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wprivate-header"
#import "MDCBottomNavigationBar+Private.h"
#import "MDCBottomNavigationItemView.h"
#pragma clang diagnostic pop
#import "MDCAvailability.h"
#import "MDCBottomNavigationBar.h"
#import "MDCShadowElevations.h"
NS_ASSUME_NONNULL_BEGIN
static NSString *const kTestItemTitleText = @"Title";
/**
A testing MDCBottomNavigationBar that allows safeAreaInsets to be set programmatically.
*/
@interface MDCSafeAreaCustomizingBottomNavigationBar : MDCBottomNavigationBar
/** Set this to override the value returned by @c safeAreaInsets. */
@property(nonatomic, assign) UIEdgeInsets test_safeAreaInsets;
@end
@implementation MDCSafeAreaCustomizingBottomNavigationBar
- (UIEdgeInsets)safeAreaInsets {
return self.test_safeAreaInsets;
}
@end
@interface MDCBottomNavigationBar (Testing)
@property(nonatomic, strong) NSMutableArray<MDCBottomNavigationItemView *> *itemViews;
@end
@interface MDCBottomNavigationItemView (Testing)
@property(nonatomic, strong) UILabel *label;
@end
@interface MDCBottomNavigationBarTests : XCTestCase
@property(nonatomic, strong, nullable) MDCBottomNavigationBar *bottomNavBar;
@end
@implementation MDCBottomNavigationBarTests
- (void)setUp {
self.bottomNavBar = [[MDCBottomNavigationBar alloc] init];
}
- (void)tearDown {
self.bottomNavBar = nil;
}
#pragma mark - Fonts
- (void)testItemTitleFontDefault {
// Then
XCTAssertNotNil(self.bottomNavBar.itemTitleFont);
}
- (void)testItemTitleFontAppliesToNewItems {
// Given
UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];
// When
self.bottomNavBar.itemTitleFont = [UIFont systemFontOfSize:31];
self.bottomNavBar.items = @[ item1, item2 ];
// Then
for (MDCBottomNavigationItemView *item in self.bottomNavBar.itemViews) {
XCTAssertEqualObjects(item.itemTitleFont, self.bottomNavBar.itemTitleFont);
}
}
- (void)testItemTitleFontAppliesToExistingItems {
// Given
UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];
// When
self.bottomNavBar.items = @[ item1, item2 ];
self.bottomNavBar.itemTitleFont = [UIFont systemFontOfSize:31];
// Then
for (MDCBottomNavigationItemView *item in self.bottomNavBar.itemViews) {
XCTAssertEqualObjects(item.itemTitleFont, self.bottomNavBar.itemTitleFont);
}
}
- (void)testItemReset {
// Given
UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];
UITabBarItem *item3 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];
// When
self.bottomNavBar.items = @[ item1, item2 ];
self.bottomNavBar.items = @[ item1, item2, item3 ];
// Then
NSUInteger tabsCount = 3;
XCTAssertEqual(self.bottomNavBar.itemViews.count, tabsCount);
}
- (void)testFramesAfterReset {
// Given
UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:1];
UITabBarItem *item3 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:2];
[self.bottomNavBar sizeToFit];
self.bottomNavBar.frame = CGRectMake(0, 0, 320, 56);
// When
self.bottomNavBar.items = @[ item1, item2 ];
self.bottomNavBar.items = @[ item1, item2, item3 ];
[self.bottomNavBar layoutIfNeeded];
// Then
XCTAssertFalse(CGRectEqualToRect(self.bottomNavBar.itemViews[0].frame, CGRectZero));
}
- (void)testSelectedItemAfterReset {
// Given
UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];
UITabBarItem *item3 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];
// When
self.bottomNavBar.items = @[ item1, item2 ];
self.bottomNavBar.items = @[ item1, item2, item3 ];
// Then
XCTAssertNil(self.bottomNavBar.selectedItem);
}
- (NSInteger)countOfViewsWithAccessibilityIdentifier:(NSString *)accessibilityIdentifier
fromRootView:(UIView *)view {
NSInteger sum = [view.accessibilityIdentifier isEqualToString:accessibilityIdentifier] ? 1 : 0;
for (UIView *subview in view.subviews) {
sum += [self countOfViewsWithAccessibilityIdentifier:accessibilityIdentifier
fromRootView:subview];
}
return sum;
}
- (void)testSettingAccessibilityIdentifierAffectsExactlyOneSubview {
// Given
UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:@"Title" image:nil tag:1];
item.accessibilityIdentifier = @"__i_d__";
// When
self.bottomNavBar.items = @[ item ];
// Then
XCTAssertEqual([self countOfViewsWithAccessibilityIdentifier:item.accessibilityIdentifier
fromRootView:self.bottomNavBar],
1);
}
- (void)testAccessibilityLabelInitialValue {
// Given
NSString *initialLabel = @"initialLabel";
UITabBarItem *tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Home" image:nil tag:0];
tabBarItem.accessibilityLabel = initialLabel;
MDCBottomNavigationBar *bar = [[MDCBottomNavigationBar alloc] init];
// When
bar.items = @[ tabBarItem ];
// Then
XCTAssertEqualObjects(bar.itemViews.firstObject.accessibilityLabel, initialLabel);
}
- (void)testAccessibilityLabelValueChanged {
// Given
NSString *oldLabel = @"oldLabel";
NSString *newLabel = @"newLabel";
UITabBarItem *tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Home" image:nil tag:0];
tabBarItem.accessibilityLabel = oldLabel;
MDCBottomNavigationBar *bar = [[MDCBottomNavigationBar alloc] init];
bar.items = @[ tabBarItem ];
// When
tabBarItem.accessibilityLabel = newLabel;
// Then
XCTAssertEqualObjects(bar.itemViews.firstObject.accessibilityLabel, newLabel);
}
- (void)testAccessibilityHintInitialValue {
// Given
NSString *initialHint = @"initialHint";
UITabBarItem *tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Home" image:nil tag:0];
tabBarItem.accessibilityHint = initialHint;
MDCBottomNavigationBar *bar = [[MDCBottomNavigationBar alloc] init];
// When
bar.items = @[ tabBarItem ];
// Then
MDCBottomNavigationItemView *itemView = bar.itemViews.firstObject;
UIButton *itemViewButton = itemView.button;
XCTAssertEqualObjects(itemViewButton.accessibilityHint, initialHint);
}
- (void)testAccessibilityHintValueChanged {
// Given
NSString *oldHint = @"oldHint";
NSString *newHint = @"newHint";
UITabBarItem *tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Home" image:nil tag:0];
tabBarItem.accessibilityHint = oldHint;
MDCBottomNavigationBar *bar = [[MDCBottomNavigationBar alloc] init];
bar.items = @[ tabBarItem ];
// When
tabBarItem.accessibilityHint = newHint;
// Then
MDCBottomNavigationItemView *itemView = bar.itemViews.firstObject;
UIButton *itemViewButton = itemView.button;
XCTAssertEqualObjects(itemViewButton.accessibilityHint, newHint);
}
- (void)testIsAccessibilityElementInitialValue {
// Given
UITabBarItem *tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Home" image:nil tag:0];
tabBarItem.isAccessibilityElement = NO;
MDCBottomNavigationBar *bar = [[MDCBottomNavigationBar alloc] init];
// When
bar.items = @[ tabBarItem ];
// Then
XCTAssert(!bar.itemViews.firstObject.isAccessibilityElement);
}
- (void)testIsAccessibilityElementValueChanged {
// Given
UITabBarItem *tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Home" image:nil tag:0];
tabBarItem.isAccessibilityElement = NO;
MDCBottomNavigationBar *bar = [[MDCBottomNavigationBar alloc] init];
bar.items = @[ tabBarItem ];
// When
tabBarItem.isAccessibilityElement = YES;
// Then
XCTAssert(bar.itemViews.firstObject.isAccessibilityElement);
}
- (void)testTagInitialValue {
// Given
UITabBarItem *tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Home" image:nil tag:0];
tabBarItem.tag = 1234;
MDCBottomNavigationBar *bar = [[MDCBottomNavigationBar alloc] init];
// When
bar.items = @[ tabBarItem ];
// Then
XCTAssertEqual(bar.itemViews.firstObject.tag, tabBarItem.tag);
}
- (void)testTagValueChanged {
// Given
UITabBarItem *tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Home" image:nil tag:0];
tabBarItem.tag = 1234;
MDCBottomNavigationBar *bar = [[MDCBottomNavigationBar alloc] init];
bar.items = @[ tabBarItem ];
// When
tabBarItem.tag = 4321;
// Then
XCTAssertEqual(bar.itemViews.firstObject.tag, tabBarItem.tag);
}
- (void)testTitleVisibility {
UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];
self.bottomNavBar.items = @[ item1, item2 ];
self.bottomNavBar.titleVisibility = MDCBottomNavigationBarTitleVisibilityNever;
for (MDCBottomNavigationItemView *itemView in self.bottomNavBar.itemViews) {
XCTAssertEqualWithAccuracy(itemView.label.alpha, 0.0, 0.001);
}
self.bottomNavBar.titleVisibility = MDCBottomNavigationBarTitleVisibilityAlways;
for (MDCBottomNavigationItemView *itemView in self.bottomNavBar.itemViews) {
XCTAssertEqualWithAccuracy(itemView.label.alpha, 1.0, 0.001);
}
self.bottomNavBar.titleVisibility = MDCBottomNavigationBarTitleVisibilitySelected;
self.bottomNavBar.itemViews.firstObject.selected = YES;
self.bottomNavBar.itemViews.lastObject.selected = NO;
XCTAssertEqualWithAccuracy(self.bottomNavBar.itemViews.firstObject.label.alpha, 1.0, 0.001);
XCTAssertEqualWithAccuracy(self.bottomNavBar.itemViews.lastObject.label.alpha, 0.0, 0.001);
}
- (void)testDefaultElevation {
// Then
XCTAssertEqualWithAccuracy(self.bottomNavBar.elevation, MDCShadowElevationBottomNavigationBar,
0.001);
}
- (void)testCustomElevation {
// Given
CGFloat customElevation = 20;
// When
self.bottomNavBar.elevation = customElevation;
// Then
XCTAssertEqualWithAccuracy(self.bottomNavBar.elevation, customElevation, 0.001);
}
- (void)testDefaultShadowColor {
// Then
XCTAssertEqualObjects(self.bottomNavBar.shadowColor, UIColor.blackColor);
XCTAssertTrue(
CGColorEqualToColor(self.bottomNavBar.layer.shadowColor, UIColor.blackColor.CGColor),
@"(%@) is not equal to (%@)", self.bottomNavBar.layer.shadowColor,
UIColor.blackColor.CGColor);
}
- (void)testCustomShadowColor {
// Given
UIColor *fakeColor = UIColor.orangeColor;
// When
self.bottomNavBar.shadowColor = fakeColor;
// Then
XCTAssertEqualObjects(self.bottomNavBar.shadowColor, fakeColor);
XCTAssertTrue(CGColorEqualToColor(self.bottomNavBar.layer.shadowColor, fakeColor.CGColor),
@"(%@) is not equal to (%@)", self.bottomNavBar.layer.shadowColor,
fakeColor.CGColor);
}
- (void)testViewForItemFound {
// Given
UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];
// When
self.bottomNavBar.items = @[ item1, item2 ];
// Then
MDCBottomNavigationItemView *viewForItem1 =
(MDCBottomNavigationItemView *)[self.bottomNavBar viewForItem:item1];
MDCBottomNavigationItemView *viewForItem2 =
(MDCBottomNavigationItemView *)[self.bottomNavBar viewForItem:item2];
XCTAssertNotEqual(viewForItem1, viewForItem2);
XCTAssertTrue(
[self.bottomNavBar.itemViews containsObject:viewForItem1],
@"BottomNavBar.itemViews did not contain the view (%@) returned for UITabBarItem (%@)",
viewForItem1, item1);
XCTAssertTrue(
[self.bottomNavBar.itemViews containsObject:viewForItem2],
@"BottomNavBar.itemViews did not contain the view (%@) returned for UITabBarItem (%@)",
viewForItem2, item2);
}
- (void)testViewForItemNotFound {
// Given
UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];
UITabBarItem *item3 = [[UITabBarItem alloc] initWithTitle:@"3" image:nil tag:0];
// When
self.bottomNavBar.items = @[ item1, item2 ];
// Then
XCTAssert([self.bottomNavBar viewForItem:item3] == nil);
}
- (void)testSizeThatFitsDefaultIncludesSafeArea {
// Given
CGRect barFrame = CGRectMake(0, 0, 360, 56);
MDCSafeAreaCustomizingBottomNavigationBar *bottomNavBar =
[[MDCSafeAreaCustomizingBottomNavigationBar alloc] initWithFrame:barFrame];
bottomNavBar.test_safeAreaInsets = UIEdgeInsetsZero;
CGSize initialSize = [bottomNavBar sizeThatFits:barFrame.size];
// When
bottomNavBar.test_safeAreaInsets = UIEdgeInsetsMake(20, 20, 20, 20);
// Then
CGSize finalSize = [bottomNavBar sizeThatFits:barFrame.size];
XCTAssertFalse(CGSizeEqualToSize(finalSize, CGSizeZero),
"sizeThatFits: should not return CGSizeZero");
XCTAssertTrue(CGSizeEqualToSize(finalSize, initialSize), @"(%@) is not equal to (%@)",
NSStringFromCGSize(finalSize), NSStringFromCGSize(initialSize));
}
- (void)testSizeThatFitsExcludesSafeArea {
// Given
CGRect barFrame = CGRectMake(0, 0, 360, 56);
MDCSafeAreaCustomizingBottomNavigationBar *bottomNavBar =
[[MDCSafeAreaCustomizingBottomNavigationBar alloc] initWithFrame:barFrame];
bottomNavBar.test_safeAreaInsets = UIEdgeInsetsZero;
CGSize initialSize = [bottomNavBar sizeThatFits:barFrame.size];
// When
bottomNavBar.test_safeAreaInsets = UIEdgeInsetsMake(20, 20, 20, 20);
// Then
CGSize finalSize = [bottomNavBar sizeThatFits:barFrame.size];
XCTAssertFalse(CGSizeEqualToSize(finalSize, CGSizeZero),
"sizeThatFits: should not return CGSizeZero");
XCTAssertTrue(CGSizeEqualToSize(finalSize, initialSize), @"(%@) is not equal to (%@)",
NSStringFromCGSize(finalSize), NSStringFromCGSize(initialSize));
}
/*
Tests when the bottomNavBar width is not evenly divisible by the number of items, the difference
between start of the frame and center of the first element and end of the frame and center of the
last element is max one pixel
*/
- (void)testLaysoutAcrossBarEvenly {
// Given
UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];
UITabBarItem *item3 = [[UITabBarItem alloc] initWithTitle:@"3" image:nil tag:0];
UITabBarItem *item4 = [[UITabBarItem alloc] initWithTitle:@"4" image:nil tag:0];
UITabBarItem *item5 = [[UITabBarItem alloc] initWithTitle:@"5" image:nil tag:0];
CGFloat bottomNavBarWidth = 304;
self.bottomNavBar.frame = CGRectMake(0, 0, bottomNavBarWidth, 56);
// When
self.bottomNavBar.items = @[ item1, item2, item3, item4, item5 ];
[self.bottomNavBar layoutIfNeeded];
// Then
MDCBottomNavigationItemView *viewForItem1 =
(MDCBottomNavigationItemView *)[self.bottomNavBar viewForItem:item1];
MDCBottomNavigationItemView *viewForItem5 =
(MDCBottomNavigationItemView *)[self.bottomNavBar viewForItem:item5];
XCTAssertEqualWithAccuracy(viewForItem1.center.x, bottomNavBarWidth - viewForItem5.center.x, 1.0);
}
#pragma mark - Autolayout support
- (void)testIntrinsicContentSizeIgnoresSafeArea {
// Given
CGRect barFrame = CGRectMake(0, 0, 360, 56);
MDCSafeAreaCustomizingBottomNavigationBar *bottomNavBar =
[[MDCSafeAreaCustomizingBottomNavigationBar alloc] initWithFrame:barFrame];
bottomNavBar.test_safeAreaInsets = UIEdgeInsetsZero;
CGSize initialSize = [bottomNavBar intrinsicContentSize];
// When
bottomNavBar.test_safeAreaInsets = UIEdgeInsetsMake(20, 20, 20, 20);
// Then
CGSize finalSize = [bottomNavBar intrinsicContentSize];
XCTAssertFalse(CGSizeEqualToSize(finalSize, CGSizeZero),
"intrinsicContentSize should not return CGSizeZero");
XCTAssertTrue(CGSizeEqualToSize(finalSize, initialSize), @"(%@) is not equal to (%@)",
NSStringFromCGSize(finalSize), NSStringFromCGSize(initialSize));
}
#pragma mark - Property propagation
- (void)testTitlesNumberOfLinesPassedToViewsBeforeItemsAssigned {
// Given
UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];
self.bottomNavBar.alignment = MDCBottomNavigationBarAlignmentJustified;
// When
self.bottomNavBar.titlesNumberOfLines = 7;
self.bottomNavBar.items = @[ item1, item2 ];
// Then
for (MDCBottomNavigationItemView *itemView in self.bottomNavBar.itemViews) {
XCTAssertEqual(itemView.titleNumberOfLines, self.bottomNavBar.titlesNumberOfLines);
}
}
- (void)testTitlesNumberOfLinesPassedToViewsAfterItemsAssigned {
// Given
UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];
self.bottomNavBar.alignment = MDCBottomNavigationBarAlignmentJustified;
// When
self.bottomNavBar.items = @[ item1, item2 ];
self.bottomNavBar.titlesNumberOfLines = 7;
// Then
for (MDCBottomNavigationItemView *itemView in self.bottomNavBar.itemViews) {
XCTAssertEqual(itemView.titleNumberOfLines, self.bottomNavBar.titlesNumberOfLines);
}
}
- (void)testItemForPointInsideItemViewReturnsCorrespondingItem {
// Given
UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];
self.bottomNavBar.frame = CGRectMake(0, 0, 320, 56);
// When
self.bottomNavBar.items = @[ item1, item2 ];
[self.bottomNavBar layoutIfNeeded];
UIView *view1 = [self.bottomNavBar viewForItem:item1];
UIView *view2 = [self.bottomNavBar viewForItem:item2];
UITabBarItem *result1 = [self.bottomNavBar tabBarItemForPoint:view1.center];
UITabBarItem *result2 = [self.bottomNavBar tabBarItemForPoint:view2.center];
// Then
XCTAssertEqualObjects(result1, item1);
XCTAssertEqualObjects(result2, item2);
}
- (void)testItemForPointOutsideNavigationBarReturnsNil {
// Given
UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];
CGFloat navBarHeight = 56;
self.bottomNavBar.frame = CGRectMake(0, 0, 320, navBarHeight);
CGPoint testPoint = CGPointMake(0, navBarHeight + 10);
// When
self.bottomNavBar.items = @[ item1, item2 ];
[self.bottomNavBar layoutIfNeeded];
UITabBarItem *result = [self.bottomNavBar tabBarItemForPoint:testPoint];
// Then
XCTAssertNil(result);
}
- (void)testItemForPointInsideNavigationBarNoTabBarItemsReturnsNil {
// Given
self.bottomNavBar.frame = CGRectMake(0, 0, 320, 56);
// When
[self.bottomNavBar layoutIfNeeded];
UITabBarItem *result = [self.bottomNavBar tabBarItemForPoint:self.bottomNavBar.center];
// Then
XCTAssertNil(result);
}
- (NSInteger)countOfButtonsWithAccessibilityHint:(NSString *)accessibilityHint
fromRootView:(UIView *)view {
BOOL foundMatchingElement = NO;
foundMatchingElement = ([view isKindOfClass:[UIButton class]] &&
[view.accessibilityHint isEqualToString:accessibilityHint]);
NSInteger count = foundMatchingElement ? 1 : 0;
for (UIView *subview in view.subviews) {
count += [self countOfButtonsWithAccessibilityHint:accessibilityHint fromRootView:subview];
}
return count;
}
- (void)testSettingAccessibilityHintPropagatesToOneUIButtonSubview {
// Given
UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:@"Title" image:nil tag:9];
item.accessibilityHint = @"__h_i_n_t__";
// When
self.bottomNavBar.items = @[ item ];
// Then
XCTAssertEqual([self countOfButtonsWithAccessibilityHint:item.accessibilityHint
fromRootView:self.bottomNavBar],
1);
}
#pragma mark - traitCollectionDidChangeBlock
- (void)testTraitCollectionDidChangeBlockCalledWhenTraitCollectionChanges {
// Given
__block MDCBottomNavigationBar *passedBottomNavigationBar = nil;
__block UITraitCollection *passedTraitCollection = nil;
XCTestExpectation *expectation =
[self expectationWithDescription:@"Called traitCollectionDidChangeBlock"];
UITraitCollection *testTraitCollection = [UITraitCollection traitCollectionWithDisplayScale:77];
void (^block)(MDCBottomNavigationBar *_Nonnull, UITraitCollection *_Nullable) = ^void(
MDCBottomNavigationBar *bottomNavigationBar, UITraitCollection *previousTraitCollection) {
passedBottomNavigationBar = bottomNavigationBar;
passedTraitCollection = previousTraitCollection;
[expectation fulfill];
};
self.bottomNavBar.traitCollectionDidChangeBlock = block;
// When
[self.bottomNavBar traitCollectionDidChange:testTraitCollection];
[self waitForExpectations:@[ expectation ] timeout:1];
// Then
XCTAssertEqual(passedBottomNavigationBar, self.bottomNavBar);
XCTAssertEqual(passedTraitCollection, testTraitCollection);
}
#pragma mark - MaterialElevation
- (void)testDefaultValueForOverrideBaseElevationIsNegative {
// Given
MDCBottomNavigationBar *bottomNavigationBar = [[MDCBottomNavigationBar alloc] init];
// Then
XCTAssertLessThan(bottomNavigationBar.mdc_overrideBaseElevation, 0);
}
- (void)testSettingOverrideBaseElevationReturnsSetValue {
// Given
CGFloat expectedBaseElevation = 99;
// When
self.bottomNavBar.mdc_overrideBaseElevation = expectedBaseElevation;
// Then
XCTAssertEqualWithAccuracy(self.bottomNavBar.mdc_overrideBaseElevation, expectedBaseElevation,
0.001);
}
- (void)testCurrentElevationMatchesElevationWhenElevationChanges {
// When
self.bottomNavBar.elevation = MDCShadowElevationQuickEntryResting;
// Then
XCTAssertEqualWithAccuracy(self.bottomNavBar.mdc_currentElevation, self.bottomNavBar.elevation,
0.001);
}
- (void)testElevationDidChangeBlockCalledWhenElevationChangesValue {
// Given
self.bottomNavBar.elevation = 5;
__block BOOL blockCalled = NO;
self.bottomNavBar.mdc_elevationDidChangeBlock = ^(id<MDCElevatable> _, CGFloat elevation) {
blockCalled = YES;
};
// When
self.bottomNavBar.elevation = self.bottomNavBar.elevation + 1;
// Then
XCTAssertTrue(blockCalled);
}
- (void)testElevationDidChangeBlockNotCalledWhenElevationIsSetWithoutChangingValue {
// Given
self.bottomNavBar.elevation = 5;
__block BOOL blockCalled = NO;
self.bottomNavBar.mdc_elevationDidChangeBlock = ^(id<MDCElevatable> _, CGFloat elevation) {
blockCalled = YES;
};
// When
self.bottomNavBar.elevation = self.bottomNavBar.elevation;
// Then
XCTAssertFalse(blockCalled);
}
- (void)testSettingBarHeightPropertyUpdatesIntrinsicContentSizeOfBar {
// Given
CGFloat barHeight = 20;
// When
self.bottomNavBar.barHeight = barHeight;
// Then
XCTAssertEqual(self.bottomNavBar.intrinsicContentSize.height, barHeight);
}
- (void)testSettingBarHeightPropertyUpdatesSizeThatFitsOfBar {
// Given
CGFloat barHeight = 20;
// When
self.bottomNavBar.barHeight = barHeight;
// Then
XCTAssertEqual([self.bottomNavBar sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)].height,
barHeight);
}
- (void)testSettingSelectionIndicatorColor {
// Given
UIColor *testColor = UIColor.greenColor;
UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:@"Title" image:nil tag:0];
// When
self.bottomNavBar.items = @[ item ];
self.bottomNavBar.selectionIndicatorColor = testColor;
// Then
XCTAssertEqualObjects(self.bottomNavBar.itemViews.firstObject.selectionIndicatorColor,
UIColor.greenColor);
}
- (void)testSettingSelectionIndicatorColorBeforeSettingItems {
// Given
UIColor *testColor = UIColor.greenColor;
UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:@"Title" image:nil tag:0];
// When
self.bottomNavBar.selectionIndicatorColor = testColor;
self.bottomNavBar.items = @[ item ];
// Then
XCTAssertEqualObjects(self.bottomNavBar.itemViews.firstObject.selectionIndicatorColor,
UIColor.greenColor);
}
- (void)testSettingSelectionIndicatorSize {
// Given
CGSize testSize = CGSizeMake(20, 20);
UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:@"Title" image:nil tag:0];
// When
self.bottomNavBar.items = @[ item ];
self.bottomNavBar.selectionIndicatorSize = testSize;
// Then
XCTAssertTrue(
CGSizeEqualToSize(self.bottomNavBar.itemViews.firstObject.selectionIndicatorSize, testSize),
@"%@ not equal to %@",
NSStringFromCGSize(self.bottomNavBar.itemViews.firstObject.selectionIndicatorSize),
NSStringFromCGSize(testSize));
}
- (void)testSettingSelectionIndicatorSizeBeforeSettingItems {
// Given
CGSize testSize = CGSizeMake(20, 20);
UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:@"Title" image:nil tag:0];
// When
self.bottomNavBar.selectionIndicatorSize = testSize;
self.bottomNavBar.items = @[ item ];
// Then
XCTAssertTrue(
CGSizeEqualToSize(self.bottomNavBar.itemViews.firstObject.selectionIndicatorSize, testSize),
@"%@ not equal to %@",
NSStringFromCGSize(self.bottomNavBar.itemViews.firstObject.selectionIndicatorSize),
NSStringFromCGSize(testSize));
}
#pragma mark - UILargeContentViewerItem
#if MDC_AVAILABLE_SDK_IOS(13_0)
/** Tests the large content title when the title should not contain a badge. */
- (void)testLargeContentTitle {
if (@available(iOS 13.0, *)) {
// Given.
UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:@"Title" image:nil tag:0];
self.bottomNavBar.items = @[ item ];
// When.
NSString *largeContentTitle = self.bottomNavBar.itemViews.firstObject.largeContentTitle;
// Then.
XCTAssertEqualObjects(largeContentTitle, item.title);
}
}
/**
Tests the large content image is the @c image property when no @c largeContentImage is
specified.
*/
- (void)testLargeContentImageWithDefaultImage {
if (@available(iOS 13.0, *)) {
// Given.
UIImage *image = [[UIImage alloc] init];
UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:nil image:image tag:0];
item.largeContentSizeImage = image;
self.bottomNavBar.items = @[ item ];
// When.
UIImage *largeContentImage = self.bottomNavBar.itemViews.firstObject.largeContentImage;
// Then.
XCTAssertEqualObjects(largeContentImage, image);
}
}
/** Tests the large content item's image is the @c largeContentSizeImage . */
- (void)testLargeContentImageWithTabBarLargeContentImage {
if (@available(iOS 13.0, *)) {
// Given.
UIImage *image = [[UIImage alloc] init];
UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:nil image:nil tag:0];
item.largeContentSizeImage = image;
self.bottomNavBar.items = @[ item ];
// When.
UIImage *largeContentImage = self.bottomNavBar.itemViews.firstObject.largeContentImage;
// Then.
XCTAssertEqualObjects(largeContentImage, image);
}
}
/** Tests the large content image insets are set. */
- (void)testLargeContentImageInsets {
if (@available(iOS 13.0, *)) {
// Given.
UIEdgeInsets insets = UIEdgeInsetsMake(10, 10, 10, 10);
UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:nil image:nil tag:0];
item.largeContentSizeImageInsets = insets;
self.bottomNavBar.items = @[ item ];
// When.
UIEdgeInsets largeImageInsets = self.bottomNavBar.itemViews.firstObject.largeContentImageInsets;
// Then.
XCTAssertEqual(largeImageInsets.bottom, insets.bottom);
XCTAssertEqual(largeImageInsets.top, insets.top);
XCTAssertEqual(largeImageInsets.left, insets.left);
XCTAssertEqual(largeImageInsets.right, insets.right);
}
}
/**
Tests the large content item's image is updated when the tab bar's @c largeContentSizeImage is
updated.
*/
- (void)testLargeContentImageUpdatedWhenTabBarPropertyUpdates {
if (@available(iOS 13.0, *)) {
// Given.
UIImage *image = [[UIImage alloc] init];
UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:nil image:nil tag:0];
self.bottomNavBar.items = @[ item ];
// When.
item.largeContentSizeImage = image;
UIImage *largeContentImage = self.bottomNavBar.itemViews.firstObject.largeContentImage;
// Then.
XCTAssertEqualObjects(largeContentImage, image);
}
}
/**
Tests the large content inets are update when the tab bar's @c largeContentSizeImageInsets are
updated.
*/
- (void)testLargeContentInsetUpdatedWhenTabBarPropertyUpdates {
if (@available(iOS 13.0, *)) {
// Given.
UIEdgeInsets insets = UIEdgeInsetsMake(10, 10, 10, 10);
UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:nil image:nil tag:0];
self.bottomNavBar.items = @[ item ];
// When.
item.largeContentSizeImageInsets = insets;
UIEdgeInsets largeContentInsets =
self.bottomNavBar.itemViews.firstObject.largeContentImageInsets;
// Then.
XCTAssertEqual(largeContentInsets.left, insets.left);
XCTAssertEqual(largeContentInsets.bottom, insets.bottom);
XCTAssertEqual(largeContentInsets.right, insets.right);
XCTAssertEqual(largeContentInsets.top, insets.top);
}
}
/**
Tests the last shown large content item is still returned when the interaction touch point is
still within the bounds of the navigation bar.
*/
- (void)testLargeContentViewerInteractionWhenItemIsSelectedThenDeselectedButStillInNavBarBounds {
if (@available(iOS 13.0, *)) {
// Given.
NSString *title1 = @"Title1";
UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:title1 image:nil tag:0];
self.bottomNavBar.items = @[ item1 ];
self.bottomNavBar.frame = CGRectMake(0, 0, 350, 125);
[self.bottomNavBar layoutIfNeeded];
UILargeContentViewerInteraction *interaction = [[UILargeContentViewerInteraction alloc] init];
self.continueAfterFailure = NO;
// When/Then.
XCTAssertTrue([self.bottomNavBar respondsToSelector:@selector(largeContentViewerInteraction:
itemAtPoint:)]);
CGPoint itemViewOrigin = self.bottomNavBar.itemViews.firstObject.frame.origin;
id<UILargeContentViewerItem> largeContentItem =
[self.bottomNavBar largeContentViewerInteraction:interaction itemAtPoint:itemViewOrigin];
XCTAssertEqualObjects(largeContentItem.largeContentTitle, title1);
largeContentItem = [self.bottomNavBar largeContentViewerInteraction:interaction
itemAtPoint:CGPointZero];
XCTAssertEqualObjects(largeContentItem.largeContentTitle, title1);
}
}
/**
Tests the large content item is nil when the touch point is outside the navigation bar bounds.
*/
- (void)testLargeContentViewerInteractionWhenPointIsOutSideNavBarBounds {
if (@available(iOS 13.0, *)) {
// Given.
UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:@"Title1" image:nil tag:0];
self.bottomNavBar.items = @[ item ];
self.bottomNavBar.frame = CGRectMake(0, 0, 350, 125);
[self.bottomNavBar layoutIfNeeded];
UILargeContentViewerInteraction *interaction = [[UILargeContentViewerInteraction alloc] init];
self.continueAfterFailure = NO;
// When/Then.
XCTAssertTrue([self.bottomNavBar respondsToSelector:@selector(largeContentViewerInteraction:
itemAtPoint:)]);
CGPoint pointOutsideNavBar = CGPointMake(-1, -1);
id<UILargeContentViewerItem> largeContentItem =
[self.bottomNavBar largeContentViewerInteraction:interaction
itemAtPoint:pointOutsideNavBar];
XCTAssertNil(largeContentItem);
}
}
/** Tests the last shown large item is reset after an interaction ends. */
- (void)testLargeContentViewerInteractionResetsAfterEnding {
if (@available(iOS 13.0, *)) {
// Given.
UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:@"Title1" image:nil tag:0];
self.bottomNavBar.items = @[ item ];
self.bottomNavBar.frame = CGRectMake(0, 0, 350, 125);
[self.bottomNavBar layoutIfNeeded];
UILargeContentViewerInteraction *interaction = [[UILargeContentViewerInteraction alloc] init];
self.continueAfterFailure = NO;
// When/Then.
XCTAssertTrue([self.bottomNavBar respondsToSelector:@selector(largeContentViewerInteraction:
itemAtPoint:)]);
CGPoint pointInsideFirstItem = self.bottomNavBar.itemViews.firstObject.frame.origin;
id<UILargeContentViewerItem> endItem =
[self.bottomNavBar largeContentViewerInteraction:interaction
itemAtPoint:pointInsideFirstItem];
XCTAssertTrue([self.bottomNavBar
respondsToSelector:@selector(largeContentViewerInteraction:didEndOnItem:atPoint:)]);
[self.bottomNavBar largeContentViewerInteraction:interaction
didEndOnItem:endItem
atPoint:CGPointZero];
id<UILargeContentViewerItem> itemAfterReset =
[self.bottomNavBar largeContentViewerInteraction:interaction itemAtPoint:CGPointZero];
if (@available(iOS 13, *)) {
// TODO(b/184162265): Evaluate why this behavior is different on iOS 13+.
XCTAssertNotNil(itemAfterReset);
} else {
XCTAssertNil(itemAfterReset);
}
}
}
#endif // MDC_AVAILABLE_SDK_IOS(13_0)
/**
Tests the @c itemsHorizontalPadding property
*/
- (void)testItemsHorizontalPaddingProperty {
// Given
NSString *title = @"Title";
UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:title image:nil tag:0];
self.bottomNavBar.items = @[ item ];
self.bottomNavBar.frame = CGRectMake(0, 0, 350, 125);
[self.bottomNavBar sizeToFit];
// When
[self.bottomNavBar setNeedsLayout];
[self.bottomNavBar layoutIfNeeded];
MDCBottomNavigationItemView *itemView = [self.bottomNavBar.itemViews firstObject];
CGRect expectedItemViewFrameWithDefaultItemsHorizontalPadding =
CGRectInset(self.bottomNavBar.frame, 0, 0);
CGRect actualItemViewFrameWithDefaultItemsHorizontalPadding = itemView.frame;
self.bottomNavBar.itemsHorizontalPadding = 0;
[self.bottomNavBar setNeedsLayout];
[self.bottomNavBar layoutIfNeeded];
CGRect expectedItemViewFrameWithZeroItemsHorizontalPadding = self.bottomNavBar.frame;
CGRect actualItemViewFrameWithZeroItemsHorizontalPadding = itemView.frame;
// Then
XCTAssertTrue(CGRectEqualToRect(expectedItemViewFrameWithDefaultItemsHorizontalPadding,
actualItemViewFrameWithDefaultItemsHorizontalPadding));
XCTAssertTrue(CGRectEqualToRect(expectedItemViewFrameWithZeroItemsHorizontalPadding,
actualItemViewFrameWithZeroItemsHorizontalPadding));
}
#ifdef __IPHONE_13_4
/**
Verifies that MDCBottomNavigation generates a non-nil UIPointerStyle for its item views.
*/
- (void)testPointerStyleIsNonNilWithMDCBottomNavigationItemView {
if (@available(iOS 13.4, *)) {
// Given
// Pointer interactions require that the view being interacted with is in a UIWindow
[self createWindowWithView:self.bottomNavBar];
UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:kTestItemTitleText image:nil tag:0];
[self.bottomNavBar setItems:@[ item ]];
MDCBottomNavigationItemView *firstItemView = self.bottomNavBar.itemViews.firstObject;
UIPointerInteraction *interaction = firstItemView.interactions.firstObject;
XCTAssertEqual(interaction.delegate, self.bottomNavBar);
UIPointerRegion *region = [UIPointerRegion regionWithRect:firstItemView.frame identifier:nil];
UIPointerStyle *pointerStyle = [self.bottomNavBar pointerInteraction:interaction
styleForRegion:region];
// Then
XCTAssertNotNil(pointerStyle);
}
}
/**
Verifies that MDCBottomNavigation does not generate a UIPointerStyle for a non-item view.
*/
- (void)testPointerStyleIsNilWithNonMDCBottomNavigationItemView {
if (@available(iOS 13.4, *)) {
// Given
// Pointer interactions require that the view being interacted with is in a UIWindow
[self createWindowWithView:self.bottomNavBar];
UIView *nonBottomNavigationItemView = [[UIView alloc] init];
UIPointerInteraction *interaction =
[[UIPointerInteraction alloc] initWithDelegate:self.bottomNavBar];
[nonBottomNavigationItemView addInteraction:interaction];
UIPointerRegion *region = [UIPointerRegion regionWithRect:nonBottomNavigationItemView.frame
identifier:nil];
UIPointerStyle *pointerStyle = [self.bottomNavBar pointerInteraction:interaction
styleForRegion:region];
// Then
XCTAssertNil(pointerStyle);
}
}
#endif
#pragma mark - Helpers
- (void)createWindowWithView:(UIView *)view {
UIWindow *window = [[UIWindow alloc] init];
[window addSubview:view];
}
@end
NS_ASSUME_NONNULL_END