diff --git a/build/config/eslint.json b/build/config/eslint.json index ee23d4ebf..c2d4d40ab 100644 --- a/build/config/eslint.json +++ b/build/config/eslint.json @@ -5,7 +5,9 @@ "$n": false, "waitsForPromise": false, "advanceClock": false, - "TEST_ACCOUNT_ID": false + "TEST_ACCOUNT_ID": false, + "TEST_ACCOUNT_NAME": false, + "TEST_ACCOUNT_ALIAS_EMAIL": false }, "env": { "browser": true, diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index 0599c3abb..06eb0d417 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -110,6 +110,7 @@ window.TEST_ACCOUNT_ID = "test-account-server-id" window.TEST_ACCOUNT_EMAIL = "tester@nylas.com" window.TEST_ACCOUNT_NAME = "Nylas Test" window.TEST_PLUGIN_ID = "test-plugin-id-123" +window.TEST_ACCOUNT_ALIAS_EMAIL = "tester+alternative@nylas.com" beforeEach -> NylasEnv.testOrganizationUnit = null @@ -161,7 +162,6 @@ beforeEach -> spyOn(NylasEnv.menu, 'sendToBrowserProcess') # Log in a fake user, and ensure that accountForId, etc. work - AccountStore._index = 0 AccountStore._accounts = [ new Account({ provider: "gmail" @@ -169,7 +169,23 @@ beforeEach -> emailAddress: TEST_ACCOUNT_EMAIL organizationUnit: NylasEnv.testOrganizationUnit || 'label' clientId: TEST_ACCOUNT_CLIENT_ID - serverId: TEST_ACCOUNT_ID + serverId: TEST_ACCOUNT_ID, + aliases: [ + "#{TEST_ACCOUNT_NAME} Alternate <#{TEST_ACCOUNT_ALIAS_EMAIL}>" + ] + }), + new Account({ + provider: "gmail" + name: 'Second' + emailAddress: 'second@gmail.com' + organizationUnit: NylasEnv.testOrganizationUnit || 'label' + clientId: 'second-test-account-id' + serverId: 'second-test-account-id' + aliases: [ + 'Second Support ' + 'Second Alternate ' + 'Second ' + ] }) ] diff --git a/spec/stores/draft-factory-spec.es6 b/spec/stores/draft-factory-spec.es6 index 935c39f04..42f081e32 100644 --- a/spec/stores/draft-factory-spec.es6 +++ b/spec/stores/draft-factory-spec.es6 @@ -148,6 +148,57 @@ describe("DraftFactory", () => { }); }); + it("should set the accountId and from address based on the message", () => { + waitsForPromise(() => { + const secondAccount = AccountStore.accounts()[1]; + fakeMessage1.to = [ + new Contact({email: secondAccount.emailAddress}), + new Contact({email: 'evan@nylas.com'}), + ] + fakeMessage1.accountId = secondAccount.id + fakeThread.accountId = secondAccount.id + + return DraftFactory.createDraftForReply({thread: fakeThread, message: fakeMessage1, type: 'reply'}).then((draft) => { + expect(draft.accountId).toEqual(secondAccount.id); + expect(draft.from[0].email).toEqual(secondAccount.defaultMe().email); + }); + }); + }); + + describe("when the email is TO an alias", () => { + it("should use the alias as the from address", () => { + waitsForPromise(() => { + fakeMessage1.to = [ + new Contact({email: TEST_ACCOUNT_ALIAS_EMAIL}), + new Contact({email: 'evan@nylas.com'}), + ] + + return DraftFactory.createDraftForReply({thread: fakeThread, message: fakeMessage1, type: 'reply'}).then((draft) => { + expect(draft.accountId).toEqual(TEST_ACCOUNT_ID); + expect(draft.from[0].email).toEqual(TEST_ACCOUNT_ALIAS_EMAIL); + }); + }); + }); + }); + + describe("when the email is CC'd to an alias", () => { + it("should use the alias as the from address", () => { + waitsForPromise(() => { + fakeMessage1.to = [ + new Contact({email: 'juan@nylas.com'}), + ] + fakeMessage1.cc = [ + new Contact({email: TEST_ACCOUNT_ALIAS_EMAIL}), + new Contact({email: 'evan@nylas.com'}), + ] + + return DraftFactory.createDraftForReply({thread: fakeThread, message: fakeMessage1, type: 'reply'}).then((draft) => { + expect(draft.accountId).toEqual(TEST_ACCOUNT_ID); + expect(draft.from[0].email).toEqual(TEST_ACCOUNT_ALIAS_EMAIL); + }); + }); + }); + }); it("should sanitize the HTML", () => { waitsForPromise(() => { return DraftFactory.createDraftForReply({thread: fakeThread, message: fakeMessage1, type: 'reply'}).then(() => { @@ -455,6 +506,53 @@ describe("DraftFactory", () => { }); }); + describe("_fromContactForReply", () => { + it("should work correctly in a range of test cases", () => { + // Note: These specs are based on the second account hard-coded in SpecHelper + account = AccountStore.accounts()[1]; + const cases = [ + { + to: [new Contact({name: 'Ben', email: 'ben@nylas.com'})], // user is not present, must have been BCC'd + cc: [], + expected: account.defaultMe(), + }, + { + to: [new Contact({name: 'Second Support', email: 'second@gmail.com'})], // only name identifies alias + cc: [], + expected: new Contact({name: 'Second Support', email: 'second@gmail.com'}), + }, + { + to: [new Contact({name: 'Second Wrong!', email: 'second+alternate@gmail.com'})], // only email identifies alias, name wrong + cc: [], + expected: new Contact({name: 'Second Alternate', email: 'second+alternate@gmail.com'}), + }, + { + to: [new Contact({name: 'Second Alternate', email: 'second+alternate@gmail.com'})], // exact alias match + cc: [], + expected: new Contact({name: 'Second Alternate', email: 'second+alternate@gmail.com'}), + }, + { + to: [new Contact({email: 'second+third@gmail.com'})], // exact alias match, name not present + cc: [], + expected: new Contact({name: 'Second', email: 'second+third@gmail.com'}), + }, + { + to: [new Contact({email: 'ben@nylas.com'})], + cc: [new Contact({email: 'second+third@gmail.com'})], // exact alias match, but in CC + expected: new Contact({name: 'Second', email: 'second+third@gmail.com'}), + }, + ] + cases.forEach(({to, cc, expected}) => { + const contact = DraftFactory._fromContactForReply(new Message({ + accountId: account.id, + to: to, + cc: cc, + })); + expect(contact.name).toEqual(expected.name); + expect(contact.email).toEqual(expected.email); + }); + }); + }); describe("_prepareBodyForQuoting", () => { it("should transform inline styles and sanitize unsafe html", () => { diff --git a/spec/stores/focused-perspective-store-spec.coffee b/spec/stores/focused-perspective-store-spec.coffee index b17f653ee..e3e09f9bc 100644 --- a/spec/stores/focused-perspective-store-spec.coffee +++ b/spec/stores/focused-perspective-store-spec.coffee @@ -55,18 +55,21 @@ describe "FocusedPerspectiveStore", -> expect(current).toEqual saved describe "_onCategoryStoreChanged", -> - it "should set the current category to Inbox when it is unset", -> + it "should set the current category to unified inbox when it is unset", -> FocusedPerspectiveStore._perspective = null FocusedPerspectiveStore._onCategoryStoreChanged() expect(FocusedPerspectiveStore.current()).not.toBe(null) - expect(FocusedPerspectiveStore.current().categories()).toEqual([@inboxCategory]) + + # same because the stub above returns @inboxCategory for both accounts + expect(FocusedPerspectiveStore.current().categories()).toEqual([@inboxCategory, @inboxCategory]) it "should set the current category to Inbox when the current category no longer exists in the CategoryStore", -> otherAccountInbox = @inboxCategory.clone() otherAccountInbox.serverId = 'other-id' FocusedPerspectiveStore._perspective = MailboxPerspective.forCategory(otherAccountInbox) FocusedPerspectiveStore._onCategoryStoreChanged() - expect(FocusedPerspectiveStore.current().categories()).toEqual([@inboxCategory]) + # same because the stub above returns @inboxCategory for both accounts + expect(FocusedPerspectiveStore.current().categories()).toEqual([@inboxCategory, @inboxCategory]) describe "_onFocusPerspective", -> it "should focus the category and trigger", -> diff --git a/src/flux/stores/draft-factory.coffee b/src/flux/stores/draft-factory.coffee index e4e1f9e1e..59eaa7f7a 100644 --- a/src/flux/stores/draft-factory.coffee +++ b/src/flux/stores/draft-factory.coffee @@ -106,7 +106,9 @@ class DraftFactory subject: subjectWithPrefix(message.subject, 'Re:') to: to, cc: cc, + from: [@_fromContactForReply(message)], threadId: thread.id, + accountId: message.accountId replyToMessageId: message.id, body: """

@@ -133,7 +135,9 @@ class DraftFactory @createDraft( subject: subjectWithPrefix(message.subject, 'Fwd:') files: [].concat(message.files), + from: [@_fromContactForReply(message)], threadId: thread.id, + accountId: message.accountId, body: """

---------- Forwarded message --------- @@ -219,6 +223,31 @@ class DraftFactory InlineStyleTransformer.run(body).then (body) => SanitizeTransformer.run(body, SanitizeTransformer.Preset.UnsafeOnly) + _fromContactForReply: (message) => + account = AccountStore.accountForId(message.accountId) + defaultMe = account.defaultMe() + result = defaultMe + + for aliasString in account.aliases + alias = account.meUsingAlias(aliasString) + for recipient in [].concat(message.to, message.cc) + emailIsNotDefault = alias.email isnt defaultMe.email + emailsMatch = recipient.email is alias.email + nameIsNotDefault = alias.name isnt defaultMe.name + namesMatch = recipient.name is alias.name + + # No better match is possible + if emailsMatch and emailIsNotDefault and namesMatch and nameIsNotDefault + return alias + + # A better match is possible. eg: the user may have two aliases with the same + # email but different phrases, and we'll get an exact match on the other one. + # Continue iterating and wait to see. + if (emailsMatch and emailIsNotDefault) or (namesMatch and nameIsNotDefault) + result = alias + + return result + _accountForNewDraft: => defAccountId = NylasEnv.config.get('core.sending.defaultAccountIdForSend') account = AccountStore.accountForId(defAccountId)