2015-05-20 07:06:59 +08:00
_ = require ' underscore '
2016-03-02 09:24:37 +08:00
fs = require ' fs '
2015-12-18 03:46:05 +08:00
{ APIError ,
Actions ,
DatabaseStore ,
DatabaseTransaction ,
Message ,
2016-01-27 11:12:46 +08:00
Contact ,
2015-12-18 03:46:05 +08:00
Task ,
TaskQueue ,
SendDraftTask ,
SyncbackDraftTask ,
NylasAPI ,
SoundRegistry } = require ' nylas-exports '
2015-12-22 03:50:52 +08:00
DBt = DatabaseTransaction . prototype
fix(drafts): Various improvements and fixes to drafts, draft state management
Summary:
This diff contains a few major changes:
1. Scribe is no longer used for the text editor. It's just a plain contenteditable region. The toolbar items (bold, italic, underline) still work. Scribe was causing React inconcistency issues in the following scenario:
- View thread with draft, edit draft
- Move to another thread
- Move back to thread with draft
- Move to another thread. Notice that one or more messages from thread with draft are still there.
There may be a way to fix this, but I tried for hours and there are Github Issues open on it's repository asking for React compatibility, so it may be fixed soon. For now contenteditable is working great.
2. Action.saveDraft() is no longer debounced in the DraftStore. Instead, firing that action causes the save to happen immediately, and the DraftStoreProxy has a new "DraftChangeSet" class which is responsbile for batching saves as the user interacts with the ComposerView. There are a couple big wins here:
- In the future, we may want to be able to call Action.saveDraft() in other situations and it should behave like a normal action. We may also want to expose the DraftStoreProxy as an easy way of backing interactive draft UI.
- Previously, when you added a contact to To/CC/BCC, this happened:
<input> -> Action.saveDraft -> (delay!!) -> Database -> DraftStore -> DraftStoreProxy -> View Updates
Increasing the delay to something reasonable like 200msec meant there was 200msec of lag before you saw the new view state.
To fix this, I created a new class called DraftChangeSet which is responsible for accumulating changes as they're made and firing Action.saveDraft. "Adding" a change to the change set also causes the Draft provided by the DraftStoreProxy to change immediately (the changes are a temporary layer on top of the database object). This means no delay while changes are being applied. There's a better explanation in the source!
This diff includes a few minor fixes as well:
1. Draft.state is gone—use Message.object = draft instead
2. String model attributes should never be null
3. Pre-send checks that can cancel draft send
4. Put the entire curl history and task queue into feedback reports
5. Cache localIds for extra speed
6. Move us up to latest React
Test Plan: No new tests - once we lock down this new design I'll write tests for the DraftChangeSet
Reviewers: evan
Reviewed By: evan
Differential Revision: https://review.inboxapp.com/D1125
2015-02-04 08:24:31 +08:00
describe " SendDraftTask " , ->
2015-12-22 03:50:52 +08:00
2016-03-08 12:14:58 +08:00
describe " isDependentOnTask " , ->
2015-12-22 03:50:52 +08:00
it " is not dependent on any pending SyncbackDraftTasks " , ->
2016-03-02 09:24:37 +08:00
draftA = new Message
fix(drafts): Various improvements and fixes to drafts, draft state management
Summary:
This diff contains a few major changes:
1. Scribe is no longer used for the text editor. It's just a plain contenteditable region. The toolbar items (bold, italic, underline) still work. Scribe was causing React inconcistency issues in the following scenario:
- View thread with draft, edit draft
- Move to another thread
- Move back to thread with draft
- Move to another thread. Notice that one or more messages from thread with draft are still there.
There may be a way to fix this, but I tried for hours and there are Github Issues open on it's repository asking for React compatibility, so it may be fixed soon. For now contenteditable is working great.
2. Action.saveDraft() is no longer debounced in the DraftStore. Instead, firing that action causes the save to happen immediately, and the DraftStoreProxy has a new "DraftChangeSet" class which is responsbile for batching saves as the user interacts with the ComposerView. There are a couple big wins here:
- In the future, we may want to be able to call Action.saveDraft() in other situations and it should behave like a normal action. We may also want to expose the DraftStoreProxy as an easy way of backing interactive draft UI.
- Previously, when you added a contact to To/CC/BCC, this happened:
<input> -> Action.saveDraft -> (delay!!) -> Database -> DraftStore -> DraftStoreProxy -> View Updates
Increasing the delay to something reasonable like 200msec meant there was 200msec of lag before you saw the new view state.
To fix this, I created a new class called DraftChangeSet which is responsible for accumulating changes as they're made and firing Action.saveDraft. "Adding" a change to the change set also causes the Draft provided by the DraftStoreProxy to change immediately (the changes are a temporary layer on top of the database object). This means no delay while changes are being applied. There's a better explanation in the source!
This diff includes a few minor fixes as well:
1. Draft.state is gone—use Message.object = draft instead
2. String model attributes should never be null
3. Pre-send checks that can cancel draft send
4. Put the entire curl history and task queue into feedback reports
5. Cache localIds for extra speed
6. Move us up to latest React
Test Plan: No new tests - once we lock down this new design I'll write tests for the DraftChangeSet
Reviewers: evan
Reviewed By: evan
Differential Revision: https://review.inboxapp.com/D1125
2015-02-04 08:24:31 +08:00
version: ' 1 '
2015-12-22 03:50:52 +08:00
clientId: ' localid-A '
serverId: ' 1233123AEDF1 '
feat(accounts): Kill namespaces, long live accounts
Summary:
This diff replaces the Namespace object with the Account object, and changes all references to namespace_id => account_id, etc. The endpoints are now `/threads` instead of `/n/<id>/threads`.
This diff also adds preliminary support for multiple accounts. When you log in, we now log you in to all the attached accounts on edgehill server. From the preferences panel, you can auth with / unlink additional accounts. Shockingly, this all seems to pretty much work.
When replying to a thread, you cannot switch from addresses. However, when creating a new message in a popout composer, you can change the from address and the SaveDraftTask will delete/re-root the draft on the new account.
Search bar doesn't need to do full refresh on clear if it never committed
Allow drafts to be switched to a different account when not in reply to an existing thread
Fix edge case where ChangeMailTask throws exception if no models are modified during performLocal
Show many dots for many accounts in long polling status bar
add/remove accounts from prefs
Spec fixes!
Test Plan: Run tests, none broken!
Reviewers: evan, dillon
Reviewed By: evan, dillon
Differential Revision: https://phab.nylas.com/D1928
2015-08-22 06:29:58 +08:00
accountId: ' A12ADE '
fix(drafts): Various improvements and fixes to drafts, draft state management
Summary:
This diff contains a few major changes:
1. Scribe is no longer used for the text editor. It's just a plain contenteditable region. The toolbar items (bold, italic, underline) still work. Scribe was causing React inconcistency issues in the following scenario:
- View thread with draft, edit draft
- Move to another thread
- Move back to thread with draft
- Move to another thread. Notice that one or more messages from thread with draft are still there.
There may be a way to fix this, but I tried for hours and there are Github Issues open on it's repository asking for React compatibility, so it may be fixed soon. For now contenteditable is working great.
2. Action.saveDraft() is no longer debounced in the DraftStore. Instead, firing that action causes the save to happen immediately, and the DraftStoreProxy has a new "DraftChangeSet" class which is responsbile for batching saves as the user interacts with the ComposerView. There are a couple big wins here:
- In the future, we may want to be able to call Action.saveDraft() in other situations and it should behave like a normal action. We may also want to expose the DraftStoreProxy as an easy way of backing interactive draft UI.
- Previously, when you added a contact to To/CC/BCC, this happened:
<input> -> Action.saveDraft -> (delay!!) -> Database -> DraftStore -> DraftStoreProxy -> View Updates
Increasing the delay to something reasonable like 200msec meant there was 200msec of lag before you saw the new view state.
To fix this, I created a new class called DraftChangeSet which is responsible for accumulating changes as they're made and firing Action.saveDraft. "Adding" a change to the change set also causes the Draft provided by the DraftStoreProxy to change immediately (the changes are a temporary layer on top of the database object). This means no delay while changes are being applied. There's a better explanation in the source!
This diff includes a few minor fixes as well:
1. Draft.state is gone—use Message.object = draft instead
2. String model attributes should never be null
3. Pre-send checks that can cancel draft send
4. Put the entire curl history and task queue into feedback reports
5. Cache localIds for extra speed
6. Move us up to latest React
Test Plan: No new tests - once we lock down this new design I'll write tests for the DraftChangeSet
Reviewers: evan
Reviewed By: evan
Differential Revision: https://review.inboxapp.com/D1125
2015-02-04 08:24:31 +08:00
subject: ' New Draft '
2015-02-07 06:41:59 +08:00
draft: true
fix(drafts): Various improvements and fixes to drafts, draft state management
Summary:
This diff contains a few major changes:
1. Scribe is no longer used for the text editor. It's just a plain contenteditable region. The toolbar items (bold, italic, underline) still work. Scribe was causing React inconcistency issues in the following scenario:
- View thread with draft, edit draft
- Move to another thread
- Move back to thread with draft
- Move to another thread. Notice that one or more messages from thread with draft are still there.
There may be a way to fix this, but I tried for hours and there are Github Issues open on it's repository asking for React compatibility, so it may be fixed soon. For now contenteditable is working great.
2. Action.saveDraft() is no longer debounced in the DraftStore. Instead, firing that action causes the save to happen immediately, and the DraftStoreProxy has a new "DraftChangeSet" class which is responsbile for batching saves as the user interacts with the ComposerView. There are a couple big wins here:
- In the future, we may want to be able to call Action.saveDraft() in other situations and it should behave like a normal action. We may also want to expose the DraftStoreProxy as an easy way of backing interactive draft UI.
- Previously, when you added a contact to To/CC/BCC, this happened:
<input> -> Action.saveDraft -> (delay!!) -> Database -> DraftStore -> DraftStoreProxy -> View Updates
Increasing the delay to something reasonable like 200msec meant there was 200msec of lag before you saw the new view state.
To fix this, I created a new class called DraftChangeSet which is responsible for accumulating changes as they're made and firing Action.saveDraft. "Adding" a change to the change set also causes the Draft provided by the DraftStoreProxy to change immediately (the changes are a temporary layer on top of the database object). This means no delay while changes are being applied. There's a better explanation in the source!
This diff includes a few minor fixes as well:
1. Draft.state is gone—use Message.object = draft instead
2. String model attributes should never be null
3. Pre-send checks that can cancel draft send
4. Put the entire curl history and task queue into feedback reports
5. Cache localIds for extra speed
6. Move us up to latest React
Test Plan: No new tests - once we lock down this new design I'll write tests for the DraftChangeSet
Reviewers: evan
Reviewed By: evan
Differential Revision: https://review.inboxapp.com/D1125
2015-02-04 08:24:31 +08:00
to:
name: ' Dummy '
2015-05-15 08:08:30 +08:00
email: ' dummy@nylas.com '
fix(drafts): Various improvements and fixes to drafts, draft state management
Summary:
This diff contains a few major changes:
1. Scribe is no longer used for the text editor. It's just a plain contenteditable region. The toolbar items (bold, italic, underline) still work. Scribe was causing React inconcistency issues in the following scenario:
- View thread with draft, edit draft
- Move to another thread
- Move back to thread with draft
- Move to another thread. Notice that one or more messages from thread with draft are still there.
There may be a way to fix this, but I tried for hours and there are Github Issues open on it's repository asking for React compatibility, so it may be fixed soon. For now contenteditable is working great.
2. Action.saveDraft() is no longer debounced in the DraftStore. Instead, firing that action causes the save to happen immediately, and the DraftStoreProxy has a new "DraftChangeSet" class which is responsbile for batching saves as the user interacts with the ComposerView. There are a couple big wins here:
- In the future, we may want to be able to call Action.saveDraft() in other situations and it should behave like a normal action. We may also want to expose the DraftStoreProxy as an easy way of backing interactive draft UI.
- Previously, when you added a contact to To/CC/BCC, this happened:
<input> -> Action.saveDraft -> (delay!!) -> Database -> DraftStore -> DraftStoreProxy -> View Updates
Increasing the delay to something reasonable like 200msec meant there was 200msec of lag before you saw the new view state.
To fix this, I created a new class called DraftChangeSet which is responsible for accumulating changes as they're made and firing Action.saveDraft. "Adding" a change to the change set also causes the Draft provided by the DraftStoreProxy to change immediately (the changes are a temporary layer on top of the database object). This means no delay while changes are being applied. There's a better explanation in the source!
This diff includes a few minor fixes as well:
1. Draft.state is gone—use Message.object = draft instead
2. String model attributes should never be null
3. Pre-send checks that can cancel draft send
4. Put the entire curl history and task queue into feedback reports
5. Cache localIds for extra speed
6. Move us up to latest React
Test Plan: No new tests - once we lock down this new design I'll write tests for the DraftChangeSet
Reviewers: evan
Reviewed By: evan
Differential Revision: https://review.inboxapp.com/D1125
2015-02-04 08:24:31 +08:00
2016-03-02 09:24:37 +08:00
saveA = new SyncbackDraftTask ( draftA , [ ] )
sendA = new SendDraftTask ( draftA , [ ] )
fix(drafts): Various improvements and fixes to drafts, draft state management
Summary:
This diff contains a few major changes:
1. Scribe is no longer used for the text editor. It's just a plain contenteditable region. The toolbar items (bold, italic, underline) still work. Scribe was causing React inconcistency issues in the following scenario:
- View thread with draft, edit draft
- Move to another thread
- Move back to thread with draft
- Move to another thread. Notice that one or more messages from thread with draft are still there.
There may be a way to fix this, but I tried for hours and there are Github Issues open on it's repository asking for React compatibility, so it may be fixed soon. For now contenteditable is working great.
2. Action.saveDraft() is no longer debounced in the DraftStore. Instead, firing that action causes the save to happen immediately, and the DraftStoreProxy has a new "DraftChangeSet" class which is responsbile for batching saves as the user interacts with the ComposerView. There are a couple big wins here:
- In the future, we may want to be able to call Action.saveDraft() in other situations and it should behave like a normal action. We may also want to expose the DraftStoreProxy as an easy way of backing interactive draft UI.
- Previously, when you added a contact to To/CC/BCC, this happened:
<input> -> Action.saveDraft -> (delay!!) -> Database -> DraftStore -> DraftStoreProxy -> View Updates
Increasing the delay to something reasonable like 200msec meant there was 200msec of lag before you saw the new view state.
To fix this, I created a new class called DraftChangeSet which is responsible for accumulating changes as they're made and firing Action.saveDraft. "Adding" a change to the change set also causes the Draft provided by the DraftStoreProxy to change immediately (the changes are a temporary layer on top of the database object). This means no delay while changes are being applied. There's a better explanation in the source!
This diff includes a few minor fixes as well:
1. Draft.state is gone—use Message.object = draft instead
2. String model attributes should never be null
3. Pre-send checks that can cancel draft send
4. Put the entire curl history and task queue into feedback reports
5. Cache localIds for extra speed
6. Move us up to latest React
Test Plan: No new tests - once we lock down this new design I'll write tests for the DraftChangeSet
Reviewers: evan
Reviewed By: evan
Differential Revision: https://review.inboxapp.com/D1125
2015-02-04 08:24:31 +08:00
2016-03-08 12:14:58 +08:00
expect ( sendA . isDependentOnTask ( saveA ) ) . toBe ( false )
fix(drafts): Various improvements and fixes to drafts, draft state management
Summary:
This diff contains a few major changes:
1. Scribe is no longer used for the text editor. It's just a plain contenteditable region. The toolbar items (bold, italic, underline) still work. Scribe was causing React inconcistency issues in the following scenario:
- View thread with draft, edit draft
- Move to another thread
- Move back to thread with draft
- Move to another thread. Notice that one or more messages from thread with draft are still there.
There may be a way to fix this, but I tried for hours and there are Github Issues open on it's repository asking for React compatibility, so it may be fixed soon. For now contenteditable is working great.
2. Action.saveDraft() is no longer debounced in the DraftStore. Instead, firing that action causes the save to happen immediately, and the DraftStoreProxy has a new "DraftChangeSet" class which is responsbile for batching saves as the user interacts with the ComposerView. There are a couple big wins here:
- In the future, we may want to be able to call Action.saveDraft() in other situations and it should behave like a normal action. We may also want to expose the DraftStoreProxy as an easy way of backing interactive draft UI.
- Previously, when you added a contact to To/CC/BCC, this happened:
<input> -> Action.saveDraft -> (delay!!) -> Database -> DraftStore -> DraftStoreProxy -> View Updates
Increasing the delay to something reasonable like 200msec meant there was 200msec of lag before you saw the new view state.
To fix this, I created a new class called DraftChangeSet which is responsible for accumulating changes as they're made and firing Action.saveDraft. "Adding" a change to the change set also causes the Draft provided by the DraftStoreProxy to change immediately (the changes are a temporary layer on top of the database object). This means no delay while changes are being applied. There's a better explanation in the source!
This diff includes a few minor fixes as well:
1. Draft.state is gone—use Message.object = draft instead
2. String model attributes should never be null
3. Pre-send checks that can cancel draft send
4. Put the entire curl history and task queue into feedback reports
5. Cache localIds for extra speed
6. Move us up to latest React
Test Plan: No new tests - once we lock down this new design I'll write tests for the DraftChangeSet
Reviewers: evan
Reviewed By: evan
Differential Revision: https://review.inboxapp.com/D1125
2015-02-04 08:24:31 +08:00
describe " performLocal " , ->
2016-03-02 09:24:37 +08:00
it " rejects if we we don ' t pass a draft " , ->
2015-12-22 03:50:52 +08:00
badTask = new SendDraftTask ( )
2016-03-02 09:24:37 +08:00
badTask . performLocal ( ) . then ->
throw new Error ( " Shouldn ' t succeed " )
. catch (err) ->
expect ( err . message ) . toBe " SendDraftTask - must be provided a draft. "
it " rejects if we we don ' t pass uploads " , ->
message = new Message ( from: [ new Contact ( email: TEST_ACCOUNT_EMAIL ) ] )
2016-01-28 16:48:45 +08:00
message.uploads = null
badTask = new SendDraftTask ( message )
2016-03-02 09:24:37 +08:00
badTask . performLocal ( ) . then ->
throw new Error ( " Shouldn ' t succeed " )
. catch (err) ->
expect ( err . message ) . toBe " SendDraftTask - must be provided an array of uploads. "
it " rejects if no from address is specified " , ->
badTask = new SendDraftTask ( new Message ( from: [ ] , uploads: [ ] ) )
badTask . performLocal ( ) . then ->
throw new Error ( " Shouldn ' t succeed " )
. catch (err) ->
expect ( err . message ) . toBe " SendDraftTask - you must populate `from` before sending. "
it " rejects if the from address does not map to any account " , ->
badTask = new SendDraftTask ( new Message ( from: [ new Contact ( email: ' not-configured@nylas.com ' ) ] , uploads: null ) )
badTask . performLocal ( ) . then ->
throw new Error ( " Shouldn ' t succeed " )
. catch (err) ->
expect ( err . message ) . toBe " SendDraftTask - you can only send drafts from a configured account. "
fix(drafts): Various improvements and fixes to drafts, draft state management
Summary:
This diff contains a few major changes:
1. Scribe is no longer used for the text editor. It's just a plain contenteditable region. The toolbar items (bold, italic, underline) still work. Scribe was causing React inconcistency issues in the following scenario:
- View thread with draft, edit draft
- Move to another thread
- Move back to thread with draft
- Move to another thread. Notice that one or more messages from thread with draft are still there.
There may be a way to fix this, but I tried for hours and there are Github Issues open on it's repository asking for React compatibility, so it may be fixed soon. For now contenteditable is working great.
2. Action.saveDraft() is no longer debounced in the DraftStore. Instead, firing that action causes the save to happen immediately, and the DraftStoreProxy has a new "DraftChangeSet" class which is responsbile for batching saves as the user interacts with the ComposerView. There are a couple big wins here:
- In the future, we may want to be able to call Action.saveDraft() in other situations and it should behave like a normal action. We may also want to expose the DraftStoreProxy as an easy way of backing interactive draft UI.
- Previously, when you added a contact to To/CC/BCC, this happened:
<input> -> Action.saveDraft -> (delay!!) -> Database -> DraftStore -> DraftStoreProxy -> View Updates
Increasing the delay to something reasonable like 200msec meant there was 200msec of lag before you saw the new view state.
To fix this, I created a new class called DraftChangeSet which is responsible for accumulating changes as they're made and firing Action.saveDraft. "Adding" a change to the change set also causes the Draft provided by the DraftStoreProxy to change immediately (the changes are a temporary layer on top of the database object). This means no delay while changes are being applied. There's a better explanation in the source!
This diff includes a few minor fixes as well:
1. Draft.state is gone—use Message.object = draft instead
2. String model attributes should never be null
3. Pre-send checks that can cancel draft send
4. Put the entire curl history and task queue into feedback reports
5. Cache localIds for extra speed
6. Move us up to latest React
Test Plan: No new tests - once we lock down this new design I'll write tests for the DraftChangeSet
Reviewers: evan
Reviewed By: evan
Differential Revision: https://review.inboxapp.com/D1125
2015-02-04 08:24:31 +08:00
describe " performRemote " , ->
beforeEach ->
2015-12-22 03:50:52 +08:00
@response =
2015-08-29 04:06:21 +08:00
version: 2
2016-01-27 11:12:46 +08:00
id: ' 1233123AEDF1 '
account_id: TEST_ACCOUNT_ID
from: [ new Contact ( email: TEST_ACCOUNT_EMAIL ) ]
2015-08-29 04:06:21 +08:00
subject: ' New Draft '
body: ' hello world '
to:
name: ' Dummy '
email: ' dummy@nylas.com '
2015-05-15 08:34:29 +08:00
spyOn ( NylasAPI , ' makeRequest ' ) . andCallFake (options) =>
2015-12-22 03:50:52 +08:00
options . success ? ( @ response )
2016-03-02 09:24:37 +08:00
return Promise . resolve ( @ response )
2016-03-04 04:09:16 +08:00
spyOn ( NylasAPI , ' incrementRemoteChangeLock ' )
spyOn ( NylasAPI , ' decrementRemoteChangeLock ' )
2016-02-18 06:54:43 +08:00
spyOn ( DBt , ' unpersistModel ' ) . andReturn Promise . resolve ( )
spyOn ( DBt , ' persistModel ' ) . andReturn Promise . resolve ( )
2015-10-02 10:24:06 +08:00
spyOn ( SoundRegistry , " playSound " )
2015-03-13 05:48:56 +08:00
spyOn ( Actions , " postNotification " )
spyOn ( Actions , " sendDraftSuccess " )
2016-03-02 09:24:37 +08:00
spyOn ( Actions , " attachmentUploaded " )
spyOn ( fs , ' createReadStream ' ) . andReturn " stub "
# The tests below are invoked twice, once with a new @draft and one with a
# persisted @draft.
2015-12-22 03:50:52 +08:00
2016-01-27 11:12:46 +08:00
sharedTests = =>
2016-03-02 09:24:37 +08:00
it " should return Task.Status.Success " , ->
waitsForPromise =>
@ task . performLocal ( )
@ task . performRemote ( ) . then (status) ->
expect ( status ) . toBe Task . Status . Success
2015-12-22 03:50:52 +08:00
it " makes a send request with the correct data " , ->
2016-01-27 11:12:46 +08:00
waitsForPromise => @ task . _sendAndCreateMessage ( ) . then =>
2015-12-22 03:50:52 +08:00
expect ( NylasAPI . makeRequest ) . toHaveBeenCalled ( )
2016-03-02 09:24:37 +08:00
expect ( NylasAPI . makeRequest . callCount ) . toBe ( 1 )
options = NylasAPI . makeRequest . mostRecentCall . args [ 0 ]
expect ( options . path ) . toBe ( " /send " )
expect ( options . method ) . toBe ( ' POST ' )
expect ( options . accountId ) . toBe TEST_ACCOUNT_ID
expect ( options . body ) . toEqual @ draft . toJSON ( )
2015-12-22 03:50:52 +08:00
it " should pass returnsModel:false " , ->
2016-01-27 11:12:46 +08:00
waitsForPromise => @ task . _sendAndCreateMessage ( ) . then ->
2015-12-22 03:50:52 +08:00
expect ( NylasAPI . makeRequest . calls . length ) . toBe ( 1 )
options = NylasAPI . makeRequest . mostRecentCall . args [ 0 ]
expect ( options . returnsModel ) . toBe ( false )
2015-03-13 05:48:56 +08:00
2015-12-22 03:50:52 +08:00
it " should always send the draft body in the request body (joined attribute check) " , ->
waitsForPromise =>
2016-01-27 11:12:46 +08:00
@ task . _sendAndCreateMessage ( ) . then =>
2015-12-22 03:50:52 +08:00
expect ( NylasAPI . makeRequest . calls . length ) . toBe ( 1 )
options = NylasAPI . makeRequest . mostRecentCall . args [ 0 ]
expect ( options . body . body ) . toBe ( ' hello world ' )
2016-03-02 09:24:37 +08:00
describe " saving the sent message " , ->
it " should preserve the draft client id " , ->
waitsForPromise =>
@ task . _sendAndCreateMessage ( ) . then =>
expect ( DBt . persistModel ) . toHaveBeenCalled ( )
model = DBt . persistModel . mostRecentCall . args [ 0 ]
expect ( model . clientId ) . toEqual ( @ draft . clientId )
expect ( model . serverId ) . toEqual ( @ response . id )
expect ( model . draft ) . toEqual ( false )
it " should preserve metadata, but not version numbers " , ->
waitsForPromise =>
@ task . _sendAndCreateMessage ( ) . then =>
expect ( DBt . persistModel ) . toHaveBeenCalled ( )
model = DBt . persistModel . mostRecentCall . args [ 0 ]
fix(drafts): Various improvements and fixes to drafts, draft state management
Summary:
This diff contains a few major changes:
1. Scribe is no longer used for the text editor. It's just a plain contenteditable region. The toolbar items (bold, italic, underline) still work. Scribe was causing React inconcistency issues in the following scenario:
- View thread with draft, edit draft
- Move to another thread
- Move back to thread with draft
- Move to another thread. Notice that one or more messages from thread with draft are still there.
There may be a way to fix this, but I tried for hours and there are Github Issues open on it's repository asking for React compatibility, so it may be fixed soon. For now contenteditable is working great.
2. Action.saveDraft() is no longer debounced in the DraftStore. Instead, firing that action causes the save to happen immediately, and the DraftStoreProxy has a new "DraftChangeSet" class which is responsbile for batching saves as the user interacts with the ComposerView. There are a couple big wins here:
- In the future, we may want to be able to call Action.saveDraft() in other situations and it should behave like a normal action. We may also want to expose the DraftStoreProxy as an easy way of backing interactive draft UI.
- Previously, when you added a contact to To/CC/BCC, this happened:
<input> -> Action.saveDraft -> (delay!!) -> Database -> DraftStore -> DraftStoreProxy -> View Updates
Increasing the delay to something reasonable like 200msec meant there was 200msec of lag before you saw the new view state.
To fix this, I created a new class called DraftChangeSet which is responsible for accumulating changes as they're made and firing Action.saveDraft. "Adding" a change to the change set also causes the Draft provided by the DraftStoreProxy to change immediately (the changes are a temporary layer on top of the database object). This means no delay while changes are being applied. There's a better explanation in the source!
This diff includes a few minor fixes as well:
1. Draft.state is gone—use Message.object = draft instead
2. String model attributes should never be null
3. Pre-send checks that can cancel draft send
4. Put the entire curl history and task queue into feedback reports
5. Cache localIds for extra speed
6. Move us up to latest React
Test Plan: No new tests - once we lock down this new design I'll write tests for the DraftChangeSet
Reviewers: evan
Reviewed By: evan
Differential Revision: https://review.inboxapp.com/D1125
2015-02-04 08:24:31 +08:00
2016-03-02 09:24:37 +08:00
expect ( model . pluginMetadata . length ) . toEqual ( @ draft . pluginMetadata . length )
for { pluginId , value , version } in @ draft . pluginMetadata
updated = model . metadataObjectForPluginId ( pluginId )
expect ( updated . value ) . toEqual ( value )
expect ( updated . version ) . toEqual ( 0 )
2015-12-22 03:50:52 +08:00
it " should notify the draft was sent " , ->
2016-02-18 06:54:43 +08:00
waitsForPromise =>
@ task . performRemote ( ) . then =>
args = Actions . sendDraftSuccess . calls [ 0 ] . args [ 0 ]
2016-02-26 04:48:05 +08:00
expect ( args . message instanceof Message ) . toBe ( true )
expect ( args . messageClientId ) . toBe ( @ draft . clientId )
2015-12-22 03:50:52 +08:00
2016-03-02 09:24:37 +08:00
it " should queue tasks to sync back the metadata on the new message " , ->
waitsForPromise =>
spyOn ( Actions , ' queueTask ' )
@ task . performRemote ( ) . then =>
metadataTasks = Actions . queueTask . calls . map (call) -> call . args [ 0 ]
expect ( metadataTasks . length ) . toEqual ( @ draft . pluginMetadata . length )
for pluginMetadatum , idx in @ draft . pluginMetadata
expect ( metadataTasks [ idx ] . clientId ) . toEqual ( @ draft . clientId )
expect ( metadataTasks [ idx ] . modelClassName ) . toEqual ( ' Message ' )
expect ( metadataTasks [ idx ] . pluginId ) . toEqual ( pluginMetadatum . pluginId )
2015-12-22 03:50:52 +08:00
it " should play a sound " , ->
spyOn ( NylasEnv . config , " get " ) . andReturn true
waitsForPromise => @ task . performRemote ( ) . then ->
expect ( NylasEnv . config . get ) . toHaveBeenCalledWith ( " core.sending.sounds " )
expect ( SoundRegistry . playSound ) . toHaveBeenCalledWith ( " send " )
it " shouldn ' t play a sound if the config is disabled " , ->
spyOn ( NylasEnv . config , " get " ) . andReturn false
waitsForPromise => @ task . performRemote ( ) . then ->
expect ( NylasEnv . config . get ) . toHaveBeenCalledWith ( " core.sending.sounds " )
expect ( SoundRegistry . playSound ) . not . toHaveBeenCalled ( )
describe " when there are errors " , ->
beforeEach ->
2016-01-27 11:12:46 +08:00
spyOn ( Actions , ' draftSendingFailed ' )
2015-12-22 03:50:52 +08:00
jasmine . unspy ( NylasAPI , " makeRequest " )
it " notifies of a permanent error of misc error types " , ->
## DB error
thrownError = null
2016-02-18 06:54:43 +08:00
spyOn ( NylasEnv , " reportError " )
2015-12-22 03:50:52 +08:00
jasmine . unspy ( DBt , " persistModel " )
spyOn ( DBt , " persistModel " ) . andCallFake =>
thrownError = new Error ( ' db error ' )
throw thrownError
waitsForPromise =>
@ task . performRemote ( ) . then (status) =>
expect ( status [ 0 ] ) . toBe Task . Status . Failed
expect ( status [ 1 ] ) . toBe thrownError
2016-01-27 11:12:46 +08:00
expect ( Actions . draftSendingFailed ) . toHaveBeenCalled ( )
2016-02-04 07:06:52 +08:00
expect ( NylasEnv . reportError ) . toHaveBeenCalled ( )
2015-12-22 03:50:52 +08:00
2016-01-27 11:12:46 +08:00
it " retries the task if ' Invalid message public id ' " , ->
spyOn ( NylasAPI , ' makeRequest ' ) . andCallFake (options) =>
if options . body . reply_to_message_id
err = new APIError ( body: " Invalid message public id " )
Promise . reject ( err )
else
options . success ? ( @ response )
Promise . resolve ( @ response )
@draft.replyToMessageId = " reply-123 "
@draft.threadId = " thread-123 "
waitsForPromise => @ task . _sendAndCreateMessage ( @ draft ) . then =>
expect ( NylasAPI . makeRequest ) . toHaveBeenCalled ( )
expect ( NylasAPI . makeRequest . callCount ) . toEqual 2
req1 = NylasAPI . makeRequest . calls [ 0 ] . args [ 0 ]
req2 = NylasAPI . makeRequest . calls [ 1 ] . args [ 0 ]
expect ( req1 . body . reply_to_message_id ) . toBe " reply-123 "
expect ( req1 . body . thread_id ) . toBe " thread-123 "
expect ( req2 . body . reply_to_message_id ) . toBe null
expect ( req2 . body . thread_id ) . toBe " thread-123 "
it " retries the task if ' Invalid message public id ' " , ->
spyOn ( NylasAPI , ' makeRequest ' ) . andCallFake (options) =>
if options . body . reply_to_message_id
err = new APIError ( body: " Invalid thread " )
Promise . reject ( err )
else
options . success ? ( @ response )
Promise . resolve ( @ response )
@draft.replyToMessageId = " reply-123 "
@draft.threadId = " thread-123 "
waitsForPromise => @ task . _sendAndCreateMessage ( @ draft ) . then =>
expect ( NylasAPI . makeRequest ) . toHaveBeenCalled ( )
expect ( NylasAPI . makeRequest . callCount ) . toEqual 2
req1 = NylasAPI . makeRequest . calls [ 0 ] . args [ 0 ]
req2 = NylasAPI . makeRequest . calls [ 1 ] . args [ 0 ]
expect ( req1 . body . reply_to_message_id ) . toBe " reply-123 "
expect ( req1 . body . thread_id ) . toBe " thread-123 "
expect ( req2 . body . reply_to_message_id ) . toBe null
expect ( req2 . body . thread_id ) . toBe null
2015-12-22 03:50:52 +08:00
it " notifies of a permanent error on 500 errors " , ->
thrownError = new APIError ( statusCode: 500 , body: " err " )
2016-02-18 06:54:43 +08:00
spyOn ( NylasEnv , " reportError " )
2015-12-22 03:50:52 +08:00
spyOn ( NylasAPI , ' makeRequest ' ) . andCallFake (options) =>
Promise . reject ( thrownError )
waitsForPromise => @ task . performRemote ( ) . then (status) =>
expect ( status [ 0 ] ) . toBe Task . Status . Failed
expect ( status [ 1 ] ) . toBe thrownError
2016-01-27 11:12:46 +08:00
expect ( Actions . draftSendingFailed ) . toHaveBeenCalled ( )
2015-12-22 03:50:52 +08:00
it " notifies us and users of a permanent error on 400 errors " , ->
thrownError = new APIError ( statusCode: 400 , body: " err " )
2016-02-18 06:54:43 +08:00
spyOn ( NylasEnv , " reportError " )
2015-12-22 03:50:52 +08:00
spyOn ( NylasAPI , ' makeRequest ' ) . andCallFake (options) =>
Promise . reject ( thrownError )
waitsForPromise => @ task . performRemote ( ) . then (status) =>
expect ( status [ 0 ] ) . toBe Task . Status . Failed
expect ( status [ 1 ] ) . toBe thrownError
2016-01-27 11:12:46 +08:00
expect ( Actions . draftSendingFailed ) . toHaveBeenCalled ( )
2015-12-22 03:50:52 +08:00
2016-02-27 03:40:52 +08:00
it " presents helpful error messages for 402 errors (security blocked) " , ->
thrownError = new APIError ( statusCode: 402 , body: {
" message " : " Message content rejected for security reasons " ,
" server_error " : " 552 : 5.7.0 This message was blocked because its content presents a potential \n 5.7.0 security issue. Please visit \n 5.7.0 https://support.google.com/mail/answer/6590 to review our message \n 5.7.0 content and attachment content guidelines. fk9sm21147314pad.9 - gsmtp " ,
" type " : " api_error "
} )
expectedMessage =
"""
Sorry , this message could not be sent because it was rejected by your mail provider . ( Message content rejected for security reasons )
552 : 5.7 . 0 This message was blocked because its content presents a potential
5.7 . 0 security issue . Please visit
5.7 . 0 https : / / support . google . com / mail / answer / 6590 to review our message
5.7 . 0 content and attachment content guidelines . fk9sm21147314pad . 9 - gsmtp
"""
spyOn ( NylasEnv , " reportError " )
spyOn ( NylasAPI , ' makeRequest ' ) . andCallFake (options) =>
Promise . reject ( thrownError )
waitsForPromise => @ task . performRemote ( ) . then (status) =>
expect ( status [ 0 ] ) . toBe Task . Status . Failed
expect ( status [ 1 ] ) . toBe thrownError
expect ( Actions . draftSendingFailed ) . toHaveBeenCalled ( )
expect ( Actions . draftSendingFailed . calls [ 0 ] . args [ 0 ] . errorMessage ) . toEqual ( expectedMessage )
it " presents helpful error messages for 402 errors (recipient failed) " , ->
thrownError = new APIError ( statusCode: 402 , body: {
" message " : " Sending to at least one recipient failed. " ,
" server_error " : " <<Don ' t know what this looks like >> " ,
" type " : " api_error "
} )
expectedMessage = " This message could not be delivered to at least one recipient. (Note: other recipients may have received this message - you should check Sent Mail before re-sending this message.) "
spyOn ( NylasEnv , " reportError " )
spyOn ( NylasAPI , ' makeRequest ' ) . andCallFake (options) =>
Promise . reject ( thrownError )
waitsForPromise => @ task . performRemote ( ) . then (status) =>
expect ( status [ 0 ] ) . toBe Task . Status . Failed
expect ( status [ 1 ] ) . toBe thrownError
expect ( Actions . draftSendingFailed ) . toHaveBeenCalled ( )
expect ( Actions . draftSendingFailed . calls [ 0 ] . args [ 0 ] . errorMessage ) . toEqual ( expectedMessage )
2016-02-05 06:14:24 +08:00
it " retries on timeouts " , ->
2015-12-22 03:50:52 +08:00
thrownError = new APIError ( statusCode: NylasAPI . TimeoutErrorCode , body: " err " )
spyOn ( NylasAPI , ' makeRequest ' ) . andCallFake (options) =>
Promise . reject ( thrownError )
waitsForPromise => @ task . performRemote ( ) . then (status) =>
2016-02-05 06:14:24 +08:00
expect ( status ) . toBe Task . Status . Retry
2015-12-22 03:50:52 +08:00
2016-01-09 02:57:48 +08:00
describe " checking the promise chain halts on errors " , ->
beforeEach ->
2016-02-18 06:54:43 +08:00
spyOn ( NylasEnv , ' reportError ' )
2016-01-27 11:12:46 +08:00
spyOn ( @ task , " _sendAndCreateMessage " ) . andCallThrough ( )
2016-01-09 02:57:48 +08:00
spyOn ( @ task , " _deleteRemoteDraft " ) . andCallThrough ( )
2016-01-27 11:12:46 +08:00
spyOn ( @ task , " _onSuccess " ) . andCallThrough ( )
2016-01-09 02:57:48 +08:00
spyOn ( @ task , " _onError " ) . andCallThrough ( )
@expectBlockedChain = =>
2016-01-27 11:12:46 +08:00
expect ( @ task . _sendAndCreateMessage ) . toHaveBeenCalled ( )
2016-01-09 02:57:48 +08:00
expect ( @ task . _deleteRemoteDraft ) . not . toHaveBeenCalled ( )
2016-01-27 11:12:46 +08:00
expect ( @ task . _onSuccess ) . not . toHaveBeenCalled ( )
2016-01-09 02:57:48 +08:00
expect ( @ task . _onError ) . toHaveBeenCalled ( )
it " halts on 500s " , ->
thrownError = new APIError ( statusCode: 500 , body: " err " )
spyOn ( NylasAPI , ' makeRequest ' ) . andCallFake (options) =>
Promise . reject ( thrownError )
2016-02-18 06:54:43 +08:00
waitsForPromise =>
@ task . performRemote ( ) . then (status) =>
@ expectBlockedChain ( )
2016-01-09 02:57:48 +08:00
it " halts on 400s " , ->
thrownError = new APIError ( statusCode: 400 , body: " err " )
spyOn ( NylasAPI , ' makeRequest ' ) . andCallFake (options) =>
Promise . reject ( thrownError )
2016-02-18 06:54:43 +08:00
waitsForPromise =>
@ task . performRemote ( ) . then (status) =>
@ expectBlockedChain ( )
it " halts and retries on not permanent error codes " , ->
thrownError = new APIError ( statusCode: 409 , body: " err " )
spyOn ( NylasAPI , ' makeRequest ' ) . andCallFake (options) =>
Promise . reject ( thrownError )
waitsForPromise =>
@ task . performRemote ( ) . then (status) =>
@ expectBlockedChain ( )
2016-01-09 02:57:48 +08:00
it " halts on other errors " , ->
thrownError = new Error ( " oh no " )
spyOn ( NylasAPI , ' makeRequest ' ) . andCallFake (options) =>
Promise . reject ( thrownError )
2016-02-18 06:54:43 +08:00
waitsForPromise =>
@ task . performRemote ( ) . then (status) =>
@ expectBlockedChain ( )
it " doesn ' t halt on success " , ->
# Don't spy reportError to make sure to fail the test on unexpected
# errors
jasmine . unspy ( NylasEnv , ' reportError ' )
2016-01-09 02:57:48 +08:00
spyOn ( NylasAPI , ' makeRequest ' ) . andCallFake (options) =>
options . success ? ( @ response )
Promise . resolve ( @ response )
2016-02-18 06:54:43 +08:00
waitsForPromise =>
@ task . performRemote ( ) . then (status) =>
expect ( status ) . toBe Task . Status . Success
expect ( @ task . _sendAndCreateMessage ) . toHaveBeenCalled ( )
expect ( @ task . _deleteRemoteDraft ) . toHaveBeenCalled ( )
expect ( @ task . _onSuccess ) . toHaveBeenCalled ( )
expect ( @ task . _onError ) . not . toHaveBeenCalled ( )
2016-01-09 02:57:48 +08:00
2015-12-22 03:50:52 +08:00
describe " with a new draft " , ->
beforeEach ->
@draft = new Message
version: 1
2016-01-27 11:12:46 +08:00
clientId: ' client-id '
accountId: TEST_ACCOUNT_ID
from: [ new Contact ( email: TEST_ACCOUNT_EMAIL ) ]
2015-12-22 03:50:52 +08:00
subject: ' New Draft '
draft: true
body: ' hello world '
2016-01-28 16:48:45 +08:00
uploads: [ ]
2016-03-02 09:24:37 +08:00
@ draft . applyPluginMetadata ( ' pluginIdA ' , { tracked: true } )
@ draft . applyPluginMetadata ( ' pluginIdB ' , { a: true , b: 2 } )
@ draft . metadataObjectForPluginId ( ' pluginIdA ' ) . version = 2
2016-01-28 16:48:45 +08:00
@task = new SendDraftTask ( @ draft )
2015-12-23 02:36:25 +08:00
@calledBody = " ERROR: The body wasn ' t included! "
spyOn ( DatabaseStore , " findBy " ) . andCallFake =>
2016-02-18 06:54:43 +08:00
Promise . resolve ( @ draft )
2016-01-27 11:12:46 +08:00
sharedTests ( )
2015-12-22 03:50:52 +08:00
it " should locally convert the draft to a message on send " , ->
2016-02-18 06:54:43 +08:00
waitsForPromise =>
@ task . performRemote ( ) . then =>
expect ( DBt . persistModel ) . toHaveBeenCalled ( )
model = DBt . persistModel . calls [ 0 ] . args [ 0 ]
expect ( model . clientId ) . toBe @ draft . clientId
expect ( model . serverId ) . toBe @ response . id
expect ( model . draft ) . toBe false
fix(draft-list): draft list removes draft when the message sends
Summary:
This started when I noticed that drafts weren't dissapearing from the
draft list after send. This was a pretty big bug because if you ever
clicked on one again and tried to re-send it would throw a 400 error
saying the draft id doesn't exist.
This uncovered a few fundamental issues with the DB.
First of all, the reason the draft list wasn't updating was because the DB
trigger that happened when we got in a new message, was being ignored
since the diff contained no drafts (it's now a message).
The bigger issue was that if you had a draft with only a clientId, gave it
a serverId, and tried to call "save", the REPLACE INTO method would not
update the old object, but rather create a second duplicate. This is
because the `id` field was being used as the PRIMARY KEY, and in this
case, that `id` field changed! The fix was to change the PRIMARY KEY to be
the `cilent_id` instead of the `id` and use that as the REPLACE INTO
index.
We still need the `id` field; however, because all of our reads depend on
that field usually being the serverId
Fixes T3507
Test Plan: See new and updated tests
Reviewers: dillon, bengotow
Reviewed By: bengotow
Maniphest Tasks: T3507
Differential Revision: https://phab.nylas.com/D1992
2015-09-09 04:11:34 +08:00
2016-03-02 09:24:37 +08:00
describe " deleteRemoteDraft " , ->
it " should not make an API request " , ->
waitsForPromise =>
@ task . _deleteRemoteDraft ( @ draft ) . then =>
expect ( NylasAPI . makeRequest ) . not . toHaveBeenCalled ( )
2015-12-22 03:50:52 +08:00
describe " with an existing persisted draft " , ->
2015-03-03 03:23:35 +08:00
beforeEach ->
@draft = new Message
2015-12-22 03:50:52 +08:00
version: 1
2016-01-27 11:12:46 +08:00
clientId: ' client-id '
serverId: ' server-123 '
accountId: TEST_ACCOUNT_ID
from: [ new Contact ( email: TEST_ACCOUNT_EMAIL ) ]
2015-03-03 03:23:35 +08:00
subject: ' New Draft '
draft: true
2015-03-26 03:41:48 +08:00
body: ' hello world '
2015-03-03 03:23:35 +08:00
to:
name: ' Dummy '
2015-05-15 08:08:30 +08:00
email: ' dummy@nylas.com '
2016-01-28 16:48:45 +08:00
uploads: [ ]
2016-03-02 09:24:37 +08:00
@ draft . applyPluginMetadata ( ' pluginIdA ' , { tracked: true } )
@ draft . applyPluginMetadata ( ' pluginIdB ' , { a: true , b: 2 } )
@ draft . metadataObjectForPluginId ( ' pluginIdA ' ) . version = 2
2016-01-28 16:48:45 +08:00
@task = new SendDraftTask ( @ draft )
2015-12-23 02:36:25 +08:00
@calledBody = " ERROR: The body wasn ' t included! "
spyOn ( DatabaseStore , " findBy " ) . andCallFake =>
2016-02-18 06:54:43 +08:00
Promise . resolve ( @ draft )
2016-01-27 11:12:46 +08:00
sharedTests ( )
2015-12-22 03:50:52 +08:00
it " should locally convert the existing draft to a message on send " , ->
2016-01-27 11:12:46 +08:00
expect ( @ draft . clientId ) . toBe @ draft . clientId
2015-12-22 03:50:52 +08:00
expect ( @ draft . serverId ) . toBe " server-123 "
2016-01-27 11:12:46 +08:00
@ task . performLocal ( )
2015-12-22 03:50:52 +08:00
waitsForPromise => @ task . performRemote ( ) . then =>
expect ( DBt . persistModel ) . toHaveBeenCalled ( )
model = DBt . persistModel . calls [ 0 ] . args [ 0 ]
2016-01-27 11:12:46 +08:00
expect ( model . clientId ) . toBe @ draft . clientId
expect ( model . serverId ) . toBe @ response . id
2015-12-22 03:50:52 +08:00
expect ( model . draft ) . toBe false
2016-03-02 09:24:37 +08:00
describe " deleteRemoteDraft " , ->
it " should make an API request to delete the draft " , ->
@ task . performLocal ( )
waitsForPromise =>
@ task . _deleteRemoteDraft ( @ draft ) . then =>
expect ( NylasAPI . makeRequest ) . toHaveBeenCalled ( )
expect ( NylasAPI . makeRequest . callCount ) . toBe 1
req = NylasAPI . makeRequest . calls [ 0 ] . args [ 0 ]
expect ( req . path ) . toBe " /drafts/ #{ @ draft . serverId } "
expect ( req . accountId ) . toBe TEST_ACCOUNT_ID
expect ( req . method ) . toBe " DELETE "
expect ( req . returnsModel ) . toBe false
2016-03-04 04:09:16 +08:00
it " should increment the change tracker, preventing any further deltas about the draft " , ->
@ task . performLocal ( )
waitsForPromise =>
@ task . _deleteRemoteDraft ( @ draft ) . then =>
expect ( NylasAPI . incrementRemoteChangeLock ) . toHaveBeenCalledWith ( Message , @ draft . serverId )
2016-03-02 09:24:37 +08:00
it " should continue if the request fails " , ->
jasmine . unspy ( NylasAPI , " makeRequest " )
spyOn ( NylasAPI , ' makeRequest ' ) . andCallFake (options) =>
Promise . reject ( new APIError ( body: " Boo " , statusCode: 500 ) )
@ task . performLocal ( )
waitsForPromise =>
@ task . _deleteRemoteDraft ( @ draft )
. then =>
expect ( NylasAPI . makeRequest ) . toHaveBeenCalled ( )
expect ( NylasAPI . makeRequest . callCount ) . toBe 1
. catch =>
throw new Error ( " Shouldn ' t fail the promise " )
describe " with uploads " , ->
beforeEach ->
@uploads = [
{ targetPath: ' /test-file-1.png ' , size: 100 } ,
{ targetPath: ' /test-file-2.png ' , size: 100 }
]
@draft = new Message
version: 1
clientId: ' client-id '
accountId: TEST_ACCOUNT_ID
from: [ new Contact ( email: TEST_ACCOUNT_EMAIL ) ]
subject: ' New Draft '
draft: true
body: ' hello world '
uploads: [ ] . concat ( @ uploads )
@task = new SendDraftTask ( @ draft )
jasmine . unspy ( NylasAPI , ' makeRequest ' )
@resolves = [ ]
@resolveAll = =>
resolve ( ) for resolve in @ resolves
@resolves = [ ]
advanceClock ( )
spyOn ( NylasAPI , ' makeRequest ' ) . andCallFake (options) =>
response = @ response
if options . path is ' /files '
response = JSON . stringify ( [ {
id: ' 1234 '
account_id: TEST_ACCOUNT_ID
filename: options . formData . file . options . filename
} ] )
new Promise (resolve, reject) =>
@ resolves . push =>
options . success ? ( response )
resolve ( response )
spyOn ( DatabaseStore , ' findBy ' ) . andCallFake =>
Promise . resolve ( @ draft )
it " should begin file uploads and not hit /send until they complete " , ->
@ task . performRemote ( )
advanceClock ( )
# uploads should be queued, but not the send
expect ( NylasAPI . makeRequest . callCount ) . toEqual ( 2 )
expect ( NylasAPI . makeRequest . calls [ 0 ] . args [ 0 ] . formData ) . toEqual ( { file : { value : ' stub ' , options : { filename : ' test-file-1.png ' } } } )
expect ( NylasAPI . makeRequest . calls [ 1 ] . args [ 0 ] . formData ) . toEqual ( { file : { value : ' stub ' , options : { filename : ' test-file-2.png ' } } } )
# finish all uploads
@ resolveAll ( )
# send should now be queued
expect ( NylasAPI . makeRequest . callCount ) . toEqual ( 3 )
expect ( NylasAPI . makeRequest . calls [ 2 ] . args [ 0 ] . path ) . toEqual ( ' /send ' )
it " should convert the uploads to files " , ->
@ task . performRemote ( )
advanceClock ( )
expect ( @ task . draft . files . length ) . toEqual ( 0 )
expect ( @ task . draft . uploads . length ) . toEqual ( 2 )
@ resolves [ 0 ] ( )
advanceClock ( )
expect ( @ task . draft . files . length ) . toEqual ( 1 )
expect ( @ task . draft . uploads . length ) . toEqual ( 1 )
{ filename , accountId , id } = @ task . draft . files [ 0 ]
expect ( { filename , accountId , id } ) . toEqual ( {
filename: ' test-file-1.png ' ,
accountId: TEST_ACCOUNT_ID ,
id: ' 1234 '
} )
describe " cancel, during attachment upload " , ->
it " should make the task resolve early, before making the /send call " , ->
exitStatus = null
@ task . performRemote ( ) . then (status) => exitStatus = status
advanceClock ( )
@ task . cancel ( )
NylasAPI . makeRequest . reset ( )
@ resolveAll ( )
advanceClock ( )
expect ( NylasAPI . makeRequest ) . not . toHaveBeenCalled ( )
expect ( exitStatus ) . toEqual ( Task . Status . Continue )
describe " after the message sends " , ->
it " should notify the attachments were uploaded (so they can be deleted) " , ->
@ task . performRemote ( )
advanceClock ( )
@ resolveAll ( ) # uploads
@ resolveAll ( ) # send
expect ( Actions . attachmentUploaded ) . toHaveBeenCalled ( )
expect ( Actions . attachmentUploaded . callCount ) . toEqual ( @ uploads . length )
for upload , idx in @ uploads
expect ( Actions . attachmentUploaded . calls [ idx ] . args [ 0 ] ) . toBe ( upload )