// Copyright (c) 2010 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 #include "app/resource_bundle.h" #import "base/cocoa_protocols_mac.h" #include "base/scoped_nsobject.h" #import "chrome/browser/cocoa/location_bar/autocomplete_text_field.h" #import "chrome/browser/cocoa/location_bar/autocomplete_text_field_cell.h" #import "chrome/browser/cocoa/location_bar/autocomplete_text_field_editor.h" #import "chrome/browser/cocoa/location_bar/autocomplete_text_field_unittest_helper.h" #import "chrome/browser/cocoa/location_bar/location_bar_decoration.h" #import "chrome/browser/cocoa/cocoa_test_helper.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" 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); scoped_nsobject 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; } } AutocompleteTextField* field_; MockDecoration mock_left_decoration_; MockDecoration mock_right_decoration_; scoped_nsobject 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 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_window() clearPretendKeyWindowAndFirstResponder]; } 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())) .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())) .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([field_ currentEditor]); EXPECT_TRUE(nil != editor); // This should begin editing and indicate a change. EXPECT_CALL(field_observer_, OnDidBeginEditing()); 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 |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); scoped_nsobject image([[NSImage alloc] initWithSize:size]); scoped_nsobject 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(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"; scoped_nsobject 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"; scoped_nsobject attributedString( [[NSAttributedString alloc] initWithString:kString attributes:attributes]); [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; EXPECT_TRUE([field_ currentEditor]); NSTextView* editor = static_cast([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([field_ currentEditor]); EXPECT_TRUE(editor); EXPECT_EQ([field_ undoManagerForTextView:editor], [editor undoManager]); } TEST_F(AutocompleteTextFieldObserverTest, SendsEditingMessages) { // Many of these methods try to change the selection. EXPECT_CALL(field_observer_, SelectionRangeForProposedRange(A())) .WillRepeatedly(ReturnArg<0>()); EXPECT_CALL(field_observer_, OnSetFocus(false)); // Becoming first responder doesn't begin editing. [test_window() makePretendKeyWindowAndSetFirstResponder:field_]; NSTextView* editor = static_cast([field_ currentEditor]); EXPECT_TRUE(nil != editor); // This should begin editing and indicate a change. EXPECT_CALL(field_observer_, OnDidBeginEditing()); 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_, 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]; scoped_nsobject pin([field_ retain]); [field_ removeFromSuperview]; [test_window() resignKeyWindow]; [[test_window() contentView] addSubview:field_]; EXPECT_CALL(field_observer_, ClosePopup()); [test_window() resignKeyWindow]; } } // namespace