Index: ios/chrome/browser/ui/stack_view/card_set_unittest.mm |
diff --git a/ios/chrome/browser/ui/stack_view/card_set_unittest.mm b/ios/chrome/browser/ui/stack_view/card_set_unittest.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..b347a318d7803ee03f09a00c70174cee665f479a |
--- /dev/null |
+++ b/ios/chrome/browser/ui/stack_view/card_set_unittest.mm |
@@ -0,0 +1,372 @@ |
+// Copyright 2012 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 <QuartzCore/QuartzCore.h> |
+ |
+#include "base/mac/scoped_nsautorelease_pool.h" |
+#include "base/mac/scoped_nsobject.h" |
+#include "base/strings/stringprintf.h" |
+#import "ios/chrome/browser/tabs/tab.h" |
+#import "ios/chrome/browser/tabs/tab_model.h" |
+#import "ios/chrome/browser/tabs/tab_model_observer.h" |
+#include "ios/chrome/browser/ui/rtl_geometry.h" |
+#import "ios/chrome/browser/ui/stack_view/card_set.h" |
+#import "ios/chrome/browser/ui/stack_view/card_stack_layout_manager.h" |
+#import "ios/chrome/browser/ui/stack_view/stack_card.h" |
+#import "ios/testing/ocmock_complex_type_helper.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+#include "testing/gtest_mac.h" |
+#include "testing/platform_test.h" |
+#import "third_party/ocmock/OCMock/OCMock.h" |
+#import "third_party/ocmock/gtest_support.h" |
+ |
+@interface MockTabModel : NSObject<NSFastEnumeration> { |
+ @private |
+ base::scoped_nsobject<NSMutableArray> tabs_; |
+ id<TabModelObserver> observer_; // weak |
+} |
+ |
+// Adds a new mock tab with the given properties. |
+- (void)addTabWithTitle:(NSString*)title location:(const GURL&)url; |
+ |
+// TabModel mocking. |
+- (NSUInteger)count; |
+- (BOOL)isOffTheRecord; |
+- (Tab*)currentTab; |
+- (NSUInteger)indexOfTab:(Tab*)tab; |
+- (Tab*)tabAtIndex:(NSUInteger)tabIndex; |
+- (void)addObserver:(id<TabModelObserver>)observer; |
+- (void)removeObserver:(id<TabModelObserver>)observer; |
+- (id<TabModelObserver>)observer; |
+@end |
+ |
+@interface CardSetTestTabMock : OCMockComplexTypeHelper |
+@end |
+@implementation CardSetTestTabMock |
+ |
+typedef const GURL& (^CardSetTestTabMock_url)(void); |
+ |
+- (const GURL&)url { |
+ return static_cast<CardSetTestTabMock_url>([self blockForSelector:_cmd])(); |
+} |
+@end |
+ |
+@implementation MockTabModel |
+ |
+- (id)init { |
+ if ((self = [super init])) { |
+ tabs_.reset([[NSMutableArray alloc] init]); |
+ } |
+ return self; |
+} |
+ |
+- (void)addTabWithTitle:(NSString*)title location:(const GURL&)url { |
+ base::scoped_nsobject<id> tab([[CardSetTestTabMock alloc] |
+ initWithRepresentedObject:[OCMockObject mockForClass:[Tab class]]]); |
+ UIView* dummyView = [[[UIView alloc] initWithFrame:CGRectZero] autorelease]; |
+ static int sCounter = 0; |
+ NSString* sessionID = [NSString stringWithFormat:@"%d", sCounter++]; |
+ BOOL no = NO; |
+ [[[tab stub] andReturn:dummyView] view]; |
+ base::scoped_nsobject<id> block([^{ |
+ return (const GURL&)url; |
+ } copy]); |
+ [tab onSelector:@selector(url) callBlockExpectation:block]; |
+ |
+ [[tab expect] retrieveSnapshot:[OCMArg any]]; |
+ [[[tab stub] andReturn:nil] webController]; |
+ [[[tab stub] andReturn:nil] favicon]; |
+ [[[tab stub] andReturnValue:OCMOCK_VALUE(no)] canGoBack]; |
+ [[[tab stub] andReturnValue:OCMOCK_VALUE(no)] canGoForward]; |
+ [[[tab stub] andReturn:title] title]; |
+ [[[tab stub] andReturn:sessionID] currentSessionID]; |
+ |
+ [tabs_ addObject:tab]; |
+ [observer_ tabModel:(TabModel*)self |
+ didInsertTab:tab |
+ atIndex:([tabs_ count] - 1) |
+ inForeground:YES]; |
+} |
+ |
+- (void)removeTabAtIndex:(NSUInteger)index { |
+ id tab = [tabs_ objectAtIndex:index]; |
+ [[tab retain] autorelease]; |
+ [tabs_ removeObjectAtIndex:index]; |
+ |
+ // A tab was removed at the given index. |
+ [observer_ tabModel:(TabModel*)self didRemoveTab:tab atIndex:index]; |
+} |
+ |
+- (NSUInteger)count { |
+ return [tabs_ count]; |
+} |
+ |
+- (BOOL)isOffTheRecord { |
+ return NO; |
+} |
+ |
+- (Tab*)currentTab { |
+ if ([tabs_ count]) |
+ return [tabs_ objectAtIndex:0]; |
+ return nil; |
+} |
+ |
+- (NSUInteger)indexOfTab:(Tab*)tab { |
+ return [tabs_ indexOfObject:tab]; |
+} |
+ |
+- (Tab*)tabAtIndex:(NSUInteger)tabIndex { |
+ return [tabs_ objectAtIndex:tabIndex]; |
+} |
+ |
+- (void)addObserver:(id<TabModelObserver>)observer { |
+ observer_ = observer; |
+} |
+ |
+- (void)removeObserver:(id<TabModelObserver>)observer { |
+ observer_ = nil; |
+} |
+ |
+- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState*)state |
+ objects:(id*)stackbuf |
+ count:(NSUInteger)len { |
+ return [tabs_ countByEnumeratingWithState:state objects:stackbuf count:len]; |
+} |
+ |
+- (id<TabModelObserver>)observer { |
+ return observer_; |
+} |
+ |
+@end |
+ |
+#pragma mark - |
+ |
+namespace { |
+ |
+const CGFloat kViewportDimension = 100; |
+const CGFloat kCardDimension = 90; |
+ |
+CardStackLayoutManager* CreateMockCardStackLayoutManager() { |
+ OCMockObject* stackModelMock = |
+ [OCMockObject mockForClass:[CardStackLayoutManager class]]; |
+ BOOL no = NO; |
+ [[[stackModelMock stub] andReturnValue:OCMOCK_VALUE(no)] layoutIsVertical]; |
+ NSUInteger lastStartStackCardIndex = 0; |
+ [[[stackModelMock stub] andReturnValue:OCMOCK_VALUE(lastStartStackCardIndex)] |
+ lastStartStackCardIndex]; |
+ NSArray* cards = @[]; |
+ [[[stackModelMock stub] andReturn:cards] cards]; |
+ return (CardStackLayoutManager*)stackModelMock; |
+} |
+ |
+class CardSetTest : public PlatformTest { |
+ protected: |
+ virtual void SetUpWithTabs(int nb_tabs) { |
+ tab_model_.reset([[MockTabModel alloc] init]); |
+ |
+ for (int i = 0; i < nb_tabs; ++i) { |
+ std::string url = base::StringPrintf("http://%d.example.com", i); |
+ [tab_model_ addTabWithTitle:@"NewTab" location:GURL(url)]; |
+ } |
+ |
+ card_set_.reset( |
+ [[CardSet alloc] initWithModel:(TabModel*)tab_model_.get()]); |
+ |
+ display_view_.reset( |
+ [[UIView alloc] initWithFrame:CGRectMake(0, 0, kViewportDimension, |
+ kViewportDimension)]); |
+ // Do some initial configuration of the card set. |
+ [card_set_ setDisplayView:display_view_]; |
+ [card_set_ setCardSize:CGSizeMake(kCardDimension, kCardDimension)]; |
+ [card_set_ setLayoutAxisPosition:(kViewportDimension / 2.0) isVertical:YES]; |
+ [card_set_ configureLayoutParametersWithMargin:10]; |
+ } |
+ |
+ void SetUp() override { SetUpWithTabs(2); } |
+ |
+ base::scoped_nsobject<MockTabModel> tab_model_; |
+ base::scoped_nsobject<UIView> display_view_; |
+ base::scoped_nsobject<CardSet> card_set_; |
+}; |
+ |
+TEST_F(CardSetTest, InitialLayoutState) { |
+ NSArray* cards = [card_set_ cards]; |
+ EXPECT_EQ([tab_model_ count], [cards count]); |
+ |
+ // Tabs should be on the trailing side. |
+ EXPECT_EQ(CardCloseButtonSide::TRAILING, [card_set_ closeButtonSide]); |
+ |
+ // At least one card should be visible after layout, and cards should have |
+ // the right size and center. |
+ [card_set_ fanOutCards]; |
+ BOOL has_visible_card = NO; |
+ for (StackCard* card in cards) { |
+ if ([card viewIsLive]) |
+ has_visible_card = YES; |
+ EXPECT_FLOAT_EQ(kCardDimension, card.layout.size.width); |
+ EXPECT_FLOAT_EQ(kCardDimension, card.layout.size.height); |
+ EXPECT_FLOAT_EQ(kViewportDimension / 2.0, |
+ CGRectGetMidX(LayoutRectGetRect(card.layout))); |
+ } |
+ EXPECT_TRUE(has_visible_card); |
+} |
+ |
+TEST_F(CardSetTest, HandleRotation) { |
+ // Rotate the card set. |
+ [card_set_ setLayoutAxisPosition:(kViewportDimension / 3.0) isVertical:NO]; |
+ |
+ // Tabs should now be on the leading side. |
+ EXPECT_EQ(CardCloseButtonSide::LEADING, [card_set_ closeButtonSide]); |
+ // And the centers should be on the new axis. |
+ for (StackCard* card in [card_set_ cards]) { |
+ EXPECT_FLOAT_EQ(kViewportDimension / 3.0, |
+ CGRectGetMidY(LayoutRectGetRect(card.layout))); |
+ } |
+} |
+ |
+TEST_F(CardSetTest, CurrentCard) { |
+ NSUInteger current_tab_index = |
+ [tab_model_ indexOfTab:[tab_model_ currentTab]]; |
+ NSArray* cards = [card_set_ cards]; |
+ NSUInteger current_card_index = [cards indexOfObject:[card_set_ currentCard]]; |
+ EXPECT_EQ(current_tab_index, current_card_index); |
+} |
+ |
+TEST_F(CardSetTest, ViewClearing) { |
+ // Add the views. |
+ [card_set_ fanOutCards]; |
+ ASSERT_GT([[display_view_ subviews] count], 0U); |
+ [card_set_ setDisplayView:nil]; |
+ EXPECT_EQ(0U, [[display_view_ subviews] count]); |
+} |
+ |
+TEST_F(CardSetTest, NoticeTabAddition) { |
+ NSArray* cards = [card_set_ cards]; |
+ NSUInteger initial_card_count = [cards count]; |
+ |
+ OCMockObject* observer = |
+ [OCMockObject mockForProtocol:@protocol(CardSetObserver)]; |
+ [[observer expect] cardSet:OCMOCK_ANY didAddCard:OCMOCK_ANY]; |
+ [card_set_ setObserver:(id<CardSetObserver>)observer]; |
+ |
+ [tab_model_ addTabWithTitle:@"NewTab" |
+ location:GURL("http://www.example.com")]; |
+ cards = [card_set_ cards]; |
+ EXPECT_EQ(initial_card_count + 1, [cards count]); |
+ |
+ [card_set_ setObserver:nil]; |
+ EXPECT_OCMOCK_VERIFY(observer); |
+} |
+ |
+TEST_F(CardSetTest, NoticeTabRemoval) { |
+ NSArray* cards = [card_set_ cards]; |
+ NSUInteger initial_card_count = [cards count]; |
+ StackCard* first_card = [cards objectAtIndex:0]; |
+ |
+ OCMockObject* observer = |
+ [OCMockObject mockForProtocol:@protocol(CardSetObserver)]; |
+ [[observer expect] cardSet:OCMOCK_ANY willRemoveCard:OCMOCK_ANY atIndex:0]; |
+ [[observer expect] cardSet:OCMOCK_ANY didRemoveCard:OCMOCK_ANY atIndex:0]; |
+ |
+ [card_set_ setObserver:(id<CardSetObserver>)observer]; |
+ |
+ [tab_model_ removeTabAtIndex:0]; |
+ cards = [card_set_ cards]; |
+ EXPECT_EQ(initial_card_count - 1, [cards count]); |
+ EXPECT_NE(first_card, [cards objectAtIndex:0]); |
+ |
+ [card_set_ setObserver:nil]; |
+ EXPECT_OCMOCK_VERIFY(observer); |
+} |
+ |
+TEST_F(CardSetTest, Preloading) { |
+ // Preloading when everything is visible should return NO. |
+ [card_set_ fanOutCards]; |
+ EXPECT_FALSE([card_set_ preloadNextCard]); |
+ |
+ // Add a bunch of cards to ensure stacking. |
+ for (int i = 0; i < 20; ++i) { |
+ [tab_model_ addTabWithTitle:@"NewTab" |
+ location:GURL("http://www.example.com")]; |
+ } |
+ [card_set_ fanOutCards]; |
+ |
+ NSUInteger loaded_cards = [[display_view_ subviews] count]; |
+ // Now preloading should return YES, and should have added one more card to |
+ // the view. |
+ EXPECT_TRUE([card_set_ preloadNextCard]); |
+ EXPECT_EQ(loaded_cards + 1, [[display_view_ subviews] count]); |
+} |
+ |
+TEST_F(CardSetTest, stackIsFullyCollapsed) { |
+ [card_set_ setStackModelForTesting:CreateMockCardStackLayoutManager()]; |
+ OCMockObject* stack_model_mock = (OCMockObject*)[card_set_ stackModel]; |
+ [[stack_model_mock expect] stackIsFullyCollapsed]; |
+ [card_set_ stackIsFullyCollapsed]; |
+ EXPECT_OCMOCK_VERIFY(stack_model_mock); |
+} |
+ |
+TEST_F(CardSetTest, stackIsFullyFannedOut) { |
+ [card_set_ setStackModelForTesting:CreateMockCardStackLayoutManager()]; |
+ OCMockObject* stack_model_mock = (OCMockObject*)[card_set_ stackModel]; |
+ [[stack_model_mock expect] stackIsFullyFannedOut]; |
+ [card_set_ stackIsFullyFannedOut]; |
+ EXPECT_OCMOCK_VERIFY(stack_model_mock); |
+} |
+ |
+TEST_F(CardSetTest, stackIsFullyOverextended) { |
+ [card_set_ setStackModelForTesting:CreateMockCardStackLayoutManager()]; |
+ OCMockObject* stack_model_mock = (OCMockObject*)[card_set_ stackModel]; |
+ [[stack_model_mock expect] stackIsFullyOverextended]; |
+ [card_set_ stackIsFullyOverextended]; |
+ EXPECT_OCMOCK_VERIFY(stack_model_mock); |
+} |
+ |
+TEST_F(CardSetTest, isCardInStartStaggerRegion) { |
+ [card_set_ fanOutCards]; |
+ NSArray* cards = [card_set_ cards]; |
+ // First card should not be collapsed into start stagger region when stack is |
+ // fanned out from the first card. |
+ StackCard* first_card = [cards objectAtIndex:0]; |
+ EXPECT_FALSE([card_set_ isCardInStartStaggerRegion:first_card]); |
+ // Add a bunch of cards to ensure stacking, and fan them out so that there |
+ // is a start stack. |
+ for (int i = 0; i < 20; ++i) { |
+ [tab_model_ addTabWithTitle:@"NewTab" |
+ location:GURL("http://www.example.com")]; |
+ } |
+ [card_set_ fanOutCardsWithStartIndex:10]; |
+ // First card should now be collapsed into start stagger region. |
+ EXPECT_TRUE([card_set_ isCardInStartStaggerRegion:first_card]); |
+} |
+ |
+TEST_F(CardSetTest, isCardInEndStaggerRegion) { |
+ [card_set_ fanOutCards]; |
+ NSArray* cards = [card_set_ cards]; |
+ // Add a bunch of cards to ensure stacking, and fan them out so that there |
+ // is an end stack. |
+ for (int i = 0; i < 20; ++i) { |
+ [tab_model_ addTabWithTitle:@"NewTab" |
+ location:GURL("http://www.example.com")]; |
+ } |
+ StackCard* last_card = [cards objectAtIndex:[cards count] - 1]; |
+ [card_set_ fanOutCards]; |
+ // Last card should be collapsed into end region. |
+ EXPECT_TRUE([card_set_ isCardInEndStaggerRegion:last_card]); |
+ |
+ // Fan out cards from the last card. |
+ [card_set_ fanOutCardsWithStartIndex:[cards count] - 1]; |
+ // Last card should not be collapsed into end region. |
+ EXPECT_FALSE([card_set_ isCardInEndStaggerRegion:last_card]); |
+} |
+ |
+TEST_F(CardSetTest, setTabModel) { |
+ SetUpWithTabs(0); |
+ [card_set_ setTabModel:nil]; |
+ EXPECT_TRUE([tab_model_ observer] == nil); |
+ [card_set_ setTabModel:static_cast<id>(tab_model_)]; |
+ EXPECT_NSEQ(card_set_, static_cast<id>([tab_model_ observer])); |
+} |
+ |
+} // namespace |