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
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);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
Loading…
Reference in a new issue