chromium/ios/chrome/browser/ui/popup_menu/overflow_menu/destination_usage_history/destination_usage_history_egtest.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 <UIKit/UIKit.h>
#import <XCTest/XCTest.h>

#import "base/ios/ios_util.h"
#import "base/strings/stringprintf.h"
#import "components/feature_engagement/public/feature_constants.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/ui/popup_menu/popup_menu_constants.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h"
#import "ios/chrome/test/earl_grey/chrome_matchers.h"
#import "ios/chrome/test/earl_grey/chrome_test_case.h"
#import "ios/testing/earl_grey/app_launch_configuration.h"
#import "ios/testing/earl_grey/app_launch_manager.h"

#import "ios/testing/earl_grey/earl_grey_test.h"
#import "ios/third_party/earl_grey2/src/CommonLib/Matcher/GREYLayoutConstraint.h"  // nogncheck

namespace {
// Unexpectedly, the first and last destinations in the carousel overlap their
// neighbors. This makes `RightConstraintWithOverlap()` an insufficient layout
// constraint for comparing destinations at the carousel's ends. A constraint
// with negative minimum separation, `RightConstraintWithOverlap()`, must be
// introduced to account for this.
GREYLayoutConstraint* RightConstraintWithOverlap() {
  return [GREYLayoutConstraint
      layoutConstraintForDirection:kGREYLayoutDirectionRight
              andMinimumSeparation:-1.0];
}

}  // namespace

// Tests the Smart Sorting algorithm correctly sorts destinations in the new
// overflow menu carousel given certain usage.
@interface DestinationUsageHistoryTestCase : ChromeTestCase
@end

@implementation DestinationUsageHistoryTestCase

- (AppLaunchConfiguration)appConfigurationForTestCase {
  AppLaunchConfiguration config = [super appConfigurationForTestCase];

  // This ensures that the test will not fail when What's New is updated.
  config.additional_args.push_back(
      base::StringPrintf("--disable-features=%s",
                         feature_engagement::kIPHWhatsNewUpdatedFeature.name));

  return config;
}

- (void)setUp {
  [super setUp];
  [ChromeEarlGrey
      resetDataForLocalStatePref:prefs::kOverflowMenuDestinationUsageHistory];
  [ChromeEarlGrey
      resetDataForLocalStatePref:prefs::kOverflowMenuNewDestinations];
  [ChromeEarlGrey
      resetDataForLocalStatePref:prefs::kOverflowMenuDestinationsOrder];
  [ChromeEarlGrey
      resetDataForLocalStatePref:prefs::kOverflowMenuHiddenDestinations];
  [ChromeEarlGrey
      resetDataForLocalStatePref:prefs::kOverflowMenuDestinationBadgeData];
}

- (void)tearDown {
  // Close the overflow menu (popup menu).
  [ChromeTestCase removeAnyOpenMenusAndInfoBars];
  [super tearDown];
}

#pragma mark - Helpers

// Verifies the destination carousel displays the default sort order, which is:
// 1. Bookmarks
// 2. History
// 3. Reading List
// 4. Password Manager
// 5. Downloads
// 6. Recent Tabs
// 7. Site Information
// 8. Settings
//
// When `isNTP` is true, this method excludes the Site Information (#7)
// destination from the layout check, because Site Information is excluded from
// the destinations carousel on the NTP.
+ (void)verifyCarouselHasDefaultSortOrderOnNTP:(BOOL)isNTP {
  [ChromeEarlGreyUI openToolsMenu];
  [ChromeEarlGrey verifyAccessibilityForCurrentScreen];

  // . . . Bookmarks, History . . .
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::HistoryDestinationButton()]
      assertWithMatcher:grey_layout(
                            @[ RightConstraintWithOverlap() ],
                            chrome_test_util::BookmarksDestinationButton())];

  // . . . History, Reading List . . .
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::ReadingListDestinationButton()]
      assertWithMatcher:grey_layout(
                            @[ RightConstraintWithOverlap() ],
                            chrome_test_util::HistoryDestinationButton())];
  // . . . Reading List, Password Manager . . .
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::PasswordsDestinationButton()]
      assertWithMatcher:grey_layout(
                            @[ RightConstraintWithOverlap() ],
                            chrome_test_util::ReadingListDestinationButton())];
  // . . . Password Manager, Downloads . . .
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::DownloadsDestinationButton()]
      assertWithMatcher:grey_layout(
                            @[ RightConstraintWithOverlap() ],
                            chrome_test_util::PasswordsDestinationButton())];
  // . . . Downloads, Recent Tabs . . .
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::RecentTabsDestinationButton()]
      assertWithMatcher:grey_layout(
                            @[ RightConstraintWithOverlap() ],
                            chrome_test_util::DownloadsDestinationButton())];

  if (isNTP) {
    // . . . Recent Tabs, Settings . . .
    [[EarlGrey
        selectElementWithMatcher:chrome_test_util::SettingsDestinationButton()]
        assertWithMatcher:grey_layout(
                              @[ RightConstraintWithOverlap() ],
                              chrome_test_util::RecentTabsDestinationButton())];
  } else {
    // Site Information is included in the destinations carousel on non-NTP
    // pages.

    // . . . Recent Tabs, Site Information . . .
    [[EarlGrey
        selectElementWithMatcher:chrome_test_util::SiteInfoDestinationButton()]
        assertWithMatcher:grey_layout(
                              @[ RightConstraintWithOverlap() ],
                              chrome_test_util::RecentTabsDestinationButton())];
    // . . . Site Information, Settings . . .
    [[EarlGrey
        selectElementWithMatcher:chrome_test_util::SettingsDestinationButton()]
        assertWithMatcher:grey_layout(
                              @[ RightConstraintWithOverlap() ],
                              chrome_test_util::SiteInfoDestinationButton())];
  }

  [ChromeEarlGreyUI closeToolsMenu];
}

// Verifies the destination carousel displays the default sort order for
// incognito, which is:
// 1. Bookmarks
// 2. Reading List
// 3. Password Manager
// 4. Downloads
// 5. Site Information
// 6. Settings
//
// When `isNTP` is true, this method excludes the Site Information (#5)
// destination from the layout check, because Site Information is excluded from
// the destinations carousel on the NTP.
+ (void)verifyCarouselHasDefaultSortOrderOnNTPForIncognito:(BOOL)isNTP {
  [ChromeEarlGreyUI openToolsMenu];
  [ChromeEarlGrey verifyAccessibilityForCurrentScreen];

  // . . . Bookmarks, Reading List . . .
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::ReadingListDestinationButton()]
      assertWithMatcher:grey_layout(
                            @[ RightConstraintWithOverlap() ],
                            chrome_test_util::BookmarksDestinationButton())];
  // . . . Reading List, Password Manager . . .
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::PasswordsDestinationButton()]
      assertWithMatcher:grey_layout(
                            @[ RightConstraintWithOverlap() ],
                            chrome_test_util::ReadingListDestinationButton())];
  // . . . Password Manager, Downloads . . .
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::DownloadsDestinationButton()]
      assertWithMatcher:grey_layout(
                            @[ RightConstraintWithOverlap() ],
                            chrome_test_util::PasswordsDestinationButton())];

  if (isNTP) {
    // . . . Downloads, Settings . . .
    [[EarlGrey
        selectElementWithMatcher:chrome_test_util::SettingsDestinationButton()]
        assertWithMatcher:grey_layout(
                              @[ RightConstraintWithOverlap() ],
                              chrome_test_util::DownloadsDestinationButton())];
  } else {
    // Site Information is included in the destinations carousel on non-NTP
    // pages.

    // . . . Downloads, Site Information . . .
    [[EarlGrey
        selectElementWithMatcher:chrome_test_util::SiteInfoDestinationButton()]
        assertWithMatcher:grey_layout(
                              @[ RightConstraintWithOverlap() ],
                              chrome_test_util::DownloadsDestinationButton())];
    // . . . Site Information, Settings . . .
    [[EarlGrey
        selectElementWithMatcher:chrome_test_util::SettingsDestinationButton()]
        assertWithMatcher:grey_layout(
                              @[ RightConstraintWithOverlap() ],
                              chrome_test_util::SiteInfoDestinationButton())];
  }

  [ChromeEarlGreyUI closeToolsMenu];
}

#pragma mark - DestinationUsageHistoryTestCase Tests

// Tests the default sort order for the destinations carousel is correctly
// displayed. The default sort order is:
// 1. Bookmarks
// 2. History
// 3. Reading List
// 4. Password Manager
// 5. Downloads
// 6. Recent Tabs
// 7. Site Information
// 8. Settings
// 9. What's New
- (void)testDefaultCarouselSortOrderDisplayed {
  [ChromeEarlGrey loadURL:GURL("chrome://version")];
  [DestinationUsageHistoryTestCase verifyCarouselHasDefaultSortOrderOnNTP:NO];
}

// Tests the default sort order for the destinations carousel is correctly
// displayed on the NTP. The default sort order is:
// 1. Bookmarks
// 2. History
// 3. Reading List
// 4. Password Manager
// 5. Downloads
// 6. Recent Tabs
// 7. Settings
// 8. What's New
// NOTE: By design, the Site Information destination is removed from the
// destinations carousel on the NTP.
- (void)testDefaultCarouselSortOrderDisplayedOnNTP {
  [DestinationUsageHistoryTestCase verifyCarouselHasDefaultSortOrderOnNTP:YES];
}

// Tests the default sort order for the destinations carousel is correctly
// displayed for an incognito page. The default sort order is:
// 1. Bookmarks
// 2. Reading List
// 3. Password Manager
// 4. Downloads
// 5. Site Information
// 6. Settings
// 7. What's New
- (void)testDefaultCarouselSortOrderDisplayedForIncognito {
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey loadURL:GURL("chrome://version")];
  [DestinationUsageHistoryTestCase
      verifyCarouselHasDefaultSortOrderOnNTPForIncognito:NO];
}

// Tests the default sort order for the destinations carousel is correctly
// displayed on the incognito NTP. The default sort order is:
// 1. Bookmarks
// 2. Reading List
// 3. Password Manager
// 4. Downloads
// 5. Settings
// 6. What's New
//
// NOTE: By design, the Site Information destination is removed from the
// destinations carousel on the NTP.
- (void)testDefaultCarouselSortOrderDisplayedOnNTPForIncognito {
  [ChromeEarlGrey openNewIncognitoTab];
  [DestinationUsageHistoryTestCase
      verifyCarouselHasDefaultSortOrderOnNTPForIncognito:YES];
}

// For the following tests, it's important to note the destinations carousel is
// divided into two groups: (A) visible "above-the-fold" destinations and (B)
// non-visible "below-the-fold" destinations; "below-the-fold" destinations are
// made visible to the user when they scroll the carousel.

// Tests an above-the-fold destination never moves within group (A),
// regardless of usage.
- (void)testAboveFoldDestinationNeverPromotes {
  if ([ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_SKIPPED(
        @"b/329307989: Smart Sorting currently broken on iPad devices.");
  }

  // Tap the above-fold destination, Bookmarks, 5 times.
  for (int i = 0; i < 5; i++) {
    [ChromeEarlGreyUI openToolsMenu];
    [ChromeEarlGrey verifyAccessibilityForCurrentScreen];
    [ChromeEarlGreyUI
        tapToolsMenuButton:chrome_test_util::BookmarksDestinationButton()];
    [[EarlGrey selectElementWithMatcher:chrome_test_util::
                                            BookmarksNavigationBarDoneButton()]
        performAction:grey_tap()];
  }

  [DestinationUsageHistoryTestCase verifyCarouselHasDefaultSortOrderOnNTP:YES];
}

// Tests a below-the-fold destination gets promoted.
- (void)testBelowFoldDestinationPromotes {
  if ([ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_SKIPPED(
        @"b/329307989: Smart Sorting currently broken on iPad devices.");
  }

  // Tap the below-fold destination, Settings, 5 times.
  for (int i = 0; i < 5; i++) {
    [ChromeEarlGreyUI openToolsMenu];
    [ChromeEarlGrey verifyAccessibilityForCurrentScreen];

    [ChromeEarlGreyUI
        tapToolsMenuButton:chrome_test_util::SettingsDestinationButton()];

    [[EarlGrey selectElementWithMatcher:chrome_test_util::SettingsDoneButton()]
        performAction:grey_tap()];
  }

  // Open the overflow menu to verify no changes to the carousel sort were made.
  [ChromeEarlGreyUI openToolsMenu];
  [ChromeEarlGrey verifyAccessibilityForCurrentScreen];

  // . . . Settings, History . . .
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::HistoryDestinationButton()]
      assertWithMatcher:grey_layout(
                            @[ RightConstraintWithOverlap() ],
                            chrome_test_util::SettingsDestinationButton())];
  // . . . History, Reading List . . .
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::ReadingListDestinationButton()]
      assertWithMatcher:grey_layout(
                            @[ RightConstraintWithOverlap() ],
                            chrome_test_util::HistoryDestinationButton())];
  // . . . Reading List, Password Manager . . .
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::PasswordsDestinationButton()]
      assertWithMatcher:grey_layout(
                            @[ RightConstraintWithOverlap() ],
                            chrome_test_util::ReadingListDestinationButton())];
  // . . . Password Manager, Downloads . . .
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::DownloadsDestinationButton()]
      assertWithMatcher:grey_layout(
                            @[ RightConstraintWithOverlap() ],
                            chrome_test_util::PasswordsDestinationButton())];
  // . . . Downloads, Recent Tabs . . .
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::RecentTabsDestinationButton()]
      assertWithMatcher:grey_layout(
                            @[ RightConstraintWithOverlap() ],
                            chrome_test_util::DownloadsDestinationButton())];
  // . . . Recent Tabs, Bookmarks . . .
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::BookmarksDestinationButton()]
      assertWithMatcher:grey_layout(
                            @[ RightConstraintWithOverlap() ],
                            chrome_test_util::RecentTabsDestinationButton())];
}

// Tests a below-the-fold destination is not promoted until the third click
// for a fresh destination usage history.
- (void)testNoSwapUntilMinClickCountReached {
  if ([ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_SKIPPED(
        @"b/329307989: Smart Sorting currently broken on iPad devices.");
  }

  [DestinationUsageHistoryTestCase verifyCarouselHasDefaultSortOrderOnNTP:YES];

  // 1st Settings tap (no promotion expected after this tap)
  [ChromeEarlGreyUI openToolsMenu];
  [ChromeEarlGrey verifyAccessibilityForCurrentScreen];
  [ChromeEarlGreyUI
      tapToolsMenuButton:chrome_test_util::SettingsDestinationButton()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::SettingsDoneButton()]
      performAction:grey_tap()];

  [DestinationUsageHistoryTestCase verifyCarouselHasDefaultSortOrderOnNTP:YES];

  // 2nd Settings tap (no promotion expected after this tap)
  [ChromeEarlGreyUI openToolsMenu];
  [ChromeEarlGrey verifyAccessibilityForCurrentScreen];
  [ChromeEarlGreyUI
      tapToolsMenuButton:chrome_test_util::SettingsDestinationButton()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::SettingsDoneButton()]
      performAction:grey_tap()];

  [DestinationUsageHistoryTestCase verifyCarouselHasDefaultSortOrderOnNTP:YES];

  // 3rd Settings tap (promotion expected after this tap!)
  [ChromeEarlGreyUI openToolsMenu];
  [ChromeEarlGrey verifyAccessibilityForCurrentScreen];
  [ChromeEarlGreyUI
      tapToolsMenuButton:chrome_test_util::SettingsDestinationButton()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::SettingsDoneButton()]
      performAction:grey_tap()];

  [ChromeEarlGreyUI openToolsMenu];
  [ChromeEarlGrey verifyAccessibilityForCurrentScreen];

  // . . . Settings, History . . .
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::HistoryDestinationButton()]
      assertWithMatcher:grey_layout(
                            @[ RightConstraintWithOverlap() ],
                            chrome_test_util::SettingsDestinationButton())];

  // . . . History, Reading List . . .
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::ReadingListDestinationButton()]
      assertWithMatcher:grey_layout(
                            @[ RightConstraintWithOverlap() ],
                            chrome_test_util::HistoryDestinationButton())];
  // . . . Reading List, Password Manager . . .
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::PasswordsDestinationButton()]
      assertWithMatcher:grey_layout(
                            @[ RightConstraintWithOverlap() ],
                            chrome_test_util::ReadingListDestinationButton())];
  // . . . Password Manager, Downloads . . .
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::DownloadsDestinationButton()]
      assertWithMatcher:grey_layout(
                            @[ RightConstraintWithOverlap() ],
                            chrome_test_util::PasswordsDestinationButton())];
  // . . . Downloads, Recent Tabs . . .
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::RecentTabsDestinationButton()]
      assertWithMatcher:grey_layout(
                            @[ RightConstraintWithOverlap() ],
                            chrome_test_util::DownloadsDestinationButton())];
  // . . . Recent Tabs, Bookmarks . . .
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::BookmarksDestinationButton()]
      assertWithMatcher:grey_layout(
                            @[ RightConstraintWithOverlap() ],
                            chrome_test_util::RecentTabsDestinationButton())];
}

@end