mirror of
https://github.com/material-components/material-components-ios.git
synced 2026-02-20 08:27:32 +08:00
449 lines
18 KiB
Objective-C
449 lines
18 KiB
Objective-C
// Copyright 2019-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 <CoreGraphics/CoreGraphics.h>
|
|
#import <UIKit/UIKit.h>
|
|
|
|
#import "MDCAvailability.h"
|
|
#import "MDCButton.h"
|
|
#import "MDCAlertController.h"
|
|
#import "UIFont+MaterialScalable.h"
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wprivate-header"
|
|
#import "MDCDialogShadowedView.h"
|
|
#import "UIColor+MaterialDynamic.h"
|
|
#import "MDCSnapshotTestCase.h"
|
|
#import "UIView+MDCSnapshot.h"
|
|
#pragma clang diagnostic pop
|
|
|
|
#import "MDCAlertController+ButtonForAction.h"
|
|
#import "MDCFontScaler.h"
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
static NSDictionary<UIContentSizeCategory, NSNumber *> *CustomScalingCurve(void) {
|
|
static NSDictionary<UIContentSizeCategory, NSNumber *> *scalingCurve;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
scalingCurve = @{
|
|
UIContentSizeCategoryExtraSmall : @99,
|
|
UIContentSizeCategorySmall : @98,
|
|
UIContentSizeCategoryMedium : @97,
|
|
UIContentSizeCategoryLarge : @96,
|
|
UIContentSizeCategoryExtraLarge : @95,
|
|
UIContentSizeCategoryExtraExtraLarge : @94,
|
|
UIContentSizeCategoryExtraExtraExtraLarge : @93,
|
|
UIContentSizeCategoryAccessibilityMedium : @92,
|
|
UIContentSizeCategoryAccessibilityLarge : @91,
|
|
UIContentSizeCategoryAccessibilityExtraLarge : @90,
|
|
UIContentSizeCategoryAccessibilityExtraExtraLarge : @89,
|
|
UIContentSizeCategoryAccessibilityExtraExtraExtraLarge : @88
|
|
};
|
|
});
|
|
return scalingCurve;
|
|
}
|
|
|
|
/** A test fake window that allows overriding its @c traitCollection. */
|
|
@interface MDCAlertControllerCustomTraitCollectionTestsWindowFake : UIWindow
|
|
|
|
/** Set to override the value of @c traitCollection. */
|
|
@property(nonatomic, strong) UITraitCollection *traitCollectionOverride;
|
|
|
|
@end
|
|
|
|
@implementation MDCAlertControllerCustomTraitCollectionTestsWindowFake
|
|
|
|
- (UITraitCollection *)traitCollection {
|
|
return self.traitCollectionOverride ?: [super traitCollection];
|
|
}
|
|
|
|
@end
|
|
|
|
/**
|
|
A @c MDCAlertController test fake to override the @c traitCollection to test for dynamic type.
|
|
*/
|
|
@interface AlertControllerCustomTraitCollectionSnapshotTestFake : MDCAlertController
|
|
@property(nonatomic, strong) UITraitCollection *traitCollectionOverride;
|
|
@end
|
|
|
|
@implementation AlertControllerCustomTraitCollectionSnapshotTestFake
|
|
|
|
- (UITraitCollection *)traitCollection {
|
|
return self.traitCollectionOverride ?: [super traitCollection];
|
|
}
|
|
|
|
@end
|
|
|
|
/** An @c MDCDialogShadowedView test fake to override the @c traitCollection to test. */
|
|
@interface ShadowViewCustomTraitCollectionSnapshotTestFake : MDCDialogShadowedView
|
|
@property(nonatomic, strong) UITraitCollection *traitCollectionOverride;
|
|
@end
|
|
|
|
@implementation ShadowViewCustomTraitCollectionSnapshotTestFake
|
|
|
|
- (UITraitCollection *)traitCollection {
|
|
return self.traitCollectionOverride ?: [super traitCollection];
|
|
}
|
|
|
|
@end
|
|
|
|
@interface MDCAlertControllerCustomTraitCollectionTests : MDCSnapshotTestCase
|
|
@property(nonatomic, strong, nullable)
|
|
AlertControllerCustomTraitCollectionSnapshotTestFake *alertController;
|
|
@end
|
|
|
|
@implementation MDCAlertControllerCustomTraitCollectionTests
|
|
|
|
- (void)setUp {
|
|
[super setUp];
|
|
|
|
// Uncomment below to recreate all the goldens (or add the following line to the specific
|
|
// test you wish to recreate the golden for).
|
|
// self.recordMode = YES;
|
|
|
|
self.alertController = [[AlertControllerCustomTraitCollectionSnapshotTestFake alloc] init];
|
|
self.alertController.title = @"Material";
|
|
self.alertController.message =
|
|
@"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt "
|
|
@"ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation "
|
|
@"ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in "
|
|
@"reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur "
|
|
@"sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id "
|
|
@"est laborum.";
|
|
MDCAlertAction *fakeAction = [MDCAlertAction actionWithTitle:@"Foo"
|
|
handler:^(MDCAlertAction *action){
|
|
}];
|
|
[self.alertController addAction:fakeAction];
|
|
MDCFontScaler *titleFontScaler = [MDCFontScaler scalerForMaterialTextStyle:MDCTextStyleSubtitle1];
|
|
UIFont *titleFont = [UIFont fontWithName:@"Zapfino" size:14];
|
|
titleFont = [titleFontScaler scaledFontWithFont:titleFont];
|
|
titleFont = [titleFont mdc_scaledFontAtDefaultSize];
|
|
self.alertController.titleFont = titleFont;
|
|
MDCFontScaler *messageFontScaler = [MDCFontScaler scalerForMaterialTextStyle:MDCTextStyleBody2];
|
|
UIFont *messageFont = [UIFont fontWithName:@"Zapfino" size:14];
|
|
messageFont = [messageFontScaler scaledFontWithFont:messageFont];
|
|
messageFont = [messageFont mdc_scaledFontAtDefaultSize];
|
|
self.alertController.messageFont = messageFont;
|
|
MDCFontScaler *buttonFontScaler = [MDCFontScaler scalerForMaterialTextStyle:MDCTextStyleButton];
|
|
UIFont *buttonFont = [UIFont fontWithName:@"Zapfino" size:14];
|
|
buttonFont = [buttonFontScaler scaledFontWithFont:buttonFont];
|
|
buttonFont = [buttonFont mdc_scaledFontAtDefaultSize];
|
|
for (MDCAlertAction *action in self.alertController.actions) {
|
|
[[self.alertController buttonForAction:action] setTitleFont:buttonFont
|
|
forState:UIControlStateNormal];
|
|
}
|
|
self.alertController.view.bounds = CGRectMake(0, 0, 300, 300);
|
|
}
|
|
|
|
- (void)tearDown {
|
|
self.alertController = nil;
|
|
|
|
[super tearDown];
|
|
}
|
|
|
|
- (void)generateSnapshotAndVerifyForView:(UIView *)view {
|
|
[view layoutIfNeeded];
|
|
UIView *snapshotView = [view mdc_addToBackgroundView];
|
|
[self snapshotVerifyView:snapshotView];
|
|
}
|
|
|
|
/** Used to set the @c UIContentSizeCategory on an @c MDCAlertController. */
|
|
- (void)setAlertControllerContentSizeCategory:(UIContentSizeCategory)sizeCategory {
|
|
UITraitCollection *traitCollection = [[UITraitCollection alloc] init];
|
|
traitCollection =
|
|
[UITraitCollection traitCollectionWithPreferredContentSizeCategory:sizeCategory];
|
|
|
|
self.alertController.traitCollectionOverride = traitCollection;
|
|
}
|
|
|
|
#pragma mark - Dynamic Type
|
|
|
|
/**
|
|
Tests the original MDCTypography behavior for Dynamic Type.
|
|
|
|
@note The output depends on the host simulator and is equal to
|
|
@c testSystemFontScaledWhenScaledFontUnavailableForContentSizeAXXXL as a result.
|
|
*/
|
|
- (void)testSystemFontScaledWhenScaledFontUnavailableForContentSizeExtraSmall {
|
|
// Given
|
|
|
|
UIFont *originalFont = [UIFont fontWithName:@"Zapfino" size:20];
|
|
UIFontMetrics *fontMetrics = [UIFontMetrics metricsForTextStyle:UIFontTextStyleHeadline];
|
|
self.alertController.messageFont = [fontMetrics scaledFontForFont:originalFont];
|
|
self.alertController.titleFont = [fontMetrics scaledFontForFont:originalFont];
|
|
for (MDCAlertAction *action in self.alertController.actions) {
|
|
[[self.alertController buttonForAction:action] setTitleFont:originalFont
|
|
forState:UIControlStateNormal];
|
|
}
|
|
[self setAlertControllerContentSizeCategory:UIContentSizeCategoryExtraSmall];
|
|
|
|
// When
|
|
[self.alertController loadViewIfNeeded];
|
|
self.alertController.adjustsFontForContentSizeCategory = YES;
|
|
|
|
// Then
|
|
[self generateSnapshotAndVerifyForView:self.alertController.view];
|
|
}
|
|
|
|
/**
|
|
Tests the original MDCTypography behavior for Dynamic Type.
|
|
|
|
@note The output depends on the host simulator and is equal to
|
|
@c testSystemFontScaledWhenScaledFontUnavailableForContentSizeExtraSmall as a result.
|
|
*/
|
|
- (void)testSystemFontScaledWhenScaledFontUnavailableForContentSizeAXXXL {
|
|
// Given
|
|
|
|
// Although the font is initialized with point size 1, the MDCTypography behavior will select
|
|
// a fixed point size for the font at the current UIContentSizeCategory (of the host app),
|
|
// which is not 1.
|
|
UIFont *originalFont = [UIFont fontWithName:@"Zapfino" size:20];
|
|
UIFontMetrics *fontMetrics = [UIFontMetrics metricsForTextStyle:UIFontTextStyleHeadline];
|
|
self.alertController.messageFont = [fontMetrics scaledFontForFont:originalFont];
|
|
self.alertController.titleFont = [fontMetrics scaledFontForFont:originalFont];
|
|
for (MDCAlertAction *action in self.alertController.actions) {
|
|
[[self.alertController buttonForAction:action] setTitleFont:originalFont
|
|
forState:UIControlStateNormal];
|
|
}
|
|
[self
|
|
setAlertControllerContentSizeCategory:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge];
|
|
|
|
// When
|
|
[self.alertController loadViewIfNeeded];
|
|
self.alertController.adjustsFontForContentSizeCategory = YES;
|
|
|
|
// Then
|
|
[self generateSnapshotAndVerifyForView:self.alertController.view];
|
|
}
|
|
|
|
/** Tests behavior when a font generated from a FontScaler is provided. */
|
|
- (void)testFontScalerFontScaledForContentSizeExtraSmall {
|
|
// Given
|
|
UIFont *originalFont = [UIFont fontWithName:@"Zapfino" size:20];
|
|
UIFontMetrics *fontMetrics = [UIFontMetrics metricsForTextStyle:UIFontTextStyleHeadline];
|
|
self.alertController.messageFont = [fontMetrics scaledFontForFont:originalFont];
|
|
self.alertController.titleFont = [fontMetrics scaledFontForFont:originalFont];
|
|
for (MDCAlertAction *action in self.alertController.actions) {
|
|
[[self.alertController buttonForAction:action] setTitleFont:originalFont
|
|
forState:UIControlStateNormal];
|
|
}
|
|
[self setAlertControllerContentSizeCategory:UIContentSizeCategoryExtraSmall];
|
|
|
|
// When
|
|
[self.alertController loadViewIfNeeded];
|
|
self.alertController.adjustsFontForContentSizeCategory = YES;
|
|
|
|
// Then
|
|
[self generateSnapshotAndVerifyForView:self.alertController.view];
|
|
}
|
|
|
|
/** Tests behavior when a font generated from a FontScaler is provided. */
|
|
- (void)testFontScalerFontScaledForContentSizeAXXXL {
|
|
// Given
|
|
UIFont *originalFont = [UIFont fontWithName:@"Zapfino" size:1];
|
|
// Simulates a font scaler by providing scaling curve dictionary.
|
|
originalFont.mdc_scalingCurve = CustomScalingCurve();
|
|
self.alertController.messageFont = originalFont;
|
|
self.alertController.titleFont = originalFont;
|
|
for (MDCAlertAction *action in self.alertController.actions) {
|
|
[[self.alertController buttonForAction:action] setTitleFont:originalFont
|
|
forState:UIControlStateNormal];
|
|
}
|
|
[self
|
|
setAlertControllerContentSizeCategory:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge];
|
|
|
|
// When
|
|
[self.alertController loadViewIfNeeded];
|
|
self.alertController.adjustsFontForContentSizeCategory = YES;
|
|
|
|
// Then
|
|
[self generateSnapshotAndVerifyForView:self.alertController.view];
|
|
}
|
|
|
|
/**
|
|
Test that @c adjustsFontForContentSizeCategory will scale an appropriate font to a larger
|
|
size when the preferred content size category increases.
|
|
*/
|
|
- (void)testAdjustsFontForContentSizeUpscalesUIFontMetricsFontsForSizeCategoryAXXXL {
|
|
// Given
|
|
UIFontMetrics *bodyMetrics = [UIFontMetrics metricsForTextStyle:UIFontTextStyleBody];
|
|
UITraitCollection *extraSmallTraits = [UITraitCollection
|
|
traitCollectionWithPreferredContentSizeCategory:UIContentSizeCategoryExtraSmall];
|
|
|
|
UIFont *titleFont = [UIFont fontWithName:@"Zapfino" size:20];
|
|
XCTAssertNotNil(titleFont);
|
|
titleFont = [bodyMetrics scaledFontForFont:titleFont
|
|
compatibleWithTraitCollection:extraSmallTraits];
|
|
self.alertController.titleFont = titleFont;
|
|
|
|
UIFont *messageFont = [UIFont fontWithName:@"Zapfino" size:15];
|
|
messageFont = [bodyMetrics scaledFontForFont:messageFont
|
|
compatibleWithTraitCollection:extraSmallTraits];
|
|
self.alertController.messageFont = messageFont;
|
|
|
|
UIFont *buttonFont = [UIFont fontWithName:@"Zapfino" size:20];
|
|
buttonFont = [bodyMetrics scaledFontForFont:buttonFont
|
|
compatibleWithTraitCollection:extraSmallTraits];
|
|
for (MDCAlertAction *action in self.alertController.actions) {
|
|
MDCButton *button = [self.alertController buttonForAction:action];
|
|
button.titleLabel.font = buttonFont;
|
|
}
|
|
|
|
self.alertController.adjustsFontForContentSizeCategory = YES;
|
|
[self.alertController loadViewIfNeeded];
|
|
|
|
// The initial size is calculated without constraints, so start the view bounds there.
|
|
CGSize alertSize = self.alertController.preferredContentSize;
|
|
self.alertController.view.bounds = CGRectMake(0, 0, alertSize.width, alertSize.height);
|
|
|
|
// Create a window so the Alert's view can inherit the trait environment.
|
|
MDCAlertControllerCustomTraitCollectionTestsWindowFake *window =
|
|
[[MDCAlertControllerCustomTraitCollectionTestsWindowFake alloc] init];
|
|
[window makeKeyWindow];
|
|
window.hidden = NO;
|
|
[window addSubview:self.alertController.view];
|
|
|
|
// When
|
|
window.traitCollectionOverride =
|
|
[UITraitCollection traitCollectionWithPreferredContentSizeCategory:
|
|
UIContentSizeCategoryAccessibilityExtraExtraExtraLarge];
|
|
[window traitCollectionDidChange:nil];
|
|
// Recalculates the preferredContentSize of the AlertController.
|
|
[self.alertController.view layoutIfNeeded];
|
|
alertSize = self.alertController.preferredContentSize;
|
|
window.bounds = CGRectMake(0, 0, alertSize.width, alertSize.height);
|
|
self.alertController.view.frame = window.bounds;
|
|
|
|
// Then
|
|
// Can't add a UIWindow to a UIView, so just screenshot the window directly.
|
|
[window layoutIfNeeded];
|
|
[self snapshotVerifyView:window];
|
|
}
|
|
|
|
/**
|
|
Test that @c adjustsFontForContentSizeCategory will scale an appropriate font to a
|
|
smaller size when the preferred content size category decreases.
|
|
*/
|
|
- (void)testAdjustsFontForContentSizeDownscalesUIFontMetricsFontsForSizeCategoryXS {
|
|
// Given
|
|
UIFontMetrics *bodyMetrics = [UIFontMetrics metricsForTextStyle:UIFontTextStyleBody];
|
|
UITraitCollection *aXXXLTraits =
|
|
[UITraitCollection traitCollectionWithPreferredContentSizeCategory:
|
|
UIContentSizeCategoryAccessibilityExtraExtraExtraLarge];
|
|
|
|
UIFont *titleFont = [UIFont fontWithName:@"Zapfino" size:20];
|
|
XCTAssertNotNil(titleFont);
|
|
titleFont = [bodyMetrics scaledFontForFont:titleFont compatibleWithTraitCollection:aXXXLTraits];
|
|
self.alertController.titleFont = titleFont;
|
|
|
|
UIFont *messageFont = [UIFont fontWithName:@"Zapfino" size:15];
|
|
messageFont = [bodyMetrics scaledFontForFont:messageFont
|
|
compatibleWithTraitCollection:aXXXLTraits];
|
|
self.alertController.messageFont = messageFont;
|
|
|
|
UIFont *buttonFont = [UIFont fontWithName:@"Zapfino" size:20];
|
|
buttonFont = [bodyMetrics scaledFontForFont:buttonFont compatibleWithTraitCollection:aXXXLTraits];
|
|
for (MDCAlertAction *action in self.alertController.actions) {
|
|
MDCButton *button = [self.alertController buttonForAction:action];
|
|
button.titleLabel.font = buttonFont;
|
|
}
|
|
|
|
self.alertController.adjustsFontForContentSizeCategory = YES;
|
|
[self.alertController loadViewIfNeeded];
|
|
|
|
// The initial size is calculated without constraints, so start the view bounds there.
|
|
CGSize alertSize = self.alertController.preferredContentSize;
|
|
self.alertController.view.bounds = CGRectMake(0, 0, alertSize.width, alertSize.height);
|
|
|
|
// Create a window so the Alert's view can inherit the trait environment.
|
|
MDCAlertControllerCustomTraitCollectionTestsWindowFake *window =
|
|
[[MDCAlertControllerCustomTraitCollectionTestsWindowFake alloc] init];
|
|
[window makeKeyWindow];
|
|
window.hidden = NO;
|
|
[window addSubview:self.alertController.view];
|
|
|
|
// When
|
|
window.traitCollectionOverride = [UITraitCollection
|
|
traitCollectionWithPreferredContentSizeCategory:UIContentSizeCategoryExtraSmall];
|
|
[window traitCollectionDidChange:nil];
|
|
// Recalculates the preferredContentSize of the AlertController.
|
|
[self.alertController.view layoutIfNeeded];
|
|
alertSize = self.alertController.preferredContentSize;
|
|
window.bounds = CGRectMake(0, 0, alertSize.width, alertSize.height);
|
|
self.alertController.view.frame = window.bounds;
|
|
|
|
// Then
|
|
// Can't add a UIWindow to a UIView, so just screenshot the window directly.
|
|
[window layoutIfNeeded];
|
|
[self snapshotVerifyView:window];
|
|
}
|
|
|
|
#pragma mark - Dynamic Color
|
|
|
|
- (void)testDynamicColorSupport {
|
|
#if MDC_AVAILABLE_SDK_IOS(13_0)
|
|
if (@available(iOS 13.0, *)) {
|
|
// Given
|
|
UIColor *titleColor = [UIColor colorWithUserInterfaceStyleDarkColor:UIColor.greenColor
|
|
defaultColor:UIColor.blackColor];
|
|
UIColor *messageColor = [UIColor colorWithUserInterfaceStyleDarkColor:UIColor.purpleColor
|
|
defaultColor:UIColor.blackColor];
|
|
UIColor *backgroundColor = [UIColor colorWithUserInterfaceStyleDarkColor:UIColor.blueColor
|
|
defaultColor:UIColor.blackColor];
|
|
self.alertController.titleColor = titleColor;
|
|
self.alertController.messageColor = messageColor;
|
|
self.alertController.backgroundColor = backgroundColor;
|
|
|
|
// When
|
|
self.alertController.traitCollectionOverride =
|
|
[UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark];
|
|
|
|
// Then
|
|
UIView *snapshotView = [self.alertController.view
|
|
mdc_addToBackgroundViewWithInsets:UIEdgeInsetsMake(50, 50, 50, 50)];
|
|
[self snapshotVerifyViewForIOS13:snapshotView];
|
|
}
|
|
#endif // MDC_AVAILABLE_SDK_IOS(13_0)
|
|
}
|
|
|
|
- (void)testDynamicColorSupportForTrackingView {
|
|
#if MDC_AVAILABLE_SDK_IOS(13_0)
|
|
if (@available(iOS 13.0, *)) {
|
|
// Given
|
|
UIColor *shadowColor = [UIColor colorWithUserInterfaceStyleDarkColor:UIColor.greenColor
|
|
defaultColor:UIColor.blackColor];
|
|
ShadowViewCustomTraitCollectionSnapshotTestFake *trackingView =
|
|
[[ShadowViewCustomTraitCollectionSnapshotTestFake alloc] init];
|
|
trackingView.frame = CGRectMake(0, 0, 100, 200);
|
|
trackingView.shadowColor = shadowColor;
|
|
trackingView.backgroundColor = UIColor.whiteColor;
|
|
|
|
// When
|
|
trackingView.traitCollectionOverride =
|
|
[UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark];
|
|
[trackingView layoutIfNeeded];
|
|
|
|
// Then
|
|
UIView *snapshotView =
|
|
[trackingView mdc_addToBackgroundViewWithInsets:UIEdgeInsetsMake(50, 50, 50, 50)];
|
|
[self snapshotVerifyViewForIOS13:snapshotView];
|
|
}
|
|
#endif // MDC_AVAILABLE_SDK_IOS(13_0)
|
|
}
|
|
|
|
@end
|
|
|
|
NS_ASSUME_NONNULL_END
|