mirror of
https://github.com/moparisthebest/mail
synced 2025-02-16 15:10:10 -05:00
Merge branch 'dev/email-dao-refactoring'
This commit is contained in:
commit
3790009260
@ -38,7 +38,8 @@ define(function(require) {
|
|||||||
},
|
},
|
||||||
checkOutboxInterval: 5000,
|
checkOutboxInterval: 5000,
|
||||||
iconPath: '/img/icon.png',
|
iconPath: '/img/icon.png',
|
||||||
verificationUrl: '/verify/'
|
verificationUrl: '/verify/',
|
||||||
|
verificationUuidLength: 36
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -155,7 +155,7 @@ define(function(require) {
|
|||||||
*/
|
*/
|
||||||
self.init = function(userId, token, callback) {
|
self.init = function(userId, token, callback) {
|
||||||
var auth, imapOptions, smtpOptions, certificate,
|
var auth, imapOptions, smtpOptions, certificate,
|
||||||
lawnchairDao, restDao, pubkeyDao, invitationDao,
|
lawnchairDao, restDao, pubkeyDao, invitationDao, emailDao,
|
||||||
keychain, imapClient, smtpClient, pgp, userStorage, xhr;
|
keychain, imapClient, smtpClient, pgp, userStorage, xhr;
|
||||||
|
|
||||||
// fetch pinned local ssl certificate
|
// fetch pinned local ssl certificate
|
||||||
@ -213,10 +213,10 @@ define(function(require) {
|
|||||||
pgp = new PGP();
|
pgp = new PGP();
|
||||||
self._crypto = pgp;
|
self._crypto = pgp;
|
||||||
userStorage = new DeviceStorageDAO(lawnchairDao);
|
userStorage = new DeviceStorageDAO(lawnchairDao);
|
||||||
self._emailDao = new EmailDAO(keychain, imapClient, smtpClient, pgp, userStorage);
|
self._emailDao = emailDao = new EmailDAO(keychain, imapClient, smtpClient, pgp, userStorage);
|
||||||
|
|
||||||
invitationDao = new InvitationDAO(restDao);
|
invitationDao = new InvitationDAO(restDao);
|
||||||
self._outboxBo = new OutboxBO(self._emailDao, invitationDao);
|
self._outboxBo = new OutboxBO(emailDao, keychain, userStorage, invitationDao);
|
||||||
|
|
||||||
// init email dao
|
// init email dao
|
||||||
var account = {
|
var account = {
|
||||||
@ -224,7 +224,13 @@ define(function(require) {
|
|||||||
asymKeySize: config.asymKeySize
|
asymKeySize: config.asymKeySize
|
||||||
};
|
};
|
||||||
|
|
||||||
self._emailDao.init(account, callback);
|
self._emailDao.init({
|
||||||
|
account: account
|
||||||
|
}, function(err, keypair) {
|
||||||
|
self._outboxBo.init();
|
||||||
|
callback(err, keypair);
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -12,10 +12,36 @@ define(function(require) {
|
|||||||
* The local outbox takes care of the emails before they are being sent.
|
* The local outbox takes care of the emails before they are being sent.
|
||||||
* It also checks periodically if there are any mails in the local device storage to be sent.
|
* It also checks periodically if there are any mails in the local device storage to be sent.
|
||||||
*/
|
*/
|
||||||
var OutboxBO = function(emailDao, invitationDao) {
|
var OutboxBO = function(emailDao, keychain, devicestorage, invitationDao) {
|
||||||
|
/** @private */
|
||||||
this._emailDao = emailDao;
|
this._emailDao = emailDao;
|
||||||
|
|
||||||
|
/** @private */
|
||||||
|
this._keychain = keychain;
|
||||||
|
|
||||||
|
/** @private */
|
||||||
|
this._devicestorage = devicestorage;
|
||||||
|
|
||||||
|
|
||||||
|
/** @private */
|
||||||
this._invitationDao = invitationDao;
|
this._invitationDao = invitationDao;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Semaphore-esque flag to avoid 'concurrent' calls to _processOutbox when the timeout fires, but a call is still in process.
|
||||||
|
* @private */
|
||||||
this._outboxBusy = false;
|
this._outboxBusy = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pending, unsent emails stored in the outbox. Updated on each call to _processOutbox
|
||||||
|
* @public */
|
||||||
|
this.pendingEmails = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
OutboxBO.prototype.init = function() {
|
||||||
|
var outboxFolder = _.findWhere(this._emailDao._account.folders, {
|
||||||
|
type: 'Outbox'
|
||||||
|
});
|
||||||
|
outboxFolder.messages = this.pendingEmails;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,7 +86,7 @@ define(function(require) {
|
|||||||
self._outboxBusy = true;
|
self._outboxBusy = true;
|
||||||
|
|
||||||
// get last item from outbox
|
// get last item from outbox
|
||||||
self._emailDao._devicestorage.listItems(dbType, 0, null, function(err, pending) {
|
self._emailDao.list(function(err, pending) {
|
||||||
if (err) {
|
if (err) {
|
||||||
self._outboxBusy = false;
|
self._outboxBusy = false;
|
||||||
callback(err);
|
callback(err);
|
||||||
@ -70,6 +96,12 @@ define(function(require) {
|
|||||||
// update outbox folder count
|
// update outbox folder count
|
||||||
emails = pending;
|
emails = pending;
|
||||||
|
|
||||||
|
// fill all the pending mails into the pending mails array
|
||||||
|
self.pendingEmails.length = 0; //fastest way to empty an array
|
||||||
|
pending.forEach(function(i) {
|
||||||
|
self.pendingEmails.push(i);
|
||||||
|
});
|
||||||
|
|
||||||
// sending pending mails
|
// sending pending mails
|
||||||
processMails();
|
processMails();
|
||||||
});
|
});
|
||||||
@ -80,12 +112,12 @@ define(function(require) {
|
|||||||
if (emails.length === 0) {
|
if (emails.length === 0) {
|
||||||
// in the navigation controller, this updates the folder count
|
// in the navigation controller, this updates the folder count
|
||||||
self._outboxBusy = false;
|
self._outboxBusy = false;
|
||||||
callback(null, 0);
|
callback(null, self.pendingEmails.length);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// in the navigation controller, this updates the folder count
|
// in the navigation controller, this updates the folder count
|
||||||
callback(null, emails.length);
|
callback(null, self.pendingEmails.length);
|
||||||
var email = emails.shift();
|
var email = emails.shift();
|
||||||
checkReceivers(email);
|
checkReceivers(email);
|
||||||
}
|
}
|
||||||
@ -107,7 +139,7 @@ define(function(require) {
|
|||||||
|
|
||||||
// find out if there are unregistered users
|
// find out if there are unregistered users
|
||||||
email.to.forEach(function(recipient) {
|
email.to.forEach(function(recipient) {
|
||||||
self._emailDao._keychain.getReceiverPublicKey(recipient.address, function(err, key) {
|
self._keychain.getReceiverPublicKey(recipient.address, function(err, key) {
|
||||||
if (err) {
|
if (err) {
|
||||||
self._outboxBusy = false;
|
self._outboxBusy = false;
|
||||||
callback(err);
|
callback(err);
|
||||||
@ -187,7 +219,7 @@ define(function(require) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// send invitation mail
|
// send invitation mail
|
||||||
self._emailDao.send(invitationMail, function(err) {
|
self._emailDao.sendPlaintext(invitationMail, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
self._outboxBusy = false;
|
self._outboxBusy = false;
|
||||||
callback(err);
|
callback(err);
|
||||||
@ -199,7 +231,10 @@ define(function(require) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function sendEncrypted(email) {
|
function sendEncrypted(email) {
|
||||||
self._emailDao.encryptedSend(email, function(err) {
|
removeFromPendingMails(email);
|
||||||
|
self._emailDao.sendEncrypted({
|
||||||
|
email: email
|
||||||
|
}, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
self._outboxBusy = false;
|
self._outboxBusy = false;
|
||||||
callback(err);
|
callback(err);
|
||||||
@ -210,6 +245,12 @@ define(function(require) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update the member so that the outbox can visualize
|
||||||
|
function removeFromPendingMails(email) {
|
||||||
|
var i = self.pendingEmails.indexOf(email);
|
||||||
|
self.pendingEmails.splice(i, 1);
|
||||||
|
}
|
||||||
|
|
||||||
function removeFromStorage(id) {
|
function removeFromStorage(id) {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
self._outboxBusy = false;
|
self._outboxBusy = false;
|
||||||
@ -221,7 +262,7 @@ define(function(require) {
|
|||||||
|
|
||||||
// delete email from local storage
|
// delete email from local storage
|
||||||
var key = dbType + '_' + id;
|
var key = dbType + '_' + id;
|
||||||
self._emailDao._devicestorage.removeList(key, function(err) {
|
self._devicestorage.removeList(key, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
self._outboxBusy = false;
|
self._outboxBusy = false;
|
||||||
callback(err);
|
callback(err);
|
||||||
|
@ -37,7 +37,11 @@ define(function(require) {
|
|||||||
handleError(err);
|
handleError(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
emailDao.unlock(keypair, $scope.passphrase, onUnlock);
|
|
||||||
|
emailDao.unlock({
|
||||||
|
keypair: keypair,
|
||||||
|
passphrase: $scope.passphrase
|
||||||
|
}, onUnlock);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,10 @@ define(function(require) {
|
|||||||
encryptedKey: $scope.key.privateKeyArmored
|
encryptedKey: $scope.key.privateKeyArmored
|
||||||
};
|
};
|
||||||
|
|
||||||
emailDao.unlock(keypair, $scope.passphrase, function(err) {
|
emailDao.unlock({
|
||||||
|
keypair: keypair,
|
||||||
|
passphrase: $scope.passphrase
|
||||||
|
}, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
$scope.incorrect = true;
|
$scope.incorrect = true;
|
||||||
$scope.onError(err);
|
$scope.onError(err);
|
||||||
|
@ -45,15 +45,7 @@ define(function(require) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// login to imap backend
|
redirect(availableKeys);
|
||||||
appController._emailDao.imapLogin(function(err) {
|
|
||||||
if (err) {
|
|
||||||
$scope.onError(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
redirect(availableKeys);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,23 @@
|
|||||||
define(function(require) {
|
define(function(require) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var _ = require('underscore'),
|
var angular = require('angular'),
|
||||||
angular = require('angular'),
|
_ = require('underscore'),
|
||||||
appController = require('js/app-controller'),
|
appController = require('js/app-controller'),
|
||||||
IScroll = require('iscroll'),
|
IScroll = require('iscroll'),
|
||||||
str = require('js/app-config').string,
|
str = require('js/app-config').string,
|
||||||
cfg = require('js/app-config').config,
|
cfg = require('js/app-config').config,
|
||||||
emailDao;
|
emailDao, outboxBo;
|
||||||
|
|
||||||
var MailListCtrl = function($scope) {
|
var MailListCtrl = function($scope) {
|
||||||
var offset = 0,
|
|
||||||
num = 100,
|
|
||||||
firstSelect = true;
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Init
|
// Init
|
||||||
//
|
//
|
||||||
|
|
||||||
emailDao = appController._emailDao;
|
emailDao = appController._emailDao;
|
||||||
|
outboxBo = appController._outboxBo;
|
||||||
|
|
||||||
|
// push handler
|
||||||
if (emailDao) {
|
if (emailDao) {
|
||||||
emailDao.onIncomingMessage = function(email) {
|
emailDao.onIncomingMessage = function(email) {
|
||||||
if (email.subject.indexOf(str.subjectPrefix) === -1) {
|
if (email.subject.indexOf(str.subjectPrefix) === -1) {
|
||||||
@ -41,7 +40,7 @@ define(function(require) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
email = _.findWhere($scope.emails, {
|
email = _.findWhere(getFolder().messages, {
|
||||||
uid: uid
|
uid: uid
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -62,29 +61,39 @@ define(function(require) {
|
|||||||
|
|
||||||
$scope.state.mailList.selected = email;
|
$scope.state.mailList.selected = email;
|
||||||
|
|
||||||
// mark selected message as 'read'
|
// // mark selected message as 'read'
|
||||||
markAsRead(email);
|
// markAsRead(email);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.synchronize = function(callback) {
|
$scope.synchronize = function(callback) {
|
||||||
|
// if we're in the outbox, don't do an imap sync
|
||||||
|
if (getFolder().type === 'Outbox') {
|
||||||
|
updateStatus('Last update: ', new Date());
|
||||||
|
displayEmails(outboxBo.pendingEmails);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
updateStatus('Syncing ...');
|
updateStatus('Syncing ...');
|
||||||
// sync from imap to local db
|
|
||||||
syncImapFolder({
|
// let email dao handle sync transparently
|
||||||
folder: getFolder().path,
|
emailDao.sync({
|
||||||
offset: -num,
|
folder: getFolder().path
|
||||||
num: offset
|
}, function(err) {
|
||||||
}, function() {
|
if (err) {
|
||||||
// list again from local db after syncing
|
updateStatus('Error on sync!');
|
||||||
listLocalMessages({
|
$scope.onError(err);
|
||||||
folder: getFolder().path,
|
return;
|
||||||
offset: offset,
|
}
|
||||||
num: num
|
|
||||||
}, function() {
|
// sort emails
|
||||||
updateStatus('Last update: ', new Date());
|
displayEmails(getFolder().messages);
|
||||||
if (callback) {
|
// display last update
|
||||||
callback();
|
updateStatus('Last update: ', new Date());
|
||||||
}
|
$scope.$apply();
|
||||||
});
|
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -93,88 +102,67 @@ define(function(require) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var index, trashFolder;
|
var index, currentFolder, outboxFolder;
|
||||||
|
|
||||||
trashFolder = _.findWhere($scope.folders, {
|
currentFolder = getFolder();
|
||||||
type: 'Trash'
|
// trashFolder = _.findWhere($scope.folders, {
|
||||||
|
// type: 'Trash'
|
||||||
|
// });
|
||||||
|
outboxFolder = _.findWhere($scope.account.folders, {
|
||||||
|
type: 'Outbox'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (getFolder() === trashFolder) {
|
if (currentFolder === outboxFolder) {
|
||||||
$scope.state.dialog = {
|
$scope.onError({
|
||||||
open: true,
|
errMsg: 'Deleting messages from the outbox is not yet supported.'
|
||||||
title: 'Delete',
|
});
|
||||||
message: 'Delete this message permanently?',
|
|
||||||
callback: function(ok) {
|
|
||||||
if (!ok) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
removeLocalAndShowNext();
|
|
||||||
removeRemote();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeLocalAndShowNext();
|
removeAndShowNext();
|
||||||
removeRemote();
|
$scope.synchronize();
|
||||||
|
|
||||||
function removeLocalAndShowNext() {
|
function removeAndShowNext() {
|
||||||
index = $scope.emails.indexOf(email);
|
index = getFolder().messages.indexOf(email);
|
||||||
// show the next mail
|
// show the next mail
|
||||||
if ($scope.emails.length > 1) {
|
if (getFolder().messages.length > 1) {
|
||||||
// if we're about to delete the last entry of the array, show the previous (i.e. the one below in the list),
|
// if we're about to delete the last entry of the array, show the previous (i.e. the one below in the list),
|
||||||
// otherwise show the next one (i.e. the one above in the list)
|
// otherwise show the next one (i.e. the one above in the list)
|
||||||
$scope.select(_.last($scope.emails) === email ? $scope.emails[index - 1] : $scope.emails[index + 1]);
|
$scope.select(_.last(getFolder().messages) === email ? getFolder().messages[index - 1] : getFolder().messages[index + 1]);
|
||||||
} else {
|
} else {
|
||||||
// if we have only one email in the array, show nothing
|
// if we have only one email in the array, show nothing
|
||||||
$scope.select();
|
$scope.select();
|
||||||
$scope.state.mailList.selected = undefined;
|
$scope.state.mailList.selected = undefined;
|
||||||
}
|
}
|
||||||
$scope.emails.splice(index, 1);
|
getFolder().messages.splice(index, 1);
|
||||||
}
|
|
||||||
|
|
||||||
function removeRemote() {
|
|
||||||
if (getFolder() === trashFolder) {
|
|
||||||
emailDao.imapDeleteMessage({
|
|
||||||
folder: getFolder().path,
|
|
||||||
uid: email.uid
|
|
||||||
}, moved);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
emailDao.imapMoveMessage({
|
|
||||||
folder: getFolder().path,
|
|
||||||
uid: email.uid,
|
|
||||||
destination: trashFolder.path
|
|
||||||
}, moved);
|
|
||||||
}
|
|
||||||
|
|
||||||
function moved(err) {
|
|
||||||
if (err) {
|
|
||||||
$scope.emails.splice(index, 0, email);
|
|
||||||
$scope.onError(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.$watch('state.nav.currentFolder', function() {
|
$scope._stopWatchTask = $scope.$watch('state.nav.currentFolder', function() {
|
||||||
if (!getFolder()) {
|
if (!getFolder()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// production... in chrome packaged app
|
// development... display dummy mail objects
|
||||||
if (window.chrome && chrome.identity) {
|
if (!window.chrome || !chrome.identity) {
|
||||||
initList();
|
updateStatus('Last update: ', new Date());
|
||||||
|
getFolder().messages = createDummyMails();
|
||||||
|
displayEmails(getFolder().messages);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// development... display dummy mail objects
|
// production... in chrome packaged app
|
||||||
firstSelect = true;
|
|
||||||
updateStatus('Last update: ', new Date());
|
// if we're in the outbox, read directly from there.
|
||||||
$scope.emails = createDummyMails();
|
if (getFolder().type === 'Outbox') {
|
||||||
$scope.select($scope.emails[0]);
|
updateStatus('Last update: ', new Date());
|
||||||
|
displayEmails(outboxBo.pendingEmails);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
displayEmails(getFolder().messages);
|
||||||
|
|
||||||
|
$scope.synchronize();
|
||||||
});
|
});
|
||||||
|
|
||||||
// share local scope functions with root state
|
// share local scope functions with root state
|
||||||
@ -196,60 +184,6 @@ define(function(require) {
|
|||||||
}, function() {});
|
}, function() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
function initList() {
|
|
||||||
updateStatus('Read cache ...');
|
|
||||||
|
|
||||||
// list messaged from local db
|
|
||||||
listLocalMessages({
|
|
||||||
folder: getFolder().path,
|
|
||||||
offset: offset,
|
|
||||||
num: num
|
|
||||||
}, function sync() {
|
|
||||||
updateStatus('Syncing ...');
|
|
||||||
$scope.$apply();
|
|
||||||
|
|
||||||
// sync imap folder to local db
|
|
||||||
$scope.synchronize();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function syncImapFolder(options, callback) {
|
|
||||||
emailDao.unreadMessages(getFolder().path, function(err, unreadCount) {
|
|
||||||
if (err) {
|
|
||||||
updateStatus('Error on sync!');
|
|
||||||
$scope.onError(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// set unread count in folder model
|
|
||||||
getFolder().count = unreadCount;
|
|
||||||
$scope.$apply();
|
|
||||||
|
|
||||||
emailDao.imapSync(options, function(err) {
|
|
||||||
if (err) {
|
|
||||||
updateStatus('Error on sync!');
|
|
||||||
$scope.onError(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function listLocalMessages(options, callback) {
|
|
||||||
firstSelect = true;
|
|
||||||
emailDao.listMessages(options, function(err, emails) {
|
|
||||||
if (err) {
|
|
||||||
updateStatus('Error listing cache!');
|
|
||||||
$scope.onError(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(emails);
|
|
||||||
displayEmails(emails);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateStatus(lbl, time) {
|
function updateStatus(lbl, time) {
|
||||||
$scope.lastUpdateLbl = lbl;
|
$scope.lastUpdateLbl = lbl;
|
||||||
$scope.lastUpdate = (time) ? time : '';
|
$scope.lastUpdate = (time) ? time : '';
|
||||||
@ -257,59 +191,54 @@ define(function(require) {
|
|||||||
|
|
||||||
function displayEmails(emails) {
|
function displayEmails(emails) {
|
||||||
if (!emails || emails.length < 1) {
|
if (!emails || emails.length < 1) {
|
||||||
$scope.emails = [];
|
|
||||||
$scope.select();
|
$scope.select();
|
||||||
$scope.$apply();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort by uid
|
if (!$scope.state.mailList.selected) {
|
||||||
emails = _.sortBy(emails, function(e) {
|
// select first message
|
||||||
return -e.uid;
|
$scope.select(emails[emails.length - 1]);
|
||||||
});
|
}
|
||||||
|
|
||||||
$scope.emails = emails;
|
|
||||||
$scope.select($scope.emails[0]);
|
|
||||||
$scope.$apply();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFolder() {
|
function getFolder() {
|
||||||
return $scope.state.nav.currentFolder;
|
return $scope.state.nav.currentFolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
function markAsRead(email) {
|
// function markAsRead(email) {
|
||||||
// don't mark top selected email automatically
|
// // marking mails as read is meaningless in the outbox
|
||||||
if (firstSelect) {
|
// if (getFolder().type === 'Outbox') {
|
||||||
firstSelect = false;
|
// return;
|
||||||
return;
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
$scope.state.read.toggle(true);
|
// $scope.state.read.toggle(true);
|
||||||
if (!window.chrome || !chrome.socket) {
|
// if (!window.chrome || !chrome.socket) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (!email.unread) {
|
// if (!email.unread) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
email.unread = false;
|
// email.unread = false;
|
||||||
emailDao.imapMarkMessageRead({
|
// emailDao.imapMarkMessageRead({
|
||||||
folder: getFolder().path,
|
// folder: getFolder().path,
|
||||||
uid: email.uid
|
// uid: email.uid
|
||||||
}, function(err) {
|
// }, function(err) {
|
||||||
if (err) {
|
// if (err) {
|
||||||
updateStatus('Error marking read!');
|
// updateStatus('Error marking read!');
|
||||||
$scope.onError(err);
|
// $scope.onError(err);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
};
|
};
|
||||||
|
|
||||||
function createDummyMails() {
|
function createDummyMails() {
|
||||||
|
var uid = 0;
|
||||||
|
|
||||||
var Email = function(unread, attachments, answered, html) {
|
var Email = function(unread, attachments, answered, html) {
|
||||||
this.uid = '1';
|
this.uid = uid++;
|
||||||
this.from = [{
|
this.from = [{
|
||||||
name: 'Whiteout Support',
|
name: 'Whiteout Support',
|
||||||
address: 'support@whiteout.io'
|
address: 'support@whiteout.io'
|
||||||
@ -336,17 +265,17 @@ define(function(require) {
|
|||||||
//
|
//
|
||||||
|
|
||||||
var ngModule = angular.module('mail-list', []);
|
var ngModule = angular.module('mail-list', []);
|
||||||
ngModule.directive('ngIscroll', function($parse) {
|
ngModule.directive('ngIscroll', function() {
|
||||||
return {
|
return {
|
||||||
link: function(scope, elm, attrs) {
|
link: function(scope, elm, attrs) {
|
||||||
var model = $parse(attrs.ngIscroll);
|
var model = attrs.ngIscroll;
|
||||||
scope.$watch(model, function() {
|
scope.$watch(model, function() {
|
||||||
var myScroll;
|
var myScroll;
|
||||||
// activate iscroll
|
// activate iscroll
|
||||||
myScroll = new IScroll(elm[0], {
|
myScroll = new IScroll(elm[0], {
|
||||||
mouseWheel: true
|
mouseWheel: true
|
||||||
});
|
});
|
||||||
});
|
}, true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -42,7 +42,7 @@ define(function(require) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var outbox = _.findWhere($scope.folders, {
|
var outbox = _.findWhere($scope.account.folders, {
|
||||||
type: 'Outbox'
|
type: 'Outbox'
|
||||||
});
|
});
|
||||||
outbox.count = count;
|
outbox.count = count;
|
||||||
@ -54,40 +54,29 @@ define(function(require) {
|
|||||||
//
|
//
|
||||||
|
|
||||||
// init folders
|
// init folders
|
||||||
initFolders(function(folders) {
|
initFolders();
|
||||||
$scope.folders = folders;
|
// select inbox as the current folder on init
|
||||||
// select inbox as the current folder on init
|
$scope.openFolder($scope.account.folders[0]);
|
||||||
$scope.openFolder($scope.folders[0]);
|
|
||||||
});
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// helper functions
|
// helper functions
|
||||||
//
|
//
|
||||||
|
|
||||||
function initFolders(callback) {
|
function initFolders() {
|
||||||
if (window.chrome && chrome.identity) {
|
if (window.chrome && chrome.identity) {
|
||||||
emailDao.imapListFolders(function(err, folders) {
|
// get pointer to account/folder/message tree on root scope
|
||||||
if (err) {
|
$scope.$root.account = emailDao._account;
|
||||||
$scope.onError(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
folders.forEach(function(f) {
|
// start checking outbox periodically
|
||||||
f.count = 0;
|
outboxBo.startChecking($scope.onOutboxUpdate);
|
||||||
});
|
// make function available globally for write controller
|
||||||
|
$scope.emptyOutbox = outboxBo._processOutbox.bind(outboxBo);
|
||||||
// start checking outbox periodically
|
|
||||||
outboxBo.startChecking($scope.onOutboxUpdate);
|
|
||||||
// make function available globally for write controller
|
|
||||||
$scope.emptyOutbox = outboxBo._processOutbox;
|
|
||||||
|
|
||||||
callback(folders);
|
|
||||||
$scope.$apply();
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
callback([{
|
// attach dummy folders for development
|
||||||
|
$scope.$root.account = {};
|
||||||
|
$scope.account.folders = [{
|
||||||
type: 'Inbox',
|
type: 'Inbox',
|
||||||
count: 2,
|
count: 2,
|
||||||
path: 'INBOX'
|
path: 'INBOX'
|
||||||
@ -107,7 +96,7 @@ define(function(require) {
|
|||||||
type: 'Trash',
|
type: 'Trash',
|
||||||
count: 0,
|
count: 0,
|
||||||
path: 'TRASH'
|
path: 'TRASH'
|
||||||
}]);
|
}];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -159,9 +159,7 @@ define(function(require) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// set an id for the email and store in outbox
|
emailDao.store(email, function(err) {
|
||||||
email.id = util.UUID();
|
|
||||||
emailDao._devicestorage.storeList([email], 'email_OUTBOX', function(err) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
$scope.onError(err);
|
$scope.onError(err);
|
||||||
return;
|
return;
|
||||||
@ -185,7 +183,7 @@ define(function(require) {
|
|||||||
$scope.replyTo.answered = true;
|
$scope.replyTo.answered = true;
|
||||||
|
|
||||||
// mark remote imap object
|
// mark remote imap object
|
||||||
emailDao.imapMarkAnswered({
|
emailDao.markAnswered({
|
||||||
uid: $scope.replyTo.uid,
|
uid: $scope.replyTo.uid,
|
||||||
folder: $scope.state.nav.currentFolder.path
|
folder: $scope.state.nav.currentFolder.path
|
||||||
}, $scope.onError);
|
}, $scope.onError);
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -4,9 +4,9 @@
|
|||||||
<h2>{{state.nav.currentFolder.type}}</h2>
|
<h2>{{state.nav.currentFolder.type}}</h2>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="list-wrapper" ng-iscroll="emails">
|
<div class="list-wrapper" ng-iscroll="state.nav.currentFolder.messages">
|
||||||
<ul class="mail-list">
|
<ul class="mail-list">
|
||||||
<li ng-class="{'mail-list-active': email === state.mailList.selected, 'mail-list-attachment': email.attachments !== undefined && email.attachments.length > 0, 'mail-list-unread': email.unread && !email.answered, 'mail-list-replied': email.answered}" ng-click="select(email)" ng-repeat="email in emails">
|
<li ng-class="{'mail-list-active': email === state.mailList.selected, 'mail-list-attachment': email.attachments !== undefined && email.attachments.length > 0, 'mail-list-unread': email.unread && !email.answered, 'mail-list-replied': email.answered}" ng-click="select(email)" ng-repeat="email in state.nav.currentFolder.messages | orderBy:'uid':true">
|
||||||
<h3>{{email.from[0].name || email.from[0].address}}</h3>
|
<h3>{{email.from[0].name || email.from[0].address}}</h3>
|
||||||
<div class="head">
|
<div class="head">
|
||||||
<p class="subject">{{email.subject || 'No subject'}}</p>
|
<p class="subject">{{email.subject || 'No subject'}}</p>
|
||||||
@ -17,7 +17,7 @@
|
|||||||
</ul><!--/.mail-list-->
|
</ul><!--/.mail-list-->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer ng-class="{syncing: lastUpdateLbl === 'Syncing ...'}" ng-click="synchronize()">
|
<footer ng-class="{syncing: account.busy}" ng-click="synchronize()">
|
||||||
<span class="spinner"></span>
|
<span class="spinner"></span>
|
||||||
<span class="text">{{lastUpdateLbl}} {{lastUpdate | date:'shortTime'}}</span>
|
<span class="text">{{lastUpdateLbl}} {{lastUpdate | date:'shortTime'}}</span>
|
||||||
</footer>
|
</footer>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<ul class="nav-primary">
|
<ul class="nav-primary">
|
||||||
<li ng-repeat="folder in folders" ng-switch="folder.count !== undefined">
|
<li ng-repeat="folder in account.folders" ng-switch="folder.count !== undefined">
|
||||||
<a href="#" ng-click="openFolder(folder); $event.preventDefault()">
|
<a href="#" ng-click="openFolder(folder); $event.preventDefault()">
|
||||||
{{folder.type}}
|
{{folder.type}}
|
||||||
<span class="label-wrapper" ng-switch-when="true"><span class="label label-light">{{folder.count}}</span></span>
|
<span class="label-wrapper" ng-switch-when="true"><span class="label label-light">{{folder.count}}</span></span>
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -21,8 +21,8 @@ define(function(require) {
|
|||||||
var hasChrome, hasIdentity;
|
var hasChrome, hasIdentity;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
hasChrome = !!window.chrome;
|
hasChrome = !! window.chrome;
|
||||||
hasIdentity = !!window.chrome.identity;
|
hasIdentity = !! window.chrome.identity;
|
||||||
window.chrome = window.chrome || {};
|
window.chrome = window.chrome || {};
|
||||||
window.chrome.identity = window.chrome.identity || {};
|
window.chrome.identity = window.chrome.identity || {};
|
||||||
|
|
||||||
@ -66,15 +66,12 @@ define(function(require) {
|
|||||||
publicKey: 'b'
|
publicKey: 'b'
|
||||||
});
|
});
|
||||||
|
|
||||||
emailDaoMock.imapLogin.yields();
|
|
||||||
|
|
||||||
angular.module('logintest', []);
|
angular.module('logintest', []);
|
||||||
mocks.module('logintest');
|
mocks.module('logintest');
|
||||||
mocks.inject(function($controller, $rootScope, $location) {
|
mocks.inject(function($controller, $rootScope, $location) {
|
||||||
location = $location;
|
location = $location;
|
||||||
sinon.stub(location, 'path', function(path) {
|
sinon.stub(location, 'path', function(path) {
|
||||||
expect(path).to.equal('/login-existing');
|
expect(path).to.equal('/login-existing');
|
||||||
expect(emailDaoMock.imapLogin.calledOnce).to.be.true;
|
|
||||||
expect(startAppStub.calledOnce).to.be.true;
|
expect(startAppStub.calledOnce).to.be.true;
|
||||||
expect(checkForUpdateStub.calledOnce).to.be.true;
|
expect(checkForUpdateStub.calledOnce).to.be.true;
|
||||||
expect(fetchOAuthStub.calledOnce).to.be.true;
|
expect(fetchOAuthStub.calledOnce).to.be.true;
|
||||||
@ -103,15 +100,12 @@ define(function(require) {
|
|||||||
publicKey: 'b'
|
publicKey: 'b'
|
||||||
});
|
});
|
||||||
|
|
||||||
emailDaoMock.imapLogin.yields();
|
|
||||||
|
|
||||||
angular.module('logintest', []);
|
angular.module('logintest', []);
|
||||||
mocks.module('logintest');
|
mocks.module('logintest');
|
||||||
mocks.inject(function($controller, $rootScope, $location) {
|
mocks.inject(function($controller, $rootScope, $location) {
|
||||||
location = $location;
|
location = $location;
|
||||||
sinon.stub(location, 'path', function(path) {
|
sinon.stub(location, 'path', function(path) {
|
||||||
expect(path).to.equal('/login-new-device');
|
expect(path).to.equal('/login-new-device');
|
||||||
expect(emailDaoMock.imapLogin.calledOnce).to.be.true;
|
|
||||||
expect(startAppStub.calledOnce).to.be.true;
|
expect(startAppStub.calledOnce).to.be.true;
|
||||||
expect(checkForUpdateStub.calledOnce).to.be.true;
|
expect(checkForUpdateStub.calledOnce).to.be.true;
|
||||||
expect(fetchOAuthStub.calledOnce).to.be.true;
|
expect(fetchOAuthStub.calledOnce).to.be.true;
|
||||||
@ -138,15 +132,12 @@ define(function(require) {
|
|||||||
initStub = sinon.stub(appController, 'init');
|
initStub = sinon.stub(appController, 'init');
|
||||||
initStub.yields();
|
initStub.yields();
|
||||||
|
|
||||||
emailDaoMock.imapLogin.yields();
|
|
||||||
|
|
||||||
angular.module('logintest', []);
|
angular.module('logintest', []);
|
||||||
mocks.module('logintest');
|
mocks.module('logintest');
|
||||||
mocks.inject(function($controller, $rootScope, $location) {
|
mocks.inject(function($controller, $rootScope, $location) {
|
||||||
location = $location;
|
location = $location;
|
||||||
sinon.stub(location, 'path', function(path) {
|
sinon.stub(location, 'path', function(path) {
|
||||||
expect(path).to.equal('/login-initial');
|
expect(path).to.equal('/login-initial');
|
||||||
expect(emailDaoMock.imapLogin.calledOnce).to.be.true;
|
|
||||||
expect(startAppStub.calledOnce).to.be.true;
|
expect(startAppStub.calledOnce).to.be.true;
|
||||||
expect(checkForUpdateStub.calledOnce).to.be.true;
|
expect(checkForUpdateStub.calledOnce).to.be.true;
|
||||||
expect(fetchOAuthStub.calledOnce).to.be.true;
|
expect(fetchOAuthStub.calledOnce).to.be.true;
|
||||||
|
@ -71,7 +71,10 @@ define(function(require) {
|
|||||||
pathSpy = sinon.spy(location, 'path');
|
pathSpy = sinon.spy(location, 'path');
|
||||||
scope.passphrase = passphrase;
|
scope.passphrase = passphrase;
|
||||||
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, keypair);
|
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, keypair);
|
||||||
emailDaoMock.unlock.withArgs(keypair, passphrase).yields(null);
|
emailDaoMock.unlock.withArgs({
|
||||||
|
keypair: keypair,
|
||||||
|
passphrase: passphrase
|
||||||
|
}).yields();
|
||||||
|
|
||||||
|
|
||||||
scope.confirmPassphrase();
|
scope.confirmPassphrase();
|
||||||
|
@ -93,7 +93,7 @@ define(function(require) {
|
|||||||
_id: keyId,
|
_id: keyId,
|
||||||
publicKey: 'a'
|
publicKey: 'a'
|
||||||
});
|
});
|
||||||
emailDaoMock.unlock.withArgs(sinon.match.any, passphrase).yields();
|
emailDaoMock.unlock.yields();
|
||||||
keychainMock.putUserKeyPair.yields({
|
keychainMock.putUserKeyPair.yields({
|
||||||
errMsg: 'yo mamma.'
|
errMsg: 'yo mamma.'
|
||||||
});
|
});
|
||||||
@ -115,7 +115,7 @@ define(function(require) {
|
|||||||
_id: keyId,
|
_id: keyId,
|
||||||
publicKey: 'a'
|
publicKey: 'a'
|
||||||
});
|
});
|
||||||
emailDaoMock.unlock.withArgs(sinon.match.any, passphrase).yields({
|
emailDaoMock.unlock.yields({
|
||||||
errMsg: 'yo mamma.'
|
errMsg: 'yo mamma.'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ define(function(require) {
|
|||||||
|
|
||||||
describe('Mail List controller unit test', function() {
|
describe('Mail List controller unit test', function() {
|
||||||
var scope, ctrl, origEmailDao, emailDaoMock, keychainMock, deviceStorageMock,
|
var scope, ctrl, origEmailDao, emailDaoMock, keychainMock, deviceStorageMock,
|
||||||
emailAddress, notificationClickedHandler,
|
emailAddress, notificationClickedHandler, emails,
|
||||||
hasChrome, hasNotifications, hasSocket, hasRuntime, hasIdentity;
|
hasChrome, hasNotifications, hasSocket, hasRuntime, hasIdentity;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
@ -44,6 +44,18 @@ define(function(require) {
|
|||||||
if (!hasIdentity) {
|
if (!hasIdentity) {
|
||||||
window.chrome.identity = {};
|
window.chrome.identity = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emails = [{
|
||||||
|
unread: true
|
||||||
|
}, {
|
||||||
|
unread: true
|
||||||
|
}, {
|
||||||
|
unread: true
|
||||||
|
}];
|
||||||
|
appController._outboxBo = {
|
||||||
|
pendingEmails: emails
|
||||||
|
};
|
||||||
|
|
||||||
origEmailDao = appController._emailDao;
|
origEmailDao = appController._emailDao;
|
||||||
emailDaoMock = sinon.createStubInstance(EmailDAO);
|
emailDaoMock = sinon.createStubInstance(EmailDAO);
|
||||||
appController._emailDao = emailDaoMock;
|
appController._emailDao = emailDaoMock;
|
||||||
@ -86,7 +98,7 @@ define(function(require) {
|
|||||||
if (!hasIdentity) {
|
if (!hasIdentity) {
|
||||||
delete window.chrome.identity;
|
delete window.chrome.identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
// restore the module
|
// restore the module
|
||||||
appController._emailDao = origEmailDao;
|
appController._emailDao = origEmailDao;
|
||||||
});
|
});
|
||||||
@ -97,7 +109,7 @@ define(function(require) {
|
|||||||
expect(scope.synchronize).to.exist;
|
expect(scope.synchronize).to.exist;
|
||||||
expect(scope.remove).to.exist;
|
expect(scope.remove).to.exist;
|
||||||
expect(scope.state.mailList).to.exist;
|
expect(scope.state.mailList).to.exist;
|
||||||
expect(emailDaoMock.onIncomingMessage).to.exist;
|
// expect(emailDaoMock.onIncomingMessage).to.exist;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -105,6 +117,8 @@ define(function(require) {
|
|||||||
it('should focus mail and not mark it read', function(done) {
|
it('should focus mail and not mark it read', function(done) {
|
||||||
var uid, mail, currentFolder;
|
var uid, mail, currentFolder;
|
||||||
|
|
||||||
|
scope._stopWatchTask();
|
||||||
|
|
||||||
uid = 123;
|
uid = 123;
|
||||||
mail = {
|
mail = {
|
||||||
uid: uid,
|
uid: uid,
|
||||||
@ -122,20 +136,12 @@ define(function(require) {
|
|||||||
toggle: function() {}
|
toggle: function() {}
|
||||||
};
|
};
|
||||||
scope.emails = [mail];
|
scope.emails = [mail];
|
||||||
emailDaoMock.imapMarkMessageRead.withArgs({
|
emailDaoMock.sync.yieldsAsync();
|
||||||
folder: currentFolder,
|
|
||||||
uid: uid
|
|
||||||
}).yields();
|
|
||||||
emailDaoMock.unreadMessages.yieldsAsync(null, 10);
|
|
||||||
emailDaoMock.imapSync.yieldsAsync();
|
|
||||||
emailDaoMock.listMessages.yieldsAsync(null, [mail]);
|
|
||||||
window.chrome.notifications.create = function(id, opts) {
|
window.chrome.notifications.create = function(id, opts) {
|
||||||
expect(id).to.equal('123');
|
expect(id).to.equal('123');
|
||||||
expect(opts.type).to.equal('basic');
|
expect(opts.type).to.equal('basic');
|
||||||
expect(opts.message).to.equal('asdasd');
|
expect(opts.message).to.equal('asdasd');
|
||||||
expect(opts.title).to.equal('asd');
|
expect(opts.title).to.equal('asd');
|
||||||
expect(scope.state.mailList.selected).to.deep.equal(mail);
|
|
||||||
expect(emailDaoMock.imapMarkMessageRead.callCount).to.equal(0);
|
|
||||||
done();
|
done();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -144,77 +150,123 @@ define(function(require) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('clicking push notification', function() {
|
describe('clicking push notification', function() {
|
||||||
it('should focus mail and mark it read', function() {
|
it('should focus mail', function() {
|
||||||
var uid, mail, currentFolder;
|
var mail, currentFolder;
|
||||||
|
|
||||||
|
scope._stopWatchTask();
|
||||||
|
|
||||||
uid = 123;
|
|
||||||
mail = {
|
mail = {
|
||||||
uid: uid,
|
uid: 123,
|
||||||
from: [{
|
from: [{
|
||||||
address: 'asd'
|
address: 'asd'
|
||||||
}],
|
}],
|
||||||
subject: '[whiteout] asdasd',
|
subject: '[whiteout] asdasd',
|
||||||
unread: true
|
unread: true
|
||||||
};
|
};
|
||||||
currentFolder = 'asd';
|
currentFolder = {
|
||||||
|
type: 'asd',
|
||||||
|
messages: [mail]
|
||||||
|
};
|
||||||
scope.state.nav = {
|
scope.state.nav = {
|
||||||
currentFolder: currentFolder
|
currentFolder: currentFolder
|
||||||
};
|
};
|
||||||
scope.state.read = {
|
|
||||||
toggle: function() {}
|
|
||||||
};
|
|
||||||
scope.emails = [mail];
|
|
||||||
emailDaoMock.imapMarkMessageRead.withArgs({
|
|
||||||
folder: currentFolder,
|
|
||||||
uid: uid
|
|
||||||
}).yields();
|
|
||||||
|
|
||||||
notificationClickedHandler('123'); // first select, irrelevant
|
|
||||||
notificationClickedHandler('123');
|
notificationClickedHandler('123');
|
||||||
|
|
||||||
expect(scope.state.mailList.selected).to.deep.equal(mail);
|
expect(scope.state.mailList.selected).to.equal(mail);
|
||||||
expect(emailDaoMock.imapMarkMessageRead.callCount).to.be.at.least(1);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('remove', function() {
|
|
||||||
it('should not delete without a selected mail', function() {
|
|
||||||
scope.remove();
|
|
||||||
|
|
||||||
expect(emailDaoMock.imapDeleteMessage.called).to.be.false;
|
describe('synchronize', function() {
|
||||||
});
|
it('should do imap sync and display mails', function(done) {
|
||||||
|
scope._stopWatchTask();
|
||||||
|
|
||||||
it('should delete the selected mail from trash folder after clicking ok', function() {
|
emailDaoMock.sync.yieldsAsync();
|
||||||
var uid, mail, currentFolder;
|
|
||||||
|
|
||||||
uid = 123;
|
var currentFolder = {
|
||||||
mail = {
|
type: 'Inbox',
|
||||||
uid: uid,
|
messages: emails
|
||||||
from: [{
|
|
||||||
address: 'asd'
|
|
||||||
}],
|
|
||||||
subject: '[whiteout] asdasd',
|
|
||||||
unread: true
|
|
||||||
};
|
|
||||||
scope.emails = [mail];
|
|
||||||
currentFolder = {
|
|
||||||
type: 'Trash'
|
|
||||||
};
|
};
|
||||||
scope.folders = [currentFolder];
|
scope.folders = [currentFolder];
|
||||||
scope.state.nav = {
|
scope.state.nav = {
|
||||||
currentFolder: currentFolder
|
currentFolder: currentFolder
|
||||||
};
|
};
|
||||||
emailDaoMock.imapDeleteMessage.yields();
|
|
||||||
|
|
||||||
scope.remove(mail);
|
scope.synchronize(function() {
|
||||||
scope.state.dialog.callback(true);
|
expect(scope.state.nav.currentFolder.messages).to.deep.equal(emails);
|
||||||
|
expect(scope.state.mailList.selected).to.exist;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
expect(emailDaoMock.imapDeleteMessage.calledOnce).to.be.true;
|
|
||||||
expect(scope.state.mailList.selected).to.not.exist;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should move the selected mail to the trash folder', function() {
|
it('should read directly from outbox instead of doing a full imap sync', function() {
|
||||||
var uid, mail, currentFolder, trashFolder;
|
scope._stopWatchTask();
|
||||||
|
|
||||||
|
var currentFolder = {
|
||||||
|
type: 'Outbox'
|
||||||
|
};
|
||||||
|
scope.folders = [currentFolder];
|
||||||
|
scope.state.nav = {
|
||||||
|
currentFolder: currentFolder
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.synchronize();
|
||||||
|
|
||||||
|
// emails array is also used as the outbox's pending mail
|
||||||
|
expect(scope.state.mailList.selected).to.deep.equal(emails[0]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('remove', function() {
|
||||||
|
it('should not delete without a selected mail', function() {
|
||||||
|
scope.remove();
|
||||||
|
|
||||||
|
expect(emailDaoMock.sync.called).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not delete from the outbox', function(done) {
|
||||||
|
var currentFolder, mail;
|
||||||
|
|
||||||
|
scope._stopWatchTask();
|
||||||
|
|
||||||
|
scope.account = {};
|
||||||
|
mail = {
|
||||||
|
uid: 123,
|
||||||
|
from: [{
|
||||||
|
address: 'asd'
|
||||||
|
}],
|
||||||
|
subject: '[whiteout] asdasd',
|
||||||
|
unread: true
|
||||||
|
};
|
||||||
|
currentFolder = {
|
||||||
|
type: 'Outbox',
|
||||||
|
path: 'OUTBOX',
|
||||||
|
messages: [mail]
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.emails = [mail];
|
||||||
|
scope.account.folders = [currentFolder];
|
||||||
|
scope.state.nav = {
|
||||||
|
currentFolder: currentFolder
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.onError = function(err) {
|
||||||
|
expect(err).to.exist; // would normally display the notification
|
||||||
|
expect(emailDaoMock.sync.called).to.be.false;
|
||||||
|
done();
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.remove(mail);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete the selected mail', function() {
|
||||||
|
var uid, mail, currentFolder;
|
||||||
|
|
||||||
|
scope._stopWatchTask();
|
||||||
|
|
||||||
|
scope.account = {};
|
||||||
uid = 123;
|
uid = 123;
|
||||||
mail = {
|
mail = {
|
||||||
uid: uid,
|
uid: uid,
|
||||||
@ -224,28 +276,20 @@ define(function(require) {
|
|||||||
subject: '[whiteout] asdasd',
|
subject: '[whiteout] asdasd',
|
||||||
unread: true
|
unread: true
|
||||||
};
|
};
|
||||||
scope.emails = [mail];
|
|
||||||
currentFolder = {
|
currentFolder = {
|
||||||
type: 'Inbox',
|
type: 'Inbox',
|
||||||
path: 'INBOX'
|
path: 'INBOX',
|
||||||
|
messages: [mail]
|
||||||
};
|
};
|
||||||
trashFolder = {
|
scope.account.folders = [currentFolder];
|
||||||
type: 'Trash',
|
|
||||||
path: 'TRASH'
|
|
||||||
};
|
|
||||||
scope.folders = [currentFolder, trashFolder];
|
|
||||||
scope.state.nav = {
|
scope.state.nav = {
|
||||||
currentFolder: currentFolder
|
currentFolder: currentFolder
|
||||||
};
|
};
|
||||||
emailDaoMock.imapMoveMessage.withArgs({
|
emailDaoMock.sync.yields();
|
||||||
folder: currentFolder,
|
|
||||||
uid: uid,
|
|
||||||
destination: trashFolder.path
|
|
||||||
}).yields();
|
|
||||||
|
|
||||||
scope.remove(mail);
|
scope.remove(mail);
|
||||||
|
|
||||||
expect(emailDaoMock.imapMoveMessage.calledOnce).to.be.true;
|
expect(emailDaoMock.sync.calledOnce).to.be.true;
|
||||||
expect(scope.state.mailList.selected).to.not.exist;
|
expect(scope.state.mailList.selected).to.not.exist;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -21,15 +21,21 @@ define(function(require) {
|
|||||||
origEmailDao = appController._emailDao;
|
origEmailDao = appController._emailDao;
|
||||||
|
|
||||||
emailDaoMock = sinon.createStubInstance(EmailDAO);
|
emailDaoMock = sinon.createStubInstance(EmailDAO);
|
||||||
|
emailDaoMock._account = {
|
||||||
|
folders: [{
|
||||||
|
type: 'Inbox',
|
||||||
|
count: 2,
|
||||||
|
path: 'INBOX'
|
||||||
|
}, {
|
||||||
|
type: 'Outbox',
|
||||||
|
count: 0,
|
||||||
|
path: 'OUTBOX'
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
outboxFolder = emailDaoMock._account.folders[1];
|
||||||
appController._emailDao = emailDaoMock;
|
appController._emailDao = emailDaoMock;
|
||||||
outboxBoMock = sinon.createStubInstance(OutboxBO);
|
outboxBoMock = sinon.createStubInstance(OutboxBO);
|
||||||
appController._outboxBo = outboxBoMock;
|
appController._outboxBo = outboxBoMock;
|
||||||
|
|
||||||
// for outbox checking
|
|
||||||
outboxFolder = {
|
|
||||||
type: 'Outbox'
|
|
||||||
};
|
|
||||||
emailDaoMock.imapListFolders.yields(null, [outboxFolder]);
|
|
||||||
outboxBoMock.startChecking.returns();
|
outboxBoMock.startChecking.returns();
|
||||||
|
|
||||||
angular.module('navigationtest', []);
|
angular.module('navigationtest', []);
|
||||||
@ -55,7 +61,7 @@ define(function(require) {
|
|||||||
it('should be well defined', function() {
|
it('should be well defined', function() {
|
||||||
expect(scope.state).to.exist;
|
expect(scope.state).to.exist;
|
||||||
expect(scope.state.nav.open).to.be.false;
|
expect(scope.state.nav.open).to.be.false;
|
||||||
expect(scope.folders).to.not.be.empty;
|
expect(scope.account.folders).to.not.be.empty;
|
||||||
|
|
||||||
expect(scope.onError).to.exist;
|
expect(scope.onError).to.exist;
|
||||||
expect(scope.openFolder).to.exist;
|
expect(scope.openFolder).to.exist;
|
||||||
@ -86,7 +92,6 @@ define(function(require) {
|
|||||||
it('should work', function() {
|
it('should work', function() {
|
||||||
var callback;
|
var callback;
|
||||||
|
|
||||||
expect(emailDaoMock.imapListFolders.callCount).to.equal(1);
|
|
||||||
expect(outboxBoMock.startChecking.callCount).to.equal(1);
|
expect(outboxBoMock.startChecking.callCount).to.equal(1);
|
||||||
|
|
||||||
outboxBoMock.startChecking.calledWith(sinon.match(function(cb) {
|
outboxBoMock.startChecking.calledWith(sinon.match(function(cb) {
|
||||||
|
@ -16,12 +16,16 @@ define(function(require) {
|
|||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
emailDaoStub = sinon.createStubInstance(EmailDAO);
|
emailDaoStub = sinon.createStubInstance(EmailDAO);
|
||||||
emailDaoStub._account = {
|
emailDaoStub._account = {
|
||||||
emailAddress: dummyUser
|
emailAddress: dummyUser,
|
||||||
|
folders: [{
|
||||||
|
type: 'Outbox'
|
||||||
|
}]
|
||||||
};
|
};
|
||||||
emailDaoStub._devicestorage = devicestorageStub = sinon.createStubInstance(DeviceStorageDAO);
|
devicestorageStub = sinon.createStubInstance(DeviceStorageDAO);
|
||||||
emailDaoStub._keychain = keychainStub = sinon.createStubInstance(KeychainDAO);
|
keychainStub = sinon.createStubInstance(KeychainDAO);
|
||||||
invitationDaoStub = sinon.createStubInstance(InvitationDAO);
|
invitationDaoStub = sinon.createStubInstance(InvitationDAO);
|
||||||
outbox = new OutboxBO(emailDaoStub, invitationDaoStub);
|
outbox = new OutboxBO(emailDaoStub, keychainStub, devicestorageStub, invitationDaoStub);
|
||||||
|
outbox.init();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function() {});
|
afterEach(function() {});
|
||||||
@ -30,8 +34,12 @@ define(function(require) {
|
|||||||
it('should work', function() {
|
it('should work', function() {
|
||||||
expect(outbox).to.exist;
|
expect(outbox).to.exist;
|
||||||
expect(outbox._emailDao).to.equal(emailDaoStub);
|
expect(outbox._emailDao).to.equal(emailDaoStub);
|
||||||
|
expect(outbox._keychain).to.equal(keychainStub);
|
||||||
|
expect(outbox._devicestorage).to.equal(devicestorageStub);
|
||||||
expect(outbox._invitationDao).to.equal(invitationDaoStub);
|
expect(outbox._invitationDao).to.equal(invitationDaoStub);
|
||||||
expect(outbox._outboxBusy).to.be.false;
|
expect(outbox._outboxBusy).to.be.false;
|
||||||
|
expect(outbox.pendingEmails).to.be.empty;
|
||||||
|
expect(emailDaoStub._account.folders[0].messages).to.equal(outbox.pendingEmails);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -50,50 +58,73 @@ define(function(require) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('process outbox', function() {
|
describe('process outbox', function() {
|
||||||
it('should work', function(done) {
|
it('should send to registered users and update pending mails', function(done) {
|
||||||
var dummyMails = [{
|
var member, invited, notinvited, dummyMails, unsentCount;
|
||||||
|
|
||||||
|
member = {
|
||||||
id: '123',
|
id: '123',
|
||||||
to: [{
|
to: [{
|
||||||
name: 'member',
|
name: 'member',
|
||||||
address: 'member@whiteout.io'
|
address: 'member@whiteout.io'
|
||||||
}]
|
}]
|
||||||
}, {
|
};
|
||||||
|
invited = {
|
||||||
id: '456',
|
id: '456',
|
||||||
to: [{
|
to: [{
|
||||||
name: 'invited',
|
name: 'invited',
|
||||||
address: 'invited@whiteout.io'
|
address: 'invited@whiteout.io'
|
||||||
}]
|
}]
|
||||||
}, {
|
};
|
||||||
|
notinvited = {
|
||||||
id: '789',
|
id: '789',
|
||||||
to: [{
|
to: [{
|
||||||
name: 'notinvited',
|
name: 'notinvited',
|
||||||
address: 'notinvited@whiteout.io'
|
address: 'notinvited@whiteout.io'
|
||||||
}]
|
}]
|
||||||
}];
|
};
|
||||||
|
dummyMails = [member, invited, notinvited];
|
||||||
|
|
||||||
devicestorageStub.listItems.yieldsAsync(null, dummyMails);
|
emailDaoStub.list.yieldsAsync(null, dummyMails);
|
||||||
emailDaoStub.encryptedSend.yieldsAsync();
|
emailDaoStub.sendEncrypted.withArgs(sinon.match(function(opts) {
|
||||||
emailDaoStub.send.yieldsAsync();
|
return typeof opts.email !== 'undefined' && opts.email.to.address === member.to.address;
|
||||||
|
})).yieldsAsync();
|
||||||
|
emailDaoStub.sendPlaintext.yieldsAsync();
|
||||||
devicestorageStub.removeList.yieldsAsync();
|
devicestorageStub.removeList.yieldsAsync();
|
||||||
invitationDaoStub.check.withArgs(sinon.match(function(o) { return o.recipient === 'invited@whiteout.io'; })).yieldsAsync(null, InvitationDAO.INVITE_PENDING);
|
invitationDaoStub.check.withArgs(sinon.match(function(o) {
|
||||||
invitationDaoStub.check.withArgs(sinon.match(function(o) { return o.recipient === 'notinvited@whiteout.io'; })).yieldsAsync(null, InvitationDAO.INVITE_MISSING);
|
return o.recipient === 'invited@whiteout.io';
|
||||||
invitationDaoStub.invite.withArgs(sinon.match(function(o) { return o.recipient === 'notinvited@whiteout.io'; })).yieldsAsync(null, InvitationDAO.INVITE_SUCCESS);
|
})).yieldsAsync(null, InvitationDAO.INVITE_PENDING);
|
||||||
keychainStub.getReceiverPublicKey.withArgs(sinon.match(function(o) { return o === 'member@whiteout.io'; })).yieldsAsync(null, 'this is not the key you are looking for...');
|
invitationDaoStub.check.withArgs(sinon.match(function(o) {
|
||||||
keychainStub.getReceiverPublicKey.withArgs(sinon.match(function(o) { return o === 'invited@whiteout.io' || o === 'notinvited@whiteout.io'; })).yieldsAsync();
|
return o.recipient === 'notinvited@whiteout.io';
|
||||||
|
})).yieldsAsync(null, InvitationDAO.INVITE_MISSING);
|
||||||
|
invitationDaoStub.invite.withArgs(sinon.match(function(o) {
|
||||||
|
return o.recipient === 'notinvited@whiteout.io';
|
||||||
|
})).yieldsAsync(null, InvitationDAO.INVITE_SUCCESS);
|
||||||
|
keychainStub.getReceiverPublicKey.withArgs(sinon.match(function(o) {
|
||||||
|
return o === 'member@whiteout.io';
|
||||||
|
})).yieldsAsync(null, 'this is not the key you are looking for...');
|
||||||
|
keychainStub.getReceiverPublicKey.withArgs(sinon.match(function(o) {
|
||||||
|
return o === 'invited@whiteout.io' || o === 'notinvited@whiteout.io';
|
||||||
|
})).yieldsAsync();
|
||||||
|
|
||||||
var check = _.after(dummyMails.length + 1, function() {
|
var check = _.after(dummyMails.length + 1, function() {
|
||||||
expect(devicestorageStub.listItems.callCount).to.equal(1);
|
expect(unsentCount).to.equal(2);
|
||||||
expect(emailDaoStub.encryptedSend.callCount).to.equal(1);
|
expect(emailDaoStub.list.callCount).to.equal(1);
|
||||||
expect(emailDaoStub.send.callCount).to.equal(1);
|
expect(emailDaoStub.sendEncrypted.callCount).to.equal(1);
|
||||||
|
expect(emailDaoStub.sendPlaintext.callCount).to.equal(1);
|
||||||
expect(devicestorageStub.removeList.callCount).to.equal(1);
|
expect(devicestorageStub.removeList.callCount).to.equal(1);
|
||||||
expect(invitationDaoStub.check.callCount).to.equal(2);
|
expect(invitationDaoStub.check.callCount).to.equal(2);
|
||||||
expect(invitationDaoStub.invite.callCount).to.equal(1);
|
expect(invitationDaoStub.invite.callCount).to.equal(1);
|
||||||
|
|
||||||
|
expect(outbox.pendingEmails.length).to.equal(2);
|
||||||
|
expect(outbox.pendingEmails).to.contain(invited);
|
||||||
|
expect(outbox.pendingEmails).to.contain(notinvited);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
function onOutboxUpdate(err, count) {
|
function onOutboxUpdate(err, count) {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
expect(count).to.exist;
|
expect(count).to.exist;
|
||||||
|
unsentCount = count;
|
||||||
check();
|
check();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,17 +6,17 @@ define(function(require) {
|
|||||||
mocks = require('angularMocks'),
|
mocks = require('angularMocks'),
|
||||||
WriteCtrl = require('js/controller/write'),
|
WriteCtrl = require('js/controller/write'),
|
||||||
EmailDAO = require('js/dao/email-dao'),
|
EmailDAO = require('js/dao/email-dao'),
|
||||||
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
|
|
||||||
KeychainDAO = require('js/dao/keychain-dao'),
|
KeychainDAO = require('js/dao/keychain-dao'),
|
||||||
appController = require('js/app-controller');
|
appController = require('js/app-controller');
|
||||||
|
|
||||||
describe('Write controller unit test', function() {
|
describe('Write controller unit test', function() {
|
||||||
var ctrl, scope, origEmailDao, emailDaoMock, keychainMock, deviceStorageMock, emailAddress;
|
var ctrl, scope, origEmailDao, emailDaoMock, keychainMock, emailAddress;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
origEmailDao = appController._emailDao;
|
origEmailDao = appController._emailDao;
|
||||||
emailDaoMock = sinon.createStubInstance(EmailDAO);
|
emailDaoMock = sinon.createStubInstance(EmailDAO);
|
||||||
appController._emailDao = emailDaoMock;
|
appController._emailDao = emailDaoMock;
|
||||||
|
|
||||||
emailAddress = 'fred@foo.com';
|
emailAddress = 'fred@foo.com';
|
||||||
emailDaoMock._account = {
|
emailDaoMock._account = {
|
||||||
emailAddress: emailAddress,
|
emailAddress: emailAddress,
|
||||||
@ -25,9 +25,6 @@ define(function(require) {
|
|||||||
keychainMock = sinon.createStubInstance(KeychainDAO);
|
keychainMock = sinon.createStubInstance(KeychainDAO);
|
||||||
emailDaoMock._keychain = keychainMock;
|
emailDaoMock._keychain = keychainMock;
|
||||||
|
|
||||||
deviceStorageMock = sinon.createStubInstance(DeviceStorageDAO);
|
|
||||||
emailDaoMock._devicestorage = deviceStorageMock;
|
|
||||||
|
|
||||||
angular.module('writetest', []);
|
angular.module('writetest', []);
|
||||||
mocks.module('writetest');
|
mocks.module('writetest');
|
||||||
mocks.inject(function($rootScope, $controller) {
|
mocks.inject(function($rootScope, $controller) {
|
||||||
@ -145,21 +142,37 @@ define(function(require) {
|
|||||||
|
|
||||||
describe('send to outbox', function() {
|
describe('send to outbox', function() {
|
||||||
it('should work', function(done) {
|
it('should work', function(done) {
|
||||||
scope.state.writer.open = true;
|
var verifyToSpy = sinon.spy(scope, 'verifyTo'),
|
||||||
scope.to = 'a, b, c';
|
re = {
|
||||||
scope.body = 'asd';
|
from: [{
|
||||||
scope.subject = 'yaddablabla';
|
address: 'pity@dafool'
|
||||||
scope.toKey = 'Public Key';
|
}],
|
||||||
|
subject: 'Ermahgerd!',
|
||||||
|
sentDate: new Date(),
|
||||||
|
body: 'so much body!'
|
||||||
|
};
|
||||||
|
|
||||||
deviceStorageMock.storeList.withArgs(sinon.match(function(mail) {
|
scope.state.nav = {
|
||||||
return mail[0].from[0].address === emailAddress && mail[0].to.length === 3;
|
currentFolder: 'currentFolder'
|
||||||
})).yieldsAsync();
|
};
|
||||||
scope.emptyOutbox = function() {
|
|
||||||
|
scope.emptyOutbox = function() {};
|
||||||
|
|
||||||
|
emailDaoMock.store.yields();
|
||||||
|
emailDaoMock.markAnswered.yields();
|
||||||
|
|
||||||
|
scope.onError = function(err) {
|
||||||
|
expect(err).to.not.exist;
|
||||||
expect(scope.state.writer.open).to.be.false;
|
expect(scope.state.writer.open).to.be.false;
|
||||||
expect(deviceStorageMock.storeList.calledOnce).to.be.true;
|
expect(emailDaoMock.store.calledOnce).to.be.true;
|
||||||
|
expect(emailDaoMock.store.calledOnce).to.be.true;
|
||||||
|
expect(verifyToSpy.calledOnce).to.be.true;
|
||||||
|
|
||||||
|
scope.verifyTo.restore();
|
||||||
done();
|
done();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
scope.state.writer.write(re);
|
||||||
scope.sendToOutbox();
|
scope.sendToOutbox();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -170,8 +183,8 @@ define(function(require) {
|
|||||||
scope.subject = 'yaddablabla';
|
scope.subject = 'yaddablabla';
|
||||||
scope.toKey = 'Public Key';
|
scope.toKey = 'Public Key';
|
||||||
|
|
||||||
deviceStorageMock.storeList.withArgs(sinon.match(function(mail) {
|
emailDaoMock.store.withArgs(sinon.match(function(mail) {
|
||||||
return mail[0].from[0].address === emailAddress && mail[0].to.length === 3;
|
return mail.from[0].address === emailAddress && mail.to.length === 3;
|
||||||
})).yields({
|
})).yields({
|
||||||
errMsg: 'snafu'
|
errMsg: 'snafu'
|
||||||
});
|
});
|
||||||
@ -179,7 +192,7 @@ define(function(require) {
|
|||||||
scope.onError = function(err) {
|
scope.onError = function(err) {
|
||||||
expect(err).to.exist;
|
expect(err).to.exist;
|
||||||
expect(scope.state.writer.open).to.be.true;
|
expect(scope.state.writer.open).to.be.true;
|
||||||
expect(deviceStorageMock.storeList.calledOnce).to.be.true;
|
expect(emailDaoMock.store.calledOnce).to.be.true;
|
||||||
done();
|
done();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user