autocomplete_text_field_unittest.mm revision ddb351dbec246cf1fab5ec20d2d5520909041de1
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
22using ::testing::A;
23using ::testing::InSequence;
24using ::testing::Return;
25using ::testing::ReturnArg;
26using ::testing::StrictMock;
27using ::testing::_;
28
29namespace {
30
31class 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.
42NSUInteger 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".
49NSEvent* 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}
64NSEvent* 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.
70static const CGFloat kWidth(300.0);
71
72class 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
130TEST_VIEW(AutocompleteTextFieldTest, field_);
131
132// Base class for testing AutocompleteTextFieldObserver messages.
133class 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.
153TEST_F(AutocompleteTextFieldTest, CellClass) {
154  EXPECT_TRUE([[field_ cell] isKindOfClass:[AutocompleteTextFieldCell class]]);
155}
156
157// Test that becoming first responder sets things up correctly.
158TEST_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
171TEST_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.
206TEST_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
215TEST_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.
230TEST_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_|.
251TEST_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.
261TEST_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.
284TEST_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.
320TEST_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.
347TEST_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.
382TEST_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.
417TEST_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.
439TEST_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.
454TEST_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.
480TEST_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
503TEST_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.
528TEST_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.
575TEST_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|.
606TEST_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.
650TEST_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.
691TEST_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
732TEST_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
740TEST_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.
782TEST_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