OLD | NEW |
(Empty) | |
| 1 // Copyright 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import <QuartzCore/QuartzCore.h> |
| 6 |
| 7 #include "base/mac/scoped_nsautorelease_pool.h" |
| 8 #include "base/mac/scoped_nsobject.h" |
| 9 #include "base/strings/stringprintf.h" |
| 10 #import "ios/chrome/browser/tabs/tab.h" |
| 11 #import "ios/chrome/browser/tabs/tab_model.h" |
| 12 #import "ios/chrome/browser/tabs/tab_model_observer.h" |
| 13 #include "ios/chrome/browser/ui/rtl_geometry.h" |
| 14 #import "ios/chrome/browser/ui/stack_view/card_set.h" |
| 15 #import "ios/chrome/browser/ui/stack_view/card_stack_layout_manager.h" |
| 16 #import "ios/chrome/browser/ui/stack_view/stack_card.h" |
| 17 #import "ios/testing/ocmock_complex_type_helper.h" |
| 18 #include "testing/gtest/include/gtest/gtest.h" |
| 19 #include "testing/gtest_mac.h" |
| 20 #include "testing/platform_test.h" |
| 21 #import "third_party/ocmock/OCMock/OCMock.h" |
| 22 #import "third_party/ocmock/gtest_support.h" |
| 23 |
| 24 @interface MockTabModel : NSObject<NSFastEnumeration> { |
| 25 @private |
| 26 base::scoped_nsobject<NSMutableArray> tabs_; |
| 27 id<TabModelObserver> observer_; // weak |
| 28 } |
| 29 |
| 30 // Adds a new mock tab with the given properties. |
| 31 - (void)addTabWithTitle:(NSString*)title location:(const GURL&)url; |
| 32 |
| 33 // TabModel mocking. |
| 34 - (NSUInteger)count; |
| 35 - (BOOL)isOffTheRecord; |
| 36 - (Tab*)currentTab; |
| 37 - (NSUInteger)indexOfTab:(Tab*)tab; |
| 38 - (Tab*)tabAtIndex:(NSUInteger)tabIndex; |
| 39 - (void)addObserver:(id<TabModelObserver>)observer; |
| 40 - (void)removeObserver:(id<TabModelObserver>)observer; |
| 41 - (id<TabModelObserver>)observer; |
| 42 @end |
| 43 |
| 44 @interface CardSetTestTabMock : OCMockComplexTypeHelper |
| 45 @end |
| 46 @implementation CardSetTestTabMock |
| 47 |
| 48 typedef const GURL& (^CardSetTestTabMock_url)(void); |
| 49 |
| 50 - (const GURL&)url { |
| 51 return static_cast<CardSetTestTabMock_url>([self blockForSelector:_cmd])(); |
| 52 } |
| 53 @end |
| 54 |
| 55 @implementation MockTabModel |
| 56 |
| 57 - (id)init { |
| 58 if ((self = [super init])) { |
| 59 tabs_.reset([[NSMutableArray alloc] init]); |
| 60 } |
| 61 return self; |
| 62 } |
| 63 |
| 64 - (void)addTabWithTitle:(NSString*)title location:(const GURL&)url { |
| 65 base::scoped_nsobject<id> tab([[CardSetTestTabMock alloc] |
| 66 initWithRepresentedObject:[OCMockObject mockForClass:[Tab class]]]); |
| 67 UIView* dummyView = [[[UIView alloc] initWithFrame:CGRectZero] autorelease]; |
| 68 static int sCounter = 0; |
| 69 NSString* sessionID = [NSString stringWithFormat:@"%d", sCounter++]; |
| 70 BOOL no = NO; |
| 71 [[[tab stub] andReturn:dummyView] view]; |
| 72 base::scoped_nsobject<id> block([^{ |
| 73 return (const GURL&)url; |
| 74 } copy]); |
| 75 [tab onSelector:@selector(url) callBlockExpectation:block]; |
| 76 |
| 77 [[tab expect] retrieveSnapshot:[OCMArg any]]; |
| 78 [[[tab stub] andReturn:nil] webController]; |
| 79 [[[tab stub] andReturn:nil] favicon]; |
| 80 [[[tab stub] andReturnValue:OCMOCK_VALUE(no)] canGoBack]; |
| 81 [[[tab stub] andReturnValue:OCMOCK_VALUE(no)] canGoForward]; |
| 82 [[[tab stub] andReturn:title] title]; |
| 83 [[[tab stub] andReturn:sessionID] currentSessionID]; |
| 84 |
| 85 [tabs_ addObject:tab]; |
| 86 [observer_ tabModel:(TabModel*)self |
| 87 didInsertTab:tab |
| 88 atIndex:([tabs_ count] - 1) |
| 89 inForeground:YES]; |
| 90 } |
| 91 |
| 92 - (void)removeTabAtIndex:(NSUInteger)index { |
| 93 id tab = [tabs_ objectAtIndex:index]; |
| 94 [[tab retain] autorelease]; |
| 95 [tabs_ removeObjectAtIndex:index]; |
| 96 |
| 97 // A tab was removed at the given index. |
| 98 [observer_ tabModel:(TabModel*)self didRemoveTab:tab atIndex:index]; |
| 99 } |
| 100 |
| 101 - (NSUInteger)count { |
| 102 return [tabs_ count]; |
| 103 } |
| 104 |
| 105 - (BOOL)isOffTheRecord { |
| 106 return NO; |
| 107 } |
| 108 |
| 109 - (Tab*)currentTab { |
| 110 if ([tabs_ count]) |
| 111 return [tabs_ objectAtIndex:0]; |
| 112 return nil; |
| 113 } |
| 114 |
| 115 - (NSUInteger)indexOfTab:(Tab*)tab { |
| 116 return [tabs_ indexOfObject:tab]; |
| 117 } |
| 118 |
| 119 - (Tab*)tabAtIndex:(NSUInteger)tabIndex { |
| 120 return [tabs_ objectAtIndex:tabIndex]; |
| 121 } |
| 122 |
| 123 - (void)addObserver:(id<TabModelObserver>)observer { |
| 124 observer_ = observer; |
| 125 } |
| 126 |
| 127 - (void)removeObserver:(id<TabModelObserver>)observer { |
| 128 observer_ = nil; |
| 129 } |
| 130 |
| 131 - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState*)state |
| 132 objects:(id*)stackbuf |
| 133 count:(NSUInteger)len { |
| 134 return [tabs_ countByEnumeratingWithState:state objects:stackbuf count:len]; |
| 135 } |
| 136 |
| 137 - (id<TabModelObserver>)observer { |
| 138 return observer_; |
| 139 } |
| 140 |
| 141 @end |
| 142 |
| 143 #pragma mark - |
| 144 |
| 145 namespace { |
| 146 |
| 147 const CGFloat kViewportDimension = 100; |
| 148 const CGFloat kCardDimension = 90; |
| 149 |
| 150 CardStackLayoutManager* CreateMockCardStackLayoutManager() { |
| 151 OCMockObject* stackModelMock = |
| 152 [OCMockObject mockForClass:[CardStackLayoutManager class]]; |
| 153 BOOL no = NO; |
| 154 [[[stackModelMock stub] andReturnValue:OCMOCK_VALUE(no)] layoutIsVertical]; |
| 155 NSUInteger lastStartStackCardIndex = 0; |
| 156 [[[stackModelMock stub] andReturnValue:OCMOCK_VALUE(lastStartStackCardIndex)] |
| 157 lastStartStackCardIndex]; |
| 158 NSArray* cards = @[]; |
| 159 [[[stackModelMock stub] andReturn:cards] cards]; |
| 160 return (CardStackLayoutManager*)stackModelMock; |
| 161 } |
| 162 |
| 163 class CardSetTest : public PlatformTest { |
| 164 protected: |
| 165 virtual void SetUpWithTabs(int nb_tabs) { |
| 166 tab_model_.reset([[MockTabModel alloc] init]); |
| 167 |
| 168 for (int i = 0; i < nb_tabs; ++i) { |
| 169 std::string url = base::StringPrintf("http://%d.example.com", i); |
| 170 [tab_model_ addTabWithTitle:@"NewTab" location:GURL(url)]; |
| 171 } |
| 172 |
| 173 card_set_.reset( |
| 174 [[CardSet alloc] initWithModel:(TabModel*)tab_model_.get()]); |
| 175 |
| 176 display_view_.reset( |
| 177 [[UIView alloc] initWithFrame:CGRectMake(0, 0, kViewportDimension, |
| 178 kViewportDimension)]); |
| 179 // Do some initial configuration of the card set. |
| 180 [card_set_ setDisplayView:display_view_]; |
| 181 [card_set_ setCardSize:CGSizeMake(kCardDimension, kCardDimension)]; |
| 182 [card_set_ setLayoutAxisPosition:(kViewportDimension / 2.0) isVertical:YES]; |
| 183 [card_set_ configureLayoutParametersWithMargin:10]; |
| 184 } |
| 185 |
| 186 void SetUp() override { SetUpWithTabs(2); } |
| 187 |
| 188 base::scoped_nsobject<MockTabModel> tab_model_; |
| 189 base::scoped_nsobject<UIView> display_view_; |
| 190 base::scoped_nsobject<CardSet> card_set_; |
| 191 }; |
| 192 |
| 193 TEST_F(CardSetTest, InitialLayoutState) { |
| 194 NSArray* cards = [card_set_ cards]; |
| 195 EXPECT_EQ([tab_model_ count], [cards count]); |
| 196 |
| 197 // Tabs should be on the trailing side. |
| 198 EXPECT_EQ(CardCloseButtonSide::TRAILING, [card_set_ closeButtonSide]); |
| 199 |
| 200 // At least one card should be visible after layout, and cards should have |
| 201 // the right size and center. |
| 202 [card_set_ fanOutCards]; |
| 203 BOOL has_visible_card = NO; |
| 204 for (StackCard* card in cards) { |
| 205 if ([card viewIsLive]) |
| 206 has_visible_card = YES; |
| 207 EXPECT_FLOAT_EQ(kCardDimension, card.layout.size.width); |
| 208 EXPECT_FLOAT_EQ(kCardDimension, card.layout.size.height); |
| 209 EXPECT_FLOAT_EQ(kViewportDimension / 2.0, |
| 210 CGRectGetMidX(LayoutRectGetRect(card.layout))); |
| 211 } |
| 212 EXPECT_TRUE(has_visible_card); |
| 213 } |
| 214 |
| 215 TEST_F(CardSetTest, HandleRotation) { |
| 216 // Rotate the card set. |
| 217 [card_set_ setLayoutAxisPosition:(kViewportDimension / 3.0) isVertical:NO]; |
| 218 |
| 219 // Tabs should now be on the leading side. |
| 220 EXPECT_EQ(CardCloseButtonSide::LEADING, [card_set_ closeButtonSide]); |
| 221 // And the centers should be on the new axis. |
| 222 for (StackCard* card in [card_set_ cards]) { |
| 223 EXPECT_FLOAT_EQ(kViewportDimension / 3.0, |
| 224 CGRectGetMidY(LayoutRectGetRect(card.layout))); |
| 225 } |
| 226 } |
| 227 |
| 228 TEST_F(CardSetTest, CurrentCard) { |
| 229 NSUInteger current_tab_index = |
| 230 [tab_model_ indexOfTab:[tab_model_ currentTab]]; |
| 231 NSArray* cards = [card_set_ cards]; |
| 232 NSUInteger current_card_index = [cards indexOfObject:[card_set_ currentCard]]; |
| 233 EXPECT_EQ(current_tab_index, current_card_index); |
| 234 } |
| 235 |
| 236 TEST_F(CardSetTest, ViewClearing) { |
| 237 // Add the views. |
| 238 [card_set_ fanOutCards]; |
| 239 ASSERT_GT([[display_view_ subviews] count], 0U); |
| 240 [card_set_ setDisplayView:nil]; |
| 241 EXPECT_EQ(0U, [[display_view_ subviews] count]); |
| 242 } |
| 243 |
| 244 TEST_F(CardSetTest, NoticeTabAddition) { |
| 245 NSArray* cards = [card_set_ cards]; |
| 246 NSUInteger initial_card_count = [cards count]; |
| 247 |
| 248 OCMockObject* observer = |
| 249 [OCMockObject mockForProtocol:@protocol(CardSetObserver)]; |
| 250 [[observer expect] cardSet:OCMOCK_ANY didAddCard:OCMOCK_ANY]; |
| 251 [card_set_ setObserver:(id<CardSetObserver>)observer]; |
| 252 |
| 253 [tab_model_ addTabWithTitle:@"NewTab" |
| 254 location:GURL("http://www.example.com")]; |
| 255 cards = [card_set_ cards]; |
| 256 EXPECT_EQ(initial_card_count + 1, [cards count]); |
| 257 |
| 258 [card_set_ setObserver:nil]; |
| 259 EXPECT_OCMOCK_VERIFY(observer); |
| 260 } |
| 261 |
| 262 TEST_F(CardSetTest, NoticeTabRemoval) { |
| 263 NSArray* cards = [card_set_ cards]; |
| 264 NSUInteger initial_card_count = [cards count]; |
| 265 StackCard* first_card = [cards objectAtIndex:0]; |
| 266 |
| 267 OCMockObject* observer = |
| 268 [OCMockObject mockForProtocol:@protocol(CardSetObserver)]; |
| 269 [[observer expect] cardSet:OCMOCK_ANY willRemoveCard:OCMOCK_ANY atIndex:0]; |
| 270 [[observer expect] cardSet:OCMOCK_ANY didRemoveCard:OCMOCK_ANY atIndex:0]; |
| 271 |
| 272 [card_set_ setObserver:(id<CardSetObserver>)observer]; |
| 273 |
| 274 [tab_model_ removeTabAtIndex:0]; |
| 275 cards = [card_set_ cards]; |
| 276 EXPECT_EQ(initial_card_count - 1, [cards count]); |
| 277 EXPECT_NE(first_card, [cards objectAtIndex:0]); |
| 278 |
| 279 [card_set_ setObserver:nil]; |
| 280 EXPECT_OCMOCK_VERIFY(observer); |
| 281 } |
| 282 |
| 283 TEST_F(CardSetTest, Preloading) { |
| 284 // Preloading when everything is visible should return NO. |
| 285 [card_set_ fanOutCards]; |
| 286 EXPECT_FALSE([card_set_ preloadNextCard]); |
| 287 |
| 288 // Add a bunch of cards to ensure stacking. |
| 289 for (int i = 0; i < 20; ++i) { |
| 290 [tab_model_ addTabWithTitle:@"NewTab" |
| 291 location:GURL("http://www.example.com")]; |
| 292 } |
| 293 [card_set_ fanOutCards]; |
| 294 |
| 295 NSUInteger loaded_cards = [[display_view_ subviews] count]; |
| 296 // Now preloading should return YES, and should have added one more card to |
| 297 // the view. |
| 298 EXPECT_TRUE([card_set_ preloadNextCard]); |
| 299 EXPECT_EQ(loaded_cards + 1, [[display_view_ subviews] count]); |
| 300 } |
| 301 |
| 302 TEST_F(CardSetTest, stackIsFullyCollapsed) { |
| 303 [card_set_ setStackModelForTesting:CreateMockCardStackLayoutManager()]; |
| 304 OCMockObject* stack_model_mock = (OCMockObject*)[card_set_ stackModel]; |
| 305 [[stack_model_mock expect] stackIsFullyCollapsed]; |
| 306 [card_set_ stackIsFullyCollapsed]; |
| 307 EXPECT_OCMOCK_VERIFY(stack_model_mock); |
| 308 } |
| 309 |
| 310 TEST_F(CardSetTest, stackIsFullyFannedOut) { |
| 311 [card_set_ setStackModelForTesting:CreateMockCardStackLayoutManager()]; |
| 312 OCMockObject* stack_model_mock = (OCMockObject*)[card_set_ stackModel]; |
| 313 [[stack_model_mock expect] stackIsFullyFannedOut]; |
| 314 [card_set_ stackIsFullyFannedOut]; |
| 315 EXPECT_OCMOCK_VERIFY(stack_model_mock); |
| 316 } |
| 317 |
| 318 TEST_F(CardSetTest, stackIsFullyOverextended) { |
| 319 [card_set_ setStackModelForTesting:CreateMockCardStackLayoutManager()]; |
| 320 OCMockObject* stack_model_mock = (OCMockObject*)[card_set_ stackModel]; |
| 321 [[stack_model_mock expect] stackIsFullyOverextended]; |
| 322 [card_set_ stackIsFullyOverextended]; |
| 323 EXPECT_OCMOCK_VERIFY(stack_model_mock); |
| 324 } |
| 325 |
| 326 TEST_F(CardSetTest, isCardInStartStaggerRegion) { |
| 327 [card_set_ fanOutCards]; |
| 328 NSArray* cards = [card_set_ cards]; |
| 329 // First card should not be collapsed into start stagger region when stack is |
| 330 // fanned out from the first card. |
| 331 StackCard* first_card = [cards objectAtIndex:0]; |
| 332 EXPECT_FALSE([card_set_ isCardInStartStaggerRegion:first_card]); |
| 333 // Add a bunch of cards to ensure stacking, and fan them out so that there |
| 334 // is a start stack. |
| 335 for (int i = 0; i < 20; ++i) { |
| 336 [tab_model_ addTabWithTitle:@"NewTab" |
| 337 location:GURL("http://www.example.com")]; |
| 338 } |
| 339 [card_set_ fanOutCardsWithStartIndex:10]; |
| 340 // First card should now be collapsed into start stagger region. |
| 341 EXPECT_TRUE([card_set_ isCardInStartStaggerRegion:first_card]); |
| 342 } |
| 343 |
| 344 TEST_F(CardSetTest, isCardInEndStaggerRegion) { |
| 345 [card_set_ fanOutCards]; |
| 346 NSArray* cards = [card_set_ cards]; |
| 347 // Add a bunch of cards to ensure stacking, and fan them out so that there |
| 348 // is an end stack. |
| 349 for (int i = 0; i < 20; ++i) { |
| 350 [tab_model_ addTabWithTitle:@"NewTab" |
| 351 location:GURL("http://www.example.com")]; |
| 352 } |
| 353 StackCard* last_card = [cards objectAtIndex:[cards count] - 1]; |
| 354 [card_set_ fanOutCards]; |
| 355 // Last card should be collapsed into end region. |
| 356 EXPECT_TRUE([card_set_ isCardInEndStaggerRegion:last_card]); |
| 357 |
| 358 // Fan out cards from the last card. |
| 359 [card_set_ fanOutCardsWithStartIndex:[cards count] - 1]; |
| 360 // Last card should not be collapsed into end region. |
| 361 EXPECT_FALSE([card_set_ isCardInEndStaggerRegion:last_card]); |
| 362 } |
| 363 |
| 364 TEST_F(CardSetTest, setTabModel) { |
| 365 SetUpWithTabs(0); |
| 366 [card_set_ setTabModel:nil]; |
| 367 EXPECT_TRUE([tab_model_ observer] == nil); |
| 368 [card_set_ setTabModel:static_cast<id>(tab_model_)]; |
| 369 EXPECT_NSEQ(card_set_, static_cast<id>([tab_model_ observer])); |
| 370 } |
| 371 |
| 372 } // namespace |
OLD | NEW |