2 * Copyright 2013 The Chromium Authors. All rights reserved.
3 * Use of this source code is governed by a BSD-style license that can be
4 * found in the LICENSE file.
8 * Set to true while debugging virtual keyboard tests, for verbose debug output.
10 var debugging = false;
13 * The enumeration of keyset modifiers.
16 var KeysetModifier = {
24 * Flag values for the shift, control and alt modifiers as defined by
25 * EventFlags in "event_constants.h".
36 * Display diagnostic messages when debugging tests.
37 * @param {string} Message to conditionally display.
39 function Debug(message) {
47 * Adds mocks for chrome extension API calls.
49 * @param {MockController} mockController Controller for validating
50 * calls with expectations.
52 function mockExtensionApis(mockController) {
55 * Mocks methods within a namespace.
56 * @param {string} namespace Dot delimited namespace.
57 * @param {Array.<string>} methods List of methods names to mock.
59 var addMocks = function(namespace, methods) {
60 var parts = namespace.split('.');
62 for (var i = 0; i < parts.length; i++) {
65 base = base[parts[i]];
67 methods.forEach(function(m) {
68 var fn = base[m] = mockController.createFunctionMock(m);
69 fn.functionName = [namespace, m].join('.');
70 // Method to arm triggering a callback function with the specified
71 // arguments. Skip validation of callbacks.
72 fn.setCallbackData = function() {
73 fn.callbackData = Array.prototype.slice.call(arguments);
74 fn.verifyMock = function() {};
76 // TODO(kevers): Add validateCall override that strips functions from the
77 // argument signature before calling MockMethod.Prototype.validateCall
81 var virtualKeyboardPrivateMethods = [
90 var inputMethodPrivateMethods = [
91 'getCurrentInputMethod',
93 'setCurrentInputMethod'
96 addMocks('chrome.virtualKeyboardPrivate', virtualKeyboardPrivateMethods);
97 addMocks('chrome.inputMethodPrivate', inputMethodPrivateMethods);
98 addMocks('chrome.runtime', ['getBackgroundPage']);
99 addMocks('chrome.runtime.onMessage', ['addListener']);
100 // Ignore calls to addListener. Reevaluate if important to properly track the
102 chrome.runtime.onMessage.addListener = function() {};
105 chrome.i18n.getMessage = function(name) {
111 * Create mocks for the virtualKeyboardPrivate API. Any tests that trigger API
112 * calls must set expectations for call signatures.
115 mockController = new MockController();
117 // It is not safe to install the mockTimer during setUp, as intialization of
118 // the keyboard uses polling to determine when loading is complete. Instead,
119 // install the mockTimer as needed once a test is initiated and unintall on
120 // completion of the test.
122 mockExtensionApis(mockController);
124 var validateSendCall = function(index, expected, observed) {
125 // Only consider the first argument (VirtualKeyEvent) for the validation of
126 // sendKeyEvent calls.
127 var expectedEvent = expected[0];
128 var observedEvent = observed[0];
129 assertEquals(expectedEvent.type,
131 'Mismatched event types.');
132 assertEquals(expectedEvent.charValue,
133 observedEvent.charValue,
134 'Mismatched unicode values for character.');
135 assertEquals(expectedEvent.keyCode,
136 observedEvent.keyCode,
137 'Mismatched key codes.');
138 assertEquals(expectedEvent.modifiers,
139 observedEvent.modifiers,
140 'Mismatched states for modifiers.');
142 chrome.virtualKeyboardPrivate.sendKeyEvent.validateCall = validateSendCall;
144 var validateLockKeyboard = function(index, expected, observed) {
145 assertEquals(expected[0],
147 'Mismatched keyboard lock/unlock state.');
149 chrome.virtualKeyboardPrivate.lockKeyboard.validateCall =
150 validateLockKeyboard;
152 chrome.virtualKeyboardPrivate.hideKeyboard.validateCall = function() {
153 // hideKeyboard has one optional argument for error logging that does not
154 // matter for the purpose of validating the call.
157 // Set data to be provided to callbacks in response to API calls.
158 // TODO(kevers): Provide mechanism to override these values for individual
160 chrome.virtualKeyboardPrivate.getKeyboardConfig.setCallbackData({
166 chrome.inputMethodPrivate.getCurrentInputMethod.setCallbackData('us:en');
168 chrome.inputMethodPrivate.getInputMethods.setCallbackData([
169 {id: 'us', name: 'US Keyboard', indicator: 'US'},
170 {id: 'fr', name: 'French Keyboard', indicator: 'Fr'},
171 {id: 'de', name: 'German Keyboard', indicator: 'De'}
174 chrome.runtime.getBackgroundPage.setCallbackData(undefined);
176 // TODO(kevers): Mock additional extension API calls as required.
180 * Verify that API calls match expectations.
182 function tearDown() {
183 mockController.verifyMocks();
184 mockController.reset();
188 // ------------------- Helper Functions -------------------------
192 * Runs a test asynchronously once keyboard loading is complete.
193 * @param {Function} runTestCallback Asynchronous test function.
194 * @param {Object=} opt_config Optional configuration for the keyboard.
196 function onKeyboardReady(runTestCallback, opt_config) {
197 var default_config = {
198 keyset: 'us.compact.qwerty',
200 passwordLayout: 'us',
203 var config = opt_config || default_config;
204 chrome.virtualKeyboardPrivate.keyboardLoaded = function() {
205 // Disarm to prevent repeated calls to run a test.
206 chrome.virtualKeyboardPrivate.keyboardLoaded = function() {};
209 window.initializeVirtualKeyboard(config.keyset,
211 config.passwordLayout,
217 * Mocks a touch event targeted on a key.
218 * @param {!Element} key .
219 * @param {string} eventType .
221 function mockTouchEvent(key, eventType) {
222 var e = new Event(eventType, {
226 return key.dispatchEvent(e);
230 * Simulates tapping on a key.
231 * @param {!Element} key .
233 function mockTap(key) {
234 mockTouchEvent(key, 'touchstart');
235 mockTouchEvent(key, 'touchend');
239 * Returns the active keyboard view.
240 * @return {!HTMLElement}
242 function getActiveView() {
243 var container = document.querySelector('.inputview-container');
244 var views = container.querySelectorAll('.inputview-view');
245 for (var i = 0; i < views.length; i++) {
246 var display = views[i].style.display;
247 if (!display || display != 'none') {
254 * Finds the ancestor element corresponding the the soft key view.
255 * @param {Element} source .
256 * @return {?Element} .
258 function getSoftKeyView(source) {
259 var parent = source.parentElement;
260 while (parent && !parent.classList.contains('inputview-skv')) {
261 parent = parent.parentElement;
267 * Locates a key by label.
268 * @param {string} label The label on the key. If the key has multiple labels,
269 * |label| can match any of them.
270 * @param {string=} opt_rowId Optional ID of the row containing the key.
271 * @returns {?Element} .
273 function findKey(label, opt_rowId) {
274 var view = getActiveView();
275 assertTrue(!!view, 'Unable to find active keyboard view');
277 view = view.querySelector('#' + opt_rowId);
278 assertTrue(!!view, 'Unable to locate row ' + opt_rowId);
280 var candidates = view.querySelectorAll('.inputview-ch');
281 // Compact layouts use a different naming convention.
282 if (candidates.length == 0)
283 candidates = view.querySelectorAll('.inputview-special-key-name');
284 for (var i = 0; i < candidates.length; i++) {
285 if (candidates[i].textContent == label)
286 return getSoftKeyView(candidates[i]);
288 assertTrue(false, 'Cannot find key labeled \'' + label + '\'');
292 * Locates a key with matching ID. Note that IDs are not necessarily unique
293 * across keysets; however, it is unique within the active layout.
295 function findKeyById(label) {
296 var view = getActiveView();
297 assertTrue(!!view, 'Unable to find active keyboard view');
298 var key = view.querySelector('#' + label);
299 assertTrue(!!key, 'Cannot find key with ID ' + label);
304 * Verifies if a key contains a matching label.
305 * @param {Element} key .
306 * @param {string} label .
307 * @return {boolean} .
309 function hasLabel(key, label) {
310 var characters = key.querySelectorAll('.inputview-ch');
311 // Compact layouts represent keys differently.
312 if (characters.length == 0)
313 characters = key.querySelectorAll('.inputview-special-key-name');
314 for (var i = 0; i < characters.length; i++) {
315 if (characters[i].textContent == label)
322 * Mock typing of basic keys. Each keystroke should trigger a pair of
323 * API calls to send viritual key events.
324 * @param {string} label The character being typed.
325 * @param {number} keyCode The legacy key code for the character.
326 * @param {number} modifiers Indicates which if any of the shift, control and
327 * alt keys are being virtually pressed.
328 * @param {number=} opt_unicode Optional unicode value for the character. Only
329 * required if it cannot be directly calculated from the label.
331 function mockTypeCharacter(label, keyCode, modifiers, opt_unicode) {
332 var key = label.length == 1 ? findKey(label) : findKeyById(label);
333 var unicodeValue = opt_unicode || label.charCodeAt(0);
334 var send = chrome.virtualKeyboardPrivate.sendKeyEvent;
335 send.addExpectation({
337 charValue: unicodeValue,
341 send.addExpectation({
343 charValue: unicodeValue,
351 * Emulates a user triggering a keyset modifier.
352 * @param {!KeysetModifier} The modifier to apply.
354 function applyKeysetModifier(modifier) {
355 if (modifier == KeysetModifier.NONE) {
358 var activeView = getActiveView();
359 if (modifier == KeysetModifier.SHIFT) {
360 // Set state of shift key.
361 var leftShift = activeView.querySelector('#ShiftLeft');
362 assertTrue(!!leftShift, 'Unable to find left shift key');
363 var currentShiftState = !!leftShift.querySelector(
364 '.inputview-special-key-highlight');
365 if (!currentShiftState) {
368 } else if (modifier == KeysetModifier.SYMBOL) {
369 var switchKey = findKey('?123', 'spaceKeyrow');
370 assertTrue(!!switchKey, 'Unable to find symbol transition key');
371 // Switch to symbol keyset.
374 var switchKey = findKey('~[<', 'row3');
375 assertTrue(!!switchKey, 'Unable to find more transition key');
376 // Switch to more keyset.