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