mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-01-29 03:17:47 +08:00
3a947ccf54
Summary: Give UpdateThreadsTask a description method for strings like "Marked as read" Give ChangeLabelsTask a better description method that returns shorter strings and is specific when possible Give ChangeFolderTask a more specific description and don't assume folderOrId will always be a folder Make it so that passive "mark as read" from the message store isn't undoable Give the base class a description method that names the object Change UndoRedo component CSS a bit: - Use "inline-flexbox" with a max-width so that we can define shrinking rules on the label and Undo button (label gets ellipsis, button does not shrink at all) - Avoid left:39%, since it assumed that the undo-redo element would be 22% of the width of the thread list, which wasn't always true. Instead, make the `undo-redo-manager` container "text-align:center", so the `undo-redo` div is always centered within it. - Add `cursor:default` so that the user sees the pointer, not the text insertion cursor when hovering over "Undo" - Add overflow / text-overflow so that if the message is ever too long, the user sees `...` ellipsis properly. Test Plan: Run a few new tests Reviewers: evan, ethanb Reviewed By: ethanb Differential Revision: https://phab.nylas.com/D1830
281 lines
12 KiB
CoffeeScript
281 lines
12 KiB
CoffeeScript
_ = require 'underscore'
|
|
Label = require '../../src/flux/models/label'
|
|
Thread = require '../../src/flux/models/thread'
|
|
Message = require '../../src/flux/models/message'
|
|
Actions = require '../../src/flux/actions'
|
|
NylasAPI = require '../../src/flux/nylas-api'
|
|
DatabaseStore = require '../../src/flux/stores/database-store'
|
|
ChangeLabelsTask = require '../../src/flux/tasks/change-labels-task'
|
|
|
|
{APIError} = require '../../src/flux/errors'
|
|
{Utils} = require '../../src/flux/models/utils'
|
|
|
|
testLabels = {}
|
|
testThreads = {}
|
|
testMessages = {}
|
|
|
|
describe "ChangeLabelsTask", ->
|
|
beforeEach ->
|
|
spyOn(DatabaseStore, 'persistModel').andCallFake -> Promise.resolve()
|
|
spyOn(DatabaseStore, 'persistModels').andCallFake -> Promise.resolve()
|
|
spyOn(DatabaseStore, 'find').andCallFake (klass, id) =>
|
|
if klass is Thread
|
|
Promise.resolve(testThreads[id])
|
|
else if klass is Message
|
|
Promise.resolve(testMessages[id])
|
|
else if klass is Label
|
|
Promise.resolve(testLabels[id])
|
|
else
|
|
throw new Error("Not stubbed!")
|
|
|
|
spyOn(DatabaseStore, 'findAll').andCallFake (klass, finder) =>
|
|
if klass is Message
|
|
Promise.resolve(_.values(testMessages))
|
|
else if klass is Thread
|
|
Promise.resolve(_.values(testThreads))
|
|
else if klass is Label
|
|
Promise.resolve(_.values(testLabels))
|
|
else
|
|
throw new Error("Not stubbed!")
|
|
|
|
testLabels = @testLabels =
|
|
"l1": new Label({name: 'inbox', id: 'l1', displayName: "INBOX"}),
|
|
"l2": new Label({name: 'drafts', id: 'l2', displayName: "MyDrafts"})
|
|
"l3": new Label({name: null, id: 'l3', displayName: "My Label"})
|
|
|
|
testThreads = @testThreads =
|
|
't1': new Thread(id: 't1', labels: [@testLabels['l1']])
|
|
't2': new Thread(id: 't2', labels: _.values(@testLabels))
|
|
't3': new Thread(id: 't3', labels: [@testLabels['l2'], @testLabels['l3']])
|
|
|
|
testMessages = @testMessages =
|
|
'm1': new Message(id: 'm1', labels: [@testLabels['l1']])
|
|
'm2': new Message(id: 'm2', labels: _.values(@testLabels))
|
|
'm3': new Message(id: 'm3', labels: [@testLabels['l2'], @testLabels['l3']])
|
|
|
|
@basicThreadTask = new ChangeLabelsTask
|
|
labelsToAdd: ["l1", "l2"]
|
|
labelsToRemove: ["l3"]
|
|
threadIds: ['t1']
|
|
|
|
@basicMessageTask = new ChangeLabelsTask
|
|
labelsToAdd: ["l1", "l2"]
|
|
labelsToRemove: ["l3"]
|
|
messageIds: ['m1']
|
|
|
|
describe "description", ->
|
|
it "should include the name of the added label if it's the only mutation and it was provided as an object", ->
|
|
task = new ChangeLabelsTask(labelsToAdd: ["l1"], labelsToRemove: [], threadIds: ['t1'])
|
|
expect(task.description()).toEqual("Changed labels on 1 thread")
|
|
task = new ChangeLabelsTask(labelsToAdd: [new Label(id: 'l1', displayName: 'LABEL')], labelsToRemove: [], threadIds: ['t1'])
|
|
expect(task.description()).toEqual("Added LABEL to 1 thread")
|
|
task = new ChangeLabelsTask(labelsToAdd: [new Label(id: 'l1', displayName: 'LABEL')], labelsToRemove: ['l2'], threadIds: ['t1'])
|
|
expect(task.description()).toEqual("Changed labels on 1 thread")
|
|
|
|
it "should include the name of the removed label if it's the only mutation and it was provided as an object", ->
|
|
task = new ChangeLabelsTask(labelsToAdd: [], labelsToRemove: ["l1"], threadIds: ['t1'])
|
|
expect(task.description()).toEqual("Changed labels on 1 thread")
|
|
task = new ChangeLabelsTask(labelsToAdd: [], labelsToRemove: [new Label(id: 'l1', displayName: 'LABEL')], threadIds: ['t1'])
|
|
expect(task.description()).toEqual("Removed LABEL from 1 thread")
|
|
task = new ChangeLabelsTask(labelsToAdd: ['l2'], labelsToRemove: [new Label(id: 'l1', displayName: 'LABEL')], threadIds: ['t1'])
|
|
expect(task.description()).toEqual("Changed labels on 1 thread")
|
|
|
|
it "should pluralize properly", ->
|
|
task = new ChangeLabelsTask(labelsToAdd: ["l2"], labelsToRemove: ["l1"], threadIds: ['t1', 't2', 't3'])
|
|
expect(task.description()).toEqual("Changed labels on 3 threads")
|
|
|
|
describe "shouldWaitForTask", ->
|
|
it "should return true if another, older ChangeLabelsTask involves the same threads", ->
|
|
a = new ChangeLabelsTask(threadIds: ['t1', 't2', 't3'])
|
|
a.creationDate = new Date(1000)
|
|
b = new ChangeLabelsTask(threadIds: ['t3', 't4', 't7'])
|
|
b.creationDate = new Date(2000)
|
|
c = new ChangeLabelsTask(threadIds: ['t0', 't7'])
|
|
c.creationDate = new Date(3000)
|
|
expect(a.shouldWaitForTask(b)).toEqual(false)
|
|
expect(a.shouldWaitForTask(c)).toEqual(false)
|
|
expect(b.shouldWaitForTask(a)).toEqual(true)
|
|
expect(c.shouldWaitForTask(a)).toEqual(false)
|
|
expect(c.shouldWaitForTask(b)).toEqual(true)
|
|
|
|
describe "performLocal", ->
|
|
it "should throw an exception if task has not been given a thread", ->
|
|
badTasks = [
|
|
new ChangeLabelsTask(),
|
|
new ChangeLabelsTask(threadIds: [123]),
|
|
new ChangeLabelsTask(threadIds: [123], messageIds: ["foo"]),
|
|
new ChangeLabelsTask(threadIds: "Thread"),
|
|
]
|
|
goodTasks = [
|
|
new ChangeLabelsTask(
|
|
labelsToAdd: ['l2']
|
|
labelsToRemove: ['l1']
|
|
threadIds: ['t1']
|
|
)
|
|
new ChangeLabelsTask(
|
|
labelsToAdd: ['l2']
|
|
labelsToRemove: []
|
|
messageIds: ['m1']
|
|
)
|
|
]
|
|
caught = []
|
|
succeeded = []
|
|
|
|
runs ->
|
|
[].concat(badTasks, goodTasks).forEach (task) ->
|
|
task.performLocal()
|
|
.then -> succeeded.push(task)
|
|
.catch (err) -> caught.push(task)
|
|
waitsFor ->
|
|
succeeded.length + caught.length == 6
|
|
runs ->
|
|
expect(caught.length).toEqual(badTasks.length)
|
|
expect(succeeded.length).toEqual(goodTasks.length)
|
|
|
|
it 'finds all of the labels to add by id', ->
|
|
waitsForPromise =>
|
|
@basicThreadTask.collectCategories().then (categories) =>
|
|
expect(categories.labelsToAdd).toEqual [@testLabels['l1'], @testLabels['l2']]
|
|
expect(categories.labelsToRemove).toEqual [@testLabels['l3']]
|
|
|
|
it 'finds all of the labels to add by object', ->
|
|
task = new ChangeLabelsTask
|
|
labelsToAdd: [@testLabels['l1'], @testLabels['l2']]
|
|
labelsToRemove: []
|
|
threadIds: ['t1']
|
|
|
|
waitsForPromise =>
|
|
task.collectCategories().then (categories) =>
|
|
expect(categories.labelsToAdd).toEqual [@testLabels['l1'], @testLabels['l2']]
|
|
expect(categories.labelsToRemove).toEqual []
|
|
|
|
it 'increments optimistic changes', ->
|
|
spyOn(@basicThreadTask, "localUpdateThread").andReturn Promise.resolve()
|
|
spyOn(NylasAPI, "incrementOptimisticChangeCount")
|
|
@basicThreadTask.performLocal().then ->
|
|
expect(NylasAPI.incrementOptimisticChangeCount)
|
|
.toHaveBeenCalledWith(Thread, 't1')
|
|
|
|
it 'decrements optimistic changes if reverting', ->
|
|
spyOn(@basicThreadTask, "localUpdateThread").andReturn Promise.resolve()
|
|
spyOn(NylasAPI, "decrementOptimisticChangeCount")
|
|
@basicThreadTask.performLocal(reverting: true).then ->
|
|
expect(NylasAPI.decrementOptimisticChangeCount)
|
|
.toHaveBeenCalledWith(Thread, 't1')
|
|
|
|
describe 'when creating a _newLabelSet', ->
|
|
it 'properly adds labels', ->
|
|
t1 = @testThreads['t1']
|
|
toAdd = [@testLabels['l1'], @testLabels['l2']]
|
|
out = @basicThreadTask._newLabelSet(t1, labelsToAdd: toAdd)
|
|
expect(out).toEqual toAdd
|
|
|
|
it 'properly removes labels', ->
|
|
t3 = @testThreads['t3']
|
|
toRemove = [@testLabels['l1'], @testLabels['l2']]
|
|
out = @basicThreadTask._newLabelSet(t3, labelsToRemove: toRemove)
|
|
expect(out).toEqual [@testLabels['l3']]
|
|
|
|
it 'properly adds and removes labels', ->
|
|
t1 = @testThreads['t1']
|
|
toAdd = [@testLabels['l1'], @testLabels['l2']]
|
|
toRemove = [@testLabels['l2'], @testLabels['l3']]
|
|
out = @basicThreadTask._newLabelSet(t1, labelsToAdd: toAdd, labelsToRemove: toRemove)
|
|
expect(out).toEqual [@testLabels['l1']]
|
|
|
|
it 'updates a thread with the new labels', ->
|
|
expectedLabels = [@testLabels['l1'], @testLabels['l2']]
|
|
@basicThreadTask.performLocal().then ->
|
|
thread = DatabaseStore.persistModel.calls[0].args[0]
|
|
expect(thread.labels).toEqual expectedLabels
|
|
|
|
it "updates a thread's messages with the new labels", ->
|
|
# Our stub of DatabaseStore.findAll ignores the scoping parameter.
|
|
# We simply return all messages.
|
|
|
|
expectedLabels = [@testLabels['l1'], @testLabels['l2']]
|
|
@basicThreadTask.performLocal().then ->
|
|
messages = DatabaseStore.persistModels.calls[0].args[0]
|
|
expect(messages.length).toBe 3
|
|
for message in messages
|
|
expect(message.labels).toEqual expectedLabels
|
|
|
|
it "doesn't botter updating the message if it already has the correct labels", ->
|
|
@testMessages['m4'] =
|
|
new Message(id: 'm4', labels: [@testLabels['l1'], @testLabels['l2']])
|
|
@testMessages['m5'] =
|
|
new Message(id: 'm5', labels: [])
|
|
|
|
expectedLabels = [@testLabels['l1'], @testLabels['l2']]
|
|
@basicThreadTask.performLocal().then =>
|
|
messages = DatabaseStore.persistModels.calls[0].args[0]
|
|
expect(messages.length).toBe 4
|
|
for message in messages
|
|
expect(message.labels).toEqual expectedLabels
|
|
expect(@testMessages['m4'] not in messages).toBe true
|
|
|
|
it 'updates a message with the new labels on a message task', ->
|
|
expectedLabels = [@testLabels['l1'], @testLabels['l2']]
|
|
@basicMessageTask.performLocal().then ->
|
|
thread = DatabaseStore.persistModel.calls[0].args[0]
|
|
expect(thread.labels).toEqual expectedLabels
|
|
|
|
it 'saves the new label set to an instance variable on the task so performRemote can access it later', ->
|
|
expectedLabels = [@testLabels['l1'], @testLabels['l2']]
|
|
@basicThreadTask.performLocal().then =>
|
|
expect(@basicThreadTask._newLabels['t1']).toEqual expectedLabels
|
|
|
|
describe 'performRemote', ->
|
|
beforeEach ->
|
|
spyOn(NylasAPI, "makeRequest").andCallFake (options) ->
|
|
options.beforeProcessing?(options.body)
|
|
return Promise.resolve()
|
|
|
|
@multiThreadTask = new ChangeLabelsTask
|
|
labelsToAdd: ["l1", "l2"]
|
|
labelsToRemove: ["l3"]
|
|
threadIds: ['t1', 't2']
|
|
|
|
@multiMessageTask = new ChangeLabelsTask
|
|
labelsToAdd: ["l1", "l2"]
|
|
labelsToRemove: ["l3"]
|
|
messageIds: ['m1', 'm2']
|
|
|
|
expectedLabels = [@testLabels['l1'], @testLabels['l2']]
|
|
@multiThreadTask._newLabels['t1'] = expectedLabels
|
|
@multiThreadTask._newLabels['t2'] = expectedLabels
|
|
@multiMessageTask._newLabels['m1'] = expectedLabels
|
|
@multiMessageTask._newLabels['m2'] = expectedLabels
|
|
|
|
it 'makes a new request object for each object', ->
|
|
@multiThreadTask.performRemote().then ->
|
|
expect(NylasAPI.makeRequest.calls.length).toBe 2
|
|
|
|
it 'decrements the optimistic change count on each request', ->
|
|
spyOn(NylasAPI, "decrementOptimisticChangeCount")
|
|
@multiThreadTask.performRemote().then ->
|
|
klass = NylasAPI.decrementOptimisticChangeCount.calls[0].args[0]
|
|
expect(NylasAPI.decrementOptimisticChangeCount.calls.length).toBe 2
|
|
expect(klass).toBe Thread
|
|
|
|
it 'decrements the optimistic change for messages too', ->
|
|
spyOn(NylasAPI, "decrementOptimisticChangeCount")
|
|
@multiMessageTask.performRemote().then ->
|
|
klass = NylasAPI.decrementOptimisticChangeCount.calls[0].args[0]
|
|
expect(NylasAPI.decrementOptimisticChangeCount.calls.length).toBe 2
|
|
expect(klass).toBe Message
|
|
|
|
it 'properly passes the label IDs to the body', ->
|
|
@multiThreadTask.performRemote().then ->
|
|
opts = NylasAPI.makeRequest.calls[0].args[0]
|
|
expect(opts.body).toEqual labels: ['l1', 'l2']
|
|
|
|
it 'gets the correct endpoint for the thread tasks', ->
|
|
@multiThreadTask.performRemote().then ->
|
|
opts = NylasAPI.makeRequest.calls[0].args[0]
|
|
expect(opts.path).toEqual "/n/nsid/threads/t1"
|
|
|
|
it 'gets the correct endpoint for the message tasks', ->
|
|
@multiMessageTask.performRemote().then ->
|
|
opts = NylasAPI.makeRequest.calls[0].args[0]
|
|
expect(opts.path).toEqual "/n/nsid/messages/m1"
|