Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / ui / views / cocoa / bridged_native_widget_unittest.mm
blob14a7f9c3c0bfc17fe35a420705f2ce6209b2426a
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 #include "base/memory/scoped_ptr.h"
10 #include "base/strings/sys_string_conversions.h"
11 #include "base/strings/utf_string_conversions.h"
12 #import "testing/gtest_mac.h"
13 #import "ui/gfx/test/ui_cocoa_test_helper.h"
14 #import "ui/views/cocoa/bridged_content_view.h"
15 #include "ui/views/controls/textfield/textfield.h"
16 #include "ui/views/ime/input_method.h"
17 #include "ui/views/view.h"
18 #include "ui/views/widget/native_widget_mac.h"
19 #include "ui/views/widget/widget.h"
20 #include "ui/views/widget/widget_observer.h"
22 using base::ASCIIToUTF16;
23 using base::SysNSStringToUTF8;
24 using base::SysNSStringToUTF16;
25 using base::SysUTF8ToNSString;
27 #define EXPECT_EQ_RANGE(a, b)        \
28   EXPECT_EQ(a.location, b.location); \
29   EXPECT_EQ(a.length, b.length);
31 namespace {
33 // Empty range shortcut for readibility.
34 NSRange EmptyRange() {
35   return NSMakeRange(NSNotFound, 0);
38 }  // namespace
40 namespace views {
41 namespace test {
43 // Provides the |parent| argument to construct a BridgedNativeWidget.
44 class MockNativeWidgetMac : public NativeWidgetMac {
45  public:
46   MockNativeWidgetMac(Widget* delegate) : NativeWidgetMac(delegate) {}
48   // Expose a reference, so that it can be reset() independently.
49   scoped_ptr<BridgedNativeWidget>& bridge() {
50     return bridge_;
51   }
53   // internal::NativeWidgetPrivate:
54   virtual void InitNativeWidget(const Widget::InitParams& params) OVERRIDE {
55     ownership_ = params.ownership;
57     // Usually the bridge gets initialized here. It is skipped to run extra
58     // checks in tests, and so that a second window isn't created.
59     delegate()->OnNativeWidgetCreated(true);
60   }
62   virtual void ReorderNativeViews() OVERRIDE {
63     // Called via Widget::Init to set the content view. No-op in these tests.
64   }
66  private:
67   DISALLOW_COPY_AND_ASSIGN(MockNativeWidgetMac);
70 // Helper test base to construct a BridgedNativeWidget with a valid parent.
71 class BridgedNativeWidgetTestBase : public ui::CocoaTest {
72  public:
73   BridgedNativeWidgetTestBase()
74       : widget_(new Widget),
75         native_widget_mac_(new MockNativeWidgetMac(widget_.get())) {
76   }
78   scoped_ptr<BridgedNativeWidget>& bridge() {
79     return native_widget_mac_->bridge();
80   }
82   // Overridden from testing::Test:
83   virtual void SetUp() OVERRIDE {
84     ui::CocoaTest::SetUp();
86     Widget::InitParams params;
87     params.native_widget = native_widget_mac_;
88     // To control the lifetime without an actual window that must be closed,
89     // tests in this file need to use WIDGET_OWNS_NATIVE_WIDGET.
90     params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
91     native_widget_mac_->GetWidget()->Init(params);
92   }
94  protected:
95   scoped_ptr<Widget> widget_;
96   MockNativeWidgetMac* native_widget_mac_;  // Weak. Owned by |widget_|.
99 class BridgedNativeWidgetTest : public BridgedNativeWidgetTestBase {
100  public:
101   BridgedNativeWidgetTest();
102   virtual ~BridgedNativeWidgetTest();
104   // Install a textfield in the view hierarchy and make it the text input
105   // client.
106   void InstallTextField(const std::string& text);
108   // Returns the current text as std::string.
109   std::string GetText();
111   // testing::Test:
112   virtual void SetUp() OVERRIDE;
113   virtual void TearDown() OVERRIDE;
115  protected:
116   // TODO(tapted): Make this a EventCountView from widget_unittest.cc.
117   scoped_ptr<views::View> view_;
118   scoped_ptr<BridgedNativeWidget> bridge_;
119   BridgedContentView* ns_view_;  // Weak. Owned by bridge_.
121  private:
122   DISALLOW_COPY_AND_ASSIGN(BridgedNativeWidgetTest);
125 BridgedNativeWidgetTest::BridgedNativeWidgetTest() {
128 BridgedNativeWidgetTest::~BridgedNativeWidgetTest() {
131 void BridgedNativeWidgetTest::InstallTextField(const std::string& text) {
132   Textfield* textfield = new Textfield();
133   textfield->SetText(ASCIIToUTF16(text));
134   view_->AddChildView(textfield);
135   [ns_view_ setTextInputClient:textfield];
138 std::string BridgedNativeWidgetTest::GetText() {
139   NSRange range = NSMakeRange(0, NSUIntegerMax);
140   NSAttributedString* text =
141       [ns_view_ attributedSubstringForProposedRange:range actualRange:NULL];
142   return SysNSStringToUTF8([text string]);
145 void BridgedNativeWidgetTest::SetUp() {
146   BridgedNativeWidgetTestBase::SetUp();
148   view_.reset(new views::View);
149   base::scoped_nsobject<NSWindow> window([test_window() retain]);
151   EXPECT_FALSE([window delegate]);
152   bridge()->Init(window, Widget::InitParams());
154   // The delegate should exist before setting the root view.
155   EXPECT_TRUE([window delegate]);
156   bridge()->SetRootView(view_.get());
157   ns_view_ = bridge()->ns_view();
159   [test_window() makePretendKeyWindowAndSetFirstResponder:bridge()->ns_view()];
162 void BridgedNativeWidgetTest::TearDown() {
163   view_.reset();
164   BridgedNativeWidgetTestBase::TearDown();
167 // The TEST_VIEW macro expects the view it's testing to have a superview. In
168 // these tests, the NSView bridge is a contentView, at the root. These mimic
169 // what TEST_VIEW usually does.
170 TEST_F(BridgedNativeWidgetTest, BridgedNativeWidgetTest_TestViewAddRemove) {
171   base::scoped_nsobject<BridgedContentView> view([bridge()->ns_view() retain]);
172   EXPECT_NSEQ([test_window() contentView], view);
173   EXPECT_NSEQ(test_window(), [view window]);
175   // The superview of a contentView is an NSNextStepFrame.
176   EXPECT_TRUE([view superview]);
177   EXPECT_TRUE([view hostedView]);
179   // Ensure the tracking area to propagate mouseMoved: events to the RootView is
180   // installed.
181   EXPECT_EQ(1u, [[view trackingAreas] count]);
183   // Destroying the C++ bridge should remove references to any C++ objects in
184   // the ObjectiveC object, and remove it from the hierarchy.
185   bridge().reset();
186   EXPECT_FALSE([view hostedView]);
187   EXPECT_FALSE([view superview]);
188   EXPECT_FALSE([view window]);
189   EXPECT_EQ(0u, [[view trackingAreas] count]);
190   EXPECT_FALSE([test_window() contentView]);
191   EXPECT_FALSE([test_window() delegate]);
194 TEST_F(BridgedNativeWidgetTest, BridgedNativeWidgetTest_TestViewDisplay) {
195   [bridge()->ns_view() display];
198 // Test that resizing the window resizes the root view appropriately.
199 TEST_F(BridgedNativeWidgetTest, ViewSizeTracksWindow) {
200   const int kTestNewWidth = 400;
201   const int kTestNewHeight = 300;
203   // |test_window()| is borderless, so these should align.
204   NSSize window_size = [test_window() frame].size;
205   EXPECT_EQ(view_->width(), static_cast<int>(window_size.width));
206   EXPECT_EQ(view_->height(), static_cast<int>(window_size.height));
208   // Make sure a resize actually occurs.
209   EXPECT_NE(kTestNewWidth, view_->width());
210   EXPECT_NE(kTestNewHeight, view_->height());
212   [test_window() setFrame:NSMakeRect(0, 0, kTestNewWidth, kTestNewHeight)
213                   display:NO];
214   EXPECT_EQ(kTestNewWidth, view_->width());
215   EXPECT_EQ(kTestNewHeight, view_->height());
218 TEST_F(BridgedNativeWidgetTest, CreateInputMethodShouldNotReturnNull) {
219   scoped_ptr<views::InputMethod> input_method(bridge()->CreateInputMethod());
220   EXPECT_TRUE(input_method);
223 TEST_F(BridgedNativeWidgetTest, GetHostInputMethodShouldNotReturnNull) {
224   EXPECT_TRUE(bridge()->GetHostInputMethod());
227 // A simpler test harness for testing initialization flows.
228 typedef BridgedNativeWidgetTestBase BridgedNativeWidgetInitTest;
230 // Test that BridgedNativeWidget remains sane if Init() is never called.
231 TEST_F(BridgedNativeWidgetInitTest, InitNotCalled) {
232   EXPECT_FALSE(bridge()->ns_view());
233   EXPECT_FALSE(bridge()->ns_window());
234   bridge().reset();
237 // Test attaching to a parent window that is not a NativeWidgetMac. When the
238 // parent is a NativeWidgetMac, that is covered in widget_unittest.cc by
239 // WidgetOwnershipTest.Ownership_ViewsNativeWidgetOwnsWidget*.
240 TEST_F(BridgedNativeWidgetInitTest, ParentWindowNotNativeWidgetMac) {
241   Widget::InitParams params;
242   params.parent = [test_window() contentView];
243   EXPECT_EQ(0u, [[test_window() childWindows] count]);
245   base::scoped_nsobject<NSWindow> child_window(
246       [[NSWindow alloc] initWithContentRect:NSMakeRect(50, 50, 400, 300)
247                                   styleMask:NSBorderlessWindowMask
248                                     backing:NSBackingStoreBuffered
249                                       defer:NO]);
250   [child_window setReleasedWhenClosed:NO];  // Owned by scoped_nsobject.
252   EXPECT_FALSE([child_window parentWindow]);
253   bridge()->Init(child_window, params);
255   EXPECT_EQ(1u, [[test_window() childWindows] count]);
256   EXPECT_EQ(test_window(), [bridge()->ns_window() parentWindow]);
257   bridge().reset();
258   EXPECT_EQ(0u, [[test_window() childWindows] count]);
261 // Test getting complete string using text input protocol.
262 TEST_F(BridgedNativeWidgetTest, TextInput_GetCompleteString) {
263   const std::string kTestString = "foo bar baz";
264   InstallTextField(kTestString);
266   NSRange range = NSMakeRange(0, kTestString.size());
267   NSRange actual_range;
268   NSAttributedString* text =
269       [ns_view_ attributedSubstringForProposedRange:range
270                                         actualRange:&actual_range];
271   EXPECT_EQ(kTestString, SysNSStringToUTF8([text string]));
272   EXPECT_EQ_RANGE(range, actual_range);
275 // Test getting middle substring using text input protocol.
276 TEST_F(BridgedNativeWidgetTest, TextInput_GetMiddleSubstring) {
277   const std::string kTestString = "foo bar baz";
278   InstallTextField(kTestString);
280   NSRange range = NSMakeRange(4, 3);
281   NSRange actual_range;
282   NSAttributedString* text =
283       [ns_view_ attributedSubstringForProposedRange:range
284                                         actualRange:&actual_range];
285   EXPECT_EQ("bar", SysNSStringToUTF8([text string]));
286   EXPECT_EQ_RANGE(range, actual_range);
289 // Test getting ending substring using text input protocol.
290 TEST_F(BridgedNativeWidgetTest, TextInput_GetEndingSubstring) {
291   const std::string kTestString = "foo bar baz";
292   InstallTextField(kTestString);
294   NSRange range = NSMakeRange(8, 100);
295   NSRange actual_range;
296   NSAttributedString* text =
297       [ns_view_ attributedSubstringForProposedRange:range
298                                         actualRange:&actual_range];
299   EXPECT_EQ("baz", SysNSStringToUTF8([text string]));
300   EXPECT_EQ(range.location, actual_range.location);
301   EXPECT_EQ(3U, actual_range.length);
304 // Test getting empty substring using text input protocol.
305 TEST_F(BridgedNativeWidgetTest, TextInput_GetEmptySubstring) {
306   const std::string kTestString = "foo bar baz";
307   InstallTextField(kTestString);
309   NSRange range = EmptyRange();
310   NSRange actual_range;
311   NSAttributedString* text =
312       [ns_view_ attributedSubstringForProposedRange:range
313                                         actualRange:&actual_range];
314   EXPECT_EQ("", SysNSStringToUTF8([text string]));
315   EXPECT_EQ_RANGE(range, actual_range);
318 // Test inserting text using text input protocol.
319 TEST_F(BridgedNativeWidgetTest, TextInput_InsertText) {
320   const std::string kTestString = "foo";
321   InstallTextField(kTestString);
323   [ns_view_ insertText:SysUTF8ToNSString(kTestString)
324       replacementRange:EmptyRange()];
325   gfx::Range range(0, kTestString.size());
326   base::string16 text;
327   EXPECT_TRUE([ns_view_ textInputClient]->GetTextFromRange(range, &text));
328   EXPECT_EQ(ASCIIToUTF16(kTestString), text);
331 // Test replacing text using text input protocol.
332 TEST_F(BridgedNativeWidgetTest, TextInput_ReplaceText) {
333   const std::string kTestString = "foo bar";
334   InstallTextField(kTestString);
336   [ns_view_ insertText:@"baz" replacementRange:NSMakeRange(4, 3)];
337   EXPECT_EQ("foo baz", GetText());
340 // Test IME composition using text input protocol.
341 TEST_F(BridgedNativeWidgetTest, TextInput_Compose) {
342   const std::string kTestString = "foo ";
343   InstallTextField(kTestString);
345   EXPECT_FALSE([ns_view_ hasMarkedText]);
346   EXPECT_EQ_RANGE(EmptyRange(), [ns_view_ markedRange]);
348   // Start composition.
349   NSString* compositionText = @"bar";
350   NSUInteger compositionLength = [compositionText length];
351   [ns_view_ setMarkedText:compositionText
352             selectedRange:NSMakeRange(0, 2)
353          replacementRange:EmptyRange()];
354   EXPECT_TRUE([ns_view_ hasMarkedText]);
355   EXPECT_EQ_RANGE(NSMakeRange(kTestString.size(), compositionLength),
356                   [ns_view_ markedRange]);
357   EXPECT_EQ_RANGE(NSMakeRange(kTestString.size(), 2), [ns_view_ selectedRange]);
359   // Confirm composition.
360   [ns_view_ unmarkText];
361   EXPECT_FALSE([ns_view_ hasMarkedText]);
362   EXPECT_EQ_RANGE(EmptyRange(), [ns_view_ markedRange]);
363   EXPECT_EQ("foo bar", GetText());
364   EXPECT_EQ_RANGE(NSMakeRange(GetText().size(), 0), [ns_view_ selectedRange]);
367 // Test moving the caret left and right using text input protocol.
368 TEST_F(BridgedNativeWidgetTest, TextInput_MoveLeftRight) {
369   InstallTextField("foo");
370   EXPECT_EQ_RANGE(NSMakeRange(3, 0), [ns_view_ selectedRange]);
372   // Move right not allowed, out of range.
373   [ns_view_ doCommandBySelector:@selector(moveRight:)];
374   EXPECT_EQ_RANGE(NSMakeRange(3, 0), [ns_view_ selectedRange]);
376   // Move left.
377   [ns_view_ doCommandBySelector:@selector(moveLeft:)];
378   EXPECT_EQ_RANGE(NSMakeRange(2, 0), [ns_view_ selectedRange]);
380   // Move right.
381   [ns_view_ doCommandBySelector:@selector(moveRight:)];
382   EXPECT_EQ_RANGE(NSMakeRange(3, 0), [ns_view_ selectedRange]);
385 // Test backward delete using text input protocol.
386 TEST_F(BridgedNativeWidgetTest, TextInput_DeleteBackward) {
387   InstallTextField("a");
388   EXPECT_EQ_RANGE(NSMakeRange(1, 0), [ns_view_ selectedRange]);
390   // Delete one character.
391   [ns_view_ doCommandBySelector:@selector(deleteBackward:)];
392   EXPECT_EQ("", GetText());
393   EXPECT_EQ_RANGE(NSMakeRange(0, 0), [ns_view_ selectedRange]);
395   // Try to delete again on an empty string.
396   [ns_view_ doCommandBySelector:@selector(deleteBackward:)];
397   EXPECT_EQ("", GetText());
398   EXPECT_EQ_RANGE(NSMakeRange(0, 0), [ns_view_ selectedRange]);
401 // Test forward delete using text input protocol.
402 TEST_F(BridgedNativeWidgetTest, TextInput_DeleteForward) {
403   InstallTextField("a");
404   EXPECT_EQ_RANGE(NSMakeRange(1, 0), [ns_view_ selectedRange]);
406   // At the end of the string, can't delete forward.
407   [ns_view_ doCommandBySelector:@selector(deleteForward:)];
408   EXPECT_EQ("a", GetText());
409   EXPECT_EQ_RANGE(NSMakeRange(1, 0), [ns_view_ selectedRange]);
411   // Should succeed after moving left first.
412   [ns_view_ doCommandBySelector:@selector(moveLeft:)];
413   [ns_view_ doCommandBySelector:@selector(deleteForward:)];
414   EXPECT_EQ("", GetText());
415   EXPECT_EQ_RANGE(NSMakeRange(0, 0), [ns_view_ selectedRange]);
418 }  // namespace test
419 }  // namespace views