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 = [
93 var inputMethodPrivateMethods = [
94 'getCurrentInputMethod',
96 'setCurrentInputMethod'
99 addMocks('chrome.virtualKeyboardPrivate', virtualKeyboardPrivateMethods);
100 addMocks('chrome.inputMethodPrivate', inputMethodPrivateMethods);
101 addMocks('chrome.runtime', ['getBackgroundPage']);
102 addMocks('chrome.runtime.onMessage', ['addListener']);
103 // Ignore calls to addListener. Reevaluate if important to properly track the
105 chrome.runtime.onMessage.addListener = function() {};
108 chrome.i18n.getMessage = function(name) {
114 * Adjust the size and position of the keyboard for testing.
116 function adjustScreenForTesting() {
117 var style = document.body.style;
122 style.background = 'transparent';
123 style.position = 'absolute';
125 // Adjust positioning for testing in a non-fullscreen display.
126 Object.defineProperty(window.screen, 'width', {
128 return document.body.clientWidth;
132 Object.defineProperty(window.screen, 'height', {
134 return document.body.clientHeight;
141 * Create mocks for the virtualKeyboardPrivate API. Any tests that trigger API
142 * calls must set expectations for call signatures.
145 mockController = new MockController();
147 // It is not safe to install the mockTimer during setUp, as intialization of
148 // the keyboard uses polling to determine when loading is complete. Instead,
149 // install the mockTimer as needed once a test is initiated and unintall on
150 // completion of the test.
152 mockExtensionApis(mockController);
154 adjustScreenForTesting();
156 var validateSendCall = function(index, expected, observed) {
157 // Only consider the first argument (VirtualKeyEvent) for the validation of
158 // sendKeyEvent calls.
159 var expectedEvent = expected[0];
160 var observedEvent = observed[0];
161 assertEquals(expectedEvent.type,
163 'Mismatched event types.');
164 assertEquals(expectedEvent.charValue,
165 observedEvent.charValue,
166 'Mismatched unicode values for character.');
167 assertEquals(expectedEvent.keyCode,
168 observedEvent.keyCode,
169 'Mismatched key codes.');
170 assertEquals(expectedEvent.modifiers,
171 observedEvent.modifiers,
172 'Mismatched states for modifiers.');
174 chrome.virtualKeyboardPrivate.sendKeyEvent.validateCall = validateSendCall;
176 var validateLockKeyboard = function(index, expected, observed) {
177 assertEquals(expected[0],
179 'Mismatched keyboard lock/unlock state.');
181 chrome.virtualKeyboardPrivate.lockKeyboard.validateCall =
182 validateLockKeyboard;
184 chrome.virtualKeyboardPrivate.hideKeyboard.validateCall = function() {
185 // hideKeyboard has one optional argument for error logging that does not
186 // matter for the purpose of validating the call.
189 // Set data to be provided to callbacks in response to API calls.
190 // TODO(kevers): Provide mechanism to override these values for individual
192 chrome.virtualKeyboardPrivate.getKeyboardConfig.setCallbackData({
199 chrome.inputMethodPrivate.getCurrentInputMethod.setCallbackData('us:en');
201 // Set an empty list. Tests that care about input methods in the menu will
202 // need to call this again with their own list of input methods.
203 chrome.inputMethodPrivate.getInputMethods.setCallbackData([]);
205 chrome.runtime.getBackgroundPage.setCallbackData(undefined);
207 // TODO(kevers): Mock additional extension API calls as required.
211 * Verify that API calls match expectations.
213 function tearDown() {
214 mockController.verifyMocks();
215 mockController.reset();
219 // ------------------- Helper Functions -------------------------
222 * Runs a test asynchronously once keyboard loading is complete.
223 * @param {Function} runTestCallback Asynchronous test function.
224 * @param {Object=} opt_config Optional configuration for the keyboard.
226 function onKeyboardReady(runTestCallback, opt_config) {
227 var default_config = {
228 keyset: 'us.compact.qwerty',
230 passwordLayout: 'us',
233 var config = opt_config || default_config;
234 var options = config.options || {};
235 chrome.virtualKeyboardPrivate.keyboardLoaded = function() {
238 window.initializeVirtualKeyboard(config.keyset,
240 config.passwordLayout,
246 * Defers continuation of a test until one or more keysets are loaded.
247 * @param {string|Array<string>} keyset Name of the target keyset or list of
249 * @param {Function} continueTestCallback Callback function to invoke in order
250 * to resume the test.
252 function onKeysetsReady(keyset, continueTestCallback) {
253 if (keyset instanceof Array) {
255 keyset.forEach(function(id) {
256 if (!(id in controller.container_.keysetViewMap))
260 continueTestCallback();
263 } else if (keyset in controller.container_.keysetViewMap) {
264 continueTestCallback();
267 setTimeout(function() {
268 onKeysetsReady(keyset, continueTestCallback);
273 * Mocks a touch event targeted on a key.
274 * @param {!Element} key .
275 * @param {string} eventType .
277 function mockTouchEvent(key, eventType) {
278 var rect = key.getBoundingClientRect();
279 var x = rect.left + rect.width/2;
280 var y = rect.top + rect.height/2;
281 var e = document.createEvent('UIEvent');
282 e.initUIEvent(eventType, true, true);
283 e.touches = [{pageX: x, pageY: y}];
285 return key.dispatchEvent(e);
289 * Simulates tapping on a key.
290 * @param {!Element} key .
292 function mockTap(key) {
293 mockTouchEvent(key, 'touchstart');
294 mockTouchEvent(key, 'touchend');
298 * Returns the active keyboard view.
299 * @return {!HTMLElement}
301 function getActiveView() {
302 var container = document.querySelector('.inputview-container');
303 var views = container.querySelectorAll('.inputview-view');
304 for (var i = 0; i < views.length; i++) {
305 var display = views[i].style.display;
306 if (!display || display != 'none') {
313 * Finds the ancestor element corresponding the the soft key view.
314 * @param {Element} source .
315 * @return {?Element} .
317 function getSoftKeyView(source) {
318 var parent = source.parentElement;
319 while (parent && !parent.classList.contains('inputview-skv')) {
320 parent = parent.parentElement;
326 * Locates a key by label.
327 * @param {string} label The label on the key. If the key has multiple labels,
328 * |label| can match any of them.
329 * @param {string=} opt_rowId Optional ID of the row containing the key.
330 * @returns {?Element} .
332 function findKey(label, opt_rowId) {
333 var view = getActiveView();
334 assertTrue(!!view, 'Unable to find active keyboard view');
336 view = view.querySelector('#' + opt_rowId);
337 assertTrue(!!view, 'Unable to locate row ' + opt_rowId);
339 var candidates = view.querySelectorAll('.inputview-ch');
340 // Compact layouts use a different naming convention.
341 if (candidates.length == 0)
342 candidates = view.querySelectorAll('.inputview-special-key-name');
343 for (var i = 0; i < candidates.length; i++) {
344 if (candidates[i].textContent == label)
345 return candidates[i];
347 assertTrue(false, 'Cannot find key labeled \'' + label + '\'');
351 * Locates a key with matching ID. Note that IDs are not necessarily unique
352 * across keysets; however, it is unique within the active layout.
354 function findKeyById(label) {
355 var view = getActiveView();
356 assertTrue(!!view, 'Unable to find active keyboard view');
357 var key = view.querySelector('#' + label);
358 assertTrue(!!key, 'Cannot find key with ID ' + label);
363 * Verifies if a key contains a matching label.
364 * @param {Element} key .
365 * @param {string} label .
366 * @return {boolean} .
368 function hasLabel(key, label) {
369 var characters = key.querySelectorAll('.inputview-ch');
370 // Compact layouts represent keys differently.
371 if (characters.length == 0)
372 characters = key.querySelectorAll('.inputview-special-key-name');
373 for (var i = 0; i < characters.length; i++) {
374 if (characters[i].textContent == label)
381 * Mock typing of basic keys. Each keystroke should trigger a pair of
382 * API calls to send viritual key events.
383 * @param {string} label The character being typed.
384 * @param {number} keyCode The legacy key code for the character.
385 * @param {number} modifiers Indicates which if any of the shift, control and
386 * alt keys are being virtually pressed.
387 * @param {number=} opt_unicode Optional unicode value for the character. Only
388 * required if it cannot be directly calculated from the label.
390 function mockTypeCharacter(label, keyCode, modifiers, opt_unicode) {
391 var key = label.length == 1 ? findKey(label) : findKeyById(label);
392 // opt_unicode is allowed to be 0.
393 var unicodeValue = opt_unicode;
394 if (unicodeValue === undefined)
395 unicodeValue = label.charCodeAt(0);
396 var send = chrome.virtualKeyboardPrivate.sendKeyEvent;
397 send.addExpectation({
399 charValue: unicodeValue,
403 send.addExpectation({
405 charValue: unicodeValue,
413 * Emulates a user triggering a keyset modifier.
414 * @param {!KeysetModifier} The modifier to apply.
416 function applyKeysetModifier(modifier) {
417 if (modifier == KeysetModifier.NONE) {
420 var activeView = getActiveView();
421 if (modifier == KeysetModifier.SHIFT) {
422 // Set state of shift key.
423 var leftShift = activeView.querySelector('#ShiftLeft');
424 assertTrue(!!leftShift, 'Unable to find left shift key');
425 var currentShiftState = !!leftShift.querySelector(
426 '.inputview-special-key-highlight');
427 if (!currentShiftState) {
430 } else if (modifier == KeysetModifier.SYMBOL) {
431 var switchKey = findKey('?123', 'spaceKeyrow');
432 assertTrue(!!switchKey, 'Unable to find symbol transition key');
433 // Switch to symbol keyset.
436 var switchKey = findKey('~[<', 'row3');
437 assertTrue(!!switchKey, 'Unable to find more transition key');
438 // Switch to more keyset.