chromium/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_cell_utils_unittest.mm

// Copyright 2024 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/autofill/ui_bundled/manual_fill/manual_fill_cell_utils.h"

#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/autofill/ui_bundled/manual_fill/chip_button.h"
#import "ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_labeled_chip.h"
#import "ios/chrome/common/ui/util/constraints_ui_util.h"
#import "testing/gmock/include/gmock/gmock.h"
#import "testing/platform_test.h"

// Button whose intrinsic content size mathes that of its frame. Used for
// testing purposes only.
@interface FixedSizeButton : UIButton
@end

@implementation FixedSizeButton

- (CGSize)intrinsicContentSize {
  return self.frame.size;
}

@end

namespace {

using ManualFillTestUtilsTest = PlatformTest;

// Creates a ManualFillCellView with the given `view` and `type`.
ManualFillCellView CreateManualFillCellView(
    UIView* view,
    ManualFillCellView::ElementType type) {
  return (struct ManualFillCellView){view, type};
}

// Creates a button and sizes it with the given `width`.
UIButton* CreateButtonOfWidth(CGFloat width) {
  return [[FixedSizeButton alloc] initWithFrame:CGRectMake(0, 0, width, 0)];
}

// Creates a layout guide and sizes it with the given `width`.
UILayoutGuide* CreateLayoutGuideOfWidth(CGFloat width) {
  UIView* view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, 0)];
  UILayoutGuide* layout_guide = [[UILayoutGuide alloc] init];
  [view addLayoutGuide:layout_guide];
  AddSameConstraintsToSides(layout_guide, view,
                            LayoutSides::kTop | LayoutSides::kBottom |
                                LayoutSides::kLeading | LayoutSides::kTrailing);

  [view setNeedsLayout];
  [view layoutIfNeeded];

  return layout_guide;
}

// Tests that `AddChipGroupsToVerticalLeadViews` adds the expected
// ManualFillCellViews to the `vertical_lead_views` array.
TEST_F(ManualFillTestUtilsTest, TestAddChipGroupsToVerticalLeadViews) {
  // Create group with unlabeled chip buttons.
  ChipButton* chip_button_1 = [[ChipButton alloc] initWithFrame:CGRectZero];
  ChipButton* chip_button_2 = [[ChipButton alloc] initWithFrame:CGRectZero];
  NSArray<UIView*>* group_1 = @[ chip_button_1, chip_button_2 ];

  // Create empty group.
  NSArray<UIView*>* group_2 = @[];

  // Create group with only one unlabeled chip button.
  ChipButton* chip_button_3 = [[ChipButton alloc] initWithFrame:CGRectZero];
  NSArray<UIView*>* group_3 = @[ chip_button_3 ];

  // Create group with labeled chip buttons.
  ManualFillLabeledChip* chip_button_4 =
      [[ManualFillLabeledChip alloc] initSingleChipWithTarget:0 selector:0];
  ManualFillLabeledChip* chip_button_5 =
      [[ManualFillLabeledChip alloc] initSingleChipWithTarget:0 selector:0];
  ManualFillLabeledChip* chip_button_6 =
      [[ManualFillLabeledChip alloc] initSingleChipWithTarget:0 selector:0];
  NSArray<UIView*>* group_4 = @[ chip_button_4, chip_button_5, chip_button_6 ];

  std::vector<ManualFillCellView> vertical_lead_views;
  AddChipGroupsToVerticalLeadViews(@[ group_1, group_2, group_3, group_4 ],
                                   vertical_lead_views);

  std::vector<ManualFillCellView> expected_vertical_lead_views = {
      CreateManualFillCellView(
          chip_button_1,
          ManualFillCellView::ElementType::kFirstChipButtonOfGroup),
      CreateManualFillCellView(
          chip_button_2, ManualFillCellView::ElementType::kOtherChipButton),
      CreateManualFillCellView(
          chip_button_3,
          ManualFillCellView::ElementType::kFirstChipButtonOfGroup),
      CreateManualFillCellView(
          chip_button_4,
          ManualFillCellView::ElementType::kFirstChipButtonOfGroup),
      CreateManualFillCellView(
          chip_button_5, ManualFillCellView::ElementType::kLabeledChipButton),
      CreateManualFillCellView(
          chip_button_6, ManualFillCellView::ElementType::kLabeledChipButton)};

  EXPECT_THAT(vertical_lead_views,
              ::testing::ElementsAreArray(expected_vertical_lead_views));
}

// Tests that `LayViewsHorizontallyWhenPossible` lays as many views as possible
// horizontally, and starts a new row of views whenever there's not enough
// space in the current row.
TEST_F(ManualFillTestUtilsTest, TestLayViewsHorizontallyWhenPossible) {
  CGFloat max_width = 100;

  // Create the layout guide. Used in `LayViewsHorizontallyWhenPossible` to, in
  // part, determine the max width per row of views.
  UILayoutGuide* guide = CreateLayoutGuideOfWidth(max_width);

  // Generate views.
  std::vector<CGFloat> view_widths = {30, 20, 19, 39, 10, 120, 30};
  NSMutableArray<UIView*>* views = [[NSMutableArray alloc] init];
  for (CGFloat width : view_widths) {
    [views addObject:CreateButtonOfWidth(width)];
  }

  // Will contain every first view of a row.
  NSMutableArray<UIView*>* vertical_lead_views = [[NSMutableArray alloc] init];

  LayViewsHorizontallyWhenPossible(
      views, guide, /*constraints=*/[[NSMutableArray alloc] init],
      vertical_lead_views);

  // Generate `expected_vertical_lead_views`.
  NSMutableArray<UIView*>* expected_vertical_lead_views =
      [[NSMutableArray alloc] init];
  [expected_vertical_lead_views addObject:views[0]];

  // The width in the current row that's occupied.
  CGFloat row_width = 0;
  for (uint i = 0; i < views.count; i++) {
    row_width += view_widths[i];
    // Check if a new row should be started.
    if (row_width > max_width) {
      [expected_vertical_lead_views addObject:views[i]];
      row_width = view_widths[i];
    }
    row_width += GetHorizontalSpacingBetweenChips();
  }

  EXPECT_TRUE(
      [vertical_lead_views isEqualToArray:expected_vertical_lead_views]);
}

// Tests that `LayViewsHorizontallyWhenPossible` leaves `constraints` and
// `vertical_lead_views` empty when called with an empty array of views.
TEST_F(ManualFillTestUtilsTest,
       TestLayViewsHorizontallyWhenPossibleWithoutViews) {
  UILayoutGuide* guide = CreateLayoutGuideOfWidth(100);
  NSMutableArray<NSLayoutConstraint*>* constraints =
      [[NSMutableArray alloc] init];
  NSMutableArray<UIView*>* vertical_lead_views = [[NSMutableArray alloc] init];

  LayViewsHorizontallyWhenPossible(@[], guide, constraints,
                                   vertical_lead_views);

  EXPECT_EQ(constraints.count, 0u);
  EXPECT_EQ(vertical_lead_views.count, 0u);
}

}  // namespace