mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-02-04 22:40:56 +08:00
8f2211f6a0
Summary: This diff uses the new ?expanded=true threads request to fetch threads and the messages inside them at the same time. The messages from this endpoint don't contain bodies. Message bodies have been moved to a new "secondary attribute" type, which can be optionally requested when making queries. This allows us to 1) quickly fetch messages without worrying about MBs of JSON, 2) update messages without updating their bodies, and 3) avoid calls to /messages?thread_id=123. The new message store fetches just the items it wants to display in expanded mode, and we'll show snippets for the rest. Fix up forwarded message Approach: Thread.messageMetadata join approach WIP join approach complete "" || null = null. OMG. Make spinner a bit smarter, use code delays and not css delays Search suggestion store should only show first 10 matches Msg collapsing, refactored msg store that will fetch individual messages that are marked as expanded, set loaded = true when it's all done Test Plan: Tests coming soon. The query refactoring here broke a lot of tests... Reviewers: evan Reviewed By: evan Differential Revision: https://review.inboxapp.com/D1345
241 lines
8.4 KiB
CoffeeScript
241 lines
8.4 KiB
CoffeeScript
Actions = require '../../src/flux/actions'
|
|
SyncbackDraftTask = require '../../src/flux/tasks/syncback-draft'
|
|
SendDraftTask = require '../../src/flux/tasks/send-draft'
|
|
DatabaseStore = require '../../src/flux/stores/database-store'
|
|
{generateTempId} = require '../../src/flux/models/utils'
|
|
Message = require '../../src/flux/models/message'
|
|
TaskQueue = require '../../src/flux/stores/task-queue'
|
|
_ = require 'underscore-plus'
|
|
|
|
describe "SendDraftTask", ->
|
|
describe "shouldWaitForTask", ->
|
|
it "should return any SyncbackDraftTasks for the same draft", ->
|
|
@draftA = new Message
|
|
version: '1'
|
|
id: '1233123AEDF1'
|
|
namespaceId: 'A12ADE'
|
|
subject: 'New Draft'
|
|
draft: true
|
|
to:
|
|
name: 'Dummy'
|
|
email: 'dummy@inboxapp.com'
|
|
|
|
@draftB = new Message
|
|
version: '1'
|
|
id: '1233OTHERDRAFT'
|
|
namespaceId: 'A12ADE'
|
|
subject: 'New Draft'
|
|
draft: true
|
|
to:
|
|
name: 'Dummy'
|
|
email: 'dummy@inboxapp.com'
|
|
|
|
@saveA = new SyncbackDraftTask('localid-A')
|
|
@saveB = new SyncbackDraftTask('localid-B')
|
|
@sendA = new SendDraftTask('localid-A')
|
|
|
|
expect(@sendA.shouldWaitForTask(@saveA)).toBe(true)
|
|
|
|
describe "When on the TaskQueue", ->
|
|
beforeEach ->
|
|
TaskQueue._queue = []
|
|
TaskQueue._completed = []
|
|
@saveTask = new SyncbackDraftTask('localid-A')
|
|
@saveTaskB = new SyncbackDraftTask('localid-B')
|
|
@sendTask = new SendDraftTask('localid-A')
|
|
@tasks = [@saveTask, @saveTaskB, @sendTask]
|
|
|
|
describe "when tasks succeed", ->
|
|
beforeEach ->
|
|
for task in @tasks
|
|
spyOn(task, "performLocal").andCallFake -> Promise.resolve()
|
|
spyOn(task, "performRemote").andCallFake -> Promise.resolve()
|
|
runs ->
|
|
TaskQueue.enqueue(@saveTask, silent: true)
|
|
TaskQueue.enqueue(@saveTaskB, silent: true)
|
|
TaskQueue.enqueue(@sendTask)
|
|
waitsFor ->
|
|
@sendTask.queueState.performedRemote isnt false
|
|
|
|
it "processes all of the items", ->
|
|
runs ->
|
|
expect(TaskQueue._queue.length).toBe 0
|
|
expect(TaskQueue._completed.length).toBe 3
|
|
|
|
it "all of the tasks", ->
|
|
runs ->
|
|
expect(@saveTask.performRemote).toHaveBeenCalled()
|
|
expect(@saveTaskB.performRemote).toHaveBeenCalled()
|
|
expect(@sendTask.performRemote).toHaveBeenCalled()
|
|
|
|
it "finishes the save before sending", ->
|
|
runs ->
|
|
save = @saveTask.queueState.performedRemote
|
|
send = @sendTask.queueState.performedRemote
|
|
expect(save).toBeGreaterThan 0
|
|
expect(send).toBeGreaterThan 0
|
|
expect(save <= send).toBe true
|
|
|
|
|
|
describe "performLocal", ->
|
|
it "should throw an exception if the first parameter is not a localId", ->
|
|
badTasks = [new SendDraftTask()]
|
|
goodTasks = [new SendDraftTask('localid-a')]
|
|
caught = []
|
|
succeeded = []
|
|
|
|
runs ->
|
|
[].concat(badTasks, goodTasks).forEach (task) ->
|
|
task.performLocal()
|
|
.then -> succeeded.push(task)
|
|
.catch (err) -> caught.push(task)
|
|
|
|
waitsFor ->
|
|
succeeded.length + caught.length == badTasks.length + goodTasks.length
|
|
|
|
runs ->
|
|
expect(caught).toEqual(badTasks)
|
|
expect(succeeded).toEqual(goodTasks)
|
|
|
|
describe "performRemote", ->
|
|
beforeEach ->
|
|
@draft = new Message
|
|
version: '1'
|
|
id: '1233123AEDF1'
|
|
namespaceId: 'A12ADE'
|
|
subject: 'New Draft'
|
|
draft: true
|
|
body: 'hello world'
|
|
to:
|
|
name: 'Dummy'
|
|
email: 'dummy@inboxapp.com'
|
|
@draftLocalId = "local-123"
|
|
@task = new SendDraftTask(@draftLocalId)
|
|
spyOn(atom.inbox, 'makeRequest').andCallFake (options) ->
|
|
options.success() if options.success
|
|
spyOn(DatabaseStore, 'findByLocalId').andCallFake (klass, localId) =>
|
|
Promise.resolve(@draft)
|
|
spyOn(DatabaseStore, 'unpersistModel').andCallFake (draft) ->
|
|
Promise.resolve()
|
|
spyOn(atom, "playSound")
|
|
spyOn(Actions, "postNotification")
|
|
spyOn(Actions, "sendDraftSuccess")
|
|
|
|
it "should unpersist when successfully sent", ->
|
|
waitsForPromise => @task.performRemote().then =>
|
|
expect(DatabaseStore.unpersistModel).toHaveBeenCalledWith(@draft)
|
|
|
|
it "should notify the draft was sent", ->
|
|
waitsForPromise => @task.performRemote().then =>
|
|
expect(Actions.sendDraftSuccess).toHaveBeenCalledWith(@draftLocalId)
|
|
|
|
it "should play a sound", ->
|
|
waitsForPromise => @task.performRemote().then ->
|
|
expect(atom.playSound).toHaveBeenCalledWith("mail_sent.ogg")
|
|
|
|
it "post a notification", ->
|
|
waitsForPromise => @task.performRemote().then ->
|
|
expect(Actions.postNotification).toHaveBeenCalled()
|
|
|
|
it "should start an API request to /send", ->
|
|
waitsForPromise =>
|
|
@task.performRemote().then =>
|
|
expect(atom.inbox.makeRequest.calls.length).toBe(1)
|
|
options = atom.inbox.makeRequest.mostRecentCall.args[0]
|
|
expect(options.path).toBe("/n/#{@draft.namespaceId}/send")
|
|
expect(options.method).toBe('POST')
|
|
|
|
describe "when the draft has been saved", ->
|
|
it "should send the draft ID and version", ->
|
|
waitsForPromise =>
|
|
@task.performRemote().then =>
|
|
expect(atom.inbox.makeRequest.calls.length).toBe(1)
|
|
options = atom.inbox.makeRequest.mostRecentCall.args[0]
|
|
expect(options.body.version).toBe(@draft.version)
|
|
expect(options.body.draft_id).toBe(@draft.id)
|
|
|
|
describe "when the draft has not been saved", ->
|
|
beforeEach ->
|
|
@draft = new Message
|
|
id: generateTempId()
|
|
namespaceId: 'A12ADE'
|
|
subject: 'New Draft'
|
|
draft: true
|
|
body: 'hello world'
|
|
to:
|
|
name: 'Dummy'
|
|
email: 'dummy@inboxapp.com'
|
|
@task = new SendDraftTask(@draftLocalId)
|
|
|
|
it "should send the draft JSON", ->
|
|
waitsForPromise =>
|
|
@task.performRemote().then =>
|
|
expect(atom.inbox.makeRequest.calls.length).toBe(1)
|
|
options = atom.inbox.makeRequest.mostRecentCall.args[0]
|
|
expect(options.body).toEqual(@draft.toJSON(joined: true))
|
|
|
|
it "should pass returnsModel:true so that the draft is saved to the data store when returned", ->
|
|
waitsForPromise =>
|
|
@task.performRemote().then ->
|
|
expect(atom.inbox.makeRequest.calls.length).toBe(1)
|
|
options = atom.inbox.makeRequest.mostRecentCall.args[0]
|
|
expect(options.returnsModel).toBe(true)
|
|
|
|
describe "failing performRemote", ->
|
|
beforeEach ->
|
|
@draft = new Message
|
|
version: '1'
|
|
id: '1233123AEDF1'
|
|
namespaceId: 'A12ADE'
|
|
subject: 'New Draft'
|
|
draft: true
|
|
to:
|
|
name: 'Dummy'
|
|
email: 'dummy@inboxapp.com'
|
|
@task = new SendDraftTask(@draft.id)
|
|
spyOn(Actions, "dequeueTask")
|
|
spyOn(Actions, "sendDraftError")
|
|
|
|
it "throws an error if the draft can't be found", ->
|
|
spyOn(DatabaseStore, 'findByLocalId').andCallFake (klass, localId) ->
|
|
Promise.resolve()
|
|
waitsForPromise =>
|
|
@task.performRemote().catch (error) ->
|
|
expect(error.message).toBeDefined()
|
|
|
|
it "throws an error if the draft isn't saved", ->
|
|
spyOn(DatabaseStore, 'findByLocalId').andCallFake (klass, localId) ->
|
|
Promise.resolve(isSaved: false)
|
|
waitsForPromise =>
|
|
@task.performRemote().catch (error) ->
|
|
expect(error.message).toBeDefined()
|
|
|
|
it "throws an error if the DB store has issues", ->
|
|
spyOn(DatabaseStore, 'findByLocalId').andCallFake (klass, localId) ->
|
|
Promise.reject("DB error")
|
|
waitsForPromise =>
|
|
@task.performRemote().catch (error) ->
|
|
expect(error).toBe "DB error"
|
|
|
|
checkError = ->
|
|
expect(Actions.sendDraftError).toHaveBeenCalled()
|
|
args = Actions.sendDraftError.calls[0].args
|
|
expect(args[0]).toBe @draft.id
|
|
expect(args[1].length).toBeGreaterThan 0
|
|
|
|
it "onAPIError notifies of the error", ->
|
|
@task.onAPIError(message: "oh no")
|
|
checkError.call(@)
|
|
|
|
it "onOtherError notifies of the error", ->
|
|
@task.onOtherError()
|
|
checkError.call(@)
|
|
|
|
it "onTimeoutError notifies of the error", ->
|
|
@task.onTimeoutError()
|
|
checkError.call(@)
|
|
|
|
it "onOfflineError notifies of the error and dequeues", ->
|
|
@task.onOfflineError()
|
|
checkError.call(@)
|
|
expect(Actions.dequeueTask).toHaveBeenCalledWith(@task)
|