Don't show supervised user as "already on this device" while they're being imported.
[chromium-blink-merge.git] / ui / views / cocoa / bridged_native_widget_unittest.mm
blobc61b18b768ded7ca9801fbe6cb2ff9b744ccc16c
1 // Copyright 2014 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.
5 #import "ui/views/cocoa/bridged_native_widget.h"
7 #import <Cocoa/Cocoa.h>
9 #import "base/mac/foundation_util.h"
10 #import "base/mac/mac_util.h"
11 #import "base/mac/sdk_forward_declarations.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/strings/sys_string_conversions.h"
15 #include "base/strings/utf_string_conversions.h"
16 #import "testing/gtest_mac.h"
17 #import "ui/gfx/test/ui_cocoa_test_helper.h"
18 #import "ui/views/cocoa/bridged_content_view.h"
19 #import "ui/views/cocoa/native_widget_mac_nswindow.h"
20 #import "ui/views/cocoa/views_nswindow_delegate.h"
21 #include "ui/views/controls/textfield/textfield.h"
22 #include "ui/views/ime/input_method.h"
23 #include "ui/views/view.h"
24 #include "ui/views/widget/native_widget_mac.h"
25 #include "ui/views/widget/root_view.h"
26 #include "ui/views/widget/widget.h"
27 #include "ui/views/widget/widget_observer.h"
29 using base::ASCIIToUTF16;
30 using base::SysNSStringToUTF8;
31 using base::SysNSStringToUTF16;
32 using base::SysUTF8ToNSString;
34 #define EXPECT_EQ_RANGE(a, b)        \
35   EXPECT_EQ(a.location, b.location); \
36   EXPECT_EQ(a.length, b.length);
38 namespace {
40 // Empty range shortcut for readibility.
41 NSRange EmptyRange() {
42   return NSMakeRange(NSNotFound, 0);
45 }  // namespace
47 // Class to override -[NSWindow toggleFullScreen:] to a no-op. This simulates
48 // NSWindow's behavior when attempting to toggle fullscreen state again, when
49 // the last attempt failed but Cocoa has not yet sent
50 // windowDidFailToEnterFullScreen:.
51 @interface BridgedNativeWidgetTestFullScreenWindow : NativeWidgetMacNSWindow {
52  @private
53   int ignoredToggleFullScreenCount_;
55 @property(readonly, nonatomic) int ignoredToggleFullScreenCount;
56 @end
58 @implementation BridgedNativeWidgetTestFullScreenWindow
60 @synthesize ignoredToggleFullScreenCount = ignoredToggleFullScreenCount_;
62 - (void)toggleFullScreen:(id)sender {
63   ++ignoredToggleFullScreenCount_;
66 @end
68 namespace views {
69 namespace test {
71 // Provides the |parent| argument to construct a BridgedNativeWidget.
72 class MockNativeWidgetMac : public NativeWidgetMac {
73  public:
74   MockNativeWidgetMac(Widget* delegate) : NativeWidgetMac(delegate) {}
76   // Expose a reference, so that it can be reset() independently.
77   scoped_ptr<BridgedNativeWidget>& bridge() {
78     return bridge_;
79   }
81   // internal::NativeWidgetPrivate:
82   void InitNativeWidget(const Widget::InitParams& params) override {
83     ownership_ = params.ownership;
85     // Usually the bridge gets initialized here. It is skipped to run extra
86     // checks in tests, and so that a second window isn't created.
87     delegate()->OnNativeWidgetCreated(true);
89     // To allow events to dispatch to a view, it needs a way to get focus.
90     bridge_->SetFocusManager(GetWidget()->GetFocusManager());
91   }
93   void ReorderNativeViews() override {
94     // Called via Widget::Init to set the content view. No-op in these tests.
95   }
97  private:
98   DISALLOW_COPY_AND_ASSIGN(MockNativeWidgetMac);
101 // Helper test base to construct a BridgedNativeWidget with a valid parent.
102 class BridgedNativeWidgetTestBase : public ui::CocoaTest {
103  public:
104   BridgedNativeWidgetTestBase()
105       : widget_(new Widget),
106         native_widget_mac_(new MockNativeWidgetMac(widget_.get())) {
107   }
109   scoped_ptr<BridgedNativeWidget>& bridge() {
110     return native_widget_mac_->bridge();
111   }
113   // Overridden from testing::Test:
114   void SetUp() override {
115     ui::CocoaTest::SetUp();
117     init_params_.native_widget = native_widget_mac_;
119     // Use a frameless window, otherwise Widget will try to center the window
120     // before the tests covering the Init() flow are ready to do that.
121     init_params_.type = Widget::InitParams::TYPE_WINDOW_FRAMELESS;
123     // To control the lifetime without an actual window that must be closed,
124     // tests in this file need to use WIDGET_OWNS_NATIVE_WIDGET.
125     init_params_.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
127     // Opacity defaults to "infer" which is usually updated by ViewsDelegate.
128     init_params_.opacity = Widget::InitParams::OPAQUE_WINDOW;
130     native_widget_mac_->GetWidget()->Init(init_params_);
131   }
133  protected:
134   scoped_ptr<Widget> widget_;
135   MockNativeWidgetMac* native_widget_mac_;  // Weak. Owned by |widget_|.
137   // Make the InitParams available to tests to cover initialization codepaths.
138   Widget::InitParams init_params_;
141 class BridgedNativeWidgetTest : public BridgedNativeWidgetTestBase {
142  public:
143   BridgedNativeWidgetTest();
144   ~BridgedNativeWidgetTest() override;
146   // Install a textfield in the view hierarchy and make it the text input
147   // client.
148   void InstallTextField(const std::string& text);
150   // Returns the current text as std::string.
151   std::string GetText();
153   // testing::Test:
154   void SetUp() override;
155   void TearDown() override;
157  protected:
158   scoped_ptr<views::View> view_;
159   scoped_ptr<BridgedNativeWidget> bridge_;
160   BridgedContentView* ns_view_;  // Weak. Owned by bridge_.
161   base::MessageLoopForUI message_loop_;
163  private:
164   DISALLOW_COPY_AND_ASSIGN(BridgedNativeWidgetTest);
167 BridgedNativeWidgetTest::BridgedNativeWidgetTest() {
170 BridgedNativeWidgetTest::~BridgedNativeWidgetTest() {
173 void BridgedNativeWidgetTest::InstallTextField(const std::string& text) {
174   Textfield* textfield = new Textfield();
175   textfield->SetText(ASCIIToUTF16(text));
176   view_->AddChildView(textfield);
178   // Request focus so the InputMethod can dispatch events to the RootView, and
179   // have them delivered to the textfield. Note that focusing a textfield
180   // schedules a task to flash the cursor, so this requires |message_loop_|.
181   textfield->RequestFocus();
183   [ns_view_ setTextInputClient:textfield];
186 std::string BridgedNativeWidgetTest::GetText() {
187   NSRange range = NSMakeRange(0, NSUIntegerMax);
188   NSAttributedString* text =
189       [ns_view_ attributedSubstringForProposedRange:range actualRange:NULL];
190   return SysNSStringToUTF8([text string]);
193 void BridgedNativeWidgetTest::SetUp() {
194   BridgedNativeWidgetTestBase::SetUp();
196   view_.reset(new views::internal::RootView(widget_.get()));
197   base::scoped_nsobject<NSWindow> window([test_window() retain]);
199   // BridgedNativeWidget expects to be initialized with a hidden (deferred)
200   // window.
201   [window orderOut:nil];
202   EXPECT_FALSE([window delegate]);
203   bridge()->Init(window, init_params_);
205   // The delegate should exist before setting the root view.
206   EXPECT_TRUE([window delegate]);
207   bridge()->SetRootView(view_.get());
208   ns_view_ = bridge()->ns_view();
210   // Pretend it has been shown via NativeWidgetMac::Show().
211   [window orderFront:nil];
212   [test_window() makePretendKeyWindowAndSetFirstResponder:bridge()->ns_view()];
215 void BridgedNativeWidgetTest::TearDown() {
216   view_.reset();
217   BridgedNativeWidgetTestBase::TearDown();
220 // The TEST_VIEW macro expects the view it's testing to have a superview. In
221 // these tests, the NSView bridge is a contentView, at the root. These mimic
222 // what TEST_VIEW usually does.
223 TEST_F(BridgedNativeWidgetTest, BridgedNativeWidgetTest_TestViewAddRemove) {
224   base::scoped_nsobject<BridgedContentView> view([bridge()->ns_view() retain]);
225   EXPECT_NSEQ([test_window() contentView], view);
226   EXPECT_NSEQ(test_window(), [view window]);
228   // The superview of a contentView is an NSNextStepFrame.
229   EXPECT_TRUE([view superview]);
230   EXPECT_TRUE([view hostedView]);
232   // Ensure the tracking area to propagate mouseMoved: events to the RootView is
233   // installed.
234   EXPECT_EQ(1u, [[view trackingAreas] count]);
236   // Destroying the C++ bridge should remove references to any C++ objects in
237   // the ObjectiveC object, and remove it from the hierarchy.
238   bridge().reset();
239   EXPECT_FALSE([view hostedView]);
240   EXPECT_FALSE([view superview]);
241   EXPECT_FALSE([view window]);
242   EXPECT_EQ(0u, [[view trackingAreas] count]);
243   EXPECT_FALSE([test_window() contentView]);
244   EXPECT_FALSE([test_window() delegate]);
247 TEST_F(BridgedNativeWidgetTest, BridgedNativeWidgetTest_TestViewDisplay) {
248   [bridge()->ns_view() display];
251 // Test that resizing the window resizes the root view appropriately.
252 TEST_F(BridgedNativeWidgetTest, ViewSizeTracksWindow) {
253   const int kTestNewWidth = 400;
254   const int kTestNewHeight = 300;
256   // |test_window()| is borderless, so these should align.
257   NSSize window_size = [test_window() frame].size;
258   EXPECT_EQ(view_->width(), static_cast<int>(window_size.width));
259   EXPECT_EQ(view_->height(), static_cast<int>(window_size.height));
261   // Make sure a resize actually occurs.
262   EXPECT_NE(kTestNewWidth, view_->width());
263   EXPECT_NE(kTestNewHeight, view_->height());
265   [test_window() setFrame:NSMakeRect(0, 0, kTestNewWidth, kTestNewHeight)
266                   display:NO];
267   EXPECT_EQ(kTestNewWidth, view_->width());
268   EXPECT_EQ(kTestNewHeight, view_->height());
271 TEST_F(BridgedNativeWidgetTest, CreateInputMethodShouldNotReturnNull) {
272   scoped_ptr<views::InputMethod> input_method(bridge()->CreateInputMethod());
273   EXPECT_TRUE(input_method);
276 TEST_F(BridgedNativeWidgetTest, GetHostInputMethodShouldNotReturnNull) {
277   EXPECT_TRUE(bridge()->GetHostInputMethod());
280 // A simpler test harness for testing initialization flows.
281 typedef BridgedNativeWidgetTestBase BridgedNativeWidgetInitTest;
283 // Test that BridgedNativeWidget remains sane if Init() is never called.
284 TEST_F(BridgedNativeWidgetInitTest, InitNotCalled) {
285   EXPECT_FALSE(bridge()->ns_view());
286   EXPECT_FALSE(bridge()->ns_window());
287   bridge().reset();
290 // Test getting complete string using text input protocol.
291 TEST_F(BridgedNativeWidgetTest, TextInput_GetCompleteString) {
292   const std::string kTestString = "foo bar baz";
293   InstallTextField(kTestString);
295   NSRange range = NSMakeRange(0, kTestString.size());
296   NSRange actual_range;
297   NSAttributedString* text =
298       [ns_view_ attributedSubstringForProposedRange:range
299                                         actualRange:&actual_range];
300   EXPECT_EQ(kTestString, SysNSStringToUTF8([text string]));
301   EXPECT_EQ_RANGE(range, actual_range);
304 // Test getting middle substring using text input protocol.
305 TEST_F(BridgedNativeWidgetTest, TextInput_GetMiddleSubstring) {
306   const std::string kTestString = "foo bar baz";
307   InstallTextField(kTestString);
309   NSRange range = NSMakeRange(4, 3);
310   NSRange actual_range;
311   NSAttributedString* text =
312       [ns_view_ attributedSubstringForProposedRange:range
313                                         actualRange:&actual_range];
314   EXPECT_EQ("bar", SysNSStringToUTF8([text string]));
315   EXPECT_EQ_RANGE(range, actual_range);
318 // Test getting ending substring using text input protocol.
319 TEST_F(BridgedNativeWidgetTest, TextInput_GetEndingSubstring) {
320   const std::string kTestString = "foo bar baz";
321   InstallTextField(kTestString);
323   NSRange range = NSMakeRange(8, 100);
324   NSRange actual_range;
325   NSAttributedString* text =
326       [ns_view_ attributedSubstringForProposedRange:range
327                                         actualRange:&actual_range];
328   EXPECT_EQ("baz", SysNSStringToUTF8([text string]));
329   EXPECT_EQ(range.location, actual_range.location);
330   EXPECT_EQ(3U, actual_range.length);
333 // Test getting empty substring using text input protocol.
334 TEST_F(BridgedNativeWidgetTest, TextInput_GetEmptySubstring) {
335   const std::string kTestString = "foo bar baz";
336   InstallTextField(kTestString);
338   NSRange range = EmptyRange();
339   NSRange actual_range;
340   NSAttributedString* text =
341       [ns_view_ attributedSubstringForProposedRange:range
342                                         actualRange:&actual_range];
343   EXPECT_EQ("", SysNSStringToUTF8([text string]));
344   EXPECT_EQ_RANGE(range, actual_range);
347 // Test inserting text using text input protocol.
348 TEST_F(BridgedNativeWidgetTest, TextInput_InsertText) {
349   const std::string kTestString = "foo";
350   InstallTextField(kTestString);
352   [ns_view_ insertText:SysUTF8ToNSString(kTestString)
353       replacementRange:EmptyRange()];
354   gfx::Range range(0, kTestString.size());
355   base::string16 text;
356   EXPECT_TRUE([ns_view_ textInputClient]->GetTextFromRange(range, &text));
357   EXPECT_EQ(ASCIIToUTF16(kTestString), text);
360 // Test replacing text using text input protocol.
361 TEST_F(BridgedNativeWidgetTest, TextInput_ReplaceText) {
362   const std::string kTestString = "foo bar";
363   InstallTextField(kTestString);
365   [ns_view_ insertText:@"baz" replacementRange:NSMakeRange(4, 3)];
366   EXPECT_EQ("foo baz", GetText());
369 // Test IME composition using text input protocol.
370 TEST_F(BridgedNativeWidgetTest, TextInput_Compose) {
371   const std::string kTestString = "foo ";
372   InstallTextField(kTestString);
374   EXPECT_FALSE([ns_view_ hasMarkedText]);
375   EXPECT_EQ_RANGE(EmptyRange(), [ns_view_ markedRange]);
377   // Start composition.
378   NSString* compositionText = @"bar";
379   NSUInteger compositionLength = [compositionText length];
380   [ns_view_ setMarkedText:compositionText
381             selectedRange:NSMakeRange(0, 2)
382          replacementRange:EmptyRange()];
383   EXPECT_TRUE([ns_view_ hasMarkedText]);
384   EXPECT_EQ_RANGE(NSMakeRange(kTestString.size(), compositionLength),
385                   [ns_view_ markedRange]);
386   EXPECT_EQ_RANGE(NSMakeRange(kTestString.size(), 2), [ns_view_ selectedRange]);
388   // Confirm composition.
389   [ns_view_ unmarkText];
390   EXPECT_FALSE([ns_view_ hasMarkedText]);
391   EXPECT_EQ_RANGE(EmptyRange(), [ns_view_ markedRange]);
392   EXPECT_EQ("foo bar", GetText());
393   EXPECT_EQ_RANGE(NSMakeRange(GetText().size(), 0), [ns_view_ selectedRange]);
396 // Test moving the caret left and right using text input protocol.
397 TEST_F(BridgedNativeWidgetTest, TextInput_MoveLeftRight) {
398   InstallTextField("foo");
399   EXPECT_EQ_RANGE(NSMakeRange(3, 0), [ns_view_ selectedRange]);
401   // Move right not allowed, out of range.
402   [ns_view_ doCommandBySelector:@selector(moveRight:)];
403   EXPECT_EQ_RANGE(NSMakeRange(3, 0), [ns_view_ selectedRange]);
405   // Move left.
406   [ns_view_ doCommandBySelector:@selector(moveLeft:)];
407   EXPECT_EQ_RANGE(NSMakeRange(2, 0), [ns_view_ selectedRange]);
409   // Move right.
410   [ns_view_ doCommandBySelector:@selector(moveRight:)];
411   EXPECT_EQ_RANGE(NSMakeRange(3, 0), [ns_view_ selectedRange]);
414 // Test backward delete using text input protocol.
415 TEST_F(BridgedNativeWidgetTest, TextInput_DeleteBackward) {
416   InstallTextField("a");
417   EXPECT_EQ_RANGE(NSMakeRange(1, 0), [ns_view_ selectedRange]);
419   // Delete one character.
420   [ns_view_ doCommandBySelector:@selector(deleteBackward:)];
421   EXPECT_EQ("", GetText());
422   EXPECT_EQ_RANGE(NSMakeRange(0, 0), [ns_view_ selectedRange]);
424   // Try to delete again on an empty string.
425   [ns_view_ doCommandBySelector:@selector(deleteBackward:)];
426   EXPECT_EQ("", GetText());
427   EXPECT_EQ_RANGE(NSMakeRange(0, 0), [ns_view_ selectedRange]);
430 // Test forward delete using text input protocol.
431 TEST_F(BridgedNativeWidgetTest, TextInput_DeleteForward) {
432   InstallTextField("a");
433   EXPECT_EQ_RANGE(NSMakeRange(1, 0), [ns_view_ selectedRange]);
435   // At the end of the string, can't delete forward.
436   [ns_view_ doCommandBySelector:@selector(deleteForward:)];
437   EXPECT_EQ("a", GetText());
438   EXPECT_EQ_RANGE(NSMakeRange(1, 0), [ns_view_ selectedRange]);
440   // Should succeed after moving left first.
441   [ns_view_ doCommandBySelector:@selector(moveLeft:)];
442   [ns_view_ doCommandBySelector:@selector(deleteForward:)];
443   EXPECT_EQ("", GetText());
444   EXPECT_EQ_RANGE(NSMakeRange(0, 0), [ns_view_ selectedRange]);
447 typedef BridgedNativeWidgetTestBase BridgedNativeWidgetSimulateFullscreenTest;
449 // Simulate the notifications that AppKit would send out if a fullscreen
450 // operation begins, and then fails and must abort. This notification sequence
451 // was determined by posting delayed tasks to toggle fullscreen state and then
452 // mashing Ctrl+Left/Right to keep OSX in a transition between Spaces to cause
453 // the fullscreen transition to fail.
454 TEST_F(BridgedNativeWidgetSimulateFullscreenTest, FailToEnterAndExit) {
455   if (base::mac::IsOSSnowLeopard())
456     return;
458   base::scoped_nsobject<NSWindow> owned_window(
459       [[BridgedNativeWidgetTestFullScreenWindow alloc]
460           initWithContentRect:NSMakeRect(50, 50, 400, 300)
461                     styleMask:NSBorderlessWindowMask
462                       backing:NSBackingStoreBuffered
463                         defer:YES]);
464   [owned_window setReleasedWhenClosed:NO];  // Owned by scoped_nsobject.
465   bridge()->Init(owned_window, init_params_);  // Transfers ownership.
467   BridgedNativeWidgetTestFullScreenWindow* window =
468       base::mac::ObjCCastStrict<BridgedNativeWidgetTestFullScreenWindow>(
469           widget_->GetNativeWindow());
470   widget_->Show();
472   NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
474   EXPECT_FALSE(bridge()->target_fullscreen_state());
476   // Simulate an initial toggleFullScreen: (user- or Widget-initiated).
477   [center postNotificationName:NSWindowWillEnterFullScreenNotification
478                         object:window];
480   // On a failure, Cocoa starts by sending an unexpected *exit* fullscreen, and
481   // BridgedNativeWidget will think it's just a delayed transition and try to go
482   // back into fullscreen but get ignored by Cocoa.
483   EXPECT_EQ(0, [window ignoredToggleFullScreenCount]);
484   EXPECT_TRUE(bridge()->target_fullscreen_state());
485   [center postNotificationName:NSWindowDidExitFullScreenNotification
486                         object:window];
487   EXPECT_EQ(1, [window ignoredToggleFullScreenCount]);
488   EXPECT_FALSE(bridge()->target_fullscreen_state());
490   // Cocoa follows up with a failure message sent to the NSWindowDelegate (there
491   // is no equivalent notification for failure). Called via id so that this
492   // compiles on 10.6.
493   ViewsNSWindowDelegate* window_delegate =
494       base::mac::ObjCCast<ViewsNSWindowDelegate>([window delegate]);
495   [window_delegate windowDidFailToEnterFullScreen:window];
496   EXPECT_FALSE(bridge()->target_fullscreen_state());
498   // Now perform a successful fullscreen operation.
499   [center postNotificationName:NSWindowWillEnterFullScreenNotification
500                         object:window];
501   EXPECT_TRUE(bridge()->target_fullscreen_state());
502   [center postNotificationName:NSWindowDidEnterFullScreenNotification
503                         object:window];
504   EXPECT_TRUE(bridge()->target_fullscreen_state());
506   // And try to get out.
507   [center postNotificationName:NSWindowWillExitFullScreenNotification
508                         object:window];
509   EXPECT_FALSE(bridge()->target_fullscreen_state());
511   // On a failure, Cocoa sends a failure message, but then just dumps the window
512   // out of fullscreen anyway (in that order).
513   [window_delegate windowDidFailToExitFullScreen:window];
514   EXPECT_FALSE(bridge()->target_fullscreen_state());
515   [center postNotificationName:NSWindowDidExitFullScreenNotification
516                         object:window];
517   EXPECT_EQ(1, [window ignoredToggleFullScreenCount]);  // No change.
518   EXPECT_FALSE(bridge()->target_fullscreen_state());
520   widget_->CloseNow();
523 }  // namespace test
524 }  // namespace views