1
0
mirror of https://github.com/moparisthebest/mail synced 2024-11-23 01:12:19 -05:00

Fix mail-list-ctrl unit test and move functions to services

This commit is contained in:
Tankred Hase 2014-11-26 12:59:44 +01:00
parent 1c1a5a4d54
commit 03b2e10bc3
8 changed files with 300 additions and 271 deletions

View File

@ -178,6 +178,7 @@ module.exports = function(grunt) {
'test/unit/email/outbox-bo-test.js', 'test/unit/email/outbox-bo-test.js',
'test/unit/email/email-dao-test.js', 'test/unit/email/email-dao-test.js',
'test/unit/email/account-test.js', 'test/unit/email/account-test.js',
'test/unit/email/search-test.js',
'test/unit/controller/app/dialog-ctrl-test.js', 'test/unit/controller/app/dialog-ctrl-test.js',
'test/unit/controller/login/add-account-ctrl-test.js', 'test/unit/controller/login/add-account-ctrl-test.js',
'test/unit/controller/login/create-account-ctrl-test.js', 'test/unit/controller/login/create-account-ctrl-test.js',
@ -194,8 +195,8 @@ module.exports = function(grunt) {
'test/unit/controller/app/contacts-ctrl-test.js', 'test/unit/controller/app/contacts-ctrl-test.js',
'test/unit/controller/app/read-ctrl-test.js', 'test/unit/controller/app/read-ctrl-test.js',
'test/unit/controller/app/navigation-ctrl-test.js', 'test/unit/controller/app/navigation-ctrl-test.js',
/*'test/unit/mail-list-ctrl-test.js', 'test/unit/controller/app/mail-list-ctrl-test.js',
'test/unit/write-ctrl-test.js', /*'test/unit/write-ctrl-test.js',
'test/unit/action-bar-ctrl-test.js',*/ 'test/unit/action-bar-ctrl-test.js',*/
] ]
}, },

View File

@ -11,7 +11,7 @@ var INIT_DISPLAY_LEN = 20,
FOLDER_TYPE_INBOX = 'Inbox', FOLDER_TYPE_INBOX = 'Inbox',
NOTIFICATION_INBOX_TIMEOUT = 5000; NOTIFICATION_INBOX_TIMEOUT = 5000;
var MailListCtrl = function($scope, $routeParams, statusDisplay, notification, email, keychain, dialog) { var MailListCtrl = function($scope, $routeParams, statusDisplay, notification, email, keychain, dialog, search, dummy) {
// //
// Init // Init
@ -116,7 +116,7 @@ var MailListCtrl = function($scope, $routeParams, statusDisplay, notification, e
// in development, display dummy mail objects // in development, display dummy mail objects
if ($routeParams.dev) { if ($routeParams.dev) {
statusDisplay.update('Last update: ', new Date()); statusDisplay.update('Last update: ', new Date());
currentFolder().messages = createDummyMails(); currentFolder().messages = dummy.listMails();
return; return;
} }
@ -186,83 +186,13 @@ var MailListCtrl = function($scope, $routeParams, statusDisplay, notification, e
searchTimeout = setTimeout(function() { searchTimeout = setTimeout(function() {
$scope.$apply(function() { $scope.$apply(function() {
// filter relevant messages // filter relevant messages
$scope.displayMessages = $scope.search(currentFolder().messages, searchText); $scope.displayMessages = search.filter(currentFolder().messages, searchText);
statusDisplay.setSearching(false); statusDisplay.setSearching(false);
statusDisplay.update('Matches in this folder'); statusDisplay.update('Matches in this folder');
}); });
}, 500); }, 500);
}; };
/**
* Do full text search on messages. Parse meta data first
*/
$scope.search = function(messages, searchText) {
// don't filter on empty searchText
if (!searchText) {
return messages;
}
// escape search string
searchText = searchText.replace(/([.*+?^${}()|\[\]\/\\])/g, "\\$1");
// compare all strings (case insensitive)
var regex = new RegExp(searchText, 'i');
function contains(input) {
if (!input) {
return false;
}
return regex.test(input);
}
function checkAddresses(header) {
if (!header || !header.length) {
return false;
}
for (var i = 0; i < header.length; i++) {
if (contains(header[i].name) || contains(header[i].address)) {
return true;
}
}
return false;
}
/**
* Filter meta data first and then only look at plaintext and decrypted message bodies
*/
function matchMetaDataFirst(m) {
// compare subject
if (contains(m.subject)) {
return true;
}
// compares address headers
if (checkAddresses(m.from) || checkAddresses(m.to) || checkAddresses(m.cc) || checkAddresses(m.bcc)) {
return true;
}
// compare plaintext body
if (m.body && !m.encrypted && contains(m.body)) {
return true;
}
// compare decrypted body
if (m.body && m.encrypted && m.decrypted && contains(m.body)) {
return true;
}
// compare plaintex html body
if (m.html && !m.encrypted && contains(m.html)) {
return true;
}
// compare decrypted html body
if (m.html && m.encrypted && m.decrypted && contains(m.html)) {
return true;
}
return false;
}
// user native js Array.filter
return messages.filter(matchMetaDataFirst);
};
/** /**
* Sync current folder when client comes back online * Sync current folder when client comes back online
*/ */
@ -441,72 +371,4 @@ function byUidDescending(a, b) {
} }
} }
// Helper for development mode
function createDummyMails() {
var uid = 1000000;
var Email = function(unread, attachments, answered) {
this.uid = uid--;
this.from = [{
name: 'Whiteout Support',
address: 'support@whiteout.io'
}]; // sender address
this.to = [{
address: 'max.musterman@gmail.com'
}, {
address: 'max.musterman@gmail.com'
}]; // list of receivers
this.cc = [{
address: 'john.doe@gmail.com'
}]; // list of receivers
this.attachments = attachments ? [{
"filename": "a.md",
"filesize": 123,
"mimeType": "text/x-markdown",
"part": "2",
"content": null
}, {
"filename": "b.md",
"filesize": 456,
"mimeType": "text/x-markdown",
"part": "3",
"content": null
}, {
"filename": "c.md",
"filesize": 789,
"mimeType": "text/x-markdown",
"part": "4",
"content": null
}] : [];
this.unread = unread;
this.answered = answered;
this.sentDate = new Date('Thu Sep 19 2013 20:41:23 GMT+0200 (CEST)');
this.subject = 'Getting started'; // Subject line
this.body = 'And a good day to you too sir. \n' +
'\n' +
'Thursday, Apr 24, 2014 3:33 PM safewithme.testuser@gmail.com wrote:\n' +
'> adsfadfasdfasdfasfdasdfasdfas\n' +
'\n' +
'http://example.com\n' +
'\n' +
'> Tuesday, Mar 25, 2014 4:19 PM gianniarcore@gmail.com wrote:\n' +
'>> from 0.7.0.1\n' +
'>>\n' +
'>> God speed!'; // plaintext body
//this.html = '<!DOCTYPE html><html><head></head><body><h1 style="border: 1px solid red; width: 500px;">Hello there' + Math.random() + '</h1></body></html>';
this.encrypted = true;
this.decrypted = true;
};
var dummies = [],
i = 100;
while (i--) {
// every second/third/fourth dummy mail with unread/attachments/answered
dummies.push(new Email((i % 2 === 0), (i % 3 === 0), (i % 5 === 0)));
}
return dummies;
}
module.exports = MailListCtrl; module.exports = MailListCtrl;

View File

@ -7,3 +7,4 @@ require('./pgpbuilder');
require('./email'); require('./email');
require('./outbox'); require('./outbox');
require('./account'); require('./account');
require('./search');

80
src/js/email/search.js Normal file
View File

@ -0,0 +1,80 @@
'use strict';
var ngModule = angular.module('woEmail');
ngModule.service('search', Search);
module.exports = Search;
function Search() {}
/**
* Do full text search on messages. Parse meta data first.
* @param {Array} messages The messages to be filtered
* @param {String} query The text query used to filter messages
* @return {Array} The filtered messages
*/
Search.prototype.filter = function(messages, query) {
// don't filter on empty query
if (!query) {
return messages;
}
// escape search string
query = query.replace(/([.*+?^${}()|\[\]\/\\])/g, "\\$1");
// compare all strings (case insensitive)
var regex = new RegExp(query, 'i');
function contains(input) {
if (!input) {
return false;
}
return regex.test(input);
}
function checkAddresses(header) {
if (!header || !header.length) {
return false;
}
for (var i = 0; i < header.length; i++) {
if (contains(header[i].name) || contains(header[i].address)) {
return true;
}
}
return false;
}
/**
* Filter meta data first and then only look at plaintext and decrypted message bodies
*/
function matchMetaDataFirst(m) {
// compare subject
if (contains(m.subject)) {
return true;
}
// compares address headers
if (checkAddresses(m.from) || checkAddresses(m.to) || checkAddresses(m.cc) || checkAddresses(m.bcc)) {
return true;
}
// compare plaintext body
if (m.body && !m.encrypted && contains(m.body)) {
return true;
}
// compare decrypted body
if (m.body && m.encrypted && m.decrypted && contains(m.body)) {
return true;
}
// compare plaintex html body
if (m.html && !m.encrypted && contains(m.html)) {
return true;
}
// compare decrypted html body
if (m.html && m.encrypted && m.decrypted && contains(m.html)) {
return true;
}
return false;
}
// user native js Array.filter
return messages.filter(matchMetaDataFirst);
};

73
src/js/util/dummy.js Normal file
View File

@ -0,0 +1,73 @@
'use strict';
var ngModule = angular.module('woUtil');
ngModule.service('dummy', Dummy);
module.exports = Dummy;
function Dummy() {}
Dummy.prototype.listMails = function() {
var uid = 1000000;
var Email = function(unread, attachments, answered) {
this.uid = uid--;
this.from = [{
name: 'Whiteout Support',
address: 'support@whiteout.io'
}]; // sender address
this.to = [{
address: 'max.musterman@gmail.com'
}, {
address: 'max.musterman@gmail.com'
}]; // list of receivers
this.cc = [{
address: 'john.doe@gmail.com'
}]; // list of receivers
this.attachments = attachments ? [{
"filename": "a.md",
"filesize": 123,
"mimeType": "text/x-markdown",
"part": "2",
"content": null
}, {
"filename": "b.md",
"filesize": 456,
"mimeType": "text/x-markdown",
"part": "3",
"content": null
}, {
"filename": "c.md",
"filesize": 789,
"mimeType": "text/x-markdown",
"part": "4",
"content": null
}] : [];
this.unread = unread;
this.answered = answered;
this.sentDate = new Date('Thu Sep 19 2013 20:41:23 GMT+0200 (CEST)');
this.subject = 'Getting started'; // Subject line
this.body = 'And a good day to you too sir. \n' +
'\n' +
'Thursday, Apr 24, 2014 3:33 PM safewithme.testuser@gmail.com wrote:\n' +
'> adsfadfasdfasdfasfdasdfasdfas\n' +
'\n' +
'http://example.com\n' +
'\n' +
'> Tuesday, Mar 25, 2014 4:19 PM gianniarcore@gmail.com wrote:\n' +
'>> from 0.7.0.1\n' +
'>>\n' +
'>> God speed!'; // plaintext body
//this.html = '<!DOCTYPE html><html><head></head><body><h1 style="border: 1px solid red; width: 500px;">Hello there' + Math.random() + '</h1></body></html>';
this.encrypted = true;
this.decrypted = true;
};
var dummies = [],
i = 100;
while (i--) {
// every second/third/fourth dummy mail with unread/attachments/answered
dummies.push(new Email((i % 2 === 0), (i % 3 === 0), (i % 5 === 0)));
}
return dummies;
};

View File

@ -3,6 +3,7 @@
angular.module('woUtil', []); angular.module('woUtil', []);
require('./axe'); require('./axe');
require('./dummy');
require('./dialog'); require('./dialog');
require('./connection-doctor'); require('./connection-doctor');
require('./update/update-handler'); require('./update/update-handler');

View File

@ -1,17 +1,17 @@
'use strict'; 'use strict';
var mocks = angular.mock, var mocks = angular.mock,
MailListCtrl = require('../../src/js/controller/mail-list'), MailListCtrl = require('../../../../src/js/controller/app/mail-list'),
EmailDAO = require('../../src/js/dao/email-dao'), EmailDAO = require('../../../../src/js/email/email'),
DeviceStorageDAO = require('../../src/js/dao/devicestorage-dao'), KeychainDAO = require('../../../../src/js/service/keychain'),
KeychainDAO = require('../../src/js/dao/keychain-dao'), StatusDisplay = require('../../../../src/js/util/status-display'),
appController = require('../../src/js/app-controller'), Dialog = require('../../../../src/js/util/dialog'),
notification = require('../../src/js/util/notification'); Search = require('../../../../src/js/email/search');
chai.config.includeStack = true; chai.config.includeStack = true;
describe('Mail List controller unit test', function() { describe('Mail List controller unit test', function() {
var scope, ctrl, origEmailDao, emailDaoMock, keychainMock, deviceStorageMock, var scope, ctrl, statusDisplayMock, notificationMock, emailMock, keychainMock, dialogMock, searchMock,
emailAddress, emails, emailAddress, emails,
hasChrome, hasSocket, hasRuntime, hasIdentity; hasChrome, hasSocket, hasRuntime, hasIdentity;
@ -41,26 +41,21 @@ describe('Mail List controller unit test', function() {
}, { }, {
unread: true unread: true
}]; }];
appController._outboxBo = {
pendingEmails: emails
};
origEmailDao = appController._emailDao;
emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._emailDao = emailDaoMock;
emailAddress = 'fred@foo.com'; emailAddress = 'fred@foo.com';
emailDaoMock._account = {
emailAddress: emailAddress, notificationMock = {
create: function() {},
close: function() {}
}; };
statusDisplayMock = sinon.createStubInstance(StatusDisplay);
emailMock = sinon.createStubInstance(EmailDAO);
keychainMock = sinon.createStubInstance(KeychainDAO); keychainMock = sinon.createStubInstance(KeychainDAO);
appController._keychain = keychainMock; dialogMock = sinon.createStubInstance(Dialog);
searchMock = sinon.createStubInstance(Search);
deviceStorageMock = sinon.createStubInstance(DeviceStorageDAO); angular.module('maillisttest', ['woEmail', 'woServices', 'woUtil']);
emailDaoMock._devicestorage = deviceStorageMock;
angular.module('maillisttest', []);
mocks.module('maillisttest'); mocks.module('maillisttest');
mocks.inject(function($rootScope, $controller) { mocks.inject(function($rootScope, $controller) {
scope = $rootScope.$new(); scope = $rootScope.$new();
@ -73,7 +68,13 @@ describe('Mail List controller unit test', function() {
scope.loadVisibleBodies = function() {}; scope.loadVisibleBodies = function() {};
ctrl = $controller(MailListCtrl, { ctrl = $controller(MailListCtrl, {
$scope: scope, $scope: scope,
$routeParams: {} $routeParams: {},
statusDisplay: statusDisplayMock,
notification: notificationMock,
email: emailMock,
keychain: keychainMock,
dialog: dialogMock,
search: searchMock
}); });
}); });
}); });
@ -91,9 +92,6 @@ describe('Mail List controller unit test', function() {
if (!hasIdentity) { if (!hasIdentity) {
delete window.chrome.identity; delete window.chrome.identity;
} }
// restore the module
appController._emailDao = origEmailDao;
}); });
describe('displayMore', function() { describe('displayMore', function() {
@ -137,104 +135,21 @@ describe('Mail List controller unit test', function() {
it('should show initial message on empty', function() { it('should show initial message on empty', function() {
scope.displaySearchResults(); scope.displaySearchResults();
expect(scope.state.mailList.searching).to.be.false; expect(statusDisplayMock.setSearching.withArgs(false).calledOnce).to.be.true;
expect(scope.state.mailList.lastUpdateLbl).to.equal('Online'); expect(statusDisplayMock.update.withArgs('Online').calledOnce).to.be.true;
expect(scope.displayMessages.length).to.equal(2); expect(scope.displayMessages.length).to.equal(2);
}); });
it('should show initial message on empty', function() { it('should show initial message on empty', function() {
var searchStub = sinon.stub(scope, 'search'); searchMock.filter.returns(['a']);
searchStub.returns(['a']);
scope.displaySearchResults('query'); scope.displaySearchResults('query');
expect(scope.state.mailList.searching).to.be.true; expect(statusDisplayMock.setSearching.withArgs(true).calledOnce).to.be.true;
expect(scope.state.mailList.lastUpdateLbl).to.equal('Searching ...'); expect(statusDisplayMock.update.withArgs('Searching ...').calledOnce).to.be.true;
clock.tick(500); clock.tick(500);
expect(scope.displayMessages).to.deep.equal(['a']); expect(scope.displayMessages).to.deep.equal(['a']);
expect(scope.state.mailList.searching).to.be.false; expect(statusDisplayMock.setSearching.withArgs(false).calledOnce).to.be.true;
expect(scope.state.mailList.lastUpdateLbl).to.equal('Matches in this folder'); expect(statusDisplayMock.update.withArgs('Matches in this folder').calledOnce).to.be.true;
});
});
describe('search', function() {
var message1 = {
to: [{
name: 'name1',
address: 'address1'
}],
subject: 'subject1',
body: 'body1',
html: 'html1'
},
message2 = {
to: [{
name: 'name2',
address: 'address2'
}],
subject: 'subject2',
body: 'body2',
html: 'html2'
},
message3 = {
to: [{
name: 'name3',
address: 'address3'
}],
subject: 'subject3',
body: 'body1',
html: 'html1',
encrypted: true
},
message4 = {
to: [{
name: 'name4',
address: 'address4'
}],
subject: 'subject4',
body: 'body1',
html: 'html1',
encrypted: true,
decrypted: true
},
testMessages = [message1, message2, message3, message4];
it('return same messages array on empty query string', function() {
var result = scope.search(testMessages, '');
expect(result).to.equal(testMessages);
});
it('return message1 on matching subject', function() {
var result = scope.search(testMessages, 'subject1');
expect(result.length).to.equal(1);
expect(result[0]).to.equal(message1);
});
it('return message1 on matching name', function() {
var result = scope.search(testMessages, 'name1');
expect(result.length).to.equal(1);
expect(result[0]).to.equal(message1);
});
it('return message1 on matching address', function() {
var result = scope.search(testMessages, 'address1');
expect(result.length).to.equal(1);
expect(result[0]).to.equal(message1);
});
it('return plaintext and decrypted messages on matching body', function() {
var result = scope.search(testMessages, 'body1');
expect(result.length).to.equal(2);
expect(result[0]).to.equal(message1);
expect(result[1]).to.equal(message4);
});
it('return plaintext and decrypted messages on matching html', function() {
var result = scope.search(testMessages, 'html1');
expect(result.length).to.equal(2);
expect(result[0]).to.equal(message1);
expect(result[1]).to.equal(message4);
}); });
}); });
@ -251,7 +166,7 @@ describe('Mail List controller unit test', function() {
}); });
afterEach(function() { afterEach(function() {
notification.create.restore(); notificationMock.create.restore();
}); });
it('should succeed for single mail', function(done) { it('should succeed for single mail', function(done) {
@ -264,7 +179,7 @@ describe('Mail List controller unit test', function() {
unread: true unread: true
}; };
sinon.stub(notification, 'create', function(opts) { sinon.stub(notificationMock, 'create', function(opts) {
expect(opts.title).to.equal(mail.from[0].address); expect(opts.title).to.equal(mail.from[0].address);
expect(opts.message).to.equal(mail.subject); expect(opts.message).to.equal(mail.subject);
@ -280,7 +195,7 @@ describe('Mail List controller unit test', function() {
} }
}; };
emailDaoMock.onIncomingMessage([mail]); emailMock.onIncomingMessage([mail]);
}); });
it('should succeed for multiple mails', function(done) { it('should succeed for multiple mails', function(done) {
@ -307,7 +222,7 @@ describe('Mail List controller unit test', function() {
unread: false unread: false
}]; }];
sinon.stub(notification, 'create', function(opts) { sinon.stub(notificationMock, 'create', function(opts) {
expect(opts.title).to.equal('2 new messages'); expect(opts.title).to.equal('2 new messages');
expect(opts.message).to.equal(mails[0].subject + '\n' + mails[1].subject); expect(opts.message).to.equal(mails[0].subject + '\n' + mails[1].subject);
@ -323,7 +238,7 @@ describe('Mail List controller unit test', function() {
} }
}; };
emailDaoMock.onIncomingMessage(mails); emailMock.onIncomingMessage(mails);
}); });
}); });
@ -336,14 +251,14 @@ describe('Mail List controller unit test', function() {
}; };
scope.getBody(); scope.getBody();
expect(emailDaoMock.getBody.calledOnce).to.be.true; expect(emailMock.getBody.calledOnce).to.be.true;
}); });
}); });
describe('select', function() { describe('select', function() {
it('should decrypt, focus mark an unread mail as read', function() { it('should decrypt, focus mark an unread mail as read', function() {
scope.pendingNotifications = ['asd']; scope.pendingNotifications = ['asd'];
sinon.stub(notification, 'close'); sinon.stub(notificationMock, 'close');
var mail = { var mail = {
from: [{ from: [{
@ -372,13 +287,13 @@ describe('Mail List controller unit test', function() {
scope.select(mail); scope.select(mail);
expect(emailDaoMock.decryptBody.calledOnce).to.be.true; expect(emailMock.decryptBody.calledOnce).to.be.true;
expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true; expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true;
expect(scope.state.mailList.selected).to.equal(mail); expect(scope.state.mailList.selected).to.equal(mail);
expect(notification.close.calledWith('asd')).to.be.true; expect(notificationMock.close.calledWith('asd')).to.be.true;
expect(notification.close.calledOnce).to.be.true; expect(notificationMock.close.calledOnce).to.be.true;
notification.close.restore(); notificationMock.close.restore();
}); });
it('should decrypt and focus a read mail', function() { it('should decrypt and focus a read mail', function() {
@ -407,7 +322,7 @@ describe('Mail List controller unit test', function() {
scope.select(mail); scope.select(mail);
expect(emailDaoMock.decryptBody.calledOnce).to.be.true; expect(emailMock.decryptBody.calledOnce).to.be.true;
expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true; expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true;
expect(scope.state.mailList.selected).to.equal(mail); expect(scope.state.mailList.selected).to.equal(mail);
}); });

View File

@ -0,0 +1,96 @@
'use strict';
describe('Search Service unit test', function() {
var search;
beforeEach(function() {
angular.module('search-test', ['woEmail']);
angular.mock.module('search-test');
angular.mock.inject(function($injector) {
search = $injector.get('search');
});
});
afterEach(function() {});
describe('filter', function() {
var message1 = {
to: [{
name: 'name1',
address: 'address1'
}],
subject: 'subject1',
body: 'body1',
html: 'html1'
},
message2 = {
to: [{
name: 'name2',
address: 'address2'
}],
subject: 'subject2',
body: 'body2',
html: 'html2'
},
message3 = {
to: [{
name: 'name3',
address: 'address3'
}],
subject: 'subject3',
body: 'body1',
html: 'html1',
encrypted: true
},
message4 = {
to: [{
name: 'name4',
address: 'address4'
}],
subject: 'subject4',
body: 'body1',
html: 'html1',
encrypted: true,
decrypted: true
},
testMessages = [message1, message2, message3, message4];
it('return same messages array on empty query string', function() {
var result = search.filter(testMessages, '');
expect(result).to.equal(testMessages);
});
it('return message1 on matching subject', function() {
var result = search.filter(testMessages, 'subject1');
expect(result.length).to.equal(1);
expect(result[0]).to.equal(message1);
});
it('return message1 on matching name', function() {
var result = search.filter(testMessages, 'name1');
expect(result.length).to.equal(1);
expect(result[0]).to.equal(message1);
});
it('return message1 on matching address', function() {
var result = search.filter(testMessages, 'address1');
expect(result.length).to.equal(1);
expect(result[0]).to.equal(message1);
});
it('return plaintext and decrypted messages on matching body', function() {
var result = search.filter(testMessages, 'body1');
expect(result.length).to.equal(2);
expect(result[0]).to.equal(message1);
expect(result[1]).to.equal(message4);
});
it('return plaintext and decrypted messages on matching html', function() {
var result = search.filter(testMessages, 'html1');
expect(result.length).to.equal(2);
expect(result[0]).to.equal(message1);
expect(result[1]).to.equal(message4);
});
});
});