From 85d76c472fa3cf448d90d06a16aca135456de5cc Mon Sep 17 00:00:00 2001 From: Ben Gotow Date: Sun, 24 Sep 2017 12:09:38 -0500 Subject: [PATCH] Rewrite unread notifications to remove hacky delta action --- .../unread-notifications/lib/main.es6 | 156 +++---- .../unread-notifications/specs/main-spec.es6 | 414 +++++++++--------- 2 files changed, 261 insertions(+), 309 deletions(-) diff --git a/app/internal_packages/unread-notifications/lib/main.es6 b/app/internal_packages/unread-notifications/lib/main.es6 index 8309c33f0..e42bca748 100644 --- a/app/internal_packages/unread-notifications/lib/main.es6 +++ b/app/internal_packages/unread-notifications/lib/main.es6 @@ -2,6 +2,7 @@ import _ from 'underscore' import { Thread, Actions, + Message, SoundRegistry, NativeNotifications, DatabaseStore, @@ -9,14 +10,14 @@ import { export class Notifier { constructor() { - this.unlisteners = []; - this.unlisteners.push(Actions.onNewMailDeltas.listen(this._onNewMailReceived, this)); this.activationTime = Date.now(); this.unnotifiedQueue = []; this.hasScheduledNotify = false; this.activeNotifications = {}; - this.unlisteners.push(DatabaseStore.listen(this._onDatabaseUpdated, this)); + this.unlisteners = [ + DatabaseStore.listen(this._onDatabaseChanged, this), + ]; } unlisten() { @@ -25,18 +26,37 @@ export class Notifier { } } - _onDatabaseUpdated({objectClass, objects}) { - if (objectClass === 'Thread') { - objects - .filter((thread) => !thread.unread) - .forEach((thread) => this._onThreadIsRead(thread)); + // async for testing + async _onDatabaseChanged({objectClass, objects}) { + if (objectClass === Thread.name) { + // Ensure notifications are dismissed when the user reads a thread + objects.forEach(({id, unread}) => { + if (!unread && this.activeNotifications[id]) { + this.activeNotifications[id].forEach((n) => n.close()); + delete this.activeNotifications[id]; + } + }); } - } - _onThreadIsRead({id: threadId}) { - if (threadId in this.activeNotifications) { - this.activeNotifications[threadId].forEach((n) => n.close()); - delete this.activeNotifications[threadId]; + if (objectClass === Message.name) { + if (NylasEnv.config.get('core.notifications.enabled') === false) { + return; + } + const newUnread = objects.filter((msg) => { + // ensure the message is unread + if (msg.unread !== true) return false; + // ensure the message was just saved (eg: this is not a modification) + if (msg.version !== 1) return false; + // ensure the message was received after the app launched (eg: not syncing an old email) + if (!msg.date || msg.date.valueOf() < this.activationTime) return false; + // ensure the message is from someone else + if (msg.isFromMe()) return false; + return true; + }); + + if (newUnread.length > 0) { + await this._onNewMessagesReceived(newUnread); + } } } @@ -104,90 +124,44 @@ export class Notifier { } } - // https://phab.nylas.com/D2188 - _onNewMessagesMissingThreads(messages) { - setTimeout(() => { - const threads = {} - for (const {threadId} of messages) { - threads[threadId] = threads[threadId] || DatabaseStore.find(Thread, threadId); - } - Promise.props(threads).then((resolvedThreads) => { - const resolved = messages.filter((msg) => resolvedThreads[msg.threadId]); - if (resolved.length > 0) { - this._onNewMailReceived({message: resolved, thread: Object.values(resolvedThreads)}); - } - }); - }, 10000); - } + _onNewMessagesReceived(newMessages) { + // 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. - _onNewMailReceived(incoming) { - return new Promise((resolve) => { - if (NylasEnv.config.get('core.notifications.enabled') === false) { - resolve(); + const threadIds = {}; + for (const {threadId} of newMessages) { + threadIds[threadId] = true; + } + + // TODO: Use xGMLabels + folder on message to identify which ones + // are in the inbox to avoid needing threads here. + return DatabaseStore.findAll(Thread, Thread.attributes.id.in(Object.keys(threadIds))).then((threadsArray) => { + const threads = {}; + for (const t of threadsArray) { + threads[t.id] = t; + } + + // Filter new messages to just the ones in the inbox + const newMessagesInInbox = newMessages.filter(({threadId}) => { + return threads[threadId] && threads[threadId].categories.find(c => c.role === 'inbox'); + }) + + if (newMessagesInInbox.length === 0) { return; } - const incomingMessages = incoming.message || []; - const incomingThreads = incoming.thread || []; - - // Filter for new messages that are not sent by the current user - const newUnread = incomingMessages.filter((msg) => { - const isUnread = msg.unread === true; - const isNew = msg.date && msg.date.valueOf() >= this.activationTime; - const isFromMe = msg.isFromMe(); - return isUnread && isNew && !isFromMe; - }); - - if (newUnread.length === 0) { - resolve(); - return; + for (const msg of newMessagesInInbox) { + this.unnotifiedQueue.push({message: msg, thread: threads[msg.threadId]}); } - - // 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. - const threads = {} - for (const {threadId} of newUnread) { - threads[threadId] = threads[threadId] || incomingThreads.find(t => t.id === threadId) - threads[threadId] = threads[threadId] || DatabaseStore.find(Thread, threadId); + if (!this.hasScheduledNotify) { + if (NylasEnv.config.get("core.notifications.sounds")) { + this._playNewMailSound = this._playNewMailSound || _.debounce(() => SoundRegistry.playSound('new-mail'), 5000, true); + this._playNewMailSound(); + } + this._notifyMessages(); } - - Promise.props(threads).then((resolvedThreads) => { - // Filter new unread messages to just the ones in the inbox - const newUnreadInInbox = newUnread.filter((msg) => - resolvedThreads[msg.threadId] && resolvedThreads[msg.threadId].categories.find(c => c.role === 'inbox') - ) - - // Filter messages that we can't decide whether to display or not - // since no associated Thread object has arrived yet. - const newUnreadMissingThreads = newUnread.filter((msg) => !resolvedThreads[msg.threadId]) - - if (newUnreadMissingThreads.length > 0) { - this._onNewMessagesMissingThreads(newUnreadMissingThreads); - } - - if (newUnreadInInbox.length === 0) { - resolve(); - return; - } - - for (const msg of newUnreadInInbox) { - this.unnotifiedQueue.push({message: msg, thread: resolvedThreads[msg.threadId]}); - } - if (!this.hasScheduledNotify) { - if (NylasEnv.config.get("core.notifications.sounds")) { - this._playNewMailSound = this._playNewMailSound || _.debounce(() => SoundRegistry.playSound('new-mail'), 5000, true); - this._playNewMailSound(); - } - this._notifyMessages(); - } - - resolve(); - }); }); } } diff --git a/app/internal_packages/unread-notifications/specs/main-spec.es6 b/app/internal_packages/unread-notifications/specs/main-spec.es6 index 38d64918b..e802b1cef 100644 --- a/app/internal_packages/unread-notifications/specs/main-spec.es6 +++ b/app/internal_packages/unread-notifications/specs/main-spec.es6 @@ -1,20 +1,23 @@ -import Contact from '../../../src/flux/models/contact' -import Message from '../../../src/flux/models/message' -import Thread from '../../../src/flux/models/thread' -import Folder from '../../../src/flux/models/category' -import CategoryStore from '../../../src/flux/stores/category-store' -import DatabaseStore from '../../../src/flux/stores/database-store' -import AccountStore from '../../../src/flux/stores/account-store' -import SoundRegistry from '../../../src/registries/sound-registry' -import NativeNotifications from '../../../src/native-notifications' +import { + Contact, + Message, + Thread, + Folder, + CategoryStore, + DatabaseStore, + AccountStore, + SoundRegistry, + NativeNotifications, +} from 'nylas-exports'; + import {Notifier} from '../lib/main' -xdescribe("UnreadNotifications", function UnreadNotifications() { +describe("UnreadNotifications", function UnreadNotifications() { beforeEach(() => { this.notifier = new Notifier(); - const inbox = new Folder({id: "l1", name: "inbox", displayName: "Inbox"}) - const archive = new Folder({id: "l2", name: "archive", displayName: "Archive"}) + const inbox = new Folder({id: "l1", role: "inbox", path: "Inbox"}) + const archive = new Folder({id: "l2", role: "archive", path: "Archive"}) spyOn(CategoryStore, "getCategoryByRole").andReturn(inbox); @@ -35,6 +38,7 @@ xdescribe("UnreadNotifications", function UnreadNotifications() { from: [new Contact({name: 'Ben', email: 'benthis.example.com'})], subject: "Hello World", threadId: "A", + version: 1, }); this.msgNoSender = new Message({ unread: true, @@ -42,6 +46,7 @@ xdescribe("UnreadNotifications", function UnreadNotifications() { from: [], subject: "Hello World", threadId: "A", + version: 1, }); this.msg2 = new Message({ unread: true, @@ -49,6 +54,7 @@ xdescribe("UnreadNotifications", function UnreadNotifications() { from: [new Contact({name: 'Mark', email: 'markthis.example.com'})], subject: "Hello World 2", threadId: "A", + version: 1, }); this.msg3 = new Message({ unread: true, @@ -56,6 +62,7 @@ xdescribe("UnreadNotifications", function UnreadNotifications() { from: [new Contact({name: 'Ben', email: 'benthis.example.com'})], subject: "Hello World 3", threadId: "A", + version: 1, }); this.msg4 = new Message({ unread: true, @@ -63,6 +70,7 @@ xdescribe("UnreadNotifications", function UnreadNotifications() { from: [new Contact({name: 'Ben', email: 'benthis.example.com'})], subject: "Hello World 4", threadId: "A", + version: 1, }); this.msg5 = new Message({ unread: true, @@ -70,6 +78,7 @@ xdescribe("UnreadNotifications", function UnreadNotifications() { from: [new Contact({name: 'Ben', email: 'benthis.example.com'})], subject: "Hello World 5", threadId: "A", + version: 1, }); this.msgUnreadButArchived = new Message({ unread: true, @@ -77,6 +86,7 @@ xdescribe("UnreadNotifications", function UnreadNotifications() { from: [new Contact({name: 'Mark', email: 'markthis.example.com'})], subject: "Hello World 2", threadId: "B", + version: 1, }); this.msgRead = new Message({ unread: false, @@ -84,6 +94,7 @@ xdescribe("UnreadNotifications", function UnreadNotifications() { from: [new Contact({name: 'Mark', email: 'markthis.example.com'})], subject: "Hello World Read Already", threadId: "A", + version: 1, }); this.msgOld = new Message({ unread: true, @@ -91,6 +102,7 @@ xdescribe("UnreadNotifications", function UnreadNotifications() { from: [new Contact({name: 'Mark', email: 'markthis.example.com'})], subject: "Hello World Old", threadId: "A", + version: 1, }); this.msgFromMe = new Message({ unread: true, @@ -98,7 +110,16 @@ xdescribe("UnreadNotifications", function UnreadNotifications() { from: [account.me()], subject: "A Sent Mail!", threadId: "A", + version: 1, }); + this.msgHigherVersion = new Message({ + unread: true, + date: new Date(), + from: [new Contact({name: 'Ben', email: 'benthis.example.com'})], + subject: "Hello World", + threadId: "A", + version: 2, + }) spyOn(DatabaseStore, 'find').andCallFake((klass, id) => { if (id === 'A') { @@ -110,6 +131,10 @@ xdescribe("UnreadNotifications", function UnreadNotifications() { return Promise.resolve(null); }); + spyOn(DatabaseStore, 'findAll').andCallFake(() => { + return Promise.resolve([this.threadA, this.threadB]); + }); + this.notification = jasmine.createSpyObj('notification', ['close']); spyOn(NativeNotifications, 'displayNotification').andReturn(this.notification); @@ -132,171 +157,183 @@ xdescribe("UnreadNotifications", function UnreadNotifications() { }) it("should create a Notification if there is one unread message", () => { - waitsForPromise(() => { - return this.notifier._onNewMailReceived({message: [this.msgRead, this.msg1]}) - .then(() => { - advanceClock(2000) - expect(NativeNotifications.displayNotification).toHaveBeenCalled() - const options = NativeNotifications.displayNotification.mostRecentCall.args[0] - delete options.onActivate; - expect(options).toEqual({ - title: 'Ben', - subtitle: 'Hello World', - body: undefined, - canReply: true, - tag: 'unread-update', - }); + waitsForPromise(async () => { + await this.notifier._onDatabaseChanged({ + objectClass: Message.name, + objects: [this.msgRead, this.msg1], + }) + advanceClock(2000) + expect(NativeNotifications.displayNotification).toHaveBeenCalled() + const options = NativeNotifications.displayNotification.mostRecentCall.args[0] + delete options.onActivate; + expect(options).toEqual({ + title: 'Ben', + subtitle: 'Hello World', + body: undefined, + canReply: true, + tag: 'unread-update', }); }); }); it("should create multiple Notifications if there is more than one but less than five unread messages", () => { - waitsForPromise(() => { - return this.notifier._onNewMailReceived({message: [this.msg1, this.msg2, this.msg3]}) - .then(() => { - // Need to call advance clock twice because we call setTimeout twice - advanceClock(2000) - advanceClock(2000) - expect(NativeNotifications.displayNotification.callCount).toEqual(3) - }); + waitsForPromise(async () => { + await this.notifier._onDatabaseChanged({ + objectClass: Message.name, + objects: [this.msg1, this.msg2, this.msg3], + }) + // Need to call advance clock twice because we call setTimeout twice + advanceClock(2000) + advanceClock(2000) + expect(NativeNotifications.displayNotification.callCount).toEqual(3) }); }); it("should create Notifications in the order of messages received", () => { - waitsForPromise(() => { - return this.notifier._onNewMailReceived({message: [this.msg1, this.msg2]}) - .then(() => { - advanceClock(2000); - return this.notifier._onNewMailReceived({message: [this.msg3, this.msg4]}); + waitsForPromise(async () => { + await this.notifier._onDatabaseChanged({ + objectClass: Message.name, + objects: [this.msg1, this.msg2], }) - .then(() => { - advanceClock(2000); - advanceClock(2000); - expect(NativeNotifications.displayNotification.callCount).toEqual(4); - const subjects = NativeNotifications.displayNotification.calls.map((call) => { - return call.args[0].subtitle; - }); - const expected = [this.msg1, this.msg2, this.msg3, this.msg4] - .map((msg) => msg.subject); - expect(subjects).toEqual(expected); + advanceClock(2000); + await this.notifier._onDatabaseChanged({ + objectClass: Message.name, + objects: [this.msg3, this.msg4], + }) + advanceClock(2000); + advanceClock(2000); + expect(NativeNotifications.displayNotification.callCount).toEqual(4); + const subjects = NativeNotifications.displayNotification.calls.map((call) => { + return call.args[0].subtitle; }); + const expected = [this.msg1, this.msg2, this.msg3, this.msg4] + .map((msg) => msg.subject); + expect(subjects).toEqual(expected); }); }); it("should create a Notification if there are five or more unread messages", () => { - waitsForPromise(() => { - return this.notifier._onNewMailReceived({ - message: [this.msg1, this.msg2, this.msg3, this.msg4, this.msg5]}) - .then(() => { - advanceClock(2000) - expect(NativeNotifications.displayNotification).toHaveBeenCalled() - expect(NativeNotifications.displayNotification.mostRecentCall.args).toEqual([{ - title: '5 Unread Messages', - tag: 'unread-update', - }]) - }); - }); + waitsForPromise(async () => { + await this.notifier._onDatabaseChanged({ + objectClass: Message.name, + objects: [this.msg1, this.msg2, this.msg3, this.msg4, this.msg5], + }) + advanceClock(2000) + expect(NativeNotifications.displayNotification).toHaveBeenCalled() + expect(NativeNotifications.displayNotification.mostRecentCall.args).toEqual([{ + title: '5 Unread Messages', + tag: 'unread-update', + }]) + }) }); it("should create a Notification correctly, even if new mail has no sender", () => { - waitsForPromise(() => { - return this.notifier._onNewMailReceived({message: [this.msgNoSender]}) - .then(() => { - expect(NativeNotifications.displayNotification).toHaveBeenCalled() - - const options = NativeNotifications.displayNotification.mostRecentCall.args[0] - delete options.onActivate; - expect(options).toEqual({ - title: 'Unknown', - subtitle: 'Hello World', - body: undefined, - canReply: true, - tag: 'unread-update', - }) + waitsForPromise(async () => { + await this.notifier._onDatabaseChanged({ + objectClass: Message.name, + objects: [this.msgNoSender], }); + expect(NativeNotifications.displayNotification).toHaveBeenCalled() + + const options = NativeNotifications.displayNotification.mostRecentCall.args[0] + delete options.onActivate; + expect(options).toEqual({ + title: 'Unknown', + subtitle: 'Hello World', + body: undefined, + canReply: true, + tag: 'unread-update', + }) }); }); it("should not create a Notification if there are no new messages", () => { - waitsForPromise(() => { - return this.notifier._onNewMailReceived({message: []}) - .then(() => { - expect(NativeNotifications.displayNotification).not.toHaveBeenCalled() - }); - }); - - waitsForPromise(() => { - return this.notifier._onNewMailReceived({}) - .then(() => { - expect(NativeNotifications.displayNotification).not.toHaveBeenCalled() - }); + waitsForPromise(async () => { + await this.notifier._onDatabaseChanged({ + objectClass: Message.name, + objects: [], + }) + expect(NativeNotifications.displayNotification).not.toHaveBeenCalled() + await this.notifier._onDatabaseChanged({}); + expect(NativeNotifications.displayNotification).not.toHaveBeenCalled() }); }); it("should not notify about unread messages that are outside the inbox", () => { - waitsForPromise(() => { - return this.notifier._onNewMailReceived({message: [this.msgUnreadButArchived, this.msg1]}) - .then(() => { - expect(NativeNotifications.displayNotification).toHaveBeenCalled() - const options = NativeNotifications.displayNotification.mostRecentCall.args[0] - delete options.onActivate; - expect(options).toEqual({ - title: 'Ben', - subtitle: 'Hello World', - body: undefined, - canReply: true, - tag: 'unread-update', - }) - }); + waitsForPromise(async () => { + await this.notifier._onDatabaseChanged({ + objectClass: Message.name, + objects: [this.msgUnreadButArchived, this.msg1], + }) + expect(NativeNotifications.displayNotification).toHaveBeenCalled() + const options = NativeNotifications.displayNotification.mostRecentCall.args[0] + delete options.onActivate; + expect(options).toEqual({ + title: 'Ben', + subtitle: 'Hello World', + body: undefined, + canReply: true, + tag: 'unread-update', + }) }); }); it("should not create a Notification if the new messages are read", () => { - waitsForPromise(() => { - return this.notifier._onNewMailReceived({message: [this.msgRead]}) - .then(() => { - expect(NativeNotifications.displayNotification).not.toHaveBeenCalled() - }); + waitsForPromise(async () => { + await this.notifier._onDatabaseChanged({ + objectClass: Message.name, + objects: [this.msgRead], + }) + expect(NativeNotifications.displayNotification).not.toHaveBeenCalled() + }); + }); + it("should not create a Notification if the message model is being updated", () => { + waitsForPromise(async () => { + await this.notifier._onDatabaseChanged({ + objectClass: Message.name, + objects: [this.msgHigherVersion], + }) + expect(NativeNotifications.displayNotification).not.toHaveBeenCalled() }); }); it("should not create a Notification if the new messages are actually old ones", () => { - waitsForPromise(() => { - return this.notifier._onNewMailReceived({message: [this.msgOld]}) - .then(() => { - expect(NativeNotifications.displayNotification).not.toHaveBeenCalled() - }); + waitsForPromise(async () => { + await this.notifier._onDatabaseChanged({ + objectClass: Message.name, + objects: [this.msgOld], + }) + expect(NativeNotifications.displayNotification).not.toHaveBeenCalled() }); }); it("should not create a Notification if the new message is one I sent", () => { - waitsForPromise(() => { - return this.notifier._onNewMailReceived({message: [this.msgFromMe]}) - .then(() => { - expect(NativeNotifications.displayNotification).not.toHaveBeenCalled() - }); + waitsForPromise(async () => { + await this.notifier._onDatabaseChanged({ + objectClass: Message.name, + objects: [this.msgFromMe], + }) + expect(NativeNotifications.displayNotification).not.toHaveBeenCalled() }); }); it("clears notifications when a thread is read", () => { - waitsForPromise(() => { - return this.notifier._onNewMailReceived({message: [this.msg1]}) - .then(() => { - expect(NativeNotifications.displayNotification).toHaveBeenCalled(); - expect(this.notification.close).not.toHaveBeenCalled(); - this.notifier._onThreadIsRead(this.threadA); - expect(this.notification.close).toHaveBeenCalled(); - }); - }); - }); + waitsForPromise(async () => { + await this.notifier._onDatabaseChanged({ + objectClass: Message.name, + objects: [this.msg1], + }) + expect(NativeNotifications.displayNotification).toHaveBeenCalled(); + expect(this.notification.close).not.toHaveBeenCalled(); - it("detects changes that may be a thread being read", () => { - const unreadThread = { unread: true }; - const readThread = { unread: false }; - spyOn(this.notifier, '_onThreadIsRead'); - this.notifier._onDatabaseUpdated({ objectClass: 'Thread', objects: [unreadThread, readThread]}); - expect(this.notifier._onThreadIsRead.calls.length).toEqual(1); - expect(this.notifier._onThreadIsRead).toHaveBeenCalledWith(readThread); + const read = this.threadA.clone(); + read.unread = false; + await this.notifier._onDatabaseChanged({ + objectClass: Thread.name, + objects: [read], + }) + expect(this.notification.close).toHaveBeenCalled(); + }); }); it("should play a sound when it gets new mail", () => { @@ -307,12 +344,13 @@ xdescribe("UnreadNotifications", function UnreadNotifications() { }); spyOn(SoundRegistry, "playSound"); - waitsForPromise(() => { - return this.notifier._onNewMailReceived({message: [this.msg1]}) - .then(() => { - expect(NylasEnv.config.get.calls[1].args[0]).toBe("core.notifications.sounds"); - expect(SoundRegistry.playSound).toHaveBeenCalledWith("new-mail"); - }); + waitsForPromise(async () => { + await this.notifier._onDatabaseChanged({ + objectClass: Message.name, + objects: [this.msg1], + }) + expect(NylasEnv.config.get.calls[1].args[0]).toBe("core.notifications.sounds"); + expect(SoundRegistry.playSound).toHaveBeenCalledWith("new-mail"); }); }); @@ -323,12 +361,13 @@ xdescribe("UnreadNotifications", function UnreadNotifications() { return undefined; }); spyOn(SoundRegistry, "playSound") - waitsForPromise(() => { - return this.notifier._onNewMailReceived({message: [this.msg1]}) - .then(() => { - expect(NylasEnv.config.get.calls[1].args[0]).toBe("core.notifications.sounds"); - expect(SoundRegistry.playSound).not.toHaveBeenCalled() - }); + waitsForPromise(async () => { + await this.notifier._onDatabaseChanged({ + objectClass: Message.name, + objects: [this.msg1], + }) + expect(NylasEnv.config.get.calls[1].args[0]).toBe("core.notifications.sounds"); + expect(SoundRegistry.playSound).not.toHaveBeenCalled() }); }); @@ -338,80 +377,19 @@ xdescribe("UnreadNotifications", function UnreadNotifications() { if (config === "core.notifications.sounds") return true; return undefined; }); - waitsForPromise(() => { + waitsForPromise(async () => { spyOn(SoundRegistry, "playSound") - return this.notifier._onNewMailReceived({message: [this.msg1, this.msg2]}).then(() => { - expect(SoundRegistry.playSound).toHaveBeenCalled(); - SoundRegistry.playSound.reset(); - return this.notifier._onNewMailReceived({message: [this.msg3]}).then(() => { - expect(SoundRegistry.playSound).not.toHaveBeenCalled(); - }); - }); - }); - }); - - describe("when the message has no matching thread", () => { - beforeEach(() => { - this.msgNoThread = new Message({ - unread: true, - date: new Date(), - from: [new Contact({name: 'Ben', email: 'benthis.example.com'})], - subject: "Hello World", - threadId: "missing", - }); - }); - - it("should not create a Notification, since it cannot be determined whether the message is in the Inbox", () => { - waitsForPromise(() => { - return this.notifier._onNewMailReceived({message: [this.msgNoThread]}) - .then(() => { - advanceClock(2000) - expect(NativeNotifications.displayNotification).not.toHaveBeenCalled() - }); - }); - }); - - it("should call _onNewMessagesMissingThreads to try displaying a notification again in 10 seconds", () => { - waitsForPromise(() => { - spyOn(this.notifier, '_onNewMessagesMissingThreads') - return this.notifier._onNewMailReceived({message: [this.msgNoThread]}) - .then(() => { - advanceClock(2000) - expect(this.notifier._onNewMessagesMissingThreads).toHaveBeenCalledWith([this.msgNoThread]) - }); - }); - }); - }); - - describe("_onNewMessagesMissingThreads", () => { - beforeEach(() => { - this.msgNoThread = new Message({ - unread: true, - date: new Date(), - from: [new Contact({name: 'Ben', email: 'benthis.example.com'})], - subject: "Hello World", - threadId: "missing", - }); - spyOn(this.notifier, '_onNewMailReceived') - this.notifier._onNewMessagesMissingThreads([this.msgNoThread]) - advanceClock(2000) - }); - - it("should wait 10 seconds and then re-query for threads", () => { - expect(DatabaseStore.find).not.toHaveBeenCalled() - this.msgNoThread.threadId = "A" - advanceClock(10000) - expect(DatabaseStore.find).toHaveBeenCalled() - advanceClock() - expect(this.notifier._onNewMailReceived).toHaveBeenCalledWith({message: [this.msgNoThread], thread: [this.threadA]}) - }); - - it("should do nothing if the threads still can't be found", () => { - expect(DatabaseStore.find).not.toHaveBeenCalled() - advanceClock(10000) - expect(DatabaseStore.find).toHaveBeenCalled() - advanceClock() - expect(this.notifier._onNewMailReceived).not.toHaveBeenCalled() + await this.notifier._onDatabaseChanged({ + objectClass: Message.name, + objects: [this.msg1, this.msg2], + }) + expect(SoundRegistry.playSound).toHaveBeenCalled(); + SoundRegistry.playSound.reset(); + await this.notifier._onDatabaseChanged({ + objectClass: Message.name, + objects: [this.msg3], + }) + expect(SoundRegistry.playSound).not.toHaveBeenCalled(); }); }); });