diff options
-rw-r--r-- | chrome/browser/chromeos/extensions/virtual_keyboard_browsertest.cc | 92 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 1 | ||||
-rw-r--r-- | chrome/test/data/chromeos/virtual_keyboard/typing_test.js | 58 | ||||
-rw-r--r-- | chrome/test/data/chromeos/virtual_keyboard/virtual_keyboard_test_base.js | 118 | ||||
-rw-r--r-- | chrome/test/data/webui/mock_controller.js | 19 | ||||
-rw-r--r-- | ui/keyboard/resources/elements/kb-keyboard.html | 24 | ||||
-rw-r--r-- | ui/webui/resources/js/webui_resource_test.js | 10 |
7 files changed, 317 insertions, 5 deletions
diff --git a/chrome/browser/chromeos/extensions/virtual_keyboard_browsertest.cc b/chrome/browser/chromeos/extensions/virtual_keyboard_browsertest.cc new file mode 100644 index 0000000..3e0adbb --- /dev/null +++ b/chrome/browser/chromeos/extensions/virtual_keyboard_browsertest.cc @@ -0,0 +1,92 @@ +/* + * Copyright 2013 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <vector> + +#include "base/command_line.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "chrome/test/base/ui_test_utils.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" +#include "content/public/test/browser_test_utils.h" +#include "ui/keyboard/keyboard_switches.h" + +namespace { + +const base::FilePath kWebuiTestDir = + base::FilePath(FILE_PATH_LITERAL("webui")); + +const base::FilePath kVirtualKeyboardTestDir = + base::FilePath(FILE_PATH_LITERAL("chromeos/virtual_keyboard")); + +const base::FilePath kMockController = + base::FilePath(FILE_PATH_LITERAL("mock_controller.js")); + +const base::FilePath kBaseKeyboardTestFramework = + base::FilePath(FILE_PATH_LITERAL("virtual_keyboard_test_base.js")); + +} // namespace + +class VirtualKeyboardBrowserTest : public InProcessBrowserTest { + public: + + /** + * Ensure that the virtual keyboard is enabled. + */ + virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { + command_line->AppendSwitch( + keyboard::switches::kEnableVirtualKeyboard); + } + + /** + * Injects javascript in |file| into the keyboard page and runs test methods. + */ + void RunTest(const base::FilePath& file) { + ui_test_utils::NavigateToURL(browser(), GURL("chrome://keyboard")); + + content::RenderViewHost* rvh = browser()->tab_strip_model() + ->GetActiveWebContents()->GetRenderViewHost(); + ASSERT_TRUE(rvh); + + // Inject testing scripts. + InjectJavascript(kWebuiTestDir, kMockController); + InjectJavascript(kVirtualKeyboardTestDir, kBaseKeyboardTestFramework); + InjectJavascript(kVirtualKeyboardTestDir, file); + + ASSERT_TRUE(content::ExecuteScript(rvh, utf8_content_)); + + // Inject DOM-automation test harness and run tests. + std::vector<int> resource_ids; + EXPECT_TRUE(ExecuteWebUIResourceTest(rvh, resource_ids)); + } + + private: + + /** + * Injects javascript into the keyboard page. The test |file| is in + * directory |dir| relative to the root testing directory. + */ + void InjectJavascript(const base::FilePath& dir, + const base::FilePath& file) { + base::FilePath path = ui_test_utils::GetTestFilePath(dir, file); + std::string library_content; + ASSERT_TRUE(base::ReadFileToString(path, &library_content)) + << path.value(); + utf8_content_.append(library_content); + utf8_content_.append(";\n"); + } + + std::string utf8_content_; +}; + +IN_PROC_BROWSER_TEST_F(VirtualKeyboardBrowserTest, TypingTest) { + RunTest(base::FilePath(FILE_PATH_LITERAL("typing_test.js"))); +} + +// TODO(kevers|rsadam|bshe): Add UI tests for remaining virtual keyboard +// functionality. diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 4af63c5..e69dd1e 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -968,6 +968,7 @@ 'browser/chromeos/extensions/file_manager/file_browser_private_apitest.cc', 'browser/chromeos/extensions/info_private_apitest.cc', 'browser/chromeos/extensions/input_method_apitest_chromeos.cc', + 'browser/chromeos/extensions/virtual_keyboard_browsertest.cc', 'browser/chromeos/extensions/wallpaper_private_apitest.cc', 'browser/chromeos/file_manager/desktop_notifications_browsertest.cc', 'browser/chromeos/file_manager/drive_test_util.cc', diff --git a/chrome/test/data/chromeos/virtual_keyboard/typing_test.js b/chrome/test/data/chromeos/virtual_keyboard/typing_test.js new file mode 100644 index 0000000..9479a72 --- /dev/null +++ b/chrome/test/data/chromeos/virtual_keyboard/typing_test.js @@ -0,0 +1,58 @@ +/* + * Copyright 2013 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/** + * Mock typing of basic keys. Each keystroke should trigger a pair of + * API calls to send viritual key events. + * @param {string} label The character being typed. + * @param {number} keyCode The legacy key code for the character. + * @param {boolean} shiftModifier Indicates if the shift key is being + * virtually pressed. + * @param {number=} opt_unicode Optional unicode value for the character. Only + * required if it cannot be directly calculated from the label. + */ +function mockTypeCharacter(label, keyCode, shiftModifier, opt_unicode) { + var key = findKey(label); + assertTrue(!!key, 'Unable to find key labelled "' + label + '".'); + var unicodeValue = opt_unicode | label.charCodeAt(0); + var send = chrome.virtualKeyboardPrivate.sendKeyEvent; + send.addExpectation({ + type: 'keydown', + charValue: unicodeValue, + keyCode: keyCode, + shiftKey: shiftModifier + }); + send.addExpectation({ + type: 'keyup', + charValue: unicodeValue, + keyCode: keyCode, + shiftKey: shiftModifier + }); + // Fake typing the key. + key.down(); + key.up(); +} + +/** + * Tests that typing characters on the default lowercase keyboard triggers the + * correct sequence of events. The test is run asynchronously since the + * keyboard loads keysets dynamically. + */ +function testLowercaseKeysetAsync(testDoneCallback) { + var runTest = function() { + // Keyboard defaults to lowercase. + mockTypeCharacter('a', 0x41, false); + mockTypeCharacter('s', 0x53, false); + mockTypeCharacter('.', 0xBE, false); + mockTypeCharacter('\b', 0x08, false, 0x08); + mockTypeCharacter('\t', 0x09, false, 0x09); + mockTypeCharacter('\n', 0x0D, false, 0x0A); + mockTypeCharacter(' ', 0x20, false); + testDoneCallback(this.testFailure); + }; + runTest.name = 'testLowercaseKeysetAsync'; + onKeyboardReady(runTest); +} diff --git a/chrome/test/data/chromeos/virtual_keyboard/virtual_keyboard_test_base.js b/chrome/test/data/chromeos/virtual_keyboard/virtual_keyboard_test_base.js new file mode 100644 index 0000000..5a9657f --- /dev/null +++ b/chrome/test/data/chromeos/virtual_keyboard/virtual_keyboard_test_base.js @@ -0,0 +1,118 @@ +/* + * Copyright 2013 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/** + * Queue for running tests asynchronously. + */ +function TestRunner() { + this.queue = []; + keyboard.addEventListener('stateChange', this.onStateChange.bind(this)); +} + +TestRunner.prototype = { + + /** + * Queues a test to run after the keyboard has finished initializing. + * @param {!Function} callback The deferred function call. + */ + append: function(callback) { + this.queue.push(callback); + }, + + /** + * Notification of a change in the state of the keyboard. Runs all queued + * tests if the keyboard has finished initializing. + * @param {Object} event The state change event. + */ + onStateChange: function(event) { + if (event.detail.state == 'keysetLoaded') { + for (var i = 0; i < this.queue.length; i++) { + var callback = this.queue[i]; + try { + callback(); + } catch(err) { + console.error('Failure in test ' + callback.name + '\n' + err); + console.log(err.stack); + callback.testFailure = true; + } + } + this.queue = []; + } + }, +}; + +var testRunner; +var mockController; + +/** + * Create mocks for the virtualKeyboardPrivate API. Any tests that trigger API + * calls must set expectations for call signatures. + */ +function setUp() { + testRunner = new TestRunner(); + mockController = new MockController(); + mockController.createFunctionMock(chrome.virtualKeyboardPrivate, + 'insertText'); + + mockController.createFunctionMock(chrome.virtualKeyboardPrivate, + 'sendKeyEvent'); + + var validateSendCall = function(index, expected, observed) { + // Only consider the first argument (VirtualKeyEvent) for the validation of + // sendKeyEvent calls. + var expectedEvent = expected[0]; + var observedEvent = observed[0]; + assertEquals(expectedEvent.type, + observedEvent.type, + 'Mismatched event types.'); + assertEquals(expectedEvent.charValue, + observedEvent.charValue, + 'Mismatched unicode values for character.'); + assertEquals(expectedEvent.keyCode, + observedEvent.keyCode, + 'Mismatched key codes.'); + assertEquals(expectedEvent.shiftKey, + observedEvent.shiftKey, + 'Mismatched states for shift modifier.'); + }; + chrome.virtualKeyboardPrivate.sendKeyEvent.validateCall = validateSendCall; + + // TODO(kevers): Mock additional extension API calls as required. +} + +/** + * Verify that API calls match expectations. + */ +function tearDown() { + mockController.verifyMocks(); + mockController.reset(); +} + +/** + * Finds the key on the keyboard with the matching label. + * @param {string} label The label in the key. + * @return {?kb-key} The key element with matching label or undefined if no + * matching key is found. + */ +function findKey(label) { + var keys = keyboard.querySelectorAll('kb-key'); + for (var i = 0; i < keys.length; i++) { + if (keys[i].charValue == label) + return keys[i]; + } +} + +/** + * Triggers a callback function to run post initialization of the virtual + * keyboard. + * @param {Function} callback The callback function. + */ +function onKeyboardReady(callback) { + if (keyboard.initialized) + callback(); + else + testRunner.append(callback); +} diff --git a/chrome/test/data/webui/mock_controller.js b/chrome/test/data/webui/mock_controller.js index 00ab9b2..0cb981f 100644 --- a/chrome/test/data/webui/mock_controller.js +++ b/chrome/test/data/webui/mock_controller.js @@ -65,10 +65,22 @@ MockMethod.prototype = { this.calls_.length, 'Number of method calls did not match expectation.'); for (var i = 0; i < this.expectations_.length; i++) { - assertDeepEquals(this.expectations_[i], - this.calls_[i]); + this.validateCall(i, this.expectations_[i], this.calls_[i]); } - } + }, + + /** + * Verifies that the observed function arguments match expectations. + * Override if strict equality is not required. + * @param {number} index Canonical index of the function call. Unused in the + * base implementation, but provides context that may be useful for + * overrides. + * @param {!Array} expected The expected arguments. + * @parma {!Array} observed The observed arguments. + */ + validateCall: function(index, expected, observed) { + assertDeepEquals(expected, observed); + }, }; /** @@ -138,4 +150,5 @@ MockController.prototype = { override.parent[override.functionName] = override.originalFunction; } }, + }; diff --git a/ui/keyboard/resources/elements/kb-keyboard.html b/ui/keyboard/resources/elements/kb-keyboard.html index 361cef3..ab64de4 100644 --- a/ui/keyboard/resources/elements/kb-keyboard.html +++ b/ui/keyboard/resources/elements/kb-keyboard.html @@ -133,6 +133,14 @@ var swipeInProgress = false; /** + * A boolean used to track if the keyboard is ready for user input. As + * alternate layouts are dynamically loaded, the keyboard may be in a state + * where it is not fully initialized until all links, key-sequences, and + * imports are fully resolved. + */ + var isReady = false; + + /** * The enumeration of swipe directions. * @const * @type {Enum} @@ -523,6 +531,9 @@ */ layoutChanged: function() { if (!this.selectDefaultKeyset()) { + this.isReady = false; + this.fire('stateChange', {state: 'loadingKeyset'}); + // Keyset selection fails if the keysets have not been loaded yet. var keysets = document.querySelector('#' + this.layout); if (keysets) { @@ -556,6 +567,14 @@ }, /** + * Indicate if the keyboard is ready for user input. + * @type {boolean} + */ + get initialized() { + return this.isReady; + }, + + /** * Generates fabricated key events to simulate typing on a * physical keyboard. * @param {Object} detail Attributes of the key being typed. @@ -582,6 +601,11 @@ keysetsLoaded = true; if (keysets[i].isDefault) { this.keyset = matches[REGEX_KEYSET_INDEX]; + this.isReady = true; + this.fire('stateChange', { + state: 'keysetLoaded', + keyset: this.keyset, + }); return true; } } diff --git a/ui/webui/resources/js/webui_resource_test.js b/ui/webui/resources/js/webui_resource_test.js index cce993d..7d6ebf9 100644 --- a/ui/webui/resources/js/webui_resource_test.js +++ b/ui/webui/resources/js/webui_resource_test.js @@ -156,8 +156,13 @@ function assertDeepEquals(expected, observed, opt_message) { /** * Runs the next test in the queue. Reports the test results if the queue is * empty. + * @param {boolean=} opt_asyncTestFailure Optional parameter indicated if the + * last asynchronous test failed. */ - function continueTesting() { + function continueTesting(opt_asyncTestFailure) { + if (opt_asyncTestFailure) + cleanTestRun = false; + var done = false; if (pendingTearDown) { pendingTearDown(); pendingTearDown = null; @@ -179,9 +184,10 @@ function assertDeepEquals(expected, observed, opt_message) { if (!isAsyncTest) continueTesting(); } else { + done = true; endTests(cleanTestRun); } - if (testCases.length) { + if (!done) { domAutomationController.setAutomationId(1); domAutomationController.send('PENDING'); } |