| Index: chrome/browser/cocoa/nsmenuitem_additions_unittest.mm
|
| diff --git a/chrome/browser/cocoa/nsmenuitem_additions_unittest.mm b/chrome/browser/cocoa/nsmenuitem_additions_unittest.mm
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..1279e0c63779197a8cd112be7c985b19479902e2
|
| --- /dev/null
|
| +++ b/chrome/browser/cocoa/nsmenuitem_additions_unittest.mm
|
| @@ -0,0 +1,341 @@
|
| +#import "chrome/browser/cocoa/nsmenuitem_additions.h"
|
| +
|
| +#include <iostream>
|
| +#include <Carbon/Carbon.h>
|
| +
|
| +#include "base/scoped_nsobject.h"
|
| +#include "base/sys_string_conversions.h"
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +
|
| +NSEvent* KeyEvent(const NSUInteger modifierFlags,
|
| + NSString* chars,
|
| + NSString* charsNoMods,
|
| + const NSUInteger keyCode) {
|
| + return [NSEvent keyEventWithType:NSKeyDown
|
| + location:NSZeroPoint
|
| + modifierFlags:modifierFlags
|
| + timestamp:0.0
|
| + windowNumber:0
|
| + context:nil
|
| + characters:chars
|
| + charactersIgnoringModifiers:charsNoMods
|
| + isARepeat:NO
|
| + keyCode:keyCode];
|
| +}
|
| +
|
| +NSMenuItem* MenuItem(NSString* equiv, NSUInteger mask) {
|
| + NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle:@""
|
| + action:NULL
|
| + keyEquivalent:@""] autorelease];
|
| + [item setKeyEquivalent:equiv];
|
| + [item setKeyEquivalentModifierMask:mask];
|
| + return item;
|
| +}
|
| +
|
| +std::ostream& operator<<(std::ostream& out, NSObject* obj) {
|
| + return out << base::SysNSStringToUTF8([obj description]);
|
| +}
|
| +
|
| +std::ostream& operator<<(std::ostream& out, NSMenuItem* item) {
|
| + return out << "NSMenuItem " << base::SysNSStringToUTF8([item keyEquivalent]);
|
| +}
|
| +
|
| +void ExpectKeyFiresItemEq(bool result, NSEvent* key, NSMenuItem* item,
|
| + bool compareCocoa ) {
|
| + EXPECT_EQ(result, [item cr_firesForKeyEvent:key]) << key << '\n' << item;
|
| +
|
| + // Make sure that Cocoa does in fact agree with our expectations. However,
|
| + // in some cases cocoa behaves weirdly (if you create e.g. a new event that
|
| + // contains all fields of the event that you get when hitting cmd-a with a
|
| + // russion keyboard layout, the copy won't fire a menu item that has cmd-a as
|
| + // key equivalent, even though the original event would) and isn't a good
|
| + // oracle function.
|
| + if (compareCocoa) {
|
| + scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:@"Menu!"]);
|
| + [menu setAutoenablesItems:NO];
|
| + EXPECT_FALSE([menu performKeyEquivalent:key]);
|
| + [menu addItem:item];
|
| + EXPECT_EQ(result, [menu performKeyEquivalent:key]) << key << '\n' << item;
|
| + }
|
| +}
|
| +
|
| +void ExpectKeyFiresItem(
|
| + NSEvent* key, NSMenuItem* item, bool compareCocoa = true) {
|
| + ExpectKeyFiresItemEq(true, key, item, compareCocoa);
|
| +}
|
| +
|
| +void ExpectKeyDoesntFireItem(
|
| + NSEvent* key, NSMenuItem* item, bool compareCocoa = true) {
|
| + ExpectKeyFiresItemEq(false, key, item, compareCocoa);
|
| +}
|
| +
|
| +TEST(NSMenuItemAdditionsTest, TestFiresForKeyEvent) {
|
| + // These test cases were built by writing a small test app that has a
|
| + // MainMenu.xib with a given key equivalent set in Interface Builder and a
|
| + // some code that prints both the key equivalent that fires a menu item and
|
| + // the menu item's key equivalent and modifier masks. I then pasted those
|
| + // below. This was done with a US layout, unless otherwise noted. In the
|
| + // comments, "z" always means the physical "z" key on a US layout no matter
|
| + // what character that key produces.
|
| +
|
| + NSMenuItem* item;
|
| + NSEvent* key;
|
| + unichar ch;
|
| + NSString* s;
|
| +
|
| + // Sanity
|
| + item = MenuItem(@"", 0);
|
| + EXPECT_TRUE([item isEnabled]);
|
| +
|
| + // a
|
| + key = KeyEvent(0x100, @"a", @"a", 0);
|
| + item = MenuItem(@"a", 0);
|
| + ExpectKeyFiresItem(key, item);
|
| + ExpectKeyDoesntFireItem(KeyEvent(0x20102, @"A", @"A", 0), item);
|
| +
|
| + // Disabled menu item
|
| + key = KeyEvent(0x100, @"a", @"a", 0);
|
| + item = MenuItem(@"a", 0);
|
| + [item setEnabled:NO];
|
| + ExpectKeyDoesntFireItem(key, item, false);
|
| +
|
| + // shift-a
|
| + key = KeyEvent(0x20102, @"A", @"A", 0);
|
| + item = MenuItem(@"A", 0);
|
| + ExpectKeyFiresItem(key, item);
|
| + ExpectKeyDoesntFireItem(KeyEvent(0x100, @"a", @"a", 0), item);
|
| +
|
| + // cmd-opt-shift-a
|
| + key = KeyEvent(0x1a012a, @"\u00c5", @"A", 0);
|
| + item = MenuItem(@"A", 0x180000);
|
| + ExpectKeyFiresItem(key, item);
|
| +
|
| + // cmd-opt-a
|
| + key = KeyEvent(0x18012a, @"\u00e5", @"a", 0);
|
| + item = MenuItem(@"a", 0x180000);
|
| + ExpectKeyFiresItem(key, item);
|
| +
|
| + // cmd-=
|
| + key = KeyEvent(0x100110, @"=", @"=", 0x18);
|
| + item = MenuItem(@"=", 0x100000);
|
| + ExpectKeyFiresItem(key, item);
|
| +
|
| + // cmd-shift-=
|
| + key = KeyEvent(0x12010a, @"=", @"+", 0x18);
|
| + item = MenuItem(@"+", 0x100000);
|
| + ExpectKeyFiresItem(key, item);
|
| +
|
| + // Turns out Cocoa fires "+ 100108 + 18" if you hit cmd-= and the menu only
|
| + // has a cmd-+ shortcut. But that's transparent for |cr_firesForKeyEvent:|.
|
| +
|
| + // ctrl-3
|
| + key = KeyEvent(0x40101, @"3", @"3", 0x14);
|
| + item = MenuItem(@"3", 0x40000);
|
| + ExpectKeyFiresItem(key, item);
|
| +
|
| + // return
|
| + key = KeyEvent(0, @"\r", @"\r", 0x24);
|
| + item = MenuItem(@"\r", 0);
|
| + ExpectKeyFiresItem(key, item);
|
| +
|
| + // shift-return
|
| + key = KeyEvent(0x20102, @"\r", @"\r", 0x24);
|
| + item = MenuItem(@"\r", 0x20000);
|
| + ExpectKeyFiresItem(key, item);
|
| +
|
| + // shift-left
|
| + ch = NSLeftArrowFunctionKey;
|
| + s = [NSString stringWithCharacters:&ch length:1];
|
| + key = KeyEvent(0xa20102, s, s, 0x7b);
|
| + item = MenuItem(s, 0x20000);
|
| + ExpectKeyFiresItem(key, item);
|
| +
|
| + // shift-f1 (with a layout that needs the fn key down for f1)
|
| + ch = NSF1FunctionKey;
|
| + s = [NSString stringWithCharacters:&ch length:1];
|
| + key = KeyEvent(0x820102, s, s, 0x7a);
|
| + item = MenuItem(s, 0x20000);
|
| + ExpectKeyFiresItem(key, item);
|
| +
|
| + // esc
|
| + // Turns out this doesn't fire.
|
| + key = KeyEvent(0x100, @"\e", @"\e", 0x35);
|
| + item = MenuItem(@"\e", 0);
|
| + ExpectKeyDoesntFireItem(key,item, false);
|
| +
|
| + // shift-esc
|
| + // Turns out this doesn't fire.
|
| + key = KeyEvent(0x20102, @"\e", @"\e", 0x35);
|
| + item = MenuItem(@"\e", 0x20000);
|
| + ExpectKeyDoesntFireItem(key,item, false);
|
| +
|
| + // cmd-esc
|
| + key = KeyEvent(0x100108, @"\e", @"\e", 0x35);
|
| + item = MenuItem(@"\e", 0x100000);
|
| + ExpectKeyFiresItem(key, item);
|
| +
|
| + // ctrl-esc
|
| + key = KeyEvent(0x40101, @"\e", @"\e", 0x35);
|
| + item = MenuItem(@"\e", 0x40000);
|
| + ExpectKeyFiresItem(key, item);
|
| +
|
| + // delete ("backspace")
|
| + key = KeyEvent(0x100, @"\x7f", @"\x7f", 0x33);
|
| + item = MenuItem(@"\x08", 0);
|
| + ExpectKeyFiresItem(key, item, false);
|
| +
|
| + // shift-delete
|
| + key = KeyEvent(0x20102, @"\x7f", @"\x7f", 0x33);
|
| + item = MenuItem(@"\x08", 0x20000);
|
| + ExpectKeyFiresItem(key, item, false);
|
| +
|
| + // forwarddelete (fn-delete / fn-backspace)
|
| + ch = NSDeleteFunctionKey;
|
| + s = [NSString stringWithCharacters:&ch length:1];
|
| + key = KeyEvent(0x800100, s, s, 0x75);
|
| + item = MenuItem(@"\x7f", 0);
|
| + ExpectKeyFiresItem(key, item, false);
|
| +
|
| + // shift-forwarddelete (shift-fn-delete / shift-fn-backspace)
|
| + ch = NSDeleteFunctionKey;
|
| + s = [NSString stringWithCharacters:&ch length:1];
|
| + key = KeyEvent(0x820102, s, s, 0x75);
|
| + item = MenuItem(@"\x7f", 0x20000);
|
| + ExpectKeyFiresItem(key, item, false);
|
| +
|
| + // fn-left
|
| + ch = NSHomeFunctionKey;
|
| + s = [NSString stringWithCharacters:&ch length:1];
|
| + key = KeyEvent(0x800100, s, s, 0x73);
|
| + item = MenuItem(s, 0);
|
| + ExpectKeyFiresItem(key, item);
|
| +
|
| + // cmd-left
|
| + ch = NSLeftArrowFunctionKey;
|
| + s = [NSString stringWithCharacters:&ch length:1];
|
| + key = KeyEvent(0xb00108, s, s, 0x7b);
|
| + item = MenuItem(s, 0x100000);
|
| + ExpectKeyFiresItem(key, item);
|
| +
|
| + // Hitting the "a" key with a russian keyboard layout -- does not fire
|
| + // a menu item that has "a" as key equiv.
|
| + key = KeyEvent(0x100, @"\u0444", @"\u0444", 0);
|
| + item = MenuItem(@"a", 0);
|
| + ExpectKeyDoesntFireItem(key,item);
|
| +
|
| + // cmd-a on a russion layout -- fires for a menu item with cmd-a as key equiv.
|
| + key = KeyEvent(0x100108, @"a", @"\u0444", 0);
|
| + item = MenuItem(@"a", 0x100000);
|
| + ExpectKeyFiresItem(key, item, false);
|
| +
|
| + // cmd-z on US layout
|
| + key = KeyEvent(0x100108, @"z", @"z", 6);
|
| + item = MenuItem(@"z", 0x100000);
|
| + ExpectKeyFiresItem(key, item);
|
| +
|
| + // cmd-y on german layout (has same keycode as cmd-z on us layout, shouldn't
|
| + // fire).
|
| + key = KeyEvent(0x100108, @"y", @"y", 6);
|
| + item = MenuItem(@"z", 0x100000);
|
| + ExpectKeyDoesntFireItem(key,item);
|
| +
|
| + // cmd-z on german layout
|
| + key = KeyEvent(0x100108, @"z", @"z", 0x10);
|
| + item = MenuItem(@"z", 0x100000);
|
| + ExpectKeyFiresItem(key, item);
|
| +
|
| + // fn-return (== enter)
|
| + key = KeyEvent(0x800100, @"\x3", @"\x3", 0x4c);
|
| + item = MenuItem(@"\r", 0);
|
| + ExpectKeyDoesntFireItem(key,item);
|
| +
|
| + // cmd-z on dvorak layout (so that the key produces ';')
|
| + key = KeyEvent(0x100108, @";", @";", 6);
|
| + ExpectKeyDoesntFireItem(key, MenuItem(@"z", 0x100000));
|
| + ExpectKeyFiresItem(key, MenuItem(@";", 0x100000));
|
| +
|
| + // cmd-z on dvorak qwerty layout (so that the key produces ';', but 'z' if
|
| + // cmd is down)
|
| + key = KeyEvent(0x100108, @"z", @";", 6);
|
| + ExpectKeyFiresItem(key, MenuItem(@"z", 0x100000), false);
|
| + ExpectKeyDoesntFireItem(key, MenuItem(@";", 0x100000), false);
|
| +
|
| + // cmd-shift-z on dvorak layout (so that we get a ':')
|
| + key = KeyEvent(0x12010a, @";", @":", 6);
|
| + ExpectKeyFiresItem(key, MenuItem(@":", 0x100000));
|
| + ExpectKeyDoesntFireItem(key, MenuItem(@";", 0x100000));
|
| +
|
| + // cmd-s with a serbian layout (just "s" produces something that looks a lot
|
| + // like "c" in some fonts, but is actually \u0441. cmd-s activates a menu item
|
| + // with key equivalent "s", not "c")
|
| + key = KeyEvent(0x100108, @"s", @"\u0441", 1);
|
| + ExpectKeyFiresItem(key, MenuItem(@"s", 0x100000), false);
|
| + ExpectKeyDoesntFireItem(key, MenuItem(@"c", 0x100000));
|
| +}
|
| +
|
| +NSString* keyCodeToCharacter(NSUInteger keyCode,
|
| + EventModifiers modifiers,
|
| + TISInputSourceRef layout) {
|
| + CFDataRef uchr = (CFDataRef)TISGetInputSourceProperty(
|
| + layout, kTISPropertyUnicodeKeyLayoutData);
|
| + UCKeyboardLayout* keyLayout = (UCKeyboardLayout*)CFDataGetBytePtr(uchr);
|
| +
|
| + UInt32 deadKeyState = 0;
|
| + OSStatus err = noErr;
|
| + UniCharCount maxStringLength = 4, actualStringLength;
|
| + UniChar unicodeString[4];
|
| + err = UCKeyTranslate(keyLayout,
|
| + (UInt16)keyCode,
|
| + kUCKeyActionDown,
|
| + modifiers,
|
| + LMGetKbdType(),
|
| + kUCKeyTranslateNoDeadKeysBit,
|
| + &deadKeyState,
|
| + maxStringLength,
|
| + &actualStringLength,
|
| + unicodeString);
|
| + assert(err == noErr);
|
| +
|
| + CFStringRef temp = CFStringCreateWithCharacters(
|
| + kCFAllocatorDefault, unicodeString, 1);
|
| + return [(NSString*)temp autorelease];
|
| +}
|
| +
|
| +TEST(NSMenuItemAdditionsTest, TestMOnDifferentLayouts) {
|
| + // There's one key -- "m" -- that has the same keycode on most keyboard
|
| + //layouts. This function tests a menu item with cmd-m as key equivalent
|
| + // can be fired on all layouts.
|
| + NSMenuItem* item = MenuItem(@"m", 0x100000);
|
| +
|
| + NSDictionary* filter = [NSDictionary
|
| + dictionaryWithObject:(NSString*)kTISTypeKeyboardLayout
|
| + forKey:(NSString*)kTISPropertyInputSourceType];
|
| +
|
| + // Docs say that including all layouts instead of just the active ones is
|
| + // slow, but there's no way around that.
|
| + NSArray* list = (NSArray*)TISCreateInputSourceList(
|
| + (CFDictionaryRef)filter, true);
|
| + for (id layout in list) {
|
| + TISInputSourceRef ref = (TISInputSourceRef)layout;
|
| +
|
| + NSUInteger keyCode = 0x2e; // "m" on a us layout and most other layouts.
|
| +
|
| + // On a few layouts, "m" has a different key code.
|
| + NSString* layoutId = (NSString*)TISGetInputSourceProperty(
|
| + ref, kTISPropertyInputSourceID);
|
| + if ([layoutId isEqualToString:@"com.apple.keylayout.Belgian"] ||
|
| + [layoutId isEqualToString:@"com.apple.keylayout.French"] ||
|
| + [layoutId isEqualToString:@"com.apple.keylayout.French-numerical"] ||
|
| + [layoutId isEqualToString:@"com.apple.keylayout.Italian"])
|
| + keyCode = 0x29;
|
| + else if ([layoutId isEqualToString:@"com.apple.keylayout.Turkish"])
|
| + keyCode = 0x28;
|
| +
|
| + EventModifiers modifiers = cmdKey >> 8;
|
| + NSString* chars = keyCodeToCharacter(keyCode, modifiers, ref);
|
| + NSString* charsIgnoringMods = keyCodeToCharacter(keyCode, 0, ref);
|
| + NSEvent* key = KeyEvent(0x100000, chars, charsIgnoringMods, keyCode);
|
| + ExpectKeyFiresItem(key, item, false);
|
| + }
|
| + CFRelease(list);
|
| +}
|
|
|