Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,9 @@
{
"extends": "../../../../.jshintrc",
"node": true,
"predef": [
"test",
"setup",
"marionette"
]
}

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
<meta charset="utf-8">
<title>Keyboard Test App</title>
<script type="text/javascript" src="js/main.js"></script>
<link rel="stylesheet" type="text/css" href="style/keyboard.css" />
</head>
<body>
<div id="keyboard" class="ime-test-app">
<button id="sendKey" class="key">Send Key: \o/ </button>
<button id="switchLayout" class="key">Switch layout</button>
</div>
</body>
</html>

View file

@ -0,0 +1,73 @@
'use strict';
/* global KeyEvent */
var inputContext = null;
var keyboardElement;
function init() {
keyboardElement = document.getElementById('keyboard');
window.navigator.mozInputMethod.oninputcontextchange = function() {
inputContext = navigator.mozInputMethod.inputcontext;
resizeWindow();
};
window.addEventListener('resize', resizeWindow);
keyboardElement.addEventListener('mousedown', function onMouseDown(evt) {
// Prevent loosing focus to the currently focused app
// Otherwise, right after mousedown event, the app will receive a focus event.
evt.preventDefault();
});
var sendKeyElement = document.getElementById('sendKey');
sendKeyElement.addEventListener('click', function sendKeyHandler() {
var testString = '\\o/';
for (var i = 0; i < testString.length; i++) {
sendKey(testString.charCodeAt(i));
}
});
var switchElement = document.getElementById('switchLayout');
switchElement.addEventListener('click', function switchHandler() {
var mgmt = navigator.mozInputMethod.mgmt;
mgmt.next();
});
// long press to trigger IME menu
var menuTimeout = 0;
switchElement.addEventListener('touchstart', function longHandler() {
menuTimeout = window.setTimeout(function menuTimeout() {
var mgmt = navigator.mozInputMethod.mgmt;
mgmt.showAll();
}, 700);
});
switchElement.addEventListener('touchend', function longHandler() {
clearTimeout(menuTimeout);
});
}
function resizeWindow() {
window.resizeTo(window.innerWidth, keyboardElement.clientHeight);
}
function sendKey(keyCode) {
switch (keyCode) {
case KeyEvent.DOM_VK_BACK_SPACE:
case KeyEvent.DOM_VK_RETURN:
if (inputContext) {
inputContext.sendKey(keyCode, 0, 0);
}
break;
default:
if (inputContext) {
inputContext.sendKey(0, keyCode, 0);
}
break;
}
}
window.addEventListener('load', init);

View file

@ -0,0 +1,19 @@
'use strict';
/* global MozActivity */
document.getElementById('back').addEventListener('click', function() {
document.addEventListener('visibilitychange', function() {
// Close ourself after the activity transition is completed.
window.close();
});
var activity = new MozActivity({
name: 'configure',
data: {
target: 'device'
}
});
activity.onerror = function() {};
});

View file

@ -0,0 +1,25 @@
{
"name": "LOL Keyboard",
"description": "An useless virtual keyboard that sends \\o/.",
"type": "privileged",
"role": "input",
"launch_path": "/settings.html",
"developer": {
"name": "The Gaia Team",
"url": "https://github.com/mozilla-b2g/gaia"
},
"permissions": {
"input": {
"description": "Send key events and characters to user focused input fields."
}
},
"inputs": {
"lol": {
"launch_path": "/index.html",
"name": "\\o/",
"description": "LOL layout",
"types": ["text", "number"]
}
},
"default_locale": "en-US"
}

View file

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
<meta charset="utf-8">
<title>LOL Keyboard Settings</title>
<script defer src="./js/settings.js"></script>
<style>
button {
font-size: 2em;
}
</style>
</head>
<body>
<h1>LOL Keyboard Settings</h1>
<p>Nothing to see here.</p>
<button id="back">&lt; Back</button>
</body>
</html>

View file

@ -0,0 +1,25 @@
html {
font-size: 10px;
pointer-events: none;
}
body {
margin: 0;
}
#keyboard {
width: 100%;
height: 20rem;
position: absolute;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
pointer-events: auto;
}
.key {
background-color: #333;
font-size: 2.4rem;
border: 0.2rem solid gray;
color: white;
margin: 0.2rem;
}

View file

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Keyboard APP Test</title>
<script defer src="script.js"></script>
</head>
<body class="keyboard-test-app">
<div>
<label for="textInput">Text</label>
<input type="text" id="textInput"/>
</div>
<div>
<label for="textareInput">Textarea</label>
<textarea id="textareaInput"></textarea>
</div>
<div>
<label for="urlInput">URL</label>
<input type="url" id="urlInput"/>
</div>
<div>
<label for="emailInput">Email</label>
<input type="email" id="emailInput"/>
</div>
<div>
<label for="passwordInput">Password</label>
<input type="password" id="passwordInput"/>
</div>
<div>
<label for="searchInput">Search</label>
<input type="search" id="searchInput"/>
</div>
<div>
<label for="numberInput">Number</label>
<input type="number" id="numberInput"/>
</div>
<div>
<label for="telInput">Tel</label>
<input type="tel" id="telInput"/>
</div>
<div>
<div id="nonInput" style="height: 100px;">
</div>
</div>
<button type="button" id="triggerPromptButton">Prompt</button>
<p id="promptResult"></p>
</body>
</html>

View file

@ -0,0 +1,9 @@
{
"name": "keyboard tests",
"description": "A keyboard testing app.",
"launch_path": "/index.html",
"developer": {
"name": "The Gaia Team",
"url": "https://github.com/mozilla-b2g/gaia"
}
}

View file

@ -0,0 +1,9 @@
'use strict';
document.getElementById('triggerPromptButton').onclick = () => {
// setTimeout is needed so we don't block marionette itself.
window.setTimeout(() => {
var str = window.prompt('The quick brown fox jumps over a', '');
document.getElementById('promptResult').textContent = str;
});
};

View file

@ -0,0 +1,187 @@
/* global suite */
'use strict';
var KeyboardTestApp = require('./lib/keyboard_test_app'),
Keyboard = require('./lib/keyboard'),
KeyboardSettings = require('./lib/keyboard_settings.js'),
assert = require('assert');
marionette('Keyboard Auto correction tests', function() {
var apps = {};
var keyboardTestApp = null;
var keyboard = null;
var keyboardSettings = null;
var client = null;
var system = null;
apps[KeyboardTestApp.ORIGIN] = __dirname + '/apps/keyboardtestapp';
client = marionette.client({
profile: {
apps: apps,
prefs: {
'focusmanager.testmode': true
},
settings: {
'lockscreen.enabled': false,
'ftu.manifestURL': null,
'keyboard.autocorrect': true
}
},
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
setup(function() {
system = client.loader.getAppClass('system');
system.waitForFullyLoaded();
keyboard = new Keyboard(client);
keyboardSettings = new KeyboardSettings(client);
keyboardTestApp = new KeyboardTestApp(client);
keyboard.waitForKeyboardReady();
});
suite('AutoCorrect switch', function() {
setup(function() {
keyboardSettings.launch();
});
test('Auto correct switch should equal to settings context',
function() {
var currentSwitch = client.settings.get('keyboard.autocorrect');
keyboardSettings.switchTo();
// Toggle one time
keyboardSettings.clickAutoCorrectOption();
assert.equal(keyboardSettings.autoCorrect, !currentSwitch);
// Toggle again
keyboardSettings.clickAutoCorrectOption();
assert.equal(keyboardSettings.autoCorrect, !!currentSwitch);
});
});
suite('With autocorrect on', function() {
setup(function () {
keyboardTestApp.launch();
keyboardTestApp.switchTo();
keyboardTestApp.textInput.tap();
keyboard.switchTo();
});
test('Predictive tests', function() {
// Test 1 - type first 7 letters of the expected word
// select suggestion will append a space.
var expectedWord = 'keyboard ';
keyboard.type(expectedWord.slice(0, 7));
keyboard.tapAutoCorrectWord();
keyboardTestApp.switchTo();
assert.equal(
keyboardTestApp.textInput.getAttribute('value'), expectedWord);
// Test 2 - tap second suggestion, then press space
keyboard.switchTo();
keyboard.type('Tes');
keyboardTestApp.switchTo();
assert.equal(
keyboardTestApp.textInput.getAttribute('value'), 'keyboard Tes');
keyboard.switchTo();
keyboard.tapSuggestionWord('Tea');
keyboardTestApp.switchTo();
assert.equal(
keyboardTestApp.textInput.getAttribute('value'), 'keyboard Tea ');
// Test 3 - type something with autocorrect and press space
keyboard.switchTo();
keyboard.type('ye ');
keyboardTestApp.switchTo();
assert.equal(
keyboardTestApp.textInput.getAttribute('value'), 'keyboard Tea he ');
// Test 4 - autocorrect, dot and backspace
keyboard.switchTo();
keyboard.type('wot.');
keyboardTestApp.switchTo();
assert.equal(
keyboardTestApp.textInput.getAttribute('value'),
'keyboard Tea he wit.');
keyboard.switchTo();
keyboard.tapBackspaceKey();
keyboardTestApp.switchTo();
assert.equal(
keyboardTestApp.textInput.getAttribute('value'), 'keyboard Tea he wot');
});
// For Bug 1073870
test('Move caret before autocorrect', function() {
keyboard.type('Hello worl');
keyboardTestApp.switchTo();
client.executeScript(
'var el = document.activeElement;' +
'el.selectionStart = el.selectionEnd = 5;');
keyboard.switchTo();
keyboard.type(' ');
keyboardTestApp.switchTo();
assert.equal(
keyboardTestApp.textInput.getAttribute('value'), 'Hello worl');
});
});
suite('With autocorrect off', function() {
setup(function () {
client.settings.set('keyboard.autocorrect', false);
keyboardTestApp.launch();
keyboardTestApp.switchTo();
keyboardTestApp.textInput.tap();
keyboard.switchTo();
});
test('No autocorrect while typing', function() {
keyboard.type('Tes');
keyboardTestApp.switchTo();
assert.equal(
keyboardTestApp.textInput.getAttribute('value'), 'Tes');
keyboard.switchTo();
keyboard.tapSuggestionWord('Tea');
keyboardTestApp.switchTo();
assert.equal(
keyboardTestApp.textInput.getAttribute('value'), 'Tea ');
keyboard.switchTo();
keyboard.type('ye.');
keyboardTestApp.switchTo();
assert.equal(
keyboardTestApp.textInput.getAttribute('value'), 'Tea ye.');
keyboard.switchTo();
keyboard.tapBackspaceKey();
keyboardTestApp.switchTo();
assert.equal(
keyboardTestApp.textInput.getAttribute('value'), 'Tea ye');
});
});
});

View file

@ -0,0 +1,84 @@
'use strict';
var KeyboardTestApp = require('./lib/keyboard_test_app'),
Keyboard = require('./lib/keyboard'),
assert = require('assert');
marionette('Dimiss the keyboard', function() {
var apps = {};
var keyboardTestApp = null;
var keyboard = null;
var systemInputMgmt = null;
var system = null;
var actions;
apps[KeyboardTestApp.ORIGIN] = __dirname + '/apps/keyboardtestapp';
var client = marionette.client({
profile: {
apps: apps,
prefs: {
'focusmanager.testmode': true
}
},
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
setup(function() {
systemInputMgmt = client.loader.getAppClass('system', 'input_management');
system = client.loader.getAppClass('system');
system.waitForFullyLoaded();
actions = client.loader.getActions();
keyboard = new Keyboard(client);
keyboardTestApp = new KeyboardTestApp(client);
keyboard.waitForKeyboardReady();
keyboardTestApp.launch();
keyboardTestApp.switchTo();
keyboardTestApp.textInput.click();
keyboard.switchTo();
});
test('Longpressing the space bar should dismiss the keyboard', function() {
// The time needed for dismiss is 500ms.
keyboard.longPressSpaceBar(0.7);
client.waitFor(() => {
return !systemInputMgmt.keyboardFrameDisplayed();
});
assert.ok(true);
});
test('Longpressing for only 0.5 sec should not dimiss keyboard', function() {
keyboard.longPressSpaceBar(0.5);
var keyboardContainer = keyboard.currentPanel;
assert.ok(keyboardContainer.displayed());
});
test('Click on a non-input field, should dimiss keyboard', function() {
keyboardTestApp.switchTo();
keyboardTestApp.nonInputArea.click();
client.waitFor(() => {
return !systemInputMgmt.keyboardFrameDisplayed();
});
assert.ok(true);
});
test('Pressing [Home] button should dimiss keyboard', function() {
system.tapHome();
client.waitFor(() => {
return !systemInputMgmt.keyboardFrameDisplayed();
});
assert.ok(true);
});
});

View file

@ -0,0 +1,81 @@
'use strict';
var KeyboardTestApp = require('./lib/keyboard_test_app'),
Keyboard = require('./lib/keyboard'),
assert = require('assert');
marionette('Email keyboard input tests', function() {
var apps = {};
var keyboardTestApp = null;
var keyboard = null;
var client = null;
var system = null;
apps[KeyboardTestApp.ORIGIN] = __dirname + '/apps/keyboardtestapp';
client = marionette.client({
profile: {
apps: apps,
prefs: {
'focusmanager.testmode': true
},
settings: {
'lockscreen.enabled': false,
'ftu.manifestURL': null
}
},
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
setup(function() {
system = client.loader.getAppClass('system');
system.waitForFullyLoaded();
keyboard = new Keyboard(client);
keyboardTestApp = new KeyboardTestApp(client);
keyboard.waitForKeyboardReady();
keyboardTestApp.launch();
keyboardTestApp.switchTo();
keyboardTestApp.emailInput.tap();
keyboard.switchTo();
});
test('Should be email layout', function() {
var atSymbol = '@';
var commaSymbol = ',';
assert.equal(keyboard.isKeyPresent(atSymbol), true);
assert.equal(keyboard.isKeyPresent(commaSymbol), false);
});
test('Type post@mydomain.com', function() {
keyboard.type('post');
keyboardTestApp.switchTo();
keyboardTestApp.emailInput.tap();
keyboard.switchTo();
keyboard.type('@');
keyboard.type('mydomain.com');
keyboardTestApp.switchTo();
assert.equal(
keyboardTestApp.emailInput.getAttribute('value'), 'post@mydomain.com');
});
test('Type post123@mydomain.com', function() {
keyboard.type('post123');
keyboard.type('@');
keyboard.type('mydomain.com');
keyboardTestApp.switchTo();
assert.equal(
keyboardTestApp.emailInput.getAttribute('value'),
'post123@mydomain.com');
});
});

View file

@ -0,0 +1,119 @@
'use strict';
var KeyboardTestApp = require('./lib/keyboard_test_app'),
Keyboard = require('./lib/keyboard'),
assert = require('assert');
marionette('Launch Keyboard APP', function() {
var apps = {};
var keyboardTestApp = null;
var keyboard = null;
var client = null;
var system = null;
apps[KeyboardTestApp.ORIGIN] = __dirname + '/apps/keyboardtestapp';
client = marionette.client({
profile: {
apps: apps,
prefs: {
'focusmanager.testmode': true
}
},
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
setup(function() {
system = client.loader.getAppClass('system');
system.waitForFullyLoaded();
keyboard = new Keyboard(client);
keyboardTestApp = new KeyboardTestApp(client);
keyboard.waitForKeyboardReady();
keyboardTestApp.launch();
keyboardTestApp.switchTo();
keyboardTestApp.textInput.click();
keyboard.switchTo();
});
test('Should show lowercase layout', function() {
// XXX: Workaround
// To get the #keyboard element to instead of the body element.
// The value of `client.findElement('body').displayed()` could not be true
// when the keyboard app is show up in the screen currently.
// Please refer to http://bugzil.la/995865.
var keyboardContainer = keyboard.currentPanel;
assert.ok(keyboardContainer.displayed());
var shiftKey = keyboard.shiftKey;
assert.ok(shiftKey.getAttribute('aria-pressed') === 'false');
var alphaKey = keyboard.getKey('a');
assert.ok(alphaKey.displayed());
});
test('Switch between inputs w/o waiting for layout loading', function() {
var keyboardContainer = keyboard.currentPanel;
assert.ok(keyboardContainer.displayed());
var shiftKey = keyboard.shiftKey;
var alphaKey = keyboard.getKey('a');
assert.ok(shiftKey.getAttribute('aria-pressed') === 'false');
assert.ok(alphaKey.displayed());
// Switch to test app frame.
keyboardTestApp.switchTo();
// Focus on the 2nd input
keyboardTestApp.numberInput.click();
// Without waiting for the keyboard, focus the 3rd input
keyboardTestApp.textareaInput.click();
// Switch back to keyboard
keyboard.switchTo();
// Should remain, or switched back to alpha keyboard.
client.waitFor(() => {
return alphaKey.displayed();
});
// Since the 3rd input is a <textarea>,
// we should reach upper case at this point.
client.waitFor(() => {
return (shiftKey.getAttribute('aria-pressed') === 'true');
});
});
test('Keys and buttons should be have aria-label set', function() {
var returnKey = keyboard.returnKey;
client.waitFor(() => {
return (returnKey.getAttribute('aria-label') === 'return');
});
var dismissSuggestionsKey = keyboard.dismissSuggestionsKey;
client.waitFor(() => {
var label = dismissSuggestionsKey.getAttribute('aria-label');
return (label === 'Dismiss');
});
});
test('Touching the status bar should not dismiss keyboard', function() {
// Click on the status bar
client.switchToFrame();
var topPanel = client.findElement('#top-panel');
topPanel.click();
client.helper.wait(3000);
keyboard.switchTo();
var keyboardContainer = keyboard.currentPanel;
assert.ok(keyboardContainer.displayed());
});
});

View file

@ -0,0 +1,10 @@
{
"extends": "../../../../../.jshintrc",
"node": true,
"predef": [
"test",
"setup",
"marionette",
"client"
]
}

View file

@ -0,0 +1,34 @@
'use strict';
function Base(client, origin) {
this.client = client;
this.origin = origin;
this.elements = {};
}
Base.prototype = {
launch: function() {
this.client.apps.launch(this.origin);
this.switchTo();
},
switchTo: function() {
this.client.switchToFrame();
this.client.apps.switchToApp(this.origin);
this.waitForReady();
},
close: function() {
this.client.apps.close(this.origin);
},
waitForReady: function() {
this.client.helper.waitForElement('body');
}
};
module.exports = Base;

View file

@ -0,0 +1,48 @@
'use strict';
/*
* A helper module for the testing 3rd-party IME app.
*/
var Base = require('./base');
function ImeTestApp(client) {
Base.call(this, client, ImeTestApp.ORIGIN);
}
module.exports = ImeTestApp;
ImeTestApp.ORIGIN = 'app://imetestapp.gaiamobile.org';
ImeTestApp.MANIFEST_URL = 'app://imetestapp.gaiamobile.org/manifest.webapp';
ImeTestApp.Selectors = Object.freeze({
sendKeyButton: '#sendKey',
switchLayoutButton: '#switchLayout'
});
ImeTestApp.prototype = {
__proto__: Base.prototype,
get sendKeyButton() {
return this.client.helper.waitForElement(
ImeTestApp.Selectors.sendKeyButton);
},
get switchLayoutButton() {
return this.client.helper.waitForElement(
ImeTestApp.Selectors.switchLayoutButton);
},
switchTo: function() {
var systemInputMgmt = this.client.loader.getAppClass(
'system', 'input_management');
var imeKeyboard;
systemInputMgmt.waitForKeyboardFrameDisplayed();
systemInputMgmt.switchToActiveKeyboardFrame();
imeKeyboard = this.client.helper.waitForElement('#keyboard.ime-test-app');
this.client.waitFor(() => {
return imeKeyboard.displayed();
});
}
};

View file

@ -0,0 +1,340 @@
'use strict';
var Base = require('./base');
var Marionette = require('marionette-client');
function Keyboard(client) {
Base.call(this, client);
this.actions = new Marionette.Actions(client);
this.currentLayout = null;
}
module.exports = Keyboard;
Keyboard.Selectors = Object.freeze({
currentPanel:
'.keyboard-type-container[data-active]',
imeSwitchingKey:
'.keyboard-type-container[data-active] ' +
'.keyboard-key[data-keycode="-3"]',
backspaceKey:
'.keyboard-type-container[data-active] ' +
'.keyboard-key[data-keycode="8"]',
returnKey:
'.keyboard-type-container[data-active] ' +
'.keyboard-key[data-l10n-id="returnKey2"]',
spaceBarKey: '.keyboard-type-container[data-active]' +
' .keyboard-key[data-keycode="32"]',
dismissSuggestionsKey:
'.keyboard-type-container[data-active] ' +
'.dismiss-suggestions-button',
shiftKey:
'.keyboard-type-container[data-active] ' +
'button.keyboard-key[data-l10n-id="upperCaseKey2"]',
autoCorrectWord:
'.keyboard-type-container[data-active] ' +
'.autocorrect',
});
Keyboard.Templates = Object.freeze({
key:
'.keyboard-type-container[data-active] ' +
'button.keyboard-key[data-keycode="%s"], ' +
'.keyboard-type-container[data-active] ' +
'button.keyboard-key[data-keycode-upper="%s"]',
upperCaseKey:
'.keyboard-type-container[data-active] ' +
'button.keyboard-key[data-keycode-upper="%s"]',
pageSwitchingKey:
'.keyboard-type-container[data-active] ' +
'button.keyboard-key[data-target-page="%s"]',
suggestionKey:
'.keyboard-type-container[data-active] ' +
'.suggestions-container span[data-data="%s"]',
});
Keyboard.LONGPRESS_CHARS = Object.freeze({
'0': 'º',
'?': '¿',
'$': '€£¥',
'!': '¡',
'a': 'áàâäåãāæ',
'c': 'çćč',
'e': 'éèêëēę€ɛ',
'i': 'įīîìíï',
'l': '£ł',
'n': 'ńñ',
'o': 'ɵøœōôòóö',
's': 'ßśš$',
'u': 'ūûùúü',
'y': '¥ÿ',
'z': 'žźż',
'A': 'ÁÀÂÄÅÃĀÆ',
'C': 'ÇĆČ',
'E': 'ÉÈÊËĒĘ€Ɛ',
'I': 'ĮĪÎÌÍÏ',
'L': '£Ł',
'N': 'ŃÑ',
'O': 'ƟØŒŌÔÒÓÖ',
'S': 'ŚŠŞ',
'U': 'ŪÛÙÚÜ',
'Y': '¥Ÿ',
'Z': 'ŽŹŻ'
});
Keyboard.TypeGroupMap = Object.freeze({
'text': 'text',
'textarea': 'text',
'url': 'url',
'email': 'email',
'password': 'password',
'search': 'text',
'number': 'number',
'tel': 'number'
});
Keyboard.prototype = {
__proto__: Base.prototype,
get currentPanel() {
return this.client.findElement(Keyboard.Selectors.currentPanel);
},
get imeSwitchingKey() {
return this.client.findElement(Keyboard.Selectors.imeSwitchingKey);
},
get backspaceKey() {
return this.client.findElement(Keyboard.Selectors.backspaceKey);
},
get returnKey() {
return this.client.findElement(Keyboard.Selectors.returnKey);
},
get spaceBarKey() {
return this.client.findElement(Keyboard.Selectors.spaceBarKey);
},
get shiftKey() {
return this.client.findElement(Keyboard.Selectors.shiftKey);
},
get dismissSuggestionsKey() {
return this.client.findElement(Keyboard.Selectors.dismissSuggestionsKey);
},
get autoCorrectWord() {
return this.client.findElement(Keyboard.Selectors.autoCorrectWord);
},
getKey: function(char) {
var selector = Keyboard.Templates.key.replace(/%s/g, char.charCodeAt(0));
return this.client.findElement(selector);
},
getPageSwitchingKey: function(index) {
var selector = Keyboard.Templates.pageSwitchingKey.replace(/%s/g, index);
return this.client.findElement(selector);
},
getSuggestionKey: function(word) {
var selector = Keyboard.Templates.suggestionKey.replace(/%s/, word);
return this.client.findElement(selector);
},
getLongPressCharMiddleKey: function(char) {
var chars = Keyboard.LONGPRESS_CHARS;
for (var middleChar in chars) {
if (chars[middleChar].indexOf(char) !== -1) {
return middleChar;
}
}
return undefined;
},
getCurrentInputType: function() {
return this.client.executeScript(
'return window.wrappedJSObject.app.getBasicInputType();');
},
getCurrentInputMode: function() {
return this.client.executeScript(
'return window.wrappedJSObject.app.inputContext.inputMode;');
},
getCurrentPageIndex: function() {
return this.client.executeScript(
'return window.wrappedJSObject.app.layoutManager.currentPageIndex;');
},
getCurrentKeyboardLayout: function() {
var keyboardFrame;
this.client.switchToFrame();
keyboardFrame = this.client.findElement(
'#keyboards .inputWindow.active iframe');
this.currentLayout = keyboardFrame.getAttribute('data-frame-name');
this.switchTo();
return this.currentLayout;
},
getSymbolPageIndex: function(key) {
this.switchToPage(1);
if (this.isKeyPresent(key)) {
return 1;
}
return 2;
},
tapBackspaceKey: function(word) {
this.backspaceKey.tap();
},
tapAutoCorrectWord: function() {
this.client.waitFor(() => {
return this.autoCorrectWord.displayed();
});
this.autoCorrectWord.tap();
},
tapSuggestionWord: function(word) {
this.getSuggestionKey(word).tap();
},
isUpperCase: function() {
return this.client.executeScript(
'return ' +
'window.wrappedJSObject.app.upperCaseStateManager.isUpperCase;');
},
isElementPresent: function(selector) {
var pollClient = this.client.scope({ searchTimeout: 50 });
try {
pollClient.findElement(selector);
} catch (e) {
return false;
}
return true;
},
isKeyPresent: function(key) {
var keyTemplate = Keyboard.Templates.key;
return this.isElementPresent(keyTemplate.replace(/%s/g, key.charCodeAt(0)));
},
switchCase: function(isUpperCase) {
if (isUpperCase === undefined || this.isUpperCase() === isUpperCase) {
return;
}
this.shiftKey.tap();
this.client.waitFor(() => {
var expected = isUpperCase ? 'true' : 'false';
return (this.shiftKey.getAttribute('aria-pressed') === expected);
});
},
switchToPage: function(index) {
var pageIndex = this.getCurrentPageIndex();
if (index === undefined || pageIndex === index) {
return;
}
this.getPageSwitchingKey(index).tap();
this.client.waitFor(() => {
return (this.getCurrentPageIndex() === index);
});
this.waitForPageReady();
},
switchToKeyLayout: function(key) {
var page;
var upperCase;
var currentLayout = this.currentLayout || this.getCurrentKeyboardLayout();
if (['number'].indexOf(currentLayout) !== -1) {
return;
}
if (key >= '0' && key <= '9') {
page = 1;
} else if (key >= 'A' && key <='Z') {
page = 0;
upperCase = true;
} else if (key >= 'a' && key <= 'z') {
page = 0;
upperCase = false;
} else if (key === ' ' || key === '\u0008' || key === '\u000d') {
} else {
page = this.getSymbolPageIndex(key);
}
this.switchToPage(page);
this.switchCase(upperCase);
},
type: function(string) {
string.split('').forEach((char) => {
var middleChar = this.getLongPressCharMiddleKey(char);
var lookingForKey = middleChar || char;
var keyElement;
this.switchToKeyLayout(lookingForKey);
if (!middleChar) {
keyElement = this.getKey(char);
this.actions.wait(0.1).tap(keyElement).perform();
} else {
keyElement = this.getKey(middleChar);
this.actions
.press(keyElement).wait(1.5).perform()
.move(this.getKey(char)).wait(0.2).release().perform();
}
});
},
longPressSpaceBar: function(time) {
this.actions.longPress(this.spaceBarKey, time).perform();
},
switchTo: function() {
var systemInputMgmt = this.client.loader.getAppClass(
'system', 'input_management');
systemInputMgmt.waitForKeyboardFrameDisplayed();
systemInputMgmt.switchToActiveKeyboardFrame();
this.waitForPageReady();
},
waitForPageReady: function() {
this.client.waitFor(() => {
return this.currentPanel.displayed();
});
},
waitForKeyboardReady: function() {
this.client.switchToFrame();
this.client.waitFor(() => {
return this.client.findElement(
'#keyboards .inputWindow .browser-container');
});
}
};

View file

@ -0,0 +1,50 @@
'use strict';
var Base = require('./base.js');
function KeyboardSettings(client) {
this.client = client.scope({ searchTimeout: 500 });
Base.call(this, client, KeyboardSettings.ORIGIN);
}
module.exports = KeyboardSettings;
KeyboardSettings.ORIGIN = 'app://keyboard.gaiamobile.org';
KeyboardSettings.Selectors = Object.freeze({
autoCorrectCheckBox: '#cb-autoCorrect'
});
KeyboardSettings.prototype = {
__proto__: Base.prototype,
get autoCorrectCheckBox() {
return this.client.findElement(
KeyboardSettings.Selectors.autoCorrectCheckBox);
},
get autoCorrect() {
return this.autoCorrectCheckBox.scriptWith(function(ele) {
return ele.wrappedJSObject.checked;
});
},
clickAutoCorrectOption: function() {
var currentSwitch = this.client.settings.get('keyboard.autocorrect');
this.autoCorrectCheckBox.click();
this.client.waitFor(() => {
return (this.autoCorrect !== currentSwitch);
});
},
waitForReady: function() {
this.client.waitFor(() => {
return this.autoCorrectCheckBox.displayed();
});
}
};

View file

@ -0,0 +1,97 @@
'use strict';
var Base = require('./base');
function KeyboardTests(client) {
Base.call(this, client, KeyboardTests.ORIGIN);
}
module.exports = KeyboardTests;
KeyboardTests.ORIGIN = 'app://keyboardtestapp.gaiamobile.org';
KeyboardTests.Selector = Object.freeze({
textInput: '#textInput',
textareaInput: '#textareaInput',
urlInput: '#urlInput',
emailInput: '#emailInput',
passwordInput: '#passwordInput',
searchInput: '#searchInput',
numberInput: '#numberInput',
telInput: '#telInput',
nonInputArea: '#nonInput',
triggerPromptButton: '#triggerPromptButton',
promptResult: '#promptResult'
});
KeyboardTests.prototype = {
__proto__: Base.prototype,
get textInput() {
return this.client.findElement(KeyboardTests.Selector.textInput);
},
get textareaInput() {
return this.client.findElement(KeyboardTests.Selector.textareaInput);
},
get urlInput() {
return this.client.findElement(KeyboardTests.Selector.urlInput);
},
get emailInput() {
return this.client.findElement(KeyboardTests.Selector.emailInput);
},
get passwordInput() {
return this.client.findElement(KeyboardTests.Selector.passwordInput);
},
get searchInput() {
return this.client.findElement(KeyboardTests.Selector.searchInput);
},
get numberInput() {
return this.client.findElement(KeyboardTests.Selector.numberInput);
},
get telInput() {
return this.client.findElement(KeyboardTests.Selector.telInput);
},
get nonInputArea() {
return this.client.findElement(KeyboardTests.Selector.nonInputArea);
},
get triggerPromptButton() {
return this.client.findElement(KeyboardTests.Selector.triggerPromptButton);
},
get promptResult() {
return this.client.findElement(KeyboardTests.Selector.promptResult);
},
triggerPromptModalDialog: function() {
this.triggerPromptButton.tap();
},
waitForReady: function() {
var inputs = [
this.textInput,
this.textareaInput,
this.urlInput,
this.emailInput,
this.passwordInput,
this.searchInput,
this.numberInput,
this.telInput
];
this.client.waitFor(() => {
return inputs.every((ele) => {
return ele.displayed();
});
});
}
};

View file

@ -0,0 +1,74 @@
'use strict';
var KeyboardTestApp = require('./lib/keyboard_test_app'),
Keyboard = require('./lib/keyboard'),
assert = require('assert');
marionette('Number keyboard input tests', function() {
var apps = {};
var keyboardTestApp = null;
var keyboard = null;
var client = null;
var system = null;
apps[KeyboardTestApp.ORIGIN] = __dirname + '/apps/keyboardtestapp';
client = marionette.client({
profile: {
apps: apps,
prefs: {
'focusmanager.testmode': true
},
settings: {
'lockscreen.enabled': false,
'ftu.manifestURL': null
}
},
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
setup(function() {
system = client.loader.getAppClass('system');
system.waitForFullyLoaded();
keyboard = new Keyboard(client);
keyboardTestApp = new KeyboardTestApp(client);
keyboard.waitForKeyboardReady();
keyboardTestApp.launch();
keyboardTestApp.switchTo();
keyboardTestApp.numberInput.tap();
keyboard.switchTo();
});
test('Should be number layout', function() {
assert.equal(
keyboard.getCurrentKeyboardLayout(), Keyboard.TypeGroupMap.number);
});
test('Alphabet should not present on keyboard', function() {
var testAlphabetKey = 'a';
assert.equal(keyboard.isKeyPresent(testAlphabetKey), false);
});
test('Type 1', function() {
var inputString = '1';
keyboard.type(inputString);
keyboardTestApp.switchTo();
assert.equal(
keyboardTestApp.numberInput.getAttribute('value'), inputString);
});
test('Type 7997979979', function() {
var inputString = '7997979979';
keyboard.type(inputString);
keyboardTestApp.switchTo();
assert.equal(
keyboardTestApp.numberInput.getAttribute('value'), inputString);
});
});

View file

@ -0,0 +1,134 @@
'use strict';
var KeyboardTestApp = require('./lib/keyboard_test_app');
var ImeTestApp = require('./lib/ime_test_app');
var Keyboard = require('./lib/keyboard');
var assert = require('assert');
marionette('Switch Keyboard App', function() {
var keyboardTestApp = null;
var keyboard = null;
var systemInputMgmt = null;
var imeTestApp = null;
var client = null;
var actions = null;
var apps = {};
var system = null;
// Pre-install keyboard testing app
apps[KeyboardTestApp.ORIGIN] = __dirname + '/apps/keyboardtestapp';
// And a testing 3rd-party IME app
apps[ImeTestApp.ORIGIN] = __dirname + '/apps/imetestapp';
var keyboardSettings = {};
keyboardSettings[Keyboard.MANIFEST_URL] = {
'en': true,
'number': true
};
// Enable a testing IME app
keyboardSettings[ImeTestApp.MANIFEST_URL] = {
'lol': true
};
client = marionette.client({
profile: {
apps: apps,
prefs: {
'focusmanager.testmode': true
},
settings: {
'keyboard.enabled-layouts': keyboardSettings
}
},
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
/*
* To check the 3rd-party IME is shown.
*/
function check3rdPartyIme() {
// switch back to system
client.switchToFrame();
// wait for the 2nd keyboard is loaded
systemInputMgmt.ensureInputWindowCount(2);
systemInputMgmt.switchToActiveKeyboardFrame();
assert.ok(imeTestApp.sendKeyButton.displayed());
}
setup(function() {
actions = client.loader.getActions();
systemInputMgmt = client.loader.getAppClass('system', 'input_management');
system = client.loader.getAppClass('system');
system.waitForFullyLoaded();
imeTestApp = new ImeTestApp(client);
keyboard = new Keyboard(client);
keyboardTestApp = new KeyboardTestApp(client);
keyboard.waitForKeyboardReady();
keyboardTestApp.launch();
keyboardTestApp.switchTo();
keyboardTestApp.urlInput.click();
keyboard.switchTo();
keyboardTestApp.switchTo();
keyboardTestApp.textInput.click();
// Wait for the keyboard pop up and switch to it
keyboard.switchTo();
});
test('Checking the switching IME function is available', function() {
var imeSwitchingKey = keyboard.imeSwitchingKey;
assert.ok(imeSwitchingKey.displayed());
// Click to switch to next IME
imeSwitchingKey.click();
check3rdPartyIme();
});
test('Long press to show the IME menu', function() {
var imeSwitchingKey = keyboard.imeSwitchingKey;
// Long press to show IME menu
actions.longPress(imeSwitchingKey, 0.7).perform();
// switch back to system
client.switchToFrame();
var imeMenu = systemInputMgmt.imeMenu;
assert.ok(imeMenu.displayed());
// select the 3rd-party IME
systemInputMgmt.selectImeOption(1);
check3rdPartyIme();
});
test('Drag down the utility tray', function() {
// Swipe to drag down the utility tray
systemInputMgmt.dragDownUtilityTray();
// Check the IME switching buttion in notification
var imeNotification = systemInputMgmt.imeNotification;
client.waitFor(function() {
return imeNotification.displayed();
});
imeNotification.click();
assert.ok(systemInputMgmt.imeMenu.displayed());
//XXX: Wait for the previous keyboard to hide or we may not be able to
// switch to the next keyboard.
client.waitFor(function() {
return systemInputMgmt.keyboardFrameHidden();
});
// select the 3rd-party IME
systemInputMgmt.selectImeOption(1);
check3rdPartyIme();
});
});

View file

@ -0,0 +1,119 @@
'use strict';
var KeyboardTestApp = require('./lib/keyboard_test_app'),
Dialog = require('../../../system/test/marionette/lib/dialog'),
Keyboard = require('./lib/keyboard'),
assert = require('assert');
marionette('Text keyboard input tests', function() {
var apps = {};
var keyboardTestApp = null;
var keyboard = null;
var systemInputMgmt = null;
var client = null;
var system = null;
apps[KeyboardTestApp.ORIGIN] = __dirname + '/apps/keyboardtestapp';
client = marionette.client({
profile: {
apps: apps,
prefs: {
'focusmanager.testmode': true
},
settings: {
'lockscreen.enabled': false,
'ftu.manifestURL': null
}
},
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
setup(function() {
systemInputMgmt = client.loader.getAppClass('system', 'input_management');
system = client.loader.getAppClass('system');
system.waitForFullyLoaded();
keyboard = new Keyboard(client);
keyboardTestApp = new KeyboardTestApp(client);
keyboard.waitForKeyboardReady();
keyboardTestApp.launch();
keyboardTestApp.switchTo();
keyboardTestApp.textInput.tap();
keyboard.switchTo();
});
test('Type abc', function() {
var inputString = 'abc';
keyboard.type(inputString);
keyboardTestApp.switchTo();
assert.equal(
keyboardTestApp.textInput.getAttribute('value'), inputString);
});
test('Type multiple alphabet', function() {
var inputString = 'aGDsaIabcmvljdDFDFDDs śZîd';
keyboard.type(inputString);
keyboardTestApp.switchTo();
assert.equal(
keyboardTestApp.textInput.getAttribute('value'), inputString);
});
test('Type number and then alphabet', function() {
var inputString = '123abc';
keyboard.type(inputString);
keyboardTestApp.switchTo();
assert.equal(
keyboardTestApp.textInput.getAttribute('value'), inputString);
});
test('Type alphabet and then number', function() {
var inputString = 'abc123';
keyboard.type(inputString);
keyboardTestApp.switchTo();
assert.equal(
keyboardTestApp.textInput.getAttribute('value'), inputString);
});
test('Type symbols', function() {
var inputString = 'aG1 !@#$&*-_/(),.:;"\'?[]{}<>+=`^~|\\%';
keyboard.type(inputString);
keyboardTestApp.switchTo();
assert.equal(
keyboardTestApp.textInput.getAttribute('value'), inputString);
});
var dialog;
test('Type into an window.prompt() dialog and hit enter', function() {
// Hide keyboard before click prompt
keyboardTestApp.switchTo();
keyboardTestApp.nonInputArea.tap();
client.waitFor(() => {
return !systemInputMgmt.keyboardFrameDisplayed();
});
dialog = new Dialog(client);
keyboardTestApp.switchTo();
keyboardTestApp.triggerPromptModalDialog();
client.switchToFrame();
dialog.promptInput.tap();
keyboard.switchTo();
keyboard.type('lazy dog.');
keyboard.getKey('\u000d').tap();
keyboardTestApp.switchTo();
assert.equal('lazy dog.', keyboardTestApp.promptResult.text());
});
});

View file

@ -0,0 +1,100 @@
'use strict';
var KeyboardTestApp = require('./lib/keyboard_test_app'),
Keyboard = require('./lib/keyboard'),
assert = require('assert');
marionette('Textarea keyboard input tests', function() {
var apps = {};
var keyboardTestApp = null;
var keyboard = null;
var client = null;
var system = null;
apps[KeyboardTestApp.ORIGIN] = __dirname + '/apps/keyboardtestapp';
client = marionette.client({
profile: {
apps: apps,
prefs: {
'focusmanager.testmode': true
},
settings: {
'lockscreen.enabled': false,
'ftu.manifestURL': null
}
},
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
setup(function() {
system = client.loader.getAppClass('system');
system.waitForFullyLoaded();
keyboard = new Keyboard(client);
keyboardTestApp = new KeyboardTestApp(client);
keyboard.waitForKeyboardReady();
keyboardTestApp.launch();
keyboardTestApp.switchTo();
keyboardTestApp.textareaInput.tap();
keyboard.switchTo();
});
test('Type Abc', function() {
var inputString = 'Abc';
keyboard.type(inputString.substring(0, 1));
var shiftKey = keyboard.shiftKey;
client.waitFor(function() {
return (shiftKey.getAttribute('aria-pressed') === 'false');
});
keyboard.type(inputString.substring(1));
keyboardTestApp.switchTo();
assert.equal(
keyboardTestApp.textareaInput.getAttribute('value'), inputString);
});
test('Double tapping space bar', function() {
var inputString = 'Aa';
keyboard.type(inputString);
client.waitFor(() => {
return keyboard.autoCorrectWord.displayed();
});
// type 2 spaces
keyboard.type(' ');
// Switch to test app frame.
keyboardTestApp.switchTo();
assert.equal(
'As. ', keyboardTestApp.textareaInput.getAttribute('value'));
});
test('Tap space bar and then wait for a while before tapping again',
function() {
var inputString = 'Aa';
keyboard.type(inputString);
client.waitFor(() => {
return keyboard.autoCorrectWord.displayed();
});
// type one space and then tap again after a while
keyboard.type(' ');
// The timeout for double tapping is set as 700ms.
client.helper.wait(800);
keyboard.type(' ');
// Switch to test app frame.
keyboardTestApp.switchTo();
assert.equal('As ', keyboardTestApp.textareaInput.getAttribute('value'));
});
});

View file

@ -0,0 +1,127 @@
/*
* This test is to ensure that system's keyboard manager would
* correctly show the built-in keyboard app after the previous activated
* keyboard has been removed.
*/
'use strict';
var KeyboardTestApp = require('./lib/keyboard_test_app');
var ImeTestApp = require('./lib/ime_test_app');
var Keyboard = require('./lib/keyboard');
var assert = require('assert');
marionette('Show keyboard app after uninstallation', function() {
var keyboardTestApp = null;
var keyboard = null;
var systemInputMgmt = null;
var system = null;
var imeTestApp = null;
var appInstall = null;
var client = null;
var apps = {};
var confirmDialog = null;
// Pre-install keyboard testing app
apps[KeyboardTestApp.ORIGIN] = __dirname + '/apps/keyboardtestapp';
// And a testing 3rd-party IME app
apps[ImeTestApp.ORIGIN] = __dirname + '/apps/imetestapp';
var keyboardSettings = {};
keyboardSettings[Keyboard.MANIFEST_URL] = {
'en': true,
'number': true
};
// Enable a testing IME app
keyboardSettings[ImeTestApp.MANIFEST_URL] = {
'lol': true
};
client = marionette.client({
profile: {
apps: apps,
prefs: {
'focusmanager.testmode': true
},
settings: {
'keyboard.enabled-layouts': keyboardSettings
}
},
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
/*
* To check the 3rd-party IME is shown.
*/
function check3rdPartyIme() {
// switch back to system
client.switchToFrame();
// wait for the 2nd keyboard is loaded
systemInputMgmt.ensureInputWindowCount(2);
systemInputMgmt.switchToActiveKeyboardFrame();
return imeTestApp.sendKeyButton.displayed();
}
setup(function() {
systemInputMgmt = client.loader.getAppClass('system', 'input_management');
system = client.loader.getAppClass('system');
system.waitForFullyLoaded();
keyboard = new Keyboard(client);
appInstall = client.loader.getAppClass('system', 'app_install');
confirmDialog = client.loader.getAppClass('system', 'confirm_dialog');
imeTestApp = new ImeTestApp(client);
keyboard.waitForKeyboardReady();
// create a keyboard test app
keyboardTestApp = new KeyboardTestApp(client);
keyboardTestApp.launch();
keyboardTestApp.switchTo();
keyboardTestApp.urlInput.click();
keyboard.switchTo();
keyboardTestApp.switchTo();
keyboardTestApp.textInput.click();
keyboard.switchTo();
keyboard.imeSwitchingKey.click();
// Click to switch to next IME
check3rdPartyIme();
// XXX: Blur the input field, otherwise the keyboard frame has higher
// z-index than dialog and we can't click it
systemInputMgmt.waitForKeyboardFrameDisplayed();
keyboardTestApp.switchTo();
keyboardTestApp.nonInputArea.click();
// Uninstall the current active IME
appInstall.uninstall(ImeTestApp.MANIFEST_URL);
client.switchToFrame();
confirmDialog.confirm('remove');
// Click the input field again to check the built-in keyboard
keyboardTestApp.switchTo();
// XXX: do something to remove the focus
keyboardTestApp.nonInputArea.click();
keyboardTestApp.textInput.click();
// Wait for the keyboard pop up and switch to it
keyboard.switchTo();
});
test('Fallback to built-in keyboard when the active IME has been ' +
'uninstalled', function() {
assert.ok(keyboard.currentPanel.displayed());
});
test('Should not show IME switching key after uninstallation', function() {
// Since the switching key is gone, we would show ',' on the first page
var commaKey = keyboard.getKey(',');
assert.ok(commaKey.displayed());
});
});