mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-09-23 08:46:07 +08:00
ded4da1505
Summary: This diff attempts to improve the responsiveness of the app when you hit "Reply". This is achieved by being smarter about creating the draft and loading it into the draft store, and also by allowing the compose* actions to take objects instead of just IDs (resulting in a fetch of the object). Allow Actions.composeReply,etc. to optionally be called with thread and message objects instead of IDs. This prevents a database lookup and the data is "right there." Create DraftStoreProxy for new drafts optimistically—this allows us to hand it the draft model we just created and it doesn't have to go query for it When we create a new Draft, immediately bind it to a LocalId. This means that when the MessageStore receives the trigger() event from the Database, it doesn't have to wait while a localId is created When MessageStore sees a new Message come in which is on the current thread, a draft, and not in the localIds map, assume it's a new draft and shortcut fetchFromCaceh to manually add it to the items array and display. This means the user sees the... ...draft instantly. Remove delays from focusing draft, scrolling to draft after content is ready. I actually removed these thinking it would break something, and it didn't break anything.... Maybe new Chromium handles better? Fix specs Test Plan: Run specs - more in progress right now Reviewers: evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D1598
264 lines
7.5 KiB
CoffeeScript
264 lines
7.5 KiB
CoffeeScript
_ = require "underscore"
|
|
moment = require "moment"
|
|
proxyquire = require "proxyquire"
|
|
|
|
CSON = require "season"
|
|
React = require "react/addons"
|
|
TestUtils = React.addons.TestUtils
|
|
|
|
{Thread,
|
|
Contact,
|
|
Actions,
|
|
Message,
|
|
Namespace,
|
|
MessageStore,
|
|
NamespaceStore,
|
|
ComponentRegistry} = require "nylas-exports"
|
|
|
|
{InjectedComponent} = require 'nylas-component-kit'
|
|
|
|
MessageItem = proxyquire("../lib/message-item", {
|
|
"./email-frame": React.createClass({render: -> <div></div>})
|
|
})
|
|
|
|
MessageList = proxyquire("../lib/message-list", {
|
|
"./message-item": MessageItem
|
|
})
|
|
|
|
MessageParticipants = require "../lib/message-participants"
|
|
|
|
me = new Namespace(
|
|
"name": "User One",
|
|
"email": "user1@nylas.com"
|
|
"provider": "inbox"
|
|
)
|
|
NamespaceStore._current = me
|
|
|
|
user_headers =
|
|
id: null
|
|
object: null
|
|
namespace_id: null
|
|
|
|
user_1 = _.extend _.clone(user_headers),
|
|
name: "User One"
|
|
email: "user1@nylas.com"
|
|
user_2 = _.extend _.clone(user_headers),
|
|
name: "User Two"
|
|
email: "user2@nylas.com"
|
|
user_3 = _.extend _.clone(user_headers),
|
|
name: "User Three"
|
|
email: "user3@nylas.com"
|
|
user_4 = _.extend _.clone(user_headers),
|
|
name: "User Four"
|
|
email: "user4@nylas.com"
|
|
user_5 = _.extend _.clone(user_headers),
|
|
name: "User Five"
|
|
email: "user5@nylas.com"
|
|
|
|
m1 = (new Message).fromJSON({
|
|
"id" : "111",
|
|
"from" : [ user_1 ],
|
|
"to" : [ user_2 ],
|
|
"cc" : [ user_3, user_4 ],
|
|
"bcc" : null,
|
|
"body" : "Body One",
|
|
"date" : 1415814587,
|
|
"draft" : false
|
|
"files" : [],
|
|
"unread" : false,
|
|
"object" : "message",
|
|
"snippet" : "snippet one...",
|
|
"subject" : "Subject One",
|
|
"thread_id" : "thread_12345",
|
|
"namespace_id" : "nsid"
|
|
})
|
|
m2 = (new Message).fromJSON({
|
|
"id" : "222",
|
|
"from" : [ user_2 ],
|
|
"to" : [ user_1 ],
|
|
"cc" : [ user_3, user_4 ],
|
|
"bcc" : null,
|
|
"body" : "Body Two",
|
|
"date" : 1415814587,
|
|
"draft" : false
|
|
"files" : [],
|
|
"unread" : false,
|
|
"object" : "message",
|
|
"snippet" : "snippet Two...",
|
|
"subject" : "Subject Two",
|
|
"thread_id" : "thread_12345",
|
|
"namespace_id" : "nsid"
|
|
})
|
|
m3 = (new Message).fromJSON({
|
|
"id" : "333",
|
|
"from" : [ user_3 ],
|
|
"to" : [ user_1 ],
|
|
"cc" : [ user_2, user_4 ],
|
|
"bcc" : [],
|
|
"body" : "Body Three",
|
|
"date" : 1415814587,
|
|
"draft" : false
|
|
"files" : [],
|
|
"unread" : false,
|
|
"object" : "message",
|
|
"snippet" : "snippet Three...",
|
|
"subject" : "Subject Three",
|
|
"thread_id" : "thread_12345",
|
|
"namespace_id" : "nsid"
|
|
})
|
|
m4 = (new Message).fromJSON({
|
|
"id" : "444",
|
|
"from" : [ user_4 ],
|
|
"to" : [ user_1 ],
|
|
"cc" : [],
|
|
"bcc" : [ user_5 ],
|
|
"body" : "Body Four",
|
|
"date" : 1415814587,
|
|
"draft" : false
|
|
"files" : [],
|
|
"unread" : false,
|
|
"object" : "message",
|
|
"snippet" : "snippet Four...",
|
|
"subject" : "Subject Four",
|
|
"thread_id" : "thread_12345",
|
|
"namespace_id" : "nsid"
|
|
})
|
|
m5 = (new Message).fromJSON({
|
|
"id" : "555",
|
|
"from" : [ user_1 ],
|
|
"to" : [ user_4 ],
|
|
"cc" : [],
|
|
"bcc" : [],
|
|
"body" : "Body Five",
|
|
"date" : 1415814587,
|
|
"draft" : false
|
|
"files" : [],
|
|
"unread" : false,
|
|
"object" : "message",
|
|
"snippet" : "snippet Five...",
|
|
"subject" : "Subject Five",
|
|
"thread_id" : "thread_12345",
|
|
"namespace_id" : "nsid"
|
|
})
|
|
testMessages = [m1, m2, m3, m4, m5]
|
|
draftMessages = [
|
|
(new Message).fromJSON({
|
|
"id" : "666",
|
|
"from" : [ user_1 ],
|
|
"to" : [ ],
|
|
"cc" : [ ],
|
|
"bcc" : null,
|
|
"body" : "Body One",
|
|
"date" : 1415814587,
|
|
"draft" : true
|
|
"files" : [],
|
|
"unread" : false,
|
|
"object" : "draft",
|
|
"snippet" : "draft snippet one...",
|
|
"subject" : "Draft One",
|
|
"thread_id" : "thread_12345",
|
|
"namespace_id" : "nsid"
|
|
}),
|
|
]
|
|
|
|
test_thread = (new Thread).fromJSON({
|
|
"id" : "thread_12345"
|
|
"subject" : "Subject 12345"
|
|
})
|
|
|
|
describe "MessageList", ->
|
|
beforeEach ->
|
|
MessageStore._items = []
|
|
MessageStore._threadId = null
|
|
spyOn(MessageStore, "itemLocalIds").andCallFake ->
|
|
{"666": "666"}
|
|
spyOn(MessageStore, "itemsLoading").andCallFake ->
|
|
false
|
|
|
|
@message_list = TestUtils.renderIntoDocument(<MessageList />)
|
|
@message_list_node = React.findDOMNode(@message_list)
|
|
|
|
it "renders into the document", ->
|
|
expect(TestUtils.isCompositeComponentWithType(@message_list,
|
|
MessageList)).toBe true
|
|
|
|
it "by default has zero children", ->
|
|
items = TestUtils.scryRenderedComponentsWithType(@message_list,
|
|
MessageItem)
|
|
|
|
expect(items.length).toBe 0
|
|
|
|
describe "Populated Message list", ->
|
|
beforeEach ->
|
|
MessageStore._items = testMessages
|
|
MessageStore._expandItemsToDefault()
|
|
MessageStore.trigger(MessageStore)
|
|
@message_list.setState currentThread: test_thread
|
|
|
|
it "renders all the correct number of messages", ->
|
|
items = TestUtils.scryRenderedComponentsWithType(@message_list,
|
|
MessageItem)
|
|
expect(items.length).toBe 5
|
|
|
|
it "renders the correct number of expanded messages", ->
|
|
msgs = TestUtils.scryRenderedDOMComponentsWithClass(@message_list, "message-item-wrap collapsed")
|
|
expect(msgs.length).toBe 4
|
|
|
|
it "aggregates participants across all messages", ->
|
|
expect(@message_list._threadParticipants().length).toBe 4
|
|
expect(@message_list._threadParticipants()[0] instanceof Contact).toBe true
|
|
|
|
it "displays lists of participants on the page", ->
|
|
items = TestUtils.scryRenderedComponentsWithType(@message_list,
|
|
MessageParticipants)
|
|
expect(items.length).toBe 1
|
|
|
|
it "focuses new composers when a draft is added", ->
|
|
spyOn(@message_list, "_focusDraft")
|
|
msgs = @message_list.state.messages
|
|
|
|
@message_list.setState
|
|
messages: msgs.concat(draftMessages)
|
|
|
|
expect(@message_list._focusDraft).toHaveBeenCalled()
|
|
expect(@message_list._focusDraft.mostRecentCall.args[0].props.exposedProps.localId).toEqual(draftMessages[0].id)
|
|
|
|
describe "MessageList with draft", ->
|
|
beforeEach ->
|
|
MessageStore._items = testMessages.concat draftMessages
|
|
MessageStore.trigger(MessageStore)
|
|
spyOn(@message_list, "_focusDraft")
|
|
@message_list.setState(currentThread: test_thread)
|
|
|
|
it "renders the composer", ->
|
|
items = TestUtils.scryRenderedComponentsWithTypeAndProps(@message_list, InjectedComponent, matching: {role:"Composer"})
|
|
expect(@message_list.state.messages.length).toBe 6
|
|
expect(items.length).toBe 1
|
|
|
|
it "doesn't focus on initial load", ->
|
|
expect(@message_list._focusDraft).not.toHaveBeenCalled()
|
|
|
|
describe "reply type", ->
|
|
it "prompts for a reply when there's only one participant", ->
|
|
MessageStore._items = [m3, m5]
|
|
MessageStore.trigger()
|
|
@message_list.setState currentThread: test_thread
|
|
expect(@message_list._replyType()).toBe "reply"
|
|
cs = TestUtils.scryRenderedDOMComponentsWithClass(@message_list, "footer-reply-area")
|
|
expect(cs.length).toBe 1
|
|
|
|
it "prompts for a reply-all when there's more then one participant", ->
|
|
MessageStore._items = [m5, m3]
|
|
MessageStore.trigger()
|
|
@message_list.setState currentThread: test_thread
|
|
expect(@message_list._replyType()).toBe "reply-all"
|
|
cs = TestUtils.scryRenderedDOMComponentsWithClass(@message_list, "footer-reply-area")
|
|
expect(cs.length).toBe 1
|
|
|
|
it "hides the reply type if the last message is a draft", ->
|
|
MessageStore._items = [m5, m3, draftMessages[0]]
|
|
MessageStore.trigger()
|
|
@message_list.setState currentThread: test_thread
|
|
cs = TestUtils.scryRenderedDOMComponentsWithClass(@message_list, "footer-reply-area")
|
|
expect(cs.length).toBe 0
|