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.