Mailspring/spec/list-selection-spec.coffee
Ben Gotow 8b3f7f0578 feat(outbox): Sending status now appears beside drafts
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
2016-02-04 14:14:24 -08:00

209 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'])