mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-11-14 21:57:55 +08:00
815b21ad36
Summary: Fixes T2275 Messages were still double sending sometimes because it took a while for the Task to end up on the TaskQueue, which is what we were using to determine re-sendability Test Plan: edgehill --test Reviewers: bengotow Reviewed By: bengotow Maniphest Tasks: T2275 Differential Revision: https://phab.nylas.com/D1728
214 lines
7.5 KiB
CoffeeScript
214 lines
7.5 KiB
CoffeeScript
Actions = require '../../src/flux/actions'
|
|
TaskQueue = require '../../src/flux/stores/task-queue'
|
|
Task = require '../../src/flux/tasks/task'
|
|
|
|
{isTempId} = require '../../src/flux/models/utils'
|
|
|
|
{APIError,
|
|
OfflineError,
|
|
TimeoutError} = require '../../src/flux/errors'
|
|
|
|
class TaskSubclassA extends Task
|
|
constructor: (val) -> @aProp = val; super
|
|
|
|
class TaskSubclassB extends Task
|
|
constructor: (val) -> @bProp = val; super
|
|
|
|
describe "TaskQueue", ->
|
|
|
|
makeUnstartedTask = (task) ->
|
|
task
|
|
|
|
makeProcessing = (task) ->
|
|
task.queueState.isProcessing = true
|
|
task
|
|
|
|
beforeEach ->
|
|
@task = new Task()
|
|
@unstartedTask = makeUnstartedTask(new Task())
|
|
@processingTask = makeProcessing(new Task())
|
|
|
|
afterEach ->
|
|
# Flush any throttled or debounced updates
|
|
advanceClock(1000)
|
|
|
|
describe "findTask", ->
|
|
beforeEach ->
|
|
@subclassA = new TaskSubclassA()
|
|
@subclassB1 = new TaskSubclassB("B1")
|
|
@subclassB2 = new TaskSubclassB("B2")
|
|
TaskQueue._queue = [@subclassA, @subclassB1, @subclassB2]
|
|
|
|
it "accepts type as a string", ->
|
|
expect(TaskQueue.findTask('TaskSubclassB', {bProp: 'B1'})).toEqual(@subclassB1)
|
|
|
|
it "accepts type as a class", ->
|
|
expect(TaskQueue.findTask(TaskSubclassB, {bProp: 'B1'})).toEqual(@subclassB1)
|
|
|
|
it "works without a set of match criteria", ->
|
|
expect(TaskQueue.findTask(TaskSubclassA)).toEqual(@subclassA)
|
|
|
|
it "only returns a task that matches the criteria", ->
|
|
expect(TaskQueue.findTask(TaskSubclassB, {bProp: 'B1'})).toEqual(@subclassB1)
|
|
expect(TaskQueue.findTask(TaskSubclassB, {bProp: 'B2'})).toEqual(@subclassB2)
|
|
expect(TaskQueue.findTask(TaskSubclassB, {bProp: 'B3'})).toEqual(null)
|
|
|
|
describe "enqueue", ->
|
|
beforeEach ->
|
|
spyOn(@unstartedTask, 'runLocal').andCallFake =>
|
|
@unstartedTask.queueState.localComplete = true
|
|
Promise.resolve()
|
|
|
|
it "makes sure you've queued a real task", ->
|
|
expect( -> TaskQueue.enqueue("asamw")).toThrow()
|
|
|
|
it "adds it to the queue", ->
|
|
spyOn(TaskQueue, '_processQueue').andCallFake ->
|
|
TaskQueue.enqueue(@unstartedTask)
|
|
advanceClock()
|
|
expect(TaskQueue._queue.length).toBe(1)
|
|
|
|
it "immediately calls runLocal", ->
|
|
TaskQueue.enqueue(@unstartedTask)
|
|
expect(@unstartedTask.runLocal).toHaveBeenCalled()
|
|
|
|
it "add it to the queue after `performLocalComplete` has run", ->
|
|
task = new Task()
|
|
spyOn(atom, "isMainWindow").andReturn true
|
|
waitsForPromise ->
|
|
TaskQueue.enqueue(task)
|
|
task.waitForPerformLocal().then ->
|
|
expect(TaskQueue._queue.length).toBe 1
|
|
expect(TaskQueue._queue[0]).toBe task
|
|
|
|
it "notifies the queue should be processed", ->
|
|
spyOn(TaskQueue, "_processQueue").andCallThrough()
|
|
spyOn(TaskQueue, "_processTask")
|
|
|
|
TaskQueue.enqueue(@unstartedTask)
|
|
advanceClock()
|
|
expect(TaskQueue._processQueue).toHaveBeenCalled()
|
|
expect(TaskQueue._processTask).toHaveBeenCalledWith(@unstartedTask)
|
|
expect(TaskQueue._processTask.calls.length).toBe(1)
|
|
|
|
it "throws an exception if the task does not have a queueState", ->
|
|
task = new TaskSubclassA()
|
|
task.queueState = undefined
|
|
expect( => TaskQueue.enqueue(task)).toThrow()
|
|
|
|
it "throws an exception if the task does not have an ID", ->
|
|
task = new TaskSubclassA()
|
|
task.id = undefined
|
|
expect( => TaskQueue.enqueue(task)).toThrow()
|
|
|
|
it "dequeues obsolete tasks", ->
|
|
task = new TaskSubclassA()
|
|
spyOn(TaskQueue, '_dequeueObsoleteTasks').andCallFake ->
|
|
TaskQueue.enqueue(task)
|
|
expect(TaskQueue._dequeueObsoleteTasks).toHaveBeenCalled()
|
|
|
|
describe "_dequeueObsoleteTasks", ->
|
|
it "should dequeue tasks based on `shouldDequeueOtherTask`", ->
|
|
class KillsTaskA extends Task
|
|
shouldDequeueOtherTask: (other) -> other instanceof TaskSubclassA
|
|
performRemote: -> new Promise (resolve, reject) ->
|
|
|
|
otherTask = new Task()
|
|
otherTask.queueState.localComplete = true
|
|
obsoleteTask = new TaskSubclassA()
|
|
obsoleteTask.queueState.localComplete = true
|
|
replacementTask = new KillsTaskA()
|
|
replacementTask.queueState.localComplete = true
|
|
|
|
spyOn(TaskQueue, 'dequeue').andCallThrough()
|
|
TaskQueue._queue = [obsoleteTask, otherTask]
|
|
TaskQueue._dequeueObsoleteTasks(replacementTask)
|
|
expect(TaskQueue._queue.length).toBe(1)
|
|
expect(TaskQueue.dequeue).toHaveBeenCalledWith(obsoleteTask)
|
|
expect(TaskQueue.dequeue.calls.length).toBe(1)
|
|
|
|
describe "dequeue", ->
|
|
beforeEach ->
|
|
TaskQueue._queue = [@unstartedTask, @processingTask]
|
|
|
|
it "grabs the task by object", ->
|
|
found = TaskQueue._resolveTaskArgument(@unstartedTask)
|
|
expect(found).toBe @unstartedTask
|
|
|
|
it "grabs the task by id", ->
|
|
found = TaskQueue._resolveTaskArgument(@unstartedTask.id)
|
|
expect(found).toBe @unstartedTask
|
|
|
|
it "throws an error if the task isn't found", ->
|
|
expect( -> TaskQueue.dequeue("bad")).toThrow()
|
|
|
|
describe "with an unstarted task", ->
|
|
it "moves it from the queue", ->
|
|
TaskQueue.dequeue(@unstartedTask)
|
|
expect(TaskQueue._queue.length).toBe(1)
|
|
expect(TaskQueue._completed.length).toBe(1)
|
|
|
|
it "notifies the queue has been updated", ->
|
|
spyOn(TaskQueue, "_processQueue")
|
|
TaskQueue.dequeue(@unstartedTask)
|
|
advanceClock(20)
|
|
expect(TaskQueue._processQueue).toHaveBeenCalled()
|
|
expect(TaskQueue._processQueue.calls.length).toBe(1)
|
|
|
|
describe "with a processing task", ->
|
|
it "calls cancel() to allow the task to resolve or reject from runRemote()", ->
|
|
spyOn(@processingTask, 'cancel')
|
|
TaskQueue.dequeue(@processingTask)
|
|
expect(@processingTask.cancel).toHaveBeenCalled()
|
|
expect(TaskQueue._queue.length).toBe(2)
|
|
expect(TaskQueue._completed.length).toBe(0)
|
|
|
|
describe "process Task", ->
|
|
it "doesn't process processing tasks", ->
|
|
spyOn(@processingTask, "runRemote").andCallFake -> Promise.resolve()
|
|
TaskQueue._processTask(@processingTask)
|
|
expect(@processingTask.runRemote).not.toHaveBeenCalled()
|
|
|
|
it "doesn't process blocked tasks", ->
|
|
class BlockedByTaskA extends Task
|
|
shouldWaitForTask: (other) -> other instanceof TaskSubclassA
|
|
|
|
taskA = new TaskSubclassA()
|
|
otherTask = new Task()
|
|
blockedByTaskA = new BlockedByTaskA()
|
|
|
|
taskA.queueState.localComplete = true
|
|
otherTask.queueState.localComplete = true
|
|
blockedByTaskA.queueState.localComplete = true
|
|
|
|
spyOn(taskA, "runRemote").andCallFake -> new Promise (resolve, reject) ->
|
|
spyOn(blockedByTaskA, "runRemote").andCallFake -> Promise.resolve()
|
|
|
|
TaskQueue._queue = [taskA, otherTask, blockedByTaskA]
|
|
TaskQueue._processQueue()
|
|
|
|
advanceClock()
|
|
|
|
expect(TaskQueue._queue.length).toBe(2)
|
|
expect(taskA.runRemote).toHaveBeenCalled()
|
|
expect(blockedByTaskA.runRemote).not.toHaveBeenCalled()
|
|
|
|
it "doesn't block itself, even if the shouldWaitForTask method is implemented naively", ->
|
|
class BlockingTask extends Task
|
|
shouldWaitForTask: (other) -> other instanceof BlockingTask
|
|
|
|
blockedTask = new BlockingTask()
|
|
spyOn(blockedTask, "runRemote").andCallFake -> Promise.resolve()
|
|
|
|
TaskQueue.enqueue(blockedTask)
|
|
advanceClock()
|
|
blockedTask.runRemote.callCount > 0
|
|
|
|
it "sets the processing bit", ->
|
|
spyOn(@unstartedTask, "runRemote").andCallFake -> Promise.resolve()
|
|
task = new Task()
|
|
task.queueState.localComplete = true
|
|
TaskQueue._queue = [task]
|
|
TaskQueue._processTask(task)
|
|
expect(task.queueState.isProcessing).toBe true
|
|
|