diff --git a/apps/sms/js/message_manager.js b/apps/sms/js/message_manager.js index c34650921d5e..d89a39cc5886 100644 --- a/apps/sms/js/message_manager.js +++ b/apps/sms/js/message_manager.js @@ -48,21 +48,14 @@ var MessageManager = { var message = e.message; var threadId = message.threadId; - if (Threads.has(threadId)) { - Threads.get(message.threadId).messages.push(message); - } + Threads.registerMessage(message); - if (window.location.hash === '#new') { - // If we are in 'new' we go to right to thread view + if (threadId === Threads.currentId) { + ThreadUI.onMessageSending(message); + } else { window.location.hash = '#thread=' + threadId; - } else if (threadId === Threads.currentId) { - ThreadUI.appendMessage(message); - ThreadUI.forceScrollViewToBottom(); } - - MessageManager.getThreads(function() { - ThreadListUI.updateThread(message); - }); + ThreadListUI.onMessageSending(message); }, onMessageFailed: function mm_onMessageFailed(e) { @@ -79,7 +72,6 @@ var MessageManager = { onMessageReceived: function mm_onMessageReceived(e) { var message = e.message; - var threadId; if (message.messageClass && message.messageClass === 'class-0') { return; @@ -93,14 +85,10 @@ var MessageManager = { return; } - threadId = message.threadId; - - if (Threads.has(threadId)) { - Threads.get(threadId).messages.push(message); - } + Threads.registerMessage(message); - if (threadId === Threads.currentId) { - //Append message and mark as read + if (message.threadId === Threads.currentId) { + // Mark as read in Gecko this.markMessagesRead([message.id], function() { ThreadListUI.updateThread(message); }); diff --git a/apps/sms/js/thread_list_ui.js b/apps/sms/js/thread_list_ui.js index 1340466723f9..da6906b441b2 100644 --- a/apps/sms/js/thread_list_ui.js +++ b/apps/sms/js/thread_list_ui.js @@ -408,60 +408,38 @@ var ThreadListUI = { } }, - // This method fills the gap while we wait for next 'getThreads' request, - // letting us rendering the new thread with a better performance. - createThreadMockup: function thlui_createThreadMockup(message, options) { - // Given a message we create a thread as a mockup. This let us render the - // thread without requesting Gecko, so we increase the performance and we - // reduce Gecko requests. - return { - id: message.threadId, - participants: [message.sender || message.receiver], - body: message.body, - timestamp: message.timestamp, - unreadCount: (options && !options.read) ? 1 : 0, - lastMessageType: message.type || 'sms' - }; - }, - updateThread: function thlui_updateThread(message, options) { - var thread = this.createThreadMockup(message, options); + var thread = Threads.createThreadMockup(message, options); // We remove the previous one in order to place the new one properly var existingThreadElement = document.getElementById('thread-' + thread.id); - if (existingThreadElement) { - this.removeThread(thread.id); - } - ThreadListUI.appendThread(thread); - ThreadListUI.setEmpty(false); - FixedHeader.refresh(); - }, - onMessageReceived: function thlui_onMessageReceived(message) { - var threadMockup = this.createThreadMockup(message); - var threadId = message.threadId; - - if (!Threads.get(threadId)) { - Threads.set(threadId, threadMockup); - Threads.get(threadId).messages.push(message); - } - - if (this.container.querySelector('ul')) { - var timestamp = threadMockup.timestamp.getTime(); - var previousThread = document.getElementById('thread-' + threadId); - if (previousThread && previousThread.dataset.time > timestamp) { - // If the received SMS it's older that the latest one - // We need only to update the 'unread status' - this.mark(threadId, 'unread'); - return; + // New message is older than the latest one? + var timestamp = message.timestamp.getTime(); + if (existingThreadElement && + existingThreadElement.dataset.time > timestamp) { + // If the received SMS it's older that the latest one + // We need only to update the 'unread status' if needed + if (options && !options.read) { + this.mark(thread.id, 'unread'); } - - this.updateThread(message, {read: false}); - this.setEmpty(false); } else { - this.renderThreads([threadMockup]); + if (existingThreadElement) { + this.removeThread(thread.id); + } + this.appendThread(thread); + this.setEmpty(false); + FixedHeader.refresh(); } }, + onMessageSending: function thlui_onMessageSending(message) { + this.updateThread(message); + }, + + onMessageReceived: function thlui_onMessageReceived(message) { + this.updateThread(message, {read: false}); + }, + appendThread: function thlui_appendThread(thread) { var timestamp = thread.timestamp.getTime(); // We create the DOM element of the thread diff --git a/apps/sms/js/thread_ui.js b/apps/sms/js/thread_ui.js index 62c888b159c0..9e8caecb9f64 100644 --- a/apps/sms/js/thread_ui.js +++ b/apps/sms/js/thread_ui.js @@ -479,15 +479,25 @@ var ThreadUI = global.ThreadUI = { } }, - onMessageReceived: function thui_onMessageReceived(message) { + // Function for handling when a new message (sent/received) + // is detected + onMessage: function onMessage(message) { this.appendMessage(message); - this.scrollViewToBottom(); TimeHeaders.updateAll(); + }, + + onMessageReceived: function thui_onMessageReceived(message) { + this.onMessage(message); if (this.isScrolledManually) { this.showNewMessageNotice(message); } }, + onMessageSending: function thui_onMessageReceived(message) { + this.onMessage(message); + this.forceScrollViewToBottom(); + }, + onNewMessageNoticeClick: function thui_onNewMessageNoticeClick(event) { event.preventDefault(); this.hideNewMessageNotice(); diff --git a/apps/sms/js/threads.js b/apps/sms/js/threads.js index 6d860da7f3f5..78deb4f8e67d 100644 --- a/apps/sms/js/threads.js +++ b/apps/sms/js/threads.js @@ -24,6 +24,30 @@ } var Threads = exports.Threads = { + // TODO: https://bugzilla.mozilla.org/show_bug.cgi?id=943778 + // This method fills the gap while we wait for next 'getThreads' request, + // letting us rendering the new thread with a better performance. + createThreadMockup: function(message, options) { + // Given a message we create a thread as a mockup. This let us render the + // thread without requesting Gecko, so we increase the performance and we + // reduce Gecko requests. + return { + id: message.threadId, + participants: [message.sender || message.receiver], + body: message.body, + timestamp: message.timestamp, + unreadCount: (options && !options.read) ? 1 : 0, + lastMessageType: message.type || 'sms' + }; + }, + registerMessage: function(message) { + var threadMockup = this.createThreadMockup(message); + var threadId = message.threadId; + if (!this.has(threadId)) { + this.set(threadId, threadMockup); + } + this.get(threadId).messages.push(message); + }, set: function(id, thread) { var old; id = +id; diff --git a/apps/sms/test/unit/message_manager_test.js b/apps/sms/test/unit/message_manager_test.js index 4e30f45e37ba..834527d4fb69 100644 --- a/apps/sms/test/unit/message_manager_test.js +++ b/apps/sms/test/unit/message_manager_test.js @@ -61,7 +61,8 @@ suite('message_manager.js >', function() { suite('on message sent > ', function() { setup(function() { - this.sinon.spy(ThreadUI, 'appendMessage'); + this.sinon.spy(ThreadUI, 'onMessageSending'); + this.sinon.stub(Threads, 'registerMessage'); }); test('message is shown in the current thread if it belongs to the thread', @@ -70,7 +71,7 @@ suite('message_manager.js >', function() { // ensure the threadId is different Threads.currentId = sms.threadId + 1; MessageManager.onMessageSending({ message: sms }); - assert.isFalse(ThreadUI.appendMessage.called); + assert.isFalse(ThreadUI.onMessageSending.calledOnce); } ); }); diff --git a/apps/sms/test/unit/mock_message_manager.js b/apps/sms/test/unit/mock_message_manager.js index 145df419400c..22a283d52971 100644 --- a/apps/sms/test/unit/mock_message_manager.js +++ b/apps/sms/test/unit/mock_message_manager.js @@ -12,6 +12,7 @@ var MockMessageManager = { launchComposer: function() {}, handleActivity: function() {}, handleForward: function() {}, + registerMessage: function() {}, sendSMS: function() { return {}; }, diff --git a/apps/sms/test/unit/mock_thread_list_ui.js b/apps/sms/test/unit/mock_thread_list_ui.js index 0d3a07773cea..d8fb0b1ac70d 100644 --- a/apps/sms/test/unit/mock_thread_list_ui.js +++ b/apps/sms/test/unit/mock_thread_list_ui.js @@ -22,7 +22,7 @@ var MockThreadListUI = { renderThreads: function() {}, createThread: function() {}, insertThreadContainer: function() {}, - createThreadMockup: function() {}, + onMessageSending: function() {}, onMessageReceived: function() {}, appendThread: function() {}, createThreadContainer: function() {}, diff --git a/apps/sms/test/unit/mock_thread_ui.js b/apps/sms/test/unit/mock_thread_ui.js index 09c40a953e60..72a9b418ceef 100644 --- a/apps/sms/test/unit/mock_thread_ui.js +++ b/apps/sms/test/unit/mock_thread_ui.js @@ -67,6 +67,7 @@ var MockThreadUI = { handleEvent: function() {}, cleanFields: function() {}, onSendClick: function() {}, + onMessageSending: function() {}, onMessageSent: function() {}, onMessageFailed: function() {}, onDeliverySuccess: function() {}, diff --git a/apps/sms/test/unit/mock_threads.js b/apps/sms/test/unit/mock_threads.js index f6b4e40f2726..9094d96ea017 100644 --- a/apps/sms/test/unit/mock_threads.js +++ b/apps/sms/test/unit/mock_threads.js @@ -4,12 +4,23 @@ var MockThreads = { currentId: null, + createThreadMockup: function() { + }, + registerMessage: function() { + + }, mTeardown: function mt_mTeardown() { this.currentId = null; }, has: function() { return false; + }, + set: function() { + + }, + get: function() { + return {}; } }; diff --git a/apps/sms/test/unit/notify_test.js b/apps/sms/test/unit/notify_test.js index c1072e8c076a..f0090948b792 100644 --- a/apps/sms/test/unit/notify_test.js +++ b/apps/sms/test/unit/notify_test.js @@ -80,6 +80,17 @@ suite('check the ringtone and vibrate function', function() { Notify.vibrate(); assert.ok(Audio.prototype.play.called); + + // As document is not shown in the test, we launch the + // event of visibility + var visibilityEvent = new CustomEvent( + 'visibilitychange', + { + bubbles: true + } + ); + + window.dispatchEvent(visibilityEvent); assert.ok(navigator.vibrate.called); assert.ok(Audio.called); @@ -127,6 +138,17 @@ suite('check the ringtone and vibrate function', function() { Notify.ringtone(); Notify.vibrate(); + // As document is not shown in the test, we launch the + // event of visibility + var visibilityEvent = new CustomEvent( + 'visibilitychange', + { + bubbles: true + } + ); + + window.dispatchEvent(visibilityEvent); + assert.ok(!Audio.prototype.play.called); assert.ok(navigator.vibrate.called); diff --git a/apps/sms/test/unit/thread_list_ui_test.js b/apps/sms/test/unit/thread_list_ui_test.js index 8c2c7cdafafd..da49665fec8d 100644 --- a/apps/sms/test/unit/thread_list_ui_test.js +++ b/apps/sms/test/unit/thread_list_ui_test.js @@ -205,84 +205,175 @@ suite('thread_list_ui', function() { }); }); - suite('createThreadMockup', function() { - var message; - + suite('updateThread', function() { setup(function() { - // Create a message with read status 'true' - message = MockMessages.sms(); + this.sinon.spy(Threads, 'createThreadMockup'); + this.sinon.spy(ThreadListUI, 'removeThread'); + this.sinon.spy(ThreadListUI, 'appendThread'); + this.sinon.spy(FixedHeader, 'refresh'); + this.sinon.spy(ThreadListUI, 'mark'); + this.sinon.spy(ThreadListUI, 'setEmpty'); }); - test(' > createThreadMockup with unread status in options', function() { - var options = { read: false }; - var thread = ThreadListUI.createThreadMockup(message, options); - - assert.equal(thread.unreadCount, 1); + teardown(function() { + Threads.clear(); + ThreadListUI.container.innerHTML = ''; }); - test(' > createThreadMockup without options', function() { - var thread = ThreadListUI.createThreadMockup(message); - assert.equal(thread.unreadCount, 0); - }); + suite(' > in empty welcome screen,', function() { + var message; + setup(function() { + message = MockMessages.sms(); + ThreadListUI.updateThread(message); + }); - test(' > createThreadMockup with read status in options', function() { - var options = { read: true }; - var thread = ThreadListUI.createThreadMockup(message, options); - assert.equal(thread.unreadCount, 0); + test('setEmpty & appended', function() { + sinon.assert.calledOnce(ThreadListUI.setEmpty); + // first call, first argument, first item + + sinon.assert.calledWithMatch(ThreadListUI.appendThread, { + id: message.threadId, + body: message.body + }); + }); }); + suite(' > Method ', function() { + var message; + setup(function() { + var someDate = new Date(2013, 1, 1); + insertMockMarkup(someDate); + // A new message of a previous thread + var nextDate = new Date(2013, 1, 2); + message = MockMessages.sms({ + threadId: 2, + timestamp: nextDate + }); - }); + ThreadListUI.updateThread(message); + }); + test(' > createThreadMockup is called', function() { + sinon.assert.calledOnce(Threads.createThreadMockup); + }); - suite('updateThread', function() { - var message; - setup(function() { - var someDate = new Date(2013, 1, 1); - insertMockMarkup(someDate); - // A new message of a previous thread - var nextDate = new Date(2013, 1, 2); - message = MockMessages.sms({ - threadId: 2, - timestamp: nextDate + test(' > removeThread is called', function() { + sinon.assert.calledOnce(ThreadListUI.removeThread); + sinon.assert.calledOnce(ThreadListUI.appendThread); }); - this.sinon.spy(ThreadListUI, 'createThreadMockup'); - this.sinon.spy(ThreadListUI, 'removeThread'); - this.sinon.spy(ThreadListUI, 'appendThread'); - this.sinon.spy(FixedHeader, 'refresh'); + test(' > new message, new thread.', function() { + var newDate = new Date(2013, 1, 2); + var newMessage = MockMessages.sms({ + threadId: 20, + timestamp: newDate + }); + ThreadListUI.updateThread(newMessage, {read: false}); + // As this is a new message we dont have to remove threads + // So we have only one removeThread for the first appending + sinon.assert.calledOnce(ThreadListUI.removeThread); + // But we have appended twice + sinon.assert.calledTwice(ThreadListUI.appendThread); + }); - ThreadListUI.updateThread(message, true); + test('refresh the fixed header', function() { + sinon.assert.called(FixedHeader.refresh); + }); }); - test(' > createThreadMockup is called', function() { - assert.ok(ThreadListUI.createThreadMockup.called); - }); + suite(' > same thread exist, older', function() { + var message, thread; + setup(function() { + var someDate = new Date(2013, 1, 1); + insertMockMarkup(someDate); - test(' > removeThread is called', function() { - assert.ok(ThreadListUI.removeThread.called); - assert.ok(ThreadListUI.appendThread.called); - }); + var nextDate = new Date(2013, 1, 2); + message = MockMessages.sms({ + threadId: 2, + timestamp: nextDate + }); + thread = Threads.createThreadMockup(message); + ThreadListUI.updateThread(message); + }); + + teardown(function() { + message = null; + thread = null; + }); - test(' > new message, new thread.', function() { - var newDate = new Date(2013, 1, 2); - var newMessage = MockMessages.sms({ - threadId: 20, - timestamp: newDate - }); - ThreadListUI.updateThread(newMessage, true); - // As this is a new message we dont have to remove threads - assert.ok(ThreadListUI.removeThread.calledOnce); - // But we have appended twice - assert.ok(ThreadListUI.appendThread.calledTwice); + test('new thread is appended/updated', function() { + sinon.assert.calledOnce(ThreadListUI.appendThread); + // first call, first argument + sinon.assert.calledWith(ThreadListUI.appendThread, thread); + }); + + test('old thread is removed', function() { + sinon.assert.calledOnce(ThreadListUI.removeThread); + sinon.assert.calledWith(ThreadListUI.removeThread, message.threadId); + }); }); - test('refresh the fixed header', function() { - assert.ok(FixedHeader.refresh.called); + suite(' > other threads exist', function() { + var message, thread; + setup(function() { + var someDate = new Date(2013, 1, 1); + insertMockMarkup(someDate); + + var nextDate = new Date(2013, 1, 2); + message = MockMessages.sms({ + threadId: 3, + timestamp: nextDate + }); + thread = Threads.createThreadMockup(message); + ThreadListUI.updateThread(message); + }); + + teardown(function() { + message = null; + thread = null; + }); + + test('new thread is appended', function() { + sinon.assert.calledOnce(ThreadListUI.appendThread); + // first call, first argument + sinon.assert.calledWith(ThreadListUI.appendThread, thread); + }); + + test('no thread is removed', function() { + assert.isFalse(ThreadListUI.removeThread.called); + }); + + test('refresh the fixed header', function() { + sinon.assert.called(FixedHeader.refresh); + }); }); + suite(' > same thread exist, but newer', function() { + var message; - teardown(function() { - ThreadListUI.container.innerHTML = ''; + setup(function() { + var someDate = new Date(2013, 1, 1); + insertMockMarkup(someDate); + + var prevDate = new Date(2013, 1, 0); + message = MockMessages.sms({ + threadId: 2, + timestamp: prevDate + }); + ThreadListUI.updateThread(message, {read: false}); + }); + + test('no new thread is appended', function() { + assert.isFalse(ThreadListUI.appendThread.called); + }); + + test('no old thread is removed', function() { + assert.isFalse(ThreadListUI.removeThread.called); + }); + + test('old thread is marked unread', function() { + sinon.assert.called(ThreadListUI.mark); + sinon.assert.calledWith(ThreadListUI.mark, message.threadId, 'unread'); + }); }); }); @@ -414,127 +505,21 @@ suite('thread_list_ui', function() { }); suite('onMessageReceived', function() { + var updateThreadSpy; setup(function() { - this.sinon.stub(ThreadListUI, 'removeThread'); - this.sinon.stub(ThreadListUI, 'appendThread'); - this.sinon.stub(ThreadListUI, 'renderThreads'); - this.sinon.stub(ThreadListUI, 'mark'); - this.sinon.stub(ThreadListUI, 'setEmpty'); - this.sinon.spy(FixedHeader, 'refresh'); + updateThreadSpy = this.sinon.spy(ThreadListUI, 'updateThread'); + var message = MockMessages.sms(); + ThreadListUI.onMessageReceived(message); }); teardown(function() { - Threads.clear(); - }); - - suite('in empty welcome screen,', function() { - var message; - setup(function() { - message = MockMessages.sms(); - ThreadListUI.onMessageReceived(message); - }); - - test('render the whole list', function() { - assert.ok(ThreadListUI.renderThreads.called); - // first call, first argument, first item - var thread = ThreadListUI.renderThreads.args[0][0][0]; - assert.equal(thread.id, message.threadId); - assert.equal(thread.body, message.body); - }); - }); - - suite('other threads exist', function() { - var message; - setup(function() { - var someDate = new Date(2013, 1, 1); - insertMockMarkup(someDate); - - var nextDate = new Date(2013, 1, 2); - message = MockMessages.sms({ - threadId: 3, - timestamp: nextDate - }); - ThreadListUI.onMessageReceived(message); - }); - - test('new thread is appended', function() { - assert.ok(ThreadListUI.appendThread.called); - // first call, first argument - var thread = ThreadListUI.appendThread.args[0][0]; - assert.equal(thread.id, message.threadId); - assert.equal(thread.body, message.body); - }); - - test('no thread is removed', function() { - assert.isFalse(ThreadListUI.removeThread.called); - }); - - test('refresh the fixed header', function() { - assert.ok(FixedHeader.refresh.called); - }); + updateThreadSpy = null; }); - suite('same thread exist, older', function() { - var message; - - setup(function() { - this.sinon.spy(ThreadListUI, 'updateThread'); - var someDate = new Date(2013, 1, 1); - insertMockMarkup(someDate); - - var nextDate = new Date(2013, 1, 2); - message = MockMessages.sms({ - threadId: 2, - timestamp: nextDate - }); - ThreadListUI.onMessageReceived(message); - }); - - test('new thread is appended/updated', function() { - assert.ok(ThreadListUI.updateThread.called); - // first call, first argument - var messageArg = ThreadListUI.updateThread.args[0][0]; - assert.equal(messageArg.threadId, message.threadId); - assert.equal(messageArg.body, message.body); - }); - - test('old thread is removed', function() { - assert.ok(ThreadListUI.removeThread.called); - var threadId = ThreadListUI.removeThread.args[0][0]; - assert.equal(threadId, message.threadId); - }); + test(' updateThread is called when a new message is received', function() { + assert.ok(updateThreadSpy.called); }); - suite('same thread exist, newer', function() { - var message; - - setup(function() { - var someDate = new Date(2013, 1, 1); - insertMockMarkup(someDate); - - var prevDate = new Date(2013, 1, 0); - message = MockMessages.sms({ - threadId: 2, - timestamp: prevDate - }); - ThreadListUI.onMessageReceived(message); - }); - - test('no new thread is appended', function() { - assert.isFalse(ThreadListUI.appendThread.called); - }); - - test('no old thread is removed', function() { - assert.isFalse(ThreadListUI.removeThread.called); - }); - - test('old thread is marked unread', function() { - assert.ok(ThreadListUI.mark.called); - var args = ThreadListUI.mark.args[0]; - assert.equal(args[0], message.threadId); - assert.equal(args[1], 'unread'); - }); - }); }); suite('appendThread', function() { @@ -556,7 +541,7 @@ suite('thread_list_ui', function() { timestamp: nextDate }); - thread = ThreadListUI.createThreadMockup(message); + thread = Threads.createThreadMockup(message); ThreadListUI.appendThread(thread); }); @@ -583,7 +568,7 @@ suite('thread_list_ui', function() { timestamp: nextDate }); - thread = ThreadListUI.createThreadMockup(message); + thread = Threads.createThreadMockup(message); ThreadListUI.appendThread(thread); }); diff --git a/apps/sms/test/unit/threads_test.js b/apps/sms/test/unit/threads_test.js index c58928f15b16..6aba66ddc4d9 100644 --- a/apps/sms/test/unit/threads_test.js +++ b/apps/sms/test/unit/threads_test.js @@ -1,7 +1,8 @@ -/*global Threads */ +/*global Threads, MockMessages */ 'use strict'; +requireApp('sms/test/unit/mock_messages.js'); requireApp('sms/js/threads.js'); suite('Threads', function() { @@ -13,6 +14,35 @@ suite('Threads', function() { Threads.clear(); }); + suite('createThreadMockup', function() { + var message; + + setup(function() { + // Create a message with read status 'true' + message = MockMessages.sms(); + }); + + test(' > createThreadMockup with unread status in options', function() { + var options = { read: false }; + var thread = Threads.createThreadMockup(message, options); + + assert.equal(thread.unreadCount, 1); + }); + + test(' > createThreadMockup without options', function() { + var thread = Threads.createThreadMockup(message); + assert.equal(thread.unreadCount, 0); + }); + + test(' > createThreadMockup with read status in options', function() { + var options = { read: true }; + var thread = Threads.createThreadMockup(message, options); + assert.equal(thread.unreadCount, 0); + }); + + + }); + suite('Collection', function() { test('is like a Map', function() { assert.ok(Threads);