1// Copyright 2014 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#include "base/logging.h"
8#import "chrome/browser/ui/cocoa/accelerators_cocoa.h"
9#include "chrome/test/base/in_process_browser_test.h"
10#include "ui/base/accelerators/platform_accelerator_cocoa.h"
11#import "ui/events/keycodes/keyboard_code_conversion_mac.h"
12#include "testing/gtest_mac.h"
13
14typedef InProcessBrowserTest AcceleratorsCocoaBrowserTest;
15
16namespace {
17
18// Adds all NSMenuItems with an accelerator to the array.
19void AddAcceleratorItemsToArray(NSMenu* menu, NSMutableArray* array) {
20  for (NSMenuItem* item in [menu itemArray]) {
21    NSMenu* submenu = item.submenu;
22    if (submenu)
23      AddAcceleratorItemsToArray(submenu, array);
24
25    if (item.keyEquivalent.length > 0)
26      [array addObject:item];
27  }
28}
29
30// Returns the NSMenuItem that has the given keyEquivalent and modifiers, or
31// nil.
32NSMenuItem* MenuContainsAccelerator(NSMenu* menu,
33                                    NSString* key_equivalent,
34                                    NSUInteger modifiers) {
35  for (NSMenuItem* item in [menu itemArray]) {
36    NSMenu* submenu = item.submenu;
37    if (submenu) {
38      NSMenuItem* result =
39          MenuContainsAccelerator(submenu, key_equivalent, modifiers);
40      if (result)
41        return result;
42    }
43
44    if ([item.keyEquivalent isEqual:key_equivalent]) {
45      BOOL maskEqual =
46          (modifiers == item.keyEquivalentModifierMask) ||
47          ((modifiers & (~NSShiftKeyMask)) == item.keyEquivalentModifierMask);
48      if (maskEqual)
49        return item;
50    }
51  }
52  return nil;
53}
54
55}  // namespace
56
57// Checks that each NSMenuItem in the main menu has a corresponding accelerator,
58// and the keyEquivalent/modifiers match.
59IN_PROC_BROWSER_TEST_F(AcceleratorsCocoaBrowserTest,
60                       MainMenuAcceleratorsInMapping) {
61  NSMenu* menu = [NSApp mainMenu];
62  NSMutableArray* array = [NSMutableArray array];
63  AddAcceleratorItemsToArray(menu, array);
64
65  for (NSMenuItem* item in array) {
66    NSInteger command_id = item.tag;
67    AcceleratorsCocoa* keymap = AcceleratorsCocoa::GetInstance();
68    const ui::Accelerator* accelerator;
69
70    // If the tag is zero, then the NSMenuItem must use a custom selector.
71    // Check that the accelerator is present as an un-mapped accelerator.
72    if (command_id == 0) {
73      accelerator = keymap->GetAcceleratorForHotKey(
74          item.keyEquivalent, item.keyEquivalentModifierMask);
75
76      EXPECT_TRUE(accelerator);
77      return;
78    }
79
80    // If the tag isn't zero, then it must correspond to an IDC_* command.
81    accelerator = keymap->GetAcceleratorForCommand(command_id);
82    EXPECT_TRUE(accelerator);
83    if (!accelerator)
84      continue;
85
86    // Get the Cocoa key_equivalent associated with the accelerator.
87    const ui::PlatformAcceleratorCocoa* platform_accelerator =
88        static_cast<const ui::PlatformAcceleratorCocoa*>(
89            accelerator->platform_accelerator());
90    NSString* key_equivalent = platform_accelerator->characters();
91
92    // Check that the menu item's keyEquivalent matches the one from the
93    // Cocoa accelerator map.
94    EXPECT_NSEQ(key_equivalent, item.keyEquivalent);
95
96    // Check that the menu item's modifier mask matches the one stored in the
97    // accelerator. A mask that include NSShiftKeyMask may not include the
98    // relevant bit (the information is reflected in the keyEquivalent of the
99    // NSMenuItem).
100    NSUInteger mask = platform_accelerator->modifier_mask();
101    BOOL maskEqual =
102        (mask == item.keyEquivalentModifierMask) ||
103        ((mask & (~NSShiftKeyMask)) == item.keyEquivalentModifierMask);
104    EXPECT_TRUE(maskEqual);
105  }
106}
107
108// Check that each accelerator with a command_id has an associated NSMenuItem
109// in the main menu. If the selector is commandDispatch:, then the tag must
110// match the command_id.
111IN_PROC_BROWSER_TEST_F(AcceleratorsCocoaBrowserTest,
112                       MappingAcceleratorsInMainMenu) {
113  AcceleratorsCocoa* keymap = AcceleratorsCocoa::GetInstance();
114  for (AcceleratorsCocoa::AcceleratorMap::iterator it =
115           keymap->accelerators_.begin();
116       it != keymap->accelerators_.end();
117       ++it) {
118    const ui::PlatformAcceleratorCocoa* platform_accelerator =
119        static_cast<const ui::PlatformAcceleratorCocoa*>(
120            it->second.platform_accelerator());
121
122    // Check that there exists a corresponding NSMenuItem.
123    NSMenuItem* item =
124        MenuContainsAccelerator([NSApp mainMenu],
125                                platform_accelerator->characters(),
126                                platform_accelerator->modifier_mask());
127    EXPECT_TRUE(item);
128
129    // If the menu uses a commandDispatch:, the tag must match the command id!
130    if (item.action == @selector(commandDispatch:))
131      EXPECT_EQ(item.tag, it->first);
132  }
133}
134