chromium/ios/chrome/browser/ui/omnibox/popup/pedal_section_extractor_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/pedal_section_extractor.h"

#import "base/test/ios/wait_util.h"
#import "ios/chrome/browser/ui/omnibox/popup/autocomplete_suggestion_group_impl.h"
#import "ios/chrome/browser/ui/omnibox/popup/omnibox_pedal.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 {

using base::test::ios::WaitUntilConditionOrTimeout;

class PedalSectionExtractorTest : public PlatformTest {
 protected:
  void SetUp() override {
    PlatformTest::SetUp();
    extractor_ = [[PedalSectionExtractor alloc] init];
    delegate_ =
        [OCMockObject mockForProtocol:@protocol(PedalSectionExtractorDelegate)];
    extractor_.delegate = delegate_;

    mock_suggestion_no_pedal_ =
        [OCMockObject mockForProtocol:@protocol(AutocompleteSuggestion)];
    [[[mock_suggestion_no_pedal_ stub] andReturn:nil] pedal];

    mock_pedal_ = [OCMockObject mockForProtocol:@protocol(OmniboxPedal)];
    [[[mock_pedal_ stub] andReturn:@"pedal title"] title];
    mock_suggestion_with_pedal_ =
        [OCMockObject mockForProtocol:@protocol(AutocompleteSuggestion)];
    [[[mock_suggestion_with_pedal_ stub] andReturn:mock_pedal_] pedal];
  }

  PedalSectionExtractor* extractor_;
  OCMockObject<PedalSectionExtractorDelegate>* delegate_;
  OCMockObject<OmniboxPedal>* mock_pedal_;
  id mock_suggestion_no_pedal_;
  id mock_suggestion_with_pedal_;
};

// When there's no pedals, extractor returns nil.
TEST_F(PedalSectionExtractorTest, NoPedal) {
  id<AutocompleteSuggestionGroup> pedalGroup =
      [extractor_ extractPedals:@[ mock_suggestion_no_pedal_ ]];
  EXPECT_FALSE(pedalGroup);
}

// When there's a pedal, it's extracted into a "suggestion" with the same name
// in a separate group.
TEST_F(PedalSectionExtractorTest, ExtractPedals) {
  id<AutocompleteSuggestionGroup> pedalGroup = [extractor_ extractPedals:@[
    mock_suggestion_no_pedal_, mock_suggestion_with_pedal_
  ]];

  EXPECT_TRUE(pedalGroup);
  EXPECT_EQ(pedalGroup.suggestions.count, 1u);

  id<AutocompleteSuggestion> suggestion = pedalGroup.suggestions[0];
  EXPECT_EQ(suggestion.text.string, mock_pedal_.title);
}

// When a pedal disappears from the result list, the extractor prevents
// its disappearance for a short time to reduce UI updates.
TEST_F(PedalSectionExtractorTest, Debounce) {
  // Showing a result with pedals.
  {
    id<AutocompleteSuggestionGroup> pedalGroup = [extractor_ extractPedals:@[
      mock_suggestion_no_pedal_, mock_suggestion_with_pedal_
    ]];
    EXPECT_TRUE(pedalGroup);
    EXPECT_EQ(pedalGroup.suggestions.count, 1u);
  }

  // Updating with no pedals continues to return a pedal.
  {
    id<AutocompleteSuggestionGroup> pedalGroup =
        [extractor_ extractPedals:@[ mock_suggestion_no_pedal_ ]];
    EXPECT_TRUE(pedalGroup);
    EXPECT_EQ(pedalGroup.suggestions.count, 1u);
  }

  // Expect pedal removal when debounce timer expires
  [[delegate_ expect] invalidatePedals];

  // Verify that the pedal cache expires.
  ASSERT_TRUE(WaitUntilConditionOrTimeout(base::Seconds(1), ^{
    return ![extractor_ hasCachedPedals];
  }));

  [delegate_ verify];

  // Now updating from no pedals to no pedals, nothing happens
  [[delegate_ reject] invalidatePedals];
  {
    id<AutocompleteSuggestionGroup> pedalGroup =
        [extractor_ extractPedals:@[ mock_suggestion_no_pedal_ ]];
    EXPECT_FALSE(pedalGroup);
  }
  [delegate_ verify];

  // Since there's no update, nothing happens after the debounce timer expires
  // again.
  EXPECT_FALSE([extractor_ hasCachedPedals]);
}

// When the list of suggestions is completely empty, prevent the pedal from
// sticking around. This should only happen when the popup is being closed,
// otherwise there's at least a what-you-type suggestion.
TEST_F(PedalSectionExtractorTest, DontDebounceEmptyList) {
  // Showing a result with pedals.
  {
    id<AutocompleteSuggestionGroup> pedalGroup = [extractor_ extractPedals:@[
      mock_suggestion_no_pedal_, mock_suggestion_with_pedal_
    ]];
    EXPECT_TRUE(pedalGroup);
    EXPECT_EQ(pedalGroup.suggestions.count, 1u);
  }

  // Updating with no pedals continues to return a pedal.
  {
    id<AutocompleteSuggestionGroup> pedalGroup =
        [extractor_ extractPedals:@[ mock_suggestion_no_pedal_ ]];
    EXPECT_TRUE(pedalGroup);
    EXPECT_EQ(pedalGroup.suggestions.count, 1u);
  }

  // Close popup. Pedal should be removed from cache.
  {
    [[delegate_ expect] invalidatePedals];
    id<AutocompleteSuggestionGroup> pedalGroup = [extractor_ extractPedals:@[]];
    EXPECT_FALSE(pedalGroup);
    [delegate_ verify];
  }

  // Open popup with no pedals, no pedal should show.
  {
    id<AutocompleteSuggestionGroup> pedalGroup =
        [extractor_ extractPedals:@[ mock_suggestion_no_pedal_ ]];
    EXPECT_FALSE(pedalGroup);
  }
}

}  // namespace