blob: 16004514be8342f08120420724a2a9f58ef428af [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// 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/tab_grid/tab_grid_mediator.h"
#import <Foundation/Foundation.h>
#include <memory>
#include "base/mac/foundation_util.h"
#include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
#include "ios/chrome/browser/chrome_url_constants.h"
#import "ios/chrome/browser/ntp/new_tab_page_tab_helper.h"
#import "ios/chrome/browser/ntp/new_tab_page_tab_helper_delegate.h"
#import "ios/chrome/browser/tabs/tab_model.h"
#import "ios/chrome/browser/ui/tab_grid/grid/grid_commands.h"
#import "ios/chrome/browser/ui/tab_grid/grid/grid_consumer.h"
#import "ios/chrome/browser/ui/tab_grid/grid/grid_item.h"
#import "ios/chrome/browser/web/tab_id_tab_helper.h"
#include "ios/chrome/browser/web_state_list/fake_web_state_list_delegate.h"
#include "ios/chrome/browser/web_state_list/web_state_list.h"
#import "ios/chrome/browser/web_state_list/web_state_opener.h"
#import "ios/web/public/test/fakes/test_web_state.h"
#include "ios/web/public/test/test_web_thread_bundle.h"
#include "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#include "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#include "third_party/ocmock/gtest_support.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
// Test object that conforms to GridConsumer and exposes inner state for test
// verification.
@interface FakeConsumer : NSObject<GridConsumer>
// The fake consumer only keeps the identifiers of items for simplicity
@property(nonatomic, strong) NSMutableArray<NSString*>* items;
@property(nonatomic, assign) NSString* selectedItemID;
@end
@implementation FakeConsumer
@synthesize items = _items;
@synthesize selectedItemID = _selectedItemID;
- (void)populateItems:(NSArray<GridItem*>*)items
selectedItemID:(NSString*)selectedItemID {
self.selectedItemID = selectedItemID;
self.items = [NSMutableArray array];
for (GridItem* item in items) {
[self.items addObject:item.identifier];
}
}
- (void)insertItem:(GridItem*)item
atIndex:(NSUInteger)index
selectedItemID:(NSString*)selectedItemID {
[self.items insertObject:item.identifier atIndex:index];
self.selectedItemID = selectedItemID;
}
- (void)removeItemWithID:(NSString*)removedItemID
selectedItemID:(NSString*)selectedItemID {
[self.items removeObject:removedItemID];
self.selectedItemID = selectedItemID;
}
- (void)selectItemWithID:(NSString*)selectedItemID {
self.selectedItemID = selectedItemID;
}
- (void)replaceItemID:(NSString*)itemID withItem:(GridItem*)item {
NSUInteger index = [self.items indexOfObject:itemID];
self.items[index] = item.identifier;
}
- (void)moveItemWithID:(NSString*)itemID toIndex:(NSUInteger)toIndex {
[self.items removeObject:itemID];
[self.items insertObject:itemID atIndex:toIndex];
}
@end
// Fake WebStateList delegate that attaches the tab ID tab helper.
class TabIdFakeWebStateListDelegate : public FakeWebStateListDelegate {
public:
TabIdFakeWebStateListDelegate() {}
~TabIdFakeWebStateListDelegate() override {}
// WebStateListDelegate implementation.
void WillAddWebState(web::WebState* web_state) override {
TabIdTabHelper::CreateForWebState(web_state);
// Create NTPTabHelper to ensure VisibleURL is set to kChromeUINewTabURL.
id delegate = OCMProtocolMock(@protocol(NewTabPageTabHelperDelegate));
NewTabPageTabHelper::CreateForWebState(web_state, delegate);
}
};
class TabGridMediatorTest : public PlatformTest {
public:
TabGridMediatorTest() {}
~TabGridMediatorTest() override {}
void SetUp() override {
PlatformTest::SetUp();
browser_state_ = TestChromeBrowserState::Builder().Build();
web_state_list_delegate_ =
std::make_unique<TabIdFakeWebStateListDelegate>();
web_state_list_ =
std::make_unique<WebStateList>(web_state_list_delegate_.get());
tab_model_ = OCMClassMock([TabModel class]);
OCMStub([tab_model_ webStateList]).andReturn(web_state_list_.get());
OCMStub([tab_model_ browserState]).andReturn(browser_state_.get());
NSMutableSet<NSString*>* identifiers = [[NSMutableSet alloc] init];
// Insert some web states.
for (int i = 0; i < 3; i++) {
auto web_state = std::make_unique<web::TestWebState>();
TabIdTabHelper::CreateForWebState(web_state.get());
NSString* identifier =
TabIdTabHelper::FromWebState(web_state.get())->tab_id();
// Tab IDs should be unique.
ASSERT_FALSE([identifiers containsObject:identifier]);
[identifiers addObject:identifier];
web_state_list_->InsertWebState(i, std::move(web_state),
WebStateList::INSERT_FORCE_INDEX,
WebStateOpener());
}
original_identifiers_ = [identifiers copy];
web_state_list_->ActivateWebStateAt(1);
original_selected_identifier_ =
TabIdTabHelper::FromWebState(web_state_list_->GetWebStateAt(1))
->tab_id();
consumer_ = [[FakeConsumer alloc] init];
mediator_ = [[TabGridMediator alloc] initWithConsumer:consumer_];
mediator_.tabModel = tab_model_;
}
protected:
web::TestWebThreadBundle thread_bundle_;
std::unique_ptr<ios::ChromeBrowserState> browser_state_;
std::unique_ptr<TabIdFakeWebStateListDelegate> web_state_list_delegate_;
std::unique_ptr<WebStateList> web_state_list_;
id tab_model_;
FakeConsumer* consumer_;
TabGridMediator* mediator_;
NSSet<NSString*>* original_identifiers_;
NSString* original_selected_identifier_;
};
#pragma mark - Consumer tests
// Tests that the consumer is populated after the tab model is set on the
// mediator.
TEST_F(TabGridMediatorTest, ConsumerPopulateItems) {
EXPECT_EQ(3UL, consumer_.items.count);
EXPECT_NSEQ(original_selected_identifier_, consumer_.selectedItemID);
}
// Tests that the consumer is notified when a web state is inserted.
TEST_F(TabGridMediatorTest, ConsumerInsertItem) {
ASSERT_EQ(3UL, consumer_.items.count);
auto web_state = std::make_unique<web::TestWebState>();
TabIdTabHelper::CreateForWebState(web_state.get());
NSString* item_identifier =
TabIdTabHelper::FromWebState(web_state.get())->tab_id();
web_state_list_->InsertWebState(1, std::move(web_state),
WebStateList::INSERT_FORCE_INDEX,
WebStateOpener());
EXPECT_EQ(4UL, consumer_.items.count);
// The same ID should be selected after the insertion, since the new web state
// wasn't selected.
EXPECT_NSEQ(original_selected_identifier_, consumer_.selectedItemID);
EXPECT_NSEQ(item_identifier, consumer_.items[1]);
EXPECT_FALSE([original_identifiers_ containsObject:item_identifier]);
}
// Tests that the consumer is notified when a web state is removed.
// The selected web state at index 1 is removed. The web state originally
// at index 2 should be the new selected item.
TEST_F(TabGridMediatorTest, ConsumerRemoveItem) {
web_state_list_->CloseWebStateAt(1, WebStateList::CLOSE_NO_FLAGS);
EXPECT_EQ(2UL, consumer_.items.count);
// Expect that a different web state is selected now.
EXPECT_NSNE(original_selected_identifier_, consumer_.selectedItemID);
}
// Tests that the consumer is notified when the active web state is changed.
TEST_F(TabGridMediatorTest, ConsumerUpdateSelectedItem) {
EXPECT_NSEQ(original_selected_identifier_, consumer_.selectedItemID);
web_state_list_->ActivateWebStateAt(2);
EXPECT_NSEQ(
TabIdTabHelper::FromWebState(web_state_list_->GetWebStateAt(2))->tab_id(),
consumer_.selectedItemID);
}
// Tests that the consumer is notified when a web state is replaced.
// The selected item is replaced, so the new selected item id should be the
// id of the new item.
TEST_F(TabGridMediatorTest, ConsumerReplaceItem) {
auto new_web_state = std::make_unique<web::TestWebState>();
TabIdTabHelper::CreateForWebState(new_web_state.get());
NSString* new_item_identifier =
TabIdTabHelper::FromWebState(new_web_state.get())->tab_id();
web_state_list_->ReplaceWebStateAt(1, std::move(new_web_state));
EXPECT_EQ(3UL, consumer_.items.count);
EXPECT_NSEQ(new_item_identifier, consumer_.selectedItemID);
EXPECT_NSEQ(new_item_identifier, consumer_.items[1]);
EXPECT_FALSE([original_identifiers_ containsObject:new_item_identifier]);
}
// Tests that the consumer is notified when a web state is moved.
TEST_F(TabGridMediatorTest, ConsumerMoveItem) {
NSString* item1 = consumer_.items[1];
NSString* item2 = consumer_.items[2];
web_state_list_->MoveWebStateAt(1, 2);
EXPECT_NSEQ(item1, consumer_.items[2]);
EXPECT_NSEQ(item2, consumer_.items[1]);
}
#pragma mark - Command tests
// Tests that the active index is updated when |-selectItemWithID:| is called.
// Tests that the consumer's selected index is updated.
TEST_F(TabGridMediatorTest, SelectItemCommand) {
// Previous selected index is 1.
NSString* identifier =
TabIdTabHelper::FromWebState(web_state_list_->GetWebStateAt(2))->tab_id();
[mediator_ selectItemWithID:identifier];
EXPECT_EQ(2, web_state_list_->active_index());
EXPECT_NSEQ(identifier, consumer_.selectedItemID);
}
// Tests that the |web_state_list_| count is decremented when
// |-closeItemWithID:| is called.
// Tests that the consumer's item count is also decremented.
TEST_F(TabGridMediatorTest, CloseItemCommand) {
// Previously there were 3 items.
NSString* identifier =
TabIdTabHelper::FromWebState(web_state_list_->GetWebStateAt(0))->tab_id();
[mediator_ closeItemWithID:identifier];
EXPECT_EQ(2, web_state_list_->count());
EXPECT_EQ(2UL, consumer_.items.count);
}
// Tests that the |web_state_list_| and consumer's list are empty when
// |-closeAllItems| is called. Tests that |-undoCloseAllItems| does not restore
// the |web_state_list_|.
TEST_F(TabGridMediatorTest, CloseAllItemsCommand) {
// Previously there were 3 items.
[mediator_ closeAllItems];
EXPECT_EQ(0, web_state_list_->count());
EXPECT_EQ(0UL, consumer_.items.count);
[mediator_ undoCloseAllItems];
EXPECT_EQ(0, web_state_list_->count());
}
// Tests that the |web_state_list_| and consumer's list are empty when
// |-saveAndCloseAllItems| is called.
TEST_F(TabGridMediatorTest, SaveAndCloseAllItemsCommand) {
// Previously there were 3 items.
[mediator_ saveAndCloseAllItems];
EXPECT_EQ(0, web_state_list_->count());
EXPECT_EQ(0UL, consumer_.items.count);
}
// Tests that the |web_state_list_| is not restored to 3 items when
// |-undoCloseAllItems| is called after |-discardSavedClosedItems| is called.
TEST_F(TabGridMediatorTest, DiscardSavedClosedItemsCommand) {
// Previously there were 3 items.
[mediator_ saveAndCloseAllItems];
[mediator_ discardSavedClosedItems];
[mediator_ undoCloseAllItems];
EXPECT_EQ(0, web_state_list_->count());
EXPECT_EQ(0UL, consumer_.items.count);
}
// Tests that the |web_state_list_| is restored to 3 items when
// |-undoCloseAllItems| is called.
TEST_F(TabGridMediatorTest, UndoCloseAllItemsCommand) {
// Previously there were 3 items.
[mediator_ saveAndCloseAllItems];
[mediator_ undoCloseAllItems];
EXPECT_EQ(3, web_state_list_->count());
EXPECT_EQ(3UL, consumer_.items.count);
EXPECT_TRUE([original_identifiers_ containsObject:consumer_.items[0]]);
EXPECT_TRUE([original_identifiers_ containsObject:consumer_.items[1]]);
EXPECT_TRUE([original_identifiers_ containsObject:consumer_.items[2]]);
}
// Tests that when |-addNewItem| is called, the |web_state_list_| count is
// incremented, the |active_index| is at the end of |web_state_list_|, the new
// web state has no opener, and the URL is the New Tab Page.
// Tests that the consumer has added an item with the correct identifier.
TEST_F(TabGridMediatorTest, AddNewItemAtEndCommand) {
// Previously there were 3 items and the selected index was 1.
[mediator_ addNewItem];
EXPECT_EQ(4, web_state_list_->count());
EXPECT_EQ(3, web_state_list_->active_index());
web::WebState* web_state = web_state_list_->GetWebStateAt(3);
ASSERT_TRUE(web_state);
EXPECT_EQ(web_state->GetBrowserState(), browser_state_.get());
EXPECT_FALSE(web_state->HasOpener());
EXPECT_EQ(kChromeUINewTabURL, web_state->GetVisibleURL().spec());
NSString* identifier = TabIdTabHelper::FromWebState(web_state)->tab_id();
EXPECT_FALSE([original_identifiers_ containsObject:identifier]);
// Consumer checks.
EXPECT_EQ(4UL, consumer_.items.count);
EXPECT_NSEQ(identifier, consumer_.selectedItemID);
EXPECT_NSEQ(identifier, consumer_.items[3]);
}
// Tests that when |-insertNewItemAtIndex:| is called, the |web_state_list_|
// count is incremented, the |active_index| is the newly added index, the new
// web state has no opener, and the URL is the new tab page.
// Checks that the consumer has added an item with the correct identifier.
TEST_F(TabGridMediatorTest, InsertNewItemCommand) {
// Previously there were 3 items and the selected index was 1.
[mediator_ insertNewItemAtIndex:0];
EXPECT_EQ(4, web_state_list_->count());
EXPECT_EQ(0, web_state_list_->active_index());
web::WebState* web_state = web_state_list_->GetWebStateAt(0);
ASSERT_TRUE(web_state);
EXPECT_EQ(web_state->GetBrowserState(), browser_state_.get());
EXPECT_FALSE(web_state->HasOpener());
EXPECT_EQ(kChromeUINewTabURL, web_state->GetVisibleURL().spec());
NSString* identifier = TabIdTabHelper::FromWebState(web_state)->tab_id();
EXPECT_FALSE([original_identifiers_ containsObject:identifier]);
// Consumer checks.
EXPECT_EQ(4UL, consumer_.items.count);
EXPECT_NSEQ(identifier, consumer_.selectedItemID);
EXPECT_NSEQ(identifier, consumer_.items[0]);
}
// Tests that |-insertNewItemAtIndex:| is a no-op if the mediator's TabModel
// is nil.
TEST_F(TabGridMediatorTest, InsertNewItemWithNoTabModelCommand) {
mediator_.tabModel = nil;
ASSERT_EQ(3, web_state_list_->count());
ASSERT_EQ(1, web_state_list_->active_index());
[mediator_ insertNewItemAtIndex:0];
EXPECT_EQ(3, web_state_list_->count());
EXPECT_EQ(1, web_state_list_->active_index());
}
// Tests that when |-moveItemFromIndex:toIndex:| is called, there is no change
// in the item count in |web_state_list_|, but that the constituent web states
// have been reordered.
TEST_F(TabGridMediatorTest, MoveItemCommand) {
// Capture ordered original IDs.
NSMutableArray<NSString*>* pre_move_ids = [[NSMutableArray alloc] init];
for (int i = 0; i < 3; i++) {
web::WebState* web_state = web_state_list_->GetWebStateAt(i);
[pre_move_ids addObject:TabIdTabHelper::FromWebState(web_state)->tab_id()];
}
NSString* pre_move_selected_id =
pre_move_ids[web_state_list_->active_index()];
// Items start ordered [A, B, C].
[mediator_ moveItemWithID:consumer_.items[0] toIndex:2];
// Items should now be ordered [B, C, A] -- the pre-move identifiers should
// still be in this order.
// Item count hasn't changed.
EXPECT_EQ(3, web_state_list_->count());
// Active index has moved -- it was 1, now 0.
EXPECT_EQ(0, web_state_list_->active_index());
// Identifier at 0, 1, 2 should match the original_identifier_ at 1, 2, 0.
for (int index = 0; index < 2; index++) {
web::WebState* web_state = web_state_list_->GetWebStateAt(index);
ASSERT_TRUE(web_state);
NSString* identifier = TabIdTabHelper::FromWebState(web_state)->tab_id();
EXPECT_NSEQ(identifier, pre_move_ids[(index + 1) % 3]);
EXPECT_NSEQ(identifier, consumer_.items[index]);
}
EXPECT_EQ(pre_move_selected_id, consumer_.selectedItemID);
}