'),
collection: configs,
profiles: profiles
});
view.render();
done();
});
describe('is not empty', function() {
it('ok', function() {
expect(view.$el).not.be.empty();
expect(view.$el).to.have('input');
console.log(view.options.profiles.get('value'));
});
it('shows profiles list', function() {
expect(view.$('table').length !== 0).to.be.equal(true);
expect(view.$('tr').length === 4).to.be.equal(true);
});
});
describe('Triggers events', function() {
it('create:profile', function(done) {
var e = $.Event('keypress');
e.which = 13;
view.once('create:profile', function(name) {
expect(name).to.be.equal(view.ui.profile.val());
done();
});
view.ui.profile.val('profileI');
view.ui.profile.trigger(e);
});
it('remove:profile', function(done) {
console.log($('.removeProfile'));
view.once('remove:profile', function(name) {
expect(name !== '').to.be.equal(true);
done();
});
view.$('.removeProfile').trigger('click');
});
});
});
});
================================================
FILE: test/spec/apps/settings/show/views/shortcuts.js
================================================
/* global chai, define, describe, before, it */
define([
'jquery',
'spec/apps/settings/show/formBehavior',
'collections/configs',
'apps/settings/show/views/keybindings'
], function($, settingsBehavior, Configs, Shortcuts) {
'use strict';
var expect = chai.expect;
describe('Shortcuts settings view', function() {
var configs,
view;
before(function(done) {
configs = new Configs();
configs.resetFromJSON(Configs.prototype.configNames);
view = new Shortcuts({
el: $('
'),
collection: configs
});
view.render();
done();
});
describe('Instantiated', function() {
it('ok', function() {
expect(view instanceof Shortcuts).to.be.equal(true);
expect(view.collection instanceof Configs).to.be.equal(true);
expect(view.collection.length > 0).to.be.equal(true);
});
it('has behaviors', function() {
expect(typeof view.behaviors).to.be.equal('object');
expect(view.behaviors.hasOwnProperty('FormBehavior')).to.be.equal(true);
});
it('is not empty', function() {
expect(view.$el).not.to.be.empty();
expect(view.$el).to.have('input');
});
});
describe('Triggers events', function() {
it('collection:new:value when something has changed', function(done) {
settingsBehavior(view, done);
});
});
});
});
================================================
FILE: test/spec/apps/settings/show/views/showView.js
================================================
/* global chai, define, describe, before, it */
define([
'underscore',
'jquery',
'collections/configs',
'apps/settings/show/views/showView',
], function(_, $, Configs, View) {
'use strict';
var expect = chai.expect;
describe('Settings view', function() {
var view,
configs;
before(function() {
configs = new Configs();
configs.resetFromJSON(Configs.prototype.configNames);
view = new View({
el: $('
'),
collection : configs,
tab: 'general',
args: {}
});
view.render();
});
it('should exist', function() {
expect(view instanceof View).to.be.equal(true);
});
it('has "content" region', function() {
expect(typeof view.regions).to.be.equal('object');
expect(typeof view.regions.content).to.be.equal('string');
});
it('has "ContentBehavior"', function() {
expect(typeof view.behaviors).to.be.equal('object');
expect(typeof view.behaviors.ContentBehavior).to.be.equal('object');
});
});
});
================================================
FILE: test/spec/apps/settings/test.js
================================================
/* global define */
define([
'spec/apps/settings/show/views/basic',
'spec/apps/settings/show/views/importExport',
'spec/apps/settings/show/views/profiles',
'spec/apps/settings/show/views/shortcuts',
'spec/apps/settings/show/views/showView'
], function() {
'use strict';
});
================================================
FILE: test/spec/backbone.sync.js
================================================
/*jshint expr: true*/
define([
'sinon',
'underscore',
'q',
'backbone.sync',
'models/note',
'collections/notes'
], function(sinon, _, Q, bSync, Model, Collection) {
'use strict';
describe('backbone.sync', function() {
var sandbox,
sync;
before(function() {
sync = _.clone(bSync);
});
beforeEach(function() {
sandbox = sinon.sandbox.create();
});
afterEach(function() {
sandbox.restore();
});
describe('object', function() {
it('is an object', function() {
expect(sync).to.be.an('object');
});
it('stores promises', function() {
expect(sync.promises).to.be.an('object');
});
});
describe('.listenToWorker()', function() {
after(function() {
sync.workerPromise = null;
});
it('resolves the worker promise', function() {
sync.workerPromise = {resolve: sandbox.stub()};
sync.listenToWorker({data: {msg: 'ready'}});
expect(sync.workerPromise.resolve).called;
});
it('resolves a request promise', function(done) {
sync.promises['test-id'] = Q.defer();
sync.promises['test-id'].promise.then(function(data) {
expect(data).to.be.equal('Data');
done();
});
sync.listenToWorker({data: {
msg: 'done', promiseId : 'test-id', data: 'Data'
}});
});
it('rejects a failed WebWorker', function(done) {
sync.promises['test-id'] = Q.defer();
sync.promises['test-id'].promise.fail(function(data) {
expect(data).to.be.equal('Error');
done();
});
sync.listenToWorker({data: {
msg: 'fail', promiseId: 'test-id', data: 'Error'
}});
});
});
describe('.read()', function() {
it('it calls .find() if the 1st argument is model', function() {
sandbox.stub(sync, 'find');
sync.read({id: 'test-id'}, {profile: '1'});
expect(sync.find).calledWith({id: 'test-id'}, {profile: '1'});
});
it('it calls .findAll() if the 1st argument is not model', function() {
sandbox.stub(sync, 'findAll');
sync.read({models: [{id: 'test'}]}, {profile: '1'});
expect(sync.findAll).calledWith({models: [{id: 'test'}]}, {profile: '1'});
});
});
describe('.create()', function() {
beforeEach(function() {
sandbox.stub(sync, 'save');
});
it('calls .save()', function() {
sync.create();
expect(sync.save).called;
});
it('provides all arguments', function() {
sync.create({id: 'test-id-1'}, {profile: '1'});
expect(sync.save).calledWith({id: 'test-id-1'}, {profile: '1'});
});
});
describe('.update()', function() {
beforeEach(function() {
sandbox.stub(sync, 'save');
});
it('calls .save()', function() {
sync.update();
expect(sync.save).called;
});
it('provides all arguments', function() {
sync.update({id: 'test-id-1'}, {profile: '1'});
expect(sync.save).calledWith({id: 'test-id-1'}, {profile: '1'});
});
});
describe('.s4()', function() {
it('returns a string', function() {
expect(sync.s4()).to.be.a('string');
expect(sync.s4()).to.have.property('length').equal(4);
});
});
describe('.guid()', function() {
it('returns a string', function() {
expect(sync.guid()).to.be.a('string');
expect(sync.guid()).to.have.property('length').equal((8 * 4) + 4);
});
it('calls .s4() 8 times', function() {
sandbox.spy(sync, 's4');
sync.guid();
expect(sync.s4).callCount(8);
});
});
describe('.save()', function() {
var model;
beforeEach(function() {
model = new Model();
sandbox.stub(sync, '_emit');
});
it('generates an ID for a model if does not have one', function() {
sandbox.spy(sync, 'guid');
sandbox.spy(model, 'set');
sync.save(model, {});
expect(sync.guid).called;
expect(model.set).calledAfter(sync.guid);
});
it('calls ._emit()', function() {
sync.save(model, {});
expect(sync._emit).called;
});
it('tells ._emit() to trigger `save` message', function() {
sync.save(model, {});
expect(sync._emit).calledWithMatch('save', {});
});
it('provides data for saving', function() {
model.set('title', 'Test title');
sync.save(model, {profile: 'test'});
expect(sync._emit).calledWithMatch('save', {
id : model.id,
data : model.toJSON(),
options : {
profile : 'test',
storeName : model.storeName,
encryptKeys : model.encryptKeys
}
});
});
});
describe('.find()', function() {
var model;
beforeEach(function() {
model = new Model({id: 'test-id'});
sandbox.stub(sync, '_emit').returns(Q.resolve({title: 'Test'}));
});
it('tells .emit() to trigger `find` message', function() {
sync.find(model, {});
expect(sync._emit).calledWithMatch('find');
expect(sync._emit).calledWithMatch('find', {
id : model.id,
options : {
profile : model.profileId,
storeName : model.storeName
}
});
});
it('changes the models attributes to fetched values', function() {
sandbox.spy(model, 'set');
return sync.find(model, {}).then(function() {
expect(model.set).calledWithMatch({title: 'Test'});
});
});
});
describe('.findAll()', function() {
var coll;
beforeEach(function() {
coll = new Collection();
sandbox.stub(sync, '_emit').returns(Q.resolve([{title: 'Test'}]));
});
it('tells ._emit() to trigger `findAll` message', function() {
sync.findAll(coll, {conditions: {t: 1}});
expect(sync._emit).calledWithMatch('findAll', {
options: {
conditions: {t: 1},
storeName : coll.storeName,
profile : coll.profileId
}
});
});
it('adds data to the collection', function() {
sandbox.spy(coll, 'add');
return sync.findAll(coll, {}).then(function() {
expect(coll.add).calledWithMatch([{title: 'Test'}]);
});
});
});
describe('._emit()', function() {
after(function() {
delete sync.worker;
});
beforeEach(function() {
sync.promises = [];
sandbox.stub(sync, 'guid').returns('test-worker-id');
sync.worker = {postMessage: sandbox.stub()};
});
it('it generates an ID for the worker promise', function() {
sync._emit('test', {});
expect(sync.guid).called;
});
it('caches the worker promise', function() {
expect(sync.promises['test-worker-id']).to.be.an('undefined');
sync._emit('test', {});
expect(sync.promises['test-worker-id']).to.be.an('object');
});
it('sends the message to the worker', function() {
sync._emit('test', {data: {id: '1'}});
expect(sync.worker.postMessage).calledWithMatch({
msg : 'test',
data : {data : {id : '1'}},
promiseId : 'test-worker-id'
});
});
it('returns a promise', function() {
expect(sync._emit('test', {})).to.have.property('promiseDispatch');
});
});
});
});
================================================
FILE: test/spec/classes/encryption.js
================================================
/* global define, requirejs, describe, before, beforeEach, after, afterEach, chai, it */
define(function(require) {
'use strict';
var _ = require('underscore'),
Q = require('q'),
Radio = require('backbone.radio'),
sjcl = require('sjcl'),
Encrypt = require('classes/encryption'),
expect = chai.expect,
helpers = require('spec/classes/helpers'),
configs,
encrypt,
keys;
function setKey(callback) {
encrypt.saveSecureKey('1')
.then(function() {
keys = encrypt.keys;
callback();
})
.fail(function(e) {
console.error('Error:', e);
});
}
function requirePromise(name) {
var defer = Q.defer();
requirejs([name], function(res) {
defer.resolve(res);
});
return defer.promise;
}
function encryptModel(name) {
return requirePromise('models/' + name)
.then(function(model) {
model = new model({id: 'random'}); // jshint ignore:line
_.each(model.encryptKeys, function(key) {
model.set(key, 'random ' + key);
});
return encrypt.encryptModel(model)
.then(function() {
_.each(model.encryptKeys, function(key) {
model.set(key, 'not random ' + key);
});
expect(typeof sjcl.json.decode(model.get('encryptedData'))).to.be.equal('object');
return model;
});
});
}
function decryptModel(model) {
return encrypt.decryptModel(model)
.then(function() {
_.each(model.encryptKeys, function(key) {
expect(model.get(key)).to.be.equal('random ' + key);
expect(model.get(key).match(/^\{.*\}$/)).not.to.be.equal(true);
});
});
}
function encryptModels(name) {
return requirePromise('collections/' + name)
.then(function(Collection) {
var collection = new Collection(),
keys = Collection.prototype.model.prototype.encryptKeys;
function createData(i) {
var data = {};
_.each(keys, function(key) {
data[key] = 'Random ' + key + ' ' + i;
});
return data;
}
for (var i = 0; i < 20; i++) {
collection.add(createData(i));
}
return encrypt.encryptModels(collection).thenResolve(collection);
})
.then(function(collection) {
collection.each(function(model) {
expect(typeof sjcl.json.decode(model.get('encryptedData'))).to.be.equal('object');
_.each(keys, function(key) {
model.set(key, '{"string": "string"}');
});
});
return collection;
});
}
function decryptModels(collection) {
return encrypt.decryptModels(collection)
.then(function() {
collection.each(function(model) {
_.each(model.encryptKeys, function(key) {
expect(typeof model.get(key)).to.be.equal('string');
expect(model.get(key).match(/^\{.*\}$/)).not.to.be.equal(true);
});
});
return;
});
}
describe('classes/encryption.js', function() {
beforeEach(function() {
configs = {
'encrypt' : '1',
'encryptPass' : sjcl.hash.sha256.hash('1'),
'encryptSalt' : sjcl.random.randomWords(5, 0),
'encryptIter' : '1000',
'encryptTag' : '64',
'encryptKeySize' : '128',
};
Radio.reply('configs', 'get:object', configs);
encrypt = new Encrypt();
});
after(function() {
Radio.stopReplying('configs', 'get:object');
});
it('is an object', function() {
expect(typeof encrypt).to.be.equal('object');
});
it('uses configs', function() {
expect(typeof encrypt.configs).to.be.equal('object');
expect(_.isEqual(encrypt.configs, configs)).to.be.equal(true);
});
describe('randomize()', function() {
it('exists', function() {
expect(typeof encrypt.randomize).to.be.equal('function');
});
it('returns a string', function() {
expect(typeof encrypt.randomize(5, 0)).to.be.equal('string');
});
it('uses "number" parameter', function() {
expect(encrypt.randomize(1, 0).length).to.be.equal(8);
expect(encrypt.randomize(2, 0).length).to.be.equal(8 * 2);
expect(encrypt.randomize(5, 0).length).to.be.equal(8 * 5);
});
});
describe('changeConfigs()', function() {
var old;
beforeEach(function() {
old = _.clone(configs);
});
it('uses new configs', function() {
configs.encrypt = 0;
encrypt.changeConfigs(configs);
expect(_.isEqual(old, encrypt.configs)).to.be.equal(false);
expect(encrypt.configs.encrypt).to.be.equal(0);
});
it('extends from the previous configs', function() {
configs.encrypt = 1;
configs.encryptIter = 2000;
encrypt.changeConfigs(configs);
expect(encrypt.configs.encrypt).to.be.equal(1);
expect(encrypt.configs.encryptIter).to.be.equal(2000);
expect(_.isEqual(
_.omit(old, 'encrypt', 'encryptIter'),
_.omit(encrypt.configs, 'encrypt', 'encryptIter')
)).to.be.equal(true);
});
});
describe('checkAuth()', function() {
afterEach(function() {
encrypt.configs = configs;
});
it('returns false if the user is not authorized', function() {
encrypt.keys = {};
if (window.sessionStorage) {
window.sessionStorage.clear();
}
expect(encrypt.checkAuth()).to.be.equal(false);
});
it('returns true if encryption is disabled', function() {
encrypt.configs.encrypt = 0;
expect(encrypt.checkAuth()).to.be.equal(true);
});
it('returns true if encryption password is empty', function() {
encrypt.configs.encryptPass = '';
expect(encrypt.checkAuth()).to.be.equal(true);
});
it('returns {isChanged:true} if encryption settings have changed', function() {
encrypt.configs.encryptBackup = {encryptPass: ''};
var res = encrypt.checkAuth();
expect(typeof res).to.be.equal('object');
expect(res.isChanged).to.be.equal(true);
});
});
describe('checkPassword()', function() {
it('returns a promise', function() {
expect(typeof encrypt.checkPassword(1)).to.be.equal('object');
expect(typeof encrypt.checkPassword(1).promiseDispatch).to.be.equal('function');
});
it('returns true if the password is correct', function(done) {
encrypt.configs.encryptPassword = sjcl.hash.sha256.hash('1');
encrypt.checkPassword('1')
.then(function(res) {
expect(res).to.be.equal(true);
done();
})
.fail(function(e) {
console.error('Error:', e);
});
});
it('returns false if the password is incorrect', function(done) {
encrypt.checkPassword('2')
.then(function(res) {
expect(res).to.be.equal(false);
done();
})
.fail(function(e) {
console.error('Error:', e);
});
});
});
describe('saveSecureKey()', function() {
it('returns a promise', function() {
expect(typeof encrypt.saveSecureKey(1)).to.be.equal('object');
expect(typeof encrypt.saveSecureKey(1).promiseDispatch).to.be.equal('function');
});
it('caches keys in memory', function(done) {
encrypt.saveSecureKey('1')
.then(function() {
expect(typeof encrypt.keys).to.be.equal('object');
expect(typeof encrypt.keys.key).to.be.equal('object');
done();
})
.fail(function(e) {
console.error('Error:', e);
});
});
it('saves keys in sessionStorage', function(done) {
if (!window.sessionStorage) {
return;
}
encrypt.saveSecureKey('1')
.then(function() {
return encrypt._getSession();
})
.then(function(keys) {
expect(typeof keys).to.be.equal('object');
expect(typeof keys.key).to.be.equal('object');
done();
})
.fail(function(e) {
console.error('Error:', e);
});
});
it('saves PBKDF2, not plain text password', function(done) {
encrypt.saveSecureKey('1')
.then(function() {
expect(encrypt.keys.key).not.to.be.equal('1');
expect(encrypt.keys.hexKey).not.to.be.equal('1');
done();
})
.fail(function(e) {
console.error('Error:', e);
});
});
});
describe('deleteSecureKey()', function() {
var keys;
before(function() {
keys = {key: 'pseudo key'};
encrypt.keys = keys;
});
it('removes locally cached key', function() {
encrypt.deleteSecureKey();
expect(_.isEqual(encrypt.keys, {})).to.be.equal(true);
expect(!_.isEqual(encrypt.keys, keys)).to.be.equal(true);
});
it('removes keys from the session storage', function() {
if (!window.sessionStorage) {
return;
}
window.sessionStorage.setItem(encrypt._getSessionKey(), keys.key);
expect(window.sessionStorage.getItem(encrypt._getSessionKey())).to.be.equal(keys.key);
encrypt.deleteSecureKey();
expect(window.sessionStorage.getItem(encrypt._getSessionKey())).not.to.be.equal(keys.key);
});
});
describe('encrypt()', function() {
before(function(done) {
setKey(done);
});
beforeEach(function() {
encrypt.keys = keys;
});
it('returns a promise', function() {
expect(typeof encrypt.encrypt(1)).to.be.equal('object');
expect(typeof encrypt.encrypt(1).promiseDispatch).to.be.equal('function');
});
it('resolves with encrypted string', function(done) {
encrypt.encrypt('String')
.then(function(res) {
expect(typeof res).to.be.equal('string');
expect(res).not.to.be.equal('String');
expect(res.match(/^\{.*\}$/).length !== 0).to.be.equal(true);
done();
})
.fail(function(e) {
console.error('Error:', e);
});
});
it('encrypted string is a simple JSON', function(done) {
encrypt.encrypt('Hello world')
.then(function(res) {
res = sjcl.json.decode(res);
expect(typeof res).to.be.equal('object');
expect(_.keys(res).length >= 9).to.be.equal(true);
done();
})
.fail(function(e) {
console.error('Error:', e);
});
});
it('generates random IV every time', function(done) {
var promises = [],
previousIvs = [];
function testIv(res) {
res = sjcl.json.decode(res);
expect(_.indexOf(previousIvs, res.iv) === -1).to.be.equal(true);
previousIvs.push(res.iv);
expect(_.indexOf(previousIvs, res.iv) !== -1).to.be.equal(true);
}
for (var i = 0; i < 100; i++) {
promises.push(
encrypt.encrypt('Hello world ' + i)
.then(testIv)
);
}
Q.all(promises)
.then(function() {
done();
})
.fail(function(e) {
console.error('Error:', e);
});
});
it('JSON data contains everything', function(done) {
encrypt.encrypt('Hello world.')
.then(function(res) {
res = sjcl.json.decode(res);
res = _.pick(res, 'cipher', 'ct', 'iter', 'iv', 'ks', 'mode', 'salt', 'ts', 'v');
expect(_.keys(res).length >= 9).to.be.equal(true);
done();
})
.fail(function(e) {
console.error('Error:', e);
});
});
});
describe('decrypt()', function() {
before(function(done) {
setKey(done);
});
beforeEach(function() {
encrypt.keys = keys;
});
it('returns a promise', function() {
expect(typeof encrypt.decrypt('string')).to.be.equal('object');
expect(typeof encrypt.decrypt('string').promiseDispatch).to.be.equal('function');
});
it('resolves with a decrypted string', function(done) {
var str = 'Encrypted string ' + sjcl.random.randomWords(10, 0),
encrypted;
encrypt.encrypt(str)
.then(function(res) {
encrypted = res;
return encrypt.decrypt(str);
})
.then(function(decrypted) {
expect(typeof decrypted).to.be.equal('string');
expect(decrypted).not.to.be.equal(encrypted);
expect(decrypted).to.be.equal(str);
done();
})
.fail(function(e) {
console.error('Error:', e);
});
});
});
describe('encryptModel()', function() {
before(function(done) {
setKey(done);
});
beforeEach(function() {
encrypt.keys = keys;
});
it('returns a promise', function() {
var model = require('models/note');
model = new model({id: 'random', title: 'Title'}); // jshint ignore:line
expect(typeof encrypt.encryptModel(model)).to.be.equal('object');
expect(typeof encrypt.encryptModel(model).promiseDispatch).to.be.equal('function');
});
it('creates "encryptedData" attribute', function(done) {
var model = require('models/note');
model = new model({id: 'random', title: 'Title'}); // jshint ignore:line
encrypt.encryptModel(model)
.then(function() {
var res = sjcl.json.decode(model.get('encryptedData'));
expect(typeof res).to.be.equal('object');
expect(typeof model.get('encryptedData')).to.be.equal('string');
expect(_.isEqual(
model.get('encryptedData'),
_.pick(model.attributes, model.encryptKeys)
)).to.be.equal(false);
done();
})
.fail(function(e) {
console.error('Error:', e);
});
});
it('resolves with the model', function() {
var model = require('models/note');
model = new model({id: 'random', title: 'Title'}); // jshint ignore:line
encrypt.encryptModel(model)
.then(function(m) {
expect(typeof m).to.be.equal('object');
expect(typeof m.get('encryptedData')).to.be.equal('string');
})
.fail(function(e) {
console.error('Error:', e);
});
});
it('can encrypt all models', function(done) {
var promises = [];
function testModel(m) {
var defer = Q.defer();
requirejs(['models/' + m], function(m) {
m = new m({id: 'random', title: 'Random', name: 'Random'}); // jshint ignore:line
encrypt.encryptModel(m)
.then(function(model) {
var res = sjcl.json.decode(model.get('encryptedData'));
expect(typeof res).to.be.equal('object');
expect(typeof model.get('encryptedData')).to.be.equal('string');
defer.resolve();
});
});
return defer.promise;
}
_.each(['note', 'notebook', 'tag'], function(m) {
promises.push(testModel(m));
});
Q.all(promises)
.then(function() {
done();
})
.fail(function(e) {
console.error('Error:', e);
});
});
});
describe('decryptModel()', function() {
var Model;
before(function(done) {
Model = require('models/note');
setKey(done);
});
beforeEach(function() {
encrypt.keys = keys;
});
it('returns a promise', function() {
var model = new Model({id: 'random', title: 'Title'});
expect(typeof encrypt.decryptModel(model)).to.be.equal('object');
expect(typeof encrypt.decryptModel(model).promiseDispatch).to.be.equal('function');
});
it('calls _decryptModel if a model has "encryptedData" attribute', function(done) {
var fun = encrypt._decryptModel,
model = new Model({id: 'random', title: 'Title', encryptedData: 'data'});
encrypt._decryptModel = function() {
encrypt._decryptModel = fun;
done();
};
encrypt.decryptModel(model);
});
it('calls _decryptModelKeys if a model does not have "encryptedData" attribute', function(done) {
var fun = encrypt._decryptModelKeys,
model = new Model({id: 'random', title: 'Title'});
encrypt._decryptModelKeys = function() {
encrypt._decryptModelKeys = fun;
done();
};
encrypt.decryptModel(model);
});
it('decrypts a model\'s data', function(done) {
var promises = [];
_.each(['note', 'notebook', 'tag'], function(model) {
promises.push(
encryptModel(model)
.then(decryptModel)
);
});
Q.all(promises)
.then(function() {
done();
})
.fail(function(e) {
console.error('Error:', e);
});
});
});
describe('decryptModel() decrypts a model from 0.6.x', function() {
before(function(done) {
// We used to save salt as a string
configs.encryptSalt = configs.encryptSalt.toString();
encrypt.configs.encryptSalt = configs.encryptSalt;
setKey(done);
});
beforeEach(function() {
encrypt.keys = keys;
});
it('can decrypt models', function(done) {
var promises = [];
function encrypt06(Model) {
var model = new Model({id: 'random'});
_.each(model.encryptKeys, function(key) {
var data = helpers.encrypt062(
'random ' + key,
encrypt.keys.key,
configs
);
model.set(key, data);
});
return model;
}
_.each(['note', 'notebook', 'tag'], function(model) {
promises.push(
requirePromise('models/' + model)
.then(encrypt06)
.then(decryptModel)
);
});
Q.all(promises)
.then(function() { done(); })
.fail(function(e) {
console.error('Error:', e);
});
});
});
describe('decryptModel() decrypts a model from 0.7.0', function() {
var salt;
before(function(done) {
// We used to save encryption salt in HEX format in 0.7.0
salt = sjcl.codec.hex.fromBits(sjcl.random.randomWords(4, 0));
salt = encrypt.sjcl.toUpperCase(salt);
encrypt.configs.encryptSalt = salt;
// Generate new PBKDF2
setKey(done);
});
beforeEach(function() {
encrypt.keys = keys;
encrypt.configs.encryptSalt = salt;
});
it('can decrypt a model', function(done) {
var promises = [];
function encrypt07(Model) {
var model = new Model({id: 'random'});
_.each(model.encryptKeys, function(key) {
var data = helpers.encrypt070(
'random ' + key,
encrypt.configs.encryptSalt,
encrypt.sjcl.toUpperCase(encrypt.keys.hexKey),
configs
);
model.set(key, data);
});
return model;
}
_.each(['note', 'notebook', 'tag'], function(model) {
promises.push(
requirePromise('models/' + model)
.then(encrypt07)
.then(decryptModel)
);
});
Q.all(promises)
.then(function() { done(); })
.fail(function(e) {
console.error('Error:', e);
});
});
});
describe('encryptModels()', function() {
var Notes,
notes;
before(function(done) {
Notes = require('collections/notes');
setKey(done);
});
beforeEach(function() {
encrypt.keys = keys;
notes = new Notes();
notes.add({id: Math.random()});
});
it('returns a promise', function() {
expect(typeof encrypt.encryptModels(notes)).to.be.equal('object');
expect(typeof encrypt.encryptModels(notes).promiseDispatch).to.be.equal('function');
});
it('resolves a promise if a collection is empty', function(done) {
notes.reset([]);
expect(notes.length).to.be.equal(0);
encrypt.encryptModels(notes)
.then(function() {
done();
});
});
it('resolves a promise if encryption is disabled', function(done) {
encrypt.configs.encrypt = 0;
expect(notes.length).not.to.be.equal(0);
encrypt.encryptModels(notes)
.then(function() {
done();
});
});
it('resolves a promise if there are not any keys', function(done) {
encrypt.keys = {};
expect(notes.length).not.to.be.equal(0);
expect(Number(encrypt.configs.encrypt)).not.to.be.equal(0);
encrypt.encryptModels(notes)
.then(function() {
done();
});
});
it('encrypts every model in a collection', function(done) {
var promises = [];
_.each(['notes', 'notebooks', 'tags'], function(name) {
promises.push(
encryptModels(name)
);
});
Q.all(promises)
.then(function() {
done();
})
.fail(function(e) {
console.error('Error:', e);
});
});
});
describe('decryptModels()', function() {
var Notes,
notes;
before(function(done) {
Notes = require('collections/notes');
setKey(done);
});
beforeEach(function() {
encrypt.keys = keys;
notes = new Notes();
notes.add({id: Math.random()});
});
it('returns a promise', function() {
expect(typeof encrypt.decryptModels(notes)).to.be.equal('object');
expect(typeof encrypt.decryptModels(notes).promiseDispatch).to.be.equal('function');
});
it('resolves a promise if a collection is empty', function(done) {
notes.reset([]);
expect(notes.length).to.be.equal(0);
encrypt.decryptModels(notes)
.then(function() {
done();
});
});
it('resolves a promise if encryption is disabled', function(done) {
encrypt.configs.encrypt = 0;
expect(notes.length).not.to.be.equal(0);
encrypt.decryptModels(notes)
.then(function() {
encrypt.configs.encrypt = 1;
done();
});
});
it('resolves a promise if PBKDF2 is empty', function(done) {
encrypt.keys = {};
encrypt.decryptModels(notes)
.then(function() {
done();
});
});
it('decrypts models in a collection', function(done) {
var promises = [];
_.each(['notes', 'notebooks', 'tags'], function(name) {
promises.push(
encryptModels(name)
.then(decryptModels)
);
});
Q.all(promises)
.then(function() {
done();
})
.fail(function(e) {
console.error('Error:', e);
});
});
});
});
});
================================================
FILE: test/spec/classes/helpers.js
================================================
/* global define */
define(function(require) {
'use strict';
var sjcl = require('sjcl'),
_ = require('underscore');
return {
encrypt062: function(str, pass, configs) {
var p = {
iter : configs.encryptIter,
ts : Number(configs.encryptTag),
ks : Number(configs.encryptKeySize),
// Random initialization vector every time
iv : sjcl.random.randomWords(4, 0)
};
return sjcl.encrypt(pass.toString(), str, p);
},
encrypt070: function(str, salt, pass, configs) {
var p = {
mode : 'ccm',
iter : Number(configs.encryptIter),
ts : Number(configs.encryptTag),
ks : Number(configs.encryptKeySize),
salt : salt,
v : 1,
adata : '',
cipher : 'aes',
// Random initialization vector every time
iv : sjcl.random.randomWords(4, 0)
};
str = sjcl.encrypt(pass.toString(), str, p);
str = _.pick(JSON.parse(str), 'ct', 'iv');
return JSON.stringify(str);
},
};
});
================================================
FILE: test/spec/classes/sjcl.js
================================================
/* global define, chai, describe, it, before, after, afterEach, beforeEach */
define(function(require) {
'use strict';
var Encrypt = require('classes/sjcl'),
sjcl = require('sjcl'),
_ = require('underscore'),
expect = chai.expect,
helpers = require('spec/classes/helpers'),
configs;
configs = {
'encrypt' : '1',
'encryptPass' : sjcl.hash.sha256.hash('1'),
'encryptIter' : '1000',
'encryptTag' : '64',
'encryptKeySize' : '128',
};
describe('classes/sjcl', function() {
var encrypt;
beforeEach(function() {
var salt = sjcl.random.randomWords(4, 0);
configs.encryptSalt = sjcl.codec.hex.fromBits(salt);
expect(typeof configs.encryptSalt).to.be.equal('string');
});
before(function() {
encrypt = new Encrypt();
});
after(function() {
});
it('hex() can convert string to HEX format', function() {
var rand = sjcl.random.randomWords(4, 0),
hex = encrypt.hex(rand);
expect(typeof rand).to.be.equal('object');
expect(typeof hex).to.be.equal('string');
expect(rand).not.to.be.equal(hex);
});
it('toUpperCase() can convert string to uppercase', function() {
var rand = encrypt.hex(sjcl.random.randomWords(4, 0)),
upper = encrypt.toUpperCase(rand);
expect(/^[A-F0-9 ]*$/.test(upper)).to.be.equal(true);
expect(/^[A-F0-9 ]*$/.test(rand)).to.be.equal(false);
});
it('sha256() can convert a string to SHA256 hash', function(done) {
encrypt.sha256('hello')
.then(function(hash) {
expect(hash).to.be.not.equal('hello');
expect(typeof hash).to.be.equal('object');
expect(hash.length).to.be.equal(8);
done();
});
});
it('getConfigs() returns encryption configs', function() {
var c = encrypt.getConfigs(configs);
expect(typeof c).to.be.equal('object');
expect(c.mode).to.be.equal('ccm');
expect(c.iter).to.be.equal(Number(configs.encryptIter));
expect(c.ts).to.be.equal(Number(configs.encryptTag));
expect(c.ks).to.be.equal(Number(configs.encryptKeySize));
expect(c.salt).to.be.equal(configs.encryptSalt);
expect(c.v).to.be.equal(1);
expect(c.adata).to.be.equal('');
expect(c.cipher).to.be.equal('aes');
});
it('deriveKey() generates PBKDF2', function() {
var p = encrypt.deriveKey({
password: 'my secret key #1',
configs : configs
});
expect(typeof p).to.be.equal('object');
expect(typeof p.key).to.be.equal('object');
expect(p.key.length).to.be.equal(configs.encryptKeySize / 32);
expect(typeof p.hexKey).to.be.equal('string');
});
it('deriveKey() generated key is equal to the result returned by sjcl.misc.pbkdf2()', function() {
var p = encrypt.deriveKey({password:'my secret key #1', configs: configs}),
p2 = sjcl.misc.pbkdf2('my secret key #1', configs.encryptSalt, Number(configs.encryptIter), Number(configs.encryptKeySize));
expect(_.isEqual(p.key, p2)).to.be.equal(true);
expect(p.hexKey).to.be.equal(sjcl.codec.hex.fromBits(p2));
});
describe('Can decrypt data from the previous versions', function() {
var str,
encrypted,
decrypted;
beforeEach(function() {
str = 'My secret note ' + encrypt.hex(sjcl.random.randomWords(4, 0));
for (var i = 0; i < 20; i++) {
str += '\r\n ' + encrypt.hex(sjcl.random.randomWords(4, 0));
}
});
afterEach(function() {
expect(encrypted).to.be.not.equal(str);
expect(encrypted).to.be.not.equal(decrypted);
expect(typeof JSON.parse(encrypted)).to.be.equal('object');
expect(str).to.be.equal(decrypted);
expect(str.length).to.be.equal(decrypted.length);
});
it('can decrypt string from v0.6.2', function() {
var p = encrypt.deriveKey({password:'my secret key #1', configs: configs});
encrypt.options = p;
encrypted = helpers.encrypt062(str, p.key, configs);
decrypted = encrypt.decryptLegacy({string: encrypted, configs: configs});
expect(_.size(JSON.parse(encrypted))).to.be.equal(10);
});
it('can decrypt string from v0.7.0', function() {
var salt = encrypt.toUpperCase(encrypt.hex(sjcl.random.randomWords(4, 0))),
p;
configs.encryptSalt = salt;
p = encrypt.deriveKey({password: 'my secret key #1', configs: configs});
encrypt.keys = p;
encrypted = helpers.encrypt070(str, salt, encrypt.toUpperCase(p.hexKey), configs);
decrypted = encrypt.decryptLegacy({string: encrypted, configs: configs});
expect(_.size(JSON.parse(encrypted))).to.be.equal(2);
});
});
describe('Encryption', function() {
var str,
encrypted;
before(function() {
var p = encrypt.deriveKey({password: 'my secret key #1', configs: configs});
encrypt.keys = p;
str = 'My string';
});
it('can encrypt', function() {
encrypted = encrypt.encrypt({
string : str,
configs : configs,
iv : sjcl.random.randomWords(4, 0)
});
expect(encrypted).not.to.be.equal(str);
expect(typeof encrypted).to.be.equal('string');
expect(_.size(JSON.parse(encrypted))).to.be.equal(10);
});
it('can decrypt', function() {
var decrypted = encrypt.decrypt({string: encrypted, configs: configs});
expect(decrypted).not.to.be.equal(encrypted);
expect(typeof decrypted).to.be.equal('string');
expect(decrypted.length).to.be.equal(str.length);
expect(decrypted).to.be.equal(str);
});
it('returns an original string if it was not encrypted', function() {
var decrypted = encrypt.decrypt({string: 'Hello', configs: configs});
expect(decrypted).to.be.equal('Hello');
});
});
});
});
================================================
FILE: test/spec/collections/configs.js
================================================
/*jshint expr: true*/
/* global expect, define, describe, beforeEach, before, after, afterEach, it */
define([
'sinon',
'q',
'underscore',
'backbone.radio',
'collections/configs'
], function(sinon, Q, _, Radio, Collection) {
'use strict';
describe('collections/configs', function() {
var collection;
beforeEach(function() {
collection = new Collection();
});
describe('.hasNewConfigs()', function() {
it('.configNames length is not equal to .length', function() {
expect(collection.hasNewConfigs()).to.be.equal(true);
});
it('.configNames length is equal to .length', function() {
collection.configNames = {};
expect(collection.hasNewConfigs()).to.be.equal(false);
});
});
it('.changeDB()', function() {
collection.changeDB('testP');
expect(collection.profileId).to.be.equal('testP');
expect(collection.model.prototype.profileId).to.be.equal('testP');
collection.model.prototype.profileId = 'notes-db';
});
describe('.migrateFromLocal()', function() {
before(function() {
localStorage.setItem(
'vimarkable.configs-testingKey',
JSON.stringify({name: 'testingKey', value: 11})
);
});
after(function() {
localStorage.removeItem('vimarkable.configs-testingKey');
});
it('changes .configNames[key] if it exists in localStorage', function() {
collection.configNames.testingKey = 10;
collection.migrateFromLocal();
expect(collection.configNames.testingKey).not.to.be.equal(10);
expect(collection.configNames.testingKey).to.be.equal(11);
});
it('does nothing if it does not exist in localStorage', function() {
collection.configNames.testingKey2 = 10;
collection.migrateFromLocal();
expect(collection.configNames.testingKey2).to.be.equal(10);
});
});
describe('.createDefault()', function() {
var sandbox;
beforeEach(function() {
sandbox = sinon.sandbox.create();
sandbox.stub(collection.model.prototype, 'save', function() {
return Q.resolve();
});
});
afterEach(function() {
sandbox.restore();
});
it('returns a promise', function() {
expect(collection.createDefault()).to.be.an('object');
expect(collection.createDefault()).to.have.property('promiseDispatch');
});
it('creates new models', function() {
collection.createDefault();
expect(collection.model.prototype.save).to.have.been
.callCount(_.keys(collection.configNames).length);
});
});
describe('.getConfigs()', function() {
it('returns object', function() {
expect(collection.getConfigs()).to.be.an('object');
});
it('converts current models into key=value', function() {
collection.add([{name: 'test', value: 'test'}, {name: 'test2', value: 'test2'}]);
var configs = collection.getConfigs();
expect(_.keys(configs).length).to.be.equal(collection.length + 1);
collection.each(function(model) {
expect(model.get('value')).to.be.equal(configs[model.get('name')]);
});
});
it('parses .appProfiles', function() {
collection.add({name: 'appProfiles', value: JSON.stringify(['notes-db'])});
expect(collection.getConfigs().appProfiles).to.be.an('array');
});
});
describe('.getDefault()', function() {
it('returns a model', function() {
expect(collection.getDefault('firstStart')).to.be.an('object');
expect(collection.getDefault('firstStart').get('name')).to.be.equal('firstStart');
});
it('the value of the model is equal to default\'s', function() {
expect(collection.getDefault('firstStart').get('value'))
.to.be.equal(collection.configNames.firstStart);
});
});
describe('.resetFromJSON()', function() {
it('creates models from an object', function() {
collection.resetFromJSON({test: '1', test2: '2'});
expect(collection.length).to.be.equal(2);
expect(collection.get('test').get('value')).to.be.equal('1');
expect(collection.get('test2').get('value')).to.be.equal('2');
});
it('resets the controller', function(done) {
collection.once('reset', function() { done(); });
collection.resetFromJSON({test: '1', test2: '2'});
});
});
describe('.shortcuts()', function() {
beforeEach(function() {
collection.resetFromJSON(collection.configNames);
});
it('returns an array', function() {
expect(collection.shortcuts()).to.be.an('array');
expect(collection.shortcuts().length).to.be.equal(14);
});
it('filters the collection', function() {
expect(collection.shortcuts().length).to.be.equal(14);
expect(collection.shortcuts().length).not.to.be.equal(collection.length);
});
});
it('.appShortcuts()', function() {
collection.resetFromJSON(collection.configNames);
expect(collection.appShortcuts()).to.be.an('array');
expect(collection.appShortcuts().length).to.be.equal(3);
});
describe('.filterName()', function() {
beforeEach(function() {
collection.resetFromJSON(collection.configNames);
});
it('searches a string in models\' names', function() {
expect(collection.filterName('action')).to.be.an('array');
expect(collection.filterName('action').length).to.be.equal(4);
expect(collection.filterName('navigate').length).to.be.equal(2);
expect(collection.filterName('jump').length).to.be.equal(5);
});
it('performs case sensitive search', function() {
expect(collection.filterName('edit').length).to.be.equal(1);
});
});
});
});
================================================
FILE: test/spec/collections/modules/configs.js
================================================
/*jshint expr: true*/
/* global expect, define, describe, before, beforeEach, after, afterEach, it */
define([
'sinon',
'q',
'underscore',
'sjcl',
'backbone.radio',
'collections/modules/configs',
'collections/modules/module',
], function(sinon, Q, _, sjcl, Radio, colModule, Module) {
'use strict';
describe('collections/modules/configs', function() {
var col,
defReplies;
before(function() {
// Default replies
defReplies = Module.prototype.reply();
});
beforeEach(function() {
col = _.clone(colModule);
col.collection = new col.Collection();
col.collection.resetFromJSON(col.collection.configNames);
});
after(function() {
col.destroy();
});
describe('.initialize()', function() {
it('has .Collection', function() {
expect(col.Collection.prototype.storeName).to.be.equal('configs');
});
it('listens on `.col.storeName` channel', function() {
expect(col.vent).to.be.an('object');
expect(col.vent.channelName).to.be.equal(col.Collection.prototype.storeName);
});
it('listens to requests', function() {
_.each(_.union(defReplies, col.reply()), function(reply) {
expect(col.vent._requests[reply]).to.be.an('object');
});
});
it('listens to events', function() {
expect(col.vent._events['destroy:collection']).to.be.an('array');
});
});
describe('.reply()', function() {
it('returns replies', function() {
var mReplies = col.reply();
expect(mReplies).to.be.an('object');
expect(_.keys(mReplies).length).to.be.equal(8);
});
});
describe('.resetEncrypt()', function() {
it('empties the value of `encryptBackup` model', function() {
col.collection.get('encryptBackup')
.set('value', {password: '1'});
col.resetEncrypt();
expect(_.keys(col.collection.get('encryptBackup').get('value')).length)
.to.be.equal(0);
});
});
it('.createProfile()', function() {
var model = col.collection.get('appProfiles'),
stub = sinon.stub(model, 'createProfile');
col.createProfile(model, 'test');
expect(stub).calledWith('test');
});
describe('.removeProfile()', function() {
var model;
beforeEach(function() {
model = col.collection.get('appProfiles');
sinon.stub(model, 'removeProfile').returns(Q.resolve());
});
afterEach(function() {
model.removeProfile.restore();
});
it('returns a promise', function(done) {
var res = col.removeProfile(model, 'test---');
expect(res).to.have.property('promiseDispatch');
res.should.be.fulfilled.and.notify(done);
});
it('triggers `removed:profile` event', function(done) {
col.vent.once('removed:profile', function(name) {
expect(name).to.be.equal('test--1');
done();
});
col.removeProfile(model, 'test--1');
});
});
describe('.saveModel()', function() {
var model;
beforeEach(function() {
model = new col.collection.model({
value: 'hello', name: 'encryptPass'
});
});
it('returns a promise', function() {
expect(col.saveModel(model, {})).to.have.property('promiseDispatch');
});
it('checks if the model stores password', function() {
var spy = sinon.spy(model, 'isPassword');
col.saveModel(model, {});
expect(spy).called;
});
it('hashes the password', function(done) {
Radio.replyOnce('encrypt', 'sha256', function(pass) {
expect(pass).to.be.equal('1');
done();
});
col.saveModel(model, {value: '1'});
});
});
describe('.saveObjects()', function() {
beforeEach(function() {
sinon.stub(col, 'saveObject');
});
it('returns a promise', function() {
expect(col.saveObjects({})).to.have.property('promiseDispatch');
});
it(
'saves current encryption configs to a profile\'s backup if' +
'`useDefaultConfigs` has changed',
function() {
var spy = sinon.spy(col, '_backupEncrypt');
col.saveObjects({useDefaultConfigs: true}, {profileId: 'testBackup'});
expect(spy).calledWith('testBackup');
}
);
it('backs up encryption configs', function() {
var spy = sinon.spy(col, '_backupEncryption');
col.saveObjects({key: '1', key2: '2'}, {profileId: 'testBackup'});
expect(spy).calledWith({key: '1', key2: '2'});
});
it('saves all configs', function() {
col.saveObjects({key: {name: 'key'}, key2: {name: 'key2'}}, {profileId: 'testDb'});
expect(col.saveObject).callCount(2);
});
it('triggers `changed` event on `configs` channel', function(done) {
Radio.once('configs', 'changed', function() { done(); });
col.saveObjects({key: {name: 'key'}, key2: {name: 'key2'}}, {profileId: 'testDb'});
});
});
describe('.saveObject()', function() {
it('returns a promise', function() {
expect(col.saveObject({})).to.have.property('promiseDispatch');
});
it('calls .getModel()', function() {
var stub = sinon.stub(col, 'getModel').returns(new Q());
col.saveObject({name: 'test'});
expect(stub).calledWith({name: 'test'});
});
});
describe('.getConfig()', function() {
it('returns the value of a config', function() {
expect(col.getConfig('firstStart')).to.be.equal('1');
});
it('returns the second argument if a config was not found', function() {
expect(col.getConfig('config404Test', '404')).to.be.equal('404');
});
});
it('.getObject()', function() {
expect(col.getObject()).to.deep.equal(col.collection.getConfigs());
});
describe('.getModel()', function() {
it('can find by a config name', function() {
return col.getModel('appVersion').should.eventually.be.an('object');
});
});
describe('.getAll()', function() {
beforeEach(function() {
});
it('returns cached collection if it exists', function() {
expect(col.collection).to.be.an('object');
expect(col.getAll()).to.have.property('promiseDispatch');
return col.getAll().should.eventually.be.equal(col.collection);
});
});
describe('.useDefaultConfigs()', function() {
var model;
beforeEach(function() {
col.getModel = function() { return Q.resolve(model); };
});
it('returns null if `useDefaultConfigs` model doesn\'t exist', function() {
return col.useDefaultConfigs('testDB').should.eventually.be.a('null');
});
it('returns null if the value is `1`', function() {
model = {get: function() { return 1; }};
return col.useDefaultConfigs('testDB').should.eventually.be.a('null');
});
it('returns the profile name otherwise', function() {
model = {get: function() { return 0; }};
return col.useDefaultConfigs('testDB').should.eventually.be.equal('testDB');
});
});
describe('.getProfiles()', function() {
it('returns backup\'s profileId if current profile isn\'t default', function() {
col.collection.profileId = 'testDB1';
col.collection.get('encryptBackup').profileId = 'testDB1';
return col.getProfiles().should.eventually.contain('testDB1');
});
it('returns backup\'s profileId if backup uses none default profile', function() {
col.collection.get('encryptBackup').profileId = 'testDB1';
return col.getProfiles().should.eventually.contain('testDB1');
});
it('returns all profiles which use configs from default profile', function(done) {
col.getModel = function() { return Q.resolve(); };
col._getDefaultProfiles = done;
col.getProfiles();
});
});
describe('._getDefaultProfiles()', function() {
var model;
before(function() {
model = new col.collection.model({
name : 'appProfiles',
value: JSON.stringify(['notes-db', 'testDb', 'noDefault'])
});
});
beforeEach(function() {
sinon.stub(col, 'getModel', function(profile) {
var m = new col.collection.model();
m.set('value', (profile.profile !== 'noDefault' ? 1 : 0));
m.profileId = profile.profile;
return Q.resolve(m);
});
});
afterEach(function() {
col.getModel.reset();
});
it('returns all profiles which use configs from default profile', function() {
return col._getDefaultProfiles(model).should.eventually
.to.include.members(['notes-db', 'testDb'])
.and.not.to.include.members(['noDefault']);
});
});
describe('._checkBackup()', function() {
var model;
beforeEach(function() {
sinon.stub(col, 'getModel', function(options) {
expect(options.name).to.be.equal('encryptBackup');
if (model) {
model.profileId = options.profile;
}
return Q.resolve(model);
});
});
afterEach(function() {
col.getModel.reset();
});
it('does nothing if it is the default profile', function() {
return col._checkBackup('testDb').should.eventually.be.an('undefined');
});
it('does nothing if backup was not found', function() {
return col._checkBackup(col.defaultDB).should.eventually.be.an('undefined');
});
it('does nothing if backup\'s is not value empty', function() {
model = new col.collection.model({value: {key: 'value'}});
return col._checkBackup('testDb').should.eventually.be.an('undefined');
});
it('returns current profile\'s encryption backup', function() {
model = new col.collection.model({value: {}});
return col._checkBackup('testDb').should.eventually
.be.an('object')
.and.have.property('profileId').equal('testDb');
});
});
describe('._createDefault()', function() {
it('does nothing if there aren\'t new configs', function() {
expect(col.collection.hasNewConfigs()).to.be.equal(false);
return col._createDefault().should.eventually.be.equal(col.collection);
});
it('triggers `collection:empty` event', function(done) {
col.collection.reset([]);
col.vent.once('collection:empty', done);
col._createDefault();
});
it('will try to migrate from localStorage', function() {
var stub = sinon.stub(col.collection, 'migrateFromLocal');
col.collection.reset([]);
col._createDefault();
expect(stub).to.be.called;
});
it('will create configs with default values', function(done) {
col.collection.reset([]);
col.collection.createDefault = done;
col._createDefault();
});
it('triggers `reset:all` event on collection', function(done) {
col.collection.reset([]);
col.collection.createDefault = function() {};
col.collection.once('reset:all', done);
col._createDefault();
});
});
describe('._getEncryption()', function() {
it('returns empty array if encryption config was not provided', function() {
expect(col._getEncryption({})).to.be.an('array');
expect(col._getEncryption({})).to.have.length.below(1);
});
it('returns empty array if encryption is disabled', function() {
expect(Number(col.getConfig('encrypt'))).to.be.equal(0);
expect(col._getEncryption({encrypt: {value: 0}})).to.be.an('array');
expect(col._getEncryption({encrypt: {value: 0}})).to.have.length.below(1);
});
it('disables encryption if password was not provided', function() {
var data = {encrypt: {value: 1}};
expect(col.getConfig('encryptPass')).to.have.length.below(1);
expect(col._getEncryption(data)).to.be.an('array');
expect(col._getEncryption(data)).to.have.length.below(1);
expect(data.encrypt.value).to.be.equal('0');
});
it('disables encryption if the provided password is empty', function() {
var data = {encrypt: {value: 1}, encryptPass: {value: ''}};
expect(col._getEncryption(data)).to.have.length.below(1);
expect(data.encrypt.value).to.be.equal('0');
});
it('returns only encryption configs which changed', function() {
var data = {
encrypt : {value : 1 , name : 'encrypt'},
encryptPass : {value : '1', name : 'encryptPass'},
appLang : {value : 'e', name : 'appLang'}
};
expect(col._getEncryption(data)).to.have.length.within(2, 2);
expect(_.findWhere(col._getEncryption(data), {name: 'encrypt'})).to.have.property('value').equal(1);
expect(_.findWhere(col._getEncryption(data), {name: 'encryptPass'})).to.have.property('value').equal('1');
});
});
describe('._checkPassChanged()', function() {
it('returns true if name is not equal `encryptPass`', function() {
expect(col._checkPassChanged({name: 'encrypt'})).to.be.equal(true);
});
it('returns false if new and old passwords are equal', function() {
col.collection.get('encryptPass').set('value', '1');
expect(col._checkPassChanged({name: 'encryptPass', value: '1'})).to.be.equal(false);
});
it('returns false if password hashes are not different', function() {
col.collection.get('encryptPass').set('value', sjcl.hash.sha256.hash('1'));
expect(col._checkPassChanged({name: 'encryptPass', value: '1'})).to.be.equal(false);
});
it('returns true if password hashes are different', function() {
col.collection.get('encryptPass').set('value', sjcl.hash.sha256.hash('2'));
expect(col._checkPassChanged({name: 'encryptPass', value: '1'})).to.be.equal(true);
});
});
describe('._backupEncrypt()', function() {
it('backs up all current encryption configs', function() {
sinon.stub(col, 'saveModel', function(model, data) {
model.set(data);
return model;
});
return col._backupEncrypt('test')
.then(function(model) {
expect(model.profileId).to.be.equal('test');
expect(model.get('value'))
.to.deep.equal(_.pick(col.collection.getConfigs(), col.encryptionKeys));
});
});
});
describe('._backupEncryption()', function() {
it('does nothing if encryption configs haven\'t changed', function() {
expect(col._backupEncryption({})).to.be.an('undefined');
});
it('does nothing if encryption backup is not empty', function() {
var data = {encrypt: {value: 1, name: 'encrypt'}};
col.collection.get('encryptBackup').set('value', {key: '1'});
col.collection.get('encryptPass').set('value', '1');
expect(col._backupEncryption(data)).to.be.an('undefined');
expect(data.encrypt.value).to.be.equal(1);
});
it('saves changed encryption configs to backup', function() {
var data = {
encrypt : {value : 1 , name : 'encrypt'},
encryptPass : {value : '1', name : 'encryptPass'},
};
expect(col._backupEncryption(data)).to.be.an('object');
expect(col._backupEncryption(data).encryptBackup)
.to.have.property('value')
.deep.equal(_.pick(col.getObject(), ['encrypt', 'encryptPass']));
});
it('does not back up encryption password if it has not changed', function() {
var data = {
encrypt : {value : 1 , name : 'encrypt'},
encryptPass : {value : '1', name : 'encryptPass'},
};
col.collection.get('encryptPass').set('value', '1');
expect(col._backupEncryption(data).encryptBackup)
.to.have.property('value')
.deep.equal(_.pick(col.getObject(), ['encrypt']));
});
});
});
});
================================================
FILE: test/spec/collections/modules/files.js
================================================
/*jshint expr: true*/
/* global expect, define, describe, before, beforeEach, after, afterEach, it */
define([
'sinon',
'q',
'underscore',
'backbone.radio',
'collections/modules/files',
'collections/modules/module',
], function(sinon, Q, _, Radio, ColModule, Module) {
'use strict';
describe('collections/modules/files', function() {
var col,
sandbox;
before(function() {
col = new ColModule();
});
after(function() {
col.destroy();
});
beforeEach(function() {
sandbox = sinon.sandbox.create();
sandbox.stub(col, 'save').returns(Q.resolve());
});
afterEach(function() {
sandbox.restore();
});
describe('.initialize()', function() {
it('has .Collection', function() {
expect(col.Collection.prototype.storeName).to.be.equal('files');
});
it('listens on `.Collection.storeName` channel', function() {
expect(col.vent).to.be.an('object');
expect(col.vent.channelName).to.be.equal(col.Collection.prototype.storeName);
});
it('listens to requests', function() {
_.each(_.union(Module.prototype.reply(), col.reply()), function(reply) {
expect(col.vent._requests[reply]).to.be.an('object');
});
});
});
describe('.getFiles()', function() {
var ids;
before(function() {
ids = [];
for (var i = 0; i < 10; i++) {
ids.push('id' + i);
}
});
beforeEach(function() {
sandbox.stub(col, 'getModel').returns(Q.resolve());
});
it('will fetch all models with the provided IDs', function() {
return col.getFiles(ids, {profile: 'test'}).then(function() {
expect(col.getModel).callCount(ids.length);
expect(col.getModel).calledWithMatch({profile: 'test'});
});
});
});
describe('.saveAll()', function() {
beforeEach(function() {
sandbox.stub(col, 'saveModel').returns(Q.resolve());
});
it('saves all models', function() {
var data = [{id: 1}, {id: 2}];
return col.saveAll(data, {profile: 'test'}).then(function() {
expect(col.saveModel).callCount(data.length);
expect(col.saveModel).calledWithMatch({profileId: 'test'});
});
});
});
});
});
================================================
FILE: test/spec/collections/modules/module.js
================================================
/*jshint expr: true*/
/* global expect, define, describe, before, beforeEach, after, afterEach, it */
define([
'sinon',
'q',
'underscore',
'backbone.radio',
'collections/modules/module',
'collections/notes'
], function(sinon, Q, _, Radio, Module, Collection) {
'use strict';
describe('collections/modules/module', function() {
var col,
models,
collection,
sandbox,
itStub;
before(function() {
Module.prototype.Collection = Collection;
col = new Module();
models = [];
for (var i = 0; i < 10; i++) {
models.push({id: 'id-' + i});
}
collection = new col.Collection(models);
col.collection = collection.clone();
});
after(function() {
col.destroy();
});
beforeEach(function() {
sandbox = sinon.sandbox.create();
itStub = sandbox.stub();
});
afterEach(function() {
sandbox.restore();
});
describe('.initialize()', function() {
it('the default database is `notes-db`', function() {
expect(col.defaultDB).to.be.equal('notes-db');
});
it('listens on `.Collection.storeName` channel', function() {
expect(col.vent).to.be.an('object');
expect(col.vent.channelName).to.be.equal(col.Collection.prototype.storeName);
});
it('listens to requests', function() {
var keys = _.keys(col.vent._requests);
_.each(col.reply(), function(reply, key) {
expect(keys).to.contain(key);
});
});
it('listens to `destroy:collection` event', function() {
expect(_.keys(col.vent._events)).to.be.contain('destroy:collection');
});
});
describe('.changeDatabase', function() {
after(function() {
col.changeDatabase(col.defaultDB);
});
it('changes profileId of a collection', function() {
expect(col.changeDatabase({profile: 'test'}).prototype.profileId).to.be.equal('test');
});
it('changes profileId of a model', function() {
expect(col.changeDatabase({profile: 'test1'}).prototype.model.prototype.profileId)
.to.be.equal('test1');
});
});
describe('.onReset()', function() {
beforeEach(function() {
col.collection = collection.clone();
});
it('resets the collection', function(done) {
col.collection.once('reset', function() { done(); });
col.onReset();
});
it('calls collection.removeEvents() if it exists', function() {
var stub = sandbox.stub(col.collection, 'removeEvents');
col.onReset();
expect(stub).called;
});
});
describe('.save()', function() {
var model;
before(function() {
model = collection.at(0).clone();
});
beforeEach(function() {
sandbox.stub(col, 'encryptModel').returns(model);
sandbox.stub(model, 'save');
});
it('triggers `invalid` event if validation fails', function() {
model.once('invalid', itStub);
col.save(model, {title: ''});
expect(itStub).calledWithMatch(model, ['title']);
});
it('rejects the promise if validation fails', function() {
return col.save(model, {title: ''}).should.eventually.be.rejected;
});
it('calls model.setEscape() if it exists', function() {
sandbox.spy(model, 'setEscape');
col.save(model, {title: 'Hello', content: 'World'});
expect(model.setEscape).calledWith({title: 'Hello', content: 'World'});
});
it('encrypts the model before saving', function() {
col.save(model, {title: 'Hello', content: 'World'});
expect(col.encryptModel).called;
});
it('saves the model', function() {
return col.save(model, {title: 'hello'}).then(function() {
expect(model.save).calledWithMatch({title: 'hello'}, {validate: false});
});
});
});
describe('.saveModel()', function() {
var model;
before(function() {
model = new col.Collection.prototype.model();
});
beforeEach(function() {
sandbox.stub(col, 'save', function(m, data) {
m.set(data);
return Q.resolve(m);
});
sandbox.stub(col, 'decryptModel').returns(model);
});
it('changes dates of model', function() {
expect(model.attributes.updated).to.be.equal(0);
expect(model.attributes.created).to.be.equal(0);
return col.saveModel(model, {title: 'Test'}).then(function() {
expect(model.attributes.updated).not.to.be.equal(0);
expect(model.attributes.created).not.to.be.equal(0);
});
});
it('triggers `sync:model` event', function() {
col.vent.once('sync:model', itStub);
return col.saveModel(model, {title: 'Test'}).then(function() {
expect(itStub).calledWith(model);
});
});
it('triggers `update:model` event', function() {
col.vent.once('update:model', itStub);
return col.saveModel(model, {title: 'Test'}).then(function() {
expect(itStub).calledWith(model);
});
});
});
describe('.saveCollection()', function() {
before(function() {
col.collection = collection.clone();
});
beforeEach(function() {
sandbox.stub(Q, 'invoke').returns(Q.resolve());
});
it('saves all changes in a collection', function() {
col.saveCollection();
expect(Q.invoke).callCount(col.collection.length);
expect(Q.invoke).calledWithMatch({}, 'save', {trash: 0});
});
it('triggers `saved:collection` event', function() {
col.vent.once('saved:collection', itStub);
return col.saveCollection().then(function() {
expect(itStub).called;
});
});
});
describe('.saveRaw()', function() {
var model;
before(function() {
model = new col.collection.model({id: 'test-1', title: 'Test'});
});
beforeEach(function() {
sandbox.stub(col, 'decryptModel').returns(Q.resolve(model));
sandbox.stub(col, 'save').returns(Q.resolve(model));
});
it('instantiates a model with the provided profileId', function() {
sandbox.spy(col, 'changeDatabase');
col.saveRaw({id: 'test-1', title: 'Test'}, {profile: 'test-db'});
expect(col.changeDatabase).calledWith({profile: 'test-db'});
});
it('decrypts data before saving', function() {
col.saveRaw({id: 'test-1', title: 'Test'});
expect(col.decryptModel).calledBefore(col.save);
expect(col.decryptModel).calledWithMatch({id: 'test-1', attributes: {title: 'Test'}});
});
it('validates data before saving', function() {
var spy = sandbox.spy(col.Collection.prototype.model.prototype, 'validate');
return col.saveRaw({id: 'test-1', title: 'Test'}).then(function() {
expect(spy).calledBefore(col.save);
});
});
it('saves data', function() {
return col.saveRaw({id: 'test-1', title: 'Test'}).then(function() {
expect(col.save).calledWithMatch({id: 'test-1'}, {id: 'test-1', title: 'Test'});
});
});
it('triggers `update:model` and `synced:ID` after saving', function() {
var stub = sandbox.stub();
col.vent.once('synced:test-1', stub);
col.vent.once('update:model', itStub);
return col.saveRaw({id: 'test-1', title: 'Test'}).then(function() {
expect(itStub).calledWithMatch({id: 'test-1'});
expect(stub).calledWithMatch({id: 'test-1'});
});
});
});
describe('.saveAllRaw()', function() {
beforeEach(function() {
sandbox.stub(col, 'saveRaw').returns(Q.resolve());
});
it('saves all data', function() {
return col.saveAllRaw(models, {profile: 'test-db'}).then(function() {
expect(col.saveRaw).callCount(models.length);
expect(col.saveRaw).calledWithMatch({}, {profile: 'test-db'});
});
});
});
describe('.remove()', function() {
beforeEach(function() {
sandbox.stub(col, 'save').returns(Q.resolve());
});
it('changes a model\'s `trash` status to `2`', function() {
var spy = sandbox.spy(col.collection.model.prototype, 'set');
col.remove({id: 'test-1'}, {});
expect(spy).calledWithMatch({trash: 2});
});
it('changes a model\'s atributes to default values', function() {
var defaults = _.omit(col.collection.model.prototype.attributes, 'id', 'trash', 'updated');
defaults = _.extend(defaults, {trash: 2});
return col.remove({id: 'test-1'}, {}).then(function() {
expect(col.save).calledWithMatch({id: 'test-1'}, defaults);
});
});
it('triggers `destroy:model` event', function() {
col.vent.once('destroy:model', itStub);
return col.remove({id: 'test-1'}, {}).then(function() {
expect(itStub).calledAfter(col.save);
});
});
});
describe('.getModel()', function() {
before(function() {
col.collection.add({id: 'test-1'});
});
beforeEach(function() {
sandbox.stub(col.collection.model.prototype, 'fetch').returns(Q.resolve());
sandbox.stub(col, 'decryptModel').returns(Q.resolve());
});
it('returns a new model with default values if ID was not provided', function() {
return col.getModel({}).should.eventually.contain({id: undefined});
});
it('returns model from cache if it is there', function() {
return col.getModel({id: 'test-1'}).should.eventually
.contain({id: 'test-1'}, {profileId: col.defaultDB});
});
it('fetches from database if profileId of a model is different from collection\'s', function() {
return col.getModel({id: 'test-1', profile: 'test-db'})
.then(function() {
expect(col.collection.model.prototype.fetch).called;
});
});
it('returns null if if fetch rejects with `not found` error', function() {
col.collection.model.prototype.fetch.returns(Q.reject('not found'));
return col.getModel({id: 'test-1', profile: 'test-db'}).should.eventually
.be.equal(null);
});
});
describe('.getAll()', function() {
beforeEach(function() {
sandbox.stub(col, 'fetch').returns(Q.resolve(collection));
});
it('triggers `destroy:collection`', function() {
col.vent.once('destroy:collection', itStub);
col.getAll({});
expect(itStub).called;
});
it('calls .fetch()', function() {
col.getAll({profile: 'test-db'});
expect(col.fetch).calledWith({profile: 'test-db'});
});
it('provides filter conditions', function() {
col.getAll({profile: 'test-db', filter: 'active'});
expect(col.fetch).calledWithMatch({
profile : 'test-db',
conditions : col.Collection.prototype.conditions.active
});
});
it('caches collection locally', function() {
return col.getAll({profile: 'test-db', filter: 'active'})
.then(function() {
expect(col.collection).to.be.equal(collection);
expect(col.collection.conditionFilter).to.be.equal('active');
expect(col.collection.conditionCurrent)
.to.be.equal(col.Collection.prototype.conditions.active);
});
});
it('calls .registerEvents() if it exist in a collection', function() {
sandbox.stub(col.collection, 'registerEvents');
return col.getAll({}).then(function() {
expect(col.collection.registerEvents).called;
});
});
});
describe('.fetch()', function() {
beforeEach(function() {
sandbox.stub(col, 'decryptModels').returns(Q.resolve());
sandbox.stub(col.Collection.prototype, 'fetch').returns(Q.resolve());
});
it('changes profileId before fetching', function() {
sandbox.spy(col, 'changeDatabase');
col.fetch({profile: 'test'});
expect(col.changeDatabase).calledWith({profile: 'test'});
expect(col.changeDatabase).calledBefore(col.Collection.prototype.fetch);
});
it('calls collection\'s .fetch() function', function() {
col.fetch({profile: 'test'});
expect(col.Collection.prototype.fetch).calledWith({profile: 'test'});
});
it('returns in decrypted format if `encrypt` option is false', function() {
return col.fetch({profile: 'test'}).then(function() {
expect(col.decryptModels).called;
});
});
});
describe('._isEncryptEnabled()', function() {
var configs,
model;
before(function() {
configs = {encrypt: 0, encryptBackup: {}};
Radio.reply('configs', 'get:object', function() { return configs; });
});
beforeEach(function() {
model = new col.Collection.prototype.model();
});
after(function() {
Radio.stopReplying('configs', 'get:object');
});
it('returns false if a collection store name is configs', function() {
col.Collection.prototype.storeName = 'configs';
expect(col._isEncryptEnabled()).to.be.equal(false);
col.Collection.prototype.storeName = 'notes';
});
it('returns false if encryption is disabled', function() {
expect(col._isEncryptEnabled(model)).to.be.equal(false);
});
it('returns false if a model does not have encryptKeys', function() {
model.encryptKeys = undefined;
expect(col._isEncryptEnabled(model)).to.be.equal(false);
});
it('returns true', function() {
configs.encrypt = 1;
expect(col._isEncryptEnabled(col.collection.model.prototype)).to.be.equal(true);
});
});
describe('.encryptModel()', function() {
beforeEach(function() {
sandbox.stub(col, '_isEncryptEnabled').returns(false);
});
it('does nothing if encryption is disabled', function() {
return col.encryptModel({clear: true}).should.eventually
.deep.equal({clear: true});
});
it('encrypts if encryption is enabled', function() {
col._isEncryptEnabled.returns(true);
Radio.replyOnce('encrypt', 'encrypt:model', itStub);
col.encryptModel({clear: true});
expect(itStub).calledWith({clear: true});
});
});
describe('.decryptModel()', function() {
beforeEach(function() {
sandbox.stub(col, '_isEncryptEnabled').returns(false);
});
it('does nothing if encryption is disabled', function() {
return col.decryptModel({clear: true}).should.eventually
.deep.equal({clear: true});
});
it('encrypts if encryption is enabled', function() {
col._isEncryptEnabled.returns(true);
Radio.replyOnce('encrypt', 'decrypt:model', itStub);
col.decryptModel({clear: true});
expect(itStub).calledWith({clear: true});
});
});
describe('.decryptModels()', function() {
beforeEach(function() {
sandbox.stub(col, '_isEncryptEnabled').returns(false);
});
it('checks if encryption is enabled', function() {
col.decryptModels();
expect(col._isEncryptEnabled).calledWith(col.collection.model.prototype);
});
it('does nothing if encryption is disabled', function() {
return col.decryptModels().should.eventually
.be.equal(col.collection);
});
it('decrypts models if encryption is enabled', function() {
col._isEncryptEnabled.returns(true);
Radio.replyOnce('encrypt', 'decrypt:models', itStub);
col.decryptModels();
expect(itStub).calledWith(col.collection);
});
});
});
});
================================================
FILE: test/spec/collections/modules/notebooks.js
================================================
/*jshint expr: true*/
/* global expect, define, describe, before, beforeEach, after, afterEach, it */
define([
'sinon',
'q',
'underscore',
'backbone.radio',
'collections/modules/notebooks',
'collections/modules/module',
], function(sinon, Q, _, Radio, ColModule, Module) {
'use strict';
describe('collections/modules/notebooks', function() {
var col,
sandbox,
collection;
before(function() {
col = new ColModule();
collection = [];
for (var i = 0; i < 10; i++) {
collection.push({id: 'id-' + i});
}
collection = new col.Collection(collection);
});
after(function() {
col.destroy();
});
beforeEach(function() {
sandbox = sinon.sandbox.create();
sandbox.stub(col, 'save').returns(Q.resolve());
sandbox.stub(col, 'getModel').returns(Q.resolve());
});
afterEach(function() {
sandbox.restore();
});
describe('.initialize()', function() {
it('has .Collection', function() {
expect(col.Collection.prototype.storeName).to.be.equal('notebooks');
});
it('listens on `.Collection.storeName` channel', function() {
expect(col.vent).to.be.an('object');
expect(col.vent.channelName).to.be.equal(col.Collection.prototype.storeName);
});
it('listens to requests', function() {
_.each(_.union(Module.prototype.reply(), col.reply()), function(reply) {
expect(col.vent._requests[reply]).to.be.an('object');
});
});
});
describe('.remove()', function() {
var model,
notesStub;
beforeEach(function() {
model = new col.Collection.prototype.model({id: 'test-id-1'});
sandbox.stub(col, 'updateChildren').returns(Q.resolve());
sandbox.stub(Module.prototype, 'remove');
notesStub = sandbox.stub().returns(Q.resolve([]));
Radio.reply('notes', 'change:notebookId', notesStub);
});
after(function() {
Radio.stopReplying('notes', 'change:notebookId');
});
it('tries to fetch model from database if the first argument is ID', function() {
col.remove('test-id', {profile: 'test1'});
expect(col.getModel).calledWith({id: 'test-id', profile: 'test1'});
});
it('calls .updateChildren()', function() {
return col.remove(model, {profile: 'test1'}).then(function() {
expect(col.updateChildren).calledWithMatch(model);
});
});
it('makes `change:notebookId` request on `notes` channel', function() {
return col.remove(model, {profile: 'test1'}, true).then(function() {
expect(notesStub).calledWith(model, true);
});
});
it('calls parent function', function() {
return col.remove(model, {profile: 'test1'}, true).then(function() {
expect(Module.prototype.remove).calledWith(model, {profile: 'test1'});
});
});
});
describe('.updateChildren()', function() {
var model;
beforeEach(function() {
model = new col.Collection.prototype.model({
id : 'test-id-1',
parentId : 'test-id-0'
});
sandbox.stub(col, 'getChildren').returns(Q.resolve(new col.Collection()));
sandbox.stub(col, 'saveModel').returns(Q.resolve());
});
it('calls .getChildren()', function() {
col.updateChildren(model);
expect(col.getChildren).calledWith(model.id, {profile: model.profileId});
});
it('updates parentId of each child notebook', function() {
col.getChildren.returns(Q.resolve(collection));
return col.updateChildren(model).then(function() {
expect(col.saveModel).callCount(collection.length);
expect(col.saveModel).calledWithMatch({}, {parentId: model.get('parentId')});
});
});
});
describe('.getChildren()', function() {
it('uses this.collection if it exists', function() {
col.collection = collection;
sandbox.spy(collection, 'clone');
col.getChildren('test-id-1', {});
expect(collection.clone).called;
col.collection = null;
});
it('fetches all child notebooks', function() {
sandbox.stub(col, 'fetch');
col.getChildren('test-id-1', {profile: 'test'});
expect(col.fetch).calledWith({
conditions : {parentId : 'test-id-1'},
profile : 'test'
});
});
});
describe('.getAll()', function() {
beforeEach(function() {
sandbox.stub(Module.prototype, 'getAll').returns(Q.resolve());
});
it('requests sorting direction configs', function() {
var stub = sandbox.stub().returns('');
Radio.replyOnce('configs', 'get:config', stub);
col.getAll({});
expect(stub).called;
});
it('returns current cached collection if it exists', function() {
col.collection = collection;
col.collection.profileId = 'testDb';
return col.getAll({profile: 'testDb'}).should.eventually
.be.equal(collection);
});
it('calls the parent function otherwise', function() {
col.collection = null;
col.getAll({profile: 'testDb', filter: 'task'});
expect(Module.prototype.getAll).calledWith({profile: 'testDb', filter: 'task'});
});
});
});
});
================================================
FILE: test/spec/collections/modules/notes.js
================================================
/*jshint expr: true*/
/* global expect, define, describe, before, beforeEach, after, afterEach, it */
define([
'sinon',
'q',
'underscore',
'backbone.radio',
'collections/modules/notes',
'collections/modules/module',
], function(sinon, Q, _, Radio, ColModule, Module) {
'use strict';
describe('collections/modules/notes', function() {
var col,
sandbox,
collection;
before(function() {
col = new ColModule();
collection = [];
for (var i = 0; i < 10; i++) {
collection.push({id: 'id-' + i});
}
collection = new col.Collection(collection);
});
after(function() {
col.destroy();
});
beforeEach(function() {
sandbox = sinon.sandbox.create();
sandbox.stub(col, 'save').returns(Q.resolve());
sandbox.stub(col, 'getModel').returns(Q.resolve());
});
afterEach(function() {
sandbox.restore();
});
describe('.initialize()', function() {
it('has .Collection', function() {
expect(col.Collection.prototype.storeName).to.be.equal('notes');
});
it('listens on `.Collection.storeName` channel', function() {
expect(col.vent).to.be.an('object');
expect(col.vent.channelName).to.be.equal(col.Collection.prototype.storeName);
});
it('listens to requests', function() {
_.each(_.union(Module.prototype.reply(), col.reply()), function(reply) {
expect(col.vent._requests[reply]).to.be.an('object');
});
});
});
describe('.reply()', function() {
it('returns replies', function() {
expect(col.reply()).to.be.an('object');
expect(_.keys(col.reply()).length).to.be.equal(3);
});
});
describe('.saveModel()', function() {
beforeEach(function() {
sandbox.stub(Module.prototype, 'saveModel');
});
it('will try to add tags', function(done) {
Radio.replyOnce('tags', 'add', function(tags, options) {
expect(tags).to.deep.equal(['tag1', 'tag2']);
expect(options.profile).to.be.equal('test');
done();
});
col.saveModel({profileId: 'test'}, {tags: ['tag1', 'tag2']});
});
it('saves the model afterwards', function() {
return col.saveModel({profileId: 'test'}, {tags: ['tag1']})
.then(function() {
expect(Module.prototype.saveModel)
.calledWith({profileId: 'test'}, {tags: ['tag1']});
});
});
});
describe('.remove()', function() {
var model;
beforeEach(function() {
model = new col.Collection.prototype.model();
});
it('tries to fetch model from database if only ID was provided', function() {
col.remove('test-id', {profile: 'test1'});
expect(col.getModel).calledWith('test-id', {profile: 'test1'});
});
it('removes the model instead of putting to trash if it is already there', function() {
var stub = sandbox.stub(Module.prototype, 'remove');
model.set('trash', 1);
return col.remove(model, {profile: 'test1'})
.then(function() {
expect(stub).to.be.calledWith(model, {profile: 'test1'});
});
});
it('changes `trash` status to `1`', function() {
return col.remove(model, {profile: 'test1'})
.then(function() {
expect(col.save).calledWithMatch(model, {trash: 1});
});
});
it('triggers `destroy:model` event', function(done) {
col.vent.once('destroy:model', function() { done(); });
col.remove(model, {profile: 'test1'});
});
});
describe('.restore()', function() {
var model;
before(function() {
model = new col.Collection.prototype.model({trash: 1});
});
it('tries to fetch model from database if only ID was provided', function() {
col.restore('test-id-2', {profile: 'test2'});
expect(col.getModel).calledWith('test-id-2', {profile: 'test2'});
});
it('changes `trash` status to `0`', function() {
return col.restore(model, {profile: 'test2'})
.then(function() {
expect(col.save).calledWithMatch(model, {trash: 0});
});
});
it('triggers `restore:model` event', function(done) {
col.vent.once('restore:model', function() { done(); });
col.restore(model, {profile: 'test2'});
});
});
describe('.onNotebookRemove()', function() {
beforeEach(function() {
sandbox.stub(col, 'fetch').returns(Q.resolve(collection));
sandbox.stub(col, 'saveModel').returns(Q.resolve());
});
it('fetches all notes which are attached to the notebook', function() {
return col.onNotebookRemove({id: 'test-id-3', profileId: 'test3'})
.then(function() {
expect(col.fetch).calledWith({
conditions: {notebookId: 'test-id-3'},
profile : 'test3'
});
});
});
it('changes notes\' notebookId to `0`', function() {
return col.onNotebookRemove({id: 'test-id-3', profileId: 'test3'})
.then(function() {
expect(col.saveModel).callCount(collection.length);
expect(col.saveModel).calledWithMatch({}, {notebookId: 0});
});
});
it('changes notes\' trash status to `1` if remove argument is true', function() {
return col.onNotebookRemove({id: 'test-id-3', profileId: 'test3'}, true)
.then(function() {
expect(col.saveModel).callCount(collection.length);
expect(col.saveModel).calledWithMatch({}, {notebookId: 0, trash: 1});
});
});
it('does nothing if no notes were found', function() {
col.fetch.returns(Q.resolve([]));
return col.onNotebookRemove({id: 'test-id-3'}).should.eventually
.be.equal(undefined);
});
});
describe('.getAll()', function() {
beforeEach(function() {
sandbox.stub(Module.prototype, 'getAll').returns(Q.resolve(collection));
});
it('uses `active` filter if no filter is provided', function() {
col.getAll({});
expect(Module.prototype.getAll).calledWith({filter: 'active'});
});
it('calls ._filterOnFetch()', function() {
sandbox.spy(col, '_filterOnFetch');
return col.getAll({filter: 'task', query: 'hello'}).then(function() {
expect(col._filterOnFetch)
.calledWithMatch({}, {filter: 'task', query: 'hello'});
});
});
});
describe('._filterOnFetch()', function() {
it('calls collection.filterList()', function() {
var options = {filter: 'task', query: 'hello'};
sandbox.spy(collection, 'filterList');
col._filterOnFetch(collection, options);
expect(collection.filterList).calledWith(options.filter, options);
});
});
describe('.getModelFull()', function() {
var model;
beforeEach(function() {
model = new col.Collection.prototype.model({
id : 'test-id-4',
notebookId : 'test-notebook-id',
files : ['file-id-1']
});
col.getModel.returns(Q.resolve(model));
});
it('fetches a model', function() {
col.getModelFull({id: 'test-id-4'});
expect(col.getModel).calledWith({id: 'test-id-4'});
});
it('requests the notebook attached to a note', function(done) {
Radio.replyOnce('notebooks', 'get:model', function(data) {
expect(data.id).to.be.equal(model.get('notebookId'));
done();
});
col.getModelFull({id: 'test-id-4', profile: 'test-db'});
});
it('requests files attached to a note', function(done) {
Radio.replyOnce('files', 'get:files', function(files) {
expect(files).to.be.equal(model.get('files'));
done();
});
col.getModelFull({id: 'test-id-4', profile: 'test-db'});
});
});
});
});
================================================
FILE: test/spec/collections/modules/tags.js
================================================
/*jshint expr: true*/
/* global expect, define, describe, before, beforeEach, after, afterEach, it */
define([
'sinon',
'q',
'underscore',
'backbone.radio',
'collections/modules/tags',
'collections/modules/module',
], function(sinon, Q, _, Radio, ColModule, Module) {
'use strict';
describe('collections/modules/tags', function() {
var col,
sandbox,
stubSha;
before(function() {
col = new ColModule();
});
after(function() {
col.destroy();
Radio.stopReplying('encrypt', 'sha256');
});
beforeEach(function() {
sandbox = sinon.sandbox.create();
sandbox.stub(col, 'save').returns(Q.resolve());
sandbox.stub(col, 'getModel').returns(Q.resolve());
stubSha = sandbox.stub().returns('tag'.split(''));
Radio.reply('encrypt', 'sha256', stubSha);
});
afterEach(function() {
sandbox.restore();
});
describe('.initialize()', function() {
it('has .Collection', function() {
expect(col.Collection.prototype.storeName).to.be.equal('tags');
});
it('listens on `.Collection.storeName` channel', function() {
expect(col.vent).to.be.an('object');
expect(col.vent.channelName).to.be.equal(col.Collection.prototype.storeName);
});
it('listens to requests', function() {
_.each(_.union(Module.prototype.reply(), col.reply()), function(reply) {
expect(col.vent._requests[reply]).to.be.an('object');
});
});
});
describe('.addTags()', function() {
beforeEach(function() {
sandbox.stub(col, 'addTag').returns(Q.resolve());
});
it('does nothing if tags array is empty', function() {
return col.addTags([]).should.eventually.be.an('undefined');
});
it('adds all tags from array', function() {
return col.addTags(['tag', 'tag2', 'tag3']).then(function() {
expect(col.addTag).callCount(3);
});
});
});
describe('.addTag()', function() {
var model;
before(function() {
model = new col.Collection.prototype.model({id: 'test-id-tag'});
});
beforeEach(function() {
sandbox.stub(col, 'saveModel');
});
it('requests sha256 salt of a tag', function() {
col.addTag('tag');
expect(stubSha).calledWith('tag');
});
it('will check if model with the same ID exists in database', function() {
return col.addTag('tag', {}).then(function() {
expect(col.getModel).calledWithMatch({id: 'tag'});
expect(col.saveModel).calledWithMatch({}, {name: 'tag'});
});
});
it('will save it if model exists but its name is empty', function() {
col.getModel.returns(model);
return col.addTag('tag', {}).then(function() {
expect(col.saveModel).calledWithMatch({}, {name: 'tag'});
});
});
it('will not create a new model if a tag already exists', function() {
model.set('name', 'tag-name');
col.getModel.returns(model);
return col.addTag('tag', {}).should.eventually.equal(model);
});
});
describe('.saveModel()', function() {
var model;
beforeEach(function() {
model = new col.Collection.prototype.model({id: 'test-id-1'});
sandbox.stub(Module.prototype, 'saveModel');
});
it('throws an error if validation fails', function() {
return col.saveModel(model, {name: ''}).should.be.rejected;
});
it('triggers `invalid` event on model', function(done) {
model.once('invalid', function() { done(); });
col.saveModel(model, {name: ''});
});
it('generates ID from SHA salted name of the tag to avoid duplicates', function() {
col.saveModel(model, {name: 'testTag'});
expect(stubSha).calledWith('testTag');
});
it('removes model if it has ID to avoid duplicates', function() {
sandbox.stub(col, 'remove').returns(Q.resolve());
return col.saveModel(model, {name: 'testTag'}).then(function() {
expect(col.remove).calledWith(model, {profile: model.profileId});
});
});
it('calls the parent function to save changes', function() {
return col.saveModel(model, {name: 'testTag'}).then(function() {
expect(Module.prototype.saveModel)
.calledWithMatch(model, {name: 'testTag'});
});
});
});
});
});
================================================
FILE: test/spec/collections/notebooks.js
================================================
/*jshint expr: true*/
/* global expect, define, describe, it, beforeEach, before */
define([
'sinon',
'underscore',
'backbone.radio',
'collections/notebooks'
], function(sinon, _, Radio, Collection) {
'use strict';
describe('collections/notebooks', function() {
var collection,
models;
before(function() {
models = [];
for (var i = 0; i < 10; i++) {
models.push({
id : 'id-' + i,
name : 'Notebook' + i,
parentId : (i < 5 && i !== 0 ? 'id-' + (i - 1) : '0'),
trash : 0
});
}
});
beforeEach(function() {
collection = new Collection(models);
});
describe('.sortItOut()', function() {
beforeEach(function() {
sinon.stub(collection, 'getTree').returns([collection.at(0)]);
});
it('calls .getTree()', function() {
collection.sortItOut();
expect(collection.getTree).to.have.been.called;
});
it('overwrites .models with what .getTree() returns', function() {
collection.sortItOut();
expect(collection.models.length).to.be.equal(1);
expect(collection.models[0]).to.be.equal(collection.at(0));
});
});
describe('.sortFullCollection()', function() {
beforeEach(function() {
sinon.stub(collection, 'sortItOut', function() {
this.models = [collection.at(0)];
});
});
it('calls .sortItOut()', function() {
collection.sortFullCollection();
expect(collection.sortItOut).to.have.been.called;
});
it('resets the collection', function() {
var spy = sinon.spy(collection, 'reset');
collection.sortFullCollection();
expect(spy).to.have.been.called;
});
});
describe('._onAddItem()', function() {
beforeEach(function() {
sinon.stub(collection, 'sortFullCollection');
sinon.stub(collection, '_navigateOnRemove');
});
it('removes a model from collection if it does not meet condition', function() {
collection.conditionCurrent = {id: 'id-2'};
collection._onAddItem(collection.at(0));
expect(collection._navigateOnRemove).to.have.been
.calledWith(collection.at(0));
});
it('removes a model from collection if trash === 0', function() {
collection.at(1).set('trash', 1);
collection._onAddItem(collection.at(1));
expect(collection._navigateOnRemove).to.have.been
.calledWith(collection.at(1));
});
it('if the model was found, it updates its attributes', function() {
var model = collection.at(2).clone();
model.set({name: 'Hello World'});
expect(model.get('name')).not.to.be.equal(collection.at(2).get('name'));
collection._onAddItem(model);
expect(model.get('name')).to.be.equal('Hello World');
});
it('if the model was not found, it adds it to the collection', function() {
var model = new Collection.prototype.model({name: 'Test', id: 'id-test-1'});
expect(collection.get(model.id)).to.be.an('undefined');
collection._onAddItem(model);
expect(collection.get(model.id)).to.be.an('object');
});
it('triggers `model:navigate` event', function(done) {
Radio.once('notebooks', 'model:navigate', function(model) {
expect(model).to.be.equal(collection.at(0));
done();
});
collection._onAddItem(collection.at(0));
});
});
it('.rejectTree()', function() {
for (var i = 0; i < 10; i++) {
// 5 first models are linked in the mockup
var l = (i < 5 ? (5 - i) : 1);
expect(collection.rejectTree(collection.at(i).id).length)
.to.be.equal(collection.length - l);
}
});
describe('.getTree()', function() {
it('returns array', function() {
expect(collection.getTree()).to.be.an('array');
});
});
it('.getChildren()', function() {
expect(collection.getChildren('id-0')).to.be.an('array');
expect(collection.getChildren('id-0').length).to.be.equal(1);
expect(collection.getChildren('id-1').length).to.be.equal(1);
});
it('.getRoots()', function() {
expect(collection.getRoots()).to.be.an('array');
expect(collection.getRoots().length).to.be.equal(collection.length - 4);
});
});
});
================================================
FILE: test/spec/collections/notes.js
================================================
/*jshint expr: true*/
/* global expect, define, describe, beforeEach, before, it */
define([
'sinon',
'underscore',
'collections/notes'
], function(sinon, _, Collection) {
'use strict';
/*jshint multistr: true */
var lorem = 'Lorem Ipsum is simply dummy text of the printing and \
typesetting industry. Lorem Ipsum has been the industry\'s standard \
dummy text ever since the 1500s, when an unknown printer took a galley\
of type and scrambled it to make a type specimen book.';
describe('collections/notes', function() {
var collection;
beforeEach(function() {
collection = new Collection();
});
describe('.conditions', function() {
it('.notebook returns {notebookId: `query`, trash: 0}', function() {
var res = collection.conditions.notebook({query: 'tasks'});
expect(res.notebookId).to.be.equal('tasks');
expect(res.trash).to.be.equal(0);
});
});
describe('.filterList()', function() {
it('does nothing if filter argument was not provided', function() {
expect(collection.filterList()).to.be.an('undefined');
});
it('does nothing if a filter method does not exist', function() {
expect(collection.filterList('all404')).to.be.an('undefined');
});
it('executes filter method', function() {
var spy = sinon.spy(collection, 'taskFilter');
collection.filterList('task', {query: 'testing'});
expect(spy).to.have.been.calledWith('testing');
});
it('resets the collection', function(done) {
collection.add([{isOk: false}, {isOk: true}]);
sinon.stub(collection, 'taskFilter').returns([collection.at(0)]);
collection.once('reset', function() {
expect(collection.length).to.be.equal(1);
done();
});
collection.filterList('task', {});
});
});
it('.taskFilter()', function() {
collection.add([
{taskCompleted: 1, taskAll: 10},
{taskCompleted: 9, taskAll: 10},
{taskCompleted: 20, taskAll: 20},
]);
expect(collection.taskFilter().length).to.be.equal(2);
});
describe('.tagFilter()', function() {
it('requires notes to have tags', function() {
collection.add([{id: '1'}, {id: '2'}]);
expect(collection.tagFilter('tag').length).to.be.equal(0);
});
it('returns notes which are tagged with the tag', function() {
collection.add([
{trash: 0, tags: ['test', 'test2']},
{trash: 1, tags: ['test']},
{trash: 0, tags: ['test2', 'test3']},
{trash: 0, tags: ['test2', 'test3', 'test']},
]);
expect(collection.tagFilter('test').length).to.be.equal(2);
});
});
describe('.searchFilter()', function() {
var models;
before(function() {
models = [];
for (var i = 0; i < 20; i++) {
models.push({
'id': i,
'title': 'Test note number title ' + i,
'content': lorem + ' ' + i + ' content test content' + i
});
}
});
beforeEach(function() {
collection.add(models);
});
it('does nothing if letters argument is empty', function() {
expect(collection.searchFilter().length).to.be.equal(models.length);
expect(collection.searchFilter('').length).to.be.equal(models.length);
});
it('can find a string in the title', function() {
expect(collection.searchFilter('title 10').length).to.be.equal(1);
});
it('can find a string in the content', function() {
expect(collection.searchFilter('content19').length).to.be.equal(1);
});
it('can perform case-insensetive search', function() {
expect(collection.searchFilter('test').length).to.be.equal(models.length);
expect(collection.searchFilter('NotE').length).to.be.equal(models.length);
expect(collection.searchFilter('NUMBER tITle 11').length).to.be.equal(1);
});
});
it('.fuzzySearch()', function() {
collection.add([
{id: 1, title: 'The Great Gatsby. Test'},
{id: 2, title: 'The DaVinci Code. Test'},
{id: 3, title: 'Angels & Demons'},
]);
collection.fullCollection = collection.clone();
expect(collection.fuzzySearch('gaby').length).to.be.equal(1);
expect(collection.fuzzySearch('gaby')[0].id).to.be.equal(1);
expect(collection.fuzzySearch('tst').length).to.be.equal(2);
});
});
});
================================================
FILE: test/spec/collections/pageable.js
================================================
/*jshint expr: true*/
/* global expect, define, describe, before, beforeEach, afterEach, it */
define([
'sinon',
'underscore',
'backbone.radio',
'collections/pageable'
], function(sinon, _, Radio, Pageable) {
'use strict';
describe('collections/pageable', function() {
var page,
vent,
models;
before(function() {
vent = Radio.channel('notes');
models = [];
for (var i = 0; i < 20; i++) {
models.push({id: i, title: 'hello'});
}
});
beforeEach(function() {
page = new Pageable(models);
page.storeName = 'notes';
page.fullCollection = page.clone();
});
describe('.registerEvents()', function() {
beforeEach(function() {
page.registerEvents();
});
afterEach(function() {
page.stopListening();
});
it('channel name for events is `storeName`', function() {
expect(page.vent).to.be.an('object');
expect(page.vent.channelName).to.be.equal(page.storeName);
});
it('starts listening to events', function() {
page.stopListening();
expect((page._events || {})['change:isFavorite']).to.be.an('undefined');
expect((page.vent._events || {})['update:model']).to.be.an('undefined');
page.registerEvents();
expect(page._events).to.be.an('object');
expect(page.vent).to.be.an('object');
});
it('listens to events triggered to itself', function() {
expect(page._events['change:isFavorite']).to.be.an('array');
expect(page._events.reset).to.be.an('array');
});
it('listens to events triggered to .vent', function() {
expect(page.vent._events['update:model']).to.be.an('array');
expect(page.vent._events['destroy:model']).to.be.an('array');
expect(page.vent._events['restore:model']).to.be.an('array');
});
});
describe('.removeEvents()', function() {
it('stops listening to events', function() {
page.registerEvents().removeEvents();
expect((page._events || {})['change:isFavorite']).to.be.an('undefined');
expect((page.vent._events || {})['update:model']).to.be.an('undefined');
});
it('resets .fullCollection', function() {
var spy = sinon.spy(page.fullCollection, 'reset');
page.removeEvents();
expect(spy).to.have.been.called;
});
});
describe('.getNextPage()', function() {
it('resets itself', function() {
var spy = sinon.spy(page, 'reset');
page.getNextPage();
expect(spy).to.have.been.called;
});
it('increases current page number', function() {
var initial = page.state.currentPage;
page.getNextPage();
expect(page.state.currentPage).to.be.equal(initial + 1);
});
});
describe('.getPreviousPage()', function() {
it('resets itself', function() {
var spy = sinon.spy(page, 'reset');
page.getPreviousPage();
expect(spy).to.have.been.called;
});
it('increases current page number', function() {
var initial = page.state.currentPage;
page.getPreviousPage();
expect(page.state.currentPage).to.be.equal(initial - 1);
});
});
describe('.getPage()', function() {
it('returns an array', function() {
expect(page.getPage(0)).to.be.an('array');
});
it('changes currentPage state', function() {
page.getPage(25);
expect(page.state.currentPage).to.be.equal(25);
});
it('overwrites .models', function() {
var length = page.models.length;
page.getPage(1);
expect(page.models.length).to.be.equal(page.state.pageSize);
expect(page.models.length).to.be.below(length);
});
});
describe('.getOffset()', function() {
it('offset is equal page number * page size', function() {
expect(page.getOffset(2)).to.be.equal(2 * page.state.pageSize);
});
it('uses different formula if first page does not start from 0', function() {
page.state.firstPage = 1;
expect(page.getOffset(2)).to.be.equal((2 - 1) * page.state.pageSize);
});
});
it('.hasPreviousPage()', function() {
expect(page.hasPreviousPage()).to.be.equal(false);
page.state.currentPage = 1;
expect(page.hasPreviousPage()).to.be.equal(true);
});
it('.hasNextPage()', function() {
page.state.totalPages = models.length;
expect(page.hasNextPage()).to.be.equal(true);
page.state.currentPage = models.length;
expect(page.hasNextPage()).to.be.equal(true);
});
describe('.sortFullCollection()', function() {
it('does nothing if .fullCollection does not exist', function() {
page.fullCollection = null;
page.sortFullCollection();
expect(page.models.length).to.be.equal(models.length);
});
it('sorts .fullCollection', function() {
var spy = sinon.spy(page.fullCollection, 'sortItOut');
page.sortFullCollection();
expect(spy).to.have.been.called;
});
it('calls ._updateTotalPages()', function() {
var spy = sinon.spy(page, '_updateTotalPages');
page.sortFullCollection();
expect(spy).to.have.been.called;
});
it('calls .getPage()', function() {
var spy = sinon.spy(page, 'getPage');
page.sortFullCollection();
expect(spy).to.have.been.calledWith(page.state.currentPage);
});
it('resets itself', function(done) {
page.once('reset', function() { done(); });
page.sortFullCollection();
});
});
describe('.getNextItem()', function() {
beforeEach(function() {
page.registerEvents();
});
afterEach(function() {
page.removeEvents();
});
it('returns false if the collection is empty', function() {
page.reset([]);
expect(page.getNextItem()).to.be.equal(false);
});
it('triggers `model:navigate` event', function(done) {
page.vent.once('model:navigate', function(model) {
expect(model).to.be.equal(page.at(1));
done();
});
page.getNextItem(page.at(0));
});
it('returns the first model if ID is incorrect', function(done) {
page.vent.once('model:navigate', function(model) {
expect(model.id).to.be.equal(page.at(0).id);
done();
});
page.getNextItem('hello-world');
});
it('triggers `page:next` if it\'s the last model on page', function(done) {
page.models = page.models.slice(0, page.state.pageSize);
page.once('page:next', done);
page.getNextItem(page.at(page.state.pageSize - 1));
});
it('triggers `page:end` if it\'s the last model', function(done) {
page.models = page.models.slice(0, page.state.pageSize);
page.state = _.extend(page.state, {
currentPage: 1,
totalPages : 2
});
page.once('page:end', done);
page.getNextItem(page.at(page.models.length - 1));
});
});
describe('.getPreviousPage()', function() {
beforeEach(function() {
page.registerEvents();
});
afterEach(function() {
page.removeEvents();
});
it('returns false if the collection is empty', function() {
page.reset([]);
expect(page.getPreviousItem()).to.be.equal(false);
});
it('triggers `model:navigate` event', function(done) {
page.vent.once('model:navigate', function(model) {
expect(model.id).to.be.equal(page.at(2).id);
done();
});
page.getPreviousItem(page.at(3));
});
it('returns the last model if ID is incorrect', function(done) {
page.vent.once('model:navigate', function(model) {
expect(model.id).to.be.equal(page.at(page.length - 1).id);
done();
});
page.getPreviousItem('hello-world');
});
it('triggers `page:previous` if it\'s the first model on the page', function(done) {
page.state.currentPage = 2;
page.once('page:previous', done);
page.getPreviousItem(page.at(0));
});
it('triggers `page:start` if it\'s the first model', function(done) {
page.state.currentPage = page.state.firstPage;
page.once('page:start', done);
page.getPreviousItem(page.at(0));
});
});
describe('._navigateOnRemove()', function() {
it('does nothing if the model isn\'t in the collection', function() {
var model = page.at(0);
page.remove(model);
expect(page._navigateOnRemove(model)).to.be.equal(false);
});
it('removes the model from the collection', function() {
var spy = sinon.spy(page.fullCollection, 'remove');
page._navigateOnRemove(page.at(1));
expect(spy).called;
});
it('sorts .fullCollection', function() {
var spy = sinon.spy(page, 'sortFullCollection');
page._navigateOnRemove(page.at(0));
expect(spy).to.be.called;
});
it('triggers `model:navigate`', function(done) {
vent.once('model:navigate', function(model) {
expect(model.id).to.be.equal(page.at(0).id);
done();
});
page._navigateOnRemove(page.at(0));
});
});
describe('._onRestore()', function() {
it('calls ._onAddItem() if .conditionFilter is not equal to `trashed`', function(done) {
page._onAddItem = done;
page._onRestore();
});
it('.conditionFilter is equal to `trashed` and it\'s not the last model', function(done) {
page.conditionFilter = 'trashed';
page._navigateOnRemove = done;
page._onRestore();
});
});
describe('._onAddItem()', function() {
var model;
beforeEach(function() {
model = new page.model({id: 'test-on-add-item', title: 'hello', trash: 0});
});
it('does not add models from other profiles', function() {
model.profileId = 'test-db';
page._onAddItem(model);
expect(page.get(model.id)).to.be.an('undefined');
});
it('removes the model from the collection if it does not meet condition', function() {
var spy = sinon.spy(page, '_navigateOnRemove');
page.conditionCurrent = {title: 'world'};
page._onAddItem(model);
expect(spy).calledWith(model);
});
it('adds a model to the collection if it does not exist', function() {
page._onAddItem(model);
expect(page.at(0).id).to.be.equal(model.id);
});
});
describe('._onRemoveItem()', function() {
it('removes the model from .fullCollection', function() {
var spy = sinon.spy(page.fullCollection, 'remove');
page._onRemoveItem(page.at(0));
expect(spy).to.be.called;
});
it('sorts .fullCollection afterwards', function(done) {
page.sortFullCollection = done;
page._onRemoveItem(page.at(0));
});
});
});
});
================================================
FILE: test/spec/collections/tags.js
================================================
/*jshint expr: true*/
/* global expect, define, describe, beforeEach, before, it */
define([
'sinon',
'underscore',
'backbone.radio',
'collections/tags'
], function(sinon, _, Radio, Collection) {
'use strict';
describe('collections/tags', function() {
var collection,
models;
before(function() {
models = [];
for (var i = 0; i < 40; i++) {
models.push({
id: 'id-' + i,
name: 'Tag ' + i,
});
}
});
beforeEach(function() {
collection = new Collection(models);
collection.fullCollection = collection.clone();
});
describe('._onAddItem()', function() {
it('triggers `model:navigate` event', function(done) {
Radio.once('tags', 'model:navigate', function(model) {
expect(model).to.be.equal(collection.at(0));
done();
});
collection._onAddItem(collection.at(0));
});
});
describe('.sortFullCollection()', function() {
it('does nothing if .fullCollection does not exist', function() {
collection.fullCollection = undefined;
expect(collection.sortFullCollection()).to.be.an('undefined');
});
it('sorts full collection', function() {
var spy = sinon.spy(collection.fullCollection, 'sortItOut');
collection.sortFullCollection();
expect(spy).to.have.been.called;
});
it('updates pagination state', function() {
var spy = sinon.spy(collection, '_updateTotalPages');
collection.sortFullCollection();
expect(spy).to.have.been.called;
});
it('resets the collection', function(done) {
collection.once('reset', function() {
expect(collection.length).not.to.be.equal(models.length);
expect(collection.fullCollection.length).to.be.equal(models.length);
done();
});
collection.sortFullCollection();
});
});
describe('.getPage()', function() {
var firstModels;
beforeEach(function(done) {
firstModels = collection.models.slice(0, collection.state.pageSize);
collection.once('reset', function() { done(); });
collection.reset(firstModels);
});
it('gets offset number', function() {
var spy = sinon.spy(collection, 'getOffset');
collection.getPage(0);
expect(spy).to.have.been.called;
});
it('saves the page number into .state', function() {
collection.getPage(1);
expect(collection.state.currentPage).to.be.equal(1);
});
it('adds more models if page number is not 0', function() {
var spy = sinon.spy(collection, 'add');
expect(collection.length).not.to.be.equal(models.length);
collection.getPage(1);
expect(collection.length).to.be.equal(2 * collection.state.pageSize);
expect(spy).to.have.been.called;
});
it('resets the collection if page number is 0', function() {
var spy = sinon.spy(collection, 'reset');
collection.getPage(0);
expect(spy).to.have.been.called;
});
});
it('.hasPreviousPage()', function() {
expect(collection.hasPreviousPage()).to.be.equal(false);
});
});
});
================================================
FILE: test/spec/helpers/db.js
================================================
/* global define, describe, it, expect, before, after */
define([
'q',
'underscore',
'helpers/db'
], function(Q, _, db) {
'use strict';
describe('helpers/db', function() {
var options;
this.timeout(8000);
before(function() {
options = {profile: 'test-lav', storeName: 'notes'};
});
after(function() {
db.dbs = {};
window.indexedDB.deleteDatabase('test-lav');
});
describe('.getDB()', function() {
it('returns a localForage instance', function() {
var dbInstance = db.getDb(options);
expect(dbInstance).to.be.an('object');
expect(dbInstance.INDEXEDDB).to.be.equal('asyncStorage');
});
it('caches an instance to `dbs` variable', function() {
var dbId = options.profile + '/notebooks';
expect(db.dbs[dbId]).to.be.an('undefined');
db.getDb({profile: options.profile, storeName: 'notebooks'});
expect(db.dbs[dbId]).to.be.an('object');
});
});
describe('.find()', function() {
it('returns a promise', function() {
expect(db.find({id: 'random', options: options}))
.to.have.property('promiseDispatch');
});
it('fails if an item was not found', function(done) {
return db.find({id: '404data', options: options})
.should.be.rejectedWith('not found')
.and.notify(done);
});
it('can find an item', function(done) {
new Q(db.getDb(options).setItem('testId', {content: 'hello'}))
.then(function() {
return db.find({id: 'testId', options: options});
})
.then(function(data) {
expect(data).to.be.an('object');
expect(data.content).to.be.equal('hello');
done();
});
});
});
describe('.findAll()', function() {
before(function(done) {
Q.all([
db.getDb(options).setItem('testId-1', {content: 'hello'}),
db.getDb(options).setItem('testId-2', {content: 'hello'})
]).then(function() { done(); });
});
it('returns a promise', function() {
expect(db.findAll({options: options})).to.have.property('promiseDispatch');
});
it('resolves with empty array if DB is empty', function() {
return db.findAll({options: {profile: options.profile, storeName: 'tags'}})
.should.eventually.have.property('length').equal(0);
});
it('resolves with data if items were found', function() {
return db.findAll({options: options}).should.eventually
.have.property('length').least(1);
});
});
describe('.findByKeys', function() {
before(function(done) {
Q.all([
db.getDb(options).setItem('key1', {key: true, content: 'hello'}),
db.getDb(options).setItem('key2', {key: false, content: 'hello'})
]).then(function() { done(); });
});
it('returns a promise', function() {
expect(db.findByKeys(['key1', 'key2'], {options: options}))
.to.have.property('promiseDispatch');
});
it('can find items with provided keys', function() {
return db.findByKeys(['key1', 'key2'], {options: options})
.should.eventually.have.property('length').equal(2);
});
it('can filter items', function() {
return db.findByKeys(['key1', 'key2'], {options: {
conditions : {key: true},
profile : options.profile,
storeName : options.storeName
}}).should.eventually.have.property('length').equal(1);
});
});
describe('.save()', function() {
var opt;
before(function() {
opt = _.extend({encryptKeys: ['content', 'secret']}, options);
});
it('returns a promise', function() {
expect(db.save({options: options, id: 'save1', data: {}}))
.to.have.property('promiseDispatch');
});
it('saves an item', function(done) {
db.save({options: options, id: 'save2', data: {key: 'hello'}})
.then(function() {
return db.find({options: options, id: 'save2'});
})
.then(function(data) {
expect(data.key).to.be.equal('hello');
done();
});
});
it('omits all encryptKeys if .encryptedData is not empty', function(done) {
var data = {
content : 'hello',
secret : 'world',
disclosed : 'data',
encryptedData : 'encrypted data'
};
db.save({options: opt, id: 'save3', data: data})
.then(function() {
return db.find({options: options, id: 'save3'});
})
.then(function(sData) {
expect(_.keys(sData).length).to.be.equal(2);
expect(sData.content).to.be.an('undefined');
done();
});
});
});
});
});
================================================
FILE: test/spec/helpers/i18next.js
================================================
/*jshint expr: true*/
define([
'sinon',
'q',
'jquery',
'underscore',
'backbone.radio',
'i18next',
'i18nextXHRBackend',
'helpers/i18next'
], function(sinon, Q, $, _, Radio, i18n, XHR, helper) {
'use strict';
describe('helpers/i18next', function() {
var sandbox;
beforeEach(function() {
sandbox = sinon.sandbox.create();
});
afterEach(function() {
sandbox.restore();
});
describe('.init()', function() {
beforeEach(function() {
sandbox.stub(i18n, 'init');
sandbox.stub(helper, 'getLang');
});
it('adds i18next to jQuery', function() {
helper.init();
expect($.t).to.be.a('function');
});
it('uses XHR backend', function() {
sandbox.spy(i18n, 'use');
helper.init();
expect(i18n.use).calledWith(XHR);
});
it('initializes i18next', function() {
helper.init();
expect(i18n.init).called;
});
it('calls .getLang()', function() {
helper.init();
expect(helper.getLang).called;
});
});
describe('.getLang()', function() {
var reqStub;
beforeEach(function() {
reqStub = sandbox.stub().returns('');
Radio.reply('configs', 'get:config', reqStub);
});
it('requests language from configs', function() {
reqStub.returns('en_us');
expect(helper.getLang()).to.be.equal('en_us');
expect(reqStub).called;
});
it('searches language from browser settings', function() {
sandbox.spy(_, 'chain');
helper.getLang();
expect(_.chain).called;
});
});
});
});
================================================
FILE: test/spec/helpers/storage.js
================================================
/*jshint expr: true*/
/* global define, expect, describe, it, beforeEach, afterEach, Modernizr */
define([
'sinon',
'q',
'underscore',
'backbone',
'backbone.radio',
'helpers/storage'
], function(sinon, Q, _, Backbone, Radio, storage) {
'use strict';
describe('helpers/storage', function() {
describe('.check()', function() {
beforeEach(function() {
sinon.stub(storage, 'switchDb');
sinon.stub(storage, 'testDb');
Radio.reply('global', 'use:webworkers', function() { return true; });
});
afterEach(function() {
storage.switchDb.restore();
storage.testDb.restore();
Modernizr.indexeddb = true;
});
it('uses backbone.noworker.sync if IndexedDB can\'t be used', function() {
Modernizr.indexeddb = false;
storage.check();
expect(storage.switchDb).to.have.been.calledWith('backbone.noworker.sync');
});
it('uses backbone.noworker.sync if WebWorkers can\'t be used', function() {
Radio.replyOnce('global', 'use:webworkers', function() { return false; });
storage.check();
expect(storage.switchDb).to.have.been.calledWith('backbone.noworker.sync');
});
it('uses backbone.sync', function() {
storage.testDb.returns(Q.resolve());
return storage.check().then(function() {
expect(storage.switchDb).to.have.been.calledWith('backbone.sync');
});
});
it('uses backbone.noworker.sync if .testDb() fails', function() {
storage.testDb.returns(Q.reject());
return storage.check().then(function() {
expect(storage.switchDb).to.have.been.calledWith('backbone.noworker.sync');
});
});
});
describe('.testDb()', function() {
it('returns a promise', function() {
expect(storage.testDb()).to.have.property('promiseDispatch');
});
it('successfully opens the DB', function(done) {
return storage.testDb().should.be.fulfilled.and.notify(done);
});
});
describe('.switchDb()', function() {
it('overwrites Backbone.sync with the provided adapter', function(done) {
var sync = Backbone.sync;
storage.switchDb('backbone.noworker.sync').then(function() {
expect(Backbone.sync).not.to.be.equal(sync);
Backbone.sync = sync;
done();
});
});
});
});
});
================================================
FILE: test/spec/helpers/underscore-util.js
================================================
/*jshint expr: true*/
/* global define, describe, it, expect */
define([
'sinon',
'helpers/underscore-util'
], function(sinon, _) {
'use strict';
describe('helpers/underscore-util', function() {
describe('Extends Underscore', function() {
it('adds functions', function() {
expect(_).to.have.property('cleanXSS');
expect(_).to.have.property('runTimes');
expect(_).to.have.property('stripTags');
});
});
describe('.cleanXSS()', function() {
it('calls _.runTimes if `unescape` argument is true', function() {
var spy = sinon.spy(_, 'runTimes');
_.cleanXSS('string', true);
expect(spy).to.have.been.calledWith(_.unescape, 2);
});
it('removes all HTML if `stripTags` argument is true', function() {
expect(_.cleanXSS('Hello', false, true))
.to.be.equal('Hello');
});
});
describe('.runTimes()', function() {
it('executes the provided function N times', function() {
var spy = sinon.spy();
_.runTimes(spy, 5);
expect(spy).to.have.been.callCount(5);
});
it('passes arguments with indexes greater 2', function() {
var spy = sinon.spy();
_.runTimes(spy, 2, 'hello', 'world');
expect(spy).to.have.been.calledWith('hello', 'world');
});
it('returns the last result', function() {
var i = 0;
expect(_.runTimes(function() { i++; return i; }, 4)).to.be.equal(4);
});
});
});
});
================================================
FILE: test/spec/helpers/uri.js
================================================
/*jshint expr: true*/
/* global define, expect, describe, it, before, after, afterEach */
define([
'sinon',
'underscore',
'backbone',
'helpers/uri'
], function(sinon, _, Backbone, Uri) {
'use strict';
describe('helpers/uri', function() {
var uri;
before(function() {
uri = new Uri();
// Prevent it from reloading the page
$(window).off('hashchange');
Backbone.history.start();
Backbone.history.navigate('', {trigger: false});
});
after(function() {
uri.destroy();
Backbone.history.stop();
Backbone.history.navigate('', {trigger: false});
});
afterEach(function() {
uri.navigate('/');
});
describe('.navigate()', function() {
it('changes location hash', function() {
uri.navigate('/notebooks');
expect(document.location.hash).to.contain('notebooks');
});
it('builds URL to a notes list and changes location hash', function() {
uri.navigate({options: {filter: 'notebooks', query: 'id-1', page: 1}});
expect(document.location.hash).to.contain('notes/f/notebooks/q/id-1');
});
it('it is not neccessary to provide options.options', function() {
uri.navigate({filter: 'notebooks', query: 'id-2', page: 1});
expect(document.location.hash).to.contain('notes/f/notebooks/q/id-2');
});
it('adds a profile link to the link and changes the hash', function() {
uri.navigate('/p/notes/', {trigger: false});
uri.navigate('/notebooks', {includeProfile: true});
expect(document.location.hash).to.contain('p/notes/notebooks');
});
});
describe('.navigateBack()', function() {
it('navigates back', function() {
if (/WebKit/.test(window.navigator.userAgent)) {
console.warn('history.back does not work properly in Chrome');
return;
}
uri.navigate('/p/notes1/');
uri.navigate('/p/notes2/');
uri.navigateBack();
expect(document.location.hash).to.contain('p/notes1');
});
});
describe('.getFileLink()', function() {
it('generates a pseudo URL', function() {
expect(uri.getFileLink({id: 'id1'})).to.be.equal('#file:id1');
});
});
describe('.getProfileLink()', function() {
it('adds profile link to the original URL', function() {
expect(uri.getProfileLink('/notes', 'default'))
.to.be.equal('/p/default/notes');
});
it('calls .getProfile() if profile name was not provided', function() {
var spy = sinon.spy(uri, 'getProfile');
uri.getProfileLink('/notes');
expect(spy).to.have.been.called;
});
it('adds `/` to the beginning', function() {
expect(uri.getProfileLink('notes')).to.be.equal('/notes');
});
});
describe('.getProfile()', function() {
it('returns null if profile is not in the hash', function() {
expect(uri.getProfile()).to.be.equal(null);
});
it('returns profile name', function() {
uri.navigate('/p/profileTest/');
expect(uri.getProfile()).to.be.equal('profileTest');
});
});
describe('.getRoute()', function() {
it('returns location hash', function() {
uri.navigate('/test/page');
expect(document.location.hash).to.contain(uri.getRoute());
});
});
describe('.getLink()', function() {
it('returns a link to notes list', function() {
expect(uri.getLink()).to.be.equal('/notes');
});
it('returns a link to filtered notes list', function() {
expect(uri.getLink({filter: 'tags', query: 'hello'}))
.to.be.equal('/notes/f/tags/q/hello');
});
it('adds pagination links', function() {
expect(uri.getLink({filter: 'tags', query: 'hello', page: 10}))
.to.be.equal('/notes/f/tags/q/hello/p10');
});
it('returns a link to a note', function() {
expect(uri.getLink({filter: 'tags', query: 'hello'}, {id: 'model-id'}))
.to.be.equal('/notes/f/tags/q/hello/show/model-id');
});
});
describe('Replies to requests', function() {
it('listens on `uri` channel', function() {
expect(uri).to.have.property('vent');
expect(uri.vent.channelName).to.be.equal('uri');
});
it('has replies', function() {
var replies = ['navigate', 'back', 'profile', 'link:profile', 'link:file', 'link', 'get:current'];
expect(_.keys(uri.vent._requests).length).to.be.equal(replies.length);
_.each(replies, function(name) {
expect(_.keys(uri.vent._requests)).to.include(name);
});
});
});
});
});
================================================
FILE: test/spec/init.js
================================================
/* global mocha, requirejs */
requirejs([
'chai',
'underscore',
'chai-jquery',
'chai-promise',
'sinon-chai',
'helpers/radio.shim',
], function(chai, _, chaiJquery, chaiAsPromised, sinon) {
'use strict';
// Setup Mocha and Chai
mocha.setup('bdd');
mocha.bail(false);
chai.use(chaiJquery);
chai.use(chaiAsPromised);
chai.use(sinon);
// Make `expect` and `should` globally available
window.expect = chai.expect;
window.should = chai.should();
requirejs([
// Core
'spec/app',
'spec/moduleLoader',
'spec/initializers',
'spec/backbone.sync',
// Helpers
'spec/helpers/db',
'spec/helpers/i18next',
'spec/helpers/storage',
'spec/helpers/underscore-util',
'spec/helpers/uri',
// Models
'spec/models/note',
'spec/models/notebook',
'spec/models/tag',
'spec/models/file',
'spec/models/config',
// Collections
'spec/collections/notes',
'spec/collections/notebooks',
'spec/collections/tags',
'spec/collections/configs',
'spec/collections/pageable',
// Collection modules
'spec/collections/modules/notes',
'spec/collections/modules/notebooks',
'spec/collections/modules/tags',
'spec/collections/modules/files',
'spec/collections/modules/configs',
'spec/collections/modules/module',
], function() {
if (window.__karma__) {
return window.__karma__.start();
}
mocha.reporter('html');
(window.mochaPhantomJS || mocha).run();
});
});
================================================
FILE: test/spec/initializers.js
================================================
/*jshint expr: true*/
/* global define, describe, it, expect, after */
define([
'sinon',
'underscore',
'backbone.radio',
'initializers'
], function(sinon, _, Radio, init) {
'use strict';
describe('initializers', function() {
after(function() {
init._inits = {};
Radio.stopReplying('init', 'add', 'start');
init.destroy();
});
describe('Object', function() {
it('initializes itself automatically', function() {
expect(init).to.be.an('object');
});
it('has `_inits` variable', function() {
expect(init).to.have.property('_inits');
expect(init._inits).to.be.an('object');
});
});
describe('.addInit()', function() {
it('adds a new initializer', function() {
init.addInit('test', function() {});
expect(init._inits).to.have.property('test');
expect(init._inits.test).to.have.lengthOf(1);
});
it('pushes initializers to the existing key', function() {
init.addInit('test', function() {});
init.addInit('test', function() {});
expect(init._inits.test).to.have.lengthOf(3);
});
});
describe('.executeInits()', function() {
it('returns a function', function() {
expect(init.executeInits('test')).to.be.a('function');
});
it('the function returns a promise', function() {
var res = init.executeInits('test')();
expect(res).to.be.an('object');
expect(res).to.have.property('promiseDispatch');
});
it('can execute several initializers', function() {
var spy = sinon.spy();
init.addInit('test1', spy);
init.addInit('test2', spy);
return init.executeInits('test1 test2')()
.then(function() {
expect(spy).to.have.been.calledTwice;
});
});
});
describe('._executeInit()', function() {
it('returns a promise', function() {
expect(init._executeInit('test')).to.have.property('promiseDispatch');
});
it('can execute several functions in an initializer', function(done) {
var spy = sinon.spy();
init.addInit('testInit', spy);
init.addInit('testInit', spy);
return init._executeInit('testInit')
.then(function() {
expect(spy).to.have.been.calledTwice;
done();
});
});
it('passes arguments to an initializer function', function() {
var spy = sinon.spy();
init.addInit('testInitArg', spy);
return init._executeInit('testInitArg', ['hello', 'world'])
.then(function() {
spy.should.have.been.calledWith('hello', 'world');
});
});
});
describe('Replies', function() {
it('`add`', function() {
Radio.request('init', 'add', 'testReply', function() {});
expect(init._inits).to.have.property('testReply');
});
it('`start`', function(done) {
Radio.request('init', 'add', 'testRStart', function() { done(); });
Radio.request('init', 'start', 'testRStart')();
});
});
});
});
================================================
FILE: test/spec/models/config.js
================================================
/*jshint expr: true*/
/* global expect, define, describe, beforeEach, it */
define([
'sinon',
'helpers/underscore-util',
'models/config'
], function(sinon, _, Model) {
'use strict';
describe('models/config', function() {
var config;
beforeEach(function() {
config = new Model();
});
it('.defaults', function() {
expect(config.get('name')).to.be.equal('');
expect(config.get('value')).to.be.equal('');
});
describe('.validate()', function() {
it('name should not be empty', function() {
expect(config.validate({name: ''}).length > 0).to.be.equal(true);
});
it('value can be empty', function() {
expect(config.validate({name: 'hello', value: ''})).to.be.equal(undefined);
});
});
it('.changeDB()', function() {
expect(config.profileId).to.be.equal('notes-db');
config.changeDB('profile');
expect(config.profileId).to.be.equal('profile');
});
it('.getValueJSON()', function() {
config.set('value', JSON.stringify({content: 'hello'}));
expect(config.getValueJSON().content).to.be.equal('hello');
});
describe('.createProfile()', function() {
beforeEach(function() {
config.set('value', JSON.stringify(['p1', 'p2']));
sinon.stub(config, 'save');
});
it('does not do anything if name was not provided', function() {
expect(config.createProfile()).to.be.equal(undefined);
});
it('adds profile name to the array', function() {
config.createProfile('p3');
expect(config.save).to.have.been
.calledWith({value: JSON.stringify(['p1', 'p2', 'p3'])});
});
it('does not do anything if profile is in the array', function() {
config.createProfile('p1');
config.createProfile('p3');
expect(config.save).to.have.been.calledOnce;
});
});
describe('.removeProfile()', function() {
beforeEach(function() {
config.set('value', JSON.stringify(['p1', 'p2']));
sinon.stub(config, 'save');
});
it('does not do anything if name was not provided', function() {
expect(config.removeProfile()).to.be.equal(undefined);
});
it('removes the name from the array', function() {
config.removeProfile('p1');
expect(config.save).to.have.been.calledWith({value: JSON.stringify(['p2'])});
});
});
describe('.isPassword()', function() {
beforeEach(function() {
config.set({name: 'encryptPass', value: 'hello'});
});
it('returns false if `name` is not `encryptPass`', function() {
config.set('name', 'null');
expect(config.isPassword({})).to.be.equal(false);
});
it('returns true if `data.name` is `encryptPass`', function() {
config.set('name', 'null');
expect(config.isPassword({name: 'encryptPass'})).to.be.equal(true);
});
it('returns false if `data.value` is an object', function() {
expect(config.isPassword({value: []})).to.be.equal(false);
});
it('returns false if `value` is equal to `data.value`', function() {
expect(config.isPassword({value: 'hello'})).to.be.equal(false);
});
it('returns true', function() {
expect(config.isPassword({value: 'world'})).to.be.equal(true);
});
});
});
});
================================================
FILE: test/spec/models/file.js
================================================
/* global expect, define, describe, beforeEach, it */
define([
'helpers/underscore-util',
'models/file'
], function(_, Model) {
'use strict';
describe('models/file', function() {
var file;
beforeEach(function() {
file = new Model();
});
describe('.defaults', function() {
it('id is undefined', function() {
expect(file.get('id')).to.be.an('undefined');
});
it('empty string', function() {
_.each(['name', 'src', 'fileType'], function(n) {
expect(file.get(n)).to.be.equal('');
});
});
it('equals to 0', function() {
_.each(['trash', 'created', 'updated'], function(n) {
expect(file.get(n)).to.be.equal(0);
});
});
});
describe('.validate()', function() {
it('src should not be empty', function() {
expect(file.validate({src: '', fileType: 'img'}).length).to.be.equal(1);
});
it('fileType should not be empty', function() {
expect(file.validate({src: 'http://', fileType: ''}).length).to.be.equal(1);
});
});
describe('.setEscape()', function() {
it('filters `name`', function() {
var str = 'Hello\n';
file.setEscape({'name': str});
expect(file.get('name')).not.to.be.equal(str);
expect(file.get('name')).to.be.equal(_.cleanXSS(str));
});
});
});
});
================================================
FILE: test/spec/models/note.js
================================================
/* global expect, define, describe, beforeEach, it */
define([
'require',
'helpers/underscore-util',
'models/note'
], function(require, _, Note) {
'use strict';
describe('models/note', function() {
var note;
beforeEach(function() {
note = new Note();
});
describe('instance', function() {
it('should be instance of Note Model', function() {
expect(note).to.be.instanceof(Note);
});
});
describe('default values', function() {
it('"id" is undefined', function() {
expect(note.get('id')).to.be.an('undefined');
});
it('"title" and "content" are be empty strings', function() {
expect(note.get('title')).to.be.equal('');
expect(note.get('content')).to.be.equal('');
});
it('"notebookId" is equal to "0"', function() {
expect(note.get('notebookId')).to.be.equal('0');
});
it('should be equal to 0', function() {
_.each(['taskAll', 'taskCompleted', 'created', 'updated', 'isFavorite', 'trash'], function(prop) {
expect(note.get(prop)).to.be.equal(0);
});
});
it('tags and files are arrays', function() {
expect(note.get('tags')).to.be.an('array');
expect(note.get('files')).to.be.an('array');
});
});
describe('can change it\'s values', function() {
it('it can change "title" property', function() {
note.set('title', 'New title');
expect(note.get('title')).to.be.equal('New title');
});
it('it can change "content" property', function() {
note.set('content', 'New content');
expect(note.get('content')).to.be.equal('New content');
});
});
describe('.validate()', function() {
it('"title" shouldn\'t be empty', function() {
expect(note.validate({title: ''}).length > 0).to.be.equal(true);
expect(note.validate({title: ''})).to.include.members(['title']);
});
it('doesn\'t validate if "trash" is equal to 2', function() {
expect(note.validate({title: '', trash: 2})).to.be.equal(undefined);
expect(note.validate({title: '', trash: 2})).not.to.be.an('object');
});
});
describe('.toggleFavorite()', function() {
it('toggles favourite status', function() {
note.set('isFavorite', 0);
expect(note.toggleFavorite().isFavorite).to.be.equal(1);
note.set('isFavorite', 1);
expect(note.toggleFavorite().isFavorite).to.be.equal(0);
});
});
describe('.setEscape()', function() {
var data;
beforeEach(function() {
data = {
title : '',
content : '\n'
};
});
it('sanitizes data to prevent XSS', function() {
note.setEscape(_.extend({}, data));
_.each(['title', 'content'], function(name) {
expect(note.get(name)).to.be.equal(_.cleanXSS(data[name]));
expect(note.get(name)).not.to.contain(data[name]);
});
});
it('does not escape characters over and over', function() {
var sData = _.extend({}, data);
for (var i = 0; i < 10; i++) {
sData.title = _.cleanXSS(sData.title);
sData.content = _.cleanXSS(sData.content);
}
note.setEscape(sData);
_.each(['title', 'content'], function(name) {
expect(note.get(name)).to.be.equal(_.cleanXSS(data[name]));
expect(note.get(name)).not.to.contain(data[name]);
});
});
});
});
});
================================================
FILE: test/spec/models/notebook.js
================================================
/* global expect, define, describe, beforeEach, it */
define([
'helpers/underscore-util',
'models/notebook'
], function(_, Model) {
'use strict';
describe('models/notebook', function() {
var notebook;
beforeEach(function() {
notebook = new Model();
});
describe('instance', function() {
it('should be instance of Notebook Model', function() {
expect(notebook).to.be.instanceof(Model);
});
it('converts `id` and `parentId` to string on start', function() {
var n = new Model({id: 2, parentId: 1});
expect(n.get('id')).to.be.a('string');
expect(n.get('parentId')).to.be.a('string');
});
});
describe('default values', function() {
it('id is undefined', function() {
expect(notebook.get('id')).to.be.equal(undefined);
});
it('parentId is equal to "0"', function() {
expect(notebook.get('parentId')).to.be.equal('0');
});
it('name is an empty string', function() {
expect(notebook.get('name')).to.be.equal('');
});
it('is equal to 0', function() {
_.each(['count', 'trash', 'created', 'updated'], function(name) {
expect(notebook.get(name)).to.be.equal(0);
});
});
});
describe('can change it\'s values', function() {
it('it can change "name" property', function() {
notebook.set('name', 'New notebook');
expect(notebook.get('name')).to.be.equal('New notebook');
});
it('it can change "parentId" property', function() {
notebook.set('parentId', '1');
expect(notebook.get('parentId')).to.be.equal('1');
});
});
describe('.validate()', function() {
it('"name" shouldn\'t be empty', function() {
expect(notebook.validate({name: ''}).length > 0).to.be.equal(true);
expect(notebook.validate({name: ''})).to.contain('name');
});
it('doesn\'t validate if "trash" is equal to 2', function() {
expect(notebook.validate({name: '', trash: 2})).to.be.equal(undefined);
expect(notebook.validate({name: '', trash: 2})).not.to.be.an('object');
});
it('it can\'t have itself as parent', function(done) {
notebook.once('invalid', function(m, error) {
expect(error).to.contain('parentId');
done();
});
notebook.set({id: '1', parentId: '1'});
notebook.save();
});
});
describe('.setEscape()', function() {
it('sanitizes data to prevent XSS', function() {
var data = {name: 'Hello\n'};
notebook.setEscape(_.extend({}, data));
expect(notebook.get('name')).to.be.equal(_.cleanXSS(data.name));
expect(notebook.get('name')).not.to.contain(data.name);
});
});
});
});
================================================
FILE: test/spec/models/tag.js
================================================
/* global expect, define, describe, beforeEach, it */
define([
'helpers/underscore-util',
'models/tag'
], function(_, Model) {
'use strict';
describe('models/tag', function() {
var tag;
beforeEach(function() {
tag = new Model();
});
describe('default values', function() {
it('id is undefined', function() {
expect(tag.get('id')).to.be.equal(undefined);
});
it('name is an empty string', function() {
expect(tag.get('name')).to.be.equal('');
});
it('is equal to 0', function() {
_.each(['trash', 'created', 'updated'], function(name) {
expect(tag.get(name)).to.be.equal(0);
});
});
});
describe('.validate()', function() {
it('"name" shouldn\'t be empty', function() {
expect(tag.validate({name: ''}).length > 0).to.be.equal(true);
expect(tag.validate({name: ''})).to.contain('name');
});
it('doesn\'t validate if "trash" is equal to 2', function() {
expect(tag.validate({name: '', trash: 2})).to.be.equal(undefined);
expect(tag.validate({name: '', trash: 2})).not.to.be.an('object');
});
});
describe('.setEscape()', function() {
it('sanitizes data to prevent XSS', function() {
var data = {name: 'Hello\n'};
tag.setEscape(_.extend({}, data));
expect(tag.get('name')).to.be.equal(_.cleanXSS(data.name));
expect(tag.get('name')).not.to.contain(data.name);
});
});
});
});
================================================
FILE: test/spec/moduleLoader.js
================================================
/*jshint expr: true*/
/* global define, describe, it, expect, before, after */
define([
'sinon',
'underscore',
'backbone.radio',
'moduleLoader'
], function(sinon, _, Radio, Loader) {
'use strict';
describe('moduleLoader', function() {
after(function() {
Radio.request('init', 'add', 'load:modules', function() {});
});
describe('Object', function() {
it('is an object', function() {
expect(Loader).to.be.an('object');
});
it('has functions', function() {
_.each(['init', 'load', 'get'], function(name) {
expect(Loader).to.have.property(name);
expect(Loader[name]).to.be.a('function');
});
});
});
describe('.init()', function() {
it('calls load function', function() {
var load = sinon.stub(Loader, 'load');
Loader.init();
expect(load).to.have.been.called;
});
it('replies to `modules` request on global channel', function() {
expect(Radio.request('global', 'modules')).to.be.an('array');
});
});
describe('.get()', function() {
var configs;
before(function() {
configs = {modules: ['mathjax', 'fuzzySearch']};
Radio.reply('configs', 'get:config', function(key) {
return configs[key];
});
});
after(function() {
Radio.stopReplying('configs', 'get:config');
});
it('returns array of modules', function() {
expect(Loader.get()).to.be.an('array');
expect(Loader.get().length).to.be.equal(configs.modules.length);
});
it('includes sync adapter', function() {
_.each(['dropbox', 'remotestorage'], function(adapter) {
configs.cloudStorage = adapter;
expect(Loader.get().length).to.be.equal(configs.modules.length + 1);
expect(Loader.get()).to.include.members(['modules/' + adapter + '/module']);
});
});
it('does not load none existent module', function() {
configs.cloudStorage = null;
configs.modules.push('module404');
expect(Loader.get().length).not.to.be.equal(configs.modules.length);
expect(Loader.get().length).to.be.equal(configs.modules.length - 1);
});
});
});
});
================================================
FILE: test/spec/test.js
================================================
/* global requirejs */
var dir = {
base : (window.__karma__ ? '/base/' : '../'),
other : (window.__karma__ ? '/base/app/' : '../'),
test : (window.__karma__ ? '/base/' : '../../'),
};
requirejs.config({
baseUrl : dir.base + 'app/scripts',
urlArgs : 'bust=' + (new Date()).getTime(),
deps : ['modernizr'],
paths : {
'modernizr' : dir.other + 'bower_components/modernizr/modernizr',
'chai' : dir.test + 'test/bower_components/chai/chai',
'chai-jquery' : dir.test + 'test/bower_components/chai-jquery/chai-jquery',
'chai-promise' : dir.test + 'test/bower_components/chai-as-promised/lib/chai-as-promised',
'sinon-chai' : dir.test + 'test/bower_components/sinon-chai/lib/sinon-chai',
'sinon' : dir.test + 'test/bower_components/sinonjs/sinon',
'spec' : dir.test + 'test/spec',
'init' : dir.test + 'test/spec/init',
},
map: {
'*': {
'classes/sjcl.worker' : 'classes/sjcl'
}
},
shim: {
'chai-jquery': ['jquery'],
},
});
/**
* Just include main.js and it will include init script
*/
requirejs(['main']);
================================================
FILE: test/spec-ui/commands/addNote.js
================================================
'use strict';
exports.command = function(item) {
this
.urlHash('notes/add')
.expect.element('#editor--input--title').to.be.visible.before(50000);
this
.setValue('#editor--input--title', item.title)
.expect.element('#editor--input--title').value.to.contain(item.title).before(2000);
this
.click('.CodeMirror-lines')
.keys(item.content);
if (item.notebook) {
this
.click('.addNotebook')
.expect.element('.modal--input[name=name]').to.be.visible.before(1000);
this
.setValue('.modal--input[name=name]', [item.notebook, this.Keys.ENTER])
.pause(1000);
}
this
.click('.editor--save')
.expect.element('.layout--body.-form').not.to.be.present.before(2000);
return this;
};
================================================
FILE: test/spec-ui/commands/addNotebook.js
================================================
'use strict';
exports.command = function(item) {
this
.urlHash('notebooks/add')
.expect.element('#modal input[name="name"]').to.be.present.before(5000);
this.expect.element('#modal input[name="name"]').to.be.visible.before(5000);
// this.expect.element('#modal .form-group').to.be.visible.before(5000);
this.setValue('#modal input[name="name"]', item.name);
this.perform(function(client, done) {
client.execute(function(filter) {
var ops = document.querySelectorAll('#modal select[name="parentId"] option');
for (var i = 0, len = ops.length; i < len; i++) {
if (filter && ops[i].text.indexOf(filter) > -1) {
document
.querySelector('#modal select[name="parentId"]')
.selectedIndex = ops[i].index;
}
}
}, [item.parentId], function() {
done();
});
});
this.keys(this.Keys.ENTER);
this.expect.element('#modal input[name="name"]').to.be.not.present.before(5000);
return this;
};
================================================
FILE: test/spec-ui/commands/addTag.js
================================================
'use strict';
exports.command = function(item) {
this
.urlHash('notebooks')
.pause(100)
.urlHash('tags/add')
.expect.element('#modal .form-group').to.be.present.before(5000);
// this.expect.element('#modal .form-group').to.be.visible.before(5000);
this.setValue('#modal input[name="name"]', [item.name, this.Keys.ENTER]);
return this;
};
================================================
FILE: test/spec-ui/commands/changeEncryption.js
================================================
'use strict';
/**
* This Nightwatch command changes encryption settings.
*/
exports.command = function(data) {
this
.urlHash('settings/encryption')
.expect.element('.-tab-encryption').to.be.present.before(5000);
this.getAttribute('input[name=encrypt]', 'checked', function(res) {
if (data.use && res.value === null) {
this.click('input[name="encrypt"]');
}
});
this
.clearValue('input[name="encryptPass"]')
.setValue('input[name="encryptPass"]', data.password)
.click('#randomize')
.click('.settings--save')
.pause(1000)
.click('.settings--cancel');
return this;
};
================================================
FILE: test/spec-ui/commands/closeWelcome.js
================================================
'use strict';
exports.command = function() {
this
.urlHash('/notes')
.expect.element('#welcome--page').to.be.present.before(100000);
this
.expect.element('.modal-header .close').to.be.present.before(5000);
this
.click('.modal-header .close')
.keys(this.Keys.ESCAPE)
.expect.element('#welcome--page').not.to.be.present.before(10000);
return this;
};
================================================
FILE: test/spec-ui/commands/findAll.js
================================================
exports.command = function(selector, attr, callback) {
this.execute(function(selector, attr) {
var els = document.querySelectorAll(selector),
param = [];
for (var i = 0, len = els.length; i < len; i++) {
if (attr) {
param.push(els[i].getAttribute(attr));
} else {
param.push(els[i]);
}
}
return param;
}, [selector, attr], function(res) {
callback(res.value);
});
return this;
};
================================================
FILE: test/spec-ui/modules/remotestorage/auth.js
================================================
/* global it */
'use strict';
/**
* It tests if it's possible to login to a RemoteStorage server.
*/
it('creates a new user on RemoteStorage server', function(client) {
client
.url('http://localhost:9100/signup')
.expect.element('input[type="password"]').to.be.present.before(50000);
client
.setValue('#username', 'test')
.setValue('#email', 'test@example.com')
.setValue('#password', ['1', client.Keys.ENTER])
.pause(300);
});
it('shows RemoteStorage widget', function(client) {
client
.urlHash('notes')
.expect.element('.remotestorage-initial').to.be.present.before(50000);
});
it('can login to a RemoteStorage server', function(client) {
client
.click('.rs-bubble')
.setValue('.remotestorage-initial input[name="userAddress"]', 'test@localhost:9100')
.click('.remotestorage-initial .connect')
.expect.element('input[type="password"]').to.be.present.before(10000);
client.assert.urlContains('localhost:9100');
client
.setValue('#password', ['1', client.Keys.ENTER])
.expect.element('.remotestorage-connected').to.be.present.before(50000);
});
================================================
FILE: test/spec-ui/modules/remotestorage/client1.js
================================================
/* global describe, before, after, it */
'use strict';
var exec = require('child_process').exec;
/**
* Remove reStore's files.
*/
try {
exec('rm -rf /tmp/reStore/rs.sync');
} catch (e) {
console.log('Unable to remove reStore files', e);
}
describe('RemoteStorage: client 1', function() {
var data = {};
before(function(client, done) {
data = {
note : {title: 'Note from client 1'},
notebook : {name: 'Notebook from client 1'},
tag : {name: 'Tag from client 1'}
};
done();
});
after(function(client, done) {
client.end(function() {
done();
});
});
it('wait', function(client) {
client
.urlHash('notes')
.expect.element('.list').to.be.present.before(50000);
});
it('creates new data', function(client) {
client
.addNote(data.note)
.pause(500)
.addNotebook(data.notebook)
.pause(500)
.addTag(data.tag);
client.pause(1000);
setTimeout(function() {
console.log('start client 2');
}, 500);
});
// Try to login to a RemoteStorage first
require('./auth.js');
it('fetches notes from Remotestorage', function(client) {
client.urlHash('notes');
client.expect.element('#header--add').to.be.present.before(50000);
client.expect
.element('#sidebar--content').to.have.text.that.contains('Note from client 2')
.before(50000);
});
it('fetches notebooks & tags from Remotestorage', function(client) {
client.urlHash('notebooks');
client.expect
.element('#notebooks').text.to.contain('Notebook from client 2')
.before(50000);
client.expect
.element('#tags').text.to.contain('Tag from client 2')
.that.contains('Tag from client 1')
.before(50000);
});
});
================================================
FILE: test/spec-ui/modules/remotestorage/client2.js
================================================
/* global describe, before, after, it */
'use strict';
describe('RemoteStorage: client 2', function() {
before(function(client, done) {
done();
});
after(function(client, done) {
client.end(function() {
done();
});
});
it('wait', function(client) {
client
.urlHash('notes')
.expect.element('.list').to.be.present.before(50000);
});
it('creates new data', function(client) {
client
.addNote({title: 'Note from client 2'})
.pause(500)
.addNotebook({name: 'Notebook from client 2'})
.pause(500)
.addTag({name: 'Tag from client 2'});
});
// Try to login to a RemoteStorage first
require('./auth.js');
it('fetches notes from Remotestorage', function(client) {
client.urlHash('notes');
client.expect.element('#header--add').to.be.present.before(50000);
client.expect
.element('#sidebar--content').to.have.text.that.contains('Note from client 1')
.before(50000);
});
it('fetches notebooks & tags from Remotestorage', function(client) {
client.urlHash('notebooks');
client.expect
.element('#notebooks').text.to.contain('Notebook from client 1')
.before(50000);
client.expect
.element('#tags').text.to.contain('Tag from client 1')
.that.contains('Tag from client 1')
.before(50000);
});
});
================================================
FILE: test/spec-ui/tests/apps/encryption/encrypt.js
================================================
'use strict';
var notes = [];
module.exports = {
before: function(client) {
client.closeWelcome();
},
after: function(client) {
client.end();
},
'wait': function(client) {
client
.urlHash('notes')
.expect.element('.list').to.be.present.before(50000);
},
'shows encryption page': function(client) {
for (var i = 0; i < 8; i++) {
notes.push({
title : 'Encrypted title ' + i,
content : 'Encrypted content ' + i
});
client.addNote(notes[i]);
}
client
.changeEncryption({password: '1', use: true})
.expect.element('.container.-auth').to.be.present.before(5000);
},
'asks for a new password': function(client) {
client.expect.element('input[name="password"]').to.be.present.before(5000);
client
.pause(100)
.click('input[name="password"]')
.keys('1')
// .setValue('input[name="password"]', '1')
.click('[type="submit"]');
},
'shows backup': function(client) {
client.expect.element('.-backup').to.be.present.before(50000);
client.expect.element('#btn--next').to.be.present.before(5000);
client
.pause(300)
.click('#btn--next')
.pause(200)
.expect.element('.container.-auth').not.to.be.present.before(6000);
},
'asks for a new password again after encrypting': function(client) {
client.expect.element('.container.-auth').to.be.present.before(5000);
client
.setValue('input[name="password"]', '1')
.click('[type="submit"]')
.expect.element('.container.-auth').not.to.be.present.before(5000);
},
'shows notes in unencrypted format': function(client) {
client.pause(100);
notes.forEach(function(note) {
client.expect.element('.list').text.to.contain(note.title).before(5000);
client.expect.element('.list').text.to.contain(note.content).before(5000);
});
},
'shows encryption page again if encryption settings are changed': function(client) {
client
.pause(1000)
.changeEncryption({password: '2', use: true})
.expect.element('.container.-auth').to.be.present.before(5000);
},
'asks the old and new passwords': function(client) {
client.expect.element('input[name="oldpass"]').to.be.present.before(5000);
client.expect.element('input[name="password"]').to.be.present.before(5000);
},
're-encrypts everything': function(client) {
client
.setValue('input[name=oldpass]', '1')
.setValue('input[name=password]', '2')
.click('[type="submit"]');
client.expect.element('.-backup').to.be.present.before(50000);
client.expect.element('#btn--next').to.be.present.before(5000);
client
.pause(300)
.click('#btn--next')
.expect.element('.container.-auth').not.to.be.present.before(5000);
},
'asks for a new password again after re-encrypting': function(client) {
client.expect.element('.container.-auth').to.be.present.before(5000);
client
.setValue('input[name="password"]', '2')
.click('[type="submit"]')
.expect.element('.container.-auth').not.to.be.present.before(5000);
},
'shows notes in decrypted format': function(client) {
notes.forEach(function(note) {
client.expect.element('.list').text.to.contain(note.title).before(5000);
client.expect.element('.list').text.to.contain(note.content).before(5000);
});
},
'is possible to disable encryption entirely': function(client) {
client
.urlHash('settings/encryption')
.expect.element('.-tab-encryption').to.be.present.before(5000);
client
.click('input[name="encrypt"]')
.click('.settings--save')
.pause(1000)
.click('.settings--cancel')
.expect.element('.container.-auth').to.be.present.before(5000);
client
.setValue('input[name=oldpass]', '2')
.click('[type="submit"]');
client.expect.element('.-backup').to.be.present.before(50000);
client.expect.element('#btn--next').to.be.present.before(5000);
client
.pause(200)
.click('#btn--next')
.expect.element('.container.-auth').not.to.be.present.before(5000);
notes.forEach(function(note) {
client.expect.element('.list').text.to.contain(note.title).before(5000);
client.expect.element('.list').text.to.contain(note.content).before(5000);
});
},
};
================================================
FILE: test/spec-ui/tests/apps/navbar/navbar.js
================================================
'use strict';
/**
* Add notebook form test
// |)}>#
module.exports = {
before: function(client) {
client.closeWelcome();
client
.urlHash('notes')
.expect.element('#header--add').to.be.present.before(50000);
},
after: function(client) {
client.end();
},
'can show current title and add button': function(client) {
client.expect.element('#header--title').to.have.text.that.equals('All notes');
client.expect.element('#header--add').to.be.present.before(5000);
client.expect.element('#header--add').to.be.visible.before(5000);
client.getTitle(function(title) {
this.assert.equal(title, 'All notes - Laverna');
});
},
'can change title in notebooks page': function(client) {
client.urlHash('notebooks');
client
.expect.element('#header--title')
.to.have.text.that.equals('Notebooks & Tags')
.before(5000);
client.expect.element('#header--add').to.be.present.before(5000);
client.expect.element('#header--add').to.be.visible.before(5000);
client.getTitle(function(title) {
this.assert.equal(title, 'Notebooks & Tags - Laverna');
});
},
'can change title in trashed notes page': function(client) {
client.urlHash('notes/f/trashed');
client
.expect.element('#header--title')
.to.have.text.that.equals('Trashed')
.before(5000);
client.expect.element('#header--add').to.be.present.before(5000);
client.expect.element('#header--add').to.be.visible.before(5000);
client.getTitle(function(title) {
this.assert.equal(title, 'Trashed - Laverna');
});
},
'can change title in favourite notes page': function(client) {
client.urlHash('notes/f/favorite');
client
.expect.element('#header--title')
.to.have.text.that.equals('Favourites')
.before(5000);
client.expect.element('#header--add').to.be.present.before(5000);
client.expect.element('#header--add').to.be.visible.before(5000);
client.getTitle(function(title) {
this.assert.equal(title, 'Favourites - Laverna');
});
},
'search button shows input': function(client) {
client.expect.element('#header--search').to.be.not.visible.before(5000);
client.click('#header--sbtn');
client.expect.element('#header--search').to.be.visible.before(5000);
},
'hitting ESCAPE hides search form': function(client) {
client.pause(500);
client.setValue('#header--search--input', client.Keys.ESCAPE);
client.expect.element('#header--search').to.be.not.visible.before(5000);
},
'can show navbar sidemenu on click on #header--title': function(client) {
client.expect.element('#sidebar--navbar .sidemenu.-show').to.be.not.present.before(5000);
client.click('#header--title');
client.expect.element('#sidebar--navbar .sidemenu.-show').to.be.present.before(5000);
},
'all urls are correct': function(client) {
client
.expect.element('#sidebar--navbar a[href="#/notes"]')
.to.be.visible.before(5000);
client
.expect.element('#sidebar--navbar a[href="#/notes/f/favorite"]')
.to.be.visible.before(5000);
client
.expect.element('#sidebar--navbar a[href="#/notes/f/trashed"]')
.to.be.visible.before(5000);
client
.expect.element('#sidebar--navbar a[href="#/notebooks"]')
.to.be.visible.before(5000);
client
.expect.element('#sidebar--navbar a[href="#/settings"]')
.to.be.visible.before(5000);
},
'can be closed with a click on the close button': function(client) {
client.click('#sidebar--navbar .sidemenu--close');
client.expect.element('#sidebar--navbar .sidemenu.-show').to.be.not.present.before(5000);
},
'can be closed with ESCAPE': function(client) {
client.expect.element('#sidebar--navbar .sidemenu.-show').to.be.not.present.before(5000);
client.click('#header--title');
client.expect.element('#sidebar--navbar .sidemenu.-show').to.be.present.before(5000);
client.keys(client.Keys.ESCAPE);
client.expect.element('#sidebar--navbar .sidemenu.-show').to.be.not.present.before(5000);
},
};
*/
================================================
FILE: test/spec-ui/tests/apps/notebooks/form.js
================================================
'use strict';
var expect = require('chai').expect;
/**
* Add notebook form test
*/
module.exports = {
before: function(client) {
client.closeWelcome();
},
after: function(client) {
client.end();
},
/**
* Saves a notebook
*/
'can show notebook form when button is clicked': function(client) {
client
.urlHash('notebooks')
.expect.element('#header--add').to.be.present.before(50000);
client.click('#header--add')
.expect.element('#modal .form-group').to.be.visible.before(2000);
},
'can change title of a notebook': function(client) {
client
.expect.element('#modal .form-group').to.be.visible.before(50000);
client.setValue('#modal input[name="name"]', ['Nightwatch'])
.expect.element('#modal input[name="name"]').value.to.contain('Nightwatch');
},
'can save notebooks': function(client) {
client
.expect.element('#modal .ok').text.to.contain('Save');
client
.click('#modal .ok')
.expect.element('#modal .form-group').not.to.be.present.before(5000);
},
'saved notebooks appear in list': function(client) {
client
.expect.element('#notebooks .list--item.-notebook').text.to.contain('Nightwatch').before(5000);
},
'redirects to notebooks list on save': function(client) {
client
.pause(500)
.url(function(data) {
expect(data.value).to.contain('#notebooks');
expect(data.value).not.to.contain('/add');
});
},
'saved notebooks appear in the add form': function(client) {
client.click('#header--add')
.expect.element('#modal select[name="parentId"]').text.to.contain('Nightwatch').before(2000);
},
'can add nested notebooks': function(client) {
client
.click('#header--add')
.expect.element('#modal .form-group').to.be.present.before(5000);
client
.setValue('#modal input[name="name"]', ['Sub-notebook'])
// Change parentId of a notebook
.perform((client, done) => {
client.execute(function(filter) {
var ops = document.querySelectorAll('#modal select[name="parentId"] option'),
res = false;
for (var i = 0, len = ops.length; i < len; i++) {
if (ops[i].text.indexOf(filter) > -1) {
document
.querySelector('#modal select[name="parentId"]')
.selectedIndex = ops[i].index;
res = true;
}
}
return res;
}, ['Nightwatch'], function(res) {
expect(res.value).to.be.equal(true);
done();
});
})
.setValue('#modal input[name="name"]', [client.Keys.ENTER]);
},
'doesn\'t save if title is empty': function(client) {
client
.urlHash('notebooks')
.urlHash('notebooks/add')
.expect.element('#modal .form-group').to.be.visible.before(2000);
client
.clearValue('#modal input[name="name"]')
.click('#modal .ok');
client.expect.element('#notebooks .list--item').to.have.text.that.contains('Nightwatch');
},
'closes modal window on escape': function(client) {
client
.urlHash('notebooks')
.urlHash('notebooks/add')
.expect.element('#modal .form-group').to.be.visible.before(2000);
client
.setValue('#modal input[name="name"]', ['Doesn\'t save', client.Keys.ESCAPE])
.expect.element('#notebooks').text.not.to.contain('Doesn\'t save').before(2000);
},
'closes modal window on cancel button click': function(client) {
client
.urlHash('notebooks')
.urlHash('notebooks/add')
.expect.element('#modal .form-group').to.be.visible.before(2000);
client
.setValue('#modal input[name="name"]', ['Doesn\'t save'])
.click('#modal .cancelBtn')
.expect.element('#notebooks').text.not.to.contain('Doesn\'t save').before(2000);
},
};
================================================
FILE: test/spec-ui/tests/apps/notebooks/formEdit.js
================================================
'use strict';
var expect = require('chai').expect,
ids = [];
/**
* Edit notebook form test
*/
module.exports = {
before: function(client) {
client.closeWelcome();
},
after: function(client) {
client.end();
},
'load': function(client) {
client.addNotebook({name: 'notebook 1', parentId: 0});
client.addNotebook({name: 'Sub-notebook', parentId: 'notebook 1'});
client.perform(function(client, done) {
client
.urlHash('notebooks');
// Get all rendered notebooks
client.findAll('#notebooks .list--item', 'data-id', (res) => {
expect(typeof res).to.be.equal('object');
expect(res.length).to.be.equal(2);
ids = res;
done();
});
});
},
'shows notebook edit form': function(client) {
client
.urlHash(`notebooks/edit/${ids[0]}`)
.expect.element('#modal .form-group').to.be.present.before(2000);
client
.clearValue('#modal input[name="name"]')
.pause(200)
.setValue('#modal input[name="name"]', 'Changed-Title')
.expect.element('#modal input[name="name"]').to.have.value.that.contains('Changed-Title');
},
'list re-renders notebooks with new values': function(client) {
client
.setValue('#modal input[name="name"]', [client.Keys.ENTER])
.pause(500)
.expect.element('#notebooks').text.to.contain('Changed-Title');
client.expect.element('#notebooks').text.to.contain('Sub-notebook');
},
'shows notebook form with updated data': function(client) {
client
.urlHash(`notebooks/edit/${ids[1]}`)
.expect.element('#modal .form-group').to.be.visible.before(5000);
client.expect.element('#modal input[name="name"]').value.to.contain('Sub-notebook');
client.expect.element('#modal select[name="parentId"]').text.to.contain('Changed-Title');
},
'shows notebook form with correct notebookId': function(client) {
client
.expect.element('#modal select[name="parentId"]').to.have.value.that.equals(ids[0]);
},
'can update sub-notebooks': function(client) {
client
.clearValue('#modal input[name="name"]')
.setValue('#modal input[name="name"]', ['Sub-CT', client.Keys.ENTER]);
},
're-renders notebooks list with updated data': function(client) {
client
.expect.element('#notebooks').text.to.contain('Sub-CT');
client
.expect.element('#notebooks').text.to.contain('Changed-Title');
},
'doesn\'t update if title is empty': function(client) {
client
.urlHash('notebooks')
.urlHash(`notebooks/edit/${ids[0]}`)
.expect.element('#modal .form-group').to.be.visible.before(2000);
client
.clearValue('#modal input[name="name"]')
.setValue('#modal input[name="name"]', [client.Keys.ENTER]);
client
.expect.element('#notebooks').text.to.contain('Changed-Title');
},
};
================================================
FILE: test/spec-ui/tests/apps/notebooks/list.js
================================================
'use strict';
var expect = require('chai').expect,
ids;
/**
* Notebook list test
*/
module.exports = {
before: function(client, done) {
client.closeWelcome();
client.urlHash('notes');
client.expect.element('#header--add').to.be.visible.before(50000);
client
.pause(100)
.urlHash('notebooks');
client.addNotebook({name: 'notebook 1', parentId: 0});
client.addNotebook({name: 'notebook 4', parentId: 0});
client.addNotebook({name: 'notebook 5', parentId: 'notebook 4'});
client.findAll('#notebooks .list--item', 'data-id', (res) => {
ids = res;
expect(ids.length).to.be.equal(3);
done();
});
},
after: function(client) {
client.end();
},
'shows notebooks list': function(client) {
expect(ids.length).not.to.be.equal(0);
client.expect.element('#header--add').to.be.visible.before(5000);
},
'shows a button that shows menu': function(client) {
client.expect.element('#notebooks .list--buttons .drop-edit').to.be.visible.before(5000);
client.expect.element('#notebooks .list--buttons .dropdown-menu').to.be.not.visible.before(5000);
},
'click on the button shows menu': function(client) {
client.click('#notebooks .list--buttons .drop-edit');
client.expect.element('#notebooks .list--buttons .dropdown-menu').to.be.visible.before(5000);
client.click('#notebooks .list--buttons .drop-edit');
},
'edit button shows notebook form': function(client) {
client
.click('#notebooks .list--buttons .drop-edit')
.click('#notebooks .list--buttons .edit-link');
client.expect.element('#modal .form-group').to.be.present.before(2000);
client.expect.element('#modal .form-group').to.be.visible.before(2000);
client.keys(client.Keys.ESCAPE);
client.expect.element('#modal .form-group').not.to.be.present.before(5000);
},
'remove button shows dialog': function(client) {
client
.click('#notebooks .list--buttons .drop-edit')
.click('#notebooks .list--buttons .remove-link');
client.expect.element('#modal .modal-title').to.be.present.before(2000);
client.expect.element('#modal .modal-title').to.be.visible.before(2000);
client.keys(client.Keys.ESCAPE);
client.expect.element('#modal .form-group').not.to.be.present.before(5000);
client.click('#notebooks .list--buttons .drop-edit');
},
'add button shows notebook form': function(client) {
client
.click('#header--add');
client.expect.element('#modal .modal-title').to.be.present.before(2000);
client.expect.element('#modal .modal-title').to.be.visible.before(2000);
client
.pause(200)
.keys(client.Keys.ESCAPE);
client.expect.element('#modal .form-group').not.to.be.present.before(5000);
},
'navigation keybindings work': function(client) {
client.getValue('#notebooks .list--item.active', function(value) {
client.keys('k');
client.expect.element('#notebooks .list--item.active').value.not.to.be.equal(value).before(5000);
});
client.getValue('#notebooks .list--item.active', function(value) {
client.keys('j');
client.expect.element('#notebooks .list--item.active').value.not.to.be.equal(value).before(5000);
});
},
// SHIFT+3, c, e and c, SHIFT+3, e
'shift+3 shows delete form': function(client) {
client
.keys([client.Keys.SHIFT, '3'])
// Hit Shift again to disable it
.keys(client.Keys.SHIFT);
client.expect.element('#modal .modal-title').to.be.present.before(5000);
client.expect.element('#modal .modal-title').to.be.visible.before(5000);
client.expect.element('#modal .modal').to.have.css('display').which.equals('block').before(5000);
client.click('#modal .close');
client.expect.element('#modal .modal-title').not.to.be.present.before(5000);
},
'`c` shows create form': function(client) {
client.pause(200);
client.keys('c');
client.expect.element('#modal .modal-dialog').to.be.present.before(6000);
client.expect.element('#modal .modal-title').to.be.visible.before(6000);
client.keys(client.Keys.ESCAPE);
client.expect.element('#modal .modal-dialog').not.to.be.present.before(6000);
},
'`e` shows edit form': function(client) {
client.keys('e');
client.expect.element('#modal .modal-title').to.be.present.before(5000);
client.expect.element('#modal .modal-title').to.be.visible.before(2000);
client.click('#modal .cancelBtn');
client.expect.element('#modal .modal-title').not.to.be.present.before(2000);
},
'can navigate to active notes': function(client) {
client.keys(['g', 'i']);
client
.expect.element('#header--title')
.to.have.text.that.equals('All notes')
.before(5000);
},
'can navigate to notebooks from notes': function(client) {
client.keys(['g', 'n']);
client
.expect.element('#header--title')
.to.have.text.that.equals('Notebooks & Tags')
.before(5000);
},
'can navigate to favourite notes': function(client) {
client.keys(['g', 'f']);
client
.expect.element('#header--title')
.to.have.text.that.equals('Favourites')
.before(5000);
client.keys(['g', 'n']);
client
.expect.element('#header--title')
.to.have.text.that.equals('Notebooks & Tags')
.before(5000);
},
'can navigate to removed notes': function(client) {
client.keys(['g', 't']);
client
.expect.element('#header--title')
.to.have.text.that.equals('Trashed')
.before(5000);
client.keys(['g', 'n']);
client
.expect.element('#header--title')
.to.have.text.that.equals('Notebooks & Tags')
.before(5000);
},
'can filter notes by a notebook name': function(client) {
client.perform((client, done) => {
client.getText('#notebooks .list--item.-notebook', (res) => {
client.click('#notebooks .list--item.-notebook');
client
.expect.element('#header--title')
.to.have.text.that.matches(new RegExp(res.value, 'gi'))
.before(5000);
client.keys(['g', 'n']);
client
.expect.element('#header--title')
.to.have.text.that.equals('Notebooks & Tags')
.before(5000);
client.perform(done);
});
});
},
'can filter notes by a notebook name with a keybinding': function(client) {
client.perform((client, done) => {
client.getText('#notebooks .list--item.-notebook', (res) => {
client.keys('j');
client.expect.element('#notebooks .list--item.active').to.be.present.before(5000);
client.keys('o');
client
.expect.element('#header--title')
.to.have.text.that.matches(new RegExp(res.value, 'gi'))
.before(5000);
client.keys(['g', 'n']);
client
.expect.element('#header--title')
.to.have.text.that.equals('Notebooks & Tags')
.before(5000);
client.perform(done);
});
});
},
};
================================================
FILE: test/spec-ui/tests/apps/notebooks/remove.js
================================================
'use strict';
var expect = require('chai').expect,
notebookCount;
/**
* Notebook removal test
*/
module.exports = {
before: function(client, done) {
client.closeWelcome();
client.urlHash('notes')
.expect.element('#header--add').to.be.visible.before(50000);
client.urlHash('notebooks');
client.expect.element('#header--add').to.be.visible.before(5000);
client.elements('css selector', '#notebooks .list--item', (res) => {
notebookCount = res.value.length;
done();
});
},
after: function(client) {
client.end();
},
'can remove a notebook': function(client) {
// Prepare a notebook to delete
client.addNotebook({name: '1.ToRemove', parentId: 0});
client
.urlHash('notes')
.pause(100)
.urlHash('notebooks');
client.expect.element('#notebooks').text.to.contain('1.ToRemove').before(5000);
// Delete notebook
client
.click('#notebooks .list--buttons .drop-edit')
.click('#notebooks .list--buttons .remove-link');
client.expect.element('#modal .modal-title').to.be.present.before(5000);
client.expect.element('#modal .modal-title').to.be.visible.before(5000);
client.click('#modal .btn[data-event="confirm"]');
client.pause(500);
client.expect.element('#notebooks').text.not.to.contain('1.ToRemove').before(5000);
},
'deleted notebooks don\'t re-appear after url change': function(client) {
client
.urlHash('notes')
.pause(1000)
.urlHash('notebooks');
client.expect.element('#notebooks').text.not.to.contain('1.ToRemove').before(5000);
},
'nested notebooks are not removed, but their parentId is changed': function(client) {
// Prepare notebooks
client.addNotebook({name: '1.ToRemove', parentId: 0});
client.addNotebook({name: '1.NestedRemove', parentId: '1.ToRemove'});
client.addNotebook({name: '1.RemoveNested', parentId: '1.ToRemove'});
client
.urlHash('notes')
.pause(100)
.urlHash('notebooks');
client.expect.element('#notebooks').text.to.contain('1.ToRemove').before(5000);
// Delete a notebook
client
.click('#notebooks .list--buttons .drop-edit')
.click('#notebooks .list--buttons .remove-link');
client.expect.element('#modal .modal-title').to.be.present.before(5000);
client.expect.element('#modal .modal-title').to.be.visible.before(5000);
client.click('#modal .btn[data-event="confirm"]');
client.pause(500);
client.expect.element('#notebooks').text.not.to.contain('1.ToRemove').before(5000);
client.expect.element('#notebooks').text.to.contain('1.NestedRemove').before(5000);
client.expect.element('#notebooks').text.to.contain('1.RemoveNested').before(5000);
},
'cleanup': function(client) {
client
.urlHash('notes')
.pause(100)
.urlHash('notebooks');
for (var i = 0, len = 2; i <= len; i++) {
if (i === 2) {
return;
}
client
.keys('j')
.pause(100)
.keys([client.Keys.SHIFT, '3', client.Keys.SHIFT]);
client.expect.element('#modal .modal-title').to.be.present.before(5000);
client.expect.element('#modal .modal-title').to.be.visible.before(5000);
client.click('#modal .btn[data-event="confirm"]');
client.pause(500);
}
},
'shows notebooks in the navbar menu': function(client) {
client
.urlHash('notes')
.pause(100)
.urlHash('notebooks');
client.perform((client, done) => {
client.elements('css selector', '#notebooks .list--item', (res) => {
expect(notebookCount).to.be.equal(res.value.length);
done();
});
});
},
// @TODO add tests for notes behaviour after linked notebooks are deleted
};
================================================
FILE: test/spec-ui/tests/apps/notes/form.js
================================================
'use strict';
var notebookId;
module.exports = {
before: function(client) {
client.closeWelcome();
},
after: function(client) {
client.end();
},
'can change note title': function(client) {
client
.urlHash('notes')
.pause(200)
.urlHash('notes/add')
.expect.element('#editor--input--title').to.be.visible.before(50000);
client
.setValue('#editor--input--title', ['Night'])
.expect.element('#editor--input--title').value.to.contain('Night').before(1000);
},
'shows confirm dialog if title is not empty': function(client) {
client
.setValue('#editor--input--title', [' Watch'])
.click('.editor--cancel')
.expect.element('.modal-dialog').to.be.visible.before(1000);
},
'hides confirm dialog on "Esc"': function(client) {
client
.keys([client.Keys.ESCAPE])
.click('.layout--body.-form')
.waitForElementNotPresent('.modal-dialog', 1000);
},
'closes the form if title is empty': function(client) {
client
.clearValue('#editor--input--title')
.click('.editor--cancel')
.expect.element('.layout--body.-form').not.to.be.present.before(5000);
client.expect.element('.list--group').not.to.be.present.after(1000);
},
'shows notebook add form': function(client) {
client
.urlHash('notes/add')
.expect.element('#editor--input--title').to.be.visible.before(50000);
client.expect.element('.addNotebook').to.be.visible.before(50000);
client
.click('.addNotebook')
.expect.element('.modal--input[name=name]').to.be.present.before(5000);
},
'makes a new notebook active': function(client) {
client
.setValue('.modal--input[name=name]', ['Nightwatch', client.Keys.ENTER])
.pause(1000)
.perform(function(client, done) {
client.getValue('[name="notebookId"]', function(res) {
notebookId = res.value;
client.assert.containsText('option[value="' + notebookId + '"]', 'Nightwatch');
done();
});
});
},
'switches into fullscreen mode': function(client) {
client.expect.element('a[data-mode="fullscreen"]').to.be.present.before(1000);
client
.click('button[title="Mode"]')
.click('a[data-mode="fullscreen"]')
.expect.element('body').to.have.attribute('class').which.does.not.contain('-preview').before(2000);
client.expect.element('body').to.have.attribute('class').which.contains('editor--fullscreen').before(2000);
},
'switches into preview mode': function(client) {
client
.click('button[title="Mode"]')
.click('a[data-mode="preview"]')
.expect.element('body').to.have.attribute('class').which.contains('-preview').before(2000);
},
'switches into normal mode': function(client) {
client
.click('button[title="Mode"]')
.click('a[data-mode="normal"]')
.expect.element('body').to.have.attribute('class').which.does.not.contain('-preview').before(2000);
client.expect.element('body').to.have.attribute('class').which.does.not.contain('editor--fullscreen').before(2000);
},
'closes the form': function(client) {
client
.click('.editor--cancel')
.expect.element('.modal-dialog').to.be.visible.before(1000);
client
.click('.modal-dialog button[data-event="confirm"]')
.expect.element('.layout--body.-form').not.to.be.present.before(5000);
},
'makes previously selected notebook active': function(client) {
client
.urlHash('/notes/f/notebook/q/' + notebookId)
.expect.element('#sidebar--content').to.be.visible.before(50000);
client
.urlHash('/notes/add')
.expect.element('#editor--input--title').to.be.visible.before(50000);
client
.expect.element('[name="notebookId"]').to.have.attribute('value').which.equals(notebookId);
},
'does not save a note if its title is empty': function(client) {
client
.clearValue('#editor--input--title')
.click('.editor--save')
.expect.element('.layout--body.-form').to.be.visible.after(1000);
},
'saves a note': function(client) {
client
.setValue('#editor--input--title', ['Nightwatch'])
.expect.element('#editor--input--title').value.to.contain('Nightwatch').before(1000);
client
.click('.CodeMirror-lines')
.keys(['Nightwatch test content.']);
client
.click('.editor--save')
.expect.element('.layout--body.-form').to.be.not.present.before(5000);
},
'saved the note': function(client) {
client
.urlHash('/notes/')
.expect.element('.list--group').to.be.visible.after(5000);
client.expect.element('.list').text.to.contain('Nightwatch test content.').before(5000);
client.expect.element('.list--group').to.be.present.before(5000);
client.expect.element('.list--item').to.be.present.before(5000);
},
'opens an edit page': function(client) {
client.getAttribute('.list--item', 'data-id', function(res) {
client
.urlHash('/notes/edit/' + res.value)
.expect.element('.layout--body.-form').to.be.visible.before(1500);
});
},
'can change a note': function(client) {
client
.setValue('#editor--input--title', ['Night Watch'])
.expect.element('#editor--input--title').value.to.contain('Night Watch').before(1000);
client
.click('.CodeMirror-lines')
.keys(['Added a new content.']);
client
.keys([client.Keys.CONTROL, 's'])
.expect.element('.layout--body.-form').to.be.not.present.before(5000);
},
'updated the note': function(client) {
client
.urlHash('notes')
.expect.element('.list--group').to.be.visible.after(1000);
client.expect.element('#sidebar--content').text.to.contain('Added a new content');
client.expect.element('#sidebar--content').text.to.contain('Night Watch');
},
};
================================================
FILE: test/spec-ui/tests/apps/notes/list.js
================================================
'use strict';
var expect = require('chai').expect;
module.exports = {
before: function(client) {
client.closeWelcome();
},
after: function(client) {
client.end();
},
'opens #/notes': function(client) {
client
.urlHash('notes')
.expect.element('.list').to.be.present.before(50000);
},
'renders new notes': function(client) {
for (var i = 1; i <= 15; i++) {
client.addNote({title: i + '. Note', content: 'Nightwatch test content ' + i + '.'});
}
client
.urlHash('/notes')
.expect.element('.list').to.be.present.before(2000);
},
'shows pagination buttons': function(client) {
client
.expect.element('.list--pager').to.be.visible.before(2000);
},
'is possible to navigate by pressing "j" key': function(client) {
client
.expect.element('.list--item.active').not.to.be.present.before(500);
client
.pause(300)
.keys(['j'])
.expect.element('.list--item.active').to.be.visible.before(5000);
},
'is possible to navigate by pressing "k" key': function(client) {
client
.pause(300)
.keys(['j'])
.expect.element('.list--item:first-child').to.have.attribute('class').which.does.not.contain('active').before(5000);
client
.pause(300)
.keys(['k'])
.expect.element('.list--item:first-child').to.have.attribute('class').which.contains('active').before(5000);
},
'if it reaches the last note on the page, it navigates to the next page': function(client) {
client.expect.element('#prevPage').to.have.attribute('class').which.contains('disabled');
client.expect.element('#nextPage').to.have.attribute('class').which.does.not.contain('disabled');
for (var i = 0; i < 14; i++) {
client
.pause(300)
.keys(['j']);
}
client.expect.element('.list--item.active').to.be.visible.before(2000);
client.expect.element('#prevPage').to.have.attribute('class').which.does.not.contain('disabled').before(2000);
client.expect.element('#nextPage').to.have.attribute('class').which.contains('disabled').before(2000);
},
'if it reaches the first note on the page, it navigates to the previous page': function(client) {
client.expect.element('#prevPage').to.have.attribute('class').which.does.not.contain('disabled').before(2000);
client.expect.element('#nextPage').to.have.attribute('class').which.contains('disabled').before(2000);
for (var i = 0; i < 10; i++) {
client
.pause(300)
.keys(['k']);
}
client.expect.element('.list--item.active').to.be.visible.before(2000);
client.expect.element('#prevPage').to.have.attribute('class').which.contains('disabled');
client.expect.element('#nextPage').to.have.attribute('class').which.does.not.contain('disabled');
},
// 'can add a new note by pressing "c"': function(client) {
// client
// .keys('c')
// .expect.element('.layout--body.-form').to.be.present.before(2000);
//
// client
// .keys(client.Keys.ESCAPE)
// .expect.element('.layout--body.-form').not.to.be.present.before(2000);
// },
'changes favourite status of an active note if favourite button is clicked': function(client) {
client
.click('.list--group .favorite')
.expect.element('.list--group .icon-favorite').to.be.present.before(2000);
client
.click('.list--group .favorite')
.expect.element('.list--group .icon-favorite').not.to.be.present.before(2000);
client
.click('.list--group .favorite')
.waitForElementPresent('.list--group .icon-favorite', 2000);
},
'navigates to favourite page on "gf" shortcut': function(client) {
client
.keys('gf')
.pause(300)
.assert.urlContains('notes/f/favorite');
},
'navigates to trash page on "gt" shortcut': function(client) {
client
.keys('gt')
.pause(300)
.assert.urlContains('notes/f/trashed');
},
'navigates to active page on "gi" shortcut': function(client) {
client
.keys('gi')
.pause(300)
.url(function(url) {
expect(url.value.search('notes/f/trashed') !== -1).not.to.be.equal(true);
expect(url.value.search('notes/f/favorite') !== -1).not.to.be.equal(true);
expect(url.value.search('notes') !== -1).to.be.equal(true);
});
},
'navigates to notebook page on "gn" shortcut': function(client) {
client
.keys('gn')
.pause(300)
.assert.urlContains('notebooks');
},
'can filter notes by a notebook name': function(client) {
var note = {
title : 'A note with a notebook:' + Math.floor((Math.random() * 10) + 1),
content : 'A note content with a notebook.',
notebook : 'Notebook:' + Math.floor((Math.random() * 10) + 1)
};
client.addNote(note);
client
.keys('gn')
.pause(300)
.expect.element('.list--item.-notebook').to.be.present.before(50000);
client.getAttribute('partial link text', note.notebook, 'data-id', function(res) {
this.assert.equal(typeof res.value !== 'undefined', true);
client
.urlHash('notes/f/notebook/q/' + res.value)
.expect.element('.list').to.be.present.before(50000);
client.expect.element('#sidebar--content').to.have.text.that.contains(note.title).before(5000);
client.elements('css selector', '.list--item', function(res) {
this.assert.equal(res.value.length, 1);
});
});
},
'can filter notes by a tag': function(client) {
var note = {
title : 'A note with a tag:' + Math.floor((Math.random() * 10) + 1),
content : [client.Keys.SHIFT, '3', client.Keys.SHIFT, 'tagname']
};
client
.addNote(note)
.keys('gn');
client
.urlHash('notes/f/tag/q/tagname')
.expect.element('.list').to.be.present.before(50000);
client.expect.element('.list--item').to.be.present.before(50000);
client.expect.element('#sidebar--content').to.have.text.that.contains(note.title).before(5000);
client.elements('css selector', '.list--item', function(res) {
this.assert.equal(res.value.length, 1);
});
},
'can search notes': function(client) {
var note = {
title : 'A unique note title:' + Math.floor((Math.random() * 10) + 1),
content : 'A unique note content'
};
client.addNote(note);
client
.pause(300)
.urlHash('notes/f/search/q/' + note.title)
.expect.element('.list').to.be.present.before(50000);
client.pause(500);
client.expect.element('#sidebar--content').to.have.text.that.contains(note.title).before(5000);
client.elements('css selector', '.list--item.-note', function(res) {
this.assert.equal(res.value.length, 1);
});
},
};
================================================
FILE: test/spec-ui/tests/apps/notes/show.js
================================================
'use strict';
var expect = require('chai').expect;
module.exports = {
before: function(client) {
client.closeWelcome();
},
after: function(client) {
client.end();
},
'wait': function(client) {
client
.urlHash('notes')
.expect.element('.list').to.be.present.before(50000);
},
'can open a note from the list': function(client) {
client
.addNote({title: 'A new note', content: 'Note content\r[ ] A task\r[ ] The second task'})
.addNote({title: 'A second note', content: 'Note content 2\r[ ] A task\r[ ] The second task'})
.urlHash('notebooks')
.pause(200)
.urlHash('notes')
.expect.element('.list').to.be.present.before(5000);
client
.keys(['j'])
.pause(500)
.expect.element('.list--item.active').to.be.visible.before(3000);
client.expect.element('.layout--body.-note').to.be.present.before(2000);
client.assert.urlContains('notes/f/active/show/');
},
'shows edit button': function(client) {
client.expect.element('.note--edit').to.be.visible.before(2000);
},
'shows remove button': function(client) {
client.expect.element('.note--remove').to.be.visible.before(2000);
},
'shows "favourite" button': function(client) {
client.expect.element('.btn--favourite').to.be.visible.before(2000);
},
'has tasks': function(client) {
client.expect.element('.layout--body.-note').to.be.present.before(5000);
client.expect.element('.layout--body.-note .checkbox--input').to.be.present.before(5000);
},
'shows the task progress': function(client) {
client.expect.element('.layout--body.-note .progress-bar').to.be.visible.before(200);
client.expect.element('.layout--body.-note .progress-bar').to.have.css('width', '0%');
},
'changes a task\'s status when a checkbox is clicked': function(client) {
client
.click('.layout--body.-note .task--checkbox')
.expect.element('.layout--body.-note .task--checkbox input:checked').to.be.present.before(5000);
client.pause(500);
},
'changes the task progress %': function(client) {
client.expect.element('.layout--body.-note .progress-bar').to.have.css('width').which.not.equal('0%');
},
'opens the edit page if the edit button is clicked': function(client) {
client
.click('.note--edit')
.expect.element('.layout--body.-form').to.be.present.before(4000);
client
.keys([client.Keys.ESCAPE])
.expect.element('.layout--body.-form').not.to.be.present.before(4000);
},
'removes a note if the remove button is clicked': function(client) {
client
.click('.note--remove')
.expect.element('.modal-dialog').to.be.present.before(2000);
client
.keys([client.Keys.ESCAPE])
.expect.element('.modal-dialog').not.to.be.present.before(2000);
},
'changes favourite status if the button is clicked': function(client) {
client
.click('.btn--favourite')
.pause(300)
.expect.element('.btn--favourite--icon').to.have.attribute('class').which.contains('icon-favorite');
},
'opens edit page if "e" is pressed': function(client) {
client.expect.element('.layout--body.-note').to.be.present.before(2000);
client
.pause(300)
.keys(['e'])
.expect.element('.layout--body.-form').to.be.present.before(4000);
client
.click('.editor--cancel')
.expect.element('.layout--body.-form').not.to.be.present.before(4000);
},
'shows delete dialog on "Shift+3" key combination': function(client) {
client.expect.element('.layout--body.-note').to.be.present.before(2000);
client
.pause(1000)
.keys([client.Keys.SHIFT, '3'])
// Hit Shift key again to disable it
.keys([client.Keys.SHIFT])
.expect.element('.modal-dialog').to.be.present.before(2000);
},
'deletes a note': function(client) {
client.expect.element('.modal-dialog .btn-success').to.be.present.before(2000);
client
.perform(function(cli, done) {
cli.getAttribute('.list--group:first-child .list--item', 'data-id', function(res) {
client
.pause(500)
.click('.modal-dialog .btn-success')
.expect.element('.modal-dialog').not.to.be.present.before(5000);
client.expect.element('.list--group:first-child .list--item').to.be.present.before(2000);
cli
.getAttribute('.list--group:first-child .list--item', 'data-id', function(newRes) {
expect(newRes.value).not.to.be.equal(res.value);
done();
});
});
});
},
};
================================================
FILE: test/spec-ui/tests/apps/settings/general.js
================================================
'use strict';
/**
* Settings list test
// |)}>#
module.exports = {
before: function(client) {
client.closeWelcome();
client.urlHash('notes');
client.expect.element('#header--add').to.be.visible.before(50000);
client.addNote({title: 'hello', content: 'hello'});
client.addNote({title: 'hello2', content: 'hello2'});
client.addNote({title: 'hello3', content: 'hello3'});
client.addNotebook({name: 'AAAAAA', parentId: 0});
client.addNotebook({name: 'AAAAAB', parentId: 0});
client.urlHash('settings');
client.expect.element('.header--title').to.have.text.that.contains('Settings').before(5000);
},
after: function(client) {
client.end();
},
'general tab is open by default': function(client) {
client
.expect.element('.list--settings.active').to.have.text.that.contains('General');
},
'can change how many notes should be displayed': function(client) {
client.expect.element('input[name="pagination"]').to.be.present.before(5000);
// Set pagination to 1
client.clearValue('input[name="pagination"]');
client.setValue('input[name="pagination"]', '1');
client.click('.settings--save');
client.click('.settings--cancel');
client.expect.element('#header--add').to.be.visible.before(50000);
client.urlHash('notes');
client.expect.element('#header--add').to.be.visible.before(50000);
client.findAll('#sidebar--content .list--item.-note', 'data-id', (res) => {
client.assert.equal(res.length, 1);
});
// Set pagination to 10
client.urlHash('settings');
client.expect.element('.header--title').to.have.text.that.contains('Settings').before(5000);
client.expect.element('input[name="pagination"]').to.be.present.before(5000);
client.clearValue('input[name="pagination"]');
client.setValue('input[name="pagination"]', '10');
client.click('.settings--save');
client.click('.settings--cancel');
client.expect.element('#header--add').to.be.visible.before(50000);
client.urlHash('notes');
client.expect.element('#header--add').to.be.visible.before(50000);
client.findAll('#sidebar--content .list--item.-note', 'data-id', (res) => {
client.assert.notEqual(res.length, 1);
});
},
'can change default edit mode': function(client) {
client.urlHash('settings');
client.expect.element('.header--title').to.have.text.that.contains('Settings').before(5000);
client.expect.element('select[name="editMode"]').to.be.present.before(5000);
client.setValue('select[name="editMode"]', 'normal');
client
.click('.settings--save')
.pause(500)
.click('.settings--cancel');
client.expect.element('#header--add').to.be.visible.before(50000);
client.urlHash('notes/add');
client.expect.element('#editor').to.be.present.before(5000);
client.expect.element('#editor').to.be.visible.before(5000);
// :TODO Nightwatch returns wrong classes
// client.pause(1000);
// client.expect.element('.editor--fullscreen').to.be.not.present.after(5000);
// client.expect.element('.-preview').to.be.not.present.after(5000);
},
'can change sort notebooks settings': function(client) {
client.urlHash('settings');
client.expect.element('.header--title').to.have.text.that.contains('Settings').before(5000);
client.expect.element('select[name="sortnotebooks"]').to.be.present.before(5000);
client.setValue('select[name="sortnotebooks"]', 'created');
client.click('.settings--save');
client.click('.settings--cancel');
client.expect.element('#header--add').to.be.visible.before(50000);
client.urlHash('notebooks');
// :TODO FIX sorting
// client.expect.element('#notebooks .list--item.-notebook').to.have.text.that.equals('AAAAAB').before(5000);
},
};
*/
================================================
FILE: test/spec-ui/tests/apps/settings/import.js
================================================
'use strict';
/**
* Notebook list test
// |)}>#
module.exports = {
before: function(client) {
client.closeWelcome();
client.urlHash('settings/importExport');
client.expect.element('.header--title').to.have.text.that.contains('Settings').before(50000);
},
after: function(client) {
client.end();
},
'"Import & Export" tab is active in #/settings/importExports': function(client) {
client
.expect.element('.list--settings.active').to.have.text.that.contains('Import & Export');
},
// @TODO Test importing & exporting json config files
};
*/
================================================
FILE: test/spec-ui/tests/apps/settings/keybindings.js
================================================
'use strict';
var keys;
/**
* Notebook list test
// |)}>#
module.exports = {
before: function(client) {
client.closeWelcome();
keys = {
actionsEdit: 'e',
actionsOpen: 'o',
actionsRemove: 'shift+3',
actionsRotateStar: 's',
appCreateNote: 'c',
// appKeyboardHelp: '?',
// appSearch: '/',
jumpFavorite: 'g f',
jumpInbox: 'g i',
jumpNotebook: 'g n',
jumpOpenTasks: 'g o',
jumpRemoved: 'g t',
navigateBottom: 'j',
navigateTop: 'k',
};
client.urlHash('settings/keybindings');
client.expect.element('.header--title').to.have.text.that.contains('Settings').before(50000);
},
after: function(client) {
client.end();
},
'keybindings tab is active in #/settings/keybindings': function(client) {
client
.expect.element('.list--settings.active').to.have.text.that.contains('Keybindings');
},
'can change keybindings': function(client) {
client.perform(function(client, done) {
for (var name in keys) {
client
.expect.element('[name="' + name + '"]').to.be.present.before(5000);
client
.clearValue('[name="' + name + '"]')
.setValue('[name="' + name + '"]', keys[name].toUpperCase());
}
client
.clearValue('[name="appKeyboardHelp"]')
.setValue('[name="appKeyboardHelp"]', '/');
client
.clearValue('[name="appSearch"]')
.setValue('[name="appSearch"]', '?');
client.perform(() => {done();});
});
},
'keybindings are saved': function(client) {
client.click('.settings--save');
client.click('.settings--cancel');
client.expect.element('#header--add').to.be.visible.before(50000);
client.urlHash('settings/keybindings');
client.expect.element('.header--title').to.have.text.that.contains('Settings').before(5000);
client.perform(function(client, done) {
for (var name in keys) {
client.assert.value('[name="' + name + '"]', keys[name].toUpperCase());
}
client.assert.value('[name="appKeyboardHelp"]', '/');
client.assert.value('[name="appSearch"]', '?');
client.perform(() => {done();});
});
},
'change to old settings': function(client) {
client.perform(function(client, done) {
for (var name in keys) {
client
.clearValue('[name="' + name + '"]')
.setValue('[name="' + name + '"]', keys[name].toUpperCase());
}
client
.clearValue('[name="appKeyboardHelp"]')
.setValue('[name="appKeyboardHelp"]', '/');
client
.clearValue('[name="appSearch"]')
.setValue('[name="appSearch"]', '?');
client.perform(() => {done();});
});
client.click('.settings--save');
client.click('.settings--cancel');
client.expect.element('#header--add').to.be.visible.before(50000);
},
};
*/
================================================
FILE: test/spec-ui/tests/apps/settings/profiles.js
================================================
'use strict';
/**
* Notebook list test
// |)}>#
module.exports = {
before: function(client) {
client.closeWelcome();
client.urlHash('settings/profiles');
client.expect.element('.header--title').to.have.text.that.contains('Settings').before(50000);
},
after: function(client) {
client.end();
},
'profiles tab is active in #/settings/profiles': function(client) {
client
.expect.element('.list--settings.active').to.have.text.that.contains('Profiles');
},
'can add new profiles': function(client) {
client.expect.element('#profileName').to.be.present.before(5000);
client.setValue('#profileName', ['NightwatchProfile', client.Keys.ENTER]);
client.assert.value('#profileName', '');
client.expect.element('.layout--body.-settings').to.have.text.that.contains('NightwatchProfile');
},
'new profiles are saved': function(client) {
client.expect.element('.settings--save').to.be.present.before(5000);
client.click('.settings--save');
client.click('.settings--cancel');
client.expect.element('#header--add').to.be.visible.before(50000);
client.urlHash('settings/profiles');
client.expect.element('.layout--body.-settings').to.be.present.before(5000);
client.expect.element('.header--title').to.have.text.that.contains('Settings').before(5000);
client.expect.element('.layout--body.-settings').to.have.text.that.contains('NightwatchProfile');
},
'new profiles appear in sidemenu': function(client) {
client.urlHash('notes');
client.expect.element('#header--add').to.be.visible.before(50000);
client.click('#header--title');
client.expect.element('.sidemenu.-show').to.be.present.before(5000);
client.expect.element('.sidemenu.-show').to.be.visible.before(5000);
client.expect.element('.sidemenu').to.have.text.that.contains('NightwatchProfile');
},
};
*/
================================================
FILE: test/spec-ui/tests/modules/fuzzySearch/fuzzySearch.js
================================================
'use strict';
/**
* Test fuzzy search module
*/
module.exports = {
before: function(client) {
client.closeWelcome();
},
after: function(client) {
client.end();
},
'wait': function(client) {
client.urlHash('notes');
client.expect.element('#header--add').to.be.visible.before(50000);
client.addNote({title: 'note1', content: 'content1'});
client.addNote({title: 'note2', content: 'content2'});
client.addNote({title: 'note3', content: 'content3'});
client.addNote({title: 'Nightwatch', content: 'Nightwatch'});
},
'can show search form': function(client) {
client.expect.element('#header--sbtn').to.be.present.before(5000);
client.click('#header--sbtn');
client.expect.element('#header--search--input').to.be.present.before(5000);
client.expect.element('#header--search--input').to.be.visible.before(5000);
},
'can search by note\'s name': function(client) {
client.clearValue('#header--search--input');
client.expect.element('#sidebar--fuzzy').text.to.be.equal('');
client
.setValue('#header--search--input', 'note')
.expect.element('#sidebar--fuzzy').text.to.be.not.equal('').before(5000);
client.expect.element('#sidebar--fuzzy').text.to.contain('note1');
client.expect.element('#sidebar--fuzzy').text.to.contain('note2');
client.expect.element('#sidebar--fuzzy').text.to.contain('note3');
client.expect.element('#sidebar--fuzzy').text.to.not.contain('Nightwatch');
},
'can close fuzzySearch module on Escape': function(client) {
client
.setValue('#header--search--input', client.Keys.ESCAPE)
.expect.element('#sidebar--fuzzy').text.to.be.equal('').before(5000);
},
'can search by note\'s content': function(client) {
client.click('#header--sbtn');
client.expect.element('#header--search--input').to.be.present.before(5000);
client.expect.element('#header--search--input').to.be.visible.before(5000);
client.setValue('#header--search--input', 'content');
client.expect.element('#sidebar--fuzzy').text.to.contain('note1').before(5000);
client.expect.element('#sidebar--fuzzy').text.to.contain('note2');
client.expect.element('#sidebar--fuzzy').text.to.contain('note3');
client.expect.element('#sidebar--fuzzy').text.to.not.contain('Nightwatch');
},
'after hitting ESCAPE notes are normally rendered': function(client) {
client
.clearValue('#header--search--input')
.pause(1000)
.setValue('#header--search--input', client.Keys.ESCAPE)
.expect.element('#sidebar--fuzzy').to.be.not.visible.before(5000);
client.expect.element('#header--search--input').to.be.not.visible.before(5000);
client.expect.element('#sidebar--content').to.be.visible.before(5000);
client.expect.element('#sidebar--content').text.to.contain('note1').before(5000);
client.expect.element('#sidebar--content').text.to.contain('note2').before(5000);
client.expect.element('#sidebar--content').text.to.contain('note3').before(5000);
client.expect.element('#sidebar--content').text.to.contain('Nightwatch').before(5000);
},
'redirects to notes search page on `enter`': function(client) {
client.expect.element('#header--sbtn').to.be.visible.before(5000);
client.click('#header--sbtn');
client.expect.element('#header--search--input').to.be.present.before(5000);
client.expect.element('#header--search--input').to.be.visible.before(5000);
client
.clearValue('#header--search--input')
.setValue('#header--search--input', 'note')
.expect.element('#sidebar--fuzzy').text.to.be.not.equal('').before(5000);
client.expect.element('#sidebar--fuzzy').text.to.contain('note1');
client.expect.element('#sidebar--fuzzy').text.to.contain('note2');
client.expect.element('#sidebar--fuzzy').text.to.contain('note3');
client.expect.element('#sidebar--fuzzy').text.to.not.contain('Nightwatch');
client
.setValue('#header--search--input', client.Keys.ENTER)
.assert.urlContains('notes/f/search/q/note');
}
};