allow mark read/unread with unread badge

closes VICE-1128
flag=react_inbox

Test Plan:
 - go to inbox
 - create a conversation
 > mark read/unread with unread badge

Change-Id: Ifd89ad7705e2f68ce2edf70a6a8ecc4e86e93309
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/273709
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Omar Soto-Fortuño <omar.soto@instructure.com>
QA-Review: Omar Soto-Fortuño <omar.soto@instructure.com>
Product-Review: Omar Soto-Fortuño <omar.soto@instructure.com>
This commit is contained in:
Drake Harper 2021-09-16 10:05:09 -06:00
parent 9c9059c8c4
commit dfdcd0355e
5 changed files with 107 additions and 35 deletions

View File

@ -16,15 +16,20 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {AlertManagerContext} from '@canvas/alerts/react/AlertManager'
import I18n from 'i18n!conversations_2'
import PropTypes from 'prop-types'
import React, {useEffect, useState} from 'react'
import React, {useEffect, useState, useContext} from 'react'
import {useMutation} from 'react-apollo'
import {View} from '@instructure/ui-view'
import {MessageListItem, conversationProp} from './MessageListItem'
import {UPDATE_CONVERSATION_PARTICIPANTS} from '../../../graphql/Mutations'
export const MessageListHolder = ({...props}) => {
const [selectedMessages, setSelectedMessages] = useState([])
const [rangeClickStart, setRangeClickStart] = useState()
const {setOnFailure, setOnSuccess} = useContext(AlertManagerContext)
const provideConversationsForOnSelect = conversationIds => {
const matchedConversations = props.conversations
@ -118,6 +123,27 @@ export const MessageListHolder = ({...props}) => {
provideConversationsForOnSelect([...updatedSelectedMessage])
}
const [readStateChangeConversationParticipants] = useMutation(UPDATE_CONVERSATION_PARTICIPANTS, {
onCompleted(data) {
if (data.updateConversationParticipants.errors) {
setOnFailure(I18n.t('Read state change operation failed'))
} else {
setOnSuccess(
I18n.t(
{
one: 'Read state Changed!',
other: 'Read states Changed!'
},
{count: '1000'}
)
)
}
},
onError() {
setOnFailure(I18n.t('Read state change failed'))
}
})
return (
<View
as="div"
@ -139,6 +165,7 @@ export const MessageListHolder = ({...props}) => {
onSelect={handleItemSelection}
onStar={props.onStar}
key={conversation._id}
readStateChangeConversationParticipants={readStateChangeConversationParticipants}
/>
)
})}
@ -148,13 +175,15 @@ export const MessageListHolder = ({...props}) => {
const conversationParticipantsProp = PropTypes.shape({
id: PropTypes.string,
_id: PropTypes.string,
workflowState: PropTypes.string,
conversation: conversationProp
conversation: conversationProp,
label: PropTypes.string
})
MessageListHolder.propTypes = {
conversations: PropTypes.arrayOf(conversationParticipantsProp),
id: PropTypes.number,
id: PropTypes.string,
onOpen: PropTypes.func,
onSelect: PropTypes.func,
onStar: PropTypes.func

View File

@ -21,7 +21,12 @@ import {Button, IconButton} from '@instructure/ui-buttons'
import {Checkbox} from '@instructure/ui-checkbox'
import {Focusable} from '@instructure/ui-focusable'
import {Grid} from '@instructure/ui-grid'
import {IconStarLightLine, IconStarSolid} from '@instructure/ui-icons'
import {
IconStarLightLine,
IconStarSolid,
IconEmptyLine,
IconEmptySolid
} from '@instructure/ui-icons'
import PropTypes from 'prop-types'
import React, {useState} from 'react'
import {ScreenReaderContent} from '@instructure/ui-a11y-content'
@ -161,17 +166,25 @@ export const MessageListItem = ({...props}) => {
<Grid.Row>
<Grid.Col width="auto">
<View textAlign="center" as="div" width={30} height={30} margin="0 small 0 0">
{props.isUnread && (
<Badge
type="notification"
data-testid="unread-badge"
standalone
margin="x-small"
formatOutput={() => {
return <ScreenReaderContent>{I18n.t('Unread')}</ScreenReaderContent>
}}
/>
)}
<IconButton
color="primary"
data-testid={props.isUnread ? 'unread-badge' : 'read-badge'}
margin="x-small"
onClick={() =>
props.readStateChangeConversationParticipants({
variables: {
conversationIds: [props.conversation._id],
workflowState: props.isUnread ? 'read' : 'unread'
}
})
}
screenReaderLabel={props.isUnread ? I18n.t('Unread') : I18n.t('Read')}
size="small"
withBackground={false}
withBorder={false}
>
{props.isUnread ? <IconEmptySolid /> : <IconEmptyLine />}
</IconButton>
</View>
</Grid.Col>
<Grid.Col>{formatParticipants()}</Grid.Col>
@ -277,7 +290,8 @@ export const conversationProp = PropTypes.shape({
subject: PropTypes.string,
participants: PropTypes.arrayOf(participantProp),
conversationMessages: PropTypes.arrayOf(conversationMessageProp),
conversationMessagesConnection: PropTypes.object
conversationMessagesConnection: PropTypes.object,
conversationParticipantsConnection: PropTypes.object
})
MessageListItem.propTypes = {
@ -288,5 +302,6 @@ MessageListItem.propTypes = {
isUnread: PropTypes.bool,
onOpen: PropTypes.func,
onSelect: PropTypes.func,
onStar: PropTypes.func
onStar: PropTypes.func,
readStateChangeConversationParticipants: PropTypes.func
}

View File

@ -24,6 +24,7 @@ describe('MessageListItem', () => {
const createProps = overrides => {
return {
conversation: {
_id: '191',
subject: 'This is the subject line',
conversationParticipantsConnection: {
nodes: [
@ -118,8 +119,36 @@ describe('MessageListItem', () => {
it('renders the unread badge when the conversation is unread', () => {
const props = createProps({isUnread: true})
const {getByText} = render(<MessageListItem {...props} />)
const container = render(<MessageListItem {...props} />)
expect(getByText('Unread')).toBeInTheDocument()
expect(container.getByText('Unread')).toBeInTheDocument()
expect(container.getByTestId('unread-badge')).toBeInTheDocument()
})
it('renders the read badge when the conversation is read', () => {
const props = createProps()
const container = render(<MessageListItem {...props} />)
expect(container.getByText('Read')).toBeInTheDocument()
expect(container.getByTestId('read-badge')).toBeInTheDocument()
})
it('update read state called with correct parameters', () => {
const changeReadState = jest.fn()
const props = createProps({readStateChangeConversationParticipants: changeReadState})
const container = render(<MessageListItem {...props} />)
const unreadBadge = container.queryByTestId('read-badge')
fireEvent.click(unreadBadge)
expect(changeReadState).toHaveBeenCalledWith({
variables: {
conversationIds: ['191'],
workflowState: 'unread'
}
})
})
})

View File

@ -160,21 +160,6 @@ const MessageListActionContainer = props => {
}
}
const handleReadStateComplete = data => {
const readStateChangeSuccessMsg = I18n.t(
{
one: 'Read state Changed!',
other: 'Read states Changed!'
},
{count: props.selectedConversations.length}
)
if (data.updateConversationParticipants.errors) {
setOnFailure(I18n.t('Read state change operation failed'))
} else {
setOnSuccess(readStateChangeSuccessMsg)
}
}
const [archiveConversationParticipants] = useMutation(UPDATE_CONVERSATION_PARTICIPANTS, {
update: removeOutOfScopeConversationsFromCache,
onCompleted(data) {
@ -187,7 +172,19 @@ const MessageListActionContainer = props => {
const [readStateChangeConversationParticipants] = useMutation(UPDATE_CONVERSATION_PARTICIPANTS, {
onCompleted(data) {
handleReadStateComplete(data)
if (data.updateConversationParticipants.errors) {
setOnFailure(I18n.t('Read state change operation failed'))
} else {
setOnSuccess(
I18n.t(
{
one: 'Read state Changed!',
other: 'Read states Changed!'
},
{count: props.selectedConversations.length}
)
)
}
},
onError() {
setOnFailure(I18n.t('Read state change failed'))

View File

@ -106,6 +106,8 @@ describe('CanvasInbox Full Page', () => {
).toBeInTheDocument()
})
// TODO: will be fixed with VICE-2077
// eslint-disable-next-line jest/no-disabled-tests
it.skip('should change the read state of a message', async () => {
const container = setup()
const conversation = await container.findByTestId('messageListItem-Checkbox')