1// Copyright (c) 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 "chrome/browser/ui/cocoa/constrained_window/constrained_window_sheet_controller.h"
6
7#import "chrome/browser/ui/cocoa/cocoa_test_helper.h"
8#import "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_sheet.h"
9#import "testing/gtest_mac.h"
10
11namespace {
12
13const int kSystemSheetReturnCode = 77;
14
15}  // namespace
16
17@interface ConstrainedWindowSystemSheetTest
18    : NSObject <ConstrainedWindowSheet> {
19  int returnCode_;
20  NSAlert* alert_;  // weak
21}
22
23@property(nonatomic, readonly) int returnCode;
24@property(nonatomic, assign) NSAlert* alert;
25
26@end
27
28@implementation ConstrainedWindowSystemSheetTest
29
30@synthesize returnCode = returnCode_;
31@synthesize alert = alert_;
32
33- (void)showSheetForWindow:(NSWindow*)window {
34  [alert_ beginSheetModalForWindow:window
35                     modalDelegate:self
36                    didEndSelector:@selector(alertDidEnd:returnCode:ctxInfo:)
37                       contextInfo:NULL];
38}
39
40- (void)closeSheetWithAnimation:(BOOL)withAnimation {
41  [NSApp endSheet:[alert_ window] returnCode:kSystemSheetReturnCode];
42}
43
44- (void)hideSheet {
45}
46
47- (void)unhideSheet {
48}
49
50- (void)pulseSheet {
51}
52
53- (void)makeSheetKeyAndOrderFront {
54}
55
56- (void)updateSheetPosition {
57}
58
59- (void)alertDidEnd:(NSAlert *)alert
60         returnCode:(NSInteger)returnCode
61            ctxInfo:(void *)contextInfo {
62  returnCode_ = returnCode;
63}
64
65@end
66
67class ConstrainedWindowSheetControllerTest : public CocoaTest {
68 protected:
69  virtual ~ConstrainedWindowSheetControllerTest() {
70  }
71
72  virtual void SetUp() OVERRIDE {
73    CocoaTest::SetUp();
74
75    // Center the window so that the sheet doesn't go offscreen.
76    [test_window() center];
77
78    // The real view setup is quite a few levels deep; recreate something
79    // similar.
80    NSRect dummy_rect = NSMakeRect(0, 0, 50, 50);
81    tab_view_parent_ = [test_window() contentView];
82    for (int i = 0; i < 3; ++i) {
83      base::scoped_nsobject<NSView> new_view(
84          [[NSView alloc] initWithFrame:dummy_rect]);
85      [tab_view_parent_ addSubview:new_view.get()];
86      tab_view_parent_ = new_view.get();
87    }
88
89    // Create two dummy tabs and make the first one active.
90    tab_views_.reset([[NSMutableArray alloc] init]);
91    for (int i = 0; i < 2; ++i) {
92      base::scoped_nsobject<NSView> view(
93          [[NSView alloc] initWithFrame:dummy_rect]);
94      [tab_views_ addObject:view];
95    }
96    tab0_ = [tab_views_ objectAtIndex:0];
97    tab1_ = [tab_views_ objectAtIndex:1];
98    ActivateTabView(tab0_);
99
100    // Create a test sheet.
101    sheet_window_.reset([[NSWindow alloc]
102        initWithContentRect:dummy_rect
103                  styleMask:NSTitledWindowMask
104                    backing:NSBackingStoreBuffered
105                      defer:NO]);
106    [sheet_window_ setReleasedWhenClosed:NO];
107    sheet_.reset([[CustomConstrainedWindowSheet alloc]
108        initWithCustomWindow:sheet_window_]);
109
110    controller_.reset([[ConstrainedWindowSheetController
111            controllerForParentWindow:test_window()] retain]);
112    EXPECT_TRUE(controller_);
113    EXPECT_FALSE([ConstrainedWindowSheetController controllerForSheet:sheet_]);
114  }
115
116  virtual void TearDown() OVERRIDE {
117    sheet_.reset();
118    sheet_window_.reset();
119    CocoaTest::TearDown();
120  }
121
122  void ActivateTabView(NSView* tab_view) {
123    for (NSView* view in tab_views_.get())
124      [view removeFromSuperview];
125    [tab_view_parent_ addSubview:tab_view];
126    active_tab_view_ = tab_view;
127
128    ConstrainedWindowSheetController* controller =
129        [ConstrainedWindowSheetController
130            controllerForParentWindow:test_window()];
131    EXPECT_TRUE(controller);
132    [controller parentViewDidBecomeActive:active_tab_view_];
133  }
134
135  NSRect GetViewFrameInScreenCoordinates(NSView* view) {
136    NSRect rect = [view convertRect:[view bounds] toView:nil];
137    rect.origin = [[view window] convertBaseToScreen:rect.origin];
138    return rect;
139  }
140
141  void VerifySheetXPosition(NSRect sheet_frame, NSView* parent_view) {
142    NSRect parent_frame = GetViewFrameInScreenCoordinates(parent_view);
143    CGFloat expected_x = NSMinX(parent_frame) +
144        (NSWidth(parent_frame) - NSWidth(sheet_frame)) / 2.0;
145    EXPECT_EQ(expected_x, NSMinX(sheet_frame));
146  }
147
148  CGFloat GetSheetYOffset(NSRect sheet_frame, NSView* parent_view) {
149    return NSMaxY(sheet_frame) -
150           NSMaxY(GetViewFrameInScreenCoordinates(parent_view));
151  }
152
153  base::scoped_nsobject<NSWindow> sheet_window_;
154  base::scoped_nsobject<CustomConstrainedWindowSheet> sheet_;
155  base::scoped_nsobject<ConstrainedWindowSheetController> controller_;
156  base::scoped_nsobject<NSMutableArray> tab_views_;
157  NSView* tab_view_parent_;
158  NSView* active_tab_view_;
159  NSView* tab0_;
160  NSView* tab1_;
161};
162
163// Test showing then hiding the sheet.
164TEST_F(ConstrainedWindowSheetControllerTest, ShowHide) {
165  EXPECT_FALSE([sheet_window_ isVisible]);
166  [controller_ showSheet:sheet_ forParentView:active_tab_view_];
167  EXPECT_TRUE([ConstrainedWindowSheetController controllerForSheet:sheet_]);
168  EXPECT_TRUE([sheet_window_ isVisible]);
169
170  [controller_ closeSheet:sheet_];
171  EXPECT_FALSE([ConstrainedWindowSheetController controllerForSheet:sheet_]);
172  EXPECT_FALSE([sheet_window_ isVisible]);
173}
174
175// Test that switching tabs correctly hides the inactive tab's sheet.
176TEST_F(ConstrainedWindowSheetControllerTest, SwitchTabs) {
177  [controller_ showSheet:sheet_ forParentView:active_tab_view_];
178
179  EXPECT_TRUE([sheet_window_ isVisible]);
180  EXPECT_EQ(1.0, [sheet_window_ alphaValue]);
181  ActivateTabView([tab_views_ objectAtIndex:1]);
182  EXPECT_TRUE([sheet_window_ isVisible]);
183  EXPECT_EQ(0.0, [sheet_window_ alphaValue]);
184  ActivateTabView([tab_views_ objectAtIndex:0]);
185  EXPECT_TRUE([sheet_window_ isVisible]);
186  EXPECT_EQ(1.0, [sheet_window_ alphaValue]);
187}
188
189// Test that adding a sheet to an inactive view doesn't show it.
190TEST_F(ConstrainedWindowSheetControllerTest, AddToInactiveTab) {
191  ActivateTabView(tab0_);
192  [controller_ showSheet:sheet_ forParentView:tab1_];
193  EXPECT_EQ(0.0, [sheet_window_ alphaValue]);
194
195  ActivateTabView(tab1_);
196  EXPECT_EQ(1.0, [sheet_window_ alphaValue]);
197  VerifySheetXPosition([sheet_window_ frame], tab1_);
198}
199
200// Test that two parent windows with two sheet controllers don't conflict.
201TEST_F(ConstrainedWindowSheetControllerTest, TwoParentWindows) {
202  base::scoped_nsobject<NSWindow> parent_window2(
203      [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 30, 30)
204                                  styleMask:NSTitledWindowMask
205                                    backing:NSBackingStoreBuffered
206                                      defer:NO]);
207  [parent_window2 setReleasedWhenClosed:NO];
208
209  ConstrainedWindowSheetController* controller2 =
210      [ConstrainedWindowSheetController
211          controllerForParentWindow:parent_window2];
212  EXPECT_TRUE(controller2);
213  EXPECT_NSNE(controller_, controller2);
214
215  [controller2 showSheet:sheet_ forParentView:[parent_window2 contentView]];
216  EXPECT_NSEQ(controller2,
217              [ConstrainedWindowSheetController controllerForSheet:sheet_]);
218
219  [parent_window2 close];
220}
221
222// Test that resizing sheet works.
223TEST_F(ConstrainedWindowSheetControllerTest, Resize) {
224  [controller_ showSheet:sheet_ forParentView:active_tab_view_];
225
226  NSRect old_frame = [sheet_window_ frame];
227
228  NSRect sheet_frame;
229  sheet_frame.size = NSMakeSize(NSWidth(old_frame) + 100,
230                                NSHeight(old_frame) + 50);
231  sheet_frame.origin = [controller_ originForSheet:sheet_
232                                    withWindowSize:sheet_frame.size];
233
234  // Y pos should not have changed.
235  EXPECT_EQ(NSMaxY(sheet_frame), NSMaxY(old_frame));
236
237  // X pos should be centered on parent view.
238  VerifySheetXPosition(sheet_frame, active_tab_view_);
239}
240
241// Test that resizing a hidden sheet works.
242TEST_F(ConstrainedWindowSheetControllerTest, ResizeHiddenSheet) {
243  [controller_ showSheet:sheet_ forParentView:tab0_];
244  EXPECT_EQ(1.0, [sheet_window_ alphaValue]);
245  ActivateTabView(tab1_);
246  EXPECT_EQ(0.0, [sheet_window_ alphaValue]);
247
248  NSRect old_frame = [sheet_window_ frame];
249  NSRect new_inactive_frame = NSInsetRect(old_frame, -30, -40);
250  [sheet_window_ setFrame:new_inactive_frame display:YES];
251
252  ActivateTabView(tab0_);
253  EXPECT_EQ(1.0, [sheet_window_ alphaValue]);
254
255  NSRect new_active_frame = [sheet_window_ frame];
256  EXPECT_EQ(NSWidth(new_inactive_frame), NSWidth(new_active_frame));
257  EXPECT_EQ(NSHeight(new_inactive_frame), NSHeight(new_active_frame));
258}
259
260// Test resizing parent window keeps the sheet anchored.
261TEST_F(ConstrainedWindowSheetControllerTest, ResizeParentWindow) {
262  [controller_ showSheet:sheet_ forParentView:active_tab_view_];
263  CGFloat sheet_offset =
264      GetSheetYOffset([sheet_window_ frame], active_tab_view_);
265
266  // Test 3x3 different parent window sizes.
267  CGFloat insets[] = {-10, 0, 10};
268  NSRect old_frame = [test_window() frame];
269
270  for (size_t x = 0; x < arraysize(insets); x++) {
271    for (size_t y = 0; y < arraysize(insets); y++) {
272      NSRect resized_frame = NSInsetRect(old_frame, insets[x], insets[y]);
273      [test_window() setFrame:resized_frame display:YES];
274      NSRect sheet_frame = [sheet_window_ frame];
275
276      // Y pos should track parent view's position.
277      EXPECT_EQ(sheet_offset, GetSheetYOffset(sheet_frame, active_tab_view_));
278
279      // X pos should be centered on parent view.
280      VerifySheetXPosition(sheet_frame, active_tab_view_);
281    }
282  }
283}
284
285// Test system sheets.
286TEST_F(ConstrainedWindowSheetControllerTest, SystemSheet) {
287  base::scoped_nsobject<ConstrainedWindowSystemSheetTest> system_sheet(
288      [[ConstrainedWindowSystemSheetTest alloc] init]);
289  base::scoped_nsobject<NSAlert> alert([[NSAlert alloc] init]);
290  [system_sheet setAlert:alert];
291
292  EXPECT_FALSE([[alert window] isVisible]);
293  [controller_ showSheet:system_sheet forParentView:active_tab_view_];
294  EXPECT_TRUE([[alert window] isVisible]);
295
296  [controller_ closeSheet:system_sheet];
297  EXPECT_FALSE([[alert window] isVisible]);
298  EXPECT_EQ(kSystemSheetReturnCode, [system_sheet returnCode]);
299}
300
301// Test showing a system sheet on an inactive tab.
302TEST_F(ConstrainedWindowSheetControllerTest, SystemSheetAddToInactiveTab) {
303  base::scoped_nsobject<ConstrainedWindowSystemSheetTest> system_sheet(
304      [[ConstrainedWindowSystemSheetTest alloc] init]);
305  base::scoped_nsobject<NSAlert> alert([[NSAlert alloc] init]);
306  [system_sheet setAlert:alert];
307
308  EXPECT_FALSE([[alert window] isVisible]);
309  [controller_ showSheet:system_sheet forParentView:tab1_];
310  EXPECT_FALSE([[alert window] isVisible]);
311
312  ActivateTabView(tab1_);
313  EXPECT_TRUE([[alert window] isVisible]);
314  EXPECT_EQ(1.0, [[alert window] alphaValue]);
315
316  [controller_ closeSheet:system_sheet];
317}
318