Roll src/third_party/WebKit d9c6159:8139f33 (svn 201974:201975)
[chromium-blink-merge.git] / chrome / test / data / chromeos / virtual_keyboard / virtual_keyboard_test_base.js
blob2fa60a1c6bdeb5fcf846722bd6f97c5b4927a662
1 /*
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.
5  */
7 /**
8  * Set to true while debugging virtual keyboard tests, for verbose debug output.
9  */
10 var debugging = false;
12 /**
13  * The enumeration of keyset modifiers.
14  * @enum {string}
15  */
16 var KeysetModifier = {
17   NONE: 'none',
18   SHIFT: 'shift',
19   MORE: 'more',
20   SYMBOL: 'symbol'
23 /**
24  * Flag values for the shift, control and alt modifiers as defined by
25  * EventFlags in "event_constants.h".
26  * @type {enum}
27  */
28 var Modifier = {
29   NONE: 0,
30   SHIFT: 2,
31   CONTROL: 4,
32   ALT: 8
35 /**
36  * Display diagnostic messages when debugging tests.
37  * @param {string} Message to conditionally display.
38  */
39 function Debug(message) {
40   if (debugging)
41     console.log(message);
44 var mockController;
46 /**
47  * Adds mocks for chrome extension API calls.
48  *
49  * @param {MockController} mockController Controller for validating
50  *     calls with expectations.
51  */
52 function mockExtensionApis(mockController) {
54   /**
55    * Mocks methods within a namespace.
56    * @param {string} namespace Dot delimited namespace.
57    * @param {Array<string>} methods List of methods names to mock.
58    */
59   var addMocks = function(namespace, methods) {
60     var parts = namespace.split('.');
61     var base = window;
62     for (var i = 0; i < parts.length; i++) {
63       if (!base[parts[i]])
64         base[parts[i]] = {};
65       base = base[parts[i]];
66     }
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() {};
75       };
76       // TODO(kevers): Add validateCall override that strips functions from the
77       // argument signature before calling MockMethod.Prototype.validateCall
78     });
79   };
81   var virtualKeyboardPrivateMethods = [
82     'getKeyboardConfig',
83     'hideKeyboard',
84     'insertText',
85     'lockKeyboard',
86     'moveCursor',
87     'sendKeyEvent',
88     'setMode',
89     'setKeyboardState',
90     'setHotrodKeyboard'
91   ];
93   var inputMethodPrivateMethods = [
94     'getCurrentInputMethod',
95     'getInputMethods',
96     'setCurrentInputMethod'
97   ];
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
104   // flow of events.
105   chrome.runtime.onMessage.addListener = function() {};
107   chrome.i18n = {};
108   chrome.i18n.getMessage = function(name) {
109     return name;
110   };
114  * Adjust the size and position of the keyboard for testing.
115  */
116 function adjustScreenForTesting() {
117   var style = document.body.style;
118   style.left = 0;
119   style.top = 0;
120   style.bottom = 0;
121   style.right = 0;
122   style.background = 'transparent';
123   style.position = 'absolute';
125   // Adjust positioning for testing in a non-fullscreen display.
126   Object.defineProperty(window.screen, 'width', {
127     get: function() {
128       return document.body.clientWidth;
129     },
130     configurable: true
131   });
132   Object.defineProperty(window.screen, 'height', {
133     get: function() {
134       return document.body.clientHeight;
135     },
136     configurable: true
137   });
141  * Create mocks for the virtualKeyboardPrivate API. Any tests that trigger API
142  * calls must set expectations for call signatures.
143  */
144 function setUp() {
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,
162                  observedEvent.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.');
173   };
174   chrome.virtualKeyboardPrivate.sendKeyEvent.validateCall = validateSendCall;
176   var validateLockKeyboard = function(index, expected, observed) {
177     assertEquals(expected[0],
178                  observed[0],
179                  'Mismatched keyboard lock/unlock state.');
180   };
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.
187   };
189   // Set data to be provided to callbacks in response to API calls.
190   // TODO(kevers): Provide mechanism to override these values for individual
191   // tests as needed.
192   chrome.virtualKeyboardPrivate.getKeyboardConfig.setCallbackData({
193     layout: 'qwerty',
194     a11ymode: false,
195     experimental: false,
196     features: [],
197   });
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.
212  */
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.
225  */
226 function onKeyboardReady(runTestCallback, opt_config) {
227   var default_config = {
228     keyset: 'us.compact.qwerty',
229     languageCode: 'en',
230     passwordLayout: 'us',
231     name: 'English'
232   };
233   var config = opt_config || default_config;
234   var options = config.options || {};
235   chrome.virtualKeyboardPrivate.keyboardLoaded = function() {
236     runTestCallback();
237   };
238   window.initializeVirtualKeyboard(config.keyset,
239                                    config.languageCode,
240                                    config.passwordLayout,
241                                    config.name,
242                                    options);
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
248  *     keysets.
249  * @param {Function} continueTestCallback Callback function to invoke in order
250  *     to resume the test.
251  */
252 function onKeysetsReady(keyset, continueTestCallback) {
253   if (keyset instanceof Array) {
254     var blocked = false;
255     keyset.forEach(function(id) {
256       if (!(id in controller.container_.keysetViewMap))
257         blocked = true;
258     });
259     if (!blocked) {
260       continueTestCallback();
261       return;
262     }
263   } else if (keyset in controller.container_.keysetViewMap) {
264     continueTestCallback();
265     return;
266   }
267   setTimeout(function() {
268     onKeysetsReady(keyset, continueTestCallback);
269   }, 100);
273  * Mocks a touch event targeted on a key.
274  * @param {!Element} key .
275  * @param {string} eventType .
276  */
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}];
284   e.target = key;
285   return key.dispatchEvent(e);
289  * Simulates tapping on a key.
290  * @param {!Element} key .
291  */
292 function mockTap(key) {
293   mockTouchEvent(key, 'touchstart');
294   mockTouchEvent(key, 'touchend');
298  * Returns the active keyboard view.
299  * @return {!HTMLElement}
300  */
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') {
307       return views[i];
308     }
309   }
313  * Finds the ancestor element corresponding the the soft key view.
314  * @param {Element} source .
315  * @return {?Element} .
316  */
317 function getSoftKeyView(source) {
318   var parent = source.parentElement;
319   while (parent && !parent.classList.contains('inputview-skv')) {
320     parent = parent.parentElement;
321   }
322   return parent;
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} .
331  */
332 function findKey(label, opt_rowId) {
333   var view = getActiveView();
334   assertTrue(!!view, 'Unable to find active keyboard view');
335   if (opt_rowId) {
336     view = view.querySelector('#' + opt_rowId);
337     assertTrue(!!view, 'Unable to locate row ' + opt_rowId);
338   }
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];
346   }
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.
353  */
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);
359   return key;
363  * Verifies if a key contains a matching label.
364  * @param {Element} key .
365  * @param {string} label .
366  * @return {boolean} .
367  */
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)
375       return true;
376   }
377   return false;
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.
389  */
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({
398     type: 'keydown',
399     charValue: unicodeValue,
400     keyCode: keyCode,
401     modifiers: modifiers
402   });
403   send.addExpectation({
404     type: 'keyup',
405     charValue: unicodeValue,
406     keyCode: keyCode,
407     modifiers: modifiers
408   });
409   mockTap(key);
413  * Emulates a user triggering a keyset modifier.
414  * @param {!KeysetModifier} The modifier to apply.
415  */
416 function applyKeysetModifier(modifier) {
417   if (modifier == KeysetModifier.NONE) {
418     return;
419   }
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) {
428       mockTap(leftShift);
429     }
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.
434     mockTap(switchKey);
435   } else {
436     var switchKey = findKey('~[<', 'row3');
437     assertTrue(!!switchKey, 'Unable to find more transition key');
438     // Switch to more keyset.
439     mockTap(switchKey);
440   }