| 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
|
|
|