chromium/ios/chrome/browser/bubble/ui_bundled/bubble_view_unittest.mm

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/chrome/browser/bubble/ui_bundled/bubble_view.h"

#import "base/apple/foundation_util.h"
#import "base/ios/ios_util.h"
#import "ios/chrome/browser/bubble/ui_bundled/bubble_constants.h"
#import "ios/chrome/browser/bubble/ui_bundled/bubble_unittest_util.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"

@interface BubbleViewDelegateTest : NSObject <BubbleViewDelegate>

- (instancetype)init;

@property(nonatomic) int tapCounter;

@end

@implementation BubbleViewDelegateTest

- (instancetype)init {
  self = [super init];
  if (self) {
    _tapCounter = 0;
  }
  return self;
}

- (void)didTapCloseButton {
  _tapCounter += 1;
}

- (void)didTapSnoozeButton {
  _tapCounter += 1;
}

@end

// Fixture to test BubbleView.
class BubbleViewTest : public PlatformTest {
 public:
  BubbleViewTest()
      : max_size_(CGSizeMake(500.0f, 500.0f)),
        arrow_direction_(BubbleArrowDirectionUp),
        alignment_(BubbleAlignmentCenter),
        alignment_offset_(35.0f),
        short_text_(@"I"),
        long_text_(@"Lorem ipsum dolor sit amet, consectetur adipiscing elit."),
        text_alignment_(NSTextAlignmentNatural) {}

 protected:
  // The maximum size of the bubble.
  const CGSize max_size_;
  // The direction that the bubble's arrow points.
  const BubbleArrowDirection arrow_direction_;
  // The alignment of the bubble's arrow relative to the rest of the bubble.
  const BubbleAlignment alignment_;
  // Distance between the arrow's centerX and the (leading or trailing) edge of
  // the bubble, depending on the BubbleAlignment. If BubbleAlignment is center,
  // then `alignmentOffset` is ignored.
  const CGFloat alignment_offset_;
  // Text that is shorter than the minimum line width.
  NSString* short_text_;
  // Text that is longer than the maximum line width. It should wrap onto
  // multiple lines.
  NSString* long_text_;
  // Text alignment for bubble's title, text and snooze button.
  NSTextAlignment text_alignment_;
};

// Test `sizeThatFits` given short text.
TEST_F(BubbleViewTest, BubbleSizeShortText) {
  BubbleView* bubble = [[BubbleView alloc] initWithText:short_text_
                                         arrowDirection:arrow_direction_
                                              alignment:alignment_];
  CGSize bubble_size = [bubble sizeThatFits:max_size_];
  // Since the label is shorter than the minimum line width, expect the bubble
  // to be the minimum width and accommodate one line of text.
  EXPECT_NEAR(68.0f, bubble_size.width, 1.0f);
  EXPECT_NEAR(67.0f, bubble_size.height, 1.0f);
}

// Test that the accessibility label matches the display text.
TEST_F(BubbleViewTest, Accessibility) {
  BubbleView* bubble = [[BubbleView alloc] initWithText:long_text_
                                         arrowDirection:arrow_direction_
                                              alignment:alignment_];
  UIView* superview = [[UIView alloc] initWithFrame:CGRectZero];
  // Add the bubble view to the view hierarchy.
  [superview addSubview:bubble];
  EXPECT_NSEQ(long_text_, bubble.accessibilityValue);
}

// Tests that the close button is not showed when the option is set to hidden.
TEST_F(BubbleViewTest, CloseButtonIsNotPresent) {
  BubbleView* bubble = [[BubbleView alloc] initWithText:long_text_
                                         arrowDirection:arrow_direction_
                                              alignment:alignment_
                                       showsCloseButton:NO
                                                  title:nil
                                                  image:nil
                                      showsSnoozeButton:NO
                                          textAlignment:text_alignment_
                                               delegate:nil];
  UIView* superview = [[UIView alloc] initWithFrame:CGRectZero];
  [superview addSubview:bubble];
  UIButton* close_button = GetCloseButtonFromBubbleView(bubble);
  ASSERT_FALSE(close_button);
}

// Tests the close button action and its presence.
TEST_F(BubbleViewTest, CloseButtonActionAndPresent) {
  BubbleViewDelegateTest* delegate = [[BubbleViewDelegateTest alloc] init];
  BubbleView* bubble = [[BubbleView alloc] initWithText:long_text_
                                         arrowDirection:arrow_direction_
                                              alignment:alignment_
                                       showsCloseButton:YES
                                                  title:nil
                                                  image:nil
                                      showsSnoozeButton:NO
                                          textAlignment:text_alignment_
                                               delegate:delegate];
  UIView* superview = [[UIView alloc] initWithFrame:CGRectZero];
  [superview addSubview:bubble];
  UIButton* close_button = GetCloseButtonFromBubbleView(bubble);
  ASSERT_TRUE(close_button);
  // Tests close button action.
  [close_button sendActionsForControlEvents:UIControlEventTouchUpInside];
  EXPECT_EQ(delegate.tapCounter, 1);
}

// Tests that the title is not showed when the option is set to hidden.
TEST_F(BubbleViewTest, TitleIsNotPresent) {
  BubbleView* bubble = [[BubbleView alloc] initWithText:long_text_
                                         arrowDirection:arrow_direction_
                                              alignment:alignment_];
  UIView* superview = [[UIView alloc] initWithFrame:CGRectZero];
  [superview addSubview:bubble];
  UILabel* title_label = GetTitleLabelFromBubbleView(bubble);
  ASSERT_FALSE(title_label);
}

// Tests that the title is present and correct.
TEST_F(BubbleViewTest, TitleIsPresentAndCorrect) {
  BubbleView* bubble = [[BubbleView alloc] initWithText:long_text_
                                         arrowDirection:arrow_direction_
                                              alignment:alignment_
                                       showsCloseButton:NO
                                                  title:short_text_
                                                  image:nil
                                      showsSnoozeButton:NO
                                          textAlignment:text_alignment_
                                               delegate:nil];
  UIView* superview = [[UIView alloc] initWithFrame:CGRectZero];
  [superview addSubview:bubble];
  UILabel* title_label = GetTitleLabelFromBubbleView(bubble);
  ASSERT_TRUE(title_label);
  ASSERT_EQ(title_label.text, short_text_);
}

// Tests that the title is aligned correctly.
TEST_F(BubbleViewTest, TitleIsAligned) {
  BubbleView* bubble = [[BubbleView alloc] initWithText:long_text_
                                         arrowDirection:arrow_direction_
                                              alignment:alignment_
                                       showsCloseButton:NO
                                                  title:short_text_
                                                  image:nil
                                      showsSnoozeButton:NO
                                          textAlignment:NSTextAlignmentNatural
                                               delegate:nil];
  UIView* superview = [[UIView alloc] initWithFrame:CGRectZero];
  [superview addSubview:bubble];
  UILabel* title_label = GetTitleLabelFromBubbleView(bubble);
  ASSERT_TRUE(title_label);
  ASSERT_EQ(title_label.textAlignment, NSTextAlignmentNatural);
}

// Tests that the image is not showed when the image is empty.
TEST_F(BubbleViewTest, ImageIsNotPresent) {
  BubbleView* bubble = [[BubbleView alloc] initWithText:long_text_
                                         arrowDirection:arrow_direction_
                                              alignment:alignment_];
  UIView* superview = [[UIView alloc] initWithFrame:CGRectZero];
  [superview addSubview:bubble];
  UIImageView* image_view = GetImageViewFromBubbleView(bubble);
  ASSERT_FALSE(image_view);
}

// Tests that the image is present and correct.
TEST_F(BubbleViewTest, ImageIsPresentAndCorrect) {
  UIImage* testImage = [[UIImage alloc] init];
  BubbleView* bubble = [[BubbleView alloc] initWithText:long_text_
                                         arrowDirection:arrow_direction_
                                              alignment:alignment_
                                       showsCloseButton:NO
                                                  title:nil
                                                  image:testImage
                                      showsSnoozeButton:NO
                                          textAlignment:text_alignment_
                                               delegate:nil];
  UIView* superview = [[UIView alloc] initWithFrame:CGRectZero];
  [superview addSubview:bubble];
  UIImageView* image_view = GetImageViewFromBubbleView(bubble);
  ASSERT_TRUE(image_view);
  EXPECT_EQ(image_view.image, testImage);
}

// Tests that the snooze button is not showed when the option is set to hidden.
TEST_F(BubbleViewTest, SnoozeButtonIsNotPresent) {
  BubbleView* bubble = [[BubbleView alloc] initWithText:long_text_
                                         arrowDirection:arrow_direction_
                                              alignment:alignment_
                                       showsCloseButton:NO
                                                  title:nil
                                                  image:nil
                                      showsSnoozeButton:NO
                                          textAlignment:text_alignment_
                                               delegate:nil];
  UIView* superview = [[UIView alloc] initWithFrame:CGRectZero];
  [superview addSubview:bubble];
  UIButton* snooze_button = GetSnoozeButtonFromBubbleView(bubble);
  ASSERT_FALSE(snooze_button);
}

// Tests the snooze button action and its presence.
TEST_F(BubbleViewTest, SnoozeButtonActionAndPresent) {
  BubbleViewDelegateTest* delegate = [[BubbleViewDelegateTest alloc] init];
  BubbleView* bubble = [[BubbleView alloc] initWithText:long_text_
                                         arrowDirection:arrow_direction_
                                              alignment:alignment_
                                       showsCloseButton:NO
                                                  title:nil
                                                  image:nil
                                      showsSnoozeButton:YES
                                          textAlignment:text_alignment_
                                               delegate:delegate];
  UIView* superview = [[UIView alloc] initWithFrame:CGRectZero];
  [superview addSubview:bubble];
  UIButton* snooze_button = GetSnoozeButtonFromBubbleView(bubble);
  ASSERT_TRUE(snooze_button);
  // Tests snooze button action.
  [snooze_button sendActionsForControlEvents:UIControlEventTouchUpInside];
  EXPECT_EQ(delegate.tapCounter, 1);
}

// Tests the arrow view is aligned properly with BubbleAlignmentTopOrLeading.
TEST_F(BubbleViewTest, ArrowViewLeadingAligned) {
  BubbleView* bubble =
      [[BubbleView alloc] initWithText:long_text_
                        arrowDirection:arrow_direction_
                             alignment:BubbleAlignmentTopOrLeading];
  bubble.alignmentOffset = alignment_offset_;
  UIView* superview = [[UIView alloc] initWithFrame:CGRectZero];
  [superview addSubview:bubble];
  [bubble layoutIfNeeded];
  UIView* arrow_view = GetArrowViewFromBubbleView(bubble);
  ASSERT_TRUE(arrow_view);
  // The center of the arrow must be at a distance of `alignment_offset_` to the
  // leading edge of the bubble.
  EXPECT_EQ(CGRectGetMidX(arrow_view.frame), alignment_offset_);
}

// Tests the arrow view is aligned properly with BubbleAlignmentCenter.
TEST_F(BubbleViewTest, ArrowViewCenterAligned) {
  BubbleView* bubble = [[BubbleView alloc] initWithText:long_text_
                                         arrowDirection:arrow_direction_
                                              alignment:BubbleAlignmentCenter];
  bubble.alignmentOffset = alignment_offset_;
  UIView* superview = [[UIView alloc] initWithFrame:CGRectZero];
  [superview addSubview:bubble];
  [bubble layoutIfNeeded];
  UIView* arrow_view = GetArrowViewFromBubbleView(bubble);
  ASSERT_TRUE(arrow_view);
  // `alignmentOffset` should be ignored with BubbleAlignmentCenter.
  EXPECT_EQ(CGRectGetMidX(arrow_view.frame), CGRectGetMidX(bubble.frame));
}

// Tests the arrow view is aligned properly with
// BubbleAlignmentBottomOrTrailing.
TEST_F(BubbleViewTest, ArrowViewTrailingAligned) {
  BubbleView* bubble =
      [[BubbleView alloc] initWithText:long_text_
                        arrowDirection:arrow_direction_
                             alignment:BubbleAlignmentBottomOrTrailing];
  bubble.alignmentOffset = alignment_offset_;
  UIView* superview = [[UIView alloc] initWithFrame:CGRectZero];
  [superview addSubview:bubble];
  [bubble layoutIfNeeded];
  UIView* arrow_view = GetArrowViewFromBubbleView(bubble);
  ASSERT_TRUE(arrow_view);
  // The center of the arrow must be at a distance of `alignment_offset_` to the
  // trailing edge of the bubble.
  EXPECT_EQ(CGRectGetMidX(arrow_view.frame),
            bubble.frame.size.width - alignment_offset_);
}