From 224a6f4d85a6436330b702c4f08a93a615f74cb4 Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Sun, 23 Mar 2014 21:05:25 +0100 Subject: [PATCH] Bug 936729 - [Messages][Sharing]MMS does not return to original app after cancelling or sending message. Share activity converted from 'window' to 'inline'. r=julien --- apps/sms/index.html | 3 + apps/sms/js/activity_handler.js | 180 +++++++----- apps/sms/js/thread_ui.js | 152 +++++----- apps/sms/manifest.webapp | 4 +- apps/sms/style/sms.css | 13 + apps/sms/test/unit/activity_handler_test.js | 83 +++++- apps/sms/test/unit/mock_activity_handler.js | 11 +- apps/sms/test/unit/mock_thread_ui.js | 2 - .../test/unit/thread_ui_integration_test.js | 4 +- apps/sms/test/unit/thread_ui_test.js | 266 +++++++++--------- 10 files changed, 405 insertions(+), 313 deletions(-) diff --git a/apps/sms/index.html b/apps/sms/index.html index c9fbdbc2c113..f499c3f1a3cb 100755 --- a/apps/sms/index.html +++ b/apps/sms/index.html @@ -137,6 +137,9 @@

Delete messages + + + diff --git a/apps/sms/js/activity_handler.js b/apps/sms/js/activity_handler.js index fadfab399219..83baa8e2a7ee 100755 --- a/apps/sms/js/activity_handler.js +++ b/apps/sms/js/activity_handler.js @@ -9,16 +9,26 @@ 'use strict'; var ActivityHandler = { + // CSS class applied to the body element when app requested via activity + REQUEST_ACTIVITY_MODE_CLASS_NAME: 'request-activity-mode', + isLocked: false, // Will hold current activity object - currentActivity: { new: null }, + _activity: null, init: function() { if (!window.navigator.mozSetMessageHandler) { return; } - window.navigator.mozSetMessageHandler('activity', this.global.bind(this)); + + // A mapping of MozActivity names to their associated event handler + window.navigator.mozSetMessageHandler('activity', + this._onActivity.bind(this, { + 'new': this._onNewActivity, + 'share': this._onShareActivity + }) + ); // We want to register the handler only when we're on the launch path if (!window.location.hash.length) { @@ -30,108 +40,124 @@ var ActivityHandler = { } }, + isInActivity: function isInActivity() { + return !!this._activity; + }, + + setActivity: function setActivity(value) { + if (!value) { + throw new Error('Activity should be defined!'); + } + this._toggleActivityRequestMode(true); + this._activity = value; + }, + // The Messaging application's global Activity handler. Delegates to specific // handler based on the Activity name. - global: function activityHandler(activity) { - + _onActivity: function activityHandler(handlers, activity) { var name = activity.source.name; - var handler = this._handlers[name]; + var handler = handlers[name]; if (typeof handler === 'function') { - handler.apply(this, arguments); + this.setActivity(activity); + + handler.call(this, activity); } else { console.error('Unrecognized activity: "' + name + '"'); } }, - // A mapping of MozActivity names to their associated event handler - _handlers: { - 'new': function newHandler(activity) { - - // This lock is for avoiding several calls at the same time. - if (this.isLocked) { - return; - } - - this.currentActivity.new = activity; - this.isLocked = true; + _onNewActivity: function newHandler(activity) { + // This lock is for avoiding several calls at the same time. + if (this.isLocked) { + return; + } - var number = activity.source.data.number; - var body = activity.source.data.body; + this.isLocked = true; - Contacts.findByPhoneNumber(number, function findContact(results) { - var record, details, name, contact; + var number = activity.source.data.number; + var body = activity.source.data.body; - // Bug 867948: results null is a legitimate case - if (results && results.length) { - record = results[0]; - details = Utils.getContactDetails(number, record); - name = record.name.length && record.name[0]; - contact = { - number: number, - name: name, - source: 'contacts' - }; - } + Contacts.findByPhoneNumber(number, function findContact(results) { + var record, details, name, contact; - ActivityHandler.toView({ - body: body, + // Bug 867948: results null is a legitimate case + if (results && results.length) { + record = results[0]; + details = Utils.getContactDetails(number, record); + name = record.name.length && record.name[0]; + contact = { number: number, - contact: contact || null - }); - }); + name: name, + source: 'contacts' + }; + } - ThreadUI.enableActivityRequestMode(); - }, - share: function shareHandler(activity) { - var blobs = activity.source.data.blobs, - names = activity.source.data.filenames; + ActivityHandler.toView({ + body: body, + number: number, + contact: contact || null + }); + }); + }, - function insertAttachments() { - window.removeEventListener('hashchange', insertAttachments); + _onShareActivity: function shareHandler(activity) { + var activityData = activity.source.data; - var attachments = blobs.map(function(blob, idx) { - var attachment = new Attachment(blob, { - name: names[idx], - isDraft: true - }); + var attachments = activityData.blobs.map(function(blob, idx) { + var attachment = new Attachment(blob, { + name: activityData.filenames[idx], + isDraft: true + }); - return attachment; - }); + return attachment; + }); - var size = attachments.reduce(function(size, attachment) { - if (attachment.type !== 'img') { - size += attachment.size; - } + var size = attachments.reduce(function(size, attachment) { + if (attachment.type !== 'img') { + size += attachment.size; + } - return size; - }, 0); + return size; + }, 0); - if (size > Settings.mmsSizeLimitation) { - alert(navigator.mozL10n.get('files-too-large', { n: blobs.length })); - return; - } + if (size > Settings.mmsSizeLimitation) { + alert(navigator.mozL10n.get('files-too-large', { + n: activityData.blobs.length + })); + this.leaveActivity(); + return; + } - ThreadUI.cleanFields(true); + // Navigating to the 'New Message' page is an asynchronous operation that + // clears the Composition field. If the application is not already in the + // 'New Message' page, delay attachment insertion until after the + // navigation is complete. + if (window.location.hash !== '#new') { + window.addEventListener('hashchange', function onHashChanged() { + window.removeEventListener('hashchange', onHashChanged); Compose.append(attachments); - } - - // Navigating to the 'New Message' page is an asynchronous operation that - // clears the Composition field. If the application is not already in the - // 'New Message' page, delay attachment insertion until after the - // navigation is complete. - if (window.location.hash !== '#new') { - window.addEventListener('hashchange', insertAttachments); - window.location.hash = '#new'; - } else { - insertAttachments(); - } + }); + window.location.hash = '#new'; + } else { + Compose.append(attachments); } }, - resetActivity: function ah_resetActivity() { - this.currentActivity.new = null; - ThreadUI.resetActivityRequestMode(); + _toggleActivityRequestMode: function(toggle) { + document.body.classList.toggle( + this.REQUEST_ACTIVITY_MODE_CLASS_NAME, + toggle + ); + }, + + leaveActivity: function ah_leaveActivity() { + if (this.isInActivity()) { + this._activity.postResult({ success: true }); + this._activity = null; + + this._toggleActivityRequestMode(false); + } }, handleMessageNotification: function ah_handleMessageNotification(message) { diff --git a/apps/sms/js/thread_ui.js b/apps/sms/js/thread_ui.js index 85f4a2213c7f..86d3bb59e969 100755 --- a/apps/sms/js/thread_ui.js +++ b/apps/sms/js/thread_ui.js @@ -6,7 +6,7 @@ ActivityPicker, ThreadListUI, OptionMenu, Threads, Contacts, Attachment, WaitingScreen, MozActivity, LinkActionHandler, ActivityHandler, TimeHeaders, ContactRenderer, Draft, Drafts, - Thread, MultiSimActionButton, LazyLoader */ + Thread, MultiSimActionButton, LazyLoader, Promise */ /*exported ThreadUI */ (function(global) { @@ -72,7 +72,6 @@ var ThreadUI = global.ThreadUI = { inThread: false, isNewMessageNoticeShown: false, shouldChangePanelNextEvent: false, - inActivity: false, timeouts: { update: null, subjectLengthNotice: null @@ -92,11 +91,11 @@ var ThreadUI = global.ThreadUI = { [ 'container', 'subheader', 'to-field', 'recipients-list', 'recipient', 'input', 'compose-form', 'check-all-button', 'uncheck-all-button', - 'contact-pick-button', 'back-button', 'send-button', 'attach-button', - 'delete-button', 'cancel-button', 'subject-input', 'new-message-notice', - 'options-icon', 'edit-mode', 'edit-form', 'tel-form', 'header-text', - 'max-length-notice', 'convert-notice', 'resize-notice', - 'dual-sim-information' + 'contact-pick-button', 'back-button', 'close-button', 'send-button', + 'attach-button', 'delete-button', 'cancel-button', 'subject-input', + 'new-message-notice', 'options-icon', 'edit-mode', 'edit-form', + 'tel-form', 'header-text', 'max-length-notice', 'convert-notice', + 'resize-notice', 'dual-sim-information' ].forEach(function(id) { this[Utils.camelCase(id)] = document.getElementById('messages-' + id); }, this); @@ -178,6 +177,10 @@ var ThreadUI = global.ThreadUI = { 'click', this.back.bind(this) ); + this.closeButton.addEventListener( + 'click', this.close.bind(this) + ); + this.checkAllButton.addEventListener( 'click', this.toggleCheckedAll.bind(this, true) ); @@ -306,7 +309,6 @@ var ThreadUI = global.ThreadUI = { this.HEADER_HEIGHT = document.querySelector('.view-header').offsetHeight; this.shouldChangePanelNextEvent = false; - this.inActivity = false; ThreadUI.updateInputMaxHeight(); }, @@ -437,21 +439,6 @@ var ThreadUI = global.ThreadUI = { } }, - // Change the back button to close button - enableActivityRequestMode: function thui_enableActivityRequestMode() { - this.inActivity = true; - var domBackButtonSpan = this.backButton.querySelector('span'); - domBackButtonSpan.classList.remove('icon-back'); - domBackButtonSpan.classList.add('icon-close'); - }, - - resetActivityRequestMode: function thui_resetActivityRequestMode() { - this.inActivity = false; - var domBackButtonSpan = this.backButton.querySelector('span'); - domBackButtonSpan.classList.remove('icon-close'); - domBackButtonSpan.classList.add('icon-back'); - }, - getAllInputs: function thui_getAllInputs() { if (this.container) { return Array.prototype.slice.call( @@ -856,38 +843,42 @@ var ThreadUI = global.ThreadUI = { generateHeightRule(maxHeight); }, - leaveActivity: function thui_leaveActivity() { - var currentActivity = ActivityHandler.currentActivity.new; + close: function thui_close() { + return this._onNavigatingBack().then(function() { + ActivityHandler.leaveActivity(); + }).catch(function(e) { + // If we don't have any error that means that action was rejected + // intentionally and there is nothing critical to report about. + e && console.error('Unexpected error while closing the activity: ', e); - if (currentActivity) { - currentActivity.postResult({ success: true }); - ActivityHandler.resetActivity(); - } + return Promise.reject(e); + }); }, back: function thui_back() { - if (window.location.hash === '#group-view' || window.location.hash.startsWith('#report-view')) { window.location.hash = '#thread=' + Threads.lastId; this.updateHeaderData(); - return; + + return Promise.resolve(); } - var goBack = (function() { - this.stopRendering(); + return this._onNavigatingBack().then(function() { + this.cleanFields(true); + window.location.hash = '#thread-list'; + }.bind(this)).catch(function(e) { + e && console.error('Unexpected error while navigating back: ', e); - var leave = (function() { - this.cleanFields(true); - window.location.hash = '#thread-list'; - }).bind(this); + return Promise.reject(e); + }); + }, - if (this.inActivity) { - this.leaveActivity(); - return; - } + _onNavigatingBack: function() { + this.stopRendering(); - // TODO Add comment about assimilation above on line #183? + // We're waiting for the keyboard to disappear before animating back + return this._ensureKeyboardIsHidden().then(function() { // Need to assimilate recipients in order to check if any entered this.assimilateRecipients(); @@ -905,7 +896,6 @@ var ThreadUI = global.ThreadUI = { if (Compose.isEmpty() && (Threads.active || this.recipients.length === 0)) { this.discardDraft(); - leave(); return; } @@ -918,61 +908,63 @@ var ThreadUI = global.ThreadUI = { if (!Threads.currentId) { this.saveDraft({autoSave: true}); } - leave(); return; } - var prompt = 'save-as-draft'; - if (this.draft) { - prompt = 'replace-draft'; - } + return this._showMessageSaveOrDiscardPrompt(); + }.bind(this)); + }, + + isKeyboardDisplayed: function thui_isKeyboardDisplayed() { + /* XXX: Detect if the keyboard is visible. The keyboard minimal height is + * 150px; when in reduced attention screen mode however the difference + * between window height and the screen height will be larger than 150px + * thus correctly yielding false here. */ + return ((window.screen.height - window.innerHeight) > 150); + }, + + _ensureKeyboardIsHidden: function() { + if (this.isKeyboardDisplayed()) { + return new Promise(function(resolve) { + var setTimer = window.setTimeout(resolve, 400); + window.addEventListener('resize', function keyboardHidden() { + window.clearTimeout(setTimer); + window.removeEventListener('resize', keyboardHidden); + resolve(); + }); + }); + } + return Promise.resolve(); + }, + _showMessageSaveOrDiscardPrompt: function() { + return new Promise(function(resolve, reject) { var options = { items: [ { - l10nId: prompt, - method: function onsave() { + l10nId: this.draft ? 'replace-draft' : 'save-as-draft', + method: function onSave() { this.saveDraft(); - leave(); + resolve(); }.bind(this) }, { l10nId: 'discard-message', - method: function ondiscard() { + method: function onDiscard() { this.discardDraft(); - leave(); + resolve(); }.bind(this) }, { - l10nId: 'cancel' + l10nId: 'cancel', + method: function onCancel() { + reject(); + } } ] }; - new OptionMenu(options).show(); - - }).bind(this); - - // We're waiting for the keyboard to disappear before animating back - if (this.isKeyboardDisplayed()) { - - window.addEventListener('resize', function keyboardHidden() { - window.removeEventListener('resize', keyboardHidden); - window.clearTimeout(setTimer); - goBack(); - }); - var setTimer = window.setTimeout(goBack, 400); - } else { - goBack(); - } - }, - - isKeyboardDisplayed: function thui_isKeyboardDisplayed() { - /* XXX: Detect if the keyboard is visible. The keyboard minimal height is - * 150px; when in reduced attention screen mode however the difference - * between window height and the screen height will be larger than 150px - * thus correctly yielding false here. */ - return ((window.screen.height - window.innerHeight) > 150); + }.bind(this)); }, enableSend: function thui_enableSend() { @@ -2235,8 +2227,8 @@ var ThreadUI = global.ThreadUI = { if (recipients.length > 1) { this.shouldChangePanelNextEvent = false; window.location.hash = '#thread-list'; - if (this.inActivity) { - setTimeout(this.leaveActivity.bind(this), this.LEAVE_ACTIVITY_DELAY); + if (ActivityHandler.isInActivity()) { + setTimeout(this.close.bind(this), this.LEAVE_ACTIVITY_DELAY); } } diff --git a/apps/sms/manifest.webapp b/apps/sms/manifest.webapp index 8067c3e51b36..2652d4dab8c7 100755 --- a/apps/sms/manifest.webapp +++ b/apps/sms/manifest.webapp @@ -56,13 +56,15 @@ "returnValue": true }, "share": { + "href": "/index.html#activity-share", "filters": { "type": ["image/*", "audio/*", "video/*"], "number": { "max": 5 } }, - "disposition": "window" + "disposition": "inline", + "returnValue": true } }, "messages": [ diff --git a/apps/sms/style/sms.css b/apps/sms/style/sms.css index 42bb4200422c..eef974a8cf01 100644 --- a/apps/sms/style/sms.css +++ b/apps/sms/style/sms.css @@ -1082,3 +1082,16 @@ section[role="region"].information > header:first-child .icon.icon-back { #messages-back-button:hover:after { background-color: transparent; } + +/* Activity mode specific style */ +#messages-close-button { + display: none; +} + +.request-activity-mode #messages-close-button { + display: block; +} + +.request-activity-mode #messages-back-button { + display: none; +} diff --git a/apps/sms/test/unit/activity_handler_test.js b/apps/sms/test/unit/activity_handler_test.js index d3b772eae339..fd4ab49c0101 100755 --- a/apps/sms/test/unit/activity_handler_test.js +++ b/apps/sms/test/unit/activity_handler_test.js @@ -134,12 +134,14 @@ suite('ActivityHandler', function() { filenames: ['testBlob1', 'testBlob2', 'testBlob3', 'testBlob4', 'testBlob5'] } - } + }, + postResult: sinon.stub() }; }); teardown(function() { window.location.hash = this.prevHash; + ActivityHandler.leaveActivity(); }); test('test for pushing an attachments to an array', function() { @@ -207,10 +209,18 @@ suite('ActivityHandler', function() { sinon.assert.notCalled(window.alert); }); - test('share shouldn\'t change the ThreadUI back button', function() { - this.sinon.stub(ThreadUI, 'enableActivityRequestMode'); + test('share message should switch application to request activity mode', + function() { + MockNavigatormozSetMessageHandler.mTrigger('activity', shareActivity); + assert.isTrue(document.body.classList.contains( + ActivityHandler.REQUEST_ACTIVITY_MODE_CLASS_NAME) + ); + } + ); + + test('share message should set the current activity', function() { MockNavigatormozSetMessageHandler.mTrigger('activity', shareActivity); - assert.isFalse(ThreadUI.enableActivityRequestMode.called); + assert.isTrue(ActivityHandler.isInActivity()); }); }); @@ -518,7 +528,8 @@ suite('ActivityHandler', function() { number: '123', body: 'foo' } - } + }, + postResult: sinon.stub() }; var newActivity_empty = { @@ -527,7 +538,8 @@ suite('ActivityHandler', function() { data: { number: '123' } - } + }, + postResult: sinon.stub() }; setup(function() { @@ -537,6 +549,7 @@ suite('ActivityHandler', function() { teardown(function() { MessageManager.activity = null; + ActivityHandler.leaveActivity(); }); suiteSetup(function() { @@ -633,14 +646,17 @@ suite('ActivityHandler', function() { test('new message should set the current activity', function() { MockNavigatormozSetMessageHandler.mTrigger('activity', newActivity); - assert.equal(ActivityHandler.currentActivity.new, newActivity); + assert.isTrue(ActivityHandler.isInActivity()); }); - test('new message should change the ThreadUI back button', function() { - this.sinon.stub(ThreadUI, 'enableActivityRequestMode'); - MockNavigatormozSetMessageHandler.mTrigger('activity', newActivity); - assert.isTrue(ThreadUI.enableActivityRequestMode.called); - }); + test('new message should switch application to request activity mode', + function() { + MockNavigatormozSetMessageHandler.mTrigger('activity', newActivity); + assert.isTrue(document.body.classList.contains( + ActivityHandler.REQUEST_ACTIVITY_MODE_CLASS_NAME) + ); + } + ); }); @@ -695,4 +711,47 @@ suite('ActivityHandler', function() { }); }); }); + + suite('setActivity', function() { + teardown(function() { + ActivityHandler.leaveActivity(); + }); + + test('setting current activity should switch on activity mode', function() { + ActivityHandler.setActivity({ + postResult: sinon.stub() + }); + assert.isTrue(document.body.classList.contains( + ActivityHandler.REQUEST_ACTIVITY_MODE_CLASS_NAME) + ); + }); + + test('setting current activity as null or undefined should throw exception', + function() { + assert.throws(function() { + ActivityHandler.setActivity(null); + }); + assert.throws(function() { + ActivityHandler.setActivity(); + }); + assert.isFalse(document.body.classList.contains( + ActivityHandler.REQUEST_ACTIVITY_MODE_CLASS_NAME) + ); + } + ); + }); + + suite('leaveActivity', function() { + test('should call postResult on current activity', function() { + var mockActivity = { + postResult: sinon.stub() + }; + ActivityHandler.setActivity(mockActivity); + assert.isTrue(ActivityHandler.isInActivity()); + + ActivityHandler.leaveActivity(); + sinon.assert.called(mockActivity.postResult); + assert.isFalse(ActivityHandler.isInActivity()); + }); + }); }); diff --git a/apps/sms/test/unit/mock_activity_handler.js b/apps/sms/test/unit/mock_activity_handler.js index cc3c4f859a65..6a7c30eacdc0 100644 --- a/apps/sms/test/unit/mock_activity_handler.js +++ b/apps/sms/test/unit/mock_activity_handler.js @@ -2,19 +2,14 @@ 'use strict'; var MockActivityHandler = { - currentActivity: { new: null }, - init: function() {}, - global: function() {}, - resetActivity: function() {}, + isInActivity: function() {}, + leaveActivity: function() {}, handleMessageNotification: function() {}, displayUnsentConfirmation: function() {}, launchComposer: function() {}, triggerNewMessage: function() {}, toView: function() {}, onSmsReceived: function() {}, - onNotification: function() {}, - mTeardown: function mah_mTeardown() { - this.currentActivity = { new: null }; - } + onNotification: function() {} }; diff --git a/apps/sms/test/unit/mock_thread_ui.js b/apps/sms/test/unit/mock_thread_ui.js index 30e284e626d3..3f1687f52105 100644 --- a/apps/sms/test/unit/mock_thread_ui.js +++ b/apps/sms/test/unit/mock_thread_ui.js @@ -33,8 +33,6 @@ var MockThreadUI = { }); }, initSentAudio: function() {}, - enableActivityRequestMode: function() {}, - resetActivityRequestMode: function() {}, getAllInputs: function() {}, getSelectedInputs: function() {}, messageComposerInputHandler: function() {}, diff --git a/apps/sms/test/unit/thread_ui_integration_test.js b/apps/sms/test/unit/thread_ui_integration_test.js index b64b16579892..b82d30e5ab6d 100644 --- a/apps/sms/test/unit/thread_ui_integration_test.js +++ b/apps/sms/test/unit/thread_ui_integration_test.js @@ -14,6 +14,7 @@ requireApp('sms/test/unit/mock_navigatormoz_sms.js'); requireApp('sms/test/unit/mock_message_manager.js'); requireApp('sms/test/unit/mock_moz_activity.js'); requireApp('sms/test/unit/mock_information.js'); +requireApp('sms/test/unit/mock_activity_handler.js'); requireApp('sms/js/utils.js'); requireApp('sms/js/settings.js'); requireApp('sms/js/attachment_menu.js'); @@ -30,7 +31,8 @@ requireApp('sms/js/contact_renderer.js'); var mHelperIntegration = new MocksHelper([ 'MessageManager', 'MozActivity', - 'Information' + 'Information', + 'ActivityHandler' ]).init(); suite('ThreadUI Integration', function() { diff --git a/apps/sms/test/unit/thread_ui_test.js b/apps/sms/test/unit/thread_ui_test.js index ddeee78ef03f..8fccc9107c3c 100755 --- a/apps/sms/test/unit/thread_ui_test.js +++ b/apps/sms/test/unit/thread_ui_test.js @@ -27,7 +27,6 @@ require('/js/thread_list_ui.js'); require('/js/utils.js'); require('/test/unit/mock_time_headers.js'); -require('/test/unit/mock_alert.js'); require('/test/unit/mock_link_action_handler.js'); require('/test/unit/mock_attachment.js'); require('/test/unit/mock_attachment_menu.js'); @@ -4092,18 +4091,16 @@ suite('thread_ui.js >', function() { assert.equal(window.location.hash, '#thread-list'); }); - test('then closes the activity if we\'re in an an activity', function() { - var mockActivity = { - postResult: sinon.stub() - }; - - ActivityHandler.currentActivity.new = mockActivity; - ThreadUI.enableActivityRequestMode(); + test('then closes if we\'re in the activity', function() { + this.sinon.stub(ThreadUI, 'close'); + this.sinon.stub(ActivityHandler, 'isInActivity').returns(true); sendSmsToSeveralRecipients(); assert.equal(window.location.hash, '#thread-list'); - this.sinon.clock.tick(3000); - sinon.assert.called(mockActivity.postResult); + + this.sinon.clock.tick(ThreadUI.LEAVE_ACTIVITY_DELAY); + + sinon.assert.called(ThreadUI.close); }); }); @@ -4419,16 +4416,6 @@ suite('thread_ui.js >', function() { }); }); - suite('enableActivityRequestMode', function() { - test('calling function change the back button icon', function() { - var backButtonSpan = ThreadUI.backButton.querySelector('span'); - - ThreadUI.enableActivityRequestMode(); - assert.isTrue(backButtonSpan.classList.contains('icon-close')); - assert.isFalse(backButtonSpan.classList.contains('icon-back')); - }); - }); - suite('saveDraft() > ', function() { var addSpy, updateSpy, bannerSpy, arg; @@ -4754,38 +4741,16 @@ suite('thread_ui.js >', function() { }); suite('Back button behaviour', function() { - - suite('During activity', function() { - setup(function() { - this.sinon.stub(ThreadUI, 'isKeyboardDisplayed').returns(false); - this.sinon.stub(ThreadUI, 'stopRendering'); - ThreadUI.enableActivityRequestMode(); - }); - - test('Call postResult when there is an activity', function() { - var mockActivity = { - postResult: sinon.stub() - }; - - ActivityHandler.currentActivity.new = mockActivity; - - ThreadUI.back(); - sinon.assert.called(mockActivity.postResult); - }); - }); - suite('From new message', function() { var showCalled = false; - var spy; - - suiteSetup(function() { - spy = sinon.spy(ThreadUI, 'saveDraft'); - }); + var optionMenuTargetItemIndex = 0; setup(function() { showCalled = false; this.sinon.stub(window, 'OptionMenu').returns({ show: function() { + var item = OptionMenu.args[0][0].items[optionMenuTargetItemIndex]; + item.method.apply(null); showCalled = true; }, hide: function() {} @@ -4803,77 +4768,97 @@ suite('thread_ui.js >', function() { ThreadUI.draft = null; }); - test('Displays OptionMenu prompt if recipients', function() { - ThreadUI.back(); - - assert.isTrue(OptionMenu.calledOnce); - assert.isTrue(showCalled); + test('Displays OptionMenu prompt if recipients', function(done) { + ThreadUI.back().then(function() { + assert.isTrue(OptionMenu.calledOnce); + assert.isTrue(showCalled); - var items = OptionMenu.args[0][0].items; + var items = OptionMenu.args[0][0].items; - // Assert the correct menu items were displayed - assert.equal(items[0].l10nId, 'save-as-draft'); - assert.equal(items[1].l10nId, 'discard-message'); - assert.equal(items[2].l10nId, 'cancel'); + // Assert the correct menu items were displayed + assert.equal(items[0].l10nId, 'save-as-draft'); + assert.equal(items[1].l10nId, 'discard-message'); + assert.equal(items[2].l10nId, 'cancel'); + }).then(done, done); }); - test('Displays OptionMenu prompt if recipients & content', function() { + test('Displays OptionMenu prompt if recipients & content', + function(done) { + Compose.append('foo'); - ThreadUI.back(); - assert.isTrue(OptionMenu.calledOnce); - assert.isTrue(showCalled); + ThreadUI.back().then(function() { + assert.isTrue(OptionMenu.calledOnce); + assert.isTrue(showCalled); - var items = OptionMenu.args[0][0].items; + var items = OptionMenu.args[0][0].items; - // Assert the correct menu items were displayed - assert.equal(items[0].l10nId, 'save-as-draft'); - assert.equal(items[1].l10nId, 'discard-message'); - assert.equal(items[2].l10nId, 'cancel'); + // Assert the correct menu items were displayed + assert.equal(items[0].l10nId, 'save-as-draft'); + assert.equal(items[1].l10nId, 'discard-message'); + assert.equal(items[2].l10nId, 'cancel'); + }).then(done, done); }); - test('Displays OptionMenu prompt if content', function() { + test('Displays OptionMenu prompt if content', function(done) { ThreadUI.recipients.remove('999'); Compose.append('foo'); - ThreadUI.back(); - assert.isTrue(OptionMenu.calledOnce); - assert.isTrue(showCalled); + ThreadUI.back().then(function() { + assert.isTrue(OptionMenu.calledOnce); + assert.isTrue(showCalled); - var items = OptionMenu.args[0][0].items; + var items = OptionMenu.args[0][0].items; - // Assert the correct menu items were displayed - assert.equal(items[0].l10nId, 'save-as-draft'); - assert.equal(items[1].l10nId, 'discard-message'); - assert.equal(items[2].l10nId, 'cancel'); + // Assert the correct menu items were displayed + assert.equal(items[0].l10nId, 'save-as-draft'); + assert.equal(items[1].l10nId, 'discard-message'); + assert.equal(items[2].l10nId, 'cancel'); + }).then(done, done); }); - suite('OptionMenu operations', function() { - test('Save as Draft', function() { - ThreadUI.back(); + suite('OptionMenu operations', function(done) { + test('Save as Draft', function(done) { + var spy = this.sinon.spy(ThreadUI, 'saveDraft'); + optionMenuTargetItemIndex = 0; - OptionMenu.args[0][0].items[0].method(); - - // These things will be true - assert.isTrue(spy.calledOnce); - assert.equal(window.location.hash, '#thread-list'); - assert.equal(ThreadUI.recipients.length, 0); - assert.equal(Compose.getContent(), ''); + ThreadUI.back().then(function() { + // These things will be true + assert.isTrue(spy.calledOnce); + assert.equal(window.location.hash, '#thread-list'); + assert.equal(ThreadUI.recipients.length, 0); + assert.equal(Compose.getContent(), ''); + }).then(done, done); }); - test('Discard', function() { + test('Discard', function(done) { + var spy = this.sinon.spy(ThreadListUI, 'removeThread'); + optionMenuTargetItemIndex = 1; ThreadUI.draft = new Draft({id: 3}); ThreadUI.draft.isEdited = true; - var spy = this.sinon.spy(ThreadListUI, 'removeThread'); - ThreadUI.back(); - OptionMenu.args[0][0].items[1].method(); + ThreadUI.back().then(function() { + assert.equal(window.location.hash, '#thread-list'); + assert.equal(ThreadUI.recipients.length, 0); + assert.equal(Compose.getContent(), ''); + assert.isTrue(spy.calledOnce); + assert.isNull(ThreadUI.draft); + }).then(done, done); + }); - assert.equal(window.location.hash, '#thread-list'); - assert.equal(ThreadUI.recipients.length, 0); - assert.equal(Compose.getContent(), ''); - assert.isTrue(spy.calledOnce); - assert.isNull(ThreadUI.draft); + test('Cancel', function(done) { + var saveDraftSpy = this.sinon.spy(ThreadUI, 'saveDraft'); + var discardDraftSpy = this.sinon.spy(ThreadListUI, 'removeThread'); + optionMenuTargetItemIndex = 2; + + ThreadUI.back().then(function() { + throw new Error('Success callback should not have been called.'); + }, function() { + // These things will be true + sinon.assert.notCalled(saveDraftSpy); + sinon.assert.notCalled(discardDraftSpy); + assert.equal(window.location.hash, '#new'); + }).then(done, done); }); }); @@ -4893,49 +4878,52 @@ suite('thread_ui.js >', function() { ThreadUI.draft.isEdited = true; // can't set this via options }); - test('Prompts for replacement if recipients', function() { - ThreadUI.back(); - - assert.isTrue(OptionMenu.calledOnce); - assert.isTrue(showCalled); + test('Prompts for replacement if recipients', function(done) { + ThreadUI.back().then(function() { + assert.isTrue(OptionMenu.calledOnce); + assert.isTrue(showCalled); - var items = OptionMenu.args[0][0].items; + var items = OptionMenu.args[0][0].items; - // Assert the correct menu items were displayed - assert.equal(items[0].l10nId, 'replace-draft'); - assert.equal(items[1].l10nId, 'discard-message'); - assert.equal(items[2].l10nId, 'cancel'); + // Assert the correct menu items were displayed + assert.equal(items[0].l10nId, 'replace-draft'); + assert.equal(items[1].l10nId, 'discard-message'); + assert.equal(items[2].l10nId, 'cancel'); + }).then(done, done); }); - test('Prompts for replacement if recipients & content', function() { + test('Prompts for replacement if recipients & content', + function(done) { Compose.append('foo'); - ThreadUI.back(); - assert.isTrue(OptionMenu.calledOnce); - assert.isTrue(showCalled); + ThreadUI.back().then(function() { + assert.isTrue(OptionMenu.calledOnce); + assert.isTrue(showCalled); - var items = OptionMenu.args[0][0].items; + var items = OptionMenu.args[0][0].items; - // Assert the correct menu items were displayed - assert.equal(items[0].l10nId, 'replace-draft'); - assert.equal(items[1].l10nId, 'discard-message'); - assert.equal(items[2].l10nId, 'cancel'); + // Assert the correct menu items were displayed + assert.equal(items[0].l10nId, 'replace-draft'); + assert.equal(items[1].l10nId, 'discard-message'); + assert.equal(items[2].l10nId, 'cancel'); + }).then(done, done); }); - test('Prompts for replacement if content', function() { + test('Prompts for replacement if content', function(done) { ThreadUI.recipients.remove('999'); Compose.append('foo'); - ThreadUI.back(); - assert.isTrue(OptionMenu.calledOnce); - assert.isTrue(showCalled); + ThreadUI.back().then(function() { + assert.isTrue(OptionMenu.calledOnce); + assert.isTrue(showCalled); - var items = OptionMenu.args[0][0].items; + var items = OptionMenu.args[0][0].items; - // Assert the correct menu items were displayed - assert.equal(items[0].l10nId, 'replace-draft'); - assert.equal(items[1].l10nId, 'discard-message'); - assert.equal(items[2].l10nId, 'cancel'); + // Assert the correct menu items were displayed + assert.equal(items[0].l10nId, 'replace-draft'); + assert.equal(items[1].l10nId, 'discard-message'); + assert.equal(items[2].l10nId, 'cancel'); + }).then(done, done); }); }); @@ -4945,37 +4933,51 @@ suite('thread_ui.js >', function() { ThreadUI.draft = {id: 55}; }); - test('No prompt for replacement if recipients', function() { + test('No prompt for replacement if recipients', function(done) { ThreadUI.draft.isEdited = false; - ThreadUI.back(); - assert.isFalse(OptionMenu.calledOnce); - assert.isFalse(showCalled); + ThreadUI.back().then(function() { + assert.isFalse(OptionMenu.calledOnce); + assert.isFalse(showCalled); + }).then(done, done); }); - test('No prompt for replacement if recipients & content', function() { + test('No prompt for replacement if recipients & content', + function(done) { + Compose.append('foo'); ThreadUI.draft.isEdited = false; - ThreadUI.back(); - assert.isFalse(OptionMenu.calledOnce); - assert.isFalse(showCalled); + ThreadUI.back().then(function() { + assert.isFalse(OptionMenu.calledOnce); + assert.isFalse(showCalled); + }).then(done, done); }); - test('No prompt for replacement if content', function() { + test('No prompt for replacement if content', function(done) { ThreadUI.recipients.remove('999'); Compose.append('foo'); ThreadUI.draft.isEdited = false; - ThreadUI.back(); - assert.isFalse(OptionMenu.calledOnce); - assert.isFalse(showCalled); + ThreadUI.back().then(function() { + assert.isFalse(OptionMenu.calledOnce); + assert.isFalse(showCalled); + }).then(done, done); }); }); }); }); }); + suite('Close button behaviour', function() { + test('Call ActivityHandler.leaveActivity', function(done) { + this.sinon.stub(ActivityHandler, 'leaveActivity'); + ThreadUI.close().then(function() { + sinon.assert.called(ActivityHandler.leaveActivity); + }).then(done, done); + }); + }); + suite('New Message banner', function() { var notice;