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 TEST(CommandTest
, ExtensionCommandParsing
) {
18 const ui::Accelerator none
= ui::Accelerator();
19 const ui::Accelerator shift_f
= ui::Accelerator(ui::VKEY_F
,
21 #if defined(OS_MACOSX)
22 int ctrl
= ui::EF_COMMAND_DOWN
;
24 int ctrl
= ui::EF_CONTROL_DOWN
;
27 const ui::Accelerator ctrl_f
= ui::Accelerator(ui::VKEY_F
, ctrl
);
28 const ui::Accelerator alt_f
= ui::Accelerator(ui::VKEY_F
, ui::EF_ALT_DOWN
);
29 const ui::Accelerator ctrl_shift_f
=
30 ui::Accelerator(ui::VKEY_F
, ctrl
| ui::EF_SHIFT_DOWN
);
31 const ui::Accelerator alt_shift_f
=
32 ui::Accelerator(ui::VKEY_F
, ui::EF_ALT_DOWN
| ui::EF_SHIFT_DOWN
);
33 const ui::Accelerator ctrl_1
= ui::Accelerator(ui::VKEY_1
, ctrl
);
34 const ui::Accelerator ctrl_comma
= ui::Accelerator(ui::VKEY_OEM_COMMA
, ctrl
);
35 const ui::Accelerator ctrl_dot
= ui::Accelerator(ui::VKEY_OEM_PERIOD
, ctrl
);
36 const ui::Accelerator ctrl_left
= ui::Accelerator(ui::VKEY_LEFT
, ctrl
);
37 const ui::Accelerator ctrl_right
= ui::Accelerator(ui::VKEY_RIGHT
, ctrl
);
38 const ui::Accelerator ctrl_up
= ui::Accelerator(ui::VKEY_UP
, ctrl
);
39 const ui::Accelerator ctrl_down
= ui::Accelerator(ui::VKEY_DOWN
, ctrl
);
40 const ui::Accelerator ctrl_ins
= ui::Accelerator(ui::VKEY_INSERT
, ctrl
);
41 const ui::Accelerator ctrl_del
= ui::Accelerator(ui::VKEY_DELETE
, ctrl
);
42 const ui::Accelerator ctrl_home
= ui::Accelerator(ui::VKEY_HOME
, ctrl
);
43 const ui::Accelerator ctrl_end
= ui::Accelerator(ui::VKEY_END
, ctrl
);
44 const ui::Accelerator ctrl_pgup
= ui::Accelerator(ui::VKEY_PRIOR
, ctrl
);
45 const ui::Accelerator ctrl_pgdwn
= ui::Accelerator(ui::VKEY_NEXT
, ctrl
);
46 const ui::Accelerator next_track
=
47 ui::Accelerator(ui::VKEY_MEDIA_NEXT_TRACK
, ui::EF_NONE
);
48 const ui::Accelerator prev_track
=
49 ui::Accelerator(ui::VKEY_MEDIA_PREV_TRACK
, ui::EF_NONE
);
50 const ui::Accelerator play_pause
=
51 ui::Accelerator(ui::VKEY_MEDIA_PLAY_PAUSE
, ui::EF_NONE
);
52 const ui::Accelerator stop
=
53 ui::Accelerator(ui::VKEY_MEDIA_STOP
, ui::EF_NONE
);
57 ui::Accelerator accelerator
;
58 const char* command_name
;
60 const char* description
;
62 // Negative test (one or more missing required fields). We don't need to
63 // test |command_name| being blank as it is used as a key in the manifest,
64 // so it can't be blank (and we CHECK() when it is). A blank shortcut is
66 { false, none
, "command", "", "" },
67 { false, none
, "command", "Ctrl+f", "" },
68 // Ctrl+Alt is not permitted, see MSDN link in comments in Parse function.
69 { false, none
, "command", "Ctrl+Alt+F", "description" },
70 // Unsupported shortcuts/too many, or missing modifier.
71 { false, none
, "command", "A", "description" },
72 { false, none
, "command", "F10", "description" },
73 { false, none
, "command", "Ctrl+F+G", "description" },
74 { false, none
, "command", "Ctrl+Alt+Shift+G", "description" },
75 // Shift on its own is not supported.
76 { false, shift_f
, "command", "Shift+F", "description" },
77 { false, shift_f
, "command", "F+Shift", "description" },
79 { true, none
, "command", "", "description" },
80 { true, ctrl_f
, "command", "Ctrl+F", "description" },
81 { true, alt_f
, "command", "Alt+F", "description" },
82 { true, ctrl_shift_f
, "command", "Ctrl+Shift+F", "description" },
83 { true, alt_shift_f
, "command", "Alt+Shift+F", "description" },
84 { true, ctrl_1
, "command", "Ctrl+1", "description" },
85 // Shortcut token order tests.
86 { true, ctrl_f
, "command", "F+Ctrl", "description" },
87 { true, alt_f
, "command", "F+Alt", "description" },
88 { true, ctrl_shift_f
, "command", "F+Ctrl+Shift", "description" },
89 { true, ctrl_shift_f
, "command", "F+Shift+Ctrl", "description" },
90 { true, alt_shift_f
, "command", "F+Alt+Shift", "description" },
91 { true, alt_shift_f
, "command", "F+Shift+Alt", "description" },
92 // Case insensitivity is not OK.
93 { false, ctrl_f
, "command", "Ctrl+f", "description" },
94 { false, ctrl_f
, "command", "cTrL+F", "description" },
95 // Skipping description is OK for browser- and pageActions.
96 { true, ctrl_f
, "_execute_browser_action", "Ctrl+F", "" },
97 { true, ctrl_f
, "_execute_page_action", "Ctrl+F", "" },
98 // Home, End, Arrow keys, etc.
99 { true, ctrl_comma
, "_execute_browser_action", "Ctrl+Comma", "" },
100 { true, ctrl_dot
, "_execute_browser_action", "Ctrl+Period", "" },
101 { true, ctrl_left
, "_execute_browser_action", "Ctrl+Left", "" },
102 { true, ctrl_right
, "_execute_browser_action", "Ctrl+Right", "" },
103 { true, ctrl_up
, "_execute_browser_action", "Ctrl+Up", "" },
104 { true, ctrl_down
, "_execute_browser_action", "Ctrl+Down", "" },
105 { true, ctrl_ins
, "_execute_browser_action", "Ctrl+Insert", "" },
106 { true, ctrl_del
, "_execute_browser_action", "Ctrl+Delete", "" },
107 { true, ctrl_home
, "_execute_browser_action", "Ctrl+Home", "" },
108 { true, ctrl_end
, "_execute_browser_action", "Ctrl+End", "" },
109 { true, ctrl_pgup
, "_execute_browser_action", "Ctrl+PageUp", "" },
110 { true, ctrl_pgdwn
, "_execute_browser_action", "Ctrl+PageDown", "" },
112 { true, next_track
, "command", "MediaNextTrack", "description" },
113 { true, play_pause
, "command", "MediaPlayPause", "description" },
114 { true, prev_track
, "command", "MediaPrevTrack", "description" },
115 { true, stop
, "command", "MediaStop", "description" },
116 { false, none
, "_execute_browser_action", "MediaNextTrack", "" },
117 { false, none
, "_execute_page_action", "MediaPrevTrack", "" },
118 { false, none
, "command", "Ctrl+Shift+MediaPrevTrack", "description" },
121 for (size_t i
= 0; i
< ARRAYSIZE_UNSAFE(kTests
); ++i
) {
122 // First parse the command as a simple string.
123 scoped_ptr
<base::DictionaryValue
> input(new base::DictionaryValue
);
124 input
->SetString("suggested_key", kTests
[i
].key
);
125 input
->SetString("description", kTests
[i
].description
);
127 SCOPED_TRACE(std::string("Command name: |") + kTests
[i
].command_name
+
128 "| key: |" + kTests
[i
].key
+
129 "| description: |" + kTests
[i
].description
+
130 "| index: " + base::IntToString(i
));
132 extensions::Command command
;
133 base::string16 error
;
135 command
.Parse(input
.get(), kTests
[i
].command_name
, i
, &error
);
137 EXPECT_EQ(kTests
[i
].expected_result
, result
);
139 EXPECT_STREQ(kTests
[i
].description
,
140 base::UTF16ToASCII(command
.description()).c_str());
141 EXPECT_STREQ(kTests
[i
].command_name
, command
.command_name().c_str());
142 EXPECT_EQ(kTests
[i
].accelerator
, command
.accelerator());
145 // Now parse the command as a dictionary of multiple values.
146 if (kTests
[i
].key
[0] != '\0') {
147 input
.reset(new base::DictionaryValue
);
148 base::DictionaryValue
* key_dict
= new base::DictionaryValue();
149 key_dict
->SetString("default", kTests
[i
].key
);
150 key_dict
->SetString("windows", kTests
[i
].key
);
151 key_dict
->SetString("mac", kTests
[i
].key
);
152 input
->Set("suggested_key", key_dict
);
153 input
->SetString("description", kTests
[i
].description
);
155 result
= command
.Parse(input
.get(), kTests
[i
].command_name
, i
, &error
);
157 EXPECT_EQ(kTests
[i
].expected_result
, result
);
159 EXPECT_STREQ(kTests
[i
].description
,
160 base::UTF16ToASCII(command
.description()).c_str());
161 EXPECT_STREQ(kTests
[i
].command_name
, command
.command_name().c_str());
162 EXPECT_EQ(kTests
[i
].accelerator
, command
.accelerator());
168 TEST(CommandTest
, ExtensionCommandParsingFallback
) {
169 std::string description
= "desc";
170 std::string command_name
= "foo";
172 // Test that platform specific keys are honored on each platform, despite
173 // fallback being given.
174 scoped_ptr
<base::DictionaryValue
> input(new base::DictionaryValue
);
175 base::DictionaryValue
* key_dict
= new base::DictionaryValue();
176 key_dict
->SetString("default", "Ctrl+Shift+D");
177 key_dict
->SetString("windows", "Ctrl+Shift+W");
178 key_dict
->SetString("mac", "Ctrl+Shift+M");
179 key_dict
->SetString("linux", "Ctrl+Shift+L");
180 key_dict
->SetString("chromeos", "Ctrl+Shift+C");
181 input
->Set("suggested_key", key_dict
);
182 input
->SetString("description", description
);
184 extensions::Command command
;
185 base::string16 error
;
186 EXPECT_TRUE(command
.Parse(input
.get(), command_name
, 0, &error
));
187 EXPECT_STREQ(description
.c_str(),
188 base::UTF16ToASCII(command
.description()).c_str());
189 EXPECT_STREQ(command_name
.c_str(), command
.command_name().c_str());
192 ui::Accelerator
accelerator(ui::VKEY_W
,
193 ui::EF_SHIFT_DOWN
| ui::EF_CONTROL_DOWN
);
194 #elif defined(OS_MACOSX)
195 ui::Accelerator
accelerator(ui::VKEY_M
,
196 ui::EF_SHIFT_DOWN
| ui::EF_COMMAND_DOWN
);
197 #elif defined(OS_CHROMEOS)
198 ui::Accelerator
accelerator(ui::VKEY_C
,
199 ui::EF_SHIFT_DOWN
| ui::EF_CONTROL_DOWN
);
200 #elif defined(OS_LINUX)
201 ui::Accelerator
accelerator(ui::VKEY_L
,
202 ui::EF_SHIFT_DOWN
| ui::EF_CONTROL_DOWN
);
204 ui::Accelerator
accelerator(ui::VKEY_D
,
205 ui::EF_SHIFT_DOWN
| ui::EF_CONTROL_DOWN
);
207 EXPECT_EQ(accelerator
, command
.accelerator());
209 // Misspell a platform.
210 key_dict
->SetString("windosw", "Ctrl+M");
211 EXPECT_FALSE(command
.Parse(input
.get(), command_name
, 0, &error
));
212 EXPECT_TRUE(key_dict
->Remove("windosw", NULL
));
214 // Now remove platform specific keys (leaving just "default") and make sure
215 // every platform falls back to the default.
216 EXPECT_TRUE(key_dict
->Remove("windows", NULL
));
217 EXPECT_TRUE(key_dict
->Remove("mac", NULL
));
218 EXPECT_TRUE(key_dict
->Remove("linux", NULL
));
219 EXPECT_TRUE(key_dict
->Remove("chromeos", NULL
));
220 EXPECT_TRUE(command
.Parse(input
.get(), command_name
, 0, &error
));
221 EXPECT_EQ(ui::VKEY_D
, command
.accelerator().key_code());
223 // Now remove "default", leaving no option but failure. Or, in the words of
224 // the immortal Adam Savage: "Failure is always an option".
225 EXPECT_TRUE(key_dict
->Remove("default", NULL
));
226 EXPECT_FALSE(command
.Parse(input
.get(), command_name
, 0, &error
));
228 // Make sure Command is not supported for non-Mac platforms.
229 key_dict
->SetString("default", "Command+M");
230 EXPECT_FALSE(command
.Parse(input
.get(), command_name
, 0, &error
));
231 EXPECT_TRUE(key_dict
->Remove("default", NULL
));
232 key_dict
->SetString("windows", "Command+M");
233 EXPECT_FALSE(command
.Parse(input
.get(), command_name
, 0, &error
));
234 EXPECT_TRUE(key_dict
->Remove("windows", NULL
));
236 // Now add only a valid platform that we are not running on to make sure devs
237 // are notified of errors on other platforms.
239 key_dict
->SetString("mac", "Ctrl+Shift+M");
241 key_dict
->SetString("windows", "Ctrl+Shift+W");
243 EXPECT_FALSE(command
.Parse(input
.get(), command_name
, 0, &error
));
245 // Make sure Mac specific keys are not processed on other platforms.
246 #if !defined(OS_MACOSX)
247 key_dict
->SetString("windows", "Command+Shift+M");
248 EXPECT_FALSE(command
.Parse(input
.get(), command_name
, 0, &error
));