mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-09-21 07:46:06 +08:00
fix(quoted-text): Show quoted text for short forwarded msgs
Summary: tiny patch, but with lots of tests for message-item and factored out message-timestamp Test Plan: Run brand new specs! Reviewers: evan Reviewed By: evan Differential Revision: https://review.inboxapp.com/D1225
This commit is contained in:
parent
0468cb4b39
commit
2a93c70214
|
@ -26,6 +26,7 @@ module.exports =
|
|||
|
||||
# Models
|
||||
Tag: require '../src/flux/models/tag'
|
||||
File: require '../src/flux/models/file'
|
||||
Thread: require '../src/flux/models/thread'
|
||||
Contact: require '../src/flux/models/contact'
|
||||
Message: require '../src/flux/models/message'
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
moment = require 'moment'
|
||||
React = require 'react'
|
||||
_ = require 'underscore-plus'
|
||||
EmailFrame = require './email-frame'
|
||||
MessageParticipants = require "./message-participants.cjsx"
|
||||
MessageTimestamp = require "./message-timestamp.cjsx"
|
||||
{ComponentRegistry, FileDownloadStore, Utils} = require 'inbox-exports'
|
||||
Autolinker = require 'autolinker'
|
||||
|
||||
|
@ -21,7 +21,7 @@ MessageItem = React.createClass
|
|||
# Holds the downloadData (if any) for all of our files. It's a hash
|
||||
# keyed by a fileId. The value is the downloadData.
|
||||
downloads: FileDownloadStore.downloadsForFileIds(@props.message.fileIds())
|
||||
showQuotedText: false
|
||||
showQuotedText: @_messageIsEmptyForward()
|
||||
collapsed: @props.collapsed
|
||||
|
||||
componentDidMount: ->
|
||||
|
@ -44,7 +44,7 @@ MessageItem = React.createClass
|
|||
|
||||
header =
|
||||
<header className="message-header" onClick={@_onToggleCollapsed}>
|
||||
<div className="message-time">{@_messageTime()}</div>
|
||||
<MessageTimestamp date={@props.message.date} />
|
||||
<div className="message-actions">
|
||||
{<Action thread={@props.thread} message={@props.message} /> for Action in messageActions}
|
||||
</div>
|
||||
|
@ -106,26 +106,14 @@ MessageItem = React.createClass
|
|||
attachments.map (file) =>
|
||||
<AttachmentComponent file={file} download={@state.downloads[file.id]}/>
|
||||
|
||||
_messageTime: ->
|
||||
moment(@props.message.date).format(@_timeFormat())
|
||||
|
||||
_timeFormat: ->
|
||||
today = moment(@_today())
|
||||
dayOfEra = today.dayOfYear() + today.year() * 365
|
||||
msgDate = moment(@props.message.date)
|
||||
msgDayOfEra = msgDate.dayOfYear() + msgDate.year() * 365
|
||||
diff = dayOfEra - msgDayOfEra
|
||||
if diff < 1
|
||||
return "h:mm a"
|
||||
if diff < 4
|
||||
return "MMM D, h:mm a"
|
||||
else if diff > 1 and diff <= 365
|
||||
return "MMM D"
|
||||
else
|
||||
return "MMM D YYYY"
|
||||
|
||||
# Stubbable for testing. Returns a `moment`
|
||||
_today: -> moment()
|
||||
_messageIsEmptyForward: ->
|
||||
# Returns true if the message contains "Forwarded" or "Fwd" in the first 250 characters.
|
||||
# A strong indicator that the quoted text should be shown. Needs to be limited to first 250
|
||||
# to prevent replies to forwarded messages from also being expanded.
|
||||
body = @props.message.body.toLowerCase()
|
||||
indexForwarded = body.indexOf('forwarded')
|
||||
indexFwd = body.indexOf('fwd')
|
||||
(indexForwarded >= 0 and indexForwarded < 250) or (indexFwd >= 0 and indexFwd < 250)
|
||||
|
||||
_onDownloadStoreChange: ->
|
||||
@setState
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
_ = require 'underscore-plus'
|
||||
React = require 'react'
|
||||
MessageItem = require "./message-item.cjsx"
|
||||
|
||||
{Actions, ThreadStore, MessageStore, ComponentRegistry} = require("inbox-exports")
|
||||
|
||||
module.exports =
|
||||
|
|
31
internal_packages/message-list/lib/message-timestamp.cjsx
Normal file
31
internal_packages/message-list/lib/message-timestamp.cjsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
moment = require 'moment'
|
||||
React = require 'react'
|
||||
|
||||
module.exports =
|
||||
MessageTimestamp = React.createClass
|
||||
displayName: 'MessageTimestamp'
|
||||
propTypes:
|
||||
date: React.PropTypes.object.isRequired,
|
||||
|
||||
render: ->
|
||||
<div>{moment(@props.date).format(@_timeFormat())}</div>
|
||||
|
||||
_timeFormat: ->
|
||||
today = moment(@_today())
|
||||
dayOfEra = today.dayOfYear() + today.year() * 365
|
||||
msgDate = moment(@props.date)
|
||||
msgDayOfEra = msgDate.dayOfYear() + msgDate.year() * 365
|
||||
diff = dayOfEra - msgDayOfEra
|
||||
if diff < 1
|
||||
return "h:mm a"
|
||||
if diff < 4
|
||||
return "MMM D, h:mm a"
|
||||
else if diff > 1 and diff <= 365
|
||||
return "MMM D"
|
||||
else
|
||||
return "MMM D YYYY"
|
||||
|
||||
# Stubbable for testing. Returns a `moment`
|
||||
_today: -> moment()
|
||||
|
||||
|
241
internal_packages/message-list/spec/message-item-spec.cjsx
Normal file
241
internal_packages/message-list/spec/message-item-spec.cjsx
Normal file
|
@ -0,0 +1,241 @@
|
|||
proxyquire = require 'proxyquire'
|
||||
React = require "react/addons"
|
||||
ReactTestUtils = React.addons.TestUtils
|
||||
|
||||
{Contact,
|
||||
Message,
|
||||
File,
|
||||
ComponentRegistry,
|
||||
FileDownloadStore,
|
||||
InboxTestUtils} = require "inbox-exports"
|
||||
|
||||
file = new File
|
||||
id: 'file_1_id'
|
||||
filename: 'a.png'
|
||||
contentType: 'image/png'
|
||||
size: 10
|
||||
file_not_downloaded = new File
|
||||
id: 'file_2_id'
|
||||
filename: 'b.png'
|
||||
contentType: 'image/png'
|
||||
size: 10
|
||||
file_inline = new File
|
||||
id: 'file_inline_id'
|
||||
filename: 'c.png'
|
||||
contentId: 'file_inline_id'
|
||||
contentType: 'image/png'
|
||||
size: 10
|
||||
file_inline_downloading = new File
|
||||
id: 'file_inline_downloading_id'
|
||||
filename: 'd.png'
|
||||
contentId: 'file_inline_downloading_id'
|
||||
contentType: 'image/png'
|
||||
size: 10
|
||||
file_inline_not_downloaded = new File
|
||||
id: 'file_inline_not_downloaded_id'
|
||||
filename: 'e.png'
|
||||
contentId: 'file_inline_not_downloaded_id'
|
||||
contentType: 'image/png'
|
||||
size: 10
|
||||
|
||||
download =
|
||||
fileId: 'file_1_id'
|
||||
download_inline =
|
||||
fileId: 'file_inline_downloading_id'
|
||||
|
||||
user_1 = new Contact
|
||||
name: "User One"
|
||||
email: "user1@inboxapp.com"
|
||||
user_2 = new Contact
|
||||
name: "User Two"
|
||||
email: "user2@inboxapp.com"
|
||||
user_3 = new Contact
|
||||
name: "User Three"
|
||||
email: "user3@inboxapp.com"
|
||||
user_4 = new Contact
|
||||
name: "User Four"
|
||||
email: "user4@inboxapp.com"
|
||||
user_5 = new Contact
|
||||
name: "User Five"
|
||||
email: "user5@inboxapp.com"
|
||||
|
||||
|
||||
AttachmentStub = React.createClass({render: -> <div></div>})
|
||||
EmailFrameStub = React.createClass({render: -> <div></div>})
|
||||
|
||||
MessageItem = proxyquire '../lib/message-item.cjsx',
|
||||
'./email-frame': EmailFrameStub
|
||||
|
||||
|
||||
describe "MessageItem", ->
|
||||
beforeEach ->
|
||||
ComponentRegistry.register
|
||||
name: 'AttachmentComponent'
|
||||
view: AttachmentStub
|
||||
|
||||
spyOn(FileDownloadStore, 'pathForFile').andCallFake (f) ->
|
||||
return '/fake/path.png' if f.id is file.id
|
||||
return '/fake/path-inline.png' if f.id is file_inline.id
|
||||
return '/fake/path-downloading.png' if f.id is file_inline_downloading.id
|
||||
return null
|
||||
spyOn(FileDownloadStore, 'downloadsForFileIds').andCallFake (ids) ->
|
||||
return {'file_1_id': download, 'file_inline_downloading_id': download_inline}
|
||||
|
||||
@message = new Message
|
||||
id: "111"
|
||||
from: [user_1]
|
||||
to: [user_2]
|
||||
cc: [user_3, user_4]
|
||||
bcc: null
|
||||
body: "Body One"
|
||||
date: new Date(1415814587)
|
||||
draft: false
|
||||
files: []
|
||||
unread: false
|
||||
snippet: "snippet one..."
|
||||
subject: "Subject One"
|
||||
threadId: "thread_12345"
|
||||
namespaceId: "nsid"
|
||||
|
||||
@threadParticipants = [user_1, user_2, user_3, user_4]
|
||||
|
||||
# Generate the test component. Should be called after @message is configured
|
||||
# for the test, since MessageItem assumes attributes of the message will not
|
||||
# change after getInitialState runs.
|
||||
@createComponent = ({collapsed} = {}) =>
|
||||
collapsed ?= false
|
||||
@component = ReactTestUtils.renderIntoDocument(
|
||||
<MessageItem key={@message.id}
|
||||
message={@message}
|
||||
collapsed={collapsed}
|
||||
thread_participants={@threadParticipants} />
|
||||
)
|
||||
|
||||
describe "when collapsed", ->
|
||||
beforeEach ->
|
||||
@createComponent({collapsed: true})
|
||||
|
||||
it "should not render the EmailFrame", ->
|
||||
expect( -> ReactTestUtils.findRenderedComponentWithType(@component, EmailFrameStub)).toThrow()
|
||||
|
||||
it "should have the `collapsed` class", ->
|
||||
expect(@component.getDOMNode().className.indexOf('collapsed') >= 0).toBe(true)
|
||||
|
||||
describe "when not collapsed", ->
|
||||
beforeEach ->
|
||||
@createComponent({collapsed: false})
|
||||
|
||||
it "should render the EmailFrame", ->
|
||||
frame = ReactTestUtils.findRenderedComponentWithType(@component, EmailFrameStub)
|
||||
expect(frame).toBeDefined()
|
||||
|
||||
it "should not have the `collapsed` class", ->
|
||||
expect(@component.getDOMNode().className.indexOf('collapsed') >= 0).toBe(false)
|
||||
|
||||
describe "when the message contains attachments", ->
|
||||
beforeEach ->
|
||||
@message.files = [file, file_not_downloaded]
|
||||
@createComponent()
|
||||
|
||||
it "should include the attachments area", ->
|
||||
attachments = ReactTestUtils.findRenderedDOMComponentWithClass(@component, 'attachments-area')
|
||||
expect(attachments).toBeDefined()
|
||||
|
||||
it "should render the registered AttachmentComponent for each attachment", ->
|
||||
attachments = ReactTestUtils.scryRenderedComponentsWithType(@component, AttachmentStub)
|
||||
expect(attachments.length).toEqual(2)
|
||||
expect(attachments[0].props.file).toBe(file)
|
||||
expect(attachments[1].props.file).toBe(file_not_downloaded)
|
||||
|
||||
it "should provide file download state to each AttachmentComponent", ->
|
||||
attachments = ReactTestUtils.scryRenderedComponentsWithType(@component, AttachmentStub)
|
||||
expect(attachments[0].props.download).toBe(download)
|
||||
expect(attachments[1].props.download).toBe(undefined)
|
||||
|
||||
describe "when the message contains inline attachments", ->
|
||||
beforeEach ->
|
||||
@message.files = [
|
||||
file,
|
||||
file_inline,
|
||||
file_inline_downloading,
|
||||
file_inline_not_downloaded
|
||||
]
|
||||
@message.body = """
|
||||
<img alt=\"A\" src=\"cid:#{file_inline.contentId}\"/>
|
||||
<img alt=\"B\" src=\"cid:#{file_inline_downloading.contentId}\"/>
|
||||
<img alt=\"C\" src=\"cid:#{file_inline_not_downloaded.contentId}\"/>
|
||||
<img src=\"cid:missing-attachment\"/>
|
||||
"""
|
||||
@createComponent()
|
||||
|
||||
it "should never include src=cid:// in the message body", ->
|
||||
body = @component._formatBody()
|
||||
expect(body.indexOf('cid')).toEqual(-1)
|
||||
|
||||
it "should replace cid://<file.contentId> with the FileDownloadStore's path for the file", ->
|
||||
body = @component._formatBody()
|
||||
expect(body.indexOf('alt="A" src="/fake/path-inline.png"')).toEqual(@message.body.indexOf('alt="A"'))
|
||||
|
||||
it "should not replace cid://<file.contentId> with the FileDownloadStore's path if the download is in progress", ->
|
||||
body = @component._formatBody()
|
||||
expect(body.indexOf('/fake/path-downloading.png')).toEqual(-1)
|
||||
|
||||
it "should not include them in the attachments area", ->
|
||||
attachments = ReactTestUtils.scryRenderedComponentsWithType(@component, AttachmentStub)
|
||||
expect(attachments.length).toEqual(1)
|
||||
expect(attachments[0].props.file).toBe(file)
|
||||
|
||||
describe "showQuotedText", ->
|
||||
it "should be initialized to false", ->
|
||||
@createComponent()
|
||||
expect(@component.state.showQuotedText).toBe(false)
|
||||
|
||||
it "should show the `show quoted text` toggle in the off state", ->
|
||||
@createComponent()
|
||||
toggle = ReactTestUtils.findRenderedDOMComponentWithClass(@component, 'quoted-text-toggle')
|
||||
expect(toggle.getDOMNode().className.indexOf('state-on')).toBe(-1)
|
||||
|
||||
it "should be initialized to true if the message contains `Forwarded`...", ->
|
||||
@message.body = """
|
||||
Hi guys, take a look at this. Very relevant. -mg
|
||||
<br>
|
||||
<br>
|
||||
<div class="gmail_quote">
|
||||
---- Forwarded Message -----
|
||||
blablalba
|
||||
</div>
|
||||
"""
|
||||
@createComponent()
|
||||
expect(@component.state.showQuotedText).toBe(true)
|
||||
|
||||
it "should be initialized to false if the message is a response to a Forwarded message", ->
|
||||
@message.body = """
|
||||
Thanks mg, that indeed looks very relevant. Will bring it up
|
||||
with the rest of the team.
|
||||
|
||||
On Sunday, March 4th at 12:32AM, Michael Grinich Wrote:
|
||||
<div class="gmail_quote">
|
||||
Hi guys, take a look at this. Very relevant. -mg
|
||||
<br>
|
||||
<br>
|
||||
<div class="gmail_quote">
|
||||
---- Forwarded Message -----
|
||||
blablalba
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
@createComponent()
|
||||
expect(@component.state.showQuotedText).toBe(false)
|
||||
|
||||
describe "when true", ->
|
||||
beforeEach ->
|
||||
@createComponent()
|
||||
@component.setState(showQuotedText: true)
|
||||
|
||||
it "should show the `show quoted text` toggle in the on state", ->
|
||||
toggle = ReactTestUtils.findRenderedDOMComponentWithClass(@component, 'quoted-text-toggle')
|
||||
expect(toggle.getDOMNode().className.indexOf('state-on') > 0).toBe(true)
|
||||
|
||||
it "should pass the value into the EmailFrame", ->
|
||||
frame = ReactTestUtils.findRenderedComponentWithType(@component, EmailFrameStub)
|
||||
expect(frame.props.showQuotedText).toBe(true)
|
|
@ -259,38 +259,10 @@ describe "MessageList", ->
|
|||
MessageItem)
|
||||
item = items.filter (message) -> message.props.message.id is "111"
|
||||
@message_item = item[0]
|
||||
@message_date = moment([2010, 1, 14, 15, 25, 50, 125])
|
||||
@message_item.props.message.date = moment(@message_date)
|
||||
|
||||
it "finds the message by id", ->
|
||||
expect(@message_item.props.message.id).toBe "111"
|
||||
|
||||
# test messsage time is 1415814587
|
||||
it "displays the time from messages LONG ago", ->
|
||||
spyOn(@message_item, "_today").andCallFake =>
|
||||
@message_date.add(2, 'years')
|
||||
expect(@message_item._timeFormat()).toBe "MMM D YYYY"
|
||||
|
||||
it "displays the time and date from messages a bit ago", ->
|
||||
spyOn(@message_item, "_today").andCallFake =>
|
||||
@message_date.add(2, 'days')
|
||||
expect(@message_item._timeFormat()).toBe "MMM D, h:mm a"
|
||||
|
||||
it "displays the time and date messages exactly a day ago", ->
|
||||
spyOn(@message_item, "_today").andCallFake =>
|
||||
@message_date.add(1, 'day')
|
||||
expect(@message_item._timeFormat()).toBe "MMM D, h:mm a"
|
||||
|
||||
it "displays the time from messages yesterday with the day, even though it's less than 24 hours ago", ->
|
||||
spyOn(@message_item, "_today").andCallFake ->
|
||||
moment([2010, 1, 15, 2, 25, 50, 125])
|
||||
expect(@message_item._timeFormat()).toBe "MMM D, h:mm a"
|
||||
|
||||
it "displays the time from messages recently", ->
|
||||
spyOn(@message_item, "_today").andCallFake =>
|
||||
@message_date.add(2, 'hours')
|
||||
expect(@message_item._timeFormat()).toBe "h:mm a"
|
||||
|
||||
describe "MessageList with draft", ->
|
||||
beforeEach ->
|
||||
MessageStore._items = testMessages.concat draftMessages
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
moment = require 'moment'
|
||||
React = require 'react/addons'
|
||||
TestUtils = React.addons.TestUtils
|
||||
MessageTimestamp = require '../lib/message-timestamp.cjsx'
|
||||
|
||||
testDate = ->
|
||||
moment([2010, 1, 14, 15, 25, 50, 125])
|
||||
|
||||
describe "MessageTimestamp", ->
|
||||
beforeEach ->
|
||||
@item = TestUtils.renderIntoDocument(
|
||||
<MessageTimestamp date={testDate()} />
|
||||
)
|
||||
@itemNode = @item.getDOMNode()
|
||||
|
||||
# test messsage time is 1415814587
|
||||
it "displays the time from messages LONG ago", ->
|
||||
spyOn(@item, "_today").andCallFake -> testDate().add(2, 'years')
|
||||
expect(@item._timeFormat()).toBe "MMM D YYYY"
|
||||
|
||||
it "displays the time and date from messages a bit ago", ->
|
||||
spyOn(@item, "_today").andCallFake -> testDate().add(2, 'days')
|
||||
expect(@item._timeFormat()).toBe "MMM D, h:mm a"
|
||||
|
||||
it "displays the time and date messages exactly a day ago", ->
|
||||
spyOn(@item, "_today").andCallFake -> testDate().add(1, 'day')
|
||||
expect(@item._timeFormat()).toBe "MMM D, h:mm a"
|
||||
|
||||
it "displays the time from messages yesterday with the day, even though it's less than 24 hours ago", ->
|
||||
spyOn(@item, "_today").andCallFake -> moment([2010, 1, 15, 2, 25, 50, 125])
|
||||
expect(@item._timeFormat()).toBe "MMM D, h:mm a"
|
||||
|
||||
it "displays the time from messages recently", ->
|
||||
spyOn(@item, "_today").andCallFake -> testDate().add(2, 'hours')
|
||||
expect(@item._timeFormat()).toBe "h:mm a"
|
Loading…
Reference in a new issue