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 = [
91 var inputMethodPrivateMethods = [
92 'getCurrentInputMethod',
94 'setCurrentInputMethod'
97 addMocks('chrome.virtualKeyboardPrivate', virtualKeyboardPrivateMethods);
98 addMocks('chrome.inputMethodPrivate', inputMethodPrivateMethods);
99 addMocks('chrome.runtime', ['getBackgroundPage']);
100 addMocks('chrome.runtime.onMessage', ['addListener']);
101 // Ignore calls to addListener. Reevaluate if important to properly track the
103 chrome.runtime.onMessage.addListener = function() {};
106 chrome.i18n.getMessage = function(name) {
112 * Adjust the size and position of the keyboard for testing.
114 function adjustScreenForTesting() {
115 var style = document.body.style;
120 style.background = 'transparent';
121 style.position = 'absolute';
123 // Adjust positioning for testing in a non-fullscreen display.
124 Object.defineProperty(window.screen, 'width', {
126 return document.body.clientWidth;
130 Object.defineProperty(window.screen, 'height', {
132 return document.body.clientHeight;
139 * Create mocks for the virtualKeyboardPrivate API. Any tests that trigger API
140 * calls must set expectations for call signatures.
143 mockController = new MockController();
145 // It is not safe to install the mockTimer during setUp, as intialization of
146 // the keyboard uses polling to determine when loading is complete. Instead,
147 // install the mockTimer as needed once a test is initiated and unintall on
148 // completion of the test.
150 mockExtensionApis(mockController);
152 adjustScreenForTesting();
154 var validateSendCall = function(index, expected, observed) {
155 // Only consider the first argument (VirtualKeyEvent) for the validation of
156 // sendKeyEvent calls.
157 var expectedEvent = expected[0];
158 var observedEvent = observed[0];
159 assertEquals(expectedEvent.type,
161 'Mismatched event types.');
162 assertEquals(expectedEvent.charValue,
163 observedEvent.charValue,
164 'Mismatched unicode values for character.');
165 assertEquals(expectedEvent.keyCode,
166 observedEvent.keyCode,
167 'Mismatched key codes.');
168 assertEquals(expectedEvent.modifiers,
169 observedEvent.modifiers,
170 'Mismatched states for modifiers.');
172 chrome.virtualKeyboardPrivate.sendKeyEvent.validateCall = validateSendCall;
174 var validateLockKeyboard = function(index, expected, observed) {
175 assertEquals(expected[0],
177 'Mismatched keyboard lock/unlock state.');
179 chrome.virtualKeyboardPrivate.lockKeyboard.validateCall =
180 validateLockKeyboard;
182 chrome.virtualKeyboardPrivate.hideKeyboard.validateCall = function() {
183 // hideKeyboard has one optional argument for error logging that does not
184 // matter for the purpose of validating the call.
187 // Set data to be provided to callbacks in response to API calls.
188 // TODO(kevers): Provide mechanism to override these values for individual
190 chrome.virtualKeyboardPrivate.getKeyboardConfig.setCallbackData({
197 chrome.inputMethodPrivate.getCurrentInputMethod.setCallbackData('us:en');
199 // Set an empty list. Tests that care about input methods in the menu will
200 // need to call this again with their own list of input methods.
201 chrome.inputMethodPrivate.getInputMethods.setCallbackData([]);
203 chrome.runtime.getBackgroundPage.setCallbackData(undefined);
205 // TODO(kevers): Mock additional extension API calls as required.
209 * Verify that API calls match expectations.
211 function tearDown() {
212 mockController.verifyMocks();
213 mockController.reset();
217 // ------------------- Helper Functions -------------------------
220 * Runs a test asynchronously once keyboard loading is complete.
221 * @param {Function} runTestCallback Asynchronous test function.
222 * @param {Object=} opt_config Optional configuration for the keyboard.
224 function onKeyboardReady(runTestCallback, opt_config) {
225 var default_config = {
226 keyset: 'us.compact.qwerty',
228 passwordLayout: 'us',
231 var config = opt_config || default_config;
232 var options = config.options || {};
233 chrome.virtualKeyboardPrivate.keyboardLoaded = function() {
236 window.initializeVirtualKeyboard(config.keyset,
238 config.passwordLayout,
244 * Defers continuation of a test until one or more keysets are loaded.
245 * @param {string|Array.<string>} keyset Name of the target keyset or list of
247 * @param {Function} continueTestCallback Callback function to invoke in order
248 * to resume the test.
250 function onKeysetsReady(keyset, continueTestCallback) {
251 if (keyset instanceof Array) {
253 keyset.forEach(function(id) {
254 if (!(id in controller.container_.keysetViewMap))
258 continueTestCallback();
261 } else if (keyset in controller.container_.keysetViewMap) {
262 continueTestCallback();
265 setTimeout(function() {
266 onKeysetsReady(keyset, continueTestCallback);
271 * Mocks a touch event targeted on a key.
272 * @param {!Element} key .
273 * @param {string} eventType .
275 function mockTouchEvent(key, eventType) {
276 var rect = key.getBoundingClientRect();
277 var x = rect.left + rect.width/2;
278 var y = rect.top + rect.height/2;
279 var e = document.createEvent('UIEvent');
280 e.initUIEvent(eventType, true, true);
281 e.touches = [{pageX: x, pageY: y}];
283 return key.dispatchEvent(e);
287 * Simulates tapping on a key.
288 * @param {!Element} key .
290 function mockTap(key) {
291 mockTouchEvent(key, 'touchstart');
292 mockTouchEvent(key, 'touchend');
296 * Returns the active keyboard view.
297 * @return {!HTMLElement}
299 function getActiveView() {
300 var container = document.querySelector('.inputview-container');
301 var views = container.querySelectorAll('.inputview-view');
302 for (var i = 0; i < views.length; i++) {
303 var display = views[i].style.display;
304 if (!display || display != 'none') {
311 * Finds the ancestor element corresponding the the soft key view.
312 * @param {Element} source .
313 * @return {?Element} .
315 function getSoftKeyView(source) {
316 var parent = source.parentElement;
317 while (parent && !parent.classList.contains('inputview-skv')) {
318 parent = parent.parentElement;
324 * Locates a key by label.
325 * @param {string} label The label on the key. If the key has multiple labels,
326 * |label| can match any of them.
327 * @param {string=} opt_rowId Optional ID of the row containing the key.
328 * @returns {?Element} .
330 function findKey(label, opt_rowId) {
331 var view = getActiveView();
332 assertTrue(!!view, 'Unable to find active keyboard view');
334 view = view.querySelector('#' + opt_rowId);
335 assertTrue(!!view, 'Unable to locate row ' + opt_rowId);
337 var candidates = view.querySelectorAll('.inputview-ch');
338 // Compact layouts use a different naming convention.
339 if (candidates.length == 0)
340 candidates = view.querySelectorAll('.inputview-special-key-name');
341 for (var i = 0; i < candidates.length; i++) {
342 if (candidates[i].textContent == label)
343 return candidates[i];
345 assertTrue(false, 'Cannot find key labeled \'' + label + '\'');
349 * Locates a key with matching ID. Note that IDs are not necessarily unique
350 * across keysets; however, it is unique within the active layout.
352 function findKeyById(label) {
353 var view = getActiveView();
354 assertTrue(!!view, 'Unable to find active keyboard view');
355 var key = view.querySelector('#' + label);
356 assertTrue(!!key, 'Cannot find key with ID ' + label);
361 * Verifies if a key contains a matching label.
362 * @param {Element} key .
363 * @param {string} label .
364 * @return {boolean} .
366 function hasLabel(key, label) {
367 var characters = key.querySelectorAll('.inputview-ch');
368 // Compact layouts represent keys differently.
369 if (characters.length == 0)
370 characters = key.querySelectorAll('.inputview-special-key-name');
371 for (var i = 0; i < characters.length; i++) {
372 if (characters[i].textContent == label)
379 * Mock typing of basic keys. Each keystroke should trigger a pair of
380 * API calls to send viritual key events.
381 * @param {string} label The character being typed.
382 * @param {number} keyCode The legacy key code for the character.
383 * @param {number} modifiers Indicates which if any of the shift, control and
384 * alt keys are being virtually pressed.
385 * @param {number=} opt_unicode Optional unicode value for the character. Only
386 * required if it cannot be directly calculated from the label.
388 function mockTypeCharacter(label, keyCode, modifiers, opt_unicode) {
389 var key = label.length == 1 ? findKey(label) : findKeyById(label);
390 // opt_unicode is allowed to be 0.
391 var unicodeValue = opt_unicode;
392 if (unicodeValue === undefined)
393 unicodeValue = label.charCodeAt(0);
394 var send = chrome.virtualKeyboardPrivate.sendKeyEvent;
395 send.addExpectation({
397 charValue: unicodeValue,
401 send.addExpectation({
403 charValue: unicodeValue,
411 * Emulates a user triggering a keyset modifier.
412 * @param {!KeysetModifier} The modifier to apply.
414 function applyKeysetModifier(modifier) {
415 if (modifier == KeysetModifier.NONE) {
418 var activeView = getActiveView();
419 if (modifier == KeysetModifier.SHIFT) {
420 // Set state of shift key.
421 var leftShift = activeView.querySelector('#ShiftLeft');
422 assertTrue(!!leftShift, 'Unable to find left shift key');
423 var currentShiftState = !!leftShift.querySelector(
424 '.inputview-special-key-highlight');
425 if (!currentShiftState) {
428 } else if (modifier == KeysetModifier.SYMBOL) {
429 var switchKey = findKey('?123', 'spaceKeyrow');
430 assertTrue(!!switchKey, 'Unable to find symbol transition key');
431 // Switch to symbol keyset.
434 var switchKey = findKey('~[<', 'row3');
435 assertTrue(!!switchKey, 'Unable to find more transition key');
436 // Switch to more keyset.