Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / nsmenuitem_additions_unittest.mm
blob33ed98c4950b41d8bd1396c4a7d3ba0ee04b13eb
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.
5 #import "chrome/browser/ui/cocoa/nsmenuitem_additions.h"
7 #include <Carbon/Carbon.h>
9 #include <ostream>
11 #include "base/mac/scoped_nsobject.h"
12 #include "base/strings/sys_string_conversions.h"
13 #include "testing/gtest/include/gtest/gtest.h"
15 NSEvent* KeyEvent(const NSUInteger modifierFlags,
16                   NSString* chars,
17                   NSString* charsNoMods,
18                   const NSUInteger keyCode) {
19   return [NSEvent keyEventWithType:NSKeyDown
20                           location:NSZeroPoint
21                      modifierFlags:modifierFlags
22                          timestamp:0.0
23                       windowNumber:0
24                            context:nil
25                         characters:chars
26        charactersIgnoringModifiers:charsNoMods
27                          isARepeat:NO
28                            keyCode:keyCode];
31 NSMenuItem* MenuItem(NSString* equiv, NSUInteger mask) {
32   NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle:@""
33                                                  action:NULL
34                                           keyEquivalent:@""] autorelease];
35   [item setKeyEquivalent:equiv];
36   [item setKeyEquivalentModifierMask:mask];
37   return item;
40 std::ostream& operator<<(std::ostream& out, NSObject* obj) {
41   return out << base::SysNSStringToUTF8([obj description]);
44 std::ostream& operator<<(std::ostream& out, NSMenuItem* item) {
45   return out << "NSMenuItem " << base::SysNSStringToUTF8([item keyEquivalent]);
48 void ExpectKeyFiresItemEq(bool result, NSEvent* key, NSMenuItem* item,
49     bool compareCocoa) {
50   EXPECT_EQ(result, [item cr_firesForKeyEvent:key]) << key << '\n' << item;
52   // Make sure that Cocoa does in fact agree with our expectations. However,
53   // in some cases cocoa behaves weirdly (if you create e.g. a new event that
54   // contains all fields of the event that you get when hitting cmd-a with a
55   // russion keyboard layout, the copy won't fire a menu item that has cmd-a as
56   // key equivalent, even though the original event would) and isn't a good
57   // oracle function.
58   if (compareCocoa) {
59     base::scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:@"Menu!"]);
60     [menu setAutoenablesItems:NO];
61     EXPECT_FALSE([menu performKeyEquivalent:key]);
62     [menu addItem:item];
63     EXPECT_EQ(result, [menu performKeyEquivalent:key]) << key << '\n' << item;
64   }
67 void ExpectKeyFiresItem(
68     NSEvent* key, NSMenuItem* item, bool compareCocoa = true) {
69   ExpectKeyFiresItemEq(true, key, item, compareCocoa);
72 void ExpectKeyDoesntFireItem(
73     NSEvent* key, NSMenuItem* item, bool compareCocoa = true) {
74   ExpectKeyFiresItemEq(false, key, item, compareCocoa);
77 TEST(NSMenuItemAdditionsTest, TestFiresForKeyEvent) {
78   // These test cases were built by writing a small test app that has a
79   // MainMenu.xib with a given key equivalent set in Interface Builder and a
80   // some code that prints both the key equivalent that fires a menu item and
81   // the menu item's key equivalent and modifier masks. I then pasted those
82   // below. This was done with a US layout, unless otherwise noted. In the
83   // comments, "z" always means the physical "z" key on a US layout no matter
84   // what character that key produces.
86   NSMenuItem* item;
87   NSEvent* key;
88   unichar ch;
89   NSString* s;
91   // Sanity
92   item = MenuItem(@"", 0);
93   EXPECT_TRUE([item isEnabled]);
95   // a
96   key = KeyEvent(0x100, @"a", @"a", 0);
97   item = MenuItem(@"a", 0);
98   ExpectKeyFiresItem(key, item);
99   ExpectKeyDoesntFireItem(KeyEvent(0x20102, @"A", @"A", 0), item);
101   // Disabled menu item
102   key = KeyEvent(0x100, @"a", @"a", 0);
103   item = MenuItem(@"a", 0);
104   [item setEnabled:NO];
105   ExpectKeyDoesntFireItem(key, item, false);
107   // shift-a
108   key = KeyEvent(0x20102, @"A", @"A", 0);
109   item = MenuItem(@"A", 0);
110   ExpectKeyFiresItem(key, item);
111   ExpectKeyDoesntFireItem(KeyEvent(0x100, @"a", @"a", 0), item);
113   // cmd-opt-shift-a
114   key = KeyEvent(0x1a012a, @"\u00c5", @"A", 0);
115   item = MenuItem(@"A", 0x180000);
116   ExpectKeyFiresItem(key, item);
118   // cmd-opt-a
119   key = KeyEvent(0x18012a, @"\u00e5", @"a", 0);
120   item = MenuItem(@"a", 0x180000);
121   ExpectKeyFiresItem(key, item);
123   // cmd-=
124   key = KeyEvent(0x100110, @"=", @"=", 0x18);
125   item = MenuItem(@"=", 0x100000);
126   ExpectKeyFiresItem(key, item);
128   // cmd-shift-=
129   key = KeyEvent(0x12010a, @"=", @"+", 0x18);
130   item = MenuItem(@"+", 0x100000);
131   ExpectKeyFiresItem(key, item);
133   // Turns out Cocoa fires "+ 100108 + 18" if you hit cmd-= and the menu only
134   // has a cmd-+ shortcut. But that's transparent for |cr_firesForKeyEvent:|.
136   // ctrl-3
137   key = KeyEvent(0x40101, @"3", @"3", 0x14);
138   item = MenuItem(@"3", 0x40000);
139   ExpectKeyFiresItem(key, item);
141   // return
142   key = KeyEvent(0, @"\r", @"\r", 0x24);
143   item = MenuItem(@"\r", 0);
144   ExpectKeyFiresItem(key, item);
146   // shift-return
147   key = KeyEvent(0x20102, @"\r", @"\r", 0x24);
148   item = MenuItem(@"\r", 0x20000);
149   ExpectKeyFiresItem(key, item);
151   // shift-left
152   ch = NSLeftArrowFunctionKey;
153   s = [NSString stringWithCharacters:&ch length:1];
154   key = KeyEvent(0xa20102, s, s, 0x7b);
155   item = MenuItem(s, 0x20000);
156   ExpectKeyFiresItem(key, item);
158   // shift-f1 (with a layout that needs the fn key down for f1)
159   ch = NSF1FunctionKey;
160   s = [NSString stringWithCharacters:&ch length:1];
161   key = KeyEvent(0x820102, s, s, 0x7a);
162   item = MenuItem(s, 0x20000);
163   ExpectKeyFiresItem(key, item);
165   // esc
166   // Turns out this doesn't fire.
167   key = KeyEvent(0x100, @"\e", @"\e", 0x35);
168   item = MenuItem(@"\e", 0);
169   ExpectKeyDoesntFireItem(key,item, false);
171   // shift-esc
172   // Turns out this doesn't fire.
173   key = KeyEvent(0x20102, @"\e", @"\e", 0x35);
174   item = MenuItem(@"\e", 0x20000);
175   ExpectKeyDoesntFireItem(key,item, false);
177   // cmd-esc
178   key = KeyEvent(0x100108, @"\e", @"\e", 0x35);
179   item = MenuItem(@"\e", 0x100000);
180   ExpectKeyFiresItem(key, item);
182   // ctrl-esc
183   key = KeyEvent(0x40101, @"\e", @"\e", 0x35);
184   item = MenuItem(@"\e", 0x40000);
185   ExpectKeyFiresItem(key, item);
187   // delete ("backspace")
188   key = KeyEvent(0x100, @"\x7f", @"\x7f", 0x33);
189   item = MenuItem(@"\x08", 0);
190   ExpectKeyFiresItem(key, item, false);
192   // shift-delete
193   key = KeyEvent(0x20102, @"\x7f", @"\x7f", 0x33);
194   item = MenuItem(@"\x08", 0x20000);
195   ExpectKeyFiresItem(key, item, false);
197   // forwarddelete (fn-delete / fn-backspace)
198   ch = NSDeleteFunctionKey;
199   s = [NSString stringWithCharacters:&ch length:1];
200   key = KeyEvent(0x800100, s, s, 0x75);
201   item = MenuItem(@"\x7f", 0);
202   ExpectKeyFiresItem(key, item, false);
204   // shift-forwarddelete (shift-fn-delete / shift-fn-backspace)
205   ch = NSDeleteFunctionKey;
206   s = [NSString stringWithCharacters:&ch length:1];
207   key = KeyEvent(0x820102, s, s, 0x75);
208   item = MenuItem(@"\x7f", 0x20000);
209   ExpectKeyFiresItem(key, item, false);
211   // fn-left
212   ch = NSHomeFunctionKey;
213   s = [NSString stringWithCharacters:&ch length:1];
214   key = KeyEvent(0x800100, s, s, 0x73);
215   item = MenuItem(s, 0);
216   ExpectKeyFiresItem(key, item);
218   // cmd-left
219   ch = NSLeftArrowFunctionKey;
220   s = [NSString stringWithCharacters:&ch length:1];
221   key = KeyEvent(0xb00108, s, s, 0x7b);
222   item = MenuItem(s, 0x100000);
223   ExpectKeyFiresItem(key, item);
225   // Hitting the "a" key with a russian keyboard layout -- does not fire
226   // a menu item that has "a" as key equiv.
227   key = KeyEvent(0x100, @"\u0444", @"\u0444", 0);
228   item = MenuItem(@"a", 0);
229   ExpectKeyDoesntFireItem(key,item);
231   // cmd-a on a russion layout -- fires for a menu item with cmd-a as key equiv.
232   key = KeyEvent(0x100108, @"a", @"\u0444", 0);
233   item = MenuItem(@"a", 0x100000);
234   ExpectKeyFiresItem(key, item, false);
236   // cmd-z on US layout
237   key = KeyEvent(0x100108, @"z", @"z", 6);
238   item = MenuItem(@"z", 0x100000);
239   ExpectKeyFiresItem(key, item);
241   // cmd-y on german layout (has same keycode as cmd-z on us layout, shouldn't
242   // fire).
243   key = KeyEvent(0x100108, @"y", @"y", 6);
244   item = MenuItem(@"z", 0x100000);
245   ExpectKeyDoesntFireItem(key,item);
247   // cmd-z on german layout
248   key = KeyEvent(0x100108, @"z", @"z", 0x10);
249   item = MenuItem(@"z", 0x100000);
250   ExpectKeyFiresItem(key, item);
252   // fn-return (== enter)
253   key = KeyEvent(0x800100, @"\x3", @"\x3", 0x4c);
254   item = MenuItem(@"\r", 0);
255   ExpectKeyDoesntFireItem(key,item);
257   // cmd-z on dvorak layout (so that the key produces ';')
258   key = KeyEvent(0x100108, @";", @";", 6);
259   ExpectKeyDoesntFireItem(key, MenuItem(@"z", 0x100000));
260   ExpectKeyFiresItem(key, MenuItem(@";", 0x100000));
262   // cmd-z on dvorak qwerty layout (so that the key produces ';', but 'z' if
263   // cmd is down)
264   key = KeyEvent(0x100108, @"z", @";", 6);
265   ExpectKeyFiresItem(key, MenuItem(@"z", 0x100000), false);
266   ExpectKeyDoesntFireItem(key, MenuItem(@";", 0x100000), false);
268   // cmd-shift-z on dvorak layout (so that we get a ':')
269   key = KeyEvent(0x12010a, @";", @":", 6);
270   ExpectKeyFiresItem(key, MenuItem(@":", 0x100000));
271   ExpectKeyDoesntFireItem(key, MenuItem(@";", 0x100000));
273   // cmd-s with a serbian layout (just "s" produces something that looks a lot
274   // like "c" in some fonts, but is actually \u0441. cmd-s activates a menu item
275   // with key equivalent "s", not "c")
276   key = KeyEvent(0x100108, @"s", @"\u0441", 1);
277   ExpectKeyFiresItem(key, MenuItem(@"s", 0x100000), false);
278   ExpectKeyDoesntFireItem(key, MenuItem(@"c", 0x100000));
281 NSString* keyCodeToCharacter(NSUInteger keyCode,
282                              EventModifiers modifiers,
283                              TISInputSourceRef layout) {
284   CFDataRef uchr = (CFDataRef)TISGetInputSourceProperty(
285       layout, kTISPropertyUnicodeKeyLayoutData);
286   UCKeyboardLayout* keyLayout = (UCKeyboardLayout*)CFDataGetBytePtr(uchr);
288   UInt32 deadKeyState = 0;
289   OSStatus err = noErr;
290   UniCharCount maxStringLength = 4, actualStringLength;
291   UniChar unicodeString[4];
292   err = UCKeyTranslate(keyLayout,
293       (UInt16)keyCode,
294       kUCKeyActionDown,
295       modifiers,
296       LMGetKbdType(),
297       kUCKeyTranslateNoDeadKeysBit,
298       &deadKeyState,
299       maxStringLength,
300       &actualStringLength,
301       unicodeString);
302   assert(err == noErr);
304   CFStringRef temp = CFStringCreateWithCharacters(
305       kCFAllocatorDefault, unicodeString, 1);
306   return [(NSString*)temp autorelease];
309 TEST(NSMenuItemAdditionsTest, TestMOnDifferentLayouts) {
310   // There's one key -- "m" -- that has the same keycode on most keyboard
311   // layouts. This function tests a menu item with cmd-m as key equivalent
312   // can be fired on all layouts.
313   NSMenuItem* item = MenuItem(@"m", 0x100000);
315   NSDictionary* filter = [NSDictionary
316     dictionaryWithObject:(NSString*)kTISTypeKeyboardLayout
317                   forKey:(NSString*)kTISPropertyInputSourceType];
319   // Docs say that including all layouts instead of just the active ones is
320   // slow, but there's no way around that.
321   NSArray* list = (NSArray*)TISCreateInputSourceList(
322       (CFDictionaryRef)filter, true);
323   for (id layout in list) {
324     TISInputSourceRef ref = (TISInputSourceRef)layout;
326     NSUInteger keyCode = 0x2e;  // "m" on a US layout and most other layouts.
328     // On a few layouts, "m" has a different key code.
329     NSString* layoutId = (NSString*)TISGetInputSourceProperty(
330         ref, kTISPropertyInputSourceID);
331     if ([layoutId isEqualToString:@"com.apple.keylayout.Belgian"] ||
332         [layoutId isEqualToString:@"com.apple.keylayout.French"] ||
333         [layoutId isEqualToString:@"com.apple.keylayout.French-numerical"] ||
334         [layoutId isEqualToString:@"com.apple.keylayout.Italian"]) {
335       keyCode = 0x29;
336     } else if ([layoutId isEqualToString:@"com.apple.keylayout.Turkish"]) {
337       keyCode = 0x28;
338     } else if ([layoutId isEqualToString:@"com.apple.keylayout.Dvorak-Left"]) {
339       keyCode = 0x16;
340     } else if ([layoutId isEqualToString:@"com.apple.keylayout.Dvorak-Right"]) {
341       keyCode = 0x1a;
342     }
344     EventModifiers modifiers = cmdKey >> 8;
345     NSString* chars = keyCodeToCharacter(keyCode, modifiers, ref);
346     NSString* charsIgnoringMods = keyCodeToCharacter(keyCode, 0, ref);
347     NSEvent* key = KeyEvent(0x100000, chars, charsIgnoringMods, keyCode);
348     ExpectKeyFiresItem(key, item, false);
349   }
350   CFRelease(list);