mirror of
				https://github.com/Foundry376/Mailspring.git
				synced 2025-11-01 00:46:25 +08:00 
			
		
		
		
	feat(unread-notifications): Check that unread messages are in inbox
Summary: When we get unread messages, grab their threads and then make sure they're in the inbox before notifying. Test Plan: See updated specs! Reviewers: evan Reviewed By: evan Differential Revision: https://review.inboxapp.com/D1361
This commit is contained in:
		
							parent
							
								
									23e9d8ccf5
								
							
						
					
					
						commit
						d0d96f90f5
					
				
					 4 changed files with 123 additions and 39 deletions
				
			
		|  | @ -31,6 +31,7 @@ | ||||||
|     input.filter { |     input.filter { | ||||||
|       margin-left: 4px; |       margin-left: 4px; | ||||||
|       padding: 2px; |       padding: 2px; | ||||||
|  |       color:black; | ||||||
|       vertical-align: middle; |       vertical-align: middle; | ||||||
|       width: 400px; |       width: 400px; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| _ = require 'underscore-plus' | _ = require 'underscore-plus' | ||||||
| {Actions} = require 'inbox-exports' | {Actions, DatabaseStore, Thread} = require 'inbox-exports' | ||||||
| 
 | 
 | ||||||
| module.exports = | module.exports = | ||||||
|   activate: -> |   activate: -> | ||||||
|  | @ -12,13 +12,37 @@ module.exports = | ||||||
| 
 | 
 | ||||||
|   serialize: -> |   serialize: -> | ||||||
| 
 | 
 | ||||||
|   _onNewMailReceived: (models) -> |   _onNewMailReceived: (incoming) -> | ||||||
|     # Display a notification if we've received new messages |     new Promise (resolve, reject) => | ||||||
|     newUnreadMessages = _.filter (models['message'] ? []), (msg) => |       incomingMessages = incoming['message'] ? [] | ||||||
|  |       incomingThreads = incoming['thread'] ? [] | ||||||
|  | 
 | ||||||
|  |       # Filter for new messages | ||||||
|  |       newUnread = _.filter incomingMessages, (msg) => | ||||||
|         msg.unread is true and msg.date?.valueOf() >= @activationTime |         msg.unread is true and msg.date?.valueOf() >= @activationTime | ||||||
| 
 | 
 | ||||||
|     if newUnreadMessages.length is 1 |       return resolve() if newUnread.length is 0 | ||||||
|       msg = newUnreadMessages.pop() | 
 | ||||||
|  |       # For each message, find it's corresponding thread. First, look to see | ||||||
|  |       # if it's already in the `incoming` payload (sent via delta sync | ||||||
|  |       # at the same time as the message.) If it's not, try loading it from | ||||||
|  |       # the local cache. | ||||||
|  |       # | ||||||
|  |       # Note we may receive multiple unread msgs for the same thread. | ||||||
|  |       # Using a map and ?= to avoid repeating work. | ||||||
|  |       threads = {} | ||||||
|  |       for msg in newUnread | ||||||
|  |         threads[msg.threadId] ?= _.findWhere(incomingThreads, {id: msg.threadId}) | ||||||
|  |         threads[msg.threadId] ?= DatabaseStore.find(Thread, msg.threadId) | ||||||
|  | 
 | ||||||
|  |       Promise.props(threads).then (threads) -> | ||||||
|  | 
 | ||||||
|  |         # Filter new unread messages to just the ones in the inbox | ||||||
|  |         newUnreadInInbox = _.filter newUnread, (msg) -> | ||||||
|  |           threads[msg.threadId]?.hasTagId('inbox') | ||||||
|  | 
 | ||||||
|  |         if newUnreadInInbox.length is 1 | ||||||
|  |           msg = newUnreadInInbox.pop() | ||||||
|           notif = new Notification(msg.from[0].displayName(), { |           notif = new Notification(msg.from[0].displayName(), { | ||||||
|             body: msg.subject |             body: msg.subject | ||||||
|             tag: 'unread-update' |             tag: 'unread-update' | ||||||
|  | @ -28,10 +52,12 @@ module.exports = | ||||||
|             Actions.selectTagId("inbox") |             Actions.selectTagId("inbox") | ||||||
|             Actions.selectThreadId(msg.threadId) |             Actions.selectThreadId(msg.threadId) | ||||||
| 
 | 
 | ||||||
|     if newUnreadMessages.length > 1 |         if newUnreadInInbox.length > 1 | ||||||
|       new Notification("#{newUnreadMessages.length} Unread Messages", { |           new Notification("#{newUnreadInInbox.length} Unread Messages", { | ||||||
|             tag: 'unread-update' |             tag: 'unread-update' | ||||||
|           }) |           }) | ||||||
| 
 | 
 | ||||||
|     if newUnreadMessages.length > 0 |         if newUnreadInInbox.length > 0 | ||||||
|           atom.playSound('new_mail.ogg') |           atom.playSound('new_mail.ogg') | ||||||
|  | 
 | ||||||
|  |         resolve() | ||||||
|  |  | ||||||
|  | @ -1,57 +1,111 @@ | ||||||
| _ = require 'underscore-plus' | _ = require 'underscore-plus' | ||||||
|  | Promise = require 'bluebird' | ||||||
| Contact = require '../../../src/flux/models/contact' | Contact = require '../../../src/flux/models/contact' | ||||||
| Message = require '../../../src/flux/models/message' | Message = require '../../../src/flux/models/message' | ||||||
|  | Thread = require '../../../src/flux/models/thread' | ||||||
|  | Tag = require '../../../src/flux/models/tag' | ||||||
|  | DatabaseStore = require '../../../src/flux/stores/database-store' | ||||||
| Main = require '../lib/main' | Main = require '../lib/main' | ||||||
| 
 | 
 | ||||||
| describe "UnreadNotifications", -> | describe "UnreadNotifications", -> | ||||||
|   beforeEach -> |   beforeEach -> | ||||||
|     Main.activate() |     Main.activate() | ||||||
|     spyOn(window, 'Notification').andCallFake -> | 
 | ||||||
|  |     @threadA = new Thread | ||||||
|  |       tags: [new Tag(id: 'inbox')] | ||||||
|  |     @threadB = new Thread | ||||||
|  |       tags: [new Tag(id: 'archive')] | ||||||
|  | 
 | ||||||
|     @msg1 = new Message |     @msg1 = new Message | ||||||
|       unread: true |       unread: true | ||||||
|       date: new Date() |       date: new Date() | ||||||
|       from: [new Contact(name: 'Ben', email: 'ben@example.com')] |       from: [new Contact(name: 'Ben', email: 'ben@example.com')] | ||||||
|       subject: "Hello World" |       subject: "Hello World" | ||||||
|  |       threadId: "A" | ||||||
|     @msg2 = new Message |     @msg2 = new Message | ||||||
|       unread: true |       unread: true | ||||||
|       date: new Date() |       date: new Date() | ||||||
|       from: [new Contact(name: 'Mark', email: 'mark@example.com')] |       from: [new Contact(name: 'Mark', email: 'mark@example.com')] | ||||||
|       subject: "Hello World 2" |       subject: "Hello World 2" | ||||||
|  |       threadId: "A" | ||||||
|  |     @msgUnreadButArchived = new Message | ||||||
|  |       unread: true | ||||||
|  |       date: new Date() | ||||||
|  |       from: [new Contact(name: 'Mark', email: 'mark@example.com')] | ||||||
|  |       subject: "Hello World 2" | ||||||
|  |       threadId: "B" | ||||||
|     @msgRead = new Message |     @msgRead = new Message | ||||||
|       unread: false |       unread: false | ||||||
|       date: new Date() |       date: new Date() | ||||||
|       from: [new Contact(name: 'Mark', email: 'mark@example.com')] |       from: [new Contact(name: 'Mark', email: 'mark@example.com')] | ||||||
|       subject: "Hello World Read Already" |       subject: "Hello World Read Already" | ||||||
|  |       threadId: "A" | ||||||
|     @msgOld = new Message |     @msgOld = new Message | ||||||
|       unread: true |       unread: true | ||||||
|       date: new Date(2000,1,1) |       date: new Date(2000,1,1) | ||||||
|       from: [new Contact(name: 'Mark', email: 'mark@example.com')] |       from: [new Contact(name: 'Mark', email: 'mark@example.com')] | ||||||
|       subject: "Hello World Old" |       subject: "Hello World Old" | ||||||
|  |       threadId: "A" | ||||||
|  | 
 | ||||||
|  |     spyOn(DatabaseStore, 'find').andCallFake (klass, id) => | ||||||
|  |       return Promise.resolve(@threadA) if id is 'A' | ||||||
|  |       return Promise.resolve(@threadB) if id is 'B' | ||||||
|  |       return Promise.resolve(null) | ||||||
|  | 
 | ||||||
|  |     spyOn(window, 'Notification').andCallFake -> | ||||||
|  |     spyOn(Promise, 'props').andCallFake (dict) -> | ||||||
|  |       dictOut = {} | ||||||
|  |       for key, val of dict | ||||||
|  |         if val.value? | ||||||
|  |           dictOut[key] = val.value() | ||||||
|  |         else | ||||||
|  |           dictOut[key] = val | ||||||
|  |       Promise.resolve(dictOut) | ||||||
| 
 | 
 | ||||||
|   afterEach -> |   afterEach -> | ||||||
|     Main.deactivate() |     Main.deactivate() | ||||||
| 
 | 
 | ||||||
|   it "should create a Notification if there is one unread message", -> |   it "should create a Notification if there is one unread message", -> | ||||||
|  |     waitsForPromise => | ||||||
|       Main._onNewMailReceived({message: [@msgRead, @msg1]}) |       Main._onNewMailReceived({message: [@msgRead, @msg1]}) | ||||||
|  |       .then -> | ||||||
|         expect(window.Notification).toHaveBeenCalled() |         expect(window.Notification).toHaveBeenCalled() | ||||||
|         expect(window.Notification.mostRecentCall.args).toEqual([ 'Ben', { body : 'Hello World', tag : 'unread-update' } ]) |         expect(window.Notification.mostRecentCall.args).toEqual([ 'Ben', { body : 'Hello World', tag : 'unread-update' } ]) | ||||||
| 
 | 
 | ||||||
|   it "should create a Notification if there is more than one unread message", -> |   it "should create a Notification if there is more than one unread message", -> | ||||||
|  |     waitsForPromise => | ||||||
|       Main._onNewMailReceived({message: [@msg1, @msg2, @msgRead]}) |       Main._onNewMailReceived({message: [@msg1, @msg2, @msgRead]}) | ||||||
|  |       .then -> | ||||||
|         expect(window.Notification).toHaveBeenCalled() |         expect(window.Notification).toHaveBeenCalled() | ||||||
|         expect(window.Notification.mostRecentCall.args).toEqual([ '2 Unread Messages', { tag : 'unread-update' } ]) |         expect(window.Notification.mostRecentCall.args).toEqual([ '2 Unread Messages', { tag : 'unread-update' } ]) | ||||||
| 
 | 
 | ||||||
|   it "should not create a Notification if there are no new messages", -> |   it "should not create a Notification if there are no new messages", -> | ||||||
|  |     waitsForPromise -> | ||||||
|       Main._onNewMailReceived({message: []}) |       Main._onNewMailReceived({message: []}) | ||||||
|     expect(window.Notification).not.toHaveBeenCalled() |       .then -> | ||||||
|     Main._onNewMailReceived({}) |  | ||||||
|         expect(window.Notification).not.toHaveBeenCalled() |         expect(window.Notification).not.toHaveBeenCalled() | ||||||
| 
 | 
 | ||||||
|  |     waitsForPromise -> | ||||||
|  |       Main._onNewMailReceived({}) | ||||||
|  |       .then -> | ||||||
|  |         expect(window.Notification).not.toHaveBeenCalled() | ||||||
|  | 
 | ||||||
|  |   it "should not notify about unread messages that are outside the inbox", -> | ||||||
|  |     waitsForPromise => | ||||||
|  |       Main._onNewMailReceived({message: [@msgUnreadButArchived, @msg1]}) | ||||||
|  |       .then -> | ||||||
|  |         expect(window.Notification).toHaveBeenCalled() | ||||||
|  |         expect(window.Notification.mostRecentCall.args).toEqual([ 'Ben', { body : 'Hello World', tag : 'unread-update' } ]) | ||||||
|  | 
 | ||||||
|   it "should not create a Notification if the new messages are not unread", -> |   it "should not create a Notification if the new messages are not unread", -> | ||||||
|  |     waitsForPromise => | ||||||
|       Main._onNewMailReceived({message: [@msgRead]}) |       Main._onNewMailReceived({message: [@msgRead]}) | ||||||
|  |       .then -> | ||||||
|         expect(window.Notification).not.toHaveBeenCalled() |         expect(window.Notification).not.toHaveBeenCalled() | ||||||
| 
 | 
 | ||||||
|   it "should not create a Notification if the new messages are actually old ones", -> |   it "should not create a Notification if the new messages are actually old ones", -> | ||||||
|  |     waitsForPromise => | ||||||
|       Main._onNewMailReceived({message: [@msgOld]}) |       Main._onNewMailReceived({message: [@msgOld]}) | ||||||
|  |       .then -> | ||||||
|         expect(window.Notification).not.toHaveBeenCalled() |         expect(window.Notification).not.toHaveBeenCalled() | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -146,14 +146,17 @@ class InboxAPI | ||||||
|     # each type of model in the `create` hash, waits for them all to resolve. |     # each type of model in the `create` hash, waits for them all to resolve. | ||||||
|     create[type] = @_handleModelResponse(items) for type, items of create |     create[type] = @_handleModelResponse(items) for type, items of create | ||||||
|     Promise.props(create).then (created) => |     Promise.props(create).then (created) => | ||||||
|       if _.flatten(_.values(created)).length > 0 |  | ||||||
|         Actions.didPassivelyReceiveNewModels(created) |  | ||||||
| 
 |  | ||||||
|       # Apply all the deltas to modify objects. Gets promises for handling |       # Apply all the deltas to modify objects. Gets promises for handling | ||||||
|       # each type of model in the `modify` hash, waits for them all to resolve. |       # each type of model in the `modify` hash, waits for them all to resolve. | ||||||
|       modify[type] = @_handleModelResponse(items) for type, items of modify |       modify[type] = @_handleModelResponse(items) for type, items of modify | ||||||
|       Promise.props(modify).then (modified) -> |       Promise.props(modify).then (modified) -> | ||||||
| 
 | 
 | ||||||
|  |         # Now that we've persisted creates/updates, fire an action | ||||||
|  |         # that allows other parts of the app to update based on new models | ||||||
|  |         # (notifications) | ||||||
|  |         if _.flatten(_.values(created)).length > 0 | ||||||
|  |           Actions.didPassivelyReceiveNewModels(created) | ||||||
|  | 
 | ||||||
|         # Apply all of the deletions |         # Apply all of the deletions | ||||||
|         destroyPromises = destroy.map (delta) -> |         destroyPromises = destroy.map (delta) -> | ||||||
|           console.log(" - 1 #{delta.object} (#{delta.id})") |           console.log(" - 1 #{delta.object} (#{delta.id})") | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue