mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-11-11 18:32:20 +08:00
8b3f7f0578
Summary: This diff adds an "OutboxStore" which reflects the TaskQueue and adds a progress bar / cancel button to drafts which are currently sending. - Sending state is different from things like Send later because drafts which are sending shouldn't be editable. You should have to stop them from sending before editing. I think we can implement "Send Later" indicators, etc. with a simple InjectedComponentSet on the draft list rows, but the OutboxStore is woven into the DraftList query subscription so every draft has a `uploadTaskId`. - The TaskQueue now saves periodically (every one second) when there are "Processing" tasks. This is not really necessary, but makes it super easy for tasks to expose "progress", because they're essentially serialized and propagated to all windows every one second with the current progress value. Kind of questionable, but super convenient. - I also cleaned up ListTabular and MultiselectList a bit because they applied the className prop to an inner element and not the top one. - If a DestroyDraft task is created for a draft without a server id, it ends with Task.Status.Continue and not Failed. - The SendDraftTask doesn't delete uploads until the send actually goes through, in case the app crashes and it forgets the file IDs it created. Test Plan: Tests coming soon Reviewers: juan, evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D2524
208 lines
7.5 KiB
CoffeeScript
208 lines
7.5 KiB
CoffeeScript
_ = require 'underscore'
|
|
|
|
{Thread} = require 'nylas-exports'
|
|
{ListTabular} = require 'nylas-component-kit'
|
|
|
|
ListDataSource = ListTabular.DataSource
|
|
ListSelection = ListTabular.Selection
|
|
|
|
describe "ListSelection", ->
|
|
beforeEach ->
|
|
@trigger = jasmine.createSpy('trigger')
|
|
|
|
@items = []
|
|
@items.push(new Thread(id: "#{ii}", clientId: "#{ii}")) for ii in [0..99]
|
|
|
|
@view = new ListDataSource()
|
|
@view.indexOfId = jasmine.createSpy('indexOfId').andCallFake (id) =>
|
|
_.findIndex(@items, _.matcher({id}))
|
|
@view.get = jasmine.createSpy('get').andCallFake (idx) =>
|
|
@items[idx]
|
|
|
|
@selection = new ListSelection(@view, @trigger)
|
|
|
|
it "should initialize with an empty set", ->
|
|
expect(@selection.items()).toEqual([])
|
|
expect(@selection.ids()).toEqual([])
|
|
|
|
it "should throw an exception if a view is not provided", ->
|
|
expect( => new ListSelection(null, @trigger)).toThrow()
|
|
|
|
describe "set", ->
|
|
it "should replace the current selection with the provided models", ->
|
|
@selection.set([@items[2], @items[4], @items[7]])
|
|
expect(@selection.ids()).toEqual(['2', '4', '7'])
|
|
@selection.set([@items[2], @items[5], @items[6]])
|
|
expect(@selection.ids()).toEqual(['2', '5', '6'])
|
|
|
|
it "should throw an exception if the items passed are not models", ->
|
|
expect( => @selection.set(['hi'])).toThrow()
|
|
|
|
it "should trigger", ->
|
|
@selection.set([@items[2], @items[4], @items[7]])
|
|
expect(@trigger).toHaveBeenCalled()
|
|
|
|
describe "clear", ->
|
|
beforeEach ->
|
|
@selection.set([@items[2]])
|
|
|
|
it "should empty the selection set", ->
|
|
@selection.clear()
|
|
expect(@selection.ids()).toEqual([])
|
|
|
|
it "should trigger", ->
|
|
@selection.clear()
|
|
expect(@trigger).toHaveBeenCalled()
|
|
|
|
describe "remove", ->
|
|
beforeEach ->
|
|
@selection.set([@items[2], @items[4], @items[7]])
|
|
|
|
it "should do nothing if called without a valid item", ->
|
|
@selection.remove(null)
|
|
@selection.remove(undefined)
|
|
@selection.remove(false)
|
|
expect(@selection.ids()).toEqual(['2', '4', '7'])
|
|
|
|
it "should remove the item from the set", ->
|
|
@selection.remove(@items[2])
|
|
expect(@selection.ids()).toEqual(['4', '7'])
|
|
|
|
it "should throw an exception if any item passed is not a model", ->
|
|
expect( => @selection.remove('hi')).toThrow()
|
|
|
|
it "should accept an array of models as well as a single item", ->
|
|
@selection.remove([@items[2], @items[4]])
|
|
expect(@selection.ids()).toEqual(['7'])
|
|
|
|
it "should trigger", ->
|
|
@selection.remove()
|
|
expect(@trigger).toHaveBeenCalled()
|
|
|
|
describe "_applyChangeRecord", ->
|
|
it "should replace items in the selection with the matching provided items, if present", ->
|
|
@selection.set([@items[2], @items[4], @items[7]])
|
|
expect(@selection.items()[0]).toBe(@items[2])
|
|
expect(@selection.items()[0].subject).toBe(undefined)
|
|
newItem2 = new Thread(id: '2', clientId: '2', subject:'Hello world!')
|
|
@selection._applyChangeRecord({objectClass: 'Thread', objects: [newItem2], type: 'persist'})
|
|
expect(@selection.items()[0].subject).toBe('Hello world!')
|
|
|
|
it "should rremove items in the selection if type is unpersist", ->
|
|
@selection.set([@items[2], @items[4], @items[7]])
|
|
newItem2 = new Thread(id: '2', clientId: '2', subject:'Hello world!')
|
|
@selection._applyChangeRecord({objectClass: 'Thread', objects: [newItem2], type: 'unpersist'})
|
|
expect(@selection.ids()).toEqual(['4', '7'])
|
|
|
|
describe "toggle", ->
|
|
beforeEach ->
|
|
@selection.set([@items[2]])
|
|
|
|
it "should do nothing if called without a valid item", ->
|
|
@selection.toggle(null)
|
|
@selection.toggle(undefined)
|
|
@selection.toggle(false)
|
|
expect(@selection.ids()).toEqual(['2'])
|
|
|
|
it "should throw an exception if the item passed is not a model", ->
|
|
expect( => @selection.toggle('hi')).toThrow()
|
|
|
|
it "should select the item if it is not selected", ->
|
|
@selection.toggle(@items[3])
|
|
expect(@selection.ids()).toEqual(['2', '3'])
|
|
|
|
it "should de-select the item if it is selected", ->
|
|
@selection.toggle(@items[2])
|
|
expect(@selection.ids()).toEqual([])
|
|
|
|
it "should trigger", ->
|
|
@selection.toggle(@items[2])
|
|
expect(@trigger).toHaveBeenCalled()
|
|
|
|
describe "expandTo", ->
|
|
it "should select the item, if no other items are selected", ->
|
|
@selection.clear()
|
|
@selection.expandTo(@items[2])
|
|
expect(@selection.ids()).toEqual(['2'])
|
|
|
|
it "should do nothing if called without a valid item", ->
|
|
@selection.expandTo(null)
|
|
@selection.expandTo(undefined)
|
|
@selection.expandTo(false)
|
|
expect(@selection.ids()).toEqual([])
|
|
|
|
it "should throw an exception if the item passed is not a model", ->
|
|
expect( => @selection.expandTo('hi')).toThrow()
|
|
|
|
it "should select all items from the last selected item to the provided item", ->
|
|
@selection.set([@items[2], @items[5]])
|
|
@selection.expandTo(@items[8])
|
|
expect(@selection.ids()).toEqual(['2','5','6','7','8'])
|
|
|
|
it "should not do anything if the provided item is not in the view set", ->
|
|
@selection.set([@items[2]])
|
|
@selection.expandTo(new Thread(id:'not-in-view!'))
|
|
expect(@selection.ids()).toEqual(['2'])
|
|
|
|
it "should re-order items so that the order still reflects the order selection actions were taken", ->
|
|
@selection.set([@items[10], @items[4], @items[1]])
|
|
@selection.expandTo(@items[8])
|
|
expect(@selection.ids()).toEqual(['10','1','2','3','4','5','6','7','8'])
|
|
|
|
it "should trigger", ->
|
|
@selection.set([@items[5], @items[4], @items[1]])
|
|
@selection.expandTo(@items[8])
|
|
expect(@trigger).toHaveBeenCalled()
|
|
|
|
describe "walk", ->
|
|
beforeEach ->
|
|
@selection.set([@items[2]])
|
|
|
|
it "should trigger", ->
|
|
current = @items[4]
|
|
next = @items[5]
|
|
@selection.walk({current, next})
|
|
expect(@trigger).toHaveBeenCalled()
|
|
|
|
it "should select both items if neither the start row or the end row are selected", ->
|
|
current = @items[4]
|
|
next = @items[5]
|
|
@selection.walk({current, next})
|
|
expect(@selection.ids()).toEqual(['2', '4', '5'])
|
|
|
|
it "should select only one item if either current or next is null or undefined", ->
|
|
current = null
|
|
next = @items[5]
|
|
@selection.walk({current, next})
|
|
expect(@selection.ids()).toEqual(['2', '5'])
|
|
|
|
next = null
|
|
current = @items[7]
|
|
@selection.walk({current, next})
|
|
expect(@selection.ids()).toEqual(['2', '5', '7'])
|
|
|
|
describe "when the `next` item is a step backwards in the selection history", ->
|
|
it "should deselect the current item", ->
|
|
@selection.set([@items[2], @items[3], @items[4], @items[5]])
|
|
current = @items[5]
|
|
next = @items[4]
|
|
@selection.walk({current, next})
|
|
expect(@selection.ids()).toEqual(['2', '3', '4'])
|
|
|
|
describe "otherwise", ->
|
|
it "should select the next item", ->
|
|
@selection.set([@items[2], @items[3], @items[4], @items[5]])
|
|
current = @items[5]
|
|
next = @items[6]
|
|
@selection.walk({current, next})
|
|
expect(@selection.ids()).toEqual(['2', '3', '4', '5', '6'])
|
|
|
|
describe "if the item was already selected", ->
|
|
it "should re-order the selection array so the selection still represents selection history", ->
|
|
@selection.set([@items[5], @items[8], @items[7], @items[6]])
|
|
expect(@selection.ids()).toEqual(['5', '8', '7', '6'])
|
|
|
|
current = @items[6]
|
|
next = @items[5]
|
|
@selection.walk({current, next})
|
|
expect(@selection.ids()).toEqual(['8', '7', '6', '5'])
|