diff options
author | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 23:55:29 +0000 |
---|---|---|
committer | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 23:55:29 +0000 |
commit | 09911bf300f1a419907a9412154760efd0b7abc3 (patch) | |
tree | f131325fb4e2ad12c6d3504ab75b16dd92facfed /chrome/browser/tabs/tab_strip_model_unittest.cc | |
parent | 586acc5fe142f498261f52c66862fa417c3d52d2 (diff) | |
download | chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.zip chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.tar.gz chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.tar.bz2 |
Add chrome to the repository.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@15 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/tabs/tab_strip_model_unittest.cc')
-rw-r--r-- | chrome/browser/tabs/tab_strip_model_unittest.cc | 1130 |
1 files changed, 1130 insertions, 0 deletions
diff --git a/chrome/browser/tabs/tab_strip_model_unittest.cc b/chrome/browser/tabs/tab_strip_model_unittest.cc new file mode 100644 index 0000000..49e64ac --- /dev/null +++ b/chrome/browser/tabs/tab_strip_model_unittest.cc @@ -0,0 +1,1130 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/file_util.h" +#include "base/path_service.h" +#include "chrome/browser/dom_ui/new_tab_ui.h" +#include "chrome/browser/navigation_controller.h" +#include "chrome/browser/navigation_entry.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/profile_manager.h" +#include "chrome/browser/tabs/tab_strip_model.h" +#include "chrome/browser/tabs/tab_strip_model_order_controller.h" +#include "chrome/browser/tab_contents.h" +#include "chrome/browser/tab_contents_factory.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/stl_util-inl.h" +#include "testing/gtest/include/gtest/gtest.h" + +const TabContentsType kHTTPTabContentsType = + static_cast<TabContentsType>(TAB_CONTENTS_NUM_TYPES + 1); +const TabContentsType kReplacementContentsType = + static_cast<TabContentsType>(kHTTPTabContentsType + 1); + +// Since you can't just instantiate a TabContents, and some of its methods +// are protected, we subclass TabContents with our own testing dummy which +// knows how to drive the base class' NavigationController as URLs are +// loaded. +class TabStripModelTestTabContents : public TabContents { + public: + TabStripModelTestTabContents(const TabContentsType type) + : TabContents(type) { + } + + bool Navigate(const NavigationEntry& entry, bool reload) { + NavigationEntry* pending_entry = new NavigationEntry(entry); + if (pending_entry->GetPageID() == -1) { + pending_entry->SetPageID(g_page_id_++); + } + DidNavigateToEntry(pending_entry); + + return true; + } + + private: + // We need to use valid, incrementing page ids otherwise the TabContents + // and NavController will not play nice when we try to go back and forward. + static int g_page_id_; +}; + +int TabStripModelTestTabContents::g_page_id_ = 0; + +// This constructs our fake TabContents. +class TabStripModelTestTabContentsFactory : public TabContentsFactory { + public: + virtual TabContents* CreateInstance() { + return new TabStripModelTestTabContents(kHTTPTabContentsType); + } + + virtual bool CanHandleURL(const GURL& url) { + return url.scheme() == "http"; + } +}; + +TabStripModelTestTabContentsFactory factory; + +class TabStripModelTest : public testing::Test { + public: + // Overridden from testing::Test + virtual void SetUp() { + TabContents::RegisterFactory(kHTTPTabContentsType, &factory); + + // Name a subdirectory of the temp directory. + ASSERT_TRUE(PathService::Get(base::DIR_TEMP, &test_dir_)); + file_util::AppendToPath(&test_dir_, L"TabStripModelTest"); + + // Create a fresh, empty copy of this directory. + file_util::Delete(test_dir_, true); + CreateDirectory(test_dir_.c_str(), NULL); + + profile_path_ = test_dir_; + file_util::AppendToPath(&profile_path_, L"New Profile"); + + profile_ = ProfileManager::CreateProfile(profile_path_, + L"New Profile", L"new-profile", L""); + ASSERT_TRUE(profile_); + pm_.AddProfile(profile_); + } + + virtual void TearDown() { + TabContents::RegisterFactory(kHTTPTabContentsType, NULL); + + // Removes a profile from the set of currently-loaded profiles. + pm_.RemoveProfileByPath(profile_path_); + + // Clean up test directory + ASSERT_TRUE(file_util::Delete(test_dir_, true)); + ASSERT_FALSE(file_util::PathExists(test_dir_)); + } + + protected: + TabContents* CreateTabContents() { + TabStripModelTestTabContents* contents = + new TabStripModelTestTabContents(kHTTPTabContentsType); + contents->SetupController(profile_); + return contents; + } + TabContents* CreateReplacementContents() { + TabStripModelTestTabContents* contents = + new TabStripModelTestTabContents(kReplacementContentsType); + contents->SetupController(profile_); + return contents; + } + + // Forwards a URL "load" request through to our dummy TabContents + // implementation. + void LoadURL(TabContents* contents, const std::wstring& url) { + contents->controller()->LoadURL(GURL(url), PageTransition::LINK); + } + + void GoBack(TabContents* contents) { + contents->controller()->GoBack(); + } + + void GoForward(TabContents* contents) { + contents->controller()->GoForward(); + } + + void SwitchTabTo(TabContents* contents) { + contents->DidBecomeSelected(); + } + + Profile* profile_; + + private: + std::wstring test_dir_; + std::wstring profile_path_; + ProfileManager pm_; +}; + +class MockTabStripModelObserver : public TabStripModelObserver { + public: + MockTabStripModelObserver() : empty_(true) {} + ~MockTabStripModelObserver() { + STLDeleteContainerPointers(states_.begin(), states_.end()); + } + + enum TabStripModelObserverAction { + INSERT, + CLOSE, + DETACH, + SELECT, + MOVE, + CHANGE + }; + + struct State { + State(TabContents* a_dst_contents, + int a_dst_index, + TabStripModelObserverAction a_action) + : src_contents(NULL), + dst_contents(a_dst_contents), + src_index(-1), + dst_index(a_dst_index), + action(a_action), + user_gesture(false), + foreground(false) { + } + + TabContents* src_contents; + TabContents* dst_contents; + int src_index; + int dst_index; + bool user_gesture; + bool foreground; + TabStripModelObserverAction action; + }; + + int GetStateCount() const { + return static_cast<int>(states_.size()); + } + + State* GetStateAt(int index) const { + DCHECK(index >= 0 && index < GetStateCount()); + return states_.at(index); + } + + bool StateEquals(int index, const State& state) { + State* s = GetStateAt(index); + EXPECT_EQ(s->src_contents, state.src_contents); + EXPECT_EQ(s->dst_contents, state.dst_contents); + EXPECT_EQ(s->src_index, state.src_index); + EXPECT_EQ(s->dst_index, state.dst_index); + EXPECT_EQ(s->user_gesture, state.user_gesture); + EXPECT_EQ(s->foreground, state.foreground); + EXPECT_EQ(s->action, state.action); + return (s->src_contents == state.src_contents && + s->dst_contents == state.dst_contents && + s->src_index == state.src_index && + s->dst_index == state.dst_index && + s->user_gesture == state.user_gesture && + s->foreground == state.foreground && + s->action == state.action); + } + + // TabStripModelObserver implementation: + virtual void TabInsertedAt(TabContents* contents, + int index, + bool foreground) { + empty_ = false; + State* s = new State(contents, index, INSERT); + s->foreground = foreground; + states_.push_back(s); + } + virtual void TabSelectedAt(TabContents* old_contents, + TabContents* new_contents, + int index, + bool user_gesture) { + State* s = new State(new_contents, index, SELECT); + s->src_contents = old_contents; + s->user_gesture = user_gesture; + states_.push_back(s); + } + virtual void TabMoved( + TabContents* contents, int from_index, int to_index) { + State* s = new State(contents, to_index, MOVE); + s->src_index = from_index; + states_.push_back(s); + } + + virtual void TabClosingAt(TabContents* contents, int index) { + states_.push_back(new State(contents, index, CLOSE)); + } + virtual void TabDetachedAt(TabContents* contents, int index) { + states_.push_back(new State(contents, index, DETACH)); + } + virtual void TabChangedAt(TabContents* contents, int index) { + states_.push_back(new State(contents, index, CHANGE)); + } + virtual void TabStripEmpty() { + empty_ = true; + } + + void ClearStates() { + STLDeleteContainerPointers(states_.begin(), states_.end()); + states_.clear(); + } + + bool empty() const { return empty_; } + + private: + std::vector<State*> states_; + + bool empty_; + + DISALLOW_EVIL_CONSTRUCTORS(MockTabStripModelObserver); +}; + +TEST_F(TabStripModelTest, TestBasicAPI) { + TabStripModel tabstrip(NULL, profile_); + MockTabStripModelObserver observer; + tabstrip.AddObserver(&observer); + + EXPECT_TRUE(tabstrip.empty()); + + typedef MockTabStripModelObserver::State State; + + TabContents* contents1 = CreateTabContents(); + + // Note! The ordering of these tests is important, each subsequent test + // builds on the state established in the previous. This is important if you + // ever insert tests rather than append. + + // Test AppendTabContents, ContainsIndex + { + EXPECT_FALSE(tabstrip.ContainsIndex(0)); + tabstrip.AppendTabContents(contents1, true); + EXPECT_TRUE(tabstrip.ContainsIndex(0)); + EXPECT_EQ(1, tabstrip.count()); + EXPECT_EQ(2, observer.GetStateCount()); + State s1(contents1, 0, MockTabStripModelObserver::INSERT); + s1.foreground = true; + EXPECT_TRUE(observer.StateEquals(0, s1)); + State s2(contents1, 0, MockTabStripModelObserver::SELECT); + s2.src_contents = NULL; + EXPECT_TRUE(observer.StateEquals(1, s2)); + observer.ClearStates(); + } + + // Test InsertTabContentsAt, foreground tab. + TabContents* contents2 = CreateTabContents(); + { + tabstrip.InsertTabContentsAt(1, contents2, true, false); + + EXPECT_EQ(2, tabstrip.count()); + EXPECT_EQ(2, observer.GetStateCount()); + State s1(contents2, 1, MockTabStripModelObserver::INSERT); + s1.foreground = true; + EXPECT_TRUE(observer.StateEquals(0, s1)); + State s2(contents2, 1, MockTabStripModelObserver::SELECT); + s2.src_contents = contents1; + EXPECT_TRUE(observer.StateEquals(1, s2)); + observer.ClearStates(); + } + + // Test InsertTabContentsAt, background tab. + TabContents* contents3 = CreateTabContents(); + { + tabstrip.InsertTabContentsAt(2, contents3, false, false); + + EXPECT_EQ(3, tabstrip.count()); + EXPECT_EQ(1, observer.GetStateCount()); + State s1(contents3, 2, MockTabStripModelObserver::INSERT); + s1.foreground = false; + EXPECT_TRUE(observer.StateEquals(0, s1)); + observer.ClearStates(); + } + + // Test SelectTabContentsAt + { + tabstrip.SelectTabContentsAt(2, true); + EXPECT_EQ(1, observer.GetStateCount()); + State s1(contents3, 2, MockTabStripModelObserver::SELECT); + s1.src_contents = contents2; + s1.user_gesture = true; + EXPECT_TRUE(observer.StateEquals(0, s1)); + observer.ClearStates(); + } + + // Test ReplaceTabContentsAt, replacing the selected index + TabContents* replacement_contents3 = CreateReplacementContents(); + { + tabstrip.ReplaceTabContentsAt(2, replacement_contents3); + EXPECT_EQ(2, observer.GetStateCount()); + State s1(replacement_contents3, 2, MockTabStripModelObserver::CHANGE); + EXPECT_TRUE(observer.StateEquals(0, s1)); + State s2(replacement_contents3, 2, MockTabStripModelObserver::SELECT); + s2.src_contents = contents3; + s2.user_gesture = false; + EXPECT_TRUE(observer.StateEquals(1, s2)); + observer.ClearStates(); + } + + // Test ReplaceTabContentsAt, replacing NOT the selected index + TabContents* replacement_contents2 = CreateReplacementContents(); + { + tabstrip.ReplaceTabContentsAt(1, replacement_contents2); + + EXPECT_EQ(1, observer.GetStateCount()); + State s1(replacement_contents2, 1, MockTabStripModelObserver::CHANGE); + EXPECT_TRUE(observer.StateEquals(0, s1)); + observer.ClearStates(); + } + + // Test DetachTabContentsAt + { + // Detach + TabContents* detached = tabstrip.DetachTabContentsAt(2); + // ... and append again because we want this for later. + tabstrip.AppendTabContents(detached, true); + EXPECT_EQ(4, observer.GetStateCount()); + State s1(detached, 2, MockTabStripModelObserver::DETACH); + EXPECT_TRUE(observer.StateEquals(0, s1)); + State s2(replacement_contents2, 1, MockTabStripModelObserver::SELECT); + s2.src_contents = replacement_contents3; + s2.user_gesture = false; + EXPECT_TRUE(observer.StateEquals(1, s2)); + State s3(detached, 2, MockTabStripModelObserver::INSERT); + s3.foreground = true; + EXPECT_TRUE(observer.StateEquals(2, s3)); + State s4(detached, 2, MockTabStripModelObserver::SELECT); + s4.src_contents = replacement_contents2; + s4.user_gesture = false; + EXPECT_TRUE(observer.StateEquals(3, s4)); + observer.ClearStates(); + } + + // Test CloseTabContentsAt + { + tabstrip.CloseTabContentsAt(2); + EXPECT_EQ(2, tabstrip.count()); + + EXPECT_EQ(3, observer.GetStateCount()); + State s1(replacement_contents3, 2, MockTabStripModelObserver::CLOSE); + EXPECT_TRUE(observer.StateEquals(0, s1)); + State s2(replacement_contents3, 2, MockTabStripModelObserver::DETACH); + EXPECT_TRUE(observer.StateEquals(1, s2)); + State s3(replacement_contents2, 1, MockTabStripModelObserver::SELECT); + s3.src_contents = replacement_contents3; + s3.user_gesture = false; + EXPECT_TRUE(observer.StateEquals(2, s3)); + observer.ClearStates(); + } + + // Test MoveTabContentsAt + { + tabstrip.MoveTabContentsAt(1, 0); + + EXPECT_EQ(1, observer.GetStateCount()); + State s1(replacement_contents2, 0, MockTabStripModelObserver::MOVE); + s1.src_index = 1; + EXPECT_TRUE(observer.StateEquals(0, s1)); + observer.ClearStates(); + } + + // Test Getters + { + EXPECT_EQ(replacement_contents2, tabstrip.GetSelectedTabContents()); + EXPECT_EQ(replacement_contents2, tabstrip.GetTabContentsAt(0)); + EXPECT_EQ(contents1, tabstrip.GetTabContentsAt(1)); + EXPECT_EQ(0, tabstrip.GetIndexOfTabContents(replacement_contents2)); + EXPECT_EQ(1, tabstrip.GetIndexOfTabContents(contents1)); + EXPECT_EQ(0, tabstrip.GetIndexOfController( + replacement_contents2->controller())); + EXPECT_EQ(1, tabstrip.GetIndexOfController(contents1->controller())); + } + + // Test UpdateTabContentsStateAt + { + tabstrip.UpdateTabContentsStateAt(0); + EXPECT_EQ(1, observer.GetStateCount()); + State s1(replacement_contents2, 0, MockTabStripModelObserver::CHANGE); + EXPECT_TRUE(observer.StateEquals(0, s1)); + observer.ClearStates(); + } + + // Test SelectNextTab, SelectPreviousTab, SelectLastTab + { + // Make sure the second of the two tabs is selected first... + tabstrip.SelectTabContentsAt(1, true); + tabstrip.SelectPreviousTab(); + EXPECT_EQ(0, tabstrip.selected_index()); + tabstrip.SelectLastTab(); + EXPECT_EQ(1, tabstrip.selected_index()); + tabstrip.SelectNextTab(); + EXPECT_EQ(0, tabstrip.selected_index()); + } + + // Test CloseSelectedTab + { + tabstrip.CloseSelectedTab(); + // |CloseSelectedTab| calls CloseTabContentsAt, we already tested that, now + // just verify that the count and selected index have changed + // appropriately... + EXPECT_EQ(1, tabstrip.count()); + EXPECT_EQ(0, tabstrip.selected_index()); + } + + tabstrip.CloseAllTabs(); + // TabStripModel should now be empty. + EXPECT_TRUE(tabstrip.empty()); + + // Opener methods are tested below... + + tabstrip.RemoveObserver(&observer); +} + +TEST_F(TabStripModelTest, TestBasicOpenerAPI) { + TabStripModel tabstrip(NULL, profile_); + EXPECT_TRUE(tabstrip.empty()); + + // This is a basic test of opener functionality. opener_contents is created + // as the first tab in the strip and then we create 5 other tabs in the + // background with opener_contents set as their opener. + + TabContents* opener_contents = CreateTabContents(); + NavigationController* opener = opener_contents->controller(); + tabstrip.AppendTabContents(opener_contents, true); + TabContents* contents1 = CreateTabContents(); + TabContents* contents2 = CreateTabContents(); + TabContents* contents3 = CreateTabContents(); + TabContents* contents4 = CreateTabContents(); + TabContents* contents5 = CreateTabContents(); + + // We use |InsertTabContentsAt| here instead of AppendTabContents so that + // openership relationships are preserved. + tabstrip.InsertTabContentsAt(tabstrip.count(), contents1, false, true); + tabstrip.InsertTabContentsAt(tabstrip.count(), contents2, false, true); + tabstrip.InsertTabContentsAt(tabstrip.count(), contents3, false, true); + tabstrip.InsertTabContentsAt(tabstrip.count(), contents4, false, true); + tabstrip.InsertTabContentsAt(tabstrip.count(), contents5, false, true); + + // All the tabs should have the same opener. + for (int i = 1; i < tabstrip.count(); ++i) + EXPECT_EQ(opener, tabstrip.GetOpenerOfTabContentsAt(i)); + + // If there is a next adjacent item, then the index should be of that item. + EXPECT_EQ(2, tabstrip.GetIndexOfNextTabContentsOpenedBy(opener, 1, false)); + // If the last tab in the group is closed, the preceding tab in the same + // group should be selected. + EXPECT_EQ(4, tabstrip.GetIndexOfNextTabContentsOpenedBy(opener, 5, false)); + + // Tests the method that finds the last tab opened by the same opener in the + // strip (this is the insertion index for the next background tab for the + // specified opener). + EXPECT_EQ(5, tabstrip.GetIndexOfLastTabContentsOpenedBy(opener, 1)); + + // For a tab that has opened no other tabs, the return value should always be + // -1... + NavigationController* o1 = contents1->controller(); + EXPECT_EQ(-1, tabstrip.GetIndexOfNextTabContentsOpenedBy(o1, 3, false)); + EXPECT_EQ(-1, tabstrip.GetIndexOfLastTabContentsOpenedBy(o1, 3)); + + // ForgetAllOpeners should destroy all opener relationships. + tabstrip.ForgetAllOpeners(); + EXPECT_EQ(-1, tabstrip.GetIndexOfNextTabContentsOpenedBy(opener, 1, false)); + EXPECT_EQ(-1, tabstrip.GetIndexOfNextTabContentsOpenedBy(opener, 5, false)); + EXPECT_EQ(-1, tabstrip.GetIndexOfLastTabContentsOpenedBy(opener, 1)); + + tabstrip.CloseAllTabs(); + EXPECT_TRUE(tabstrip.empty()); +} + +static int GetInsertionIndex(TabStripModel* tabstrip, TabContents* contents) { + return tabstrip->order_controller()->DetermineInsertionIndex( + contents, PageTransition::LINK, false); +} + +static void InsertTabContentses(TabStripModel* tabstrip, + TabContents* contents1, + TabContents* contents2, + TabContents* contents3) { + tabstrip->InsertTabContentsAt(GetInsertionIndex(tabstrip, contents1), + contents1, false, true); + tabstrip->InsertTabContentsAt(GetInsertionIndex(tabstrip, contents2), + contents2, false, true); + tabstrip->InsertTabContentsAt(GetInsertionIndex(tabstrip, contents3), + contents3, false, true); +} + +// Tests opening background tabs. +TEST_F(TabStripModelTest, TestLTRInsertionOptions) { + TabStripModel tabstrip(NULL, profile_); + EXPECT_TRUE(tabstrip.empty()); + + TabContents* opener_contents = CreateTabContents(); + tabstrip.AppendTabContents(opener_contents, true); + + TabContents* contents1 = CreateTabContents(); + TabContents* contents2 = CreateTabContents(); + TabContents* contents3 = CreateTabContents(); + + // Test LTR + InsertTabContentses(&tabstrip, contents1, contents2, contents3); + EXPECT_EQ(contents1, tabstrip.GetTabContentsAt(1)); + EXPECT_EQ(contents2, tabstrip.GetTabContentsAt(2)); + EXPECT_EQ(contents3, tabstrip.GetTabContentsAt(3)); + + tabstrip.CloseAllTabs(); + EXPECT_TRUE(tabstrip.empty()); +} + +// This test constructs a tabstrip, and then simulates loading several tabs in +// the background from link clicks on the first tab. Then it simulates opening +// a new tab from the first tab in the foreground via a link click, verifies +// that this tab is opened adjacent to the opener, then closes it. +// Finally it tests that a tab opened for some non-link purpose openes at the +// end of the strip, not bundled to any existing context. +TEST_F(TabStripModelTest, TestInsertionIndexDetermination) { + TabStripModel tabstrip(NULL, profile_); + EXPECT_TRUE(tabstrip.empty()); + + TabContents* opener_contents = CreateTabContents(); + NavigationController* opener = opener_contents->controller(); + tabstrip.AppendTabContents(opener_contents, true); + + // Open some other random unrelated tab in the background to monkey with our + // insertion index. + TabContents* other_contents = CreateTabContents(); + tabstrip.AppendTabContents(other_contents, false); + + TabContents* contents1 = CreateTabContents(); + TabContents* contents2 = CreateTabContents(); + TabContents* contents3 = CreateTabContents(); + + // Start by testing LTR + InsertTabContentses(&tabstrip, contents1, contents2, contents3); + EXPECT_EQ(opener_contents, tabstrip.GetTabContentsAt(0)); + EXPECT_EQ(contents1, tabstrip.GetTabContentsAt(1)); + EXPECT_EQ(contents2, tabstrip.GetTabContentsAt(2)); + EXPECT_EQ(contents3, tabstrip.GetTabContentsAt(3)); + EXPECT_EQ(other_contents, tabstrip.GetTabContentsAt(4)); + + // The opener API should work... + EXPECT_EQ(3, tabstrip.GetIndexOfNextTabContentsOpenedBy(opener, 2, false)); + EXPECT_EQ(2, tabstrip.GetIndexOfNextTabContentsOpenedBy(opener, 3, false)); + EXPECT_EQ(3, tabstrip.GetIndexOfLastTabContentsOpenedBy(opener, 1)); + + // Now open a foreground tab from a link. It should be opened adjacent to the + // opener tab. + TabContents* fg_link_contents = CreateTabContents(); + int insert_index = tabstrip.order_controller()->DetermineInsertionIndex( + fg_link_contents, PageTransition::LINK, true); + EXPECT_EQ(1, insert_index); + tabstrip.InsertTabContentsAt(insert_index, fg_link_contents, true, true); + EXPECT_EQ(1, tabstrip.selected_index()); + EXPECT_EQ(fg_link_contents, tabstrip.GetSelectedTabContents()); + + // Now close this contents. The selection should move to the opener contents. + tabstrip.CloseSelectedTab(); + EXPECT_EQ(0, tabstrip.selected_index()); + + // Now open a new empty tab. It should open at the end of the strip. + TabContents* fg_nonlink_contents = CreateTabContents(); + insert_index = tabstrip.order_controller()->DetermineInsertionIndex( + fg_nonlink_contents, PageTransition::AUTO_BOOKMARK, true); + EXPECT_EQ(tabstrip.count(), insert_index); + // We break the opener relationship... + tabstrip.InsertTabContentsAt(insert_index, fg_nonlink_contents, false, false); + // Now select it, so that user_gesture == true causes the opener relationship + // to be forgotten... + tabstrip.SelectTabContentsAt(tabstrip.count() - 1, true); + EXPECT_EQ(tabstrip.count() - 1, tabstrip.selected_index()); + EXPECT_EQ(fg_nonlink_contents, tabstrip.GetSelectedTabContents()); + + // Verify that all opener relationships are forgotten. + EXPECT_EQ(-1, tabstrip.GetIndexOfNextTabContentsOpenedBy(opener, 2, false)); + EXPECT_EQ(-1, tabstrip.GetIndexOfNextTabContentsOpenedBy(opener, 3, false)); + EXPECT_EQ(-1, tabstrip.GetIndexOfNextTabContentsOpenedBy(opener, 3, false)); + EXPECT_EQ(-1, tabstrip.GetIndexOfLastTabContentsOpenedBy(opener, 1)); + + tabstrip.CloseAllTabs(); + EXPECT_TRUE(tabstrip.empty()); +} + +// Tests that selection is shifted to the correct tab when a tab is closed. +// If a tab is in the background when it is closed, the selection does not +// change. +// If a tab is in the foreground (selected), +// If that tab does not have an opener, selection shifts to the right. +// If the tab has an opener, +// The next tab (scanning LTR) in the entire strip that has the same opener +// is selected +// If there are no other tabs that have the same opener, +// The opener is selected +// +TEST_F(TabStripModelTest, TestSelectOnClose) { + TabStripModel tabstrip(NULL, profile_); + EXPECT_TRUE(tabstrip.empty()); + + TabContents* opener_contents = CreateTabContents(); + NavigationController* opener = opener_contents->controller(); + tabstrip.AppendTabContents(opener_contents, true); + + TabContents* contents1 = CreateTabContents(); + TabContents* contents2 = CreateTabContents(); + TabContents* contents3 = CreateTabContents(); + + // Note that we use Detach instead of Close throughout this test to avoid + // having to keep reconstructing these TabContentses. + + // First test that closing tabs that are in the background doesn't adjust the + // current selection. + InsertTabContentses(&tabstrip, contents1, contents2, contents3); + EXPECT_EQ(0, tabstrip.selected_index()); + + tabstrip.DetachTabContentsAt(1); + EXPECT_EQ(0, tabstrip.selected_index()); + + for (int i = tabstrip.count() - 1; i >= 1; --i) + tabstrip.DetachTabContentsAt(i); + + // Now test that when a tab doesn't have an opener, selection shifts to the + // right when the tab is closed. + InsertTabContentses(&tabstrip, contents1, contents2, contents3); + EXPECT_EQ(0, tabstrip.selected_index()); + + tabstrip.ForgetAllOpeners(); + tabstrip.SelectTabContentsAt(1, true); + EXPECT_EQ(1, tabstrip.selected_index()); + tabstrip.DetachTabContentsAt(1); + EXPECT_EQ(1, tabstrip.selected_index()); + tabstrip.DetachTabContentsAt(1); + EXPECT_EQ(1, tabstrip.selected_index()); + tabstrip.DetachTabContentsAt(1); + EXPECT_EQ(0, tabstrip.selected_index()); + + for (int i = tabstrip.count() - 1; i >= 1; --i) + tabstrip.DetachTabContentsAt(i); + + // Now test that when a tab does have an opener, it selects the next tab + // opened by the same opener scanning LTR when it is closed. + InsertTabContentses(&tabstrip, contents1, contents2, contents3); + EXPECT_EQ(0, tabstrip.selected_index()); + tabstrip.SelectTabContentsAt(2, false); + EXPECT_EQ(2, tabstrip.selected_index()); + tabstrip.CloseTabContentsAt(2); + EXPECT_EQ(2, tabstrip.selected_index()); + tabstrip.CloseTabContentsAt(2); + EXPECT_EQ(1, tabstrip.selected_index()); + tabstrip.CloseTabContentsAt(1); + EXPECT_EQ(0, tabstrip.selected_index()); + + // Finally test that when a tab has no "siblings" that the opener is + // selected. + TabContents* other_contents = CreateTabContents(); + tabstrip.InsertTabContentsAt(1, other_contents, false, false); + EXPECT_EQ(2, tabstrip.count()); + TabContents* opened_contents = CreateTabContents(); + tabstrip.InsertTabContentsAt(2, opened_contents, true, true); + EXPECT_EQ(2, tabstrip.selected_index()); + tabstrip.CloseTabContentsAt(2); + EXPECT_EQ(0, tabstrip.selected_index()); + + tabstrip.CloseAllTabs(); + EXPECT_TRUE(tabstrip.empty()); +} + +// Tests the following context menu commands: +// - Close Tab +// - Close Other Tabs +// - Close Tabs To Right +// - Close Tabs Opened By +TEST_F(TabStripModelTest, TestContextMenuCloseCommands) { + TabStripModel tabstrip(NULL, profile_); + EXPECT_TRUE(tabstrip.empty()); + + TabContents* opener_contents = CreateTabContents(); + NavigationController* opener = opener_contents->controller(); + tabstrip.AppendTabContents(opener_contents, true); + + TabContents* contents1 = CreateTabContents(); + TabContents* contents2 = CreateTabContents(); + TabContents* contents3 = CreateTabContents(); + + InsertTabContentses(&tabstrip, contents1, contents2, contents3); + EXPECT_EQ(0, tabstrip.selected_index()); + + tabstrip.ExecuteContextMenuCommand(2, TabStripModel::CommandCloseTab); + EXPECT_EQ(3, tabstrip.count()); + + tabstrip.ExecuteContextMenuCommand(0, TabStripModel::CommandCloseTabsToRight); + EXPECT_EQ(1, tabstrip.count()); + EXPECT_EQ(opener_contents, tabstrip.GetSelectedTabContents()); + + TabContents* dummy_contents = CreateTabContents(); + tabstrip.AppendTabContents(dummy_contents, false); + + contents1 = CreateTabContents(); + contents2 = CreateTabContents(); + contents3 = CreateTabContents(); + InsertTabContentses(&tabstrip, contents1, contents2, contents3); + EXPECT_EQ(5, tabstrip.count()); + + tabstrip.ExecuteContextMenuCommand(0, TabStripModel::CommandCloseTabsOpenedBy); + EXPECT_EQ(2, tabstrip.count()); + EXPECT_EQ(dummy_contents, tabstrip.GetTabContentsAt(1)); + + contents1 = CreateTabContents(); + contents2 = CreateTabContents(); + contents3 = CreateTabContents(); + InsertTabContentses(&tabstrip, contents1, contents2, contents3); + EXPECT_EQ(5, tabstrip.count()); + + int dummy_index = tabstrip.count() - 1; + tabstrip.SelectTabContentsAt(dummy_index, true); + EXPECT_EQ(dummy_contents, tabstrip.GetSelectedTabContents()); + + tabstrip.ExecuteContextMenuCommand(dummy_index, + TabStripModel::CommandCloseOtherTabs); + EXPECT_EQ(1, tabstrip.count()); + EXPECT_EQ(dummy_contents, tabstrip.GetSelectedTabContents()); + + tabstrip.CloseAllTabs(); + EXPECT_TRUE(tabstrip.empty()); +} + +// Tests whether or not TabContentses are inserted in the correct position +// using this "smart" function with a simulated middle click action on a series +// of links on the home page. +TEST_F(TabStripModelTest, AddTabContents_MiddleClickLinksAndClose) { + TabStripModel tabstrip(NULL, profile_); + EXPECT_TRUE(tabstrip.empty()); + + // Open the Home Page + TabContents* homepage_contents = CreateTabContents(); + tabstrip.AddTabContents( + homepage_contents, -1, PageTransition::AUTO_BOOKMARK, true); + + // Open some other tab, by user typing. + TabContents* typed_page_contents = CreateTabContents(); + tabstrip.AddTabContents( + typed_page_contents, -1, PageTransition::TYPED, true); + + EXPECT_EQ(2, tabstrip.count()); + + // Re-select the home page. + tabstrip.SelectTabContentsAt(0, true); + + // Open a bunch of tabs by simulating middle clicking on links on the home + // page. + TabContents* middle_click_contents1 = CreateTabContents(); + tabstrip.AddTabContents( + middle_click_contents1, -1, PageTransition::LINK, false); + TabContents* middle_click_contents2 = CreateTabContents(); + tabstrip.AddTabContents( + middle_click_contents2, -1, PageTransition::LINK, false); + TabContents* middle_click_contents3 = CreateTabContents(); + tabstrip.AddTabContents( + middle_click_contents3, -1, PageTransition::LINK, false); + + EXPECT_EQ(5, tabstrip.count()); + + EXPECT_EQ(homepage_contents, tabstrip.GetTabContentsAt(0)); + EXPECT_EQ(middle_click_contents1, tabstrip.GetTabContentsAt(1)); + EXPECT_EQ(middle_click_contents2, tabstrip.GetTabContentsAt(2)); + EXPECT_EQ(middle_click_contents3, tabstrip.GetTabContentsAt(3)); + EXPECT_EQ(typed_page_contents, tabstrip.GetTabContentsAt(4)); + + // Now simulate seleting a tab in the middle of the group of tabs opened from + // the home page and start closing them. Each TabContents in the group should + // be closed, right to left. This test is constructed to start at the middle + // TabContents in the group to make sure the cursor wraps around to the first + // TabContents in the group before closing the opener or any other + // TabContents. + tabstrip.SelectTabContentsAt(2, true); + tabstrip.CloseSelectedTab(); + EXPECT_EQ(middle_click_contents3, tabstrip.GetSelectedTabContents()); + tabstrip.CloseSelectedTab(); + EXPECT_EQ(middle_click_contents1, tabstrip.GetSelectedTabContents()); + tabstrip.CloseSelectedTab(); + EXPECT_EQ(homepage_contents, tabstrip.GetSelectedTabContents()); + tabstrip.CloseSelectedTab(); + EXPECT_EQ(typed_page_contents, tabstrip.GetSelectedTabContents()); + + EXPECT_EQ(1, tabstrip.count()); + + tabstrip.CloseAllTabs(); + EXPECT_TRUE(tabstrip.empty()); +} + +// Tests whether or not a TabContents created by a left click on a link that +// opens a new tab is inserted correctly adjacent to the tab that spawned it. +TEST_F(TabStripModelTest, AddTabContents_LeftClickPopup) { + TabStripModel tabstrip(NULL, profile_); + EXPECT_TRUE(tabstrip.empty()); + + // Open the Home Page + TabContents* homepage_contents = CreateTabContents(); + tabstrip.AddTabContents( + homepage_contents, -1, PageTransition::AUTO_BOOKMARK, true); + + // Open some other tab, by user typing. + TabContents* typed_page_contents = CreateTabContents(); + tabstrip.AddTabContents( + typed_page_contents, -1, PageTransition::TYPED, true); + + EXPECT_EQ(2, tabstrip.count()); + + // Re-select the home page. + tabstrip.SelectTabContentsAt(0, true); + + // Open a tab by simulating a left click on a link that opens in a new tab. + TabContents* left_click_contents = CreateTabContents(); + tabstrip.AddTabContents(left_click_contents, -1, PageTransition::LINK, true); + + // Verify the state meets our expectations. + EXPECT_EQ(3, tabstrip.count()); + EXPECT_EQ(homepage_contents, tabstrip.GetTabContentsAt(0)); + EXPECT_EQ(left_click_contents, tabstrip.GetTabContentsAt(1)); + EXPECT_EQ(typed_page_contents, tabstrip.GetTabContentsAt(2)); + + // The newly created tab should be selected. + EXPECT_EQ(left_click_contents, tabstrip.GetSelectedTabContents()); + + // After closing the selected tab, the selection should move to the left, to + // the opener. + tabstrip.CloseSelectedTab(); + EXPECT_EQ(homepage_contents, tabstrip.GetSelectedTabContents()); + + EXPECT_EQ(2, tabstrip.count()); + + tabstrip.CloseAllTabs(); + EXPECT_TRUE(tabstrip.empty()); +} + +// Tests whether or not new tabs that should split context (typed pages, +// generated urls, also blank tabs) open at the end of the tabstrip instead of +// in the middle. +TEST_F(TabStripModelTest, AddTabContents_CreateNewBlankTab) { + TabStripModel tabstrip(NULL, profile_); + EXPECT_TRUE(tabstrip.empty()); + + // Open the Home Page + TabContents* homepage_contents = CreateTabContents(); + tabstrip.AddTabContents( + homepage_contents, -1, PageTransition::AUTO_BOOKMARK, true); + + // Open some other tab, by user typing. + TabContents* typed_page_contents = CreateTabContents(); + tabstrip.AddTabContents( + typed_page_contents, -1, PageTransition::TYPED, true); + + EXPECT_EQ(2, tabstrip.count()); + + // Re-select the home page. + tabstrip.SelectTabContentsAt(0, true); + + // Open a new blank tab in the foreground. + TabContents* new_blank_contents = CreateTabContents(); + tabstrip.AddTabContents(new_blank_contents, -1, PageTransition::TYPED, true); + + // Verify the state of the tabstrip. + EXPECT_EQ(3, tabstrip.count()); + EXPECT_EQ(homepage_contents, tabstrip.GetTabContentsAt(0)); + EXPECT_EQ(typed_page_contents, tabstrip.GetTabContentsAt(1)); + EXPECT_EQ(new_blank_contents, tabstrip.GetTabContentsAt(2)); + + // Now open a couple more blank tabs in the background. + TabContents* background_blank_contents1 = CreateTabContents(); + tabstrip.AddTabContents( + background_blank_contents1, -1, PageTransition::TYPED, false); + TabContents* background_blank_contents2 = CreateTabContents(); + tabstrip.AddTabContents( + background_blank_contents2, -1, PageTransition::GENERATED, false); + EXPECT_EQ(5, tabstrip.count()); + EXPECT_EQ(homepage_contents, tabstrip.GetTabContentsAt(0)); + EXPECT_EQ(typed_page_contents, tabstrip.GetTabContentsAt(1)); + EXPECT_EQ(new_blank_contents, tabstrip.GetTabContentsAt(2)); + EXPECT_EQ(background_blank_contents1, tabstrip.GetTabContentsAt(3)); + EXPECT_EQ(background_blank_contents2, tabstrip.GetTabContentsAt(4)); + + tabstrip.CloseAllTabs(); + EXPECT_TRUE(tabstrip.empty()); +} + +// Tests whether opener state is correctly forgotten when the user switches +// context. +TEST_F(TabStripModelTest, AddTabContents_ForgetOpeners) { + TabStripModel tabstrip(NULL, profile_); + EXPECT_TRUE(tabstrip.empty()); + + // Open the Home Page + TabContents* homepage_contents = CreateTabContents(); + tabstrip.AddTabContents( + homepage_contents, -1, PageTransition::AUTO_BOOKMARK, true); + + // Open some other tab, by user typing. + TabContents* typed_page_contents = CreateTabContents(); + tabstrip.AddTabContents( + typed_page_contents, -1, PageTransition::TYPED, true); + + EXPECT_EQ(2, tabstrip.count()); + + // Re-select the home page. + tabstrip.SelectTabContentsAt(0, true); + + // Open a bunch of tabs by simulating middle clicking on links on the home + // page. + TabContents* middle_click_contents1 = CreateTabContents(); + tabstrip.AddTabContents( + middle_click_contents1, -1, PageTransition::LINK, false); + TabContents* middle_click_contents2 = CreateTabContents(); + tabstrip.AddTabContents( + middle_click_contents2, -1, PageTransition::LINK, false); + TabContents* middle_click_contents3 = CreateTabContents(); + tabstrip.AddTabContents( + middle_click_contents3, -1, PageTransition::LINK, false); + + // Break out of the context by selecting a tab in a different context. + EXPECT_EQ(typed_page_contents, tabstrip.GetTabContentsAt(4)); + tabstrip.SelectLastTab(); + EXPECT_EQ(typed_page_contents, tabstrip.GetSelectedTabContents()); + + // Step back into the context by selecting a tab inside it. + tabstrip.SelectTabContentsAt(2, true); + EXPECT_EQ(middle_click_contents2, tabstrip.GetSelectedTabContents()); + + // Now test that closing tabs selects to the right until there are no more, + // then to the left, as if there were no context (context has been + // successfully forgotten). + tabstrip.CloseSelectedTab(); + EXPECT_EQ(middle_click_contents3, tabstrip.GetSelectedTabContents()); + tabstrip.CloseSelectedTab(); + EXPECT_EQ(typed_page_contents, tabstrip.GetSelectedTabContents()); + tabstrip.CloseSelectedTab(); + EXPECT_EQ(middle_click_contents1, tabstrip.GetSelectedTabContents()); + tabstrip.CloseSelectedTab(); + EXPECT_EQ(homepage_contents, tabstrip.GetSelectedTabContents()); + + EXPECT_EQ(1, tabstrip.count()); + + tabstrip.CloseAllTabs(); + EXPECT_TRUE(tabstrip.empty()); +} + +class TabStripDummyDelegate : public TabStripModelDelegate { + public: + explicit TabStripDummyDelegate(TabContents* dummy) + : dummy_contents_(dummy) {} + virtual ~TabStripDummyDelegate() {} + + // Overridden from TabStripModelDelegate: + virtual void CreateNewStripWithContents(TabContents* contents, + const gfx::Point& creation_point) {} + virtual int GetDragActions() const { return 0; } + virtual TabContents* CreateTabContentsForURL( + const GURL& url, + Profile* profile, + PageTransition::Type transition, + bool defer_load, + SiteInstance* instance) const { + if (url == NewTabUIURL()) + return dummy_contents_; + return NULL; + } + virtual void ShowApplicationMenu(const gfx::Point p) {} + virtual bool CanDuplicateContentsAt(int index) { return false; } + virtual void DuplicateContentsAt(int index) {} + virtual void ValidateLoadingAnimations() {} + virtual void CloseFrameAfterDragSession() {} + + private: + // A dummy TabContents we give to callers that expect us to actually build a + // Destinations tab for them. + TabContents* dummy_contents_; + + DISALLOW_EVIL_CONSTRUCTORS(TabStripDummyDelegate); +}; + +// Added for http://b/issue?id=958960 +TEST_F(TabStripModelTest, AppendContentsReselectionTest) { + TabContents* fake_destinations_tab = CreateTabContents(); + TabStripDummyDelegate delegate(fake_destinations_tab); + TabStripModel tabstrip(&delegate, profile_); + EXPECT_TRUE(tabstrip.empty()); + + // Open the Home Page + TabContents* homepage_contents = CreateTabContents(); + tabstrip.AddTabContents( + homepage_contents, -1, PageTransition::AUTO_BOOKMARK, true); + + // Open some other tab, by user typing. + TabContents* typed_page_contents = CreateTabContents(); + tabstrip.AddTabContents( + typed_page_contents, -1, PageTransition::TYPED, false); + + // The selected tab should still be the first. + EXPECT_EQ(0, tabstrip.selected_index()); + + // Now simulate a link click that opens a new tab (by virtue of target=_blank) + // and make sure the right tab gets selected when the new tab is closed. + TabContents* target_blank_contents = CreateTabContents(); + tabstrip.AppendTabContents(target_blank_contents, true); + EXPECT_EQ(2, tabstrip.selected_index()); + tabstrip.CloseTabContentsAt(2); + EXPECT_EQ(0, tabstrip.selected_index()); + + // Now open a blank tab... + tabstrip.AddBlankTab(true); + EXPECT_EQ(2, tabstrip.selected_index()); + tabstrip.CloseTabContentsAt(2); + EXPECT_EQ(1, tabstrip.selected_index()); + + // clean up after ourselves + tabstrip.CloseAllTabs(); +} + +// Added for http://b/issue?id=1027661 +TEST_F(TabStripModelTest, ReselectionConsidersChildrenTest) { + TabStripDummyDelegate delegate(NULL); + TabStripModel strip(&delegate, profile_); + + // Open page A + TabContents* page_a_contents = CreateTabContents(); + strip.AddTabContents( + page_a_contents, -1, PageTransition::AUTO_BOOKMARK, true); + + // Simulate middle click to open page A.A and A.B + TabContents* page_a_a_contents = CreateTabContents(); + strip.AddTabContents(page_a_a_contents, -1, PageTransition::LINK, false); + TabContents* page_a_b_contents = CreateTabContents(); + strip.AddTabContents(page_a_b_contents, -1, PageTransition::LINK, false); + + // Select page A.A + strip.SelectTabContentsAt(1, true); + EXPECT_EQ(page_a_a_contents, strip.GetSelectedTabContents()); + + // Simulate a middle click to open page A.A.A + TabContents* page_a_a_a_contents = CreateTabContents(); + strip.AddTabContents(page_a_a_a_contents, -1, PageTransition::LINK, false); + + EXPECT_EQ(page_a_a_a_contents, strip.GetTabContentsAt(2)); + + // Close page A.A + strip.CloseTabContentsAt(strip.selected_index()); + + // Page A.A.A should be selected, NOT A.B + EXPECT_EQ(page_a_a_a_contents, strip.GetSelectedTabContents()); + + // Close page A.A.A + strip.CloseTabContentsAt(strip.selected_index()); + + // Page A.B should be selected + EXPECT_EQ(page_a_b_contents, strip.GetSelectedTabContents()); + + // Close page A.B + strip.CloseTabContentsAt(strip.selected_index()); + + // Page A should be selected + EXPECT_EQ(page_a_contents, strip.GetSelectedTabContents()); + + // Clean up. + strip.CloseAllTabs(); +} |