1 // Copyright (c) 2011 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 <Cocoa/Cocoa.h> 6 7 #import "base/mac/cocoa_protocols.h" 8 #include "base/memory/scoped_nsobject.h" 9 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field.h" 10 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_cell.h" 11 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.h" 12 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_unittest_helper.h" 13 #import "chrome/browser/ui/cocoa/location_bar/location_bar_decoration.h" 14 #import "chrome/browser/ui/cocoa/cocoa_test_helper.h" 15 #include "grit/theme_resources.h" 16 #include "testing/gmock/include/gmock/gmock.h" 17 #include "testing/gtest/include/gtest/gtest.h" 18 #import "testing/gtest_mac.h" 19 #include "testing/platform_test.h" 20 #include "ui/base/resource/resource_bundle.h" 21 22 using ::testing::A; 23 using ::testing::InSequence; 24 using ::testing::Return; 25 using ::testing::ReturnArg; 26 using ::testing::StrictMock; 27 using ::testing::_; 28 29 namespace { 30 31 class MockDecoration : public LocationBarDecoration { 32 public: 33 virtual CGFloat GetWidthForSpace(CGFloat width) { return 20.0; } 34 35 virtual void DrawInFrame(NSRect frame, NSView* control_view) { ; } 36 MOCK_METHOD0(AcceptsMousePress, bool()); 37 MOCK_METHOD1(OnMousePressed, bool(NSRect frame)); 38 MOCK_METHOD0(GetMenu, NSMenu*()); 39 }; 40 41 // Mock up an incrementing event number. 42 NSUInteger eventNumber = 0; 43 44 // Create an event of the indicated |type| at |point| within |view|. 45 // TODO(shess): Would be nice to have a MockApplication which provided 46 // nifty accessors to create these things and inject them. It could 47 // even provide functions for "Click and drag mouse from point A to 48 // point B". 49 NSEvent* Event(NSView* view, const NSPoint point, const NSEventType type, 50 const NSUInteger clickCount) { 51 NSWindow* window([view window]); 52 const NSPoint locationInWindow([view convertPoint:point toView:nil]); 53 const NSPoint location([window convertBaseToScreen:locationInWindow]); 54 return [NSEvent mouseEventWithType:type 55 location:location 56 modifierFlags:0 57 timestamp:0 58 windowNumber:[window windowNumber] 59 context:nil 60 eventNumber:eventNumber++ 61 clickCount:clickCount 62 pressure:0.0]; 63 } 64 NSEvent* Event(NSView* view, const NSPoint point, const NSEventType type) { 65 return Event(view, point, type, 1); 66 } 67 68 // Width of the field so that we don't have to ask |field_| for it all 69 // the time. 70 static const CGFloat kWidth(300.0); 71 72 class AutocompleteTextFieldTest : public CocoaTest { 73 public: 74 AutocompleteTextFieldTest() { 75 // Make sure this is wide enough to play games with the cell 76 // decorations. 77 NSRect frame = NSMakeRect(0, 0, kWidth, 30); 78 scoped_nsobject<AutocompleteTextField> field( 79 [[AutocompleteTextField alloc] initWithFrame:frame]); 80 field_ = field.get(); 81 [field_ setStringValue:@"Test test"]; 82 [[test_window() contentView] addSubview:field_]; 83 84 AutocompleteTextFieldCell* cell = [field_ cell]; 85 [cell clearDecorations]; 86 87 mock_left_decoration_.SetVisible(false); 88 [cell addLeftDecoration:&mock_left_decoration_]; 89 90 mock_right_decoration_.SetVisible(false); 91 [cell addRightDecoration:&mock_right_decoration_]; 92 93 window_delegate_.reset( 94 [[AutocompleteTextFieldWindowTestDelegate alloc] init]); 95 [test_window() setDelegate:window_delegate_.get()]; 96 } 97 98 NSEvent* KeyDownEventWithFlags(NSUInteger flags) { 99 return [NSEvent keyEventWithType:NSKeyDown 100 location:NSZeroPoint 101 modifierFlags:flags 102 timestamp:0.0 103 windowNumber:[test_window() windowNumber] 104 context:nil 105 characters:@"a" 106 charactersIgnoringModifiers:@"a" 107 isARepeat:NO 108 keyCode:'a']; 109 } 110 111 // Helper to return the field-editor frame being used w/in |field_|. 112 NSRect EditorFrame() { 113 EXPECT_TRUE([field_ currentEditor]); 114 EXPECT_EQ([[field_ subviews] count], 1U); 115 if ([[field_ subviews] count] > 0) { 116 return [[[field_ subviews] objectAtIndex:0] frame]; 117 } else { 118 // Return something which won't work so the caller can soldier 119 // on. 120 return NSZeroRect; 121 } 122 } 123 124 AutocompleteTextField* field_; 125 MockDecoration mock_left_decoration_; 126 MockDecoration mock_right_decoration_; 127 scoped_nsobject<AutocompleteTextFieldWindowTestDelegate> window_delegate_; 128 }; 129 130 TEST_VIEW(AutocompleteTextFieldTest, field_); 131 132 // Base class for testing AutocompleteTextFieldObserver messages. 133 class AutocompleteTextFieldObserverTest : public AutocompleteTextFieldTest { 134 public: 135 virtual void SetUp() { 136 AutocompleteTextFieldTest::SetUp(); 137 [field_ setObserver:&field_observer_]; 138 } 139 140 virtual void TearDown() { 141 // Clear the observer so that we don't show output for 142 // uninteresting messages to the mock (for instance, if |field_| has 143 // focus at the end of the test). 144 [field_ setObserver:NULL]; 145 146 AutocompleteTextFieldTest::TearDown(); 147 } 148 149 StrictMock<MockAutocompleteTextFieldObserver> field_observer_; 150 }; 151 152 // Test that we have the right cell class. 153 TEST_F(AutocompleteTextFieldTest, CellClass) { 154 EXPECT_TRUE([[field_ cell] isKindOfClass:[AutocompleteTextFieldCell class]]); 155 } 156 157 // Test that becoming first responder sets things up correctly. 158 TEST_F(AutocompleteTextFieldTest, FirstResponder) { 159 EXPECT_EQ(nil, [field_ currentEditor]); 160 EXPECT_EQ([[field_ subviews] count], 0U); 161 [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; 162 EXPECT_FALSE(nil == [field_ currentEditor]); 163 EXPECT_EQ([[field_ subviews] count], 1U); 164 EXPECT_TRUE([[field_ currentEditor] isDescendantOf:field_]); 165 166 // Check that the window delegate is providing the right editor. 167 Class c = [AutocompleteTextFieldEditor class]; 168 EXPECT_TRUE([[field_ currentEditor] isKindOfClass:c]); 169 } 170 171 TEST_F(AutocompleteTextFieldTest, AvailableDecorationWidth) { 172 // A fudge factor to account for how much space the border takes up. 173 // The test shouldn't be too dependent on the field's internals, but 174 // it also shouldn't let deranged cases fall through the cracks 175 // (like nothing available with no text, or everything available 176 // with some text). 177 const CGFloat kBorderWidth = 20.0; 178 179 // With no contents, almost the entire width is available for 180 // decorations. 181 [field_ setStringValue:@""]; 182 CGFloat availableWidth = [field_ availableDecorationWidth]; 183 EXPECT_LE(availableWidth, kWidth); 184 EXPECT_GT(availableWidth, kWidth - kBorderWidth); 185 186 // With minor contents, most of the remaining width is available for 187 // decorations. 188 NSDictionary* attributes = 189 [NSDictionary dictionaryWithObject:[field_ font] 190 forKey:NSFontAttributeName]; 191 NSString* string = @"Hello world"; 192 const NSSize size([string sizeWithAttributes:attributes]); 193 [field_ setStringValue:string]; 194 availableWidth = [field_ availableDecorationWidth]; 195 EXPECT_LE(availableWidth, kWidth - size.width); 196 EXPECT_GT(availableWidth, kWidth - size.width - kBorderWidth); 197 198 // With huge contents, nothing at all is left for decorations. 199 string = @"A long string which is surely wider than field_ can hold."; 200 [field_ setStringValue:string]; 201 availableWidth = [field_ availableDecorationWidth]; 202 EXPECT_LT(availableWidth, 0.0); 203 } 204 205 // Test drawing, mostly to ensure nothing leaks or crashes. 206 TEST_F(AutocompleteTextFieldTest, Display) { 207 [field_ display]; 208 209 // Test focussed drawing. 210 [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; 211 [field_ display]; 212 [test_window() clearPretendKeyWindowAndFirstResponder]; 213 } 214 215 TEST_F(AutocompleteTextFieldObserverTest, FlagsChanged) { 216 InSequence dummy; // Call mock in exactly the order specified. 217 218 // Test without Control key down, but some other modifier down. 219 EXPECT_CALL(field_observer_, OnControlKeyChanged(false)); 220 [field_ flagsChanged:KeyDownEventWithFlags(NSShiftKeyMask)]; 221 222 // Test with Control key down. 223 EXPECT_CALL(field_observer_, OnControlKeyChanged(true)); 224 [field_ flagsChanged:KeyDownEventWithFlags(NSControlKeyMask)]; 225 } 226 227 // This test is here rather than in the editor's tests because the 228 // field catches -flagsChanged: because it's on the responder chain, 229 // the field editor doesn't implement it. 230 TEST_F(AutocompleteTextFieldObserverTest, FieldEditorFlagsChanged) { 231 // Many of these methods try to change the selection. 232 EXPECT_CALL(field_observer_, SelectionRangeForProposedRange(A<NSRange>())) 233 .WillRepeatedly(ReturnArg<0>()); 234 235 InSequence dummy; // Call mock in exactly the order specified. 236 EXPECT_CALL(field_observer_, OnSetFocus(false)); 237 [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; 238 NSResponder* firstResponder = [[field_ window] firstResponder]; 239 EXPECT_EQ(firstResponder, [field_ currentEditor]); 240 241 // Test without Control key down, but some other modifier down. 242 EXPECT_CALL(field_observer_, OnControlKeyChanged(false)); 243 [firstResponder flagsChanged:KeyDownEventWithFlags(NSShiftKeyMask)]; 244 245 // Test with Control key down. 246 EXPECT_CALL(field_observer_, OnControlKeyChanged(true)); 247 [firstResponder flagsChanged:KeyDownEventWithFlags(NSControlKeyMask)]; 248 } 249 250 // Frame size changes are propagated to |observer_|. 251 TEST_F(AutocompleteTextFieldObserverTest, FrameChanged) { 252 EXPECT_CALL(field_observer_, OnFrameChanged()); 253 NSRect frame = [field_ frame]; 254 frame.size.width += 10.0; 255 [field_ setFrame:frame]; 256 } 257 258 // Test that the field editor gets the same bounds when focus is 259 // delivered by the standard focusing machinery, or by 260 // -resetFieldEditorFrameIfNeeded. 261 TEST_F(AutocompleteTextFieldTest, ResetFieldEditorBase) { 262 // Capture the editor frame resulting from the standard focus 263 // machinery. 264 [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; 265 const NSRect baseEditorFrame = EditorFrame(); 266 267 // A decoration should result in a strictly smaller editor frame. 268 mock_left_decoration_.SetVisible(true); 269 [field_ resetFieldEditorFrameIfNeeded]; 270 EXPECT_FALSE(NSEqualRects(baseEditorFrame, EditorFrame())); 271 EXPECT_TRUE(NSContainsRect(baseEditorFrame, EditorFrame())); 272 273 // Removing the decoration and using -resetFieldEditorFrameIfNeeded 274 // should result in the same frame as the standard focus machinery. 275 mock_left_decoration_.SetVisible(false); 276 [field_ resetFieldEditorFrameIfNeeded]; 277 EXPECT_TRUE(NSEqualRects(baseEditorFrame, EditorFrame())); 278 } 279 280 // Test that the field editor gets the same bounds when focus is 281 // delivered by the standard focusing machinery, or by 282 // -resetFieldEditorFrameIfNeeded, this time with a decoration 283 // pre-loaded. 284 TEST_F(AutocompleteTextFieldTest, ResetFieldEditorWithDecoration) { 285 AutocompleteTextFieldCell* cell = [field_ cell]; 286 287 // Make sure decoration isn't already visible, then make it visible. 288 EXPECT_TRUE(NSIsEmptyRect([cell frameForDecoration:&mock_left_decoration_ 289 inFrame:[field_ bounds]])); 290 mock_left_decoration_.SetVisible(true); 291 EXPECT_FALSE(NSIsEmptyRect([cell frameForDecoration:&mock_left_decoration_ 292 inFrame:[field_ bounds]])); 293 294 // Capture the editor frame resulting from the standard focus 295 // machinery. 296 297 [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; 298 const NSRect baseEditorFrame = EditorFrame(); 299 300 // When the decoration is not visible the frame should be strictly larger. 301 mock_left_decoration_.SetVisible(false); 302 EXPECT_TRUE(NSIsEmptyRect([cell frameForDecoration:&mock_left_decoration_ 303 inFrame:[field_ bounds]])); 304 [field_ resetFieldEditorFrameIfNeeded]; 305 EXPECT_FALSE(NSEqualRects(baseEditorFrame, EditorFrame())); 306 EXPECT_TRUE(NSContainsRect(EditorFrame(), baseEditorFrame)); 307 308 // When the decoration is visible, -resetFieldEditorFrameIfNeeded 309 // should result in the same frame as the standard focus machinery. 310 mock_left_decoration_.SetVisible(true); 311 EXPECT_FALSE(NSIsEmptyRect([cell frameForDecoration:&mock_left_decoration_ 312 inFrame:[field_ bounds]])); 313 314 [field_ resetFieldEditorFrameIfNeeded]; 315 EXPECT_TRUE(NSEqualRects(baseEditorFrame, EditorFrame())); 316 } 317 318 // Test that resetting the field editor bounds does not cause untoward 319 // messages to the field's observer. 320 TEST_F(AutocompleteTextFieldObserverTest, ResetFieldEditorContinuesEditing) { 321 // Many of these methods try to change the selection. 322 EXPECT_CALL(field_observer_, SelectionRangeForProposedRange(A<NSRange>())) 323 .WillRepeatedly(ReturnArg<0>()); 324 325 EXPECT_CALL(field_observer_, OnSetFocus(false)); 326 // Becoming first responder doesn't begin editing. 327 [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; 328 const NSRect baseEditorFrame = EditorFrame(); 329 NSTextView* editor = static_cast<NSTextView*>([field_ currentEditor]); 330 EXPECT_TRUE(nil != editor); 331 332 // This should begin editing and indicate a change. 333 EXPECT_CALL(field_observer_, OnDidBeginEditing()); 334 EXPECT_CALL(field_observer_, OnBeforeChange()); 335 EXPECT_CALL(field_observer_, OnDidChange()); 336 [editor shouldChangeTextInRange:NSMakeRange(0, 0) replacementString:@""]; 337 [editor didChangeText]; 338 339 // No messages to |field_observer_| when the frame actually changes. 340 mock_left_decoration_.SetVisible(true); 341 [field_ resetFieldEditorFrameIfNeeded]; 342 EXPECT_FALSE(NSEqualRects(baseEditorFrame, EditorFrame())); 343 } 344 345 // Clicking in a right-hand decoration which does not handle the mouse 346 // puts the caret rightmost. 347 TEST_F(AutocompleteTextFieldTest, ClickRightDecorationPutsCaretRightmost) { 348 // Decoration does not handle the mouse event, so the cell should 349 // process it. Called at least once. 350 EXPECT_CALL(mock_right_decoration_, AcceptsMousePress()) 351 .WillOnce(Return(false)) 352 .WillRepeatedly(Return(false)); 353 354 // Set the decoration before becoming responder. 355 EXPECT_FALSE([field_ currentEditor]); 356 mock_right_decoration_.SetVisible(true); 357 358 // Make first responder should select all. 359 [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; 360 EXPECT_TRUE([field_ currentEditor]); 361 const NSRange allRange = NSMakeRange(0, [[field_ stringValue] length]); 362 EXPECT_TRUE(NSEqualRanges(allRange, [[field_ currentEditor] selectedRange])); 363 364 // Generate a click on the decoration. 365 AutocompleteTextFieldCell* cell = [field_ cell]; 366 const NSRect bounds = [field_ bounds]; 367 const NSRect iconFrame = 368 [cell frameForDecoration:&mock_right_decoration_ inFrame:bounds]; 369 const NSPoint point = NSMakePoint(NSMidX(iconFrame), NSMidY(iconFrame)); 370 NSEvent* downEvent = Event(field_, point, NSLeftMouseDown); 371 NSEvent* upEvent = Event(field_, point, NSLeftMouseUp); 372 [NSApp postEvent:upEvent atStart:YES]; 373 [field_ mouseDown:downEvent]; 374 375 // Selection should be a right-hand-side caret. 376 EXPECT_TRUE(NSEqualRanges(NSMakeRange([[field_ stringValue] length], 0), 377 [[field_ currentEditor] selectedRange])); 378 } 379 380 // Clicking in a left-side decoration which doesn't handle the event 381 // puts the selection in the leftmost position. 382 TEST_F(AutocompleteTextFieldTest, ClickLeftDecorationPutsCaretLeftmost) { 383 // Decoration does not handle the mouse event, so the cell should 384 // process it. Called at least once. 385 EXPECT_CALL(mock_left_decoration_, AcceptsMousePress()) 386 .WillOnce(Return(false)) 387 .WillRepeatedly(Return(false)); 388 389 // Set the decoration before becoming responder. 390 EXPECT_FALSE([field_ currentEditor]); 391 mock_left_decoration_.SetVisible(true); 392 393 // Make first responder should select all. 394 [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; 395 EXPECT_TRUE([field_ currentEditor]); 396 const NSRange allRange = NSMakeRange(0, [[field_ stringValue] length]); 397 EXPECT_TRUE(NSEqualRanges(allRange, [[field_ currentEditor] selectedRange])); 398 399 // Generate a click on the decoration. 400 AutocompleteTextFieldCell* cell = [field_ cell]; 401 const NSRect bounds = [field_ bounds]; 402 const NSRect iconFrame = 403 [cell frameForDecoration:&mock_left_decoration_ inFrame:bounds]; 404 const NSPoint point = NSMakePoint(NSMidX(iconFrame), NSMidY(iconFrame)); 405 NSEvent* downEvent = Event(field_, point, NSLeftMouseDown); 406 NSEvent* upEvent = Event(field_, point, NSLeftMouseUp); 407 [NSApp postEvent:upEvent atStart:YES]; 408 [field_ mouseDown:downEvent]; 409 410 // Selection should be a left-hand-side caret. 411 EXPECT_TRUE(NSEqualRanges(NSMakeRange(0, 0), 412 [[field_ currentEditor] selectedRange])); 413 } 414 415 // Clicks not in the text area or the cell's decorations fall through 416 // to the editor. 417 TEST_F(AutocompleteTextFieldTest, ClickBorderSelectsAll) { 418 // Can't rely on the window machinery to make us first responder, 419 // here. 420 [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; 421 EXPECT_TRUE([field_ currentEditor]); 422 423 const NSPoint point(NSMakePoint(20.0, 1.0)); 424 NSEvent* downEvent(Event(field_, point, NSLeftMouseDown)); 425 NSEvent* upEvent(Event(field_, point, NSLeftMouseUp)); 426 [NSApp postEvent:upEvent atStart:YES]; 427 [field_ mouseDown:downEvent]; 428 429 // Clicking in the narrow border area around a Cocoa NSTextField 430 // does a select-all. Regardless of whether this is a good call, it 431 // works as a test that things get passed down to the editor. 432 const NSRange selectedRange([[field_ currentEditor] selectedRange]); 433 EXPECT_EQ(selectedRange.location, 0U); 434 EXPECT_EQ(selectedRange.length, [[field_ stringValue] length]); 435 } 436 437 // Single-click with no drag should setup a field editor and 438 // select all. 439 TEST_F(AutocompleteTextFieldTest, ClickSelectsAll) { 440 EXPECT_FALSE([field_ currentEditor]); 441 442 const NSPoint point = NSMakePoint(20.0, NSMidY([field_ bounds])); 443 NSEvent* downEvent(Event(field_, point, NSLeftMouseDown)); 444 NSEvent* upEvent(Event(field_, point, NSLeftMouseUp)); 445 [NSApp postEvent:upEvent atStart:YES]; 446 [field_ mouseDown:downEvent]; 447 EXPECT_TRUE([field_ currentEditor]); 448 const NSRange selectedRange([[field_ currentEditor] selectedRange]); 449 EXPECT_EQ(selectedRange.location, 0U); 450 EXPECT_EQ(selectedRange.length, [[field_ stringValue] length]); 451 } 452 453 // Click-drag selects text, not select all. 454 TEST_F(AutocompleteTextFieldTest, ClickDragSelectsText) { 455 EXPECT_FALSE([field_ currentEditor]); 456 457 NSEvent* downEvent(Event(field_, NSMakePoint(20.0, 5.0), NSLeftMouseDown)); 458 NSEvent* upEvent(Event(field_, NSMakePoint(0.0, 5.0), NSLeftMouseUp)); 459 [NSApp postEvent:upEvent atStart:YES]; 460 [field_ mouseDown:downEvent]; 461 EXPECT_TRUE([field_ currentEditor]); 462 463 // Expect this to have selected a prefix of the content. Mostly 464 // just don't want the select-all behavior. 465 const NSRange selectedRange([[field_ currentEditor] selectedRange]); 466 EXPECT_EQ(selectedRange.location, 0U); 467 EXPECT_LT(selectedRange.length, [[field_ stringValue] length]); 468 } 469 470 // TODO(shess): Test that click/pause/click allows cursor placement. 471 // In this case the first click goes to the field, but the second 472 // click goes to the field editor, so the current testing pattern 473 // can't work. What really needs to happen is to push through the 474 // NSWindow event machinery so that we can say "two independent clicks 475 // at the same location have the right effect". Once that is done, it 476 // might make sense to revise the other tests to use the same 477 // machinery. 478 479 // Double-click selects word, not select all. 480 TEST_F(AutocompleteTextFieldTest, DoubleClickSelectsWord) { 481 EXPECT_FALSE([field_ currentEditor]); 482 483 const NSPoint point = NSMakePoint(20.0, NSMidY([field_ bounds])); 484 NSEvent* downEvent(Event(field_, point, NSLeftMouseDown, 1)); 485 NSEvent* upEvent(Event(field_, point, NSLeftMouseUp, 1)); 486 NSEvent* downEvent2(Event(field_, point, NSLeftMouseDown, 2)); 487 NSEvent* upEvent2(Event(field_, point, NSLeftMouseUp, 2)); 488 [NSApp postEvent:upEvent atStart:YES]; 489 [field_ mouseDown:downEvent]; 490 [NSApp postEvent:upEvent2 atStart:YES]; 491 [field_ mouseDown:downEvent2]; 492 EXPECT_TRUE([field_ currentEditor]); 493 494 // Selected the first word. 495 const NSRange selectedRange([[field_ currentEditor] selectedRange]); 496 const NSRange spaceRange([[field_ stringValue] rangeOfString:@" "]); 497 EXPECT_GT(spaceRange.location, 0U); 498 EXPECT_LT(spaceRange.length, [[field_ stringValue] length]); 499 EXPECT_EQ(selectedRange.location, 0U); 500 EXPECT_EQ(selectedRange.length, spaceRange.location); 501 } 502 503 TEST_F(AutocompleteTextFieldTest, TripleClickSelectsAll) { 504 EXPECT_FALSE([field_ currentEditor]); 505 506 const NSPoint point(NSMakePoint(20.0, 5.0)); 507 NSEvent* downEvent(Event(field_, point, NSLeftMouseDown, 1)); 508 NSEvent* upEvent(Event(field_, point, NSLeftMouseUp, 1)); 509 NSEvent* downEvent2(Event(field_, point, NSLeftMouseDown, 2)); 510 NSEvent* upEvent2(Event(field_, point, NSLeftMouseUp, 2)); 511 NSEvent* downEvent3(Event(field_, point, NSLeftMouseDown, 3)); 512 NSEvent* upEvent3(Event(field_, point, NSLeftMouseUp, 3)); 513 [NSApp postEvent:upEvent atStart:YES]; 514 [field_ mouseDown:downEvent]; 515 [NSApp postEvent:upEvent2 atStart:YES]; 516 [field_ mouseDown:downEvent2]; 517 [NSApp postEvent:upEvent3 atStart:YES]; 518 [field_ mouseDown:downEvent3]; 519 EXPECT_TRUE([field_ currentEditor]); 520 521 // Selected the first word. 522 const NSRange selectedRange([[field_ currentEditor] selectedRange]); 523 EXPECT_EQ(selectedRange.location, 0U); 524 EXPECT_EQ(selectedRange.length, [[field_ stringValue] length]); 525 } 526 527 // Clicking a decoration should call decoration's OnMousePressed. 528 TEST_F(AutocompleteTextFieldTest, LeftDecorationMouseDown) { 529 // At this point, not focussed. 530 EXPECT_FALSE([field_ currentEditor]); 531 532 mock_left_decoration_.SetVisible(true); 533 EXPECT_CALL(mock_left_decoration_, AcceptsMousePress()) 534 .WillRepeatedly(Return(true)); 535 536 AutocompleteTextFieldCell* cell = [field_ cell]; 537 const NSRect iconFrame = 538 [cell frameForDecoration:&mock_left_decoration_ inFrame:[field_ bounds]]; 539 const NSPoint location = NSMakePoint(NSMidX(iconFrame), NSMidY(iconFrame)); 540 NSEvent* downEvent = Event(field_, location, NSLeftMouseDown, 1); 541 NSEvent* upEvent = Event(field_, location, NSLeftMouseUp, 1); 542 543 // Since decorations can be dragged, the mouse-press is sent on 544 // mouse-up. 545 [NSApp postEvent:upEvent atStart:YES]; 546 547 EXPECT_CALL(mock_left_decoration_, OnMousePressed(_)) 548 .WillOnce(Return(true)); 549 [field_ mouseDown:downEvent]; 550 551 // Focus the field and test that handled clicks don't affect selection. 552 [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; 553 EXPECT_TRUE([field_ currentEditor]); 554 const NSRange allRange = NSMakeRange(0, [[field_ stringValue] length]); 555 EXPECT_TRUE(NSEqualRanges(allRange, [[field_ currentEditor] selectedRange])); 556 557 // Generate another click on the decoration. 558 downEvent = Event(field_, location, NSLeftMouseDown, 1); 559 upEvent = Event(field_, location, NSLeftMouseUp, 1); 560 [NSApp postEvent:upEvent atStart:YES]; 561 EXPECT_CALL(mock_left_decoration_, OnMousePressed(_)) 562 .WillOnce(Return(true)); 563 [field_ mouseDown:downEvent]; 564 565 // The selection should not have changed. 566 EXPECT_TRUE(NSEqualRanges(allRange, [[field_ currentEditor] selectedRange])); 567 568 // TODO(shess): Test that mouse drags are initiated if the next 569 // event is a drag, or if the mouse-up takes too long to arrive. 570 // IDEA: mock decoration to return a pasteboard which a mock 571 // AutocompleteTextField notes in -dragImage:*. 572 } 573 574 // Clicking a decoration should call decoration's OnMousePressed. 575 TEST_F(AutocompleteTextFieldTest, RightDecorationMouseDown) { 576 // At this point, not focussed. 577 EXPECT_FALSE([field_ currentEditor]); 578 579 mock_right_decoration_.SetVisible(true); 580 EXPECT_CALL(mock_right_decoration_, AcceptsMousePress()) 581 .WillRepeatedly(Return(true)); 582 583 AutocompleteTextFieldCell* cell = [field_ cell]; 584 const NSRect bounds = [field_ bounds]; 585 const NSRect iconFrame = 586 [cell frameForDecoration:&mock_right_decoration_ inFrame:bounds]; 587 const NSPoint location = NSMakePoint(NSMidX(iconFrame), NSMidY(iconFrame)); 588 NSEvent* downEvent = Event(field_, location, NSLeftMouseDown, 1); 589 NSEvent* upEvent = Event(field_, location, NSLeftMouseUp, 1); 590 591 // Since decorations can be dragged, the mouse-press is sent on 592 // mouse-up. 593 [NSApp postEvent:upEvent atStart:YES]; 594 595 EXPECT_CALL(mock_right_decoration_, OnMousePressed(_)) 596 .WillOnce(Return(true)); 597 [field_ mouseDown:downEvent]; 598 } 599 600 // Test that page action menus are properly returned. 601 // TODO(shess): Really, this should test that things are forwarded to 602 // the cell, and the cell tests should test that the right things are 603 // selected. It's easier to mock the event here, though. This code's 604 // event-mockers might be worth promoting to |test_event_utils.h| or 605 // |cocoa_test_helper.h|. 606 TEST_F(AutocompleteTextFieldTest, DecorationMenu) { 607 AutocompleteTextFieldCell* cell = [field_ cell]; 608 const NSRect bounds([field_ bounds]); 609 610 const CGFloat edge = NSHeight(bounds) - 4.0; 611 const NSSize size = NSMakeSize(edge, edge); 612 scoped_nsobject<NSImage> image([[NSImage alloc] initWithSize:size]); 613 614 scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:@"Menu"]); 615 616 mock_left_decoration_.SetVisible(true); 617 mock_right_decoration_.SetVisible(true); 618 619 // The item with a menu returns it. 620 NSRect actionFrame = [cell frameForDecoration:&mock_right_decoration_ 621 inFrame:bounds]; 622 NSPoint location = NSMakePoint(NSMidX(actionFrame), NSMidY(actionFrame)); 623 NSEvent* event = Event(field_, location, NSRightMouseDown, 1); 624 625 // Check that the decoration is called, and the field returns the 626 // menu. 627 EXPECT_CALL(mock_right_decoration_, GetMenu()) 628 .WillOnce(Return(menu.get())); 629 NSMenu *decorationMenu = [field_ decorationMenuForEvent:event]; 630 EXPECT_EQ(decorationMenu, menu); 631 632 // The item without a menu returns nil. 633 EXPECT_CALL(mock_left_decoration_, GetMenu()) 634 .WillOnce(Return(static_cast<NSMenu*>(nil))); 635 actionFrame = [cell frameForDecoration:&mock_left_decoration_ 636 inFrame:bounds]; 637 location = NSMakePoint(NSMidX(actionFrame), NSMidY(actionFrame)); 638 event = Event(field_, location, NSRightMouseDown, 1); 639 EXPECT_FALSE([field_ decorationMenuForEvent:event]); 640 641 // Something not in an action returns nil. 642 location = NSMakePoint(NSMidX(bounds), NSMidY(bounds)); 643 event = Event(field_, location, NSRightMouseDown, 1); 644 EXPECT_FALSE([field_ decorationMenuForEvent:event]); 645 } 646 647 // Verify that -setAttributedStringValue: works as expected when 648 // focussed or when not focussed. Our code mostly depends on about 649 // whether -stringValue works right. 650 TEST_F(AutocompleteTextFieldTest, SetAttributedStringBaseline) { 651 EXPECT_EQ(nil, [field_ currentEditor]); 652 653 // So that we can set rich text. 654 [field_ setAllowsEditingTextAttributes:YES]; 655 656 // Set an attribute different from the field's default so we can 657 // tell we got the same string out as we put in. 658 NSFont* font = [NSFont fontWithDescriptor:[[field_ font] fontDescriptor] 659 size:[[field_ font] pointSize] + 2]; 660 NSDictionary* attributes = 661 [NSDictionary dictionaryWithObject:font 662 forKey:NSFontAttributeName]; 663 NSString* const kString = @"This is a test"; 664 scoped_nsobject<NSAttributedString> attributedString( 665 [[NSAttributedString alloc] initWithString:kString 666 attributes:attributes]); 667 668 // Check that what we get back looks like what we put in. 669 EXPECT_NSNE(kString, [field_ stringValue]); 670 [field_ setAttributedStringValue:attributedString]; 671 EXPECT_TRUE([[field_ attributedStringValue] 672 isEqualToAttributedString:attributedString]); 673 EXPECT_NSEQ(kString, [field_ stringValue]); 674 675 // Try that again with focus. 676 [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; 677 678 EXPECT_TRUE([field_ currentEditor]); 679 680 // Check that what we get back looks like what we put in. 681 [field_ setStringValue:@""]; 682 EXPECT_NSNE(kString, [field_ stringValue]); 683 [field_ setAttributedStringValue:attributedString]; 684 EXPECT_TRUE([[field_ attributedStringValue] 685 isEqualToAttributedString:attributedString]); 686 EXPECT_NSEQ(kString, [field_ stringValue]); 687 } 688 689 // -setAttributedStringValue: shouldn't reset the undo state if things 690 // are being editted. 691 TEST_F(AutocompleteTextFieldTest, SetAttributedStringUndo) { 692 NSColor* redColor = [NSColor redColor]; 693 NSDictionary* attributes = 694 [NSDictionary dictionaryWithObject:redColor 695 forKey:NSForegroundColorAttributeName]; 696 NSString* const kString = @"This is a test"; 697 scoped_nsobject<NSAttributedString> attributedString( 698 [[NSAttributedString alloc] initWithString:kString 699 attributes:attributes]); 700 [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; 701 EXPECT_TRUE([field_ currentEditor]); 702 NSTextView* editor = static_cast<NSTextView*>([field_ currentEditor]); 703 NSUndoManager* undoManager = [editor undoManager]; 704 EXPECT_TRUE(undoManager); 705 706 // Nothing to undo, yet. 707 EXPECT_FALSE([undoManager canUndo]); 708 709 // Starting an editing action creates an undoable item. 710 [editor shouldChangeTextInRange:NSMakeRange(0, 0) replacementString:@""]; 711 [editor didChangeText]; 712 EXPECT_TRUE([undoManager canUndo]); 713 714 // -setStringValue: resets the editor's undo chain. 715 [field_ setStringValue:kString]; 716 EXPECT_FALSE([undoManager canUndo]); 717 718 // Verify that -setAttributedStringValue: does not reset the 719 // editor's undo chain. 720 [field_ setStringValue:@""]; 721 [editor shouldChangeTextInRange:NSMakeRange(0, 0) replacementString:@""]; 722 [editor didChangeText]; 723 EXPECT_TRUE([undoManager canUndo]); 724 [field_ setAttributedStringValue:attributedString]; 725 EXPECT_TRUE([undoManager canUndo]); 726 727 // Verify that calling -clearUndoChain clears the undo chain. 728 [field_ clearUndoChain]; 729 EXPECT_FALSE([undoManager canUndo]); 730 } 731 732 TEST_F(AutocompleteTextFieldTest, EditorGetsCorrectUndoManager) { 733 [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; 734 735 NSTextView* editor = static_cast<NSTextView*>([field_ currentEditor]); 736 EXPECT_TRUE(editor); 737 EXPECT_EQ([field_ undoManagerForTextView:editor], [editor undoManager]); 738 } 739 740 TEST_F(AutocompleteTextFieldObserverTest, SendsEditingMessages) { 741 // Many of these methods try to change the selection. 742 EXPECT_CALL(field_observer_, SelectionRangeForProposedRange(A<NSRange>())) 743 .WillRepeatedly(ReturnArg<0>()); 744 745 EXPECT_CALL(field_observer_, OnSetFocus(false)); 746 // Becoming first responder doesn't begin editing. 747 [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; 748 NSTextView* editor = static_cast<NSTextView*>([field_ currentEditor]); 749 EXPECT_TRUE(nil != editor); 750 751 // This should begin editing and indicate a change. 752 EXPECT_CALL(field_observer_, OnDidBeginEditing()); 753 EXPECT_CALL(field_observer_, OnBeforeChange()); 754 EXPECT_CALL(field_observer_, OnDidChange()); 755 [editor shouldChangeTextInRange:NSMakeRange(0, 0) replacementString:@""]; 756 [editor didChangeText]; 757 758 // Further changes don't send the begin message. 759 EXPECT_CALL(field_observer_, OnBeforeChange()); 760 EXPECT_CALL(field_observer_, OnDidChange()); 761 [editor shouldChangeTextInRange:NSMakeRange(0, 0) replacementString:@""]; 762 [editor didChangeText]; 763 764 // -doCommandBySelector: should forward to observer via |field_|. 765 // TODO(shess): Test with a fake arrow-key event? 766 const SEL cmd = @selector(moveDown:); 767 EXPECT_CALL(field_observer_, OnDoCommandBySelector(cmd)) 768 .WillOnce(Return(true)); 769 [editor doCommandBySelector:cmd]; 770 771 // Finished with the changes. 772 EXPECT_CALL(field_observer_, OnKillFocus()); 773 EXPECT_CALL(field_observer_, OnDidEndEditing()); 774 [test_window() clearPretendKeyWindowAndFirstResponder]; 775 } 776 777 // Test that the resign-key notification is forwarded right, and that 778 // the notification is registered and unregistered when the view moves 779 // in and out of the window. 780 // TODO(shess): Should this test the key window for realz? That would 781 // be really annoying to whoever is running the tests. 782 TEST_F(AutocompleteTextFieldObserverTest, ClosePopupOnResignKey) { 783 EXPECT_CALL(field_observer_, ClosePopup()); 784 [test_window() resignKeyWindow]; 785 786 scoped_nsobject<AutocompleteTextField> pin([field_ retain]); 787 [field_ removeFromSuperview]; 788 [test_window() resignKeyWindow]; 789 790 [[test_window() contentView] addSubview:field_]; 791 EXPECT_CALL(field_observer_, ClosePopup()); 792 [test_window() resignKeyWindow]; 793 } 794 795 } // namespace 796