Index: ui/views/cocoa/bridged_native_widget_unittest.mm |
diff --git a/ui/views/cocoa/bridged_native_widget_unittest.mm b/ui/views/cocoa/bridged_native_widget_unittest.mm |
index 56f911cb2bc6fb1821211455b6f3d7015f43049e..d289af521760fda2615f267ecd6be338ffe9dae0 100644 |
--- a/ui/views/cocoa/bridged_native_widget_unittest.mm |
+++ b/ui/views/cocoa/bridged_native_widget_unittest.mm |
@@ -18,6 +18,7 @@ |
#import "ui/base/cocoa/window_size_constants.h" |
#include "ui/base/ime/input_method.h" |
#import "ui/gfx/test/ui_cocoa_test_helper.h" |
+#import "ui/gfx/mac/coordinate_conversion.h" |
#import "ui/views/cocoa/bridged_content_view.h" |
#import "ui/views/cocoa/native_widget_mac_nswindow.h" |
#import "ui/views/cocoa/views_nswindow_delegate.h" |
@@ -44,6 +45,54 @@ NSRange EmptyRange() { |
return NSMakeRange(NSNotFound, 0); |
} |
+// Sets |composition_text| as the composition text with caret placed at |
+// |caret_pos| and updates |caret_range|. |
+void SetCompositionText(ui::TextInputClient* client, |
+ const base::string16& composition_text, |
+ const int caret_pos, |
+ NSRange* caret_range) { |
+ ui::CompositionText composition; |
+ composition.selection = gfx::Range(caret_pos); |
+ composition.text = composition_text; |
+ client->SetCompositionText(composition); |
+ if (caret_range) |
+ *caret_range = NSMakeRange(caret_pos, 0); |
+} |
+ |
+// Returns a zero width rectangle corresponding to current caret position. |
+gfx::Rect GetCaretBounds(const ui::TextInputClient* client) { |
+ gfx::Rect caret_bounds = client->GetCaretBounds(); |
+ caret_bounds.set_width(0); |
+ return caret_bounds; |
+} |
+ |
+// Returns a zero width rectangle corresponding to caret bounds when it's placed |
+// at |caret_pos| and updates |caret_range|. |
+gfx::Rect GetCaretBoundsForPosition(ui::TextInputClient* client, |
+ const base::string16& composition_text, |
+ const int caret_pos, |
+ NSRange* caret_range) { |
+ SetCompositionText(client, composition_text, caret_pos, caret_range); |
+ return GetCaretBounds(client); |
+} |
+ |
+// Returns the expected boundary rectangle for characters of |composition_text| |
+// within the |query_range|. |
+gfx::Rect GetExpectedBoundsForRange(ui::TextInputClient* client, |
+ const base::string16& composition_text, |
+ NSRange query_range) { |
+ gfx::Rect left_caret = GetCaretBoundsForPosition( |
+ client, composition_text, query_range.location, nullptr); |
+ gfx::Rect right_caret = GetCaretBoundsForPosition( |
+ client, composition_text, query_range.location + query_range.length, |
+ nullptr); |
+ |
+ // The expected bounds correspond to the area between the left and right caret |
+ // positions. |
+ return gfx::Rect(left_caret.x(), left_caret.y(), |
+ right_caret.x() - left_caret.x(), left_caret.height()); |
+} |
+ |
} // namespace |
// Class to override -[NSWindow toggleFullScreen:] to a no-op. This simulates |
@@ -129,6 +178,8 @@ class BridgedNativeWidgetTestBase : public ui::CocoaTest { |
// Opacity defaults to "infer" which is usually updated by ViewsDelegate. |
init_params_.opacity = Widget::InitParams::OPAQUE_WINDOW; |
+ init_params_.bounds = gfx::Rect(100, 100, 100, 100); |
+ |
native_widget_mac_->GetWidget()->Init(init_params_); |
} |
@@ -185,6 +236,7 @@ void BridgedNativeWidgetTest::InstallTextField( |
textfield->SetTextInputType(text_input_type); |
view_->RemoveAllChildViews(true); |
view_->AddChildView(textfield); |
+ textfield->SetBoundsRect(init_params_.bounds); |
// Request focus so the InputMethod can dispatch events to the RootView, and |
// have them delivered to the textfield. Note that focusing a textfield |
@@ -533,6 +585,96 @@ TEST_F(BridgedNativeWidgetTest, TextInput_DeleteForward) { |
EXPECT_EQ_RANGE(NSMakeRange(0, 0), [ns_view_ selectedRange]); |
} |
+// Test firstRectForCharacterRange:actualRange for cases where query range is |
+// empty or outside composition range. |
+TEST_F(BridgedNativeWidgetTest, TextInput_FirstRectForCharacterRange_Caret) { |
+ InstallTextField(""); |
+ ui::TextInputClient* client = [ns_view_ textInputClient]; |
+ |
+ // No composition. Ensure bounds and range corresponding to the current caret |
+ // position are returned. |
+ // Initially selection range will be [0, 0]. |
+ NSRange caret_range = NSMakeRange(0, 0); |
+ NSRange query_range = NSMakeRange(1, 1); |
+ NSRange actual_range; |
+ NSRect rect = [ns_view_ firstRectForCharacterRange:query_range |
+ actualRange:&actual_range]; |
+ EXPECT_EQ(GetCaretBounds(client), gfx::ScreenRectFromNSRect(rect)); |
+ EXPECT_EQ_RANGE(caret_range, actual_range); |
+ |
+ // Set composition with caret before second character ('e'). |
+ const base::string16 test_string = base::ASCIIToUTF16("test_str"); |
+ const size_t kTextLength = 8; |
+ SetCompositionText(client, test_string, 1, &caret_range); |
+ |
+ // Test bounds returned for empty ranges within composition range. As per |
+ // Apple's documentation for firstRectForCharacterRange:actualRange:, for an |
+ // empty query range, the returned rectangle should coincide with the |
+ // insertion point and have zero width. However in our implementation, if the |
+ // empty query range lies within the composition range, we return a zero width |
+ // rectangle corresponding to the query range location. |
+ |
+ // Test bounds returned for empty range before second character ('e') are same |
+ // as caret bounds when placed before second character. |
+ query_range = NSMakeRange(1, 0); |
+ rect = [ns_view_ firstRectForCharacterRange:query_range |
+ actualRange:&actual_range]; |
+ EXPECT_EQ(GetCaretBoundsForPosition(client, test_string, 1, &caret_range), |
+ gfx::ScreenRectFromNSRect(rect)); |
+ EXPECT_EQ_RANGE(query_range, actual_range); |
+ |
+ // Test bounds returned for empty range after the composition text are same as |
+ // caret bounds when placed after the composition text. |
+ query_range = NSMakeRange(kTextLength, 0); |
+ rect = [ns_view_ firstRectForCharacterRange:query_range |
+ actualRange:&actual_range]; |
+ EXPECT_NE(GetCaretBoundsForPosition(client, test_string, 1, &caret_range), |
+ gfx::ScreenRectFromNSRect(rect)); |
+ EXPECT_EQ( |
+ GetCaretBoundsForPosition(client, test_string, kTextLength, &caret_range), |
+ gfx::ScreenRectFromNSRect(rect)); |
+ EXPECT_EQ_RANGE(query_range, actual_range); |
+ |
+ // Query outside composition range. Ensure bounds and range corresponding to |
+ // the current caret position are returned. |
+ query_range = NSMakeRange(kTextLength + 1, 0); |
+ rect = [ns_view_ firstRectForCharacterRange:query_range |
+ actualRange:&actual_range]; |
+ EXPECT_EQ(GetCaretBounds(client), gfx::ScreenRectFromNSRect(rect)); |
+ EXPECT_EQ_RANGE(caret_range, actual_range); |
+ |
+ // Make sure not crashing by passing null pointer instead of actualRange. |
+ rect = [ns_view_ firstRectForCharacterRange:query_range actualRange:nullptr]; |
+} |
+ |
+// Test firstRectForCharacterRange:actualRange for non-empty query ranges within |
+// the composition range. |
+TEST_F(BridgedNativeWidgetTest, TextInput_FirstRectForCharacterRange) { |
+ InstallTextField(""); |
+ ui::TextInputClient* client = [ns_view_ textInputClient]; |
+ |
+ const base::string16 kTestString = base::ASCIIToUTF16("test_str"); |
+ const size_t kTextLength = 8; |
+ SetCompositionText(client, kTestString, 1, nullptr); |
+ |
+ // Query bounds for the whole composition string. |
+ NSRange query_range = NSMakeRange(0, kTextLength); |
+ NSRange actual_range; |
+ NSRect rect = [ns_view_ firstRectForCharacterRange:query_range |
+ actualRange:&actual_range]; |
+ EXPECT_EQ(GetExpectedBoundsForRange(client, kTestString, query_range), |
+ gfx::ScreenRectFromNSRect(rect)); |
+ EXPECT_EQ_RANGE(query_range, actual_range); |
+ |
+ // Query bounds for the substring "est_". |
+ query_range = NSMakeRange(1, 4); |
+ rect = [ns_view_ firstRectForCharacterRange:query_range |
+ actualRange:&actual_range]; |
+ EXPECT_EQ(GetExpectedBoundsForRange(client, kTestString, query_range), |
+ gfx::ScreenRectFromNSRect(rect)); |
+ EXPECT_EQ_RANGE(query_range, actual_range); |
+} |
+ |
typedef BridgedNativeWidgetTestBase BridgedNativeWidgetSimulateFullscreenTest; |
// Simulate the notifications that AppKit would send out if a fullscreen |