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:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
437
gaia/apps/keyboard/test/build/integration/keyboard_test.js
Executable file
437
gaia/apps/keyboard/test/build/integration/keyboard_test.js
Executable file
|
|
@ -0,0 +1,437 @@
|
|||
'use strict';
|
||||
|
||||
/* jshint node: true, mocha: true */
|
||||
/* global suiteSetup */
|
||||
|
||||
var assert = require('chai').assert;
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var helper = require('helper');
|
||||
var AdmZip = require('adm-zip');
|
||||
var jsdom = require('jsdom').jsdom;
|
||||
|
||||
function checkLayoutsJSON(zipPath, expectedArr) {
|
||||
var zip = new AdmZip(zipPath);
|
||||
var entry = zip.getEntry('js/settings/layouts.json');
|
||||
var actualArr = JSON.parse(zip.readAsText(entry));
|
||||
|
||||
actualArr.forEach(function(layout) {
|
||||
assert.isTrue(
|
||||
!!layout.dictFileSize,
|
||||
'Expect layout contain to non-zero dictFileSize, id=' + layout.id);
|
||||
delete layout.dictFileSize;
|
||||
});
|
||||
|
||||
expectedArr.forEach(function(layout) {
|
||||
delete layout.dictFileSize;
|
||||
});
|
||||
|
||||
assert.deepEqual(actualArr, expectedArr);
|
||||
}
|
||||
|
||||
suite('Keyboard layouts building tests', function() {
|
||||
suiteSetup(helper.cleanupWorkspace);
|
||||
teardown(helper.cleanupWorkspace);
|
||||
|
||||
// default make -- build with selected layouts and dictionaries
|
||||
test('APP=keyboard make', function(done) {
|
||||
var cmd = 'APP=keyboard make';
|
||||
helper.exec(cmd, function(error, stdout, stderr) {
|
||||
helper.checkError(error, stdout, stderr);
|
||||
|
||||
var config = JSON.parse(process.env.BUILD_CONFIG);
|
||||
var layoutIds = ['ar',
|
||||
'bn-Avro',
|
||||
'bn-Probhat',
|
||||
'de',
|
||||
'de-Neo',
|
||||
'dz-BT',
|
||||
'en',
|
||||
'en-Colemak',
|
||||
'en-Dvorak',
|
||||
'en-Neo',
|
||||
'eo',
|
||||
'es',
|
||||
'es-Americas',
|
||||
'fa',
|
||||
'fr',
|
||||
'fr-CA',
|
||||
'fr-CH',
|
||||
'fr-Dvorak-bepo',
|
||||
'he',
|
||||
'hi',
|
||||
'ig',
|
||||
'ko',
|
||||
'mk',
|
||||
'my',
|
||||
'pl',
|
||||
'pt-BR',
|
||||
'ta',
|
||||
'te',
|
||||
'th',
|
||||
'uk',
|
||||
'vi-Qwerty',
|
||||
'vi-Telex',
|
||||
'vi-Typewriter',
|
||||
'wo',
|
||||
'zh-Hans-Pinyin'];
|
||||
var zipPath = path.join(process.cwd(), 'profile',
|
||||
'webapps', 'keyboard.gaiamobile.org', 'application.zip');
|
||||
var appDirPath = config.GAIA_DIR + '/apps/keyboard';
|
||||
|
||||
var layouts = layoutIds.map(function(layout) {
|
||||
return 'js/layouts/' + layout + '.js';
|
||||
});
|
||||
|
||||
// Unfortunately we can only whilelist files here because we shouldn't
|
||||
// throw error if excluded files are missing (e.g. README).
|
||||
var imes = [
|
||||
'js/imes/jshangul/jshangul.js',
|
||||
'js/imes/jspinyin/empinyin_files.data',
|
||||
'js/imes/jspinyin/empinyin_files.js',
|
||||
'js/imes/jspinyin/jspinyin.js',
|
||||
'js/imes/jspinyin/libpinyin.js',
|
||||
'js/imes/jspinyin/worker.js',
|
||||
'js/imes/latin/latin.js',
|
||||
'js/imes/latin/predictions.js',
|
||||
'js/imes/latin/worker.js'
|
||||
];
|
||||
|
||||
var dicts = [
|
||||
'js/imes/latin/dictionaries/de.dict',
|
||||
'js/imes/latin/dictionaries/en_us.dict',
|
||||
'js/imes/latin/dictionaries/es.dict',
|
||||
'js/imes/latin/dictionaries/fr.dict',
|
||||
'js/imes/latin/dictionaries/pl.dict',
|
||||
'js/imes/latin/dictionaries/pt_br.dict'
|
||||
];
|
||||
|
||||
var checkList = [].concat(layouts, imes, dicts);
|
||||
checkList.forEach(function(path) {
|
||||
helper.checkFileInZip(zipPath, path, appDirPath + '/' + path);
|
||||
});
|
||||
|
||||
// Verify inputs entry in manifest
|
||||
var zip = new AdmZip(zipPath);
|
||||
var entry = zip.getEntry('manifest.webapp');
|
||||
var manifest = JSON.parse(zip.readAsText(entry));
|
||||
var inputKeysInManifest = Object.keys(manifest.inputs);
|
||||
|
||||
assert.deepEqual(inputKeysInManifest.sort(),
|
||||
[].concat(layoutIds, 'number', 'emoji').sort());
|
||||
|
||||
// Verify dictionary config
|
||||
var dictJSON = JSON.parse(fs.readFileSync(
|
||||
appDirPath +
|
||||
'/test/build/integration/resources/' +
|
||||
'default-make-layouts.json'));
|
||||
|
||||
checkLayoutsJSON(zipPath, dictJSON);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// Build with all layouts and dictionaries
|
||||
test('APP=keyboard GAIA_KEYBOARD_LAYOUTS=* make', function(done) {
|
||||
var cmd = 'APP=keyboard GAIA_KEYBOARD_LAYOUTS=* make';
|
||||
helper.exec(cmd, function(error, stdout, stderr) {
|
||||
helper.checkError(error, stdout, stderr);
|
||||
|
||||
var config = JSON.parse(process.env.BUILD_CONFIG);
|
||||
var zipPath = path.join(process.cwd(), 'profile',
|
||||
'webapps', 'keyboard.gaiamobile.org', 'application.zip');
|
||||
var appDirPath = config.GAIA_DIR + '/apps/keyboard';
|
||||
var layoutIds =
|
||||
fs.readdirSync(appDirPath + '/js/layouts').filter(function(filename) {
|
||||
return (path.extname(filename) === '.js');
|
||||
}).map(function(filename) {
|
||||
return path.basename(filename, '.js');
|
||||
}).filter(function(layoutId) {
|
||||
// Exclude noIncludeInExpandLayoutIdSet layouts
|
||||
return (['emoji'].indexOf(layoutId) === -1);
|
||||
});
|
||||
|
||||
var layouts = layoutIds.map(function(layout) {
|
||||
return 'js/layouts/' + layout + '.js';
|
||||
});
|
||||
|
||||
// Unfortunately we can only whilelist files here because we shouldn't
|
||||
// throw error if excluded files are missing (e.g. README).
|
||||
|
||||
// For the sake of simplicity let's only check the main scripts.
|
||||
var imes = [
|
||||
'js/imes/handwriting/handwriting.js',
|
||||
'js/imes/india/india.js',
|
||||
'js/imes/jshangul/jshangul.js',
|
||||
'js/imes/jskanji/jskanji.js',
|
||||
'js/imes/jspinyin/jspinyin.js',
|
||||
'js/imes/latin/latin.js',
|
||||
'js/imes/vietnamese/vietnamese.js',
|
||||
'js/imes/myanmar/myanmar.js'
|
||||
];
|
||||
|
||||
var dicts = [
|
||||
'js/imes/latin/dictionaries/bg.dict',
|
||||
'js/imes/latin/dictionaries/bs.dict',
|
||||
'js/imes/latin/dictionaries/ca.dict',
|
||||
'js/imes/latin/dictionaries/cs.dict',
|
||||
'js/imes/latin/dictionaries/da.dict',
|
||||
'js/imes/latin/dictionaries/de.dict',
|
||||
'js/imes/latin/dictionaries/el.dict',
|
||||
'js/imes/latin/dictionaries/en_gb.dict',
|
||||
'js/imes/latin/dictionaries/en_us.dict',
|
||||
'js/imes/latin/dictionaries/es.dict',
|
||||
'js/imes/latin/dictionaries/eu.dict',
|
||||
'js/imes/latin/dictionaries/fr.dict',
|
||||
'js/imes/latin/dictionaries/ga.dict',
|
||||
'js/imes/latin/dictionaries/gd.dict',
|
||||
'js/imes/latin/dictionaries/gl.dict',
|
||||
'js/imes/latin/dictionaries/gv.dict',
|
||||
'js/imes/latin/dictionaries/hr.dict',
|
||||
'js/imes/latin/dictionaries/hu.dict',
|
||||
'js/imes/latin/dictionaries/it.dict',
|
||||
'js/imes/latin/dictionaries/lt.dict',
|
||||
'js/imes/latin/dictionaries/lv.dict',
|
||||
'js/imes/latin/dictionaries/nb.dict',
|
||||
'js/imes/latin/dictionaries/nl.dict',
|
||||
'js/imes/latin/dictionaries/pl.dict',
|
||||
'js/imes/latin/dictionaries/pt_br.dict',
|
||||
'js/imes/latin/dictionaries/pt_pt.dict',
|
||||
'js/imes/latin/dictionaries/ro.dict',
|
||||
'js/imes/latin/dictionaries/ru.dict',
|
||||
'js/imes/latin/dictionaries/sk.dict',
|
||||
'js/imes/latin/dictionaries/sq.dict',
|
||||
'js/imes/latin/dictionaries/sr-Cyrl.dict',
|
||||
'js/imes/latin/dictionaries/sr-Latn.dict',
|
||||
'js/imes/latin/dictionaries/sv.dict',
|
||||
'js/imes/latin/dictionaries/tr.dict',
|
||||
'js/imes/latin/dictionaries/af.dict'
|
||||
];
|
||||
|
||||
var checkList = [].concat(layouts, imes, dicts);
|
||||
checkList.forEach(function(path) {
|
||||
helper.checkFileInZip(zipPath, path, appDirPath + '/' + path);
|
||||
});
|
||||
|
||||
// Verify inputs entry in manifest
|
||||
var zip = new AdmZip(zipPath);
|
||||
var entry = zip.getEntry('manifest.webapp');
|
||||
var manifest = JSON.parse(zip.readAsText(entry));
|
||||
var inputKeysInManifest = Object.keys(manifest.inputs);
|
||||
|
||||
assert.deepEqual(inputKeysInManifest.sort(),
|
||||
[].concat(layoutIds, 'number', 'emoji').sort());
|
||||
|
||||
// Verify dictionary config
|
||||
var dictJSON = JSON.parse(fs.readFileSync(
|
||||
appDirPath +
|
||||
'/test/build/integration/resources/' +
|
||||
'all-layout-make-layouts.json'));
|
||||
|
||||
checkLayoutsJSON(zipPath, dictJSON);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// Build with all layouts with no dictionaries
|
||||
// (preload ko layout here to avoid empty check.)
|
||||
test('APP=keyboard GAIA_KEYBOARD_LAYOUTS=ko ' +
|
||||
'GAIA_KEYBOARD_DOWNLOADABLE_LAYOUTS=noPreloadDictRequired make',
|
||||
function(done) {
|
||||
var cmd = 'APP=keyboard GAIA_KEYBOARD_LAYOUTS=ko ' +
|
||||
'GAIA_KEYBOARD_DOWNLOADABLE_LAYOUTS=noPreloadDictRequired make';
|
||||
helper.exec(cmd, function(error, stdout, stderr) {
|
||||
helper.checkError(error, stdout, stderr);
|
||||
|
||||
var config = JSON.parse(process.env.BUILD_CONFIG);
|
||||
var zipPath = path.join(process.cwd(), 'profile',
|
||||
'webapps', 'keyboard.gaiamobile.org', 'application.zip');
|
||||
var appDirPath = config.GAIA_DIR + '/apps/keyboard';
|
||||
|
||||
// For this test, we verify there isn't any dictionary
|
||||
// in the zip.
|
||||
var zip = new AdmZip(zipPath);
|
||||
var entries = zip.getEntries();
|
||||
var imePath = 'js/imes';
|
||||
var sizeLimit = (1 << 10) * 100; // 100K
|
||||
entries.forEach(function(entry) {
|
||||
if (entry.entryName.substr(0, imePath.length) !== imePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
var fileSize = entry.getData().length;
|
||||
assert.isTrue(fileSize < sizeLimit,
|
||||
'IME file is larger than non-dictionary limit: ' + entry.entryName +
|
||||
', size: ' + fileSize + ' bytes.');
|
||||
});
|
||||
|
||||
// Verify dictionary config
|
||||
var dictJSON = JSON.parse(fs.readFileSync(
|
||||
appDirPath +
|
||||
'/test/build/integration/resources/' +
|
||||
'no-preload-dict-required-make-layouts.json'));
|
||||
|
||||
checkLayoutsJSON(zipPath, dictJSON);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// Build default layouts with only en dictionary, and extra IMEs
|
||||
test('APP=keyboard GAIA_KEYBOARD_LAYOUTS=en ' +
|
||||
'GAIA_KEYBOARD_DOWNLOADABLE_LAYOUTS=' +
|
||||
'en,pt-BR,es,de,fr,fr-CA,pl,ko,zh-Hans-Pinyin,en-Dvorak make',
|
||||
function(done) {
|
||||
var cmd = 'APP=keyboard GAIA_KEYBOARD_LAYOUTS=en ' +
|
||||
'GAIA_KEYBOARD_DOWNLOADABLE_LAYOUTS=' +
|
||||
'en,pt-BR,es,de,fr,fr-CA,pl,ko,zh-Hans-Pinyin,en-Dvorak make';
|
||||
helper.exec(cmd, function(error, stdout, stderr) {
|
||||
helper.checkError(error, stdout, stderr);
|
||||
|
||||
var config = JSON.parse(process.env.BUILD_CONFIG);
|
||||
var zipPath = path.join(process.cwd(), 'profile',
|
||||
'webapps', 'keyboard.gaiamobile.org', 'application.zip');
|
||||
var appDirPath = config.GAIA_DIR + '/apps/keyboard';
|
||||
var layoutIds = config.GAIA_KEYBOARD_LAYOUTS.split(',').sort()
|
||||
.filter(function(layoutId) {
|
||||
// Exclude noIncludeInExpandLayoutIdSet layouts
|
||||
return (['emoji'].indexOf(layoutId) === -1);
|
||||
});
|
||||
|
||||
var layouts = layoutIds.map(function(layout) {
|
||||
return 'js/layouts/' + layout + '.js';
|
||||
});
|
||||
|
||||
// Unfortunately we can only whilelist files here because we shouldn't
|
||||
// throw error if excluded files are missing (e.g. README).
|
||||
var imes = [
|
||||
'js/imes/jshangul/jshangul.js',
|
||||
'js/imes/jspinyin/empinyin_files.data',
|
||||
'js/imes/jspinyin/empinyin_files.js',
|
||||
'js/imes/jspinyin/jspinyin.js',
|
||||
'js/imes/jspinyin/libpinyin.js',
|
||||
'js/imes/jspinyin/worker.js',
|
||||
'js/imes/latin/latin.js',
|
||||
'js/imes/latin/predictions.js',
|
||||
'js/imes/latin/worker.js'
|
||||
];
|
||||
|
||||
var dicts = [
|
||||
'js/imes/latin/dictionaries/es.dict',
|
||||
'js/imes/latin/dictionaries/fr.dict',
|
||||
'js/imes/latin/dictionaries/pl.dict',
|
||||
'js/imes/latin/dictionaries/pt_br.dict'
|
||||
];
|
||||
|
||||
var checkList = [].concat(layouts, imes);
|
||||
checkList.forEach(function(path) {
|
||||
helper.checkFileInZip(zipPath, path, appDirPath + '/' + path);
|
||||
});
|
||||
|
||||
// Verify inputs entry in manifest
|
||||
var zip = new AdmZip(zipPath);
|
||||
var entry = zip.getEntry('manifest.webapp');
|
||||
var manifest = JSON.parse(zip.readAsText(entry));
|
||||
var inputKeysInManifest = Object.keys(manifest.inputs);
|
||||
|
||||
// Only layouts with dictionaries should be declaried.
|
||||
// Noted that en-Dvorak use the same dictionary as en so it should also
|
||||
// be declaried.
|
||||
assert.deepEqual(inputKeysInManifest.sort(),
|
||||
['en', 'en-Dvorak', 'ko', 'number', 'zh-Hans-Pinyin']);
|
||||
|
||||
// Verify dictionaries are not built (except en_us.dict)
|
||||
dicts.forEach(function(dict) {
|
||||
var entry = zip.getEntry(dict);
|
||||
assert.equal(entry, null, 'Dictionary should not be built: ' + dict);
|
||||
});
|
||||
|
||||
// Verify dictionary config
|
||||
var dictJSON = JSON.parse(fs.readFileSync(
|
||||
appDirPath +
|
||||
'/test/build/integration/resources/' +
|
||||
'default-make-en-dict-layouts.json'));
|
||||
|
||||
checkLayoutsJSON(zipPath, dictJSON);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('Keyboard settings building tests', function() {
|
||||
suiteSetup(helper.cleanupWorkspace);
|
||||
teardown(helper.cleanupWorkspace);
|
||||
|
||||
// parse settings.html and return domDoc.
|
||||
var getSettingsDomDoc = function() {
|
||||
var zipPath = path.join(process.cwd(), 'profile',
|
||||
'webapps', 'keyboard.gaiamobile.org', 'application.zip');
|
||||
|
||||
// Verify settings.html content in manifest
|
||||
var zip = new AdmZip(zipPath);
|
||||
var entry = zip.getEntry('settings.html');
|
||||
return jsdom(zip.readAsText(entry));
|
||||
};
|
||||
|
||||
// return an array of <scripts> tag in <head>
|
||||
var getScriptsFromDomDoc = function(domDoc) {
|
||||
// We don't have Array.from in our node version, so use an old way to
|
||||
// convert HTMLCollections to array
|
||||
return Array.prototype.slice.call(
|
||||
domDoc.head.getElementsByTagName('script'));
|
||||
};
|
||||
|
||||
suite('For handwriting', function() {
|
||||
// return an array of <sections> in the general panel
|
||||
var getSectionsFromGeneralPanel = function(domDoc) {
|
||||
return Array.prototype.slice.call(
|
||||
domDoc.querySelectorAll('#general-container > section'));
|
||||
};
|
||||
|
||||
// default: there shouldn't be handwriting elements in resulting file
|
||||
test('APP=keyboard make', function(done) {
|
||||
var cmd = 'APP=keyboard make';
|
||||
helper.exec(cmd, function(error, stdout, stderr) {
|
||||
helper.checkError(error, stdout, stderr);
|
||||
|
||||
var settingsDOMDoc = getSettingsDomDoc();
|
||||
|
||||
assert.isTrue(getScriptsFromDomDoc(settingsDOMDoc).every(function(elem){
|
||||
return elem.src !== 'js/settings/handwriting_settings_view.js';
|
||||
}), 'No script should include handwriting_settings_view.js');
|
||||
|
||||
assert.isTrue(
|
||||
getSectionsFromGeneralPanel(settingsDOMDoc).every(function(elem){
|
||||
return elem.id !== 'handwriting-settings';
|
||||
}), 'No section in general panel should include handwriting settings');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('GAIA_KEYBOARD_LAYOUTS=zh-Hans-Handwriting APP=keyboard make',
|
||||
function(done) {
|
||||
var cmd = 'GAIA_KEYBOARD_LAYOUTS=zh-Hans-Handwriting APP=keyboard make';
|
||||
helper.exec(cmd, function(error, stdout, stderr) {
|
||||
helper.checkError(error, stdout, stderr);
|
||||
|
||||
var settingsDOMDoc = getSettingsDomDoc();
|
||||
|
||||
assert.isTrue(getScriptsFromDomDoc(settingsDOMDoc).some(function(elem){
|
||||
return elem.src === 'js/settings/handwriting_settings_view.js';
|
||||
}), 'Some script should include handwriting_settings_view.js');
|
||||
|
||||
assert.isTrue(
|
||||
getSectionsFromGeneralPanel(settingsDOMDoc).some(function(elem){
|
||||
return elem.id === 'handwriting-settings';
|
||||
}),
|
||||
'Some section in general panel should include handwriting settings');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
1060
gaia/apps/keyboard/test/build/integration/resources/all-layout-make-layouts.json
Executable file
1060
gaia/apps/keyboard/test/build/integration/resources/all-layout-make-layouts.json
Executable file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,146 @@
|
|||
[
|
||||
{
|
||||
"id": "de",
|
||||
"name": "Deutsch",
|
||||
"imEngineId": "latin",
|
||||
"types": [
|
||||
"email",
|
||||
"password",
|
||||
"text",
|
||||
"url"
|
||||
],
|
||||
"dictFileSize": 2344627,
|
||||
"preloaded": false,
|
||||
"dictFilename": "de.dict",
|
||||
"dictFilePath": "dictionaries/de.dict"
|
||||
},
|
||||
{
|
||||
"id": "en",
|
||||
"name": "English",
|
||||
"imEngineId": "latin",
|
||||
"types": [
|
||||
"email",
|
||||
"password",
|
||||
"text",
|
||||
"url"
|
||||
],
|
||||
"dictFileSize": 1451390,
|
||||
"preloaded": true,
|
||||
"dictFilename": "en_us.dict",
|
||||
"dictFilePath": "dictionaries/en_us.dict"
|
||||
},
|
||||
{
|
||||
"id": "en-Dvorak",
|
||||
"name": "Dvorak",
|
||||
"imEngineId": "latin",
|
||||
"types": [
|
||||
"email",
|
||||
"password",
|
||||
"text",
|
||||
"url"
|
||||
],
|
||||
"dictFileSize": 1451390,
|
||||
"preloaded": true,
|
||||
"dictFilename": "en_us.dict",
|
||||
"dictFilePath": "dictionaries/en_us.dict"
|
||||
},
|
||||
{
|
||||
"id": "es",
|
||||
"name": "Español",
|
||||
"imEngineId": "latin",
|
||||
"types": [
|
||||
"email",
|
||||
"password",
|
||||
"text",
|
||||
"url"
|
||||
],
|
||||
"dictFileSize": 1564842,
|
||||
"preloaded": false,
|
||||
"dictFilename": "es.dict",
|
||||
"dictFilePath": "dictionaries/es.dict"
|
||||
},
|
||||
{
|
||||
"id": "fr",
|
||||
"name": "Français",
|
||||
"imEngineId": "latin",
|
||||
"types": [
|
||||
"email",
|
||||
"password",
|
||||
"text",
|
||||
"url"
|
||||
],
|
||||
"dictFileSize": 1874745,
|
||||
"preloaded": false,
|
||||
"dictFilename": "fr.dict",
|
||||
"dictFilePath": "dictionaries/fr.dict"
|
||||
},
|
||||
{
|
||||
"id": "fr-CA",
|
||||
"name": "Français (Canadien)",
|
||||
"imEngineId": "latin",
|
||||
"types": [
|
||||
"email",
|
||||
"password",
|
||||
"text",
|
||||
"url"
|
||||
],
|
||||
"dictFileSize": 1874745,
|
||||
"preloaded": false,
|
||||
"dictFilename": "fr.dict",
|
||||
"dictFilePath": "dictionaries/fr.dict"
|
||||
},
|
||||
{
|
||||
"id": "ko",
|
||||
"name": "한국어",
|
||||
"imEngineId": "jshangul",
|
||||
"types": [
|
||||
"email",
|
||||
"text",
|
||||
"url"
|
||||
],
|
||||
"dictFileSize": 9562,
|
||||
"preloaded": true
|
||||
},
|
||||
{
|
||||
"id": "pl",
|
||||
"name": "Polski",
|
||||
"imEngineId": "latin",
|
||||
"types": [
|
||||
"email",
|
||||
"password",
|
||||
"text",
|
||||
"url"
|
||||
],
|
||||
"dictFileSize": 1644422,
|
||||
"preloaded": false,
|
||||
"dictFilename": "pl.dict",
|
||||
"dictFilePath": "dictionaries/pl.dict"
|
||||
},
|
||||
{
|
||||
"id": "pt-BR",
|
||||
"name": "Português (do Brasil)",
|
||||
"imEngineId": "latin",
|
||||
"types": [
|
||||
"email",
|
||||
"password",
|
||||
"text",
|
||||
"url"
|
||||
],
|
||||
"dictFileSize": 1524964,
|
||||
"preloaded": false,
|
||||
"dictFilename": "pt_br.dict",
|
||||
"dictFilePath": "dictionaries/pt_br.dict"
|
||||
},
|
||||
{
|
||||
"id": "zh-Hans-Pinyin",
|
||||
"name": "拼音",
|
||||
"imEngineId": "jspinyin",
|
||||
"types": [
|
||||
"email",
|
||||
"text",
|
||||
"url"
|
||||
],
|
||||
"dictFileSize": 2139624,
|
||||
"preloaded": true
|
||||
}
|
||||
]
|
||||
1026
gaia/apps/keyboard/test/build/integration/resources/default-make-layouts.json
Executable file
1026
gaia/apps/keyboard/test/build/integration/resources/default-make-layouts.json
Executable file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
9
gaia/apps/keyboard/test/marionette/.jshintrc
Executable file
9
gaia/apps/keyboard/test/marionette/.jshintrc
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "../../../../.jshintrc",
|
||||
"node": true,
|
||||
"predef": [
|
||||
"test",
|
||||
"setup",
|
||||
"marionette"
|
||||
]
|
||||
}
|
||||
16
gaia/apps/keyboard/test/marionette/apps/imetestapp/index.html
Executable file
16
gaia/apps/keyboard/test/marionette/apps/imetestapp/index.html
Executable 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>
|
||||
73
gaia/apps/keyboard/test/marionette/apps/imetestapp/js/main.js
Executable file
73
gaia/apps/keyboard/test/marionette/apps/imetestapp/js/main.js
Executable 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);
|
||||
19
gaia/apps/keyboard/test/marionette/apps/imetestapp/js/settings.js
Executable file
19
gaia/apps/keyboard/test/marionette/apps/imetestapp/js/settings.js
Executable 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() {};
|
||||
});
|
||||
25
gaia/apps/keyboard/test/marionette/apps/imetestapp/manifest.webapp
Executable file
25
gaia/apps/keyboard/test/marionette/apps/imetestapp/manifest.webapp
Executable 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"
|
||||
}
|
||||
19
gaia/apps/keyboard/test/marionette/apps/imetestapp/settings.html
Executable file
19
gaia/apps/keyboard/test/marionette/apps/imetestapp/settings.html
Executable 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">< Back</button>
|
||||
</body>
|
||||
</html>
|
||||
25
gaia/apps/keyboard/test/marionette/apps/imetestapp/style/keyboard.css
Executable file
25
gaia/apps/keyboard/test/marionette/apps/imetestapp/style/keyboard.css
Executable 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;
|
||||
}
|
||||
48
gaia/apps/keyboard/test/marionette/apps/keyboardtestapp/index.html
Executable file
48
gaia/apps/keyboard/test/marionette/apps/keyboardtestapp/index.html
Executable 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>
|
||||
9
gaia/apps/keyboard/test/marionette/apps/keyboardtestapp/manifest.webapp
Executable file
9
gaia/apps/keyboard/test/marionette/apps/keyboardtestapp/manifest.webapp
Executable 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"
|
||||
}
|
||||
}
|
||||
9
gaia/apps/keyboard/test/marionette/apps/keyboardtestapp/script.js
Executable file
9
gaia/apps/keyboard/test/marionette/apps/keyboardtestapp/script.js
Executable 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;
|
||||
});
|
||||
};
|
||||
187
gaia/apps/keyboard/test/marionette/auto_correction_test.js
Executable file
187
gaia/apps/keyboard/test/marionette/auto_correction_test.js
Executable 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
84
gaia/apps/keyboard/test/marionette/dismiss_test.js
Executable file
84
gaia/apps/keyboard/test/marionette/dismiss_test.js
Executable 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);
|
||||
});
|
||||
});
|
||||
81
gaia/apps/keyboard/test/marionette/email_keyboard_test.js
Executable file
81
gaia/apps/keyboard/test/marionette/email_keyboard_test.js
Executable 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');
|
||||
});
|
||||
});
|
||||
119
gaia/apps/keyboard/test/marionette/launch_test.js
Executable file
119
gaia/apps/keyboard/test/marionette/launch_test.js
Executable 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());
|
||||
});
|
||||
});
|
||||
10
gaia/apps/keyboard/test/marionette/lib/.jshintrc
Executable file
10
gaia/apps/keyboard/test/marionette/lib/.jshintrc
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"extends": "../../../../../.jshintrc",
|
||||
"node": true,
|
||||
"predef": [
|
||||
"test",
|
||||
"setup",
|
||||
"marionette",
|
||||
"client"
|
||||
]
|
||||
}
|
||||
34
gaia/apps/keyboard/test/marionette/lib/base.js
Executable file
34
gaia/apps/keyboard/test/marionette/lib/base.js
Executable 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;
|
||||
48
gaia/apps/keyboard/test/marionette/lib/ime_test_app.js
Executable file
48
gaia/apps/keyboard/test/marionette/lib/ime_test_app.js
Executable 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();
|
||||
});
|
||||
}
|
||||
};
|
||||
340
gaia/apps/keyboard/test/marionette/lib/keyboard.js
Executable file
340
gaia/apps/keyboard/test/marionette/lib/keyboard.js
Executable 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');
|
||||
});
|
||||
}
|
||||
};
|
||||
50
gaia/apps/keyboard/test/marionette/lib/keyboard_settings.js
Executable file
50
gaia/apps/keyboard/test/marionette/lib/keyboard_settings.js
Executable 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();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
97
gaia/apps/keyboard/test/marionette/lib/keyboard_test_app.js
Executable file
97
gaia/apps/keyboard/test/marionette/lib/keyboard_test_app.js
Executable 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();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
74
gaia/apps/keyboard/test/marionette/number_keyboard_test.js
Executable file
74
gaia/apps/keyboard/test/marionette/number_keyboard_test.js
Executable 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);
|
||||
});
|
||||
});
|
||||
134
gaia/apps/keyboard/test/marionette/switching_test.js
Executable file
134
gaia/apps/keyboard/test/marionette/switching_test.js
Executable 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();
|
||||
});
|
||||
});
|
||||
119
gaia/apps/keyboard/test/marionette/text_keyboard_test.js
Executable file
119
gaia/apps/keyboard/test/marionette/text_keyboard_test.js
Executable 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());
|
||||
});
|
||||
});
|
||||
100
gaia/apps/keyboard/test/marionette/textarea_keyboard_test.js
Executable file
100
gaia/apps/keyboard/test/marionette/textarea_keyboard_test.js
Executable 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'));
|
||||
});
|
||||
});
|
||||
127
gaia/apps/keyboard/test/marionette/uninstallation_test.js
Executable file
127
gaia/apps/keyboard/test/marionette/uninstallation_test.js
Executable 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());
|
||||
});
|
||||
});
|
||||
17
gaia/apps/keyboard/test/unit/.jshintrc
Executable file
17
gaia/apps/keyboard/test/unit/.jshintrc
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"extends": "../../../../.jshintrc",
|
||||
"predef": [
|
||||
"assert",
|
||||
"suite",
|
||||
"test",
|
||||
"setup",
|
||||
"teardown",
|
||||
"suiteSetup",
|
||||
"suiteTeardown",
|
||||
"require",
|
||||
"requireApp",
|
||||
"sinon",
|
||||
"mocha",
|
||||
"loadBodyHTML"
|
||||
]
|
||||
}
|
||||
13
gaia/apps/keyboard/test/unit/fake-imes/foo/foo.js
Executable file
13
gaia/apps/keyboard/test/unit/fake-imes/foo/foo.js
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
'use strict';
|
||||
|
||||
/* global InputMethods */
|
||||
|
||||
InputMethods.foo = {
|
||||
init: function(glue) {
|
||||
this._glue = glue;
|
||||
},
|
||||
activate: sinon.stub(),
|
||||
deactivate: sinon.stub(),
|
||||
click: sinon.stub(),
|
||||
stateChange: sinon.stub()
|
||||
};
|
||||
11
gaia/apps/keyboard/test/unit/fake-layouts/foo.js
Executable file
11
gaia/apps/keyboard/test/unit/fake-layouts/foo.js
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
'use strict';
|
||||
|
||||
/* global Keyboards */
|
||||
|
||||
Keyboards.foo = {
|
||||
keys: [
|
||||
[
|
||||
{ value: 'foo' }
|
||||
]
|
||||
]
|
||||
};
|
||||
21
gaia/apps/keyboard/test/unit/fake-layouts/foo2.js
Executable file
21
gaia/apps/keyboard/test/unit/fake-layouts/foo2.js
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
'use strict';
|
||||
|
||||
/* global Keyboards */
|
||||
|
||||
/* This file exposes two layouts */
|
||||
|
||||
Keyboards.foo2 = {
|
||||
keys: [
|
||||
[
|
||||
{ value: 'foo2' }
|
||||
]
|
||||
]
|
||||
};
|
||||
|
||||
Keyboards.foo3 = {
|
||||
keys: [
|
||||
[
|
||||
{ value: 'foo3' }
|
||||
]
|
||||
]
|
||||
};
|
||||
490
gaia/apps/keyboard/test/unit/imes/jspinyin/jspinyin_test.js
Executable file
490
gaia/apps/keyboard/test/unit/imes/jspinyin/jspinyin_test.js
Executable file
|
|
@ -0,0 +1,490 @@
|
|||
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
/* globals InputMethods, KeyEvent */
|
||||
'use strict';
|
||||
|
||||
requireApp('keyboard/test/unit/setup_engine.js');
|
||||
requireApp('keyboard/js/imes/jspinyin/jspinyin.js');
|
||||
|
||||
suite('jspinyin', function() {
|
||||
|
||||
var NUMBER_OF_CANDIDATES_PER_ROW = 8;
|
||||
|
||||
var jspinyin;
|
||||
var firstCandidate;
|
||||
var firstPredict;
|
||||
var dummyFunction = function() {};
|
||||
var glue = {
|
||||
path: '/js/imes/jspinyin',
|
||||
sendCandidates: dummyFunction,
|
||||
setComposition: dummyFunction,
|
||||
endComposition: dummyFunction,
|
||||
sendKey: dummyFunction,
|
||||
sendString: dummyFunction,
|
||||
setLayoutPage: dummyFunction,
|
||||
setUpperCase: dummyFunction,
|
||||
resetUpperCase: dummyFunction,
|
||||
replaceSurroundingText: dummyFunction,
|
||||
getNumberOfCandidatesPerRow: function() {
|
||||
return NUMBER_OF_CANDIDATES_PER_ROW;
|
||||
}
|
||||
};
|
||||
var fakeUserDict = [240, 222, 188, 10, 0, 2, 239, 0, 176, 1, 11, 140, 122,
|
||||
102, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 252, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
|
||||
10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0,
|
||||
0];
|
||||
|
||||
var fakeUserDictNew = [240, 222, 188, 10, 0, 2, 239, 0, 176, 1, 11, 140, 122,
|
||||
102, 0, 2, 239, 0, 176, 1, 208, 103, 234, 83, 10];
|
||||
|
||||
function mockIndexedDB(args) {
|
||||
var receivedData;
|
||||
var request = {};
|
||||
var transRequest = {};
|
||||
var fakeDB = {
|
||||
objectStoreNames: [1, 2],
|
||||
createObjectStore: dummyFunction,
|
||||
deleteObjectStore: dummyFunction,
|
||||
transaction: sinon.stub(),
|
||||
objectStore: sinon.stub(),
|
||||
get: sinon.stub(),
|
||||
put: dummyFunction,
|
||||
close: dummyFunction
|
||||
};
|
||||
var evt = {
|
||||
target: {
|
||||
result: fakeDB
|
||||
}
|
||||
};
|
||||
var evtError = {
|
||||
target: {
|
||||
errorCode: 123,
|
||||
result: {
|
||||
name: 'test'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sinon.stub(window.indexedDB, 'open');
|
||||
window.indexedDB.open.returns(request);
|
||||
|
||||
fakeDB.transaction.returns(fakeDB);
|
||||
fakeDB.objectStore.returns(fakeDB);
|
||||
fakeDB.get.returns(transRequest);
|
||||
|
||||
sinon.stub(fakeDB, 'put', function(data) {
|
||||
receivedData = data;
|
||||
return transRequest;
|
||||
});
|
||||
|
||||
if (args.testing) {
|
||||
args.testing();
|
||||
}
|
||||
|
||||
function indexedDBActions() {
|
||||
if (!args.ignoreUndefinedHandler) {
|
||||
assert.isFunction(request.onupgradeneeded);
|
||||
assert.isFunction(request.onsuccess);
|
||||
}
|
||||
|
||||
if (args.openDBFailed) {
|
||||
if (request.onerror) {
|
||||
request.onerror(evtError);
|
||||
}
|
||||
if (args.callback) {
|
||||
args.callback(receivedData);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.onupgradeneeded) {
|
||||
request.onupgradeneeded(evt);
|
||||
}
|
||||
|
||||
if (request.onsuccess) {
|
||||
request.onsuccess(evt);
|
||||
}
|
||||
|
||||
if (!args.ignoreUndefinedHandler) {
|
||||
assert.isFunction(transRequest.onsuccess);
|
||||
}
|
||||
|
||||
if (args.transactionFailed) {
|
||||
if (transRequest.onerror) {
|
||||
transRequest.onerror(evtError);
|
||||
}
|
||||
if (args.callback) {
|
||||
args.callback(receivedData);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (transRequest.onsuccess) {
|
||||
transRequest.onsuccess({
|
||||
target: {
|
||||
result: {
|
||||
content: fakeUserDict
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (args.callback) {
|
||||
args.callback(receivedData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (args.waitForHandler) {
|
||||
var indexedDBActionsTimer = setInterval(function() {
|
||||
if (request.onsuccess) {
|
||||
indexedDBActions();
|
||||
clearInterval(indexedDBActionsTimer);
|
||||
}
|
||||
}, 100);
|
||||
} else {
|
||||
indexedDBActions();
|
||||
}
|
||||
}
|
||||
|
||||
suiteTeardown(function() {
|
||||
jspinyin.uninit();
|
||||
assert.isNull(jspinyin._uninitTimer);
|
||||
|
||||
delete InputMethods.jspinyin;
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
if (window.indexedDB.open.restore) {
|
||||
window.indexedDB.open.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('load', function() {
|
||||
assert.isDefined(InputMethods.jspinyin);
|
||||
jspinyin = InputMethods.jspinyin;
|
||||
});
|
||||
|
||||
test('init', function() {
|
||||
jspinyin.init(glue);
|
||||
assert.equal(jspinyin._glue.path, glue.path);
|
||||
});
|
||||
|
||||
test('do something before activate', function(done) {
|
||||
jspinyin.click(KeyEvent.DOM_VK_RETURN);
|
||||
jspinyin.select('', 0);
|
||||
jspinyin.getMoreCandidates(
|
||||
0,
|
||||
NUMBER_OF_CANDIDATES_PER_ROW,
|
||||
function(list) {
|
||||
done(function() {
|
||||
assert.isNull(list);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('activate', function(done) {
|
||||
this.sinon.stub(jspinyin, '_start', function() {
|
||||
done(function() {
|
||||
assert.equal(jspinyin._keypressQueue[0], KeyEvent.DOM_VK_RETURN);
|
||||
assert.isNull(jspinyin._uninitTimer);
|
||||
jspinyin._resetKeypressQueue();
|
||||
});
|
||||
});
|
||||
|
||||
mockIndexedDB({
|
||||
testing: function() {
|
||||
jspinyin._uninitTimer = setTimeout(function() {
|
||||
throw 'Should not call this function.';
|
||||
}, 0);
|
||||
jspinyin.activate('zh-Hans', { type: 'text' }, {});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('activate twice', function() {
|
||||
this.sinon.stub(jspinyin, '_start');
|
||||
|
||||
mockIndexedDB({
|
||||
ignoreUndefinedHandler: true,
|
||||
testing: function() {
|
||||
jspinyin._uninitTimer = setTimeout(function() {
|
||||
throw 'Should not call this function.';
|
||||
}, 0);
|
||||
|
||||
jspinyin.activate('zh-Hans', { type: 'textarea' }, {});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('click \'z\'', function(done) {
|
||||
var len;
|
||||
|
||||
this.sinon.stub(glue, 'sendCandidates', function(list) {
|
||||
firstCandidate = list[0];
|
||||
len = list.length;
|
||||
});
|
||||
|
||||
this.sinon.stub(glue, 'setComposition', function(symbols, cursor) {
|
||||
if (glue.sendCandidates.called) {
|
||||
done(function() {
|
||||
assert.equal(symbols, 'z');
|
||||
assert.deepEqual(firstCandidate, ['在', 0]);
|
||||
assert.equal(len, NUMBER_OF_CANDIDATES_PER_ROW + 1);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
jspinyin.click('z'.charCodeAt(0));
|
||||
});
|
||||
|
||||
test('get more candidates', function(done) {
|
||||
jspinyin.getMoreCandidates(
|
||||
NUMBER_OF_CANDIDATES_PER_ROW,
|
||||
NUMBER_OF_CANDIDATES_PER_ROW * 12,
|
||||
function(list) {
|
||||
done(function() {
|
||||
assert.deepEqual(list[0], ['真', 8]);
|
||||
assert.deepEqual(list[list.length - 1], ['忠', 103]);
|
||||
assert.equal(list.length, NUMBER_OF_CANDIDATES_PER_ROW * 12);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('select first candidate and get predicts', function(done) {
|
||||
var endCompositionString;
|
||||
|
||||
this.sinon.stub(glue, 'endComposition', function(text) {
|
||||
if (text) {
|
||||
endCompositionString = text;
|
||||
}
|
||||
});
|
||||
|
||||
this.sinon.stub(glue, 'sendCandidates', function(list) {
|
||||
firstPredict = list[0];
|
||||
done(function() {
|
||||
assert.equal(endCompositionString, firstCandidate[0]);
|
||||
assert.deepEqual(firstPredict, ['线', 0]);
|
||||
assert.equal(list.length, NUMBER_OF_CANDIDATES_PER_ROW + 1);
|
||||
});
|
||||
});
|
||||
|
||||
jspinyin.select(firstCandidate[0], firstCandidate[1]);
|
||||
});
|
||||
|
||||
test('get more predicts', function(done) {
|
||||
jspinyin.getMoreCandidates(
|
||||
NUMBER_OF_CANDIDATES_PER_ROW,
|
||||
NUMBER_OF_CANDIDATES_PER_ROW * 12,
|
||||
function(list) {
|
||||
done(function() {
|
||||
assert.deepEqual(list[0], ['乎', 8]);
|
||||
assert.deepEqual(list[list.length - 1], ['新窗口', 97]);
|
||||
assert.equal(list.length, 90);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('select first predict', function(done) {
|
||||
this.sinon.stub(jspinyin, '_start');
|
||||
this.sinon.stub(glue, 'endComposition', function(text) {
|
||||
done(function() {
|
||||
assert.equal(text, firstPredict[0]);
|
||||
});
|
||||
});
|
||||
|
||||
jspinyin.select(firstPredict[0], firstPredict[1]);
|
||||
});
|
||||
|
||||
test('empty', function(done) {
|
||||
this.sinon.spy(glue, 'endComposition');
|
||||
this.sinon.stub(glue, 'sendCandidates', function(list) {
|
||||
if (glue.endComposition.called) {
|
||||
done(function() {
|
||||
assert.isTrue(glue.endComposition.calledWith(undefined));
|
||||
assert.equal(list.length, 0);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
jspinyin.empty();
|
||||
});
|
||||
|
||||
test('input the phrase in userdict from indexedDB', function(done) {
|
||||
jspinyin.click('m'.charCodeAt(0));
|
||||
jspinyin.click('o'.charCodeAt(0));
|
||||
jspinyin.click('u'.charCodeAt(0));
|
||||
jspinyin.click('z'.charCodeAt(0));
|
||||
jspinyin.click('h'.charCodeAt(0));
|
||||
jspinyin.click('i'.charCodeAt(0));
|
||||
jspinyin.click(KeyEvent.DOM_VK_RETURN);
|
||||
|
||||
this.sinon.stub(glue, 'endComposition', function(text) {
|
||||
if (!text) {
|
||||
done(function() {
|
||||
assert.isTrue(glue.endComposition.calledWith('谋智'));
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('create new phrase into userdict', function(done) {
|
||||
this.sinon.stub(glue, 'endComposition', function(text) {
|
||||
if (!text) {
|
||||
done(function() {
|
||||
assert.isTrue(glue.endComposition.calledWith('某只'));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.sinon.stub(glue, 'setComposition', function(symbol, cursor) {
|
||||
if (symbol == 'mou zhi') {
|
||||
jspinyin.select('某', 1);
|
||||
} else if (symbol == '某zhi') {
|
||||
jspinyin.select('只', 1);
|
||||
}
|
||||
});
|
||||
|
||||
jspinyin.click('m'.charCodeAt(0));
|
||||
jspinyin.click('o'.charCodeAt(0));
|
||||
jspinyin.click('u'.charCodeAt(0));
|
||||
jspinyin.click('z'.charCodeAt(0));
|
||||
jspinyin.click('h'.charCodeAt(0));
|
||||
jspinyin.click('i'.charCodeAt(0));
|
||||
});
|
||||
|
||||
test('input the phrase in userdict that we just created', function(done) {
|
||||
var isOk = false;
|
||||
|
||||
jspinyin.click('m'.charCodeAt(0));
|
||||
jspinyin.click('o'.charCodeAt(0));
|
||||
jspinyin.click('u'.charCodeAt(0));
|
||||
jspinyin.click('z'.charCodeAt(0));
|
||||
jspinyin.click('h'.charCodeAt(0));
|
||||
jspinyin.click('i'.charCodeAt(0));
|
||||
jspinyin.click(KeyEvent.DOM_VK_RETURN);
|
||||
|
||||
this.sinon.stub(glue, 'sendCandidates', function(list) {
|
||||
if (list[1] && list[1][0] == '某只') {
|
||||
isOk = true;
|
||||
}
|
||||
});
|
||||
|
||||
this.sinon.stub(glue, 'endComposition', function(text) {
|
||||
if (!text) {
|
||||
done(function() {
|
||||
assert.isTrue(isOk);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('click backspace to clear pendingSymbols', function(done) {
|
||||
this.sinon.stub(glue, 'setComposition');
|
||||
this.sinon.stub(glue, 'endComposition');
|
||||
|
||||
this.sinon.stub(glue, 'sendKey', function(keyCode) {
|
||||
done(function() {
|
||||
assert.equal(keyCode, KeyEvent.DOM_VK_BACK_SPACE);
|
||||
assert.isTrue(glue.endComposition.called);
|
||||
});
|
||||
});
|
||||
|
||||
jspinyin.click('z'.charCodeAt(0));
|
||||
|
||||
// "A" means "'" for pinyin composition
|
||||
jspinyin.click('A'.charCodeAt(0));
|
||||
jspinyin.click(KeyEvent.DOM_VK_BACK_SPACE);
|
||||
jspinyin.click(KeyEvent.DOM_VK_BACK_SPACE);
|
||||
jspinyin.click(KeyEvent.DOM_VK_BACK_SPACE);
|
||||
});
|
||||
|
||||
test('click a symbol', function(done) {
|
||||
this.sinon.stub(glue, 'sendKey', function(keyCode) {
|
||||
done(function() {
|
||||
assert.equal(keyCode, '\''.charCodeAt(0));
|
||||
});
|
||||
});
|
||||
jspinyin.click('\''.charCodeAt(0));
|
||||
});
|
||||
|
||||
test('deactivate', function(done) {
|
||||
this.sinon.spy(jspinyin, 'empty');
|
||||
|
||||
mockIndexedDB({
|
||||
waitForHandler: true,
|
||||
callback: function(data) {
|
||||
done(function() {
|
||||
assert.isTrue(jspinyin.empty.calledOnce);
|
||||
assert.deepEqual(data.content.slice(0, fakeUserDictNew.length),
|
||||
fakeUserDictNew);
|
||||
});
|
||||
},
|
||||
testing: function() {
|
||||
jspinyin.deactivate();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('uninit', function() {
|
||||
this.sinon.spy(jspinyin, 'empty');
|
||||
jspinyin.uninit();
|
||||
assert.isNull(jspinyin._uninitTimer);
|
||||
assert.isTrue(jspinyin.empty.calledOnce);
|
||||
});
|
||||
|
||||
test('activate (open indexedDB failed)', function(done) {
|
||||
mockIndexedDB({
|
||||
openDBFailed: true,
|
||||
ignoreUndefinedHandler: true,
|
||||
callback: function() {
|
||||
done();
|
||||
},
|
||||
testing: function() {
|
||||
jspinyin.activate('zh-Hans', { type: 'text' }, {});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('activate (load indexedDB failed)', function(done) {
|
||||
mockIndexedDB({
|
||||
transactionFailed: true,
|
||||
ignoreUndefinedHandler: true,
|
||||
callback: function() {
|
||||
done();
|
||||
},
|
||||
testing: function() {
|
||||
jspinyin.activate('zh-Hans', { type: 'text' }, {});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Bug 1146260
|
||||
test.skip('deactivate (save indexedDB failed)', function(done) {
|
||||
this.sinon.stub(jspinyin, '_start', function() {
|
||||
window.indexedDB.open.restore();
|
||||
setTimeout(function() {
|
||||
mockIndexedDB({
|
||||
waitForHandler: true,
|
||||
transactionFailed: true,
|
||||
ignoreUndefinedHandler: true,
|
||||
callback: function() {
|
||||
done();
|
||||
},
|
||||
testing: function() {
|
||||
jspinyin.deactivate();
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
});
|
||||
|
||||
mockIndexedDB({
|
||||
testing: function() {
|
||||
jspinyin.activate('zh-Hans', { type: 'text' }, {});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
999
gaia/apps/keyboard/test/unit/imes/latin/latin_test.js
Executable file
999
gaia/apps/keyboard/test/unit/imes/latin/latin_test.js
Executable file
|
|
@ -0,0 +1,999 @@
|
|||
'use strict';
|
||||
|
||||
/* global KeyboardEvent, KeyEvent, PAGE_INDEX_DEFAULT, PromiseStorage */
|
||||
|
||||
require('/js/shared/promise_storage.js');
|
||||
|
||||
window.PAGE_INDEX_DEFAULT = 0;
|
||||
window.InputMethods = {};
|
||||
|
||||
suite('latin.js', function() {
|
||||
var engine;
|
||||
var glue;
|
||||
|
||||
// We pass the same workerStub because latin engine reuse the worker
|
||||
// between activations.
|
||||
var workerStub = {
|
||||
onmessage: null,
|
||||
postMessage: function() { /* to be stubbed between tests */}
|
||||
};
|
||||
|
||||
// Load the script once and attach it to |engine|.
|
||||
suiteSetup(function(done) {
|
||||
require('/js/imes/latin/latin.js', function() {
|
||||
engine = window.InputMethods.latin;
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
this.sinon.stub(workerStub, 'postMessage');
|
||||
|
||||
this.sinon.stub(window, 'Worker').returns(workerStub);
|
||||
|
||||
var stubPromiseStorage =
|
||||
this.sinon.stub(Object.create(PromiseStorage.prototype));
|
||||
this.sinon.stub(window, 'PromiseStorage')
|
||||
.returns(stubPromiseStorage);
|
||||
|
||||
// object is what we'd used to resolve into (see getData below)
|
||||
stubPromiseStorage.getItem.returns(Promise.resolve({user: 'user'}));
|
||||
|
||||
glue = {
|
||||
mOutput: '',
|
||||
mIsUpperCase: false,
|
||||
mResolveSendKey: true,
|
||||
|
||||
sendKey: this.sinon.spy(function(keycode) {
|
||||
if (!this.mResolveSendKey) {
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
if (keycode === 8) { // backspace
|
||||
this.mOutput =
|
||||
this.mOutput.substring(0, this.mOutput.length - 1);
|
||||
}
|
||||
else {
|
||||
this.mOutput += String.fromCharCode(keycode);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}),
|
||||
|
||||
sendCandidates: this.sinon.stub(),
|
||||
|
||||
setUpperCase: this.sinon.spy(function setUpperCase(state) {
|
||||
this.mIsUpperCase = state.isUpperCase;
|
||||
}),
|
||||
|
||||
setLayoutPage: this.sinon.stub(),
|
||||
|
||||
isCapitalized: this.sinon.spy(function isCapitalized() {
|
||||
return this.mIsUpperCase;
|
||||
}),
|
||||
|
||||
replaceSurroundingText: this.sinon.stub().returns(Promise.resolve()),
|
||||
|
||||
getData: this.sinon.stub().returns(Promise.resolve({}))
|
||||
};
|
||||
|
||||
// We are not supposed to call init() more than once in the real setup,
|
||||
// but we need to call it here, as we need to have the engine use a
|
||||
// new glue object for each test.
|
||||
//
|
||||
// Moreover, for each of the tests, we will activate() the engine with
|
||||
// desired conditions and deactivate on teardown().
|
||||
engine.init(glue);
|
||||
});
|
||||
|
||||
suite('Capitalization and punctuation behavior', function() {
|
||||
// These tests verify that the latin input method is doing what it should.
|
||||
// They pass input to the IM by calling its click() method, and verify that
|
||||
// the input method sends back the expected output through the sendKey
|
||||
// function that we pass it. The latin input method sends suggestions
|
||||
// asynchronously, but other parts are synchronous
|
||||
|
||||
|
||||
// The capitalization and punctuation behavior of the Latin IM depends
|
||||
// on these variables:
|
||||
//
|
||||
// input field type
|
||||
// input mode
|
||||
// existing text in input field
|
||||
// cursor position
|
||||
// whether there is a selection
|
||||
|
||||
// Test each of these types
|
||||
var types = [
|
||||
'text',
|
||||
'textarea',
|
||||
'search',
|
||||
'url',
|
||||
'email'
|
||||
];
|
||||
|
||||
// Test these input modes
|
||||
var modes = [
|
||||
'',
|
||||
'verbatim',
|
||||
'latin',
|
||||
'latin-prose'
|
||||
];
|
||||
|
||||
var contentStates = {
|
||||
// empty input field
|
||||
empty: { text: '', cursor: 0 },
|
||||
// cursor is in the middle of a bunch of spaces
|
||||
inSpace: { text: 'a b', cursor: 4},
|
||||
|
||||
// cursor is at the start, middle, or end of the input field
|
||||
start: { text: 'word', cursor: 0 },
|
||||
middle: { text: 'word', cursor: 2 },
|
||||
end: { text: 'word', cursor: 4 },
|
||||
|
||||
// like the above, but all uppercase
|
||||
startUppercase: { text: 'WORD', cursor: 0 },
|
||||
middleUppercase: { text: 'WORD', cursor: 2 },
|
||||
endUppercase: { text: 'WORD', cursor: 4 },
|
||||
|
||||
// cursor is at the start, middle, or end of a word in the middle
|
||||
wordStart: { text: 'and then what', cursor: 4 },
|
||||
wordMiddle: { text: 'and then what', cursor: 6 },
|
||||
wordEnd: { text: 'and then what', cursor: 8 },
|
||||
|
||||
// cursor is after a sentence, before another
|
||||
afterSentence: { text: 'Foo. Bar.', cursor: 5 },
|
||||
afterQuestion: { text: 'Foo? Bar.', cursor: 5 },
|
||||
afterExclamation: { text: 'Foo! Bar.', cursor: 5 }
|
||||
};
|
||||
|
||||
// Utility function
|
||||
var capitalizeWord = function capitalizeWord(word) {
|
||||
if (word.length === 0) {
|
||||
return word;
|
||||
}
|
||||
return word[0].toUpperCase() + word.substring(1);
|
||||
};
|
||||
|
||||
// Test all the permutations of states above against these inputs.
|
||||
// The property name is the input. The property value is a function
|
||||
// that returns the expected output
|
||||
|
||||
// Does space punc get transposed to punc space?
|
||||
var expectedPunctuation =
|
||||
function expectedPunctuation(input, type, mode, value, cursor) {
|
||||
// Don't run all permutations of this test for all inputs.
|
||||
if (input[1] !== '.' && (type !== 'textarea' || mode !== 'latin-prose')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if the type is wrong, do nothing
|
||||
if (type !== 'text' && type !== 'textarea' && type !== 'search') {
|
||||
return input;
|
||||
}
|
||||
// if the mode is wrong do nothing
|
||||
if (mode === 'verbatim' || mode === 'latin') {
|
||||
return input;
|
||||
}
|
||||
// If mode is not specified, and we're not a text area, that is the
|
||||
// same as latin mode, so do nothing
|
||||
if (!mode && type !== 'textarea') {
|
||||
return input;
|
||||
}
|
||||
// If input is a space followed by a colon or semicolon, do not transpose.
|
||||
// This facilitates the entry of emoticons such as :O
|
||||
if (input === ' :' || input === ' ;') {
|
||||
return input;
|
||||
}
|
||||
|
||||
// If the previous character is a letter, transpose otherwise don't
|
||||
if (cursor > 0 && /[a-zA-Z]/.test(value.charAt(cursor - 1))) {
|
||||
return input[1] + input[0];
|
||||
}
|
||||
|
||||
return input;
|
||||
};
|
||||
|
||||
var expectedSpaceSpace =
|
||||
function expectedSpaceSpace(input, type, mode, value, cursor) {
|
||||
// if the type is wrong, do nothing
|
||||
if (type !== 'text' && type !== 'textarea' && type !== 'search') {
|
||||
return input;
|
||||
}
|
||||
// if the mode is wrong do nothing
|
||||
if (mode === 'verbatim' || mode === 'latin') {
|
||||
return input;
|
||||
}
|
||||
// If mode is not specified, and we're not a text area, that is the
|
||||
// same as latin mode, so do nothing
|
||||
if (!mode && type !== 'textarea') {
|
||||
return input;
|
||||
}
|
||||
|
||||
// If the previous character is a letter, return dot space
|
||||
if (cursor > 0 && /[a-zA-Z]/.test(value[cursor - 1])) {
|
||||
return '. ';
|
||||
}
|
||||
|
||||
return ' ';
|
||||
};
|
||||
|
||||
var expectedCapitalization =
|
||||
function expectedCapitalization(input, type, mode, value, cursor) {
|
||||
// if the type is wrong, do nothing
|
||||
if (type !== 'text' && type !== 'textarea' && type !== 'search') {
|
||||
return input;
|
||||
}
|
||||
// if the mode is wrong do nothing
|
||||
if (mode === 'verbatim' || mode === 'latin') {
|
||||
return input;
|
||||
}
|
||||
// If mode is not specified, and we're not a text area, that is the
|
||||
// same as latin mode, so do nothing
|
||||
if (!mode && type !== 'textarea') {
|
||||
return input;
|
||||
}
|
||||
|
||||
// If we're still here, we're in latin-prose mode, and we may need
|
||||
// to capitalize, depending on the value and cursor position.
|
||||
if (cursor === 0) {
|
||||
return capitalizeWord(input);
|
||||
}
|
||||
|
||||
// if the character before the cursor is not a space, don't capitalize
|
||||
if (!/\s/.test(value[cursor - 1])) {
|
||||
return input;
|
||||
}
|
||||
|
||||
// If we're at then end of a sentence, capitalize
|
||||
if (/[.?!]\s+$/.test(value.substring(0, cursor))) {
|
||||
return capitalizeWord(input);
|
||||
}
|
||||
|
||||
// Otherwise, just return the input
|
||||
return input;
|
||||
};
|
||||
|
||||
var inputs = {
|
||||
'ab': expectedCapitalization, // Does it get capitalized?
|
||||
' ': expectedSpaceSpace, // Does it turn into ". "?
|
||||
' .': expectedPunctuation, // Does it get transposed?
|
||||
' !': expectedPunctuation, // Does it get transposed?
|
||||
' ?': expectedPunctuation, // Does it get transposed?
|
||||
' ,': expectedPunctuation, // Does it get transposed?
|
||||
' ;': expectedPunctuation, // Does it get transposed?
|
||||
' :': expectedPunctuation // Does it get transposed?
|
||||
};
|
||||
|
||||
var constructTest =
|
||||
function constructTest(input, type, mode, statename) {
|
||||
var modeTitle = '-' + (mode ? mode : 'default');
|
||||
var testname = type + modeTitle + '-' + statename +
|
||||
'-' + input;
|
||||
var state = contentStates[statename];
|
||||
var expected =
|
||||
inputs[input](input, type, mode, state.text, state.cursor);
|
||||
|
||||
// Skip the test if the expected function returns nothing.
|
||||
// This is so we don't have too large a number of tests.
|
||||
if (expected === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
test(testname, function(done) {
|
||||
engine.activate('en', {
|
||||
type: type,
|
||||
inputmode: mode,
|
||||
text: state.text,
|
||||
selectionStart: state.cursor,
|
||||
selectionEnd: state.cursor
|
||||
},{ suggest: false, correct: false });
|
||||
|
||||
// Wait for getData() to resolve.
|
||||
Promise.resolve().then(function() {
|
||||
var promise = Promise.resolve();
|
||||
input.split('').forEach(function(c) {
|
||||
promise = promise.then(function() {
|
||||
if (glue.mIsUpperCase) {
|
||||
c = c.toUpperCase();
|
||||
}
|
||||
return engine.click(c.charCodeAt(0));
|
||||
});
|
||||
});
|
||||
|
||||
return promise.then(function() {
|
||||
assert.equal(
|
||||
glue.mOutput, expected,
|
||||
'expected "' + expected + '" for input "' + input + '"');
|
||||
});
|
||||
}).then(done, done);
|
||||
});
|
||||
};
|
||||
|
||||
// For each test, we activate() the IM with a given initial state,
|
||||
// then send it some input, and check the output.
|
||||
// The initial state includes language, whether suggestions are enabled,
|
||||
// input type, input mode, input value, cursor position
|
||||
// (or selectionstart, selection end).
|
||||
// There are lots of possible initial states, and we may have different
|
||||
// output in each case.
|
||||
|
||||
teardown(function() {
|
||||
engine.deactivate();
|
||||
});
|
||||
|
||||
suite('Input keys one by one', function() {
|
||||
for (var t = 0; t < types.length; t++) {
|
||||
var type = types[t];
|
||||
for (var m = 0; m < modes.length; m++) {
|
||||
var mode = modes[m];
|
||||
for (var statename in contentStates) {
|
||||
for (var input in inputs) {
|
||||
constructTest(input, type, mode, statename);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
suite('Suggestions', function() {
|
||||
// For most tests here we ignore user dictionary predictions (i.e. testing
|
||||
// old behaviors). We explicitly test it as noted.
|
||||
var activateEngineWithState =
|
||||
function activateEngineWithState(value, cursorStart, cursorEnd) {
|
||||
engine.activate('en', {
|
||||
type: 'text',
|
||||
inputmode: 'latin-prose',
|
||||
text: value,
|
||||
selectionStart: cursorStart || value.length,
|
||||
selectionEnd: cursorEnd || value.length
|
||||
}, {
|
||||
suggest: true,
|
||||
correct: true
|
||||
});
|
||||
|
||||
// Must wait for getData() to resolve in these tests.
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
var activateAndTestPrediction =
|
||||
function activateAndTestPrediction(value, input, suggestions) {
|
||||
return activateEngineWithState(value).then(function() {
|
||||
workerStub.onmessage({
|
||||
data: {
|
||||
cmd: 'predictions',
|
||||
input: input, // old input
|
||||
suggestions: suggestions
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// for most tests we deliberately ignore all user-dictionary mechanisms to
|
||||
// ease testing of flow, by directly intercepting the last Promise.all call
|
||||
setup(function() {
|
||||
// the empty {} was what we'd resolved into for built-in dictionary (see
|
||||
// getData above for glue stub)
|
||||
this.sinon.stub(Promise, 'all').returns(Promise.resolve([{}]));
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
Promise.all.restore();
|
||||
engine.deactivate();
|
||||
});
|
||||
|
||||
suite('Handling user dictionary blob', function() {
|
||||
test('correctly passed to worker through setLanguageSync',
|
||||
function(done) {
|
||||
// test strategy is a bit complex: we want to test that the blob from
|
||||
// PromiseStorage is propogated to the Promise.all call and we want to
|
||||
// know the setLanguage call used results from Promise.all call.
|
||||
// The former is tested by then()'ing the Promise.all argument, and the
|
||||
// latter by asserting worker postMessage stub. However, since the
|
||||
// former one will happen in the next tick, we'll also need to wrap
|
||||
// ourselves in Promise.all. So proper restoration on Promise.all is
|
||||
// very important.
|
||||
|
||||
var resolvePromiseStorageBlob, rejectPromiseStorageBlob;
|
||||
var resolveWorkerPostMessage;
|
||||
|
||||
var pPromiseStorageBlob = new Promise(function(resolve, reject) {
|
||||
resolvePromiseStorageBlob = resolve;
|
||||
rejectPromiseStorageBlob = reject;
|
||||
});
|
||||
|
||||
var pWorkerPostMessage = new Promise(function(resolve) {
|
||||
resolveWorkerPostMessage = resolve;
|
||||
|
||||
resolve();
|
||||
});
|
||||
|
||||
Promise.all.restore();
|
||||
|
||||
var pAll = Promise.all([pPromiseStorageBlob, pWorkerPostMessage]);
|
||||
|
||||
this.sinon.stub(Promise, 'all')
|
||||
.returns(Promise.resolve([{}, {user: 'user'}]));
|
||||
|
||||
activateEngineWithState('').then(function(){
|
||||
Promise.all.firstCall.args[0][1].then(blob => {
|
||||
assert.deepEqual(blob, {user: 'user'});
|
||||
resolvePromiseStorageBlob();
|
||||
}).catch(e => {
|
||||
rejectPromiseStorageBlob(e);
|
||||
});
|
||||
|
||||
sinon.assert.calledWith(
|
||||
workerStub.postMessage,
|
||||
{ args: ['en', {}, {user: 'user'}], cmd: 'setLanguage' },
|
||||
[{}, {user: 'user'}]);
|
||||
resolveWorkerPostMessage();
|
||||
});
|
||||
|
||||
// calling then(done) (the first parameter) will make done receive an
|
||||
// array of Promise resolutions and trigger mocha fail, so let's wrap it
|
||||
pAll.then(() => {
|
||||
done();
|
||||
}, done);
|
||||
});
|
||||
|
||||
test('call setUserDictionary on activation', function(done) {
|
||||
// activate the engine twice to make sure worker is there at the second
|
||||
// time.
|
||||
activateEngineWithState('').then(function(){
|
||||
activateEngineWithState('').then(function(){
|
||||
sinon.assert.calledWith(
|
||||
workerStub.postMessage,
|
||||
{ args: [{user: 'user'}], cmd: 'setUserDictionary'},
|
||||
[{user: 'user'}]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('Without handling user dictionary', function() {
|
||||
test('After activation (and do nothing)', function(done) {
|
||||
activateEngineWithState('').then(function() {
|
||||
// maybe we shouldnt call this at all? don't know...
|
||||
sinon.assert.callCount(glue.sendCandidates, 1);
|
||||
sinon.assert.calledWith(glue.sendCandidates, []);
|
||||
|
||||
// Also, a space should not be inserted
|
||||
sinon.assert.callCount(glue.sendKey, 0);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('Suggestion data doesnt match input? Ignore.', function(done) {
|
||||
activateAndTestPrediction('janj', 'jan', [
|
||||
['Jan', 1],
|
||||
['jan', 1],
|
||||
['Pietje', 1]
|
||||
]).then(function() {
|
||||
// maybe we shouldnt call this at all? don't know...
|
||||
sinon.assert.callCount(glue.sendCandidates, 1);
|
||||
sinon.assert.calledWith(glue.sendCandidates, []);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('One char input "n" should not autocorrect to a multichar word',
|
||||
function(done) {
|
||||
activateAndTestPrediction('n', 'n', [
|
||||
['no', 1], // we want to ensure that this first suggestion is not
|
||||
// marked (with * prefix) as an autocorrection
|
||||
['not', 1],
|
||||
['now', 1]
|
||||
]).then(function() {
|
||||
sinon.assert.callCount(glue.sendCandidates, 1);
|
||||
// maybe we shouldnt call this at all? don't know...
|
||||
sinon.assert.calledWith(glue.sendCandidates,
|
||||
['no', 'not', 'now']); // Make sure we do not get "*no"
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('One char input "i" should autocorrect to a multichar word',
|
||||
function(done) {
|
||||
// But we also want to be sure that single letters like i do get
|
||||
// autocorrected to single letter words like I
|
||||
activateAndTestPrediction('i', 'i', [
|
||||
['I', 1], // we want to ensure that this first suggestion is not
|
||||
// marked (with * prefix) as an autocorrection
|
||||
['in', 1],
|
||||
['it', 1]
|
||||
]).then(function() {
|
||||
sinon.assert.calledWith(
|
||||
glue.sendCandidates, ['*I', 'in', 'it']);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('Input "im" should autocorrect to "I\'m", not "in"',
|
||||
function(done) {
|
||||
activateAndTestPrediction('im', 'im', [
|
||||
['in', 21],
|
||||
['I\'m', 16],
|
||||
['km', 9],
|
||||
['um', 9]
|
||||
]).then(function() {
|
||||
sinon.assert.calledWith(
|
||||
glue.sendCandidates, ['*I\'m', 'in', 'km']);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('Input "id" should autocorrect to "I\'d", not "is"',
|
||||
function(done) {
|
||||
activateAndTestPrediction('id', 'id', [
|
||||
['is', 20],
|
||||
['I\'d', 19],
|
||||
['if', 17],
|
||||
['ID', 16]
|
||||
]).then(function() {
|
||||
sinon.assert.calledWith(
|
||||
glue.sendCandidates, ['*I\'d', 'ID', 'is']);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('Space to accept suggestion', function(done) {
|
||||
activateAndTestPrediction('jan', 'jan', [
|
||||
['Jan'],
|
||||
['han'],
|
||||
['Pietje']
|
||||
]).then(function() {
|
||||
return engine.click(KeyboardEvent.DOM_VK_SPACE).then(function() {
|
||||
sinon.assert.callCount(glue.replaceSurroundingText, 1);
|
||||
sinon.assert.calledWith(glue.replaceSurroundingText, 'Jan', -3, 3);
|
||||
sinon.assert.calledWith(glue.sendKey, KeyboardEvent.DOM_VK_SPACE);
|
||||
});
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('Should communicate updated text to worker', function(done) {
|
||||
function clickAndAssert(key, assertion) {
|
||||
return engine.click(key.charCodeAt(0)).then(function() {
|
||||
sinon.assert.calledWith(workerStub.postMessage,
|
||||
{ args: [assertion], cmd: 'predict' });
|
||||
});
|
||||
}
|
||||
|
||||
activateEngineWithState('').then(function() {
|
||||
return clickAndAssert('p', 'p');
|
||||
}).then(function() {
|
||||
return clickAndAssert('a', 'pa');
|
||||
}).then(function() {
|
||||
return clickAndAssert('i', 'pai');
|
||||
}).then(function() {
|
||||
// 4 because we have one extra setUserDictionary call
|
||||
sinon.assert.callCount(workerStub.postMessage, 4);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('Two spaces after suggestion should autopunctuate', function(done) {
|
||||
activateAndTestPrediction('jan', 'jan', [
|
||||
['Jan'],
|
||||
['han'],
|
||||
['Pietje']
|
||||
]).then(function() {
|
||||
return engine.click(KeyboardEvent.DOM_VK_SPACE);
|
||||
}).then(function() {
|
||||
return engine.click(KeyboardEvent.DOM_VK_SPACE);
|
||||
}).then(function() {
|
||||
sinon.assert.callCount(glue.replaceSurroundingText, 1);
|
||||
sinon.assert.calledWith(glue.replaceSurroundingText, 'Jan', -3, 3);
|
||||
|
||||
sinon.assert.callCount(glue.sendKey, 4);
|
||||
assert.equal(glue.sendKey.args[0][0], KeyboardEvent.DOM_VK_SPACE);
|
||||
assert.equal(glue.sendKey.args[1][0],
|
||||
KeyboardEvent.DOM_VK_BACK_SPACE);
|
||||
assert.equal(glue.sendKey.args[2][0], '.'.charCodeAt(0));
|
||||
assert.equal(glue.sendKey.args[3][0], ' '.charCodeAt(0));
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('New line then dot should not remove newline', function(done) {
|
||||
activateEngineWithState('Hello').then(function() {
|
||||
return engine.click(KeyboardEvent.DOM_VK_RETURN);
|
||||
}).then(function() {
|
||||
return engine.click('.'.charCodeAt(0));
|
||||
}).then(function() {
|
||||
sinon.assert.callCount(glue.replaceSurroundingText, 0);
|
||||
sinon.assert.callCount(glue.sendKey, 2);
|
||||
assert.equal(glue.sendKey.args[0][0], KeyboardEvent.DOM_VK_RETURN);
|
||||
assert.equal(glue.sendKey.args[1][0], '.'.charCodeAt(0));
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('dismissSuggestions hides suggestions', function(done) {
|
||||
activateEngineWithState('').then(function() {
|
||||
engine.dismissSuggestions();
|
||||
|
||||
// Send candidates should be called once with an empty array
|
||||
// to clear the list of word suggestions
|
||||
sinon.assert.callCount(glue.sendCandidates, 2);
|
||||
sinon.assert.calledWith(glue.sendCandidates, []);
|
||||
|
||||
// Also, a space should not be inserted
|
||||
sinon.assert.callCount(glue.sendKey, 0);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
suite('Uppercase suggestions', function(done) {
|
||||
test('All uppercase input yields uppercase suggestions', function() {
|
||||
activateAndTestPrediction('HOLO', 'HOLO', [
|
||||
['yolo', 10],
|
||||
['Yelp', 5],
|
||||
['whuuu', 4]
|
||||
]).then(function() {
|
||||
sinon.assert.callCount(glue.sendCandidates, 1);
|
||||
// Verify that we show 3 suggestions that do not include the input
|
||||
// and that we do not mark the first as an autocorrection.
|
||||
sinon.assert.calledWith(
|
||||
glue.sendCandidates, ['*YOLO', 'YELP', 'WHUUU']);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('One char uppercase not yields uppercase suggestions',
|
||||
function(done) {
|
||||
activateAndTestPrediction('F', 'F', [
|
||||
['yolo', 10],
|
||||
['Yelp', 5],
|
||||
['whuuu', 4]
|
||||
]).then(function() {
|
||||
sinon.assert.callCount(glue.sendCandidates, 1);
|
||||
// Verify that we show 3 suggestions that do not include the input
|
||||
// and that we do not mark the first as an autocorrection.
|
||||
sinon.assert.calledWith(
|
||||
glue.sendCandidates, ['yolo', 'Yelp', 'whuuu']);
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('handleSuggestions', function() {
|
||||
test('input is not a word', function(done) {
|
||||
activateAndTestPrediction('jan', 'jan', [
|
||||
['Jan', 1],
|
||||
['han', 1],
|
||||
['Pietje', 1],
|
||||
['extra', 1]
|
||||
]).then(function() {
|
||||
sinon.assert.callCount(glue.sendCandidates, 1);
|
||||
// Show 3 suggestions and mark the first as an autocorrect
|
||||
sinon.assert.calledWith(glue.sendCandidates,
|
||||
['*Jan', 'han', 'Pietje']);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('input is a common word', function(done) {
|
||||
activateAndTestPrediction('the', 'the', [
|
||||
['the', 10],
|
||||
['they', 5],
|
||||
['then', 4],
|
||||
['there', 3]
|
||||
]).then(function() {
|
||||
sinon.assert.callCount(glue.sendCandidates, 1);
|
||||
// Verify that we show 3 suggestions that do not include the input
|
||||
// and that we do not mark the first as an autocorrection.
|
||||
sinon.assert.calledWith(glue.sendCandidates,
|
||||
['they', 'then', 'there']);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('input is an uncommon word', function(done) {
|
||||
activateAndTestPrediction('wont', 'wont', [
|
||||
['won\'t', 11],
|
||||
['wont', 8],
|
||||
['won', 7],
|
||||
['went', 6]
|
||||
]).then(function() {
|
||||
sinon.assert.callCount(glue.sendCandidates, 1);
|
||||
// Verify that we show 3 suggestions that do not include the input
|
||||
// and that we do mark the first as an autocorrection because it is
|
||||
// more common than the valid word input.
|
||||
sinon.assert.calledWith(glue.sendCandidates,
|
||||
['*won\'t', 'won', 'went']);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('Foe', function(done) {
|
||||
activateAndTestPrediction('foe', 'foe', [
|
||||
['for', 16.878906249999996],
|
||||
['foe', 15],
|
||||
['Doe', 7.566406249999998],
|
||||
['doe', 6.984374999999998]
|
||||
]).then(function() {
|
||||
sinon.assert.callCount(glue.sendCandidates, 1);
|
||||
sinon.assert.calledWith(glue.sendCandidates,
|
||||
['for', 'Doe', 'doe']);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('Hid', function(done) {
|
||||
activateAndTestPrediction('hid', 'hid', [
|
||||
['his', 16.296874999999996],
|
||||
['hid', 16],
|
||||
['HUD', 7.415834765624998],
|
||||
['hide', 7.2]
|
||||
]).then(function() {
|
||||
sinon.assert.callCount(glue.sendCandidates, 1);
|
||||
sinon.assert.calledWith(
|
||||
glue.sendCandidates, ['his', 'HUD', 'hide']);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
suite('Suggestion length mismatch', function(done) {
|
||||
test('Length mismatch, low freq', function(done) {
|
||||
activateAndTestPrediction('zoolgy', 'zoolgy', [
|
||||
['zoology', 4.2],
|
||||
['Zoology\'s', 0.09504000000000001]
|
||||
]).then(function() {
|
||||
sinon.assert.callCount(glue.sendCandidates, 1);
|
||||
sinon.assert.calledWith(
|
||||
glue.sendCandidates, ['zoology', 'Zoology\'s']);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('Length mismatch, medium freq', function(done) {
|
||||
activateAndTestPrediction('Folow', 'Folow', [
|
||||
['Follow', 6.237],
|
||||
['Follows', 2.4948],
|
||||
['Followed', 1.0454400000000001],
|
||||
['Follower', 0.7603200000000001]
|
||||
]).then(function() {
|
||||
sinon.assert.callCount(glue.sendCandidates, 1);
|
||||
sinon.assert.calledWith(
|
||||
glue.sendCandidates, ['*Follow', 'Follows', 'Followed']);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('Length mismatch, high freq', function(done) {
|
||||
activateAndTestPrediction('awesomeo', 'awesomeo', [
|
||||
['awesome', 31],
|
||||
['trahlah', 8],
|
||||
['moarstu', 7]
|
||||
]).then(function() {
|
||||
sinon.assert.callCount(glue.sendCandidates, 1);
|
||||
sinon.assert.calledWith(
|
||||
glue.sendCandidates, ['*awesome', 'trahlah', 'moarstu']);
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Always keep at least one user dictionary word', function() {
|
||||
test('User dictionary word frequency is high', function(done){
|
||||
activateAndTestPrediction('ster', 'ster', [
|
||||
['stery', 4],
|
||||
['star', 3, true],
|
||||
['stak', 2],
|
||||
['stack', 1]
|
||||
]).then(function() {
|
||||
sinon.assert.callCount(glue.sendCandidates, 1);
|
||||
sinon.assert.calledWith(
|
||||
glue.sendCandidates, ['stery', 'star', 'stak']);
|
||||
}).then(done, done);
|
||||
});
|
||||
suite('User dictionary word frequency is low but not too low',
|
||||
function(){
|
||||
var testWithLastUDSuggestionFreq = function(freq, done) {
|
||||
activateAndTestPrediction('ster', 'ster', [
|
||||
['stery', 4],
|
||||
['star', 3],
|
||||
['stak', 2],
|
||||
['stack', freq, true]
|
||||
]).then(function() {
|
||||
sinon.assert.callCount(glue.sendCandidates, 1);
|
||||
sinon.assert.calledWith(
|
||||
glue.sendCandidates, ['stery', 'star', 'stack']);
|
||||
}).then(done, done);
|
||||
};
|
||||
|
||||
test('UD word freq = 1.5', function(done){
|
||||
testWithLastUDSuggestionFreq(1.5, done);
|
||||
});
|
||||
test('UD word req = 1', function(done){
|
||||
testWithLastUDSuggestionFreq(1, done);
|
||||
});
|
||||
});
|
||||
suite('User dictionary word frequency is too low',
|
||||
function(){
|
||||
var testWithLastUDSuggestionFreq = function(freq, done) {
|
||||
activateAndTestPrediction('ster', 'ster', [
|
||||
['stery', 4],
|
||||
['star', 3],
|
||||
['stak', 2],
|
||||
['stack', freq, true]
|
||||
]).then(function() {
|
||||
sinon.assert.callCount(glue.sendCandidates, 1);
|
||||
sinon.assert.calledWith(
|
||||
glue.sendCandidates, ['stery', 'star', 'stak']);
|
||||
}).then(done, done);
|
||||
};
|
||||
|
||||
test('UD word freq = 0.99', function(done){
|
||||
testWithLastUDSuggestionFreq(0.99, done);
|
||||
});
|
||||
test('UD word req = 0.1', function(done){
|
||||
testWithLastUDSuggestionFreq(0.1, done);
|
||||
});
|
||||
test('UD word req = 0.01', function(done){
|
||||
testWithLastUDSuggestionFreq(0.01, done);
|
||||
});
|
||||
});
|
||||
test('A frequent user dictionary word is input', function(done){
|
||||
activateAndTestPrediction('ster', 'ster', [
|
||||
['ster', 10, true],
|
||||
['star', 3],
|
||||
['stak', 2],
|
||||
['stack', 1]
|
||||
]).then(function() {
|
||||
sinon.assert.callCount(glue.sendCandidates, 1);
|
||||
sinon.assert.calledWith(
|
||||
glue.sendCandidates, ['star', 'stak', 'stack']);
|
||||
}).then(done, done);
|
||||
});
|
||||
test('User dictionary word frequency is too low but still higher' +
|
||||
' than next built-in dictionary\'s frequency',
|
||||
function(done){
|
||||
activateAndTestPrediction('ster', 'ster', [
|
||||
['stery', 4],
|
||||
['star', 3],
|
||||
['stack', 0.99, true],
|
||||
['stak', 0.95],
|
||||
]).then(function() {
|
||||
sinon.assert.callCount(glue.sendCandidates, 1);
|
||||
sinon.assert.calledWith(
|
||||
glue.sendCandidates, ['stery', 'star', 'stack']);
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('Reject one of the keys', function() {
|
||||
setup(function(done) {
|
||||
engine.activate('en', {
|
||||
type: 'text',
|
||||
inputmode: '',
|
||||
text: '',
|
||||
selectionStart: 0,
|
||||
selectionEnd: 0
|
||||
},{ suggest: false, correct: false });
|
||||
|
||||
// Wait for getData() to resolve.
|
||||
Promise.resolve().then(done);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
engine.deactivate();
|
||||
});
|
||||
|
||||
test('reject and resolve another', function(done) {
|
||||
glue.mResolveSendKey = false;
|
||||
|
||||
engine
|
||||
.click('a'.charCodeAt(0))
|
||||
.then(function() {
|
||||
glue.mResolveSendKey = true;
|
||||
|
||||
return engine.click('b'.charCodeAt(0));
|
||||
})
|
||||
.then(function() {
|
||||
assert.equal('b', glue.mOutput);
|
||||
})
|
||||
.catch(function(e) {
|
||||
throw (e || 'Should not reject.');
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('stateChange', function() {
|
||||
setup(function(done) {
|
||||
engine.activate('en', {
|
||||
type: 'text',
|
||||
inputmode: '',
|
||||
text: 'before after',
|
||||
selectionStart: 5,
|
||||
selectionEnd: 5
|
||||
}, { suggest: true, correct: true });
|
||||
|
||||
// Wait for getData() to resolve.
|
||||
Promise.resolve().then(done);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
engine.deactivate();
|
||||
});
|
||||
|
||||
test('will clear the suggestions if stateChange', function() {
|
||||
engine.stateChange({
|
||||
ownAction: false,
|
||||
type: 'text',
|
||||
inputmode: '',
|
||||
selectionStart: 0,
|
||||
selectionEnd: 0,
|
||||
text: 'before after'
|
||||
});
|
||||
|
||||
// will clear the suggestions since cursor changed
|
||||
sinon.assert.calledThrice(glue.sendCandidates);
|
||||
});
|
||||
|
||||
test('Do nothing if stateChange called w/o changes', function() {
|
||||
engine.stateChange({
|
||||
ownAction: true,
|
||||
type: 'text',
|
||||
inputmode: '',
|
||||
text: 'before after',
|
||||
selectionStart: 5,
|
||||
selectionEnd: 5
|
||||
});
|
||||
|
||||
// will clear the suggestions since cursor changed
|
||||
sinon.assert.calledOnce(glue.sendCandidates);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Layout handling', function() {
|
||||
setup(function() {
|
||||
engine.activate('en', {
|
||||
type: 'text',
|
||||
inputmode: '',
|
||||
text: '',
|
||||
selectionStart: 0,
|
||||
selectionEnd: 0
|
||||
},{ suggest: false, correct: false });
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
engine.deactivate();
|
||||
});
|
||||
|
||||
test('Do not switch layout if current layout is default', function(done) {
|
||||
engine.click(KeyEvent.DOM_VK_RETURN)
|
||||
.then(() => sinon.assert.notCalled(glue.setLayoutPage))
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('Do not switch layout if current layout is default', function(done) {
|
||||
engine.setLayoutPage(1);
|
||||
engine.setLayoutPage(PAGE_INDEX_DEFAULT);
|
||||
engine.click(KeyEvent.DOM_VK_RETURN)
|
||||
.then(() => sinon.assert.notCalled(glue.setLayoutPage))
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('Do not switch layout if a normal key was not clicked before',
|
||||
function(done) {
|
||||
engine.setLayoutPage(1);
|
||||
engine.click(KeyEvent.DOM_VK_SPACE)
|
||||
.then(() => sinon.assert.notCalled(glue.setLayoutPage))
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('Switch layout if a normal key then space clicked', function(done) {
|
||||
engine.setLayoutPage(1);
|
||||
engine.click('1'.charCodeAt(0))
|
||||
.then(() => engine.click(KeyEvent.DOM_VK_SPACE))
|
||||
.then(
|
||||
() => sinon.assert.calledWith(glue.setLayoutPage, PAGE_INDEX_DEFAULT)
|
||||
)
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('Switch layout if a normal key is clicked on one non-default layout, '+
|
||||
'then space clicked on another non-default layout', function(done) {
|
||||
engine.setLayoutPage(1);
|
||||
engine.click('1'.charCodeAt(0))
|
||||
.then(() => {
|
||||
engine.setLayoutPage(2);
|
||||
return engine.click(KeyEvent.DOM_VK_SPACE);
|
||||
})
|
||||
.then(
|
||||
() => sinon.assert.calledWith(glue.setLayoutPage, PAGE_INDEX_DEFAULT)
|
||||
)
|
||||
.then(done, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
875
gaia/apps/keyboard/test/unit/imes/latin/worker_test.js
Executable file
875
gaia/apps/keyboard/test/unit/imes/latin/worker_test.js
Executable file
|
|
@ -0,0 +1,875 @@
|
|||
/*global InputMethodDatabaseLoader, WordListConverter, InputMethods, dump */
|
||||
'use strict';
|
||||
|
||||
require('/js/settings/word_list_converter.js');
|
||||
require('/js/keyboard/input_method_database_loader.js');
|
||||
|
||||
suite('Latin worker', function() {
|
||||
var worker;
|
||||
var keymaps = {};
|
||||
|
||||
suiteSetup(function(done) {
|
||||
window.InputMethods = {};
|
||||
require('/js/imes/latin/latin.js', function() {
|
||||
keymaps.qwerty = InputMethods.latin.generateNearbyKeyMap({
|
||||
'keyboardWidth': 320,
|
||||
'keyboardHeight': 205,
|
||||
'keyArray': [{
|
||||
'code': 113,
|
||||
'x': 0,
|
||||
'y': 29,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 119,
|
||||
'x': 32,
|
||||
'y': 29,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 101,
|
||||
'x': 64,
|
||||
'y': 29,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 114,
|
||||
'x': 96,
|
||||
'y': 29,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 116,
|
||||
'x': 128,
|
||||
'y': 29,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 121,
|
||||
'x': 160,
|
||||
'y': 29,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 117,
|
||||
'x': 192,
|
||||
'y': 29,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 105,
|
||||
'x': 224,
|
||||
'y': 29,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 111,
|
||||
'x': 256,
|
||||
'y': 29,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 112,
|
||||
'x': 288,
|
||||
'y': 29,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 97,
|
||||
'x': 16,
|
||||
'y': 80,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 115,
|
||||
'x': 48,
|
||||
'y': 80,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 100,
|
||||
'x': 80,
|
||||
'y': 80,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 102,
|
||||
'x': 112,
|
||||
'y': 80,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 103,
|
||||
'x': 144,
|
||||
'y': 80,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 104,
|
||||
'x': 176,
|
||||
'y': 80,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 106,
|
||||
'x': 208,
|
||||
'y': 80,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 107,
|
||||
'x': 240,
|
||||
'y': 80,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 108,
|
||||
'x': 272,
|
||||
'y': 80,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 20,
|
||||
'x': 0,
|
||||
'y': 131,
|
||||
'width': 48,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 122,
|
||||
'x': 48,
|
||||
'y': 131,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 120,
|
||||
'x': 80,
|
||||
'y': 131,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 99,
|
||||
'x': 112,
|
||||
'y': 131,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 118,
|
||||
'x': 144,
|
||||
'y': 131,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 98,
|
||||
'x': 176,
|
||||
'y': 131,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 110,
|
||||
'x': 208,
|
||||
'y': 131,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 109,
|
||||
'x': 240,
|
||||
'y': 131,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 8,
|
||||
'x': 272,
|
||||
'y': 131,
|
||||
'width': 48,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 65534,
|
||||
'x': 0,
|
||||
'y': 182,
|
||||
'width': 48,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 65533,
|
||||
'x': 48,
|
||||
'y': 182,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 32,
|
||||
'x': 80,
|
||||
'y': 182,
|
||||
'width': 144,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 46,
|
||||
'x': 224,
|
||||
'y': 182,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 13,
|
||||
'x': 256,
|
||||
'y': 182,
|
||||
'width': 64,
|
||||
'height': 43
|
||||
}],
|
||||
'keyWidth': 32,
|
||||
'keyHeight': 50
|
||||
});
|
||||
|
||||
keymaps.azerty = InputMethods.latin.generateNearbyKeyMap({
|
||||
'keyboardWidth': 320,
|
||||
'keyboardHeight': 205,
|
||||
'keyArray': [{
|
||||
'code': 97,
|
||||
'x': 0,
|
||||
'y': 29,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 122,
|
||||
'x': 32,
|
||||
'y': 29,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 101,
|
||||
'x': 64,
|
||||
'y': 29,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 114,
|
||||
'x': 96,
|
||||
'y': 29,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 116,
|
||||
'x': 128,
|
||||
'y': 29,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 121,
|
||||
'x': 160,
|
||||
'y': 29,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 117,
|
||||
'x': 192,
|
||||
'y': 29,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 105,
|
||||
'x': 224,
|
||||
'y': 29,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 111,
|
||||
'x': 256,
|
||||
'y': 29,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 112,
|
||||
'x': 288,
|
||||
'y': 29,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 113,
|
||||
'x': 0,
|
||||
'y': 80,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 115,
|
||||
'x': 32,
|
||||
'y': 80,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 100,
|
||||
'x': 64,
|
||||
'y': 80,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 102,
|
||||
'x': 96,
|
||||
'y': 80,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 103,
|
||||
'x': 128,
|
||||
'y': 80,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 104,
|
||||
'x': 160,
|
||||
'y': 80,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 106,
|
||||
'x': 192,
|
||||
'y': 80,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 107,
|
||||
'x': 224,
|
||||
'y': 80,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 108,
|
||||
'x': 256,
|
||||
'y': 80,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 109,
|
||||
'x': 288,
|
||||
'y': 80,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 20,
|
||||
'x': 0,
|
||||
'y': 131,
|
||||
'width': 48,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 119,
|
||||
'x': 48,
|
||||
'y': 131,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 120,
|
||||
'x': 80,
|
||||
'y': 131,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 99,
|
||||
'x': 112,
|
||||
'y': 131,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 118,
|
||||
'x': 144,
|
||||
'y': 131,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 98,
|
||||
'x': 176,
|
||||
'y': 131,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 110,
|
||||
'x': 208,
|
||||
'y': 131,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 39,
|
||||
'x': 240,
|
||||
'y': 131,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 8,
|
||||
'x': 272,
|
||||
'y': 131,
|
||||
'width': 48,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 18,
|
||||
'x': 0,
|
||||
'y': 182,
|
||||
'width': 64,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': -3,
|
||||
'x': 64,
|
||||
'y': 182,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 32,
|
||||
'x': 96,
|
||||
'y': 182,
|
||||
'width': 128,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 46,
|
||||
'x': 224,
|
||||
'y': 182,
|
||||
'width': 32,
|
||||
'height': 43
|
||||
}, {
|
||||
'code': 13,
|
||||
'x': 256,
|
||||
'y': 182,
|
||||
'width': 64,
|
||||
'height': 43
|
||||
}],
|
||||
'keyWidth': 32,
|
||||
'keyHeight': 51
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
worker = new Worker('../../../../js/imes/latin/worker.js');
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
worker.terminate();
|
||||
worker.onmessage = null;
|
||||
worker = null;
|
||||
});
|
||||
|
||||
function onWorkerMessage(filter) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
worker.onmessage = function handleWorkerMessage(evt) {
|
||||
if (filter !== undefined && evt.data.cmd !== filter) {
|
||||
return;
|
||||
}
|
||||
|
||||
worker.onmessage = null;
|
||||
resolve(evt.data);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function prediction(input, expected) {
|
||||
worker.postMessage({cmd: 'predict', args: [input]});
|
||||
|
||||
return onWorkerMessage('predictions').then(function(data) {
|
||||
|
||||
var suggestions = data.suggestions;
|
||||
|
||||
assert.equal(data.cmd, 'predictions');
|
||||
assert.equal(data.input, input);
|
||||
|
||||
suggestions = suggestions.map(function(s) {
|
||||
return s[0];
|
||||
});
|
||||
|
||||
assert.equal(suggestions[0], expected[0]);
|
||||
|
||||
for (var i = 1; i < 6; i++) {
|
||||
if (expected[i] !== null) {
|
||||
assert.equal(suggestions[i], expected[i], 'index: ' + i);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setupLanguage(langCode, keymap, userDictBlob) {
|
||||
var loader = new InputMethodDatabaseLoader();
|
||||
loader.start();
|
||||
loader.SOURCE_DIR = '/js/imes/';
|
||||
var loaderPromise =
|
||||
loader.load('latin', 'dictionaries/' + langCode + '.dict')
|
||||
.then(function(dictData) {
|
||||
worker.postMessage({
|
||||
cmd: 'setLanguage',
|
||||
args: [langCode, dictData, userDictBlob]
|
||||
}, [dictData]);
|
||||
});
|
||||
|
||||
worker.postMessage({
|
||||
cmd: 'setNearbyKeys',
|
||||
args: [keymap]
|
||||
});
|
||||
|
||||
var successCount = 0;
|
||||
var waitForSuccessPromise = new Promise(function(resolve, reject) {
|
||||
worker.onmessage = function(e) {
|
||||
if (e.data.cmd !== 'success') {
|
||||
reject('worker.onmessage unexpected result ' + e.data.message + '\n');
|
||||
}
|
||||
|
||||
if (e.data.fn.startsWith('setLanguage') ||
|
||||
e.data.fn.startsWith('setNearbyKeys')) {
|
||||
successCount++;
|
||||
|
||||
if (successCount === 4) {
|
||||
worker.onmessage = null;
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
return loaderPromise
|
||||
.then(function() {
|
||||
return waitForSuccessPromise;
|
||||
});
|
||||
}
|
||||
|
||||
test('Worker should throw if no dictData is passed', function(done) {
|
||||
worker.postMessage({ cmd: 'setLanguage', args: ['en_us', null] });
|
||||
|
||||
onWorkerMessage('error').then(function(data){
|
||||
assert.equal(data.cmd, 'error');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
suite('en_us predictions', function() {
|
||||
setup(function(done) {
|
||||
setupLanguage('en_us', keymaps.qwerty, undefined
|
||||
).then(done,done);
|
||||
});
|
||||
|
||||
test('i should be predicted as I', function(done) {
|
||||
prediction('i', ['I', 'in', 'is', null, null, null]
|
||||
).then(done,done);
|
||||
});
|
||||
|
||||
test('Capital input should give capital output', function(done) {
|
||||
prediction('City', ['City', 'City\'s', 'Fit', null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('Non-Capital input should give non-capital output', function(done) {
|
||||
prediction('city', ['city', 'city\'s', 'fit', null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('Non existing word should not be matched', function(done) {
|
||||
prediction('sadjasuufehwuefhwejfd', [undefined, null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('$ should not yield autosuggest', function(done) {
|
||||
prediction('$', [undefined, null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
suite('Capitalization and suggestions', function() {
|
||||
test('virgule', function(done) {
|
||||
prediction('virgule',
|
||||
['virgule', 'virgules', 'Virgil', null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('Virgule', function(done) {
|
||||
prediction('Virgule',
|
||||
['Virgule', 'Virgules', 'Virgil', null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('virgul', function(done) {
|
||||
prediction('virgul',
|
||||
['Virgil', 'Virgil\'s', 'virgule', null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('Virgul', function(done) {
|
||||
prediction('Virgul',
|
||||
['Virgil', 'Virgil\'s', 'Virgule', null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('balds', function(done) {
|
||||
prediction('balds',
|
||||
['balds', 'Baldwin', 'Baldwins', null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('Balds', function(done) {
|
||||
prediction('Balds',
|
||||
['Balds', 'Baldwin', 'Baldwins', null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('chaot', function(done) {
|
||||
prediction('chaot',
|
||||
['chaotic', 'chapter', 'chapters', null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('Chaot', function(done) {
|
||||
prediction('Chaot',
|
||||
['Chaotic', 'Chapter', 'Chapters', null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('as', function(done) {
|
||||
prediction('as', ['as', 'ad', 'AD', null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('As', function(done) {
|
||||
prediction('As', ['As', 'Ad', 'AD', null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('keyboa', function(done) {
|
||||
prediction('keyboa', ['keyboard', null, null, null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Low frequency dictionary words with better suggestion', function() {
|
||||
test('wont', function(done) {
|
||||
prediction('wont', ['won\'t', 'wont', 'Wong', null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('cant', function(done) {
|
||||
prediction('cant', ['can\'t', 'cant', 'canto', null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Swear words', function() {
|
||||
test('Should not suggest word in offensive list #1', function(done) {
|
||||
worker.postMessage({ cmd: 'predict', args: ['fuc'] });
|
||||
|
||||
onWorkerMessage().then(function(data) {
|
||||
var suggestions = data.suggestions;
|
||||
|
||||
assert.notEqual(suggestions[0][0], 'fuck');
|
||||
assert.notEqual(suggestions[1][0], 'fuck');
|
||||
assert.notEqual(suggestions[2][0], 'fuck');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('Should not suggest word in offensive list #2', function(done) {
|
||||
worker.postMessage({ cmd: 'predict', args: ['peni'] });
|
||||
|
||||
onWorkerMessage().then(function(data) {
|
||||
var suggestions = data.suggestions;
|
||||
|
||||
assert.notEqual(suggestions[0][0], 'penis');
|
||||
assert.notEqual(suggestions[1][0], 'penis');
|
||||
assert.notEqual(suggestions[2][0], 'penis');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('Should not suggest word in offensive list #3', function(done) {
|
||||
worker.postMessage({ cmd: 'predict', args: ['fuck'] });
|
||||
|
||||
onWorkerMessage().then(function(data) {
|
||||
var suggestions = data.suggestions;
|
||||
|
||||
assert.equal(suggestions[1][0], 'duck');
|
||||
assert.equal(suggestions[2][0], 'Tuck');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('Should suggest offensive word if matches input #1', function(done) {
|
||||
prediction('fuck', ['fuck', null, null, null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('Should suggest offensive word if matches input #2', function(done) {
|
||||
prediction('penis', ['penis', null, null, null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('Should suggest offensive word if matches input #3', function(done) {
|
||||
prediction('Penis', ['Penis', null, null, null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('Should suggest offensive word if matches input #4', function(done) {
|
||||
prediction('Vagina', ['Vagina', null, null, null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('Should suggest offensive word if matches input #5', function(done) {
|
||||
prediction('Fuck', ['Fuck', null, null, null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('Should suggest offensive word if matches input #6', function(done) {
|
||||
prediction('shit', ['shit', null, null, null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('Should suggest offensive word if matches input #7', function(done) {
|
||||
prediction('prick', ['prick', null, null, null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('Should suggest offensive word if matches input #7', function(done) {
|
||||
prediction('ass', ['ass', null, null, null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Vertical nearby keys', function() {
|
||||
test('Asjan / Asian', function(done) {
|
||||
prediction('Asjan', ['Asian', null, null, null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('flr / for', function(done) {
|
||||
prediction('flr', ['for', null, null, null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('kn / km / in / on', function(done) {
|
||||
prediction('kn', ['km', 'in', 'on', null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// a bit "cheating" here: if we use en_us in this suite then setLanguage will
|
||||
// early-return at setting built-in dict and "success" count will only be 3.
|
||||
// to avoid changing too many assertion logics we'll use en_gb and en_us
|
||||
// alternately.
|
||||
suite('predictions in conjuction with user dictionary', function() {
|
||||
var blob;
|
||||
setup(function() {
|
||||
blob = new WordListConverter(
|
||||
['Mozilla', 'MozSpace', 'MozTrap', 'mozSettings', 'Mozillian'])
|
||||
.toBlob();
|
||||
});
|
||||
|
||||
suite('with user dictionary blob at setupLanguage', function() {
|
||||
setup(function(done) {
|
||||
setupLanguage('en_gb', keymaps.qwerty, blob
|
||||
).then(done,done);
|
||||
});
|
||||
|
||||
test('Moz', function(done) {
|
||||
prediction('Moz',
|
||||
['Mox', 'Mos', 'Most', 'Mod', 'MozTrap', 'Mozilla']
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('mozs', function(done) {
|
||||
prediction('mozs',
|
||||
['moss', 'mods', 'mossy', 'Mossi', 'MozSpace', 'MozTrap']
|
||||
).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('with user dictionary blob supplied later', function() {
|
||||
setup(function(done) {
|
||||
setupLanguage('en_us', keymaps.qwerty, undefined).then(function() {
|
||||
worker.addEventListener('message', function onMessage(e) {
|
||||
if ('success' === e.data.cmd && 'setUserDictionary' === e.data.fn) {
|
||||
worker.removeEventListener('messgae', onMessage);
|
||||
}
|
||||
});
|
||||
worker.postMessage({
|
||||
cmd: 'setUserDictionary',
|
||||
args: [blob]
|
||||
});
|
||||
}).then(done,done);
|
||||
});
|
||||
|
||||
test('Moz', function(done) {
|
||||
prediction('Moz',
|
||||
['MOX', 'Mos', 'Most', 'Mod', 'MozTrap', 'Mozilla']
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('mozs', function(done) {
|
||||
prediction('mozs',
|
||||
['moss', 'Moss', 'mods', 'mossy', 'MozSpace', 'MozTrap']
|
||||
).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
suite('with user dictionary blob nullified later', function() {
|
||||
setup(function(done) {
|
||||
setupLanguage('en_gb', keymaps.qwerty, blob).then(function() {
|
||||
worker.addEventListener('message', function onMessage(e) {
|
||||
if ('success' === e.data.cmd && 'setUserDictionary' === e.data.fn) {
|
||||
worker.removeEventListener('messgae', onMessage);
|
||||
}
|
||||
});
|
||||
worker.postMessage({
|
||||
cmd: 'setUserDictionary',
|
||||
args: [undefined]
|
||||
});
|
||||
}).then(done,done);
|
||||
});
|
||||
|
||||
// do not append "null" there: we want to make sure the suggestions cut at
|
||||
// the fourth element.
|
||||
test('Moz', function(done) {
|
||||
prediction('Moz', ['Mox', 'Mos', 'Most', 'Mod']
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('mozs', function(done) {
|
||||
prediction('mozs', ['moss', 'mods', 'mossy', 'Mossi']
|
||||
).then(done, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('fr predictions', function() {
|
||||
setup(function(done) {
|
||||
setupLanguage('fr', keymaps.azerty, undefined
|
||||
).then(done,done);
|
||||
});
|
||||
|
||||
test('123 should not yield prediction', function(done) {
|
||||
prediction('123', [null, null, null, null, null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('validChars() is tolerant on small dicts', function() {
|
||||
setup(function(done) {
|
||||
var dictData = new WordListConverter(['Ápple']).toBlob();
|
||||
|
||||
worker.postMessage({
|
||||
cmd: 'setNearbyKeys',
|
||||
args: [keymaps.qwerty]
|
||||
});
|
||||
|
||||
worker.postMessage({
|
||||
cmd: 'setLanguage',
|
||||
args: ['en_us', dictData]
|
||||
});
|
||||
|
||||
var successCount = 0;
|
||||
worker.onmessage = function(e) {
|
||||
if (e.data.cmd !== 'success') {
|
||||
dump('worker.onmessage unexpected result ' + e.message + '\n');
|
||||
}
|
||||
assert.equal(e.data.cmd, 'success');
|
||||
|
||||
if (e.data.fn.startsWith('setLanguage') ||
|
||||
e.data.fn.startsWith('setNearbyKeys')) {
|
||||
successCount++;
|
||||
|
||||
if (successCount === 2) {
|
||||
done();
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
test('app should yield prediction', function(done) {
|
||||
prediction('app', ['Ápple', null, null]
|
||||
).then(done, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
106
gaia/apps/keyboard/test/unit/imes/myanmar/myanmar_test.js
Executable file
106
gaia/apps/keyboard/test/unit/imes/myanmar/myanmar_test.js
Executable file
|
|
@ -0,0 +1,106 @@
|
|||
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
|
||||
'use strict';
|
||||
|
||||
requireApp('keyboard/test/unit/setup_engine.js');
|
||||
requireApp('keyboard/js/imes/myanmar/myanmar.js');
|
||||
|
||||
suite('myanmar.js', function() {
|
||||
|
||||
var engine;
|
||||
|
||||
var BACKSPACE = 8;
|
||||
var outputString = '';
|
||||
var glue = {
|
||||
sendKey: function(key) {
|
||||
outputString = outputString + String.fromCharCode(key);
|
||||
},
|
||||
sendString: sinon.stub(),
|
||||
setUpperCase: sinon.stub(),
|
||||
replaceSurroundingText: function(str, index, num) {
|
||||
assert.equal(num+index, 0);
|
||||
outputString = str;
|
||||
},
|
||||
getNumberOfCandidatesPerRow: function() {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
setup(function() {
|
||||
outputString = '';
|
||||
this.sinon.spy(glue, 'replaceSurroundingText');
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
glue.replaceSurroundingText.reset();
|
||||
});
|
||||
|
||||
test('Init', function() {
|
||||
assert.isDefined(window.InputMethods.myanmar);
|
||||
engine = window.InputMethods.myanmar;
|
||||
engine.init(glue);
|
||||
});
|
||||
|
||||
test('Input VowelSE', function() {
|
||||
outputString = '';
|
||||
engine.click('\u1031'.charCodeAt(0));
|
||||
assert.equal(outputString, '\u200B\u1031');
|
||||
});
|
||||
|
||||
function runTest(rules, action) {
|
||||
rules.forEach(function(rule, index) {
|
||||
test('case ' + index, function() {
|
||||
engine.surroundingtextChange({beforeString:rule.input, afterString:''});
|
||||
action();
|
||||
assert.ok(glue.replaceSurroundingText.called);
|
||||
assert.equal(outputString, rule.result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
suite('Add rules', function() {
|
||||
runTest([
|
||||
{ input:'\u1025\u102E', result: '\u1026' },
|
||||
{ input:'\u1025\u103A', result: '\u1009\u103A' },
|
||||
{ input:'\u101E\u103C\u1031\u102C\u103A', result: '\u102A' }
|
||||
], sinon.stub());
|
||||
});
|
||||
|
||||
suite('Swap rules', function() {
|
||||
runTest([
|
||||
// p1 + p2 => p2 + p1
|
||||
{ input:'\u103E\u103B', result: '\u103B\u103E'},
|
||||
{ input:'\u103D\u103B', result: '\u103B\u103D'},
|
||||
{ input:'\u1036\u102E', result: '\u102E\u1036'},
|
||||
{ input:'\u102F\u102D', result: '\u102D\u102F'},
|
||||
{ input:'\u103A\u1037', result: '\u1037\u103A'},
|
||||
{ input:'\u1037\u102E', result: '\u102E\u1037'},
|
||||
{ input:'\u1036\u1032', result: '\u1032\u1036'},
|
||||
{ input:'\u1031\u103B', result: '\u103B\u1031'},
|
||||
// p1 + p2 + p3 => p2 + p3 + p1
|
||||
{ input:'\u1031\u1039\u1000', result: '\u1039\u1000\u1031'},
|
||||
// p1 + p2 + p3 => p3 + p1 + p2
|
||||
{ input:'\u103D\u1031\u103C', result: '\u103C\u103D\u1031'},
|
||||
{ input:'\u103D\u103E\u103C', result: '\u103C\u103D\u103E'},
|
||||
{ input:'\u103E\u1031\u103B', result: '\u103B\u103E\u1031'},
|
||||
// p1 + p2 + p3 => p3 + p2
|
||||
{ input:'\u200B\u1031\u1000', result: '\u1000\u1031'},
|
||||
// p1 + p2 + p3 + p4 => p4 + p1 + p2 + p3
|
||||
{ input:'\u103D\u103E\u1031\u103C', result: '\u103C\u103D\u103E\u1031'}
|
||||
], sinon.stub());
|
||||
});
|
||||
|
||||
suite('Delete rules', function() {
|
||||
runTest([
|
||||
{ input:'\u1039\u1019\u1031', result: '\u1031\u1039' },
|
||||
{ input:'\u1019\u1031', result: '\u200B\u1031' },
|
||||
{ input:'\u103B\u1031', result: '\u1031' },
|
||||
{ input:'\u1039\u1019', result: '' },
|
||||
{ input:'\u200B\u1031', result: '' },
|
||||
], function() {
|
||||
engine.click(BACKSPACE);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
257
gaia/apps/keyboard/test/unit/jshangul_test.js
Executable file
257
gaia/apps/keyboard/test/unit/jshangul_test.js
Executable file
|
|
@ -0,0 +1,257 @@
|
|||
/* global InputMethods, KeyEvent */
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/test/unit/setup_engine.js');
|
||||
require('/js/imes/jshangul/jshangul.js');
|
||||
|
||||
var SPACE = KeyEvent.DOM_VK_SPACE;
|
||||
var BACKSPACE = KeyEvent.DOM_VK_BACK_SPACE;
|
||||
|
||||
suite('jshangul', function() {
|
||||
|
||||
var im;
|
||||
|
||||
function dummy() {}
|
||||
|
||||
var testableKeyboardGlue = {
|
||||
// For Test Methods
|
||||
_ready: function() {
|
||||
this.gul = undefined;
|
||||
this.endCompositionCount = 0;
|
||||
},
|
||||
_status: function() {
|
||||
return [this.gul, this.endCompositionCount];
|
||||
},
|
||||
|
||||
// Keyboard APIs
|
||||
resetUpperCase: dummy,
|
||||
sendKey: function(keyCode) {
|
||||
this.gul = String.fromCharCode(keyCode);
|
||||
},
|
||||
sendCandidates: dummy,
|
||||
setComposition: function(gul) {
|
||||
this.gul = gul;
|
||||
},
|
||||
endComposition: function() {
|
||||
this.endCompositionCount ++;
|
||||
},
|
||||
setUpperCase: dummy,
|
||||
setLayoutPage: dummy,
|
||||
isCapitalized: dummy
|
||||
};
|
||||
|
||||
suiteSetup(function() {
|
||||
im = InputMethods.jshangul;
|
||||
im.init(testableKeyboardGlue);
|
||||
});
|
||||
|
||||
test('load', function() {
|
||||
assert.isDefined(im);
|
||||
});
|
||||
|
||||
suite('Hangul Composition Test', function() {
|
||||
var cases = [
|
||||
{
|
||||
info: 'should return "ㄲㄸ" when type "ㄱㄱㄷㄷ"',
|
||||
scenario: [
|
||||
[ 'ㄱ', [ 'ㄱ', 0 ] ],
|
||||
[ 'ㄱ', [ 'ㄲ', 0 ] ],
|
||||
[ 'ㄷ', [ 'ㄷ', 1 ] ],
|
||||
[ 'ㄷ', [ 'ㄸ', 1 ] ]
|
||||
]
|
||||
},
|
||||
{
|
||||
info: 'should return "각" when type "ㄱㅏㄱ"',
|
||||
scenario: [
|
||||
[ 'ㄱ', [ 'ㄱ', 0 ] ],
|
||||
[ 'ㅏ', [ '가', 0 ] ],
|
||||
[ 'ㄱ', [ '각', 0 ] ]
|
||||
]
|
||||
},
|
||||
{
|
||||
info: 'should return "냐" when type "ㄴㅑ"',
|
||||
scenario: [
|
||||
[ 'ㄴ', [ 'ㄴ', 0 ] ],
|
||||
[ 'ㅑ', [ '냐', 0 ] ]
|
||||
]
|
||||
},
|
||||
{
|
||||
info: 'should return "ㅏㄱ" when type "ㅏㄱ"',
|
||||
scenario: [
|
||||
[ 'ㅏ', [ 'ㅏ', 0 ] ],
|
||||
[ 'ㄱ', [ 'ㄱ', 1 ] ]
|
||||
]
|
||||
},
|
||||
{
|
||||
info: 'should return "ㅢㅣ" when type "ㅡㅣㅣ"' ,
|
||||
scenario: [
|
||||
[ 'ㅡ', [ 'ㅡ', 0 ] ],
|
||||
[ 'ㅣ', [ 'ㅢ', 0 ] ],
|
||||
[ 'ㅣ', [ 'ㅣ', 1 ] ]
|
||||
]
|
||||
},
|
||||
{
|
||||
info: 'should return "ㅏㅑㅓㅕㅗㅛ" when type "ㅏㅑㅓㅕㅗㅛ"' ,
|
||||
scenario: [
|
||||
[ 'ㅏ', [ 'ㅏ', 0 ] ],
|
||||
[ 'ㅑ', [ 'ㅑ', 1 ] ],
|
||||
[ 'ㅓ', [ 'ㅓ', 2 ] ],
|
||||
[ 'ㅕ', [ 'ㅕ', 3 ] ],
|
||||
[ 'ㅗ', [ 'ㅗ', 4 ] ],
|
||||
[ 'ㅛ', [ 'ㅛ', 5 ] ]
|
||||
]
|
||||
},
|
||||
{
|
||||
info: 'should return 신장미 when type "ㅅㅣㄴㅈㅏㅇㅁㅣ"',
|
||||
scenario: [
|
||||
[ 'ㅅ', [ 'ㅅ', 0 ] ],
|
||||
[ 'ㅣ', [ '시', 0 ] ],
|
||||
[ 'ㄴ', [ '신', 0 ] ],
|
||||
[ 'ㅈ', [ '싡', 0 ] ],
|
||||
[ 'ㅏ', [ '자', 1 ] ],
|
||||
[ 'ㅇ', [ '장', 1 ] ],
|
||||
[ 'ㅁ', [ 'ㅁ', 2 ] ],
|
||||
[ 'ㅣ', [ '미', 2 ] ]
|
||||
]
|
||||
},
|
||||
{
|
||||
info: 'should return 깨끗 when type "ㄱㄱㅐㄲㅡㅅ"',
|
||||
scenario: [
|
||||
[ 'ㄱ', [ 'ㄱ', 0 ] ],
|
||||
[ 'ㄱ', [ 'ㄲ', 0 ] ],
|
||||
[ 'ㅐ', [ '깨', 0 ] ],
|
||||
[ 'ㄲ', [ '깪', 0 ] ],
|
||||
[ 'ㅡ', [ '끄', 1 ] ],
|
||||
[ 'ㅅ', [ '끗', 1 ] ]
|
||||
]
|
||||
},
|
||||
{
|
||||
info: 'should return 밥&미 when type "ㅂㅏㅂ&ㅁㅣ"',
|
||||
scenario: [
|
||||
[ 'ㅂ', [ 'ㅂ', 0 ] ],
|
||||
[ 'ㅏ', [ '바', 0 ] ],
|
||||
[ 'ㅂ', [ '밥', 0 ] ],
|
||||
[ '&', [ '&', 1 ] ],
|
||||
[ 'ㅁ', [ 'ㅁ', 1 ] ],
|
||||
[ 'ㅣ', [ '미', 1 ] ]
|
||||
]
|
||||
},
|
||||
{
|
||||
info: 'should return 미&밥 when type "ㅁㅣ&ㅂㅏㅂ"',
|
||||
scenario: [
|
||||
[ 'ㅁ', [ 'ㅁ', 0 ] ],
|
||||
[ 'ㅣ', [ '미', 0 ] ],
|
||||
[ '&', [ '&', 1 ] ],
|
||||
[ 'ㅂ', [ 'ㅂ', 1 ] ],
|
||||
[ 'ㅏ', [ '바', 1 ] ],
|
||||
[ 'ㅂ', [ '밥', 1 ] ]
|
||||
]
|
||||
},
|
||||
{
|
||||
info: 'should return 뷁 when type "ㅂㅜㅔㄹㄱ"',
|
||||
scenario: [
|
||||
[ 'ㅂ', [ 'ㅂ', 0 ] ],
|
||||
[ 'ㅜ', [ '부', 0 ] ],
|
||||
[ 'ㅔ', [ '붸', 0 ] ],
|
||||
[ 'ㄹ', [ '뷀', 0 ] ],
|
||||
[ 'ㄱ', [ '뷁', 0 ] ]
|
||||
]
|
||||
},
|
||||
{
|
||||
info: 'should return 한 글 when type "ㅎㅏㄴ[SPACE]ㄱㅡㄹ"',
|
||||
scenario: [
|
||||
[ 'ㅎ', [ 'ㅎ', 0 ] ],
|
||||
[ 'ㅏ', [ '하', 0 ] ],
|
||||
[ 'ㄴ', [ '한', 0 ] ],
|
||||
[ SPACE ,[ ' ', 1 ] ],
|
||||
[ 'ㄱ', [ 'ㄱ', 1 ] ],
|
||||
[ 'ㅡ', [ '그', 1 ] ],
|
||||
[ 'ㄹ', [ '글', 1 ] ]
|
||||
]
|
||||
},
|
||||
{
|
||||
info: 'should return ㄱㄴㄷㄹㅁㅂ when type "ㄱㄴㄷㄹㅁㅂ"',
|
||||
scenario: [
|
||||
[ 'ㄱ', [ 'ㄱ', 0 ] ],
|
||||
[ 'ㄴ', [ 'ㄴ', 1 ] ],
|
||||
[ 'ㄷ', [ 'ㄷ', 2 ] ],
|
||||
[ 'ㄹ', [ 'ㄹ', 3 ] ],
|
||||
[ 'ㅁ', [ 'ㅁ', 4 ] ]
|
||||
]
|
||||
},
|
||||
{
|
||||
info: 'should return 사랑 when type "ㅅㅏㄹㅏㅇ"',
|
||||
scenario: [
|
||||
[ 'ㅅ', [ 'ㅅ', 0 ] ],
|
||||
[ 'ㅏ', [ '사', 0 ] ],
|
||||
[ 'ㄹ', [ '살', 0 ] ],
|
||||
[ 'ㅏ', [ '라', 1 ] ],
|
||||
[ 'ㅇ', [ '랑', 1 ] ]
|
||||
]
|
||||
},
|
||||
{
|
||||
info: 'should return 장미 when type "ㅈㅏㅇㅁㅣ"',
|
||||
scenario: [
|
||||
[ 'ㅈ', [ 'ㅈ', 0 ] ],
|
||||
[ 'ㅏ', [ '자', 0 ] ],
|
||||
[ 'ㅇ', [ '장', 0 ] ],
|
||||
[ 'ㅁ', [ 'ㅁ', 1 ] ],
|
||||
[ 'ㅣ', [ '미', 1 ] ]
|
||||
]
|
||||
},
|
||||
{
|
||||
info: 'should return 어ㅠㅠ when type "ㅇㅓㅠㅠ"',
|
||||
scenario: [
|
||||
[ 'ㅇ', [ 'ㅇ', 0 ] ],
|
||||
[ 'ㅓ', [ '어', 0 ] ],
|
||||
[ 'ㅠ', [ 'ㅠ', 1 ] ],
|
||||
[ 'ㅠ', [ 'ㅠ', 2 ] ]
|
||||
]
|
||||
},
|
||||
{
|
||||
info: 'should 깎 rightly write and delete by jamo.',
|
||||
scenario: [
|
||||
[ 'ㄱ', [ 'ㄱ' , 0 ] ],
|
||||
[ 'ㄱ', [ 'ㄲ' , 0 ] ],
|
||||
[ 'ㅏ', [ '까' , 0 ] ],
|
||||
[ 'ㄲ', [ '깎' , 0 ] ],
|
||||
[ BACKSPACE, [ '까', 0 ] ],
|
||||
[ BACKSPACE, [ 'ㄲ', 0 ] ],
|
||||
[ BACKSPACE, [ 'ㄱ', 0 ] ],
|
||||
[ BACKSPACE, [ '', 0 ] ]
|
||||
]
|
||||
},
|
||||
{
|
||||
info: 'should ㄱ rightly write and delete 2 times.',
|
||||
scenario: [
|
||||
[ 'ㄱ', [ 'ㄱ', 0 ] ],
|
||||
[ BACKSPACE, [ '', 0 ] ],
|
||||
[ 'ㄱ', [ 'ㄱ', 0 ] ],
|
||||
[ BACKSPACE, [ '', 0 ] ]
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
setup(function() {
|
||||
im.deactivate();
|
||||
testableKeyboardGlue._ready();
|
||||
});
|
||||
|
||||
cases.forEach(function(theCase) {
|
||||
var msg = theCase.info;
|
||||
var clicksAndExpects = theCase.scenario;
|
||||
|
||||
test(msg, function() {
|
||||
for(var i = 0; i < clicksAndExpects.length; i++) {
|
||||
var clickAndThen = clicksAndExpects[i];
|
||||
var input = clickAndThen[0];
|
||||
var charCode = (typeof input === 'number') ?
|
||||
input : input.charCodeAt(0);
|
||||
im.click(charCode);
|
||||
assert.deepEqual(testableKeyboardGlue._status(), clickAndThen[1]);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
92
gaia/apps/keyboard/test/unit/keyboard/abortable_promise_queue_test.js
Executable file
92
gaia/apps/keyboard/test/unit/keyboard/abortable_promise_queue_test.js
Executable file
|
|
@ -0,0 +1,92 @@
|
|||
'use strict';
|
||||
|
||||
/* global AbortablePromiseQueue */
|
||||
|
||||
require('/js/keyboard/abortable_promise_queue.js');
|
||||
|
||||
suite('AbortablePromiseQueue', function() {
|
||||
var queue;
|
||||
|
||||
setup(function() {
|
||||
queue = new AbortablePromiseQueue();
|
||||
queue.start();
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
queue.run([function() {
|
||||
queue.stop();
|
||||
done();
|
||||
}]);
|
||||
});
|
||||
|
||||
test('run()', function(done) {
|
||||
var tasks = [
|
||||
this.sinon.stub(),
|
||||
this.sinon.stub(),
|
||||
this.sinon.stub(),
|
||||
this.sinon.stub(),
|
||||
this.sinon.stub(),
|
||||
this.sinon.stub()
|
||||
];
|
||||
|
||||
var p = queue.run(tasks);
|
||||
p.then(function() {
|
||||
tasks.forEach(function(stub, i) {
|
||||
assert.isTrue(stub.calledOnce);
|
||||
if (i !== 0) {
|
||||
assert.isTrue(tasks[i - 1].calledBefore(stub));
|
||||
}
|
||||
});
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('abort()', function(done) {
|
||||
var tasks = [
|
||||
this.sinon.stub(),
|
||||
this.sinon.stub(),
|
||||
function() {
|
||||
queue.abort();
|
||||
},
|
||||
this.sinon.stub(),
|
||||
this.sinon.stub(),
|
||||
this.sinon.stub()
|
||||
];
|
||||
|
||||
var p = queue.run(tasks);
|
||||
p.then(function() {
|
||||
assert.isTrue(tasks[0].calledOnce);
|
||||
assert.isTrue(tasks[1].calledOnce);
|
||||
assert.isFalse(tasks[3].calledOnce);
|
||||
assert.isFalse(tasks[4].calledOnce);
|
||||
assert.isFalse(tasks[5].calledOnce);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('run() another task', function(done) {
|
||||
var tasks2 = [
|
||||
this.sinon.stub()
|
||||
];
|
||||
|
||||
var tasks = [
|
||||
this.sinon.stub(),
|
||||
this.sinon.stub(),
|
||||
function() {
|
||||
queue.run(tasks2).then(function() {
|
||||
assert.isTrue(tasks[0].calledOnce);
|
||||
assert.isTrue(tasks[1].calledOnce);
|
||||
assert.isFalse(tasks[3].calledOnce);
|
||||
assert.isFalse(tasks[4].calledOnce);
|
||||
assert.isFalse(tasks[5].calledOnce);
|
||||
|
||||
assert.isTrue(tasks2[0].calledOnce);
|
||||
assert.isTrue(tasks[1].calledBefore(tasks2[0]));
|
||||
}).then(done, done);
|
||||
},
|
||||
this.sinon.stub(),
|
||||
this.sinon.stub(),
|
||||
this.sinon.stub()
|
||||
];
|
||||
|
||||
queue.run(tasks);
|
||||
});
|
||||
});
|
||||
746
gaia/apps/keyboard/test/unit/keyboard/active_targets_manager_test.js
Executable file
746
gaia/apps/keyboard/test/unit/keyboard/active_targets_manager_test.js
Executable file
|
|
@ -0,0 +1,746 @@
|
|||
'use strict';
|
||||
|
||||
/* global ActiveTargetsManager, AlternativesCharMenuManager, UserPressManager,
|
||||
KeyboardConsole */
|
||||
|
||||
require('/js/keyboard/active_targets_manager.js');
|
||||
require('/js/keyboard/alternatives_char_menu_manager.js');
|
||||
require('/js/keyboard/handwriting_pads_manager.js');
|
||||
require('/js/keyboard/user_press_manager.js');
|
||||
require('/js/keyboard/console.js');
|
||||
|
||||
suite('ActiveTargetsManager', function() {
|
||||
var app;
|
||||
var manager;
|
||||
var userPressManagerStub;
|
||||
var alternativesCharMenuManagerStub;
|
||||
|
||||
setup(function() {
|
||||
// Stub the depending constructors and have them returns a stub instance.
|
||||
alternativesCharMenuManagerStub =
|
||||
this.sinon.stub(AlternativesCharMenuManager.prototype);
|
||||
this.sinon.stub(window, 'AlternativesCharMenuManager')
|
||||
.returns(alternativesCharMenuManagerStub);
|
||||
userPressManagerStub = this.sinon.stub(UserPressManager.prototype);
|
||||
this.sinon.stub(window, 'UserPressManager').returns(userPressManagerStub);
|
||||
|
||||
this.sinon.stub(window, 'setTimeout', function () {
|
||||
return 1;
|
||||
});
|
||||
this.sinon.stub(window, 'clearTimeout');
|
||||
|
||||
app = {
|
||||
console: this.sinon.stub(KeyboardConsole.prototype)
|
||||
};
|
||||
|
||||
manager = new ActiveTargetsManager(app);
|
||||
manager.ontargetactivated = this.sinon.stub();
|
||||
manager.ontargetlongpressed = this.sinon.stub();
|
||||
manager.ontargetmoved = this.sinon.stub();
|
||||
manager.ontargetmovedout = this.sinon.stub();
|
||||
manager.ontargetmovedin = this.sinon.stub();
|
||||
manager.ontargetcommitted = this.sinon.stub();
|
||||
manager.ontargetcancelled = this.sinon.stub();
|
||||
manager.ontargetdoubletapped = this.sinon.stub();
|
||||
manager.onnewtargetwillactivate = this.sinon.stub();
|
||||
manager.start();
|
||||
|
||||
assert.isTrue(window.UserPressManager.calledWithNew());
|
||||
assert.isTrue(window.UserPressManager.calledWith(app));
|
||||
assert.isTrue(userPressManagerStub.start.calledOnce);
|
||||
|
||||
assert.isTrue(window.AlternativesCharMenuManager.calledWithNew());
|
||||
assert.isTrue(window.AlternativesCharMenuManager.calledWith(app));
|
||||
assert.isTrue(alternativesCharMenuManagerStub.start.calledOnce);
|
||||
});
|
||||
|
||||
test('stop', function() {
|
||||
manager.stop();
|
||||
|
||||
assert.isTrue(userPressManagerStub.stop.calledOnce);
|
||||
assert.isTrue(alternativesCharMenuManagerStub.stop.calledOnce);
|
||||
assert.isTrue(window.clearTimeout.calledOnce);
|
||||
});
|
||||
|
||||
suite('start first press', function() {
|
||||
var id0 = 0;
|
||||
var press0 = {
|
||||
target: {
|
||||
text: '1',
|
||||
}
|
||||
};
|
||||
|
||||
setup(function() {
|
||||
alternativesCharMenuManagerStub.isShown = false;
|
||||
|
||||
userPressManagerStub.onpressstart(press0, id0);
|
||||
|
||||
assert.isTrue(
|
||||
manager.ontargetactivated.calledWith(press0.target, press0));
|
||||
assert.isTrue(window.clearTimeout.calledOnce);
|
||||
assert.isTrue(window.setTimeout.calledOnce);
|
||||
assert.equal(
|
||||
window.setTimeout.getCall(0).args[1], manager.LONG_PRESS_TIMEOUT);
|
||||
});
|
||||
|
||||
test('press move (ignore same target), press end', function() {
|
||||
userPressManagerStub.onpressmove(press0, id0);
|
||||
|
||||
assert.isFalse(window.clearTimeout.calledTwice);
|
||||
assert.isFalse(window.setTimeout.calledTwice);
|
||||
assert.isFalse(manager.ontargetmovedout.called);
|
||||
assert.isFalse(manager.ontargetmovedin.called);
|
||||
assert.isFalse(alternativesCharMenuManagerStub.hide.calledTwice);
|
||||
assert.isTrue(
|
||||
manager.ontargetmoved.calledWith(press0.target, press0));
|
||||
|
||||
var pressEnd = {
|
||||
target: {
|
||||
text: 'e'
|
||||
}
|
||||
};
|
||||
userPressManagerStub.onpressend(pressEnd, id0);
|
||||
|
||||
assert.isTrue(alternativesCharMenuManagerStub.hide.calledOnce);
|
||||
assert.isTrue(
|
||||
manager.ontargetcommitted.calledWith(press0.target));
|
||||
assert.isTrue(window.clearTimeout.calledThrice);
|
||||
});
|
||||
|
||||
test('press end', function() {
|
||||
var pressEnd = {
|
||||
target: {
|
||||
text: 'e'
|
||||
}
|
||||
};
|
||||
userPressManagerStub.onpressend(pressEnd, id0);
|
||||
|
||||
assert.isTrue(alternativesCharMenuManagerStub.hide.calledOnce);
|
||||
assert.isTrue(
|
||||
manager.ontargetcommitted.calledWith(press0.target));
|
||||
assert.isTrue(window.clearTimeout.calledThrice);
|
||||
});
|
||||
|
||||
test('press end (moved press)', function() {
|
||||
var pressEnd = {
|
||||
target: {
|
||||
text: 'e'
|
||||
},
|
||||
moved: true
|
||||
};
|
||||
userPressManagerStub.onpressend(pressEnd, id0);
|
||||
|
||||
assert.isTrue(alternativesCharMenuManagerStub.hide.calledOnce);
|
||||
assert.isTrue(
|
||||
manager.ontargetcommitted.calledWith(press0.target));
|
||||
assert.isTrue(window.clearTimeout.calledThrice);
|
||||
});
|
||||
|
||||
test('press move to handwriting pad', function() {
|
||||
var pressMove = {
|
||||
target: {
|
||||
isHandwritingPad: true
|
||||
},
|
||||
moved: true
|
||||
};
|
||||
userPressManagerStub.onpressmove(pressMove, id0);
|
||||
|
||||
assert.isTrue(window.clearTimeout.calledTwice);
|
||||
assert.isTrue(window.setTimeout.calledTwice);
|
||||
assert.isTrue(manager.ontargetmovedout.called);
|
||||
assert.isTrue(manager.ontargetmovedin.called);
|
||||
assert.isTrue(alternativesCharMenuManagerStub.hide.calledOnce);
|
||||
});
|
||||
|
||||
test('double tap (within DOUBLE_TAP_TIMEOUT)', function() {
|
||||
var pressEnd = {
|
||||
target: press0.target
|
||||
};
|
||||
userPressManagerStub.onpressend(pressEnd, id0);
|
||||
|
||||
assert.isTrue(manager.ontargetcommitted.calledOnce);
|
||||
assert.isFalse(manager.ontargetdoubletapped.calledOnce,
|
||||
'ontargetdoubletapped should not be called yet.');
|
||||
assert.isTrue(
|
||||
manager.ontargetcommitted.calledWith(press0.target));
|
||||
|
||||
userPressManagerStub.onpressstart(press0, id0);
|
||||
userPressManagerStub.onpressend(pressEnd, id0);
|
||||
|
||||
assert.isTrue(manager.ontargetcommitted.calledOnce);
|
||||
assert.isTrue(manager.ontargetdoubletapped.calledOnce);
|
||||
assert.isTrue(
|
||||
manager.ontargetdoubletapped.calledWith(press0.target));
|
||||
});
|
||||
|
||||
test('double tap (after DOUBLE_TAP_TIMEOUT)', function() {
|
||||
var pressEnd = {
|
||||
target: press0.target
|
||||
};
|
||||
userPressManagerStub.onpressend(pressEnd, id0);
|
||||
|
||||
assert.isTrue(alternativesCharMenuManagerStub.hide.calledOnce);
|
||||
assert.isTrue(manager.ontargetcommitted.calledOnce);
|
||||
assert.isFalse(manager.ontargetdoubletapped.calledOnce);
|
||||
assert.isTrue(
|
||||
manager.ontargetcommitted.calledWith(press0.target));
|
||||
assert.equal(window.setTimeout.getCall(1).args[1],
|
||||
manager.DOUBLE_TAP_TIMEOUT);
|
||||
window.setTimeout.getCall(1).args[0].call(window);
|
||||
|
||||
userPressManagerStub.onpressstart(press0, id0);
|
||||
userPressManagerStub.onpressend(pressEnd, id0);
|
||||
|
||||
assert.isTrue(manager.ontargetcommitted.calledTwice);
|
||||
assert.isFalse(manager.ontargetdoubletapped.calledOnce,
|
||||
'ontargetdoubletapped should not be called.');
|
||||
assert.isTrue(
|
||||
manager.ontargetcommitted.getCall(1).calledWith(press0.target));
|
||||
});
|
||||
|
||||
test('double tap (with an intermediate different target)', function() {
|
||||
var pressEnd = {
|
||||
target: press0.target
|
||||
};
|
||||
userPressManagerStub.onpressend(pressEnd, id0);
|
||||
|
||||
assert.isTrue(alternativesCharMenuManagerStub.hide.calledOnce);
|
||||
assert.isTrue(manager.ontargetcommitted.calledOnce);
|
||||
assert.isFalse(manager.ontargetdoubletapped.calledOnce);
|
||||
assert.isTrue(
|
||||
manager.ontargetcommitted.calledWith(press0.target));
|
||||
assert.equal(window.setTimeout.getCall(1).args[1],
|
||||
manager.DOUBLE_TAP_TIMEOUT);
|
||||
|
||||
// Touch another target in the middle
|
||||
var press1 = {
|
||||
target: {
|
||||
text: '2'
|
||||
}
|
||||
};
|
||||
|
||||
userPressManagerStub.onpressstart(press1, id0);
|
||||
userPressManagerStub.onpressend(press1, id0);
|
||||
|
||||
assert.isTrue(manager.ontargetcommitted.calledTwice);
|
||||
assert.isTrue(
|
||||
manager.ontargetcommitted.getCall(1).calledWith(press1.target));
|
||||
|
||||
userPressManagerStub.onpressstart(press0, id0);
|
||||
userPressManagerStub.onpressend(pressEnd, id0);
|
||||
|
||||
assert.isTrue(manager.ontargetcommitted.calledThrice,
|
||||
'ontargetcommitted should be called for the 3rd time.');
|
||||
assert.isFalse(manager.ontargetdoubletapped.calledOnce,
|
||||
'ontargetdoubletapped should not be called.');
|
||||
assert.isTrue(
|
||||
manager.ontargetcommitted.getCall(2).calledWith(press0.target));
|
||||
});
|
||||
|
||||
test('tapping on a different target will clear ' +
|
||||
'the double tap timer', function() {
|
||||
var pressEnd = {
|
||||
target: press0.target
|
||||
};
|
||||
userPressManagerStub.onpressend(pressEnd, id0);
|
||||
|
||||
assert.isTrue(alternativesCharMenuManagerStub.hide.calledOnce);
|
||||
assert.isTrue(manager.ontargetcommitted.calledOnce);
|
||||
assert.isFalse(manager.ontargetdoubletapped.calledOnce);
|
||||
assert.isTrue(
|
||||
manager.ontargetcommitted.calledWith(press0.target));
|
||||
assert.equal(window.setTimeout.getCall(1).args[1],
|
||||
manager.DOUBLE_TAP_TIMEOUT);
|
||||
|
||||
// Touch another target
|
||||
var press1 = {
|
||||
target: {
|
||||
text: '2'
|
||||
}
|
||||
};
|
||||
|
||||
userPressManagerStub.onpressstart(press1, id0);
|
||||
userPressManagerStub.onpressend(press1, id0);
|
||||
|
||||
assert.equal(window.clearTimeout.callCount, 6);
|
||||
|
||||
assert.isTrue(manager.ontargetcommitted.calledTwice);
|
||||
assert.isTrue(
|
||||
manager.ontargetcommitted.getCall(1).calledWith(press1.target));
|
||||
});
|
||||
|
||||
test('triple tap (within DOUBLE_TAP_TIMEOUT)', function() {
|
||||
var pressEnd = {
|
||||
target: press0.target
|
||||
};
|
||||
userPressManagerStub.onpressend(pressEnd, id0);
|
||||
|
||||
assert.isTrue(manager.ontargetcommitted.calledOnce);
|
||||
assert.isFalse(manager.ontargetdoubletapped.calledOnce);
|
||||
assert.isTrue(
|
||||
manager.ontargetcommitted.calledWith(press0.target));
|
||||
|
||||
userPressManagerStub.onpressstart(press0, id0);
|
||||
userPressManagerStub.onpressend(pressEnd, id0);
|
||||
|
||||
assert.isTrue(manager.ontargetcommitted.calledOnce);
|
||||
assert.isTrue(manager.ontargetdoubletapped.calledOnce);
|
||||
assert.isTrue(
|
||||
manager.ontargetdoubletapped.calledWith(press0.target));
|
||||
|
||||
userPressManagerStub.onpressstart(press0, id0);
|
||||
userPressManagerStub.onpressend(pressEnd, id0);
|
||||
|
||||
assert.isTrue(manager.ontargetcommitted.calledTwice);
|
||||
assert.isTrue(manager.ontargetdoubletapped.calledOnce,
|
||||
'ontargetdoubletapped should not be called again.');
|
||||
assert.isTrue(
|
||||
manager.ontargetcommitted.getCall(1).calledWith(press0.target));
|
||||
});
|
||||
|
||||
suite('clearAllTargets', function() {
|
||||
setup(function() {
|
||||
manager.clearAllTargets();
|
||||
|
||||
assert.isTrue(alternativesCharMenuManagerStub.hide.calledOnce);
|
||||
assert.isTrue(
|
||||
manager.ontargetcancelled.calledWith(press0.target));
|
||||
assert.isTrue(window.clearTimeout.calledTwice);
|
||||
});
|
||||
|
||||
test('ignore press end', function() {
|
||||
var pressEnd = {
|
||||
target: {
|
||||
text: 'e'
|
||||
}
|
||||
};
|
||||
userPressManagerStub.onpressend(pressEnd, id0);
|
||||
|
||||
assert.isFalse(manager.ontargetcommitted.called);
|
||||
assert.isFalse(alternativesCharMenuManagerStub.hide.calledTwice);
|
||||
});
|
||||
|
||||
suite('ignore press moved', function() {
|
||||
setup(function() {
|
||||
var pressMove = {
|
||||
target: {
|
||||
text: 'm'
|
||||
}
|
||||
};
|
||||
|
||||
alternativesCharMenuManagerStub.isMenuTarget.returns(false);
|
||||
alternativesCharMenuManagerStub.isInMenuArea.returns(false);
|
||||
|
||||
userPressManagerStub.onpressmove(pressMove, id0);
|
||||
|
||||
assert.isFalse(manager.ontargetmovedout.called);
|
||||
assert.isFalse(manager.ontargetmovedin.called);
|
||||
assert.isFalse(alternativesCharMenuManagerStub.hide.calledTwice);
|
||||
});
|
||||
|
||||
test('ignore press end', function() {
|
||||
var pressEnd = {
|
||||
target: {
|
||||
text: 'e'
|
||||
}
|
||||
};
|
||||
userPressManagerStub.onpressend(pressEnd, id0);
|
||||
|
||||
assert.isFalse(manager.ontargetcommitted.called);
|
||||
assert.isFalse(alternativesCharMenuManagerStub.hide.calledTwice);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('trigger long press, without alt char menu', function() {
|
||||
setup(function() {
|
||||
window.setTimeout.getCall(0).args[0].call(window);
|
||||
|
||||
assert.isTrue(
|
||||
manager.ontargetlongpressed.calledWith(press0.target));
|
||||
assert.isTrue(
|
||||
alternativesCharMenuManagerStub.show.calledWith(press0.target));
|
||||
});
|
||||
|
||||
test('press end', function() {
|
||||
var pressEnd = {
|
||||
target: {
|
||||
text: 'e'
|
||||
}
|
||||
};
|
||||
userPressManagerStub.onpressend(pressEnd, id0);
|
||||
|
||||
assert.isTrue(alternativesCharMenuManagerStub.hide.calledOnce);
|
||||
assert.isTrue(
|
||||
manager.ontargetcommitted.calledWith(press0.target));
|
||||
assert.isTrue(window.clearTimeout.calledThrice);
|
||||
});
|
||||
});
|
||||
|
||||
suite('trigger long press, with alt char menu', function() {
|
||||
var altTarget;
|
||||
|
||||
setup(function() {
|
||||
altTarget = {
|
||||
text: 'a'
|
||||
};
|
||||
|
||||
alternativesCharMenuManagerStub.isShown = true;
|
||||
alternativesCharMenuManagerStub
|
||||
.isInMenuArea.withArgs(press0).returns(true);
|
||||
alternativesCharMenuManagerStub.getMenuTarget.returns(altTarget);
|
||||
alternativesCharMenuManagerStub
|
||||
.isMenuTarget.withArgs(altTarget).returns(true);
|
||||
|
||||
window.setTimeout.getCall(0).args[0].call(window);
|
||||
|
||||
assert.isTrue(
|
||||
manager.ontargetlongpressed.calledWith(press0.target));
|
||||
assert.isTrue(
|
||||
manager.ontargetmovedout.calledWith(press0.target));
|
||||
assert.isTrue(
|
||||
manager.ontargetmovedin.calledWith(altTarget));
|
||||
});
|
||||
|
||||
test('press end', function() {
|
||||
var pressEnd = {
|
||||
target: {
|
||||
text: 'e'
|
||||
}
|
||||
};
|
||||
userPressManagerStub.onpressend(pressEnd, id0);
|
||||
|
||||
assert.isTrue(alternativesCharMenuManagerStub.hide.calledOnce);
|
||||
assert.isTrue(
|
||||
manager.ontargetcommitted.calledWith(altTarget));
|
||||
assert.equal(window.clearTimeout.callCount, 4);
|
||||
});
|
||||
|
||||
suite('ignore second press', function() {
|
||||
var id1 = 1;
|
||||
var press1 = {
|
||||
target: {
|
||||
text: '2'
|
||||
}
|
||||
};
|
||||
|
||||
setup(function() {
|
||||
userPressManagerStub.onpressstart(press1, id1);
|
||||
|
||||
assert.isFalse(manager.ontargetactivated.calledTwice);
|
||||
assert.isFalse(window.clearTimeout.calledThrice);
|
||||
assert.isFalse(window.setTimeout.calledThrice);
|
||||
});
|
||||
|
||||
test('ignore second press, press end first press', function() {
|
||||
var pressMove = {
|
||||
target: {
|
||||
text: 'm'
|
||||
}
|
||||
};
|
||||
userPressManagerStub.onpressmove(pressMove, id1);
|
||||
|
||||
assert.isFalse(manager.ontargetmovedout.calledTwice);
|
||||
assert.isFalse(manager.ontargetmovedin.calledTwice);
|
||||
|
||||
var pressEnd = {
|
||||
target: {
|
||||
text: 'e'
|
||||
}
|
||||
};
|
||||
userPressManagerStub.onpressend(pressEnd, id1);
|
||||
|
||||
assert.isFalse(manager.ontargetcommitted.called);
|
||||
|
||||
var pressEnd2 = {
|
||||
target: {
|
||||
text: 'e'
|
||||
}
|
||||
};
|
||||
userPressManagerStub.onpressend(pressEnd2, id0);
|
||||
|
||||
assert.isTrue(alternativesCharMenuManagerStub.hide.calledOnce);
|
||||
assert.isTrue(
|
||||
manager.ontargetcommitted.calledWith(altTarget));
|
||||
assert.equal(window.clearTimeout.callCount, 4);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('press moved', function() {
|
||||
var movedTarget;
|
||||
var pressMove;
|
||||
|
||||
setup(function() {
|
||||
alternativesCharMenuManagerStub.isMenuTarget.returns(false);
|
||||
alternativesCharMenuManagerStub.isInMenuArea.returns(false);
|
||||
|
||||
var oldTarget = press0.target;
|
||||
movedTarget = {
|
||||
text: 'm'
|
||||
};
|
||||
pressMove = {
|
||||
target: movedTarget
|
||||
};
|
||||
|
||||
userPressManagerStub.onpressmove(pressMove, id0);
|
||||
|
||||
assert.isTrue(
|
||||
manager.ontargetmovedout.calledWith(oldTarget));
|
||||
assert.isTrue(
|
||||
manager.ontargetmovedin.calledWith(movedTarget));
|
||||
assert.isTrue(alternativesCharMenuManagerStub.hide.calledOnce);
|
||||
assert.isTrue(window.clearTimeout.calledTwice);
|
||||
assert.isTrue(window.setTimeout.calledTwice);
|
||||
});
|
||||
|
||||
test('press end', function() {
|
||||
var pressEnd = {
|
||||
target: {
|
||||
text: 'e'
|
||||
}
|
||||
};
|
||||
userPressManagerStub.onpressend(pressEnd, id0);
|
||||
|
||||
assert.isTrue(alternativesCharMenuManagerStub.hide.calledTwice);
|
||||
assert.isTrue(
|
||||
manager.ontargetcommitted.calledWith(movedTarget));
|
||||
assert.equal(window.clearTimeout.callCount, 4);
|
||||
});
|
||||
|
||||
suite('trigger long press, without alt char menu', function() {
|
||||
setup(function() {
|
||||
window.setTimeout.getCall(1).args[0].call(window);
|
||||
|
||||
assert.isTrue(
|
||||
manager.ontargetlongpressed.calledWith(movedTarget));
|
||||
assert.isTrue(
|
||||
alternativesCharMenuManagerStub.show.calledWith(movedTarget));
|
||||
});
|
||||
|
||||
test('press end', function() {
|
||||
var pressEnd = {
|
||||
target: {
|
||||
text: 'e'
|
||||
}
|
||||
};
|
||||
userPressManagerStub.onpressend(pressEnd, id0);
|
||||
|
||||
assert.isTrue(alternativesCharMenuManagerStub.hide.calledTwice);
|
||||
assert.isTrue(
|
||||
manager.ontargetcommitted.calledWith(movedTarget));
|
||||
assert.equal(window.clearTimeout.callCount, 4);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('start second press', function() {
|
||||
var id1 = 1;
|
||||
var press1 = {
|
||||
target: {
|
||||
text: '2'
|
||||
}
|
||||
};
|
||||
|
||||
setup(function() {
|
||||
userPressManagerStub.onpressstart(press1, id1);
|
||||
|
||||
assert.isTrue(manager.onnewtargetwillactivate.calledWith(press0.target),
|
||||
'onnewtargetwillactivate called.');
|
||||
|
||||
assert.isTrue(manager.ontargetactivated.calledWith(press1.target));
|
||||
|
||||
assert.equal(window.clearTimeout.callCount, 2);
|
||||
assert.equal(window.setTimeout.callCount, 1);
|
||||
});
|
||||
|
||||
test('press end second press, and first press', function() {
|
||||
var pressEnd = {
|
||||
target: {
|
||||
text: 'e'
|
||||
}
|
||||
};
|
||||
userPressManagerStub.onpressend(pressEnd, id1);
|
||||
|
||||
assert.isTrue(alternativesCharMenuManagerStub.hide.calledOnce);
|
||||
assert.isTrue(
|
||||
manager.ontargetcommitted.calledWith(press1.target));
|
||||
assert.equal(window.clearTimeout.callCount, 4);
|
||||
|
||||
var pressEnd2 = {
|
||||
target: {
|
||||
text: 'e'
|
||||
}
|
||||
};
|
||||
userPressManagerStub.onpressend(pressEnd2, id0);
|
||||
|
||||
assert.isTrue(alternativesCharMenuManagerStub.hide.calledTwice,
|
||||
'No additional calls to hide()');
|
||||
assert.isTrue(manager.ontargetcommitted.calledTwice,
|
||||
'No additional calls to ontargetcommitted');
|
||||
});
|
||||
|
||||
test('press end first press, and second press', function() {
|
||||
var pressEnd = {
|
||||
target: {
|
||||
text: 'e'
|
||||
}
|
||||
};
|
||||
userPressManagerStub.onpressend(pressEnd, id0);
|
||||
|
||||
assert.isTrue(alternativesCharMenuManagerStub.hide.calledOnce);
|
||||
assert.isTrue(
|
||||
manager.ontargetcommitted.calledWith(press0.target));
|
||||
assert.equal(window.clearTimeout.callCount, 4);
|
||||
|
||||
var pressEnd2 = {
|
||||
target: {
|
||||
text: 'e'
|
||||
}
|
||||
};
|
||||
userPressManagerStub.onpressend(pressEnd2, id1);
|
||||
|
||||
assert.isTrue(alternativesCharMenuManagerStub.hide.calledTwice,
|
||||
'No additional calls to hide()');
|
||||
assert.isTrue(manager.ontargetcommitted.calledTwice,
|
||||
'No additional calls to ontargetcommitted');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('start first press (on a selection)', function() {
|
||||
var id0 = 0;
|
||||
var press0 = {
|
||||
target: {
|
||||
text: '1',
|
||||
selection: 'true'
|
||||
}
|
||||
};
|
||||
|
||||
setup(function() {
|
||||
alternativesCharMenuManagerStub.isShown = false;
|
||||
|
||||
userPressManagerStub.onpressstart(press0, id0);
|
||||
|
||||
assert.isTrue(
|
||||
manager.ontargetactivated.calledWith(press0.target));
|
||||
assert.isTrue(window.clearTimeout.calledOnce);
|
||||
assert.isTrue(window.setTimeout.calledOnce);
|
||||
assert.equal(
|
||||
window.setTimeout.getCall(0).args[1], manager.LONG_PRESS_TIMEOUT);
|
||||
});
|
||||
|
||||
test('press end', function() {
|
||||
var pressEnd = {
|
||||
target: press0.target
|
||||
};
|
||||
userPressManagerStub.onpressend(pressEnd, id0);
|
||||
|
||||
assert.isTrue(alternativesCharMenuManagerStub.hide.calledOnce);
|
||||
assert.isTrue(
|
||||
manager.ontargetcommitted.calledWith(press0.target));
|
||||
assert.isTrue(window.clearTimeout.calledThrice);
|
||||
});
|
||||
|
||||
test('press move and press end (moved press)', function() {
|
||||
var pressMove = {
|
||||
target: press0.target,
|
||||
moved: true
|
||||
};
|
||||
userPressManagerStub.onpressmove(pressMove, id0);
|
||||
|
||||
var pressEnd = {
|
||||
target: press0.target,
|
||||
moved: true
|
||||
};
|
||||
userPressManagerStub.onpressend(pressEnd, id0);
|
||||
|
||||
assert.isTrue(alternativesCharMenuManagerStub.hide.calledOnce);
|
||||
assert.isTrue(
|
||||
manager.ontargetcancelled.calledWith(press0.target),
|
||||
'target should be cancelled.');
|
||||
assert.isTrue(window.clearTimeout.calledTwice);
|
||||
});
|
||||
});
|
||||
|
||||
suite('start first press on handwriting pad', function() {
|
||||
var id0 = 0;
|
||||
var press0 = {
|
||||
target: {
|
||||
isHandwritingPad: true
|
||||
}
|
||||
};
|
||||
|
||||
setup(function() {
|
||||
alternativesCharMenuManagerStub.isShown = false;
|
||||
|
||||
userPressManagerStub.onpressstart(press0, id0);
|
||||
|
||||
assert.isTrue(
|
||||
manager.ontargetactivated.calledWith(press0.target));
|
||||
assert.isTrue(window.clearTimeout.calledOnce);
|
||||
assert.isTrue(window.setTimeout.calledOnce);
|
||||
assert.equal(
|
||||
window.setTimeout.getCall(0).args[1], manager.LONG_PRESS_TIMEOUT);
|
||||
|
||||
manager.blockNewUserPress = true;
|
||||
manager.blockTargetMovedOut = true;
|
||||
});
|
||||
|
||||
test('ignore new press after start handwriting', function() {
|
||||
var newPress = {
|
||||
target: {
|
||||
text: 'n'
|
||||
}
|
||||
};
|
||||
var id1 = 1;
|
||||
|
||||
userPressManagerStub.onpressstart(newPress, id1);
|
||||
assert.isFalse(
|
||||
manager.ontargetactivated.calledWith(newPress.target));
|
||||
assert.isFalse(window.clearTimeout.calledTwice);
|
||||
assert.isFalse(window.setTimeout.calledTwice);
|
||||
});
|
||||
|
||||
test('press move on handwriting pad, press end', function() {
|
||||
userPressManagerStub.onpressmove(press0, id0);
|
||||
|
||||
assert.isFalse(window.setTimeout.calledTwice);
|
||||
assert.isFalse(manager.ontargetmovedout.called);
|
||||
assert.isTrue(manager.ontargetmoved.called);
|
||||
assert.isFalse(manager.ontargetmovedin.called);
|
||||
assert.isFalse(alternativesCharMenuManagerStub.hide.called);
|
||||
|
||||
// Press end
|
||||
userPressManagerStub.onpressend(press0, id0);
|
||||
|
||||
assert.isTrue(alternativesCharMenuManagerStub.hide.calledOnce);
|
||||
assert.isTrue(window.clearTimeout.calledThrice);
|
||||
assert.isTrue(window.setTimeout.calledTwice);
|
||||
assert.isTrue(manager.ontargetcommitted.called);
|
||||
});
|
||||
|
||||
test('press move out handwriting pad, press end', function() {
|
||||
var pressMove = {
|
||||
target: {
|
||||
text: 'm'
|
||||
}
|
||||
};
|
||||
|
||||
userPressManagerStub.onpressmove(pressMove, id0);
|
||||
|
||||
assert.isFalse(window.setTimeout.calledTwice);
|
||||
assert.isFalse(manager.ontargetmovedout.called);
|
||||
assert.isFalse(manager.ontargetmovedin.called);
|
||||
assert.isFalse(alternativesCharMenuManagerStub.hide.called);
|
||||
|
||||
// Press end
|
||||
userPressManagerStub.onpressend(press0, id0);
|
||||
|
||||
assert.isTrue(alternativesCharMenuManagerStub.hide.calledOnce);
|
||||
assert.isTrue(window.clearTimeout.calledThrice);
|
||||
assert.isTrue(window.setTimeout.calledTwice);
|
||||
assert.isTrue(manager.ontargetcommitted.called);
|
||||
});
|
||||
});
|
||||
});
|
||||
355
gaia/apps/keyboard/test/unit/keyboard/alternatives_char_menu_manager_test.js
Executable file
355
gaia/apps/keyboard/test/unit/keyboard/alternatives_char_menu_manager_test.js
Executable file
|
|
@ -0,0 +1,355 @@
|
|||
'use strict';
|
||||
|
||||
/* global AlternativesCharMenuManager */
|
||||
|
||||
require('/js/keyboard/alternatives_char_menu_manager.js');
|
||||
|
||||
suite('AlternativesCharMenuManager', function() {
|
||||
var app;
|
||||
var container;
|
||||
var manager;
|
||||
var target;
|
||||
var targetElem;
|
||||
|
||||
var getFakeElementWithGetBoundingClientRect;
|
||||
|
||||
var dummyObjForChild1;
|
||||
|
||||
var fakeMenuView;
|
||||
|
||||
var viewManager;
|
||||
|
||||
setup(function() {
|
||||
getFakeElementWithGetBoundingClientRect = function() {
|
||||
return {
|
||||
getBoundingClientRect: this.sinon.stub(),
|
||||
dataset: {
|
||||
lowercaseValue: 'x',
|
||||
uppercaseValue: 'X'
|
||||
}
|
||||
};
|
||||
}.bind(this);
|
||||
|
||||
// Create fake menu container element
|
||||
container = document.createElement('div');
|
||||
container.style.position = 'absolute';
|
||||
container.style.left = '5px';
|
||||
container.style.top = '40px';
|
||||
container.style.width = '60px';
|
||||
container.style.height = '10px';
|
||||
container.id = 'test-menu';
|
||||
|
||||
var child1 = document.createElement('div');
|
||||
container.appendChild(child1);
|
||||
|
||||
var child2 = document.createElement('div');
|
||||
container.appendChild(child2);
|
||||
|
||||
dummyObjForChild1 = {
|
||||
dummy: 'dummy'
|
||||
};
|
||||
|
||||
document.body.appendChild(container);
|
||||
|
||||
var viewMap = new WeakMap();
|
||||
|
||||
fakeMenuView = {
|
||||
getMenuContainer: function() {
|
||||
return container;
|
||||
},
|
||||
getBoundingClientRect: function() {
|
||||
return container.getBoundingClientRect();
|
||||
},
|
||||
getMenuTarget: this.sinon.stub(),
|
||||
isMenuTarget: this.sinon.stub()
|
||||
};
|
||||
|
||||
// Create fake viewManager
|
||||
viewManager = {
|
||||
showAlternativesCharMenu: function(target, alternatives) {
|
||||
return fakeMenuView;
|
||||
},
|
||||
hideAlternativesCharMenu: this.sinon.stub(),
|
||||
registerView: function(target, view) {
|
||||
viewMap.set(target, view);
|
||||
},
|
||||
getView(target) {
|
||||
return viewMap.get(target);
|
||||
}
|
||||
};
|
||||
this.sinon.spy(viewManager, 'showAlternativesCharMenu');
|
||||
|
||||
// Create fake app
|
||||
app = {
|
||||
upperCaseStateManager: {
|
||||
isUpperCaseLocked: undefined,
|
||||
isUpperCase: undefined
|
||||
},
|
||||
layoutManager: {
|
||||
currentPage: {
|
||||
alt: {
|
||||
}
|
||||
}
|
||||
},
|
||||
layoutRenderingManager: {
|
||||
getTargetObject: this.sinon.stub()
|
||||
},
|
||||
viewManager: viewManager
|
||||
};
|
||||
|
||||
// Create fake target object and DOM element
|
||||
targetElem = getFakeElementWithGetBoundingClientRect();
|
||||
targetElem.getBoundingClientRect.returns({
|
||||
top: 50,
|
||||
bottom: 60,
|
||||
left: 10,
|
||||
right: 40,
|
||||
width: 30
|
||||
});
|
||||
|
||||
target = {
|
||||
keyCode: 'x'.charCodeAt(0),
|
||||
keyCodeUpper: 'X'.charCodeAt(0),
|
||||
lowercaseValue: 'x',
|
||||
uppercaseValue: 'X'
|
||||
};
|
||||
|
||||
viewManager.registerView(target, {element: targetElem});
|
||||
|
||||
// Show an alternatives chars menu
|
||||
manager = new AlternativesCharMenuManager(app);
|
||||
manager.start();
|
||||
|
||||
assert.equal(manager.isShown, false);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
app = null;
|
||||
container = null;
|
||||
});
|
||||
|
||||
test('show (lower case)', function() {
|
||||
app.upperCaseStateManager.isUpperCase = false;
|
||||
app.upperCaseStateManager.isUpperCaseLocked = false;
|
||||
|
||||
app.layoutManager.currentPage.alt.x =
|
||||
['a', 'b', 'c', 'd'];
|
||||
|
||||
manager.show(target);
|
||||
|
||||
assert.isTrue(viewManager.
|
||||
showAlternativesCharMenu.calledWith(target, ['x', 'a', 'b', 'c', 'd']));
|
||||
assert.isTrue(app.layoutManager.currentPage.alt.x !==
|
||||
viewManager.showAlternativesCharMenu.getCall(0).args[1],
|
||||
'A copy of the array should be sent instead of the original one.');
|
||||
assert.isTrue(manager.isShown);
|
||||
});
|
||||
|
||||
test('show (upper case)', function() {
|
||||
app.upperCaseStateManager.isUpperCase = true;
|
||||
app.upperCaseStateManager.isUpperCaseLocked = false;
|
||||
|
||||
app.layoutManager.currentPage.alt.X =
|
||||
['A', 'B', 'C', 'D'];
|
||||
app.layoutManager.currentPage.alt.X.upperCaseLocked =
|
||||
['E', 'F', 'G', 'H'];
|
||||
|
||||
manager.show(target);
|
||||
|
||||
assert.isTrue(viewManager.
|
||||
showAlternativesCharMenu.calledWith(target, ['X', 'A', 'B', 'C', 'D']));
|
||||
assert.isTrue(app.layoutManager.currentPage.alt.x !==
|
||||
viewManager.showAlternativesCharMenu.getCall(0).args[1],
|
||||
'A copy of the array should be sent instead of the original one.');
|
||||
assert.isTrue(manager.isShown);
|
||||
});
|
||||
|
||||
test('show (upper case locked)', function() {
|
||||
app.upperCaseStateManager.isUpperCase = true;
|
||||
app.upperCaseStateManager.isUpperCaseLocked = true;
|
||||
|
||||
app.layoutManager.currentPage.alt.X =
|
||||
['A', 'B', 'C', 'D'];
|
||||
app.layoutManager.currentPage.alt.X.upperCaseLocked =
|
||||
['E', 'F', 'G', 'H'];
|
||||
|
||||
manager.show(target);
|
||||
|
||||
assert.isTrue(viewManager.
|
||||
showAlternativesCharMenu.calledWith(target, ['X', 'E', 'F', 'G', 'H']));
|
||||
assert.isTrue(app.layoutManager.currentPage.alt.x !==
|
||||
viewManager.showAlternativesCharMenu.getCall(0).args[1],
|
||||
'A copy of the array should be sent instead of the original one.');
|
||||
assert.isTrue(manager.isShown);
|
||||
});
|
||||
|
||||
test('show (ignore key w/o alternatives)', function() {
|
||||
app.upperCaseStateManager.isUpperCase = false;
|
||||
app.upperCaseStateManager.isUpperCaseLocked = false;
|
||||
|
||||
manager.show(target);
|
||||
|
||||
assert.isFalse(viewManager.showAlternativesCharMenu.called);
|
||||
assert.isFalse(manager.isShown);
|
||||
});
|
||||
|
||||
suite('after shown', function() {
|
||||
setup(function() {
|
||||
app.upperCaseStateManager.isUpperCase = false;
|
||||
app.upperCaseStateManager.isUpperCaseLocked = false;
|
||||
|
||||
app.layoutManager.currentPage.alt.x =
|
||||
['a', 'b', 'c', 'd'];
|
||||
|
||||
manager.show(target);
|
||||
});
|
||||
|
||||
test('hide', function() {
|
||||
manager.hide();
|
||||
|
||||
assert.equal(manager.isShown, false);
|
||||
assert.isTrue(viewManager.hideAlternativesCharMenu.calledOnce);
|
||||
});
|
||||
|
||||
suite('isMenuTarget', function() {
|
||||
var dummyTarget = {
|
||||
dummy: null
|
||||
};
|
||||
|
||||
setup(function() {
|
||||
manager.isMenuTarget(dummyTarget);
|
||||
});
|
||||
|
||||
test('will invoke MenuView\'s isMenuTarget', function() {
|
||||
assert.isTrue(fakeMenuView.isMenuTarget.calledOnce);
|
||||
assert.isTrue(fakeMenuView.isMenuTarget.calledWith(dummyTarget));
|
||||
});
|
||||
});
|
||||
|
||||
suite('isInMenuArea', function() {
|
||||
test('above menu', function() {
|
||||
var press = {
|
||||
clientX: 45,
|
||||
clientY: 35
|
||||
};
|
||||
|
||||
assert.equal(manager.isInMenuArea(press), false);
|
||||
});
|
||||
|
||||
test('below key', function() {
|
||||
var press = {
|
||||
clientX: 45,
|
||||
clientY: 70
|
||||
};
|
||||
|
||||
assert.equal(manager.isInMenuArea(press), false);
|
||||
});
|
||||
|
||||
test('left of menu', function() {
|
||||
var press = {
|
||||
clientX: 2,
|
||||
clientY: 55
|
||||
};
|
||||
|
||||
assert.equal(manager.isInMenuArea(press), true);
|
||||
});
|
||||
|
||||
test('right of menu', function() {
|
||||
var press = {
|
||||
clientX: 80,
|
||||
clientY: 55
|
||||
};
|
||||
|
||||
assert.equal(manager.isInMenuArea(press), true);
|
||||
});
|
||||
|
||||
test('right of menu, exceeds the key width', function() {
|
||||
var press = {
|
||||
clientX: 150,
|
||||
clientY: 55
|
||||
};
|
||||
|
||||
assert.equal(manager.isInMenuArea(press), false);
|
||||
});
|
||||
|
||||
test('on top of the menu', function() {
|
||||
var press = {
|
||||
clientX: 45,
|
||||
clientY: 40
|
||||
};
|
||||
|
||||
// We've extend the locked area to include the menu itself.
|
||||
assert.equal(manager.isInMenuArea(press), true);
|
||||
});
|
||||
|
||||
test('on top of the key', function() {
|
||||
var press = {
|
||||
clientX: 15,
|
||||
clientY: 55
|
||||
};
|
||||
|
||||
assert.equal(manager.isInMenuArea(press), true);
|
||||
});
|
||||
|
||||
test('below menu and beside key', function() {
|
||||
var press = {
|
||||
clientX: 65,
|
||||
clientY: 55
|
||||
};
|
||||
|
||||
assert.equal(manager.isInMenuArea(press), true);
|
||||
});
|
||||
|
||||
test('below menu and above key', function() {
|
||||
var press = {
|
||||
clientX: 65,
|
||||
clientY: 47
|
||||
};
|
||||
|
||||
assert.equal(manager.isInMenuArea(press), true);
|
||||
});
|
||||
});
|
||||
|
||||
suite('getMenuTarget', function() {
|
||||
test('will invoke menuView.getMenuTarget', function() {
|
||||
var press = {
|
||||
target: {},
|
||||
clientX: 35,
|
||||
clientY: 55
|
||||
};
|
||||
|
||||
manager.getMenuTarget(press);
|
||||
|
||||
assert.isTrue(fakeMenuView.getMenuTarget.calledOnce);
|
||||
assert.isTrue(fakeMenuView.getMenuTarget.calledWith(press.clientX,
|
||||
press.clientY));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('after hiding the menu', function() {
|
||||
setup(function() {
|
||||
app.upperCaseStateManager.isUpperCase = false;
|
||||
app.upperCaseStateManager.isUpperCaseLocked = false;
|
||||
|
||||
app.layoutManager.currentPage.alt.x =
|
||||
['a', 'b', 'c', 'd'];
|
||||
|
||||
manager.show(target);
|
||||
manager.hide();
|
||||
});
|
||||
|
||||
test('isMenuTarget should be false', function() {
|
||||
assert.isFalse(manager.isMenuTarget(target));
|
||||
});
|
||||
|
||||
test('isInMenuArea() should return false', function() {
|
||||
var press = {
|
||||
clientX: 65,
|
||||
clientY: 47
|
||||
};
|
||||
|
||||
assert.isFalse(manager.isInMenuArea(press));
|
||||
});
|
||||
});
|
||||
});
|
||||
256
gaia/apps/keyboard/test/unit/keyboard/candidate_panel_manager_test.js
Executable file
256
gaia/apps/keyboard/test/unit/keyboard/candidate_panel_manager_test.js
Executable file
|
|
@ -0,0 +1,256 @@
|
|||
'use strict';
|
||||
|
||||
/* global CandidatePanelManager, MockEventTarget */
|
||||
|
||||
require('/shared/test/unit/mocks/mock_event_target.js');
|
||||
require('/js/keyboard/candidate_panel_manager.js');
|
||||
|
||||
suite('CandidatePanelManager', function() {
|
||||
var app;
|
||||
var manager;
|
||||
var suggestionsContainer;
|
||||
var viewManager;
|
||||
|
||||
setup(function() {
|
||||
suggestionsContainer = new MockEventTarget();
|
||||
this.sinon.spy(suggestionsContainer, 'addEventListener');
|
||||
this.sinon.spy(suggestionsContainer, 'removeEventListener');
|
||||
|
||||
viewManager = {
|
||||
showMoreCandidates: this.sinon.stub(),
|
||||
candidatePanel: {},
|
||||
getNumberOfCandidatesPerRow: this.sinon.stub(),
|
||||
toggleCandidatePanel: this.sinon.stub()
|
||||
};
|
||||
|
||||
viewManager.getNumberOfCandidatesPerRow.returns(8);
|
||||
viewManager.candidatePanel.dataset = {};
|
||||
viewManager.candidatePanel.scrollHeight = 500;
|
||||
viewManager.candidatePanel.clientHeight = 100;
|
||||
viewManager.candidatePanel.scrollTop = 398;
|
||||
|
||||
viewManager.candidatePanel.querySelector = this.sinon.stub();
|
||||
viewManager.candidatePanel.querySelector.returns(
|
||||
suggestionsContainer);
|
||||
|
||||
this.sinon.stub(window, 'setTimeout');
|
||||
this.sinon.stub(window, 'clearTimeout');
|
||||
|
||||
app = {
|
||||
inputMethodManager: {
|
||||
currentIMEngine: {}
|
||||
},
|
||||
viewManager: viewManager
|
||||
};
|
||||
|
||||
manager = new CandidatePanelManager(app);
|
||||
manager.oncandidateschange = this.sinon.stub();
|
||||
manager.start();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
app = null;
|
||||
});
|
||||
|
||||
test('stop', function() {
|
||||
manager.stop();
|
||||
|
||||
assert.isTrue(window.clearTimeout.calledOnce);
|
||||
});
|
||||
|
||||
suite('updateCandidates', function() {
|
||||
var candidates;
|
||||
setup(function() {
|
||||
candidates = (function() {
|
||||
var arr = [];
|
||||
var i = 500;
|
||||
while (i--) {
|
||||
arr.push(['candidate ' + i, i]);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}());
|
||||
|
||||
manager.updateCandidates(candidates);
|
||||
|
||||
assert.equal(manager.currentCandidates, candidates);
|
||||
assert.isTrue(manager.oncandidateschange.calledOnce);
|
||||
|
||||
viewManager.candidatePanel.dataset.truncated = 'true';
|
||||
viewManager.candidatePanel.dataset.rowCount = '1';
|
||||
});
|
||||
|
||||
test('reset', function() {
|
||||
this.sinon.stub(manager, 'hideFullPanel');
|
||||
manager.reset();
|
||||
|
||||
assert.isTrue(manager.hideFullPanel.calledOnce);
|
||||
assert.deepEqual(manager.currentCandidates, []);
|
||||
});
|
||||
|
||||
test('toggleFullPanel', function() {
|
||||
this.sinon.stub(manager, 'showFullPanel');
|
||||
this.sinon.stub(manager, 'hideFullPanel');
|
||||
|
||||
manager.toggleFullPanel();
|
||||
assert.isTrue(manager.showFullPanel.calledOnce);
|
||||
manager.isFullPanelShown = true;
|
||||
|
||||
manager.toggleFullPanel();
|
||||
assert.isTrue(manager.hideFullPanel.calledOnce);
|
||||
});
|
||||
|
||||
suite('With engine.getMoreCandidates',function() {
|
||||
var getMoreCandidatesStub;
|
||||
setup(function() {
|
||||
getMoreCandidatesStub =
|
||||
app.inputMethodManager.currentIMEngine.getMoreCandidates =
|
||||
this.sinon.stub();
|
||||
|
||||
// Assume the candidates are truncated at 10th candidate.
|
||||
viewManager.candidatePanel.dataset.candidateIndicator = '10';
|
||||
});
|
||||
|
||||
suite('showFullPanel', function() {
|
||||
setup(function() {
|
||||
manager.showFullPanel();
|
||||
|
||||
assert.isTrue(manager.isFullPanelShown);
|
||||
assert.isTrue(getMoreCandidatesStub.calledWith(
|
||||
10, manager.FIRST_PAGE_ROWS * 8 + 1));
|
||||
|
||||
var firstPageList =
|
||||
candidates.slice(10, 10 + manager.FIRST_PAGE_ROWS * 8 + 1);
|
||||
getMoreCandidatesStub.getCall(0).args[2].call(window, firstPageList);
|
||||
|
||||
assert.isTrue(viewManager.showMoreCandidates
|
||||
.calledWith(manager.FIRST_PAGE_ROWS, firstPageList));
|
||||
assert.isTrue(viewManager.toggleCandidatePanel.calledWith(true));
|
||||
|
||||
assert.isTrue(suggestionsContainer.addEventListener.calledOnce);
|
||||
});
|
||||
|
||||
suite('showNextCandidatePage', function() {
|
||||
setup(function() {
|
||||
viewManager.candidatePanel.dataset.rowCount = '2';
|
||||
viewManager.candidatePanel.dataset.candidateIndicator = '90';
|
||||
|
||||
var scrollEvent = {
|
||||
type: 'scroll'
|
||||
};
|
||||
suggestionsContainer.dispatchEvent(scrollEvent);
|
||||
|
||||
assert.isTrue(window.clearTimeout.calledOnce);
|
||||
assert.isTrue(window.setTimeout.calledOnce);
|
||||
assert.equal(window.setTimeout.getCall(0).args[1], 200);
|
||||
window.setTimeout.getCall(0).args[0].call(window);
|
||||
|
||||
assert.isTrue(getMoreCandidatesStub.getCall(1).calledWith(
|
||||
90, manager.PAGE_ROWS * 8 + 1));
|
||||
|
||||
var nextPageList =
|
||||
candidates.slice(90, 90 + manager.PAGE_ROWS * 8 + 1);
|
||||
getMoreCandidatesStub.getCall(1).args[2].call(window, nextPageList);
|
||||
|
||||
assert.isTrue(viewManager.showMoreCandidates
|
||||
.calledWith(manager.PAGE_ROWS, nextPageList));
|
||||
});
|
||||
|
||||
test('hideFullPanel & show panel', function () {
|
||||
viewManager.candidatePanel.dataset.rowCount = '3';
|
||||
viewManager.candidatePanel.dataset.candidateIndicator = '187';
|
||||
|
||||
manager.hideFullPanel();
|
||||
|
||||
assert.isFalse(manager.isFullPanelShown);
|
||||
assert.isTrue(window.clearTimeout.calledTwice);
|
||||
assert.isTrue(suggestionsContainer.removeEventListener.calledOnce);
|
||||
|
||||
manager.showFullPanel();
|
||||
|
||||
assert.isTrue(viewManager.showMoreCandidates.calledTwice,
|
||||
'No showMoreCandidates call here.');
|
||||
assert.isTrue(suggestionsContainer.addEventListener.calledTwice);
|
||||
});
|
||||
});
|
||||
|
||||
test('hideFullPanel', function () {
|
||||
manager.hideFullPanel();
|
||||
|
||||
assert.isFalse(manager.isFullPanelShown);
|
||||
assert.isTrue(suggestionsContainer.removeEventListener.calledOnce);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('No engine.getMoreCandidates',function() {
|
||||
setup(function() {
|
||||
// Assume the candidates are truncated at 10th candidate.
|
||||
viewManager.candidatePanel.dataset.candidateIndicator = '10';
|
||||
});
|
||||
|
||||
suite('showFullPanel', function() {
|
||||
setup(function() {
|
||||
manager.showFullPanel();
|
||||
|
||||
var firstPageList =
|
||||
candidates.slice(10, 10 + manager.FIRST_PAGE_ROWS * 8 + 1);
|
||||
assert.isTrue(manager.isFullPanelShown);
|
||||
assert.isTrue(viewManager.showMoreCandidates
|
||||
.calledWith(manager.FIRST_PAGE_ROWS, firstPageList));
|
||||
assert.isTrue(
|
||||
viewManager.toggleCandidatePanel.calledWith(true));
|
||||
|
||||
assert.isTrue(suggestionsContainer.addEventListener.calledOnce);
|
||||
});
|
||||
|
||||
suite('showNextCandidatePage', function() {
|
||||
setup(function() {
|
||||
viewManager.candidatePanel.dataset.rowCount = '2';
|
||||
viewManager.candidatePanel.dataset.candidateIndicator = '90';
|
||||
|
||||
var scrollEvent = {
|
||||
type: 'scroll'
|
||||
};
|
||||
suggestionsContainer.dispatchEvent(scrollEvent);
|
||||
|
||||
assert.isTrue(window.clearTimeout.calledOnce);
|
||||
assert.isTrue(window.setTimeout.calledOnce);
|
||||
window.setTimeout.getCall(0).args[0].call(window);
|
||||
|
||||
var nextPageList =
|
||||
candidates.slice(90, 90 + manager.PAGE_ROWS * 8 + 1);
|
||||
|
||||
assert.isTrue(viewManager.showMoreCandidates
|
||||
.calledWith(manager.PAGE_ROWS, nextPageList));
|
||||
});
|
||||
|
||||
test('hideFullPanel & show panel', function () {
|
||||
viewManager.candidatePanel.dataset.rowCount = '3';
|
||||
viewManager.candidatePanel.dataset.candidateIndicator = '187';
|
||||
|
||||
manager.hideFullPanel();
|
||||
|
||||
assert.isFalse(manager.isFullPanelShown);
|
||||
assert.isTrue(window.clearTimeout.calledTwice);
|
||||
assert.isTrue(suggestionsContainer.removeEventListener.calledOnce);
|
||||
|
||||
manager.showFullPanel();
|
||||
|
||||
assert.isTrue(viewManager.showMoreCandidates.calledTwice,
|
||||
'No showMoreCandidates call here.');
|
||||
assert.isTrue(suggestionsContainer.addEventListener.calledTwice);
|
||||
});
|
||||
});
|
||||
|
||||
test('hideFullPanel', function () {
|
||||
manager.hideFullPanel();
|
||||
|
||||
assert.isFalse(manager.isFullPanelShown);
|
||||
assert.isTrue(window.clearTimeout.calledOnce);
|
||||
assert.isTrue(suggestionsContainer.removeEventListener.calledOnce);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
315
gaia/apps/keyboard/test/unit/keyboard/feedback_manager_test.js
Executable file
315
gaia/apps/keyboard/test/unit/keyboard/feedback_manager_test.js
Executable file
|
|
@ -0,0 +1,315 @@
|
|||
'use strict';
|
||||
|
||||
/* global VibrationFeedback, SoundFeedback, FeedbackManager,
|
||||
MockNavigatorMozSettings, MockNavigatorMozSettingsLock,
|
||||
SettingsPromiseManager, SoundFeedbackPlayer */
|
||||
|
||||
require('/js/keyboard/feedback_manager.js');
|
||||
require('/js/keyboard/settings.js');
|
||||
require('/shared/test/unit/mocks/mock_event_target.js');
|
||||
require('/shared/test/unit/mocks/mock_dom_request.js');
|
||||
require('/shared/js/input_mgmt/mock_navigator_mozsettings.js');
|
||||
|
||||
require('/js/keyboard/sound_feedback_player.js');
|
||||
|
||||
suite('VibrationFeedback', function() {
|
||||
var realMozSettings;
|
||||
var feedback;
|
||||
var mozSettings;
|
||||
var lock;
|
||||
|
||||
suiteSetup(function() {
|
||||
realMozSettings = navigator.mozSettings;
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
navigator.mozSettings = realMozSettings;
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
mozSettings = navigator.mozSettings = new MockNavigatorMozSettings();
|
||||
var createLockStub = this.sinon.stub(mozSettings, 'createLock');
|
||||
lock = new MockNavigatorMozSettingsLock();
|
||||
this.sinon.spy(lock, 'get');
|
||||
createLockStub.returns(lock);
|
||||
|
||||
var promiseManager = new SettingsPromiseManager();
|
||||
|
||||
feedback = new VibrationFeedback({
|
||||
settingsPromiseManager: promiseManager
|
||||
});
|
||||
feedback.start();
|
||||
});
|
||||
|
||||
suite('init with vibrate=true', function() {
|
||||
setup(function(done) {
|
||||
var req = lock.get.getCall(0).returnValue;
|
||||
req.fireSuccess({ 'keyboard.vibration': true });
|
||||
|
||||
feedback.settings.initSettings().then(function() {
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('vibrate', function() {
|
||||
this.sinon.stub(navigator, 'vibrate');
|
||||
|
||||
feedback.triggerFeedback();
|
||||
assert.isTrue(navigator.vibrate.calledOnce);
|
||||
});
|
||||
|
||||
test('change to vibrate=false', function() {
|
||||
mozSettings.dispatchSettingChange('keyboard.vibration', false);
|
||||
|
||||
this.sinon.stub(navigator, 'vibrate');
|
||||
|
||||
feedback.triggerFeedback();
|
||||
assert.equal(navigator.vibrate.calledOnce, false);
|
||||
});
|
||||
});
|
||||
|
||||
suite('init with vibrate=false', function() {
|
||||
setup(function(done) {
|
||||
var req = lock.get.getCall(0).returnValue;
|
||||
req.fireSuccess({ 'keyboard.vibration': false });
|
||||
|
||||
feedback.settings.initSettings().then(function() {
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('vibrate', function() {
|
||||
this.sinon.stub(navigator, 'vibrate');
|
||||
|
||||
feedback.triggerFeedback();
|
||||
assert.equal(navigator.vibrate.calledOnce, false);
|
||||
});
|
||||
|
||||
test('change to vibrate=true', function() {
|
||||
mozSettings.dispatchSettingChange('keyboard.vibration', true);
|
||||
|
||||
this.sinon.stub(navigator, 'vibrate');
|
||||
|
||||
feedback.triggerFeedback();
|
||||
assert.isTrue(navigator.vibrate.calledOnce);
|
||||
});
|
||||
});
|
||||
|
||||
test('not ready -- don\'t vibrate', function() {
|
||||
this.sinon.stub(navigator, 'vibrate');
|
||||
|
||||
feedback.triggerFeedback();
|
||||
assert.equal(navigator.vibrate.calledOnce, false);
|
||||
});
|
||||
});
|
||||
|
||||
suite('SoundFeedback', function() {
|
||||
var realMozSettings;
|
||||
var feedback;
|
||||
var mozSettings;
|
||||
var lock;
|
||||
|
||||
var normalTarget;
|
||||
var specialTarget;
|
||||
|
||||
var stubSoundFeedbackPlayer;
|
||||
|
||||
suiteSetup(function() {
|
||||
realMozSettings = navigator.mozSettings;
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
navigator.mozSettings = realMozSettings;
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
mozSettings = navigator.mozSettings = new MockNavigatorMozSettings();
|
||||
var createLockStub = this.sinon.stub(mozSettings, 'createLock');
|
||||
lock = new MockNavigatorMozSettingsLock();
|
||||
this.sinon.spy(lock, 'get');
|
||||
createLockStub.returns(lock);
|
||||
|
||||
var promiseManager = new SettingsPromiseManager();
|
||||
|
||||
stubSoundFeedbackPlayer =
|
||||
this.sinon.stub(Object.create(SoundFeedbackPlayer.prototype));
|
||||
this.sinon.stub(window, 'SoundFeedbackPlayer')
|
||||
.returns(stubSoundFeedbackPlayer);
|
||||
|
||||
normalTarget = {
|
||||
keyCode: 60,
|
||||
isSpecialKey: false
|
||||
};
|
||||
|
||||
specialTarget = {
|
||||
keyCode: 60,
|
||||
isSpecialKey: true
|
||||
};
|
||||
|
||||
feedback = new SoundFeedback({
|
||||
settingsPromiseManager: promiseManager
|
||||
});
|
||||
feedback.start();
|
||||
});
|
||||
|
||||
suite('init with sound=true', function() {
|
||||
setup(function(done) {
|
||||
var req = lock.get.getCall(0).returnValue;
|
||||
req.fireSuccess({ 'keyboard.clicksound': true });
|
||||
var req1 = lock.get.getCall(1).returnValue;
|
||||
req1.fireSuccess({ 'audio.volume.notification': 10 });
|
||||
|
||||
feedback.settings.initSettings().then(function() {
|
||||
}).then(function() {
|
||||
assert.isTrue(stubSoundFeedbackPlayer.prepare.calledOnce);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('sound (normal key)', function() {
|
||||
feedback.triggerFeedback(normalTarget);
|
||||
assert.isTrue(stubSoundFeedbackPlayer.play.calledWith(false));
|
||||
});
|
||||
|
||||
test('sound (special key)', function() {
|
||||
feedback.triggerFeedback(specialTarget);
|
||||
assert.isTrue(stubSoundFeedbackPlayer.play.calledWith(true));
|
||||
});
|
||||
|
||||
test('change to sound=false', function() {
|
||||
mozSettings.dispatchSettingChange('keyboard.clicksound', false);
|
||||
|
||||
assert.equal(feedback.player, null, 'player should be dropped');
|
||||
|
||||
feedback.triggerFeedback(normalTarget);
|
||||
feedback.triggerFeedback(specialTarget);
|
||||
assert.isFalse(stubSoundFeedbackPlayer.play.called);
|
||||
});
|
||||
|
||||
suite('activate', function() {
|
||||
setup(function() {
|
||||
feedback.activate();
|
||||
|
||||
assert.isTrue(stubSoundFeedbackPlayer.activate.called);
|
||||
});
|
||||
|
||||
test('deactivate', function() {
|
||||
feedback.deactivate();
|
||||
|
||||
assert.isTrue(stubSoundFeedbackPlayer.deactivate.called);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('init with sound=false', function() {
|
||||
setup(function(done) {
|
||||
var req = lock.get.getCall(0).returnValue;
|
||||
req.fireSuccess({ 'keyboard.clicksound': false });
|
||||
var req1 = lock.get.getCall(1).returnValue;
|
||||
req1.fireSuccess({ 'audio.volume.notification': 10 });
|
||||
|
||||
feedback.settings.initSettings().then(function() {
|
||||
}).then(function() {
|
||||
assert.isFalse(stubSoundFeedbackPlayer.prepare.calledOnce);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('sound (normal key)', function() {
|
||||
assert.equal(feedback.player, null, 'player should be dropped');
|
||||
|
||||
feedback.triggerFeedback(normalTarget);
|
||||
assert.isFalse(stubSoundFeedbackPlayer.play.called);
|
||||
});
|
||||
|
||||
test('sound (special key)', function() {
|
||||
assert.equal(feedback.player, null, 'player should be dropped');
|
||||
|
||||
feedback.triggerFeedback(specialTarget);
|
||||
assert.isFalse(stubSoundFeedbackPlayer.play.called);
|
||||
});
|
||||
|
||||
test('change to sound=true and sound (normal key)', function() {
|
||||
mozSettings.dispatchSettingChange('keyboard.clicksound', true);
|
||||
|
||||
assert.isTrue(stubSoundFeedbackPlayer.prepare.calledOnce);
|
||||
|
||||
feedback.triggerFeedback(normalTarget);
|
||||
assert.isTrue(stubSoundFeedbackPlayer.play.calledWith(false));
|
||||
});
|
||||
|
||||
test('change to sound=true and sound (special key)', function() {
|
||||
mozSettings.dispatchSettingChange('keyboard.clicksound', true);
|
||||
|
||||
assert.isTrue(stubSoundFeedbackPlayer.prepare.calledOnce);
|
||||
|
||||
feedback.triggerFeedback(specialTarget);
|
||||
assert.isTrue(stubSoundFeedbackPlayer.play.calledWith(true));
|
||||
});
|
||||
|
||||
|
||||
suite('activate', function() {
|
||||
setup(function() {
|
||||
feedback.activate();
|
||||
});
|
||||
|
||||
test('deactivate', function() {
|
||||
feedback.deactivate();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('FeedbackManager', function() {
|
||||
test('start, feedback, stop', function(done) {
|
||||
var mozSettings = navigator.mozSettings = new MockNavigatorMozSettings();
|
||||
var createLockStub = this.sinon.stub(mozSettings, 'createLock');
|
||||
var lock = new MockNavigatorMozSettingsLock();
|
||||
var stubSoundFeedbackPlayer =
|
||||
this.sinon.stub(Object.create(SoundFeedbackPlayer.prototype));
|
||||
this.sinon.stub(window, 'SoundFeedbackPlayer')
|
||||
.returns(stubSoundFeedbackPlayer);
|
||||
|
||||
this.sinon.spy(lock, 'get');
|
||||
createLockStub.returns(lock);
|
||||
|
||||
var normalTarget = {
|
||||
keyCode: 60,
|
||||
isSpecialKey: false
|
||||
};
|
||||
|
||||
var specialTarget = {
|
||||
keyCode: 60,
|
||||
isSpecialKey: true
|
||||
};
|
||||
|
||||
var app = {
|
||||
settingsPromiseManager: new SettingsPromiseManager()
|
||||
};
|
||||
|
||||
var feedbackManager = new FeedbackManager(app);
|
||||
feedbackManager.start();
|
||||
feedbackManager.activate();
|
||||
|
||||
var req = lock.get.getCall(0).returnValue;
|
||||
req.fireSuccess({ 'keyboard.vibration': true });
|
||||
var req1 = lock.get.getCall(1).returnValue;
|
||||
req1.fireSuccess({ 'keyboard.clicksound': true });
|
||||
var req2 = lock.get.getCall(2).returnValue;
|
||||
req2.fireSuccess({ 'audio.volume.notification': 10 });
|
||||
|
||||
feedbackManager.soundFeedback.settings.initSettings().then(function() {
|
||||
assert.isTrue(stubSoundFeedbackPlayer.activate.called);
|
||||
|
||||
this.sinon.stub(navigator, 'vibrate');
|
||||
|
||||
feedbackManager.triggerFeedback(normalTarget);
|
||||
|
||||
assert.isTrue(stubSoundFeedbackPlayer.play.calledWith(false));
|
||||
assert.isTrue(navigator.vibrate.calledOnce);
|
||||
|
||||
feedbackManager.triggerFeedback(specialTarget);
|
||||
|
||||
assert.isTrue(stubSoundFeedbackPlayer.play.calledWith(true));
|
||||
assert.isTrue(navigator.vibrate.calledTwice);
|
||||
|
||||
feedbackManager.stop();
|
||||
}.bind(this)).then(done, done);
|
||||
});
|
||||
});
|
||||
69
gaia/apps/keyboard/test/unit/keyboard/handwriting_pads_manager_test.js
Executable file
69
gaia/apps/keyboard/test/unit/keyboard/handwriting_pads_manager_test.js
Executable file
|
|
@ -0,0 +1,69 @@
|
|||
'use strict';
|
||||
|
||||
/* global HandwritingPadsManager, SettingsPromiseManager */
|
||||
|
||||
require('/js/keyboard/handwriting_pads_manager.js');
|
||||
require('/js/keyboard/settings.js');
|
||||
|
||||
suite('HandwritingPadsManager ', function() {
|
||||
var app;
|
||||
var manager;
|
||||
var point;
|
||||
|
||||
setup(function() {
|
||||
// Create fake app
|
||||
app = {
|
||||
settingsPromiseManager: new SettingsPromiseManager(),
|
||||
layoutRenderingManager: {
|
||||
drawHandwritingPad: function() {
|
||||
return point;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.sinon.stub(window, 'setTimeout');
|
||||
this.sinon.stub(window, 'clearTimeout');
|
||||
|
||||
manager = new HandwritingPadsManager(app);
|
||||
manager.start();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
app = null;
|
||||
manager = null;
|
||||
point = null;
|
||||
});
|
||||
|
||||
test('handle press events', function() {
|
||||
point = {
|
||||
posX: 20,
|
||||
posY: 20
|
||||
};
|
||||
|
||||
var press = {
|
||||
clientX: 10,
|
||||
clientY: 10
|
||||
};
|
||||
|
||||
// Press start
|
||||
manager.handlePressStart(press);
|
||||
assert.isTrue(window.clearTimeout.calledOnce);
|
||||
assert.equal(manager._strokePoints[0], 20);
|
||||
assert.equal(manager._strokePoints[1], 20);
|
||||
|
||||
point = {
|
||||
posX: 25,
|
||||
posY: 25
|
||||
};
|
||||
// Press move
|
||||
manager.handlePressMove(press);
|
||||
assert.equal(manager._strokePoints[2], 25);
|
||||
assert.equal(manager._strokePoints[3], 25);
|
||||
|
||||
// Press End
|
||||
manager.handlePressEnd();
|
||||
assert.isTrue(window.setTimeout.calledOnce);
|
||||
assert.equal(manager._strokePoints[4], -1);
|
||||
assert.equal(manager._strokePoints[5], 0);
|
||||
});
|
||||
});
|
||||
380
gaia/apps/keyboard/test/unit/keyboard/input_method_database_loader_test.js
Executable file
380
gaia/apps/keyboard/test/unit/keyboard/input_method_database_loader_test.js
Executable file
|
|
@ -0,0 +1,380 @@
|
|||
'use strict';
|
||||
|
||||
/* global InputMethodDatabaseLoader, InputMethodDatabaseConfig,
|
||||
PromiseStorage */
|
||||
|
||||
require('/js/shared/promise_storage.js');
|
||||
require('/js/keyboard/input_method_database_loader.js');
|
||||
|
||||
suite('InputMethodDatabaseConfig', function() {
|
||||
var config;
|
||||
|
||||
suite('With config', function() {
|
||||
var fakeXhr;
|
||||
|
||||
suite('Fully preloaded config', function() {
|
||||
setup(function() {
|
||||
var requests = [];
|
||||
|
||||
fakeXhr = sinon.useFakeXMLHttpRequest();
|
||||
fakeXhr.onCreate = function(request) {
|
||||
requests.push(request);
|
||||
};
|
||||
|
||||
config = new InputMethodDatabaseConfig();
|
||||
config.start();
|
||||
|
||||
assert.equal(requests.length, 1);
|
||||
|
||||
var request = requests[0];
|
||||
assert.equal(request.url, config.CONFIG_FILE_PATH);
|
||||
assert.equal(request.responseType, 'json');
|
||||
request.response = [
|
||||
{
|
||||
'id': 'en',
|
||||
'name': 'English',
|
||||
'imEngineId': 'latin',
|
||||
'preloaded': true,
|
||||
'dictFilename': 'en_us.dict',
|
||||
'dictFilePath': 'dictionaries/en_us.dict',
|
||||
'dictFileSize': 1451400,
|
||||
'types': ['email', 'password', 'text', 'url']
|
||||
},
|
||||
{
|
||||
'id': 'es',
|
||||
'name': 'Español',
|
||||
'imEngineId': 'latin',
|
||||
'preloaded': true,
|
||||
'dictFilename': 'es.dict',
|
||||
'dictFilePath': 'dictionaries/es.dict',
|
||||
'dictFileSize': 1564842,
|
||||
'types': ['email', 'password', 'text', 'url']
|
||||
}
|
||||
];
|
||||
|
||||
request.respond(200, {}, '');
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
config.stop();
|
||||
config = null;
|
||||
|
||||
fakeXhr.restore();
|
||||
});
|
||||
|
||||
test('isAllDataPreloaded', function(done) {
|
||||
config.isAllDataPreloaded().then(function(val) {
|
||||
assert.ok(val, 'preload=true.');
|
||||
}, function(e) {
|
||||
throw e || 'Should not reject.';
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Partly preloaded config', function() {
|
||||
setup(function() {
|
||||
var requests = [];
|
||||
|
||||
fakeXhr = sinon.useFakeXMLHttpRequest();
|
||||
fakeXhr.onCreate = function(request) {
|
||||
requests.push(request);
|
||||
};
|
||||
|
||||
config = new InputMethodDatabaseConfig();
|
||||
config.start();
|
||||
|
||||
assert.equal(requests.length, 1);
|
||||
|
||||
var request = requests[0];
|
||||
assert.equal(request.url, config.CONFIG_FILE_PATH);
|
||||
assert.equal(request.responseType, 'json');
|
||||
request.response = [
|
||||
{
|
||||
'id': 'en',
|
||||
'name': 'English',
|
||||
'imEngineId': 'latin',
|
||||
'preloaded': true,
|
||||
'dictFilename': 'en_us.dict',
|
||||
'dictFilePath': 'dictionaries/en_us.dict',
|
||||
'dictFileSize': 1451400,
|
||||
'types': ['email', 'password', 'text', 'url']
|
||||
},
|
||||
{
|
||||
'id': 'es',
|
||||
'name': 'Español',
|
||||
'imEngineId': 'latin',
|
||||
'preloaded': false,
|
||||
'dictFilename': 'es.dict',
|
||||
'dictFilePath': 'dictionaries/es.dict',
|
||||
'dictFileSize': 1564842,
|
||||
'types': ['email', 'password', 'text', 'url']
|
||||
}
|
||||
];
|
||||
|
||||
request.respond(200, {}, '');
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
config.stop();
|
||||
config = null;
|
||||
|
||||
fakeXhr.restore();
|
||||
});
|
||||
|
||||
test('isDataPreloaded=true (unlisted)', function(done) {
|
||||
config.isDataPreloaded('latin', 'dictionaries/foo.dict')
|
||||
.then(function(val) {
|
||||
assert.ok(val);
|
||||
}, function(e) {
|
||||
throw e || 'Should not reject.';
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('isDataPreloaded=true', function(done) {
|
||||
config.isDataPreloaded('latin', 'dictionaries/en_us.dict')
|
||||
.then(function(val) {
|
||||
assert.ok(val);
|
||||
}, function(e) {
|
||||
throw e || 'Should not reject.';
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('isDataPreloaded=false', function(done) {
|
||||
config.isDataPreloaded('latin', 'dictionaries/es.dict')
|
||||
.then(function(val) {
|
||||
assert.isFalse(val);
|
||||
}, function(e) {
|
||||
throw e || 'Should not reject.';
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('isAllDataPreloaded', function(done) {
|
||||
config.isAllDataPreloaded().then(function(val) {
|
||||
assert.isFalse(val, 'preload=false.');
|
||||
}, function(e) {
|
||||
throw e || 'Should not reject.';
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('Without config', function() {
|
||||
setup(function() {
|
||||
config = new InputMethodDatabaseConfig();
|
||||
config.CONFIG_FILE_PATH = '404.json';
|
||||
config.start();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
config.stop();
|
||||
config = null;
|
||||
});
|
||||
|
||||
test('isDataPreloaded', function(done) {
|
||||
config.isDataPreloaded('foo', 'bar').then(function(val) {
|
||||
assert.ok(val, 'preload=true without config file.');
|
||||
}, function(e) {
|
||||
throw e || 'Should not reject.';
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('isAllDataPreloaded', function(done) {
|
||||
config.isAllDataPreloaded().then(function(val) {
|
||||
assert.ok(val, 'preload=true without config file.');
|
||||
}, function(e) {
|
||||
throw e || 'Should not reject.';
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('InputMethodDatabaseLoader', function() {
|
||||
var promiseStorageStub;
|
||||
|
||||
var loader;
|
||||
|
||||
setup(function() {
|
||||
promiseStorageStub =
|
||||
this.sinon.stub(Object.create(PromiseStorage.prototype));
|
||||
this.sinon.stub(window, 'PromiseStorage').returns(promiseStorageStub);
|
||||
});
|
||||
|
||||
suite('Without config', function() {
|
||||
setup(function() {
|
||||
loader = new InputMethodDatabaseLoader();
|
||||
// Use absolute path here.
|
||||
loader.SOURCE_DIR = '/js/imes/';
|
||||
loader.start();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
loader.stop();
|
||||
loader = null;
|
||||
});
|
||||
|
||||
test('load data from package', function(done) {
|
||||
loader.load('latin', 'dictionaries/en_us.dict').then(function(data) {
|
||||
assert.equal(data && data.byteLength, 1451400, 'Got data');
|
||||
}, function(e) {
|
||||
if (e) { throw e; }
|
||||
throw 'Should not reject';
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('load data from package (non-exist path)', function(done) {
|
||||
loader.load('latin', 'dictionaries/404.dict').then(function(data) {
|
||||
assert.isTrue(false, 'Should not resolve');
|
||||
}, function() {
|
||||
assert.isTrue(true, 'Rejected');
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('With config', function() {
|
||||
var fakeXhr;
|
||||
|
||||
suite('Fully preloaded config', function() {
|
||||
setup(function() {
|
||||
var requests = [];
|
||||
|
||||
fakeXhr = sinon.useFakeXMLHttpRequest();
|
||||
fakeXhr.onCreate = function(request) {
|
||||
requests.push(request);
|
||||
};
|
||||
|
||||
loader = new InputMethodDatabaseLoader();
|
||||
// Use absolute path here.
|
||||
loader.SOURCE_DIR = '/js/imes/';
|
||||
loader.start();
|
||||
|
||||
assert.equal(requests.length, 1);
|
||||
|
||||
var request = requests[0];
|
||||
request.response = [
|
||||
{
|
||||
'id': 'en',
|
||||
'name': 'English',
|
||||
'imEngineId': 'latin',
|
||||
'preloaded': true,
|
||||
'dictFilename': 'en_us.dict',
|
||||
'dictFilePath': 'dictionaries/en_us.dict',
|
||||
'dictFileSize': 1451400,
|
||||
'types': ['email', 'password', 'text', 'url']
|
||||
},
|
||||
{
|
||||
'id': 'es',
|
||||
'name': 'Español',
|
||||
'imEngineId': 'latin',
|
||||
'preloaded': true,
|
||||
'dictFilename': 'es.dict',
|
||||
'dictFilePath': 'dictionaries/es.dict',
|
||||
'dictFileSize': 1564842,
|
||||
'types': ['email', 'password', 'text', 'url']
|
||||
}
|
||||
];
|
||||
|
||||
request.respond(200, {}, '');
|
||||
|
||||
// Restore here so that we can use the real XHR to get en_us.dict.
|
||||
fakeXhr.restore();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
assert.isFalse(window.PromiseStorage.called,
|
||||
'PromiseStorage is not initialized.');
|
||||
|
||||
loader.stop();
|
||||
loader = null;
|
||||
});
|
||||
|
||||
test('load data from package', function(done) {
|
||||
loader.load('latin', 'dictionaries/en_us.dict').then(function(data) {
|
||||
assert.equal(data && data.byteLength, 1451400, 'Got data');
|
||||
}, function(e) {
|
||||
if (e) { throw e; }
|
||||
throw 'Should not reject';
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Partly preloaded config', function() {
|
||||
setup(function() {
|
||||
var requests = [];
|
||||
|
||||
fakeXhr = sinon.useFakeXMLHttpRequest();
|
||||
fakeXhr.onCreate = function(request) {
|
||||
requests.push(request);
|
||||
};
|
||||
|
||||
loader = new InputMethodDatabaseLoader();
|
||||
// Use absolute path here.
|
||||
loader.SOURCE_DIR = '/js/imes/';
|
||||
loader.start();
|
||||
|
||||
assert.equal(requests.length, 1);
|
||||
|
||||
var request = requests[0];
|
||||
request.response = [
|
||||
{
|
||||
'id': 'en',
|
||||
'name': 'English',
|
||||
'imEngineId': 'latin',
|
||||
'preloaded': true,
|
||||
'dictFilename': 'en_us.dict',
|
||||
'dictFilePath': 'dictionaries/en_us.dict',
|
||||
'dictFileSize': 1451400,
|
||||
'types': ['email', 'password', 'text', 'url']
|
||||
},
|
||||
{
|
||||
'id': 'es',
|
||||
'name': 'Español',
|
||||
'imEngineId': 'latin',
|
||||
'preloaded': false,
|
||||
'dictFilename': 'es.dict',
|
||||
'dictFilePath': 'dictionaries/es.dict',
|
||||
'dictFileSize': 1564842,
|
||||
'types': ['email', 'password', 'text', 'url']
|
||||
}
|
||||
];
|
||||
|
||||
request.respond(200, {}, '');
|
||||
|
||||
// Restore here so that we can use the real XHR to get en_us.dict.
|
||||
fakeXhr.restore();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
assert.ok(window.PromiseStorage.calledWith(loader.DATABASE_NAME));
|
||||
|
||||
loader.stop();
|
||||
loader = null;
|
||||
});
|
||||
|
||||
test('load data from package', function(done) {
|
||||
loader.load('latin', 'dictionaries/en_us.dict').then(function(data) {
|
||||
assert.equal(data && data.byteLength, 1451400, 'Got data');
|
||||
}, function(e) {
|
||||
if (e) { throw e; }
|
||||
throw 'Should not reject';
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('load data from PromiseStorage', function(done) {
|
||||
promiseStorageStub.getItem.returns(Promise.resolve({ stub: 'esDict '}));
|
||||
|
||||
loader.load('latin', 'dictionaries/es.dict').then(function(data) {
|
||||
assert.deepEqual(data, { stub: 'esDict '}, 'Got data');
|
||||
assert.ok(promiseStorageStub.getItem.calledWith(
|
||||
'latin/dictionaries/es.dict'));
|
||||
|
||||
}, function(e) {
|
||||
if (e) { throw e; }
|
||||
throw 'Should not reject';
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
655
gaia/apps/keyboard/test/unit/keyboard/input_method_manager_test.js
Executable file
655
gaia/apps/keyboard/test/unit/keyboard/input_method_manager_test.js
Executable file
|
|
@ -0,0 +1,655 @@
|
|||
'use strict';
|
||||
|
||||
/* global InputMethodGlue, InputMethodLoader, InputMethodManager,
|
||||
KeyboardConsole, IMEngineSettings, KeyEvent, Promise */
|
||||
|
||||
require('/js/keyboard/settings.js');
|
||||
require('/js/keyboard/input_method_manager.js');
|
||||
require('/js/keyboard/console.js');
|
||||
|
||||
suite('InputMethodGlue', function() {
|
||||
test('init', function() {
|
||||
var glue = new InputMethodGlue();
|
||||
var app = {};
|
||||
glue.init(app, 'foo');
|
||||
|
||||
assert.equal(glue.app, app);
|
||||
assert.equal(glue.path, './js/imes/foo');
|
||||
});
|
||||
|
||||
test('sendCandidates', function() {
|
||||
var glue = new InputMethodGlue();
|
||||
var app = {
|
||||
console: this.sinon.stub(KeyboardConsole.prototype),
|
||||
candidatePanelManager: {
|
||||
updateCandidates: this.sinon.stub()
|
||||
},
|
||||
inputContext: {}
|
||||
};
|
||||
var data = [['foo', 1]];
|
||||
glue.init(app, 'foo');
|
||||
glue.sendCandidates(data);
|
||||
|
||||
assert.isTrue(app.candidatePanelManager.updateCandidates.calledWith(data));
|
||||
});
|
||||
|
||||
test('setComposition', function() {
|
||||
var glue = new InputMethodGlue();
|
||||
var app = {
|
||||
console: this.sinon.stub(KeyboardConsole.prototype),
|
||||
inputContext: {
|
||||
setComposition: this.sinon.stub().returns(Promise.resolve())
|
||||
}
|
||||
};
|
||||
var symbols = 'bar';
|
||||
var cursor = 1;
|
||||
glue.init(app, 'foo');
|
||||
glue.setComposition(symbols, cursor);
|
||||
|
||||
assert.isTrue(app.inputContext.setComposition.calledWith(symbols, cursor));
|
||||
});
|
||||
|
||||
test('endComposition', function() {
|
||||
var glue = new InputMethodGlue();
|
||||
var app = {
|
||||
console: this.sinon.stub(KeyboardConsole.prototype),
|
||||
inputContext: {
|
||||
endComposition: this.sinon.stub().returns(Promise.resolve())
|
||||
}
|
||||
};
|
||||
var data = 'bar';
|
||||
glue.init(app, 'foo');
|
||||
glue.endComposition(data);
|
||||
|
||||
assert.isTrue(app.inputContext.endComposition.calledWith(data));
|
||||
});
|
||||
|
||||
suite('sendKey', function() {
|
||||
var glue;
|
||||
var app;
|
||||
var p;
|
||||
setup(function() {
|
||||
glue = new InputMethodGlue();
|
||||
app = {
|
||||
console: this.sinon.stub(KeyboardConsole.prototype),
|
||||
inputContext: {
|
||||
sendKey: this.sinon.stub()
|
||||
}
|
||||
};
|
||||
p = Promise.resolve();
|
||||
this.sinon.stub(p, 'catch').returns(p);
|
||||
app.inputContext.sendKey.returns(p);
|
||||
});
|
||||
|
||||
test('KeyEvent.DOM_VK_BACK_SPACE', function() {
|
||||
var keyCode = KeyEvent.DOM_VK_BACK_SPACE;
|
||||
var isRepeat = false;
|
||||
glue.init(app, 'foo');
|
||||
var returned = glue.sendKey(keyCode, isRepeat);
|
||||
|
||||
assert.isTrue(
|
||||
app.inputContext.sendKey.calledWith(keyCode, 0, 0, isRepeat));
|
||||
assert.equal(returned, p);
|
||||
});
|
||||
|
||||
test('KeyEvent.DOM_VK_RETURN', function() {
|
||||
var keyCode = KeyEvent.DOM_VK_RETURN;
|
||||
var isRepeat = false;
|
||||
glue.init(app, 'foo');
|
||||
var returned = glue.sendKey(keyCode, isRepeat);
|
||||
|
||||
assert.isTrue(
|
||||
app.inputContext.sendKey.calledWith(keyCode, 0, 0));
|
||||
assert.equal(returned, p);
|
||||
});
|
||||
|
||||
test('99', function() {
|
||||
var keyCode = 99;
|
||||
var isRepeat = false;
|
||||
glue.init(app, 'foo');
|
||||
var returned = glue.sendKey(keyCode, isRepeat);
|
||||
|
||||
assert.isTrue(
|
||||
app.inputContext.sendKey.calledWith(0, 99, 0));
|
||||
assert.equal(returned, p);
|
||||
});
|
||||
|
||||
test('-99', function() {
|
||||
var keyCode = -99;
|
||||
var isRepeat = false;
|
||||
glue.init(app, 'foo');
|
||||
var returned = glue.sendKey(keyCode, isRepeat);
|
||||
|
||||
assert.isTrue(
|
||||
app.inputContext.sendKey.calledWith(0, -99, 0));
|
||||
assert.equal(returned, p);
|
||||
});
|
||||
|
||||
test('(dict)', function() {
|
||||
glue.init(app, 'foo');
|
||||
|
||||
var dict = {};
|
||||
var returned = glue.sendKey(dict);
|
||||
|
||||
assert.isTrue(
|
||||
app.inputContext.sendKey.firstCall.args[0] === dict);
|
||||
assert.equal(returned, p);
|
||||
});
|
||||
});
|
||||
|
||||
test('sendString', function() {
|
||||
var glue = new InputMethodGlue();
|
||||
var app = {
|
||||
console: this.sinon.stub(KeyboardConsole.prototype),
|
||||
inputContext: {
|
||||
sendKey: this.sinon.stub().returns(Promise.resolve())
|
||||
}
|
||||
};
|
||||
var text = 'foobar';
|
||||
glue.init(app, 'foo');
|
||||
glue.sendString(text);
|
||||
|
||||
assert.isTrue(
|
||||
app.inputContext.sendKey.getCall(0).calledWith(0, text.charCodeAt(0), 0));
|
||||
assert.isTrue(
|
||||
app.inputContext.sendKey.getCall(1).calledWith(0, text.charCodeAt(1), 0));
|
||||
assert.isTrue(
|
||||
app.inputContext.sendKey.getCall(2).calledWith(0, text.charCodeAt(2), 0));
|
||||
assert.isTrue(
|
||||
app.inputContext.sendKey.getCall(3).calledWith(0, text.charCodeAt(3), 0));
|
||||
assert.isTrue(
|
||||
app.inputContext.sendKey.getCall(4).calledWith(0, text.charCodeAt(4), 0));
|
||||
assert.isTrue(
|
||||
app.inputContext.sendKey.getCall(5).calledWith(0, text.charCodeAt(5), 0));
|
||||
});
|
||||
|
||||
test('setLayoutPage', function() {
|
||||
var glue = new InputMethodGlue();
|
||||
var app = {
|
||||
console: this.sinon.stub(KeyboardConsole.prototype),
|
||||
layoutManager: {
|
||||
PAGE_INDEX_DEFAULT: 0
|
||||
},
|
||||
setLayoutPage: this.sinon.stub(),
|
||||
inputContext: {}
|
||||
};
|
||||
var pageIndex = 0;
|
||||
glue.init(app, 'foo');
|
||||
glue.setLayoutPage(pageIndex);
|
||||
|
||||
assert.isTrue(app.setLayoutPage.calledWith(pageIndex));
|
||||
});
|
||||
|
||||
test('setUpperCase', function() {
|
||||
var glue = new InputMethodGlue();
|
||||
var app = {
|
||||
console: this.sinon.stub(KeyboardConsole.prototype),
|
||||
upperCaseStateManager: {
|
||||
switchUpperCaseState: this.sinon.stub()
|
||||
},
|
||||
inputContext: {}
|
||||
};
|
||||
var state = {
|
||||
isUpperCase: true,
|
||||
isUpperCaseLocked: false
|
||||
};
|
||||
glue.init(app, 'foo');
|
||||
glue.setUpperCase(state);
|
||||
|
||||
assert.isTrue(
|
||||
app.upperCaseStateManager.switchUpperCaseState.calledWith(state));
|
||||
});
|
||||
|
||||
test('replaceSurroundingText', function() {
|
||||
var glue = new InputMethodGlue();
|
||||
var app = {
|
||||
console: this.sinon.stub(KeyboardConsole.prototype),
|
||||
inputContext: {
|
||||
replaceSurroundingText: this.sinon.stub()
|
||||
}
|
||||
};
|
||||
var p = Promise.resolve();
|
||||
app.inputContext.replaceSurroundingText.returns(p);
|
||||
|
||||
var text = 'foobar';
|
||||
var offset = 3;
|
||||
var length = 1;
|
||||
glue.init(app, 'foo');
|
||||
var returned = glue.replaceSurroundingText(text, offset, length);
|
||||
|
||||
assert.isTrue(
|
||||
app.inputContext.replaceSurroundingText.calledWith(text, offset, length));
|
||||
assert.equal(returned, p);
|
||||
});
|
||||
|
||||
test('getNumberOfCandidatesPerRow', function() {
|
||||
var glue = new InputMethodGlue();
|
||||
var app = {
|
||||
console: this.sinon.stub(KeyboardConsole.prototype),
|
||||
viewManager: {
|
||||
getNumberOfCandidatesPerRow: this.sinon.stub()
|
||||
}
|
||||
};
|
||||
app.viewManager.getNumberOfCandidatesPerRow.returns(123);
|
||||
glue.init(app, 'foo');
|
||||
var row = glue.getNumberOfCandidatesPerRow();
|
||||
|
||||
assert.equal(row, 123);
|
||||
});
|
||||
|
||||
test('getData', function() {
|
||||
var glue = new InputMethodGlue();
|
||||
var p = { stub: 'promise' };
|
||||
var app = {
|
||||
inputMethodDatabaseLoader: {
|
||||
load: this.sinon.stub().returns(p)
|
||||
}
|
||||
};
|
||||
glue.init(app, 'foo');
|
||||
var result = glue.getData('bar');
|
||||
|
||||
assert.isTrue(
|
||||
app.inputMethodDatabaseLoader.load.calledWithExactly('foo', 'bar'));
|
||||
assert.equal(result, p);
|
||||
});
|
||||
});
|
||||
|
||||
suite('InputMethodLoader', function() {
|
||||
var realInputMethods;
|
||||
|
||||
suiteSetup(function() {
|
||||
realInputMethods = window.InputMethods;
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
window.InputMethods = realInputMethods;
|
||||
});
|
||||
|
||||
test('start', function(done) {
|
||||
var initStub = this.sinon.stub();
|
||||
window.InputMethods = {
|
||||
'preloaded': {
|
||||
init: initStub
|
||||
}
|
||||
};
|
||||
|
||||
var loader = new InputMethodLoader({});
|
||||
loader.start();
|
||||
|
||||
assert.equal(!!window.InputMethods.preloaded, false, 'original removed');
|
||||
assert.isTrue(!!loader.getInputMethod('preloaded'), 'preloaded loaded');
|
||||
assert.equal(initStub.getCall(0).args[0].app, loader.app,
|
||||
'init with a glue object with a correct app');
|
||||
|
||||
var p = loader.getInputMethodAsync('preloaded');
|
||||
p.then(function(imEngine) {
|
||||
assert.isTrue(true, 'loaded');
|
||||
assert.equal(imEngine, loader.getInputMethod('preloaded'));
|
||||
|
||||
done();
|
||||
}, function() {
|
||||
assert.isTrue(false, 'should not reject');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('getInputMethodAsync', function(done) {
|
||||
window.InputMethods = {};
|
||||
|
||||
var loader = new InputMethodLoader({});
|
||||
loader.SOURCE_DIR = './fake-imes/';
|
||||
loader.start();
|
||||
|
||||
var p = loader.getInputMethodAsync('foo');
|
||||
p.then(function(imEngine) {
|
||||
assert.isTrue(true, 'loaded');
|
||||
assert.isTrue(!!loader.getInputMethod('foo'), 'foo loaded');
|
||||
assert.equal(imEngine, loader.getInputMethod('foo'));
|
||||
|
||||
done();
|
||||
}, function() {
|
||||
assert.isTrue(false, 'should not reject');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('getInputMethodAsync (twice)', function(done) {
|
||||
window.InputMethods = {};
|
||||
|
||||
var loader = new InputMethodLoader({});
|
||||
loader.SOURCE_DIR = './fake-imes/';
|
||||
loader.start();
|
||||
|
||||
var p = loader.getInputMethodAsync('foo');
|
||||
p.then(function(imEngine) {
|
||||
assert.isTrue(true, 'loaded');
|
||||
assert.isTrue(!!loader.getInputMethod('foo'), 'foo loaded');
|
||||
assert.equal(imEngine, loader.getInputMethod('foo'));
|
||||
|
||||
var p2 = loader.getInputMethodAsync('foo');
|
||||
assert.equal(p2, p,
|
||||
'Should return the same promise without creating a new one');
|
||||
|
||||
p.then(function(imEngine) {
|
||||
assert.isTrue(true, 'loaded');
|
||||
assert.equal(imEngine, loader.getInputMethod('foo'));
|
||||
|
||||
done();
|
||||
}, function() {
|
||||
assert.isTrue(false, 'should not reject');
|
||||
|
||||
done();
|
||||
});
|
||||
}, function() {
|
||||
assert.isTrue(false, 'should not reject');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('getInputMethodAsync (failed)', function(done) {
|
||||
window.InputMethods = {};
|
||||
|
||||
var loader = new InputMethodLoader({});
|
||||
loader.SOURCE_DIR = './fake-imes/';
|
||||
loader.start();
|
||||
|
||||
var p = loader.getInputMethodAsync('bar');
|
||||
p.then(function() {
|
||||
assert.isTrue(false, 'should not resolve');
|
||||
|
||||
done();
|
||||
}, function() {
|
||||
assert.isTrue(true, 'rejected');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('getInputMethodAsync (twice)', function(done) {
|
||||
window.InputMethods = {};
|
||||
|
||||
var loader = new InputMethodLoader({});
|
||||
loader.SOURCE_DIR = './fake-imes/';
|
||||
loader.start();
|
||||
|
||||
var p = loader.getInputMethodAsync('foo');
|
||||
var p2 = loader.getInputMethodAsync('foo');
|
||||
|
||||
assert.equal(p, p2, 'Return same promise instance for the same IMEngine.');
|
||||
|
||||
p.then(function() {
|
||||
assert.isTrue(true, 'loaded');
|
||||
assert.isTrue(!!loader.getInputMethod('foo'), 'foo loaded');
|
||||
|
||||
done();
|
||||
}, function() {
|
||||
assert.isTrue(false, 'should not reject');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('InputMethodManager', function() {
|
||||
var realInputMethods;
|
||||
var imEngineSettingsStub;
|
||||
var app;
|
||||
var initSettingsPromise;
|
||||
var manager;
|
||||
|
||||
suiteSetup(function() {
|
||||
realInputMethods = window.InputMethods;
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
window.InputMethods = realInputMethods;
|
||||
});
|
||||
|
||||
setup(function () {
|
||||
imEngineSettingsStub = this.sinon.stub(IMEngineSettings.prototype);
|
||||
this.sinon.stub(window, 'IMEngineSettings').returns(imEngineSettingsStub);
|
||||
initSettingsPromise = Promise.resolve({
|
||||
suggestionsEnabled: true,
|
||||
correctionsEnabled: true
|
||||
});
|
||||
imEngineSettingsStub.initSettings.returns(initSettingsPromise);
|
||||
|
||||
app = {
|
||||
promiseManager: {},
|
||||
layoutManager: {
|
||||
currentPage: {
|
||||
autoCorrectLanguage: 'xx-XX',
|
||||
autoCorrectPunctuation: true
|
||||
}
|
||||
},
|
||||
console: this.sinon.stub(KeyboardConsole.prototype),
|
||||
inputContext: {
|
||||
inputType: 'text',
|
||||
inputMode: '',
|
||||
selectionStart: 0,
|
||||
selectionEnd: 0,
|
||||
text: 'foobar',
|
||||
textBeforeCursor: '',
|
||||
textAfterCursor: '',
|
||||
addEventListener: this.sinon.stub(),
|
||||
removeEventListener: this.sinon.stub()
|
||||
}
|
||||
};
|
||||
|
||||
window.InputMethods = {
|
||||
'default': realInputMethods['default']
|
||||
};
|
||||
|
||||
manager = new InputMethodManager(app);
|
||||
manager.start();
|
||||
manager.loader.SOURCE_DIR = './fake-imes/';
|
||||
|
||||
assert.equal(manager.loader.app, manager.app);
|
||||
assert.isTrue(!!manager.currentIMEngine, 'started with default IMEngine.');
|
||||
});
|
||||
|
||||
test('activateIMEngine', function(done) {
|
||||
var p = manager.activateIMEngine('foo');
|
||||
p.then(function() {
|
||||
assert.isTrue(true, 'resolved');
|
||||
var imEngine = manager.loader.getInputMethod('foo');
|
||||
assert.isTrue(!!imEngine, 'foo loaded');
|
||||
assert.equal(manager.currentIMEngine, imEngine, 'currentIMEngine is set');
|
||||
|
||||
var activateStub = imEngine.activate;
|
||||
assert.isTrue(activateStub.calledWithExactly('xx-XX', {
|
||||
type: 'text',
|
||||
inputmode: '',
|
||||
selectionStart: 0,
|
||||
selectionEnd: 0,
|
||||
text: 'foobar'
|
||||
}, {
|
||||
suggest: true,
|
||||
correct: true,
|
||||
correctPunctuation: true
|
||||
}));
|
||||
assert.equal(activateStub.getCall(0).thisValue,
|
||||
imEngine);
|
||||
}, function(e) {
|
||||
if (e) {
|
||||
throw e;
|
||||
}
|
||||
assert.isTrue(false, 'should not reject');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('activateIMEngine, autoCorrectPunctuation false', function(done) {
|
||||
initSettingsPromise = Promise.resolve({
|
||||
suggestionsEnabled: true,
|
||||
correctionsEnabled: true
|
||||
});
|
||||
imEngineSettingsStub.initSettings.returns(initSettingsPromise);
|
||||
|
||||
manager.app.layoutManager.currentPage = {
|
||||
autoCorrectPunctuation: false
|
||||
};
|
||||
|
||||
var p = manager.activateIMEngine('foo');
|
||||
p.then(function() {
|
||||
assert.isTrue(true, 'resolved');
|
||||
var imEngine = manager.loader.getInputMethod('foo');
|
||||
var activateStub = imEngine.activate;
|
||||
|
||||
assert.deepEqual(activateStub.args[0][2], {
|
||||
suggest: true,
|
||||
correct: true,
|
||||
correctPunctuation: false
|
||||
});
|
||||
}, function(e) {
|
||||
if (e) {
|
||||
throw e;
|
||||
}
|
||||
assert.isTrue(false, 'should not reject');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('activateIMEngine (failed loader)', function(done) {
|
||||
var p = manager.activateIMEngine('bar');
|
||||
p.then(function() {
|
||||
assert.isTrue(false, 'should not resolve');
|
||||
}, function() {
|
||||
assert.isTrue(true, 'rejected');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('activateIMEngine (deactivate and reload after loaded)', function(done) {
|
||||
var p = manager.activateIMEngine('foo');
|
||||
p.then(function() {
|
||||
assert.isTrue(true, 'resolved');
|
||||
var imEngine = manager.loader.getInputMethod('foo');
|
||||
assert.isTrue(!!imEngine, 'foo loaded');
|
||||
assert.equal(manager.currentIMEngine, imEngine, 'currentIMEngine is set');
|
||||
|
||||
var activateStub = imEngine.activate;
|
||||
assert.isTrue(activateStub.calledWithExactly('xx-XX', {
|
||||
type: 'text',
|
||||
inputmode: '',
|
||||
selectionStart: 0,
|
||||
selectionEnd: 0,
|
||||
text: 'foobar'
|
||||
}, {
|
||||
suggest: true,
|
||||
correct: true,
|
||||
correctPunctuation: true
|
||||
}));
|
||||
assert.equal(activateStub.getCall(0).thisValue,
|
||||
imEngine);
|
||||
|
||||
manager.deactivateIMEngine();
|
||||
|
||||
var deactivateStub = imEngine.deactivate;
|
||||
assert.isTrue(deactivateStub.calledOnce,
|
||||
'deactivate should be called at ' +
|
||||
'InputMethodManager#deactivateIMEngine()');
|
||||
assert.equal(deactivateStub.getCall(0).thisValue,
|
||||
imEngine);
|
||||
|
||||
var p2 = manager.activateIMEngine('foo');
|
||||
|
||||
assert.equal(manager.currentIMEngine,
|
||||
manager.loader.getInputMethod('default'),
|
||||
'currentIMEngine is set to default');
|
||||
|
||||
return p2;
|
||||
}, function(e) {
|
||||
if (e) {
|
||||
throw e;
|
||||
}
|
||||
assert.isTrue(false, 'should not reject');
|
||||
}).then(function() {
|
||||
assert.isTrue(true, 'resolved');
|
||||
var imEngine = manager.loader.getInputMethod('foo');
|
||||
assert.isTrue(!!imEngine, 'foo loaded');
|
||||
assert.equal(manager.currentIMEngine, imEngine,
|
||||
'currentIMEngine is set');
|
||||
|
||||
var activateStub = imEngine.activate;
|
||||
assert.isTrue(activateStub.calledTwice);
|
||||
assert.isTrue(activateStub.getCall(1).calledWithExactly('xx-XX', {
|
||||
type: 'text',
|
||||
inputmode: '',
|
||||
selectionStart: 0,
|
||||
selectionEnd: 0,
|
||||
text: 'foobar'
|
||||
}, {
|
||||
suggest: true,
|
||||
correct: true,
|
||||
correctPunctuation: true
|
||||
}));
|
||||
assert.equal(activateStub.getCall(1).thisValue,
|
||||
imEngine);
|
||||
}, function(e) {
|
||||
if (e) {
|
||||
throw e;
|
||||
}
|
||||
assert.isTrue(false, 'should not reject');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('selectionchange', function(done) {
|
||||
var p = manager.activateIMEngine('foo');
|
||||
p.then(function() {
|
||||
assert.isTrue(app.inputContext.addEventListener.calledTwice);
|
||||
|
||||
manager.handleEvent({
|
||||
type: 'selectionchange',
|
||||
target: app.inputContext,
|
||||
detail: {
|
||||
selectionStart: 0,
|
||||
selectionEnd: 0,
|
||||
ownAction: true
|
||||
}
|
||||
});
|
||||
}, function() {
|
||||
assert.isTrue(false, 'should not reject');
|
||||
}).then(function() {
|
||||
var imEngine = manager.loader.getInputMethod('foo');
|
||||
assert.isTrue(imEngine.stateChange.calledWithExactly({
|
||||
ownAction: true,
|
||||
type: 'text',
|
||||
inputmode: '',
|
||||
selectionStart: 0,
|
||||
selectionEnd: 0,
|
||||
text: 'foobar'
|
||||
}));
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('surroundingtextchange', function(done) {
|
||||
var p = manager.activateIMEngine('foo');
|
||||
p.then(function() {
|
||||
assert.isTrue(app.inputContext.addEventListener.calledTwice);
|
||||
|
||||
manager.handleEvent({
|
||||
type: 'surroundingtextchange',
|
||||
target: app.inputContext,
|
||||
detail: {
|
||||
text: '',
|
||||
textBeforeCursor: '',
|
||||
textAfterCursor: '',
|
||||
ownAction: true
|
||||
}
|
||||
});
|
||||
}, function() {
|
||||
assert.isTrue(false, 'should not reject');
|
||||
}).then(function() {
|
||||
var imEngine = manager.loader.getInputMethod('foo');
|
||||
assert.isTrue(imEngine.stateChange.calledWithExactly({
|
||||
ownAction: true,
|
||||
type: 'text',
|
||||
inputmode: '',
|
||||
selectionStart: 0,
|
||||
selectionEnd: 0,
|
||||
text: 'foobar'
|
||||
}));
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
313
gaia/apps/keyboard/test/unit/keyboard/keyboard_app_test.js
Executable file
313
gaia/apps/keyboard/test/unit/keyboard/keyboard_app_test.js
Executable file
|
|
@ -0,0 +1,313 @@
|
|||
'use strict';
|
||||
|
||||
/* global KeyboardApp, KeyboardConsole, InputMethodManager,
|
||||
InputMethodDatabaseLoader, LayoutManager, SettingsPromiseManager,
|
||||
L10nLoader, TargetHandlersManager, FeedbackManager,
|
||||
VisualHighlightManager, CandidatePanelManager, UpperCaseStateManager,
|
||||
LayoutRenderingManager, StateManager,
|
||||
MockInputMethodManager, HandwritingPadsManager, ViewManager */
|
||||
|
||||
require('/js/keyboard/console.js');
|
||||
require('/js/keyboard/input_method_manager.js');
|
||||
require('/js/keyboard/input_method_database_loader.js');
|
||||
require('/js/keyboard/layout_manager.js');
|
||||
require('/js/keyboard/settings.js');
|
||||
require('/js/keyboard/l10n_loader.js');
|
||||
require('/js/keyboard/target_handlers_manager.js');
|
||||
require('/js/keyboard/handwriting_pads_manager.js');
|
||||
require('/js/keyboard/feedback_manager.js');
|
||||
require('/js/keyboard/visual_highlight_manager.js');
|
||||
require('/js/keyboard/candidate_panel_manager.js');
|
||||
require('/js/keyboard/upper_case_state_manager.js');
|
||||
require('/js/keyboard/layout_rendering_manager.js');
|
||||
require('/js/keyboard/state_manager.js');
|
||||
require('/js/keyboard/view_manager.js');
|
||||
|
||||
requireApp('keyboard/shared/test/unit/mocks/mock_event_target.js');
|
||||
requireApp('keyboard/shared/test/unit/mocks/mock_navigator_input_method.js');
|
||||
|
||||
require('/js/keyboard/keyboard_app.js');
|
||||
|
||||
suite('KeyboardApp', function() {
|
||||
var consoleStub;
|
||||
var inputMethodManagerStub;
|
||||
var inputMethodDatabaseLoaderStub;
|
||||
var layoutManagerStub;
|
||||
var settingsPromiseManagerStub;
|
||||
var l10nLoaderStub;
|
||||
var targetHandlersManagerStub;
|
||||
var handwritingPadsManagerStub;
|
||||
var feedbackManagerStub;
|
||||
var visualHighlightManagerStub;
|
||||
var candidatePanelManagerStub;
|
||||
var upperCaseStateManagerStub;
|
||||
var layoutRenderingManagerStub;
|
||||
var stateManagerStub;
|
||||
var viewManagerStub;
|
||||
|
||||
var app;
|
||||
var realMozInputMethod;
|
||||
|
||||
setup(function() {
|
||||
realMozInputMethod = navigator.mozInputMethod;
|
||||
navigator.mozInputMethod = {
|
||||
mgmt: this.sinon.stub(MockInputMethodManager.prototype)
|
||||
};
|
||||
|
||||
consoleStub = this.sinon.stub(KeyboardConsole.prototype);
|
||||
this.sinon.stub(window, 'KeyboardConsole').returns(consoleStub);
|
||||
|
||||
inputMethodDatabaseLoaderStub =
|
||||
this.sinon.stub(InputMethodDatabaseLoader.prototype);
|
||||
this.sinon.stub(window, 'InputMethodDatabaseLoader')
|
||||
.returns(inputMethodDatabaseLoaderStub);
|
||||
|
||||
inputMethodManagerStub = this.sinon.stub(InputMethodManager.prototype);
|
||||
this.sinon.stub(window, 'InputMethodManager')
|
||||
.returns(inputMethodManagerStub);
|
||||
|
||||
layoutManagerStub = this.sinon.stub(LayoutManager.prototype);
|
||||
this.sinon.stub(window, 'LayoutManager').returns(layoutManagerStub);
|
||||
|
||||
settingsPromiseManagerStub =
|
||||
this.sinon.stub(SettingsPromiseManager.prototype);
|
||||
this.sinon.stub(window, 'SettingsPromiseManager')
|
||||
.returns(settingsPromiseManagerStub);
|
||||
|
||||
l10nLoaderStub = this.sinon.stub(L10nLoader.prototype);
|
||||
this.sinon.stub(window, 'L10nLoader').returns(l10nLoaderStub);
|
||||
|
||||
targetHandlersManagerStub =
|
||||
this.sinon.stub(TargetHandlersManager.prototype);
|
||||
this.sinon.stub(window, 'TargetHandlersManager')
|
||||
.returns(targetHandlersManagerStub);
|
||||
|
||||
handwritingPadsManagerStub =
|
||||
this.sinon.stub(HandwritingPadsManager.prototype);
|
||||
this.sinon.stub(window, 'HandwritingPadsManager')
|
||||
.returns(handwritingPadsManagerStub);
|
||||
|
||||
feedbackManagerStub = this.sinon.stub(FeedbackManager.prototype);
|
||||
this.sinon.stub(window, 'FeedbackManager').returns(feedbackManagerStub);
|
||||
|
||||
visualHighlightManagerStub =
|
||||
this.sinon.stub(VisualHighlightManager.prototype);
|
||||
this.sinon.stub(window, 'VisualHighlightManager')
|
||||
.returns(visualHighlightManagerStub);
|
||||
|
||||
candidatePanelManagerStub =
|
||||
this.sinon.stub(CandidatePanelManager.prototype);
|
||||
this.sinon.stub(window, 'CandidatePanelManager')
|
||||
.returns(candidatePanelManagerStub);
|
||||
|
||||
upperCaseStateManagerStub =
|
||||
this.sinon.stub(UpperCaseStateManager.prototype);
|
||||
this.sinon.stub(window, 'UpperCaseStateManager')
|
||||
.returns(upperCaseStateManagerStub);
|
||||
|
||||
layoutRenderingManagerStub =
|
||||
this.sinon.stub(LayoutRenderingManager.prototype);
|
||||
this.sinon.stub(window, 'LayoutRenderingManager')
|
||||
.returns(layoutRenderingManagerStub);
|
||||
|
||||
stateManagerStub =
|
||||
this.sinon.stub(StateManager.prototype);
|
||||
this.sinon.stub(window, 'StateManager')
|
||||
.returns(stateManagerStub);
|
||||
|
||||
viewManagerStub =
|
||||
this.sinon.stub(ViewManager.prototype);
|
||||
this.sinon.stub(window, 'ViewManager')
|
||||
.returns(viewManagerStub);
|
||||
|
||||
window.requestAnimationFrame = this.sinon.stub();
|
||||
|
||||
app = new KeyboardApp();
|
||||
app.start();
|
||||
|
||||
assert.isTrue(window.KeyboardConsole.calledWithNew());
|
||||
assert.isTrue(window.InputMethodManager.calledWithNew());
|
||||
assert.isTrue(window.InputMethodDatabaseLoader.calledWithNew());
|
||||
assert.isTrue(window.LayoutManager.calledWithNew());
|
||||
assert.isTrue(window.SettingsPromiseManager.calledWithNew());
|
||||
assert.isTrue(window.L10nLoader.calledWithNew());
|
||||
assert.isTrue(window.TargetHandlersManager.calledWithNew());
|
||||
assert.isTrue(window.FeedbackManager.calledWithNew());
|
||||
assert.isTrue(window.VisualHighlightManager.calledWithNew());
|
||||
assert.isTrue(window.CandidatePanelManager.calledWithNew());
|
||||
assert.isTrue(window.UpperCaseStateManager.calledWithNew());
|
||||
assert.isTrue(window.LayoutRenderingManager.calledWithNew());
|
||||
assert.isTrue(window.StateManager.calledWithNew());
|
||||
assert.isTrue(window.ViewManager.calledWithNew());
|
||||
|
||||
assert.isTrue(window.InputMethodManager.calledWith(app));
|
||||
assert.isTrue(window.InputMethodDatabaseLoader.calledWith(app));
|
||||
assert.isTrue(window.LayoutManager.calledWith(app));
|
||||
assert.isTrue(window.TargetHandlersManager.calledWith(app));
|
||||
assert.isTrue(window.FeedbackManager.calledWith(app));
|
||||
assert.isTrue(window.VisualHighlightManager.calledWith(app));
|
||||
assert.isTrue(window.CandidatePanelManager.calledWith(app));
|
||||
assert.isTrue(window.ViewManager.calledWith(app));
|
||||
|
||||
assert.isTrue(consoleStub.start.calledOnce);
|
||||
assert.isTrue(inputMethodManagerStub.start.calledOnce);
|
||||
assert.isTrue(inputMethodDatabaseLoaderStub.start.calledOnce);
|
||||
assert.isTrue(layoutManagerStub.start.calledOnce);
|
||||
assert.isTrue(targetHandlersManagerStub.start.calledOnce);
|
||||
assert.isTrue(handwritingPadsManagerStub.start.calledOnce);
|
||||
assert.isTrue(feedbackManagerStub.start.calledOnce);
|
||||
assert.isTrue(visualHighlightManagerStub.start.calledOnce);
|
||||
assert.isTrue(candidatePanelManagerStub.start.calledOnce);
|
||||
assert.isTrue(upperCaseStateManagerStub.start.calledOnce);
|
||||
assert.isTrue(layoutRenderingManagerStub.start.calledOnce);
|
||||
assert.isTrue(stateManagerStub.start.calledOnce);
|
||||
assert.isTrue(viewManagerStub.start.calledOnce);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
app.stop();
|
||||
|
||||
assert.isTrue(inputMethodDatabaseLoaderStub.stop.calledOnce);
|
||||
assert.isTrue(targetHandlersManagerStub.stop.calledOnce);
|
||||
assert.isTrue(handwritingPadsManagerStub.stop.calledOnce);
|
||||
assert.isTrue(feedbackManagerStub.stop.calledOnce);
|
||||
assert.isTrue(visualHighlightManagerStub.stop.calledOnce);
|
||||
assert.isTrue(candidatePanelManagerStub.stop.calledOnce);
|
||||
assert.isTrue(upperCaseStateManagerStub.stop.calledOnce);
|
||||
assert.isTrue(layoutRenderingManagerStub.stop.calledOnce);
|
||||
assert.isTrue(stateManagerStub.stop.calledOnce);
|
||||
assert.isTrue(viewManagerStub.stop.calledOnce);
|
||||
|
||||
navigator.mozInputMethod = realMozInputMethod;
|
||||
});
|
||||
|
||||
test('getContainer', function() {
|
||||
var el = {};
|
||||
this.sinon.stub(document, 'getElementById').returns(el);
|
||||
|
||||
var result = app.getContainer();
|
||||
assert.isTrue(
|
||||
document.getElementById.calledWith(app.CONATINER_ELEMENT_ID));
|
||||
assert.equal(result, el);
|
||||
});
|
||||
|
||||
test('setInputContext', function() {
|
||||
var inputContext = {};
|
||||
|
||||
app.setInputContext(inputContext);
|
||||
|
||||
assert.equal(app.inputContext, inputContext);
|
||||
});
|
||||
|
||||
suite('getBasicInputType', function() {
|
||||
test('without inputContext', function() {
|
||||
var result = app.getBasicInputType();
|
||||
assert.equal(result, 'text');
|
||||
});
|
||||
|
||||
test('inputType = url', function() {
|
||||
app.inputContext = {
|
||||
inputType: 'url'
|
||||
};
|
||||
|
||||
var result = app.getBasicInputType();
|
||||
assert.equal(result, 'url');
|
||||
});
|
||||
|
||||
test('inputType = tel', function() {
|
||||
app.inputContext = {
|
||||
inputType: 'tel'
|
||||
};
|
||||
|
||||
var result = app.getBasicInputType();
|
||||
assert.equal(result, 'tel');
|
||||
});
|
||||
|
||||
test('inputType = email', function() {
|
||||
app.inputContext = {
|
||||
inputType: 'email'
|
||||
};
|
||||
|
||||
var result = app.getBasicInputType();
|
||||
assert.equal(result, 'email');
|
||||
});
|
||||
|
||||
test('inputType = text', function() {
|
||||
app.inputContext = {
|
||||
inputType: 'text'
|
||||
};
|
||||
|
||||
var result = app.getBasicInputType();
|
||||
assert.equal(result, 'text');
|
||||
});
|
||||
|
||||
test('inputType = password', function() {
|
||||
app.inputContext = {
|
||||
inputType: 'password'
|
||||
};
|
||||
|
||||
var result = app.getBasicInputType();
|
||||
assert.equal(result, 'text');
|
||||
});
|
||||
|
||||
test('inputType = search', function() {
|
||||
app.inputContext = {
|
||||
inputType: 'search'
|
||||
};
|
||||
|
||||
var result = app.getBasicInputType();
|
||||
assert.equal(result, 'search');
|
||||
});
|
||||
|
||||
test('inputType = foo', function() {
|
||||
app.inputContext = {
|
||||
inputType: 'foo'
|
||||
};
|
||||
|
||||
var result = app.getBasicInputType();
|
||||
assert.equal(result, 'text');
|
||||
});
|
||||
|
||||
test('inputType = number', function() {
|
||||
app.inputContext = {
|
||||
inputType: 'number'
|
||||
};
|
||||
|
||||
var result = app.getBasicInputType();
|
||||
assert.equal(result, 'number');
|
||||
});
|
||||
|
||||
test('inputType = range', function() {
|
||||
app.inputContext = {
|
||||
inputType: 'range'
|
||||
};
|
||||
|
||||
var result = app.getBasicInputType();
|
||||
assert.equal(result, 'number');
|
||||
});
|
||||
});
|
||||
|
||||
test('supportsSwitching', function() {
|
||||
navigator.mozInputMethod.mgmt.supportsSwitching.returns(true);
|
||||
|
||||
var result = app.supportsSwitching();
|
||||
assert.isTrue(navigator.mozInputMethod.mgmt.supportsSwitching.calledOnce);
|
||||
assert.isTrue(result);
|
||||
});
|
||||
|
||||
test('setLayoutPage', function() {
|
||||
app.inputMethodManager.currentIMEngine = {
|
||||
setLayoutPage: this.sinon.stub()
|
||||
};
|
||||
app.layoutManager.currentPageIndex = 42;
|
||||
|
||||
app.setLayoutPage(12);
|
||||
|
||||
assert.isTrue(
|
||||
app.layoutManager.updateLayoutPage.calledWith(12));
|
||||
assert.isTrue(
|
||||
app.layoutRenderingManager.updateLayoutRendering.calledOnce);
|
||||
assert.isTrue(
|
||||
app.inputMethodManager.currentIMEngine.setLayoutPage.calledWith(42));
|
||||
});
|
||||
});
|
||||
29
gaia/apps/keyboard/test/unit/keyboard/l10n_loader_test.js
Executable file
29
gaia/apps/keyboard/test/unit/keyboard/l10n_loader_test.js
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
'use strict';
|
||||
|
||||
/* global L10nLoader */
|
||||
|
||||
require('/js/keyboard/l10n_loader.js');
|
||||
|
||||
suite('L10nLoader', function() {
|
||||
test('load() should load the script', function() {
|
||||
var appendChildSpy = this.sinon.spy(document.body, 'appendChild');
|
||||
|
||||
var loader = new L10nLoader();
|
||||
loader.SCRIPT_URL = encodeURI('data:text/javascript,');
|
||||
|
||||
loader.load();
|
||||
assert.equal(appendChildSpy.getCall(0).args[0].src, loader.SCRIPT_URL);
|
||||
});
|
||||
|
||||
test('load() should load the script once when called twice', function() {
|
||||
var appendChildSpy = this.sinon.spy(document.body, 'appendChild');
|
||||
|
||||
var loader = new L10nLoader();
|
||||
loader.SCRIPT_URL = encodeURI('data:text/javascript,');
|
||||
|
||||
loader.load();
|
||||
loader.load();
|
||||
|
||||
assert.equal(appendChildSpy.callCount, 1);
|
||||
});
|
||||
});
|
||||
217
gaia/apps/keyboard/test/unit/keyboard/layout_loader_test.js
Executable file
217
gaia/apps/keyboard/test/unit/keyboard/layout_loader_test.js
Executable file
|
|
@ -0,0 +1,217 @@
|
|||
'use strict';
|
||||
|
||||
/* global LayoutLoader, LayoutNormalizer */
|
||||
|
||||
require('/js/keyboard/layout_loader.js');
|
||||
require('/js/keyboard/layout_normalizer.js');
|
||||
|
||||
suite('LayoutLoader', function() {
|
||||
var realKeyboards;
|
||||
|
||||
var expectedFooLayout = {
|
||||
keys: [
|
||||
[
|
||||
{ value: 'foo' }
|
||||
]
|
||||
]
|
||||
};
|
||||
|
||||
var expectedFoo2Layout = {
|
||||
keys: [
|
||||
[
|
||||
{ value: 'foo2' }
|
||||
]
|
||||
]
|
||||
};
|
||||
|
||||
var expectedFoo3Layout = {
|
||||
keys: [
|
||||
[
|
||||
{ value: 'foo3' }
|
||||
]
|
||||
]
|
||||
};
|
||||
|
||||
suiteSetup(function() {
|
||||
realKeyboards = window.Keyboards;
|
||||
sinon.stub(LayoutNormalizer.prototype, 'normalize', function(){
|
||||
this._normalizedLayout = this._layout;
|
||||
});
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
window.Keyboards = realKeyboards;
|
||||
});
|
||||
|
||||
test('start', function(done) {
|
||||
window.Keyboards = {
|
||||
'preloaded': {
|
||||
keys: [
|
||||
[
|
||||
{ value: 'preloaded' }
|
||||
]
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
var loader = new LayoutLoader();
|
||||
loader.start();
|
||||
|
||||
assert.equal(!!window.Keyboards.preloaded, false, 'original removed');
|
||||
assert.isTrue(!!loader.getLayout('preloaded'), 'preloaded loaded');
|
||||
|
||||
var p = loader.getLayoutAsync('preloaded');
|
||||
p.then(function(layout) {
|
||||
assert.isTrue(true, 'loaded');
|
||||
// the loader instantiates normalizer by using its getLayout
|
||||
// so testing this would test getLayout at the same time
|
||||
// (same for following tests)
|
||||
assert.deepEqual(
|
||||
loader._initializedLayouts.preloaded,
|
||||
{
|
||||
keys: [
|
||||
[
|
||||
{ value: 'preloaded' }
|
||||
]
|
||||
]
|
||||
},
|
||||
'normalizer argument "layout" incorrect'
|
||||
);
|
||||
}, function(e) {
|
||||
if (e) {
|
||||
throw (e);
|
||||
}
|
||||
assert.isTrue(false, 'should not reject');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('getLayoutAsync', function(done) {
|
||||
window.Keyboards = {};
|
||||
|
||||
var loader = new LayoutLoader();
|
||||
loader.SOURCE_DIR = './fake-layouts/';
|
||||
loader.start();
|
||||
|
||||
var p = loader.getLayoutAsync('foo');
|
||||
p.then(function(layout) {
|
||||
assert.deepEqual(
|
||||
loader._initializedLayouts.foo,
|
||||
expectedFooLayout,
|
||||
'normalizer argument "layout" incorrect'
|
||||
);
|
||||
assert.isTrue(true, 'loaded');
|
||||
assert.equal(layout, loader.getLayout('foo'));
|
||||
}, function(e) {
|
||||
if (e) {
|
||||
throw (e);
|
||||
}
|
||||
assert.isTrue(false, 'should not reject');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('getLayoutAsync (init two layouts)', function(done) {
|
||||
window.Keyboards = {};
|
||||
|
||||
var loader = new LayoutLoader();
|
||||
loader.SOURCE_DIR = './fake-layouts/';
|
||||
loader.start();
|
||||
|
||||
var p = loader.getLayoutAsync('foo2');
|
||||
p.then(function(layout) {
|
||||
assert.isTrue(true, 'loaded');
|
||||
assert.deepEqual(
|
||||
loader._initializedLayouts.foo2,
|
||||
expectedFoo2Layout,
|
||||
'normalizer argument "layout" incorrect'
|
||||
);
|
||||
assert.deepEqual(
|
||||
loader._initializedLayouts.foo3,
|
||||
expectedFoo3Layout,
|
||||
'normalizer argument "layout" incorrect'
|
||||
);
|
||||
assert.deepEqual(window.Keyboards, {});
|
||||
|
||||
var p2 = loader.getLayoutAsync('foo3');
|
||||
|
||||
return p2;
|
||||
}).then(function(layout) {
|
||||
assert.equal(layout, loader.getLayout('foo3'));
|
||||
}, function(e) {
|
||||
if (e) {
|
||||
throw (e);
|
||||
}
|
||||
assert.isTrue(false, 'should not reject');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('getLayoutAsync (twice after first one)', function(done) {
|
||||
window.Keyboards = {};
|
||||
|
||||
var loader = new LayoutLoader();
|
||||
loader.SOURCE_DIR = './fake-layouts/';
|
||||
loader.start();
|
||||
|
||||
var p = loader.getLayoutAsync('foo');
|
||||
p.then(function(layout) {
|
||||
assert.isTrue(true, 'loaded');
|
||||
assert.deepEqual(
|
||||
loader.getLayout('foo'), expectedFooLayout, 'foo loaded');
|
||||
assert.equal(layout, loader.getLayout('foo'));
|
||||
var p2 = loader.getLayoutAsync('foo');
|
||||
assert.equal(p2, p,
|
||||
'Should return the same promise without creating a new one');
|
||||
|
||||
return p2;
|
||||
}).then(function(layout) {
|
||||
assert.isTrue(true, 'loaded');
|
||||
assert.equal(layout, loader.getLayout('foo'));
|
||||
}, function(e) {
|
||||
if (e) {
|
||||
throw (e);
|
||||
}
|
||||
assert.isTrue(false, 'should not reject');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('getLayoutAsync (failed)', function(done) {
|
||||
window.Keyboards = {};
|
||||
|
||||
var loader = new LayoutLoader();
|
||||
loader.SOURCE_DIR = './fake-layouts/';
|
||||
loader.start();
|
||||
|
||||
var p = loader.getLayoutAsync('bar');
|
||||
p.then(function() {
|
||||
assert.isTrue(false, 'should not resolve');
|
||||
}, function(e) {
|
||||
if (e) {
|
||||
throw (e);
|
||||
}
|
||||
assert.isTrue(true, 'rejected');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('getLayoutAsync (twice at the same time)', function(done) {
|
||||
window.Keyboards = {};
|
||||
|
||||
var loader = new LayoutLoader();
|
||||
loader.SOURCE_DIR = './fake-layouts/';
|
||||
loader.start();
|
||||
|
||||
var p = loader.getLayoutAsync('foo');
|
||||
var p2 = loader.getLayoutAsync('foo');
|
||||
|
||||
assert.equal(p, p2, 'Return same promise instance for the same layout.');
|
||||
|
||||
p.then(function() {
|
||||
assert.isTrue(true, 'loaded');
|
||||
assert.deepEqual(
|
||||
loader.getLayout('foo'), expectedFooLayout, 'foo loaded');
|
||||
}, function(e) {
|
||||
if (e) {
|
||||
throw (e);
|
||||
}
|
||||
assert.isTrue(false, 'should not reject');
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
1474
gaia/apps/keyboard/test/unit/keyboard/layout_manager_test.js
Executable file
1474
gaia/apps/keyboard/test/unit/keyboard/layout_manager_test.js
Executable file
File diff suppressed because it is too large
Load diff
681
gaia/apps/keyboard/test/unit/keyboard/layout_normalizer_test.js
Executable file
681
gaia/apps/keyboard/test/unit/keyboard/layout_normalizer_test.js
Executable file
|
|
@ -0,0 +1,681 @@
|
|||
'use strict';
|
||||
|
||||
/* global LayoutNormalizer, LayoutKeyNormalizer, KeyEvent */
|
||||
|
||||
require('/js/keyboard/layout_normalizer.js');
|
||||
|
||||
// little helper to build an array filled with numbers from |from| to |to|.
|
||||
// ex: range(1, 10) -> [1,2,3,...9]
|
||||
// useful for looping a specific number of iterations
|
||||
var range = (from, to) => {
|
||||
if (undefined === to) {
|
||||
to = from;
|
||||
from = 0;
|
||||
}
|
||||
|
||||
var rangeGenerator = function *() {
|
||||
for(var i = from; i < to; i++){
|
||||
yield i;
|
||||
}
|
||||
};
|
||||
|
||||
return Array.from(rangeGenerator());
|
||||
};
|
||||
|
||||
suite('LayoutKeyNormalizer', function() {
|
||||
var SPECIAL_CODES_MAP = {
|
||||
'VK_BACK_SPACE': KeyEvent.DOM_VK_BACK_SPACE,
|
||||
'VK_CAPS_LOCK': KeyEvent.DOM_VK_CAPS_LOCK,
|
||||
'VK_RETURN': KeyEvent.DOM_VK_RETURN,
|
||||
'VK_VK_ALT': KeyEvent.DOM_VK_ALT
|
||||
};
|
||||
|
||||
suite('isSpecialKey', function() {
|
||||
test('keyCode is in special codes', function(){
|
||||
var normalizer = new LayoutKeyNormalizer();
|
||||
|
||||
Object.keys(SPECIAL_CODES_MAP).forEach(keyCodeName => {
|
||||
var key = {
|
||||
keyCode: SPECIAL_CODES_MAP[keyCodeName]
|
||||
};
|
||||
assert.isTrue(normalizer._isSpecialKey(key),
|
||||
keyCodeName + ' should be regarded as special key.');
|
||||
});
|
||||
});
|
||||
|
||||
test('keyCode is <= 0', function(){
|
||||
var normalizer = new LayoutKeyNormalizer();
|
||||
|
||||
// for the sake of this test, we only test 0 to -128
|
||||
range(-128, 1).forEach(keyCode => {
|
||||
var key = {
|
||||
keyCode: keyCode
|
||||
};
|
||||
assert.isTrue(normalizer._isSpecialKey(key),
|
||||
keyCode + ' should be regarded as special key.');
|
||||
});
|
||||
});
|
||||
|
||||
test('keyCode is > 0 and is not in special codes', function(){
|
||||
var normalizer = new LayoutKeyNormalizer();
|
||||
|
||||
// for the sake of this test, we only test 1 to 255
|
||||
var keyCodeName; // linter needs this definition
|
||||
var specialKeyCodes = [for (keyCodeName of Object.keys(SPECIAL_CODES_MAP))
|
||||
SPECIAL_CODES_MAP[keyCodeName]];
|
||||
range(1, 256).forEach(keyCode => {
|
||||
if (specialKeyCodes.indexOf(keyCode) !== -1) {
|
||||
return;
|
||||
}
|
||||
var key = {
|
||||
keyCode: keyCode
|
||||
};
|
||||
assert.isFalse(normalizer._isSpecialKey(key),
|
||||
keyCode + ' should not be regarded as special key.');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('isButton', function() {
|
||||
// As per bug#1212588, some special keys are to be regarded as
|
||||
// buttons (as per accesibility programs), but not all of
|
||||
// them. This unit test makes sure the functionality exists in the
|
||||
// layout normalizer. In case different keys are later decided to
|
||||
// swap roles, please change this test so that it stays
|
||||
// meaningful.
|
||||
|
||||
var key = {
|
||||
keyCode: KeyEvent.DOM_VK_ALT,
|
||||
value: 'alt'
|
||||
};
|
||||
|
||||
var button = {
|
||||
keyCode: KeyEvent.DOM_VK_RETURN,
|
||||
value: 'enter'
|
||||
};
|
||||
|
||||
test('Alt is a key', function(){
|
||||
var normalizer = new LayoutKeyNormalizer();
|
||||
|
||||
assert.isFalse(normalizer._isButton(key),
|
||||
'alt should be regarded as a role=key, not a button.');
|
||||
});
|
||||
|
||||
test('Return is a button', function(){
|
||||
var normalizer = new LayoutKeyNormalizer();
|
||||
|
||||
assert.isTrue(normalizer._isButton(button),
|
||||
'return should be regarded as a role=button, not a key.');
|
||||
});
|
||||
|
||||
test('isButton present on normalized key', function(){
|
||||
var normalizer = new LayoutNormalizer({
|
||||
keys: [
|
||||
[{value: 'a'}]
|
||||
],
|
||||
upperCase: {'a': 'C'}
|
||||
});
|
||||
|
||||
normalizer.normalize();
|
||||
|
||||
assert.isTrue(
|
||||
normalizer._layout.pages[0].keys[0][0].hasOwnProperty('isButton'),
|
||||
'a synthetic key made from a simple keyboard layout ' +
|
||||
'should have property isButton');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
suite('getUpperCaseValue', function() {
|
||||
// For space key, special key, and composite key tests,
|
||||
// we're deliberately not passing layout,
|
||||
// as if the normalizer needs to use layout in the function, then something
|
||||
// is definitely wrong.
|
||||
|
||||
test('Space key', function(){
|
||||
var normalizer = new LayoutKeyNormalizer();
|
||||
|
||||
var key = {
|
||||
keyCode: KeyEvent.DOM_VK_SPACE,
|
||||
value: 'space'
|
||||
};
|
||||
assert.equal(normalizer._getUpperCaseValue(key), 'space');
|
||||
});
|
||||
|
||||
test('Special keys', function(){
|
||||
var normalizer = new LayoutKeyNormalizer();
|
||||
|
||||
Object.keys(SPECIAL_CODES_MAP).forEach(keyCodeName => {
|
||||
var key = {
|
||||
keyCode: SPECIAL_CODES_MAP[keyCodeName],
|
||||
value: keyCodeName
|
||||
};
|
||||
assert.equal(normalizer._getUpperCaseValue(key), keyCodeName);
|
||||
});
|
||||
});
|
||||
|
||||
test('Composite keys', function(){
|
||||
var normalizer = new LayoutKeyNormalizer();
|
||||
|
||||
var key = {
|
||||
keyCode: 'C'.charCodeAt(0),
|
||||
value: 'Com',
|
||||
compositeKey: 'Com'
|
||||
};
|
||||
assert.equal(normalizer._getUpperCaseValue(key), 'Com');
|
||||
});
|
||||
|
||||
test('Use uppercase from layout', function(){
|
||||
var normalizer = new LayoutKeyNormalizer({
|
||||
upperCase: {
|
||||
'a': 'E'
|
||||
}
|
||||
});
|
||||
|
||||
var key = {
|
||||
keyCode: 'a'.charCodeAt(0),
|
||||
value: 'a'
|
||||
};
|
||||
|
||||
assert.equal(
|
||||
normalizer._getUpperCaseValue(key), 'E',
|
||||
'upperCase of "a" should be "E" in this crafted test');
|
||||
});
|
||||
|
||||
test('Use key.value.toUpperCase()', function(){
|
||||
var normalizer = new LayoutKeyNormalizer({});
|
||||
var key = {
|
||||
keyCode: 'a'.charCodeAt(0),
|
||||
value: 'a'
|
||||
};
|
||||
assert.equal(normalizer._getUpperCaseValue(key), 'A');
|
||||
});
|
||||
});
|
||||
|
||||
suite('normalizeKey', function() {
|
||||
test('Calling getUpperCaseValue', function(){
|
||||
var normalizer = new LayoutKeyNormalizer({});
|
||||
|
||||
var key = {
|
||||
value: 'a'
|
||||
};
|
||||
|
||||
var stubGetUpperCaseValue =
|
||||
this.sinon.stub(normalizer, '_getUpperCaseValue').returns('A');
|
||||
|
||||
normalizer.normalizeKey(key);
|
||||
|
||||
assert.isTrue(stubGetUpperCaseValue.calledWith(key));
|
||||
});
|
||||
|
||||
test('keyCode is not present: value should derive into keyCode and ' +
|
||||
'keyCodeUpper', function(){
|
||||
var normalizer = new LayoutKeyNormalizer({});
|
||||
|
||||
var key = {
|
||||
value: 'a'
|
||||
};
|
||||
|
||||
this.sinon.stub(normalizer, '_getUpperCaseValue').returns('A');
|
||||
|
||||
normalizer.normalizeKey(key);
|
||||
|
||||
assert.equal(key.keyCode, 'a'.charCodeAt(0));
|
||||
assert.equal(key.keyCodeUpper, 'A'.charCodeAt(0));
|
||||
});
|
||||
|
||||
test('keyCode is present: should derive into keyCodeUpper', function(){
|
||||
var normalizer = new LayoutKeyNormalizer({});
|
||||
|
||||
var key = {
|
||||
value: 'a',
|
||||
keyCode: 'e'.charCodeAt(0)
|
||||
};
|
||||
|
||||
this.sinon.stub(normalizer, '_getUpperCaseValue').returns('A');
|
||||
|
||||
normalizer.normalizeKey(key);
|
||||
|
||||
assert.equal(key.keyCodeUpper, 'e'.charCodeAt(0),
|
||||
'keyCodeUpper should be keyCode of "e" ' +
|
||||
'in this crafted test');
|
||||
});
|
||||
|
||||
test('value should derive into lowercaseValue and uppercaseValue',
|
||||
function(){
|
||||
var normalizer = new LayoutKeyNormalizer({});
|
||||
|
||||
var key = {
|
||||
value: 'a'
|
||||
};
|
||||
|
||||
this.sinon.stub(normalizer, '_getUpperCaseValue').returns('A');
|
||||
|
||||
normalizer.normalizeKey(key);
|
||||
|
||||
assert.equal(key.lowercaseValue, 'a');
|
||||
assert.equal(key.uppercaseValue, 'A');
|
||||
});
|
||||
|
||||
test('isSpecialKey', function(){
|
||||
var normalizer = new LayoutKeyNormalizer({});
|
||||
var stubIsSpecialKey = this.sinon.stub(normalizer, '_isSpecialKey');
|
||||
|
||||
stubIsSpecialKey.returns(true);
|
||||
|
||||
var key = {
|
||||
value: 'a'
|
||||
};
|
||||
|
||||
this.sinon.stub(normalizer, '_getUpperCaseValue').returns('A');
|
||||
|
||||
normalizer.normalizeKey(key);
|
||||
|
||||
assert.isTrue(key.isSpecialKey);
|
||||
|
||||
stubIsSpecialKey.returns(false);
|
||||
|
||||
var key2 = {
|
||||
value: 'a'
|
||||
};
|
||||
|
||||
normalizer.normalizeKey(key2);
|
||||
|
||||
assert.isFalse(key2.isSpecialKey);
|
||||
});
|
||||
|
||||
test('longPressKeyCode is not present: should derive from longPressValue',
|
||||
function(){
|
||||
var normalizer = new LayoutKeyNormalizer({});
|
||||
var key = {
|
||||
value: 'a',
|
||||
longPressValue: 'a'
|
||||
};
|
||||
|
||||
this.sinon.stub(normalizer, '_getUpperCaseValue').returns('A');
|
||||
|
||||
normalizer.normalizeKey(key);
|
||||
|
||||
assert.equal(key.longPressKeyCode, 'a'.charCodeAt(0));
|
||||
});
|
||||
|
||||
test('longPressKeyCode is present: should not derive from longPressValue',
|
||||
function(){
|
||||
var normalizer = new LayoutKeyNormalizer({});
|
||||
|
||||
var key = {
|
||||
value: 'a',
|
||||
longPressValue: 'a',
|
||||
longPressKeyCode: 'b'.charCodeAt(0)
|
||||
};
|
||||
|
||||
this.sinon.stub(normalizer, '_getUpperCaseValue').returns('A');
|
||||
|
||||
normalizer.normalizeKey(key);
|
||||
|
||||
assert.equal(key.longPressKeyCode, 'b'.charCodeAt(0),
|
||||
'longPressKeyCode should be keyCode of "b" ' +
|
||||
'in this crafted test');
|
||||
});
|
||||
|
||||
test('supprtsSwitching key should be normalized too', function(){
|
||||
var normalizer = new LayoutKeyNormalizer({});
|
||||
|
||||
var key = {
|
||||
value: 'c',
|
||||
supportsSwitching: {
|
||||
value: 'a'
|
||||
}
|
||||
};
|
||||
|
||||
this.sinon.stub(normalizer, '_getUpperCaseValue')
|
||||
.onFirstCall().returns('C')
|
||||
.onSecondCall().returns('A');
|
||||
|
||||
normalizer.normalizeKey(key);
|
||||
|
||||
assert.equal(key.supportsSwitching.keyCodeUpper, 'A'.charCodeAt(0));
|
||||
});
|
||||
|
||||
test('normalize a key which has alternative keys', function() {
|
||||
var normalizer = new LayoutKeyNormalizer({});
|
||||
|
||||
this.sinon.stub(normalizer, '_getUpperCaseValue').returns('A');
|
||||
|
||||
var key = {
|
||||
value: 'a'
|
||||
};
|
||||
|
||||
normalizer.normalizeKey(key, {}, true);
|
||||
|
||||
assert.equal(key.className, 'alternate-indicator');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('LayoutNormalizer', function() {
|
||||
suite('normalizePageKeys', function() {
|
||||
var stubNormalizeKey;
|
||||
|
||||
setup(function() {
|
||||
stubNormalizeKey =
|
||||
sinon.stub(LayoutKeyNormalizer.prototype, 'normalizeKey');
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
stubNormalizeKey.restore();
|
||||
});
|
||||
|
||||
test('keys', function() {
|
||||
var normalizer = new LayoutNormalizer({});
|
||||
|
||||
var page = {
|
||||
keys: [
|
||||
[ {value: 'a'}, {value: 'b'} ],
|
||||
[ {value: 'c'}, {value: 'd'}, {value: 'e'} ]
|
||||
]
|
||||
};
|
||||
|
||||
normalizer._normalizePageKeys(page);
|
||||
|
||||
assert.isTrue(stubNormalizeKey.calledWith({value: 'a'}));
|
||||
assert.isTrue(stubNormalizeKey.calledWith({value: 'b'}));
|
||||
assert.isTrue(stubNormalizeKey.calledWith({value: 'c'}));
|
||||
assert.isTrue(stubNormalizeKey.calledWith({value: 'd'}));
|
||||
assert.isTrue(stubNormalizeKey.calledWith({value: 'e'}));
|
||||
});
|
||||
|
||||
test('textLayoutOverwrite', function() {
|
||||
var normalizer = new LayoutNormalizer({});
|
||||
|
||||
var page = {
|
||||
textLayoutOverwrite: {
|
||||
'a': false,
|
||||
'b': 'B'
|
||||
}
|
||||
};
|
||||
|
||||
stubNormalizeKey.returns('C');
|
||||
|
||||
normalizer._normalizePageKeys(page);
|
||||
|
||||
assert.isFalse(stubNormalizeKey.calledWith({value: false}));
|
||||
assert.isTrue(stubNormalizeKey.calledWith({value: 'B'}));
|
||||
|
||||
assert.deepEqual(page.textLayoutOverwrite, {
|
||||
'a': false,
|
||||
'b': 'C'
|
||||
});
|
||||
});
|
||||
|
||||
test('overwriting key in textLayoutOverwrite has alternative keys',
|
||||
function() {
|
||||
var normalizer = new LayoutNormalizer({});
|
||||
|
||||
var page = {
|
||||
textLayoutOverwrite: {
|
||||
'a': 'b'
|
||||
},
|
||||
alt: {
|
||||
'b': 'cdefg'
|
||||
}
|
||||
};
|
||||
|
||||
// we'd like to have normalizeKey really do it job in this test
|
||||
stubNormalizeKey.restore();
|
||||
|
||||
var overwritingkey = 'b';
|
||||
var hasAlternativeKeys = normalizer._hasAlternativeKeys(overwritingkey,
|
||||
page);
|
||||
assert.isTrue(hasAlternativeKeys);
|
||||
|
||||
normalizer._normalizePageKeys(page);
|
||||
assert.equal(page.textLayoutOverwrite.a.className,
|
||||
'alternate-indicator');
|
||||
});
|
||||
});
|
||||
|
||||
suite('normalizePageAltKeys', function() {
|
||||
test('normalize alt menu (single char keys)', function() {
|
||||
var normalizer = new LayoutNormalizer({});
|
||||
var page = {
|
||||
keys: [
|
||||
[
|
||||
{ value: 'preloaded' }
|
||||
]
|
||||
],
|
||||
alt: {
|
||||
'a': 'áàâäåãāæ'
|
||||
},
|
||||
upperCase: {}
|
||||
};
|
||||
|
||||
normalizer._normalizePageAltKeys(page);
|
||||
|
||||
assert.deepEqual(page, {
|
||||
keys: [
|
||||
[
|
||||
{ value: 'preloaded' }
|
||||
]
|
||||
],
|
||||
alt: { 'a': [ 'á', 'à', 'â', 'ä', 'å', 'ã', 'ā', 'æ' ],
|
||||
'A': [ 'Á', 'À', 'Â', 'Ä', 'Å', 'Ã', 'Ā', 'Æ' ] },
|
||||
upperCase: {}
|
||||
});
|
||||
});
|
||||
|
||||
test('normalize alt menu (with multi-char keys)', function() {
|
||||
var normalizer = new LayoutNormalizer({});
|
||||
var page = {
|
||||
keys: [
|
||||
[
|
||||
{ value: 'preloaded' }
|
||||
]
|
||||
],
|
||||
alt: {
|
||||
'a': 'á à â A$'
|
||||
},
|
||||
upperCase: {}
|
||||
};
|
||||
|
||||
normalizer._normalizePageAltKeys(page);
|
||||
|
||||
assert.deepEqual(page, {
|
||||
keys: [
|
||||
[
|
||||
{ value: 'preloaded' }
|
||||
]
|
||||
],
|
||||
alt: { 'a': [ 'á', 'à', 'â', 'A$' ],
|
||||
'A': [ 'Á', 'À', 'Â', 'A$' ] },
|
||||
upperCase: {}
|
||||
});
|
||||
});
|
||||
|
||||
test('normalize alt menu (with one multi-char keys)', function() {
|
||||
var normalizer = new LayoutNormalizer({});
|
||||
var page = {
|
||||
keys: [
|
||||
[
|
||||
{ value: 'preloaded' }
|
||||
]
|
||||
],
|
||||
alt: {
|
||||
'a': 'A$ '
|
||||
},
|
||||
upperCase: {}
|
||||
};
|
||||
|
||||
normalizer._normalizePageAltKeys(page);
|
||||
|
||||
assert.deepEqual(page, {
|
||||
keys: [
|
||||
[
|
||||
{ value: 'preloaded' }
|
||||
]
|
||||
],
|
||||
alt: { 'a': [ 'A$' ],
|
||||
'A': [ 'A$' ] },
|
||||
upperCase: {}
|
||||
});
|
||||
});
|
||||
|
||||
test('normalize alt menu (with Turkish \'i\' key)', function() {
|
||||
var normalizer = new LayoutNormalizer({});
|
||||
var page = {
|
||||
keys: [
|
||||
[
|
||||
{ value: 'preloaded' }
|
||||
]
|
||||
],
|
||||
alt: {
|
||||
'i': 'ß'
|
||||
},
|
||||
upperCase: {
|
||||
'i': 'İ'
|
||||
}
|
||||
};
|
||||
|
||||
normalizer._normalizePageAltKeys(page);
|
||||
|
||||
assert.deepEqual(page, {
|
||||
keys: [
|
||||
[
|
||||
{ value: 'preloaded' }
|
||||
]
|
||||
],
|
||||
alt: { 'i': [ 'ß' ],
|
||||
'İ': [ 'ß' ] },
|
||||
upperCase: {
|
||||
'i': 'İ'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('normalize alt menu (with Catalan \'l·l\' key)', function() {
|
||||
var normalizer = new LayoutNormalizer({});
|
||||
var page = {
|
||||
keys: [
|
||||
[
|
||||
{ value: 'preloaded' }
|
||||
]
|
||||
],
|
||||
alt: {
|
||||
'l': 'l·l ł £'
|
||||
},
|
||||
upperCase: {}
|
||||
};
|
||||
|
||||
normalizer._normalizePageAltKeys(page);
|
||||
|
||||
var expectedPage = {
|
||||
keys: [
|
||||
[
|
||||
{ value: 'preloaded' }
|
||||
]
|
||||
],
|
||||
alt: { 'l': [ 'l·l', 'ł', '£' ],
|
||||
'L': [ 'L·l', 'Ł', '£' ] },
|
||||
upperCase: {}
|
||||
};
|
||||
expectedPage.alt.L.upperCaseLocked = [ 'L·L', 'Ł', '£' ];
|
||||
|
||||
assert.deepEqual(page, expectedPage);
|
||||
});
|
||||
});
|
||||
|
||||
suite('normalize', function() {
|
||||
test('generation of page 0', function(){
|
||||
var normalizer = new LayoutNormalizer({
|
||||
alt: {'alt': [1]},
|
||||
keys: [2],
|
||||
upperCase: {'a': 'A'},
|
||||
width: 3,
|
||||
keyClassName: 'C',
|
||||
typeInsensitive: true,
|
||||
textLayoutOverwrite: {',': 'comma'},
|
||||
needsCommaKey: true,
|
||||
secondLayout: true,
|
||||
specificCssRule: true
|
||||
});
|
||||
|
||||
this.sinon.stub(normalizer, '_normalizePageKeys');
|
||||
this.sinon.stub(normalizer, '_normalizePageAltKeys');
|
||||
|
||||
normalizer.normalize();
|
||||
|
||||
assert.deepEqual(normalizer._layout, {
|
||||
pages: [
|
||||
{
|
||||
alt: {'alt': [1]},
|
||||
keys: [2],
|
||||
upperCase: {'a': 'A'},
|
||||
width: 3,
|
||||
keyClassName: 'C',
|
||||
typeInsensitive: true,
|
||||
textLayoutOverwrite: {',': 'comma'},
|
||||
needsCommaKey: true,
|
||||
secondLayout: true,
|
||||
specificCssRule: true
|
||||
}
|
||||
]
|
||||
}, 'page 0 of layout was not generated from layout top-most attributes');
|
||||
});
|
||||
|
||||
test('each page gets normalized', function(){
|
||||
var layout = {
|
||||
pages: []
|
||||
};
|
||||
|
||||
range(4).forEach( index => {
|
||||
layout.pages.push({index: index});
|
||||
});
|
||||
|
||||
var normalizer = new LayoutNormalizer(layout);
|
||||
|
||||
var stubNormalizePageKeys =
|
||||
this.sinon.stub(normalizer, '_normalizePageKeys');
|
||||
var stubNormalizePageAltKeys =
|
||||
this.sinon.stub(normalizer, '_normalizePageAltKeys');
|
||||
|
||||
normalizer.normalize();
|
||||
|
||||
range(4).forEach( index => {
|
||||
assert.isTrue(
|
||||
stubNormalizePageKeys.calledWith({index: index}),
|
||||
'normalizePageKeys was not called for index = ' + index
|
||||
);
|
||||
assert.isTrue(
|
||||
stubNormalizePageAltKeys.calledWith({index: index}),
|
||||
'normalizePageAltKeys was not called for index = ' + index
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('Layout normalization: Cross-module integrated tests', function() {
|
||||
test('getUpperCase correctly uses moved-to-page0 properties', function(){
|
||||
var normalizer = new LayoutNormalizer({
|
||||
keys: [
|
||||
[{value: 'a'}]
|
||||
],
|
||||
upperCase: {'a': 'C'}
|
||||
});
|
||||
|
||||
normalizer.normalize();
|
||||
|
||||
assert.deepEqual(normalizer._layout.pages[0].keys,
|
||||
[
|
||||
[{
|
||||
value: 'a',
|
||||
keyCode: 97,
|
||||
keyCodeUpper: 67,
|
||||
lowercaseValue: 'a',
|
||||
uppercaseValue: 'C',
|
||||
isSpecialKey: false,
|
||||
isButton: false
|
||||
}]
|
||||
], 'upperCase of "a" should be "C" in this crafted test');
|
||||
});
|
||||
});
|
||||
299
gaia/apps/keyboard/test/unit/keyboard/layout_rendering_manager_test.js
Executable file
299
gaia/apps/keyboard/test/unit/keyboard/layout_rendering_manager_test.js
Executable file
|
|
@ -0,0 +1,299 @@
|
|||
'use strict';
|
||||
|
||||
/* global LayoutRenderingManager, KeyboardConsole */
|
||||
|
||||
require('/js/keyboard/console.js');
|
||||
|
||||
require('/js/keyboard/layout_rendering_manager.js');
|
||||
|
||||
suite('LayoutRenderingManager', function() {
|
||||
var app;
|
||||
var manager;
|
||||
var viewManager;
|
||||
|
||||
setup(function() {
|
||||
this.sinon.stub(window, 'setTimeout');
|
||||
this.sinon.stub(window, 'clearTimeout');
|
||||
this.sinon.stub(window, 'addEventListener');
|
||||
this.sinon.stub(window, 'removeEventListener');
|
||||
this.sinon.stub(window, 'resizeTo');
|
||||
|
||||
viewManager = {
|
||||
render: this.sinon.stub(),
|
||||
resize: this.sinon.stub(),
|
||||
setUpperCaseLock: this.sinon.stub(),
|
||||
showCandidates: this.sinon.stub(),
|
||||
getWidth: this.sinon.stub().returns(600),
|
||||
getHeight: this.sinon.stub().returns(400),
|
||||
getKeyArray: this.sinon.stub().returns([]),
|
||||
getKeyWidth: this.sinon.stub().returns(15),
|
||||
getKeyHeight: this.sinon.stub().returns(12),
|
||||
candidatePanel: {
|
||||
clientHeight: 123
|
||||
}
|
||||
};
|
||||
|
||||
app = {
|
||||
getBasicInputType: this.sinon.stub().returns('foo'),
|
||||
console: this.sinon.stub(KeyboardConsole.prototype),
|
||||
candidatePanelManager: {
|
||||
currentCandidates: []
|
||||
},
|
||||
layoutManager: {
|
||||
currentPage: {
|
||||
keys: [ { value: 'currentPage' } ]
|
||||
},
|
||||
currentPageIndex: 0,
|
||||
PAGE_INDEX_DEFAULT: 0
|
||||
},
|
||||
inputMethodManager: {
|
||||
currentIMEngine: {
|
||||
setLayoutParams: this.sinon.stub()
|
||||
}
|
||||
},
|
||||
upperCaseStateManager: {
|
||||
isUpperCase: true
|
||||
},
|
||||
viewManager: viewManager
|
||||
};
|
||||
|
||||
Object.defineProperty(document, 'hidden',
|
||||
{ value: false, configurable: true });
|
||||
|
||||
manager = new LayoutRenderingManager(app);
|
||||
manager.start();
|
||||
|
||||
assert.isTrue(window.setTimeout.calledOnce);
|
||||
window.setTimeout.getCall(0).args[0].call(window);
|
||||
|
||||
assert.isTrue(window.addEventListener.calledWith('resize', manager));
|
||||
});
|
||||
|
||||
suite('updateLayoutRendering', function() {
|
||||
var p;
|
||||
|
||||
teardown(function(done) {
|
||||
viewManager.render.getCall(0).args[2].call(window);
|
||||
assert.isTrue(window.resizeTo.calledWith(600, 400));
|
||||
|
||||
p.then(function() {
|
||||
assert.equal(
|
||||
viewManager.setUpperCaseLock.firstCall.args[0],
|
||||
app.upperCaseStateManager);
|
||||
assert.equal(viewManager.showCandidates.firstCall.args[0],
|
||||
app.candidatePanelManager.currentCandidates);
|
||||
assert.isTrue(
|
||||
app.inputMethodManager.currentIMEngine.setLayoutParams.calledWith({
|
||||
keyboardWidth: 600,
|
||||
keyboardHeight: 277,
|
||||
keyArray: [],
|
||||
keyWidth: 15,
|
||||
keyHeight: 12
|
||||
}));
|
||||
}, function(e) {
|
||||
if (e) {
|
||||
throw e;
|
||||
}
|
||||
assert.isTrue(false, 'Should not reject.');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('isUpperCase = false', function() {
|
||||
app.upperCaseStateManager.isUpperCase = false;
|
||||
p = manager.updateLayoutRendering();
|
||||
|
||||
assert.isTrue(viewManager.render.calledWith(
|
||||
app.layoutManager.currentPage,
|
||||
{
|
||||
uppercase: false,
|
||||
inputType: 'foo',
|
||||
showCandidatePanel: false
|
||||
}));
|
||||
});
|
||||
|
||||
test('w/o autoCorrectLanguage, w/o displaysCandidates()', function() {
|
||||
p = manager.updateLayoutRendering();
|
||||
|
||||
assert.isTrue(viewManager.render.calledWith(
|
||||
app.layoutManager.currentPage,
|
||||
{
|
||||
uppercase: true,
|
||||
inputType: 'foo',
|
||||
showCandidatePanel: false
|
||||
}));
|
||||
});
|
||||
|
||||
test('w/ autoCorrectLanguage, w/o displaysCandidates()', function() {
|
||||
app.layoutManager.currentPage.autoCorrectLanguage = 'zz-ZZ';
|
||||
|
||||
p = manager.updateLayoutRendering();
|
||||
|
||||
assert.isTrue(viewManager.render.calledWith(
|
||||
app.layoutManager.currentPage,
|
||||
{
|
||||
uppercase: true,
|
||||
inputType: 'foo',
|
||||
showCandidatePanel: true
|
||||
}));
|
||||
});
|
||||
|
||||
test('w/ autoCorrectLanguage, w/ displaysCandidates()', function() {
|
||||
app.layoutManager.currentPage.autoCorrectLanguage = 'zz-ZZ';
|
||||
app.inputMethodManager.currentIMEngine.displaysCandidates =
|
||||
this.sinon.stub().returns(false);
|
||||
|
||||
p = manager.updateLayoutRendering();
|
||||
|
||||
assert.isTrue(viewManager.render.calledWith(
|
||||
app.layoutManager.currentPage,
|
||||
{
|
||||
uppercase: true,
|
||||
inputType: 'foo',
|
||||
showCandidatePanel: false
|
||||
}));
|
||||
});
|
||||
|
||||
test('w/ needsCandidatePanel, w/o displaysCandidates()', function() {
|
||||
app.layoutManager.currentPage.needsCandidatePanel = true;
|
||||
|
||||
p = manager.updateLayoutRendering();
|
||||
|
||||
assert.isTrue(viewManager.render.calledWith(
|
||||
app.layoutManager.currentPage,
|
||||
{
|
||||
uppercase: true,
|
||||
inputType: 'foo',
|
||||
showCandidatePanel: true
|
||||
}));
|
||||
});
|
||||
|
||||
test('w/ needsCandidatePanel, w/ displaysCandidates()', function() {
|
||||
app.layoutManager.currentPage.needsCandidatePanel = true;
|
||||
app.inputMethodManager.currentIMEngine.displaysCandidates =
|
||||
this.sinon.stub().returns(false);
|
||||
|
||||
p = manager.updateLayoutRendering();
|
||||
|
||||
assert.isTrue(viewManager.render.calledWith(
|
||||
app.layoutManager.currentPage,
|
||||
{
|
||||
uppercase: true,
|
||||
inputType: 'foo',
|
||||
showCandidatePanel: false
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
suite('Before rendering', function() {
|
||||
test('resize event', function() {
|
||||
var evt = {
|
||||
type: 'resize',
|
||||
target: window
|
||||
};
|
||||
manager.handleEvent(evt);
|
||||
|
||||
assert.isFalse(viewManager.resize.calledOnce);
|
||||
assert.isFalse(
|
||||
app.inputMethodManager.currentIMEngine.setLayoutParams.calledOnce);
|
||||
assert.isFalse(window.resizeTo.calledOnce);
|
||||
});
|
||||
|
||||
test('updateCandidatesRendering', function() {
|
||||
manager.updateCandidatesRendering();
|
||||
|
||||
assert.isFalse(viewManager.showCandidates.calledOnce);
|
||||
});
|
||||
|
||||
test('updateUpperCaseRendering', function() {
|
||||
window.requestAnimationFrame = this.sinon.stub();
|
||||
|
||||
manager.updateUpperCaseRendering();
|
||||
|
||||
assert.isFalse(window.requestAnimationFrame.calledOnce);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Rendered', function() {
|
||||
setup(function(done) {
|
||||
manager.updateLayoutRendering().then(function() {
|
||||
}, function(e) {
|
||||
if (e) {
|
||||
throw e;
|
||||
}
|
||||
assert.isTrue(false, 'Should not reject.');
|
||||
}).then(done, done);
|
||||
|
||||
viewManager.render.getCall(0).args[2].call(window);
|
||||
});
|
||||
|
||||
test('resize event', function() {
|
||||
var evt = {
|
||||
type: 'resize',
|
||||
target: window
|
||||
};
|
||||
manager.handleEvent(evt);
|
||||
|
||||
assert.equal(viewManager.resize.callCount, 1);
|
||||
|
||||
assert.isTrue(
|
||||
app.inputMethodManager.currentIMEngine.setLayoutParams.calledWith({
|
||||
keyboardWidth: 600,
|
||||
keyboardHeight: 277,
|
||||
keyArray: [],
|
||||
keyWidth: 15,
|
||||
keyHeight: 12
|
||||
}));
|
||||
|
||||
assert.isTrue(window.resizeTo.calledWith(600, 400));
|
||||
});
|
||||
|
||||
test('updateCandidatesRendering', function() {
|
||||
manager.updateCandidatesRendering();
|
||||
|
||||
assert.isTrue(viewManager.showCandidates.calledTwice);
|
||||
assert.equal(viewManager.showCandidates.secondCall.args[0],
|
||||
app.candidatePanelManager.currentCandidates);
|
||||
});
|
||||
|
||||
test('updateUpperCaseRendering', function() {
|
||||
window.requestAnimationFrame = this.sinon.stub();
|
||||
|
||||
manager.updateUpperCaseRendering();
|
||||
|
||||
window.requestAnimationFrame.getCall(0).args[0].call(window);
|
||||
|
||||
assert.isTrue(viewManager.setUpperCaseLock.calledTwice);
|
||||
assert.equal(viewManager.setUpperCaseLock.secondCall.args[0],
|
||||
app.upperCaseStateManager);
|
||||
});
|
||||
});
|
||||
|
||||
suite('domObjectMap and getTargetObject', function() {
|
||||
test('get object with existent key', function() {
|
||||
var key = {
|
||||
dummy: 'dummy'
|
||||
};
|
||||
|
||||
var object = {
|
||||
result: 'result'
|
||||
};
|
||||
|
||||
manager.domObjectMap.set(key, object);
|
||||
|
||||
assert.equal(manager.getTargetObject(key), object);
|
||||
});
|
||||
|
||||
test('null and undefined key should return empty object', function() {
|
||||
assert.deepEqual(manager.getTargetObject(null), {});
|
||||
assert.deepEqual(manager.getTargetObject(undefined), {});
|
||||
});
|
||||
|
||||
test('inexistent key should return empty object instead of undefined',
|
||||
function() {
|
||||
var inexistentKey = {
|
||||
someProp: 'dummy'
|
||||
};
|
||||
assert.deepEqual(manager.getTargetObject(inexistentKey), {});
|
||||
});
|
||||
});
|
||||
});
|
||||
376
gaia/apps/keyboard/test/unit/keyboard/settings_test.js
Executable file
376
gaia/apps/keyboard/test/unit/keyboard/settings_test.js
Executable file
|
|
@ -0,0 +1,376 @@
|
|||
'use strict';
|
||||
|
||||
/* global MockNavigatorMozSettings, MockNavigatorMozSettingsLock,
|
||||
SettingsPromiseManager, SettingsManagerBase */
|
||||
|
||||
require('/js/keyboard/settings.js');
|
||||
require('/shared/test/unit/mocks/mock_event_target.js');
|
||||
require('/shared/test/unit/mocks/mock_dom_request.js');
|
||||
require('/shared/js/input_mgmt/mock_navigator_mozsettings.js');
|
||||
|
||||
suite('SettingsPromiseManager', function() {
|
||||
var realMozSettings;
|
||||
|
||||
suiteSetup(function() {
|
||||
realMozSettings = navigator.mozSettings;
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
navigator.mozSettings = realMozSettings;
|
||||
});
|
||||
|
||||
test('get one key', function(done) {
|
||||
var mozSettings = navigator.mozSettings = new MockNavigatorMozSettings();
|
||||
var createLockStub = this.sinon.stub(mozSettings, 'createLock');
|
||||
var lock = new MockNavigatorMozSettingsLock();
|
||||
var lockGetSpy = this.sinon.spy(lock, 'get');
|
||||
createLockStub.returns(lock);
|
||||
|
||||
var promiseManager = new SettingsPromiseManager();
|
||||
var p = promiseManager.get('foo');
|
||||
|
||||
assert.isTrue(createLockStub.calledOnce);
|
||||
|
||||
assert.isTrue(lockGetSpy.calledOnce);
|
||||
assert.isTrue(lockGetSpy.calledWith('foo'));
|
||||
|
||||
var req = lockGetSpy.getCall(0).returnValue;
|
||||
req.fireSuccess({ 'foo': 'bar' });
|
||||
|
||||
p.then(function(value) {
|
||||
assert.equal(value, 'bar');
|
||||
|
||||
done();
|
||||
}, function(error) {
|
||||
assert.isTrue(false, 'Should not reject the promise.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('get multiple keys', function(done) {
|
||||
var mozSettings = navigator.mozSettings = new MockNavigatorMozSettings();
|
||||
var createLockStub = this.sinon.stub(mozSettings, 'createLock');
|
||||
var lock = new MockNavigatorMozSettingsLock();
|
||||
var lockGetSpy = this.sinon.spy(lock, 'get');
|
||||
createLockStub.returns(lock);
|
||||
|
||||
var promiseManager = new SettingsPromiseManager();
|
||||
var p = promiseManager.get(['foo', 'foo2']);
|
||||
|
||||
assert.isTrue(createLockStub.calledOnce);
|
||||
|
||||
assert.isTrue(lockGetSpy.calledTwice);
|
||||
assert.isTrue(lockGetSpy.calledWith('foo'));
|
||||
assert.isTrue(lockGetSpy.calledWith('foo2'));
|
||||
|
||||
var req1 = lockGetSpy.getCall(0).returnValue;
|
||||
req1.fireSuccess({ 'foo': 'bar' });
|
||||
var req2 = lockGetSpy.getCall(1).returnValue;
|
||||
req2.fireSuccess({ 'foo2': 'bar2' });
|
||||
|
||||
p.then(function(values) {
|
||||
assert.deepEqual(values, ['bar', 'bar2']);
|
||||
|
||||
done();
|
||||
}, function(error) {
|
||||
assert.isTrue(false, 'Should not reject the promise.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('get, handle error', function(done) {
|
||||
var mozSettings = navigator.mozSettings = new MockNavigatorMozSettings();
|
||||
var createLockStub = this.sinon.stub(mozSettings, 'createLock');
|
||||
var lock = new MockNavigatorMozSettingsLock();
|
||||
var lockGetSpy = this.sinon.spy(lock, 'get');
|
||||
createLockStub.returns(lock);
|
||||
|
||||
|
||||
var promiseManager = new SettingsPromiseManager();
|
||||
var p = promiseManager.get('foo');
|
||||
|
||||
assert.isTrue(createLockStub.calledOnce);
|
||||
|
||||
assert.isTrue(lockGetSpy.calledOnce);
|
||||
assert.isTrue(lockGetSpy.calledWith('foo'));
|
||||
|
||||
var req = lockGetSpy.getCall(0).returnValue;
|
||||
req.fireError('error');
|
||||
|
||||
p.then(function(value) {
|
||||
assert.isTrue(false, 'Should not resolve the promise.');
|
||||
|
||||
done();
|
||||
}, function(error) {
|
||||
assert.isTrue(true, 'Promise rejected.');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('set one key', function(done) {
|
||||
var mozSettings = navigator.mozSettings = new MockNavigatorMozSettings();
|
||||
var createLockStub = this.sinon.stub(mozSettings, 'createLock');
|
||||
var lock = new MockNavigatorMozSettingsLock();
|
||||
var lockSetSpy = this.sinon.spy(lock, 'set');
|
||||
createLockStub.returns(lock);
|
||||
|
||||
|
||||
var promiseManager = new SettingsPromiseManager();
|
||||
var p = promiseManager.set('foo', 'bar');
|
||||
|
||||
assert.isTrue(createLockStub.calledOnce);
|
||||
|
||||
assert.isTrue(lockSetSpy.calledOnce);
|
||||
assert.isTrue(lockSetSpy.calledWith({'foo': 'bar' }));
|
||||
|
||||
var req = lockSetSpy.getCall(0).returnValue;
|
||||
req.fireSuccess(0);
|
||||
|
||||
p.then(function() {
|
||||
assert.isTrue(true, 'set success');
|
||||
|
||||
done();
|
||||
}, function(error) {
|
||||
assert.isTrue(false, 'Should not reject the promise.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('set multiple keys', function(done) {
|
||||
var mozSettings = navigator.mozSettings = new MockNavigatorMozSettings();
|
||||
var createLockStub = this.sinon.stub(mozSettings, 'createLock');
|
||||
var lock = new MockNavigatorMozSettingsLock();
|
||||
var lockSetSpy = this.sinon.spy(lock, 'set');
|
||||
createLockStub.returns(lock);
|
||||
|
||||
|
||||
var promiseManager = new SettingsPromiseManager();
|
||||
var p = promiseManager.set({foo: 'bar', foo2: 'bar2' });
|
||||
|
||||
assert.isTrue(createLockStub.calledOnce);
|
||||
|
||||
assert.isTrue(lockSetSpy.calledOnce);
|
||||
assert.isTrue(lockSetSpy.calledWith({foo: 'bar', foo2: 'bar2' }));
|
||||
|
||||
var req = lockSetSpy.getCall(0).returnValue;
|
||||
req.fireSuccess(0);
|
||||
|
||||
p.then(function() {
|
||||
assert.isTrue(true, 'set success');
|
||||
|
||||
done();
|
||||
}, function(error) {
|
||||
assert.isTrue(false, 'Should not reject the promise.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('set, handle error', function(done) {
|
||||
var mozSettings = navigator.mozSettings = new MockNavigatorMozSettings();
|
||||
var createLockStub = this.sinon.stub(mozSettings, 'createLock');
|
||||
var lock = new MockNavigatorMozSettingsLock();
|
||||
var lockSetSpy = this.sinon.spy(lock, 'set');
|
||||
createLockStub.returns(lock);
|
||||
|
||||
|
||||
var promiseManager = new SettingsPromiseManager();
|
||||
var p = promiseManager.set('foo', 'bar');
|
||||
|
||||
assert.isTrue(createLockStub.calledOnce);
|
||||
|
||||
assert.isTrue(lockSetSpy.calledOnce);
|
||||
assert.isTrue(lockSetSpy.calledWith({'foo': 'bar' }));
|
||||
|
||||
var req = lockSetSpy.getCall(0).returnValue;
|
||||
req.fireError('error');
|
||||
|
||||
p.then(function(value) {
|
||||
assert.isTrue(false, 'Should not resolve the promise.');
|
||||
|
||||
done();
|
||||
}, function(error) {
|
||||
assert.isTrue(true, 'Promise rejected.');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('update one', function(done) {
|
||||
var mozSettings = navigator.mozSettings = new MockNavigatorMozSettings();
|
||||
var createLockStub = this.sinon.stub(mozSettings, 'createLock');
|
||||
var lock = new MockNavigatorMozSettingsLock();
|
||||
var lockGetSpy = this.sinon.spy(lock, 'get');
|
||||
var lockSetSpy = this.sinon.spy(lock, 'set');
|
||||
createLockStub.returns(lock);
|
||||
|
||||
var callback = this.sinon.spy(function(val) {
|
||||
return 'bar2';
|
||||
});
|
||||
|
||||
var promiseManager = new SettingsPromiseManager();
|
||||
var p = promiseManager.updateOne('foo', callback);
|
||||
|
||||
assert.isTrue(createLockStub.calledOnce);
|
||||
|
||||
assert.isTrue(lockGetSpy.calledOnce);
|
||||
assert.isTrue(lockGetSpy.calledWith('foo'));
|
||||
|
||||
var req = lockGetSpy.getCall(0).returnValue;
|
||||
req.fireSuccess({ 'foo': 'bar' });
|
||||
|
||||
assert.isTrue(callback.calledOnce);
|
||||
|
||||
assert.isTrue(lockSetSpy.calledOnce);
|
||||
assert.isTrue(lockSetSpy.calledWith({ 'foo': 'bar2' }));
|
||||
|
||||
var req2 = lockSetSpy.getCall(0).returnValue;
|
||||
req2.fireSuccess(0);
|
||||
|
||||
p.then(function() {
|
||||
assert.isTrue(createLockStub.calledOnce, 'Use one lock only.');
|
||||
assert.isTrue(callback.calledOnce, 'callback called once only.');
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('SettingsManagerBase', function() {
|
||||
var realMozSettings;
|
||||
|
||||
suiteSetup(function() {
|
||||
realMozSettings = navigator.mozSettings;
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
navigator.mozSettings = realMozSettings;
|
||||
});
|
||||
|
||||
test('initSettings, observe change, stopObserve', function(done) {
|
||||
var mozSettings = navigator.mozSettings = new MockNavigatorMozSettings();
|
||||
var createLockStub = this.sinon.stub(mozSettings, 'createLock');
|
||||
var lock = new MockNavigatorMozSettingsLock();
|
||||
var lockGetSpy = this.sinon.spy(lock, 'get');
|
||||
createLockStub.returns(lock);
|
||||
|
||||
var addObserverSpy = this.sinon.spy(mozSettings, 'addObserver');
|
||||
var removeObserverSpy = this.sinon.spy(mozSettings, 'removeObserver');
|
||||
|
||||
var promiseManager = new SettingsPromiseManager();
|
||||
var settingsManager = new SettingsManagerBase();
|
||||
settingsManager.promiseManager = promiseManager;
|
||||
settingsManager.KEYS = ['foo', 'foo2'];
|
||||
settingsManager.PROPERTIES = ['fooValue', 'foo2Value'];
|
||||
|
||||
var p = settingsManager.initSettings();
|
||||
|
||||
assert.isTrue(createLockStub.calledOnce);
|
||||
|
||||
assert.isTrue(lockGetSpy.calledTwice);
|
||||
assert.isTrue(lockGetSpy.calledWith('foo'));
|
||||
assert.isTrue(lockGetSpy.calledWith('foo2'));
|
||||
|
||||
var req1 = lockGetSpy.getCall(0).returnValue;
|
||||
req1.fireSuccess({ 'foo': 'bar' });
|
||||
var req2 = lockGetSpy.getCall(1).returnValue;
|
||||
req2.fireSuccess({ 'foo2': 'bar2' });
|
||||
|
||||
assert.equal(settingsManager.initialized, false);
|
||||
|
||||
var p2 = settingsManager.initSettings();
|
||||
assert.equal(p, p2);
|
||||
|
||||
p.then(function(settings) {
|
||||
assert.equal(settingsManager.initialized, true);
|
||||
|
||||
assert.equal(settings.fooValue, 'bar');
|
||||
assert.equal(settings.foo2Value, 'bar2');
|
||||
|
||||
var s = settingsManager.getSettingsSync();
|
||||
assert.equal(s, settings);
|
||||
|
||||
assert.isTrue(addObserverSpy.calledTwice);
|
||||
assert.isTrue(addObserverSpy.calledWith('foo'));
|
||||
assert.isTrue(addObserverSpy.calledWith('foo2'));
|
||||
|
||||
var calls = 0;
|
||||
settingsManager.onsettingchange = function(s2) {
|
||||
calls++;
|
||||
|
||||
var s = settingsManager.getSettingsSync();
|
||||
assert.equal(s2, s);
|
||||
|
||||
switch (calls) {
|
||||
case 1:
|
||||
assert.equal(s2.fooValue, 'BAR');
|
||||
|
||||
break;
|
||||
|
||||
case 2:
|
||||
assert.equal(s2.foo2Value, 'BAR2');
|
||||
|
||||
settingsManager.stopObserve();
|
||||
assert.isTrue(removeObserverSpy.calledTwice);
|
||||
assert.isTrue(removeObserverSpy.calledWith('foo'));
|
||||
assert.isTrue(removeObserverSpy.calledWith('foo2'));
|
||||
|
||||
done();
|
||||
break;
|
||||
|
||||
default:
|
||||
assert.isTrue(false, 'called more than twice.');
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
mozSettings.dispatchSettingChange('foo', 'BAR');
|
||||
mozSettings.dispatchSettingChange('foo2', 'BAR2');
|
||||
}, function(error) {
|
||||
assert.isTrue(false, 'Should not reject the promise.');
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
test('initSettings and returned error', function(done) {
|
||||
var mozSettings = navigator.mozSettings = new MockNavigatorMozSettings();
|
||||
var createLockStub = this.sinon.stub(mozSettings, 'createLock');
|
||||
var lock = new MockNavigatorMozSettingsLock();
|
||||
var lockGetSpy = this.sinon.spy(lock, 'get');
|
||||
createLockStub.returns(lock);
|
||||
|
||||
var addObserverSpy = this.sinon.spy(mozSettings, 'addObserver');
|
||||
var removeObserverSpy = this.sinon.spy(mozSettings, 'removeObserver');
|
||||
|
||||
var promiseManager = new SettingsPromiseManager();
|
||||
var settingsManager = new SettingsManagerBase();
|
||||
settingsManager.promiseManager = promiseManager;
|
||||
settingsManager.KEYS = ['foo', 'foo2'];
|
||||
settingsManager.PROPERTIES = ['fooValue', 'foo2Value'];
|
||||
|
||||
var p = settingsManager.initSettings();
|
||||
|
||||
assert.isTrue(createLockStub.calledOnce);
|
||||
|
||||
assert.isTrue(lockGetSpy.calledTwice);
|
||||
assert.isTrue(lockGetSpy.calledWith('foo'));
|
||||
assert.isTrue(lockGetSpy.calledWith('foo2'));
|
||||
|
||||
var req1 = lockGetSpy.getCall(0).returnValue;
|
||||
req1.fireError('error');
|
||||
var req2 = lockGetSpy.getCall(1).returnValue;
|
||||
req2.fireError('error');
|
||||
|
||||
p.then(function() {
|
||||
assert.isTrue(false, 'Should not resolve the promise.');
|
||||
|
||||
done();
|
||||
}, function(error) {
|
||||
assert.equal(settingsManager.initialized, false);
|
||||
assert.equal(addObserverSpy.calledOnce, false);
|
||||
assert.equal(removeObserverSpy.calledOnce, false);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
121
gaia/apps/keyboard/test/unit/keyboard/sound_feedback_player_test.js
Executable file
121
gaia/apps/keyboard/test/unit/keyboard/sound_feedback_player_test.js
Executable file
|
|
@ -0,0 +1,121 @@
|
|||
'use strict';
|
||||
|
||||
/* global SoundFeedbackPlayer, MockAudioContext, MockAudioBufferSourceNode,
|
||||
MockOfflineAudioContext */
|
||||
|
||||
require('/shared/test/unit/mocks/mock_event_target.js');
|
||||
require('/test/unit/mock_audio_context.js');
|
||||
|
||||
require('/js/keyboard/sound_feedback_player.js');
|
||||
|
||||
suite('SoundFeedbackPlayer', function() {
|
||||
var audioContextStub;
|
||||
var offlineAudioContextStub;
|
||||
var stubAudioBufferSourceNode;
|
||||
var expectedRequests;
|
||||
var audioBuffers;
|
||||
var fakeXhr;
|
||||
var player;
|
||||
|
||||
setup(function(done) {
|
||||
var requests = [];
|
||||
expectedRequests = [
|
||||
{ url: SoundFeedbackPlayer.prototype.CLICK_SOUND_URL,
|
||||
response: { stub: 'clickerArrayBuffer' } },
|
||||
{ url: SoundFeedbackPlayer.prototype.SPECIAL_SOUND_URL,
|
||||
response: { stub: 'specialClickerArrayBuffer' } }
|
||||
];
|
||||
fakeXhr = sinon.useFakeXMLHttpRequest();
|
||||
fakeXhr.onCreate = function(request) {
|
||||
requests.push(request);
|
||||
};
|
||||
|
||||
audioBuffers = new Map();
|
||||
audioBuffers.set(
|
||||
expectedRequests[0].response, { stub: 'clickerAudioBuffer' });
|
||||
audioBuffers.set(
|
||||
expectedRequests[1].response, { stub: 'specialClickerAudioBuffer' });
|
||||
|
||||
audioContextStub = new MockAudioContext('system');
|
||||
this.sinon.stub(window, 'AudioContext').returns(audioContextStub);
|
||||
|
||||
offlineAudioContextStub = new MockOfflineAudioContext();
|
||||
this.sinon.stub(window, 'OfflineAudioContext')
|
||||
.returns(offlineAudioContextStub);
|
||||
this.sinon.stub(offlineAudioContextStub, 'decodeAudioData',
|
||||
function(arrayBuffer, successCallback, errorCallback) {
|
||||
var audioBuffer = audioBuffers.get(arrayBuffer);
|
||||
// Simulate async callback
|
||||
Promise.resolve(audioBuffer).then(successCallback);
|
||||
});
|
||||
|
||||
stubAudioBufferSourceNode =
|
||||
this.sinon.stub(new MockAudioBufferSourceNode(audioContextStub));
|
||||
this.sinon.stub(audioContextStub, 'createBufferSource')
|
||||
.returns(stubAudioBufferSourceNode);
|
||||
|
||||
player = new SoundFeedbackPlayer();
|
||||
var p = player.prepare();
|
||||
|
||||
assert.equal(requests.length, 2);
|
||||
requests.forEach(function(request, i) {
|
||||
var expectedRequest = expectedRequests[i];
|
||||
|
||||
assert.equal(request.url, expectedRequest.url);
|
||||
assert.equal(request.responseType, 'arraybuffer');
|
||||
request.response = expectedRequest.response;
|
||||
|
||||
request.respond(200, {}, '');
|
||||
});
|
||||
|
||||
p.then(function() { done(); }, done);
|
||||
});
|
||||
|
||||
suite('activate', function() {
|
||||
setup(function() {
|
||||
assert.isFalse(window.AudioContext.called);
|
||||
player.activate();
|
||||
|
||||
assert.isTrue(window.AudioContext.calledOnce);
|
||||
});
|
||||
|
||||
test('mozAudioChannelType is system', function() {
|
||||
assert.isTrue(window.AudioContext.calledWith('system'));
|
||||
assert.equal(player._audioCtx.mozAudioChannelType, 'system');
|
||||
});
|
||||
|
||||
test('activate again', function() {
|
||||
player.activate();
|
||||
|
||||
assert.isFalse(window.AudioContext.calledTwice, 'caused a side effect');
|
||||
});
|
||||
|
||||
test('deactivate', function() {
|
||||
player.deactivate();
|
||||
|
||||
// XXX probing "private" variable since there is no way to tell
|
||||
// if the reference has been released without doing so.
|
||||
assert.equal(player._audioCtx, null);
|
||||
});
|
||||
|
||||
test('play normal clicker', function() {
|
||||
player.play(false);
|
||||
|
||||
var audioBuffer = audioBuffers.get(expectedRequests[0].response);
|
||||
assert.equal(stubAudioBufferSourceNode.buffer, audioBuffer);
|
||||
assert.isTrue(stubAudioBufferSourceNode.connect
|
||||
.calledWith(audioContextStub.destination));
|
||||
assert.isTrue(stubAudioBufferSourceNode.start.calledWith(0));
|
||||
});
|
||||
|
||||
test('play special clicker', function() {
|
||||
player.play(true);
|
||||
|
||||
var audioBuffer = audioBuffers.get(expectedRequests[1].response);
|
||||
assert.equal(stubAudioBufferSourceNode.buffer, audioBuffer);
|
||||
assert.isTrue(stubAudioBufferSourceNode.connect
|
||||
.calledWith(audioContextStub.destination));
|
||||
assert.isTrue(stubAudioBufferSourceNode.start.calledWith(0));
|
||||
});
|
||||
});
|
||||
});
|
||||
369
gaia/apps/keyboard/test/unit/keyboard/state_manager_test.js
Executable file
369
gaia/apps/keyboard/test/unit/keyboard/state_manager_test.js
Executable file
|
|
@ -0,0 +1,369 @@
|
|||
'use strict';
|
||||
|
||||
/* global LayoutManager, InputMethodManager, L10nLoader, CandidatePanelManager,
|
||||
ActiveTargetsManager, UpperCaseStateManager, SettingsPromiseManager,
|
||||
LayoutRenderingManager, KeyboardConsole, LayoutLoader,
|
||||
InputMethodLoader, FeedbackManager, StateManager,
|
||||
AbortablePromiseQueue,
|
||||
MockInputMethod, MockInputContext, MockEventTarget */
|
||||
|
||||
require('/js/keyboard/console.js');
|
||||
require('/js/keyboard/input_method_manager.js');
|
||||
require('/js/keyboard/layout_manager.js');
|
||||
require('/js/keyboard/layout_loader.js');
|
||||
require('/js/keyboard/settings.js');
|
||||
require('/js/keyboard/l10n_loader.js');
|
||||
require('/js/keyboard/active_targets_manager.js');
|
||||
require('/js/keyboard/candidate_panel_manager.js');
|
||||
require('/js/keyboard/feedback_manager.js');
|
||||
require('/js/keyboard/upper_case_state_manager.js');
|
||||
require('/js/keyboard/layout_rendering_manager.js');
|
||||
require('/js/keyboard/abortable_promise_queue.js');
|
||||
|
||||
require('/shared/test/unit/mocks/mock_event_target.js');
|
||||
require('/shared/test/unit/mocks/mock_navigator_input_method.js');
|
||||
|
||||
require('/js/keyboard/state_manager.js');
|
||||
|
||||
suite('StateManager', function() {
|
||||
var manager;
|
||||
var app;
|
||||
var realMozInputMethod;
|
||||
var windowStub;
|
||||
|
||||
var stubAbortablePromiseQueue;
|
||||
|
||||
suiteSetup(function() {
|
||||
Object.defineProperty(
|
||||
document, 'hidden',
|
||||
{
|
||||
value: true,
|
||||
writable: true,
|
||||
configurable: true
|
||||
});
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
stubAbortablePromiseQueue =
|
||||
this.sinon.stub(AbortablePromiseQueue.prototype);
|
||||
this.sinon.stub(window, 'AbortablePromiseQueue')
|
||||
.returns(stubAbortablePromiseQueue);
|
||||
|
||||
app = {
|
||||
layoutManager: this.sinon.stub(LayoutManager.prototype),
|
||||
inputMethodManager: this.sinon.stub(InputMethodManager.prototype),
|
||||
l10nLoader: this.sinon.stub(L10nLoader.prototype),
|
||||
candidatePanelManager: this.sinon.stub(CandidatePanelManager.prototype),
|
||||
feedbackManager: this.sinon.stub(FeedbackManager.prototype),
|
||||
targetHandlersManager: {
|
||||
activeTargetsManager: this.sinon.stub(ActiveTargetsManager.prototype)
|
||||
},
|
||||
upperCaseStateManager: this.sinon.stub(UpperCaseStateManager.prototype),
|
||||
setInputContext: this.sinon.stub(),
|
||||
settingsPromiseManager: this.sinon.stub(SettingsPromiseManager.prototype),
|
||||
layoutRenderingManager: this.sinon.stub(LayoutRenderingManager.prototype),
|
||||
console: this.sinon.stub(KeyboardConsole.prototype)
|
||||
};
|
||||
|
||||
app.layoutManager.loader = this.sinon.stub(LayoutLoader.prototype);
|
||||
app.inputMethodManager.loader =
|
||||
this.sinon.stub(InputMethodLoader.prototype);
|
||||
|
||||
windowStub = new MockEventTarget();
|
||||
|
||||
this.sinon.stub(window, 'addEventListener',
|
||||
windowStub.addEventListener.bind(windowStub));
|
||||
this.sinon.stub(window, 'removeEventListener',
|
||||
windowStub.removeEventListener.bind(windowStub));
|
||||
|
||||
window.location.hash = '#foo';
|
||||
|
||||
realMozInputMethod = navigator.mozInputMethod;
|
||||
navigator.mozInputMethod = new MockInputMethod();
|
||||
|
||||
this.sinon.stub(window, 'setTimeout');
|
||||
|
||||
manager = new StateManager(app);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
manager.stop();
|
||||
|
||||
assert.isTrue(
|
||||
window.removeEventListener.calledWith('hashchange', manager));
|
||||
assert.isTrue(
|
||||
window.removeEventListener.calledWith('visibilitychange', manager));
|
||||
|
||||
navigator.mozInputMethod = realMozInputMethod;
|
||||
});
|
||||
|
||||
suite('start with not hidden', function() {
|
||||
setup(function() {
|
||||
document.hidden = false;
|
||||
navigator.mozInputMethod.setInputContext(new MockInputContext());
|
||||
app.inputContext = navigator.mozInputMethod.inputcontext;
|
||||
|
||||
app.layoutManager.currentPage = {
|
||||
imEngine: 'bar'
|
||||
};
|
||||
|
||||
manager.start();
|
||||
|
||||
assert.isTrue(
|
||||
window.addEventListener.calledWith('hashchange', manager));
|
||||
assert.isTrue(
|
||||
window.addEventListener.calledWith('visibilitychange', manager));
|
||||
|
||||
assert.isFalse(app.layoutManager.loader.getLayoutAsync.calledOnce,
|
||||
'Should not try to preload layout.');
|
||||
assert.isFalse(
|
||||
app.inputMethodManager.loader.getInputMethodAsync.calledOnce,
|
||||
'Should not try to preload IMEngine.');
|
||||
assert.isFalse(app.l10nLoader.load.calledOnce);
|
||||
|
||||
// Run the queue
|
||||
assert.isTrue(stubAbortablePromiseQueue.start.calledOnce);
|
||||
assert.isTrue(stubAbortablePromiseQueue.run.calledOnce);
|
||||
var value;
|
||||
stubAbortablePromiseQueue.run.firstCall.args[0].forEach(function(task) {
|
||||
value = task(value);
|
||||
});
|
||||
|
||||
assert.isTrue(app.inputMethodManager.deactivateIMEngine.calledOnce);
|
||||
|
||||
assert.isTrue(app.candidatePanelManager.hideFullPanel.calledOnce);
|
||||
assert.isTrue(app.candidatePanelManager.updateCandidates.calledOnce);
|
||||
assert.isFalse(app.targetHandlersManager
|
||||
.activeTargetsManager.clearAllTargets.called);
|
||||
assert.isTrue(app.feedbackManager.activate.calledOnce);
|
||||
|
||||
assert.isTrue(app.layoutManager.switchCurrentLayout.calledWith('foo'));
|
||||
|
||||
assert.isTrue(app.upperCaseStateManager.reset.calledOnce);
|
||||
assert.isTrue(app.candidatePanelManager.reset.calledOnce);
|
||||
assert.isTrue(
|
||||
app.inputMethodManager.activateIMEngine.calledWith('bar'));
|
||||
assert.isTrue(app.upperCaseStateManager.reset.calledBefore(
|
||||
app.inputMethodManager.activateIMEngine),
|
||||
'Reset the state before engine activates.');
|
||||
|
||||
assert.isTrue(
|
||||
app.layoutRenderingManager.updateLayoutRendering.calledOnce);
|
||||
|
||||
assert.isTrue(app.l10nLoader.load.calledOnce);
|
||||
assert.isTrue(app.l10nLoader.load.calledOn(app.l10nLoader));
|
||||
});
|
||||
|
||||
test('hashchange', function() {
|
||||
window.location.hash = '#foo2';
|
||||
var evt = {
|
||||
type: 'hashchange'
|
||||
};
|
||||
|
||||
app.layoutManager.currentPage = {
|
||||
imEngine: 'bar2'
|
||||
};
|
||||
|
||||
windowStub.dispatchEvent(evt);
|
||||
|
||||
// Run the queue
|
||||
assert.isTrue(stubAbortablePromiseQueue.run.calledTwice);
|
||||
var value;
|
||||
stubAbortablePromiseQueue.run.getCall(1).args[0].forEach(function(task) {
|
||||
value = task(value);
|
||||
});
|
||||
|
||||
// Start layout switching
|
||||
assert.isTrue(app.candidatePanelManager.hideFullPanel.calledTwice);
|
||||
assert.isTrue(app.candidatePanelManager.updateCandidates.calledTwice);
|
||||
assert.isFalse(app.targetHandlersManager
|
||||
.activeTargetsManager.clearAllTargets.called);
|
||||
|
||||
assert.isTrue(app.layoutManager.switchCurrentLayout.calledWith('foo2'));
|
||||
|
||||
assert.isTrue(app.upperCaseStateManager.reset.calledTwice);
|
||||
assert.isTrue(
|
||||
app.inputMethodManager.activateIMEngine.calledWith('bar2'));
|
||||
assert.isTrue(app.upperCaseStateManager.reset.calledBefore(
|
||||
app.inputMethodManager.activateIMEngine),
|
||||
'Reset the state before engine activates.');
|
||||
|
||||
assert.isTrue(
|
||||
app.layoutRenderingManager.updateLayoutRendering.calledTwice);
|
||||
|
||||
assert.isTrue(app.l10nLoader.load.calledTwice);
|
||||
assert.isTrue(app.l10nLoader.load.calledOn(app.l10nLoader));
|
||||
});
|
||||
|
||||
suite('deactivate', function() {
|
||||
setup(function() {
|
||||
// triggers inputcontextchange
|
||||
navigator.mozInputMethod.setInputContext();
|
||||
app.inputContext = null;
|
||||
});
|
||||
|
||||
test('finishing deactivation', function() {
|
||||
// Run the queue
|
||||
assert.isTrue(stubAbortablePromiseQueue.run.calledTwice);
|
||||
var value;
|
||||
stubAbortablePromiseQueue.run.getCall(1).args[0]
|
||||
.forEach(function(task) {
|
||||
value = task(value);
|
||||
});
|
||||
|
||||
assert.isTrue(app.candidatePanelManager.hideFullPanel.calledTwice);
|
||||
assert.isTrue(app.candidatePanelManager.updateCandidates.calledTwice);
|
||||
assert.isTrue(app.targetHandlersManager
|
||||
.activeTargetsManager.clearAllTargets.calledOnce);
|
||||
assert.isTrue(app.feedbackManager.deactivate.calledOnce);
|
||||
|
||||
assert.isTrue(app.inputMethodManager.deactivateIMEngine.calledTwice);
|
||||
|
||||
// trigger visibilitychange (after inputcontextchange)
|
||||
|
||||
// check if deactivation should not be called again by
|
||||
// checking hideFullPanel as an example
|
||||
app.candidatePanelManager.hideFullPanel.reset();
|
||||
|
||||
document.hidden = true;
|
||||
var evt = {
|
||||
type: 'visibilitychange'
|
||||
};
|
||||
windowStub.dispatchEvent(evt);
|
||||
|
||||
assert.isFalse(app.candidatePanelManager.hideFullPanel.called,
|
||||
'should not attempt to hide again.');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('start with hidden', function() {
|
||||
setup(function() {
|
||||
document.hidden = true;
|
||||
|
||||
var layout = { imEngine: 'bar' };
|
||||
var getLayoutAsyncPromise = {};
|
||||
|
||||
app.layoutManager.loader.getLayoutAsync.returns(getLayoutAsyncPromise);
|
||||
|
||||
manager.start();
|
||||
|
||||
assert.isTrue(
|
||||
window.addEventListener.calledWith('hashchange', manager));
|
||||
assert.isTrue(
|
||||
window.addEventListener.calledWith('visibilitychange', manager));
|
||||
|
||||
// Run the queue
|
||||
assert.isTrue(stubAbortablePromiseQueue.start.calledOnce);
|
||||
assert.isTrue(stubAbortablePromiseQueue.run.calledOnce);
|
||||
var value;
|
||||
stubAbortablePromiseQueue.run.firstCall.args[0].forEach(function(task) {
|
||||
// "resolve" this promise
|
||||
if (value === getLayoutAsyncPromise) {
|
||||
value = layout;
|
||||
}
|
||||
value = task(value);
|
||||
});
|
||||
|
||||
assert.isTrue(app.layoutManager.loader.getLayoutAsync.calledWith('foo'));
|
||||
|
||||
assert.isTrue(
|
||||
app.inputMethodManager.loader.getInputMethodAsync.calledWith('bar'));
|
||||
|
||||
assert.isTrue(app.l10nLoader.load.calledOnce);
|
||||
assert.isTrue(app.l10nLoader.load.calledOn(app.l10nLoader));
|
||||
});
|
||||
|
||||
test('hashchange', function() {
|
||||
window.location.hash = '#foo2';
|
||||
var evt = {
|
||||
type: 'hashchange'
|
||||
};
|
||||
|
||||
var layout = { imEngine: 'bar2' };
|
||||
var getLayoutAsyncPromise = {};
|
||||
|
||||
app.layoutManager.loader.getLayoutAsync.returns(getLayoutAsyncPromise);
|
||||
|
||||
windowStub.dispatchEvent(evt);
|
||||
|
||||
// Run the queue
|
||||
assert.isTrue(stubAbortablePromiseQueue.run.calledTwice);
|
||||
var value;
|
||||
stubAbortablePromiseQueue.run.getCall(1).args[0].forEach(function(task) {
|
||||
// "resolve" this promise
|
||||
if (value === getLayoutAsyncPromise) {
|
||||
value = layout;
|
||||
}
|
||||
value = task(value);
|
||||
});
|
||||
|
||||
assert.isTrue(app.layoutManager.loader.getLayoutAsync.calledWith('foo2'),
|
||||
'Start loading foo2 when hashchanged');
|
||||
assert.isTrue(
|
||||
app.inputMethodManager.loader.getInputMethodAsync.calledWith('bar2'));
|
||||
});
|
||||
|
||||
suite('activate', function() {
|
||||
setup(function() {
|
||||
document.hidden = false;
|
||||
|
||||
var evt = {
|
||||
type: 'visibilitychange'
|
||||
};
|
||||
|
||||
windowStub.dispatchEvent(evt);
|
||||
|
||||
assert.isFalse(app.layoutManager.switchCurrentLayout.calledOnce,
|
||||
'Not launched yet with visibilitychange only.');
|
||||
|
||||
app.layoutManager.currentPage = {
|
||||
imEngine: 'bar'
|
||||
};
|
||||
|
||||
navigator.mozInputMethod.setInputContext(new MockInputContext());
|
||||
app.inputContext = navigator.mozInputMethod.inputcontext;
|
||||
|
||||
assert.isFalse(app.layoutManager.loader.getLayoutAsync.calledTwice,
|
||||
'Should not try to preload layout again.');
|
||||
assert.isFalse(
|
||||
app.inputMethodManager.loader.getInputMethodAsync.calledTwice,
|
||||
'Should not try to preload IMEngine again.');
|
||||
assert.isFalse(app.l10nLoader.load.calledTwice);
|
||||
});
|
||||
|
||||
test('finishing activation', function() {
|
||||
// Run the queue
|
||||
assert.isTrue(stubAbortablePromiseQueue.run.calledTwice);
|
||||
var value;
|
||||
stubAbortablePromiseQueue.run.getCall(1).args[0]
|
||||
.forEach(function(task) {
|
||||
value = task(value);
|
||||
});
|
||||
|
||||
assert.isTrue(app.inputMethodManager.deactivateIMEngine.calledOnce);
|
||||
|
||||
assert.isTrue(app.candidatePanelManager.hideFullPanel.calledOnce);
|
||||
assert.isTrue(app.candidatePanelManager.updateCandidates.calledOnce);
|
||||
assert.isFalse(app.targetHandlersManager
|
||||
.activeTargetsManager.clearAllTargets.called);
|
||||
assert.isTrue(app.feedbackManager.activate.calledOnce);
|
||||
|
||||
assert.isTrue(app.layoutManager.switchCurrentLayout.calledWith('foo'));
|
||||
|
||||
assert.isTrue(app.upperCaseStateManager.reset.calledOnce);
|
||||
assert.isTrue(
|
||||
app.inputMethodManager.activateIMEngine.calledWith('bar'));
|
||||
assert.isTrue(app.upperCaseStateManager.reset.calledBefore(
|
||||
app.inputMethodManager.activateIMEngine),
|
||||
'Reset the state before engine activates.');
|
||||
|
||||
assert.isTrue(
|
||||
app.layoutRenderingManager.updateLayoutRendering.calledOnce);
|
||||
|
||||
assert.isTrue(app.l10nLoader.load.calledTwice);
|
||||
assert.isTrue(app.l10nLoader.load.getCall(1).calledOn(app.l10nLoader));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
404
gaia/apps/keyboard/test/unit/keyboard/target_handlers_manager_test.js
Executable file
404
gaia/apps/keyboard/test/unit/keyboard/target_handlers_manager_test.js
Executable file
|
|
@ -0,0 +1,404 @@
|
|||
'use strict';
|
||||
|
||||
/* global TargetHandlersManager, ActiveTargetsManager,
|
||||
DefaultTargetHandler, KeyEvent, LayoutManager, KeyboardConsole */
|
||||
|
||||
require('/js/keyboard/active_targets_manager.js');
|
||||
require('/js/keyboard/target_handlers.js');
|
||||
require('/js/keyboard/layout_manager.js');
|
||||
require('/js/keyboard/console.js');
|
||||
|
||||
require('/js/keyboard/target_handlers_manager.js');
|
||||
|
||||
suite('TargetHandlersManager', function() {
|
||||
var manager;
|
||||
var app;
|
||||
var handlerStub;
|
||||
var activeTargetsManagerStub;
|
||||
|
||||
var actions = [
|
||||
{name: 'commit', eventHandler: 'ontargetcommitted' },
|
||||
{name: 'move', eventHandler: 'ontargetmoved' },
|
||||
{name: 'moveOut', eventHandler: 'ontargetmovedout' },
|
||||
{name: 'cancel', eventHandler: 'ontargetcancelled' },
|
||||
{name: 'doubleTap', eventHandler: 'ontargetdoubletapped' },
|
||||
{name: 'newTargetActivate', eventHandler: 'onnewtargetwillactivate' }
|
||||
];
|
||||
|
||||
setup(function() {
|
||||
activeTargetsManagerStub = this.sinon.stub(ActiveTargetsManager.prototype);
|
||||
this.sinon.stub(window, 'ActiveTargetsManager')
|
||||
.returns(activeTargetsManagerStub);
|
||||
|
||||
handlerStub = this.sinon.stub(DefaultTargetHandler.prototype);
|
||||
this.sinon.stub(window, 'DefaultTargetHandler')
|
||||
.returns(handlerStub);
|
||||
this.sinon.stub(window, 'NullTargetHandler')
|
||||
.returns(handlerStub);
|
||||
this.sinon.stub(window, 'SpaceKeyTargetHandler')
|
||||
.returns(handlerStub);
|
||||
this.sinon.stub(window, 'CandidateSelectionTargetHandler')
|
||||
.returns(handlerStub);
|
||||
this.sinon.stub(window, 'BackspaceTargetHandler')
|
||||
.returns(handlerStub);
|
||||
this.sinon.stub(window, 'CompositeTargetHandler')
|
||||
.returns(handlerStub);
|
||||
this.sinon.stub(window, 'PageSwitchingTargetHandler')
|
||||
.returns(handlerStub);
|
||||
this.sinon.stub(window, 'CapsLockTargetHandler')
|
||||
.returns(handlerStub);
|
||||
this.sinon.stub(window, 'SwitchKeyboardTargetHandler')
|
||||
.returns(handlerStub);
|
||||
this.sinon.stub(window, 'ToggleCandidatePanelTargetHandler')
|
||||
.returns(handlerStub);
|
||||
this.sinon.stub(window, 'DismissSuggestionsTargetHandler')
|
||||
.returns(handlerStub);
|
||||
this.sinon.stub(window, 'HandwritingPadTargetHandler')
|
||||
.returns(handlerStub);
|
||||
|
||||
app = {
|
||||
console: this.sinon.stub(KeyboardConsole.prototype),
|
||||
layoutManager: this.sinon.stub(LayoutManager.prototype)
|
||||
};
|
||||
manager = new TargetHandlersManager(app);
|
||||
manager.start();
|
||||
|
||||
assert.isTrue(window.ActiveTargetsManager.calledWith(app));
|
||||
assert.isTrue(activeTargetsManagerStub.start.calledOnce);
|
||||
});
|
||||
|
||||
suite('activate', function() {
|
||||
var target;
|
||||
setup(function(done) {
|
||||
target = {
|
||||
keyCode: 99
|
||||
};
|
||||
activeTargetsManagerStub.ontargetactivated(target);
|
||||
|
||||
assert.isTrue(window.DefaultTargetHandler.calledWith(target, app));
|
||||
assert.isTrue(window.DefaultTargetHandler.calledOnce);
|
||||
|
||||
manager.promiseQueue.then(function() {
|
||||
assert.isTrue(handlerStub.activate.calledOnce);
|
||||
}, function(e) {
|
||||
assert.isTrue(false, 'promiseQueue should not reject.');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
assert.isTrue(window.DefaultTargetHandler.calledOnce,
|
||||
'Same handler was used for every following action.');
|
||||
});
|
||||
|
||||
actions.forEach(function(action) {
|
||||
test(action.name, function(done) {
|
||||
activeTargetsManagerStub[action.eventHandler](target);
|
||||
|
||||
manager.promiseQueue.then(function() {
|
||||
assert.isTrue(handlerStub[action.name].calledOnce);
|
||||
}, function(e) {
|
||||
assert.isTrue(false, 'promiseQueue should not reject.');
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('longPress', function() {
|
||||
setup(function(done) {
|
||||
activeTargetsManagerStub.ontargetlongpressed(target);
|
||||
|
||||
manager.promiseQueue.then(function() {
|
||||
assert.isTrue(handlerStub.longPress.calledOnce);
|
||||
}, function(e) {
|
||||
assert.isTrue(false, 'promiseQueue should not reject.');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
actions.forEach(function(action) {
|
||||
test(action.name, function(done) {
|
||||
activeTargetsManagerStub[action.eventHandler](target);
|
||||
|
||||
manager.promiseQueue.then(function() {
|
||||
assert.isTrue(handlerStub[action.name].calledOnce);
|
||||
}, function(e) {
|
||||
assert.isTrue(false, 'promiseQueue should not reject.');
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('moveIn', function() {
|
||||
var target;
|
||||
setup(function(done) {
|
||||
target = {
|
||||
keyCode: 99
|
||||
};
|
||||
activeTargetsManagerStub.ontargetmovedin(target);
|
||||
|
||||
assert.isTrue(window.DefaultTargetHandler.calledWith(target, app));
|
||||
assert.isTrue(window.DefaultTargetHandler.calledOnce);
|
||||
|
||||
manager.promiseQueue.then(function() {
|
||||
assert.isTrue(handlerStub.moveIn.calledOnce);
|
||||
}, function(e) {
|
||||
assert.isTrue(false, 'promiseQueue should not reject.');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
assert.isTrue(window.DefaultTargetHandler.calledOnce,
|
||||
'Same handler was used for every following action.');
|
||||
});
|
||||
|
||||
actions.forEach(function(action) {
|
||||
test(action.name, function(done) {
|
||||
activeTargetsManagerStub[action.eventHandler](target);
|
||||
|
||||
manager.promiseQueue.then(function() {
|
||||
assert.isTrue(handlerStub[action.name].calledOnce);
|
||||
}, function(e) {
|
||||
assert.isTrue(false, 'promiseQueue should not reject.');
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('longPress', function() {
|
||||
setup(function(done) {
|
||||
activeTargetsManagerStub.ontargetlongpressed(target);
|
||||
|
||||
manager.promiseQueue.then(function() {
|
||||
assert.isTrue(handlerStub.longPress.calledOnce);
|
||||
}, function(e) {
|
||||
assert.isTrue(false, 'promiseQueue should not reject.');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
actions.forEach(function(action) {
|
||||
test(action.name, function(done) {
|
||||
activeTargetsManagerStub[action.eventHandler](target);
|
||||
|
||||
manager.promiseQueue.then(function() {
|
||||
assert.isTrue(handlerStub[action.name].calledOnce);
|
||||
}, function(e) {
|
||||
assert.isTrue(false, 'promiseQueue should not reject.');
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('activate different targets', function() {
|
||||
test('DismissSuggestionsTargetHandler', function(done) {
|
||||
var target = {
|
||||
isDismissSuggestionsButton: true
|
||||
};
|
||||
|
||||
activeTargetsManagerStub.ontargetactivated(target);
|
||||
|
||||
assert.isTrue(
|
||||
window.DismissSuggestionsTargetHandler.calledWith(target, app));
|
||||
|
||||
manager.promiseQueue.then(function() {
|
||||
assert.isTrue(handlerStub.activate.calledOnce);
|
||||
}, function(e) {
|
||||
assert.isTrue(false, 'promiseQueue should not reject.');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('CandidateSelectionTargetHandler', function(done) {
|
||||
var target = {
|
||||
suggestion: 'foo'
|
||||
};
|
||||
|
||||
activeTargetsManagerStub.ontargetactivated(target);
|
||||
|
||||
assert.isTrue(
|
||||
window.CandidateSelectionTargetHandler.calledWith(target, app));
|
||||
|
||||
manager.promiseQueue.then(function() {
|
||||
assert.isTrue(handlerStub.activate.calledOnce);
|
||||
}, function(e) {
|
||||
assert.isTrue(false, 'promiseQueue should not reject.');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('CompositeTargetHandler', function(done) {
|
||||
var target = {
|
||||
compositeKey: 'lol'
|
||||
};
|
||||
|
||||
activeTargetsManagerStub.ontargetactivated(target);
|
||||
|
||||
assert.isTrue(
|
||||
window.CompositeTargetHandler.calledWith(target, app));
|
||||
|
||||
manager.promiseQueue.then(function() {
|
||||
assert.isTrue(handlerStub.activate.calledOnce);
|
||||
}, function(e) {
|
||||
assert.isTrue(false, 'promiseQueue should not reject.');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('BackspaceTargetHandler', function(done) {
|
||||
var target = {
|
||||
keyCode: KeyEvent.DOM_VK_BACK_SPACE
|
||||
};
|
||||
|
||||
activeTargetsManagerStub.ontargetactivated(target);
|
||||
|
||||
assert.isTrue(
|
||||
window.BackspaceTargetHandler.calledWith(target, app));
|
||||
|
||||
manager.promiseQueue.then(function() {
|
||||
assert.isTrue(handlerStub.activate.calledOnce);
|
||||
}, function(e) {
|
||||
assert.isTrue(false, 'promiseQueue should not reject.');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('SpaceKeyTargetHandler', function(done) {
|
||||
var target = {
|
||||
keyCode: KeyEvent.DOM_VK_SPACE
|
||||
};
|
||||
|
||||
activeTargetsManagerStub.ontargetactivated(target);
|
||||
|
||||
assert.isTrue(
|
||||
window.SpaceKeyTargetHandler.calledWith(target, app));
|
||||
|
||||
manager.promiseQueue.then(function() {
|
||||
assert.isTrue(handlerStub.activate.calledOnce);
|
||||
}, function(e) {
|
||||
assert.isTrue(false, 'promiseQueue should not reject.');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('PageSwitchingTargetHandler', function(done) {
|
||||
var target = {
|
||||
keyCode: KeyEvent.DOM_VK_ALT
|
||||
};
|
||||
|
||||
activeTargetsManagerStub.ontargetactivated(target);
|
||||
|
||||
assert.isTrue(
|
||||
window.PageSwitchingTargetHandler.calledWith(target, app));
|
||||
|
||||
manager.promiseQueue.then(function() {
|
||||
assert.isTrue(handlerStub.activate.calledOnce);
|
||||
}, function(e) {
|
||||
assert.isTrue(false, 'promiseQueue should not reject.');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('SwitchKeyboardTargetHandler', function(done) {
|
||||
var target = {
|
||||
keyCode: app.layoutManager.KEYCODE_SWITCH_KEYBOARD
|
||||
};
|
||||
|
||||
activeTargetsManagerStub.ontargetactivated(target);
|
||||
|
||||
assert.isTrue(
|
||||
window.SwitchKeyboardTargetHandler.calledWith(target, app));
|
||||
|
||||
manager.promiseQueue.then(function() {
|
||||
assert.isTrue(handlerStub.activate.calledOnce);
|
||||
}, function(e) {
|
||||
assert.isTrue(false, 'promiseQueue should not reject.');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('CapsLockTargetHandler', function(done) {
|
||||
var target = {
|
||||
keyCode: KeyEvent.DOM_VK_CAPS_LOCK
|
||||
};
|
||||
|
||||
activeTargetsManagerStub.ontargetactivated(target);
|
||||
|
||||
assert.isTrue(
|
||||
window.CapsLockTargetHandler.calledWith(target, app));
|
||||
|
||||
manager.promiseQueue.then(function() {
|
||||
assert.isTrue(handlerStub.activate.calledOnce);
|
||||
}, function(e) {
|
||||
assert.isTrue(false, 'promiseQueue should not reject.');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
suite('DefaultTargetHandler', function() {
|
||||
test('-99', function(done) {
|
||||
var target = {
|
||||
keyCode: -99
|
||||
};
|
||||
|
||||
activeTargetsManagerStub.ontargetactivated(target);
|
||||
|
||||
assert.isTrue(
|
||||
window.DefaultTargetHandler.calledWith(target, app));
|
||||
|
||||
manager.promiseQueue.then(function() {
|
||||
assert.isTrue(handlerStub.activate.calledOnce);
|
||||
}, function(e) {
|
||||
assert.isTrue(false, 'promiseQueue should not reject.');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('99', function(done) {
|
||||
var target = {
|
||||
keyCode: 99
|
||||
};
|
||||
|
||||
activeTargetsManagerStub.ontargetactivated(target);
|
||||
|
||||
assert.isTrue(
|
||||
window.DefaultTargetHandler.calledWith(target, app));
|
||||
|
||||
manager.promiseQueue.then(function() {
|
||||
assert.isTrue(handlerStub.activate.calledOnce);
|
||||
}, function(e) {
|
||||
assert.isTrue(false, 'promiseQueue should not reject.');
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
test('NullTargetHandler', function(done) {
|
||||
var target = {};
|
||||
|
||||
activeTargetsManagerStub.ontargetactivated(target);
|
||||
|
||||
assert.isTrue(
|
||||
window.NullTargetHandler.calledWith(target, app));
|
||||
|
||||
manager.promiseQueue.then(function() {
|
||||
assert.isTrue(handlerStub.activate.calledOnce);
|
||||
}, function(e) {
|
||||
assert.isTrue(false, 'promiseQueue should not reject.');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('HandwritingPadTargetHandler', function(done) {
|
||||
var target = {
|
||||
isHandwritingPad: true
|
||||
};
|
||||
|
||||
activeTargetsManagerStub.ontargetactivated(target);
|
||||
|
||||
assert.isTrue(
|
||||
window.HandwritingPadTargetHandler.calledWith(target, app));
|
||||
|
||||
manager.promiseQueue.then(function() {
|
||||
assert.isTrue(handlerStub.activate.calledOnce);
|
||||
assert.isTrue(activeTargetsManagerStub.blockNewUserPress);
|
||||
assert.isTrue(activeTargetsManagerStub.blockTargetMovedOut);
|
||||
|
||||
activeTargetsManagerStub.ontargetcommitted(target);
|
||||
}).then(function() {
|
||||
assert.isTrue(handlerStub.commit.calledOnce);
|
||||
assert.isFalse(activeTargetsManagerStub.blockNewUserPress);
|
||||
assert.isFalse(activeTargetsManagerStub.blockTargetMovedOut);
|
||||
activeTargetsManagerStub.ontargetcommitted(target);
|
||||
}, function(e) {
|
||||
assert.isTrue(false, 'promiseQueue should not reject.');
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
1178
gaia/apps/keyboard/test/unit/keyboard/target_handlers_test.js
Executable file
1178
gaia/apps/keyboard/test/unit/keyboard/target_handlers_test.js
Executable file
File diff suppressed because it is too large
Load diff
282
gaia/apps/keyboard/test/unit/keyboard/upper_case_state_manager_test.js
Executable file
282
gaia/apps/keyboard/test/unit/keyboard/upper_case_state_manager_test.js
Executable file
|
|
@ -0,0 +1,282 @@
|
|||
'use strict';
|
||||
|
||||
/* global UpperCaseStateManager */
|
||||
|
||||
require('/js/keyboard/upper_case_state_manager.js');
|
||||
|
||||
suite('UpperCaseStateManager', function() {
|
||||
var manager;
|
||||
|
||||
setup(function() {
|
||||
manager = new UpperCaseStateManager();
|
||||
manager.onstatechange = this.sinon.stub();
|
||||
manager.start();
|
||||
});
|
||||
|
||||
suite('isUpperCase = false, isUpperCaseLocked = false', function() {
|
||||
test('set isUpperCase = true', function() {
|
||||
manager.switchUpperCaseState({
|
||||
isUpperCase: true
|
||||
});
|
||||
|
||||
assert.isTrue(manager.isUpperCase);
|
||||
assert.isFalse(manager.isUpperCaseLocked);
|
||||
assert.isTrue(manager.onstatechange.calledOnce);
|
||||
});
|
||||
|
||||
test('set isUpperCase = false', function() {
|
||||
manager.switchUpperCaseState({
|
||||
isUpperCase: false
|
||||
});
|
||||
|
||||
assert.isFalse(manager.isUpperCase);
|
||||
assert.isFalse(manager.isUpperCaseLocked);
|
||||
assert.isFalse(manager.onstatechange.calledOnce);
|
||||
});
|
||||
|
||||
test('set isUpperCase = true, isUpperCaseLocked = false', function() {
|
||||
manager.switchUpperCaseState({
|
||||
isUpperCase: true,
|
||||
isUpperCaseLocked: false
|
||||
});
|
||||
|
||||
assert.isTrue(manager.isUpperCase);
|
||||
assert.isFalse(manager.isUpperCaseLocked);
|
||||
assert.isTrue(manager.onstatechange.calledOnce);
|
||||
});
|
||||
|
||||
test('set isUpperCase = false, isUpperCaseLocked = false', function() {
|
||||
manager.switchUpperCaseState({
|
||||
isUpperCase: false,
|
||||
isUpperCaseLocked: false
|
||||
});
|
||||
|
||||
assert.isFalse(manager.isUpperCase);
|
||||
assert.isFalse(manager.isUpperCaseLocked);
|
||||
assert.isFalse(manager.onstatechange.calledOnce);
|
||||
});
|
||||
|
||||
test('set isUpperCase = true, isUpperCaseLocked = true', function() {
|
||||
manager.switchUpperCaseState({
|
||||
isUpperCase: true,
|
||||
isUpperCaseLocked: true
|
||||
});
|
||||
|
||||
assert.isTrue(manager.isUpperCase);
|
||||
assert.isTrue(manager.isUpperCaseLocked);
|
||||
assert.isTrue(manager.onstatechange.calledOnce);
|
||||
});
|
||||
|
||||
test('set isUpperCase = false, isUpperCaseLocked = true', function() {
|
||||
manager.switchUpperCaseState({
|
||||
isUpperCase: false,
|
||||
isUpperCaseLocked: true
|
||||
});
|
||||
|
||||
assert.isTrue(manager.isUpperCase);
|
||||
assert.isTrue(manager.isUpperCaseLocked);
|
||||
assert.isTrue(manager.onstatechange.calledOnce);
|
||||
});
|
||||
|
||||
test('set isUpperCaseLocked = true', function() {
|
||||
manager.switchUpperCaseState({
|
||||
isUpperCaseLocked: true
|
||||
});
|
||||
|
||||
assert.isTrue(manager.isUpperCase);
|
||||
assert.isTrue(manager.isUpperCaseLocked);
|
||||
assert.isTrue(manager.onstatechange.calledOnce);
|
||||
});
|
||||
|
||||
test('set isUpperCaseLocked = false', function() {
|
||||
manager.switchUpperCaseState({
|
||||
isUpperCaseLocked: false
|
||||
});
|
||||
|
||||
assert.isFalse(manager.isUpperCase);
|
||||
assert.isFalse(manager.isUpperCaseLocked);
|
||||
assert.isFalse(manager.onstatechange.calledOnce);
|
||||
});
|
||||
});
|
||||
|
||||
suite('isUpperCase = true, isUpperCaseLocked = false', function() {
|
||||
setup(function() {
|
||||
manager.isUpperCase = true;
|
||||
});
|
||||
|
||||
test('set isUpperCase = true', function() {
|
||||
manager.switchUpperCaseState({
|
||||
isUpperCase: true
|
||||
});
|
||||
|
||||
assert.isTrue(manager.isUpperCase);
|
||||
assert.isFalse(manager.isUpperCaseLocked);
|
||||
assert.isFalse(manager.onstatechange.calledOnce);
|
||||
});
|
||||
|
||||
test('set isUpperCase = false', function() {
|
||||
manager.switchUpperCaseState({
|
||||
isUpperCase: false
|
||||
});
|
||||
|
||||
assert.isFalse(manager.isUpperCase);
|
||||
assert.isFalse(manager.isUpperCaseLocked);
|
||||
assert.isTrue(manager.onstatechange.calledOnce);
|
||||
});
|
||||
|
||||
test('set isUpperCase = true, isUpperCaseLocked = false', function() {
|
||||
manager.switchUpperCaseState({
|
||||
isUpperCase: true,
|
||||
isUpperCaseLocked: false
|
||||
});
|
||||
|
||||
assert.isTrue(manager.isUpperCase);
|
||||
assert.isFalse(manager.isUpperCaseLocked);
|
||||
assert.isFalse(manager.onstatechange.calledOnce);
|
||||
});
|
||||
|
||||
test('set isUpperCase = false, isUpperCaseLocked = false', function() {
|
||||
manager.switchUpperCaseState({
|
||||
isUpperCase: false,
|
||||
isUpperCaseLocked: false
|
||||
});
|
||||
|
||||
assert.isFalse(manager.isUpperCase);
|
||||
assert.isFalse(manager.isUpperCaseLocked);
|
||||
assert.isTrue(manager.onstatechange.calledOnce);
|
||||
});
|
||||
|
||||
test('set isUpperCase = true, isUpperCaseLocked = true', function() {
|
||||
manager.switchUpperCaseState({
|
||||
isUpperCase: true,
|
||||
isUpperCaseLocked: true
|
||||
});
|
||||
|
||||
assert.isTrue(manager.isUpperCase);
|
||||
assert.isTrue(manager.isUpperCaseLocked);
|
||||
assert.isTrue(manager.onstatechange.calledOnce);
|
||||
});
|
||||
|
||||
test('set isUpperCase = false, isUpperCaseLocked = true', function() {
|
||||
manager.switchUpperCaseState({
|
||||
isUpperCase: false,
|
||||
isUpperCaseLocked: true
|
||||
});
|
||||
|
||||
assert.isTrue(manager.isUpperCase);
|
||||
assert.isTrue(manager.isUpperCaseLocked);
|
||||
assert.isTrue(manager.onstatechange.calledOnce);
|
||||
});
|
||||
|
||||
test('set isUpperCaseLocked = true', function() {
|
||||
manager.switchUpperCaseState({
|
||||
isUpperCaseLocked: true
|
||||
});
|
||||
|
||||
assert.isTrue(manager.isUpperCase);
|
||||
assert.isTrue(manager.isUpperCaseLocked);
|
||||
assert.isTrue(manager.onstatechange.calledOnce);
|
||||
});
|
||||
|
||||
test('set isUpperCaseLocked = false', function() {
|
||||
manager.switchUpperCaseState({
|
||||
isUpperCaseLocked: false
|
||||
});
|
||||
|
||||
assert.isTrue(manager.isUpperCase);
|
||||
assert.isFalse(manager.isUpperCaseLocked);
|
||||
assert.isFalse(manager.onstatechange.calledOnce);
|
||||
});
|
||||
});
|
||||
|
||||
suite('isUpperCase = true, isUpperCaseLocked = true', function() {
|
||||
setup(function() {
|
||||
manager.isUpperCase = true;
|
||||
manager.isUpperCaseLocked = true;
|
||||
});
|
||||
|
||||
test('set isUpperCase = true', function() {
|
||||
manager.switchUpperCaseState({
|
||||
isUpperCase: true
|
||||
});
|
||||
|
||||
assert.isTrue(manager.isUpperCase);
|
||||
assert.isTrue(manager.isUpperCaseLocked);
|
||||
assert.isFalse(manager.onstatechange.calledOnce);
|
||||
});
|
||||
|
||||
test('set isUpperCase = false', function() {
|
||||
manager.switchUpperCaseState({
|
||||
isUpperCase: false
|
||||
});
|
||||
|
||||
assert.isTrue(manager.isUpperCase);
|
||||
assert.isTrue(manager.isUpperCaseLocked);
|
||||
assert.isFalse(manager.onstatechange.calledOnce);
|
||||
});
|
||||
|
||||
test('set isUpperCase = true, isUpperCaseLocked = false', function() {
|
||||
manager.switchUpperCaseState({
|
||||
isUpperCase: true,
|
||||
isUpperCaseLocked: false
|
||||
});
|
||||
|
||||
assert.isTrue(manager.isUpperCase);
|
||||
assert.isFalse(manager.isUpperCaseLocked);
|
||||
assert.isTrue(manager.onstatechange.calledOnce);
|
||||
});
|
||||
|
||||
test('set isUpperCase = false, isUpperCaseLocked = false', function() {
|
||||
manager.switchUpperCaseState({
|
||||
isUpperCase: false,
|
||||
isUpperCaseLocked: false
|
||||
});
|
||||
|
||||
assert.isFalse(manager.isUpperCase);
|
||||
assert.isFalse(manager.isUpperCaseLocked);
|
||||
assert.isTrue(manager.onstatechange.calledOnce);
|
||||
});
|
||||
|
||||
test('set isUpperCase = true, isUpperCaseLocked = true', function() {
|
||||
manager.switchUpperCaseState({
|
||||
isUpperCase: true,
|
||||
isUpperCaseLocked: true
|
||||
});
|
||||
|
||||
assert.isTrue(manager.isUpperCase);
|
||||
assert.isTrue(manager.isUpperCaseLocked);
|
||||
assert.isFalse(manager.onstatechange.calledOnce);
|
||||
});
|
||||
|
||||
test('set isUpperCase = false, isUpperCaseLocked = true', function() {
|
||||
manager.switchUpperCaseState({
|
||||
isUpperCase: false,
|
||||
isUpperCaseLocked: true
|
||||
});
|
||||
|
||||
assert.isTrue(manager.isUpperCase);
|
||||
assert.isTrue(manager.isUpperCaseLocked);
|
||||
assert.isFalse(manager.onstatechange.calledOnce);
|
||||
});
|
||||
|
||||
test('set isUpperCaseLocked = true', function() {
|
||||
manager.switchUpperCaseState({
|
||||
isUpperCaseLocked: true
|
||||
});
|
||||
|
||||
assert.isTrue(manager.isUpperCase);
|
||||
assert.isTrue(manager.isUpperCaseLocked);
|
||||
assert.isFalse(manager.onstatechange.calledOnce);
|
||||
});
|
||||
|
||||
test('set isUpperCaseLocked = false', function() {
|
||||
manager.switchUpperCaseState({
|
||||
isUpperCaseLocked: false
|
||||
});
|
||||
|
||||
assert.isTrue(manager.isUpperCase);
|
||||
assert.isFalse(manager.isUpperCaseLocked);
|
||||
assert.isTrue(manager.onstatechange.calledOnce);
|
||||
});
|
||||
});
|
||||
});
|
||||
1306
gaia/apps/keyboard/test/unit/keyboard/user_press_manager_test.js
Executable file
1306
gaia/apps/keyboard/test/unit/keyboard/user_press_manager_test.js
Executable file
File diff suppressed because it is too large
Load diff
400
gaia/apps/keyboard/test/unit/keyboard/view_manager_test.js
Executable file
400
gaia/apps/keyboard/test/unit/keyboard/view_manager_test.js
Executable file
|
|
@ -0,0 +1,400 @@
|
|||
'use strict';
|
||||
/* global ViewManager, KeyboardConsole*/
|
||||
|
||||
require('/js/keyboard/view_manager.js');
|
||||
require('/js/keyboard/console.js');
|
||||
require('/js/views/key_view.js');
|
||||
require('/js/views/base_view.js');
|
||||
require('/js/views/layout_page_view.js');
|
||||
require('/js/views/candidate_panel_view.js');
|
||||
require('/js/views/latin_candidate_panel_view.js');
|
||||
require('/js/views/view_utils.js');
|
||||
|
||||
suite('View Manager', function() {
|
||||
suiteSetup(function() {
|
||||
window.perfTimer = {
|
||||
printTime: function() {},
|
||||
startTimer: function() {}
|
||||
};
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
window.perfTimer = null;
|
||||
});
|
||||
|
||||
var app = null;
|
||||
var viewManager = null;
|
||||
var fakeRenderingManager;
|
||||
var stubRequestAnimationFrame;
|
||||
|
||||
var container, activeIme;
|
||||
|
||||
setup(function(next) {
|
||||
fakeRenderingManager = {
|
||||
getTargetObject: function(elem) {
|
||||
return this.domObjectMap.get(elem);
|
||||
},
|
||||
domObjectMap: new WeakMap()
|
||||
};
|
||||
|
||||
// Tests in CI do not necessarily run at the same resolution as a device.
|
||||
Object.defineProperty(window, 'innerWidth', {
|
||||
configurable: true,
|
||||
get: () => 320 }
|
||||
);
|
||||
|
||||
Object.defineProperty(window, 'innerHeight', {
|
||||
configurable: true,
|
||||
get: () => 480 }
|
||||
);
|
||||
|
||||
stubRequestAnimationFrame =
|
||||
this.sinon.stub(window, 'requestAnimationFrame');
|
||||
|
||||
document.body.innerHTML = '';
|
||||
|
||||
container = document.createElement('div');
|
||||
container.id = 'keyboard';
|
||||
document.body.appendChild(container);
|
||||
|
||||
activeIme = document.createElement('div');
|
||||
|
||||
container.appendChild(activeIme);
|
||||
|
||||
Object.defineProperty(container, 'clientWidth', makeDescriptor(320));
|
||||
|
||||
app = {
|
||||
layoutRenderingManager: fakeRenderingManager,
|
||||
console: this.sinon.stub(KeyboardConsole.prototype)
|
||||
};
|
||||
|
||||
viewManager = new ViewManager(app);
|
||||
viewManager.start();
|
||||
loadKeyboardStyle(next);
|
||||
});
|
||||
|
||||
function makeDescriptor(val) {
|
||||
return {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
value: val,
|
||||
writable: true
|
||||
};
|
||||
}
|
||||
|
||||
function loadKeyboardStyle(callback) {
|
||||
// Dirty trick http://www.phpied.com/when-is-a-stylesheet-really-loaded/
|
||||
var style = document.createElement('style');
|
||||
style.textContent = '@import "../../style/keyboard.css"';
|
||||
var fi = setInterval(function() {
|
||||
try {
|
||||
style.sheet.cssRules;
|
||||
clearInterval(fi);
|
||||
callback();
|
||||
} catch (e) {}
|
||||
}, 10);
|
||||
document.body.appendChild(style);
|
||||
}
|
||||
|
||||
test('screenInPortraitMode', function() {
|
||||
assert.isTrue(viewManager.screenInPortraitMode());
|
||||
});
|
||||
|
||||
test('getRemToPx', function() {
|
||||
assert.equal(viewManager.getRemToPx(), 320 / 32);
|
||||
});
|
||||
|
||||
suite(' > resize()', function() {
|
||||
test('Add portrait class to IME in portrait mode', function() {
|
||||
viewManager.cachedWindowWidth = 200;
|
||||
viewManager.cachedWindowHeight = 400;
|
||||
|
||||
viewManager.resize();
|
||||
|
||||
assert.equal(viewManager.container.classList.contains('portrait'), true);
|
||||
assert.equal(viewManager.container.classList.contains('landscape'),
|
||||
false);
|
||||
});
|
||||
|
||||
test('Add landscape class to IME in landscape mode', function() {
|
||||
viewManager.cachedWindowWidth = 400;
|
||||
viewManager.cachedWindowHeight = 200;
|
||||
|
||||
viewManager.resize();
|
||||
|
||||
assert.equal(viewManager.container.classList.contains('landscape'), true);
|
||||
assert.equal(viewManager.container.classList.contains('portrait'), false);
|
||||
});
|
||||
|
||||
test('All keys should have same visual width if fully used', function() {
|
||||
var layout = {
|
||||
width: 10,
|
||||
keys: [
|
||||
[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}]
|
||||
]
|
||||
};
|
||||
|
||||
viewManager.render(layout);
|
||||
viewManager.resize();
|
||||
|
||||
var all = [].map.call(document.querySelectorAll('.visual-wrapper'),
|
||||
function(el) {
|
||||
return el.clientWidth;
|
||||
});
|
||||
|
||||
assert.notEqual(all[0], 0);
|
||||
assert.equal(all.every(function(el) {
|
||||
return el === all[0];
|
||||
}), true, 'Every element has width ' + all[0]);
|
||||
});
|
||||
|
||||
test('Key with ratio 2 should be twice as big', function(next) {
|
||||
var layout = {
|
||||
width: 3,
|
||||
keys: [
|
||||
[{ ratio: 2 }, {}]
|
||||
]
|
||||
};
|
||||
|
||||
viewManager.render(layout, null, function() {
|
||||
var all = document.querySelectorAll('.keyboard-key');
|
||||
assert.equal(all[0].style.flexGrow, 2);
|
||||
next();
|
||||
});
|
||||
stubRequestAnimationFrame.getCall(0).args[0]();
|
||||
});
|
||||
|
||||
test('Side keys should fill up space', function(next) {
|
||||
var layout = {
|
||||
width: 4,
|
||||
keys: [
|
||||
[{}, {}, {}]
|
||||
]
|
||||
};
|
||||
|
||||
viewManager.render(layout, null, function() {
|
||||
var visual = document.querySelectorAll('.visual-wrapper');
|
||||
assert.equal(visual[0].clientWidth, visual[1].clientWidth,
|
||||
'Visually same');
|
||||
|
||||
var keys = document.querySelectorAll('.keyboard-key');
|
||||
|
||||
assert.equal(keys[0].style.flexGrow, 1.5);
|
||||
next();
|
||||
});
|
||||
stubRequestAnimationFrame.getCall(0).args[0]();
|
||||
});
|
||||
|
||||
test('Sidekeys should adjust space when rotating', function(next) {
|
||||
viewManager.cachedWindowWidth = 400;
|
||||
viewManager.cachedWindowHeight = 200;
|
||||
Object.defineProperty(container, 'clientWidth',
|
||||
makeDescriptor(400));
|
||||
|
||||
var layout = {
|
||||
width: 4,
|
||||
keys: [
|
||||
[{}, {}, {}]
|
||||
]
|
||||
};
|
||||
|
||||
viewManager.render(layout, null, function() {
|
||||
var visual = document.querySelectorAll('.visual-wrapper');
|
||||
|
||||
viewManager.cachedWindowWidth = 700;
|
||||
viewManager.cachedWindowHeight = 1000;
|
||||
Object.defineProperty(container, 'clientWidth', makeDescriptor(700));
|
||||
|
||||
viewManager.resize(function() {
|
||||
assert.equal(visual[0].clientWidth, visual[1].clientWidth,
|
||||
'Visually same');
|
||||
|
||||
next();
|
||||
});
|
||||
stubRequestAnimationFrame.getCall(1).args[0]();
|
||||
});
|
||||
stubRequestAnimationFrame.getCall(0).args[0]();
|
||||
});
|
||||
|
||||
test('getKeyArray()', function() {
|
||||
viewManager.currentPageView = {
|
||||
getVisualData: sinon.stub()
|
||||
};
|
||||
|
||||
viewManager.getKeyArray();
|
||||
|
||||
assert.ok(viewManager.currentPageView.getVisualData.called);
|
||||
});
|
||||
});
|
||||
|
||||
suite('> render()', function() {
|
||||
|
||||
test('candidate-panel class shouldnt be set if flag isnt set', function() {
|
||||
var layout = {
|
||||
width: 1,
|
||||
keys: []
|
||||
};
|
||||
viewManager.render(layout);
|
||||
assert.equal(viewManager.container.classList.contains('candidate-panel'),
|
||||
false);
|
||||
});
|
||||
|
||||
suite('UpdateByIMEngine', function() {
|
||||
test('page shouldnt be cached if updateByIMEngine is true', function() {
|
||||
var layout = {
|
||||
keys: [],
|
||||
updateByIMEngine: true
|
||||
};
|
||||
viewManager.render(layout);
|
||||
this.sinon.stub(viewManager.pageViews, 'set');
|
||||
assert.equal(viewManager.pageViews.set.called, false);
|
||||
});
|
||||
|
||||
test('updateByIMEngine page should be destoryed when switch', function() {
|
||||
var layout = {
|
||||
keys: [],
|
||||
layoutName: 'cachedLayout',
|
||||
};
|
||||
var layout2 = {
|
||||
keys: [],
|
||||
updateByIMEngine: true
|
||||
};
|
||||
var firstPage, secondPage;
|
||||
|
||||
viewManager.render(layout);
|
||||
firstPage = viewManager.currentPageView;
|
||||
this.sinon.stub(firstPage, 'destroy');
|
||||
|
||||
viewManager.render(layout2);
|
||||
secondPage = viewManager.currentPageView;
|
||||
this.sinon.stub(secondPage, 'destroy');
|
||||
|
||||
viewManager.render(layout);
|
||||
|
||||
assert.equal(firstPage.destroy.called, false);
|
||||
assert.equal(secondPage.destroy.calledOnce, true);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Dimensions', function() {
|
||||
function createDimensionTest(rowCount, orientation, suggest, latin,
|
||||
next) {
|
||||
var layout = {
|
||||
width: (Math.random() * 8 | 0) + 2,
|
||||
keys: [],
|
||||
layoutName: 'test',
|
||||
imEngine: latin ? 'latin' : 'nonLatin'
|
||||
};
|
||||
|
||||
for (var ri = 0; ri < rowCount; ri++) {
|
||||
var r = [];
|
||||
for (var ki = 0, kl = (Math.random() * 8 | 0) + 2; ki < kl; ki++) {
|
||||
r.push({ value: (Math.random() * 26 | 0) + 97 });
|
||||
}
|
||||
layout.keys.push(r);
|
||||
}
|
||||
|
||||
if (orientation === 'landscape') {
|
||||
document.querySelector('#keyboard').classList.remove('portrait');
|
||||
document.querySelector('#keyboard').classList.add('landscape');
|
||||
}
|
||||
else {
|
||||
document.querySelector('#keyboard').classList.add('portrait');
|
||||
document.querySelector('#keyboard').classList.remove('landscape');
|
||||
}
|
||||
|
||||
var flags = suggest ?
|
||||
{ showCandidatePanel: true } :
|
||||
{};
|
||||
|
||||
viewManager.render(layout, flags, function() {
|
||||
|
||||
var imeHeight = viewManager.currentPageView.element.scrollHeight;
|
||||
assert.equal(imeHeight, viewManager.getHeight());
|
||||
|
||||
next();
|
||||
});
|
||||
stubRequestAnimationFrame.getCall(0).args[0]();
|
||||
}
|
||||
|
||||
var rows = [0, 1, 2, 3, 4, 5];
|
||||
var orientation = ['portrait', 'landscape'];
|
||||
var suggest = [true, false];
|
||||
var latin = [true, false];
|
||||
|
||||
rows.forEach(function(row) {
|
||||
orientation.forEach(function(orientation) {
|
||||
suggest.forEach(function(suggest) {
|
||||
latin.forEach(function(latin) {
|
||||
var name = [
|
||||
'getHeight',
|
||||
row + ' rows',
|
||||
orientation,
|
||||
(suggest ? '' : 'no') + ' suggest',
|
||||
latin ? 'latin' : ''
|
||||
].join(', ');
|
||||
|
||||
test(name, function(next) {
|
||||
createDimensionTest(row, orientation, suggest, latin, next);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('Highlight Keys', function() {
|
||||
var dummyKey = {
|
||||
dummy: 'dummy'
|
||||
};
|
||||
|
||||
setup(function(){
|
||||
var layout = {
|
||||
width: 2,
|
||||
keys: [
|
||||
[{}, {}]
|
||||
]
|
||||
};
|
||||
viewManager.render(layout);
|
||||
});
|
||||
|
||||
test('Highlight a key with uppercase', function() {
|
||||
var keyView = {
|
||||
highlight: this.sinon.stub(),
|
||||
element: {}
|
||||
};
|
||||
|
||||
viewManager.registerView(dummyKey, keyView);
|
||||
|
||||
viewManager.setUpperCaseLock({
|
||||
isUpperCase: true,
|
||||
isUpperCaseLocked: false
|
||||
});
|
||||
|
||||
viewManager.highlightKey(dummyKey);
|
||||
|
||||
assert.isTrue(keyView.highlight.called);
|
||||
assert.isTrue(keyView.highlight.calledWith({upperCase: true}));
|
||||
});
|
||||
|
||||
test('Highlight a key with lowercase', function() {
|
||||
var keyView = {
|
||||
highlight: this.sinon.stub(),
|
||||
element: {}
|
||||
};
|
||||
|
||||
viewManager.registerView(dummyKey, keyView);
|
||||
|
||||
viewManager.setUpperCaseLock({
|
||||
isUpperCase: false,
|
||||
isUpperCaseLocked: false
|
||||
});
|
||||
|
||||
viewManager.highlightKey(dummyKey);
|
||||
|
||||
assert.isTrue(keyView.highlight.called);
|
||||
assert.isTrue(keyView.highlight.calledWith({upperCase: false}));
|
||||
});
|
||||
});
|
||||
});
|
||||
88
gaia/apps/keyboard/test/unit/keyboard/visual_highlight_manager_test.js
Executable file
88
gaia/apps/keyboard/test/unit/keyboard/visual_highlight_manager_test.js
Executable file
|
|
@ -0,0 +1,88 @@
|
|||
'use strict';
|
||||
|
||||
/* global VisualHighlightManager */
|
||||
|
||||
require('/js/keyboard/visual_highlight_manager.js');
|
||||
|
||||
suite('VisualHighlightManager', function() {
|
||||
var app;
|
||||
var manager;
|
||||
var target;
|
||||
var viewManager;
|
||||
|
||||
setup(function() {
|
||||
// Create fake viewManager
|
||||
viewManager = {
|
||||
highlightKey: this.sinon.stub(),
|
||||
unHighlightKey: this.sinon.stub()
|
||||
};
|
||||
|
||||
app = {
|
||||
viewManager: viewManager
|
||||
};
|
||||
|
||||
target = {};
|
||||
|
||||
manager = new VisualHighlightManager(app);
|
||||
manager.start();
|
||||
|
||||
this.sinon.stub(window, 'setTimeout');
|
||||
this.sinon.stub(window, 'clearTimeout');
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
app = null;
|
||||
});
|
||||
|
||||
test('show', function() {
|
||||
manager.show(target);
|
||||
|
||||
assert.isTrue(viewManager.highlightKey.calledWith(target));
|
||||
});
|
||||
|
||||
test('show after hide', function() {
|
||||
manager.hide(target);
|
||||
|
||||
var target2 = {};
|
||||
|
||||
manager.show(target2);
|
||||
|
||||
assert.isTrue(viewManager.unHighlightKey.calledWith(target),
|
||||
'The first target highlight should be hidden immediately.');
|
||||
|
||||
assert.isTrue(viewManager.highlightKey.calledWith(target2));
|
||||
});
|
||||
|
||||
test('hide', function() {
|
||||
manager.hide(target);
|
||||
|
||||
assert.isTrue(window.setTimeout.calledOnce);
|
||||
assert.equal(window.setTimeout.getCall(0).args[1],
|
||||
manager.HIGHTLIGHT_DELAY_MS);
|
||||
|
||||
window.setTimeout.getCall(0).args[0].call(window);
|
||||
|
||||
assert.isTrue(viewManager.unHighlightKey.calledWith(target));
|
||||
});
|
||||
|
||||
test('hide twice within HIGHTLIGHT_DELAY_MS', function() {
|
||||
window.setTimeout.returns(200);
|
||||
|
||||
manager.hide(target);
|
||||
|
||||
assert.isTrue(window.setTimeout.calledOnce);
|
||||
assert.equal(window.setTimeout.getCall(0).args[1],
|
||||
manager.HIGHTLIGHT_DELAY_MS);
|
||||
|
||||
window.setTimeout.returns(201);
|
||||
|
||||
manager.hide(target);
|
||||
|
||||
assert.isTrue(window.clearTimeout.calledWith(200));
|
||||
assert.isTrue(window.setTimeout.calledTwice);
|
||||
|
||||
window.setTimeout.getCall(1).args[0].call(window);
|
||||
|
||||
assert.isTrue(viewManager.unHighlightKey.calledWith(target));
|
||||
});
|
||||
});
|
||||
113
gaia/apps/keyboard/test/unit/mock_audio_context.js
Executable file
113
gaia/apps/keyboard/test/unit/mock_audio_context.js
Executable file
|
|
@ -0,0 +1,113 @@
|
|||
'use strict';
|
||||
|
||||
/* global MockEventTarget */
|
||||
|
||||
/**
|
||||
* This file provide incomplete interfaces of Web Audio API for users to
|
||||
* create stub on top of.
|
||||
* New interfaces can simply be added by manually copying and editing WebIDL
|
||||
* from Gecko source tree.
|
||||
*/
|
||||
|
||||
var MockAudioNode = function(ctx) {
|
||||
this.context = ctx;
|
||||
};
|
||||
MockAudioNode.prototype = Object.create(MockEventTarget.prototype);
|
||||
MockAudioNode.prototype.context = null;
|
||||
MockAudioNode.prototype.numberOfInputs = 0;
|
||||
MockAudioNode.prototype.numberOfOutputs = 0;
|
||||
MockAudioNode.prototype.channelCount = 2;
|
||||
MockAudioNode.prototype.channelCountMode = 'explicit';
|
||||
MockAudioNode.prototype.channelInterpretation = 'speakers';
|
||||
MockAudioNode.prototype.connect =
|
||||
MockAudioNode.prototype.disconnect = function() {
|
||||
throw 'MockAudioNode: Unimplemented. Stub me?';
|
||||
};
|
||||
|
||||
var MockAudioDestinationNode = function(ctx) {
|
||||
MockAudioNode.call(this, ctx);
|
||||
|
||||
this.numberOfInputs = 1;
|
||||
this.numberOfOutputs = 0;
|
||||
};
|
||||
MockAudioDestinationNode.prototype = Object.create(MockAudioNode.prototype);
|
||||
MockAudioDestinationNode.prototype.maxChannelCount = 2;
|
||||
|
||||
var MockAudioBufferSourceNode = function(ctx) {
|
||||
MockAudioNode.call(this, ctx);
|
||||
|
||||
this.numberOfInputs = 0;
|
||||
this.numberOfOutputs = 1;
|
||||
|
||||
// Unimplemented
|
||||
// this.playbackRate = new MockAudioParam();
|
||||
};
|
||||
MockAudioBufferSourceNode.prototype = Object.create(MockAudioNode.prototype);
|
||||
MockAudioBufferSourceNode.prototype.playbackRate = null;
|
||||
MockAudioBufferSourceNode.prototype.buffer = null;
|
||||
MockAudioBufferSourceNode.prototype.loop = false;
|
||||
MockAudioBufferSourceNode.prototype.loopEnd = 0;
|
||||
MockAudioBufferSourceNode.prototype.loopStart = 0;
|
||||
MockAudioBufferSourceNode.prototype.onended = null;
|
||||
MockAudioBufferSourceNode.prototype.start =
|
||||
MockAudioBufferSourceNode.prototype.stop = function() {
|
||||
throw 'MockAudioBufferSourceNode: Unimplemented. Stub me?';
|
||||
};
|
||||
|
||||
var MockAudioListener = function() {
|
||||
};
|
||||
MockAudioListener.prototype.dopplerFactor = 1;
|
||||
MockAudioListener.prototype.speedOfSound = 343.3;
|
||||
MockAudioListener.prototype.setPosition =
|
||||
MockAudioListener.prototype.setOrientation =
|
||||
MockAudioListener.prototype.setVelocity = function() {
|
||||
throw 'MockAudioListener: Unimplemented. Stub me?';
|
||||
};
|
||||
|
||||
var MockAudioContext = function(type) {
|
||||
this.destination = new MockAudioDestinationNode(this);
|
||||
this.listener = new MockAudioListener();
|
||||
if (type) {
|
||||
this.mozAudioChannelType = type;
|
||||
}
|
||||
};
|
||||
|
||||
MockAudioContext.prototype = Object.create(MockEventTarget.prototype);
|
||||
MockAudioContext.prototype.destination = null;
|
||||
MockAudioContext.prototype.sampleRate = 48000;
|
||||
MockAudioContext.prototype.currentTime = 0;
|
||||
MockAudioContext.prototype.listener = null;
|
||||
MockAudioContext.prototype.mozAudioChannelType = 'normal';
|
||||
|
||||
MockAudioContext.prototype.onmozinterruptbegin = null;
|
||||
MockAudioContext.prototype.onmozinterruptend = null;
|
||||
|
||||
MockAudioContext.prototype.decodeAudioData =
|
||||
MockAudioContext.prototype.createBufferSource =
|
||||
MockAudioContext.prototype.createMediaStreamDestination =
|
||||
MockAudioContext.prototype.createScriptProcessor =
|
||||
MockAudioContext.prototype.createAnalyser =
|
||||
MockAudioContext.prototype.createMediaElementSource =
|
||||
MockAudioContext.prototype.createMediaStreamSource =
|
||||
MockAudioContext.prototype.createGain =
|
||||
MockAudioContext.prototype.createDelay =
|
||||
MockAudioContext.prototype.createBiquadFilter =
|
||||
MockAudioContext.prototype.createWaveShaper =
|
||||
MockAudioContext.prototype.createPanner =
|
||||
MockAudioContext.prototype.createConvolver =
|
||||
MockAudioContext.prototype.createChannelSplitter =
|
||||
MockAudioContext.prototype.createChannelMerger =
|
||||
MockAudioContext.prototype.createDynamicsCompressor =
|
||||
MockAudioContext.prototype.createOscillator =
|
||||
MockAudioContext.prototype.createPeriodicWave = function() {
|
||||
throw 'MockAudioContext: Unimplemented. Stub me?';
|
||||
};
|
||||
|
||||
var MockOfflineAudioContext = function(numOfChannels, length, sampleRate) {
|
||||
MockAudioContext.call(this);
|
||||
};
|
||||
MockOfflineAudioContext.prototype = Object.create(MockAudioContext.prototype);
|
||||
MockOfflineAudioContext.prototype.oncomplete = null;
|
||||
MockOfflineAudioContext.prototype.startRendering = function() {
|
||||
throw 'MockOfflineAudioContext: Unimplemented. Stub me?';
|
||||
};
|
||||
246
gaia/apps/keyboard/test/unit/mock_indexeddb.js
Executable file
246
gaia/apps/keyboard/test/unit/mock_indexeddb.js
Executable file
|
|
@ -0,0 +1,246 @@
|
|||
'use strict';
|
||||
|
||||
/* global MockEventTarget */
|
||||
|
||||
/**
|
||||
* This file implement a set of nearly empty functions for you to
|
||||
* spy/stub on top of it. The native constructors are in fact exposed
|
||||
* (try console.log(IDBFactory) for example), but stub these native
|
||||
* objects or object prototypes might be to troublesome for writing
|
||||
* tests, hence the mock.
|
||||
*
|
||||
* This file requires MockEventTarget.
|
||||
*
|
||||
* These constructors contains methods from the formal spec:
|
||||
* http://www.w3.org/TR/IndexedDB/
|
||||
*
|
||||
* You may also find Gecko implementation here:
|
||||
* http://dxr.mozilla.org/mozilla-central/source/dom/indexedDB/
|
||||
* and it's WebIDL files in:
|
||||
* http://dxr.mozilla.org/mozilla-central/source/dom/webidl/
|
||||
*
|
||||
* Since window.indexedDB is not overwritable in Gecko,
|
||||
* we would have to overwrite the methods instead.
|
||||
*
|
||||
* To use the mock, do this in your setup():
|
||||
*
|
||||
* var stubIndexedDB = this.sinon.stub(new MockIDBFactory());
|
||||
* MockIDBFactory.attachToWindow(stubIndexedDB);
|
||||
*
|
||||
* In the test(), do this after the code to test to, for example, respond to
|
||||
* database open request:
|
||||
*
|
||||
* var req = stubIndexedDB.open.firstCall.returnValue;
|
||||
* var stubIDB = req.result = this.sinon.stub(new MockIDBDatabase(dbOptions));
|
||||
* req.dispatchEvent({
|
||||
* type: 'success',
|
||||
* target: req
|
||||
* });
|
||||
*
|
||||
*/
|
||||
//(function(exports) {
|
||||
|
||||
var MockIDBRequest = function(mOptions) {
|
||||
this.transaction = mOptions.transaction;
|
||||
this.source = mOptions.store;
|
||||
this.readyState = 'pending';
|
||||
};
|
||||
MockIDBRequest.prototype = Object.create(MockEventTarget.prototype);
|
||||
MockIDBRequest.prototype.result = null;
|
||||
MockIDBRequest.prototype.error = null;
|
||||
MockIDBRequest.prototype.source = null;
|
||||
MockIDBRequest.prototype.transaction = null;
|
||||
MockIDBRequest.prototype.readyState = null;
|
||||
MockIDBRequest.prototype.onsuccess = null;
|
||||
MockIDBRequest.prototype.onerror = null;
|
||||
|
||||
var MockIDBOpenDBRequest = function() {
|
||||
};
|
||||
MockIDBOpenDBRequest.prototype = Object.create(MockIDBRequest.prototype);
|
||||
MockIDBOpenDBRequest.prototype.onblocked = null;
|
||||
MockIDBOpenDBRequest.prototype.onupgradeneeded = null;
|
||||
|
||||
var MockIDBObjectStore = function(mOptions) {
|
||||
this.name = mOptions.name;
|
||||
this.keyPath = mOptions.keyPath;
|
||||
this.indexNames = mOptions.indexNames;
|
||||
this.transaction = mOptions.transaction;
|
||||
this.autoIncrement = mOptions.autoIncrement || false;
|
||||
};
|
||||
MockIDBObjectStore.prototype.name = null;
|
||||
MockIDBObjectStore.prototype.keyPath = null;
|
||||
MockIDBObjectStore.prototype.indexNames = null;
|
||||
MockIDBObjectStore.prototype.transaction = null;
|
||||
MockIDBObjectStore.prototype.autoIncrement = false;
|
||||
MockIDBObjectStore.prototype.put = function() {
|
||||
return new MockIDBRequest({
|
||||
transaction: this.transaction,
|
||||
store: this
|
||||
});
|
||||
};
|
||||
MockIDBObjectStore.prototype.add = function() {
|
||||
return new MockIDBRequest({
|
||||
transaction: this.transaction,
|
||||
store: this
|
||||
});
|
||||
};
|
||||
MockIDBObjectStore.prototype.delete = function() {
|
||||
return new MockIDBRequest({
|
||||
transaction: this.transaction,
|
||||
store: this
|
||||
});
|
||||
};
|
||||
MockIDBObjectStore.prototype.get = function() {
|
||||
return new MockIDBRequest({
|
||||
transaction: this.transaction,
|
||||
store: this
|
||||
});
|
||||
};
|
||||
MockIDBObjectStore.prototype.clear = function() {
|
||||
return new MockIDBRequest({
|
||||
transaction: this.transaction,
|
||||
store: this
|
||||
});
|
||||
};
|
||||
MockIDBObjectStore.prototype.openCursor = function() {
|
||||
return new MockIDBRequest({
|
||||
transaction: this.transaction,
|
||||
store: this
|
||||
});
|
||||
};
|
||||
MockIDBObjectStore.prototype.createIndex = function() {
|
||||
throw new Error('Not implemented');
|
||||
};
|
||||
MockIDBObjectStore.prototype.index = function() {
|
||||
throw new Error('Not implemented');
|
||||
};
|
||||
MockIDBObjectStore.prototype.deleteIndex = function() {
|
||||
// void
|
||||
};
|
||||
MockIDBObjectStore.prototype.count = function() {
|
||||
return new MockIDBRequest({
|
||||
transaction: this.transaction,
|
||||
store: this
|
||||
});
|
||||
};
|
||||
MockIDBObjectStore.prototype.mozGetAll = function() {
|
||||
return new MockIDBRequest({
|
||||
transaction: this.transaction,
|
||||
store: this
|
||||
});
|
||||
};
|
||||
|
||||
var MockIDBTransaction = function(mOptions) {
|
||||
this.db = mOptions.db;
|
||||
this.mode = mOptions.mode || 'readonly';
|
||||
this.objectStoreNames = mOptions.objectStoreNames;
|
||||
};
|
||||
MockIDBTransaction.prototype = new MockEventTarget();
|
||||
MockIDBTransaction.prototype.mode = null;
|
||||
MockIDBTransaction.prototype.db = null;
|
||||
MockIDBTransaction.prototype.error = null;
|
||||
MockIDBTransaction.prototype.onabort = null;
|
||||
MockIDBTransaction.prototype.oncomplete = null;
|
||||
MockIDBTransaction.prototype.onerror = null;
|
||||
MockIDBTransaction.prototype.objectStoreNames = null;
|
||||
MockIDBTransaction.prototype.objectStore = function(name) {
|
||||
var mOptions = {
|
||||
name: name,
|
||||
transaction: this
|
||||
};
|
||||
|
||||
if (name in this.db._objectStoreOptions) {
|
||||
mOptions.keyPath = this.db._objectStoreOptions[name].keyPath;
|
||||
mOptions.autoIncrement = this.db._objectStoreOptions[name].autoIncrement;
|
||||
mOptions.indexNames = this.db._objectStoreOptions[name].indexNames;
|
||||
}
|
||||
|
||||
return new MockIDBObjectStore(mOptions);
|
||||
};
|
||||
MockIDBTransaction.prototype.abort = function() {
|
||||
// void
|
||||
};
|
||||
|
||||
var MockIDBDatabase = function(mOptions) {
|
||||
this.name = mOptions.name;
|
||||
this.version = mOptions.version || 1;
|
||||
|
||||
this._objectStoreOptions = mOptions._objectStoreOptions || {};
|
||||
this.objectStoreNames = mOptions.objectStoreNames;
|
||||
};
|
||||
MockIDBDatabase.prototype = new MockEventTarget();
|
||||
MockIDBDatabase.prototype.name = null;
|
||||
MockIDBDatabase.prototype.version = 0;
|
||||
MockIDBDatabase.prototype.objectStoreNames = null;
|
||||
MockIDBDatabase.prototype.onabort = null;
|
||||
MockIDBDatabase.prototype.onerror = null;
|
||||
MockIDBDatabase.prototype.onversionchange = null;
|
||||
MockIDBDatabase.prototype.createObjectStore = function(name, params) {
|
||||
return new MockIDBObjectStore({
|
||||
name: name,
|
||||
keyPath: params ? (params.keyPath || null) : null,
|
||||
autoIncrement: params ? (params.autoIncrement || false) : false
|
||||
});
|
||||
};
|
||||
MockIDBDatabase.prototype.deleteObjectStore = function(name) {
|
||||
// void
|
||||
};
|
||||
MockIDBDatabase.prototype.transaction = function(storeNames, mode) {
|
||||
return new MockIDBTransaction({
|
||||
db: this,
|
||||
objectStoreNames: storeNames,
|
||||
mode: mode
|
||||
});
|
||||
};
|
||||
MockIDBDatabase.prototype.close = function() {
|
||||
// void
|
||||
};
|
||||
MockIDBDatabase.prototype.mozCreateFileHandle = function() {
|
||||
throw new Error('Not implemented');
|
||||
};
|
||||
|
||||
var MockIDBFactory = function() {
|
||||
};
|
||||
|
||||
MockIDBFactory.attachToWindow = function(instance) {
|
||||
if (!instance) {
|
||||
instance = new this();
|
||||
}
|
||||
|
||||
window.indexedDB._open = window.indexedDB.open;
|
||||
window.indexedDB.open = instance.open.bind(instance);
|
||||
window.indexedDB._cmp = window.indexedDB.cmp;
|
||||
window.indexedDB.cmp = instance.cmp.bind(instance);
|
||||
window.indexedDB._deleteDatabase = window.indexedDB.deleteDatabase;
|
||||
window.indexedDB.deleteDatabase = instance.deleteDatabase.bind(instance);
|
||||
};
|
||||
|
||||
MockIDBFactory.restore = function() {
|
||||
window.indexedDB.open = window.indexedDB._open;
|
||||
window.indexedDB.cmp = window.indexedDB._cmp;
|
||||
window.indexedDB.deleteDatabase = window.indexedDB._deleteDatabase;
|
||||
};
|
||||
|
||||
MockIDBFactory.prototype.cmp = function(first, second) {
|
||||
// Should return a short number according to
|
||||
// http://www.w3.org/TR/IndexedDB/
|
||||
// #widl-IDBFactory-cmp-short-any-first-any-second
|
||||
};
|
||||
MockIDBFactory.prototype.open = function(name, version) {
|
||||
// This DOMRequest should return a MockIDBDatabase instance.
|
||||
return new MockIDBOpenDBRequest();
|
||||
};
|
||||
MockIDBFactory.prototype.deleteDatabase = function(name) {
|
||||
return new MockIDBOpenDBRequest();
|
||||
};
|
||||
|
||||
/*
|
||||
exports.MockIDBFactory = MockIDBFactory;
|
||||
exports.MockIDBOpenDBRequest = MockIDBOpenDBRequest;
|
||||
exports.MockIDBDatabase = MockIDBDatabase;
|
||||
exports.MockIDBObjectStore = MockIDBObjectStore;
|
||||
exports.MockIDBTransaction = MockIDBTransaction;
|
||||
exports.MockIDBRequest = MockIDBRequest;
|
||||
|
||||
})(window);
|
||||
*/
|
||||
150
gaia/apps/keyboard/test/unit/mock_promise_test.js
Executable file
150
gaia/apps/keyboard/test/unit/mock_promise_test.js
Executable file
|
|
@ -0,0 +1,150 @@
|
|||
'use strict';
|
||||
|
||||
/* global MockPromise */
|
||||
|
||||
require('/shared/test/unit/mocks/mock_promise.js');
|
||||
|
||||
suite('MockPromse', function() {
|
||||
var state;
|
||||
var resolveFn;
|
||||
var rejectFn;
|
||||
var resolveTo;
|
||||
var rejectTo;
|
||||
|
||||
var p, p0, p1, p2, p3;
|
||||
|
||||
setup(function() {
|
||||
this.sinon.spy(window, 'MockPromise');
|
||||
|
||||
p = new MockPromise(function callback(resolve, reject) {
|
||||
state = 'callback';
|
||||
resolveFn = resolve;
|
||||
rejectFn = reject;
|
||||
}).then(function(val) {
|
||||
state = 'resolve1';
|
||||
resolveTo = val;
|
||||
}).then(function(val) {
|
||||
state = 'resolve2';
|
||||
resolveTo = val;
|
||||
}).then(function(val) {
|
||||
state = 'resolve3';
|
||||
resolveTo = val;
|
||||
}).catch(function(err) {
|
||||
state = 'reject4';
|
||||
rejectTo = err;
|
||||
});
|
||||
});
|
||||
|
||||
suite('callback', function() {
|
||||
setup(function() {
|
||||
p0 = window.MockPromise.firstCall.returnValue;
|
||||
p0.mExecuteCallback(function resolve(val) {
|
||||
state = 'resolve0';
|
||||
resolveTo = val;
|
||||
}, function reject(err) {
|
||||
state = 'reject0';
|
||||
rejectTo = err;
|
||||
});
|
||||
|
||||
assert.equal(state, 'callback');
|
||||
});
|
||||
|
||||
test('reject', function() {
|
||||
var error = {};
|
||||
rejectFn(error);
|
||||
|
||||
assert.equal(state, 'reject0');
|
||||
assert.equal(rejectTo, error);
|
||||
});
|
||||
|
||||
suite('resolve', function() {
|
||||
var obj;
|
||||
setup(function() {
|
||||
obj = {};
|
||||
resolveFn(obj);
|
||||
});
|
||||
|
||||
test('resolve', function() {
|
||||
assert.equal(state, 'resolve0');
|
||||
assert.equal(resolveTo, obj);
|
||||
});
|
||||
|
||||
test('reject1', function() {
|
||||
var err1 = {};
|
||||
p0.mRejectToError(err1);
|
||||
|
||||
assert.equal(state, 'reject4');
|
||||
assert.equal(rejectTo, err1);
|
||||
});
|
||||
|
||||
suite('resolve1', function() {
|
||||
var obj1;
|
||||
setup(function() {
|
||||
obj1 = {};
|
||||
p0.mFulfillToValue(obj1);
|
||||
p1 = p0.mGetNextPromise();
|
||||
});
|
||||
|
||||
test('resolve1', function() {
|
||||
assert.equal(state, 'resolve1');
|
||||
assert.equal(resolveTo, obj1);
|
||||
});
|
||||
|
||||
test('reject2', function() {
|
||||
var err2 = {};
|
||||
p1.mRejectToError(err2);
|
||||
|
||||
assert.equal(state, 'reject4');
|
||||
assert.equal(rejectTo, err2);
|
||||
});
|
||||
|
||||
suite('resolve2', function() {
|
||||
var obj2;
|
||||
setup(function() {
|
||||
obj2 = {};
|
||||
p1.mFulfillToValue(obj2);
|
||||
p2 = p1.mGetNextPromise();
|
||||
});
|
||||
|
||||
test('resolve2', function() {
|
||||
assert.equal(state, 'resolve2');
|
||||
assert.equal(resolveTo, obj2);
|
||||
});
|
||||
|
||||
test('reject3', function() {
|
||||
var err3 = {};
|
||||
p2.mRejectToError(err3);
|
||||
|
||||
assert.equal(state, 'reject4');
|
||||
assert.equal(rejectTo, err3);
|
||||
});
|
||||
|
||||
suite('resolve3', function() {
|
||||
var obj3;
|
||||
setup(function() {
|
||||
obj3 = {};
|
||||
p2.mFulfillToValue(obj3);
|
||||
p3 = p2.mGetNextPromise();
|
||||
});
|
||||
|
||||
test('resolve3', function() {
|
||||
assert.equal(state, 'resolve3');
|
||||
assert.equal(resolveTo, obj3);
|
||||
|
||||
var p4 = p3.catch.firstCall.returnValue;
|
||||
assert.equal(p4, p);
|
||||
});
|
||||
|
||||
test('reject4', function() {
|
||||
var err4 = {};
|
||||
p3.mRejectToError(err4);
|
||||
|
||||
assert.equal(state, 'reject4');
|
||||
assert.equal(rejectTo, err4);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
85
gaia/apps/keyboard/test/unit/settings/base_view_test.js
Executable file
85
gaia/apps/keyboard/test/unit/settings/base_view_test.js
Executable file
|
|
@ -0,0 +1,85 @@
|
|||
'use strict';
|
||||
|
||||
/* global BaseView */
|
||||
|
||||
require('/js/settings/base_view.js');
|
||||
|
||||
suite('BaseView', function() {
|
||||
var view;
|
||||
|
||||
setup(function() {
|
||||
var stubGetElementById = this.sinon.stub(document, 'getElementById');
|
||||
|
||||
view = new BaseView();
|
||||
|
||||
view.CONTAINER_ID = 'test-id';
|
||||
|
||||
view.start();
|
||||
|
||||
assert.isTrue(stubGetElementById.called);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
view.stop();
|
||||
});
|
||||
|
||||
suite('Transition event hooks', function() {
|
||||
var stubPromiseAll;
|
||||
|
||||
setup(function() {
|
||||
view.childViews = {
|
||||
view1: {
|
||||
beforeShow: this.sinon.stub().returns('beforeShow1'),
|
||||
show: this.sinon.stub().returns('show1'),
|
||||
beforeHide: this.sinon.stub().returns('beforeHide1'),
|
||||
hide: this.sinon.stub().returns('hide1')
|
||||
},
|
||||
view2: {
|
||||
beforeShow: this.sinon.stub().returns('beforeShow2'),
|
||||
show: this.sinon.stub().returns('show2'),
|
||||
beforeHide: this.sinon.stub().returns('beforeHide2'),
|
||||
hide: this.sinon.stub().returns('hide2')
|
||||
},
|
||||
view3: {
|
||||
beforeShow: this.sinon.stub().returns('beforeShow3'),
|
||||
show: this.sinon.stub().returns('show3'),
|
||||
beforeHide: this.sinon.stub().returns('beforeHide3'),
|
||||
hide: this.sinon.stub().returns('hide3')
|
||||
}
|
||||
};
|
||||
|
||||
stubPromiseAll = this.sinon.stub(window.Promise, 'all');
|
||||
});
|
||||
|
||||
test('beforeShow', function(){
|
||||
var options = {};
|
||||
|
||||
view.beforeShow(options);
|
||||
|
||||
assert.isTrue(stubPromiseAll.calledWith(
|
||||
['beforeShow1', 'beforeShow2', 'beforeShow3']));
|
||||
assert.isTrue(view.childViews.view1.beforeShow.calledWith(options));
|
||||
assert.isTrue(view.childViews.view2.beforeShow.calledWith(options));
|
||||
assert.isTrue(view.childViews.view3.beforeShow.calledWith(options));
|
||||
});
|
||||
|
||||
test('show', function(){
|
||||
view.show();
|
||||
|
||||
assert.isTrue(stubPromiseAll.calledWith(['show1', 'show2', 'show3']));
|
||||
});
|
||||
|
||||
test('beforeHide', function(){
|
||||
view.beforeHide();
|
||||
|
||||
assert.isTrue(stubPromiseAll.calledWith(
|
||||
['beforeHide1', 'beforeHide2', 'beforeHide3']));
|
||||
});
|
||||
|
||||
test('hide', function(){
|
||||
view.hide();
|
||||
|
||||
assert.isTrue(stubPromiseAll.calledWith(['hide1', 'hide2', 'hide3']));
|
||||
});
|
||||
});
|
||||
});
|
||||
58
gaia/apps/keyboard/test/unit/settings/close_locks_test.js
Executable file
58
gaia/apps/keyboard/test/unit/settings/close_locks_test.js
Executable file
|
|
@ -0,0 +1,58 @@
|
|||
'use strict';
|
||||
|
||||
/* global CloseLockManager */
|
||||
|
||||
require('/js/settings/close_locks.js');
|
||||
|
||||
suite('CloseLockManager', function() {
|
||||
var manager;
|
||||
|
||||
setup(function() {
|
||||
manager = new CloseLockManager();
|
||||
manager.onclose = this.sinon.stub();
|
||||
manager.start();
|
||||
});
|
||||
|
||||
test('requestClose', function() {
|
||||
manager.requestLock('requestClose');
|
||||
|
||||
assert.isTrue(manager.onclose.called);
|
||||
});
|
||||
|
||||
suite('stayAwake', function() {
|
||||
var awakeLock;
|
||||
|
||||
setup(function() {
|
||||
awakeLock = manager.requestLock('stayAwake');
|
||||
});
|
||||
|
||||
suite('requestClose', function() {
|
||||
var closeLock;
|
||||
setup(function() {
|
||||
closeLock = manager.requestLock('requestClose');
|
||||
assert.isFalse(manager.onclose.calledOnce);
|
||||
});
|
||||
|
||||
test('unlock awakeLock', function() {
|
||||
awakeLock.unlock();
|
||||
assert.isTrue(manager.onclose.calledOnce);
|
||||
});
|
||||
|
||||
test('unlock closeLock', function() {
|
||||
closeLock.unlock();
|
||||
assert.isFalse(manager.onclose.calledOnce);
|
||||
|
||||
awakeLock.unlock();
|
||||
assert.isFalse(manager.onclose.calledOnce);
|
||||
});
|
||||
});
|
||||
|
||||
test('requestClose after unlock', function() {
|
||||
awakeLock.unlock();
|
||||
assert.isFalse(manager.onclose.calledOnce);
|
||||
|
||||
manager.requestLock('requestClose');
|
||||
assert.isTrue(manager.onclose.calledOnce);
|
||||
});
|
||||
});
|
||||
});
|
||||
138
gaia/apps/keyboard/test/unit/settings/general_panel_test.js
Executable file
138
gaia/apps/keyboard/test/unit/settings/general_panel_test.js
Executable file
|
|
@ -0,0 +1,138 @@
|
|||
'use strict';
|
||||
|
||||
/* global BaseView, GeneralSettingsGroupView, HandwritingSettingsGroupView,
|
||||
LayoutItemListView, GeneralPanel, PanelController, MockEventTarget */
|
||||
|
||||
require('/shared/test/unit/mocks/mock_event_target.js');
|
||||
|
||||
require('/js/settings/base_view.js');
|
||||
require('/js/settings/general_settings_view.js');
|
||||
require('/js/settings/handwriting_settings_view.js');
|
||||
require('/js/settings/layout_item_list_view.js');
|
||||
require('/js/settings/general_panel.js');
|
||||
require('/js/settings/panel_controller.js');
|
||||
|
||||
suite('General Panel', function() {
|
||||
var panel;
|
||||
var app;
|
||||
var stubContainer;
|
||||
var stubHeader;
|
||||
var stubMenuUDItem;
|
||||
var stubGetElemById;
|
||||
var stubLayoutItemListView;
|
||||
var stubGeneralSettingsGroupView;
|
||||
var stubHandwritingSettingsGroupView;
|
||||
|
||||
setup(function() {
|
||||
stubLayoutItemListView =
|
||||
this.sinon.stub(Object.create(LayoutItemListView.prototype));
|
||||
this.sinon.stub(window, 'LayoutItemListView')
|
||||
.returns(stubLayoutItemListView);
|
||||
|
||||
stubGeneralSettingsGroupView =
|
||||
this.sinon.stub(Object.create(GeneralSettingsGroupView.prototype));
|
||||
this.sinon.stub(window, 'GeneralSettingsGroupView')
|
||||
.returns(stubGeneralSettingsGroupView);
|
||||
|
||||
stubHandwritingSettingsGroupView =
|
||||
this.sinon.stub(Object.create(HandwritingSettingsGroupView.prototype));
|
||||
this.sinon.stub(window, 'HandwritingSettingsGroupView')
|
||||
.returns(stubHandwritingSettingsGroupView);
|
||||
|
||||
app = {
|
||||
panelController: this.sinon.stub(new PanelController()),
|
||||
requestClose: this.sinon.stub()
|
||||
};
|
||||
|
||||
panel = new GeneralPanel(app);
|
||||
|
||||
stubHeader = this.sinon.stub(new MockEventTarget());
|
||||
|
||||
stubContainer = {
|
||||
querySelector:
|
||||
this.sinon.stub().returns(stubHeader)
|
||||
};
|
||||
|
||||
stubMenuUDItem = this.sinon.stub(new MockEventTarget());
|
||||
stubGetElemById = this.sinon.stub(document, 'getElementById');
|
||||
|
||||
stubGetElemById
|
||||
.withArgs('menu-userdict').returns(stubMenuUDItem)
|
||||
.withArgs('general').returns(stubContainer);
|
||||
|
||||
panel.start();
|
||||
|
||||
assert.equal(panel.container, stubContainer);
|
||||
assert.equal(panel._menuUDItem, stubMenuUDItem);
|
||||
|
||||
assert.isTrue(window.LayoutItemListView.calledWith(app));
|
||||
assert.isTrue(window.GeneralSettingsGroupView.calledWith(app));
|
||||
assert.isTrue(window.HandwritingSettingsGroupView.calledWith(app));
|
||||
|
||||
assert.isTrue(stubLayoutItemListView.start.called);
|
||||
assert.isTrue(stubGeneralSettingsGroupView.start.called);
|
||||
assert.isTrue(stubHandwritingSettingsGroupView.start.called);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
panel.stop();
|
||||
});
|
||||
|
||||
test('inheritance from BaseView', function() {
|
||||
assert.instanceOf(panel, BaseView);
|
||||
});
|
||||
|
||||
suite('Transition hooks', function() {
|
||||
test('show', function() {
|
||||
var stubBaseShow =
|
||||
this.sinon.stub(BaseView.prototype, 'show').returns('ShowReturn');
|
||||
|
||||
var ret = panel.show();
|
||||
|
||||
assert.equal(ret, 'ShowReturn');
|
||||
|
||||
assert.isTrue(stubHeader.addEventListener.calledWith('action', panel));
|
||||
assert.isTrue(stubMenuUDItem.addEventListener.calledWith('click', panel));
|
||||
|
||||
assert.isTrue(stubBaseShow.calledOn(panel));
|
||||
});
|
||||
|
||||
test('beforeHide', function() {
|
||||
var stubBaseBeforeHide =
|
||||
this.sinon.stub(BaseView.prototype, 'beforeHide')
|
||||
.returns('BeforeHideReturn');
|
||||
|
||||
var ret = panel.beforeHide();
|
||||
|
||||
assert.equal(ret, 'BeforeHideReturn');
|
||||
|
||||
assert.isTrue(stubHeader.removeEventListener.calledWith('action', panel));
|
||||
assert.isTrue(
|
||||
stubMenuUDItem.removeEventListener.calledWith('click', panel));
|
||||
|
||||
assert.isTrue(stubBaseBeforeHide.calledOn(panel));
|
||||
});
|
||||
});
|
||||
|
||||
suite('Event handling', function() {
|
||||
test('action -> app.requstClose', function() {
|
||||
panel.handleEvent({type: 'action'});
|
||||
|
||||
assert.isTrue(app.requestClose.called);
|
||||
});
|
||||
|
||||
test('click -> navigate to user dictionary list panel', function() {
|
||||
var spyPreventDefault = this.sinon.spy();
|
||||
|
||||
panel.handleEvent({
|
||||
type: 'click',
|
||||
preventDefault: spyPreventDefault
|
||||
});
|
||||
|
||||
assert.isTrue(app.panelController.navigateToPanel.calledWith(
|
||||
app.panelController.userDictionaryListPanel));
|
||||
|
||||
assert.isTrue(spyPreventDefault.called);
|
||||
});
|
||||
});
|
||||
});
|
||||
61
gaia/apps/keyboard/test/unit/settings/general_settings_view_test.js
Executable file
61
gaia/apps/keyboard/test/unit/settings/general_settings_view_test.js
Executable file
|
|
@ -0,0 +1,61 @@
|
|||
'use strict';
|
||||
|
||||
/* global SettingsView, GeneralSettingsGroupView, BaseView */
|
||||
|
||||
require('/js/settings/close_locks.js');
|
||||
require('/js/settings/base_view.js');
|
||||
require('/js/settings/settings_view.js');
|
||||
|
||||
require('/js/settings/general_settings_view.js');
|
||||
|
||||
suite('GeneralSettingsGroupView', function() {
|
||||
var groupView;
|
||||
var stubSettingsView;
|
||||
var container;
|
||||
var app;
|
||||
|
||||
setup(function() {
|
||||
container = { stub: 'container' };
|
||||
app = { stub: 'app' };
|
||||
window.SoundFeedbackSettings = { stub: 'SoundFeedbackSettings' };
|
||||
window.VibrationFeedbackSettings = { stub: 'VibrationFeedbackSettings' };
|
||||
window.IMEngineSettings = { stub: 'IMEngineSettings' };
|
||||
|
||||
this.sinon.stub(document, 'getElementById').returns(container);
|
||||
stubSettingsView = this.sinon.stub(Object.create(SettingsView.prototype));
|
||||
this.sinon.stub(window, 'SettingsView').returns(stubSettingsView);
|
||||
|
||||
groupView = new GeneralSettingsGroupView(app);
|
||||
});
|
||||
|
||||
test('inheritance of BaseView', function() {
|
||||
assert.instanceOf(groupView, BaseView);
|
||||
});
|
||||
|
||||
test('start/stop', function() {
|
||||
groupView.start();
|
||||
|
||||
assert.isTrue(document.getElementById.calledWith(groupView.CONTAINER_ID));
|
||||
|
||||
assert.isTrue(window.SettingsView.calledWith(
|
||||
app, container, window.SoundFeedbackSettings));
|
||||
assert.isTrue(window.SettingsView.calledWith(
|
||||
app, container, window.VibrationFeedbackSettings));
|
||||
assert.isTrue(window.SettingsView.calledWith(
|
||||
app, container, window.IMEngineSettings));
|
||||
assert.isTrue(stubSettingsView.start.calledThrice);
|
||||
|
||||
assert.equal(groupView.childViews.soundFeedbackSettings, stubSettingsView);
|
||||
assert.equal(groupView.childViews.vibrationFeedbackSettings,
|
||||
stubSettingsView);
|
||||
assert.equal(groupView.childViews.imEngineSettings, stubSettingsView);
|
||||
|
||||
groupView.stop();
|
||||
|
||||
assert.notProperty(groupView.childViews, 'soundFeedbackSettings');
|
||||
assert.notProperty(groupView.childViews, 'vibrationFeedbackSettings');
|
||||
assert.notProperty(groupView.childViews, 'imEngineSettings');
|
||||
|
||||
assert.isTrue(stubSettingsView.stop.calledThrice);
|
||||
});
|
||||
});
|
||||
50
gaia/apps/keyboard/test/unit/settings/handwriting_settings_view_test.js
Executable file
50
gaia/apps/keyboard/test/unit/settings/handwriting_settings_view_test.js
Executable file
|
|
@ -0,0 +1,50 @@
|
|||
'use strict';
|
||||
|
||||
/* global SettingsView, HandwritingSettingsGroupView, BaseView */
|
||||
|
||||
require('/js/settings/close_locks.js');
|
||||
require('/js/settings/base_view.js');
|
||||
require('/js/settings/settings_view.js');
|
||||
|
||||
require('/js/settings/handwriting_settings_view.js');
|
||||
|
||||
suite('HandwritingSettingsGroupView', function() {
|
||||
var groupView;
|
||||
var stubSettingsView;
|
||||
var container;
|
||||
var app;
|
||||
|
||||
setup(function() {
|
||||
container = { stub: 'container' };
|
||||
app = { stub: 'app' };
|
||||
window.HandwritingPadSettings = { stub: 'HandwritingPadSettings' };
|
||||
|
||||
this.sinon.stub(document, 'getElementById').returns(container);
|
||||
stubSettingsView = this.sinon.stub(Object.create(SettingsView.prototype));
|
||||
this.sinon.stub(window, 'SettingsView').returns(stubSettingsView);
|
||||
|
||||
groupView = new HandwritingSettingsGroupView(app);
|
||||
});
|
||||
|
||||
test('inheritance of BaseView', function() {
|
||||
assert.instanceOf(groupView, BaseView);
|
||||
});
|
||||
|
||||
test('start/stop', function() {
|
||||
groupView.start();
|
||||
|
||||
assert.isTrue(document.getElementById.calledWith(groupView.CONTAINER_ID));
|
||||
|
||||
assert.isTrue(window.SettingsView.calledWith(
|
||||
app, container, window.HandwritingPadSettings));
|
||||
assert.isTrue(stubSettingsView.start.calledOnce);
|
||||
|
||||
assert.equal(groupView.childViews.handwritingSettings, stubSettingsView);
|
||||
|
||||
groupView.stop();
|
||||
|
||||
assert.notProperty(groupView.childViews, 'handwritingSettings');
|
||||
|
||||
assert.isTrue(stubSettingsView.stop.calledOnce);
|
||||
});
|
||||
});
|
||||
155
gaia/apps/keyboard/test/unit/settings/keyboard_settings_app_test.js
Executable file
155
gaia/apps/keyboard/test/unit/settings/keyboard_settings_app_test.js
Executable file
|
|
@ -0,0 +1,155 @@
|
|||
'use strict';
|
||||
|
||||
/* global KeyboardSettingsApp, PanelController, DialogController,
|
||||
PromiseStorage */
|
||||
|
||||
require('/js/settings/close_locks.js');
|
||||
require('/js/settings/base_view.js');
|
||||
require('/js/settings/general_panel.js');
|
||||
require('/js/settings/panel_controller.js');
|
||||
require('/js/shared/promise_storage.js');
|
||||
|
||||
require('/js/settings/keyboard_settings_app.js');
|
||||
|
||||
suite('KeyboardSettingsApp', function() {
|
||||
var app;
|
||||
|
||||
var realMozActivity;
|
||||
|
||||
var stubPanelController;
|
||||
var stubDialogController;
|
||||
var stubPerferencesStore;
|
||||
|
||||
var isHidden;
|
||||
|
||||
suiteSetup(function() {
|
||||
Object.defineProperty(document, 'hidden', {
|
||||
configurable: true,
|
||||
get: function() {
|
||||
return isHidden;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
delete document.hidden;
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
isHidden = false;
|
||||
|
||||
realMozActivity = window.MozActivity;
|
||||
window.MozActivity = this.sinon.stub();
|
||||
|
||||
this.sinon.stub(window, 'close');
|
||||
this.sinon.stub(document, 'addEventListener');
|
||||
this.sinon.stub(document, 'removeEventListener');
|
||||
|
||||
window.SettingsPromiseManager = this.sinon.stub();
|
||||
|
||||
stubPanelController =
|
||||
this.sinon.stub(Object.create(PanelController.prototype));
|
||||
this.sinon.stub(window, 'PanelController')
|
||||
.returns(stubPanelController);
|
||||
|
||||
stubDialogController =
|
||||
this.sinon.stub(Object.create(DialogController.prototype));
|
||||
this.sinon.stub(window, 'DialogController')
|
||||
.returns(stubDialogController);
|
||||
|
||||
stubPerferencesStore =
|
||||
this.sinon.stub(Object.create(PromiseStorage.prototype));
|
||||
this.sinon.stub(window, 'PromiseStorage')
|
||||
.returns(stubPerferencesStore);
|
||||
|
||||
app = new KeyboardSettingsApp();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
window.MozActivity = realMozActivity;
|
||||
});
|
||||
|
||||
suite('start', function() {
|
||||
var skipStopOnTeardown;
|
||||
|
||||
setup(function() {
|
||||
skipStopOnTeardown = false;
|
||||
|
||||
app.start();
|
||||
|
||||
assert.isTrue(window.SettingsPromiseManager.calledOnce);
|
||||
|
||||
assert.isTrue(stubPanelController.start.calledOnce);
|
||||
assert.isTrue(window.PanelController.calledWith(app));
|
||||
assert.isTrue(stubDialogController.start.calledOnce);
|
||||
|
||||
assert.isTrue(
|
||||
window.PromiseStorage.calledWith(app.PREFERENCES_STORE_NAME));
|
||||
assert.isTrue(stubPerferencesStore.start.calledOnce);
|
||||
|
||||
assert.isTrue(
|
||||
document.addEventListener.calledWith('visibilitychange', app));
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
if (!skipStopOnTeardown) {
|
||||
app.stop();
|
||||
}
|
||||
|
||||
assert.isTrue(stubPanelController.stop.calledOnce);
|
||||
assert.isTrue(stubDialogController.stop.calledOnce);
|
||||
assert.isTrue(stubPerferencesStore.stop.calledOnce);
|
||||
|
||||
assert.isTrue(
|
||||
document.removeEventListener.calledWith('visibilitychange', app));
|
||||
});
|
||||
|
||||
test('requestClose', function() {
|
||||
app.requestClose();
|
||||
|
||||
assert.isTrue(window.MozActivity.calledWith({
|
||||
name: 'moz_configure_window',
|
||||
data: { target: 'device' }
|
||||
}));
|
||||
});
|
||||
|
||||
test('visibilitychange to hidden', function() {
|
||||
isHidden = true;
|
||||
skipStopOnTeardown = true;
|
||||
|
||||
this.sinon.spy(app, 'stop');
|
||||
|
||||
app.handleEvent({
|
||||
type: 'visibilitychange'
|
||||
});
|
||||
|
||||
assert.isTrue(window.close.calledOnce);
|
||||
assert.isTrue(app.stop.calledOnce);
|
||||
});
|
||||
|
||||
test('visibilitychange to visible w/ stay awake lock', function() {
|
||||
var lock = app.closeLockManager.requestLock('stayAwake');
|
||||
|
||||
isHidden = true;
|
||||
|
||||
this.sinon.spy(app, 'stop');
|
||||
|
||||
app.handleEvent({
|
||||
type: 'visibilitychange'
|
||||
});
|
||||
assert.isFalse(window.close.calledOnce);
|
||||
assert.isFalse(app.stop.calledOnce);
|
||||
|
||||
isHidden = false;
|
||||
app.handleEvent({
|
||||
type: 'visibilitychange'
|
||||
});
|
||||
assert.isFalse(window.close.calledOnce);
|
||||
assert.isFalse(app.stop.calledOnce);
|
||||
|
||||
lock.unlock();
|
||||
assert.isFalse(window.close.calledOnce);
|
||||
assert.isFalse(app.stop.calledOnce);
|
||||
});
|
||||
});
|
||||
});
|
||||
128
gaia/apps/keyboard/test/unit/settings/layout_dictionary_downloader_test.js
Executable file
128
gaia/apps/keyboard/test/unit/settings/layout_dictionary_downloader_test.js
Executable file
|
|
@ -0,0 +1,128 @@
|
|||
'use strict';
|
||||
|
||||
/* global LayoutDictionaryDownloader */
|
||||
|
||||
require('/js/settings/layout_dictionary_downloader.js');
|
||||
|
||||
suite('LayoutDictionaryDownloader', function() {
|
||||
var fakeXhr;
|
||||
var requests;
|
||||
|
||||
var downloader;
|
||||
var p;
|
||||
|
||||
var fakeLatinData = (function() {
|
||||
var view = new Uint8Array(42);
|
||||
var str = 'FxOSDICT';
|
||||
var i = str.length;
|
||||
|
||||
while (i--) {
|
||||
view[i] = str.charCodeAt(i);
|
||||
}
|
||||
|
||||
return view.buffer;
|
||||
})();
|
||||
|
||||
setup(function() {
|
||||
requests = [];
|
||||
|
||||
fakeXhr = sinon.useFakeXMLHttpRequest();
|
||||
fakeXhr.onCreate = function(request) {
|
||||
requests.push(request);
|
||||
};
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
fakeXhr.restore();
|
||||
});
|
||||
|
||||
test('onprogress', function() {
|
||||
downloader = new LayoutDictionaryDownloader('latin', 'ab_cd.dict');
|
||||
downloader.onprogress = this.sinon.stub();
|
||||
|
||||
p = downloader.load();
|
||||
|
||||
assert.equal(requests.length, 1);
|
||||
|
||||
var request = requests[0];
|
||||
request.onprogress({
|
||||
type: 'progress',
|
||||
loaded: 10,
|
||||
total: 100
|
||||
});
|
||||
|
||||
assert.isTrue(downloader.onprogress.calledWith(10, 100));
|
||||
});
|
||||
|
||||
test('abort()', function(done) {
|
||||
downloader = new LayoutDictionaryDownloader('latin', 'ab_cd.dict');
|
||||
downloader.onprogress = this.sinon.stub();
|
||||
|
||||
p = downloader.load();
|
||||
|
||||
downloader.abort();
|
||||
|
||||
var request = requests[0];
|
||||
assert.isTrue(request.aborted);
|
||||
|
||||
p.then(function(data) {
|
||||
assert.isTrue(false, 'Should not resolve.');
|
||||
}, function() {
|
||||
assert.isFalse(downloader.onprogress.calledOnce);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
suite('latin imEngine data', function() {
|
||||
setup(function() {
|
||||
downloader = new LayoutDictionaryDownloader('latin', 'ab_cd.dict');
|
||||
downloader.onprogress = this.sinon.stub();
|
||||
|
||||
p = downloader.load();
|
||||
|
||||
assert.equal(requests.length, 1);
|
||||
|
||||
var request = requests[0];
|
||||
assert.equal(request.url,
|
||||
'https://fxos.cdn.mozilla.net/dictionaries/latin/1/ab_cd.dict');
|
||||
assert.equal(request.responseType, 'arraybuffer');
|
||||
});
|
||||
|
||||
test('load() with valid data.', function(done) {
|
||||
var request = requests[0];
|
||||
request.response = fakeLatinData;
|
||||
|
||||
request.respond(200, {}, '');
|
||||
|
||||
p.then(function(data) {
|
||||
assert.isTrue(downloader.onprogress.calledWith(42, 42));
|
||||
assert.equal(data, fakeLatinData);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('load() with invalid data.', function(done) {
|
||||
var request = requests[0];
|
||||
request.response = new ArrayBuffer(42);
|
||||
|
||||
request.respond(200, {}, '');
|
||||
|
||||
p.then(function(data) {
|
||||
assert.isTrue(false, 'Should not resolve.');
|
||||
}, function() {
|
||||
assert.isFalse(downloader.onprogress.calledOnce);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('load() with xhr failure.', function(done) {
|
||||
var request = requests[0];
|
||||
request.response = null;
|
||||
|
||||
request.respond(0, {}, '');
|
||||
|
||||
p.then(function(data) {
|
||||
assert.isTrue(false, 'Should not resolve.');
|
||||
}, function() {
|
||||
assert.isFalse(downloader.onprogress.calledOnce);
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
285
gaia/apps/keyboard/test/unit/settings/layout_dictionary_list_test.js
Executable file
285
gaia/apps/keyboard/test/unit/settings/layout_dictionary_list_test.js
Executable file
|
|
@ -0,0 +1,285 @@
|
|||
'use strict';
|
||||
|
||||
/* global LayoutDictionary, LayoutDictionaryList, PromiseStorage */
|
||||
|
||||
require('/js/settings/layout_dictionary_list.js');
|
||||
require('/js/settings/layout_dictionary.js');
|
||||
require('/js/shared/promise_storage.js');
|
||||
|
||||
suite('LayoutDictionaryList', function() {
|
||||
var layoutListStub;
|
||||
var layoutDicts;
|
||||
var promiseStorageStub;
|
||||
|
||||
var list;
|
||||
|
||||
setup(function() {
|
||||
layoutDicts = [];
|
||||
|
||||
var LayoutDictionaryPrototype = LayoutDictionary.prototype;
|
||||
this.sinon.stub(window, 'LayoutDictionary', function() {
|
||||
var layoutDict =
|
||||
this.sinon.stub(Object.create(LayoutDictionaryPrototype));
|
||||
layoutDicts.push(layoutDict);
|
||||
|
||||
return layoutDict;
|
||||
}.bind(this));
|
||||
|
||||
var PromiseStoragePrototype = PromiseStorage.prototype;
|
||||
promiseStorageStub =
|
||||
this.sinon.stub(Object.create(PromiseStoragePrototype));
|
||||
this.sinon.stub(window, 'PromiseStorage')
|
||||
.returns(promiseStorageStub);
|
||||
|
||||
layoutListStub = {};
|
||||
|
||||
list = new LayoutDictionaryList(layoutListStub);
|
||||
list.start();
|
||||
|
||||
assert.isTrue(window.PromiseStorage.calledWith(list.DATABASE_NAME));
|
||||
assert.isTrue(promiseStorageStub.start.calledOnce);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
list.stop();
|
||||
|
||||
layoutDicts.forEach(function(layoutDict) {
|
||||
assert.isTrue(layoutDict.stop.calledOnce);
|
||||
}, this);
|
||||
});
|
||||
|
||||
test('getDictionary', function() {
|
||||
var layouts = [
|
||||
{
|
||||
'id': 'fr',
|
||||
'name': 'Français',
|
||||
'imEngineId': 'latin',
|
||||
'installed': true,
|
||||
'preloaded': true,
|
||||
'dictFilename': 'fr.dict',
|
||||
'dictFilePath': 'dictionaries/fr.dict',
|
||||
'dictFileSize': 1874745,
|
||||
'types': ['email', 'password', 'text', 'url']
|
||||
}
|
||||
];
|
||||
list.createDictionariesFromLayouts(layouts);
|
||||
|
||||
var layoutDict = layoutDicts[0];
|
||||
|
||||
assert.equal(list.getDictionary('latin', 'dictionaries/fr.dict'),
|
||||
layoutDict);
|
||||
});
|
||||
|
||||
suite('createDictionariesFromLayouts', function() {
|
||||
test('ignore layout def w/o dictFilename', function() {
|
||||
var layouts = [
|
||||
{
|
||||
'id': 'zh-Hans-Pinyin',
|
||||
'name': '拼音',
|
||||
'imEngineId': 'jspinyin',
|
||||
'preloaded': true
|
||||
}
|
||||
];
|
||||
list.createDictionariesFromLayouts(layouts);
|
||||
|
||||
assert.isFalse(window.LayoutDictionary.calledOnce);
|
||||
});
|
||||
|
||||
test('Mark as preloaded', function() {
|
||||
var layouts = [
|
||||
{
|
||||
'id': 'fr',
|
||||
'name': 'Français',
|
||||
'imEngineId': 'latin',
|
||||
'installed': true,
|
||||
'preloaded': true,
|
||||
'dictFilename': 'fr.dict',
|
||||
'dictFilePath': 'dictionaries/fr.dict',
|
||||
'dictFileSize': 1874745,
|
||||
'types': ['email', 'password', 'text', 'url']
|
||||
}
|
||||
];
|
||||
list.createDictionariesFromLayouts(layouts);
|
||||
|
||||
assert.isTrue(window.LayoutDictionary.calledWith(list, {
|
||||
filename: 'fr.dict',
|
||||
imEngineId: 'latin',
|
||||
filePath: 'dictionaries/fr.dict',
|
||||
fileSize: 1874745,
|
||||
databaseId: 'latin/dictionaries/fr.dict',
|
||||
preloaded: true,
|
||||
installedLayoutIds: new Set() // Must compare seperately
|
||||
}));
|
||||
|
||||
var installedLayoutIds =
|
||||
window.LayoutDictionary.getCall(0).args[1].installedLayoutIds;
|
||||
assert.deepEqual(['fr'],
|
||||
Array.from(installedLayoutIds).sort());
|
||||
|
||||
var layoutDict = layoutDicts[0];
|
||||
assert.isTrue(layoutDict.start.calledOnce);
|
||||
});
|
||||
|
||||
test('Mark as installed', function() {
|
||||
var layouts = [
|
||||
{
|
||||
'id': 'fr',
|
||||
'name': 'Français',
|
||||
'imEngineId': 'latin',
|
||||
'installed': true,
|
||||
'preloaded': false,
|
||||
'dictFilename': 'fr.dict',
|
||||
'dictFilePath': 'dictionaries/fr.dict',
|
||||
'dictFileSize': 1874745,
|
||||
'types': ['email', 'password', 'text', 'url']
|
||||
}
|
||||
];
|
||||
list.createDictionariesFromLayouts(layouts);
|
||||
|
||||
assert.isTrue(window.LayoutDictionary.calledWith(list, {
|
||||
filename: 'fr.dict',
|
||||
imEngineId: 'latin',
|
||||
filePath: 'dictionaries/fr.dict',
|
||||
fileSize: 1874745,
|
||||
databaseId: 'latin/dictionaries/fr.dict',
|
||||
preloaded: false,
|
||||
installedLayoutIds: new Set() // Must compare seperately
|
||||
}));
|
||||
|
||||
var installedLayoutIds =
|
||||
window.LayoutDictionary.getCall(0).args[1].installedLayoutIds;
|
||||
assert.deepEqual(['fr'],
|
||||
Array.from(installedLayoutIds).sort());
|
||||
|
||||
var layoutDict = layoutDicts[0];
|
||||
assert.isTrue(layoutDict.start.calledOnce);
|
||||
});
|
||||
|
||||
test('Mark as not installed', function() {
|
||||
var layouts = [
|
||||
{
|
||||
'id': 'fr',
|
||||
'name': 'Français',
|
||||
'imEngineId': 'latin',
|
||||
'installed': false,
|
||||
'preloaded': false,
|
||||
'dictFilename': 'fr.dict',
|
||||
'dictFilePath': 'dictionaries/fr.dict',
|
||||
'dictFileSize': 1874745,
|
||||
'types': ['email', 'password', 'text', 'url']
|
||||
}
|
||||
];
|
||||
list.createDictionariesFromLayouts(layouts);
|
||||
|
||||
assert.isTrue(window.LayoutDictionary.calledWith(list, {
|
||||
filename: 'fr.dict',
|
||||
imEngineId: 'latin',
|
||||
filePath: 'dictionaries/fr.dict',
|
||||
fileSize: 1874745,
|
||||
databaseId: 'latin/dictionaries/fr.dict',
|
||||
preloaded: false,
|
||||
installedLayoutIds: new Set() // Must compare seperately
|
||||
}));
|
||||
|
||||
var installedLayoutIds =
|
||||
window.LayoutDictionary.getCall(0).args[1].installedLayoutIds;
|
||||
assert.deepEqual([],
|
||||
Array.from(installedLayoutIds).sort());
|
||||
|
||||
var layoutDict = layoutDicts[0];
|
||||
assert.isTrue(layoutDict.start.calledOnce);
|
||||
});
|
||||
|
||||
test('Merge two layouts with same databaseId', function() {
|
||||
var layouts = [
|
||||
{
|
||||
'id': 'fr',
|
||||
'name': 'Français',
|
||||
'imEngineId': 'latin',
|
||||
'installed': true,
|
||||
'preloaded': true,
|
||||
'dictFilename': 'fr.dict',
|
||||
'dictFilePath': 'dictionaries/fr.dict',
|
||||
'dictFileSize': 1874745,
|
||||
'types': ['email', 'password', 'text', 'url']
|
||||
},
|
||||
{
|
||||
'id': 'fr-CA',
|
||||
'name': 'Français (Canadien)',
|
||||
'imEngineId': 'latin',
|
||||
'installed': true,
|
||||
'preloaded': true,
|
||||
'dictFilename': 'fr.dict',
|
||||
'dictFilePath': 'dictionaries/fr.dict',
|
||||
'dictFileSize': 1874745,
|
||||
'types': ['email', 'password', 'text', 'url']
|
||||
}
|
||||
];
|
||||
list.createDictionariesFromLayouts(layouts);
|
||||
|
||||
assert.isTrue(window.LayoutDictionary.calledWith(list, {
|
||||
filename: 'fr.dict',
|
||||
imEngineId: 'latin',
|
||||
filePath: 'dictionaries/fr.dict',
|
||||
fileSize: 1874745,
|
||||
databaseId: 'latin/dictionaries/fr.dict',
|
||||
preloaded: true,
|
||||
installedLayoutIds: new Set() // Must compare seperately
|
||||
}));
|
||||
|
||||
var installedLayoutIds =
|
||||
window.LayoutDictionary.getCall(0).args[1].installedLayoutIds;
|
||||
assert.deepEqual(['fr', 'fr-CA'],
|
||||
Array.from(installedLayoutIds).sort());
|
||||
|
||||
var layoutDict = layoutDicts[0];
|
||||
assert.isTrue(layoutDict.start.calledOnce);
|
||||
});
|
||||
|
||||
test('Mark as installed if one layout is installed', function() {
|
||||
var layouts = [
|
||||
{
|
||||
'id': 'fr',
|
||||
'name': 'Français',
|
||||
'imEngineId': 'latin',
|
||||
'installed': false,
|
||||
'preloaded': false,
|
||||
'dictFilename': 'fr.dict',
|
||||
'dictFilePath': 'dictionaries/fr.dict',
|
||||
'dictFileSize': 1874745,
|
||||
'types': ['email', 'password', 'text', 'url']
|
||||
},
|
||||
{
|
||||
'id': 'fr-CA',
|
||||
'name': 'Français (Canadien)',
|
||||
'imEngineId': 'latin',
|
||||
'installed': true,
|
||||
'preloaded': false,
|
||||
'dictFilename': 'fr.dict',
|
||||
'dictFilePath': 'dictionaries/fr.dict',
|
||||
'dictFileSize': 1874745,
|
||||
'types': ['email', 'password', 'text', 'url']
|
||||
}
|
||||
];
|
||||
list.createDictionariesFromLayouts(layouts);
|
||||
|
||||
assert.isTrue(window.LayoutDictionary.calledWith(list, {
|
||||
filename: 'fr.dict',
|
||||
imEngineId: 'latin',
|
||||
filePath: 'dictionaries/fr.dict',
|
||||
fileSize: 1874745,
|
||||
databaseId: 'latin/dictionaries/fr.dict',
|
||||
preloaded: false,
|
||||
installedLayoutIds: new Set() // Must compare seperately
|
||||
}));
|
||||
|
||||
var installedLayoutIds =
|
||||
window.LayoutDictionary.getCall(0).args[1].installedLayoutIds;
|
||||
assert.deepEqual(['fr-CA'],
|
||||
Array.from(installedLayoutIds).sort());
|
||||
|
||||
var layoutDict = layoutDicts[0];
|
||||
assert.isTrue(layoutDict.start.calledOnce);
|
||||
});
|
||||
});
|
||||
});
|
||||
597
gaia/apps/keyboard/test/unit/settings/layout_dictionary_test.js
Executable file
597
gaia/apps/keyboard/test/unit/settings/layout_dictionary_test.js
Executable file
|
|
@ -0,0 +1,597 @@
|
|||
'use strict';
|
||||
|
||||
/* global LayoutDictionary, LayoutDictionaryDownloader, PromiseStorage */
|
||||
|
||||
require('/js/settings/layout_dictionary_downloader.js');
|
||||
require('/js/settings/layout_dictionary.js');
|
||||
require('/js/shared/promise_storage.js');
|
||||
|
||||
/*
|
||||
* This unit test is organized to prove that we should be able to take
|
||||
* another installForLayout() or removeForLayout() in whatever state we are in,
|
||||
* except when the the dictionary is in preloaded state.
|
||||
*/
|
||||
suite('LayoutDictionary', function() {
|
||||
var listStub;
|
||||
|
||||
var layoutDictionaryDownloaderStub;
|
||||
var downloadDeferred;
|
||||
var dbSetItemDeferred;
|
||||
var dbDeleteItemDeferred;
|
||||
|
||||
var layoutDict;
|
||||
var layoutItem;
|
||||
var layoutItem2;
|
||||
|
||||
// Deferred is an object containing a promise with it's resolve/reject
|
||||
// methods exposed.
|
||||
// See
|
||||
// https://developer.mozilla.org/docs/Mozilla/JavaScript_code_modules
|
||||
// /Promise.jsm/Deferred
|
||||
//
|
||||
// For it's interfaces.
|
||||
var Deferred = function() {
|
||||
this.promise = new Promise(function(resolve, reject) {
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
setup(function() {
|
||||
layoutDictionaryDownloaderStub =
|
||||
this.sinon.stub(Object.create(LayoutDictionaryDownloader.prototype));
|
||||
this.sinon.stub(window, 'LayoutDictionaryDownloader')
|
||||
.returns(layoutDictionaryDownloaderStub);
|
||||
|
||||
downloadDeferred = new Deferred();
|
||||
layoutDictionaryDownloaderStub.load.returns(downloadDeferred.promise);
|
||||
|
||||
listStub = {
|
||||
dbStore: this.sinon.stub(Object.create(PromiseStorage.prototype))
|
||||
};
|
||||
|
||||
dbSetItemDeferred = new Deferred();
|
||||
listStub.dbStore.setItem.returns(dbSetItemDeferred.promise);
|
||||
|
||||
dbDeleteItemDeferred = new Deferred();
|
||||
listStub.dbStore.deleteItem.returns(dbDeleteItemDeferred.promise);
|
||||
|
||||
layoutItem = {
|
||||
id: 'ab',
|
||||
updateInstallProgress: this.sinon.stub()
|
||||
};
|
||||
|
||||
layoutItem2 = {
|
||||
id: 'cd',
|
||||
updateInstallProgress: this.sinon.stub()
|
||||
};
|
||||
});
|
||||
|
||||
suite('STATE_PRELOADED', function() {
|
||||
setup(function() {
|
||||
var dict = {
|
||||
filename: 'bar.dict',
|
||||
imEngineId: 'foo',
|
||||
filePath: 'path/to/bar.dict',
|
||||
fileSize: 100,
|
||||
databaseId: 'foo/bar.dict',
|
||||
preloaded: true,
|
||||
installedLayoutIds: new Set(['ab'])
|
||||
};
|
||||
|
||||
layoutDict = new LayoutDictionary(listStub, dict);
|
||||
layoutDict.start();
|
||||
|
||||
assert.equal(layoutDict.filename, 'bar.dict');
|
||||
assert.equal(layoutDict.imEngineId, 'foo');
|
||||
assert.equal(layoutDict.filePath, 'path/to/bar.dict');
|
||||
assert.equal(layoutDict.fileSize, 100);
|
||||
assert.equal(layoutDict.databaseId, 'foo/bar.dict');
|
||||
assert.equal(layoutDict.installedLayoutIds, dict.installedLayoutIds);
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_PRELOADED);
|
||||
});
|
||||
|
||||
test('installForLayout()', function() {
|
||||
assert.throw(layoutDict.installForLayout.bind(layoutDict));
|
||||
});
|
||||
|
||||
test('removeForLayout()', function() {
|
||||
assert.throw(layoutDict.removeForLayout.bind(layoutDict));
|
||||
});
|
||||
});
|
||||
|
||||
suite('STATE_INSTALLABLE', function() {
|
||||
setup(function() {
|
||||
var dict = {
|
||||
filename: 'bar.dict',
|
||||
imEngineId: 'foo',
|
||||
filePath: 'path/to/bar.dict',
|
||||
fileSize: 100,
|
||||
databaseId: 'foo/bar.dict',
|
||||
preloaded: false,
|
||||
installedLayoutIds: new Set()
|
||||
};
|
||||
|
||||
layoutDict = new LayoutDictionary(listStub, dict);
|
||||
layoutDict.start();
|
||||
|
||||
assert.equal(layoutDict.filename, 'bar.dict');
|
||||
assert.equal(layoutDict.imEngineId, 'foo');
|
||||
assert.equal(layoutDict.filePath, 'path/to/bar.dict');
|
||||
assert.equal(layoutDict.fileSize, 100);
|
||||
assert.equal(layoutDict.databaseId, 'foo/bar.dict');
|
||||
assert.equal(layoutDict.installedLayoutIds, dict.installedLayoutIds);
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_INSTALLABLE);
|
||||
});
|
||||
|
||||
test('installForLayout(layoutItem)', function() {
|
||||
layoutDict.installForLayout(layoutItem);
|
||||
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_INSTALLING_CANCELLABLE);
|
||||
|
||||
assert.isTrue(layoutDictionaryDownloaderStub.load.calledOnce);
|
||||
|
||||
layoutDictionaryDownloaderStub.onprogress(10, 100);
|
||||
assert.equal(layoutDict.downloadLoadedSize, 10);
|
||||
assert.equal(layoutDict.downloadTotalSize, 100);
|
||||
assert.isTrue(layoutItem.updateInstallProgress.calledWith(10, 100));
|
||||
});
|
||||
|
||||
test('removeForLayout(layoutItem)', function(done) {
|
||||
layoutItem = {
|
||||
id: 'ab',
|
||||
updateInstallProgress: this.sinon.stub()
|
||||
};
|
||||
var p = layoutDict.removeForLayout(layoutDict);
|
||||
|
||||
p.then(function() {
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_INSTALLABLE);
|
||||
assert.equal(layoutDict.downloadLoadedSize, 0);
|
||||
assert.equal(layoutDict.downloadTotalSize, 0);
|
||||
}, function(e) {
|
||||
throw e || 'Should not reject.';
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('STATE_INSTALLING_CANCELLABLE', function() {
|
||||
var p;
|
||||
setup(function() {
|
||||
var dict = {
|
||||
filename: 'bar.dict',
|
||||
imEngineId: 'foo',
|
||||
filePath: 'path/to/bar.dict',
|
||||
fileSize: 100,
|
||||
databaseId: 'foo/bar.dict',
|
||||
preloaded: false,
|
||||
installedLayoutIds: new Set()
|
||||
};
|
||||
|
||||
layoutDict = new LayoutDictionary(listStub, dict);
|
||||
layoutDict.start();
|
||||
|
||||
p = layoutDict.installForLayout(layoutItem);
|
||||
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_INSTALLING_CANCELLABLE);
|
||||
assert.isTrue(layoutDictionaryDownloaderStub.load.calledOnce);
|
||||
});
|
||||
|
||||
test('Download fail', function(done) {
|
||||
downloadDeferred.reject('mocked reject');
|
||||
|
||||
p.then(function() {
|
||||
throw 'Should not resolve.';
|
||||
}, function() {
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_INSTALLABLE);
|
||||
assert.isFalse(layoutDict.installedLayoutIds.has('ab'));
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('installForLayout(layoutItem)', function(done) {
|
||||
var p2 = layoutDict.installForLayout(layoutItem);
|
||||
|
||||
downloadDeferred.resolve({ data: 'data' });
|
||||
downloadDeferred.promise
|
||||
.then(function() {
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_INSTALLING);
|
||||
assert.isTrue(listStub.dbStore.setItem.calledWith(
|
||||
layoutDict.databaseId, { data: 'data' }));
|
||||
|
||||
dbSetItemDeferred.resolve();
|
||||
return p2;
|
||||
})
|
||||
.then(function() {
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_INSTALLED);
|
||||
assert.isTrue(layoutDict.installedLayoutIds.has('ab'));
|
||||
assert.equal(layoutDict.downloadLoadedSize, 0);
|
||||
assert.equal(layoutDict.downloadTotalSize, 0);
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
// TODO: This case can be further expand to
|
||||
// installForLayout(layoutItem2) -> fail to download etc.
|
||||
// but here we only verify the two layouts are both marked as installed
|
||||
// when installation is complete.
|
||||
test('installForLayout(layoutItem2)', function(done) {
|
||||
var p2 = layoutDict.installForLayout(layoutItem2);
|
||||
|
||||
layoutDictionaryDownloaderStub.onprogress(10, 100);
|
||||
assert.equal(layoutDict.downloadLoadedSize, 10);
|
||||
assert.equal(layoutDict.downloadTotalSize, 100);
|
||||
assert.isTrue(layoutItem.updateInstallProgress.calledWith(10, 100));
|
||||
assert.isTrue(layoutItem2.updateInstallProgress.calledWith(10, 100));
|
||||
|
||||
assert.isTrue(layoutDictionaryDownloaderStub.load.calledOnce);
|
||||
|
||||
downloadDeferred.resolve({ data: 'data' });
|
||||
downloadDeferred.promise
|
||||
.then(function() {
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_INSTALLING);
|
||||
assert.isTrue(listStub.dbStore.setItem.calledWith(
|
||||
layoutDict.databaseId, { data: 'data' }));
|
||||
|
||||
dbSetItemDeferred.resolve();
|
||||
return p2;
|
||||
})
|
||||
.then(function() {
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_INSTALLED);
|
||||
assert.isTrue(layoutDict.installedLayoutIds.has('ab'));
|
||||
assert.isTrue(layoutDict.installedLayoutIds.has('cd'));
|
||||
assert.equal(layoutDict.downloadLoadedSize, 0);
|
||||
assert.equal(layoutDict.downloadTotalSize, 0);
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('removeForLayout(layoutItem)', function(done) {
|
||||
var p2 = layoutDict.removeForLayout(layoutItem);
|
||||
|
||||
assert.isTrue(layoutDictionaryDownloaderStub.abort.calledOnce);
|
||||
// download is cancelled because of abort() here.
|
||||
downloadDeferred.reject('mocked reject');
|
||||
|
||||
p2.then(function() {
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_INSTALLABLE);
|
||||
assert.isFalse(layoutDict.installedLayoutIds.has('ab'));
|
||||
|
||||
assert.isFalse(listStub.dbStore.setItem.calledOnce);
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('removeForLayout(layoutItem2)', function(done) {
|
||||
var p2 = layoutDict.removeForLayout(layoutItem2);
|
||||
|
||||
assert.isFalse(layoutDictionaryDownloaderStub.abort.calledOnce);
|
||||
|
||||
p2.then(function() {
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_INSTALLING_CANCELLABLE);
|
||||
}, function(e) {
|
||||
throw e || 'Should not reject.';
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('removeForLayout(layoutItem2) (install two)', function(done) {
|
||||
var pi_2 = layoutDict.installForLayout(layoutItem2);
|
||||
var p2 = layoutDict.removeForLayout(layoutItem2);
|
||||
|
||||
assert.isFalse(layoutDictionaryDownloaderStub.abort.calledOnce);
|
||||
|
||||
p2.then(function() {
|
||||
assert.equal(
|
||||
layoutDict.state, layoutDict.STATE_INSTALLING_CANCELLABLE);
|
||||
|
||||
return pi_2;
|
||||
}, function(e) {
|
||||
throw e || 'Should not reject.';
|
||||
})
|
||||
.then(function() {
|
||||
throw 'Should not resolve';
|
||||
}, function() {
|
||||
assert.isTrue(true, 'installForLayout promise rejected');
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('STATE_INSTALLING', function() {
|
||||
var p;
|
||||
setup(function(done) {
|
||||
var dict = {
|
||||
filename: 'bar.dict',
|
||||
imEngineId: 'foo',
|
||||
filePath: 'path/to/bar.dict',
|
||||
fileSize: 100,
|
||||
databaseId: 'foo/bar.dict',
|
||||
preloaded: false,
|
||||
installedLayoutIds: new Set()
|
||||
};
|
||||
|
||||
layoutDict = new LayoutDictionary(listStub, dict);
|
||||
layoutDict.start();
|
||||
|
||||
p = layoutDict.installForLayout(layoutItem);
|
||||
downloadDeferred.resolve({ data: 'data' });
|
||||
downloadDeferred.promise
|
||||
.then(function() {
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_INSTALLING);
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('Install fail', function(done) {
|
||||
dbSetItemDeferred.reject('mocked reject');
|
||||
|
||||
p.then(function() {
|
||||
throw 'Should not resolve.';
|
||||
}, function() {
|
||||
return dbSetItemDeferred.promise.catch(function() {});
|
||||
})
|
||||
.then(function() {
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_INSTALLABLE);
|
||||
assert.isFalse(layoutDict.installedLayoutIds.has('ab'));
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('installForLayout(layoutItem)', function(done) {
|
||||
var p2 = layoutDict.installForLayout(layoutItem);
|
||||
|
||||
assert.isTrue(listStub.dbStore.setItem.calledWith(
|
||||
layoutDict.databaseId, { data: 'data' }));
|
||||
|
||||
dbSetItemDeferred.resolve();
|
||||
|
||||
p2.then(function() {
|
||||
return dbSetItemDeferred.promise;
|
||||
})
|
||||
.then(function() {
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_INSTALLED);
|
||||
assert.isTrue(layoutDict.installedLayoutIds.has('ab'));
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
// TODO: This case can be further expand to
|
||||
// installForLayout(layoutItem2) -> fail to install etc.
|
||||
// but here we only verify the two layouts are both marked as installed
|
||||
// when installation is complete.
|
||||
test('installForLayout(layoutItem2)', function(done) {
|
||||
var p2 = layoutDict.installForLayout(layoutItem2);
|
||||
|
||||
assert.isTrue(listStub.dbStore.setItem.calledWith(
|
||||
layoutDict.databaseId, { data: 'data' }));
|
||||
|
||||
dbSetItemDeferred.resolve();
|
||||
|
||||
p2.then(function() {
|
||||
return dbSetItemDeferred.promise;
|
||||
})
|
||||
.then(function() {
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_INSTALLED);
|
||||
assert.isTrue(layoutDict.installedLayoutIds.has('ab'));
|
||||
assert.isTrue(layoutDict.installedLayoutIds.has('cd'));
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('removeForLayout(layoutItem)', function(done) {
|
||||
var p2 = layoutDict.removeForLayout(layoutItem);
|
||||
|
||||
assert.isTrue(listStub.dbStore.setItem.calledWith(
|
||||
layoutDict.databaseId, { data: 'data' }));
|
||||
|
||||
dbSetItemDeferred.resolve();
|
||||
|
||||
p.then(function() {
|
||||
throw 'Should not resolve.';
|
||||
}, function() {
|
||||
return dbSetItemDeferred.promise;
|
||||
})
|
||||
.then(function() {
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_REMOVING);
|
||||
|
||||
assert.isTrue(listStub.dbStore.deleteItem.calledWith(
|
||||
layoutDict.databaseId));
|
||||
|
||||
dbDeleteItemDeferred.resolve();
|
||||
|
||||
return p2;
|
||||
})
|
||||
.then(function() {
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_INSTALLABLE);
|
||||
assert.isFalse(layoutDict.installedLayoutIds.has('ab'));
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('removeForLayout(layoutItem2)', function(done) {
|
||||
var p2 = layoutDict.removeForLayout(layoutItem2);
|
||||
|
||||
p2.then(function() {
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_INSTALLING);
|
||||
}, function(e) {
|
||||
throw e || 'Should not reject.';
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('removeForLayout(layoutItem2) (installing two)', function(done) {
|
||||
layoutDict.installForLayout(layoutItem2);
|
||||
|
||||
var p2 = layoutDict.removeForLayout(layoutItem2);
|
||||
|
||||
p2.then(function() {
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_INSTALLING);
|
||||
}, function(e) {
|
||||
throw e || 'Should not reject.';
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('STATE_INSTALLED', function() {
|
||||
setup(function() {
|
||||
var dict = {
|
||||
filename: 'bar.dict',
|
||||
imEngineId: 'foo',
|
||||
filePath: 'path/to/bar.dict',
|
||||
fileSize: 100,
|
||||
databaseId: 'foo/bar.dict',
|
||||
preloaded: false,
|
||||
installedLayoutIds: new Set(['ab'])
|
||||
};
|
||||
|
||||
layoutDict = new LayoutDictionary(listStub, dict);
|
||||
layoutDict.start();
|
||||
|
||||
assert.equal(layoutDict.filename, 'bar.dict');
|
||||
assert.equal(layoutDict.imEngineId, 'foo');
|
||||
assert.equal(layoutDict.filePath, 'path/to/bar.dict');
|
||||
assert.equal(layoutDict.fileSize, 100);
|
||||
assert.equal(layoutDict.databaseId, 'foo/bar.dict');
|
||||
assert.equal(layoutDict.installedLayoutIds, dict.installedLayoutIds);
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_INSTALLED);
|
||||
});
|
||||
|
||||
test('installForLayout(layoutItem)', function(done) {
|
||||
var p2 = layoutDict.installForLayout(layoutItem2);
|
||||
|
||||
p2.then(function() {
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_INSTALLED);
|
||||
assert.isTrue(layoutDict.installedLayoutIds.has('ab'));
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('installForLayout(layoutItem2)', function(done) {
|
||||
var p2 = layoutDict.installForLayout(layoutItem2);
|
||||
|
||||
p2.then(function() {
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_INSTALLED);
|
||||
assert.isTrue(layoutDict.installedLayoutIds.has('ab'));
|
||||
assert.isTrue(layoutDict.installedLayoutIds.has('cd'));
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('removeForLayout(layoutItem)', function(done) {
|
||||
var p2 = layoutDict.removeForLayout(layoutItem);
|
||||
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_REMOVING);
|
||||
assert.isTrue(listStub.dbStore.deleteItem.calledWith(
|
||||
layoutDict.databaseId));
|
||||
|
||||
dbDeleteItemDeferred.resolve();
|
||||
|
||||
p2.then(function() {
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_INSTALLABLE);
|
||||
assert.isFalse(layoutDict.installedLayoutIds.has('ab'));
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('removeForLayout(layoutItem2) (w/ two layouts installed)',
|
||||
function(done) {
|
||||
layoutDict.installedLayoutIds.add('cd');
|
||||
|
||||
var p2 = layoutDict.removeForLayout(layoutItem2);
|
||||
|
||||
p2.then(function() {
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_INSTALLED);
|
||||
assert.isTrue(layoutDict.installedLayoutIds.has('ab'));
|
||||
assert.isFalse(layoutDict.installedLayoutIds.has('cd'));
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('STATE_REMOVING', function() {
|
||||
var p;
|
||||
setup(function() {
|
||||
var dict = {
|
||||
filename: 'bar.dict',
|
||||
imEngineId: 'foo',
|
||||
filePath: 'path/to/bar.dict',
|
||||
fileSize: 100,
|
||||
databaseId: 'foo/bar.dict',
|
||||
preloaded: false,
|
||||
installedLayoutIds: new Set(['ab'])
|
||||
};
|
||||
|
||||
layoutDict = new LayoutDictionary(listStub, dict);
|
||||
layoutDict.start();
|
||||
|
||||
p = layoutDict.removeForLayout(layoutItem);
|
||||
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_REMOVING);
|
||||
});
|
||||
|
||||
test('Removal fail', function(done) {
|
||||
dbDeleteItemDeferred.reject('mocked reject');
|
||||
|
||||
p.then(function() {
|
||||
throw 'Should not resolve.';
|
||||
}, function() {
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_INSTALLED);
|
||||
assert.isTrue(layoutDict.installedLayoutIds.has('ab'));
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
// TODO: This case can be further expand to
|
||||
// installForLayout(layoutItem) -> fail to remove etc.
|
||||
// but here we only verify the new layouts being installed when
|
||||
// the removal is completed.
|
||||
test('installForLayout(layoutItem)', function(done) {
|
||||
layoutDict.installForLayout(layoutItem);
|
||||
|
||||
dbDeleteItemDeferred.resolve();
|
||||
p.then(function() {
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_INSTALLING_CANCELLABLE);
|
||||
assert.isFalse(layoutDict.installedLayoutIds.has('ab'));
|
||||
|
||||
assert.isTrue(layoutDictionaryDownloaderStub.load.calledOnce);
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
// TODO: This case can be further expand to
|
||||
// installForLayout(layoutItem2) -> fail to remove etc.
|
||||
// but here we only verify the new layouts being installed when
|
||||
// the removal is completed.
|
||||
test('installForLayout(layoutItem2)', function(done) {
|
||||
layoutDict.installForLayout(layoutItem2);
|
||||
|
||||
dbDeleteItemDeferred.resolve();
|
||||
p.then(function() {
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_INSTALLING_CANCELLABLE);
|
||||
assert.isFalse(layoutDict.installedLayoutIds.has('ab'));
|
||||
|
||||
assert.isTrue(layoutDictionaryDownloaderStub.load.calledOnce);
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('removeForLayout(layoutItem)', function() {
|
||||
var p2 = layoutDict.removeForLayout(layoutItem);
|
||||
|
||||
assert.isTrue(p2 === p);
|
||||
});
|
||||
|
||||
test('removeForLayout(layoutItem2)', function(done) {
|
||||
var p2 = layoutDict.removeForLayout(layoutItem2);
|
||||
|
||||
p2.then(function() {
|
||||
assert.equal(layoutDict.state, layoutDict.STATE_REMOVING);
|
||||
}, function(e) {
|
||||
throw e || 'Should not reject.';
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
392
gaia/apps/keyboard/test/unit/settings/layout_item_list_test.js
Executable file
392
gaia/apps/keyboard/test/unit/settings/layout_item_list_test.js
Executable file
|
|
@ -0,0 +1,392 @@
|
|||
'use strict';
|
||||
|
||||
/* global LayoutItem, LayoutDictionaryList, LayoutItemList, PromiseStorage,
|
||||
SettingsPromiseManager */
|
||||
|
||||
require('/js/settings/layout_item.js');
|
||||
require('/js/settings/layout_item_list.js');
|
||||
require('/js/settings/layout_dictionary_list.js');
|
||||
require('/js/shared/promise_storage.js');
|
||||
require('/js/keyboard/settings.js');
|
||||
|
||||
suite('LayoutItemList', function() {
|
||||
var layoutDictionaryListStub;
|
||||
var dbGetItemConfigDeferred;
|
||||
var dbSetItemDeferred;
|
||||
|
||||
var app;
|
||||
|
||||
var fakeXhr;
|
||||
|
||||
var list;
|
||||
|
||||
// Deferred is an object containing a promise with it's resolve/reject
|
||||
// methods exposed.
|
||||
// See
|
||||
// https://developer.mozilla.org/docs/Mozilla/JavaScript_code_modules
|
||||
// /Promise.jsm/Deferred
|
||||
//
|
||||
// For it's interfaces.
|
||||
var Deferred = function() {
|
||||
this.promise = new Promise(function(resolve, reject) {
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
setup(function(done) {
|
||||
layoutDictionaryListStub =
|
||||
this.sinon.stub(Object.create(LayoutDictionaryList.prototype));
|
||||
this.sinon.stub(window, 'LayoutDictionaryList')
|
||||
.returns(layoutDictionaryListStub);
|
||||
|
||||
var LayoutItemPrototype = LayoutItem.prototype;
|
||||
this.sinon.stub(window, 'LayoutItem', function() {
|
||||
return this.sinon.stub(Object.create(LayoutItemPrototype));
|
||||
}.bind(this));
|
||||
|
||||
var requests = [];
|
||||
fakeXhr = sinon.useFakeXMLHttpRequest();
|
||||
fakeXhr.onCreate = function(request) {
|
||||
requests.push(request);
|
||||
};
|
||||
|
||||
app = {
|
||||
closeLockManager: { stub: 'closeLockManager' },
|
||||
preferencesStore: sinon.stub(Object.create(PromiseStorage.prototype)),
|
||||
settingsPromiseManager:
|
||||
this.sinon.stub(Object.create(SettingsPromiseManager.prototype))
|
||||
};
|
||||
|
||||
dbGetItemConfigDeferred = new Deferred();
|
||||
app.preferencesStore.getItem
|
||||
.withArgs(LayoutItemList.prototype.ENABLED_LAYOUT_KEY)
|
||||
.returns(dbGetItemConfigDeferred.promise);
|
||||
|
||||
dbSetItemDeferred = new Deferred();
|
||||
app.preferencesStore.setItem.returns(dbSetItemDeferred.promise);
|
||||
|
||||
list = new LayoutItemList(app);
|
||||
list.onready = this.sinon.stub();
|
||||
var p = list.start();
|
||||
|
||||
assert.equal(list.closeLockManager, app.closeLockManager);
|
||||
|
||||
assert.isTrue(window.LayoutDictionaryList.calledWith(list));
|
||||
assert.isTrue(layoutDictionaryListStub.start.calledOnce);
|
||||
assert.equal(list.dictionaryList, layoutDictionaryListStub);
|
||||
|
||||
assert.isTrue(
|
||||
app.preferencesStore.getItem.calledWith(list.ENABLED_LAYOUT_KEY));
|
||||
dbGetItemConfigDeferred.resolve(['fr']);
|
||||
|
||||
var request = requests[0];
|
||||
assert.equal(request.url, list.CONFIG_FILE_PATH);
|
||||
assert.equal(request.responseType, 'json');
|
||||
request.response = [
|
||||
{
|
||||
'id': 'en',
|
||||
'name': 'English',
|
||||
'imEngineId': 'latin',
|
||||
'preloaded': true,
|
||||
'dictFilename': 'en_us.dict',
|
||||
'dictFilePath': 'dictionaries/en_us.dict',
|
||||
'dictFileSize': 1451390,
|
||||
'types': ['email', 'password', 'text', 'url']
|
||||
},
|
||||
{
|
||||
'id': 'fr',
|
||||
'name': 'Français',
|
||||
'imEngineId': 'latin',
|
||||
'preloaded': false,
|
||||
'dictFilename': 'fr.dict',
|
||||
'dictFilePath': 'dictionaries/fr.dict',
|
||||
'dictFileSize': 1874745,
|
||||
'types': ['email', 'password', 'text', 'url']
|
||||
},
|
||||
{
|
||||
'id': 'fr-CA',
|
||||
'name': 'Français (Canadien)',
|
||||
'imEngineId': 'latin',
|
||||
'preloaded': false,
|
||||
'dictFilename': 'fr.dict',
|
||||
'dictFilePath': 'dictionaries/fr.dict',
|
||||
'dictFileSize': 1874745,
|
||||
'types': ['email', 'password', 'text', 'url']
|
||||
}
|
||||
];
|
||||
|
||||
request.respond(200, {}, '');
|
||||
|
||||
p.then(done, done);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
list.stop();
|
||||
});
|
||||
|
||||
test('Create LayoutDictionary and LayoutItem instances', function() {
|
||||
assert.isTrue(list.onready.calledOnce);
|
||||
|
||||
var layoutsWithInstallInfo = [
|
||||
{
|
||||
'id': 'en',
|
||||
'name': 'English',
|
||||
'imEngineId': 'latin',
|
||||
'preloaded': true,
|
||||
'installed': true,
|
||||
'dictFilename': 'en_us.dict',
|
||||
'dictFilePath': 'dictionaries/en_us.dict',
|
||||
'dictFileSize': 1451390,
|
||||
'types': ['email', 'password', 'text', 'url']
|
||||
},
|
||||
{
|
||||
'id': 'fr',
|
||||
'name': 'Français',
|
||||
'imEngineId': 'latin',
|
||||
'preloaded': false,
|
||||
'installed': true,
|
||||
'dictFilename': 'fr.dict',
|
||||
'dictFilePath': 'dictionaries/fr.dict',
|
||||
'dictFileSize': 1874745,
|
||||
'types': ['email', 'password', 'text', 'url']
|
||||
},
|
||||
{
|
||||
'id': 'fr-CA',
|
||||
'name': 'Français (Canadien)',
|
||||
'imEngineId': 'latin',
|
||||
'preloaded': false,
|
||||
'installed': false,
|
||||
'dictFilename': 'fr.dict',
|
||||
'dictFilePath': 'dictionaries/fr.dict',
|
||||
'dictFileSize': 1874745,
|
||||
'types': ['email', 'password', 'text', 'url']
|
||||
}
|
||||
];
|
||||
|
||||
assert.isTrue(layoutDictionaryListStub.createDictionariesFromLayouts
|
||||
.calledWith(layoutsWithInstallInfo));
|
||||
|
||||
layoutsWithInstallInfo.forEach(function(layout, i) {
|
||||
assert.isTrue(window.LayoutItem.calledWith(list, layout));
|
||||
var layoutItemStub = window.LayoutItem.getCall(i).returnValue;
|
||||
assert.isTrue(layoutItemStub.start.calledOnce);
|
||||
|
||||
assert.equal(list.layoutItems.get(layout.id), layoutItemStub);
|
||||
});
|
||||
});
|
||||
|
||||
test('setLayoutAsInstalled', function(done) {
|
||||
var p = list.setLayoutAsInstalled('fr-CA');
|
||||
|
||||
Promise.resolve()
|
||||
.then(function() {
|
||||
assert.isTrue(app.preferencesStore.setItem
|
||||
.calledWith(list.ENABLED_LAYOUT_KEY, ['fr', 'fr-CA']));
|
||||
dbSetItemDeferred.resolve();
|
||||
|
||||
return p;
|
||||
})
|
||||
.catch(function(e) {
|
||||
throw (e || 'Should not reject.');
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('setLayoutAsUninstalled', function(done) {
|
||||
var p = list.setLayoutAsUninstalled('fr');
|
||||
|
||||
Promise.resolve()
|
||||
.then(function() {
|
||||
assert.isTrue(app.preferencesStore.setItem
|
||||
.calledWith(list.ENABLED_LAYOUT_KEY, []));
|
||||
dbSetItemDeferred.resolve();
|
||||
|
||||
return p;
|
||||
})
|
||||
.catch(function(e) {
|
||||
throw (e || 'Should not reject.');
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
suite('DownloadPreference', function() {
|
||||
suite('getCurrentState', function() {
|
||||
teardown(function() {
|
||||
delete navigator.mozMobileConnections;
|
||||
});
|
||||
|
||||
test('not using data connection', function(done) {
|
||||
navigator.mozMobileConnections = {
|
||||
'0': { data: { connected: false } },
|
||||
'1': { data: { connected: false } },
|
||||
length: 2 };
|
||||
|
||||
list.downloadPreference.getCurrentState().then(function(val) {
|
||||
assert.equal(val, list.downloadPreference.STATE_ALLOW);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('data connection not avail', function(done) {
|
||||
list.downloadPreference.getCurrentState().then(function(val) {
|
||||
assert.equal(val, list.downloadPreference.STATE_ALLOW);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
suite('w/ data connection', function() {
|
||||
var getItemDeferred;
|
||||
|
||||
setup(function() {
|
||||
navigator.mozMobileConnections = {
|
||||
'0': { data: { connected: true } },
|
||||
'1': { data: { connected: false } },
|
||||
length: 2 };
|
||||
|
||||
getItemDeferred = new Deferred();
|
||||
app.preferencesStore.getItem.returns(getItemDeferred.promise);
|
||||
});
|
||||
|
||||
test('preferences unset', function(done) {
|
||||
getItemDeferred.resolve(undefined);
|
||||
|
||||
list.downloadPreference.getCurrentState().then(function(val) {
|
||||
assert.isTrue(app.preferencesStore.getItem
|
||||
.calledWith('download.prompt-on-data-connection'));
|
||||
assert.equal(val, list.downloadPreference.STATE_PROMPT);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('preferences set to always allow', function(done) {
|
||||
getItemDeferred.resolve(true);
|
||||
|
||||
list.downloadPreference.getCurrentState().then(function(val) {
|
||||
assert.isTrue(app.preferencesStore.getItem
|
||||
.calledWith('download.prompt-on-data-connection'));
|
||||
assert.equal(val, list.downloadPreference.STATE_ALLOW);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('preferences set to always deny', function(done) {
|
||||
getItemDeferred.resolve(false);
|
||||
|
||||
list.downloadPreference.getCurrentState().then(function(val) {
|
||||
assert.isTrue(app.preferencesStore.getItem
|
||||
.calledWith('download.prompt-on-data-connection'));
|
||||
assert.equal(val, list.downloadPreference.STATE_DENY);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('preferences store rejects', function(done) {
|
||||
getItemDeferred.reject('Mocked Error');
|
||||
|
||||
list.downloadPreference.getCurrentState().then(function(val) {
|
||||
assert.isTrue(app.preferencesStore.getItem
|
||||
.calledWith('download.prompt-on-data-connection'));
|
||||
assert.equal(val, list.downloadPreference.STATE_PROMPT);
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('setDataConnectionDownloadState', function() {
|
||||
var p;
|
||||
setup(function() {
|
||||
p = { stub: 'promise' };
|
||||
|
||||
app.preferencesStore.deleteItem.returns(p);
|
||||
app.preferencesStore.setItem.returns(p);
|
||||
});
|
||||
|
||||
test('set to prompt', function() {
|
||||
var r = list.downloadPreference.setDataConnectionDownloadState(
|
||||
list.downloadPreference.STATE_PROMPT);
|
||||
|
||||
assert.isTrue(app.preferencesStore.deleteItem.calledWith(
|
||||
'download.prompt-on-data-connection'));
|
||||
assert.equal(r, p);
|
||||
});
|
||||
|
||||
test('set to allow', function() {
|
||||
var r = list.downloadPreference.setDataConnectionDownloadState(
|
||||
list.downloadPreference.STATE_ALLOW);
|
||||
|
||||
assert.isTrue(app.preferencesStore.setItem.calledWith(
|
||||
'download.prompt-on-data-connection', true));
|
||||
assert.equal(r, p);
|
||||
});
|
||||
|
||||
test('set to deny', function() {
|
||||
var r = list.downloadPreference.setDataConnectionDownloadState(
|
||||
list.downloadPreference.STATE_DENY);
|
||||
|
||||
assert.isTrue(app.preferencesStore.setItem.calledWith(
|
||||
'download.prompt-on-data-connection', false));
|
||||
assert.equal(r, p);
|
||||
});
|
||||
});
|
||||
|
||||
suite('LayoutEnabler', function() {
|
||||
var appManifestURL;
|
||||
setup(function() {
|
||||
appManifestURL =
|
||||
location.protocol + '//' + location.hostname + '/manifest.webapp';
|
||||
});
|
||||
|
||||
test('enableLayout', function(done) {
|
||||
var deferred = new Deferred();
|
||||
|
||||
app.settingsPromiseManager.updateOne.returns(deferred.promise);
|
||||
|
||||
var p = list.layoutEnabler.enableLayout('foo');
|
||||
|
||||
assert.isTrue(app.settingsPromiseManager.updateOne.calledWith(
|
||||
list.layoutEnabler.SETTING_ENABLE_LAYOUT_KEY));
|
||||
|
||||
var obj = {};
|
||||
obj[appManifestURL] = {};
|
||||
|
||||
var refObj = {};
|
||||
refObj[appManifestURL] = {};
|
||||
refObj[appManifestURL].foo = true;
|
||||
|
||||
var returnedObj =
|
||||
app.settingsPromiseManager.updateOne.firstCall.args[1](obj);
|
||||
|
||||
assert.deepEqual(returnedObj, refObj);
|
||||
deferred.resolve();
|
||||
|
||||
p.catch(function(e) { throw e || 'Should not reject.'; })
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('disableLayout', function(done) {
|
||||
var deferred = new Deferred();
|
||||
|
||||
app.settingsPromiseManager.updateOne.returns(deferred.promise);
|
||||
|
||||
var p = list.layoutEnabler.disableLayout('foo');
|
||||
|
||||
assert.isTrue(app.settingsPromiseManager.updateOne.calledWith(
|
||||
list.layoutEnabler.SETTING_ENABLE_LAYOUT_KEY));
|
||||
|
||||
var obj = {};
|
||||
obj[appManifestURL] = {};
|
||||
obj[appManifestURL].foo = true;
|
||||
obj[appManifestURL].bar = true;
|
||||
|
||||
var refObj = {};
|
||||
refObj[appManifestURL] = {};
|
||||
refObj[appManifestURL].bar = true;
|
||||
|
||||
var returnedObj =
|
||||
app.settingsPromiseManager.updateOne.firstCall.args[1](obj);
|
||||
|
||||
assert.deepEqual(returnedObj, refObj);
|
||||
deferred.resolve();
|
||||
|
||||
p.catch(function(e) { throw e || 'Should not reject.'; })
|
||||
.then(done, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
463
gaia/apps/keyboard/test/unit/settings/layout_item_list_view_test.js
Executable file
463
gaia/apps/keyboard/test/unit/settings/layout_item_list_view_test.js
Executable file
|
|
@ -0,0 +1,463 @@
|
|||
'use strict';
|
||||
|
||||
/* global LayoutItemListView, LayoutItemList, LayoutItemView,
|
||||
DownloadPreference, LayoutEnabler */
|
||||
|
||||
require('/js/settings/base_view.js');
|
||||
require('/js/settings/layout_item_list_view.js');
|
||||
|
||||
require('/js/settings/layout_item_list.js');
|
||||
require('/js/settings/layout_item_view.js');
|
||||
|
||||
var Deferred = function() {
|
||||
this.promise = new Promise(function(resolve, reject) {
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
suite('LayoutItemListView', function() {
|
||||
var listView;
|
||||
|
||||
var app;
|
||||
var installedListElStub;
|
||||
var installableListElStub;
|
||||
var containerElStub;
|
||||
var rememberMyChoiceElStub;
|
||||
var layoutItemListStub;
|
||||
|
||||
var removeDialogElStub;
|
||||
var downloadDialogElStub;
|
||||
var toastElStub;
|
||||
var enableDialogElStub;
|
||||
|
||||
var downloadPreferenceGetCurrentStateDeferred;
|
||||
var downloadPreferenceSetDataConnectionDownloadStateDeferred;
|
||||
|
||||
setup(function() {
|
||||
layoutItemListStub =
|
||||
this.sinon.stub(LayoutItemList.prototype);
|
||||
layoutItemListStub.downloadPreference =
|
||||
this.sinon.stub(DownloadPreference.prototype);
|
||||
layoutItemListStub.layoutEnabler =
|
||||
this.sinon.stub(LayoutEnabler.prototype);
|
||||
this.sinon.stub(window, 'LayoutItemList').returns(layoutItemListStub);
|
||||
|
||||
downloadPreferenceGetCurrentStateDeferred = new Deferred();
|
||||
layoutItemListStub.downloadPreference.getCurrentState
|
||||
.returns(downloadPreferenceGetCurrentStateDeferred.promise);
|
||||
|
||||
downloadPreferenceSetDataConnectionDownloadStateDeferred = new Deferred();
|
||||
layoutItemListStub.downloadPreference.setDataConnectionDownloadState
|
||||
.returns(
|
||||
downloadPreferenceSetDataConnectionDownloadStateDeferred.promise);
|
||||
|
||||
var LayoutItemViewPrototype = LayoutItemView.prototype;
|
||||
this.sinon.stub(window, 'LayoutItemView', function() {
|
||||
var viewStub = this.sinon.stub(Object.create(LayoutItemViewPrototype));
|
||||
viewStub.contaner = {};
|
||||
viewStub.inList = viewStub.IN_LIST_INSTALLED;
|
||||
|
||||
return viewStub;
|
||||
}.bind(this));
|
||||
|
||||
app = { stub: app };
|
||||
|
||||
containerElStub = { stub: 'container', hidden: true };
|
||||
installedListElStub = {
|
||||
appendChild: this.sinon.stub()
|
||||
};
|
||||
installableListElStub = {
|
||||
appendChild: this.sinon.stub()
|
||||
};
|
||||
|
||||
removeDialogElStub = document.createElement('div');
|
||||
removeDialogElStub.appendChild(document.createElement('p'));
|
||||
removeDialogElStub.hidden = true;
|
||||
|
||||
downloadDialogElStub = document.createElement('div');
|
||||
downloadDialogElStub.hidden = true;
|
||||
|
||||
toastElStub = document.createElement('div');
|
||||
toastElStub.show = this.sinon.stub();
|
||||
toastElStub.hide = this.sinon.stub();
|
||||
|
||||
rememberMyChoiceElStub = document.createElement('input');
|
||||
rememberMyChoiceElStub.type = 'checkbox';
|
||||
|
||||
enableDialogElStub = document.createElement('div');
|
||||
enableDialogElStub.appendChild(document.createElement('p'));
|
||||
enableDialogElStub.hidden = true;
|
||||
|
||||
listView = new LayoutItemListView(app);
|
||||
|
||||
this.sinon.stub(document, 'getElementById')
|
||||
.withArgs(listView.CONTAINER_ID).returns(containerElStub)
|
||||
.withArgs(listView.INSTALLED_LIST_ID).returns(installedListElStub)
|
||||
.withArgs(listView.INSTALLABLE_LIST_ID).returns(installableListElStub)
|
||||
.withArgs('installable-keyboards-removal-dialog')
|
||||
.returns(removeDialogElStub)
|
||||
.withArgs('installable-keyboards-download-error-toast')
|
||||
.returns(toastElStub)
|
||||
.withArgs('installable-keyboards-mobile-download-dialog')
|
||||
.returns(downloadDialogElStub)
|
||||
.withArgs('installable-keyboards-remember')
|
||||
.returns(rememberMyChoiceElStub)
|
||||
.withArgs('installable-keyboards-enable-dialog')
|
||||
.returns(enableDialogElStub);
|
||||
|
||||
listView.start();
|
||||
|
||||
assert.isTrue(window.LayoutItemList.calledWith(app));
|
||||
assert.isTrue(layoutItemListStub.start.calledOnce);
|
||||
assert.equal(listView.container, containerElStub);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
listView.stop();
|
||||
|
||||
assert.isTrue(layoutItemListStub.stop.calledOnce);
|
||||
});
|
||||
|
||||
test('model.onready with 3 layoutItems', function() {
|
||||
var items = layoutItemListStub.layoutItems = new Map();
|
||||
items.set('foo', { id: 'foo' });
|
||||
items.set('bar', { id: 'bar' });
|
||||
items.set('baz', { id: 'baz' });
|
||||
|
||||
layoutItemListStub.onready();
|
||||
|
||||
assert.isFalse(containerElStub.hidden);
|
||||
var i = 0;
|
||||
items.forEach(function(item, id) {
|
||||
assert.isTrue(
|
||||
window.LayoutItemView.getCall(i).calledWith(listView, item));
|
||||
var viewStub = window.LayoutItemView.getCall(i).returnValue;
|
||||
assert.isTrue(viewStub.start.calledOnce);
|
||||
assert.equal(installedListElStub.appendChild.getCall(i).args[0],
|
||||
viewStub.container);
|
||||
|
||||
i++;
|
||||
}, this);
|
||||
});
|
||||
|
||||
test('Move one layoutItemView to installable', function() {
|
||||
var items = layoutItemListStub.layoutItems = new Map();
|
||||
items.set('foo', { id: 'foo' });
|
||||
items.set('bar', { id: 'bar' });
|
||||
items.set('baz', { id: 'baz' });
|
||||
|
||||
layoutItemListStub.onready();
|
||||
|
||||
assert.isFalse(containerElStub.hidden);
|
||||
|
||||
var viewStub0 = window.LayoutItemView.getCall(0).returnValue;
|
||||
var viewStub1 = window.LayoutItemView.getCall(1).returnValue;
|
||||
var viewStub2 = window.LayoutItemView.getCall(2).returnValue;
|
||||
|
||||
viewStub1.inList = viewStub1.IN_LIST_INSTALLABLE;
|
||||
viewStub1.oninlistchange();
|
||||
|
||||
// The entire list of views must be go through in order.
|
||||
assert.equal(installedListElStub.appendChild.getCall(3).args[0],
|
||||
viewStub0.container);
|
||||
assert.equal(installableListElStub.appendChild.getCall(0).args[0],
|
||||
viewStub1.container);
|
||||
assert.equal(installedListElStub.appendChild.getCall(4).args[0],
|
||||
viewStub2.container);
|
||||
});
|
||||
|
||||
test('model.onready with 0 layoutItems', function() {
|
||||
layoutItemListStub.layoutItems = new Map();
|
||||
|
||||
layoutItemListStub.onready();
|
||||
|
||||
assert.isTrue(containerElStub.hidden);
|
||||
assert.isFalse(window.LayoutItemView.calledOnce);
|
||||
});
|
||||
|
||||
suite('confirmRemoval', function() {
|
||||
var p;
|
||||
|
||||
setup(function() {
|
||||
listView.beforeShow();
|
||||
listView.show();
|
||||
|
||||
p = listView.confirmRemoval('Foo');
|
||||
|
||||
assert.isFalse(removeDialogElStub.hidden);
|
||||
assert.equal(removeDialogElStub.firstElementChild.dataset.l10nArgs,
|
||||
'{"keyboard":"Foo"}');
|
||||
});
|
||||
|
||||
test('cancel', function(done) {
|
||||
removeDialogElStub.dispatchEvent(new CustomEvent('cancel'));
|
||||
|
||||
p.then(function(val) {
|
||||
assert.isTrue(removeDialogElStub.hidden);
|
||||
assert.isFalse(val);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('confirm', function(done) {
|
||||
removeDialogElStub.dispatchEvent(new CustomEvent('confirm'));
|
||||
|
||||
p.then(function(val) {
|
||||
assert.isTrue(removeDialogElStub.hidden);
|
||||
assert.isTrue(val);
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('confirmDownload', function() {
|
||||
var p;
|
||||
|
||||
setup(function() {
|
||||
listView.beforeShow();
|
||||
listView.show();
|
||||
|
||||
p = listView.confirmDownload('Foo');
|
||||
|
||||
assert.isTrue(
|
||||
layoutItemListStub.downloadPreference.getCurrentState.calledOnce);
|
||||
});
|
||||
|
||||
suite('STATE_PROMPT', function() {
|
||||
setup(function(done) {
|
||||
downloadPreferenceGetCurrentStateDeferred
|
||||
.resolve(layoutItemListStub.downloadPreference.STATE_PROMPT);
|
||||
|
||||
downloadPreferenceGetCurrentStateDeferred.promise.then(function() {
|
||||
assert.isFalse(downloadDialogElStub.hidden);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
suite('remember my choice checked', function() {
|
||||
setup(function() {
|
||||
rememberMyChoiceElStub.checked = true;
|
||||
});
|
||||
|
||||
test('cancel', function(done) {
|
||||
downloadDialogElStub.dispatchEvent(new CustomEvent('cancel'));
|
||||
|
||||
p.then(function(val) {
|
||||
assert.isFalse(layoutItemListStub.downloadPreference
|
||||
.setDataConnectionDownloadState.calledOnce,
|
||||
'Should not remember cancelled dialog.');
|
||||
assert.isTrue(downloadDialogElStub.hidden);
|
||||
assert.isFalse(val);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('confirm', function(done) {
|
||||
downloadDialogElStub.dispatchEvent(new CustomEvent('confirm'));
|
||||
|
||||
p.then(function(val) {
|
||||
assert.isTrue(layoutItemListStub.downloadPreference
|
||||
.setDataConnectionDownloadState.calledWith(
|
||||
layoutItemListStub.downloadPreference.STATE_ALLOW));
|
||||
|
||||
// Should not be blocked by setDataConnectionDownloadState promise.
|
||||
assert.isTrue(downloadDialogElStub.hidden);
|
||||
assert.isTrue(val);
|
||||
|
||||
// Resolve the promise.
|
||||
downloadPreferenceSetDataConnectionDownloadStateDeferred.resolve();
|
||||
return downloadPreferenceSetDataConnectionDownloadStateDeferred
|
||||
.promise;
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('remember my choice not checked', function() {
|
||||
setup(function() {
|
||||
rememberMyChoiceElStub.checked = false;
|
||||
});
|
||||
|
||||
test('cancel', function(done) {
|
||||
downloadDialogElStub.dispatchEvent(new CustomEvent('cancel'));
|
||||
|
||||
p.then(function(val) {
|
||||
assert.isFalse(layoutItemListStub.downloadPreference
|
||||
.setDataConnectionDownloadState.calledOnce);
|
||||
assert.isTrue(downloadDialogElStub.hidden);
|
||||
assert.isFalse(val);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('confirm', function(done) {
|
||||
downloadDialogElStub.dispatchEvent(new CustomEvent('confirm'));
|
||||
|
||||
p.then(function(val) {
|
||||
assert.isFalse(layoutItemListStub.downloadPreference
|
||||
.setDataConnectionDownloadState.calledOnce);
|
||||
assert.isTrue(downloadDialogElStub.hidden);
|
||||
assert.isTrue(val);
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('STATE_ALLOW', function(done) {
|
||||
downloadPreferenceGetCurrentStateDeferred
|
||||
.resolve(layoutItemListStub.downloadPreference.STATE_ALLOW);
|
||||
|
||||
p.then(function(val) {
|
||||
assert.isTrue(downloadDialogElStub.hidden);
|
||||
assert.isTrue(val);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('STATE_DENY', function(done) {
|
||||
downloadPreferenceGetCurrentStateDeferred
|
||||
.resolve(layoutItemListStub.downloadPreference.STATE_DENY);
|
||||
|
||||
p.then(function(val) {
|
||||
assert.isTrue(downloadDialogElStub.hidden);
|
||||
assert.isFalse(val);
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('confirmEnable', function() {
|
||||
var p;
|
||||
|
||||
test('queue two dialogs', function(done) {
|
||||
assert.isTrue(enableDialogElStub.hidden);
|
||||
var p = listView.confirmEnable('Foo');
|
||||
var p2 = listView.confirmEnable('Bar');
|
||||
|
||||
assert.isTrue(enableDialogElStub.hidden);
|
||||
|
||||
listView.beforeShow();
|
||||
listView.show();
|
||||
|
||||
assert.isFalse(enableDialogElStub.hidden);
|
||||
assert.equal(enableDialogElStub.firstElementChild.dataset.l10nArgs,
|
||||
'{"keyboard":"Foo"}');
|
||||
|
||||
// Confirm the first dialog
|
||||
enableDialogElStub.dispatchEvent(new CustomEvent('confirm'));
|
||||
|
||||
p.then(function(val) {
|
||||
assert.isTrue(val, 'Confirm the first dialog.');
|
||||
|
||||
assert.isFalse(enableDialogElStub.hidden, 'Dialog shown');
|
||||
assert.equal(enableDialogElStub.firstElementChild.dataset.l10nArgs,
|
||||
'{"keyboard":"Bar"}');
|
||||
|
||||
// Cancel the second dialog
|
||||
enableDialogElStub.dispatchEvent(new CustomEvent('cancel'));
|
||||
|
||||
return p2;
|
||||
})
|
||||
.then(function(val) {
|
||||
assert.isTrue(enableDialogElStub.hidden, 'Dialog hidden');
|
||||
assert.isFalse(val, 'Cancel the second dialog');
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
suite('queue before visible', function() {
|
||||
setup(function() {
|
||||
p = listView.confirmEnable('Foo');
|
||||
assert.isTrue(enableDialogElStub.hidden);
|
||||
|
||||
listView.beforeShow();
|
||||
listView.show();
|
||||
|
||||
assert.isFalse(enableDialogElStub.hidden);
|
||||
assert.equal(enableDialogElStub.firstElementChild.dataset.l10nArgs,
|
||||
'{"keyboard":"Foo"}');
|
||||
});
|
||||
|
||||
test('cancel', function(done) {
|
||||
enableDialogElStub.dispatchEvent(new CustomEvent('cancel'));
|
||||
|
||||
p.then(function(val) {
|
||||
assert.isTrue(enableDialogElStub.hidden);
|
||||
assert.isFalse(val);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('confirm', function(done) {
|
||||
enableDialogElStub.dispatchEvent(new CustomEvent('confirm'));
|
||||
|
||||
p.then(function(val) {
|
||||
assert.isTrue(enableDialogElStub.hidden);
|
||||
assert.isTrue(val);
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('show while visible', function() {
|
||||
setup(function() {
|
||||
assert.isTrue(enableDialogElStub.hidden);
|
||||
listView.beforeShow();
|
||||
listView.show();
|
||||
|
||||
p = listView.confirmEnable('Foo');
|
||||
|
||||
assert.isFalse(enableDialogElStub.hidden);
|
||||
assert.equal(enableDialogElStub.firstElementChild.dataset.l10nArgs,
|
||||
'{"keyboard":"Foo"}');
|
||||
});
|
||||
|
||||
test('cancel', function(done) {
|
||||
enableDialogElStub.dispatchEvent(new CustomEvent('cancel'));
|
||||
|
||||
p.then(function(val) {
|
||||
assert.isTrue(enableDialogElStub.hidden);
|
||||
assert.isFalse(val);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('confirm', function(done) {
|
||||
enableDialogElStub.dispatchEvent(new CustomEvent('confirm'));
|
||||
|
||||
p.then(function(val) {
|
||||
assert.isTrue(enableDialogElStub.hidden);
|
||||
assert.isTrue(val);
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('disableLayout', function() {
|
||||
layoutItemListStub.layoutEnabler.disableLayout.returns({ stub: 'promise' });
|
||||
|
||||
var p = listView.disableLayout('foo');
|
||||
assert.deepEqual(p, { stub: 'promise' });
|
||||
});
|
||||
|
||||
test('enableLayout', function() {
|
||||
layoutItemListStub.layoutEnabler.enableLayout.returns({ stub: 'promise' });
|
||||
|
||||
var p = listView.enableLayout('foo');
|
||||
assert.deepEqual(p, { stub: 'promise' });
|
||||
});
|
||||
|
||||
suite('showDownloadErrorToast', function() {
|
||||
test('call before the panel is shown', function() {
|
||||
listView.showDownloadErrorToast();
|
||||
|
||||
assert.isFalse(toastElStub.show.calledOnce);
|
||||
});
|
||||
|
||||
test('call after the panel is shown', function() {
|
||||
listView.beforeShow();
|
||||
listView.show();
|
||||
|
||||
listView.showDownloadErrorToast();
|
||||
|
||||
assert.isTrue(toastElStub.show.calledOnce);
|
||||
});
|
||||
|
||||
test('hide the panel', function() {
|
||||
listView.beforeHide();
|
||||
listView.hide();
|
||||
|
||||
assert.isTrue(toastElStub.hide.calledOnce);
|
||||
});
|
||||
});
|
||||
});
|
||||
548
gaia/apps/keyboard/test/unit/settings/layout_item_test.js
Executable file
548
gaia/apps/keyboard/test/unit/settings/layout_item_test.js
Executable file
|
|
@ -0,0 +1,548 @@
|
|||
'use strict';
|
||||
|
||||
/* global LayoutDictionary, LayoutDictionaryList,
|
||||
LayoutItemList, LayoutItem,
|
||||
CloseLockManager, CloseLock,
|
||||
MockInputMethod */
|
||||
|
||||
require('/js/settings/layout_item.js');
|
||||
require('/js/settings/layout_item_list.js');
|
||||
require('/js/settings/layout_dictionary.js');
|
||||
require('/js/settings/layout_dictionary_list.js');
|
||||
|
||||
require('/js/settings/close_locks.js');
|
||||
require('/shared/test/unit/mocks/mock_event_target.js');
|
||||
require('/shared/test/unit/mocks/mock_navigator_input_method.js');
|
||||
|
||||
suite('LayoutItem', function() {
|
||||
var item;
|
||||
|
||||
var layoutItemListStub;
|
||||
var layoutDictionaryStub;
|
||||
|
||||
var setLayoutAsInstalledDeferred;
|
||||
var setLayoutAsUninstalledDeferred;
|
||||
|
||||
var closeLockStub;
|
||||
|
||||
var installForLayoutDeferred;
|
||||
var removeForLayoutDeferred;
|
||||
|
||||
var realMozInputMethod;
|
||||
|
||||
var addInputDeferred;
|
||||
var removeInputDeferred;
|
||||
|
||||
// Deferred is an object containing a promise with it's resolve/reject
|
||||
// methods exposed.
|
||||
// See
|
||||
// https://developer.mozilla.org/docs/Mozilla/JavaScript_code_modules
|
||||
// /Promise.jsm/Deferred
|
||||
//
|
||||
// For it's interfaces.
|
||||
var Deferred = function() {
|
||||
this.promise = new Promise(function(resolve, reject) {
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
setup(function() {
|
||||
addInputDeferred = new Deferred();
|
||||
removeInputDeferred = new Deferred();
|
||||
|
||||
realMozInputMethod = navigator.mozInputMethod;
|
||||
navigator.mozInputMethod = new MockInputMethod();
|
||||
this.sinon.stub(navigator.mozInputMethod, 'addInput')
|
||||
.returns(addInputDeferred.promise);
|
||||
this.sinon.stub(navigator.mozInputMethod, 'removeInput')
|
||||
.returns(removeInputDeferred.promise);
|
||||
|
||||
installForLayoutDeferred = new Deferred();
|
||||
removeForLayoutDeferred = new Deferred();
|
||||
layoutDictionaryStub =
|
||||
this.sinon.stub(Object.create(LayoutDictionary.prototype));
|
||||
layoutDictionaryStub.installForLayout
|
||||
.returns(installForLayoutDeferred.promise);
|
||||
layoutDictionaryStub.removeForLayout
|
||||
.returns(removeForLayoutDeferred.promise);
|
||||
|
||||
layoutItemListStub =
|
||||
this.sinon.stub(Object.create(LayoutItemList.prototype));
|
||||
layoutItemListStub.dictionaryList =
|
||||
this.sinon.stub(Object.create(LayoutDictionaryList.prototype));
|
||||
layoutItemListStub.dictionaryList
|
||||
.getDictionary.returns(layoutDictionaryStub);
|
||||
layoutItemListStub.closeLockManager =
|
||||
this.sinon.stub(Object.create(CloseLockManager.prototype));
|
||||
|
||||
closeLockStub = this.sinon.stub(Object.create(CloseLock.prototype));
|
||||
layoutItemListStub.closeLockManager.requestLock.returns(closeLockStub);
|
||||
|
||||
setLayoutAsInstalledDeferred = new Deferred();
|
||||
setLayoutAsUninstalledDeferred = new Deferred();
|
||||
|
||||
layoutItemListStub.setLayoutAsInstalled
|
||||
.returns(setLayoutAsInstalledDeferred.promise);
|
||||
layoutItemListStub.setLayoutAsUninstalled
|
||||
.returns(setLayoutAsUninstalledDeferred.promise);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
item.stop();
|
||||
});
|
||||
|
||||
test('Preloaded layout', function() {
|
||||
var layout = {
|
||||
'id': 'fr',
|
||||
'name': 'Français',
|
||||
'nameL10nId': 'french',
|
||||
'imEngineId': 'latin',
|
||||
'preloaded': true,
|
||||
'installed': true,
|
||||
'dictFilename': 'fr.dict',
|
||||
'dictFilePath': 'dictionaries/fr.dict',
|
||||
'dictFileSize': 1874745,
|
||||
'types': ['email', 'password', 'text', 'url']
|
||||
};
|
||||
|
||||
item = new LayoutItem(layoutItemListStub, layout);
|
||||
item.start();
|
||||
|
||||
assert.equal(item.id, layout.id);
|
||||
assert.equal(item.name, layout.name);
|
||||
assert.equal(item.nameL10nId, layout.nameL10nId);
|
||||
assert.equal(item.imEngineId, layout.imEngineId);
|
||||
assert.equal(item.dictFilePath, layout.dictFilePath);
|
||||
assert.equal(item.fileSize, layout.dictFileSize);
|
||||
assert.deepEqual(item.types, layout.types);
|
||||
|
||||
assert.equal(item.state, item.STATE_PRELOADED);
|
||||
assert.isFalse(layoutItemListStub.dictionaryList.getDictionary.calledOnce);
|
||||
});
|
||||
|
||||
suite('Install', function() {
|
||||
var p;
|
||||
|
||||
setup(function() {
|
||||
var layout = {
|
||||
'id': 'fr',
|
||||
'name': 'Français',
|
||||
'imEngineId': 'latin',
|
||||
'preloaded': false,
|
||||
'installed': false,
|
||||
'dictFilename': 'fr.dict',
|
||||
'dictFilePath': 'dictionaries/fr.dict',
|
||||
'dictFileSize': 1874745,
|
||||
'types': ['email', 'password', 'text', 'url']
|
||||
};
|
||||
|
||||
item = new LayoutItem(layoutItemListStub, layout);
|
||||
item.onstatechange = this.sinon.stub();
|
||||
item.onprogress = this.sinon.stub();
|
||||
item.oninstall = this.sinon.stub();
|
||||
item.onerror = this.sinon.stub();
|
||||
item.start();
|
||||
|
||||
assert.equal(item.state, item.STATE_INSTALLABLE);
|
||||
assert.isTrue(layoutItemListStub.dictionaryList
|
||||
.getDictionary.calledWith('latin', 'dictionaries/fr.dict'));
|
||||
layoutDictionaryStub.state =
|
||||
layoutDictionaryStub.STATE_INSTALLING_CANCELLABLE;
|
||||
layoutDictionaryStub.downloadLoadedSize = 5;
|
||||
layoutDictionaryStub.downloadTotalSize = 100;
|
||||
|
||||
p = item.install();
|
||||
|
||||
assert.isTrue(layoutItemListStub.closeLockManager
|
||||
.requestLock.calledWith('stayAwake'));
|
||||
});
|
||||
|
||||
test('Sucessful installation', function(done) {
|
||||
Promise.resolve().then(function() {
|
||||
assert.equal(
|
||||
layoutDictionaryStub.installForLayout.firstCall.args[0], item);
|
||||
assert.equal(item.onstatechange.callCount, 1);
|
||||
assert.equal(item.state, item.STATE_INSTALLING_CANCELLABLE);
|
||||
|
||||
assert.equal(item.downloadLoadedSize, 5);
|
||||
assert.equal(item.downloadTotalSize, 100);
|
||||
|
||||
layoutDictionaryStub.downloadLoadedSize = 10;
|
||||
item.updateInstallProgress(10, 100);
|
||||
assert.isTrue(item.onprogress.calledWith(10, 100));
|
||||
|
||||
assert.equal(item.downloadLoadedSize, 10);
|
||||
assert.equal(item.downloadTotalSize, 100);
|
||||
|
||||
installForLayoutDeferred.resolve();
|
||||
return installForLayoutDeferred.promise;
|
||||
}).then(function() {
|
||||
assert.equal(item.onstatechange.callCount, 2);
|
||||
assert.equal(item.state, item.STATE_INSTALLING);
|
||||
|
||||
assert.equal(item.downloadLoadedSize, 0);
|
||||
assert.equal(item.downloadTotalSize, 0);
|
||||
|
||||
assert.isTrue(navigator.mozInputMethod.addInput.calledWith('fr', {
|
||||
launch_path: '/index.html#fr',
|
||||
name: 'Français',
|
||||
description: 'Français',
|
||||
types: ['email', 'password', 'text', 'url']
|
||||
}));
|
||||
|
||||
addInputDeferred.resolve();
|
||||
|
||||
return addInputDeferred.promise;
|
||||
}).then(function() {
|
||||
assert.isTrue(layoutItemListStub.setLayoutAsInstalled.calledWith('fr'));
|
||||
|
||||
setLayoutAsInstalledDeferred.resolve();
|
||||
assert.isFalse(closeLockStub.unlock.calledOnce,
|
||||
'Not called before setLayoutAsInstalled resolves');
|
||||
|
||||
return p;
|
||||
}).then(function() {
|
||||
assert.equal(item.onstatechange.callCount, 3);
|
||||
assert.equal(item.state, item.STATE_INSTALLED);
|
||||
assert.isTrue(closeLockStub.unlock.calledOnce);
|
||||
|
||||
assert.isTrue(item.oninstall.calledOnce);
|
||||
|
||||
assert.isFalse(item.onerror.called);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('Dictionary install fail', function(done) {
|
||||
Promise.resolve().then(function() {
|
||||
assert.equal(
|
||||
layoutDictionaryStub.installForLayout.firstCall.args[0], item);
|
||||
assert.equal(item.onstatechange.callCount, 1);
|
||||
assert.equal(item.state, item.STATE_INSTALLING_CANCELLABLE);
|
||||
|
||||
installForLayoutDeferred.reject('mocked reject');
|
||||
return p;
|
||||
}).then(function() {
|
||||
throw 'Should not resolve.';
|
||||
}, function(e) {
|
||||
if (e && e !== 'mocked reject') { throw e; }
|
||||
|
||||
assert.isFalse(navigator.mozInputMethod.addInput.calledOnce);
|
||||
assert.isFalse(layoutItemListStub.setLayoutAsInstalled.calledOnce);
|
||||
assert.isTrue(closeLockStub.unlock.calledOnce);
|
||||
|
||||
assert.equal(item.onstatechange.callCount, 2);
|
||||
assert.equal(item.state, item.STATE_INSTALLABLE);
|
||||
|
||||
assert.isTrue(item.onerror.calledOnce);
|
||||
assert.isFalse(item.oninstall.calledOnce);
|
||||
assert.equal(
|
||||
item.onerror.firstCall.args[0].error,
|
||||
item.onerror.firstCall.args[0].ERROR_DOWNLOADERROR);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('Registration fail', function(done) {
|
||||
Promise.resolve().then(function() {
|
||||
assert.equal(
|
||||
layoutDictionaryStub.installForLayout.firstCall.args[0], item);
|
||||
assert.equal(item.onstatechange.callCount, 1);
|
||||
assert.equal(item.state, item.STATE_INSTALLING_CANCELLABLE);
|
||||
|
||||
installForLayoutDeferred.resolve();
|
||||
return installForLayoutDeferred.promise;
|
||||
}).then(function() {
|
||||
assert.equal(item.onstatechange.callCount, 2);
|
||||
assert.equal(item.state, item.STATE_INSTALLING);
|
||||
|
||||
assert.isTrue(navigator.mozInputMethod.addInput.calledWith('fr', {
|
||||
launch_path: '/index.html#fr',
|
||||
name: 'Français',
|
||||
description: 'Français',
|
||||
types: ['email', 'password', 'text', 'url']
|
||||
}));
|
||||
|
||||
addInputDeferred.reject('Failed');
|
||||
|
||||
return p;
|
||||
}).then(function() {
|
||||
throw 'Should not resolve.';
|
||||
}, function(e) {
|
||||
if (e && e !== 'Failed') { throw e; }
|
||||
|
||||
assert.isFalse(layoutItemListStub.setLayoutAsInstalled.calledOnce);
|
||||
assert.isTrue(closeLockStub.unlock.calledOnce);
|
||||
|
||||
assert.equal(item.onstatechange.callCount, 3);
|
||||
assert.equal(item.state, item.STATE_INSTALLABLE);
|
||||
|
||||
assert.equal(item.downloadLoadedSize, 0);
|
||||
assert.equal(item.downloadTotalSize, 0);
|
||||
|
||||
assert.isTrue(item.onerror.calledOnce);
|
||||
assert.isFalse(item.oninstall.calledOnce);
|
||||
assert.equal(
|
||||
item.onerror.firstCall.args[0].error,
|
||||
item.onerror.firstCall.args[0].ERROR_INSTALLERROR);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('Set install fail', function(done) {
|
||||
Promise.resolve().then(function() {
|
||||
assert.equal(
|
||||
layoutDictionaryStub.installForLayout.firstCall.args[0], item);
|
||||
assert.equal(item.onstatechange.callCount, 1);
|
||||
assert.equal(item.state, item.STATE_INSTALLING_CANCELLABLE);
|
||||
|
||||
installForLayoutDeferred.resolve();
|
||||
return installForLayoutDeferred.promise;
|
||||
}).then(function() {
|
||||
assert.equal(item.onstatechange.callCount, 2);
|
||||
assert.equal(item.state, item.STATE_INSTALLING);
|
||||
|
||||
assert.isTrue(navigator.mozInputMethod.addInput.calledWith('fr', {
|
||||
launch_path: '/index.html#fr',
|
||||
name: 'Français',
|
||||
description: 'Français',
|
||||
types: ['email', 'password', 'text', 'url']
|
||||
}));
|
||||
|
||||
addInputDeferred.resolve();
|
||||
|
||||
return addInputDeferred.promise;
|
||||
}).then(function() {
|
||||
assert.isTrue(layoutItemListStub.setLayoutAsInstalled.calledWith('fr'));
|
||||
|
||||
setLayoutAsInstalledDeferred.reject('Failed');
|
||||
|
||||
return p;
|
||||
}).then(function() {
|
||||
throw 'Should not resolve.';
|
||||
}, function(e) {
|
||||
if (e && e !== 'Failed') { throw e; }
|
||||
|
||||
assert.equal(item.onstatechange.callCount, 3);
|
||||
assert.equal(item.state, item.STATE_INSTALLABLE);
|
||||
assert.equal(item.downloadLoadedSize, 0);
|
||||
assert.equal(item.downloadTotalSize, 0);
|
||||
assert.isTrue(closeLockStub.unlock.calledOnce);
|
||||
|
||||
assert.isTrue(item.onerror.calledOnce);
|
||||
assert.isFalse(item.oninstall.calledOnce);
|
||||
assert.equal(
|
||||
item.onerror.firstCall.args[0].error,
|
||||
item.onerror.firstCall.args[0].ERROR_INSTALLERROR);
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('remove', function() {
|
||||
var p;
|
||||
|
||||
setup(function() {
|
||||
var layout = {
|
||||
'id': 'fr',
|
||||
'name': 'Français',
|
||||
'imEngineId': 'latin',
|
||||
'preloaded': false,
|
||||
'installed': true,
|
||||
'dictFilename': 'fr.dict',
|
||||
'dictFilePath': 'dictionaries/fr.dict',
|
||||
'dictFileSize': 1874745,
|
||||
'types': ['email', 'password', 'text', 'url']
|
||||
};
|
||||
|
||||
item = new LayoutItem(layoutItemListStub, layout);
|
||||
item.onstatechange = this.sinon.stub();
|
||||
item.onprogress = this.sinon.stub();
|
||||
item.onerror = this.sinon.stub();
|
||||
item.start();
|
||||
|
||||
assert.equal(item.state, item.STATE_INSTALLED);
|
||||
assert.isTrue(layoutItemListStub.dictionaryList
|
||||
.getDictionary.calledWith('latin', 'dictionaries/fr.dict'));
|
||||
layoutDictionaryStub.state =
|
||||
layoutDictionaryStub.STATE_INSTALLED;
|
||||
|
||||
p = item.remove();
|
||||
|
||||
assert.isTrue(layoutItemListStub.closeLockManager
|
||||
.requestLock.calledWith('stayAwake'));
|
||||
});
|
||||
|
||||
test('Sucessful removal', function(done) {
|
||||
Promise.resolve().then(function() {
|
||||
assert.equal(item.onstatechange.callCount, 1);
|
||||
assert.equal(item.state, item.STATE_REMOVING);
|
||||
|
||||
assert.isTrue(navigator.mozInputMethod.removeInput.calledWith('fr'));
|
||||
|
||||
removeInputDeferred.resolve();
|
||||
return removeInputDeferred.promise;
|
||||
}).then(function() {
|
||||
assert.isTrue(
|
||||
layoutItemListStub.setLayoutAsUninstalled.calledWith('fr'));
|
||||
setLayoutAsUninstalledDeferred.resolve();
|
||||
|
||||
return setLayoutAsUninstalledDeferred.promise;
|
||||
}).then(function() {
|
||||
assert.equal(
|
||||
layoutDictionaryStub.removeForLayout.firstCall.args[0], item);
|
||||
removeForLayoutDeferred.resolve();
|
||||
|
||||
assert.isFalse(closeLockStub.unlock.calledOnce,
|
||||
'Not called before removeForLayout resolves');
|
||||
|
||||
return p;
|
||||
}).then(function() {
|
||||
assert.equal(item.onstatechange.callCount, 2);
|
||||
assert.equal(item.state, item.STATE_INSTALLABLE);
|
||||
assert.isTrue(closeLockStub.unlock.calledOnce);
|
||||
assert.isFalse(item.onerror.called);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('Registration fail', function(done) {
|
||||
Promise.resolve().then(function() {
|
||||
assert.equal(item.onstatechange.callCount, 1);
|
||||
assert.equal(item.state, item.STATE_REMOVING);
|
||||
|
||||
assert.isTrue(navigator.mozInputMethod.removeInput.calledWith('fr'));
|
||||
|
||||
removeInputDeferred.reject('mocked reject');
|
||||
|
||||
return p;
|
||||
}).then(function() {
|
||||
throw 'Should not resolve.';
|
||||
}, function() {
|
||||
assert.isFalse(layoutDictionaryStub.removeForLayout.calledOnce);
|
||||
assert.isFalse(layoutItemListStub.setLayoutAsUninstalled.calledOnce);
|
||||
|
||||
assert.equal(item.onstatechange.callCount, 2);
|
||||
assert.equal(item.state, item.STATE_INSTALLED);
|
||||
assert.isTrue(closeLockStub.unlock.calledOnce);
|
||||
|
||||
assert.isTrue(item.onerror.calledOnce);
|
||||
assert.equal(
|
||||
item.onerror.firstCall.args[0].error,
|
||||
item.onerror.firstCall.args[0].ERROR_REMOVEERROR);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('Set uninstall fail', function(done) {
|
||||
Promise.resolve().then(function() {
|
||||
assert.equal(item.onstatechange.callCount, 1);
|
||||
assert.equal(item.state, item.STATE_REMOVING);
|
||||
|
||||
assert.isTrue(navigator.mozInputMethod.removeInput.calledWith('fr'));
|
||||
|
||||
removeInputDeferred.resolve();
|
||||
|
||||
return removeInputDeferred.promise;
|
||||
}).then(function() {
|
||||
assert.isTrue(
|
||||
layoutItemListStub.setLayoutAsUninstalled.calledWith('fr'));
|
||||
setLayoutAsUninstalledDeferred.reject('Failed');
|
||||
|
||||
return setLayoutAsUninstalledDeferred.promise
|
||||
.catch(function() {});
|
||||
}).then(function() {
|
||||
assert.equal(
|
||||
layoutDictionaryStub.removeForLayout.firstCall.args[0], item);
|
||||
removeForLayoutDeferred.resolve();
|
||||
|
||||
return p;
|
||||
}).then(function() {
|
||||
assert.equal(item.onstatechange.callCount, 2);
|
||||
assert.equal(item.state, item.STATE_INSTALLABLE);
|
||||
assert.isTrue(closeLockStub.unlock.calledOnce);
|
||||
assert.isFalse(item.onerror.called);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('Dictionary uninstall fail', function(done) {
|
||||
Promise.resolve().then(function() {
|
||||
assert.equal(item.onstatechange.callCount, 1);
|
||||
assert.equal(item.state, item.STATE_REMOVING);
|
||||
|
||||
assert.isTrue(navigator.mozInputMethod.removeInput.calledWith('fr'));
|
||||
|
||||
removeInputDeferred.resolve();
|
||||
|
||||
return removeInputDeferred.promise;
|
||||
}).then(function() {
|
||||
assert.isTrue(
|
||||
layoutItemListStub.setLayoutAsUninstalled.calledWith('fr'));
|
||||
setLayoutAsUninstalledDeferred.resolve();
|
||||
|
||||
return setLayoutAsUninstalledDeferred.promise;
|
||||
}).then(function() {
|
||||
assert.equal(
|
||||
layoutDictionaryStub.removeForLayout.firstCall.args[0], item);
|
||||
removeForLayoutDeferred.reject('Failed');
|
||||
|
||||
return p;
|
||||
}).then(function() {
|
||||
assert.equal(item.onstatechange.callCount, 2);
|
||||
assert.equal(item.state, item.STATE_INSTALLABLE);
|
||||
assert.isTrue(closeLockStub.unlock.calledOnce);
|
||||
assert.isFalse(item.onerror.called);
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('cancelInstall', function() {
|
||||
var p;
|
||||
setup(function(done) {
|
||||
var layout = {
|
||||
'id': 'fr',
|
||||
'name': 'Français',
|
||||
'imEngineId': 'latin',
|
||||
'preloaded': false,
|
||||
'installed': false,
|
||||
'dictFilename': 'fr.dict',
|
||||
'dictFilePath': 'dictionaries/fr.dict',
|
||||
'dictFileSize': 1874745,
|
||||
'types': ['email', 'password', 'text', 'url']
|
||||
};
|
||||
|
||||
item = new LayoutItem(layoutItemListStub, layout);
|
||||
item.onstatechange = this.sinon.stub();
|
||||
item.onprogress = this.sinon.stub();
|
||||
item.onerror = this.sinon.stub();
|
||||
item.start();
|
||||
|
||||
assert.equal(item.state, item.STATE_INSTALLABLE);
|
||||
assert.isTrue(layoutItemListStub.dictionaryList
|
||||
.getDictionary.calledWith('latin', 'dictionaries/fr.dict'));
|
||||
layoutDictionaryStub.state =
|
||||
layoutDictionaryStub.STATE_INSTALLING_CANCELLABLE;
|
||||
|
||||
p = item.install();
|
||||
Promise.resolve().then(function() {
|
||||
assert.equal(item.state, item.STATE_INSTALLING_CANCELLABLE);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('cancelInstall', function(done) {
|
||||
item.cancelInstall();
|
||||
|
||||
assert.isTrue(layoutDictionaryStub.removeForLayout.calledWith(item));
|
||||
removeForLayoutDeferred.resolve();
|
||||
installForLayoutDeferred.reject('mocked reject');
|
||||
|
||||
p.then(function() {
|
||||
throw 'Should not resolve';
|
||||
}, function() {
|
||||
assert.equal(item.state, item.STATE_INSTALLABLE);
|
||||
assert.isTrue(closeLockStub.unlock.calledOnce);
|
||||
|
||||
assert.equal(item.downloadLoadedSize, 0);
|
||||
assert.equal(item.downloadTotalSize, 0);
|
||||
|
||||
assert.isFalse(item.onerror.called);
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
319
gaia/apps/keyboard/test/unit/settings/layout_item_view_test.js
Executable file
319
gaia/apps/keyboard/test/unit/settings/layout_item_view_test.js
Executable file
|
|
@ -0,0 +1,319 @@
|
|||
'use strict';
|
||||
|
||||
/* global LayoutItemListView, LayoutItemView, LayoutItem, LayoutItemErrorInfo */
|
||||
|
||||
require('/js/settings/base_view.js');
|
||||
|
||||
require('/js/settings/layout_item.js');
|
||||
require('/js/settings/layout_item_view.js');
|
||||
require('/js/settings/layout_item_list_view.js');
|
||||
|
||||
var Deferred = function() {
|
||||
this.promise = new Promise(function(resolve, reject) {
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
suite('LayoutItemView', function() {
|
||||
var view;
|
||||
|
||||
var itemStub;
|
||||
var listViewStub;
|
||||
|
||||
var labelEl;
|
||||
var statusEl;
|
||||
var progressEl;
|
||||
|
||||
var confirmRemovalDeferred;
|
||||
var confirmDownloadDeferred;
|
||||
var confirmEnableDeferred;
|
||||
var disableLayoutDeferred;
|
||||
var enableLayoutDeferred;
|
||||
|
||||
setup(function() {
|
||||
navigator.mozL10n = {
|
||||
get: this.sinon.spy(function(id) { return 'l10n_get_' + id; })
|
||||
};
|
||||
|
||||
itemStub = this.sinon.stub(LayoutItem.prototype);
|
||||
itemStub.id = 'foo';
|
||||
itemStub.name = 'Pig Latin';
|
||||
itemStub.nameL10nId = 'pig-latin';
|
||||
itemStub.fileSize = 24601;
|
||||
itemStub.state = itemStub.STATE_PRELOADED;
|
||||
|
||||
confirmRemovalDeferred = new Deferred();
|
||||
confirmDownloadDeferred = new Deferred();
|
||||
confirmEnableDeferred = new Deferred();
|
||||
disableLayoutDeferred = new Deferred();
|
||||
enableLayoutDeferred = new Deferred();
|
||||
|
||||
listViewStub = this.sinon.stub(Object.create(LayoutItemListView.prototype));
|
||||
listViewStub.confirmRemoval.returns(confirmRemovalDeferred.promise);
|
||||
listViewStub.confirmDownload.returns(confirmDownloadDeferred.promise);
|
||||
listViewStub.confirmEnable.returns(confirmEnableDeferred.promise);
|
||||
listViewStub.disableLayout.returns(disableLayoutDeferred.promise);
|
||||
listViewStub.enableLayout.returns(enableLayoutDeferred.promise);
|
||||
|
||||
view = new LayoutItemView(listViewStub, itemStub);
|
||||
|
||||
var templateEl = document.createElement('template');
|
||||
templateEl.innerHTML =
|
||||
'<div><p class="label"></p>' +
|
||||
'<p><small class="status"></small></p>' +
|
||||
'<progress class="progress" hidden></div>';
|
||||
|
||||
this.sinon.stub(document, 'getElementById')
|
||||
.withArgs(view.TEMPLATE_ID).returns(templateEl);
|
||||
|
||||
view.oninlistchange = this.sinon.stub();
|
||||
|
||||
view.start();
|
||||
|
||||
labelEl = view.container.querySelector('.label');
|
||||
statusEl = view.container.querySelector('.status');
|
||||
progressEl = view.container.querySelector('.progress');
|
||||
|
||||
assert.equal(labelEl.textContent, 'Pig Latin');
|
||||
assert.equal(labelEl.dataset.l10nId, 'pig-latin');
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
navigator.mozL10n = null;
|
||||
});
|
||||
|
||||
test('STATE_PRELOADED', function() {
|
||||
assert.equal(view.inList, view.IN_LIST_INSTALLED);
|
||||
assert.equal(view.oninlistchange.callCount, 0);
|
||||
assert.equal(view.container.dataset.enabledAction, 'none');
|
||||
assert.equal(statusEl.dataset.l10nId, 'preInstalledStatus');
|
||||
assert.equal(statusEl.dataset.l10nArgs,
|
||||
'{"size":"24.02","sizeUnit":"l10n_get_byteUnit-KB"}');
|
||||
assert.isTrue(progressEl.classList.contains('hide'));
|
||||
});
|
||||
|
||||
test('STATE_INSTALLABLE', function() {
|
||||
itemStub.state = itemStub.STATE_INSTALLABLE;
|
||||
itemStub.onstatechange();
|
||||
|
||||
assert.equal(view.inList, view.IN_LIST_INSTALLABLE);
|
||||
assert.equal(view.oninlistchange.callCount, 1);
|
||||
assert.equal(view.container.dataset.enabledAction, 'download');
|
||||
assert.equal(statusEl.dataset.l10nId, 'installableStatus');
|
||||
assert.equal(statusEl.dataset.l10nArgs,
|
||||
'{"size":"24.02","sizeUnit":"l10n_get_byteUnit-KB"}');
|
||||
assert.isTrue(progressEl.classList.contains('hide'));
|
||||
});
|
||||
|
||||
test('STATE_INSTALLING_CANCELLABLE', function() {
|
||||
itemStub.state = itemStub.STATE_INSTALLING_CANCELLABLE;
|
||||
itemStub.downloadLoadedSize = 1234;
|
||||
itemStub.downloadTotalSize = 24601;
|
||||
|
||||
itemStub.onstatechange();
|
||||
|
||||
assert.equal(view.inList, view.IN_LIST_INSTALLABLE);
|
||||
assert.equal(view.oninlistchange.callCount, 1);
|
||||
assert.equal(view.container.dataset.enabledAction, 'cancel-download');
|
||||
assert.equal(statusEl.dataset.l10nId, 'downloadingStatus');
|
||||
assert.equal(statusEl.dataset.l10nArgs,
|
||||
'{"loadedSize":"1.21","loadedSizeUnit":"l10n_get_byteUnit-KB",' +
|
||||
'"totalSize":"24.02","totalSizeUnit":"l10n_get_byteUnit-KB"}');
|
||||
assert.isFalse(progressEl.classList.contains('hide'));
|
||||
assert.equal(progressEl.value, '1234');
|
||||
assert.equal(progressEl.max, '24601');
|
||||
|
||||
itemStub.downloadLoadedSize = 2345;
|
||||
|
||||
itemStub.onprogress();
|
||||
|
||||
assert.equal(view.oninlistchange.callCount, 1);
|
||||
assert.equal(view.container.dataset.enabledAction, 'cancel-download');
|
||||
assert.equal(statusEl.dataset.l10nId, 'downloadingStatus');
|
||||
assert.equal(statusEl.dataset.l10nArgs,
|
||||
'{"loadedSize":"2.29","loadedSizeUnit":"l10n_get_byteUnit-KB",' +
|
||||
'"totalSize":"24.02","totalSizeUnit":"l10n_get_byteUnit-KB"}');
|
||||
assert.isFalse(progressEl.classList.contains('hide'));
|
||||
assert.equal(progressEl.value, '2345');
|
||||
assert.equal(progressEl.max, '24601');
|
||||
});
|
||||
|
||||
test('STATE_INSTALLING', function() {
|
||||
itemStub.state = itemStub.STATE_INSTALLING;
|
||||
|
||||
itemStub.onstatechange();
|
||||
|
||||
assert.equal(view.inList, view.IN_LIST_INSTALLABLE);
|
||||
assert.equal(view.oninlistchange.callCount, 1);
|
||||
assert.equal(view.container.dataset.enabledAction, 'none');
|
||||
assert.equal(statusEl.dataset.l10nId, 'downloadingStatus');
|
||||
assert.equal(statusEl.dataset.l10nArgs,
|
||||
'{"loadedSize":"24.02","loadedSizeUnit":"l10n_get_byteUnit-KB",' +
|
||||
'"totalSize":"24.02","totalSizeUnit":"l10n_get_byteUnit-KB"}');
|
||||
assert.isFalse(progressEl.classList.contains('hide'));
|
||||
assert.equal(progressEl.value, '24601');
|
||||
assert.equal(progressEl.max, '24601');
|
||||
});
|
||||
|
||||
test('STATE_INSTALLED', function() {
|
||||
itemStub.state = itemStub.STATE_INSTALLED;
|
||||
itemStub.onstatechange();
|
||||
|
||||
assert.equal(view.inList, view.IN_LIST_INSTALLED);
|
||||
assert.equal(view.oninlistchange.callCount, 0);
|
||||
assert.equal(view.container.dataset.enabledAction, 'remove');
|
||||
assert.equal(statusEl.dataset.l10nId, 'installedStatus');
|
||||
assert.equal(statusEl.dataset.l10nArgs,
|
||||
'{"size":"24.02","sizeUnit":"l10n_get_byteUnit-KB"}');
|
||||
assert.isTrue(progressEl.classList.contains('hide'));
|
||||
});
|
||||
|
||||
test('STATE_REMOVING', function() {
|
||||
itemStub.state = itemStub.STATE_REMOVING;
|
||||
itemStub.onstatechange();
|
||||
|
||||
assert.equal(view.inList, view.IN_LIST_INSTALLED);
|
||||
assert.equal(view.oninlistchange.callCount, 0);
|
||||
assert.equal(view.container.dataset.enabledAction, 'none');
|
||||
assert.equal(statusEl.dataset.l10nId, 'removingStatus');
|
||||
assert.equal(statusEl.dataset.l10nArgs, undefined);
|
||||
assert.isTrue(progressEl.classList.contains('hide'));
|
||||
});
|
||||
|
||||
suite('handleEvent', function() {
|
||||
suite('download', function() {
|
||||
setup(function() {
|
||||
var evt = {
|
||||
type: 'click',
|
||||
target: {
|
||||
dataset: {
|
||||
action: 'download'
|
||||
}
|
||||
}
|
||||
};
|
||||
view.handleEvent(evt);
|
||||
|
||||
assert.isTrue(listViewStub.confirmDownload.calledOnce);
|
||||
});
|
||||
|
||||
test('confirmed', function(done) {
|
||||
confirmDownloadDeferred.resolve(true);
|
||||
confirmDownloadDeferred.promise.then(function() {
|
||||
assert.isTrue(itemStub.install.calledOnce);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('cancelled', function(done) {
|
||||
confirmDownloadDeferred.resolve(false);
|
||||
confirmDownloadDeferred.promise.then(function() {
|
||||
assert.isFalse(itemStub.install.calledOnce);
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
test('cancelDownload', function() {
|
||||
var evt = {
|
||||
type: 'click',
|
||||
target: {
|
||||
dataset: {
|
||||
action: 'cancelDownload'
|
||||
}
|
||||
}
|
||||
};
|
||||
view.handleEvent(evt);
|
||||
|
||||
assert.isTrue(itemStub.cancelInstall.calledOnce);
|
||||
});
|
||||
|
||||
suite('remove', function() {
|
||||
setup(function() {
|
||||
var evt = {
|
||||
type: 'click',
|
||||
target: {
|
||||
dataset: {
|
||||
action: 'remove'
|
||||
}
|
||||
}
|
||||
};
|
||||
view.handleEvent(evt);
|
||||
|
||||
assert.isTrue(
|
||||
listViewStub.confirmRemoval.calledWith('Pig Latin'));
|
||||
});
|
||||
|
||||
test('confirmed', function(done) {
|
||||
confirmRemovalDeferred.resolve(true);
|
||||
confirmRemovalDeferred.promise.then(function() {
|
||||
assert.isTrue(listViewStub.disableLayout.calledWith('foo'));
|
||||
disableLayoutDeferred.resolve();
|
||||
|
||||
return disableLayoutDeferred.promise;
|
||||
}).then(function() {
|
||||
assert.isTrue(itemStub.remove.calledOnce);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('cancelled', function(done) {
|
||||
confirmRemovalDeferred.resolve(false);
|
||||
confirmRemovalDeferred.promise.then(function() {
|
||||
assert.isFalse(listViewStub.disableLayout.calledOnce);
|
||||
assert.isFalse(itemStub.remove.calledOnce);
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('oninstall', function() {
|
||||
setup(function() {
|
||||
itemStub.oninstall();
|
||||
assert.isTrue(listViewStub.confirmEnable.calledWith('Pig Latin'));
|
||||
});
|
||||
|
||||
test('confirmed', function(done) {
|
||||
confirmEnableDeferred.resolve(true);
|
||||
confirmEnableDeferred.promise.then(function() {
|
||||
assert.isTrue(listViewStub.enableLayout.calledWith('foo'));
|
||||
enableLayoutDeferred.resolve();
|
||||
|
||||
return enableLayoutDeferred.promise;
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('cancelled', function(done) {
|
||||
confirmEnableDeferred.resolve(false);
|
||||
confirmEnableDeferred.promise.then(function() {
|
||||
assert.isFalse(listViewStub.enableLayout.calledOnce);
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('onerror', function() {
|
||||
test('ERROR_DOWNLOADERROR', function() {
|
||||
var errorInfo = Object.create(LayoutItemErrorInfo.prototype);
|
||||
errorInfo.error = errorInfo.ERROR_DOWNLOADERROR;
|
||||
|
||||
itemStub.onerror(errorInfo);
|
||||
|
||||
assert.isTrue(listViewStub.showDownloadErrorToast.calledOnce);
|
||||
});
|
||||
|
||||
test('ERROR_INSTALLERROR', function() {
|
||||
var errorInfo = Object.create(LayoutItemErrorInfo.prototype);
|
||||
errorInfo.error = errorInfo.ERROR_INSTALLERROR;
|
||||
|
||||
itemStub.onerror(errorInfo);
|
||||
|
||||
assert.isFalse(listViewStub.showDownloadErrorToast.calledOnce);
|
||||
});
|
||||
|
||||
test('ERROR_REMOVEERROR', function() {
|
||||
var errorInfo = Object.create(LayoutItemErrorInfo.prototype);
|
||||
errorInfo.error = errorInfo.ERROR_REMOVEERROR;
|
||||
|
||||
itemStub.onerror(errorInfo);
|
||||
|
||||
assert.isFalse(listViewStub.showDownloadErrorToast.calledOnce);
|
||||
});
|
||||
});
|
||||
});
|
||||
334
gaia/apps/keyboard/test/unit/settings/panel_controller_test.js
Executable file
334
gaia/apps/keyboard/test/unit/settings/panel_controller_test.js
Executable file
|
|
@ -0,0 +1,334 @@
|
|||
'use strict';
|
||||
|
||||
/* global PanelController, DialogController, MockPromise, BaseView,
|
||||
GeneralPanel, UserDictionaryListPanel, UserDictionaryEditDialog */
|
||||
|
||||
require('/js/settings/base_view.js');
|
||||
require('/js/settings/general_panel.js');
|
||||
require('/js/settings/user_dictionary_list_panel.js');
|
||||
require('/js/settings/user_dictionary_edit_dialog.js');
|
||||
require('/js/settings/panel_controller.js');
|
||||
require('/shared/test/unit/mocks/mock_promise.js');
|
||||
|
||||
suite('PanelController', function() {
|
||||
var controller;
|
||||
var stubGeneralPanel;
|
||||
var stubUserDictionaryListPanel;
|
||||
var app;
|
||||
var panel;
|
||||
var options;
|
||||
var stubCreateTransitionPromise;
|
||||
|
||||
setup(function() {
|
||||
stubGeneralPanel = this.sinon.stub(Object.create(GeneralPanel.prototype));
|
||||
|
||||
stubUserDictionaryListPanel =
|
||||
this.sinon.stub(Object.create(UserDictionaryListPanel.prototype));
|
||||
this.sinon.stub(window, 'UserDictionaryListPanel')
|
||||
.returns(stubUserDictionaryListPanel);
|
||||
|
||||
panel = {
|
||||
beforeShow: this.sinon.stub().returns('promisePBeforeShow'),
|
||||
show: this.sinon.stub().returns('promisePShow'),
|
||||
beforeHide: this.sinon.stub().returns('promisePBeforeHide'),
|
||||
hide: this.sinon.stub().returns('promisePHide'),
|
||||
container: document.createElement('div')
|
||||
};
|
||||
|
||||
stubGeneralPanel.beforeShow.returns('promiseGBeforeShow');
|
||||
stubGeneralPanel.show.returns('promiseGShow');
|
||||
stubGeneralPanel.beforeHide.returns('promiseGBeforeHide');
|
||||
stubGeneralPanel.hide.returns('promiseGHide');
|
||||
|
||||
app = {};
|
||||
|
||||
options = {};
|
||||
|
||||
this.sinon.stub(window, 'Promise', MockPromise);
|
||||
this.sinon.spy(window.Promise, 'resolve');
|
||||
|
||||
controller = new PanelController(app);
|
||||
this.sinon.stub(controller, 'RootPanelClass').returns(stubGeneralPanel);
|
||||
controller.start();
|
||||
|
||||
// test for start()
|
||||
assert.isTrue(stubGeneralPanel.start.called);
|
||||
|
||||
// test for subcomponent constructors
|
||||
assert.isTrue(controller.RootPanelClass.calledWith(app));
|
||||
|
||||
// start(): Promise.resolve <----> 1st .then()
|
||||
assert.isTrue(controller.rootPanel.beforeShow.calledOnce);
|
||||
assert.isTrue(window.Promise.resolve.calledWith('promiseGBeforeShow'));
|
||||
|
||||
// start(): 1st .then() onwards
|
||||
var p1 = window.Promise.resolve.firstCall.returnValue;
|
||||
var val1 = p1.mFulfillToValue(undefined);
|
||||
|
||||
assert.equal(val1, 'promiseGShow');
|
||||
|
||||
assert.isTrue(controller.rootPanel.show.calledOnce);
|
||||
|
||||
|
||||
// reset some stubbed calls so the actual test cases aren't affected
|
||||
window.Promise.resolve.reset();
|
||||
controller.rootPanel.beforeShow.reset();
|
||||
controller.rootPanel.show.reset();
|
||||
|
||||
|
||||
controller.rootPanel.container = document.createElement('div');
|
||||
|
||||
stubCreateTransitionPromise =
|
||||
this.sinon.stub(controller, '_createTransitionPromise')
|
||||
.returns('promiseTransition');
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
controller.stop();
|
||||
|
||||
assert.isTrue(stubUserDictionaryListPanel.stop.called);
|
||||
assert.isTrue(stubGeneralPanel.stop.called);
|
||||
});
|
||||
|
||||
test('alternative RootPanelClass', function(){
|
||||
var AlternativeClass = function(){};
|
||||
AlternativeClass.prototype =
|
||||
this.sinon.stub(Object.create(BaseView.prototype));
|
||||
|
||||
var controller2 = new PanelController(app, AlternativeClass);
|
||||
|
||||
controller2.start(app);
|
||||
|
||||
assert.isTrue(AlternativeClass.prototype.start.called,
|
||||
'start of AlternativeClass should be called');
|
||||
});
|
||||
|
||||
test('navigateToPanel', function() {
|
||||
controller.navigateToPanel(panel, options);
|
||||
|
||||
// Promise.resolve <----> 1st .then()
|
||||
assert.isTrue(controller.rootPanel.beforeHide.called);
|
||||
|
||||
assert.isTrue(window.Promise.resolve.calledWith('promiseGBeforeHide'));
|
||||
|
||||
// 1st .then() <----> 2nd .then()
|
||||
var p1 = window.Promise.resolve.firstCall.returnValue;
|
||||
var val1 = p1.mFulfillToValue(undefined);
|
||||
|
||||
assert.isTrue(panel.beforeShow.calledWith(options));
|
||||
|
||||
assert.equal(val1, 'promisePBeforeShow');
|
||||
|
||||
// 2nd .then() <----> 3rd .then()
|
||||
var p2 = p1.mGetNextPromise();
|
||||
var val2 = p2.mFulfillToValue(undefined);
|
||||
|
||||
assert.equal(val2, 'promiseTransition');
|
||||
|
||||
assert.isTrue(stubCreateTransitionPromise.calledWith(panel.container));
|
||||
|
||||
assert.isTrue(panel.container.classList.contains('current'));
|
||||
assert.isTrue(controller.rootPanel.container.classList.contains('prev'));
|
||||
assert.isFalse(
|
||||
controller.rootPanel.container.classList.contains('current'));
|
||||
|
||||
// 3rd .then() <----> 4th .then()
|
||||
var p3 = p2.mGetNextPromise();
|
||||
var val3 = p3.mFulfillToValue(undefined);
|
||||
|
||||
assert.isTrue(controller.rootPanel.hide.calledOnce);
|
||||
assert.equal(val3, 'promiseGHide');
|
||||
|
||||
// 4th .then() onwards
|
||||
var p4 = p3.mGetNextPromise();
|
||||
var val4 = p4.mFulfillToValue(undefined);
|
||||
|
||||
assert.isTrue(panel.show.calledOnce);
|
||||
assert.equal(val4, 'promisePShow');
|
||||
});
|
||||
|
||||
test('navigateToRoot', function() {
|
||||
controller._currentPanel = panel;
|
||||
|
||||
controller.navigateToRoot();
|
||||
|
||||
// Promise.resolve <----> 1st .then()
|
||||
assert.isTrue(controller._currentPanel.beforeHide.called);
|
||||
|
||||
assert.isTrue(window.Promise.resolve.calledWith('promisePBeforeHide'));
|
||||
|
||||
// 1st .then() <----> 2nd .then()
|
||||
var p1 = window.Promise.resolve.firstCall.returnValue;
|
||||
var val1 = p1.mFulfillToValue(undefined);
|
||||
|
||||
assert.isTrue(controller.rootPanel.beforeShow.calledOnce);
|
||||
|
||||
assert.equal(val1, 'promiseGBeforeShow');
|
||||
|
||||
// 2nd .then() <----> 3rd .then()
|
||||
var p2 = p1.mGetNextPromise();
|
||||
var val2 = p2.mFulfillToValue(undefined);
|
||||
|
||||
assert.equal(val2, 'promiseTransition');
|
||||
|
||||
assert.isTrue(stubCreateTransitionPromise.calledWith(panel.container));
|
||||
|
||||
assert.isFalse(panel.container.classList.contains('current'));
|
||||
assert.isFalse(controller.rootPanel.container.classList.contains('prev'));
|
||||
assert.isTrue(
|
||||
controller.rootPanel.container.classList.contains('current'));
|
||||
|
||||
// 3rd .then() <----> 4th .then()
|
||||
var p3 = p2.mGetNextPromise();
|
||||
var val3 = p3.mFulfillToValue(undefined);
|
||||
|
||||
assert.isTrue(controller._currentPanel.hide.calledOnce);
|
||||
assert.equal(val3, 'promisePHide');
|
||||
|
||||
// 4th .then() onwards
|
||||
var p4 = p3.mGetNextPromise();
|
||||
var val4 = p4.mFulfillToValue(undefined);
|
||||
|
||||
assert.isTrue(controller.rootPanel.show.calledOnce);
|
||||
assert.equal(val4, 'promiseGShow');
|
||||
});
|
||||
});
|
||||
|
||||
suite('DialogController', function() {
|
||||
var stubUserDictionaryEditDialog;
|
||||
var controller;
|
||||
var dialog;
|
||||
var options;
|
||||
var stubCreateTransitionPromise;
|
||||
|
||||
setup(function() {
|
||||
stubUserDictionaryEditDialog =
|
||||
this.sinon.stub(Object.create(UserDictionaryEditDialog.prototype));
|
||||
this.sinon.stub(window, 'UserDictionaryEditDialog')
|
||||
.returns(stubUserDictionaryEditDialog);
|
||||
|
||||
dialog = {
|
||||
beforeShow: this.sinon.stub().returns('promiseDBeforeShow'),
|
||||
show: this.sinon.stub().returns('promiseDShow'),
|
||||
beforeHide: this.sinon.stub().returns('promiseDBeforeHide'),
|
||||
hide: this.sinon.stub().returns('promiseDHide'),
|
||||
onsubmit: undefined,
|
||||
container: document.createElement('div')
|
||||
};
|
||||
|
||||
options = {};
|
||||
|
||||
this.sinon.stub(window, 'Promise', MockPromise);
|
||||
this.sinon.spy(window.Promise, 'resolve');
|
||||
|
||||
controller = new DialogController();
|
||||
controller.start();
|
||||
|
||||
stubCreateTransitionPromise =
|
||||
this.sinon.stub(controller, '_createTransitionPromise')
|
||||
.returns('promiseTransition');
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
controller.stop();
|
||||
|
||||
assert.isTrue(stubUserDictionaryEditDialog.stop.called);
|
||||
});
|
||||
|
||||
suite('openDialog with a dialog', function() {
|
||||
var resultPromise; // openDialog's returned Promise
|
||||
var resResolve; // openDialog's internal resolve/reject func from Promise
|
||||
var resReject;
|
||||
|
||||
setup(function() {
|
||||
resultPromise = controller.openDialog(dialog, options);
|
||||
|
||||
var pResult = window.Promise.firstCall.returnValue;
|
||||
resResolve = this.sinon.spy();
|
||||
resReject = this.sinon.spy();
|
||||
|
||||
pResult.mExecuteCallback(resResolve, resReject);
|
||||
});
|
||||
|
||||
test('normal flow', function() {
|
||||
// Promise.resolve <----> 1st .then()
|
||||
|
||||
assert.isTrue(dialog.beforeShow.calledWith(options));
|
||||
|
||||
assert.isTrue(window.Promise.resolve.calledWith('promiseDBeforeShow'));
|
||||
|
||||
// 1st .then() <----> 2nd .then()
|
||||
var p1 = window.Promise.resolve.firstCall.returnValue;
|
||||
var val1 = p1.mFulfillToValue(undefined);
|
||||
assert.equal(val1, 'promiseTransition');
|
||||
|
||||
assert.isTrue(stubCreateTransitionPromise.calledWith(dialog.container));
|
||||
|
||||
assert.isTrue(dialog.container.classList.contains('displayed'));
|
||||
|
||||
|
||||
// 2nd .then() onwards
|
||||
var p2 = p1.mGetNextPromise();
|
||||
var val2 = p2.mFulfillToValue(undefined);
|
||||
|
||||
assert.isTrue(dialog.show.calledOnce);
|
||||
assert.equal(val2, 'promiseDShow');
|
||||
|
||||
// inside onsubmit
|
||||
|
||||
stubCreateTransitionPromise.reset();
|
||||
|
||||
var results = {};
|
||||
dialog.onsubmit(results);
|
||||
|
||||
assert.isTrue(resResolve.calledWith(results));
|
||||
|
||||
// Promise.resolve <-----> 1st .then(), inside onsubmit
|
||||
assert.isTrue(
|
||||
window.Promise.resolve.secondCall.calledWith('promiseDBeforeHide')
|
||||
);
|
||||
|
||||
// 1st .then() <----> 2nd .then(), inside onsubmit
|
||||
var pS1 = window.Promise.resolve.secondCall.returnValue;
|
||||
var valS1 = pS1.mFulfillToValue(undefined);
|
||||
assert.equal(valS1, 'promiseTransition');
|
||||
|
||||
assert.isTrue(stubCreateTransitionPromise.calledWith(dialog.container));
|
||||
|
||||
assert.isFalse(dialog.container.classList.contains('displayed'));
|
||||
|
||||
|
||||
// 2nd .then() onwards, inside onsubmit
|
||||
var pS2 = pS1.mGetNextPromise();
|
||||
var valS2 = pS2.mFulfillToValue(undefined);
|
||||
assert.strictEqual(dialog.onsubmit, undefined);
|
||||
|
||||
assert.equal(valS2, 'promiseDHide');
|
||||
});
|
||||
|
||||
test('exception flow', function() {
|
||||
// Promise.resolve <----> 1st .then()
|
||||
|
||||
var p1 = window.Promise.resolve.firstCall.returnValue;
|
||||
var p2 = p1.mGetNextPromise();
|
||||
|
||||
p1.mRejectToError('error1');
|
||||
assert.isTrue(resReject.calledWith('error1'));
|
||||
|
||||
resReject.reset();
|
||||
|
||||
p2.mRejectToError('error2');
|
||||
assert.isTrue(resReject.calledWith('error2'));
|
||||
});
|
||||
});
|
||||
|
||||
test('openDialog argument not having onsubmit', function() {
|
||||
this.sinon.spy(window.Promise, 'reject');
|
||||
|
||||
controller.openDialog({});
|
||||
|
||||
assert.isTrue(window.Promise.reject.calledWith(
|
||||
'Dialog does not have a onsubmit callback'));
|
||||
});
|
||||
});
|
||||
|
||||
171
gaia/apps/keyboard/test/unit/settings/settings_view_test.js
Executable file
171
gaia/apps/keyboard/test/unit/settings/settings_view_test.js
Executable file
|
|
@ -0,0 +1,171 @@
|
|||
'use strict';
|
||||
|
||||
/* global SettingsManagerBase, SettingsPromiseManager, CloseLockManager,
|
||||
CloseLock, SettingsView, BaseView */
|
||||
|
||||
require('/shared/js/component_utils.js');
|
||||
require('/shared/elements/gaia_checkbox/script.js');
|
||||
|
||||
require('/js/settings/close_locks.js');
|
||||
require('/js/settings/base_view.js');
|
||||
require('/js/settings/settings_view.js');
|
||||
|
||||
require('/js/keyboard/settings.js');
|
||||
|
||||
suite('SettingsView', function() {
|
||||
var app;
|
||||
var view;
|
||||
var stubSettings;
|
||||
var el;
|
||||
var el2;
|
||||
|
||||
setup(function(done) {
|
||||
var container = document.createElement('div');
|
||||
el = document.createElement('gaia-checkbox');
|
||||
el.setAttribute('disabled', true);
|
||||
el.dataset.setting = 'foo.bar';
|
||||
container.appendChild(el);
|
||||
el2 = document.createElement('input');
|
||||
el2.type = 'range';
|
||||
el2.disabled = true;
|
||||
el2.value = 5;
|
||||
el2.dataset.setting = 'foo.baz';
|
||||
container.appendChild(el2);
|
||||
|
||||
this.sinon.spy(el, 'addEventListener');
|
||||
this.sinon.spy(el2, 'addEventListener');
|
||||
this.sinon.spy(el, 'removeEventListener');
|
||||
this.sinon.spy(el2, 'removeEventListener');
|
||||
|
||||
stubSettings =
|
||||
this.sinon.stub(Object.create(SettingsManagerBase.prototype));
|
||||
stubSettings.initSettings.returns(Promise.resolve());
|
||||
stubSettings.getSettingsSync.returns({
|
||||
fooBar: true,
|
||||
fooBaz: 10
|
||||
});
|
||||
var SettingsConstructor = this.sinon.stub().returns(stubSettings);
|
||||
stubSettings.KEYS = ['foo.bar', 'foo.baz'];
|
||||
stubSettings.PROPERTIES = ['fooBar', 'fooBaz'];
|
||||
|
||||
app = {
|
||||
settingsPromiseManager:
|
||||
this.sinon.stub(Object.create(SettingsPromiseManager.prototype)),
|
||||
closeLockManager:
|
||||
this.sinon.stub(Object.create(CloseLockManager.prototype))
|
||||
};
|
||||
|
||||
app.closeLockManager.requestLock
|
||||
.returns(this.sinon.stub(Object.create(CloseLock.prototype)));
|
||||
|
||||
view = new SettingsView(app, container, SettingsConstructor);
|
||||
view.start();
|
||||
|
||||
assert.isTrue(el.hasAttribute('disabled'),
|
||||
'Stay disabled until init settings returns');
|
||||
assert.isTrue(el2.disabled, 'Stay disabled until init settings returns');
|
||||
|
||||
view.taskQueue.then(done, function() {
|
||||
throw 'Most not reject';
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
view.stop();
|
||||
});
|
||||
|
||||
test('inheritance from BaseView', function() {
|
||||
assert.instanceOf(view, BaseView);
|
||||
});
|
||||
|
||||
test('init settings returns', function() {
|
||||
assert.isFalse(el.disabled, 'Enabled when init settings returns');
|
||||
assert.isTrue(el.checked, 'Checked');
|
||||
|
||||
assert.isFalse(el2.disabled, 'Enabled when init settings returns');
|
||||
assert.equal(el2.valueAsNumber, 10);
|
||||
});
|
||||
|
||||
test('Update values', function(done) {
|
||||
app.settingsPromiseManager.set.returns(Promise.resolve());
|
||||
|
||||
assert.isTrue(el.addEventListener.calledWith('change', view));
|
||||
el.checked = false;
|
||||
view.handleEvent({
|
||||
target: el,
|
||||
type: 'change'
|
||||
});
|
||||
|
||||
assert.isTrue(el2.addEventListener.calledWith('change', view));
|
||||
el2.value = 7;
|
||||
view.handleEvent({
|
||||
target: el2,
|
||||
type: 'change'
|
||||
});
|
||||
|
||||
assert.isTrue(el.disabled, 'Stay disabled until updates');
|
||||
assert.isTrue(el2.disabled, 'Stay disabled until updates');
|
||||
|
||||
view.taskQueue.then(function() {
|
||||
assert.isFalse(el.disabled);
|
||||
assert.isFalse(el2.disabled);
|
||||
|
||||
assert.isTrue(app.closeLockManager.requestLock.calledWith('stayAwake'));
|
||||
assert.isTrue(app.closeLockManager.requestLock.firstCall
|
||||
.returnValue.unlock.calledTwice);
|
||||
|
||||
assert.isTrue(
|
||||
app.settingsPromiseManager.set.calledWith('foo.bar', false));
|
||||
assert.isTrue(app.settingsPromiseManager.set.calledWith('foo.baz', 7));
|
||||
|
||||
assert.isFalse(el.checked, 'Checked');
|
||||
assert.equal(el2.valueAsNumber, 7);
|
||||
}, function() {
|
||||
throw 'Most not reject';
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('Update value failure', function(done) {
|
||||
app.settingsPromiseManager.set.returns(Promise.reject('faked error'));
|
||||
|
||||
assert.isTrue(el2.addEventListener.calledWith('change', view));
|
||||
el2.value = 7;
|
||||
view.handleEvent({
|
||||
target: el2,
|
||||
type: 'change'
|
||||
});
|
||||
|
||||
assert.isTrue(el2.disabled, 'Stay disabled until updates');
|
||||
|
||||
view.taskQueue.then(function() {
|
||||
assert.isFalse(el2.disabled);
|
||||
|
||||
assert.isTrue(app.closeLockManager.requestLock.calledWith('stayAwake'));
|
||||
assert.isTrue(app.closeLockManager.requestLock.firstCall
|
||||
.returnValue.unlock.calledOnce);
|
||||
|
||||
assert.isTrue(app.settingsPromiseManager.set.calledWith('foo.baz', 7));
|
||||
|
||||
assert.equal(el2.valueAsNumber, 10);
|
||||
}, function() {
|
||||
throw 'Most not reject';
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('Handle remote settings change', function(done) {
|
||||
var newValues = {
|
||||
fooBar: false,
|
||||
fooBaz: 7
|
||||
};
|
||||
|
||||
stubSettings.getSettingsSync.returns(newValues);
|
||||
stubSettings.onsettingchange(newValues);
|
||||
|
||||
view.taskQueue.then(function() {
|
||||
assert.isFalse(el.checked, 'Checked');
|
||||
assert.equal(el2.valueAsNumber, 7);
|
||||
}, function() {
|
||||
throw 'Most not reject';
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
228
gaia/apps/keyboard/test/unit/settings/user_dictionary_edit_dialog_test.js
Executable file
228
gaia/apps/keyboard/test/unit/settings/user_dictionary_edit_dialog_test.js
Executable file
|
|
@ -0,0 +1,228 @@
|
|||
'use strict';
|
||||
|
||||
/* global UserDictionaryEditDialog, MockEventTarget, KeyEvent, BaseView */
|
||||
|
||||
require('/shared/test/unit/mocks/mock_event_target.js');
|
||||
|
||||
require('/js/settings/base_view.js');
|
||||
require('/js/settings/user_dictionary_edit_dialog.js');
|
||||
|
||||
suite('UserDictionary Edit Dialog', function() {
|
||||
var dialog;
|
||||
var stubContainer;
|
||||
var stubGetElemById;
|
||||
|
||||
var stubHeader;
|
||||
var stubSaveWordBtn;
|
||||
var stubInput;
|
||||
var stubDeleteBtn;
|
||||
var stubDialogCancelBtn;
|
||||
var stubDialogDeleteBtn;
|
||||
|
||||
setup(function() {
|
||||
dialog = new UserDictionaryEditDialog();
|
||||
|
||||
stubContainer = {
|
||||
querySelector:
|
||||
this.sinon.stub().returns(() => this.sinon.stub(new MockEventTarget())),
|
||||
classList: {
|
||||
add: this.sinon.spy(),
|
||||
remove: this.sinon.spy()
|
||||
}
|
||||
};
|
||||
|
||||
stubHeader = this.sinon.stub(new MockEventTarget());
|
||||
stubSaveWordBtn = this.sinon.stub(new MockEventTarget());
|
||||
stubInput = this.sinon.stub(new MockEventTarget());
|
||||
stubDeleteBtn = this.sinon.stub(new MockEventTarget());
|
||||
stubDialogCancelBtn = this.sinon.stub(new MockEventTarget());
|
||||
stubDialogDeleteBtn = this.sinon.stub(new MockEventTarget());
|
||||
|
||||
stubGetElemById =
|
||||
this.sinon.stub(document, 'getElementById').returns(stubContainer);
|
||||
|
||||
stubContainer.querySelector
|
||||
.withArgs('#ud-editword-header').returns(stubHeader);
|
||||
stubContainer.querySelector
|
||||
.withArgs('#ud-saveword-btn').returns(stubSaveWordBtn);
|
||||
stubContainer.querySelector
|
||||
.withArgs('#ud-editword-input').returns(stubInput);
|
||||
stubContainer.querySelector
|
||||
.withArgs('#ud-editword-delete-btn').returns(stubDeleteBtn);
|
||||
stubContainer.querySelector
|
||||
.withArgs('#ud-editword-dialog-cancel-btn').returns(stubDialogCancelBtn);
|
||||
stubContainer.querySelector
|
||||
.withArgs('#ud-editword-dialog-delete-btn').returns(stubDialogDeleteBtn);
|
||||
|
||||
dialog.start();
|
||||
|
||||
assert.isTrue(stubGetElemById.calledWith('panel-ud-editword'));
|
||||
|
||||
assert.equal(dialog._inputField, stubInput);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
dialog.stop();
|
||||
});
|
||||
|
||||
test('inheritance from BaseView', function() {
|
||||
assert.instanceOf(dialog, BaseView);
|
||||
});
|
||||
|
||||
suite('Transition hooks', function() {
|
||||
suite('beforeShow', function() {
|
||||
test('edit mode', function() {
|
||||
dialog.beforeShow({
|
||||
word: 'star'
|
||||
});
|
||||
|
||||
assert.isTrue(stubContainer.classList.remove.calledWith('add-mode'));
|
||||
assert.equal(dialog._inputField.value, 'star');
|
||||
assert.equal(dialog._oldWord, 'star');
|
||||
});
|
||||
|
||||
test('add mode', function() {
|
||||
dialog.beforeShow();
|
||||
|
||||
assert.isTrue(stubContainer.classList.add.calledWith('add-mode'));
|
||||
assert.strictEqual(dialog._oldWord, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
test('show', function() {
|
||||
stubInput.focus = this.sinon.spy();
|
||||
|
||||
dialog.show();
|
||||
|
||||
assert.isTrue(stubHeader.addEventListener.calledWith('action', dialog));
|
||||
assert.isTrue(
|
||||
stubSaveWordBtn.addEventListener.calledWith('click', dialog));
|
||||
assert.isTrue(stubInput.addEventListener.calledWith('keydown', dialog));
|
||||
assert.isTrue(stubDeleteBtn.addEventListener.calledWith('click', dialog));
|
||||
assert.isTrue(
|
||||
stubDialogCancelBtn.addEventListener.calledWith('click', dialog));
|
||||
assert.isTrue(
|
||||
stubDialogDeleteBtn.addEventListener.calledWith('click', dialog));
|
||||
|
||||
assert.isTrue(stubInput.focus.called);
|
||||
});
|
||||
|
||||
test('beforeHide', function() {
|
||||
dialog.beforeHide();
|
||||
|
||||
assert.isTrue(
|
||||
stubHeader.removeEventListener.calledWith('action', dialog));
|
||||
assert.isTrue(
|
||||
stubSaveWordBtn.removeEventListener.calledWith('click', dialog));
|
||||
assert.isTrue(
|
||||
stubInput.removeEventListener.calledWith('keydown', dialog));
|
||||
assert.isTrue(
|
||||
stubDeleteBtn.removeEventListener.calledWith('click', dialog));
|
||||
assert.isTrue(
|
||||
stubDialogCancelBtn.removeEventListener.calledWith('click', dialog));
|
||||
assert.isTrue(
|
||||
stubDialogDeleteBtn.removeEventListener.calledWith('click', dialog));
|
||||
});
|
||||
|
||||
test('hide', function() {
|
||||
dialog.hide();
|
||||
|
||||
assert.strictEqual(dialog._inputField.value, '');
|
||||
assert.strictEqual(dialog._oldWord, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Event handling', function() {
|
||||
// this suite includes behavioral tests for _commitWord, _removeWord
|
||||
// and _cancel
|
||||
|
||||
var stubDeleteDialog;
|
||||
|
||||
setup(function() {
|
||||
dialog.onsubmit = this.sinon.spy();
|
||||
|
||||
stubDeleteDialog = {
|
||||
setAttribute: this.sinon.spy(),
|
||||
removeAttribute: this.sinon.spy()
|
||||
};
|
||||
|
||||
stubContainer.querySelector
|
||||
.withArgs('#ud-editword-delete-dialog').returns(stubDeleteDialog);
|
||||
});
|
||||
|
||||
test('action -> cancel', function() {
|
||||
dialog.handleEvent({type: 'action'});
|
||||
|
||||
assert.isTrue(dialog.onsubmit.calledWith({action: 'cancel'}));
|
||||
});
|
||||
|
||||
test('enter key -> submit', function() {
|
||||
stubInput.value = 'star';
|
||||
stubInput.blur = this.sinon.spy();
|
||||
dialog.handleEvent({type: 'keydown', keyCode: KeyEvent.DOM_VK_RETURN});
|
||||
|
||||
assert.isTrue(stubInput.blur.called);
|
||||
assert.isTrue(
|
||||
dialog.onsubmit.calledWith({action: 'commit', word: 'star'}));
|
||||
});
|
||||
|
||||
test('save button -> submit', function() {
|
||||
stubInput.value = 'star';
|
||||
dialog.handleEvent({type: 'click', target: {id: 'ud-saveword-btn'}});
|
||||
|
||||
assert.isTrue(
|
||||
dialog.onsubmit.calledWith({action: 'commit', word: 'star'}));
|
||||
});
|
||||
|
||||
test('delete button -> show dialog', function() {
|
||||
dialog._oldWord = 'oldstar';
|
||||
|
||||
var oldMozL10n = navigator.mozL10n;
|
||||
navigator.mozL10n = {
|
||||
setAttributes: this.sinon.spy()
|
||||
};
|
||||
|
||||
stubContainer.querySelector
|
||||
.withArgs('#ud-editword-delete-prompt').returns('dummy-delete');
|
||||
|
||||
dialog.handleEvent({
|
||||
type: 'click',
|
||||
target: {id: 'ud-editword-delete-btn'}
|
||||
});
|
||||
|
||||
assert.isTrue(navigator.mozL10n.setAttributes.calledWith(
|
||||
'dummy-delete',
|
||||
'userDictionaryDeletePrompt',
|
||||
{word: 'oldstar'}
|
||||
));
|
||||
|
||||
assert.isTrue(stubDeleteDialog.removeAttribute.calledWith('hidden'));
|
||||
|
||||
navigator.mozL10n = oldMozL10n;
|
||||
});
|
||||
|
||||
test('dialog delete button -> delete & hide dialog', function() {
|
||||
dialog.handleEvent({
|
||||
type: 'click',
|
||||
target: {id: 'ud-editword-dialog-delete-btn'}
|
||||
});
|
||||
|
||||
assert.isTrue(
|
||||
dialog.onsubmit.calledWith({action: 'remove'}));
|
||||
|
||||
assert.isTrue(stubDeleteDialog.setAttribute.calledWith('hidden', true));
|
||||
});
|
||||
|
||||
test('dialog cancel button -> hide dialog', function() {
|
||||
dialog.handleEvent({
|
||||
type: 'click',
|
||||
target: {id: 'ud-editword-dialog-cancel-btn'}
|
||||
});
|
||||
|
||||
assert.isFalse(
|
||||
dialog.onsubmit.calledWith({action: 'remove'}));
|
||||
|
||||
assert.isTrue(stubDeleteDialog.setAttribute.calledWith('hidden', true));
|
||||
});
|
||||
});
|
||||
});
|
||||
449
gaia/apps/keyboard/test/unit/settings/user_dictionary_list_panel_test.js
Executable file
449
gaia/apps/keyboard/test/unit/settings/user_dictionary_list_panel_test.js
Executable file
|
|
@ -0,0 +1,449 @@
|
|||
'use strict';
|
||||
|
||||
/* global UserDictionaryListPanel, MockEventTarget, CloseLockManager, CloseLock,
|
||||
PanelController, DialogController, UserDictionary, MockPromise,
|
||||
BaseView */
|
||||
|
||||
require('/shared/test/unit/mocks/mock_event_target.js');
|
||||
require('/shared/test/unit/mocks/mock_promise.js');
|
||||
|
||||
require('/js/settings/close_locks.js');
|
||||
require('/js/settings/base_view.js');
|
||||
require('/js/settings/general_panel.js');
|
||||
require('/js/settings/panel_controller.js');
|
||||
require('/js/settings/user_dictionary.js');
|
||||
|
||||
require('/js/settings/user_dictionary_list_panel.js');
|
||||
|
||||
suite('UserDictionary List Panel', function() {
|
||||
var panel;
|
||||
var app;
|
||||
var stubUserDictionary;
|
||||
var stubContainer;
|
||||
var stubGetElemById;
|
||||
|
||||
var stubListContainer;
|
||||
var stubHeader;
|
||||
var stubAddWordBtn;
|
||||
|
||||
setup(function() {
|
||||
stubUserDictionary =
|
||||
this.sinon.stub(Object.create(UserDictionary.prototype));
|
||||
this.sinon.stub(window, 'UserDictionary')
|
||||
.returns(stubUserDictionary);
|
||||
|
||||
app = {
|
||||
closeLockManager: this.sinon.stub(new CloseLockManager()),
|
||||
panelController: this.sinon.stub(new PanelController()),
|
||||
dialogController: this.sinon.stub(new DialogController()),
|
||||
userDictionaryEditDialog: 'dummyEditDialog'
|
||||
};
|
||||
|
||||
panel = new UserDictionaryListPanel(app);
|
||||
|
||||
stubContainer = {
|
||||
querySelector:
|
||||
this.sinon.stub().returns(() => this.sinon.stub(new MockEventTarget())),
|
||||
classList: {
|
||||
add: this.sinon.spy(),
|
||||
remove: this.sinon.spy()
|
||||
}
|
||||
};
|
||||
|
||||
stubListContainer = this.sinon.stub(new MockEventTarget());
|
||||
stubListContainer.appendChild = this.sinon.spy();
|
||||
stubListContainer.removeChild = this.sinon.spy();
|
||||
stubListContainer.childNodes = {};
|
||||
stubHeader = this.sinon.stub(new MockEventTarget());
|
||||
stubAddWordBtn = this.sinon.stub(new MockEventTarget());
|
||||
|
||||
stubGetElemById =
|
||||
this.sinon.stub(document, 'getElementById').returns(stubContainer);
|
||||
|
||||
stubContainer.querySelector
|
||||
.withArgs('#ud-wordlist-list').returns(stubListContainer);
|
||||
stubContainer.querySelector
|
||||
.withArgs('gaia-header').returns(stubHeader);
|
||||
stubContainer.querySelector
|
||||
.withArgs('#ud-addword-btn').returns(stubAddWordBtn);
|
||||
|
||||
panel.start();
|
||||
|
||||
assert.equal(panel.container, stubContainer);
|
||||
assert.equal(panel._listContainer, stubListContainer);
|
||||
|
||||
assert.isTrue(stubUserDictionary.start.called);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
panel.stop();
|
||||
});
|
||||
|
||||
test('inheritance from BaseView', function() {
|
||||
assert.instanceOf(panel, BaseView);
|
||||
});
|
||||
|
||||
suite('Transition hooks', function() {
|
||||
suite('beforeShow', function() {
|
||||
var p;
|
||||
|
||||
setup(function(){
|
||||
p = new MockPromise();
|
||||
stubUserDictionary.getList.returns(p);
|
||||
});
|
||||
|
||||
test('do not populate more than once', function() {
|
||||
panel.beforeShow();
|
||||
panel.beforeShow();
|
||||
|
||||
assert.isTrue(stubUserDictionary.getList.calledOnce);
|
||||
});
|
||||
|
||||
suite('getList', function() {
|
||||
test('empty Set', function(){
|
||||
panel.beforeShow();
|
||||
|
||||
p.mFulfillToValue(new Set([]));
|
||||
|
||||
assert.isTrue(stubContainer.classList.add.calledWith('empty'));
|
||||
});
|
||||
test('Set with contents', function(){
|
||||
var stubRearrangeList = this.sinon.stub(panel, '_rearrangeList');
|
||||
|
||||
panel.beforeShow();
|
||||
|
||||
p.mFulfillToValue(new Set(['star', 'stare', 'starry']));
|
||||
|
||||
assert.isTrue(stubContainer.classList.remove.calledWith('empty'));
|
||||
assert.isTrue(
|
||||
stubRearrangeList.calledWith(new Set(['star', 'stare', 'starry'])));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('show', function() {
|
||||
panel.show();
|
||||
|
||||
assert.isTrue(stubHeader.addEventListener.calledWith('action', panel));
|
||||
assert.isTrue(stubAddWordBtn.addEventListener.calledWith('click', panel));
|
||||
assert.isTrue(
|
||||
stubListContainer.addEventListener.calledWith('click', panel));
|
||||
});
|
||||
|
||||
test('beforeHide', function() {
|
||||
panel.beforeHide();
|
||||
|
||||
assert.isTrue(stubHeader.removeEventListener.calledWith('action', panel));
|
||||
assert.isTrue(
|
||||
stubAddWordBtn.removeEventListener.calledWith('click', panel));
|
||||
assert.isTrue(
|
||||
stubListContainer.removeEventListener.calledWith('click', panel));
|
||||
});
|
||||
});
|
||||
|
||||
suite('Event handling', function() {
|
||||
test('action -> navigate to root', function() {
|
||||
panel.handleEvent({type: 'action'});
|
||||
|
||||
assert.isTrue(app.panelController.navigateToRoot.called);
|
||||
});
|
||||
|
||||
test('click on addWordButton -> showAddDialog', function() {
|
||||
var stubShowDialog = this.sinon.stub(panel, '_showAddDialog');
|
||||
|
||||
panel.handleEvent({type: 'click', target: {id: 'ud-addword-btn'}});
|
||||
|
||||
assert.isTrue(stubShowDialog.called);
|
||||
});
|
||||
|
||||
test('click on word -> showEditDialog', function() {
|
||||
var stubShowDialog = this.sinon.stub(panel, '_showEditDialog');
|
||||
var target = {tagName: 'LI'};
|
||||
var spyPreventDefault = this.sinon.spy();
|
||||
|
||||
panel.handleEvent({
|
||||
type: 'click',
|
||||
target: target,
|
||||
preventDefault: spyPreventDefault
|
||||
});
|
||||
|
||||
assert.isTrue(stubShowDialog.calledWith(target));
|
||||
assert.isTrue(spyPreventDefault.called);
|
||||
});
|
||||
});
|
||||
|
||||
test('rearrangeList', function() {
|
||||
panel._wordDomMap = {
|
||||
star: {t: 'star'},
|
||||
starry: {t: 'starry'}
|
||||
};
|
||||
|
||||
var stubStareWordElem = {cT: 'stare'};
|
||||
|
||||
this.sinon.stub(panel, '_createWordElem').returns(stubStareWordElem);
|
||||
|
||||
panel._rearrangeList(['star', 'stare', 'starry']);
|
||||
|
||||
// as the order of the words in the list is important, we need to make sure
|
||||
// appendChild calls are in the same order.
|
||||
assert.isTrue(stubListContainer.appendChild.getCall(0)
|
||||
.calledWith(panel._wordDomMap.star));
|
||||
assert.isTrue(stubListContainer.appendChild.getCall(1)
|
||||
.calledWith(stubStareWordElem));
|
||||
assert.isTrue(stubListContainer.appendChild.getCall(2)
|
||||
.calledWith(panel._wordDomMap.starry));
|
||||
});
|
||||
|
||||
test('createWordElem', function() {
|
||||
var stubA = {};
|
||||
var stubLI = {
|
||||
appendChild: this.sinon.spy()
|
||||
};
|
||||
|
||||
var stubCreateElem = this.sinon.stub(document, 'createElement');
|
||||
|
||||
stubCreateElem.withArgs('a').returns(stubA);
|
||||
stubCreateElem.withArgs('li').returns(stubLI);
|
||||
|
||||
var ret = panel._createWordElem('star');
|
||||
assert.equal(stubLI, ret);
|
||||
|
||||
assert.equal(stubA.textContent, 'star');
|
||||
assert.equal(stubA.href, '#star');
|
||||
|
||||
assert.isTrue(stubLI.appendChild.calledWith(stubA));
|
||||
});
|
||||
|
||||
suite('showAddDialog', function() {
|
||||
// this suite tests addWord behavior too.
|
||||
|
||||
var pDialog;
|
||||
|
||||
setup(function() {
|
||||
pDialog = new MockPromise();
|
||||
|
||||
app.dialogController.openDialog.returns(pDialog);
|
||||
});
|
||||
|
||||
test('canceled dialog', function() {
|
||||
var stubAddWord = this.sinon.stub(panel, '_addWord');
|
||||
|
||||
panel._showAddDialog();
|
||||
|
||||
assert.isTrue(app.dialogController.openDialog.calledWith(
|
||||
app.dialogController.userDictionaryEditDialog));
|
||||
|
||||
pDialog.mFulfillToValue({action: 'cancel'});
|
||||
assert.isFalse(stubAddWord.called);
|
||||
});
|
||||
|
||||
suite('submitted dialog', function() {
|
||||
var pAdd;
|
||||
var closeLock;
|
||||
|
||||
setup(function(){
|
||||
pAdd = new MockPromise();
|
||||
closeLock = this.sinon.stub(new CloseLock());
|
||||
|
||||
app.closeLockManager.requestLock.returns(closeLock);
|
||||
|
||||
stubUserDictionary.addWord.returns(pAdd);
|
||||
|
||||
panel._showAddDialog();
|
||||
});
|
||||
|
||||
test('white space only (no effect)', function(){
|
||||
pDialog.mFulfillToValue({action: 'commit', word: ' \t '});
|
||||
|
||||
assert.isFalse(app.closeLockManager.requestLock.called);
|
||||
assert.isFalse(stubUserDictionary.addWord.called);
|
||||
});
|
||||
|
||||
test('successful', function(){
|
||||
var stubRearrangeList= this.sinon.stub(panel, '_rearrangeList');
|
||||
|
||||
pDialog.mFulfillToValue({action: 'commit', word: 'star'});
|
||||
|
||||
assert.isTrue(app.closeLockManager.requestLock.calledWith('stayAwake'));
|
||||
assert.isTrue(stubUserDictionary.addWord.calledWith('star'));
|
||||
|
||||
pAdd.mFulfillToValue(['star']);
|
||||
|
||||
assert.isTrue(closeLock.unlock.called);
|
||||
assert.isTrue(stubContainer.classList.remove.calledWith('empty'));
|
||||
assert.isTrue(stubRearrangeList.calledWith(['star']));
|
||||
});
|
||||
|
||||
test('still unlocks CloseLock after rejection from model', function(){
|
||||
pDialog.mFulfillToValue({action: 'commit', word: 'star'});
|
||||
|
||||
assert.isTrue(app.closeLockManager.requestLock.calledWith('stayAwake'));
|
||||
|
||||
pAdd.mRejectToError();
|
||||
|
||||
assert.isTrue(closeLock.unlock.called);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('showEditDialog', function() {
|
||||
// this suite tests removeWord & replaceWord behavior too.
|
||||
|
||||
var pDialog;
|
||||
var stubWordElem;
|
||||
|
||||
setup(function() {
|
||||
pDialog = new MockPromise();
|
||||
|
||||
stubWordElem = {
|
||||
childNodes: [{
|
||||
textContent: 'star'
|
||||
}]
|
||||
};
|
||||
panel._domWordMap.set(stubWordElem, 'star');
|
||||
|
||||
app.dialogController.openDialog.returns(pDialog);
|
||||
});
|
||||
|
||||
test('word is correctly propogated to dialog', function() {
|
||||
panel._showEditDialog(stubWordElem);
|
||||
|
||||
assert.isTrue(app.dialogController.openDialog.calledWith(
|
||||
app.dialogController.userDictionaryEditDialog, {word: 'star'}));
|
||||
});
|
||||
|
||||
test('canceled dialog', function() {
|
||||
var stubRemoveWord = this.sinon.stub(panel, '_removeWord');
|
||||
var stubReplaceWord = this.sinon.stub(panel, '_replaceWord');
|
||||
|
||||
panel._showEditDialog(stubWordElem);
|
||||
|
||||
pDialog.mFulfillToValue({action: 'cancel'});
|
||||
|
||||
assert.isFalse(stubReplaceWord.called);
|
||||
assert.isFalse(stubRemoveWord.called);
|
||||
});
|
||||
|
||||
suite('submitted dialog for remove', function() {
|
||||
var pRemove;
|
||||
var closeLock;
|
||||
|
||||
setup(function(){
|
||||
pRemove = new MockPromise();
|
||||
closeLock = this.sinon.stub(new CloseLock());
|
||||
|
||||
app.closeLockManager.requestLock.returns(closeLock);
|
||||
|
||||
stubUserDictionary.removeWord.returns(pRemove);
|
||||
|
||||
panel._showEditDialog(stubWordElem);
|
||||
});
|
||||
|
||||
test('successful, list not empty yet', function(){
|
||||
stubListContainer.childNodes.length = 2;
|
||||
|
||||
pDialog.mFulfillToValue({action: 'remove'});
|
||||
|
||||
assert.isTrue(app.closeLockManager.requestLock.calledWith('stayAwake'));
|
||||
assert.isTrue(stubUserDictionary.removeWord.calledWith('star'));
|
||||
|
||||
pRemove.mFulfillToValue(undefined);
|
||||
|
||||
assert.isTrue(closeLock.unlock.called);
|
||||
assert.isFalse(panel._domWordMap.has(stubWordElem));
|
||||
assert.isTrue(stubListContainer.removeChild.calledWith(stubWordElem));
|
||||
|
||||
assert.isFalse(stubContainer.classList.add.called);
|
||||
});
|
||||
|
||||
test('successful, list empty (partial test)', function(){
|
||||
stubListContainer.childNodes.length = 0;
|
||||
|
||||
pDialog.mFulfillToValue({action: 'remove'});
|
||||
|
||||
pRemove.mFulfillToValue(undefined);
|
||||
|
||||
assert.isTrue(stubContainer.classList.add.calledWith('empty'));
|
||||
});
|
||||
|
||||
test('still unlocks CloseLock after rejection from model', function(){
|
||||
pDialog.mFulfillToValue({action: 'remove'});
|
||||
|
||||
assert.isTrue(app.closeLockManager.requestLock.calledWith('stayAwake'));
|
||||
|
||||
pRemove.mRejectToError();
|
||||
|
||||
assert.isTrue(closeLock.unlock.called);
|
||||
});
|
||||
});
|
||||
|
||||
suite('submitted dialog for replace', function() {
|
||||
var pReplace;
|
||||
var closeLock;
|
||||
|
||||
setup(function(){
|
||||
pReplace = new MockPromise();
|
||||
closeLock = this.sinon.stub(new CloseLock());
|
||||
|
||||
app.closeLockManager.requestLock.returns(closeLock);
|
||||
|
||||
stubUserDictionary.updateWord.returns(pReplace);
|
||||
|
||||
panel._showEditDialog(stubWordElem);
|
||||
});
|
||||
|
||||
test('newWord is empty (do nothing)', function(){
|
||||
pDialog.mFulfillToValue({action: 'commit', word: ' \t '});
|
||||
|
||||
assert.isFalse(
|
||||
app.closeLockManager.requestLock.calledWith('stayAwake'));
|
||||
assert.isFalse(stubUserDictionary.updateWord.called);
|
||||
});
|
||||
|
||||
test('newWord == oldWord (do nothing)', function(){
|
||||
pDialog.mFulfillToValue({action: 'commit', word: 'star'});
|
||||
|
||||
assert.isFalse(
|
||||
app.closeLockManager.requestLock.calledWith('stayAwake'));
|
||||
assert.isFalse(stubUserDictionary.updateWord.called);
|
||||
});
|
||||
|
||||
test('successful, new word does not exist yet', function(){
|
||||
var stubRearrangeList= this.sinon.stub(panel, '_rearrangeList');
|
||||
|
||||
pDialog.mFulfillToValue({action: 'commit', word: 'star2'});
|
||||
|
||||
assert.isTrue(app.closeLockManager.requestLock.calledWith('stayAwake'));
|
||||
assert.isTrue(
|
||||
stubUserDictionary.updateWord.calledWith('star', 'star2'));
|
||||
|
||||
pReplace.mFulfillToValue(['star2']);
|
||||
|
||||
assert.isTrue(closeLock.unlock.called);
|
||||
assert.equal(panel._domWordMap.get(stubWordElem), 'star2');
|
||||
assert.equal(stubWordElem.childNodes[0].textContent, 'star2');
|
||||
|
||||
assert.isTrue(stubRearrangeList.calledWith(['star2']));
|
||||
});
|
||||
|
||||
test('successful, new word already exists', function(){
|
||||
pDialog.mFulfillToValue({action: 'commit', word: 'star2'});
|
||||
|
||||
pReplace.mRejectToError('existing');
|
||||
|
||||
assert.isFalse(panel._domWordMap.has(stubWordElem));
|
||||
assert.isTrue(stubListContainer.removeChild.calledWith(stubWordElem));
|
||||
});
|
||||
|
||||
test('still unlocks CloseLock after rejection from model', function(){
|
||||
pDialog.mFulfillToValue({action: 'commit', word: 'star2'});
|
||||
|
||||
assert.isTrue(app.closeLockManager.requestLock.calledWith('stayAwake'));
|
||||
|
||||
pReplace.mRejectToError();
|
||||
|
||||
assert.isTrue(closeLock.unlock.called);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
289
gaia/apps/keyboard/test/unit/settings/user_dictionary_test.js
Executable file
289
gaia/apps/keyboard/test/unit/settings/user_dictionary_test.js
Executable file
|
|
@ -0,0 +1,289 @@
|
|||
'use strict';
|
||||
|
||||
/* global MockPromise, PromiseStorage, UserDictionary, WordListConverter */
|
||||
|
||||
require('/shared/test/unit/mocks/mock_promise.js');
|
||||
require('/js/shared/promise_storage.js');
|
||||
|
||||
require('/js/settings/word_list_converter.js');
|
||||
require('/js/settings/user_dictionary.js');
|
||||
|
||||
suite('UserDictionary', function() {
|
||||
var model;
|
||||
var stubPromiseStorage;
|
||||
var stubWordListConverter;
|
||||
|
||||
// Note in comparing against Set(): deepEqual won't work like
|
||||
// deepEqual(set1, set2).
|
||||
// Please use deepEqual(Array.from(set1), Array.from(set2)).
|
||||
|
||||
setup(function() {
|
||||
model = new UserDictionary();
|
||||
|
||||
this.sinon.stub(window, 'Promise', MockPromise);
|
||||
|
||||
stubPromiseStorage =
|
||||
this.sinon.stub(Object.create(PromiseStorage.prototype));
|
||||
this.sinon.stub(window, 'PromiseStorage')
|
||||
.returns(stubPromiseStorage);
|
||||
|
||||
stubWordListConverter =
|
||||
this.sinon.stub(Object.create(WordListConverter.prototype));
|
||||
this.sinon.stub(window, 'WordListConverter')
|
||||
.returns(stubWordListConverter);
|
||||
|
||||
model.start();
|
||||
|
||||
assert.isTrue(stubPromiseStorage.start.calledOnce);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
model.stop();
|
||||
|
||||
assert.isTrue(stubPromiseStorage.stop.calledOnce);
|
||||
});
|
||||
|
||||
suite('getList', function() {
|
||||
var pDB;
|
||||
var pRet;
|
||||
|
||||
setup(function() {
|
||||
pDB = new MockPromise();
|
||||
model._dbStore.getItem.returns(pDB);
|
||||
|
||||
pRet = model.getList();
|
||||
|
||||
assert.isTrue(model._dbStore.getItem.calledWith('wordlist'));
|
||||
|
||||
// see if the returned-by-getList promise depends on dbStore's
|
||||
// promise.
|
||||
assert.equal(pRet,
|
||||
pDB.then.firstCall.returnValue.catch.firstCall.returnValue);
|
||||
});
|
||||
|
||||
test('normal result', function(){
|
||||
var ret = pDB.mFulfillToValue(['star', 'stare', 'starry']);
|
||||
|
||||
assert.deepEqual(Array.from(ret), ['star', 'stare', 'starry']);
|
||||
});
|
||||
test('undefined result = empty list', function(){
|
||||
var ret = pDB.mFulfillToValue(undefined);
|
||||
|
||||
assert.deepEqual(Array.from(ret), []);
|
||||
});
|
||||
test('empty result = empty list', function(){
|
||||
var ret = pDB.mFulfillToValue([]);
|
||||
|
||||
assert.deepEqual(Array.from(ret), []);
|
||||
});
|
||||
test('promise rejected result = empty list with console log', function(){
|
||||
this.sinon.stub(window.console, 'error');
|
||||
|
||||
var ret = pDB.mRejectToError('errorInDB');
|
||||
|
||||
assert.deepEqual(Array.from(ret), []);
|
||||
assert.isTrue(window.console.error.calledOnce);
|
||||
});
|
||||
});
|
||||
|
||||
suite('addWord', function() {
|
||||
var pSaveList;
|
||||
|
||||
setup(function() {
|
||||
pSaveList = new MockPromise();
|
||||
this.sinon.stub(model, '_saveDict').returns(pSaveList);
|
||||
|
||||
model._wordSet = new Set();
|
||||
});
|
||||
|
||||
test('normal case', function(){
|
||||
var pRet = model.addWord('star');
|
||||
|
||||
assert.equal(pRet, pSaveList);
|
||||
assert.deepEqual(Array.from(model._wordSet), ['star']);
|
||||
});
|
||||
test('word is trimmed correctly', function(){
|
||||
var pRet = model.addWord(' star\t ');
|
||||
|
||||
assert.equal(pRet, pSaveList);
|
||||
assert.deepEqual(Array.from(model._wordSet), ['star']);
|
||||
});
|
||||
test('existing word', function(){
|
||||
var pRej = new MockPromise();
|
||||
var stubReject = this.sinon.stub(Promise, 'reject').returns(pRej);
|
||||
|
||||
model.addWord('star');
|
||||
var pRet = model.addWord('star');
|
||||
|
||||
assert.isTrue(stubReject.calledWith('existing'));
|
||||
assert.equal(pRej, pRet);
|
||||
});
|
||||
});
|
||||
|
||||
suite('updateWord', function() {
|
||||
var pSaveList;
|
||||
|
||||
setup(function() {
|
||||
pSaveList = new MockPromise();
|
||||
this.sinon.stub(model, '_saveDict').returns(pSaveList);
|
||||
|
||||
model._wordSet = new Set(['oldstar']);
|
||||
});
|
||||
|
||||
test('normal case', function(){
|
||||
var pRet = model.updateWord('oldstar', 'newstar');
|
||||
|
||||
assert.equal(pRet, pSaveList);
|
||||
assert.deepEqual(Array.from(model._wordSet), ['newstar']);
|
||||
});
|
||||
test('old word === new word', function(){
|
||||
var pRes = new MockPromise();
|
||||
this.sinon.stub(Promise, 'resolve').returns(pRes);
|
||||
|
||||
var pRet = model.updateWord('oldstar', 'oldstar');
|
||||
|
||||
assert.isTrue(Promise.resolve.calledWith(['oldstar']),
|
||||
'resolve() should be called with wordlist as parameter');
|
||||
assert.equal(pRet, pRes);
|
||||
});
|
||||
test('new word is trimmed correctly', function(){
|
||||
var pRet = model.updateWord('oldstar', ' newstar\t ');
|
||||
|
||||
assert.equal(pRet, pSaveList);
|
||||
assert.deepEqual(Array.from(model._wordSet), ['newstar']);
|
||||
});
|
||||
test('existing new word', function(){
|
||||
var pRej = new MockPromise();
|
||||
var stubReject = this.sinon.stub(Promise, 'reject').returns(pRej);
|
||||
|
||||
model._wordSet.add('newstar');
|
||||
|
||||
var pRet = model.updateWord('oldstar', 'newstar');
|
||||
|
||||
assert.deepEqual(Array.from(model._wordSet), ['newstar']);
|
||||
|
||||
var pSaveListThen = pSaveList.mGetNextPromise();
|
||||
|
||||
assert.equal(pRet, pSaveListThen);
|
||||
|
||||
var pRetSaveListThen = pSaveList.mFulfillToValue(undefined);
|
||||
|
||||
assert.equal(pRetSaveListThen, pRej);
|
||||
|
||||
assert.isTrue(stubReject.calledWith('existing'));
|
||||
});
|
||||
});
|
||||
|
||||
test('removeWord', function() {
|
||||
var pSaveList = new MockPromise();
|
||||
this.sinon.stub(model, '_saveDict').returns(pSaveList);
|
||||
|
||||
model._wordSet = new Set(['star']);
|
||||
|
||||
var pRet = model.removeWord('star');
|
||||
assert.equal(pRet, pSaveList);
|
||||
|
||||
assert.deepEqual(Array.from(model._wordSet), []);
|
||||
});
|
||||
|
||||
suite('saveDict', function() {
|
||||
// we test successive three calls to saveDict and make sure they're properly
|
||||
// queued. We use native Promise to ease the checking since the chaining
|
||||
// isn't very complex.
|
||||
|
||||
var rSaveQueue;
|
||||
var pSave1, pSave2, pSave3;
|
||||
var rDB1, rDB2, rDB3;
|
||||
var pDB1, pDB2, pDB3;
|
||||
|
||||
setup(function() {
|
||||
window.Promise.restore();
|
||||
|
||||
model._saveQueue = new Promise(resolve => (rSaveQueue = resolve));
|
||||
|
||||
pDB1 = new Promise(resolve => (rDB1 = resolve));
|
||||
pDB2 = new Promise(resolve => (rDB2 = resolve));
|
||||
pDB3 = new Promise(resolve => (rDB3 = resolve));
|
||||
|
||||
model._dbStore.setItems
|
||||
.onFirstCall().returns(pDB1)
|
||||
.onSecondCall().returns(pDB2)
|
||||
.onThirdCall().returns(pDB3);
|
||||
});
|
||||
|
||||
test('Correct queuing', function(done) {
|
||||
model._wordSet = new Set(['a']);
|
||||
stubWordListConverter.toBlob.returns('someblob');
|
||||
|
||||
pSave1 = model._saveDict();
|
||||
pSave2 = model._saveDict();
|
||||
pSave3 = model._saveDict();
|
||||
|
||||
assert.isFalse(model._dbStore.setItems.called);
|
||||
|
||||
assert.isTrue(window.WordListConverter.calledWith(['a']));
|
||||
assert.isTrue(stubWordListConverter.toBlob.called);
|
||||
|
||||
// trigger the queue and resolve the first setItems Promise such that
|
||||
// pSave1's then() will execute. Same for the second and third Promises.
|
||||
|
||||
rSaveQueue();
|
||||
rDB1();
|
||||
|
||||
pSave1.then(() => {
|
||||
assert.isTrue(model._dbStore.setItems.calledOnce);
|
||||
rDB2();
|
||||
}, done);
|
||||
|
||||
pSave2.then(() => {
|
||||
assert.isTrue(model._dbStore.setItems.calledTwice);
|
||||
rDB3();
|
||||
}, done);
|
||||
|
||||
pSave3.then(() => {
|
||||
assert.isTrue(model._dbStore.setItems.calledThrice);
|
||||
assert.isTrue(model._dbStore.setItems.alwaysCalledWith({
|
||||
wordlist: ['a'],
|
||||
dictblob: 'someblob'
|
||||
}));
|
||||
|
||||
done();
|
||||
}, done);
|
||||
});
|
||||
|
||||
test('Empty list => undefined blob', function(done) {
|
||||
model._wordSet = new Set([]);
|
||||
|
||||
pSave1 = model._saveDict();
|
||||
|
||||
assert.isFalse(stubWordListConverter.toBlob.called);
|
||||
|
||||
rSaveQueue();
|
||||
rDB1();
|
||||
|
||||
pSave1.then(() => {
|
||||
assert.isTrue(model._dbStore.setItems.calledWith({
|
||||
wordlist: [],
|
||||
dictblob: undefined
|
||||
}));
|
||||
|
||||
done();
|
||||
}, done);
|
||||
});
|
||||
|
||||
test('Word list is returned and sorted', function(done) {
|
||||
model._wordSet = new Set(['apply', 'Banana', 'Apple']);
|
||||
|
||||
pSave1 = model._saveDict();
|
||||
|
||||
rSaveQueue();
|
||||
rDB1();
|
||||
|
||||
pSave1.then(wordList => {
|
||||
assert.deepEqual(wordList, ['Apple', 'apply', 'Banana']);
|
||||
|
||||
done();
|
||||
}, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
883
gaia/apps/keyboard/test/unit/settings/word_list_converter_test.js
Executable file
883
gaia/apps/keyboard/test/unit/settings/word_list_converter_test.js
Executable file
|
|
@ -0,0 +1,883 @@
|
|||
'use strict';
|
||||
|
||||
/* global WordListConverter */
|
||||
|
||||
require('/js/settings/word_list_converter.js');
|
||||
|
||||
suite('WordListConverter', function() {
|
||||
var expected;
|
||||
var actual;
|
||||
|
||||
// Our test strategy is very simple here: we feed a few word lists into the
|
||||
// converter and see if the resulting blob is the same as the hard-coded
|
||||
// "correct" blob.
|
||||
//
|
||||
// Indeed, there is possible further improvement for this test strategy:
|
||||
// For example, in the character frequency table, the order of same-frequency
|
||||
// characters should not matter. Within the same level of the TST, the order
|
||||
// of nodes linked by next pointers should not matter either (assuming they
|
||||
// have the same frequency).
|
||||
//
|
||||
// Ultimately, we might still need to take such ordering-neutral comparison
|
||||
// into consideration when we test the blobs, some day, if the underlying JS
|
||||
// implementation used by the converter changes and if such change modifies
|
||||
// the ordering assumed in the "correct" blob.
|
||||
|
||||
// This test script starts with simple tests. Tests become more and more
|
||||
// complex (with differnet unicode character sets and combinations) in later
|
||||
// suites. If more than one tests fail, it is wise to fix simple tests first,
|
||||
// such as latin, before looking to complex ones such as Hindi/Emoji.
|
||||
|
||||
// For binary structure of a dictionary blob, please see:
|
||||
// https://wiki.mozilla.org/Gaia/System/Keyboard/IME/Latin/Dictionary
|
||||
// The hardcoded blob may serve as extra examples for that wiki page.
|
||||
|
||||
var compareBlobs = function(actual, expected) {
|
||||
for (var i = 0; i < actual.length; i++) {
|
||||
assert.strictEqual(actual[i], expected[i], `position ${ i } differs`);
|
||||
}
|
||||
|
||||
assert.equal(actual.length, expected.length, 'length of two blobs differs');
|
||||
};
|
||||
|
||||
test('Blob type is ArrayBuffer', function(){
|
||||
assert.instanceOf(new WordListConverter(['a']).toBlob(), ArrayBuffer);
|
||||
});
|
||||
|
||||
suite('Basic latin characters', function() {
|
||||
test('single word', function(){
|
||||
actual = new Uint8Array(new WordListConverter(['apple']).toBlob());
|
||||
|
||||
expected = new Uint8Array([
|
||||
0x46, 0x78, 0x4f, 0x53, 0x44, 0x49, 0x43, 0x54, // FxOSDICT
|
||||
0x0, 0x0, 0x0, 0x1, // Unused bytes, version 1
|
||||
0x5, // longest word is 5-char long
|
||||
0x0, 0x4, // four characters in char table
|
||||
0x0, 0x70, 0x0, 0x0, 0x0, 0x2, // p, global occurrence = 2
|
||||
0x0, 0x61, 0x0, 0x0, 0x0, 0x1, // a, global occurrence = 1
|
||||
0x0, 0x6c, 0x0, 0x0, 0x0, 0x1, // l, global occurrence = 1
|
||||
0x0, 0x65, 0x0, 0x0, 0x0, 0x1, // e, global occurrence = 1
|
||||
0x8a, 0x61, // TST tree follow...
|
||||
0x8a, 0x70,
|
||||
0x8a, 0x70,
|
||||
0x8a, 0x6c,
|
||||
0x8a, 0x65,
|
||||
0x0a
|
||||
]);
|
||||
|
||||
compareBlobs(actual, expected);
|
||||
});
|
||||
|
||||
test('multiple words', function(){
|
||||
actual = new Uint8Array(new WordListConverter(
|
||||
['apple', 'orange', 'apply', 'organic', 'blueberry']).toBlob());
|
||||
|
||||
expected = new Uint8Array([
|
||||
0x46, 0x78, 0x4f, 0x53, 0x44, 0x49, 0x43, 0x54,
|
||||
0x0, 0x0, 0x0, 0x1,
|
||||
0x9, // longest word is 9-char long
|
||||
0x0, 0xd, // 13 characters in char table
|
||||
0x0, 0x61, 0x0, 0x0, 0x0, 0x4,
|
||||
0x0, 0x70, 0x0, 0x0, 0x0, 0x4,
|
||||
0x0, 0x65, 0x0, 0x0, 0x0, 0x4,
|
||||
0x0, 0x72, 0x0, 0x0, 0x0, 0x4,
|
||||
0x0, 0x6c, 0x0, 0x0, 0x0, 0x3,
|
||||
0x0, 0x6f, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x6e, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x67, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x79, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x62, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x69, 0x0, 0x0, 0x0, 0x1,
|
||||
0x0, 0x63, 0x0, 0x0, 0x0, 0x1,
|
||||
0x0, 0x75, 0x0, 0x0, 0x0, 0x1,
|
||||
0xaa, 0x61, 0x0, 0x0, 0x2f, // root node 'a', with next pointer
|
||||
0x8a, 0x70, // center node 'p'
|
||||
0x8a, 0x70, // center node 'p'
|
||||
0x8a, 0x6c, // center node 'l'
|
||||
0xaa, 0x65, 0x0, 0x0, 0x11, // center node 'e', with next pointer
|
||||
0x0a, // EndOfWord
|
||||
0x8a, 0x79, // node 'y', next-pointed from 'e' above
|
||||
0x0a, // EndOfWord
|
||||
0x8a, 0x6f, // node 'o', next-pointed from root 'b'
|
||||
0x8a, 0x72, // center node 'r'
|
||||
0xaa, 0x61, 0x0, 0x0, 0x24, // center node 'a', with next pointer
|
||||
0x8a, 0x6e, // center node 'n'
|
||||
0x8a, 0x67, // center node 'g'
|
||||
0x8a, 0x65, // center node 'e'
|
||||
0x0a, // EndOfWord
|
||||
0x8a, 0x67, // node 'g', next-pointed from 'a' above
|
||||
0x8a, 0x61, // node 'a'
|
||||
0x8a, 0x6e, // node 'n'
|
||||
0x8a, 0x69, // node 'i'
|
||||
0x8a, 0x63, // node 'c'
|
||||
0x0a, // EndOfWord
|
||||
0xaa, 0x62, 0x0, 0x0, 0x14, // node 'b', next-pointed by root 'a'
|
||||
// and next-point to 'o' on 0x14
|
||||
0x8a, 0x6c, // node 'l'
|
||||
0x8a, 0x75, // node 'u'
|
||||
0x8a, 0x65, // node 'e'
|
||||
0x8a, 0x62, // node 'b'
|
||||
0x8a, 0x65, // node 'e'
|
||||
0x8a, 0x72, // node 'r'
|
||||
0x8a, 0x72, // node 'r'
|
||||
0x8a, 0x79, // node 'y'
|
||||
0x0a // EndOfWord
|
||||
]);
|
||||
|
||||
compareBlobs(actual, expected);
|
||||
});
|
||||
|
||||
// Uppercase/lowercase/diacritics are treated as different characters.
|
||||
test('captilization', function(){
|
||||
actual = new Uint8Array(new WordListConverter(
|
||||
['mozIlla', 'Mozilla', 'mozilla', 'MOZILLA']).toBlob());
|
||||
|
||||
expected = new Uint8Array([
|
||||
0x46, 0x78, 0x4f, 0x53, 0x44, 0x49, 0x43, 0x54,
|
||||
0x0, 0x0, 0x0, 0x1,
|
||||
0x7,
|
||||
0x0, 0xc,
|
||||
0x0, 0x6c, 0x0, 0x0, 0x0, 0x6,
|
||||
0x0, 0x6f, 0x0, 0x0, 0x0, 0x3,
|
||||
0x0, 0x7a, 0x0, 0x0, 0x0, 0x3,
|
||||
0x0, 0x61, 0x0, 0x0, 0x0, 0x3,
|
||||
0x0, 0x6d, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x49, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x4d, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x69, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x4c, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x4f, 0x0, 0x0, 0x0, 0x1,
|
||||
0x0, 0x5a, 0x0, 0x0, 0x0, 0x1,
|
||||
0x0, 0x41, 0x0, 0x0, 0x0, 0x1,
|
||||
0xaa, 0x4d, 0x0, 0x0, 0x22, // 'M', next is 'm' at 0x22
|
||||
0xaa, 0x4f, 0x0, 0x0, 0x15, // 'O', next is 'o' at 0x15
|
||||
0x8a, 0x5a, // 'Z'
|
||||
0x8a, 0x49, // 'I'
|
||||
0x8a, 0x4c, // 'L'
|
||||
0x8a, 0x4c, // 'L'
|
||||
0x8a, 0x41, // 'A'
|
||||
0x0a,
|
||||
0x8a, 0x6f, // 'o'
|
||||
0x8a, 0x7a, // 'z'
|
||||
0x8a, 0x69, // 'i'
|
||||
0x8a, 0x6c, // 'l'
|
||||
0x8a, 0x6c, // 'l'
|
||||
0x8a, 0x61, // 'a'
|
||||
0x0a,
|
||||
0x8a, 0x6d, // 'm'
|
||||
0x8a, 0x6f, // 'o'
|
||||
0x8a, 0x7a, // 'z'
|
||||
0xaa, 0x49, 0x0, 0x0, 0x34, // 'i', next is 'I' at 0x34
|
||||
0x8a, 0x6c, // 'l'
|
||||
0x8a, 0x6c, // 'l'
|
||||
0x8a, 0x61, // 'a'
|
||||
0x0a,
|
||||
0x8a, 0x69, // 'I'
|
||||
0x8a, 0x6c, // 'l'
|
||||
0x8a, 0x6c, // 'l'
|
||||
0x8a, 0x61, // 'a'
|
||||
0x0a
|
||||
]);
|
||||
|
||||
compareBlobs(actual, expected);
|
||||
});
|
||||
|
||||
test('diacritics', function(){
|
||||
actual = new Uint8Array(new WordListConverter(
|
||||
['mÖz', 'Ɱoz', 'Moz', 'mOz', 'ḿoƶ']).toBlob());
|
||||
|
||||
expected = new Uint8Array([
|
||||
0x46, 0x78, 0x4f, 0x53, 0x44, 0x49, 0x43, 0x54,
|
||||
0x0, 0x0, 0x0, 0x1,
|
||||
0x3,
|
||||
0x0, 0x9,
|
||||
0x0, 0x7a, 0x0, 0x0, 0x0, 0x4,
|
||||
0x0, 0x6f, 0x0, 0x0, 0x0, 0x3,
|
||||
0x0, 0x6d, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0xd6, 0x0, 0x0, 0x0, 0x1,
|
||||
0x2c, 0x6e, 0x0, 0x0, 0x0, 0x1, // 'Ɱ' (\u2c6e)
|
||||
0x0, 0x4d, 0x0, 0x0, 0x0, 0x1,
|
||||
0x0, 0x4f, 0x0, 0x0, 0x0, 0x1,
|
||||
0x1e, 0x3f, 0x0, 0x0, 0x0, 0x1, // 'ḿ' (\u1e3f)
|
||||
0x1, 0xb6, 0x0, 0x0, 0x0, 0x1, // 'ƶ' (\u01b6)
|
||||
0xaa, 0x4d, 0x0, 0x0, 0x16,
|
||||
0x8a, 0x6f,
|
||||
0x8a, 0x7a,
|
||||
0x0a,
|
||||
0xea, 0x1e, 0x3f, 0x0, 0x0, 0x28, // 2-byte char 'ḿ' with next pointer
|
||||
0x8a, 0x6f,
|
||||
0xca, 0x1, 0xb6, // 2-byte char 'ƶ' as center node
|
||||
0x0a,
|
||||
0xaa, 0x6d, 0x0, 0x0, 0xa,
|
||||
0xaa, 0x4f, 0x0, 0x0, 0x23,
|
||||
0x8a, 0x7a,
|
||||
0x0a,
|
||||
0x8a, 0xd6,
|
||||
0x8a, 0x7a,
|
||||
0x0a,
|
||||
0xca, 0x2c, 0x6e, // 2-byte char 'Ɱ'
|
||||
0x8a, 0x6f,
|
||||
0x8a, 0x7a,
|
||||
0x0a
|
||||
]);
|
||||
|
||||
compareBlobs(actual, expected);
|
||||
});
|
||||
});
|
||||
|
||||
// Although we don't really expect the presence of non-latin characters in the
|
||||
// blob, we still need to check that the converter won't fail (either "soft"
|
||||
// failure by encoding wrong data (that would affect latin prediction) or a
|
||||
// "strong" failure by throwing an error) when fed with such characters.
|
||||
|
||||
// The following "correct" blobs are manually pre-tested to make sure they
|
||||
// still produce latin-character predictions correctly.
|
||||
// (Actually, I believe CJK predictions should also work, as long as we have a
|
||||
// sensible |nearByKeys| structure, but... there is no such structure that
|
||||
// can be (even remotely) sensible.)
|
||||
suite('CJK characters', function() {
|
||||
test('basic CJK test with only CJK chars', function(){
|
||||
actual = new Uint8Array(new WordListConverter(
|
||||
// The words are Hello / Good Morning / Good Evening in CJK.
|
||||
['你好', '你好嗎', 'こんにちは', 'こんばんは', '안녕하세요']).toBlob());
|
||||
|
||||
expected = new Uint8Array([
|
||||
0x46, 0x78, 0x4f, 0x53, 0x44, 0x49, 0x43, 0x54,
|
||||
0x0, 0x0, 0x0, 0x1,
|
||||
0x5, // longest word is 5-char long
|
||||
0x0, 0xe, // 14 characters in char table
|
||||
0x30, 0x93, 0x0, 0x0, 0x0, 0x3,
|
||||
0x4f, 0x60, 0x0, 0x0, 0x0, 0x2,
|
||||
0x59, 0x7d, 0x0, 0x0, 0x0, 0x2,
|
||||
0x30, 0x53, 0x0, 0x0, 0x0, 0x2,
|
||||
0x30, 0x6f, 0x0, 0x0, 0x0, 0x2,
|
||||
0x55, 0xce, 0x0, 0x0, 0x0, 0x1,
|
||||
0x30, 0x6b, 0x0, 0x0, 0x0, 0x1,
|
||||
0x30, 0x61, 0x0, 0x0, 0x0, 0x1,
|
||||
0x30, 0x70, 0x0, 0x0, 0x0, 0x1,
|
||||
0xc5, 0x48, 0x0, 0x0, 0x0, 0x1,
|
||||
0xb1, 0x55, 0x0, 0x0, 0x0, 0x1,
|
||||
0xd5, 0x58, 0x0, 0x0, 0x0, 0x1,
|
||||
0xc1, 0x38, 0x0, 0x0, 0x0, 0x1,
|
||||
0xc6, 0x94, 0x0, 0x0, 0x0, 0x1,
|
||||
0xea, 0x30, 0x53, 0x0, 0x0, 0x30, // 'こ', next-point to '你'
|
||||
0xca, 0x30, 0x93, // 'ん'
|
||||
0xea, 0x30, 0x6b, 0x0, 0x0, 0x16, // 'に', next-point to 'ば'
|
||||
0xca, 0x30, 0x61, // 'ち'
|
||||
0xca, 0x30, 0x6f, // 'は'
|
||||
0x0a,
|
||||
0xca, 0x30, 0x70, // 'ば'
|
||||
0xca, 0x30, 0x93, // 'ん'
|
||||
0xca, 0x30, 0x6f, // 'は'
|
||||
0x0a,
|
||||
0xca, 0xc5, 0x48, // '안'
|
||||
0xca, 0xb1, 0x55, // '녕'
|
||||
0xca, 0xd5, 0x58, // '하'
|
||||
0xca, 0xc1, 0x38, // '세'
|
||||
0xca, 0xc6, 0x94, // '요'
|
||||
0x0a,
|
||||
0xea, 0x4f, 0x60, 0x0, 0x0, 0x20, // '你', next-point to '안'
|
||||
0xca, 0x59, 0x7d, // '好'
|
||||
0x2a, 0x0, 0x0, 0x3d, // EndOfWord, next-point to '嗎'
|
||||
0xca, 0x55, 0xce, // '嗎'
|
||||
0x0a
|
||||
]);
|
||||
|
||||
compareBlobs(actual, expected);
|
||||
});
|
||||
|
||||
// the test is crafted that the composing bytes of the chinese characters
|
||||
// (e.g. 0x4f = 'O', 0x59 = 'Y', 0x55 = 'U', 0xce = 'Î') are re-used, as
|
||||
// single-byte latin chars, appearing in other words.
|
||||
// again, the "correct" blob has been pre-tested to make sure that the
|
||||
// prediction of those single-byte latin chars works.
|
||||
test('CJK+latin chars', function(){
|
||||
actual = new Uint8Array(new WordListConverter(
|
||||
['SAY', 'SUB', '你好', 'LATÎN', 'LATIN', 'SON', 'RON',
|
||||
'你好嗎', 'RUN', 'run', '好嗎', 'Uni', 'UnÎdentity']).toBlob());
|
||||
|
||||
expected = new Uint8Array([
|
||||
0x46, 0x78, 0x4f, 0x53, 0x44, 0x49, 0x43, 0x54,
|
||||
0x0, 0x0, 0x0, 0x1,
|
||||
0xa,
|
||||
0x0, 0x17,
|
||||
0x0, 0x4e, 0x0, 0x0, 0x0, 0x5,
|
||||
0x0, 0x55, 0x0, 0x0, 0x0, 0x4,
|
||||
0x0, 0x6e, 0x0, 0x0, 0x0, 0x4,
|
||||
0x0, 0x53, 0x0, 0x0, 0x0, 0x3,
|
||||
0x0, 0x41, 0x0, 0x0, 0x0, 0x3,
|
||||
0x59, 0x7d, 0x0, 0x0, 0x0, 0x3,
|
||||
0x4f, 0x60, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x4c, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x54, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0xce, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x4f, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x52, 0x0, 0x0, 0x0, 0x2,
|
||||
0x55, 0xce, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x69, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x74, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x59, 0x0, 0x0, 0x0, 0x1,
|
||||
0x0, 0x42, 0x0, 0x0, 0x0, 0x1,
|
||||
0x0, 0x49, 0x0, 0x0, 0x0, 0x1,
|
||||
0x0, 0x72, 0x0, 0x0, 0x0, 0x1,
|
||||
0x0, 0x75, 0x0, 0x0, 0x0, 0x1,
|
||||
0x0, 0x64, 0x0, 0x0, 0x0, 0x1,
|
||||
0x0, 0x65, 0x0, 0x0, 0x0, 0x1,
|
||||
0x0, 0x79, 0x0, 0x0, 0x0, 0x1,
|
||||
0xaa, 0x4c, 0x0, 0x0, 0x3a,
|
||||
0x8a, 0x41,
|
||||
0x8a, 0x54,
|
||||
0xaa, 0x49, 0x0, 0x0, 0x11,
|
||||
0x8a, 0x4e,
|
||||
0x0a,
|
||||
0x8a, 0xce,
|
||||
0x8a, 0x4e,
|
||||
0x0a,
|
||||
0xaa, 0x72, 0x0, 0x0, 0x71,
|
||||
0x8a, 0x75,
|
||||
0x8a, 0x6e,
|
||||
0x0a,
|
||||
0xaa, 0x53, 0x0, 0x0, 0x4c,
|
||||
0xaa, 0x41, 0x0, 0x0, 0x32,
|
||||
0x8a, 0x59,
|
||||
0x0a,
|
||||
0x8a, 0x55,
|
||||
0x8a, 0x42,
|
||||
0x0a,
|
||||
0xaa, 0x4f, 0x0, 0x0, 0x2d,
|
||||
0x8a, 0x4e,
|
||||
0x0a,
|
||||
0xaa, 0x52, 0x0, 0x0, 0x20,
|
||||
0xaa, 0x4f, 0x0, 0x0, 0x47,
|
||||
0x8a, 0x4e,
|
||||
0x0a,
|
||||
0x8a, 0x55,
|
||||
0x8a, 0x4e,
|
||||
0x0a,
|
||||
0xaa, 0x55, 0x0, 0x0, 0x16,
|
||||
0x8a, 0x6e,
|
||||
0xaa, 0x69, 0x0, 0x0, 0x59,
|
||||
0x0a,
|
||||
0x8a, 0xce,
|
||||
0x8a, 0x64,
|
||||
0x8a, 0x65,
|
||||
0x8a, 0x6e,
|
||||
0x8a, 0x74,
|
||||
0x8a, 0x69,
|
||||
0x8a, 0x74,
|
||||
0x8a, 0x79,
|
||||
0x0a,
|
||||
0xca, 0x59, 0x7d,
|
||||
0xca, 0x55, 0xce,
|
||||
0x0a,
|
||||
0xea, 0x4f, 0x60, 0x0, 0x0, 0x6a,
|
||||
0xca, 0x59, 0x7d,
|
||||
0x2a, 0x0, 0x0, 0x7e,
|
||||
0xca, 0x55, 0xce,
|
||||
0x0a
|
||||
]);
|
||||
|
||||
compareBlobs(actual, expected);
|
||||
});
|
||||
});
|
||||
|
||||
// Our entire architecture has little (if any) regard to unicode combining
|
||||
// characters such as Devanagari in Hindi, and treat each one unicode char as
|
||||
// ...just one indepedent character. Still, we need to test if the converter
|
||||
// maintains such treatment, and like how we're testing in CJK, whether those
|
||||
// characters won't interfere with latin chars.
|
||||
suite('Hindi', function() {
|
||||
test('basic Hindi chars with only Hindi chars', function(){
|
||||
// The words are translated from "Hi", "Good morning" and "Thank you".
|
||||
actual = new Uint8Array(new WordListConverter(
|
||||
['नमस्ते', 'सुप्रभात', 'शुक्रिया']).toBlob());
|
||||
|
||||
expected = new Uint8Array([
|
||||
0x46, 0x78, 0x4f, 0x53, 0x44, 0x49, 0x43, 0x54,
|
||||
0x0, 0x0, 0x0, 0x1,
|
||||
0x8,
|
||||
0x0, 0xf,
|
||||
0x9, 0x4d, 0x0, 0x0, 0x0, 0x3,
|
||||
0x9, 0x38, 0x0, 0x0, 0x0, 0x2,
|
||||
0x9, 0x24, 0x0, 0x0, 0x0, 0x2,
|
||||
0x9, 0x41, 0x0, 0x0, 0x0, 0x2,
|
||||
0x9, 0x30, 0x0, 0x0, 0x0, 0x2,
|
||||
0x9, 0x3e, 0x0, 0x0, 0x0, 0x2,
|
||||
0x9, 0x28, 0x0, 0x0, 0x0, 0x1,
|
||||
0x9, 0x2e, 0x0, 0x0, 0x0, 0x1,
|
||||
0x9, 0x47, 0x0, 0x0, 0x0, 0x1,
|
||||
0x9, 0x2a, 0x0, 0x0, 0x0, 0x1,
|
||||
0x9, 0x2d, 0x0, 0x0, 0x0, 0x1,
|
||||
0x9, 0x36, 0x0, 0x0, 0x0, 0x1,
|
||||
0x9, 0x15, 0x0, 0x0, 0x0, 0x1,
|
||||
0x9, 0x3f, 0x0, 0x0, 0x0, 0x1,
|
||||
0x9, 0x2f, 0x0, 0x0, 0x0, 0x1,
|
||||
0xea, 0x9, 0x28, 0x0, 0x0, 0x2f,
|
||||
0xca, 0x9, 0x2e,
|
||||
0xca, 0x9, 0x38,
|
||||
0xca, 0x9, 0x4d,
|
||||
0xca, 0x9, 0x24,
|
||||
0xca, 0x9, 0x47,
|
||||
0x0a,
|
||||
0xca, 0x9, 0x38,
|
||||
0xca, 0x9, 0x41,
|
||||
0xca, 0x9, 0x2a,
|
||||
0xca, 0x9, 0x4d,
|
||||
0xca, 0x9, 0x30,
|
||||
0xca, 0x9, 0x2d,
|
||||
0xca, 0x9, 0x3e,
|
||||
0xca, 0x9, 0x24,
|
||||
0x0a,
|
||||
0xea, 0x9, 0x36, 0x0, 0x0, 0x16,
|
||||
0xca, 0x9, 0x41,
|
||||
0xca, 0x9, 0x15,
|
||||
0xca, 0x9, 0x4d,
|
||||
0xca, 0x9, 0x30,
|
||||
0xca, 0x9, 0x3f,
|
||||
0xca, 0x9, 0x2f,
|
||||
0xca, 0x9, 0x3e,
|
||||
0x0a
|
||||
]);
|
||||
|
||||
compareBlobs(actual, expected);
|
||||
});
|
||||
|
||||
// similar to CJK+latin test. we re-use 0x4d = 'M', 0x38 = '8', 0x41 = 'A',
|
||||
// 0x47 = 'G', 0x3f = '?' here.
|
||||
test('Hindi+latin chars', function(){
|
||||
actual = new Uint8Array(new WordListConverter(
|
||||
['Apple?', 'नमस्ते', 'MAGAZINE', 'Game',
|
||||
'सुप्रभात', 'AM?', 'शुक्रिया']).toBlob());
|
||||
|
||||
expected = new Uint8Array([
|
||||
0x46, 0x78, 0x4f, 0x53, 0x44, 0x49, 0x43, 0x54,
|
||||
0x0, 0x0, 0x0, 0x1,
|
||||
0x8,
|
||||
0x0, 0x1c,
|
||||
0x0, 0x41, 0x0, 0x0, 0x0, 0x4,
|
||||
0x9, 0x4d, 0x0, 0x0, 0x0, 0x3,
|
||||
0x0, 0x70, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x65, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x3f, 0x0, 0x0, 0x0, 0x2,
|
||||
0x9, 0x38, 0x0, 0x0, 0x0, 0x2,
|
||||
0x9, 0x24, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x4d, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x47, 0x0, 0x0, 0x0, 0x2,
|
||||
0x9, 0x41, 0x0, 0x0, 0x0, 0x2,
|
||||
0x9, 0x30, 0x0, 0x0, 0x0, 0x2,
|
||||
0x9, 0x3e, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x6c, 0x0, 0x0, 0x0, 0x1,
|
||||
0x9, 0x28, 0x0, 0x0, 0x0, 0x1,
|
||||
0x9, 0x2e, 0x0, 0x0, 0x0, 0x1,
|
||||
0x9, 0x47, 0x0, 0x0, 0x0, 0x1,
|
||||
0x0, 0x5a, 0x0, 0x0, 0x0, 0x1,
|
||||
0x0, 0x49, 0x0, 0x0, 0x0, 0x1,
|
||||
0x0, 0x4e, 0x0, 0x0, 0x0, 0x1,
|
||||
0x0, 0x45, 0x0, 0x0, 0x0, 0x1,
|
||||
0x0, 0x61, 0x0, 0x0, 0x0, 0x1,
|
||||
0x0, 0x6d, 0x0, 0x0, 0x0, 0x1,
|
||||
0x9, 0x2a, 0x0, 0x0, 0x0, 0x1,
|
||||
0x9, 0x2d, 0x0, 0x0, 0x0, 0x1,
|
||||
0x9, 0x36, 0x0, 0x0, 0x0, 0x1,
|
||||
0x9, 0x15, 0x0, 0x0, 0x0, 0x1,
|
||||
0x9, 0x3f, 0x0, 0x0, 0x0, 0x1,
|
||||
0x9, 0x2f, 0x0, 0x0, 0x0, 0x1,
|
||||
0xaa, 0x41, 0x0, 0x0, 0x42,
|
||||
0xaa, 0x4d, 0x0, 0x0, 0xd,
|
||||
0x8a, 0x3f,
|
||||
0x0a,
|
||||
0x8a, 0x70,
|
||||
0x8a, 0x70,
|
||||
0x8a, 0x6c,
|
||||
0x8a, 0x65,
|
||||
0x8a, 0x3f,
|
||||
0x0a,
|
||||
0xea, 0x9, 0x28, 0x0, 0x0, 0x67,
|
||||
0xca, 0x9, 0x2e,
|
||||
0xca, 0x9, 0x38,
|
||||
0xca, 0x9, 0x4d,
|
||||
0xca, 0x9, 0x24,
|
||||
0xca, 0x9, 0x47,
|
||||
0x0a,
|
||||
0xaa, 0x4d, 0x0, 0x0, 0x18,
|
||||
0x8a, 0x41,
|
||||
0x8a, 0x47,
|
||||
0x8a, 0x41,
|
||||
0x8a, 0x5a,
|
||||
0x8a, 0x49,
|
||||
0x8a, 0x4e,
|
||||
0x8a, 0x45,
|
||||
0x0a,
|
||||
0xaa, 0x47, 0x0, 0x0, 0x2e,
|
||||
0x8a, 0x61,
|
||||
0x8a, 0x6d,
|
||||
0x8a, 0x65,
|
||||
0x0a,
|
||||
0xca, 0x9, 0x38,
|
||||
0xca, 0x9, 0x41,
|
||||
0xca, 0x9, 0x2a,
|
||||
0xca, 0x9, 0x4d,
|
||||
0xca, 0x9, 0x30,
|
||||
0xca, 0x9, 0x2d,
|
||||
0xca, 0x9, 0x3e,
|
||||
0xca, 0x9, 0x24,
|
||||
0x0a,
|
||||
0xea, 0x9, 0x36, 0x0, 0x0, 0x4e,
|
||||
0xca, 0x9, 0x41,
|
||||
0xca, 0x9, 0x15,
|
||||
0xca, 0x9, 0x4d,
|
||||
0xca, 0x9, 0x30,
|
||||
0xca, 0x9, 0x3f,
|
||||
0xca, 0x9, 0x2f,
|
||||
0xca, 0x9, 0x3e,
|
||||
0x0a
|
||||
]);
|
||||
|
||||
compareBlobs(actual, expected);
|
||||
});
|
||||
});
|
||||
|
||||
// Emoji characters on the supplimentary plane allow us to make sure surrogate
|
||||
// pairs don't break. Indeed they shouldn't -- the converter should just treat
|
||||
// them as two unicode "chars".
|
||||
// We're getting into no man's land here, more so than Hindi's combining
|
||||
// chars -- The original xml2dict.py can't even properly encode unicode chars
|
||||
// outside BMP range since it expects a character to be at most 2 bytes.
|
||||
suite('Emoji', function() {
|
||||
// To make the tests more tangible (to human readers) those emojis are
|
||||
// separately defined as const with their UTF-8 surrogate pair
|
||||
// representation commented.
|
||||
const emoSmile = '😀'; // '\ud83d\ude00'
|
||||
const emoCar = '🚘'; // '\ud83d\ude98'
|
||||
const emoTennis = '🎾'; // '\ud83c\udfbe'
|
||||
const emoChesnut = '🌰'; // '\ud83c\udf30'
|
||||
|
||||
test('basic Emoji', function(){
|
||||
actual = new Uint8Array(new WordListConverter(
|
||||
[emoSmile,
|
||||
emoSmile + emoCar,
|
||||
emoTennis + emoChesnut + emoCar]).toBlob());
|
||||
|
||||
expected = new Uint8Array([
|
||||
0x46, 0x78, 0x4f, 0x53, 0x44, 0x49, 0x43, 0x54,
|
||||
0x0, 0x0, 0x0, 0x1,
|
||||
0x6,
|
||||
0x0, 0x6,
|
||||
0xd8, 0x3d, 0x0, 0x0, 0x0, 0x4,
|
||||
0xde, 0x0, 0x0, 0x0, 0x0, 0x2,
|
||||
0xde, 0x98, 0x0, 0x0, 0x0, 0x2,
|
||||
0xd8, 0x3c, 0x0, 0x0, 0x0, 0x2,
|
||||
0xdf, 0xbe, 0x0, 0x0, 0x0, 0x1,
|
||||
0xdf, 0x30, 0x0, 0x0, 0x0, 0x1,
|
||||
0xea, 0xd8, 0x3c, 0x0, 0x0, 0x16,
|
||||
0xca, 0xdf, 0xbe,
|
||||
0xca, 0xd8, 0x3c,
|
||||
0xca, 0xdf, 0x30,
|
||||
0xca, 0xd8, 0x3d,
|
||||
0xca, 0xde, 0x98,
|
||||
0x0a,
|
||||
0xca, 0xd8, 0x3d,
|
||||
0xca, 0xde, 0x0,
|
||||
0x2a, 0x0, 0x0, 0x20,
|
||||
0xca, 0xd8, 0x3d,
|
||||
0xca, 0xde, 0x98,
|
||||
0x0a
|
||||
]);
|
||||
|
||||
compareBlobs(actual, expected);
|
||||
});
|
||||
|
||||
// again we reuse some single byte from the emoji surrogate pairs in
|
||||
// the CJK/latin characters here, to check whether the converter doesn't
|
||||
// mess up.
|
||||
// 0x3c = '<', 0x3d = '=', 0x30 = '0'
|
||||
// \u5398 = '厘', \u58be = '墾'
|
||||
// \u30de = 'マ', \u30df = 'ミ'
|
||||
// \u30d8 = 'ヘ' (note: this is Katagana "he", not Hiragana)
|
||||
// マスタークラス is "Master Class" in Japanese
|
||||
// 墾丁 is a tourist spot in Taiwan
|
||||
// 厘米 is a way to say centimeter in Chinese
|
||||
// ミリメートル is millimeter in Japanese
|
||||
test('Emoji + CJK and latin words', function(){
|
||||
actual = new Uint8Array(new WordListConverter(
|
||||
['<' + emoChesnut + '>',
|
||||
'マスター' + '=' + emoCar + '=' + 'クラス',
|
||||
emoTennis + '墾丁',
|
||||
emoCar + '厘米' + 'ミリメートル' + emoChesnut,
|
||||
emoSmile + 'ヘ' + emoCar,
|
||||
emoSmile
|
||||
]).toBlob());
|
||||
|
||||
expected = new Uint8Array([
|
||||
0x46, 0x78, 0x4f, 0x53, 0x44, 0x49, 0x43, 0x54,
|
||||
0x0, 0x0, 0x0, 0x1,
|
||||
0xc,
|
||||
0x0, 0x19,
|
||||
0xd8, 0x3d, 0x0, 0x0, 0x0, 0x5,
|
||||
0xd8, 0x3c, 0x0, 0x0, 0x0, 0x3,
|
||||
0xde, 0x98, 0x0, 0x0, 0x0, 0x3,
|
||||
0xdf, 0x30, 0x0, 0x0, 0x0, 0x2,
|
||||
0x30, 0xb9, 0x0, 0x0, 0x0, 0x2,
|
||||
0x30, 0xfc, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x3d, 0x0, 0x0, 0x0, 0x2,
|
||||
0xde, 0x0, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x3c, 0x0, 0x0, 0x0, 0x1,
|
||||
0x0, 0x3e, 0x0, 0x0, 0x0, 0x1,
|
||||
0x30, 0xde, 0x0, 0x0, 0x0, 0x1,
|
||||
0x30, 0xbf, 0x0, 0x0, 0x0, 0x1,
|
||||
0x30, 0xaf, 0x0, 0x0, 0x0, 0x1,
|
||||
0x30, 0xe9, 0x0, 0x0, 0x0, 0x1,
|
||||
0xdf, 0xbe, 0x0, 0x0, 0x0, 0x1,
|
||||
0x58, 0xbe, 0x0, 0x0, 0x0, 0x1,
|
||||
0x4e, 0x1, 0x0, 0x0, 0x0, 0x1,
|
||||
0x53, 0x98, 0x0, 0x0, 0x0, 0x1,
|
||||
0x7c, 0x73, 0x0, 0x0, 0x0, 0x1,
|
||||
0x30, 0xdf, 0x0, 0x0, 0x0, 0x1,
|
||||
0x30, 0xea, 0x0, 0x0, 0x0, 0x1,
|
||||
0x30, 0xe1, 0x0, 0x0, 0x0, 0x1,
|
||||
0x30, 0xc8, 0x0, 0x0, 0x0, 0x1,
|
||||
0x30, 0xeb, 0x0, 0x0, 0x0, 0x1,
|
||||
0x30, 0xd8, 0x0, 0x0, 0x0, 0x1,
|
||||
0xaa, 0x3c, 0x0, 0x0, 0x1e,
|
||||
0xca, 0xd8, 0x3c,
|
||||
0xca, 0xdf, 0x30,
|
||||
0x8a, 0x3e,
|
||||
0x0a,
|
||||
0xea, 0xd8, 0x3c, 0x0, 0x0, 0x41,
|
||||
0xca, 0xdf, 0xbe,
|
||||
0xca, 0x58, 0xbe,
|
||||
0xca, 0x4e, 0x1,
|
||||
0x0a,
|
||||
0xea, 0x30, 0xde, 0x0, 0x0, 0xe,
|
||||
0xca, 0x30, 0xb9,
|
||||
0xca, 0x30, 0xbf,
|
||||
0xca, 0x30, 0xfc,
|
||||
0x8a, 0x3d,
|
||||
0xca, 0xd8, 0x3d,
|
||||
0xca, 0xde, 0x98,
|
||||
0x8a, 0x3d,
|
||||
0xca, 0x30, 0xaf,
|
||||
0xca, 0x30, 0xe9,
|
||||
0xca, 0x30, 0xb9,
|
||||
0x0a,
|
||||
0xca, 0xd8, 0x3d,
|
||||
0xea, 0xde, 0x0, 0x0, 0x0, 0x58,
|
||||
0x2a, 0x0, 0x0, 0x4e,
|
||||
0xca, 0x30, 0xd8,
|
||||
0xca, 0xd8, 0x3d,
|
||||
0xca, 0xde, 0x98,
|
||||
0x0a,
|
||||
0xca, 0xde, 0x98,
|
||||
0xca, 0x53, 0x98,
|
||||
0xca, 0x7c, 0x73,
|
||||
0xca, 0x30, 0xdf,
|
||||
0xca, 0x30, 0xea,
|
||||
0xca, 0x30, 0xe1,
|
||||
0xca, 0x30, 0xfc,
|
||||
0xca, 0x30, 0xc8,
|
||||
0xca, 0x30, 0xeb,
|
||||
0xca, 0xd8, 0x3c,
|
||||
0xca, 0xdf, 0x30,
|
||||
0x0a
|
||||
]);
|
||||
|
||||
compareBlobs(actual, expected);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Word list with frequency information', function() {
|
||||
suite('Rejections', function() {
|
||||
var dummy;
|
||||
test('Reject mixed input', function(){
|
||||
assert.throws(() => {
|
||||
dummy =
|
||||
new WordListConverter(['simpleword', {w: 'anotherword', f: 0.5}]);
|
||||
}, /^Type mismatch\. previous: \S+, this: \S+$/);
|
||||
|
||||
assert.throws(() => {
|
||||
dummy =
|
||||
new WordListConverter([{w: 'simpleword', f: 0.5}, 'anotherword']);
|
||||
}, /^Type mismatch\. previous: \S+, this: \S+$/);
|
||||
});
|
||||
|
||||
test('Reject object elements without proper fields', function(){
|
||||
assert.throws(() => {
|
||||
dummy = new WordListConverter([{f: 0.5}]);
|
||||
}, /^"w" field not found in word$/);
|
||||
|
||||
assert.throws(() => {
|
||||
dummy = new WordListConverter([{w: 'word'}]);
|
||||
}, /^"f" field not found in word$/);
|
||||
|
||||
assert.throws(() => {
|
||||
dummy = new WordListConverter([{w: 'word', f: 1}]);
|
||||
}, /^"f" value not in allowed range$/);
|
||||
|
||||
assert.throws(() => {
|
||||
dummy = new WordListConverter([{w: 'word', f: 100}]);
|
||||
}, /^"f" value not in allowed range$/);
|
||||
|
||||
assert.throws(() => {
|
||||
dummy = new WordListConverter([{w: 'word', f: 255}]);
|
||||
}, /^"f" value not in allowed range$/);
|
||||
});
|
||||
});
|
||||
|
||||
test('multiple words including capitalization differences', function(){
|
||||
actual = new Uint8Array(new WordListConverter(
|
||||
[{w: 'apple', f: 0.5},
|
||||
{w: 'origami', f: 0.18},
|
||||
{w: 'orange', f: 0.6},
|
||||
{w: 'apply', f: 0.8},
|
||||
{w: 'organic', f: 0.7},
|
||||
{w: 'blueberry', f: 0.15},
|
||||
{w: 'asymptotic', f: 0.2},
|
||||
{w: 'Asia', f: 0.75}
|
||||
]).toBlob());
|
||||
|
||||
expected = new Uint8Array([
|
||||
0x46, 0x78, 0x4f, 0x53, 0x44, 0x49, 0x43, 0x54,
|
||||
0x0, 0x0, 0x0, 0x1,
|
||||
0xa,
|
||||
0x0, 0x11,
|
||||
0x0, 0x61, 0x0, 0x0, 0x0, 0x7,
|
||||
0x0, 0x70, 0x0, 0x0, 0x0, 0x5,
|
||||
0x0, 0x72, 0x0, 0x0, 0x0, 0x5,
|
||||
0x0, 0x69, 0x0, 0x0, 0x0, 0x5,
|
||||
0x0, 0x65, 0x0, 0x0, 0x0, 0x4,
|
||||
0x0, 0x6f, 0x0, 0x0, 0x0, 0x4,
|
||||
0x0, 0x6c, 0x0, 0x0, 0x0, 0x3,
|
||||
0x0, 0x67, 0x0, 0x0, 0x0, 0x3,
|
||||
0x0, 0x79, 0x0, 0x0, 0x0, 0x3,
|
||||
0x0, 0x6d, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x6e, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x63, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x62, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x73, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x74, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x75, 0x0, 0x0, 0x0, 0x1,
|
||||
0x0, 0x41, 0x0, 0x0, 0x0, 0x1,
|
||||
0xb9, 0x61, 0x0, 0x0, 0x2a, // root node 'a' as 'apply' is highest
|
||||
// freq; 0xb9 = 0b10111001
|
||||
// (c and n bits set, freq = 25),
|
||||
// 25 is the frequency of 'apply', i.e.
|
||||
// the most-frequent word from 'a'
|
||||
0xb9, 0x70, 0x0, 0x0, 0x17, // second level 'p' as 'apply' is more
|
||||
// frequent than 'asymptotic'
|
||||
0x99, 0x70, // 0x99 = 0b10011001 (c bit set, f = 25)
|
||||
0x99, 0x6c,
|
||||
0xb9, 0x79, 0x0, 0x0, 0x14, // 'y' of apply, with next to 'e', as
|
||||
// 'apply' is more frequent than 'apple'
|
||||
0x19, // EoW of 'apply', freq = 25
|
||||
0x90, 0x65, // 'e', freq = 16
|
||||
0x10, // EoW of 'apple', freq = 16
|
||||
0x87, 0x73, // 's', next-pointed from second-level
|
||||
// 'p' below root-level 'a'
|
||||
// freq = 7
|
||||
0x87, 0x79,
|
||||
0x87, 0x6d,
|
||||
0x87, 0x70,
|
||||
0x87, 0x74,
|
||||
0x87, 0x6f,
|
||||
0x87, 0x74,
|
||||
0x87, 0x69,
|
||||
0x87, 0x63,
|
||||
0x7, // EoW of 'asymptotic'
|
||||
0xb8, 0x41, 0x0, 0x0, 0x36, // root-level 'A' as next-pointed by
|
||||
// root 'a', as 'Asia' is the second
|
||||
// frequent (than 'apply') and more
|
||||
// frequent than 'organic and
|
||||
// 'blueberry'. freq = 24,
|
||||
// 24 is the frequency of 'Asia', i.e.
|
||||
// the most-frequent word from 'A'
|
||||
0x98, 0x73,
|
||||
0x98, 0x69,
|
||||
0x98, 0x61,
|
||||
0x18, // EoW of 'Asia'
|
||||
0xb6, 0x6f, 0x0, 0x0, 0x62, // root-level 'o' as next-pointed by
|
||||
// root 'A', as 'o' is the next frequent
|
||||
// and freq = 22, frequency of 'organic'
|
||||
0x96, 0x72, // 'r'
|
||||
0xb6, 0x67, 0x0, 0x0, 0x4b, // 3rd-level 'g', next to 'a'. freq = 22
|
||||
// = freq of 'organic'
|
||||
0x96, 0x61,
|
||||
0x96, 0x6e,
|
||||
0x96, 0x69,
|
||||
0x96, 0x63,
|
||||
0x16, // EoW of 'organic'
|
||||
0xb3, 0x61, 0x0, 0x0, 0x57, // 3rd-level 'a', next-pointed by
|
||||
// 3rd-level 'g', below 'o' -> 'r';
|
||||
// with a next pointer to 'i' as
|
||||
// 'origami' freq is lower than 'orange'
|
||||
// frequency is 19 = freq of 'orange'
|
||||
0x93, 0x6e,
|
||||
0x93, 0x67,
|
||||
0x93, 0x65,
|
||||
0x13, // EoW of 'orange', freq = 19
|
||||
0x86, 0x69, // 3rd-level 'i', next-pointed by
|
||||
// 3rd-level 'a', below 'o' -> 'r';
|
||||
// freq is 6 = freq of 'origami'
|
||||
0x86, 0x67,
|
||||
0x86, 0x61,
|
||||
0x86, 0x6d,
|
||||
0x86, 0x69,
|
||||
0x6, // EoW of 'origami'
|
||||
0x85, 0x62, // root-level 'b' as nexted-pointed by
|
||||
// root-level 'o'; no more next pointers
|
||||
// freq = 5 i.e. freq of 'blueberry'
|
||||
0x85, 0x6c,
|
||||
0x85, 0x75,
|
||||
0x85, 0x65,
|
||||
0x85, 0x62,
|
||||
0x85, 0x65,
|
||||
0x85, 0x72,
|
||||
0x85, 0x72,
|
||||
0x85, 0x79,
|
||||
0x5 // EoW of 'blueberry'
|
||||
]);
|
||||
|
||||
compareBlobs(actual, expected);
|
||||
});
|
||||
|
||||
test('word freqency = 0 is properly honored', function(){
|
||||
actual = new Uint8Array(new WordListConverter(
|
||||
[{w: 'apple', f: 0.5},
|
||||
{w: 'toxicapple', f: 0}]).toBlob());
|
||||
|
||||
expected = new Uint8Array([
|
||||
0x46, 0x78, 0x4f, 0x53, 0x44, 0x49, 0x43, 0x54,
|
||||
0x0, 0x0, 0x0, 0x1,
|
||||
0xa,
|
||||
0x0, 0x9,
|
||||
0x0, 0x70, 0x0, 0x0, 0x0, 0x4,
|
||||
0x0, 0x61, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x6c, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x65, 0x0, 0x0, 0x0, 0x2,
|
||||
0x0, 0x74, 0x0, 0x0, 0x0, 0x1,
|
||||
0x0, 0x6f, 0x0, 0x0, 0x0, 0x1,
|
||||
0x0, 0x78, 0x0, 0x0, 0x0, 0x1,
|
||||
0x0, 0x69, 0x0, 0x0, 0x0, 0x1,
|
||||
0x0, 0x63, 0x0, 0x0, 0x0, 0x1,
|
||||
0xb0, 0x61, 0x0, 0x0, 0xe, // root 'a', next to 't', freq = 16
|
||||
0x90, 0x70,
|
||||
0x90, 0x70,
|
||||
0x90, 0x6c,
|
||||
0x90, 0x65,
|
||||
0x10, // EoW of 'apple', freq = 16
|
||||
0x80, 0x74, // root-next 't', freq = 0
|
||||
0x80, 0x6f,
|
||||
0x80, 0x78,
|
||||
0x80, 0x69,
|
||||
0x80, 0x63,
|
||||
0x80, 0x61,
|
||||
0x80, 0x70,
|
||||
0x80, 0x70,
|
||||
0x80, 0x6c,
|
||||
0x80, 0x65,
|
||||
0x0 // EoW of 'toxicapple', freq = 0
|
||||
]);
|
||||
|
||||
compareBlobs(actual, expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
11
gaia/apps/keyboard/test/unit/setup_engine.js
Executable file
11
gaia/apps/keyboard/test/unit/setup_engine.js
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
'use strict';
|
||||
// We need to isolate these global variables from multiple testing files by
|
||||
// using "requireApp" in order to prevent the conflict.
|
||||
|
||||
if (!window.PAGE_INDEX_DEFAULT) {
|
||||
window.PAGE_INDEX_DEFAULT = 0;
|
||||
}
|
||||
|
||||
if (!window.InputMethods) {
|
||||
window.InputMethods = {};
|
||||
}
|
||||
289
gaia/apps/keyboard/test/unit/shared/promise_storage_test.js
Executable file
289
gaia/apps/keyboard/test/unit/shared/promise_storage_test.js
Executable file
|
|
@ -0,0 +1,289 @@
|
|||
'use strict';
|
||||
|
||||
/* global PromiseStorage, MockIDBFactory, MockIDBDatabase */
|
||||
|
||||
require('/shared/test/unit/mocks/mock_event_target.js');
|
||||
require('/test/unit/mock_indexeddb.js');
|
||||
|
||||
require('/js/shared/promise_storage.js');
|
||||
|
||||
suite('PromiseStorage', function() {
|
||||
var name;
|
||||
var storage;
|
||||
|
||||
var mockIndexedDB;
|
||||
var stubIDB;
|
||||
|
||||
var p;
|
||||
setup(function() {
|
||||
name = 'storage';
|
||||
|
||||
mockIndexedDB = new MockIDBFactory();
|
||||
this.sinon.spy(mockIndexedDB, 'open');
|
||||
|
||||
MockIDBFactory.attachToWindow(mockIndexedDB);
|
||||
|
||||
storage = new PromiseStorage(name);
|
||||
p = storage.start();
|
||||
});
|
||||
|
||||
suite('open database', function() {
|
||||
test('error', function(done) {
|
||||
var openReq = mockIndexedDB.open.firstCall.returnValue;
|
||||
openReq.error = new Error('Mocked error');
|
||||
openReq.readyState = 'done';
|
||||
openReq.dispatchEvent({
|
||||
type: 'error',
|
||||
target: openReq
|
||||
});
|
||||
|
||||
p.then(function() {
|
||||
assert.isTrue(false, 'should not resolve');
|
||||
}, function reject(err) {
|
||||
assert.strictEqual(err.message, 'Mocked error');
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('upgradedneeded (0 -> 1)', function() {
|
||||
stubIDB = new MockIDBDatabase({
|
||||
name: storage.name,
|
||||
verstion: 0
|
||||
});
|
||||
this.sinon.spy(stubIDB, 'createObjectStore');
|
||||
|
||||
var openReq = mockIndexedDB.open.firstCall.returnValue;
|
||||
openReq.result = stubIDB;
|
||||
openReq.readyState = 'done';
|
||||
openReq.dispatchEvent({
|
||||
type: 'upgradeneeded',
|
||||
target: openReq,
|
||||
oldVersion: 0,
|
||||
newVersion: 1
|
||||
});
|
||||
|
||||
assert.isTrue(stubIDB.createObjectStore.calledWith(storage.STORE_NAME));
|
||||
});
|
||||
|
||||
test('success', function(done) {
|
||||
stubIDB = new MockIDBDatabase({
|
||||
name: storage.DB_NAME,
|
||||
verstion: 1
|
||||
});
|
||||
|
||||
var openReq = mockIndexedDB.open.firstCall.returnValue;
|
||||
openReq.result = stubIDB;
|
||||
openReq.readyState = 'done';
|
||||
openReq.dispatchEvent({
|
||||
type: 'success',
|
||||
target: openReq
|
||||
});
|
||||
|
||||
p.then(function() {
|
||||
assert.isTrue(true, 'resolved');
|
||||
}, function reject(err) {
|
||||
assert.isTrue(false, 'should not reject');
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
suite('operations', function() {
|
||||
var txn, store;
|
||||
setup(function() {
|
||||
var mOptions = {
|
||||
name: storage.DB_NAME,
|
||||
verstion: 1,
|
||||
objectStoreNames: [storage.STORE_NAME]
|
||||
};
|
||||
|
||||
stubIDB = new MockIDBDatabase(mOptions);
|
||||
txn = stubIDB.transaction(storage.STORE_NAME);
|
||||
store = txn.objectStore(storage.STORE_NAME);
|
||||
this.sinon.spy(store, 'get');
|
||||
this.sinon.spy(store, 'put');
|
||||
this.sinon.spy(store, 'delete');
|
||||
|
||||
this.sinon.stub(stubIDB, 'transaction').returns(txn);
|
||||
this.sinon.stub(txn, 'objectStore').returns(store);
|
||||
|
||||
var openReq = mockIndexedDB.open.firstCall.returnValue;
|
||||
openReq.result = stubIDB;
|
||||
openReq.readyState = 'done';
|
||||
openReq.dispatchEvent({
|
||||
type: 'success',
|
||||
target: openReq
|
||||
});
|
||||
});
|
||||
|
||||
test('getItem', function(done) {
|
||||
var data = {};
|
||||
|
||||
var pReturned = storage.getItem('foo');
|
||||
|
||||
p.then(function() {
|
||||
assert.isTrue(store.get.calledWith('foo'));
|
||||
var req = store.get.firstCall.returnValue;
|
||||
req.readyState = 'done';
|
||||
req.result = data;
|
||||
req.dispatchEvent({
|
||||
type: 'success',
|
||||
target: req
|
||||
});
|
||||
txn.dispatchEvent({
|
||||
type: 'complete',
|
||||
target: txn
|
||||
});
|
||||
|
||||
return pReturned;
|
||||
})
|
||||
.then(function(d) {
|
||||
assert.strictEqual(d, data, 'should resolve to data');
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('getItems', function(done) {
|
||||
var data = [{}, {}];
|
||||
|
||||
var pReturned = storage.getItems(['foo', 'foo2']);
|
||||
|
||||
p.then(function() {
|
||||
assert.isTrue(store.get.firstCall.calledWith('foo'));
|
||||
assert.isTrue(store.get.getCall(1).calledWith('foo2'));
|
||||
|
||||
var req = store.get.firstCall.returnValue;
|
||||
req.readyState = 'done';
|
||||
req.result = data[0];
|
||||
req.dispatchEvent({
|
||||
type: 'success',
|
||||
target: req
|
||||
});
|
||||
txn.dispatchEvent({
|
||||
type: 'complete',
|
||||
target: txn
|
||||
});
|
||||
|
||||
var req1 = store.get.getCall(1).returnValue;
|
||||
req1.readyState = 'done';
|
||||
req1.result = data[1];
|
||||
req1.dispatchEvent({
|
||||
type: 'success',
|
||||
target: req1
|
||||
});
|
||||
txn.dispatchEvent({
|
||||
type: 'complete',
|
||||
target: txn
|
||||
});
|
||||
|
||||
return pReturned;
|
||||
})
|
||||
.then(function(d) {
|
||||
assert.strictEqual(d[0], data[0], 'should resolve to data');
|
||||
assert.strictEqual(d[1], data[1], 'should resolve to data');
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('setItem', function(done) {
|
||||
var data = {};
|
||||
|
||||
var pReturned = storage.setItem('foo', data);
|
||||
|
||||
p.then(function() {
|
||||
assert.isTrue(
|
||||
stubIDB.transaction.calledWith(storage.STORE_NAME, 'readwrite'));
|
||||
assert.isTrue(store.put.calledWith(data, 'foo'));
|
||||
var req = store.put.firstCall.returnValue;
|
||||
req.readyState = 'done';
|
||||
req.result = 'foo';
|
||||
req.dispatchEvent({
|
||||
type: 'success',
|
||||
target: req
|
||||
});
|
||||
txn.dispatchEvent({
|
||||
type: 'complete',
|
||||
target: txn
|
||||
});
|
||||
|
||||
return pReturned;
|
||||
})
|
||||
.then(function(k) {
|
||||
assert.equal(k, 'foo');
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('setItems', function(done) {
|
||||
var data = [{}, {}];
|
||||
|
||||
var pReturned = storage.setItems({
|
||||
foo: data[0],
|
||||
foo2: data[1]
|
||||
});
|
||||
|
||||
p.then(function() {
|
||||
assert.isTrue(
|
||||
stubIDB.transaction.calledWith(storage.STORE_NAME, 'readwrite'));
|
||||
assert.isTrue(store.put.firstCall.calledWith(data[0], 'foo'));
|
||||
assert.isTrue(store.put.getCall(1).calledWith(data[1], 'foo2'));
|
||||
var req = store.put.firstCall.returnValue;
|
||||
req.readyState = 'done';
|
||||
req.result = 'foo';
|
||||
req.dispatchEvent({
|
||||
type: 'success',
|
||||
target: req
|
||||
});
|
||||
txn.dispatchEvent({
|
||||
type: 'complete',
|
||||
target: txn
|
||||
});
|
||||
|
||||
var req1 = store.put.getCall(1).returnValue;
|
||||
req1.readyState = 'done';
|
||||
req1.result = 'foo2';
|
||||
req1.dispatchEvent({
|
||||
type: 'success',
|
||||
target: req1
|
||||
});
|
||||
txn.dispatchEvent({
|
||||
type: 'complete',
|
||||
target: txn
|
||||
});
|
||||
|
||||
return pReturned;
|
||||
})
|
||||
.then(function(k) {
|
||||
assert.deepEqual(k, ['foo', 'foo2']);
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
test('deleteItem', function(done) {
|
||||
var pReturned = storage.deleteItem('foo');
|
||||
|
||||
p.then(function() {
|
||||
assert.isTrue(
|
||||
stubIDB.transaction.calledWith(storage.STORE_NAME, 'readwrite'));
|
||||
assert.isTrue(store.delete.calledWith('foo'));
|
||||
var req = store.delete.firstCall.returnValue;
|
||||
req.readyState = 'done';
|
||||
req.result = undefined;
|
||||
req.dispatchEvent({
|
||||
type: 'success',
|
||||
target: req
|
||||
});
|
||||
txn.dispatchEvent({
|
||||
type: 'complete',
|
||||
target: txn
|
||||
});
|
||||
|
||||
return pReturned;
|
||||
})
|
||||
.then(function(k) {
|
||||
assert.equal(k, undefined);
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
252
gaia/apps/keyboard/test/unit/views/alternatives_char_menu_test.js
Executable file
252
gaia/apps/keyboard/test/unit/views/alternatives_char_menu_test.js
Executable file
|
|
@ -0,0 +1,252 @@
|
|||
'use strict';
|
||||
|
||||
/* global AlternativesCharMenuView */
|
||||
|
||||
require('/js/views/alternatives_char_menu_view.js');
|
||||
require('/js/views/key_view.js');
|
||||
|
||||
suite('Views > AlternativesCharMenuView', function() {
|
||||
var menu = null;
|
||||
var options = {
|
||||
};
|
||||
|
||||
var KEY_WIDTH = 30;
|
||||
var KEY_HEIGHT = 20;
|
||||
var altChars = ['a', 'b', 'c'];
|
||||
var fakeMenu;
|
||||
|
||||
var rootElement = document.createElement('div');
|
||||
document.body.appendChild(rootElement);
|
||||
|
||||
var dummyKeyElement = document.createElement('button');
|
||||
|
||||
var viewManager = {
|
||||
registerView: sinon.stub()
|
||||
};
|
||||
|
||||
suite('basic show/hide testing', function() {
|
||||
setup(function() {
|
||||
menu = new AlternativesCharMenuView(rootElement, altChars, options,
|
||||
viewManager);
|
||||
});
|
||||
|
||||
test(' > show()', function() {
|
||||
menu.show(dummyKeyElement);
|
||||
|
||||
assert.equal(rootElement.firstElementChild,
|
||||
menu.getMenuContainer(),
|
||||
'Menu was not inserted into root Element');
|
||||
menu.hide();
|
||||
});
|
||||
|
||||
test(' > hide()', function() {
|
||||
menu.show(dummyKeyElement);
|
||||
menu.hide();
|
||||
|
||||
assert.equal(rootElement.childElementCount, 0,
|
||||
'Menu was not removed from root Element!');
|
||||
});
|
||||
});
|
||||
|
||||
suite(' > getMenuTarget() - single row case', function() {
|
||||
setup(function() {
|
||||
fakeMenu = document.createElement('div');
|
||||
fakeMenu.style.position = 'absolute';
|
||||
fakeMenu.style.fontSize = 0;
|
||||
|
||||
// insert 3 keys for 1 row
|
||||
for (var i = 0; i < 3; i++) {
|
||||
var child = document.createElement('div');
|
||||
child.style.display = 'inline-block';
|
||||
child.style.width = KEY_WIDTH + 'px';
|
||||
child.style.height = KEY_HEIGHT + 'px';
|
||||
|
||||
fakeMenu.appendChild(child);
|
||||
}
|
||||
|
||||
rootElement.appendChild(fakeMenu);
|
||||
|
||||
menu = new AlternativesCharMenuView(rootElement, altChars, options,
|
||||
viewManager);
|
||||
|
||||
this.sinon.stub(menu, 'getMenuContainer');
|
||||
menu.getMenuContainer.returns(fakeMenu);
|
||||
|
||||
this.sinon.stub(menu, 'getBoundingClientRect',
|
||||
fakeMenu.getBoundingClientRect.bind(fakeMenu));
|
||||
|
||||
this.sinon.stub(menu, 'getLineHeight');
|
||||
menu.getLineHeight.returns(KEY_HEIGHT);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
menu.hide();
|
||||
});
|
||||
|
||||
test('the first element', function() {
|
||||
menu.show(dummyKeyElement);
|
||||
|
||||
var rect = fakeMenu.getBoundingClientRect();
|
||||
var target = menu.getMenuTarget(rect.left, rect.top);
|
||||
|
||||
assert.equal(target, menu.altKeyTargets[0],
|
||||
'Should map to the first element');
|
||||
});
|
||||
|
||||
test('the second element', function() {
|
||||
menu.show(dummyKeyElement);
|
||||
|
||||
var rect = fakeMenu.getBoundingClientRect();
|
||||
var target = menu.getMenuTarget(fakeMenu.offsetLeft + KEY_WIDTH +
|
||||
rect.left, rect.top);
|
||||
|
||||
assert.equal(target, menu.altKeyTargets[1],
|
||||
'Should map to the second element');
|
||||
});
|
||||
|
||||
test('the last element', function() {
|
||||
menu.show(dummyKeyElement);
|
||||
|
||||
var rect = fakeMenu.getBoundingClientRect();
|
||||
var target = menu.getMenuTarget(fakeMenu.offsetLeft +
|
||||
fakeMenu.offsetWidth +
|
||||
rect.left, rect.top);
|
||||
|
||||
assert.equal(target, menu.altKeyTargets[menu.altKeyTargets.length -1],
|
||||
'Should map to the last element');
|
||||
});
|
||||
});
|
||||
|
||||
suite(' > getMenuTarget() - 2 row case', function() {
|
||||
setup(function() {
|
||||
fakeMenu = document.createElement('div');
|
||||
fakeMenu.style.position = 'absolute';
|
||||
fakeMenu.style.width = KEY_WIDTH * 3 + 'px'; // 2 row
|
||||
|
||||
// insert 6 keys for 2 rows
|
||||
for (var i = 0; i < 6; i++) {
|
||||
var child = document.createElement('div');
|
||||
child.style.display = 'inline-block';
|
||||
child.style.width = KEY_WIDTH + 'px';
|
||||
child.style.height = KEY_HEIGHT + 'px';
|
||||
|
||||
fakeMenu.appendChild(child);
|
||||
}
|
||||
|
||||
rootElement.appendChild(fakeMenu);
|
||||
|
||||
var altChars = ['a', 'b', 'c', 'd', 'e', 'f'];
|
||||
menu = new AlternativesCharMenuView(rootElement, altChars, options,
|
||||
viewManager);
|
||||
|
||||
this.sinon.stub(menu, 'getMenuContainer');
|
||||
menu.getMenuContainer.returns(fakeMenu);
|
||||
|
||||
this.sinon.stub(menu, 'getBoundingClientRect',
|
||||
fakeMenu.getBoundingClientRect.bind(fakeMenu));
|
||||
|
||||
this.sinon.stub(menu, 'getLineHeight');
|
||||
menu.getLineHeight.returns(KEY_HEIGHT);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
menu.hide();
|
||||
});
|
||||
|
||||
test('2 row - the first element', function() {
|
||||
menu.show(dummyKeyElement);
|
||||
|
||||
var rect = fakeMenu.getBoundingClientRect();
|
||||
|
||||
var target = menu.getMenuTarget(rect.left, fakeMenu.offsetTop +
|
||||
fakeMenu.offsetHeight +
|
||||
rect.top);
|
||||
|
||||
assert.equal(target, menu.altKeyTargets[0],
|
||||
'Should map to the first element');
|
||||
});
|
||||
|
||||
test('2 row - the second element', function() {
|
||||
menu.show(dummyKeyElement);
|
||||
|
||||
var rect = fakeMenu.getBoundingClientRect();
|
||||
var target = menu.getMenuTarget(fakeMenu.offsetLeft + KEY_WIDTH +
|
||||
rect.left,
|
||||
fakeMenu.offsetTop +
|
||||
fakeMenu.offsetHeight +
|
||||
rect.top);
|
||||
|
||||
assert.equal(target, menu.altKeyTargets[1],
|
||||
'Should map to the second element');
|
||||
});
|
||||
|
||||
test('2 row - the 4th element in the upper row', function() {
|
||||
menu.show(dummyKeyElement);
|
||||
|
||||
var rect = fakeMenu.getBoundingClientRect();
|
||||
var target = menu.getMenuTarget(rect.left, rect.top);
|
||||
|
||||
assert.equal(target, menu.altKeyTargets[3],
|
||||
'Should map to the 4th element');
|
||||
});
|
||||
});
|
||||
|
||||
suite(' > getMenuTarget() - 2 row with one empty cell case', function() {
|
||||
setup(function() {
|
||||
fakeMenu = document.createElement('div');
|
||||
fakeMenu.style.position = 'absolute';
|
||||
fakeMenu.style.width = KEY_WIDTH * 3 + 'px'; // 2 row
|
||||
|
||||
// insert 5 keys for 2 rows
|
||||
// (would result in 3x2 cells with top top-right one being empty)
|
||||
for (var i = 0; i < 5; i++) {
|
||||
var child = document.createElement('div');
|
||||
child.style.display = 'inline-block';
|
||||
child.style.width = KEY_WIDTH + 'px';
|
||||
child.style.height = KEY_HEIGHT + 'px';
|
||||
|
||||
fakeMenu.appendChild(child);
|
||||
}
|
||||
|
||||
rootElement.appendChild(fakeMenu);
|
||||
|
||||
var altChars = ['a', 'b', 'c', 'd', 'e'];
|
||||
menu = new AlternativesCharMenuView(rootElement, altChars, options,
|
||||
viewManager);
|
||||
|
||||
this.sinon.stub(menu, 'getMenuContainer');
|
||||
menu.getMenuContainer.returns(fakeMenu);
|
||||
|
||||
this.sinon.stub(menu, 'getBoundingClientRect',
|
||||
fakeMenu.getBoundingClientRect.bind(fakeMenu));
|
||||
|
||||
this.sinon.stub(menu, 'getLineHeight');
|
||||
menu.getLineHeight.returns(KEY_HEIGHT);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
menu.hide();
|
||||
});
|
||||
|
||||
test('2 row - the empty element', function() {
|
||||
menu.show(dummyKeyElement);
|
||||
|
||||
var rect = fakeMenu.getBoundingClientRect();
|
||||
var target = menu.getMenuTarget(fakeMenu.offsetLeft + rect.left +
|
||||
KEY_WIDTH * 2, rect.top);
|
||||
|
||||
assert.equal(target, menu.altKeyTargets[4],
|
||||
'Should map to the last element');
|
||||
});
|
||||
});
|
||||
|
||||
suite(' isMenuTarget()', function() {
|
||||
test('isMenuTarget() returns true', function() {
|
||||
assert.isTrue(menu.isMenuTarget(menu.altKeyTargets[0]));
|
||||
});
|
||||
|
||||
test('isMenuTarget() returns false', function() {
|
||||
assert.isFalse(menu.isMenuTarget({dummy: 'dummy'}));
|
||||
});
|
||||
});
|
||||
});
|
||||
144
gaia/apps/keyboard/test/unit/views/candidate_panel_view_test.js
Executable file
144
gaia/apps/keyboard/test/unit/views/candidate_panel_view_test.js
Executable file
|
|
@ -0,0 +1,144 @@
|
|||
'use strict';
|
||||
|
||||
/* global CandidatePanelView */
|
||||
require('/js/views/base_view.js');
|
||||
require('/js/views/candidate_panel_view.js');
|
||||
|
||||
function createFakeCandidates(count) {
|
||||
var candidates = [];
|
||||
for (var i = 0; i < count; i++) {
|
||||
candidates.push('' + i);
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
suite('Views > CandidatePanelView', function() {
|
||||
var candidatePanelView = null;
|
||||
var viewManager = {
|
||||
registerView: sinon.stub(),
|
||||
getRemToPx: sinon.stub().returns(10)
|
||||
};
|
||||
|
||||
suite('some basic functions', function() {
|
||||
setup(function() {
|
||||
var target = {};
|
||||
var options = {className: 'basic-class'};
|
||||
candidatePanelView = new CandidatePanelView(target, options, viewManager);
|
||||
});
|
||||
|
||||
test('> render() ', function() {
|
||||
assert.equal(candidatePanelView.element, null);
|
||||
|
||||
candidatePanelView.render();
|
||||
assert.notEqual(candidatePanelView.element, null);
|
||||
|
||||
var classList = candidatePanelView.element.classList;
|
||||
assert.isTrue(classList.contains('basic-class'),
|
||||
'should contain the className specified in options');
|
||||
});
|
||||
|
||||
test('> getHeight()', function() {
|
||||
assert.equal(candidatePanelView.getHeight(), 32);
|
||||
});
|
||||
|
||||
suite('> showCandidates()', function() {
|
||||
test('> show 8 candidates', function() {
|
||||
candidatePanelView.render();
|
||||
|
||||
candidatePanelView.showCandidates(createFakeCandidates(8));
|
||||
|
||||
var suggestionsContainer =
|
||||
candidatePanelView.element.querySelector('.suggestions-container');
|
||||
assert.notEqual(suggestionsContainer, null);
|
||||
|
||||
var row = suggestionsContainer.querySelector('.candidate-row-first');
|
||||
assert.equal(row.childNodes.length, 8);
|
||||
|
||||
// Should not contain the toggle button
|
||||
var toggleButton =
|
||||
row.querySelector('.keyboard-candidate-panel-toggle-button');
|
||||
assert.equal(toggleButton, null);
|
||||
});
|
||||
|
||||
test('> show 9 candidates', function() {
|
||||
candidatePanelView.render();
|
||||
|
||||
candidatePanelView.showCandidates(createFakeCandidates(9));
|
||||
|
||||
var suggestionsContainer =
|
||||
candidatePanelView.element.querySelector('.suggestions-container');
|
||||
assert.notEqual(suggestionsContainer, null);
|
||||
|
||||
var row = suggestionsContainer.querySelector('.candidate-row-first');
|
||||
assert.equal(row.childNodes.length, 8);
|
||||
|
||||
// Should contain the toggle button
|
||||
var toggleButton = candidatePanelView.element.
|
||||
querySelector('.keyboard-candidate-panel-toggle-button');
|
||||
assert.notEqual(toggleButton, null);
|
||||
});
|
||||
|
||||
test('> candidates with different length', function() {
|
||||
candidatePanelView.render();
|
||||
|
||||
var candidates = ['1', '2', '3' , '4', '55', '6', '7', '8'];
|
||||
candidatePanelView.showCandidates(candidates);
|
||||
|
||||
var suggestionsContainer =
|
||||
candidatePanelView.element.querySelector('.suggestions-container');
|
||||
assert.notEqual(suggestionsContainer, null);
|
||||
|
||||
var row = suggestionsContainer.querySelector('.candidate-row-first');
|
||||
assert.equal(row.childNodes.length, 7);
|
||||
|
||||
// Should contain the toggle button
|
||||
var toggleButton = candidatePanelView.element.
|
||||
querySelector('.keyboard-candidate-panel-toggle-button');
|
||||
assert.notEqual(toggleButton, null);
|
||||
|
||||
|
||||
candidatePanelView.showMoreCandidates(1, ['9']);
|
||||
// Check it has the 2nd row
|
||||
var rows = suggestionsContainer.querySelectorAll('.candidate-row');
|
||||
assert.equal(rows.length, 2);
|
||||
|
||||
// Check 2nd row is shown as a grid
|
||||
var candidateElements = rows[1].querySelectorAll('span');
|
||||
assert.equal(candidateElements.length, 2);
|
||||
|
||||
assert.equal(candidateElements[0].textContent, '9');
|
||||
assert.equal(candidateElements[0].dataset.data, '9');
|
||||
assert.equal(candidateElements[0].style.flexGrow, 1);
|
||||
|
||||
// Will have a dummy element here make it look like a grid
|
||||
assert.equal(candidateElements[1].style.flexGrow, 7);
|
||||
});
|
||||
});
|
||||
|
||||
suite('candidatePanel with even width for each candidate', function() {
|
||||
setup(function() {
|
||||
var options = { widthUnit: 1 };
|
||||
candidatePanelView = new CandidatePanelView({}, options, viewManager);
|
||||
});
|
||||
|
||||
test('Each candidate has the same width', function() {
|
||||
candidatePanelView.render();
|
||||
candidatePanelView.showCandidates(createFakeCandidates(5));
|
||||
|
||||
var suggestionsContainer =
|
||||
candidatePanelView.element.querySelector('.suggestions-container');
|
||||
assert.notEqual(suggestionsContainer, null);
|
||||
|
||||
var candidateElements = suggestionsContainer.
|
||||
querySelectorAll('.candidate-row span');
|
||||
|
||||
assert.equal(candidateElements.length, 5);
|
||||
|
||||
for (var i = 0; i < candidateElements.length; i++) {
|
||||
assert.equal(candidateElements[i].style.flexGrow, 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
60
gaia/apps/keyboard/test/unit/views/emoji_key_view_test.js
Executable file
60
gaia/apps/keyboard/test/unit/views/emoji_key_view_test.js
Executable file
|
|
@ -0,0 +1,60 @@
|
|||
'use strict';
|
||||
|
||||
/* global EmojiKeyView */
|
||||
|
||||
require('/js/views/base_view.js');
|
||||
require('/js/views/emoji_key_view.js');
|
||||
|
||||
suite('Views > KeyView', function() {
|
||||
var emojiKeyView = null;
|
||||
var viewManager = {
|
||||
registerView: sinon.stub()
|
||||
};
|
||||
|
||||
suite('some basic functions', function() {
|
||||
setup(function() {
|
||||
var target = {};
|
||||
var options = {};
|
||||
emojiKeyView = new EmojiKeyView(target, options, viewManager);
|
||||
});
|
||||
|
||||
test(' > render()', function() {
|
||||
assert.equal(emojiKeyView.element, null);
|
||||
|
||||
emojiKeyView.render();
|
||||
assert.notEqual(emojiKeyView.element, null);
|
||||
assert.isTrue(emojiKeyView.element.classList.contains('emoji'));
|
||||
});
|
||||
|
||||
test('additional class name', function() {
|
||||
var key = {
|
||||
value: 'k',
|
||||
};
|
||||
|
||||
var options = {
|
||||
classNames: ['another-className']
|
||||
};
|
||||
|
||||
var keyView = new EmojiKeyView(key, options, viewManager);
|
||||
keyView.render();
|
||||
|
||||
options.classNames.forEach(function(className) {
|
||||
assert.isTrue(keyView.element.classList.contains(className));
|
||||
});
|
||||
});
|
||||
|
||||
test('invoke viewManager.registerView', function() {
|
||||
var mockViewManager = {
|
||||
registerView: this.sinon.stub()
|
||||
};
|
||||
|
||||
var target = {};
|
||||
var keyView = new EmojiKeyView(target, {}, mockViewManager);
|
||||
keyView.render();
|
||||
|
||||
assert.isTrue(mockViewManager.registerView.calledOnce);
|
||||
assert.isTrue(mockViewManager.registerView.calledWith(target,
|
||||
keyView));
|
||||
});
|
||||
});
|
||||
});
|
||||
97
gaia/apps/keyboard/test/unit/views/handwriting_pad_view_test.js
Executable file
97
gaia/apps/keyboard/test/unit/views/handwriting_pad_view_test.js
Executable file
|
|
@ -0,0 +1,97 @@
|
|||
'use strict';
|
||||
|
||||
/* global HandwritingPadView */
|
||||
|
||||
require('/js/views/handwriting_pad_view.js');
|
||||
|
||||
suite('Views > HandwritingPadView', function() {
|
||||
var handwritingPadView;
|
||||
var canvas;
|
||||
|
||||
var STROKE_WIDTH = 6;
|
||||
|
||||
var pressStart = {
|
||||
moved: false,
|
||||
clientX: 20,
|
||||
clientY: 20
|
||||
};
|
||||
|
||||
var pressMove = {
|
||||
moved: true,
|
||||
clientX: 25,
|
||||
clientY: 25
|
||||
};
|
||||
|
||||
var rootElement = document.createElement('div');
|
||||
document.body.appendChild(rootElement);
|
||||
document.body.style.pad = 0;
|
||||
document.body.style.margin = 0;
|
||||
|
||||
var viewManager = {
|
||||
registerView: sinon.stub()
|
||||
};
|
||||
|
||||
suite('basic testing', function() {
|
||||
setup(function() {
|
||||
var target = {};
|
||||
handwritingPadView = new HandwritingPadView(target, {}, viewManager);
|
||||
handwritingPadView.render();
|
||||
canvas = handwritingPadView.element;
|
||||
});
|
||||
|
||||
test(' > get element', function() {
|
||||
assert.equal(canvas instanceof HTMLCanvasElement, true);
|
||||
});
|
||||
|
||||
test('invoke viewManager.registerView', function() {
|
||||
var mockViewManager = {
|
||||
registerView: this.sinon.stub()
|
||||
};
|
||||
|
||||
var target = {};
|
||||
var padView = new HandwritingPadView(target, {}, mockViewManager);
|
||||
padView.render();
|
||||
|
||||
assert.isTrue(mockViewManager.registerView.calledOnce);
|
||||
assert.isTrue(mockViewManager.registerView.calledWith(target,
|
||||
padView));
|
||||
});
|
||||
|
||||
test(' > drawHandwritingPad()', function() {
|
||||
rootElement.appendChild(canvas);
|
||||
|
||||
var point = handwritingPadView.drawHandwritingPad(pressStart, true,
|
||||
STROKE_WIDTH);
|
||||
|
||||
assert.equal(point.posX, pressStart.clientX * window.devicePixelRatio,
|
||||
'Error occured in drawCanvas when press start!');
|
||||
assert.equal(point.posY, pressStart.clientY * window.devicePixelRatio,
|
||||
'Error occured in drawCanvas when press start!');
|
||||
|
||||
point = handwritingPadView.drawHandwritingPad(pressMove, false,
|
||||
STROKE_WIDTH);
|
||||
|
||||
assert.equal(point.posX, pressMove.clientX * window.devicePixelRatio,
|
||||
'Error occured in drawCanvas when press move!');
|
||||
assert.equal(point.posY, pressMove.clientY * window.devicePixelRatio,
|
||||
'Error occured in drawCanvas when press move!');
|
||||
});
|
||||
|
||||
test(' > resize()', function() {
|
||||
Object.defineProperty(window, 'devicePixelRatio', {
|
||||
configurable: true,
|
||||
get: () => 2 }
|
||||
);
|
||||
|
||||
handwritingPadView.resize(200, 400);
|
||||
|
||||
var canvas = handwritingPadView.element;
|
||||
|
||||
assert.equal(canvas.width, '400');
|
||||
assert.equal(canvas.height, '800');
|
||||
|
||||
assert.equal(canvas.style.width, '200px');
|
||||
assert.equal(canvas.style.height, '400px');
|
||||
});
|
||||
});
|
||||
});
|
||||
244
gaia/apps/keyboard/test/unit/views/key_view_test.js
Executable file
244
gaia/apps/keyboard/test/unit/views/key_view_test.js
Executable file
|
|
@ -0,0 +1,244 @@
|
|||
'use strict';
|
||||
|
||||
/* global KeyView */
|
||||
|
||||
require('/js/views/key_view.js');
|
||||
|
||||
suite('Views > KeyView', function() {
|
||||
var keyView = null;
|
||||
var viewManager = {
|
||||
registerView: sinon.stub()
|
||||
};
|
||||
|
||||
suite('some basic functions', function() {
|
||||
setup(function() {
|
||||
var target = {};
|
||||
var options = {};
|
||||
keyView = new KeyView(target, options, viewManager);
|
||||
});
|
||||
|
||||
test(' > render()', function() {
|
||||
assert.equal(keyView.element, null);
|
||||
|
||||
keyView.render();
|
||||
assert.notEqual(keyView.element, null);
|
||||
});
|
||||
|
||||
test(' > highlight() with upper case', function() {
|
||||
keyView.render();
|
||||
assert.isFalse(keyView.element.classList.contains('highlighted'));
|
||||
|
||||
keyView.highlight({upperCase: true});
|
||||
assert.isTrue(keyView.element.classList.contains('highlighted'));
|
||||
assert.isTrue(keyView.element.classList.contains('uppercase-popup'));
|
||||
});
|
||||
|
||||
test(' > highlight() with lower case', function() {
|
||||
keyView.render();
|
||||
assert.isFalse(keyView.element.classList.contains('highlighted'));
|
||||
|
||||
keyView.highlight({upperCase: false});
|
||||
assert.isTrue(keyView.element.classList.contains('highlighted'));
|
||||
assert.isTrue(keyView.element.classList.contains('lowercase-popup'));
|
||||
});
|
||||
|
||||
test(' > unHighlight()', function() {
|
||||
keyView.render();
|
||||
|
||||
keyView.highlight();
|
||||
assert.isTrue(keyView.element.classList.contains('highlighted'));
|
||||
|
||||
keyView.unHighlight();
|
||||
assert.isFalse(keyView.element.classList.contains('highlighted'));
|
||||
assert.isFalse(keyView.element.classList.contains('uppercase-popup'));
|
||||
assert.isFalse(keyView.element.classList.contains('lowercase-popup'));
|
||||
});
|
||||
});
|
||||
|
||||
suite('DOM structures tests', function() {
|
||||
test('basic key', function() {
|
||||
var key = {
|
||||
value: 'k',
|
||||
lowercaseValue: 'l_k',
|
||||
keyCode: 999,
|
||||
keyCodeUpper: 9999,
|
||||
targetPage: 2
|
||||
};
|
||||
|
||||
var options = {
|
||||
keyWidth: 99
|
||||
};
|
||||
|
||||
var keyView = new KeyView(key, options, viewManager);
|
||||
keyView.render();
|
||||
|
||||
assert.isTrue(keyView.element.classList.contains('keyboard-key'));
|
||||
assert.isFalse(keyView.element.classList.contains('special-key'));
|
||||
|
||||
assert.equal(keyView.element.getAttribute('role'), 'key');
|
||||
assert.equal(keyView.element.style.width, options.keyWidth + 'px');
|
||||
|
||||
// Keycode dataset
|
||||
assert.equal(keyView.element.dataset.keycode, key.keyCode);
|
||||
assert.equal(keyView.element.dataset.keycodeUpper, key.keyCodeUpper);
|
||||
|
||||
// target page dataset
|
||||
assert.equal(keyView.element.dataset.targetPage, key.targetPage);
|
||||
|
||||
//visual wrapper element
|
||||
var visualWrapper = keyView.element.firstElementChild;
|
||||
assert.equal(visualWrapper.className, 'visual-wrapper');
|
||||
|
||||
// Inner label
|
||||
var labelNode = visualWrapper.querySelector('.key-element');
|
||||
assert.equal(labelNode.innerHTML, key.value);
|
||||
assert.equal(labelNode.dataset.label, key.value);
|
||||
|
||||
// popups
|
||||
var uppercasePopup = visualWrapper.querySelector('.uppercase.popup');
|
||||
assert.equal(uppercasePopup.innerHTML, key.value);
|
||||
|
||||
var lowercasePopup = visualWrapper.querySelector('.lowercase.popup');
|
||||
assert.equal(lowercasePopup.innerHTML, key.lowercaseValue);
|
||||
});
|
||||
|
||||
test('special key', function() {
|
||||
var key = {
|
||||
isSpecialKey: true,
|
||||
value: 'k',
|
||||
lowercaseValue: 'l_k'
|
||||
};
|
||||
|
||||
var options = {
|
||||
keyWidth: 99
|
||||
};
|
||||
|
||||
var keyView = new KeyView(key, options, viewManager);
|
||||
keyView.render();
|
||||
|
||||
assert.isTrue(keyView.element.classList.contains('keyboard-key'));
|
||||
assert.isTrue(keyView.element.classList.contains('special-key'));
|
||||
});
|
||||
|
||||
test('additional attribute list', function() {
|
||||
var key = {
|
||||
value: 'k',
|
||||
lowercaseValue: 'l_k'
|
||||
};
|
||||
|
||||
var options = {
|
||||
keyWidth: 99,
|
||||
attributeList: [
|
||||
{key: 'testKey', value: 'testValue'},
|
||||
{key: 'testKey2', value: 'testValue2'}
|
||||
]
|
||||
};
|
||||
|
||||
var keyView = new KeyView(key, options, viewManager);
|
||||
keyView.render();
|
||||
|
||||
options.attributeList.forEach(function(attribute) {
|
||||
assert.equal(keyView.element.getAttribute(attribute.key),
|
||||
attribute.value);
|
||||
});
|
||||
});
|
||||
|
||||
test('additional class name', function() {
|
||||
var key = {
|
||||
value: 'k',
|
||||
lowercaseValue: 'l_k',
|
||||
className: 'test-className'
|
||||
};
|
||||
|
||||
var options = {
|
||||
keyWidth: 99,
|
||||
classNames: ['another-className']
|
||||
};
|
||||
|
||||
var keyView = new KeyView(key, options, viewManager);
|
||||
keyView.render();
|
||||
|
||||
assert.isTrue(keyView.element.classList.contains('keyboard-key'));
|
||||
assert.isTrue(keyView.element.classList.contains(key.className));
|
||||
|
||||
options.classNames.forEach(function(className) {
|
||||
assert.isTrue(keyView.element.classList.contains(className));
|
||||
});
|
||||
});
|
||||
|
||||
test('disabled key', function() {
|
||||
var key = {
|
||||
value: 'k',
|
||||
lowercaseValue: 'l_k',
|
||||
disabled: true
|
||||
};
|
||||
|
||||
var options = {
|
||||
keyWidth: 99,
|
||||
};
|
||||
|
||||
var keyView = new KeyView(key, options, viewManager);
|
||||
keyView.render();
|
||||
|
||||
assert.equal(keyView.element.getAttribute('disabled'), 'true');
|
||||
});
|
||||
|
||||
test('alt label node for option w/ altOutputChar case', function() {
|
||||
var key = {
|
||||
value: 'k',
|
||||
lowercaseValue: 'l_k',
|
||||
};
|
||||
|
||||
var options = {
|
||||
keyWidth: 99,
|
||||
altOutputChar: '*k'
|
||||
};
|
||||
|
||||
var keyView = new KeyView(key, options, viewManager);
|
||||
keyView.render();
|
||||
|
||||
var visualWrapper = keyView.element.firstElementChild;
|
||||
|
||||
// Inner label
|
||||
var labelNode = visualWrapper.querySelector('.key-element.lowercase');
|
||||
assert.equal(labelNode.innerHTML, options.altOutputChar);
|
||||
});
|
||||
|
||||
test('altNote', function() {
|
||||
var key = {
|
||||
value: 'k',
|
||||
lowercaseValue: 'l_k',
|
||||
longPressValue: '#k'
|
||||
};
|
||||
|
||||
var options = {
|
||||
keyWidth: 99,
|
||||
altOutputChar: '*k'
|
||||
};
|
||||
|
||||
var keyView = new KeyView(key, options, viewManager);
|
||||
keyView.render();
|
||||
|
||||
var visualWrapper = keyView.element.firstElementChild;
|
||||
|
||||
var altNote = visualWrapper.querySelector('.alt-note');
|
||||
assert.equal(altNote.innerHTML, key.longPressValue);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Register View to viewManager', function() {
|
||||
test('invoke viewManager.registerView', function() {
|
||||
var mockViewManager = {
|
||||
registerView: this.sinon.stub()
|
||||
};
|
||||
|
||||
var target = {};
|
||||
var keyView = new KeyView(target, {}, mockViewManager);
|
||||
keyView.render();
|
||||
|
||||
assert.isTrue(mockViewManager.registerView.calledOnce);
|
||||
assert.isTrue(mockViewManager.registerView.calledWith(target,
|
||||
keyView));
|
||||
});
|
||||
});
|
||||
});
|
||||
144
gaia/apps/keyboard/test/unit/views/latin_candidate_panel_view_test.js
Executable file
144
gaia/apps/keyboard/test/unit/views/latin_candidate_panel_view_test.js
Executable file
|
|
@ -0,0 +1,144 @@
|
|||
'use strict';
|
||||
|
||||
/* global LatinCandidatePanelView, ViewUtils */
|
||||
require('/js/views/base_view.js');
|
||||
require('/js/views/view_utils.js');
|
||||
require('/js/views/candidate_panel_view.js');
|
||||
require('/js/views/latin_candidate_panel_view.js');
|
||||
|
||||
function createFakeCandidates(count) {
|
||||
var candidates = [];
|
||||
for (var i = 0; i < count; i++) {
|
||||
candidates.push('' + i);
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
suite('Views > LatinCandidatePanelView', function() {
|
||||
var candidatePanelView = null;
|
||||
var viewManager = {
|
||||
registerView: sinon.stub()
|
||||
};
|
||||
|
||||
suite('some basic functions', function() {
|
||||
setup(function() {
|
||||
var target = {};
|
||||
var options = {className: 'test-class'};
|
||||
candidatePanelView =
|
||||
new LatinCandidatePanelView(target, options, viewManager);
|
||||
});
|
||||
|
||||
test('> render() ', function() {
|
||||
assert.equal(candidatePanelView.element, null);
|
||||
|
||||
candidatePanelView.render();
|
||||
assert.notEqual(candidatePanelView.element, null);
|
||||
|
||||
var classList = candidatePanelView.element.classList;
|
||||
assert.isTrue(classList.contains('test-class'),
|
||||
'should contain the className specified in options');
|
||||
});
|
||||
|
||||
test('> show no candidates', function() {
|
||||
candidatePanelView.render();
|
||||
|
||||
candidatePanelView.showCandidates([]);
|
||||
|
||||
var suggestionsContainer =
|
||||
candidatePanelView.element.querySelector('.suggestions-container');
|
||||
assert.notEqual(suggestionsContainer, null);
|
||||
|
||||
// Should show dismiss button
|
||||
var dismissButton =
|
||||
candidatePanelView.element.querySelector('.dismiss-suggestions-button');
|
||||
assert.notEqual(dismissButton, null);
|
||||
|
||||
assert.isTrue(dismissButton.classList.contains('hide'));
|
||||
});
|
||||
|
||||
test('> show 3 candidates', function() {
|
||||
candidatePanelView.render();
|
||||
|
||||
candidatePanelView.showCandidates(createFakeCandidates(3));
|
||||
|
||||
var suggestionsContainer =
|
||||
candidatePanelView.element.querySelector('.suggestions-container');
|
||||
assert.notEqual(suggestionsContainer, null);
|
||||
|
||||
var candidateElements = suggestionsContainer.querySelectorAll('span');
|
||||
assert.equal(candidateElements.length, 3);
|
||||
|
||||
for (var i = 0; i < candidateElements.length; i++) {
|
||||
assert.equal(candidateElements[i].textContent, i);
|
||||
assert.equal(candidateElements[i].dataset.data, i);
|
||||
}
|
||||
|
||||
// Should show dismiss button
|
||||
var dismissButton =
|
||||
candidatePanelView.element.querySelector('.dismiss-suggestions-button');
|
||||
assert.notEqual(dismissButton, null);
|
||||
|
||||
assert.isFalse(dismissButton.classList.contains('hide'));
|
||||
});
|
||||
|
||||
test('> show 3 candidates, with the first as auto correction', function() {
|
||||
candidatePanelView.render();
|
||||
|
||||
candidatePanelView.showCandidates(['*1', '2', '3']);
|
||||
|
||||
var suggestionsContainer =
|
||||
candidatePanelView.element.querySelector('.suggestions-container');
|
||||
assert.notEqual(suggestionsContainer, null);
|
||||
|
||||
var candidateElements = suggestionsContainer.querySelectorAll('div');
|
||||
assert.equal(candidateElements.length, 3);
|
||||
|
||||
// The first candidate should be highlighted
|
||||
assert.isTrue(candidateElements[0].classList.contains('autocorrect'));
|
||||
});
|
||||
|
||||
test('Candidates scaling to 0.6', function() {
|
||||
candidatePanelView.render();
|
||||
|
||||
sinon.stub(ViewUtils, 'getScale', function() {
|
||||
return 0.6;
|
||||
});
|
||||
|
||||
var can = ['thisisverylongword', 'alsoverylongword', 'whatup'];
|
||||
candidatePanelView.showCandidates(can);
|
||||
|
||||
var spans = candidatePanelView.element.querySelectorAll('span');
|
||||
assert.equal(spans[0].textContent, can[0]);
|
||||
assert.equal(spans[1].textContent, can[1]);
|
||||
assert.equal(spans[2].textContent, can[2]);
|
||||
assert.equal(spans[0].style.width, '166.667%');
|
||||
assert.equal(spans[0].style.transformOrigin, 'left center 0px');
|
||||
assert.equal(spans[0].style.transform, 'scale(0.6)');
|
||||
|
||||
ViewUtils.getScale.restore();
|
||||
});
|
||||
|
||||
test('Candidats scaling to 0.5', function() {
|
||||
|
||||
candidatePanelView.render();
|
||||
|
||||
sinon.stub(ViewUtils, 'getScale', function() {
|
||||
return 0.5;
|
||||
});
|
||||
|
||||
var can = ['thisisverylongword', 'alsoverylongword', 'whatup'];
|
||||
candidatePanelView.showCandidates(can);
|
||||
|
||||
var spans = candidatePanelView.element.querySelectorAll('span');
|
||||
assert.equal(spans[0].textContent, 't…d');
|
||||
assert.equal(spans[1].textContent, 'a…d');
|
||||
assert.equal(spans[2].textContent, 'w…p');
|
||||
assert.equal(spans[0].style.width, '200%');
|
||||
assert.equal(spans[0].style.transformOrigin, 'left center 0px');
|
||||
assert.equal(spans[0].style.transform, 'scale(0.5)');
|
||||
|
||||
ViewUtils.getScale.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
523
gaia/apps/keyboard/test/unit/views/layout_page_view_test.js
Executable file
523
gaia/apps/keyboard/test/unit/views/layout_page_view_test.js
Executable file
|
|
@ -0,0 +1,523 @@
|
|||
'use strict';
|
||||
/* global LayoutPageView, KeyboardEvent */
|
||||
|
||||
require('/js/views/key_view.js');
|
||||
require('/js/views/handwriting_pad_view.js');
|
||||
require('/js/views/layout_page_view.js');
|
||||
|
||||
suite('Views > LayoutPageView', function() {
|
||||
var dummyLayout = {
|
||||
width: 2,
|
||||
keys: [
|
||||
[{ value: 'a' }, { value: 'b' }]
|
||||
]
|
||||
};
|
||||
|
||||
var viewManager = {
|
||||
registerView: sinon.stub()
|
||||
};
|
||||
|
||||
test(' > show() ', function() {
|
||||
var pageView = new LayoutPageView(dummyLayout, {}, viewManager);
|
||||
pageView.render();
|
||||
|
||||
pageView.show();
|
||||
|
||||
var container = pageView.element;
|
||||
assert.equal(container.dataset.active, 'true');
|
||||
});
|
||||
|
||||
test(' > hide() ', function() {
|
||||
var pageView = new LayoutPageView(dummyLayout, {}, viewManager);
|
||||
pageView.render();
|
||||
|
||||
pageView.hide();
|
||||
|
||||
var container = pageView.element;
|
||||
assert.equal(container.dataset.active, null);
|
||||
});
|
||||
|
||||
test(' > destroy() ', function() {
|
||||
var pageView = new LayoutPageView(dummyLayout, {}, viewManager);
|
||||
pageView.render();
|
||||
|
||||
pageView.destroy();
|
||||
|
||||
var container = pageView.element;
|
||||
assert.equal(container.parentNode, null);
|
||||
});
|
||||
|
||||
suite(' > render()', function() {
|
||||
test('Should add last-row class to last row', function() {
|
||||
var layout = {
|
||||
width: 2,
|
||||
keys: [
|
||||
[{ value: 'a' }, { value: 'b' }],
|
||||
[{ value: 'c' }, { value: 'd' }],
|
||||
[{ value: 'e' }, { value: 'f' }]
|
||||
]
|
||||
};
|
||||
|
||||
var pageView = new LayoutPageView(layout, {}, viewManager);
|
||||
pageView.render();
|
||||
|
||||
var container = pageView.element;
|
||||
|
||||
var rows = container.querySelectorAll('.keyboard-row');
|
||||
assert.equal(rows.length, 3);
|
||||
assert.equal(rows[2].classList.contains('keyboard-last-row'), true);
|
||||
var keys = container.querySelectorAll('.keyboard-key');
|
||||
assert.equal(keys.length, 6);
|
||||
});
|
||||
|
||||
test('layout.keyClassName should be added to all keys', function() {
|
||||
var layout = {
|
||||
width: 2,
|
||||
keys: [
|
||||
[{ value: 'a' }, { value: 'b' }]
|
||||
],
|
||||
keyClassName: 'c9'
|
||||
};
|
||||
|
||||
var pageView = new LayoutPageView(layout, {}, viewManager);
|
||||
pageView.render();
|
||||
|
||||
var container = pageView.element;
|
||||
assert.equal(container.querySelectorAll('.keyboard-key').length, 2);
|
||||
assert.equal(container.querySelectorAll('.keyboard-key.c9').length, 2);
|
||||
});
|
||||
|
||||
test('Keycode should be set', function() {
|
||||
var layout = {
|
||||
width: 9,
|
||||
keys: [
|
||||
[{ value: 'a', keyCode: 3 }, { value: 'b', keyCode: 98 }]
|
||||
]
|
||||
};
|
||||
|
||||
var pageView = new LayoutPageView(layout, {}, viewManager);
|
||||
pageView.render();
|
||||
|
||||
var container = pageView.element;
|
||||
|
||||
var keys = container.querySelectorAll('.keyboard-key');
|
||||
assert.equal(keys[0].dataset.keycode, 3);
|
||||
assert.equal(keys[1].dataset.keycode, 98);
|
||||
});
|
||||
|
||||
test('On uppercase flag, uppercase on keys, ' +
|
||||
'no lowercase class on container', function() {
|
||||
var layout = {
|
||||
width: 2,
|
||||
keys: [
|
||||
[{ value: 'a', uppercaseValue: 'A' },
|
||||
{ value: 'b', uppercaseValue: 'B' }]
|
||||
]
|
||||
};
|
||||
|
||||
var pageView = new LayoutPageView(layout, {}, viewManager);
|
||||
pageView.render();
|
||||
pageView.setUpperCaseLock({
|
||||
isUpperCase: true,
|
||||
isUpperCaseLocked: false
|
||||
});
|
||||
|
||||
var container = pageView.element;
|
||||
|
||||
var keys = container.querySelectorAll('.keyboard-key .key-element');
|
||||
assert.equal(keys.length, 4);
|
||||
assert.equal(keys[0].textContent, 'A');
|
||||
assert.isTrue(keys[1].classList.contains('lowercase'));
|
||||
assert.equal(keys[1].textContent, 'a');
|
||||
assert.equal(keys[2].textContent, 'B');
|
||||
assert.isTrue(keys[3].classList.contains('lowercase'));
|
||||
assert.equal(keys[3].textContent, 'b');
|
||||
|
||||
assert.isFalse(container.classList.contains('lowercase'));
|
||||
});
|
||||
|
||||
test('No uppercase flag, uppercase on keys, ' +
|
||||
'lowercase class on container', function() {
|
||||
var layout = {
|
||||
width: 2,
|
||||
keys: [
|
||||
[{ value: 'a', uppercaseValue: 'A' },
|
||||
{ value: 'b', uppercaseValue: 'B' }]
|
||||
]
|
||||
};
|
||||
|
||||
var pageView = new LayoutPageView(layout, {}, viewManager);
|
||||
pageView.render();
|
||||
pageView.setUpperCaseLock({
|
||||
isUpperCase: false,
|
||||
isUpperCaseLocked: false
|
||||
});
|
||||
|
||||
var container = pageView.element;
|
||||
|
||||
var keys = container.querySelectorAll('.keyboard-key .key-element');
|
||||
assert.equal(keys.length, 4);
|
||||
assert.equal(keys[0].textContent, 'A');
|
||||
assert.isTrue(keys[1].classList.contains('lowercase'));
|
||||
assert.equal(keys[1].textContent, 'a');
|
||||
assert.equal(keys[2].textContent, 'B');
|
||||
assert.isTrue(keys[3].classList.contains('lowercase'));
|
||||
assert.equal(keys[3].textContent, 'b');
|
||||
|
||||
assert.isTrue(container.classList.contains('lowercase'));
|
||||
});
|
||||
|
||||
test('create keyboard with handwriting pad', function() {
|
||||
var layout = {
|
||||
handwritingPadOptions: {
|
||||
ratio: 8.5,
|
||||
rowspan: 3
|
||||
},
|
||||
keys: [
|
||||
[
|
||||
{ value: 'a', ratio: 1.5 }
|
||||
], [
|
||||
{ value: 'b', ratio: 1.5 }
|
||||
], [
|
||||
{ value: 'c', ratio: 1.5 }
|
||||
], [
|
||||
{ value: 'd', ratio: 10 }
|
||||
]
|
||||
]
|
||||
};
|
||||
|
||||
var pageView = new LayoutPageView(layout, {}, viewManager);
|
||||
pageView.render();
|
||||
|
||||
var container = pageView.element;
|
||||
var pads = container.querySelectorAll('.handwriting-pad');
|
||||
assert.equal(pads.length, 1);
|
||||
});
|
||||
|
||||
suite('CSS classes on pageView', function() {
|
||||
test('with specificCssRule', function() {
|
||||
var layout = {
|
||||
width: 1,
|
||||
keys: [],
|
||||
layoutName: 'ar',
|
||||
specificCssRule: true
|
||||
};
|
||||
|
||||
var pageView = new LayoutPageView(layout, {}, viewManager);
|
||||
pageView.render();
|
||||
|
||||
var container = pageView.element;
|
||||
assert.equal(container.classList.contains('ar'), true);
|
||||
});
|
||||
|
||||
test('without specificCssRule', function() {
|
||||
var layout = {
|
||||
width: 1,
|
||||
keys: [],
|
||||
layoutName: 'ar',
|
||||
specificCssRule: false
|
||||
};
|
||||
|
||||
var pageView = new LayoutPageView(layout, {}, viewManager);
|
||||
pageView.render();
|
||||
|
||||
var container = pageView.element;
|
||||
assert.equal(container.classList.contains('ar'), false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite(' > setUpperCaseLock()', function() {
|
||||
var pageView = null;
|
||||
setup(function() {
|
||||
|
||||
var layout = {
|
||||
width: 2,
|
||||
keys: [
|
||||
[{ value: 'a' },
|
||||
{ value: 'Shift', keyCode: KeyboardEvent.DOM_VK_CAPS_LOCK }
|
||||
]
|
||||
]
|
||||
};
|
||||
pageView = new LayoutPageView(layout, {}, viewManager);
|
||||
pageView.render();
|
||||
});
|
||||
|
||||
test('turn to uppercase', function() {
|
||||
pageView.setUpperCaseLock({
|
||||
isUpperCase: true,
|
||||
isUpperCaseLocked: false
|
||||
});
|
||||
|
||||
var container = pageView.element;
|
||||
assert.isFalse(container.classList.contains('lowercase'));
|
||||
|
||||
var capsLockKey = container.querySelector(
|
||||
'[data-keycode="' + KeyboardEvent.DOM_VK_CAPS_LOCK + '"]'
|
||||
);
|
||||
|
||||
assert.isTrue(capsLockKey.classList.contains('kbr-key-active'));
|
||||
assert.isFalse(capsLockKey.classList.contains('kbr-key-hold'));
|
||||
|
||||
assert.equal(capsLockKey.getAttribute('aria-pressed'), 'true');
|
||||
});
|
||||
|
||||
test('turn to lowercase', function() {
|
||||
pageView.setUpperCaseLock({
|
||||
isUpperCase: false,
|
||||
isUpperCaseLocked: false
|
||||
});
|
||||
|
||||
var container = pageView.element;
|
||||
assert.isTrue(container.classList.contains('lowercase'));
|
||||
|
||||
var capsLockKey = container.querySelector(
|
||||
'[data-keycode="' + KeyboardEvent.DOM_VK_CAPS_LOCK + '"]'
|
||||
);
|
||||
|
||||
assert.isFalse(capsLockKey.classList.contains('kbr-key-active'));
|
||||
assert.isFalse(capsLockKey.classList.contains('kbr-key-hold'));
|
||||
|
||||
assert.equal(capsLockKey.getAttribute('aria-pressed'), 'false');
|
||||
});
|
||||
|
||||
test('enable CapsLock', function() {
|
||||
pageView.setUpperCaseLock({
|
||||
isUpperCase: true,
|
||||
isUpperCaseLocked: true
|
||||
});
|
||||
|
||||
var container = pageView.element;
|
||||
assert.isFalse(container.classList.contains('lowercase'));
|
||||
|
||||
var capsLockKey = container.querySelector(
|
||||
'[data-keycode="' + KeyboardEvent.DOM_VK_CAPS_LOCK + '"]'
|
||||
);
|
||||
|
||||
assert.isFalse(capsLockKey.classList.contains('kbr-key-active'));
|
||||
assert.isTrue(capsLockKey.classList.contains('kbr-key-hold'));
|
||||
|
||||
assert.equal(capsLockKey.getAttribute('aria-pressed'), 'true');
|
||||
});
|
||||
});
|
||||
|
||||
suite(' > highlightKey() and unHighlightKey()', function() {
|
||||
var pageView = null;
|
||||
var keyView;
|
||||
|
||||
setup(function() {
|
||||
var layout = {
|
||||
width: 2,
|
||||
keys: []
|
||||
};
|
||||
|
||||
keyView = {
|
||||
highlight: sinon.stub(),
|
||||
unHighlight: sinon.stub()
|
||||
};
|
||||
|
||||
var viewManager = {
|
||||
getView: function() {
|
||||
return keyView;
|
||||
}
|
||||
};
|
||||
|
||||
pageView = new LayoutPageView(layout, {}, viewManager);
|
||||
pageView.render();
|
||||
});
|
||||
|
||||
test('highlightKey() with upperCase', function() {
|
||||
pageView.isUpperCase = true;
|
||||
pageView.highlightKey({});
|
||||
|
||||
assert.isTrue(keyView.highlight.calledOnce);
|
||||
assert.isTrue(keyView.highlight.calledWith({upperCase: true}));
|
||||
});
|
||||
|
||||
test('highlightKey() with upperCase', function() {
|
||||
pageView.isUpperCase = false;
|
||||
pageView.highlightKey({});
|
||||
|
||||
assert.isTrue(keyView.highlight.calledOnce);
|
||||
assert.isTrue(keyView.highlight.calledWith({upperCase: false}));
|
||||
});
|
||||
|
||||
test('unHighlightKey()', function() {
|
||||
pageView.unHighlightKey({});
|
||||
assert.isTrue(keyView.unHighlight.calledOnce);
|
||||
});
|
||||
});
|
||||
|
||||
suite('> getHeight()', function() {
|
||||
var layout;
|
||||
var pageView = null;
|
||||
var viewManager = {
|
||||
registerView: sinon.stub(),
|
||||
getRemToPx: sinon.stub(),
|
||||
screenInPortraitMode: sinon.stub()
|
||||
};
|
||||
|
||||
var rootElement;
|
||||
|
||||
setup(function() {
|
||||
rootElement = document.createElement('div');
|
||||
document.body.appendChild(rootElement);
|
||||
|
||||
layout = {
|
||||
width: 2,
|
||||
keys: [
|
||||
[{ value: 'a' }, { value: 'b' }],
|
||||
[{ value: 'c' }, { value: 'd' }],
|
||||
[{ value: 'e' }, { value: 'f' }],
|
||||
[{ value: 'g' }, { value: 'h' }]
|
||||
]
|
||||
};
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
document.body.removeChild(rootElement);
|
||||
});
|
||||
|
||||
test('w/ 4 rows, portrait', function() {
|
||||
viewManager.getRemToPx.returns(10);
|
||||
viewManager.screenInPortraitMode.returns(true);
|
||||
|
||||
pageView = new LayoutPageView(layout, {}, viewManager);
|
||||
pageView.render();
|
||||
assert.equal(pageView.getHeight(), 4 * ((4.5 + 0.8) * 10));
|
||||
});
|
||||
|
||||
test('w/ 4 rows, landscape', function() {
|
||||
viewManager.getRemToPx.returns(10);
|
||||
viewManager.screenInPortraitMode.returns(false);
|
||||
|
||||
pageView = new LayoutPageView(layout, {}, viewManager);
|
||||
pageView.render();
|
||||
assert.equal(pageView.getHeight(), 4 * ((3.8 + 0.8) * 10));
|
||||
});
|
||||
|
||||
test('w/ 5 rows, portrait', function() {
|
||||
layout.keys.push([{ value: 'i' }, { value: 'j' }]);
|
||||
viewManager.getRemToPx.returns(10);
|
||||
viewManager.screenInPortraitMode.returns(true);
|
||||
|
||||
pageView = new LayoutPageView(layout, {}, viewManager);
|
||||
pageView.render();
|
||||
assert.equal(pageView.getHeight(), 5 * ((3.4 + 0.8) * 10));
|
||||
});
|
||||
|
||||
test('w/ 5 rows, landscape', function() {
|
||||
layout.keys.push([{ value: 'i' }, { value: 'j' }]);
|
||||
viewManager.getRemToPx.returns(10);
|
||||
viewManager.screenInPortraitMode.returns(false);
|
||||
|
||||
pageView = new LayoutPageView(layout, {}, viewManager);
|
||||
pageView.render();
|
||||
assert.equal(pageView.getHeight(), 5 * ((2.85 + 0.8) * 10));
|
||||
});
|
||||
});
|
||||
|
||||
suite(' > getVisualData()', function() {
|
||||
var layout = {
|
||||
width: 2,
|
||||
keys: [
|
||||
[{ value: 'a' }, { value: 'b' }],
|
||||
[{ value: 'c' }, { value: 'd' }],
|
||||
[{ value: 'e' }, { value: 'f' }]
|
||||
]
|
||||
};
|
||||
var pageView = null;
|
||||
var viewManager = {
|
||||
registerView: sinon.stub()
|
||||
};
|
||||
|
||||
var rootElement;
|
||||
|
||||
setup(function() {
|
||||
rootElement = document.createElement('div');
|
||||
document.body.appendChild(rootElement);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
document.body.removeChild(rootElement);
|
||||
});
|
||||
|
||||
test('check getVisualData sanity', function() {
|
||||
var layout = {
|
||||
width: 10,
|
||||
keys: [
|
||||
[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}]
|
||||
]
|
||||
};
|
||||
|
||||
pageView = new LayoutPageView(layout, {}, viewManager);
|
||||
pageView.render();
|
||||
|
||||
rootElement.appendChild(pageView.element);
|
||||
|
||||
var vData = pageView.getVisualData();
|
||||
|
||||
var visuals = [].slice.call(
|
||||
document.querySelectorAll('.visual-wrapper'));
|
||||
|
||||
visuals.forEach(function(v, ix) {
|
||||
assert.equal(v.offsetLeft, vData[ix].x, 'x for ' + ix);
|
||||
assert.equal(v.offsetTop, vData[ix].y, 'y for ' + ix);
|
||||
assert.equal(v.clientWidth, vData[ix].width, 'width for ' + ix);
|
||||
assert.equal(v.clientHeight, vData[ix].height, 'height for ' + ix);
|
||||
});
|
||||
});
|
||||
|
||||
test('check getVisualData sanity for filled up space', function() {
|
||||
var layout = {
|
||||
width: 4,
|
||||
keys: [
|
||||
[{}, {}, {}]
|
||||
]
|
||||
};
|
||||
|
||||
pageView = new LayoutPageView(layout, {}, viewManager);
|
||||
pageView.render();
|
||||
|
||||
rootElement.appendChild(pageView.element);
|
||||
|
||||
var vData = pageView.getVisualData();
|
||||
|
||||
|
||||
var visuals = [].slice.call(
|
||||
document.querySelectorAll('.visual-wrapper'));
|
||||
|
||||
visuals.forEach(function(v, ix) {
|
||||
assert.equal(v.offsetLeft, vData[ix].x, 'x for ' + ix);
|
||||
assert.equal(v.offsetTop, vData[ix].y, 'y for ' + ix);
|
||||
assert.equal(v.clientWidth, vData[ix].width, 'width for ' + ix);
|
||||
assert.equal(v.clientHeight, vData[ix].height, 'height for ' + ix);
|
||||
});
|
||||
});
|
||||
|
||||
test('getVisualData will return cached data', function() {
|
||||
pageView = new LayoutPageView(layout, {}, viewManager);
|
||||
pageView.render();
|
||||
|
||||
var vData = pageView.getVisualData();
|
||||
var vData2 = pageView.getVisualData();
|
||||
|
||||
assert.equal(vData, vData2);
|
||||
});
|
||||
|
||||
test('getVisualData() will return cached data for different ' +
|
||||
'size', function() {
|
||||
pageView = new LayoutPageView(layout, {}, viewManager);
|
||||
pageView.render();
|
||||
|
||||
var vData = pageView.getVisualData();
|
||||
|
||||
pageView.resize(480);
|
||||
var vData2 = pageView.getVisualData();
|
||||
assert.notEqual(vData, vData2);
|
||||
|
||||
var vData3 = pageView.getVisualData();
|
||||
assert.equal(vData2, vData3);
|
||||
});
|
||||
});
|
||||
});
|
||||
84
gaia/apps/keyboard/test/unit/views/swipeable_page_view_test.js
Executable file
84
gaia/apps/keyboard/test/unit/views/swipeable_page_view_test.js
Executable file
|
|
@ -0,0 +1,84 @@
|
|||
'use strict';
|
||||
|
||||
/* global SwipeablePageView */
|
||||
require('/js/views/key_view.js');
|
||||
require('/js/views/layout_page_view.js');
|
||||
require('/js/views/swipeable_section_view.js');
|
||||
require('/js/views/swiping_detector.js');
|
||||
require('/js/views/swipeable_panel_view.js');
|
||||
require('/js/views/swipeable_page_view.js');
|
||||
|
||||
suite('Views > SwipeablePageView', function() {
|
||||
var pageView = null;
|
||||
var viewManager = {
|
||||
registerView: sinon.stub()
|
||||
};
|
||||
|
||||
setup(function() {
|
||||
var dummyLayout = {
|
||||
width: 2,
|
||||
panelKeys: [
|
||||
[{ value: 'a' }, { value: 'b' }]
|
||||
|
||||
],
|
||||
keys: [
|
||||
[{ value: 'a' }, { value: 'b' }]
|
||||
]
|
||||
|
||||
};
|
||||
|
||||
pageView = new SwipeablePageView(dummyLayout, {},
|
||||
viewManager);
|
||||
});
|
||||
|
||||
suite('some basic functions', function() {
|
||||
test('> render() ', function() {
|
||||
assert.equal(pageView.element, null);
|
||||
|
||||
pageView.render();
|
||||
assert.notEqual(pageView.element, null);
|
||||
|
||||
pageView.show();
|
||||
assert.equal(pageView.element.dataset.active, 'true');
|
||||
assert.ok(pageView.element.classList.contains('uppercase-only'));
|
||||
|
||||
var rows = pageView.element.querySelectorAll('.keyboard-row');
|
||||
assert.equal(rows.length, 1);
|
||||
|
||||
assert.ok(rows[0].classList.contains('swipe-switching-buttons'));
|
||||
});
|
||||
});
|
||||
|
||||
suite('CSS classes on pageView', function() {
|
||||
test('with specificCssRule', function() {
|
||||
var layout = {
|
||||
panelKeys: [],
|
||||
keys: [],
|
||||
layoutName: 'ar',
|
||||
specificCssRule: true
|
||||
};
|
||||
|
||||
var pageView = new SwipeablePageView(layout, {}, viewManager);
|
||||
pageView.render();
|
||||
|
||||
var container = pageView.element;
|
||||
assert.equal(container.classList.contains('ar'), true);
|
||||
});
|
||||
|
||||
test('without specificCssRule', function() {
|
||||
var layout = {
|
||||
width: 1,
|
||||
panelKeys: [],
|
||||
keys: [],
|
||||
layoutName: 'ar',
|
||||
specificCssRule: false
|
||||
};
|
||||
|
||||
var pageView = new SwipeablePageView(layout, {}, viewManager);
|
||||
pageView.render();
|
||||
|
||||
var container = pageView.element;
|
||||
assert.equal(container.classList.contains('ar'), false);
|
||||
});
|
||||
});
|
||||
});
|
||||
395
gaia/apps/keyboard/test/unit/views/swipeable_panel_view_test.js
Executable file
395
gaia/apps/keyboard/test/unit/views/swipeable_panel_view_test.js
Executable file
|
|
@ -0,0 +1,395 @@
|
|||
'use strict';
|
||||
|
||||
/* global SwipeablePanelView */
|
||||
require('/js/views/base_view.js');
|
||||
require('/js/views/key_view.js');
|
||||
require('/js/views/emoji_key_view.js');
|
||||
require('/js/views/swiping_detector.js');
|
||||
require('/js/views/swipeable_panel_view.js');
|
||||
|
||||
suite('Views > SwipeablePanelView', function() {
|
||||
var panelView = null;
|
||||
var viewManager = {
|
||||
registerView: sinon.stub()
|
||||
};
|
||||
|
||||
var layout = {
|
||||
panelKeys: []
|
||||
};
|
||||
|
||||
var sectionCount = 4;
|
||||
|
||||
for (var i = 0; i < 18 * sectionCount; i++) {
|
||||
layout.panelKeys.push({ value: i, type: 'emoji' });
|
||||
}
|
||||
|
||||
setup(function() {
|
||||
var options = {
|
||||
};
|
||||
|
||||
panelView = new SwipeablePanelView(layout, options,
|
||||
viewManager);
|
||||
});
|
||||
|
||||
suite('some basic functions', function() {
|
||||
test('> render() ', function() {
|
||||
assert.equal(panelView.element, null);
|
||||
|
||||
panelView.render();
|
||||
assert.notEqual(panelView.element, null);
|
||||
});
|
||||
|
||||
test('> render() check indicator elements', function() {
|
||||
panelView.render();
|
||||
|
||||
var indicators = panelView.element.querySelectorAll('.section-indicator');
|
||||
assert.equal(indicators.length, sectionCount);
|
||||
|
||||
assert.ok(indicators[0].classList.contains('active'));
|
||||
});
|
||||
|
||||
test('> move to the next section', function() {
|
||||
panelView.render();
|
||||
|
||||
panelView.gotoSection(1);
|
||||
var indicators = panelView.element.querySelectorAll('.section-indicator');
|
||||
|
||||
assert.isFalse(indicators[0].classList.contains('active'));
|
||||
assert.ok(indicators[1].classList.contains('active'));
|
||||
});
|
||||
});
|
||||
|
||||
suite('utilize different KeyView type', function() {
|
||||
test('> render() with KeyView ', function() {
|
||||
var layout = {
|
||||
panelKeys: [{value: 'key1'}, {value: 'key2'}]
|
||||
};
|
||||
|
||||
var panelView = new SwipeablePanelView(layout, {}, viewManager);
|
||||
panelView.render();
|
||||
|
||||
var keyView = panelView.element.querySelector('button.keyboard-key');
|
||||
assert.notEqual(keyView, null);
|
||||
});
|
||||
|
||||
test('> render() with EmojiKeyView ', function() {
|
||||
var layout = {
|
||||
panelKeys: [{value: 'key1', type: 'emoji'},
|
||||
{value: 'key2', type: 'emoji'}]
|
||||
};
|
||||
|
||||
var panelView = new SwipeablePanelView(layout, {}, viewManager);
|
||||
panelView.render();
|
||||
|
||||
var keyView = panelView.element.querySelector('button.emoji');
|
||||
assert.notEqual(keyView, null);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Render different number of elements', function() {
|
||||
test('> render() with no elements', function() {
|
||||
var layout = {
|
||||
panelKeys: [{value: 'key1'}, {value: 'key2'}]
|
||||
};
|
||||
|
||||
panelView = new SwipeablePanelView(layout, {}, viewManager);
|
||||
panelView.render();
|
||||
|
||||
assert.equal(panelView.sections[1], null);
|
||||
});
|
||||
|
||||
test('> render() with 2 emoji elements', function() {
|
||||
var layout = {
|
||||
panelKeys: [{value: 'key1', type: 'emoji'},
|
||||
{value: 'key2', type: 'emoji'}]
|
||||
};
|
||||
|
||||
panelView = new SwipeablePanelView(layout, {}, viewManager);
|
||||
panelView.render();
|
||||
|
||||
var section = panelView.sections[0];
|
||||
var keys = section.querySelectorAll('button.emoji');
|
||||
assert.equal(keys.length, 2);
|
||||
|
||||
var dummyElement = section.querySelector('span');
|
||||
assert.equal(dummyElement.style.flexGrow, 4);
|
||||
});
|
||||
|
||||
test('> render() one section at a time', function() {
|
||||
var layout = {
|
||||
panelKeys: []
|
||||
};
|
||||
|
||||
for (var i = 0; i < 20; i++) {
|
||||
layout.panelKeys.push({value: 'key' + i, type: 'emoji'});
|
||||
}
|
||||
|
||||
panelView = new SwipeablePanelView(layout, {}, viewManager);
|
||||
panelView.render();
|
||||
|
||||
var keys = panelView.sections[0].querySelectorAll('button.emoji');
|
||||
assert.equal(keys.length, 18);
|
||||
|
||||
keys = panelView.sections[1].querySelectorAll('button.emoji');
|
||||
assert.equal(keys.length, 2);
|
||||
|
||||
assert.equal(keys[0].textContent, 'key18');
|
||||
});
|
||||
});
|
||||
|
||||
suite('panning', function() {
|
||||
var rootElement = document.createElement('div');
|
||||
|
||||
setup(function() {
|
||||
document.body.appendChild(rootElement);
|
||||
var options = {
|
||||
totalWidth: 320
|
||||
};
|
||||
|
||||
panelView = new SwipeablePanelView(layout, options,
|
||||
viewManager);
|
||||
|
||||
panelView.render();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
rootElement.innerHTML = '';
|
||||
});
|
||||
|
||||
test('> panning the first section - forward', function() {
|
||||
assert.equal(panelView.sections[0].style.transform, '');
|
||||
|
||||
panelView._handleTouchStart({
|
||||
position: {clientX: 20, clientY: 0}
|
||||
});
|
||||
|
||||
panelView._handlePan({
|
||||
dx: -20,
|
||||
position: {clientX: 0, clientY: 0}
|
||||
});
|
||||
|
||||
assert.equal(panelView.sections[0].style.transform, 'translateX(-20px)');
|
||||
assert.equal(panelView.sections[1].style.transform, 'translateX(300px)');
|
||||
});
|
||||
|
||||
test('> panning the first section - backward', function() {
|
||||
assert.equal(panelView.sections[0].style.transform, '');
|
||||
|
||||
panelView._handleTouchStart({
|
||||
position: {clientX: 0, clientY: 0}
|
||||
});
|
||||
|
||||
panelView._handlePan({
|
||||
dx: 20,
|
||||
position: {clientX: 20, clientY: 0}
|
||||
});
|
||||
|
||||
assert.equal(panelView.sections[0].style.transform, '');
|
||||
});
|
||||
|
||||
test('> panning the second section - forward', function() {
|
||||
panelView.gotoSection(1);
|
||||
|
||||
panelView._handleTouchStart({
|
||||
position: {clientX: 20, clientY: 0}
|
||||
});
|
||||
|
||||
panelView._handlePan({
|
||||
dx: -20,
|
||||
position: {clientX: 0, clientY: 0}
|
||||
});
|
||||
|
||||
assert.equal(panelView.sections[1].style.transform, 'translateX(-20px)');
|
||||
assert.equal(panelView.sections[2].style.transform, 'translateX(300px)');
|
||||
});
|
||||
|
||||
test('> panning the second section - backward', function() {
|
||||
panelView.gotoSection(1);
|
||||
|
||||
panelView._handleTouchStart({
|
||||
position: {clientX: 20, clientY: 0}
|
||||
});
|
||||
|
||||
panelView._handlePan({
|
||||
dx: 20,
|
||||
position: {clientX: 40, clientY: 0}
|
||||
});
|
||||
|
||||
assert.equal(panelView.sections[0].style.transform, 'translateX(-300px)');
|
||||
assert.equal(panelView.sections[1].style.transform, 'translateX(20px)');
|
||||
});
|
||||
|
||||
test('> panning the last section - forward', function() {
|
||||
panelView.gotoSection(sectionCount - 1);
|
||||
|
||||
panelView._handleTouchStart({
|
||||
position: {clientX: 20, clientY: 0}
|
||||
});
|
||||
|
||||
panelView._handlePan({
|
||||
dx: -20,
|
||||
position: {clientX: 0, clientY: 0}
|
||||
});
|
||||
|
||||
assert.equal(panelView.sections[sectionCount - 1].style.transform,
|
||||
'translateX(0px)');
|
||||
});
|
||||
|
||||
test('> panning the last section - backward', function() {
|
||||
panelView.gotoSection(sectionCount - 1);
|
||||
|
||||
panelView._handleTouchStart({
|
||||
position: {clientX: 20, clientY: 0}
|
||||
});
|
||||
|
||||
panelView._handlePan({
|
||||
dx: 20,
|
||||
position: {clientX: 40, clientY: 0}
|
||||
});
|
||||
|
||||
assert.equal(panelView.sections[sectionCount - 1].style.transform,
|
||||
'translateX(20px)');
|
||||
assert.equal(panelView.sections[sectionCount - 2].style.transform,
|
||||
'translateX(-300px)');
|
||||
});
|
||||
|
||||
test('> swiping through the first section - short distance', function() {
|
||||
rootElement.appendChild(panelView.element);
|
||||
|
||||
panelView._handleTouchStart({
|
||||
position: {clientX: 20, clientY: 0}
|
||||
});
|
||||
|
||||
panelView._handlePan({
|
||||
dx: -20,
|
||||
position: {clientX: 0, clientY: 0}
|
||||
});
|
||||
|
||||
assert.equal(panelView.sections[0].style.transform, 'translateX(-20px)');
|
||||
|
||||
panelView._handleSwipe({
|
||||
direction: 'left',
|
||||
vx: 0.3
|
||||
});
|
||||
|
||||
assert.equal(panelView.sections[0].style.transform, 'translateX(0px)');
|
||||
});
|
||||
|
||||
test('> swiping through the first section - far enough', function() {
|
||||
rootElement.appendChild(panelView.element);
|
||||
|
||||
panelView._handleTouchStart({
|
||||
position: {clientX: 100, clientY: 0}
|
||||
});
|
||||
|
||||
panelView._handlePan({
|
||||
dx: -100,
|
||||
position: {clientX: 0, clientY: 0}
|
||||
});
|
||||
|
||||
assert.equal(panelView.sections[0].style.transform, 'translateX(-100px)');
|
||||
|
||||
panelView._handleSwipe({
|
||||
direction: 'left',
|
||||
vx: -0.3
|
||||
});
|
||||
|
||||
assert.equal(panelView.sections[0].style.transform, 'translateX(-320px)');
|
||||
assert.equal(panelView.sections[1].style.transform, 'translateX(0px)');
|
||||
});
|
||||
|
||||
test('> swiping through the first section - fast enough', function() {
|
||||
rootElement.appendChild(panelView.element);
|
||||
|
||||
panelView._handleTouchStart({
|
||||
position: {clientX: 20, clientY: 0}
|
||||
});
|
||||
|
||||
panelView._handlePan({
|
||||
dx: -20,
|
||||
position: {clientX: 0, clientY: 0}
|
||||
});
|
||||
|
||||
assert.equal(panelView.sections[0].style.transform, 'translateX(-20px)');
|
||||
|
||||
panelView._handleSwipe({
|
||||
direction: 'left',
|
||||
vx: -1
|
||||
});
|
||||
|
||||
assert.equal(panelView.sections[0].style.transform, 'translateX(-320px)');
|
||||
assert.equal(panelView.sections[1].style.transform, 'translateX(0px)');
|
||||
});
|
||||
|
||||
test('> swiping back to the first section, > threshold', function() {
|
||||
rootElement.appendChild(panelView.element);
|
||||
|
||||
panelView.gotoSection(1);
|
||||
|
||||
panelView._handleTouchStart({
|
||||
position: {clientX: 20, clientY: 0}
|
||||
});
|
||||
|
||||
panelView._handlePan({
|
||||
dx: 100,
|
||||
position: {clientX: 120, clientY: 0}
|
||||
});
|
||||
|
||||
assert.equal(panelView.sections[1].style.transform, 'translateX(100px)');
|
||||
|
||||
panelView._handleSwipe({
|
||||
direction: 'right',
|
||||
vx: 0.3
|
||||
});
|
||||
|
||||
assert.equal(panelView.sections[0].style.transform, 'translateX(0px)');
|
||||
assert.equal(panelView.sections[1].style.transform, 'translateX(320px)');
|
||||
});
|
||||
});
|
||||
|
||||
suite('rotate', function() {
|
||||
var rootElement = document.createElement('div');
|
||||
|
||||
setup(function() {
|
||||
document.body.appendChild(rootElement);
|
||||
var options = { totalWidth: 320 };
|
||||
|
||||
panelView = new SwipeablePanelView(layout, options,
|
||||
viewManager);
|
||||
|
||||
panelView.render();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
rootElement.innerHTML = '';
|
||||
});
|
||||
|
||||
test('> rotate to landscape - at first section', function() {
|
||||
panelView.resize(590);
|
||||
|
||||
assert.equal(panelView.sections[0].style.transform, 'translateX(0px)');
|
||||
assert.equal(panelView.sections[1].style.transform, 'translateX(590px)');
|
||||
});
|
||||
|
||||
test('> rotate to landscape - at second section', function() {
|
||||
panelView.gotoSection(1);
|
||||
panelView.resize(590);
|
||||
|
||||
assert.equal(panelView.sections[0].style.transform, 'translateX(-590px)');
|
||||
assert.equal(panelView.sections[1].style.transform, 'translateX(0px)');
|
||||
assert.equal(panelView.sections[2].style.transform, 'translateX(590px)');
|
||||
});
|
||||
|
||||
test('> rotate to landscape - at last section', function() {
|
||||
panelView.gotoSection(sectionCount - 1);
|
||||
panelView.resize(590);
|
||||
|
||||
assert.equal(
|
||||
panelView.sections[sectionCount - 1].style.transform,
|
||||
'translateX(0px)');
|
||||
assert.equal(
|
||||
panelView.sections[sectionCount - 2].style.transform,
|
||||
'translateX(-590px)');
|
||||
});
|
||||
});
|
||||
});
|
||||
161
gaia/apps/keyboard/test/unit/views/swiping_detector_test.js
Executable file
161
gaia/apps/keyboard/test/unit/views/swiping_detector_test.js
Executable file
|
|
@ -0,0 +1,161 @@
|
|||
'use strict';
|
||||
|
||||
/* global SwipingDetector */
|
||||
require('/js/views/swiping_detector.js');
|
||||
|
||||
suite('View utils > SwipingDetector', function() {
|
||||
function fakeTouchDispatch(type, panel, xs, ys) {
|
||||
var touches = [];
|
||||
|
||||
for (var i = 0; i < xs.length; i++) {
|
||||
var x = xs[i];
|
||||
var y = ys[i];
|
||||
var touch = document.createTouch(window, panel, 42, x, y,
|
||||
x, y, x, y,
|
||||
0, 0, 0, 0);
|
||||
touches.push(touch);
|
||||
}
|
||||
var touchList = document.createTouchList(touches);
|
||||
|
||||
var eventTouches = (type == 'touchstart' || type == 'touchmove') ?
|
||||
touchList : null;
|
||||
var eventChanged = (type == 'touchmove') ?
|
||||
null : touchList;
|
||||
|
||||
var e = document.createEvent('TouchEvent');
|
||||
e.initTouchEvent(type, true, true,
|
||||
null, null, false, false, false, false,
|
||||
eventTouches, null, eventChanged);
|
||||
|
||||
panel.dispatchEvent(e);
|
||||
return e;
|
||||
}
|
||||
|
||||
function touchStart(panel, xs, ys) {
|
||||
return fakeTouchDispatch('touchstart', panel, xs, ys);
|
||||
}
|
||||
|
||||
function touchMove(panel, xs, ys) {
|
||||
return fakeTouchDispatch('touchmove', panel, xs, ys);
|
||||
}
|
||||
|
||||
function touchEnd(panel, xs, ys) {
|
||||
return fakeTouchDispatch('touchend', panel, xs, ys);
|
||||
}
|
||||
|
||||
function swipe(clock, panel, fromX, toX, fromY, toY, duration, noEnd) {
|
||||
var events = [];
|
||||
|
||||
duration = duration || 350;
|
||||
events.push(touchStart(panel, [fromX], [fromY]));
|
||||
|
||||
var diffX = Math.abs(toX - fromX);
|
||||
var diffY = Math.abs(toY - fromY);
|
||||
var delta = Math.max(diffX, diffY);
|
||||
var step = step || 1;
|
||||
|
||||
var x = 0, y = 0;
|
||||
var tick = duration / delta;
|
||||
for (var i = 0; i < delta; i++) {
|
||||
var newX = fromX + x;
|
||||
var newY = fromY + y;
|
||||
|
||||
events.push(touchMove(panel, [newX], [newY]));
|
||||
clock.tick(tick);
|
||||
|
||||
if (newX < toX) {
|
||||
x++;
|
||||
}
|
||||
if (newX > toX) {
|
||||
x--;
|
||||
}
|
||||
if (newY < toY) {
|
||||
y++;
|
||||
}
|
||||
if (newY > toY) {
|
||||
y--;
|
||||
}
|
||||
}
|
||||
|
||||
if (!noEnd) {
|
||||
events.push(touchEnd(panel, [toX], [toY]));
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
function createFakeCoordinates(x, y, timeStamp, id) {
|
||||
return Object.freeze({
|
||||
screenX: x,
|
||||
screenY: y,
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
timeStamp: timeStamp,
|
||||
identifier: id
|
||||
});
|
||||
}
|
||||
|
||||
var swipingDetector = null;
|
||||
var element = null;
|
||||
|
||||
setup(function() {
|
||||
element = document.createElement('div');
|
||||
swipingDetector = new SwipingDetector(element);
|
||||
|
||||
this.sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
test('init', function() {
|
||||
swipingDetector.start();
|
||||
});
|
||||
|
||||
test('pan', function() {
|
||||
swipingDetector.start();
|
||||
|
||||
var panHandler = this.sinon.stub();
|
||||
swipingDetector.onpan = panHandler;
|
||||
|
||||
swipe(this.sinon.clock, element, 21, 0, 0, 0, 100, true);
|
||||
|
||||
assert.equal(panHandler.lastCall.args[0].dx, -20);
|
||||
});
|
||||
|
||||
test('swipe', function() {
|
||||
swipingDetector.start();
|
||||
|
||||
var swipeHandler = this.sinon.stub();
|
||||
swipingDetector.onswipe = swipeHandler;
|
||||
|
||||
swipe(this.sinon.clock, element, 0, 21, 0, 0, 100);
|
||||
assert.equal(swipeHandler.firstCall.args[0].direction, 'right');
|
||||
|
||||
swipe(this.sinon.clock, element, 20, 0, 0, 0, 300);
|
||||
assert.equal(swipeHandler.lastCall.args[0].direction, 'left');
|
||||
});
|
||||
|
||||
test('swipe will report velocity', function() {
|
||||
swipingDetector.start();
|
||||
|
||||
var swipeHandler = this.sinon.stub();
|
||||
swipingDetector.onswipe = swipeHandler;
|
||||
|
||||
swipingDetector.handleEvent({
|
||||
type: 'touchstart',
|
||||
touches: [createFakeCoordinates(0, 0, 0)],
|
||||
timeStamp: 0
|
||||
});
|
||||
|
||||
swipingDetector.handleEvent({
|
||||
type: 'touchmove',
|
||||
touches: [createFakeCoordinates(20, 0, 0)],
|
||||
timeStamp: 100
|
||||
});
|
||||
|
||||
swipingDetector.handleEvent({
|
||||
type: 'touchend',
|
||||
changedTouches: [createFakeCoordinates(20, 0, 0)],
|
||||
timeStamp: 100
|
||||
});
|
||||
|
||||
assert.equal(swipeHandler.firstCall.args[0].vx, 0.2);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue