1 // Copyright (c) 2012 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 #include "chrome/common/extensions/command.h"
7 #include "base/memory/scoped_ptr.h"
8 #include "base/strings/string_number_conversions.h"
9 #include "base/strings/string_util.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "base/values.h"
12 #include "testing/gtest/include/gtest/gtest.h"
14 class CommandTest
: public testing::Test
{
17 typedef const struct {
19 ui::Accelerator accelerator
;
20 const char* command_name
;
22 const char* description
;
23 } ConstCommandsTestData
;
25 // Checks the |suggested_key| value parses into a command when specified as a
26 // string or dictionary of platform specific keys. If
27 // |platform_specific_only| is true, only the latter is tested. |platforms|
28 // specifies all platforms to use when populating the |suggested_key|
30 void CheckParse(ConstCommandsTestData data
,
32 bool platform_specific_only
,
33 std::vector
<std::string
>& platforms
) {
34 SCOPED_TRACE(std::string("Command name: |") + data
.command_name
+ "| key: |" +
35 data
.key
+ "| description: |" + data
.description
+ "| index: " +
36 base::IntToString(i
));
38 extensions::Command command
;
39 scoped_ptr
<base::DictionaryValue
> input(new base::DictionaryValue
);
42 // First, test the parse of a string suggested_key value.
43 input
->SetString("suggested_key", data
.key
);
44 input
->SetString("description", data
.description
);
46 if (!platform_specific_only
) {
47 bool result
= command
.Parse(input
.get(), data
.command_name
, i
, &error
);
48 EXPECT_EQ(data
.expected_result
, result
);
50 EXPECT_STREQ(data
.description
,
51 base::UTF16ToASCII(command
.description()).c_str());
52 EXPECT_STREQ(data
.command_name
, command
.command_name().c_str());
53 EXPECT_EQ(data
.accelerator
, command
.accelerator());
57 // Now, test the parse of a platform dictionary suggested_key value.
58 if (data
.key
[0] != '\0') {
59 std::string current_platform
= extensions::Command::CommandPlatform();
60 if (platform_specific_only
&&
61 std::find(platforms
.begin(), platforms
.end(), current_platform
) ==
63 // Given a |current_platform| without a |suggested_key|, |default| is
64 // used. However, some keys, such as Search on Chrome OS, are only valid
65 // for platform specific entries. Skip the test in this case.
69 input
.reset(new base::DictionaryValue
);
70 base::DictionaryValue
* key_dict
= new base::DictionaryValue();
72 for (size_t j
= 0; j
< platforms
.size(); ++j
)
73 key_dict
->SetString(platforms
[j
], data
.key
);
75 input
->Set("suggested_key", key_dict
);
76 input
->SetString("description", data
.description
);
78 bool result
= command
.Parse(input
.get(), data
.command_name
, i
, &error
);
79 EXPECT_EQ(data
.expected_result
, result
);
82 EXPECT_STREQ(data
.description
,
83 base::UTF16ToASCII(command
.description()).c_str());
84 EXPECT_STREQ(data
.command_name
, command
.command_name().c_str());
85 EXPECT_EQ(data
.accelerator
, command
.accelerator());
90 TEST(CommandTest
, ExtensionCommandParsing
) {
91 const ui::Accelerator none
= ui::Accelerator();
92 const ui::Accelerator shift_f
= ui::Accelerator(ui::VKEY_F
,
94 #if defined(OS_MACOSX)
95 int ctrl
= ui::EF_COMMAND_DOWN
;
97 int ctrl
= ui::EF_CONTROL_DOWN
;
100 const ui::Accelerator ctrl_f
= ui::Accelerator(ui::VKEY_F
, ctrl
);
101 const ui::Accelerator alt_f
= ui::Accelerator(ui::VKEY_F
, ui::EF_ALT_DOWN
);
102 const ui::Accelerator ctrl_shift_f
=
103 ui::Accelerator(ui::VKEY_F
, ctrl
| ui::EF_SHIFT_DOWN
);
104 const ui::Accelerator alt_shift_f
=
105 ui::Accelerator(ui::VKEY_F
, ui::EF_ALT_DOWN
| ui::EF_SHIFT_DOWN
);
106 const ui::Accelerator ctrl_1
= ui::Accelerator(ui::VKEY_1
, ctrl
);
107 const ui::Accelerator ctrl_comma
= ui::Accelerator(ui::VKEY_OEM_COMMA
, ctrl
);
108 const ui::Accelerator ctrl_dot
= ui::Accelerator(ui::VKEY_OEM_PERIOD
, ctrl
);
109 const ui::Accelerator ctrl_left
= ui::Accelerator(ui::VKEY_LEFT
, ctrl
);
110 const ui::Accelerator ctrl_right
= ui::Accelerator(ui::VKEY_RIGHT
, ctrl
);
111 const ui::Accelerator ctrl_up
= ui::Accelerator(ui::VKEY_UP
, ctrl
);
112 const ui::Accelerator ctrl_down
= ui::Accelerator(ui::VKEY_DOWN
, ctrl
);
113 const ui::Accelerator ctrl_ins
= ui::Accelerator(ui::VKEY_INSERT
, ctrl
);
114 const ui::Accelerator ctrl_del
= ui::Accelerator(ui::VKEY_DELETE
, ctrl
);
115 const ui::Accelerator ctrl_home
= ui::Accelerator(ui::VKEY_HOME
, ctrl
);
116 const ui::Accelerator ctrl_end
= ui::Accelerator(ui::VKEY_END
, ctrl
);
117 const ui::Accelerator ctrl_pgup
= ui::Accelerator(ui::VKEY_PRIOR
, ctrl
);
118 const ui::Accelerator ctrl_pgdwn
= ui::Accelerator(ui::VKEY_NEXT
, ctrl
);
119 const ui::Accelerator next_track
=
120 ui::Accelerator(ui::VKEY_MEDIA_NEXT_TRACK
, ui::EF_NONE
);
121 const ui::Accelerator prev_track
=
122 ui::Accelerator(ui::VKEY_MEDIA_PREV_TRACK
, ui::EF_NONE
);
123 const ui::Accelerator play_pause
=
124 ui::Accelerator(ui::VKEY_MEDIA_PLAY_PAUSE
, ui::EF_NONE
);
125 const ui::Accelerator stop
=
126 ui::Accelerator(ui::VKEY_MEDIA_STOP
, ui::EF_NONE
);
128 ConstCommandsTestData kTests
[] = {
129 // Negative test (one or more missing required fields). We don't need to
130 // test |command_name| being blank as it is used as a key in the manifest,
131 // so it can't be blank (and we CHECK() when it is). A blank shortcut is
133 {false, none
, "command", "", ""},
134 {false, none
, "command", "Ctrl+f", ""},
135 // Ctrl+Alt is not permitted, see MSDN link in comments in Parse function.
136 {false, none
, "command", "Ctrl+Alt+F", "description"},
137 // Unsupported shortcuts/too many, or missing modifier.
138 {false, none
, "command", "A", "description"},
139 {false, none
, "command", "F10", "description"},
140 {false, none
, "command", "Ctrl+F+G", "description"},
141 {false, none
, "command", "Ctrl+Alt+Shift+G", "description"},
142 // Shift on its own is not supported.
143 {false, shift_f
, "command", "Shift+F", "description"},
144 {false, shift_f
, "command", "F+Shift", "description"},
146 {true, none
, "command", "", "description"},
147 {true, ctrl_f
, "command", "Ctrl+F", "description"},
148 {true, alt_f
, "command", "Alt+F", "description"},
149 {true, ctrl_shift_f
, "command", "Ctrl+Shift+F", "description"},
150 {true, alt_shift_f
, "command", "Alt+Shift+F", "description"},
151 {true, ctrl_1
, "command", "Ctrl+1", "description"},
152 // Shortcut token order tests.
153 {true, ctrl_f
, "command", "F+Ctrl", "description"},
154 {true, alt_f
, "command", "F+Alt", "description"},
155 {true, ctrl_shift_f
, "command", "F+Ctrl+Shift", "description"},
156 {true, ctrl_shift_f
, "command", "F+Shift+Ctrl", "description"},
157 {true, alt_shift_f
, "command", "F+Alt+Shift", "description"},
158 {true, alt_shift_f
, "command", "F+Shift+Alt", "description"},
159 // Case insensitivity is not OK.
160 {false, ctrl_f
, "command", "Ctrl+f", "description"},
161 {false, ctrl_f
, "command", "cTrL+F", "description"},
162 // Skipping description is OK for browser- and pageActions.
163 {true, ctrl_f
, "_execute_browser_action", "Ctrl+F", ""},
164 {true, ctrl_f
, "_execute_page_action", "Ctrl+F", ""},
165 // Home, End, Arrow keys, etc.
166 {true, ctrl_comma
, "_execute_browser_action", "Ctrl+Comma", ""},
167 {true, ctrl_dot
, "_execute_browser_action", "Ctrl+Period", ""},
168 {true, ctrl_left
, "_execute_browser_action", "Ctrl+Left", ""},
169 {true, ctrl_right
, "_execute_browser_action", "Ctrl+Right", ""},
170 {true, ctrl_up
, "_execute_browser_action", "Ctrl+Up", ""},
171 {true, ctrl_down
, "_execute_browser_action", "Ctrl+Down", ""},
172 {true, ctrl_ins
, "_execute_browser_action", "Ctrl+Insert", ""},
173 {true, ctrl_del
, "_execute_browser_action", "Ctrl+Delete", ""},
174 {true, ctrl_home
, "_execute_browser_action", "Ctrl+Home", ""},
175 {true, ctrl_end
, "_execute_browser_action", "Ctrl+End", ""},
176 {true, ctrl_pgup
, "_execute_browser_action", "Ctrl+PageUp", ""},
177 {true, ctrl_pgdwn
, "_execute_browser_action", "Ctrl+PageDown", ""},
179 {true, next_track
, "command", "MediaNextTrack", "description"},
180 {true, play_pause
, "command", "MediaPlayPause", "description"},
181 {true, prev_track
, "command", "MediaPrevTrack", "description"},
182 {true, stop
, "command", "MediaStop", "description"},
183 {false, none
, "_execute_browser_action", "MediaNextTrack", ""},
184 {false, none
, "_execute_page_action", "MediaPrevTrack", ""},
185 {false, none
, "command", "Ctrl+Shift+MediaPrevTrack", "description"},
187 std::vector
<std::string
> all_platforms
;
188 all_platforms
.push_back("default");
189 all_platforms
.push_back("chromeos");
190 all_platforms
.push_back("linux");
191 all_platforms
.push_back("mac");
192 all_platforms
.push_back("windows");
194 for (size_t i
= 0; i
< arraysize(kTests
); ++i
)
195 CheckParse(kTests
[i
], i
, false, all_platforms
);
198 TEST(CommandTest
, ExtensionCommandParsingFallback
) {
199 std::string description
= "desc";
200 std::string command_name
= "foo";
202 // Test that platform specific keys are honored on each platform, despite
203 // fallback being given.
204 scoped_ptr
<base::DictionaryValue
> input(new base::DictionaryValue
);
205 base::DictionaryValue
* key_dict
= new base::DictionaryValue();
206 key_dict
->SetString("default", "Ctrl+Shift+D");
207 key_dict
->SetString("windows", "Ctrl+Shift+W");
208 key_dict
->SetString("mac", "Ctrl+Shift+M");
209 key_dict
->SetString("linux", "Ctrl+Shift+L");
210 key_dict
->SetString("chromeos", "Ctrl+Shift+C");
211 input
->Set("suggested_key", key_dict
);
212 input
->SetString("description", description
);
214 extensions::Command command
;
215 base::string16 error
;
216 EXPECT_TRUE(command
.Parse(input
.get(), command_name
, 0, &error
));
217 EXPECT_STREQ(description
.c_str(),
218 base::UTF16ToASCII(command
.description()).c_str());
219 EXPECT_STREQ(command_name
.c_str(), command
.command_name().c_str());
222 ui::Accelerator
accelerator(ui::VKEY_W
,
223 ui::EF_SHIFT_DOWN
| ui::EF_CONTROL_DOWN
);
224 #elif defined(OS_MACOSX)
225 ui::Accelerator
accelerator(ui::VKEY_M
,
226 ui::EF_SHIFT_DOWN
| ui::EF_COMMAND_DOWN
);
227 #elif defined(OS_CHROMEOS)
228 ui::Accelerator
accelerator(ui::VKEY_C
,
229 ui::EF_SHIFT_DOWN
| ui::EF_CONTROL_DOWN
);
230 #elif defined(OS_LINUX)
231 ui::Accelerator
accelerator(ui::VKEY_L
,
232 ui::EF_SHIFT_DOWN
| ui::EF_CONTROL_DOWN
);
234 ui::Accelerator
accelerator(ui::VKEY_D
,
235 ui::EF_SHIFT_DOWN
| ui::EF_CONTROL_DOWN
);
237 EXPECT_EQ(accelerator
, command
.accelerator());
239 // Misspell a platform.
240 key_dict
->SetString("windosw", "Ctrl+M");
241 EXPECT_FALSE(command
.Parse(input
.get(), command_name
, 0, &error
));
242 EXPECT_TRUE(key_dict
->Remove("windosw", NULL
));
244 // Now remove platform specific keys (leaving just "default") and make sure
245 // every platform falls back to the default.
246 EXPECT_TRUE(key_dict
->Remove("windows", NULL
));
247 EXPECT_TRUE(key_dict
->Remove("mac", NULL
));
248 EXPECT_TRUE(key_dict
->Remove("linux", NULL
));
249 EXPECT_TRUE(key_dict
->Remove("chromeos", NULL
));
250 EXPECT_TRUE(command
.Parse(input
.get(), command_name
, 0, &error
));
251 EXPECT_EQ(ui::VKEY_D
, command
.accelerator().key_code());
253 // Now remove "default", leaving no option but failure. Or, in the words of
254 // the immortal Adam Savage: "Failure is always an option".
255 EXPECT_TRUE(key_dict
->Remove("default", NULL
));
256 EXPECT_FALSE(command
.Parse(input
.get(), command_name
, 0, &error
));
258 // Make sure Command is not supported for non-Mac platforms.
259 key_dict
->SetString("default", "Command+M");
260 EXPECT_FALSE(command
.Parse(input
.get(), command_name
, 0, &error
));
261 EXPECT_TRUE(key_dict
->Remove("default", NULL
));
262 key_dict
->SetString("windows", "Command+M");
263 EXPECT_FALSE(command
.Parse(input
.get(), command_name
, 0, &error
));
264 EXPECT_TRUE(key_dict
->Remove("windows", NULL
));
266 // Now add only a valid platform that we are not running on to make sure devs
267 // are notified of errors on other platforms.
269 key_dict
->SetString("mac", "Ctrl+Shift+M");
271 key_dict
->SetString("windows", "Ctrl+Shift+W");
273 EXPECT_FALSE(command
.Parse(input
.get(), command_name
, 0, &error
));
275 // Make sure Mac specific keys are not processed on other platforms.
276 #if !defined(OS_MACOSX)
277 key_dict
->SetString("windows", "Command+Shift+M");
278 EXPECT_FALSE(command
.Parse(input
.get(), command_name
, 0, &error
));
282 TEST(CommandTest
, ExtensionCommandParsingPlatformSpecific
) {
283 ui::Accelerator
search_a(ui::VKEY_A
, ui::EF_COMMAND_DOWN
);
284 ui::Accelerator
search_shift_z(ui::VKEY_Z
,
285 ui::EF_COMMAND_DOWN
| ui::EF_SHIFT_DOWN
);
287 ConstCommandsTestData kChromeOsTests
[] = {
288 {true, search_shift_z
, "command", "Search+Shift+Z", "description"},
289 {true, search_a
, "command", "Search+A", "description"},
290 // Command is not valid on Chrome OS.
291 {false, search_shift_z
, "command", "Command+Shift+Z", "description"},
294 std::vector
<std::string
> chromeos
;
295 chromeos
.push_back("chromeos");
296 for (size_t i
= 0; i
< arraysize(kChromeOsTests
); ++i
)
297 CheckParse(kChromeOsTests
[i
], i
, true, chromeos
);
299 ConstCommandsTestData kNonChromeOsSearchTests
[] = {
300 {false, search_shift_z
, "command", "Search+Shift+Z", "description"},
302 std::vector
<std::string
> non_chromeos
;
303 non_chromeos
.push_back("default");
304 non_chromeos
.push_back("windows");
305 non_chromeos
.push_back("mac");
306 non_chromeos
.push_back("linux");
308 for (size_t i
= 0; i
< arraysize(kNonChromeOsSearchTests
); ++i
)
309 CheckParse(kNonChromeOsSearchTests
[i
], i
, true, non_chromeos
);