chromium/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_view_controller_unittest.mm

// Copyright 2022 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/ui/omnibox/popup/omnibox_popup_view_controller.h"

#import "ios/chrome/browser/ui/omnibox/popup/autocomplete_suggestion_group_impl.h"
#import "ios/chrome/browser/ui/omnibox/popup/popup_match_preview_delegate.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "third_party/ocmock/gtest_support.h"

namespace {

class OmniboxPopupViewControllerTest : public PlatformTest {
 protected:
  void ExpectPreviewSuggestion(id suggestion, BOOL is_first_update) {
    [[preview_delegate_ expect] setPreviewSuggestion:suggestion
                                       isFirstUpdate:is_first_update];
  }
  BOOL PerformKeyboardAction(OmniboxKeyboardAction keyboardAction) {
    BOOL canPerform =
        [popup_view_controller_ canPerformKeyboardAction:keyboardAction];
    if (canPerform) {
      [popup_view_controller_ performKeyboardAction:keyboardAction];
    }
    return canPerform;
  }

  BOOL MoveHighlightUp() {
    return PerformKeyboardAction(OmniboxKeyboardActionUpArrow);
  }

  BOOL MoveHighlightDown() {
    return PerformKeyboardAction(OmniboxKeyboardActionDownArrow);
  }

  NSArray<id<AutocompleteSuggestion>>* GenerateMockSuggestions(
      NSUInteger nb_suggestions) {
    NSMutableArray* array =
        [[NSMutableArray alloc] initWithCapacity:nb_suggestions];
    for (NSUInteger i = 0; i < nb_suggestions; ++i) {
      id<AutocompleteSuggestion> mockSuggestion =
          OCMProtocolMock(@protocol(AutocompleteSuggestion));
      NSString* suggestionText =
          [NSString stringWithFormat:@"Suggestion %ld", i];
      OCMStub([mockSuggestion text])
          .andReturn(
              [[NSAttributedString alloc] initWithString:suggestionText]);
      [array addObject:mockSuggestion];
    }
    return array;
  }

  void SetUp() override {
    PlatformTest::SetUp();
    delegate_ = [OCMockObject
        mockForProtocol:@protocol(AutocompleteResultConsumerDelegate)];
    preview_delegate_ =
        [OCMockObject mockForProtocol:@protocol(PopupMatchPreviewDelegate)];
    return_delegate_ =
        [OCMockObject mockForProtocol:@protocol(OmniboxReturnDelegate)];
    popup_view_controller_ = [[OmniboxPopupViewController alloc] init];
    popup_view_controller_.delegate = delegate_;
    popup_view_controller_.matchPreviewDelegate = preview_delegate_;
    popup_view_controller_.acceptReturnDelegate = return_delegate_;
    // Force view initialisation since this view controller is never added into
    // the hierarchy in this unit test.
    [popup_view_controller_ view];

    first_suggestion_group_ = [AutocompleteSuggestionGroupImpl
        groupWithTitle:@""
           suggestions:GenerateMockSuggestions(5u)];
    second_suggestion_group_ = [AutocompleteSuggestionGroupImpl
        groupWithTitle:@""
           suggestions:GenerateMockSuggestions(10u)];
    suggestion_groups_ = @[ first_suggestion_group_, second_suggestion_group_ ];
  }

  OCMockObject<AutocompleteResultConsumerDelegate>* delegate_;
  OCMockObject<OmniboxReturnDelegate>* return_delegate_;
  OCMockObject<PopupMatchPreviewDelegate>* preview_delegate_;
  OmniboxPopupViewController* popup_view_controller_;

  id<AutocompleteSuggestionGroup> first_suggestion_group_;
  id<AutocompleteSuggestionGroup> second_suggestion_group_;
  NSArray<id<AutocompleteSuggestionGroup>>* suggestion_groups_;
};

// Tests that the first suggestion of the preselected group is highlighted on
// down arrow.
TEST_F(OmniboxPopupViewControllerTest,
       HighlightFirstSuggestionOfPreselectedGroup) {
  for (NSUInteger preselectedGroupIndex = 0;
       preselectedGroupIndex < suggestion_groups_.count;
       ++preselectedGroupIndex) {
    // Expect first suggestion of preselected group to be previewed.
    ExpectPreviewSuggestion(
        suggestion_groups_[preselectedGroupIndex].suggestions[0], YES);
    [popup_view_controller_ updateMatches:suggestion_groups_
               preselectedMatchGroupIndex:preselectedGroupIndex];
    [preview_delegate_ verify];

    // Expect first suggestion of the preselected group to be highlighted on
    // down arrow.
    ExpectPreviewSuggestion(
        suggestion_groups_[preselectedGroupIndex].suggestions[0], NO);
    EXPECT_TRUE(MoveHighlightDown());
    [preview_delegate_ verify];
  }
}

// Tests that highlighting with no suggestions do not preview any suggestions
// and does not crash.
TEST_F(OmniboxPopupViewControllerTest, UpDownArrowWithZeroMatches) {
  // Expect nil for preview suggestion.
  ExpectPreviewSuggestion(nil, YES);
  [popup_view_controller_ updateMatches:@[] preselectedMatchGroupIndex:0];
  [preview_delegate_ verify];

  [[[preview_delegate_ reject] ignoringNonObjectArgs]
      setPreviewSuggestion:OCMOCK_ANY
             isFirstUpdate:NO];

  // Up arrow key with zero suggestions should do nothing.
  EXPECT_TRUE(MoveHighlightUp());

  // Down arrow key with zero suggestions should do nothing.
  EXPECT_TRUE(MoveHighlightDown());
}

// Tests highlighting suggestions with one group of suggestions.
TEST_F(OmniboxPopupViewControllerTest, UpDownArrowWithOneGroup) {
  // Open match with one group of suggestions.
  ExpectPreviewSuggestion(first_suggestion_group_.suggestions[0], YES);
  [popup_view_controller_ updateMatches:@[ first_suggestion_group_ ]
             preselectedMatchGroupIndex:0];
  [preview_delegate_ verify];

  // Up Arrow when nothing is highlighted and when no group is above preselected
  // group, does nothing.
  EXPECT_TRUE(MoveHighlightUp());

  // Down Arrow highlights the next suggestion.
  for (NSUInteger i = 0; i < first_suggestion_group_.suggestions.count; ++i) {
    ExpectPreviewSuggestion(first_suggestion_group_.suggestions[i], NO);
    EXPECT_TRUE(MoveHighlightDown());
    [preview_delegate_ verify];
  }

  // Down Arrow when the last sugggestion is highlighted continues to highlight
  // the last suggestion.
  ExpectPreviewSuggestion(
      first_suggestion_group_
          .suggestions[first_suggestion_group_.suggestions.count - 1],
      NO);
  EXPECT_TRUE(MoveHighlightDown());
  [preview_delegate_ verify];

  // Up Arrow highlights the previous suggestion.
  for (NSInteger i = first_suggestion_group_.suggestions.count - 2; i >= 0;
       --i) {
    ExpectPreviewSuggestion(first_suggestion_group_.suggestions[i], NO);
    EXPECT_TRUE(MoveHighlightUp());
    [preview_delegate_ verify];
  }

  // Up Arrow when the first sugggestion is highlighted continues to highlight
  // the first suggestion.
  ExpectPreviewSuggestion(first_suggestion_group_.suggestions[0], NO);
  EXPECT_TRUE(MoveHighlightUp());
  [preview_delegate_ verify];
}

// Tests highlighting with two groups of suggestions.
TEST_F(OmniboxPopupViewControllerTest, UpDownArrowGroupSwitch) {
  // Open match with two groups of suggestions, with the second group as
  // preselected group.
  ExpectPreviewSuggestion(second_suggestion_group_.suggestions[0], YES);
  [popup_view_controller_ updateMatches:suggestion_groups_
             preselectedMatchGroupIndex:1];
  [preview_delegate_ verify];

  // Up Arrow when nothing is highlighted and with a group above the preselected
  // group, highlights the last suggestion of the group above.
  ExpectPreviewSuggestion(
      first_suggestion_group_
          .suggestions[first_suggestion_group_.suggestions.count - 1],
      NO);
  EXPECT_TRUE(MoveHighlightUp());
  [preview_delegate_ verify];

  // Down Arrow when the last suggestion of the first group is highlighted,
  // highlights the first suggestion of the second group.
  ExpectPreviewSuggestion(second_suggestion_group_.suggestions[0], NO);
  EXPECT_TRUE(MoveHighlightDown());
  [preview_delegate_ verify];

  // Up Arrow when the first suggestion of the second group is highlighted,
  // highlights the last suggestion of the first group.
  ExpectPreviewSuggestion(
      first_suggestion_group_
          .suggestions[first_suggestion_group_.suggestions.count - 1],
      NO);
  EXPECT_TRUE(MoveHighlightUp());
  [preview_delegate_ verify];
}

// Tests Return key interaction when a suggestion is highlighted and when no
// suggestions are highlighted.
TEST_F(OmniboxPopupViewControllerTest, ReturnHighlightedSuggestion) {
  // Open match with two groups of suggestions, with the second group as
  // preselected group.
  ExpectPreviewSuggestion(first_suggestion_group_.suggestions[0], YES);
  [popup_view_controller_ updateMatches:suggestion_groups_
             preselectedMatchGroupIndex:0];
  [preview_delegate_ verify];

  // Pressing return key when no suggestion is highlighted calls the
  // OmniboxReturnDelegate.
  [[return_delegate_ expect] omniboxReturnPressed:OCMOCK_ANY];
  [popup_view_controller_ omniboxReturnPressed:nil];
  [return_delegate_ verify];

  // Select first suggestion with down arrow.
  ExpectPreviewSuggestion(first_suggestion_group_.suggestions[0], NO);
  EXPECT_TRUE(MoveHighlightDown());
  [preview_delegate_ verify];

  // Pressing return key when a suggestion is highlighted call the
  // AutocompleteResultConsumerDelegate.
  [[delegate_ expect]
      autocompleteResultConsumer:popup_view_controller_
             didSelectSuggestion:first_suggestion_group_.suggestions[0]
                           inRow:0];
  [popup_view_controller_ omniboxReturnPressed:nil];
  [delegate_ verify];
}

}  // namespace