chromium/ios/chrome/browser/ui/settings/settings_root_table_view_controller_unittest.mm

// Copyright 2018 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/settings/settings_root_table_view_controller.h"

#import "base/test/task_environment.h"
#import "ios/chrome/browser/shared/model/browser/test/test_browser.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/browser/ui/settings/settings_navigation_controller.h"
#import "ios/chrome/grit/ios_strings.h"
#import "testing/gtest/include/gtest/gtest.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"
#import "ui/base/l10n/l10n_util_mac.h"

// Test SettingsRootTableViewController subclass that conformfs to
// SettingsControllerProtocol. Used to test that 'settingsWillBeDismissed' is
// called at the right time.
@interface FakeSettingsRootTableViewController
    : SettingsRootTableViewController <SettingsControllerProtocol>

@property(nonatomic, assign) BOOL settingsWillBeDismissedCalled;

@end

@implementation FakeSettingsRootTableViewController

#pragma mark - SettingsControllerProtocol

- (void)reportDismissalUserAction {
}

- (void)reportBackUserAction {
}

- (void)settingsWillBeDismissed {
  _settingsWillBeDismissedCalled = YES;
}

@end

class SettingsRootTableViewControllerTest : public PlatformTest {
 public:
  SettingsRootTableViewController* Controller() {
    return [[SettingsRootTableViewController alloc]
        initWithStyle:UITableViewStylePlain];
  }

  SettingsNavigationController* NavigationController() {
    if (!browser_) {
      TestChromeBrowserState::Builder test_cbs_builder;
      chrome_browser_state_ = std::move(test_cbs_builder).Build();
      browser_ = std::make_unique<TestBrowser>(chrome_browser_state_.get());
    }
    return [[SettingsNavigationController alloc]
        initWithRootViewController:nil
                           browser:browser_.get()
                          delegate:nil];
  }

 protected:
  base::test::TaskEnvironment task_environment_;
  std::unique_ptr<TestChromeBrowserState> chrome_browser_state_;
  std::unique_ptr<TestBrowser> browser_;
};

TEST_F(SettingsRootTableViewControllerTest, TestUpdateUIForEditState) {
  SettingsRootTableViewController* controller = Controller();

  id mockController = OCMPartialMock(controller);
  SettingsNavigationController* navigationController = NavigationController();
  OCMStub([mockController navigationController])
      .andReturn(navigationController);

  // Check that there the navigation controller's button if the table view isn't
  // edited and the controller has the default behavior for
  // `shouldShowEditButton`. Also check that toolbar is hidden when
  // `shouldHideToolbar` returns YES.
  controller.tableView.editing = NO;
  OCMExpect([mockController shouldHideToolbar]).andReturn(YES);
  [controller updateUIForEditState];
  UIBarButtonItem* item = [[UIBarButtonItem alloc]
      initWithBarButtonSystemItem:UIBarButtonSystemItemDone
                           target:nil
                           action:nil];
  EXPECT_NSEQ(item.title, controller.navigationItem.rightBarButtonItem.title);
  EXPECT_TRUE(controller.navigationController.toolbarHidden);

  // Check that there the OK button if the table view is being edited and the
  // controller has the default behavior for `shouldShowEditButton`. Also check
  // that toolbar is not hidden when `shouldHideToolbar` returns NO.
  controller.tableView.editing = YES;
  OCMExpect([mockController shouldHideToolbar]).andReturn(NO);
  [controller updateUIForEditState];
  EXPECT_NSEQ(l10n_util::GetNSString(IDS_IOS_NAVIGATION_BAR_DONE_BUTTON),
              controller.navigationItem.rightBarButtonItem.title);
  EXPECT_FALSE(controller.navigationController.toolbarHidden);

  // Check that there the OK button if the table view isn't edited and the
  // controller returns YES for `shouldShowEditButton`. Also check that toolbar
  // is not hidden when `shouldHideToolbar` returns NO.
  controller.tableView.editing = NO;
  OCMStub([mockController shouldShowEditButton]).andReturn(YES);
  OCMExpect([mockController shouldHideToolbar]).andReturn(NO);
  [controller updateUIForEditState];
  EXPECT_NSEQ(l10n_util::GetNSString(IDS_IOS_NAVIGATION_BAR_EDIT_BUTTON),
              controller.navigationItem.rightBarButtonItem.title);
  EXPECT_FALSE(controller.navigationController.toolbarHidden);
  [navigationController cleanUpSettings];
}

// Tests that the delete button in the bottom toolbar is displayed only when the
// collection is being edited.
TEST_F(SettingsRootTableViewControllerTest, TestDeleteToolbar) {
  SettingsRootTableViewController* controller = Controller();

  id mockController = OCMPartialMock(controller);
  id mockTableView = OCMPartialMock(controller.tableView);
  SettingsNavigationController* navigationController = NavigationController();
  OCMStub([mockController navigationController])
      .andReturn(navigationController);

  NSIndexPath* testIndexPath = [NSIndexPath indexPathForRow:0 inSection:0];
  NSMutableArray* testSelectedItems = [NSMutableArray array];
  [testSelectedItems addObject:testIndexPath];
  ASSERT_TRUE(navigationController.toolbarHidden);

  // Test that if the table view isn't being edited, the toolbar is still
  // hidden.
  controller.tableView.editing = NO;
  [controller tableView:controller.tableView
      didSelectRowAtIndexPath:testIndexPath];
  EXPECT_TRUE(navigationController.toolbarHidden);

  // Test that if the table view is being edited, the toolbar is displayed when
  // the element is selected.
  controller.tableView.editing = YES;
  EXPECT_TRUE(navigationController.toolbarHidden);
  [controller tableView:controller.tableView
      didSelectRowAtIndexPath:testIndexPath];
  EXPECT_FALSE(navigationController.toolbarHidden);

  // Test that if the table view is being edited, the toolbar is not hidden when
  // an element is deselected but some elements are still selected.
  OCMStub([mockTableView indexPathsForSelectedRows])
      .andReturn(testSelectedItems);
  [controller tableView:controller.tableView
      didDeselectRowAtIndexPath:testIndexPath];
  EXPECT_FALSE(navigationController.toolbarHidden);

  // Test that if the table view is being edited, the toolbar is hidden when the
  // last selected element is deselected.
  [testSelectedItems removeAllObjects];
  [controller tableView:controller.tableView
      didDeselectRowAtIndexPath:testIndexPath];
  EXPECT_TRUE(navigationController.toolbarHidden);
  [navigationController cleanUpSettings];
}

// Tests that a subclass of SettingsRootViewController that implements the
// SettingsControllerProtocol does not have its implementation of
// 'settingsWillBeDismissed' called when 'willMoveToParentViewController' is
// triggered. 'settingsWillBeDismissed' should only be called when
// 'didMoveToParentViewController' is triggered. Otherwise, a crash may occur as
// some of the subclass objects can be reset before the subclass is deallocated.
// A call to 'willMoveToParentViewController' does not necessarily mean that the
// view controller will be removed from the navigation stack.
TEST_F(SettingsRootTableViewControllerTest,
       TestSettingsWillBeDismissedCallTiming) {
  FakeSettingsRootTableViewController* controller =
      [[FakeSettingsRootTableViewController alloc]
          initWithStyle:UITableViewStylePlain];

  [controller willMoveToParentViewController:nil];
  EXPECT_FALSE(controller.settingsWillBeDismissedCalled);

  [controller didMoveToParentViewController:nil];
  EXPECT_TRUE(controller.settingsWillBeDismissedCalled);
}