summaryrefslogtreecommitdiff
path: root/web-ui/test/spec
diff options
context:
space:
mode:
Diffstat (limited to 'web-ui/test/spec')
-rw-r--r--web-ui/test/spec/dispatchers/left_pane_dispatcher.spec.js79
-rw-r--r--web-ui/test/spec/dispatchers/middle_pane_dispatchers.spec.js27
-rw-r--r--web-ui/test/spec/dispatchers/right_pane_dispatcher.spec.js90
-rw-r--r--web-ui/test/spec/helpers/view_helper.spec.js76
-rw-r--r--web-ui/test/spec/mail_list/ui/mail_items/generic_mail_item.spec.js136
-rw-r--r--web-ui/test/spec/mail_list/ui/mail_items/mail_item.spec.js40
-rw-r--r--web-ui/test/spec/mail_list/ui/mail_list.spec.js275
-rw-r--r--web-ui/test/spec/mail_list_actions/ui/compose_trigger.spec.js16
-rw-r--r--web-ui/test/spec/mail_list_actions/ui/mail_actions.spec.js9
-rw-r--r--web-ui/test/spec/mail_list_actions/ui/pagination_trigger.spec.js26
-rw-r--r--web-ui/test/spec/mail_view/data/mail_builder.spec.js110
-rw-r--r--web-ui/test/spec/mail_view/data/mail_sender.spec.js70
-rw-r--r--web-ui/test/spec/mail_view/ui/compose_box.spec.js132
-rw-r--r--web-ui/test/spec/mail_view/ui/draft_box.spec.js67
-rw-r--r--web-ui/test/spec/mail_view/ui/draft_save_status.spec.js26
-rw-r--r--web-ui/test/spec/mail_view/ui/forward_box.spec.js90
-rw-r--r--web-ui/test/spec/mail_view/ui/mail_actions.spec.js63
-rw-r--r--web-ui/test/spec/mail_view/ui/mail_view.spec.js247
-rw-r--r--web-ui/test/spec/mail_view/ui/recipients/recipients.spec.js36
-rw-r--r--web-ui/test/spec/mail_view/ui/recipients/recipients_input.spec.js104
-rw-r--r--web-ui/test/spec/mail_view/ui/recipients/recipients_iterator.spec.js101
-rw-r--r--web-ui/test/spec/mail_view/ui/reply_box.spec.js105
-rw-r--r--web-ui/test/spec/mail_view/ui/reply_section.spec.js97
-rw-r--r--web-ui/test/spec/mail_view/ui/send_button.spec.js91
-rw-r--r--web-ui/test/spec/mixins/with_mail_edit_base.spec.js94
-rw-r--r--web-ui/test/spec/page/pane_contract_expand.spec.js67
-rw-r--r--web-ui/test/spec/page/router.spec.js70
-rw-r--r--web-ui/test/spec/page/router/url_params.spec.js68
-rw-r--r--web-ui/test/spec/search/search_trigger.spec.js78
-rw-r--r--web-ui/test/spec/services/delete_service.spec.js54
-rw-r--r--web-ui/test/spec/services/mail_service.spec.js307
-rw-r--r--web-ui/test/spec/services/model/mail.spec.js116
-rw-r--r--web-ui/test/spec/tags/data/tags.spec.js39
-rw-r--r--web-ui/test/spec/tags/ui/tag.spec.js151
-rw-r--r--web-ui/test/spec/tags/ui/tag_list.spec.js75
-rw-r--r--web-ui/test/spec/tags/ui/tag_shortcut.spec.js35
-rw-r--r--web-ui/test/spec/user_alerts/ui/user_alerts.spec.js23
37 files changed, 3290 insertions, 0 deletions
diff --git a/web-ui/test/spec/dispatchers/left_pane_dispatcher.spec.js b/web-ui/test/spec/dispatchers/left_pane_dispatcher.spec.js
new file mode 100644
index 00000000..fb5b169a
--- /dev/null
+++ b/web-ui/test/spec/dispatchers/left_pane_dispatcher.spec.js
@@ -0,0 +1,79 @@
+/*global Smail */
+
+describeComponent('dispatchers/left_pane_dispatcher', function () {
+ 'use strict';
+
+ describe('initialize', function () {
+ it('asks for tags', function () {
+ var tagWantEvent = spyOnEvent(document, Smail.events.tags.want);
+
+ setupComponent();
+
+ expect(tagWantEvent).toHaveBeenTriggeredOn(document);
+ expect(tagWantEvent.mostRecentCall.data.caller[0]).toEqual(this.$node[0]);
+ });
+ });
+
+ describe('after initialization', function () {
+ beforeEach(function () {
+ setupComponent();
+ });
+
+ it('pushes the url state when a tag is selected but not for the first tag', function () {
+ var pushStateEvent = spyOnEvent(document, Smail.events.router.pushState);
+
+ $(document).trigger(Smail.events.ui.tag.selected, { tag: 'Drafts'});
+ $(document).trigger(Smail.events.ui.tag.selected, { tag: 'inbox'});
+
+ expect(pushStateEvent).toHaveBeenTriggeredOn(document, { tag: 'inbox'});
+ });
+
+ it('fetches mails by tag when a tag is selected', function () {
+ var fetchByTagEvent = spyOnEvent(document, Smail.events.ui.mails.fetchByTag);
+
+ $(document).trigger(Smail.events.ui.tag.selected, { tag: 'Drafts'});
+
+ expect(fetchByTagEvent).toHaveBeenTriggeredOn(document, { tag: 'Drafts'});
+ });
+
+ it('doesnt fetch mails by tag when skipMailListRefresh is sent on tag.selected', function () {
+ var fetchByTagEvent = spyOnEvent(document, Smail.events.ui.mails.fetchByTag);
+
+ $(document).trigger(Smail.events.ui.tag.selected, { tag: 'Drafts', skipMailListRefresh: true});
+
+ expect(fetchByTagEvent).not.toHaveBeenTriggeredOn(document, { tag: 'Drafts'});
+ });
+
+ it('asks for more tags when refreshTagList is fired', function () {
+ var tagWantEvent = spyOnEvent(document, Smail.events.tags.want);
+
+ $(document).trigger(Smail.events.dispatchers.tags.refreshTagList);
+
+ expect(tagWantEvent).toHaveBeenTriggeredOn(document);
+ });
+
+ it('fires tagLoad when the tags are received', function () {
+ var tagListLoadEvent = spyOnEvent(document, Smail.events.ui.tagList.load);
+
+ this.$node.trigger(Smail.events.tags.received, { tags: ['tags']});
+
+ expect(tagListLoadEvent).toHaveBeenTriggeredOn(document, { tags: ['tags']});
+ });
+
+ it('on tags loaded selects the inbox tag if no data is provided', function () {
+ var selectTagEvent = spyOnEvent(document, Smail.events.ui.tag.select);
+
+ $(document).trigger(Smail.events.ui.tags.loaded);
+
+ expect(selectTagEvent).toHaveBeenTriggeredOnAndWith(document, { tag: 'inbox' });
+ });
+
+ it('on tags loaded selects the a different tag if tag is provided', function () {
+ var selectTagEvent = spyOnEvent(document, Smail.events.ui.tag.select);
+
+ $(document).trigger(Smail.events.ui.tags.loaded, { tag: 'Drafts' });
+
+ expect(selectTagEvent).toHaveBeenTriggeredOnAndWith(document, { tag: 'Drafts' });
+ });
+ });
+});
diff --git a/web-ui/test/spec/dispatchers/middle_pane_dispatchers.spec.js b/web-ui/test/spec/dispatchers/middle_pane_dispatchers.spec.js
new file mode 100644
index 00000000..2dd0de2e
--- /dev/null
+++ b/web-ui/test/spec/dispatchers/middle_pane_dispatchers.spec.js
@@ -0,0 +1,27 @@
+/*global Smail */
+
+describeComponent('dispatchers/middle_pane_dispatcher', function () {
+ 'use strict';
+
+ beforeEach(function() {
+ setupComponent('<div><div id="middle-pane" style="height: 200px; overflow-y: scroll;"><div style="height: 400px"></div></div></div>');
+ });
+
+ it ('listens to refresh mail list event', function() {
+ var mailsListRefreshEventSpy = spyOnEvent(document, Smail.events.ui.mails.fetchByTag);
+ this.component.trigger(document, Smail.events.dispatchers.middlePane.refreshMailList);
+ expect(mailsListRefreshEventSpy).toHaveBeenTriggeredOn(document);
+ });
+
+ it ('listens to unselect event', function() {
+ var mailListUnselectEvent = spyOnEvent(document, Smail.events.ui.mails.cleanSelected);
+ this.component.trigger(document, Smail.events.dispatchers.middlePane.cleanSelected);
+ expect(mailListUnselectEvent).toHaveBeenTriggeredOn(document);
+ });
+
+ it('resets the scrollTop value when asked to', function() {
+ this.component.select('middlePane').scrollTop(200);
+ this.component.trigger(document, Smail.events.dispatchers.middlePane.resetScroll);
+ expect(this.component.select('middlePane').scrollTop()).toEqual(0);
+ });
+});
diff --git a/web-ui/test/spec/dispatchers/right_pane_dispatcher.spec.js b/web-ui/test/spec/dispatchers/right_pane_dispatcher.spec.js
new file mode 100644
index 00000000..5fc7ecd7
--- /dev/null
+++ b/web-ui/test/spec/dispatchers/right_pane_dispatcher.spec.js
@@ -0,0 +1,90 @@
+/*global Smail */
+
+describeComponent('dispatchers/right_pane_dispatcher', function () {
+ 'use strict';
+
+ describe('after initialization', function () {
+ beforeEach(function () {
+ setupComponent();
+ });
+
+ it('listens to open compose box event and creates a compose box', function () {
+ var composeBox = require('mail_view/ui/compose_box');
+ spyOn(composeBox, 'attachTo');
+
+ this.component.trigger(document, Smail.events.dispatchers.rightPane.openComposeBox);
+
+ expect(composeBox.attachTo).toHaveBeenCalled();
+ });
+
+ describe('no message selected', function () {
+ var noMessageSelectedPane;
+ beforeEach(function () {
+ noMessageSelectedPane = require('mail_view/ui/no_message_selected_pane');
+ spyOn(noMessageSelectedPane, 'attachTo');
+ });
+
+ it('listen to open no message selected event and creates a no-message-selected-pane', function () {
+ this.component.trigger(document, Smail.events.dispatchers.rightPane.openNoMessageSelected);
+
+ expect(noMessageSelectedPane.attachTo).toHaveBeenCalled();
+ });
+
+ it('sends an dispatchers.middlePane.unselect event', function () {
+ var unselectEvent = spyOnEvent(document, Smail.events.dispatchers.middlePane.cleanSelected);
+ this.component.trigger(document, Smail.events.dispatchers.rightPane.openNoMessageSelected);
+
+ expect(unselectEvent).toHaveBeenTriggeredOn(document);
+ });
+
+ it('pushes the current state with the current tag', function () {
+ var pushStateEvent = spyOnEvent(document, Smail.events.router.pushState);
+
+ this.component.attr.currentTag = 'sometag';
+ this.component.trigger(document, Smail.events.dispatchers.rightPane.openNoMessageSelected);
+
+ expect(pushStateEvent).toHaveBeenTriggeredOnAndWith(document, jasmine.objectContaining({tag: this.component.attr.currentTag }));
+ });
+
+ it('pushes the current state stating that it meant to close the right pane', function () {
+ var pushStateEvent = spyOnEvent(document, Smail.events.router.pushState);
+
+ this.component.attr.currentTag = 'sometag';
+ this.component.trigger(document, Smail.events.dispatchers.rightPane.openNoMessageSelected);
+
+ expect(pushStateEvent).toHaveBeenTriggeredOnAndWith(document, jasmine.objectContaining({ isDisplayNoMessageSelected: true }));
+ });
+
+
+ });
+
+ it('listens to open a draft and creates it', function () {
+ var draftBox = require('mail_view/ui/draft_box');
+ spyOn(draftBox, 'attachTo');
+
+ this.component.trigger(document, Smail.events.dispatchers.rightPane.openDraft, { ident: '1' });
+
+ expect(draftBox.attachTo).toHaveBeenCalled();
+ });
+ });
+
+ describe('on initialization', function () {
+ var noMessageSelectedPane;
+
+ beforeEach(function () {
+ noMessageSelectedPane = require('mail_view/ui/no_message_selected_pane');
+ spyOn(noMessageSelectedPane, 'attachTo');
+ });
+
+ it('opens the no message selected pane but doesnt push the state', function () {
+ var pushStateEvent = spyOnEvent(document, Smail.events.router.pushState);
+
+ setupComponent();
+
+ expect(noMessageSelectedPane.attachTo).toHaveBeenCalled();
+ expect(pushStateEvent).not.toHaveBeenTriggeredOn(document);
+
+ });
+ });
+
+});
diff --git a/web-ui/test/spec/helpers/view_helper.spec.js b/web-ui/test/spec/helpers/view_helper.spec.js
new file mode 100644
index 00000000..7fa10e56
--- /dev/null
+++ b/web-ui/test/spec/helpers/view_helper.spec.js
@@ -0,0 +1,76 @@
+/*global Smail */
+
+define(['helpers/view_helper'], function (viewHelper) {
+ 'use strict';
+
+ var testData;
+ describe('view helper', function() {
+ beforeEach(function () {
+ testData = Smail.testData();
+ });
+
+ describe('quote email', function() {
+ it('should add > to body text', function() {
+ testData.rawMail.mail.body = 'First Line\nSecond Line';
+
+ var quotedMail = viewHelper.quoteMail(testData.rawMail.mail);
+
+ expect(quotedMail).toContain('> First Line\n> Second Line');
+ });
+ });
+
+ describe('getFormmattedDate', function() {
+ it('formats correctly a Date for today', function() {
+ var d = new Date();
+ var dtest = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 14, 2, 36);
+
+ var res = viewHelper.getFormattedDate(dtest);
+
+ expect(res).toEqual('14:02');
+
+ });
+
+ it('formats correctly a Date for a specific day', function() {
+ var dtest = new Date(2013, 2, 13, 7, 56, 1);
+
+ var res = viewHelper.getFormattedDate(dtest);
+
+ // This expectation is weird for the month - JS Dates have date numbers be zero-indexed, thus the discrepency
+ // Specifically, the 2 in the constructor DOES match the 3 in the expectation below.
+ expect(res).toEqual('2013-03-13');
+ });
+ });
+
+ describe('format status classes', function () {
+ it('formats all the status of the email to css classes', function () {
+ var statuses = ['read', 'banana'];
+
+ expect(viewHelper.formatStatusClasses(statuses)).toEqual('status-read status-banana');
+ });
+
+ it('formats a single status of the email to a css class', function () {
+ var statuses = ['read'];
+
+ expect(viewHelper.formatStatusClasses(statuses)).toEqual('status-read');
+ });
+ });
+
+ it('formats the body of a multipart email', function () {
+ expect(viewHelper.formatMailBody(testData.parsedMail.html)).toContainHtml('<p>Hello everyone!</p>');
+ });
+
+ it('decodes a quoted-printable email body', function () {
+ var result = viewHelper.formatMailBody(testData.parsedMail.htmlQuotedPrintable);
+
+ expect(result).toContainHtml('<p style="border: 5px;">Hello everyone!</p>');
+ });
+
+ it('move caret to the end of text after 1ms', function () {
+ spyOn(window, 'setTimeout');
+
+ viewHelper.moveCaretToEndOfText();
+
+ expect(window.setTimeout.calls[0].args[1]).toEqual(1);
+ });
+ });
+});
diff --git a/web-ui/test/spec/mail_list/ui/mail_items/generic_mail_item.spec.js b/web-ui/test/spec/mail_list/ui/mail_items/generic_mail_item.spec.js
new file mode 100644
index 00000000..b49bc7f0
--- /dev/null
+++ b/web-ui/test/spec/mail_list/ui/mail_items/generic_mail_item.spec.js
@@ -0,0 +1,136 @@
+describeComponent('mail_list/ui/mail_items/generic_mail_item', function () {
+ 'use strict';
+
+ var mail;
+
+ beforeEach(function () {
+ mail = Smail.testData().parsedMail.simpleTextPlain;
+ mail.tags = ['inbox'];
+
+ setupComponent('<li></li>', {
+ mail: mail,
+ selected: false,
+ tag: 'inbox'
+ });
+ });
+
+ it('should trigger ui:openMail on click', function () {
+ var spyEvent = spyOnEvent(document, Smail.events.ui.mail.open);
+
+ this.component.$node.find('a').click();
+
+ expect(spyEvent).toHaveBeenTriggeredOn(document);
+ expect(spyEvent.mostRecentCall.data).toEqual({ ident: mail.ident });
+ });
+
+ it('should add selected class when selecting', function () {
+ this.$node.find('a').click();
+
+ expect(this.$node).toHaveClass('selected');
+ });
+
+ it('should remove selected class when selecting a different mail', function () {
+ $(document).trigger(Smail.events.ui.mail.updateSelected, { ident: 2 });
+
+ expect(this.$node).not.toHaveClass('selected');
+ });
+
+ it('should remove selected class when enabling compose box', function () {
+ this.$node.find('a').click();
+
+ $(document).trigger(Smail.events.ui.composeBox.newMessage);
+
+ expect(this.$node).not.toHaveClass('selected');
+ });
+
+ it('should have the href link with mail ident and tag name', function () {
+ expect(this.$node.find('a')[0].href).toMatch('inbox/mail/' + mail.ident);
+ });
+
+ describe('clicking on a mail', function () {
+
+ function createClickEvent(options) {
+ var clickEvent = $.Event('click');
+ _.merge(clickEvent, options);
+ spyOn(clickEvent, 'preventDefault');
+ return clickEvent;
+ }
+
+ it('triggers mail open and pushes the state', function () {
+ var clickEvent = createClickEvent();
+ var mailOpenEvent = spyOnEvent(document, Smail.events.ui.mail.open);
+ var pushStateEvent = spyOnEvent(document, Smail.events.router.pushState);
+
+ $(this.$node.find('a')).trigger(clickEvent);
+
+ expect(mailOpenEvent).toHaveBeenTriggeredOnAndWith(document, { ident: mail.ident });
+ expect(pushStateEvent).toHaveBeenTriggeredOnAndWith(document, { mailIdent: mail.ident });
+ expect(clickEvent.preventDefault).toHaveBeenCalled();
+ });
+
+ describe('when opening on a new tab', function () {
+
+ _.each([
+ {metaKey: true},
+ {which: 2},
+ {ctrlKey: true}
+ ], function (specialKey) {
+ it('doesnt trigger mail open and nor pushes the state', function () {
+ var clickEvent = createClickEvent(specialKey);
+ var mailOpenEvent = spyOnEvent(document, Smail.events.ui.mail.open);
+ var pushStateEvent = spyOnEvent(document, Smail.events.router.pushState);
+
+ $(this.$node.find('a')).trigger(clickEvent);
+
+ expect(mailOpenEvent).not.toHaveBeenTriggeredOnAndWith(document, { ident: mail.ident });
+ expect(pushStateEvent).not.toHaveBeenTriggeredOnAndWith(document, { mailIdent: mail.ident });
+ expect(clickEvent.preventDefault).not.toHaveBeenCalled();
+ });
+
+ it('marks the email as read', function () {
+ debugger;
+ var mailReadEvent = spyOnEvent(document, Smail.events.mail.read);
+ var clickEvent = createClickEvent(specialKey);
+
+ $(this.$node.find('a')).trigger(clickEvent);
+
+ expect(this.component.attr.mail.status).toContain(this.component.status.READ);
+ expect(this.$node.attr('class')).toMatch('status-read');
+ expect(mailReadEvent).toHaveBeenTriggeredOnAndWith(document, { ident: mail.ident, tags: ['inbox'] });
+ });
+
+ });
+
+ });
+
+ });
+
+ describe('marking emails as read', function () {
+ it('should trigger mail:read event when unread is clicked', function () {
+ var mailReadEvent = spyOnEvent(document, Smail.events.mail.read);
+
+ this.$node.find('a').click();
+
+ expect(mailReadEvent).toHaveBeenTriggeredOnAndWith(document, jasmine.objectContaining({ident: mail.ident}));
+ });
+
+ it('should not trigger mail:read event when clicking mail that is already read', function () {
+ var mailReadEvent = spyOnEvent(document, Smail.events.mail.read);
+ this.component.attr.mail.status.push(this.component.status.READ);
+
+ this.$node.find('a').click();
+
+ expect(mailReadEvent).not.toHaveBeenTriggeredOnAndWith(document, {ident: mail.ident});
+ });
+
+ it('should add status-read class to email when clicking an unread email', function () {
+ this.$node.find('a').click();
+
+ expect(this.$node).toHaveClass('status-read');
+ });
+
+ it('should not have status-read class when initializing email without read status', function () {
+ expect(this.$node).not.toHaveClass('status-read');
+ });
+ });
+});
diff --git a/web-ui/test/spec/mail_list/ui/mail_items/mail_item.spec.js b/web-ui/test/spec/mail_list/ui/mail_items/mail_item.spec.js
new file mode 100644
index 00000000..1b5899a1
--- /dev/null
+++ b/web-ui/test/spec/mail_list/ui/mail_items/mail_item.spec.js
@@ -0,0 +1,40 @@
+/*global Smail */
+
+describeMixin('mail_list/ui/mail_items/mail_item', function () {
+ 'use strict';
+
+ beforeEach(function () {
+ var mail = Smail.testData().parsedMail.simpleTextPlain;
+ mail.tags = ['inbox'];
+
+ setupComponent('<li><input type="checkbox"></input></li>', {
+ mail: mail,
+ selected: false,
+ tag: 'inbox'
+ });
+ });
+
+ describe('mail checkbox', function () {
+ var mailCheckedEvent, mailUncheckedEvent, checkbox;
+ beforeEach(function () {
+ mailCheckedEvent = spyOnEvent(document, Smail.events.ui.mail.checked);
+ mailUncheckedEvent = spyOnEvent(document, Smail.events.ui.mail.unchecked);
+ checkbox = this.component.$node.find('input[type=checkbox]');
+ });
+
+ it('checkCheckbox checks it and triggers events.ui.mail.checked', function () {
+ this.component.checkCheckbox();
+
+ expect(checkbox.prop('checked')).toBe(true);
+ expect(mailCheckedEvent).toHaveBeenTriggeredOn(document);
+ });
+
+ it('uncheckCheckbox checks it and triggers events.ui.mail.checked', function () {
+ checkbox.prop('checked', true);
+ this.component.uncheckCheckbox();
+
+ expect(checkbox.prop('checked')).toBe(false);
+ expect(mailUncheckedEvent).toHaveBeenTriggeredOn(document);
+ });
+ });
+});
diff --git a/web-ui/test/spec/mail_list/ui/mail_list.spec.js b/web-ui/test/spec/mail_list/ui/mail_list.spec.js
new file mode 100644
index 00000000..f383d540
--- /dev/null
+++ b/web-ui/test/spec/mail_list/ui/mail_list.spec.js
@@ -0,0 +1,275 @@
+/*global Smail */
+
+describeComponent('mail_list/ui/mail_list', function () {
+ 'use strict';
+
+ var mailList;
+
+ beforeEach(function () {
+ setupComponent('<div id="mails"></div>', {
+ urlParams: {
+ hasMailIdent: function () {
+ return false;
+ }
+ }
+ });
+ mailList =
+ [
+ createMail('the mail subject', 'from@mail.com', '1', '2012-12-26T01:38:46-08:00'),
+ createMail('another mail subject', 'from_another@mail.com', '2', '2012-12-28T01:38:46-08:00')
+ ];
+ });
+
+
+ it('should open mail at first mail:available if there is a mailIdent in the url hash', function () {
+ this.component.attr.urlParams = {
+ hasMailIdent: function () {
+ return true;
+ },
+ getMailIdent: function () {
+ return '10';
+ }
+ };
+ var openMailEvent = spyOnEvent(document, Smail.events.ui.mail.open);
+
+ this.$node.trigger(Smail.events.mails.available, { mails: mailList });
+ expect(openMailEvent).toHaveBeenTriggeredOnAndWith(document, { ident: '10' });
+
+ this.$node.trigger(Smail.events.mails.available, { mails: mailList });
+ expect(openMailEvent.calls.length).toEqual(1);
+ });
+
+ it('should push the state if there is a mail ident in the hash url', function () {
+ this.component.attr.urlParams = {
+ hasMailIdent: function () {
+ return true;
+ },
+ getMailIdent: function () {
+ return '10';
+ }
+ };
+ var pushState = spyOnEvent(document, Smail.events.router.pushState);
+ this.component.attr.currentTag = 'inbox';
+
+ this.$node.trigger(Smail.events.mails.available, { mails: mailList });
+
+ expect(pushState).toHaveBeenTriggeredOnAndWith(document, { tag: 'inbox', mailIdent: '10' });
+ });
+
+ describe('checking/unchecking mails in the list', function () {
+
+ it('keeps a list with the currently checked mails', function () {
+ var checkedMails = {};
+
+ this.component.attr.checkedMails = {};
+
+ $(document).trigger(Smail.events.ui.mail.checked, {mail: mailList[0]});
+
+ checkedMails[mailList[0].ident] = mailList[0];
+
+ expect(this.component.attr.checkedMails).toEqual(checkedMails);
+ });
+
+ it('returns the list of checked mails to whomever requests them', function () {
+ var caller = {};
+ this.component.attr.checkedMails = {'1': {}};
+ var mailHereCheckedEvent = spyOnEvent(caller, Smail.events.ui.mail.hereChecked);
+
+ $(document).trigger(Smail.events.ui.mail.wantChecked, caller);
+
+ expect(mailHereCheckedEvent).toHaveBeenTriggeredOnAndWith(caller, { checkedMails: {'1': {} }});
+ });
+
+ it('returns an empty list to whomever requests the checked mails if there are no checked mails', function () {
+ var caller = {};
+ var mailHereCheckedEvent = spyOnEvent(caller, Smail.events.ui.mail.hereChecked);
+
+ $(document).trigger(Smail.events.ui.mail.wantChecked, caller);
+
+ expect(mailHereCheckedEvent).toHaveBeenTriggeredOnAndWith(caller, { checkedMails: {} });
+ });
+
+ it('removes for the checked mails when mail is unchecked', function () {
+ this.component.attr.checkedMails = {
+ '1': {},
+ '2': {},
+ '3': {}
+ };
+
+ $(document).trigger(Smail.events.ui.mail.unchecked, {mail: {ident: '1'}});
+
+ expect(this.component.attr.checkedMails).toEqual({'2': {}, '3': {} });
+ });
+
+ it('checks the check all checkbox if at least one mail is checked', function () {
+ var setCheckAllCheckboxEvent = spyOnEvent(document, Smail.events.ui.mails.hasMailsChecked);
+
+ $(document).trigger(Smail.events.ui.mail.checked, {mail: mailList[0]});
+
+ expect(setCheckAllCheckboxEvent).toHaveBeenTriggeredOnAndWith(document, true);
+ });
+
+ it('unchecks the check all checkbox if no mail is left checked', function () {
+ this.component.attr.checkedMails = {1: {}};
+
+ var setCheckAllCheckboxEvent = spyOnEvent(document, Smail.events.ui.mails.hasMailsChecked);
+
+ $(document).trigger(Smail.events.ui.mail.unchecked, {mail: {ident: '1'}});
+
+ expect(setCheckAllCheckboxEvent).toHaveBeenTriggeredOnAndWith(document, false);
+ });
+ });
+
+ describe('when mails are available', function () {
+ it('should open email if popstate event happened (when mailIdent isnt undefined)', function () {
+ var openMailEvent = spyOnEvent(document, Smail.events.ui.mail.open);
+
+ this.component.$node.trigger(Smail.events.mails.available, { mails: mailList, mailIdent: '30' });
+
+ expect(openMailEvent).toHaveBeenTriggeredOnAndWith(document, { ident: '30'});
+ });
+
+ it('should open draft in popstate event if tag is Drafts', function () {
+ var openDraftEvent = spyOnEvent(document, Smail.events.dispatchers.rightPane.openDraft);
+
+ this.component.$node.trigger(Smail.events.mails.available, { mails: mailList, mailIdent: '30', tag: 'drafts' });
+
+ expect(openDraftEvent).toHaveBeenTriggeredOnAndWith(document, { ident: '30'});
+ });
+ });
+
+ it('should not append emails when another mails:available event is triggered', function () {
+ this.component.$node.trigger(Smail.events.mails.available, { mails: mailList });
+
+ expect(this.component.$node.find('a').length).toEqual(2);
+
+ this.component.$node.trigger(Smail.events.mails.available, { mails: mailList });
+
+ expect(this.component.$node.find('a').length).toEqual(2);
+ });
+
+ it('resets scroll when opening a new tag or choosing a new tag', function () {
+ var eventSpy = spyOnEvent(document, Smail.events.dispatchers.middlePane.resetScroll);
+ this.component.$node.trigger(Smail.events.mails.available, { mails: mailList });
+ expect(eventSpy).toHaveBeenTriggeredOn(document);
+ });
+
+ describe('rendering the mails', function () {
+
+ describe('when mails are available for refreshing', function () {
+ it('renders the new mails', function () {
+ this.component.$node.trigger(Smail.events.mails.availableForRefresh, { mails: mailList });
+
+ matchMail(mailList[0], this.component.$node);
+ matchMail(mailList[1], this.component.$node);
+ });
+
+ });
+
+ it('should render all mails sent in ui:mails:show event', function () {
+ this.component.$node.trigger(Smail.events.mails.available, { mails: mailList });
+
+ matchMail(mailList[0], this.component.$node);
+ matchMail(mailList[1], this.component.$node);
+ });
+
+ it('should select the current email when mails are available', function () {
+ this.component.attr.currentMailIdent = '1';
+
+ this.component.trigger(Smail.events.mails.available, { mails: mailList });
+
+ matchSelectedMail(mailList[0], this.component.$node);
+ matchMail(mailList[1], this.component.$node);
+ });
+
+ it('should keep the mail checked when it was previously checked (so refresh works)', function () {
+ var checkbox, mailIdent;
+
+ mailIdent = mailList[0].ident;
+ this.component.attr.checkedMails[mailIdent] = mailList[0];
+ this.component.$node.trigger(Smail.events.mails.available, { mails: [mailList[0]] });
+ checkbox = this.$node.find('input[type=checkbox]');
+
+ expect(checkbox.prop('checked')).toBe(true);
+ });
+
+ it('should render links for the emails', function () {
+ this.component.$node.trigger(Smail.events.mails.available, { mails: mailList, tag: 'inbox' });
+
+ expect(this.$node.html()).toMatch('href="/#/inbox/mail/1');
+ expect(this.$node.html()).toMatch('href="/#/inbox/mail/2');
+ });
+
+ it('should clean the selected email', function () {
+ this.component.attr.currentMailIdent = '1';
+ this.component.trigger(Smail.events.ui.mails.cleanSelected);
+
+ expect(this.component.attr.currentMailIdent).toEqual('');
+ });
+
+ function matchMail(mail, node) {
+ expect(node.html()).toMatch('id="mail-' + mail.ident + '"');
+ expect(node.html()).toMatch('<div class="subject-and-tags">');
+ expect(node.html()).toMatch('<div class="from">' + mail.header.from + '</div>');
+ expect(node.html()).toMatch('<span class="received-date">' + mail.header.formattedDate + '</span>');
+ }
+
+ function matchSelectedMail(mail, node) {
+ expect(node.html()).toMatch(['id="mail-', mail.ident, '" class="selected"'].join(''));
+ }
+ });
+
+ describe('when saving a draft', function () {
+ it('refreshes the list if the current tag is drafts', function () {
+ this.component.attr.currentTag = 'drafts';
+ var spyRefresh = spyOnEvent(document, Smail.events.ui.mails.refresh);
+ var spyScroll = spyOnEvent(document, Smail.events.dispatchers.middlePane.resetScroll);
+ this.component.trigger(Smail.events.mail.draftSaved, {ident: 1});
+ expect(spyRefresh).toHaveBeenTriggeredOn(document);
+ expect(spyScroll).toHaveBeenTriggeredOn(document);
+ });
+
+ it('does not refresh the list if the current tag is not drafts', function() {
+ this.component.attr.currentTag = 'sent';
+ var spyRefresh = spyOnEvent(document, Smail.events.ui.mails.refresh);
+ var spyScroll = spyOnEvent(document, Smail.events.dispatchers.middlePane.resetScroll);
+ this.component.trigger(Smail.events.mail.draftSaved, {ident: 1});
+ expect(spyRefresh).not.toHaveBeenTriggeredOn(document);
+ expect(spyScroll).not.toHaveBeenTriggeredOn(document);
+ });
+ });
+
+ describe('when sending a mail', function () {
+ it('refreshes the list if the current tag is drafts', function () {
+ this.component.attr.currentTag = 'drafts';
+ var spyRefresh = spyOnEvent(document, Smail.events.ui.mails.refresh);
+ var spyScroll = spyOnEvent(document, Smail.events.dispatchers.middlePane.resetScroll);
+ this.component.trigger(Smail.events.mail.sent);
+ expect(spyRefresh).toHaveBeenTriggeredOn(document);
+ expect(spyScroll).toHaveBeenTriggeredOn(document);
+ });
+
+ it('refreshes the list if the current tag is sent', function() {
+ this.component.attr.currentTag = 'sent';
+ var spyRefresh = spyOnEvent(document, Smail.events.ui.mails.refresh);
+ var spyScroll = spyOnEvent(document, Smail.events.dispatchers.middlePane.resetScroll);
+ this.component.trigger(Smail.events.mail.sent);
+ expect(spyRefresh).toHaveBeenTriggeredOn(document);
+ expect(spyScroll).toHaveBeenTriggeredOn(document);
+ });
+ });
+
+ function createMail(subject, from, ident, date) {
+ var mail = Smail.testData().parsedMail.simpleTextPlain;
+
+ return _.merge(mail, {
+ header: {
+ subject: subject,
+ from: from,
+ date: date
+ },
+ ident: ident,
+ tags: ['inbox']
+ });
+ }
+});
diff --git a/web-ui/test/spec/mail_list_actions/ui/compose_trigger.spec.js b/web-ui/test/spec/mail_list_actions/ui/compose_trigger.spec.js
new file mode 100644
index 00000000..4942b5b6
--- /dev/null
+++ b/web-ui/test/spec/mail_list_actions/ui/compose_trigger.spec.js
@@ -0,0 +1,16 @@
+describeComponent('mail_list_actions/ui/compose_trigger', function () {
+ 'use strict';
+
+ beforeEach(function () {
+ setupComponent('<div></div>');
+ });
+
+ it('triggers the enableComposebox event when clicked', function () {
+ var spyEvent = spyOnEvent(document, Smail.events.dispatchers.rightPane.openComposeBox);
+
+ this.component.trigger('click');
+
+ expect(spyEvent).toHaveBeenTriggeredOn(document);
+ });
+
+});
diff --git a/web-ui/test/spec/mail_list_actions/ui/mail_actions.spec.js b/web-ui/test/spec/mail_list_actions/ui/mail_actions.spec.js
new file mode 100644
index 00000000..f7a0ed72
--- /dev/null
+++ b/web-ui/test/spec/mail_list_actions/ui/mail_actions.spec.js
@@ -0,0 +1,9 @@
+describeComponent('mail_list_actions/ui/mail_actions', function () {
+ 'use strict';
+
+ beforeEach(function () {
+ setupComponent();
+ });
+
+
+});
diff --git a/web-ui/test/spec/mail_list_actions/ui/pagination_trigger.spec.js b/web-ui/test/spec/mail_list_actions/ui/pagination_trigger.spec.js
new file mode 100644
index 00000000..67c2f3ef
--- /dev/null
+++ b/web-ui/test/spec/mail_list_actions/ui/pagination_trigger.spec.js
@@ -0,0 +1,26 @@
+describeComponent('mail_list_actions/ui/pagination_trigger', function () {
+ 'use strict';
+
+ beforeEach(function () {
+ setupComponent();
+ });
+
+ it('triggers the ui:page:previous event when the left arrow is clicked', function () {
+ var eventSpy = spyOnEvent(document, Smail.events.ui.page.previous);
+ this.component.select('previous').click();
+ expect(eventSpy).toHaveBeenTriggeredOn(document);
+ });
+
+
+ it('triggers the ui:page:next event when the right arrow is clicked', function () {
+ var eventSpy = spyOnEvent(document, Smail.events.ui.page.next);
+ this.component.select('next').click();
+ expect(eventSpy).toHaveBeenTriggeredOn(document);
+ });
+
+ it('re-renders with current page number when page changes', function () {
+ this.component.trigger(document, Smail.events.ui.page.changed, {currentPage: 0});
+
+ expect(this.component.select('currentPage').text()).toBe('1');
+ });
+});
diff --git a/web-ui/test/spec/mail_view/data/mail_builder.spec.js b/web-ui/test/spec/mail_view/data/mail_builder.spec.js
new file mode 100644
index 00000000..bf17d598
--- /dev/null
+++ b/web-ui/test/spec/mail_view/data/mail_builder.spec.js
@@ -0,0 +1,110 @@
+define(['mail_view/data/mail_builder'], function (mailBuilder) {
+ describe('mail builder', function () {
+ 'use strict';
+
+ it('sets ident if passed to constructor', function() {
+ var mail = mailBuilder.newMail('12345').build();
+
+ expect(mail.ident).toBe('12345');
+ });
+
+ it('sets ident to empty if not passed to constructor', function() {
+ var mail = mailBuilder.newMail().build();
+
+ expect(mail.ident).toBe('');
+ });
+
+ it('sets the subject', function() {
+ var mail = mailBuilder.newMail().subject("subject").build();
+
+ expect(mail.header.subject).toBe("subject");
+ });
+
+ it('sets the body', function() {
+ var mail = mailBuilder.newMail().body("some body text").build();
+
+ expect(mail.body).toBe("some body text");
+ });
+
+ describe('to field', function() {
+ it('adds a single address', function() {
+ var mail = mailBuilder.newMail().to('foo@bar.com').build();
+
+ expect(mail.header.to).toContain('foo@bar.com');
+ });
+
+ it('adds multiple addresses', function() {
+ var mail = mailBuilder.newMail().to('foo@bar.com bar@foo.com').build();
+
+ expect(mail.header.to).toContain('foo@bar.com');
+ expect(mail.header.to).toContain('bar@foo.com');
+ });
+
+ it('accepts undefined without breaking', function() {
+ var mail = mailBuilder.newMail().to(undefined).build();
+
+ expect(mail.header.to).toEqual([]);
+ });
+ });
+
+ describe('cc field', function() {
+ it('adds a single address', function() {
+ var mail = mailBuilder.newMail().cc('foo@bar.com').build();
+
+ expect(mail.header.cc).toContain('foo@bar.com');
+ });
+
+ it('adds multiple addresses', function() {
+ var mail = mailBuilder.newMail().cc('foo@bar.com bar@foo.com').build();
+
+ expect(mail.header.cc).toContain('foo@bar.com');
+ expect(mail.header.cc).toContain('bar@foo.com');
+ });
+
+ it('accepts undefined without breaking', function() {
+ var mail = mailBuilder.newMail().cc(undefined).build();
+
+ expect(mail.header.cc).toEqual([]);
+ });
+ });
+
+ describe('bcc field', function() {
+ it('adds a single address', function() {
+ var mail = mailBuilder.newMail().bcc('foo@bar.com').build();
+
+ expect(mail.header.bcc).toContain('foo@bar.com');
+ });
+
+ it('adds multiple addresses', function() {
+ var mail = mailBuilder.newMail().bcc('foo@bar.com bar@foo.com').build();
+
+ expect(mail.header.bcc).toContain('foo@bar.com');
+ expect(mail.header.bcc).toContain('bar@foo.com');
+ });
+
+ it('accepts undefined without breaking', function() {
+ var mail = mailBuilder.newMail().bcc(undefined).build();
+
+ expect(mail.header.bcc).toEqual([]);
+ });
+ });
+
+ it('adds arbitrary headers', function() {
+ var mail = mailBuilder.newMail()
+ .header('Reply-To', 'something')
+ .header('In-Reply-To', '12345')
+ .build();
+
+ expect(mail.header['Reply-To']).toBe('something');
+ expect(mail.header['In-Reply-To']).toBe('12345');
+ });
+
+ it('adds tag', function() {
+ var mail = mailBuilder.newMail()
+ .tag('tag1')
+ .build();
+
+ expect(mail.tags).toContain('tag1');
+ });
+ });
+});
diff --git a/web-ui/test/spec/mail_view/data/mail_sender.spec.js b/web-ui/test/spec/mail_view/data/mail_sender.spec.js
new file mode 100644
index 00000000..3bdc1934
--- /dev/null
+++ b/web-ui/test/spec/mail_view/data/mail_sender.spec.js
@@ -0,0 +1,70 @@
+/*global Smail */
+
+describeComponent('mail_view/data/mail_sender', function () {
+ 'use strict';
+
+ var mailBuilder;
+ var mail;
+
+ beforeEach(function () {
+ mailBuilder = require('mail_view/data/mail_builder');
+ mail = Smail.testData().parsedMail.simpleTextPlain;
+ setupComponent();
+ });
+
+ it('sends mail data with a POST to the server when asked to send email', function() {
+ var mailSentEventSpy = spyOnEvent(document, Smail.events.mail.sent);
+ var g;
+
+ spyOn($, 'ajax').andReturn({done: function(f) { g = f; return {fail: function(){}};}});
+
+ this.component.trigger(Smail.events.mail.send, mail);
+
+ g();
+
+ expect(mailSentEventSpy).toHaveBeenTriggeredOn(document);
+
+ expect($.ajax.mostRecentCall.args[0]).toEqual('/mails');
+ expect($.ajax.mostRecentCall.args[1].type).toEqual('POST');
+ expect(JSON.parse($.ajax.mostRecentCall.args[1].data).header).toEqual(mail.header);
+ expect(JSON.parse($.ajax.mostRecentCall.args[1].data).body).toEqual(mail.body);
+ });
+
+ it('save draft data with a POST to the server when asked to save draft for the first time', function() {
+ var draftSavedEventSpy = spyOnEvent(document, Smail.events.mail.draftSaved);
+ var g;
+
+ spyOn($, 'ajax').andReturn({done: function(f) { g = f; return {fail: function(){}};}});
+
+ mail.ident = '';
+ this.component.trigger(Smail.events.mail.saveDraft, mail);
+
+ g();
+
+ expect(draftSavedEventSpy).toHaveBeenTriggeredOn(document);
+
+ expect($.ajax.mostRecentCall.args[0]).toEqual('/mails');
+ expect($.ajax.mostRecentCall.args[1].type).toEqual('POST');
+ expect(JSON.parse($.ajax.mostRecentCall.args[1].data).header).toEqual(mail.header);
+ expect(JSON.parse($.ajax.mostRecentCall.args[1].data).body).toEqual(mail.body);
+ });
+
+ it('save draft data with a PUT to the server when asked to save draft for the second time', function() {
+ var draftSavedEventSpy = spyOnEvent(document, Smail.events.mail.draftSaved);
+ var g;
+
+ spyOn($, 'ajax').andReturn({done: function(f) { g = f; return {fail: function(){}};}});
+
+ mail.ident = 0;
+ this.component.trigger(Smail.events.mail.saveDraft, mail);
+
+ g();
+
+ expect(draftSavedEventSpy).toHaveBeenTriggeredOn(document);
+
+ expect($.ajax.mostRecentCall.args[0]).toEqual('/mails');
+ expect($.ajax.mostRecentCall.args[1].type).toEqual('PUT');
+ expect(JSON.parse($.ajax.mostRecentCall.args[1].data).header).toEqual(mail.header);
+ expect(JSON.parse($.ajax.mostRecentCall.args[1].data).body).toEqual(mail.body);
+ });
+});
diff --git a/web-ui/test/spec/mail_view/ui/compose_box.spec.js b/web-ui/test/spec/mail_view/ui/compose_box.spec.js
new file mode 100644
index 00000000..a131d2cf
--- /dev/null
+++ b/web-ui/test/spec/mail_view/ui/compose_box.spec.js
@@ -0,0 +1,132 @@
+/*global jasmine */
+/*global Smail */
+
+describeComponent('mail_view/ui/compose_box', function () {
+ 'use strict';
+ beforeEach(function () {
+ Smail.mockBloodhound();
+ setupComponent('<div style="display:none"></div>');
+ });
+
+
+ describe('compose new mail', function() {
+
+ it('only sends if all the recipients are valid emails', function() {
+ $(document).trigger(Smail.events.ui.recipients.updated, {recipientsName: 'to', newRecipients: ['valid@email.example']});
+
+ var eventSpy = spyOnEvent(document, Smail.events.mail.send);
+
+ $(document).trigger(Smail.events.ui.mail.send);
+
+ expect(eventSpy).toHaveBeenTriggeredOn(document);
+ });
+
+ it('sends the recipient entered', function () {
+ $(document).trigger(Smail.events.ui.recipients.updated, {recipientsName: 'to', newRecipients: ['fox@somewhere.com']});
+
+ var eventSpy = spyOnEvent(document, Smail.events.mail.send);
+
+ $(document).trigger(Smail.events.ui.mail.send);
+
+ expect(eventSpy).toHaveBeenTriggeredOn(document);
+ expect(eventSpy.mostRecentCall.data.header).toEqual(jasmine.objectContaining({
+ to: ['fox@somewhere.com']
+ }));
+ });
+
+ it('sends the multiple recipients entered', function () {
+ $(document).trigger(Smail.events.ui.recipients.updated, {recipientsName: 'to', newRecipients: ['fox@somewhere.com', 'blarg@someone.com', 'fox2@google.se']});
+ var eventSpy = spyOnEvent(document, Smail.events.mail.send);
+
+ $(document).trigger(Smail.events.ui.mail.send);
+
+ expect(eventSpy).toHaveBeenTriggeredOn(document);
+ expect(eventSpy.mostRecentCall.data.header).toEqual(jasmine.objectContaining({
+ to: ['fox@somewhere.com', 'blarg@someone.com', 'fox2@google.se']
+ }));
+ });
+
+ it('sends the subject line entered', function () {
+ $(document).trigger(Smail.events.ui.recipients.updated, {recipientsName: 'to', newRecipients: ['aa@aa.com']});
+ this.component.select('subjectBox').val('A new fancy subject!');
+ var eventSpy = spyOnEvent(document, Smail.events.mail.send);
+
+ $(document).trigger(Smail.events.ui.mail.send);
+
+ expect(eventSpy).toHaveBeenTriggeredOn(document);
+ expect(eventSpy.mostRecentCall.data.header).toEqual(jasmine.objectContaining({
+ subject: 'A new fancy subject!'
+ }));
+ });
+
+ it('sends the multiple CCs entered', function () {
+ $(document).trigger(Smail.events.ui.recipients.updated, {recipientsName: 'cc', newRecipients: ['cc1@foo.bar', 'cc2@bar.foo', 'cc3@zz.top']});
+ var eventSpy = spyOnEvent(document, Smail.events.mail.send);
+
+ $(document).trigger(Smail.events.ui.mail.send);
+
+ expect(eventSpy).toHaveBeenTriggeredOn(document);
+ expect(eventSpy.mostRecentCall.data.header).toEqual(jasmine.objectContaining({
+ cc: ['cc1@foo.bar', 'cc2@bar.foo', 'cc3@zz.top']
+ }));
+ });
+
+ it('sends the multiple BCCs entered', function () {
+ $(document).trigger(Smail.events.ui.recipients.updated, {recipientsName: 'bcc', newRecipients: ['bcc1@foo.bar', 'bcc2@bar.foo', 'bcc3@zz.top']});
+ var eventSpy = spyOnEvent(document, Smail.events.mail.send);
+
+ $(document).trigger(Smail.events.ui.mail.send);
+
+ expect(eventSpy).toHaveBeenTriggeredOn(document);
+ expect(eventSpy.mostRecentCall.data.header).toEqual(jasmine.objectContaining({
+ bcc: ['bcc1@foo.bar', 'bcc2@bar.foo', 'bcc3@zz.top']
+ }));
+ });
+
+ it('shows no message selected pane when deleting the email being composed', function() {
+ var openNoMessageSelectedPaneEvent = spyOnEvent(document, Smail.events.dispatchers.rightPane.openNoMessageSelected);
+ var mails = [{ident: 123}];
+ this.component.attr.ident = 123;
+
+ this.component.trigger(document, Smail.events.mail.deleted, {mails: mails});
+
+ expect(openNoMessageSelectedPaneEvent).toHaveBeenTriggeredOn(document);
+ });
+
+ it('does not show no message selected pane when deleting a different set of emails', function() {
+ var openNoMessageSelectedPaneEvent = spyOnEvent(document, Smail.events.dispatchers.rightPane.openNoMessageSelected);
+ var mails = [{ident: 321}];
+ this.component.attr.ident = 123;
+
+ this.component.trigger(document, Smail.events.mail.deleted, {mails: mails});
+
+ expect(openNoMessageSelectedPaneEvent).not.toHaveBeenTriggeredOn(document);
+ });
+ });
+
+ describe('close button behavior', function() {
+
+ it('should fire Show no message selected if the close button is clicked', function() {
+ var spy = spyOnEvent(document, Smail.events.dispatchers.rightPane.openNoMessageSelected);
+ this.component.select('closeButton').click();
+ expect(spy).toHaveBeenTriggeredOn(document);
+ });
+
+ });
+
+ describe('draft compose box', function() {
+ it('should save a draft when click on draft button', function () {
+ $(document).trigger(Smail.events.ui.recipients.updated, {recipientsName: 'to', newRecipients: ['fox@somewhere.com']});
+
+ this.component.select('subjectBox').val('A new fancy subject!');
+ var eventSpy = spyOnEvent(document, Smail.events.mail.saveDraft);
+
+ this.component.select('draftButton').click();
+
+ expect(eventSpy).toHaveBeenTriggeredOn(document);
+ expect(eventSpy.mostRecentCall.data.header).toEqual(jasmine.objectContaining({
+ to: ['fox@somewhere.com']
+ }));
+ });
+ });
+});
diff --git a/web-ui/test/spec/mail_view/ui/draft_box.spec.js b/web-ui/test/spec/mail_view/ui/draft_box.spec.js
new file mode 100644
index 00000000..3e02f752
--- /dev/null
+++ b/web-ui/test/spec/mail_view/ui/draft_box.spec.js
@@ -0,0 +1,67 @@
+/* global Smail */
+
+describeComponent('mail_view/ui/draft_box', function () {
+ 'use strict';
+
+ var mail;
+
+ beforeEach(function () {
+ Smail.mockBloodhound();
+ mail = Smail.testData().parsedMail.simpleTextPlain;
+ });
+
+ describe('when initializing', function () {
+ it('fetches the email to draft', function () {
+ var mailWantEvent = spyOnEvent(document, Smail.events.mail.want);
+
+ setupComponent({mailIdent: '1'});
+
+ expect(mailWantEvent).toHaveBeenTriggeredOnAndWith(document, {
+ mail: '1', caller: this.component
+ });
+ });
+ });
+
+ describe('after initialize', function () {
+ beforeEach(function () {
+ setupComponent({mailIdent: '1'});
+ });
+
+ it('renders the compose box when mail is received', function () {
+ var templates = require('views/templates');
+
+ spyOn(this.component, 'render');
+
+ this.component.trigger(this.component, Smail.events.mail.here, { mail: mail});
+
+ expect(this.component.render).toHaveBeenCalledWith(templates.compose.box, {
+ recipients: { to: mail.header.to, cc: mail.header.cc, bcc: mail.header.bcc },
+ subject: mail.header.subject,
+ body: mail.body
+ });
+ });
+
+ });
+
+ it('sending a draft sends the correct mailIdent', function () {
+ setupComponent({mailIdent: mail.ident});
+ this.component.trigger(this.component, Smail.events.mail.here, { mail: mail});
+
+ var sendDraftEvent = spyOnEvent(document, Smail.events.mail.saveDraft);
+ this.component.select('draftButton').click();
+
+ expect(sendDraftEvent).toHaveBeenTriggeredOnAndWith(document, jasmine.objectContaining({ident: mail.ident}));
+ });
+
+ it('shows no message selected pane when draft is sent', function() {
+ var openNoMessageSelectedEvent = spyOnEvent(document, Smail.events.dispatchers.rightPane.openNoMessageSelected);
+
+ setupComponent({mailIdent: mail.ident});
+ this.component.trigger(this.component, Smail.events.mail.here, { mail: mail});
+
+ this.component.trigger(document, Smail.events.mail.sent);
+
+ expect(openNoMessageSelectedEvent).toHaveBeenTriggeredOn(document);
+ });
+
+});
diff --git a/web-ui/test/spec/mail_view/ui/draft_save_status.spec.js b/web-ui/test/spec/mail_view/ui/draft_save_status.spec.js
new file mode 100644
index 00000000..fb989f4c
--- /dev/null
+++ b/web-ui/test/spec/mail_view/ui/draft_save_status.spec.js
@@ -0,0 +1,26 @@
+/* global Smail */
+
+describeComponent('mail_view/ui/draft_save_status', function () {
+ 'use strict';
+
+ beforeEach(setupComponent);
+
+ it('should be empty on initialization', function() {
+ expect(this.$node.text()).toBe('');
+ });
+
+ it('should display status when saving a draft', function() {
+ $(document).trigger(Smail.events.mail.saveDraft);
+ expect(this.$node.text()).toBe('Saving to Drafts...');
+ });
+
+ it('should display status when draft is saved', function() {
+ $(document).trigger(Smail.events.mail.draftSaved);
+ expect(this.$node.text()).toBe('Draft Saved.');
+ });
+
+ it('should reset status when mail is changed since last save', function() {
+ $(document).trigger(Smail.events.ui.mail.changedSinceLastSave);
+ expect(this.$node.text()).toBe('');
+ });
+});
diff --git a/web-ui/test/spec/mail_view/ui/forward_box.spec.js b/web-ui/test/spec/mail_view/ui/forward_box.spec.js
new file mode 100644
index 00000000..fc878447
--- /dev/null
+++ b/web-ui/test/spec/mail_view/ui/forward_box.spec.js
@@ -0,0 +1,90 @@
+/*global jasmine */
+/*global Smail */
+
+describeComponent('mail_view/ui/forward_box', function () {
+ 'use strict';
+
+ var attrs;
+ var testMail;
+ beforeEach(function () {
+ testMail = Smail.testData().parsedMail.simpleTextPlain;
+
+ Smail.mockBloodhound();
+ });
+
+ it('should have a subject of Fwd: <original_message>', function() {
+ testMail.header.subject = 'Very interesting';
+ setupComponent({ mail: testMail });
+
+ expect(this.component.select('subjectDisplay').text()).toEqual('Fwd: '+ testMail.header.subject);
+ });
+
+ it('should have no recipients', function () {
+ var Recipients = require('mail_view/ui/recipients/recipients');
+ spyOn(Recipients, 'attachTo');
+
+ setupComponent({ mail: testMail });
+
+ expect(Recipients.attachTo.calls[0].args[1]).toEqual({name: 'to', addresses: []});
+ expect(Recipients.attachTo.calls[1].args[1]).toEqual({name: 'cc', addresses: []});
+ expect(Recipients.attachTo.calls[2].args[1]).toEqual({name: 'bcc', addresses: []});
+ });
+
+ it('should populate body text area with quote of email being forwarded', function() {
+ var viewHelper = require('helpers/view_helper');
+ spyOn(viewHelper, 'quoteMail').andReturn('quoted email');
+
+ setupComponent({ mail: testMail });
+
+ expect(viewHelper.quoteMail).toHaveBeenCalledWith(testMail);
+ expect(this.component.select('bodyBox').val()).toBe('quoted email');
+ });
+
+ it('should show subject field when clicking on subject display', function() {
+ setupComponent({ mail: testMail });
+
+ this.component.select('subjectDisplay').click();
+
+ expect(this.component.select('subjectInput')).not.toBeHidden();
+ expect(this.component.select('subjectDisplay')).toBeHidden();
+ });
+
+ it('should copy original message headers', function() {
+ var mailSendEvent = spyOnEvent(document, Smail.events.mail.send);
+
+ testMail.header.bcc = 'original_bcc@email.com';
+ testMail.header.cc = 'original_cc@email.com';
+ testMail.header.date = 'original_date';
+ testMail.header.from = 'original_from';
+ testMail.header.message_id = 'original_message_id';
+ testMail.header.reply_to = 'original_reply_to@email.com';
+ testMail.header.sender = 'original_sender';
+ testMail.header.to = 'original_to@email.com';
+
+ setupComponent({ mail: testMail });
+
+ this.component.attr.recipientValues.to.push('forward_to@email.com');
+ $(document).trigger(Smail.events.ui.mail.send);
+
+ expect(mailSendEvent).toHaveBeenTriggeredOn(document);
+ var sentMail = mailSendEvent.mostRecentCall.data;
+
+ expect(sentMail.header).toEqual(jasmine.objectContaining({
+ resent_bcc: 'original_bcc@email.com',
+ resent_cc: 'original_cc@email.com',
+ resent_date: 'original_date',
+ resent_from: 'original_from',
+ resent_message_id: 'original_message_id',
+ resent_reply_to: 'original_reply_to@email.com',
+ resent_sender: 'original_sender',
+ resent_to: 'original_to@email.com'
+ }));
+ });
+
+ it('triggers openMail when email is sent', function() {
+ var eventSpy = spyOnEvent(document, Smail.events.ui.mail.open);
+ setupComponent({ mail: testMail });
+ $(document).trigger(Smail.events.mail.sent, {ident: testMail.ident});
+ expect(eventSpy).toHaveBeenTriggeredOnAndWith(document, {ident: testMail.ident});
+ });
+});
diff --git a/web-ui/test/spec/mail_view/ui/mail_actions.spec.js b/web-ui/test/spec/mail_view/ui/mail_actions.spec.js
new file mode 100644
index 00000000..93db0193
--- /dev/null
+++ b/web-ui/test/spec/mail_view/ui/mail_actions.spec.js
@@ -0,0 +1,63 @@
+/*global Smail */
+
+describeComponent('mail_view/ui/mail_actions', function () {
+ 'use strict';
+
+ var testData;
+
+ beforeEach(function(){
+ testData = Smail.testData();
+ setupComponent(testData);
+ });
+
+ it('verifies if more actions list is hidden when rendering mail view', function() {
+
+ var moreActionsComponent = this.component.select('moreActions');
+ expect(moreActionsComponent.attr('style').trim()).toEqual('display: none;');
+
+ });
+
+ it('show more actions list when click on view more actions button', function(){
+
+ this.component.select('viewMoreActions').click();
+
+ var moreActionsComponent = this.component.select('moreActions');
+ expect(moreActionsComponent.attr('style').trim()).not.toEqual('display: none;');
+ });
+
+ it('triggers a show reply box event when clicking on reply-button-top', function(){
+
+ var showReplyBoxEvent = spyOnEvent(document, Smail.events.ui.replyBox.showReply);
+
+ this.component.select('replyButtonTop').click();
+
+ expect(showReplyBoxEvent).toHaveBeenTriggeredOn(document);
+ });
+
+ it('triggers a show reply all box event when clicking on reply-button-top and hide more actions list', function(){
+
+ var showReplyAllEvent = spyOnEvent(document, Smail.events.ui.replyBox.showReplyAll);
+
+ this.component.select('viewMoreActions').click();
+ this.component.select('replyAllButtonTop').click();
+
+ expect(showReplyAllEvent).toHaveBeenTriggeredOn(document);
+
+ var moreActionsComponent = this.component.select('moreActions');
+ expect(moreActionsComponent.attr('style').trim()).toEqual('display: none;');
+ });
+
+ it('triggers a delete event when clicking on delete-button-top', function(){
+
+ var deleteEvent = spyOnEvent(document, Smail.events.ui.mail.delete);
+
+ this.component.select('viewMoreActions').click();
+ this.component.select('deleteButtonTop').click();
+
+ expect(deleteEvent).toHaveBeenTriggeredOnAndWith(document, {mail: testData.mail});
+
+ var moreActionsComponent = this.component.select('moreActions');
+ expect(moreActionsComponent.attr('style').trim()).toEqual('display: none;');
+ });
+
+});
diff --git a/web-ui/test/spec/mail_view/ui/mail_view.spec.js b/web-ui/test/spec/mail_view/ui/mail_view.spec.js
new file mode 100644
index 00000000..80f253e0
--- /dev/null
+++ b/web-ui/test/spec/mail_view/ui/mail_view.spec.js
@@ -0,0 +1,247 @@
+/*global Smail */
+
+describeComponent('mail_view/ui/mail_view', function () {
+ 'use strict';
+
+ var mail;
+
+ var testData;
+
+ beforeEach(function () {
+ mail = {ident: 1, header: { date: '12/12/12T12:12' }, tags: ['inbox']};
+ testData = {mail: Smail.testData().parsedMail.simpleTextPlain};
+ Smail.mockBloodhound();
+ setupComponent('<div></div>', {mail: mail});
+ });
+
+ it('triggers mail:want on ui:openMail', function () {
+ var spyEvent = spyOnEvent(document, Smail.events.mail.want);
+
+ setupComponent('<div></div>', {ident: mail.ident });
+
+ expect(spyEvent).toHaveBeenTriggeredOn(document);
+ expect(spyEvent.mostRecentCall.data.mail).toEqual(1);
+ });
+
+ it('triggers dispatchers.rightPane.openNoMessageSelected when getting mail.notFound', function () {
+ var openNoMessageSelectedEvent = spyOnEvent(document, Smail.events.dispatchers.rightPane.openNoMessageSelected);
+
+ this.component.trigger(this.component, Smail.events.mail.notFound);
+
+ expect(openNoMessageSelectedEvent).toHaveBeenTriggeredOn(document);
+ });
+
+
+ it('removes the tag from the mail when the tag label is clicked', function() {
+ var updateSpy = spyOnEvent(document, Smail.events.mail.tags.update);
+
+ this.component.displayMail({}, testData);
+ this.component.removeTag('inbox');
+
+ expect(updateSpy).toHaveBeenTriggeredOn(document);
+ });
+
+ it('verifies if new tag input is hidden when rendering mail view', function() {
+
+ this.component.displayMail({}, testData);
+
+ var newTagInputComponent = this.component.select('newTagInput');
+ expect(newTagInputComponent.attr('style').trim()).toEqual('display: none;');
+ });
+
+ it('verifies if new tag input is shown when clicking on new tag button ', function() {
+ this.component.displayMail({}, testData);
+
+ var newTagInputComponent = this.component.select('newTagInput');
+ var addNewComponent = this.component.select('addNew');
+
+ this.component.select('newTagButton').click();
+
+ expect(newTagInputComponent.attr('style').trim()).not.toEqual('display: none;');
+ expect(addNewComponent.attr('style').trim()).toEqual('display: none;');
+ });
+
+ it('hides new tag button when pressing esc key', function(){
+ this.component.displayMail({}, testData);
+ this.component.select('newTagButton').click();
+
+ var e = creatingEvent("keydown", 27);
+ var newTagInputComponent = this.component.select('newTagInput');
+ var addNewComponent = this.component.select('addNew');
+
+ newTagInputComponent.trigger(e);
+
+ expect(newTagInputComponent.attr('style').trim()).toEqual('display: none;');
+ expect(addNewComponent.attr('style').trim()).not.toEqual('display: none;');
+ });
+
+ it('assumes that the mail is encrypted and valid if at least one of the locks are valid', function() {
+ var email = testData;
+ email.security_casing = {locks: [{state: 'valid'}, {state: 'failure'}]};
+ expect(this.component.checkEncrypted(email)).toEqual('encrypted encryption-valid');
+ });
+
+ it('assumes that the mail is encrypted and failure if all the locks are failed', function() {
+ var email = testData;
+ email.security_casing = {locks: [{state: 'failure'}, {state: 'failure'}]};
+ expect(this.component.checkEncrypted(email)).toEqual('encrypted encryption-failure');
+ });
+
+ it('assumes that the mail is not encrypted if it doesn\'t have any locks', function() {
+ var email = testData;
+ email.security_casing = {locks: []};
+ expect(this.component.checkEncrypted(email)).toEqual('not-encrypted');
+ });
+
+ it('assumes that the mail is signed only if all imprints are valid', function() {
+ var email = testData;
+ email.security_casing = {imprints: [{state: 'valid', seal: {trust: 'marginal', validity: 'marginal'}}, {state: 'valid', seal: {trust: 'marginal', validity: 'marginal'}}]};
+ expect(this.component.checkSigned(email)).toEqual('signed');
+ });
+
+ it('assumes that the mail is signed with failures if there is a revoke or expire', function() {
+ var email = testData;
+ email.security_casing = {imprints: [{state: 'valid', seal: {trust: 'marginal', validity: 'marginal'}}, {state: 'from_revoked', seal: {trust: 'marginal', validity: 'marginal'}}]};
+ expect(this.component.checkSigned(email)).toEqual('signed signature-revoked');
+ });
+
+ it('assumes that mail is not trusted if its signature contains no_trust from the user', function() {
+ var email = testData;
+ email.security_casing = {imprints: [{seal: {trust: "no_trust", validity: "ultimate"}}]};
+ expect(this.component.checkSigned(email)).toEqual('signed signature-not-trusted');
+ });
+
+ it('uses validity when trust is not present', function() {
+ var email = testData;
+ email.security_casing = {imprints: [{seal: { validity: "no_trust"}}]};
+ expect(this.component.checkSigned(email)).toEqual('signed signature-not-trusted');
+ });
+
+ it('assumes not trusted when the signature is not found', function(){
+ var email = testData;
+ email.security_casing = {imprints: [{seal: null}]};
+ expect(this.component.checkSigned(email)).toEqual('signed signature-not-trusted');
+ });
+
+ it('assumes that the mail is not signed if there are no imprints', function() {
+ var email = testData
+ email.security_casing = {imprints: []}
+ expect(this.component.checkSigned(email)).toEqual('not-signed');
+ });
+
+ it('shows that mail is encrypted if it is', function() {
+ spyOn(this.component, 'checkEncrypted').andReturn('encrypted');
+ this.component.displayMail({}, testData);
+ expect(this.component.$node.find('.encrypted')).toExist();
+ });
+
+ it('shows that mail is signed if it is', function() {
+ spyOn(this.component, 'checkSigned').andReturn('signed');
+ this.component.displayMail({}, testData);
+ expect(this.component.$node.find('.signed')).toExist();
+ });
+
+ it('shows that mail is not encrypted if it isn\'t', function() {
+ spyOn(this.component, 'checkEncrypted').andReturn('not-encrypted');
+ this.component.displayMail({}, testData);
+ expect(this.component.$node.find('.not-encrypted')).toExist();
+ });
+
+ it('shows that mail is not signed if it isn\'t', function() {
+ spyOn(this.component, 'checkEncrypted').andReturn('not-signed');
+ this.component.displayMail({}, testData);
+ expect(this.component.$node.find('.not-signed')).toExist();
+ });
+
+ it('creates new tag when pressing Enter key on new tag input', function(){
+ var tagsUpdateEvent = spyOnEvent(document, Smail.events.mail.tags.update);
+ var tagListRefreshEvent = spyOnEvent(document, Smail.events.dispatchers.tags.refreshTagList);
+ var e = creatingEvent("keydown", 13);
+
+ this.component.displayMail({}, testData);
+ this.component.select('newTagButton').click();
+
+ var newTagInputComponent = this.component.select('newTagInput');
+ newTagInputComponent.val('Test');
+ newTagInputComponent.trigger(e);
+
+ var tags = testData.mail.tags.slice();
+ tags.push('Test');
+
+ expect(tagListRefreshEvent).toHaveBeenTriggeredOn(document);
+ expect(tagsUpdateEvent).toHaveBeenTriggeredOnAndWith(document, { ident: testData.mail.ident, tags: tags});
+ });
+
+ it('trigger mail delete event when moving email to trash', function(){
+ var mailDeleteEvent = spyOnEvent(document, Smail.events.ui.mail.delete);
+
+ Foundation.global.namespace = '';
+ $(document).foundation();
+
+ this.component.displayMail({}, testData);
+ this.component.moveToTrash();
+
+ expect(mailDeleteEvent).toHaveBeenTriggeredOnAndWith(document, { mail: this.component.attr.mail });
+ });
+
+ it('shows no message selected pane when deleting the email being composed', function() {
+ var openNoMessageSelectedPaneEvent = spyOnEvent(document, Smail.events.dispatchers.rightPane.openNoMessageSelected);
+ var mails = [{ident: 123}];
+ this.component.attr.mail = mails[0];
+
+ this.component.trigger(document, Smail.events.mail.deleted, {mails: mails});
+
+ expect(openNoMessageSelectedPaneEvent).toHaveBeenTriggeredOn(document);
+ });
+
+ it('does not show no message selected pane when deleting a different set of emails', function() {
+ var openNoMessageSelectedPaneEvent = spyOnEvent(document, Smail.events.dispatchers.rightPane.openNoMessageSelected);
+ var mails = [{ident: 321}];
+ this.component.attr.mail = {ident: 123};
+
+ this.component.trigger(document, Smail.events.mail.deleted, {mails: mails});
+
+ expect(openNoMessageSelectedPaneEvent).not.toHaveBeenTriggeredOn(document);
+ });
+
+ describe('archiving email', function() {
+ it('trigger tag updates events with no tags', function(){
+ var tagsUpdateEvent = spyOnEvent(document, Smail.events.mail.tags.update);
+
+ Foundation.global.namespace = '';
+ $(document).foundation();
+
+ this.component.displayMail({}, testData);
+ this.component.archiveIt();
+
+ expect(tagsUpdateEvent).toHaveBeenTriggeredOnAndWith(document, { ident: testData.mail.ident, tags: []});
+ });
+
+ it('opens no message selected pane', function() {
+ var openNoMessageSelectedEvent = spyOnEvent(document, Smail.events.dispatchers.rightPane.openNoMessageSelected);
+
+ Foundation.global.namespace = '';
+ $(document).foundation();
+
+ this.component.displayMail({}, testData);
+ this.component.archiveIt();
+
+ expect(openNoMessageSelectedEvent).toHaveBeenTriggeredOn(document);
+ });
+ });
+
+ it('opens the no message selected pane when clicking the close button', function() {
+ var openNoMessageSelectedEvent = spyOnEvent(document, Smail.events.dispatchers.rightPane.openNoMessageSelected);
+
+ this.component.displayMail({}, testData);
+ this.component.select('closeMailButton').click();
+
+ expect(openNoMessageSelectedEvent).toHaveBeenTriggeredOn(document);
+ });
+
+ function creatingEvent(event, keyCode) {
+ var e = $.Event(event);
+ e.which = keyCode;
+ return e;
+ }
+});
diff --git a/web-ui/test/spec/mail_view/ui/recipients/recipients.spec.js b/web-ui/test/spec/mail_view/ui/recipients/recipients.spec.js
new file mode 100644
index 00000000..692bf0eb
--- /dev/null
+++ b/web-ui/test/spec/mail_view/ui/recipients/recipients.spec.js
@@ -0,0 +1,36 @@
+/* global Smail */
+
+describeComponent('mail_view/ui/recipients/recipients',function () {
+ 'use strict';
+ var recipientsUpdatedEvent;
+
+ describe('initialization', function() {
+ it('adds recipients', function() {
+ setupComponent({name: 'to', addresses: ['foobar@gmail.com'] });
+ expect(this.component.attr.recipients.length).toBe(1);
+ });
+
+ it('does not trigger recipients updated events on initialization', function() {
+ recipientsUpdatedEvent = spyOnEvent(document, Smail.events.ui.recipients.updated);
+
+ setupComponent({name: 'to', addresses: ['foobar@gmail.com'] });
+ expect(recipientsUpdatedEvent).not.toHaveBeenTriggeredOn(document);
+ });
+ });
+
+ describe('adding recipients from the ui', function() {
+ beforeEach(function () {
+ setupComponent();
+ recipientsUpdatedEvent = spyOnEvent(document, Smail.events.ui.recipients.updated);
+ this.component.trigger(Smail.events.ui.recipients.entered, {name: 'to', addresses: ['foobar@gmail.com'] });
+ });
+
+ it('triggers recipients updated', function() {
+ expect(recipientsUpdatedEvent).toHaveBeenTriggeredOn(document);
+ });
+
+ it('adds recipients', function() {
+ expect(this.component.attr.recipients.length).toBe(1);
+ });
+ });
+});
diff --git a/web-ui/test/spec/mail_view/ui/recipients/recipients_input.spec.js b/web-ui/test/spec/mail_view/ui/recipients/recipients_input.spec.js
new file mode 100644
index 00000000..0f98d007
--- /dev/null
+++ b/web-ui/test/spec/mail_view/ui/recipients/recipients_input.spec.js
@@ -0,0 +1,104 @@
+/* global Smail */
+
+describeComponent('mail_view/ui/recipients/recipients_input',function () {
+ 'use strict';
+
+ beforeEach(function () {
+ setupComponent({name: 'to'});
+ });
+
+ describe('keys that finish address input', function () {
+
+ _.each([
+ [186, 'semicolon'],
+ [188, 'comma'],
+ [32, 'space']
+
+ ], function (keycode) {
+
+ it(': ' + keycode[1], function () {
+ var addressEnteredEvent = spyOnEvent(this.$node, Smail.events.ui.recipients.entered);
+
+ var enterAddressKeyPressEvent = $.Event('keydown', { which: keycode[0] });
+ this.$node.val('a@b.c');
+ this.$node.trigger(enterAddressKeyPressEvent);
+
+ expect(addressEnteredEvent).toHaveBeenTriggeredOnAndWith(this, { name: 'to', address: 'a@b.c' });
+ });
+
+ it('wont add address if val is empty: ' + keycode[1], function () {
+ var addressEnteredEvent = spyOnEvent(this.$node, Smail.events.ui.recipients.entered);
+
+ var enterAddressKeyPressEvent = $.Event('keydown', { which: keycode[0] });
+ this.$node.val('');
+ this.$node.trigger(enterAddressKeyPressEvent);
+
+ expect(addressEnteredEvent).not.toHaveBeenTriggeredOnAndWith(this, { name: 'to', address: '' });
+ });
+
+ it('prevents event default regardless on input val when key is ' + keycode[1], function () {
+ var enterAddressKeyPressEvent = $.Event('keydown', { which: keycode[0] });
+ spyOn(enterAddressKeyPressEvent, 'preventDefault');
+
+ this.$node.val('')
+ this.$node.trigger(enterAddressKeyPressEvent);
+ expect(enterAddressKeyPressEvent.preventDefault).toHaveBeenCalled();
+
+ enterAddressKeyPressEvent.preventDefault.reset();
+ this.$node.val('anything')
+ this.$node.trigger(enterAddressKeyPressEvent);
+ expect(enterAddressKeyPressEvent.preventDefault).toHaveBeenCalled();
+ });
+
+ });
+
+ describe('when tab is pressed', function () {
+ it('enters an address and prevents event default if there is an input val', function () {
+ var addressEnteredEvent = spyOnEvent(this.$node, Smail.events.ui.recipients.entered);
+
+ var tabKeyPressEvent = $.Event('keydown', { which: 9});
+ spyOn(tabKeyPressEvent, 'preventDefault');
+
+ this.$node.val('a@b.c');
+ this.$node.trigger(tabKeyPressEvent);
+
+ expect(tabKeyPressEvent.preventDefault).toHaveBeenCalled();
+ expect(addressEnteredEvent).toHaveBeenTriggeredOnAndWith(this, { name: 'to', address: 'a@b.c'});
+ });
+
+ it('doesnt enter an address and doesnt prevent event default if input val is empty (so tab moves it to the next input)', function () {
+ var addressEnteredEvent = spyOnEvent(this.$node, Smail.events.ui.recipients.entered);
+
+ var tabKeyPressEvent = $.Event('keydown', { which: 9});
+ spyOn(tabKeyPressEvent, 'preventDefault');
+
+ this.$node.val('');
+ this.$node.trigger(tabKeyPressEvent);
+
+ expect(tabKeyPressEvent.preventDefault).not.toHaveBeenCalled();
+ expect(addressEnteredEvent).not.toHaveBeenTriggeredOnAndWith(this, { name: 'to', address: ''});
+ });
+ });
+ });
+
+ describe('on keyup', function () {
+ it('triggers inputHasNoMail if input is empty', function () {
+ var inputHasNoMailEvent = spyOnEvent(document, Smail.events.ui.recipients.inputHasNoMail);
+ this.$node.val('');
+
+ this.$node.trigger('keyup');
+
+ expect(inputHasNoMailEvent).toHaveBeenTriggeredOn(document);
+ });
+
+ it('triggers inputHasMail if input is not empty', function () {
+ var inputHasMailEvent = spyOnEvent(document, Smail.events.ui.recipients.inputHasMail);
+ this.$node.val('lalala');
+
+ this.$node.trigger('keyup');
+
+ expect(inputHasMailEvent).toHaveBeenTriggeredOn(document, { name: 'to' });
+ });
+ });
+
+});
diff --git a/web-ui/test/spec/mail_view/ui/recipients/recipients_iterator.spec.js b/web-ui/test/spec/mail_view/ui/recipients/recipients_iterator.spec.js
new file mode 100644
index 00000000..480ea256
--- /dev/null
+++ b/web-ui/test/spec/mail_view/ui/recipients/recipients_iterator.spec.js
@@ -0,0 +1,101 @@
+/* global Smail */
+
+define(['mail_view/ui/recipients/recipients_iterator'], function (RecipientsIterator) {
+ 'use strict';
+
+ function createRecipient() {
+ return jasmine.createSpyObj('recipient', ['select', 'unselect', 'destroy']);
+ }
+
+ var recipientsIterator,
+ exitInput;
+
+ function createIterator(elements) {
+ return recipientsIterator = new RecipientsIterator({ elements: elements, exitInput: exitInput });
+ }
+
+ function resetMock(m) {
+ m.destroy.reset();m.select.reset();m.unselect.reset();
+ }
+
+ beforeEach(function () {
+ exitInput = $('<input>');
+ });
+
+ describe('moving left', function () {
+ it('unselects the current element and selects the element in the left if there is one', function () {
+ var elements = _.times(2, createRecipient);
+
+ recipientsIterator = createIterator(elements);
+ recipientsIterator.moveLeft();
+
+ expect(elements[0].select).toHaveBeenCalled();
+ expect(elements[1].unselect).toHaveBeenCalled();
+ });
+
+ it('doesnt do anything if there are no elements in the left', function () {
+ var elements = _.times(2, createRecipient);
+ recipientsIterator = createIterator(elements);
+ recipientsIterator.moveLeft();
+ _.each(elements, resetMock);
+
+ recipientsIterator.moveLeft();
+
+ expect(elements[0].select).not.toHaveBeenCalled();
+ expect(elements[0].unselect).not.toHaveBeenCalled();
+ expect(elements[1].select).not.toHaveBeenCalled();
+ expect(elements[1].unselect).not.toHaveBeenCalled();
+ });
+
+ });
+
+ describe('moving right', function () {
+ it('unselects the current element and selects the one in the right if there is one', function () {
+ var elements = _.times(2, createRecipient);
+ recipientsIterator = createIterator(elements);
+ recipientsIterator.moveLeft();
+ _.each(elements, resetMock);
+
+ recipientsIterator.moveRight();
+
+ expect(elements[0].unselect).toHaveBeenCalled();
+ expect(elements[1].select).toHaveBeenCalled();
+ });
+
+ it('unselects current element and focus on exit input if there are no elements on the right', function () {
+ var elements = _.times(2, createRecipient);
+ spyOn(exitInput, 'focus');
+
+ recipientsIterator = createIterator(elements);
+ recipientsIterator.moveRight();
+
+ expect(elements[1].unselect).toHaveBeenCalled();
+ expect(exitInput.focus).toHaveBeenCalled();
+ });
+ });
+
+ describe('delete current', function () {
+ it('selects what is left in the right after deleting the current element', function () {
+ var elements = _.times(2, createRecipient);
+ var toBeDeleted = elements[0];
+ recipientsIterator = createIterator(elements);
+ recipientsIterator.moveLeft();
+
+ recipientsIterator.deleteCurrent();
+
+ expect(toBeDeleted.destroy).toHaveBeenCalled();
+ expect(elements[0].select).toHaveBeenCalled();
+ });
+
+ it('focus on the input if there are no more elements', function () {
+ recipientsIterator = createIterator([createRecipient()]);
+ spyOn(exitInput, 'focus');
+
+ recipientsIterator.deleteCurrent();
+
+ expect(exitInput.focus).toHaveBeenCalled();
+ });
+ });
+
+
+}); \ No newline at end of file
diff --git a/web-ui/test/spec/mail_view/ui/reply_box.spec.js b/web-ui/test/spec/mail_view/ui/reply_box.spec.js
new file mode 100644
index 00000000..c6db4de3
--- /dev/null
+++ b/web-ui/test/spec/mail_view/ui/reply_box.spec.js
@@ -0,0 +1,105 @@
+/*global jasmine */
+/*global Smail */
+
+describeComponent('mail_view/ui/reply_box', function () {
+ 'use strict';
+
+ var attrs, i18n;
+ beforeEach(function () {
+ attrs = {
+ mail: Smail.testData().parsedMail.simpleTextPlain
+ };
+ setupComponent(attrs);
+ i18n = require('views/i18n');
+ });
+
+ describe('reply compose box', function() {
+ it('should display subject of the reply', function() {
+ expect(this.component.select('subjectDisplay').text()).toBe(i18n('Re: ') + attrs.mail.header.subject);
+ });
+
+ it('should show recipient fields when clicking on recipient display', function() {
+ this.component.select('recipientsDisplay').click();
+
+ expect(this.component.select('recipientsFields')).not.toBeHidden();
+ expect(this.component.select('recipientsDisplay')).toBeHidden();
+ });
+
+ it('should show subject field when clicking on subject display', function() {
+ this.component.select('subjectDisplay').click();
+
+ expect(this.component.select('subjectInput')).not.toBeHidden();
+ expect(this.component.select('subjectDisplay')).toBeHidden();
+ });
+
+ it('should use the from field when Reply-To header does not exist', function() {
+ attrs.mail.header.reply_to = undefined;
+
+ setupComponent(attrs);
+
+ expect(this.component.attr.recipientValues['to']).toEqual([attrs.mail.header.from]);
+ });
+
+ it('should have a subject of Re: <original_message>', function() {
+ attrs.mail.header.subject = 'Very interesting';
+
+ setupComponent(attrs);
+
+ expect(this.component.select('subjectDisplay').text()).toEqual(i18n('Re: ')+ attrs.mail.header.subject);
+ });
+
+ it('should use set In-Reply-To header when Message-Id header is set', function() {
+ var mailSendEvent = spyOnEvent(document, Smail.events.mail.send);
+
+ attrs.mail.header.message_id = '12345';
+ setupComponent(attrs);
+
+ $(document).trigger(Smail.events.ui.mail.send);
+
+ expect(mailSendEvent).toHaveBeenTriggeredOn(document);
+ expect(mailSendEvent.mostRecentCall.data.header).toEqual(jasmine.objectContaining({
+ in_reply_to: '12345'
+ }));
+ });
+
+ it('keeps the List-Id header when it exists', function() {
+ var mailSendEvent = spyOnEvent(document, Smail.events.mail.send);
+ attrs.mail.header.list_id = 'somelist';
+
+ setupComponent(attrs);
+ $(document).trigger(Smail.events.ui.mail.send);
+
+ expect(mailSendEvent.mostRecentCall.data.header).toEqual(jasmine.objectContaining({
+ list_id: 'somelist'
+ }));
+ });
+
+ it('populates body text area with quote of email being replied', function() {
+ var viewHelper = require('helpers/view_helper');
+ spyOn(viewHelper, 'quoteMail').andReturn('quoted email');
+
+ setupComponent(attrs);
+
+ expect(viewHelper.quoteMail).toHaveBeenCalledWith(attrs.mail);
+ expect(this.component.select('bodyBox').val()).toBe('quoted email');
+ });
+
+ it('triggers mail when cancelling a reply', function () {
+ var mailSaveEvent = spyOnEvent(document, Smail.events.mail.save);
+
+ this.component.select('trashButton').click();
+
+ expect(mailSaveEvent).toHaveBeenTriggeredOn(document);
+ });
+
+ it('reopens the mail after the reply is sent', function () {
+ var mailOpenEvent = spyOnEvent(document, Smail.events.ui.mail.open);
+
+ this.component.trigger(document, Smail.events.mail.sent);
+
+ expect(mailOpenEvent).toHaveBeenTriggeredOnAndWith(document, {
+ ident: this.component.attr.mail.ident
+ });
+ });
+ });
+});
diff --git a/web-ui/test/spec/mail_view/ui/reply_section.spec.js b/web-ui/test/spec/mail_view/ui/reply_section.spec.js
new file mode 100644
index 00000000..e5571e2c
--- /dev/null
+++ b/web-ui/test/spec/mail_view/ui/reply_section.spec.js
@@ -0,0 +1,97 @@
+/*global jasmine */
+/*global Smail */
+
+describeComponent('mail_view/ui/reply_section', function () {
+ 'use strict';
+
+ beforeEach(function () {
+ setupComponent();
+ });
+
+ describe('clicking reply buttons', function() {
+ var mailWantEvent, expectEventData;
+
+ beforeEach(function () {
+ mailWantEvent = spyOnEvent(document, Smail.events.mail.want);
+ expectEventData = {
+ mail: '12345',
+ caller: this.component
+ };
+ this.component.attr.ident = '12345';
+ });
+
+ it('should ask for email when clicking on reply button', function() {
+ this.component.select('replyButton').click();
+
+ expect(mailWantEvent).toHaveBeenTriggeredOnAndWith(document, expectEventData);
+ });
+
+ it('should ask for email when clicking on replyAll button', function() {
+ this.component.select('replyAllButton').click();
+
+ expect(mailWantEvent).toHaveBeenTriggeredOnAndWith(document, expectEventData);
+ });
+ });
+
+ describe('creating reply box when getting email back', function() {
+ var mailData, ReplyBox, ForwardBox;
+
+ beforeEach(function () {
+ mailData = Smail.testData().mail;
+ ReplyBox = require('mail_view/ui/reply_box');
+ ForwardBox = require('mail_view/ui/forward_box');
+ spyOn(ReplyBox, 'attachTo');
+ spyOn(ForwardBox, 'attachTo');
+ });
+
+ it('for normal reply', function() {
+ this.component.attr.replyType = 'reply';
+ this.component.trigger(this.component, Smail.events.mail.here, { mail: mailData });
+
+ expect(ReplyBox.attachTo).toHaveBeenCalledWith(jasmine.any(Object), {
+ mail: mailData,
+ replyType: 'reply'
+ });
+ });
+
+ it('for reply to all', function() {
+ this.component.attr.replyType = 'replyall';
+ this.component.trigger(this.component, Smail.events.mail.here, { mail: mailData });
+
+ expect(ReplyBox.attachTo).toHaveBeenCalledWith(jasmine.any(Object), {
+ mail: mailData,
+ replyType: 'replyall'
+ });
+ });
+
+ it('creates a forward box', function() {
+ this.component.attr.replyType = 'forward';
+ this.component.trigger(this.component, Smail.events.mail.here, { mail: mailData });
+
+ expect(ForwardBox.attachTo).toHaveBeenCalledWith(jasmine.any(Object), {
+ mail: mailData
+ });
+ });
+ });
+
+ it('hides the buttons when clicked', function() {
+ this.component.attr.mailIdent = 12345;
+
+ this.component.select('replyButton').click();
+
+ expect(this.component.select('replyButton')).toBeHidden();
+ expect(this.component.select('replyAllButton')).toBeHidden();
+ expect(this.component.select('forwardButton')).toBeHidden();
+ });
+
+ it('shows the buttons when reply is cancelled', function() {
+ this.component.attr.mailIdent = 12345;
+ this.component.select('replyButton').click();
+
+ $(document).trigger(Smail.events.ui.composeBox.trashReply);
+
+ expect(this.component.select('replyButton')).not.toBeHidden();
+ expect(this.component.select('replyAllButton')).not.toBeHidden();
+ expect(this.component.select('forwardButton')).not.toBeHidden();
+ });
+});
diff --git a/web-ui/test/spec/mail_view/ui/send_button.spec.js b/web-ui/test/spec/mail_view/ui/send_button.spec.js
new file mode 100644
index 00000000..27bee0f3
--- /dev/null
+++ b/web-ui/test/spec/mail_view/ui/send_button.spec.js
@@ -0,0 +1,91 @@
+describeComponent('mail_view/ui/send_button', function () {
+
+ describe('send button', function () {
+ beforeEach(function () {
+ setupComponent('<button />');
+ });
+
+ describe('when it is disabled', function () {
+ beforeEach(function () {
+ this.$node.prop('disabled', true);
+ });
+
+ it('gets enabled in a inputHasMail event', function () {
+ $(document).trigger(Smail.events.ui.recipients.inputHasMail, { name: 'to' });
+
+ expect(this.$node).not.toBeDisabled();
+ });
+
+ it('gets enabled in a recipients:updated where there are new recipients', function () {
+ $(document).trigger(Smail.events.ui.recipients.updated, { newRecipients: ['a@b.c']});
+
+ expect(this.$node).not.toBeDisabled();
+ });
+ });
+
+ describe('multiple events', function () {
+ it('gets enabled and remains enabled when a inputHasMail is followed by a recipients:updated with NO new recipients', function () {
+ this.$node.prop('disabled', true);
+
+ $(document).trigger(Smail.events.ui.recipients.inputHasMail, { name: 'to' });
+ $(document).trigger(Smail.events.ui.recipients.updated, { newRecipients: [] });
+
+ expect(this.$node).not.toBeDisabled();
+ });
+
+ it('gets enabled and remains enabled when a recipients:updated with recipients is followed by a inputHasNoMail', function () {
+ this.$node.prop('disabled', true);
+
+ $(document).trigger(Smail.events.ui.recipients.updated, { newRecipients: ['a@b.c']});
+ $(document).trigger(Smail.events.ui.recipients.inputHasNoMail, { name: 'to' });
+
+ expect(this.$node).not.toBeDisabled();
+ });
+ });
+
+ describe('when it is enabled', function () {
+ beforeEach(function () {
+ this.$node.prop('disabled', false);
+ });
+
+ it('gets disabled in a inputHasNoMail', function () {
+ $(document).trigger(Smail.events.ui.recipients.inputHasNoMail, { name: 'to' });
+
+ expect(this.$node).toBeDisabled();
+ });
+
+ it('gets disabled in a recipients:updated without new recipients', function () {
+ $(document).trigger(Smail.events.ui.recipients.updated, { newRecipients: []});
+
+ expect(this.$node).toBeDisabled();
+ });
+ });
+
+ describe('on click', function () {
+
+ it ('asks for the recipients input to complete its current input', function () {
+ var doCompleteInputEvent = spyOnEvent(document, Smail.events.ui.recipients.doCompleteInput);
+
+ this.$node.click();
+
+ expect(doCompleteInputEvent).toHaveBeenTriggeredOn(document);
+ });
+ });
+
+ describe('after clicking', function () {
+ beforeEach(function () {
+ this.$node.click();
+ });
+
+ it('waits for ui:mail:recipientsUpdated to happen 3 times in the mail then sends the mail then stops listening to ui:mail:recipientsUpdated', function () {
+ var sendMailEvent = spyOnEvent(document, Smail.events.ui.mail.send);
+ spyOn(this.component, 'off');
+
+ _.times(3, function () { $(document).trigger(Smail.events.ui.mail.recipientsUpdated) });;
+
+ expect(sendMailEvent).toHaveBeenTriggeredOn(document);
+ expect(this.component.off).toHaveBeenCalledWith(document, Smail.events.ui.mail.recipientsUpdated);
+ });
+ });
+ });
+});
diff --git a/web-ui/test/spec/mixins/with_mail_edit_base.spec.js b/web-ui/test/spec/mixins/with_mail_edit_base.spec.js
new file mode 100644
index 00000000..43d3b7cd
--- /dev/null
+++ b/web-ui/test/spec/mixins/with_mail_edit_base.spec.js
@@ -0,0 +1,94 @@
+/*global Smail */
+/*global jasmine */
+/*global runs */
+/*global waits */
+
+describeMixin('mixins/with_mail_edit_base', function () {
+ 'use strict';
+
+ beforeEach(function () {
+ setupComponent();
+ // Stubing mixing wrongly!!! 'deprecated' while waiting for draft component extraction
+ this.component.buildMail = function (tag) {
+ return { header: { to: ['a@smth.com'], from: 'b@smth.com', subject: 'Sbject' } };
+ };
+ });
+
+ describe('initialization', function() {
+ it('should enable send button when rendering with recipients', function() {
+ var enableSendButtonEvent = spyOnEvent(document, Smail.events.ui.sendbutton.enable);
+
+ this.component.render(function() {}, {
+ recipients: { to: ['foobar@mail.com'], cc: [] }
+ });
+
+ expect(enableSendButtonEvent).toHaveBeenTriggeredOn(document);
+ });
+
+ it('should not enable send button when rendering without recipients', function() {
+ var enableSendButtonEvent = spyOnEvent(document, Smail.events.ui.sendbutton.enable);
+
+ this.component.render(function() {}, {
+ recipients: { to: [], cc: [] }
+ });
+
+ expect(enableSendButtonEvent).not.toHaveBeenTriggeredOn(document);
+ });
+ });
+
+ describe('when the user is typing in subject or body', function() {
+ beforeEach(function () {
+ this.component.attr.saveDraftInterval = 10;
+ });
+
+ it('saves the draft after the save draft interval number of seconds', function() {
+ var saveDraftSpy = spyOnEvent(document, Smail.events.mail.saveDraft);
+ runs(function () {
+ this.component.monitorInput();
+ expect(saveDraftSpy).not.toHaveBeenTriggeredOn(document);
+ });
+ waits(10);
+ runs(function () {
+ expect(saveDraftSpy).toHaveBeenTriggeredOn(document);
+ });
+ });
+
+ it('does not save if mail is sent before the save draft interval number of seconds', function() {
+ var saveDraftSpy = spyOnEvent(document, Smail.events.mail.saveDraft);
+ runs(function () {
+ this.component.monitorInput();
+ this.component.sendMail();
+ });
+ waits(10);
+ runs(function () {
+ expect(saveDraftSpy).not.toHaveBeenTriggeredOn(document);
+ });
+ });
+ });
+
+ describe('when a mail is sent', function () {
+ it('displays a message of mail sent', function () {
+ var spy = spyOnEvent(document, Smail.events.ui.userAlerts.displayMessage);
+ this.component.trigger(document, Smail.events.mail.sent);
+ expect(spy).toHaveBeenTriggeredOn(document);
+ });
+ });
+
+ describe('when user asks to trash the mail', function() {
+ it('triggers mail delete for this mail', function() {
+ var spy = spyOnEvent(document, Smail.events.mail.save);
+ this.component.trashMail();
+ expect(spy).toHaveBeenTriggeredOn(document);
+ });
+ });
+
+ describe('when recipients are updated', function () {
+ it('triggers an event to let the send button know that the recipients in the mail are updated', function () {
+ var uiMailRecipientsUpdated = spyOnEvent(document, Smail.events.ui.mail.recipientsUpdated);
+
+ $(document).trigger(Smail.events.ui.recipients.updated, {recipientsName: 'to', newRecipients: ['fox@somewhere.com']});
+
+ expect(uiMailRecipientsUpdated).toHaveBeenTriggeredOn(document);
+ });
+ });
+});
diff --git a/web-ui/test/spec/page/pane_contract_expand.spec.js b/web-ui/test/spec/page/pane_contract_expand.spec.js
new file mode 100644
index 00000000..803f688c
--- /dev/null
+++ b/web-ui/test/spec/page/pane_contract_expand.spec.js
@@ -0,0 +1,67 @@
+/*global Smail */
+/*global afterEach */
+
+'use strict';
+
+describeComponent('page/pane_contract_expand', function () {
+
+ var fixture;
+
+ beforeEach(function () {
+ fixture = $('<div>')
+ .append($('<div>', { id: 'middle-pane-container' }))
+ .append($('<div>', { id: 'right-pane' }));
+
+ $('body').append(fixture);
+
+
+ });
+
+ afterEach(function () {
+ fixture.remove();
+ });
+
+ describe('after initialization', function () {
+ beforeEach(function () {
+ setupComponent(document);
+ });
+
+ it('contracts middle pane and expands right pane on mail open', function () {
+ $(document).trigger(Smail.events.ui.mail.open);
+
+ expect($('#middle-pane-container').attr('class')).toEqual(this.component.attr.MIDDLE_PANE_CONTRACT_CLASSES);
+ expect($('#right-pane').attr('class')).toEqual(this.component.attr.RIGHT_PANE_EXPAND_CLASSES);
+ });
+
+ it('contracts middle pane and expands right pane on open compose box', function () {
+ $(document).trigger(Smail.events.dispatchers.rightPane.openComposeBox);
+
+ expect($('#middle-pane-container').attr('class')).toEqual(this.component.attr.MIDDLE_PANE_CONTRACT_CLASSES);
+ expect($('#right-pane').attr('class')).toEqual(this.component.attr.RIGHT_PANE_EXPAND_CLASSES);
+ });
+
+ it('contracts middle pane and expands right pane on open draft', function () {
+ $(document).trigger(Smail.events.dispatchers.rightPane.openDraft);
+
+ expect($('#middle-pane-container').attr('class')).toEqual(this.component.attr.MIDDLE_PANE_CONTRACT_CLASSES);
+ expect($('#right-pane').attr('class')).toEqual(this.component.attr.RIGHT_PANE_EXPAND_CLASSES);
+ });
+
+ it('expands middle pane and contracts right pane on event on open no message selected pane', function () {
+ $(document).trigger(Smail.events.dispatchers.rightPane.openNoMessageSelected);
+
+ expect($('#middle-pane-container').attr('class')).toEqual(this.component.attr.MIDDLE_PANE_EXPAND_CLASSES);
+ expect($('#right-pane').attr('class')).toEqual(this.component.attr.RIGHT_PANE_CONTRACT_CLASSES);
+ });
+ });
+
+ describe('on initialization', function () {
+ it('expands middle pane and contracts right pane', function () {
+ setupComponent(document);
+
+ expect($('#middle-pane-container').attr('class')).toEqual(this.component.attr.MIDDLE_PANE_EXPAND_CLASSES);
+ expect($('#right-pane').attr('class')).toEqual(this.component.attr.RIGHT_PANE_CONTRACT_CLASSES);
+ });
+ });
+
+});
diff --git a/web-ui/test/spec/page/router.spec.js b/web-ui/test/spec/page/router.spec.js
new file mode 100644
index 00000000..0b5b6b32
--- /dev/null
+++ b/web-ui/test/spec/page/router.spec.js
@@ -0,0 +1,70 @@
+/*global Smail */
+/*global jasmine */
+describeComponent('page/router', function () {
+ 'use strict';
+
+ var fakeHistory;
+
+ describe('on router:pushState coming from a tag selection', function () {
+ beforeEach(function () {
+ fakeHistory = jasmine.createSpyObj('history', ['pushState']);
+ setupComponent({history: fakeHistory});
+ });
+
+ it('pushes the state with the tag and the url', function () {
+ $(document).trigger(Smail.events.router.pushState, { tag: 'inbox'});
+
+ expect(fakeHistory.pushState).toHaveBeenCalledWith(jasmine.objectContaining({ tag: 'inbox' }), '', '/#/inbox');
+ });
+
+ it('pushes the state with mailIdent', function () {
+ $(document).trigger(Smail.events.router.pushState, { tag: 'inbox', mailIdent: 1});
+
+ expect(fakeHistory.pushState).toHaveBeenCalledWith(jasmine.objectContaining({ tag: 'inbox', mailIdent: 1 }), '', '/#/inbox/mail/1');
+ });
+
+ it('pushes the state with mailIdent even if mail ident is 0 (that happens for drafts)', function () {
+ $(document).trigger(Smail.events.router.pushState, { tag: 'inbox', mailIdent: 0});
+
+ expect(fakeHistory.pushState).toHaveBeenCalledWith(jasmine.objectContaining({ tag: 'inbox', mailIdent: 0 }), '', '/#/inbox/mail/0');
+ });
+
+ it('pushes the state with the displayNoMessage boolean forwarded from the event', function () {
+ $(document).trigger(Smail.events.router.pushState, { tag: 'inbox', mailIdent: 0});
+
+ expect(fakeHistory.pushState).toHaveBeenCalledWith(jasmine.objectContaining({ isDisplayNoMessageSelected: false}), '', '/#/inbox/mail/0');
+
+ $(document).trigger(Smail.events.router.pushState, { tag: 'inbox', mailIdent: 0, isDisplayNoMessageSelected: true});
+
+ expect(fakeHistory.pushState).toHaveBeenCalledWith(jasmine.objectContaining({ isDisplayNoMessageSelected: true}), '', '/#/inbox/mail/0');
+ });
+
+ it('when popping a state with no tag should select tag from url', function () {
+ var urlParams = require("page/router/url_params");
+ spyOn(urlParams, 'getTag').andReturn('tag');
+
+ var selectTagEvent = spyOnEvent(document, Smail.events.ui.tag.select);
+
+ this.component.smailPopState({ state: {tag: undefined} });
+
+ expect(selectTagEvent).toHaveBeenTriggeredOnAndWith(document, jasmine.objectContaining({ tag: "tag"}))
+ });
+
+ it('when popping a state triggers the displayNoMessage pane if required', function () {
+ var urlParams = require("page/router/url_params");
+ spyOn(urlParams, 'getTag').andReturn('tag');
+
+ var displayNoMessageEvent = spyOnEvent(document, Smail.events.dispatchers.rightPane.openNoMessageSelectedWithoutPushState);
+
+ this.component.smailPopState({ state: {tag: undefined, isDisplayNoMessageSelected: false} });
+
+ expect(displayNoMessageEvent).not.toHaveBeenTriggeredOn(document)
+
+ this.component.smailPopState({ state: {tag: undefined, isDisplayNoMessageSelected: true} });
+
+ expect(displayNoMessageEvent).toHaveBeenTriggeredOn(document)
+ });
+
+ });
+
+});
diff --git a/web-ui/test/spec/page/router/url_params.spec.js b/web-ui/test/spec/page/router/url_params.spec.js
new file mode 100644
index 00000000..a14ba1ba
--- /dev/null
+++ b/web-ui/test/spec/page/router/url_params.spec.js
@@ -0,0 +1,68 @@
+require(['page/router/url_params'], function (urlParams) {
+
+ describe('urlParams', function () {
+
+ beforeEach(function () {
+ //preventing the hash change to fire the onpopstate event in other components
+ //in this case in the router component
+ window.onpopstate = function () {};
+ });
+
+ afterEach(function () {
+ document.location.hash = '';
+ });
+
+ describe('getTag', function () {
+ it('returns inbox if there is no tag in the url hash', function () {
+ expect(urlParams.getTag()).toEqual('inbox');
+ });
+
+ it('returns the tag in the hash if there is one', function () {
+ document.location.hash = '/Drafts';
+
+ expect(urlParams.getTag()).toEqual('Drafts');
+ });
+
+ it('returns tag with slash', function () {
+ document.location.hash = '/Events/2011';
+
+ expect(urlParams.getTag()).toEqual('Events/2011');
+ });
+
+ it('returns tag even if there is an mail ident', function () {
+ document.location.hash = '/Events/2011/mail/1';
+
+ expect(urlParams.getTag()).toEqual('Events/2011');
+ });
+
+ it('returns the tag even if there is a trailing slash', function () {
+ document.location.hash = '/Events/';
+
+ expect(urlParams.getTag()).toEqual('Events');
+ });
+ });
+
+ describe('hasMailIdent', function () {
+ it('is true if hash has mailIdent', function () {
+ document.location.hash = '/inbox/mail/1';
+
+ expect(urlParams.hasMailIdent()).toBeTruthy();
+ });
+
+ it('is false if hash has no mail ident', function () {
+ document.location.hash = '/Drafts';
+
+ expect(urlParams.hasMailIdent()).toBeFalsy();
+ });
+ });
+
+ describe('getMailIdent', function () {
+ it('returns the mail ident that is in the hash', function () {
+ document.location.hash = '/inbox/mail/123';
+
+ expect(urlParams.getMailIdent()).toEqual('123');
+ });
+ });
+ });
+
+});
diff --git a/web-ui/test/spec/search/search_trigger.spec.js b/web-ui/test/spec/search/search_trigger.spec.js
new file mode 100644
index 00000000..aaeba3b1
--- /dev/null
+++ b/web-ui/test/spec/search/search_trigger.spec.js
@@ -0,0 +1,78 @@
+/*global jasmine */
+/*global Smail */
+
+describeComponent('search/search_trigger', function () {
+ 'use strict';
+ var self;
+
+ beforeEach(function () {
+ setupComponent();
+ self = this;
+ });
+
+ function submitSearch(queryString) {
+ self.component.select('input').val(queryString);
+ self.component.select('form').submit();
+ }
+
+ it('should trigger search when the submit occurs', function () {
+ var spy = spyOnEvent(document, Smail.events.search.perform);
+
+ submitSearch('tanana');
+ expect(spy).toHaveBeenTriggeredOnAndWith(document, { query: 'tanana' });
+ });
+
+ it('should select the "all" tag when submit occurs but should skip mail list refresh', function (){
+ var tagSelectEvent = spyOnEvent(document, Smail.events.ui.tag.select);
+
+ submitSearch('tanana');
+
+ expect(tagSelectEvent).toHaveBeenTriggeredOnAndWith(document, {
+ tag: 'all',
+ skipMailListRefresh: true
+ });
+ });
+
+ it('should select the "all" tag when an empty submit occurs and shoud refresh mail list', function() {
+ var tagSelectEvent = spyOnEvent(document, Smail.events.ui.tag.select);
+ var emptySearchEvent = spyOnEvent(document, Smail.events.search.empty);
+
+ submitSearch('');
+
+ expect(emptySearchEvent).toHaveBeenTriggeredOn(document);
+ expect(tagSelectEvent).toHaveBeenTriggeredOnAndWith(document, { tag: 'all'});
+
+ });
+
+ it('should clear input when selecting a new tag', function(){
+ submitSearch('tanana');
+ $(document).trigger(Smail.events.ui.tag.selected, { tag: 'inbox'});
+ expect(self.component.select('input').val()).toBe('');
+ });
+
+ it('should add place holder on input value after doing a search', function(){
+ submitSearch('teste');
+ expect(self.component.select('input').val()).toBe('Search results for: teste');
+ });
+
+ it('should remove place holder on input value when input is on focus', function(){
+ submitSearch('teste');
+ this.component.select('input').focus();
+ expect(self.component.select('input').val()).toBe('teste');
+ });
+
+ it('should remove place holder on input value when input is not on focus', function(){
+ submitSearch('teste');
+ this.component.select('input').focus();
+ this.component.select('input').blur();
+ expect(self.component.select('input').val()).toBe('Search results for: teste');
+ });
+
+ it('should not change input value when input is empty', function(){
+ submitSearch('');
+ this.component.select('input').focus();
+ expect(self.component.select('input').val()).toBe('');
+ });
+
+
+});
diff --git a/web-ui/test/spec/services/delete_service.spec.js b/web-ui/test/spec/services/delete_service.spec.js
new file mode 100644
index 00000000..3e098877
--- /dev/null
+++ b/web-ui/test/spec/services/delete_service.spec.js
@@ -0,0 +1,54 @@
+/*global jasmine */
+/*global Smail */
+
+describeComponent('services/delete_service', function () {
+ 'use strict';
+
+ var i18n;
+
+ beforeEach( function () {
+ setupComponent();
+ i18n = require('views/i18n');
+ });
+
+ var mailWithoutTrashTag = {
+ ident: 42,
+ isInTrash: function() { return false; },
+ tags: ['inbox', 'test']
+ };
+
+ var mailWithTrashTag = {
+ ident: 34,
+ isInTrash: function() { return true; },
+ tags: ['inbox', 'test', 'trash']
+ };
+
+ it('add Trash tag when deleting an email that does not have it', function () {
+ var mailDeleteEvent = spyOnEvent(document, Smail.events.mail.delete);
+ var openNoMessageSelectedEvent = spyOnEvent(document, Smail.events.dispatchers.rightPane.openNoMessageSelected);
+
+ this.component.trigger(document, Smail.events.ui.mail.delete, {mail: mailWithoutTrashTag});
+
+ var expectedDeleteEventData = {
+ mail: mailWithoutTrashTag,
+ successMessage: i18n('Your message was moved to trash!')
+ };
+
+ expect(mailDeleteEvent).toHaveBeenTriggeredOnAndWith(document, expectedDeleteEventData);
+ });
+
+ it('removes permanently email that has Trash tag', function(){
+ var mailDeleteEvent = spyOnEvent(document, Smail.events.mail.delete);
+ var openNoMessageSelectedEvent = spyOnEvent(document, Smail.events.dispatchers.rightPane.openNoMessageSelected);
+
+ this.component.trigger(document, Smail.events.ui.mail.delete, {mail: mailWithTrashTag});
+
+ var expectedDeleteEventData = {
+ mail: mailWithTrashTag,
+ successMessage: i18n('Your message was permanently deleted!')
+ };
+
+ expect(mailDeleteEvent).toHaveBeenTriggeredOnAndWith(document, expectedDeleteEventData );
+ });
+
+});
diff --git a/web-ui/test/spec/services/mail_service.spec.js b/web-ui/test/spec/services/mail_service.spec.js
new file mode 100644
index 00000000..31e130fa
--- /dev/null
+++ b/web-ui/test/spec/services/mail_service.spec.js
@@ -0,0 +1,307 @@
+/*global jasmine */
+/*global Smail */
+'use strict';
+
+describeComponent('services/mail_service', function () {
+
+ var email1, i18n;
+
+ beforeEach( function () {
+ setupComponent();
+ email1 = Smail.testData().parsedMail.simpleTextPlain;
+ i18n = require('views/i18n');
+ } );
+
+ it('marks the desired message as read', function () {
+ var readRequest = spyOn($, 'ajax').andReturn({});
+
+ this.component.trigger(Smail.events.mail.read, {ident: 1});
+
+ expect(readRequest.mostRecentCall.args[0]).toEqual('/mail/1/read');
+ });
+
+ describe('when marks many emails as read', function () {
+ var readRequest, checkedMails, uncheckedEmailsEvent, setCheckAllEvent, doneMarkAsRead;
+
+ beforeEach(function () {
+ readRequest = spyOn($, 'ajax').andReturn({done: function(f) { doneMarkAsRead = f; return {fail: function() {}};}});
+ uncheckedEmailsEvent = spyOnEvent(document, Smail.events.ui.mail.unchecked);
+ setCheckAllEvent = spyOnEvent(document, Smail.events.ui.mails.hasMailsChecked);
+ spyOn(this.component, 'refreshResults');
+
+ checkedMails = {
+ 1: {ident: 1},
+ 2: {ident: 2}
+ };
+
+ this.component.trigger(Smail.events.mail.read, {checkedMails: checkedMails});
+ });
+
+ it('makes the correct request to the backend', function () {
+ expect(readRequest.mostRecentCall.args[0]).toEqual('/mails/read');
+ expect(readRequest.mostRecentCall.args[1].data).toEqual({idents: '[1,2]'});
+ });
+
+ it('will trigger that a message has been deleted when it is done deleting', function() {
+ doneMarkAsRead({mails: checkedMails});
+ expect(this.component.refreshResults).toHaveBeenCalled();
+ });
+
+ it('unchecks read emails', function () {
+ doneMarkAsRead({mails: checkedMails});
+ expect(uncheckedEmailsEvent).toHaveBeenTriggeredOnAndWith(document, {mails: checkedMails});
+ });
+
+ it('clears the check all checkbox', function () {
+ doneMarkAsRead({mails: checkedMails});
+ expect(setCheckAllEvent).toHaveBeenTriggeredOnAndWith(document, false);
+ });
+ });
+
+ it('fetches a single email', function () {
+ var me = {};
+ var spyAjax = spyOn($, 'ajax').andReturn({done: function(f) { f(email1); return {fail: function() {}};}});
+ var mailHereEvent = spyOnEvent(me, Smail.events.mail.here);
+
+ this.component.trigger(Smail.events.mail.want, { caller: me, mail: email1.ident });
+
+ expect(mailHereEvent).toHaveBeenTriggeredOn(me);
+ expect(spyAjax.mostRecentCall.args[0]).toEqual('/mail/' + email1.ident);
+ });
+
+ it('answers mail:notFound if mail returned from server is null', function () {
+ var me = {};
+ var spyAjax = spyOn($, 'ajax').andReturn({done: function(f) { f(null); return {fail: function() {}};}});
+ var mailNotFound = spyOnEvent(me, Smail.events.mail.notFound);
+
+ this.component.trigger(Smail.events.mail.want, { caller: me, mail: email1.ident });
+
+ expect(mailNotFound).toHaveBeenTriggeredOn(me);
+ });
+
+ it('updates the tags of the desired message', function () {
+ spyOn(this.component, 'refreshResults');
+ var spyAjax = spyOn($, 'ajax').andReturn({done: function(f) { f(); return {fail: function() {}};}});
+
+ var spyEvent = spyOnEvent(document, Smail.events.mail.tags.updated);
+ var component = jasmine.createSpyObj('component',['successUpdateTags']);
+ spyOn(this.component, 'fetchMail');
+
+ this.component.trigger(Smail.events.mail.tags.update, { ident: email1.ident, tags: email1.tags });
+
+ expect(spyEvent).toHaveBeenTriggeredOn(document);
+ expect(spyAjax.calls[0].args[0]).toEqual('/mail/1/tags');
+ expect(spyAjax.calls[0].args[1].data).toEqual(JSON.stringify({ newtags: email1.tags } ));
+ expect(this.component.refreshResults).toHaveBeenCalled();
+ });
+
+ it('triggers an error message when it can\'t update the tags', function () {
+ var spyAjax = spyOn($, 'ajax').andReturn({done: function() { return {fail: function(f) {f();}};}});
+
+ var spyEvent = spyOnEvent(document, Smail.events.ui.userAlerts.displayMessage);
+ var component = jasmine.createSpyObj('component',['failureUpdateTags']);
+
+ this.component.trigger(Smail.events.mail.tags.update, { ident: email1.ident, tags: email1.tags });
+
+ expect(spyEvent).toHaveBeenTriggeredOn(document);
+ expect(spyAjax.mostRecentCall.args[0]).toEqual('/mail/1/tags');
+ expect(spyAjax.mostRecentCall.args[1].data).toEqual(JSON.stringify({ newtags: email1.tags } ));
+ });
+
+ it('will try to delete a message when requested to', function() {
+ var spyAjax = spyOn($, 'ajax').andReturn({done: function() { return {fail: function(f) {}};}});
+ this.component.trigger(Smail.events.mail.delete, {mail: {ident: '43'}});
+ expect(spyAjax).toHaveBeenCalled();
+ expect(spyAjax.mostRecentCall.args[0]).toEqual('/mail/43');
+ expect(spyAjax.mostRecentCall.args[1].type).toEqual('DELETE');
+ });
+
+ describe('when successfuly deletes an email', function () {
+ var displayMessageEvent, uncheckedEmailsEvent, setCheckAllEvent, mailsDeletedEvent;
+
+ beforeEach(function () {
+ displayMessageEvent = spyOnEvent(document, Smail.events.ui.userAlerts.displayMessage);
+ uncheckedEmailsEvent = spyOnEvent(document, Smail.events.ui.mail.unchecked);
+ setCheckAllEvent = spyOnEvent(document, Smail.events.ui.mails.hasMailsChecked);
+ mailsDeletedEvent = spyOnEvent(document, Smail.events.mail.deleted);
+ spyOn(this.component, 'refreshResults');
+
+ this.component.triggerDeleted({
+ successMessage: 'A success message',
+ mails: {1: 'email 1', 2: 'email 2'}
+ })();
+ });
+
+ it('will trigger that a message has been deleted when it is done deleting', function() {
+ expect(this.component.refreshResults).toHaveBeenCalled();
+ });
+
+ it('displays a success message', function () {
+ expect(displayMessageEvent).toHaveBeenTriggeredOnAndWith(document, {message: 'A success message'});
+ });
+
+ it('unchecks deleted emails', function () {
+ expect(uncheckedEmailsEvent).toHaveBeenTriggeredOnAndWith(document, { mails: {1: 'email 1', 2: 'email 2'} });
+ });
+
+ it('tells about deleted emails', function () {
+ expect(mailsDeletedEvent).toHaveBeenTriggeredOnAndWith(document, { mails: {1: 'email 1', 2: 'email 2'} });
+ });
+
+ it('clears the check all checkbox', function () {
+ expect(setCheckAllEvent).toHaveBeenTriggeredOnAndWith(document, false);
+ });
+ });
+
+ it('will trigger an error message when a message cannot be deleted', function() {
+ spyOn($, 'ajax').andReturn({done: function() { return {fail: function(f) { f(); }};}});
+ var spyEvent = spyOnEvent(document, Smail.events.ui.userAlerts.displayMessage);
+
+ this.component.trigger(Smail.events.mail.delete, {mail: {ident: '43'}});
+
+ expect(spyEvent).toHaveBeenTriggeredOnAndWith(document, {message: i18n('Could not delete email')} );
+ });
+
+ it('triggers mails:available with received mails and keeps that tag as the current tag', function() {
+ var g;
+ var eventSpy = spyOnEvent(document, Smail.events.mails.available);
+
+ spyOn($, 'ajax').andReturn({done: function(f) { g = f; return {fail: function(){}};}});
+ this.component.trigger(Smail.events.ui.mails.fetchByTag, {tag: 'inbox'});
+
+ g({stats: {}, mails: [email1]});
+ expect(eventSpy.mostRecentCall.data.stats).toEqual({});
+ expect(eventSpy.mostRecentCall.data.tag).toEqual('inbox');
+ expect(this.component.attr.currentTag).toEqual('inbox');
+ });
+
+ it('wraps the tag in quotes before fetching by tag (to support tags with spaces)', function () {
+ spyOn($, 'ajax').andReturn({done: function(f) { return {fail: function(){}};}});
+
+ this.component.trigger(Smail.events.ui.mails.fetchByTag, {tag: 'new tag'});
+
+ expect($.ajax.mostRecentCall.args[0]).toContain(encodeURI('tag:"new tag"'));
+ });
+
+ it('sends the previous tag when mails:refresh is called without a tag (this happens when the refresher calls it)', function () {
+ var g;
+ var eventSpy = spyOnEvent(document, Smail.events.mails.availableForRefresh);
+ this.component.attr.currentTag = 'sent';
+
+ spyOn($, 'ajax').andReturn({done: function(f) { g = f; return {fail: function(){}};}});
+ this.component.trigger(Smail.events.ui.mails.refresh);
+
+ g({stats: {}, mails: [email1]});
+ expect(eventSpy.mostRecentCall.data.tag).toEqual('sent');
+ expect(eventSpy.mostRecentCall.data.stats).toEqual({});
+ });
+
+ describe('pagination', function() {
+ var pageChangedEvent;
+ var g;
+
+ beforeEach(function () {
+ pageChangedEvent = spyOnEvent(document, Smail.events.ui.page.changed);
+ spyOn($, 'ajax').andReturn({done: function(f) {
+ g = f;
+ return {fail: function(){}};
+ }});
+ spyOn(this.component, 'fetchMail').andCallThrough();
+ });
+
+ it('changes to the previous page and refetch email when ui:page:previous is fired', function() {
+ this.component.attr.currentPage = 1;
+
+ this.component.trigger(Smail.events.ui.page.previous);
+
+ expect(this.component.fetchMail).toHaveBeenCalled();
+ expect(this.component.attr.currentPage).toEqual(0);
+ });
+
+ it('won\'t change the page if it was already at the first page and trying to go to previous', function() {
+ this.component.attr.currentPage = 0;
+
+ this.component.trigger(Smail.events.ui.page.previous);
+
+ expect(this.component.fetchMail).not.toHaveBeenCalled();
+ expect(this.component.attr.currentPage).toEqual(0);
+ });
+
+ it('changes to the next page and refetch email when ui:page:next is fired', function() {
+ this.component.attr.numPages = 10;
+ this.component.attr.currentPage = 1;
+
+ this.component.trigger(Smail.events.ui.page.next);
+
+ expect(this.component.fetchMail).toHaveBeenCalled();
+ expect(this.component.attr.currentPage).toEqual(2);
+ });
+
+ it('won\'t change the page if it was already at the first page and trying to go to previous', function() {
+ this.component.attr.numPages = 10;
+ this.component.attr.currentPage = 9;
+
+ this.component.trigger(Smail.events.ui.page.next);
+
+ expect(this.component.fetchMail).not.toHaveBeenCalled();
+ expect(this.component.attr.currentPage).toEqual(9);
+ });
+
+
+ it('triggers pageChanged event when going to next page', function() {
+ this.component.attr.numPages = 10;
+ this.component.trigger(Smail.events.ui.page.next);
+
+ expect(pageChangedEvent).toHaveBeenTriggeredOnAndWith(document, {currentPage: 1, numPages: 10});
+ });
+
+ it('triggers pageChanged event when going to previous page', function() {
+ this.component.attr.numPages = 10;
+ this.component.attr.currentPage = 1;
+ this.component.trigger(Smail.events.ui.page.previous);
+
+ expect(pageChangedEvent).toHaveBeenTriggeredOnAndWith(document, {currentPage: 0, numPages: 10});
+ });
+
+ it('resets currentPage when fetching mails by tag', function() {
+ this.component.attr.numPages = 10;
+ this.component.attr.currentPage = 999;
+ this.component.trigger(Smail.events.ui.mails.fetchByTag, {tag: 'inbox'});
+
+ expect(this.component.attr.currentPage).toEqual(0);
+ expect(pageChangedEvent).toHaveBeenTriggeredOnAndWith(document, {currentPage: 0, numPages: 10});
+ });
+
+ describe('total page numbers', function() {
+ var mailSetData = {
+ tag: 'inbox',
+ stats: { },
+ mails: [],
+ timing: {}
+ };
+
+ it('should have 5 pages with a 100 results and w 20', function() {
+ mailSetData.stats.total = 100;
+ this.component.attr.w = 20;
+ this.component.attr.numPages = 0;
+
+ this.component.trigger(Smail.events.ui.mails.fetchByTag, {tag: 'another tag'});
+
+ g(mailSetData);
+ expect(this.component.attr.numPages).toBe(5);
+ });
+
+ it('should have 6 pages with a 101 results and w 20', function() {
+ mailSetData.stats.total = 101;
+ this.component.attr.w = 20;
+ this.component.attr.numPages = 0;
+
+ this.component.trigger(Smail.events.ui.mails.fetchByTag, {tag: 'another tag'});
+
+ g(mailSetData);
+ expect(this.component.attr.numPages).toBe(6);
+ });
+ });
+
+ });
+});
diff --git a/web-ui/test/spec/services/model/mail.spec.js b/web-ui/test/spec/services/model/mail.spec.js
new file mode 100644
index 00000000..5bdad88b
--- /dev/null
+++ b/web-ui/test/spec/services/model/mail.spec.js
@@ -0,0 +1,116 @@
+/*global Smail */
+
+require(['services/model/mail'], function (Mail) {
+ var testData;
+
+ describe('services/model/mail', function () {
+ describe('reply addresses', function () {
+ it('returns the "to" and "cc" addresses if the mail was sent', function () {
+ var mail = Mail.create({
+ header: { to: ['a@b.c', 'e@f.g'], cc: ['x@x.x'] },
+ tags: ['sent']
+ });
+
+ var addresses = mail.replyToAddress();
+
+ expect(addresses).toEqual({ to: ['a@b.c', 'e@f.g'], cc: ['x@x.x']});
+ });
+ });
+
+ describe('parsing', function () {
+ describe('a single email', function () {
+ var sentMail, draftMail, recievedMail, recievedMailWithCC;
+ beforeEach(function () {
+ sentMail = Mail.create(Smail.testData().rawMail.sent);
+ draftMail = Mail.create(Smail.testData().rawMail.draft);
+ recievedMail = Mail.create(Smail.testData().rawMail.recieved);
+ recievedMailWithCC = Mail.create(Smail.testData().rawMail.recievedWithCC);
+ });
+
+ it('correctly identifies a sent mail', function () {
+ expect(sentMail.isSentMail()).toBe(true);
+ });
+
+ it('correctly identifies a draft mail', function () {
+ expect(draftMail.isDraftMail()).toBe(true);
+ });
+
+ it('correctly identifies a recieved mail', function () {
+ expect(recievedMail.isSentMail()).toBe(false);
+ expect(recievedMail.isDraftMail()).toBe(false);
+ });
+
+ it('reply to of a sent mail should be original recipient', function () {
+ expect(sentMail.replyToAddress()).toEqual({to: ['mariane_dach@davis.info'], cc: ['duda@la.lu']});
+ });
+
+ it('reply to of a mail should be the reply_to field if existent', function () {
+ expect(recievedMail.replyToAddress()).toEqual({to: ['afton_braun@botsford.biz'], cc: [] });
+ });
+
+ it('reply to of a mail should be the from field if no reply_to present', function () {
+ expect(recievedMailWithCC.replyToAddress()).toEqual({to: ['cleve_jaskolski@schimmelhirthe.net'], cc: []});
+ });
+
+ it('reply to all should include all email addresses in the header', function () {
+ expect(recievedMailWithCC.replyToAllAddress()).toEqual({
+ to: ['cleve_jaskolski@schimmelhirthe.net', 'stanford@sipes.com'],
+ cc: ['mariane_dach@davis.info']
+ });
+ });
+ });
+
+ describe('multipart email', function () {
+ var parsedMultipartMail;
+
+ beforeEach(function () {
+ parsedMultipartMail = Mail.create(Smail.testData().rawMail.multipart);
+ });
+
+ it('parses the mail as multipart/alternative', function () {
+ expect(parsedMultipartMail.isMailMultipartAlternative()).toBe(true);
+ });
+
+ it('lists the correct available content-type of the parts', function () {
+ expect(parsedMultipartMail.availableBodyPartsContentType()).toEqual(['text/plain;', 'text/html;']);
+ });
+
+ it('gets the list of parts', function () {
+ var expectedParts = [
+ {
+ headers: { 'Content-Type': 'text/plain;' },
+ body: 'Hello everyone!\n'
+ },
+ {
+ headers: {
+ 'Content-Type': 'text/html;',
+ 'Content-Transfer-Encoding': 'quoted-printable'
+ },
+ body: '<p><b>Hello everyone!</b></p>\n'
+ }
+ ];
+
+ expect(parsedMultipartMail.getMailMultiParts()).toEqual(expectedParts);
+ });
+
+ it('gets the text/plain body by the content-type', function () {
+ expect(parsedMultipartMail.getMailPartByContentType('text/plain;')).toEqual(
+ {
+ headers: { 'Content-Type': 'text/plain;' },
+ body: 'Hello everyone!\n'
+ });
+ });
+
+ it('parses the content type of a text/html body', function () {
+ expect(parsedMultipartMail.getMailPartByContentType('text/html;')).toEqual({
+ headers: {
+ 'Content-Type': 'text/html;',
+ 'Content-Transfer-Encoding': 'quoted-printable'
+ },
+ body: '<p><b>Hello everyone!</b></p>\n'
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/web-ui/test/spec/tags/data/tags.spec.js b/web-ui/test/spec/tags/data/tags.spec.js
new file mode 100644
index 00000000..8e7d33c3
--- /dev/null
+++ b/web-ui/test/spec/tags/data/tags.spec.js
@@ -0,0 +1,39 @@
+describeComponent('tags/data/tags', function () {
+ 'use strict';
+
+ beforeEach(function () {
+ setupComponent();
+ });
+
+ it('asks the server for tags when receiving the tags:want event', function() {
+ spyOn($, 'ajax').andReturn({done: function() {}});
+
+ this.component.trigger(Smail.events.tags.want);
+
+ expect($.ajax.mostRecentCall.args[0]).toEqual('/tags');
+ });
+
+ it('triggers an event on the initial sender after receiving tags', function() {
+ var f;
+ spyOn($, 'ajax').andReturn({done: function(d) { f = d; }});
+ var me = {};
+ var eventSpy = spyOnEvent(me, Smail.events.tags.received);
+
+ this.component.trigger(Smail.events.tags.want, { caller: me});
+
+ f(['foo', 'bar', 'quux/bar']);
+ expect(eventSpy).toHaveBeenTriggeredOn(me);
+ });
+
+ it('triggers an event containing the returned tags', function() {
+ var f;
+ spyOn($, 'ajax').andReturn({done: function(d) { f = d; }});
+ var me = {};
+ var eventSpy = spyOnEvent(me, Smail.events.tags.received);
+ this.component.trigger(Smail.events.tags.want, { caller: me });
+ var tags = ['foo', 'bar', 'quux/bar'];
+ f(tags);
+ tags.push(this.component.all);
+ expect(eventSpy.mostRecentCall.data).toEqual({tags: tags});
+ });
+});
diff --git a/web-ui/test/spec/tags/ui/tag.spec.js b/web-ui/test/spec/tags/ui/tag.spec.js
new file mode 100644
index 00000000..12f16330
--- /dev/null
+++ b/web-ui/test/spec/tags/ui/tag.spec.js
@@ -0,0 +1,151 @@
+/*global Smail */
+/*global _ */
+
+describeComponent('tags/ui/tag', function () {
+ 'use strict';
+
+ describe('inbox tag', function() {
+ beforeEach(function () {
+ setupComponent('<li></li>', {
+ tag: {
+ name: 'inbox',
+ ident: '1',
+ counts: {
+ total: 100,
+ read: 0
+ }
+ }
+ });
+ });
+
+ it('selects the tag on click', function () {
+ var tagSelectEvent = spyOnEvent(document, Smail.events.ui.tag.select);
+ var cleanSelectedEvent = spyOnEvent(document, Smail.events.ui.mails.cleanSelected);
+
+ this.component.$node.click();
+
+ expect(this.component.attr.selected).toBeTruthy();
+ expect(this.$node.attr('class')).toMatch('selected');
+ expect(tagSelectEvent).toHaveBeenTriggeredOnAndWith(document, { tag: 'inbox' });
+ expect(cleanSelectedEvent).toHaveBeenTriggeredOn(document);
+ });
+
+ it('should remove selected class when selecting a different tag', function () {
+ this.$node.click();
+
+ $(document).trigger(Smail.events.ui.tag.select, {tag: 'drafts'});
+
+ expect(this.$node).not.toHaveClass('selected');
+ });
+
+ it('triggers tag selected on tag select', function () {
+ var tagSelectedEvent = spyOnEvent(document, Smail.events.ui.tag.select);
+
+ $(document).trigger(Smail.events.ui.tag.select, { tag: 'drafts'});
+
+ expect(tagSelectedEvent).toHaveBeenTriggeredOnAndWith(document, { tag: 'drafts'});
+ });
+
+ it('increases the read count when there is an email read and the email has that tag', function () {
+ $(document).trigger(Smail.events.mail.read, { tags: ['inbox'] });
+
+ expect(this.component.attr.tag.counts.read).toEqual(1);
+ expect(this.$node.html()).toMatch('<span class="unread-count">99</span>');
+ });
+
+ it('doesnt increase the read count when the read email doesnt have the tag', function () {
+ $(document).trigger(Smail.events.mail.read, { tags: ['amazing']});
+
+ expect(this.component.attr.tag.counts.read).toEqual(0);
+ expect(this.$node.html()).not.toMatch('<span class="unread-count">99</span>');
+ });
+
+ it('doesnt display the unread count when there are no unreads', function () {
+ this.component.attr.tag.counts.read = 100;
+ $(document).trigger(Smail.events.mail.read, { tags: ['inbox']});
+ expect(this.$node.html()).not.toMatch('"unread-count"');
+ });
+ });
+
+ describe('drafts tag', function () {
+ var containerFordrafts;
+ beforeEach(function () {
+ setupComponent('<li></li>', {
+ tag: {
+ name: 'drafts',
+ ident: '42',
+ counts: {
+ total: 100,
+ read: 50
+ }
+ }
+ });
+ });
+
+ it('shows the total count instead of the unread count', function () {
+ $(document).trigger(Smail.events.mail.read, { tags: ['drafts']});
+ expect(this.$node.html()).toMatch('<span class="total-count">100</span>');
+ expect(this.$node.html()).not.toMatch('"unread-count"');
+ });
+ });
+
+ describe('all tag', function(){
+ beforeEach(function () {
+ setupComponent('<li></li>', {
+ tag: {
+ name: 'all',
+ ident: '45',
+ counts: {
+ total: 100,
+ read: 50
+ }
+ }
+ });
+ });
+
+ it('adds searching class when user is doing a search', function(){
+ $(document).trigger(Smail.events.search.perform, {});
+ expect(this.$node.attr('class')).toMatch('searching');
+ });
+
+ it('removes searching class when user searches for empty string', function(){
+ $(document).trigger(Smail.events.search.perform, {});
+ $(document).trigger(Smail.events.search.empty);
+ expect(this.$node.attr('class')).not.toMatch('searching');
+ });
+
+ it('removes searching class when user clicks in any tag', function(){
+ $(document).trigger(Smail.events.search.perform, {});
+ this.$node.click();
+ expect(this.$node.attr('class')).not.toMatch('searching');
+ });
+
+ });
+
+ _.each(['sent', 'trash'], function(tag_name) {
+ describe(tag_name + ' tag', function() {
+ beforeEach(function () {
+ setupComponent('<li></li>', {
+ tag: {
+ name: tag_name,
+ ident: '42',
+ counts: {
+ total: 100,
+ read: 50
+ }
+ }
+ });
+ });
+
+ it('doesn\'t display unread count for special folder', function () {
+ $(document).trigger(Smail.events.mail.read, { tags: [tag_name]});
+ expect(this.$node.html()).not.toMatch('unread-count');
+ });
+
+ it('doesn\'t display read count for special folder', function () {
+ $(document).trigger(Smail.events.mail.read, { tags: [tag_name]});
+ expect(this.$node.html()).not.toMatch('total-count');
+ });
+ });
+ });
+});
diff --git a/web-ui/test/spec/tags/ui/tag_list.spec.js b/web-ui/test/spec/tags/ui/tag_list.spec.js
new file mode 100644
index 00000000..af3ddd3a
--- /dev/null
+++ b/web-ui/test/spec/tags/ui/tag_list.spec.js
@@ -0,0 +1,75 @@
+describeComponent('tags/ui/tag_list', function () {
+ 'use strict';
+
+ var tag = function(name, ident, def) {
+ def = def || false;
+ return {name: name, counts: {read: 0, total: 0, replied: 0, starred: 0}, ident: ident, default: def};
+ };
+
+
+ describe('post initialization', function() {
+ beforeEach(function () {
+ setupComponent();
+ });
+
+ it('should render tags when tagsList:load is received', function() {
+ this.component.attr.default = false;
+ var tagList = [tag('tag1', 1), tag('tag2', 2), tag('tag3', 3)];
+
+ $(document).trigger(Smail.events.ui.tagList.load, {tags: tagList});
+
+ var items = _.map(this.$node.find('li'), function(el) {
+ return $(el).attr('id');
+ });
+
+ expect(items).toEqual(['tag-1', 'tag-2', 'tag-3']);
+ });
+
+ it('should render the default tags when tagsList:load is received and default attribute is true', function() {
+ var tagList = [tag('tag1', 1, false), tag('tag2', 2, true), tag('tag3', 3, true)];
+
+ $(document).trigger(Smail.events.ui.tagList.load, {tags: tagList});
+
+ var items = _.map(this.component.select('defaultTagList').find('li'), function(el) {
+ return $(el).attr('id');
+ });
+
+ expect(items).toEqual(['tag-2', 'tag-3']);
+ });
+
+ it('should render the custom tags when tagsList:load is received and default attribute is false', function() {
+ var tagList = [tag('tag1', 1, false), tag('tag2', 2, true), tag('tag3', 3, true)];
+
+ $(document).trigger(Smail.events.ui.tagList.load, {tags: tagList});
+
+ var items = _.map(this.component.select('customTagList').find('li'), function(el) {
+ return $(el).attr('id');
+ });
+
+ expect(items).toEqual(['tag-1']);
+ });
+
+ it('should trigger event to tell that tags were loaded sending the current tag', function () {
+ this.component.attr.currentTag = 'Drafts';
+ var tagsLoadedEvent = spyOnEvent(document, Smail.events.ui.tags.loaded);
+
+ $(document).trigger(Smail.events.ui.tagList.load, {tags: [] });
+
+ expect(tagsLoadedEvent).toHaveBeenTriggeredOnAndWith(document, { tag: 'Drafts'});
+ });
+
+ it('should send tag as undefined when tags are loaded and no tag was selected yet', function () {
+ var tagsLoadedEvent = spyOnEvent(document, Smail.events.ui.tags.loaded);
+
+ $(document).trigger(Smail.events.ui.tagList.load, {tags: [] });
+
+ expect(tagsLoadedEvent).toHaveBeenTriggeredOnAndWith(document, { tag: undefined });
+ });
+
+ it('should save the current tag when a tag is selected', function () {
+ $(document).trigger(Smail.events.ui.tag.selected, { tag: 'amazing'});
+
+ expect(this.component.attr.currentTag).toEqual('amazing');
+ });
+ });
+});
diff --git a/web-ui/test/spec/tags/ui/tag_shortcut.spec.js b/web-ui/test/spec/tags/ui/tag_shortcut.spec.js
new file mode 100644
index 00000000..7df35631
--- /dev/null
+++ b/web-ui/test/spec/tags/ui/tag_shortcut.spec.js
@@ -0,0 +1,35 @@
+describeComponent("tags/ui/tag_shortcut", function () {
+
+ var parent, shortcut, component, TagShortcut;
+
+ beforeEach(function () {
+ TagShortcut = require('tags/ui/tag_shortcut');
+
+ component = jasmine.createSpyObj('tagComponent', ['triggerSelect']);
+ parent = $("<ul>");
+ shortcut = TagShortcut.appendedTo(parent, { linkTo: { name: 'inbox', counts: { total: 15 }}, trigger: component });
+ });
+
+ it('renders the shortcut inside the parent', function () {
+ expect(parent.html()).toMatch('<a title="inbox">');
+ expect(parent.html()).toMatch('<i class="fa fa-inbox"></i>');
+ expect(parent.html()).toMatch('<div class="shortcut-label">inbox</div>');
+ });
+
+ it('selects and unselect on tag.select', function () {
+ $(document).trigger(Smail.events.ui.tag.select, { tag: 'inbox'});
+
+ expect(shortcut.$node).toHaveClass("selected");
+
+ $(document).trigger(Smail.events.ui.tag.select, { tag: 'sent'});
+
+ expect(shortcut.$node).not.toHaveClass("selected");
+ });
+
+ it('delegates the click to linked tag', function (){
+ shortcut.$node.click();
+
+ expect(component.triggerSelect).toHaveBeenCalled();
+ });
+
+});
diff --git a/web-ui/test/spec/user_alerts/ui/user_alerts.spec.js b/web-ui/test/spec/user_alerts/ui/user_alerts.spec.js
new file mode 100644
index 00000000..f013b744
--- /dev/null
+++ b/web-ui/test/spec/user_alerts/ui/user_alerts.spec.js
@@ -0,0 +1,23 @@
+describeComponent('user_alerts/ui/user_alerts', function () {
+ 'use strict';
+
+ beforeEach(function () {
+ setupComponent('<div id="userAlerts"></div>', { dismissTimeout: 100 });
+ });
+
+ it('should render message when ui:user_alerts:displayMessage is triggered', function () {
+ this.component.trigger(Smail.events.ui.userAlerts.displayMessage, { message: 'a message' });
+
+ expect(this.component.$node.html()).toMatch('a message');
+ });
+
+ it('should be emptied and hidden when hide is called', function() {
+ expect(this.$node).not.toBeHidden();
+ this.component.hide()
+ expect(this.$node).toBeHidden();
+ expect(this.$node.html()).toEqual('')
+ });
+
+
+
+});