diff --git a/spec/stores/message-store-spec.coffee b/spec/stores/message-store-spec.coffee index 1f619bcb7..20d41bb38 100644 --- a/spec/stores/message-store-spec.coffee +++ b/spec/stores/message-store-spec.coffee @@ -12,9 +12,50 @@ testMessage1 = new Message(id: 'a', body: '123', files: []) testMessage2 = new Message(id: 'b', body: '123', files: []) describe "MessageStore", -> - describe "when thread focus changes", -> + describe "when the receiving focus changes from the FocusedContentStore", -> + beforeEach -> + if MessageStore._onFocusChangedTimer + clearTimeout(MessageStore._onFocusChangedTimer) + MessageStore._onFocusChangedTimer = null + spyOn(MessageStore, '_onApplyFocusChange') + + afterEach -> + if MessageStore._onFocusChangedTimer + clearTimeout(MessageStore._onFocusChangedTimer) + MessageStore._onFocusChangedTimer = null + + describe "if no change has happened in the last 100ms", -> + it "should apply immediately", -> + FocusedContentStore.trigger(impactsCollection: (c) -> true ) + expect(MessageStore._onApplyFocusChange).toHaveBeenCalled() + + describe "if a change has happened in the last 100ms", -> + it "should not apply immediately", -> + noop = => + MessageStore._onFocusChangedTimer = setTimeout(noop, 100) + FocusedContentStore.trigger(impactsCollection: (c) -> true ) + expect(MessageStore._onApplyFocusChange).not.toHaveBeenCalled() + + it "should apply 100ms after the last focus change and reset", -> + FocusedContentStore.trigger(impactsCollection: (c) -> true ) + expect(MessageStore._onApplyFocusChange.callCount).toBe(1) + advanceClock(50) + FocusedContentStore.trigger(impactsCollection: (c) -> true ) + expect(MessageStore._onApplyFocusChange.callCount).toBe(1) + advanceClock(50) + FocusedContentStore.trigger(impactsCollection: (c) -> true ) + expect(MessageStore._onApplyFocusChange.callCount).toBe(1) + advanceClock(150) + FocusedContentStore.trigger(impactsCollection: (c) -> true ) + expect(MessageStore._onApplyFocusChange.callCount).toBe(3) + advanceClock(150) + FocusedContentStore.trigger(impactsCollection: (c) -> true ) + expect(MessageStore._onApplyFocusChange.callCount).toBe(5) + + describe "when applying focus changes", -> beforeEach -> MessageStore._lastLoadedThreadId = null + @focus = null spyOn(FocusedContentStore, 'focused').andCallFake (collection) => if collection is 'thread' @@ -37,7 +78,7 @@ describe "MessageStore", -> it "should retrieve the focused thread", -> @focus = testThread MessageStore._thread = null - FocusedContentStore.trigger({impactsCollection: -> true}) + MessageStore._onApplyFocusChange() expect(DatabaseStore.findAll).toHaveBeenCalled() expect(DatabaseStore.findAll.mostRecentCall.args[0]).toBe(Message) @@ -45,13 +86,13 @@ describe "MessageStore", -> it "should do nothing", -> @focus = testThread MessageStore._thread = @focus - FocusedContentStore.trigger({impactsCollection: -> true}) + MessageStore._onApplyFocusChange() expect(DatabaseStore.findAll).not.toHaveBeenCalled() describe "when the thread is unread", -> beforeEach -> @focus = null - FocusedContentStore.trigger({impactsCollection: -> true}) + MessageStore._onApplyFocusChange() testThread.unread = true spyOn(Actions, 'queueTask') spyOn(atom.config, 'get').andCallFake (key) => @@ -60,7 +101,7 @@ describe "MessageStore", -> it "should queue a task to mark the thread as read", -> @focus = testThread - FocusedContentStore.trigger({impactsCollection: -> true}) + MessageStore._onApplyFocusChange() advanceClock(500) expect(Actions.queueTask).not.toHaveBeenCalled() advanceClock(500) @@ -69,18 +110,18 @@ describe "MessageStore", -> it "should not queue a task to mark the thread as read if the thread is no longer selected 500msec later", -> @focus = testThread - FocusedContentStore.trigger({impactsCollection: -> true}) + MessageStore._onApplyFocusChange() advanceClock(500) expect(Actions.queueTask).not.toHaveBeenCalled() @focus = null - FocusedContentStore.trigger({impactsCollection: -> true}) + MessageStore._onApplyFocusChange() advanceClock(500) expect(Actions.queueTask).not.toHaveBeenCalled() it "should not re-mark the thread as read when made unread", -> @focus = testThread testThread.unread = false - FocusedContentStore.trigger({impactsCollection: -> true}) + MessageStore._onApplyFocusChange() advanceClock(500) expect(Actions.queueTask).not.toHaveBeenCalled() diff --git a/src/flux/stores/message-store.coffee b/src/flux/stores/message-store.coffee index 84ac211c2..214fc5da3 100644 --- a/src/flux/stores/message-store.coffee +++ b/src/flux/stores/message-store.coffee @@ -115,6 +115,26 @@ class MessageStore extends NylasStore @_fetchFromCache() _onFocusChanged: (change) => + # This implements a debounce that fires on the leading and trailing edge. + # + # If we haven't changed focus in the last 100ms, do it immediately. This means + # there is no delay when moving to the next thread, deselecting a thread, etc. + # + # If we have changed focus in the last 100ms, wait for focus changes to + # stop arriving for 100msec before applying. This means that flying + # through threads doesn't cause is to make a zillion queries for messages. + # + if not @_onFocusChangedTimer + @_onApplyFocusChange() + else + clearTimeout(@_onFocusChangedTimer) + + @_onFocusChangedTimer = setTimeout => + @_onFocusChangedTimer = null + @_onApplyFocusChange() + , 100 + + _onApplyFocusChange: => focused = FocusedContentStore.focused('thread') return if @_thread?.id is focused?.id