| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import <Cocoa/Cocoa.h> |
| |
| #include "base/mac/foundation_util.h" |
| #include "base/mac/scoped_nsobject.h" |
| #import "chrome/browser/ui/cocoa/cocoa_test_helper.h" |
| #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field.h" |
| #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_cell.h" |
| #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.h" |
| #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_unittest_helper.h" |
| #import "chrome/browser/ui/cocoa/location_bar/location_bar_decoration.h" |
| #include "grit/theme_resources.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #import "testing/gtest_mac.h" |
| #include "testing/platform_test.h" |
| #include "ui/base/resource/resource_bundle.h" |
| |
| using ::testing::A; |
| using ::testing::InSequence; |
| using ::testing::Return; |
| using ::testing::ReturnArg; |
| using ::testing::StrictMock; |
| using ::testing::_; |
| |
| namespace { |
| |
| class MockDecoration : public LocationBarDecoration { |
| public: |
| virtual CGFloat GetWidthForSpace(CGFloat width) { return 20.0; } |
| |
| virtual void DrawInFrame(NSRect frame, NSView* control_view) { ; } |
| MOCK_METHOD0(AcceptsMousePress, bool()); |
| MOCK_METHOD1(OnMousePressed, bool(NSRect frame)); |
| MOCK_METHOD0(GetMenu, NSMenu*()); |
| }; |
| |
| // Mock up an incrementing event number. |
| NSUInteger eventNumber = 0; |
| |
| // Create an event of the indicated |type| at |point| within |view|. |
| // TODO(shess): Would be nice to have a MockApplication which provided |
| // nifty accessors to create these things and inject them. It could |
| // even provide functions for "Click and drag mouse from point A to |
| // point B". |
| NSEvent* Event(NSView* view, const NSPoint point, const NSEventType type, |
| const NSUInteger clickCount) { |
| NSWindow* window([view window]); |
| const NSPoint locationInWindow([view convertPoint:point toView:nil]); |
| const NSPoint location([window convertBaseToScreen:locationInWindow]); |
| return [NSEvent mouseEventWithType:type |
| location:location |
| modifierFlags:0 |
| timestamp:0 |
| windowNumber:[window windowNumber] |
| context:nil |
| eventNumber:eventNumber++ |
| clickCount:clickCount |
| pressure:0.0]; |
| } |
| NSEvent* Event(NSView* view, const NSPoint point, const NSEventType type) { |
| return Event(view, point, type, 1); |
| } |
| |
| // Width of the field so that we don't have to ask |field_| for it all |
| // the time. |
| static const CGFloat kWidth(300.0); |
| |
| class AutocompleteTextFieldTest : public CocoaTest { |
| public: |
| AutocompleteTextFieldTest() { |
| // Make sure this is wide enough to play games with the cell |
| // decorations. |
| NSRect frame = NSMakeRect(0, 0, kWidth, 30); |
| base::scoped_nsobject<AutocompleteTextField> field( |
| [[AutocompleteTextField alloc] initWithFrame:frame]); |
| field_ = field.get(); |
| [field_ setStringValue:@"Test test"]; |
| [[test_window() contentView] addSubview:field_]; |
| |
| AutocompleteTextFieldCell* cell = [field_ cell]; |
| [cell clearDecorations]; |
| |
| mock_left_decoration_.SetVisible(false); |
| [cell addLeftDecoration:&mock_left_decoration_]; |
| |
| mock_right_decoration_.SetVisible(false); |
| [cell addRightDecoration:&mock_right_decoration_]; |
| |
| window_delegate_.reset( |
| [[AutocompleteTextFieldWindowTestDelegate alloc] init]); |
| [test_window() setDelegate:window_delegate_.get()]; |
| } |
| |
| NSEvent* KeyDownEventWithFlags(NSUInteger flags) { |
| return [NSEvent keyEventWithType:NSKeyDown |
| location:NSZeroPoint |
| modifierFlags:flags |
| timestamp:0.0 |
| windowNumber:[test_window() windowNumber] |
| context:nil |
| characters:@"a" |
| charactersIgnoringModifiers:@"a" |
| isARepeat:NO |
| keyCode:'a']; |
| } |
| |
| // Helper to return the field-editor frame being used w/in |field_|. |
| NSRect EditorFrame() { |
| EXPECT_TRUE([field_ currentEditor]); |
| EXPECT_EQ([[field_ subviews] count], 1U); |
| if ([[field_ subviews] count] > 0) { |
| return [[[field_ subviews] objectAtIndex:0] frame]; |
| } else { |
| // Return something which won't work so the caller can soldier |
| // on. |
| return NSZeroRect; |
| } |
| } |
| |
| AutocompleteTextFieldEditor* FieldEditor() { |
| return base::mac::ObjCCastStrict<AutocompleteTextFieldEditor>( |
| [field_ currentEditor]); |
| } |
| |
| AutocompleteTextField* field_; |
| MockDecoration mock_left_decoration_; |
| MockDecoration mock_right_decoration_; |
| base::scoped_nsobject<AutocompleteTextFieldWindowTestDelegate> |
| window_delegate_; |
| }; |
| |
| TEST_VIEW(AutocompleteTextFieldTest, field_); |
| |
| // Base class for testing AutocompleteTextFieldObserver messages. |
| class AutocompleteTextFieldObserverTest : public AutocompleteTextFieldTest { |
| public: |
| virtual void SetUp() { |
| AutocompleteTextFieldTest::SetUp(); |
| [field_ setObserver:&field_observer_]; |
| } |
| |
| virtual void TearDown() { |
| // Clear the observer so that we don't show output for |
| // uninteresting messages to the mock (for instance, if |field_| has |
| // focus at the end of the test). |
| [field_ setObserver:NULL]; |
| |
| AutocompleteTextFieldTest::TearDown(); |
| } |
| |
| StrictMock<MockAutocompleteTextFieldObserver> field_observer_; |
| }; |
| |
| // Test that we have the right cell class. |
| TEST_F(AutocompleteTextFieldTest, CellClass) { |
| EXPECT_TRUE([[field_ cell] isKindOfClass:[AutocompleteTextFieldCell class]]); |
| } |
| |
| // Test that becoming first responder sets things up correctly. |
| TEST_F(AutocompleteTextFieldTest, FirstResponder) { |
| EXPECT_EQ(nil, [field_ currentEditor]); |
| EXPECT_EQ([[field_ subviews] count], 0U); |
| [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; |
| EXPECT_FALSE(nil == [field_ currentEditor]); |
| EXPECT_EQ([[field_ subviews] count], 1U); |
| EXPECT_TRUE([[field_ currentEditor] isDescendantOf:field_]); |
| |
| // Check that the window delegate is providing the right editor. |
| Class c = [AutocompleteTextFieldEditor class]; |
| EXPECT_TRUE([[field_ currentEditor] isKindOfClass:c]); |
| } |
| |
| TEST_F(AutocompleteTextFieldTest, AvailableDecorationWidth) { |
| // A fudge factor to account for how much space the border takes up. |
| // The test shouldn't be too dependent on the field's internals, but |
| // it also shouldn't let deranged cases fall through the cracks |
| // (like nothing available with no text, or everything available |
| // with some text). |
| const CGFloat kBorderWidth = 20.0; |
| |
| // With no contents, almost the entire width is available for |
| // decorations. |
| [field_ setStringValue:@""]; |
| CGFloat availableWidth = [field_ availableDecorationWidth]; |
| EXPECT_LE(availableWidth, kWidth); |
| EXPECT_GT(availableWidth, kWidth - kBorderWidth); |
| |
| // With minor contents, most of the remaining width is available for |
| // decorations. |
| NSDictionary* attributes = |
| [NSDictionary dictionaryWithObject:[field_ font] |
| forKey:NSFontAttributeName]; |
| NSString* string = @"Hello world"; |
| const NSSize size([string sizeWithAttributes:attributes]); |
| [field_ setStringValue:string]; |
| availableWidth = [field_ availableDecorationWidth]; |
| EXPECT_LE(availableWidth, kWidth - size.width); |
| EXPECT_GT(availableWidth, kWidth - size.width - kBorderWidth); |
| |
| // With huge contents, nothing at all is left for decorations. |
| string = @"A long string which is surely wider than field_ can hold."; |
| [field_ setStringValue:string]; |
| availableWidth = [field_ availableDecorationWidth]; |
| EXPECT_LT(availableWidth, 0.0); |
| } |
| |
| // Test drawing, mostly to ensure nothing leaks or crashes. |
| TEST_F(AutocompleteTextFieldTest, Display) { |
| [field_ display]; |
| |
| // Test focussed drawing. |
| [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; |
| [field_ display]; |
| } |
| |
| // Test setting gray text, mostly to ensure nothing leaks or crashes. |
| TEST_F(AutocompleteTextFieldTest, GrayText) { |
| [field_ display]; |
| EXPECT_FALSE([field_ needsDisplay]); |
| [field_ setGrayTextAutocompletion:@"foo" textColor:[NSColor redColor]]; |
| EXPECT_TRUE([field_ needsDisplay]); |
| [field_ display]; |
| } |
| |
| TEST_F(AutocompleteTextFieldObserverTest, FlagsChanged) { |
| InSequence dummy; // Call mock in exactly the order specified. |
| |
| // Test without Control key down, but some other modifier down. |
| EXPECT_CALL(field_observer_, OnControlKeyChanged(false)); |
| [field_ flagsChanged:KeyDownEventWithFlags(NSShiftKeyMask)]; |
| |
| // Test with Control key down. |
| EXPECT_CALL(field_observer_, OnControlKeyChanged(true)); |
| [field_ flagsChanged:KeyDownEventWithFlags(NSControlKeyMask)]; |
| } |
| |
| // This test is here rather than in the editor's tests because the |
| // field catches -flagsChanged: because it's on the responder chain, |
| // the field editor doesn't implement it. |
| TEST_F(AutocompleteTextFieldObserverTest, FieldEditorFlagsChanged) { |
| // Many of these methods try to change the selection. |
| EXPECT_CALL(field_observer_, SelectionRangeForProposedRange(A<NSRange>())) |
| .WillRepeatedly(ReturnArg<0>()); |
| |
| InSequence dummy; // Call mock in exactly the order specified. |
| EXPECT_CALL(field_observer_, OnSetFocus(false)); |
| [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; |
| NSResponder* firstResponder = [[field_ window] firstResponder]; |
| EXPECT_EQ(firstResponder, [field_ currentEditor]); |
| |
| // Test without Control key down, but some other modifier down. |
| EXPECT_CALL(field_observer_, OnControlKeyChanged(false)); |
| [firstResponder flagsChanged:KeyDownEventWithFlags(NSShiftKeyMask)]; |
| |
| // Test with Control key down. |
| EXPECT_CALL(field_observer_, OnControlKeyChanged(true)); |
| [firstResponder flagsChanged:KeyDownEventWithFlags(NSControlKeyMask)]; |
| } |
| |
| // Frame size changes are propagated to |observer_|. |
| TEST_F(AutocompleteTextFieldObserverTest, FrameChanged) { |
| EXPECT_CALL(field_observer_, OnFrameChanged()); |
| NSRect frame = [field_ frame]; |
| frame.size.width += 10.0; |
| [field_ setFrame:frame]; |
| } |
| |
| // Test that the field editor gets the same bounds when focus is |
| // delivered by the standard focusing machinery, or by |
| // -resetFieldEditorFrameIfNeeded. |
| TEST_F(AutocompleteTextFieldTest, ResetFieldEditorBase) { |
| // Capture the editor frame resulting from the standard focus |
| // machinery. |
| [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; |
| const NSRect baseEditorFrame = EditorFrame(); |
| |
| // A decoration should result in a strictly smaller editor frame. |
| mock_left_decoration_.SetVisible(true); |
| [field_ resetFieldEditorFrameIfNeeded]; |
| EXPECT_FALSE(NSEqualRects(baseEditorFrame, EditorFrame())); |
| EXPECT_TRUE(NSContainsRect(baseEditorFrame, EditorFrame())); |
| |
| // Removing the decoration and using -resetFieldEditorFrameIfNeeded |
| // should result in the same frame as the standard focus machinery. |
| mock_left_decoration_.SetVisible(false); |
| [field_ resetFieldEditorFrameIfNeeded]; |
| EXPECT_TRUE(NSEqualRects(baseEditorFrame, EditorFrame())); |
| } |
| |
| // Test that the field editor gets the same bounds when focus is |
| // delivered by the standard focusing machinery, or by |
| // -resetFieldEditorFrameIfNeeded, this time with a decoration |
| // pre-loaded. |
| TEST_F(AutocompleteTextFieldTest, ResetFieldEditorWithDecoration) { |
| AutocompleteTextFieldCell* cell = [field_ cell]; |
| |
| // Make sure decoration isn't already visible, then make it visible. |
| EXPECT_TRUE(NSIsEmptyRect([cell frameForDecoration:&mock_left_decoration_ |
| inFrame:[field_ bounds]])); |
| mock_left_decoration_.SetVisible(true); |
| EXPECT_FALSE(NSIsEmptyRect([cell frameForDecoration:&mock_left_decoration_ |
| inFrame:[field_ bounds]])); |
| |
| // Capture the editor frame resulting from the standard focus |
| // machinery. |
| |
| [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; |
| const NSRect baseEditorFrame = EditorFrame(); |
| |
| // When the decoration is not visible the frame should be strictly larger. |
| mock_left_decoration_.SetVisible(false); |
| EXPECT_TRUE(NSIsEmptyRect([cell frameForDecoration:&mock_left_decoration_ |
| inFrame:[field_ bounds]])); |
| [field_ resetFieldEditorFrameIfNeeded]; |
| EXPECT_FALSE(NSEqualRects(baseEditorFrame, EditorFrame())); |
| EXPECT_TRUE(NSContainsRect(EditorFrame(), baseEditorFrame)); |
| |
| // When the decoration is visible, -resetFieldEditorFrameIfNeeded |
| // should result in the same frame as the standard focus machinery. |
| mock_left_decoration_.SetVisible(true); |
| EXPECT_FALSE(NSIsEmptyRect([cell frameForDecoration:&mock_left_decoration_ |
| inFrame:[field_ bounds]])); |
| |
| [field_ resetFieldEditorFrameIfNeeded]; |
| EXPECT_TRUE(NSEqualRects(baseEditorFrame, EditorFrame())); |
| } |
| |
| // Test that resetting the field editor bounds does not cause untoward |
| // messages to the field's observer. |
| TEST_F(AutocompleteTextFieldObserverTest, ResetFieldEditorContinuesEditing) { |
| // Many of these methods try to change the selection. |
| EXPECT_CALL(field_observer_, SelectionRangeForProposedRange(A<NSRange>())) |
| .WillRepeatedly(ReturnArg<0>()); |
| |
| EXPECT_CALL(field_observer_, OnSetFocus(false)); |
| // Becoming first responder doesn't begin editing. |
| [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; |
| const NSRect baseEditorFrame = EditorFrame(); |
| NSTextView* editor = static_cast<NSTextView*>([field_ currentEditor]); |
| EXPECT_TRUE(nil != editor); |
| |
| // This should begin editing and indicate a change. |
| EXPECT_CALL(field_observer_, OnDidBeginEditing()); |
| EXPECT_CALL(field_observer_, OnBeforeChange()); |
| EXPECT_CALL(field_observer_, OnDidChange()); |
| [editor shouldChangeTextInRange:NSMakeRange(0, 0) replacementString:@""]; |
| [editor didChangeText]; |
| |
| // No messages to |field_observer_| when the frame actually changes. |
| mock_left_decoration_.SetVisible(true); |
| [field_ resetFieldEditorFrameIfNeeded]; |
| EXPECT_FALSE(NSEqualRects(baseEditorFrame, EditorFrame())); |
| } |
| |
| // Clicking in a right-hand decoration which does not handle the mouse |
| // puts the caret rightmost. |
| TEST_F(AutocompleteTextFieldTest, ClickRightDecorationPutsCaretRightmost) { |
| // Decoration does not handle the mouse event, so the cell should |
| // process it. Called at least once. |
| EXPECT_CALL(mock_right_decoration_, AcceptsMousePress()) |
| .WillOnce(Return(false)) |
| .WillRepeatedly(Return(false)); |
| |
| // Set the decoration before becoming responder. |
| EXPECT_FALSE([field_ currentEditor]); |
| mock_right_decoration_.SetVisible(true); |
| |
| // Make first responder should select all. |
| [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; |
| EXPECT_TRUE([field_ currentEditor]); |
| const NSRange allRange = NSMakeRange(0, [[field_ stringValue] length]); |
| EXPECT_TRUE(NSEqualRanges(allRange, [[field_ currentEditor] selectedRange])); |
| |
| // Generate a click on the decoration. |
| AutocompleteTextFieldCell* cell = [field_ cell]; |
| const NSRect bounds = [field_ bounds]; |
| const NSRect iconFrame = |
| [cell frameForDecoration:&mock_right_decoration_ inFrame:bounds]; |
| const NSPoint point = NSMakePoint(NSMidX(iconFrame), NSMidY(iconFrame)); |
| NSEvent* downEvent = Event(field_, point, NSLeftMouseDown); |
| NSEvent* upEvent = Event(field_, point, NSLeftMouseUp); |
| [NSApp postEvent:upEvent atStart:YES]; |
| [field_ mouseDown:downEvent]; |
| |
| // Selection should be a right-hand-side caret. |
| EXPECT_TRUE(NSEqualRanges(NSMakeRange([[field_ stringValue] length], 0), |
| [[field_ currentEditor] selectedRange])); |
| } |
| |
| // Clicking in a left-side decoration which doesn't handle the event |
| // puts the selection in the leftmost position. |
| TEST_F(AutocompleteTextFieldTest, ClickLeftDecorationPutsCaretLeftmost) { |
| // Decoration does not handle the mouse event, so the cell should |
| // process it. Called at least once. |
| EXPECT_CALL(mock_left_decoration_, AcceptsMousePress()) |
| .WillOnce(Return(false)) |
| .WillRepeatedly(Return(false)); |
| |
| // Set the decoration before becoming responder. |
| EXPECT_FALSE([field_ currentEditor]); |
| mock_left_decoration_.SetVisible(true); |
| |
| // Make first responder should select all. |
| [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; |
| EXPECT_TRUE([field_ currentEditor]); |
| const NSRange allRange = NSMakeRange(0, [[field_ stringValue] length]); |
| EXPECT_TRUE(NSEqualRanges(allRange, [[field_ currentEditor] selectedRange])); |
| |
| // Generate a click on the decoration. |
| AutocompleteTextFieldCell* cell = [field_ cell]; |
| const NSRect bounds = [field_ bounds]; |
| const NSRect iconFrame = |
| [cell frameForDecoration:&mock_left_decoration_ inFrame:bounds]; |
| const NSPoint point = NSMakePoint(NSMidX(iconFrame), NSMidY(iconFrame)); |
| NSEvent* downEvent = Event(field_, point, NSLeftMouseDown); |
| NSEvent* upEvent = Event(field_, point, NSLeftMouseUp); |
| [NSApp postEvent:upEvent atStart:YES]; |
| [field_ mouseDown:downEvent]; |
| |
| // Selection should be a left-hand-side caret. |
| EXPECT_TRUE(NSEqualRanges(NSMakeRange(0, 0), |
| [[field_ currentEditor] selectedRange])); |
| } |
| |
| // Clicks not in the text area or the cell's decorations fall through |
| // to the editor. |
| TEST_F(AutocompleteTextFieldTest, ClickBorderSelectsAll) { |
| // Can't rely on the window machinery to make us first responder, |
| // here. |
| [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; |
| EXPECT_TRUE([field_ currentEditor]); |
| |
| const NSPoint point(NSMakePoint(20.0, 1.0)); |
| NSEvent* downEvent(Event(field_, point, NSLeftMouseDown)); |
| NSEvent* upEvent(Event(field_, point, NSLeftMouseUp)); |
| [NSApp postEvent:upEvent atStart:YES]; |
| [field_ mouseDown:downEvent]; |
| |
| // Clicking in the narrow border area around a Cocoa NSTextField |
| // does a select-all. Regardless of whether this is a good call, it |
| // works as a test that things get passed down to the editor. |
| const NSRange selectedRange([[field_ currentEditor] selectedRange]); |
| EXPECT_EQ(selectedRange.location, 0U); |
| EXPECT_EQ(selectedRange.length, [[field_ stringValue] length]); |
| } |
| |
| // Single-click with no drag should setup a field editor and |
| // select all. |
| TEST_F(AutocompleteTextFieldTest, ClickSelectsAll) { |
| EXPECT_FALSE([field_ currentEditor]); |
| |
| const NSPoint point = NSMakePoint(20.0, NSMidY([field_ bounds])); |
| NSEvent* downEvent(Event(field_, point, NSLeftMouseDown)); |
| NSEvent* upEvent(Event(field_, point, NSLeftMouseUp)); |
| [NSApp postEvent:upEvent atStart:YES]; |
| [field_ mouseDown:downEvent]; |
| EXPECT_TRUE([field_ currentEditor]); |
| const NSRange selectedRange([[field_ currentEditor] selectedRange]); |
| EXPECT_EQ(selectedRange.location, 0U); |
| EXPECT_EQ(selectedRange.length, [[field_ stringValue] length]); |
| } |
| |
| // Click-drag selects text, not select all. |
| TEST_F(AutocompleteTextFieldTest, ClickDragSelectsText) { |
| EXPECT_FALSE([field_ currentEditor]); |
| |
| NSEvent* downEvent(Event(field_, NSMakePoint(20.0, 5.0), NSLeftMouseDown)); |
| NSEvent* upEvent(Event(field_, NSMakePoint(0.0, 5.0), NSLeftMouseUp)); |
| [NSApp postEvent:upEvent atStart:YES]; |
| [field_ mouseDown:downEvent]; |
| EXPECT_TRUE([field_ currentEditor]); |
| |
| // Expect this to have selected a prefix of the content. Mostly |
| // just don't want the select-all behavior. |
| const NSRange selectedRange([[field_ currentEditor] selectedRange]); |
| EXPECT_EQ(selectedRange.location, 0U); |
| EXPECT_LT(selectedRange.length, [[field_ stringValue] length]); |
| } |
| |
| // TODO(shess): Test that click/pause/click allows cursor placement. |
| // In this case the first click goes to the field, but the second |
| // click goes to the field editor, so the current testing pattern |
| // can't work. What really needs to happen is to push through the |
| // NSWindow event machinery so that we can say "two independent clicks |
| // at the same location have the right effect". Once that is done, it |
| // might make sense to revise the other tests to use the same |
| // machinery. |
| |
| // Double-click selects word, not select all. |
| TEST_F(AutocompleteTextFieldTest, DoubleClickSelectsWord) { |
| EXPECT_FALSE([field_ currentEditor]); |
| |
| const NSPoint point = NSMakePoint(20.0, NSMidY([field_ bounds])); |
| NSEvent* downEvent(Event(field_, point, NSLeftMouseDown, 1)); |
| NSEvent* upEvent(Event(field_, point, NSLeftMouseUp, 1)); |
| NSEvent* downEvent2(Event(field_, point, NSLeftMouseDown, 2)); |
| NSEvent* upEvent2(Event(field_, point, NSLeftMouseUp, 2)); |
| [NSApp postEvent:upEvent atStart:YES]; |
| [field_ mouseDown:downEvent]; |
| [NSApp postEvent:upEvent2 atStart:YES]; |
| [field_ mouseDown:downEvent2]; |
| EXPECT_TRUE([field_ currentEditor]); |
| |
| // Selected the first word. |
| const NSRange selectedRange([[field_ currentEditor] selectedRange]); |
| const NSRange spaceRange([[field_ stringValue] rangeOfString:@" "]); |
| EXPECT_GT(spaceRange.location, 0U); |
| EXPECT_LT(spaceRange.length, [[field_ stringValue] length]); |
| EXPECT_EQ(selectedRange.location, 0U); |
| EXPECT_EQ(selectedRange.length, spaceRange.location); |
| } |
| |
| TEST_F(AutocompleteTextFieldTest, TripleClickSelectsAll) { |
| EXPECT_FALSE([field_ currentEditor]); |
| |
| const NSPoint point(NSMakePoint(20.0, 5.0)); |
| NSEvent* downEvent(Event(field_, point, NSLeftMouseDown, 1)); |
| NSEvent* upEvent(Event(field_, point, NSLeftMouseUp, 1)); |
| NSEvent* downEvent2(Event(field_, point, NSLeftMouseDown, 2)); |
| NSEvent* upEvent2(Event(field_, point, NSLeftMouseUp, 2)); |
| NSEvent* downEvent3(Event(field_, point, NSLeftMouseDown, 3)); |
| NSEvent* upEvent3(Event(field_, point, NSLeftMouseUp, 3)); |
| [NSApp postEvent:upEvent atStart:YES]; |
| [field_ mouseDown:downEvent]; |
| [NSApp postEvent:upEvent2 atStart:YES]; |
| [field_ mouseDown:downEvent2]; |
| [NSApp postEvent:upEvent3 atStart:YES]; |
| [field_ mouseDown:downEvent3]; |
| EXPECT_TRUE([field_ currentEditor]); |
| |
| // Selected the first word. |
| const NSRange selectedRange([[field_ currentEditor] selectedRange]); |
| EXPECT_EQ(selectedRange.location, 0U); |
| EXPECT_EQ(selectedRange.length, [[field_ stringValue] length]); |
| } |
| |
| // Clicking a decoration should call decoration's OnMousePressed. |
| TEST_F(AutocompleteTextFieldTest, LeftDecorationMouseDown) { |
| // At this point, not focussed. |
| EXPECT_FALSE([field_ currentEditor]); |
| |
| mock_left_decoration_.SetVisible(true); |
| EXPECT_CALL(mock_left_decoration_, AcceptsMousePress()) |
| .WillRepeatedly(Return(true)); |
| |
| AutocompleteTextFieldCell* cell = [field_ cell]; |
| const NSRect iconFrame = |
| [cell frameForDecoration:&mock_left_decoration_ inFrame:[field_ bounds]]; |
| const NSPoint location = NSMakePoint(NSMidX(iconFrame), NSMidY(iconFrame)); |
| NSEvent* downEvent = Event(field_, location, NSLeftMouseDown, 1); |
| NSEvent* upEvent = Event(field_, location, NSLeftMouseUp, 1); |
| |
| // Since decorations can be dragged, the mouse-press is sent on |
| // mouse-up. |
| [NSApp postEvent:upEvent atStart:YES]; |
| |
| EXPECT_CALL(mock_left_decoration_, OnMousePressed(_)) |
| .WillOnce(Return(true)); |
| [field_ mouseDown:downEvent]; |
| |
| // Focus the field and test that handled clicks don't affect selection. |
| [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; |
| EXPECT_TRUE([field_ currentEditor]); |
| const NSRange allRange = NSMakeRange(0, [[field_ stringValue] length]); |
| EXPECT_TRUE(NSEqualRanges(allRange, [[field_ currentEditor] selectedRange])); |
| |
| // Generate another click on the decoration. |
| downEvent = Event(field_, location, NSLeftMouseDown, 1); |
| upEvent = Event(field_, location, NSLeftMouseUp, 1); |
| [NSApp postEvent:upEvent atStart:YES]; |
| EXPECT_CALL(mock_left_decoration_, OnMousePressed(_)) |
| .WillOnce(Return(true)); |
| [field_ mouseDown:downEvent]; |
| |
| // The selection should not have changed. |
| EXPECT_TRUE(NSEqualRanges(allRange, [[field_ currentEditor] selectedRange])); |
| |
| // TODO(shess): Test that mouse drags are initiated if the next |
| // event is a drag, or if the mouse-up takes too long to arrive. |
| // IDEA: mock decoration to return a pasteboard which a mock |
| // AutocompleteTextField notes in -dragImage:*. |
| } |
| |
| // Clicking a decoration should call decoration's OnMousePressed. |
| TEST_F(AutocompleteTextFieldTest, RightDecorationMouseDown) { |
| // At this point, not focussed. |
| EXPECT_FALSE([field_ currentEditor]); |
| |
| mock_right_decoration_.SetVisible(true); |
| EXPECT_CALL(mock_right_decoration_, AcceptsMousePress()) |
| .WillRepeatedly(Return(true)); |
| |
| AutocompleteTextFieldCell* cell = [field_ cell]; |
| const NSRect bounds = [field_ bounds]; |
| const NSRect iconFrame = |
| [cell frameForDecoration:&mock_right_decoration_ inFrame:bounds]; |
| const NSPoint location = NSMakePoint(NSMidX(iconFrame), NSMidY(iconFrame)); |
| NSEvent* downEvent = Event(field_, location, NSLeftMouseDown, 1); |
| NSEvent* upEvent = Event(field_, location, NSLeftMouseUp, 1); |
| |
| // Since decorations can be dragged, the mouse-press is sent on |
| // mouse-up. |
| [NSApp postEvent:upEvent atStart:YES]; |
| |
| EXPECT_CALL(mock_right_decoration_, OnMousePressed(_)) |
| .WillOnce(Return(true)); |
| [field_ mouseDown:downEvent]; |
| } |
| |
| // Test that page action menus are properly returned. |
| // TODO(shess): Really, this should test that things are forwarded to |
| // the cell, and the cell tests should test that the right things are |
| // selected. It's easier to mock the event here, though. This code's |
| // event-mockers might be worth promoting to |cocoa_test_event_utils.h| or |
| // |cocoa_test_helper.h|. |
| TEST_F(AutocompleteTextFieldTest, DecorationMenu) { |
| AutocompleteTextFieldCell* cell = [field_ cell]; |
| const NSRect bounds([field_ bounds]); |
| |
| const CGFloat edge = NSHeight(bounds) - 4.0; |
| const NSSize size = NSMakeSize(edge, edge); |
| base::scoped_nsobject<NSImage> image([[NSImage alloc] initWithSize:size]); |
| |
| base::scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:@"Menu"]); |
| |
| mock_left_decoration_.SetVisible(true); |
| mock_right_decoration_.SetVisible(true); |
| |
| // The item with a menu returns it. |
| NSRect actionFrame = [cell frameForDecoration:&mock_right_decoration_ |
| inFrame:bounds]; |
| NSPoint location = NSMakePoint(NSMidX(actionFrame), NSMidY(actionFrame)); |
| NSEvent* event = Event(field_, location, NSRightMouseDown, 1); |
| |
| // Check that the decoration is called, and the field returns the |
| // menu. |
| EXPECT_CALL(mock_right_decoration_, GetMenu()) |
| .WillOnce(Return(menu.get())); |
| NSMenu *decorationMenu = [field_ decorationMenuForEvent:event]; |
| EXPECT_EQ(decorationMenu, menu); |
| |
| // The item without a menu returns nil. |
| EXPECT_CALL(mock_left_decoration_, GetMenu()) |
| .WillOnce(Return(static_cast<NSMenu*>(nil))); |
| actionFrame = [cell frameForDecoration:&mock_left_decoration_ |
| inFrame:bounds]; |
| location = NSMakePoint(NSMidX(actionFrame), NSMidY(actionFrame)); |
| event = Event(field_, location, NSRightMouseDown, 1); |
| EXPECT_FALSE([field_ decorationMenuForEvent:event]); |
| |
| // Something not in an action returns nil. |
| location = NSMakePoint(NSMidX(bounds), NSMidY(bounds)); |
| event = Event(field_, location, NSRightMouseDown, 1); |
| EXPECT_FALSE([field_ decorationMenuForEvent:event]); |
| } |
| |
| // Verify that -setAttributedStringValue: works as expected when |
| // focussed or when not focussed. Our code mostly depends on about |
| // whether -stringValue works right. |
| TEST_F(AutocompleteTextFieldTest, SetAttributedStringBaseline) { |
| EXPECT_EQ(nil, [field_ currentEditor]); |
| |
| // So that we can set rich text. |
| [field_ setAllowsEditingTextAttributes:YES]; |
| |
| // Set an attribute different from the field's default so we can |
| // tell we got the same string out as we put in. |
| NSFont* font = [NSFont fontWithDescriptor:[[field_ font] fontDescriptor] |
| size:[[field_ font] pointSize] + 2]; |
| NSDictionary* attributes = |
| [NSDictionary dictionaryWithObject:font |
| forKey:NSFontAttributeName]; |
| NSString* const kString = @"This is a test"; |
| base::scoped_nsobject<NSAttributedString> attributedString( |
| [[NSAttributedString alloc] initWithString:kString |
| attributes:attributes]); |
| |
| // Check that what we get back looks like what we put in. |
| EXPECT_NSNE(kString, [field_ stringValue]); |
| [field_ setAttributedStringValue:attributedString]; |
| EXPECT_TRUE([[field_ attributedStringValue] |
| isEqualToAttributedString:attributedString]); |
| EXPECT_NSEQ(kString, [field_ stringValue]); |
| |
| // Try that again with focus. |
| [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; |
| |
| EXPECT_TRUE([field_ currentEditor]); |
| |
| // Check that what we get back looks like what we put in. |
| [field_ setStringValue:@""]; |
| EXPECT_NSNE(kString, [field_ stringValue]); |
| [field_ setAttributedStringValue:attributedString]; |
| EXPECT_TRUE([[field_ attributedStringValue] |
| isEqualToAttributedString:attributedString]); |
| EXPECT_NSEQ(kString, [field_ stringValue]); |
| } |
| |
| // -setAttributedStringValue: shouldn't reset the undo state if things |
| // are being editted. |
| TEST_F(AutocompleteTextFieldTest, SetAttributedStringUndo) { |
| NSColor* redColor = [NSColor redColor]; |
| NSDictionary* attributes = |
| [NSDictionary dictionaryWithObject:redColor |
| forKey:NSForegroundColorAttributeName]; |
| NSString* const kString = @"This is a test"; |
| base::scoped_nsobject<NSAttributedString> attributedString( |
| [[NSAttributedString alloc] initWithString:kString |
| attributes:attributes]); |
| [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; |
| EXPECT_TRUE([field_ currentEditor]); |
| NSTextView* editor = static_cast<NSTextView*>([field_ currentEditor]); |
| NSUndoManager* undoManager = [editor undoManager]; |
| EXPECT_TRUE(undoManager); |
| |
| // Nothing to undo, yet. |
| EXPECT_FALSE([undoManager canUndo]); |
| |
| // Starting an editing action creates an undoable item. |
| [editor shouldChangeTextInRange:NSMakeRange(0, 0) replacementString:@""]; |
| [editor didChangeText]; |
| EXPECT_TRUE([undoManager canUndo]); |
| |
| // -setStringValue: resets the editor's undo chain. |
| [field_ setStringValue:kString]; |
| EXPECT_FALSE([undoManager canUndo]); |
| |
| // Verify that -setAttributedStringValue: does not reset the |
| // editor's undo chain. |
| [field_ setStringValue:@""]; |
| [editor shouldChangeTextInRange:NSMakeRange(0, 0) replacementString:@""]; |
| [editor didChangeText]; |
| EXPECT_TRUE([undoManager canUndo]); |
| [field_ setAttributedStringValue:attributedString]; |
| EXPECT_TRUE([undoManager canUndo]); |
| |
| // Verify that calling -clearUndoChain clears the undo chain. |
| [field_ clearUndoChain]; |
| EXPECT_FALSE([undoManager canUndo]); |
| } |
| |
| TEST_F(AutocompleteTextFieldTest, EditorGetsCorrectUndoManager) { |
| [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; |
| |
| NSTextView* editor = static_cast<NSTextView*>([field_ currentEditor]); |
| EXPECT_TRUE(editor); |
| EXPECT_EQ([field_ undoManagerForTextView:editor], [editor undoManager]); |
| } |
| |
| // Verify that hideFocusState correctly hides the focus ring and insertion |
| // pointer. |
| TEST_F(AutocompleteTextFieldTest, HideFocusState) { |
| [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; |
| [[field_ cell] setShowsFirstResponder:YES]; |
| |
| EXPECT_TRUE([[field_ cell] showsFirstResponder]); |
| EXPECT_TRUE([FieldEditor() shouldDrawInsertionPoint]); |
| |
| [[field_ cell] setHideFocusState:YES |
| ofView:field_]; |
| EXPECT_FALSE([[field_ cell] showsFirstResponder]); |
| EXPECT_FALSE([FieldEditor() shouldDrawInsertionPoint]); |
| |
| [[field_ cell] setHideFocusState:NO |
| ofView:field_]; |
| EXPECT_TRUE([[field_ cell] showsFirstResponder]); |
| EXPECT_TRUE([FieldEditor() shouldDrawInsertionPoint]); |
| } |
| |
| TEST_F(AutocompleteTextFieldObserverTest, SendsEditingMessages) { |
| // Many of these methods try to change the selection. |
| EXPECT_CALL(field_observer_, SelectionRangeForProposedRange(A<NSRange>())) |
| .WillRepeatedly(ReturnArg<0>()); |
| |
| EXPECT_CALL(field_observer_, OnSetFocus(false)); |
| // Becoming first responder doesn't begin editing. |
| [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; |
| NSTextView* editor = static_cast<NSTextView*>([field_ currentEditor]); |
| EXPECT_TRUE(nil != editor); |
| |
| // This should begin editing and indicate a change. |
| EXPECT_CALL(field_observer_, OnDidBeginEditing()); |
| EXPECT_CALL(field_observer_, OnBeforeChange()); |
| EXPECT_CALL(field_observer_, OnDidChange()); |
| [editor shouldChangeTextInRange:NSMakeRange(0, 0) replacementString:@""]; |
| [editor didChangeText]; |
| |
| // Further changes don't send the begin message. |
| EXPECT_CALL(field_observer_, OnBeforeChange()); |
| EXPECT_CALL(field_observer_, OnDidChange()); |
| [editor shouldChangeTextInRange:NSMakeRange(0, 0) replacementString:@""]; |
| [editor didChangeText]; |
| |
| // -doCommandBySelector: should forward to observer via |field_|. |
| // TODO(shess): Test with a fake arrow-key event? |
| const SEL cmd = @selector(moveDown:); |
| EXPECT_CALL(field_observer_, OnDoCommandBySelector(cmd)) |
| .WillOnce(Return(true)); |
| [editor doCommandBySelector:cmd]; |
| |
| // Finished with the changes. |
| EXPECT_CALL(field_observer_, OnKillFocus()); |
| EXPECT_CALL(field_observer_, OnDidEndEditing()); |
| [test_window() clearPretendKeyWindowAndFirstResponder]; |
| } |
| |
| // Test that the resign-key notification is forwarded right, and that |
| // the notification is registered and unregistered when the view moves |
| // in and out of the window. |
| // TODO(shess): Should this test the key window for realz? That would |
| // be really annoying to whoever is running the tests. |
| TEST_F(AutocompleteTextFieldObserverTest, ClosePopupOnResignKey) { |
| EXPECT_CALL(field_observer_, ClosePopup()); |
| [test_window() resignKeyWindow]; |
| |
| base::scoped_nsobject<AutocompleteTextField> pin([field_ retain]); |
| [field_ removeFromSuperview]; |
| [test_window() resignKeyWindow]; |
| |
| [[test_window() contentView] addSubview:field_]; |
| EXPECT_CALL(field_observer_, ClosePopup()); |
| [test_window() resignKeyWindow]; |
| } |
| |
| } // namespace |