spec: Remove enzyme.mount from DiscussionRowSpec
fixes LF-1433 flags=none test plan: - tests pass - tests are still testing the same things Change-Id: I630ae7a103e9850dae36395b54589f7e69f4c3bb Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/345962 Reviewed-by: Daniel Matyas Vincze <daniel.vincze@instructure.com> Reviewed-by: Eric Saupe <eric.saupe@instructure.com> Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> QA-Review: Eric Saupe <eric.saupe@instructure.com> Product-Review: Roland Beres <roland.beres@instructure.com>
This commit is contained in:
parent
a61791afec
commit
732f7bc186
|
@ -54,6 +54,7 @@ const ignoredErrors = [
|
|||
/You seem to have overlapping act\(\) calls/,
|
||||
/Warning: `value` prop on `%s` should not be null. Consider using an empty string to clear the component or `undefined` for uncontrolled components.%s/,
|
||||
/Warning: This synthetic event is reused for performance reasons/,
|
||||
/Invalid prop `value` supplied to `MenuItem`/, // https://instructure.atlassian.net/browse/INSTUI-4054
|
||||
]
|
||||
const globalWarn = global.console.warn
|
||||
const ignoredWarnings = [
|
||||
|
|
|
@ -1,862 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2018 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import {mount} from 'enzyme'
|
||||
import {merge} from 'lodash'
|
||||
import fakeENV from 'helpers/fakeENV'
|
||||
import {DiscussionRow} from 'ui/features/discussion_topics_index/react/components/DiscussionRow'
|
||||
|
||||
// We can't call the wrapped component because a lot of these tests are depending
|
||||
// on the class component instances. So we've got to cobble up enough of the date
|
||||
// formatter to send in as a prop.
|
||||
const dateFormatter = date => {
|
||||
const fmtr = Intl.DateTimeFormat('en').format
|
||||
try {
|
||||
if (date === null) return ''
|
||||
return fmtr(date instanceof Date ? date : new Date(date))
|
||||
} catch (e) {
|
||||
if (e instanceof RangeError) return ''
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
QUnit.module('DiscussionRow component')
|
||||
|
||||
const makeProps = (props = {}) =>
|
||||
merge(
|
||||
{
|
||||
discussion: {
|
||||
id: '1',
|
||||
position: 1,
|
||||
published: true,
|
||||
title: 'Hello World',
|
||||
message: 'Foo bar bar baz boop beep bop Foo',
|
||||
posted_at: 'January 10, 2019 at 10:00 AM',
|
||||
can_unpublish: true,
|
||||
author: {
|
||||
id: '5',
|
||||
display_name: 'John Smith',
|
||||
html_url: '',
|
||||
avatar_image_url: null,
|
||||
},
|
||||
read_state: 'unread',
|
||||
unread_count: 0,
|
||||
discussion_subentry_count: 5,
|
||||
locked: false,
|
||||
html_url: '',
|
||||
user_count: 10,
|
||||
last_reply_at: new Date(2018, 1, 14, 0, 0, 0, 0),
|
||||
},
|
||||
canPublish: false,
|
||||
masterCourseData: {},
|
||||
setCopyTo: () => {},
|
||||
setSendToOpen: () => {},
|
||||
DIRECT_SHARE_ENABLED: false,
|
||||
dateFormatter,
|
||||
},
|
||||
props
|
||||
)
|
||||
|
||||
test('renders the DiscussionRow component', () => {
|
||||
const tree = mount(<DiscussionRow {...makeProps()} />)
|
||||
ok(tree.exists())
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('renders UnreadBadge if discussion has replies > 0', () => {
|
||||
const discussion = {discussion_subentry_count: 5}
|
||||
const tree = mount(<DiscussionRow {...makeProps({discussion})} />)
|
||||
const node = tree.find('UnreadBadge')
|
||||
ok(node.exists())
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('renders Correct Screenreader message for locked discussions', () => {
|
||||
const discussion = {locked: false, title: 'blerp'}
|
||||
const tree = mount(<DiscussionRow {...makeProps({discussion})} />)
|
||||
const instance = tree.instance() // Unintuitive but remember this is the message it will do not what it actually is
|
||||
equal(instance.makeLockedSuccessFailMessages().successMessage, 'Lock discussion blerp succeeded')
|
||||
equal(instance.makeLockedSuccessFailMessages().failMessage, 'Lock discussion blerp failed')
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('renders title as a link', () => {
|
||||
const discussion = {locked: false, title: 'blerp'}
|
||||
const tree = mount(<DiscussionRow {...makeProps({discussion})} />)
|
||||
const node = tree.find('Link')
|
||||
ok(node.exists())
|
||||
ok(node.first().text().includes(discussion.title))
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('when feature flag is off, anonymous title is plain text ', () => {
|
||||
fakeENV.setup()
|
||||
ENV.discussion_anonymity_enabled = false
|
||||
const discussion = {locked: false, title: 'blerp', anonymous_state: 'full_anonymity'}
|
||||
const tree = mount(<DiscussionRow {...makeProps({discussion})} />)
|
||||
const node = tree.find('[data-testid="discussion-title-1"]')
|
||||
ok(node.exists())
|
||||
notOk(tree.find('Link').exists())
|
||||
tree.unmount()
|
||||
fakeENV.teardown()
|
||||
})
|
||||
|
||||
test('renders Correct Screenreader message for unlocked discussions', () => {
|
||||
const discussion = {locked: true, title: 'blerp'}
|
||||
const tree = mount(<DiscussionRow {...makeProps({discussion})} />)
|
||||
const instance = tree.instance() // Unintuitive but remember this is the message it will do not what it actually is
|
||||
equal(
|
||||
instance.makeLockedSuccessFailMessages().successMessage,
|
||||
'Unlock discussion blerp succeeded'
|
||||
)
|
||||
equal(instance.makeLockedSuccessFailMessages().failMessage, 'Unlock discussion blerp failed')
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('does not render UnreadBadge if discussion has replies == 0', () => {
|
||||
const discussion = {discussion_subentry_count: 0}
|
||||
const tree = mount(<DiscussionRow {...makeProps({discussion})} />)
|
||||
const node = tree.find('UnreadBadge')
|
||||
notOk(node.exists())
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('renders ReadBadge if discussion is unread', () => {
|
||||
const discussion = {read_state: 'unread'}
|
||||
const tree = mount(<DiscussionRow {...makeProps({discussion})} />)
|
||||
const node = tree.find('Badge')
|
||||
ok(node.exists())
|
||||
})
|
||||
|
||||
test('does not render ReadBadge if discussion is read', () => {
|
||||
const discussion = {read_state: 'read'}
|
||||
const tree = mount(<DiscussionRow {...makeProps({discussion})} />)
|
||||
const node = tree.find('Badge')
|
||||
notOk(node.exists())
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('renders ReadBadge if discussion has replies == 0', () => {
|
||||
const discussion = {discussion_subentry_count: 0}
|
||||
const tree = mount(<DiscussionRow {...makeProps({discussion})} />)
|
||||
const node = tree.find('UnreadBadge')
|
||||
notOk(node.exists())
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('renders the subscription ToggleIcon', () => {
|
||||
const tree = mount(<DiscussionRow {...makeProps()} />)
|
||||
const node = tree.find('ToggleIcon')
|
||||
ok(node.exists())
|
||||
strictEqual(node.length, 1)
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('disables publish button when can_unpublish is false', () => {
|
||||
const discussion = {can_unpublish: false}
|
||||
const tree = mount(<DiscussionRow {...makeProps({canPublish: true, discussion})} />)
|
||||
const node = tree.find('ToggleIcon .publish-button')
|
||||
strictEqual(node.props().children.props.disabled, true)
|
||||
})
|
||||
|
||||
test('allows to publish even if you cannot unpublish', () => {
|
||||
const discussion = {can_unpublish: false, published: false}
|
||||
const tree = mount(<DiscussionRow {...makeProps({canPublish: true, discussion})} />)
|
||||
const node = tree.find('ToggleIcon .publish-button')
|
||||
strictEqual(node.props().children.props.disabled, false)
|
||||
})
|
||||
|
||||
test('renders the publish ToggleIcon', () => {
|
||||
const tree = mount(<DiscussionRow {...makeProps({canPublish: true})} />)
|
||||
const node = tree.find('ToggleIcon')
|
||||
ok(node.exists())
|
||||
strictEqual(node.length, 2)
|
||||
})
|
||||
|
||||
test('when feature flag is off, renders anonymous discussion lock explanation for read_as_admin', () => {
|
||||
fakeENV.setup()
|
||||
ENV.discussion_anonymity_enabled = false
|
||||
const discussion = {locked: false, title: 'blerp', anonymous_state: 'full_anonymity'}
|
||||
const tree = mount(<DiscussionRow {...makeProps({canReadAsAdmin: true, discussion})} />)
|
||||
const node = tree.find('.discussion-availability Link')
|
||||
ok(node.exists())
|
||||
ok(node.first().text().includes('Discussions/Announcements Redesign'))
|
||||
tree.unmount()
|
||||
fakeENV.teardown()
|
||||
})
|
||||
|
||||
test('when feature flag is off, renders anonymous discussion unavailable for students, etc.', () => {
|
||||
fakeENV.setup()
|
||||
ENV.discussion_anonymity_enabled = false
|
||||
const discussion = {locked: false, title: 'blerp', anonymous_state: 'full_anonymity'}
|
||||
const tree = mount(<DiscussionRow {...makeProps({canReadAsAdmin: false, discussion})} />)
|
||||
const node = tree.find('.discussion-availability')
|
||||
|
||||
ok(node.text().includes('Unavailable'))
|
||||
ok(node.exists())
|
||||
tree.unmount()
|
||||
fakeENV.teardown()
|
||||
})
|
||||
|
||||
test('when feature flag is off, renders partially anonymous discussion unavailable for students, etc.', () => {
|
||||
fakeENV.setup()
|
||||
ENV.discussion_anonymity_enabled = false
|
||||
const discussion = {locked: false, title: 'blerp', anonymous_state: 'partial_anonymity'}
|
||||
const tree = mount(<DiscussionRow {...makeProps({canReadAsAdmin: false, discussion})} />)
|
||||
const node = tree.find('.discussion-availability')
|
||||
|
||||
ok(node.text().includes('Unavailable'))
|
||||
ok(node.exists())
|
||||
tree.unmount()
|
||||
fakeENV.teardown()
|
||||
})
|
||||
|
||||
test('renders "Delayed until" date label if discussion is delayed', () => {
|
||||
const delayedDate = new Date()
|
||||
delayedDate.setYear(delayedDate.getFullYear() + 1)
|
||||
const discussion = {delayed_post_at: delayedDate.toString()}
|
||||
const tree = mount(<DiscussionRow {...makeProps({discussion})} />)
|
||||
const node = tree.find('.discussion-availability')
|
||||
ok(node.text().includes('Not available'))
|
||||
ok(node.exists())
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('renders a last reply at date', () => {
|
||||
const tree = mount(<DiscussionRow {...makeProps()} />)
|
||||
const node = tree.find('.last-reply-at')
|
||||
ok(node.exists())
|
||||
ok(node.first().text().includes('Last post at'))
|
||||
ok(node.first().text().includes('2/14'))
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('does not render last reply at date if there is none', () => {
|
||||
const discussion = {last_reply_at: ''}
|
||||
const tree = mount(<DiscussionRow {...makeProps({discussion})} />)
|
||||
const node = tree.find('.ic-discussion-row')
|
||||
ok(!node.text().includes('Last post at'))
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('renders available until if approprate', () => {
|
||||
const futureDate = new Date()
|
||||
futureDate.setYear(futureDate.getFullYear() + 1)
|
||||
const discussion = {lock_at: futureDate}
|
||||
const tree = mount(<DiscussionRow {...makeProps({discussion})} />)
|
||||
const node = tree.find('.discussion-availability')
|
||||
ok(node.exists())
|
||||
ok(node.text().includes('Available until'))
|
||||
// We need a relative date to ensure future-ness, so we can't really insist
|
||||
// on a given date element appearing this time
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('renders locked at if appropriate', () => {
|
||||
const pastDate = new Date()
|
||||
pastDate.setYear(pastDate.getFullYear() - 1)
|
||||
const discussion = {lock_at: pastDate}
|
||||
const tree = mount(<DiscussionRow {...makeProps({discussion})} />)
|
||||
const node = tree.find('.discussion-availability')
|
||||
ok(node.exists())
|
||||
ok(node.text().includes('No longer available'))
|
||||
// We need a relative date to ensure past-ness, so we can't really insist
|
||||
// on a given date element appearing this time
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('renders nothing if currently available and no end date', () => {
|
||||
const tree = mount(<DiscussionRow {...makeProps()} />)
|
||||
let node = tree.find('.discussion-available-until')
|
||||
notOk(node.exists())
|
||||
node = tree.find('.discussion-delayed-until')
|
||||
notOk(node.exists())
|
||||
node = tree.find('.discussion-was-locked')
|
||||
notOk(node.exists())
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('renders due date if graded with a due date', () => {
|
||||
const props = makeProps({
|
||||
discussion: {
|
||||
assignment: {
|
||||
due_at: '2018-07-01T05:59:00Z',
|
||||
},
|
||||
},
|
||||
})
|
||||
const tree = mount(<DiscussionRow {...props} />)
|
||||
let node = tree.find('.due-date')
|
||||
ok(node.exists())
|
||||
node = tree.find('.todo-date')
|
||||
notOk(node.exists())
|
||||
})
|
||||
|
||||
test('renders to do date if ungraded with a to do date', () => {
|
||||
const props = makeProps({
|
||||
discussion: {
|
||||
todo_date: '2018-07-01T05:59:00Z',
|
||||
},
|
||||
})
|
||||
const tree = mount(<DiscussionRow {...props} />)
|
||||
let node = tree.find('.todo-date')
|
||||
ok(node.exists())
|
||||
node = tree.find('due-date')
|
||||
notOk(node.exists())
|
||||
})
|
||||
|
||||
test('renders neither a due or to do date if neither are available', () => {
|
||||
const tree = mount(<DiscussionRow {...makeProps()} />)
|
||||
let node = tree.find('.todo-date')
|
||||
notOk(node.exists())
|
||||
node = tree.find('due-date')
|
||||
notOk(node.exists())
|
||||
})
|
||||
|
||||
test('renders the SectionsTooltip component', () => {
|
||||
const discussion = {user_count: 200}
|
||||
const tree = mount(<DiscussionRow {...makeProps({discussion})} />)
|
||||
equal(tree.find('SectionsTooltip Text').at(0).text(), 'All Sections')
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('renders the SectionsTooltip component with sections', () => {
|
||||
const discussion = {
|
||||
sections: [
|
||||
{id: 6, course_id: 1, name: 'section 4', user_count: 2},
|
||||
{id: 5, course_id: 1, name: 'section 2', user_count: 1},
|
||||
],
|
||||
}
|
||||
const tree = mount(<DiscussionRow {...makeProps({discussion})} />)
|
||||
equal(tree.find('SectionsTooltip Text').at(0).text(), '2 Sectionssection 4section 2')
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('includes Anonymous Discussion prefix when discussion is anonymous', () => {
|
||||
fakeENV.setup()
|
||||
ENV.discussion_anonymity_enabled = false
|
||||
const discussion = {
|
||||
sections: [
|
||||
{id: 6, course_id: 1, name: 'section 4', user_count: 2},
|
||||
{id: 5, course_id: 1, name: 'section 2', user_count: 1},
|
||||
],
|
||||
anonymous_state: 'full_anonymity',
|
||||
}
|
||||
const tree = mount(<DiscussionRow {...makeProps({discussion})} />)
|
||||
equal(
|
||||
tree.find('SectionsTooltip Text').at(0).text(),
|
||||
'Anonymous Discussion | 2 Sectionssection 4section 2'
|
||||
)
|
||||
tree.unmount()
|
||||
fakeENV.teardown()
|
||||
})
|
||||
|
||||
test('includes Partially Anonymous Discussion prefix when discussion is anonymous', () => {
|
||||
fakeENV.setup()
|
||||
ENV.discussion_anonymity_enabled = false
|
||||
const discussion = {
|
||||
sections: [
|
||||
{id: 6, course_id: 1, name: 'section 4', user_count: 2},
|
||||
{id: 5, course_id: 1, name: 'section 2', user_count: 1},
|
||||
],
|
||||
anonymous_state: 'partial_anonymity',
|
||||
}
|
||||
const tree = mount(<DiscussionRow {...makeProps({discussion})} />)
|
||||
equal(
|
||||
tree.find('SectionsTooltip Text').at(0).text(),
|
||||
'Partially Anonymous Discussion | 2 Sectionssection 4section 2'
|
||||
)
|
||||
tree.unmount()
|
||||
fakeENV.teardown()
|
||||
})
|
||||
|
||||
test('does not render the SectionsTooltip component on a graded discussion', () => {
|
||||
const discussion = {user_count: 200, assignment: true}
|
||||
const tree = mount(<DiscussionRow {...makeProps({discussion})} />)
|
||||
const node = tree.find('SectionsTooltip')
|
||||
notOk(node.exists())
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('does not render the SectionsTooltip component on a group discussion', () => {
|
||||
const discussion = {user_count: 200, group_category_id: 13}
|
||||
const tree = mount(<DiscussionRow {...makeProps({discussion})} />)
|
||||
const node = tree.find('SectionsTooltip')
|
||||
notOk(node.exists())
|
||||
})
|
||||
|
||||
test('does not render the SectionsTooltip component within a group context', () => {
|
||||
const discussion = {user_count: 200}
|
||||
const tree = mount(<DiscussionRow {...makeProps({discussion, contextType: 'group'})} />)
|
||||
const node = tree.find('SectionsTooltip')
|
||||
notOk(node.exists())
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('does not render the SectionsTooltip component in a blueprint course', () => {
|
||||
const discussion = {user_count: 200}
|
||||
const tree = mount(<DiscussionRow {...makeProps({discussion, isMasterCourse: true})} />)
|
||||
const node = tree.find('SectionsTooltip')
|
||||
notOk(node.exists())
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('does not render master course lock icon if masterCourseData is not provided', done => {
|
||||
const masterCourseData = null
|
||||
const rowRef = row => {
|
||||
notOk(row.masterCourseLock)
|
||||
done()
|
||||
}
|
||||
const tree = mount(<DiscussionRow {...makeProps({masterCourseData, rowRef})} />)
|
||||
notOk(tree.instance().masterCourseLock)
|
||||
})
|
||||
|
||||
test('renders master course lock icon if masterCourseData is provided', done => {
|
||||
const masterCourseData = {isMasterCourse: true, masterCourse: {id: '1'}}
|
||||
const rowRef = row => {
|
||||
ok(row.masterCourseLock)
|
||||
done()
|
||||
}
|
||||
const tree = mount(<DiscussionRow {...makeProps({masterCourseData, rowRef})} />)
|
||||
ok(tree.instance().masterCourseLock)
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('renders drag icon', () => {
|
||||
const tree = mount(<DiscussionRow {...makeProps({draggable: true})} />)
|
||||
const node = tree.find('IconDragHandleLine')
|
||||
ok(node.exists())
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('does not render manage menu if not permitted', () => {
|
||||
const tree = mount(<DiscussionRow {...makeProps({displayManageMenu: false})} />)
|
||||
const node = tree.find('DiscussionManageMenu')
|
||||
notOk(node.exists())
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('does not insert the manage menu list if we have not clicked it yet', () => {
|
||||
const tree = mount(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
displayManageMenu: true,
|
||||
onMoveDiscussion: () => {},
|
||||
})}
|
||||
/>
|
||||
)
|
||||
// We still should show the menu thingy itself
|
||||
const menuNode = tree.find('DiscussionManageMenu')
|
||||
ok(menuNode.exists())
|
||||
// We have to search the whole document because the items in instui
|
||||
// popover menu are appended to the end of the document rather than
|
||||
// within the popover menu or even the discussion row
|
||||
const menuItemNode = document.querySelector('#moveTo-discussion-menu-option')
|
||||
equal(menuItemNode, null)
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('manage menu items do appear upon click', () => {
|
||||
const tree = mount(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
displayManageMenu: true,
|
||||
onMoveDiscussion: () => {},
|
||||
})}
|
||||
/>
|
||||
)
|
||||
const menuNode = tree.find('DiscussionManageMenu')
|
||||
ok(menuNode.exists())
|
||||
menuNode.find('button').simulate('click')
|
||||
// We have to search the whole document because the items in instui
|
||||
// popover menu are appended to the end of the document rather than
|
||||
// within the popover menu or even the discussion row
|
||||
const menuItemNode = document.querySelector('#moveTo-discussion-menu-option')
|
||||
ok(menuItemNode.textContent.includes('Move To'))
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('does not render sharing menu options if not DIRECT_SHARE_ENABLED', () => {
|
||||
const props = makeProps({displayManageMenu: true, DIRECT_SHARE_ENABLED: false})
|
||||
const tree = mount(<DiscussionRow {...props} />)
|
||||
tree.find('DiscussionManageMenu').find('button').simulate('click')
|
||||
notOk(document.querySelector('#copyTo-discussion-menu-option'))
|
||||
notOk(document.querySelector('#sendTo-discussion-menu-option'))
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('only leaves pin/unpin open/close for comments, and delete when inaccessibleDueToAnonymity', () => {
|
||||
fakeENV.setup()
|
||||
ENV.discussion_anonymity_enabled = false
|
||||
const discussion = {locked: false, title: 'blerp', anonymous_state: 'full_anonymity'}
|
||||
const tree = mount(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
discussion,
|
||||
displayManageMenu: true,
|
||||
DIRECT_SHARE_ENABLED: true,
|
||||
displayDeleteMenuItem: true,
|
||||
displayPinMenuItem: true,
|
||||
displayLockMenuItem: true,
|
||||
canPublish: true,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
const manageMenu = tree.find('DiscussionManageMenu')
|
||||
|
||||
const allKeys = manageMenu
|
||||
.props()
|
||||
.menuOptions()
|
||||
.map(option => option.key)
|
||||
|
||||
ok(allKeys.length, 3)
|
||||
ok(allKeys[0], 'togglelocked')
|
||||
ok(allKeys[1], 'togglepinned')
|
||||
ok(allKeys[2], 'delete')
|
||||
notOk(tree.find('ToggleIcon .publish-button').exists())
|
||||
notOk(tree.find('ToggleIcon .subscribe-button').exists())
|
||||
tree.unmount()
|
||||
fakeENV.teardown()
|
||||
})
|
||||
|
||||
test('renders sharing menu options if DIRECT_SHARE_ENABLED', () => {
|
||||
const props = makeProps({displayManageMenu: true, DIRECT_SHARE_ENABLED: true})
|
||||
const tree = mount(<DiscussionRow {...props} />)
|
||||
tree.find('DiscussionManageMenu').find('button').simulate('click')
|
||||
ok(document.querySelector('#copyTo-discussion-menu-option'))
|
||||
ok(document.querySelector('#sendTo-discussion-menu-option'))
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('opens the copyTo tray when menu item is selected', () => {
|
||||
const copySpy = sinon.spy()
|
||||
const props = makeProps({
|
||||
displayManageMenu: true,
|
||||
DIRECT_SHARE_ENABLED: true,
|
||||
setCopyTo: copySpy,
|
||||
})
|
||||
const tree = mount(<DiscussionRow {...props} />)
|
||||
tree.find('DiscussionManageMenu').find('button').simulate('click')
|
||||
document.querySelector('#copyTo-discussion-menu-option').click()
|
||||
deepEqual(copySpy.firstCall.args[0], {
|
||||
open: true,
|
||||
selection: {discussion_topics: [props.discussion.id]},
|
||||
})
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('opens the sendTo tray when menu item is selected', () => {
|
||||
const sendSpy = sinon.spy()
|
||||
const props = makeProps({
|
||||
displayManageMenu: true,
|
||||
DIRECT_SHARE_ENABLED: true,
|
||||
setSendTo: sendSpy,
|
||||
})
|
||||
const tree = mount(<DiscussionRow {...props} />)
|
||||
tree.find('DiscussionManageMenu').find('button').simulate('click')
|
||||
document.querySelector('#sendTo-discussion-menu-option').click()
|
||||
deepEqual(sendSpy.firstCall.args[0], {
|
||||
open: true,
|
||||
selection: {
|
||||
content_type: 'discussion_topic',
|
||||
content_id: props.discussion.id,
|
||||
},
|
||||
})
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('renders available date and delayed date on graded discussions', () => {
|
||||
const tree = mount(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
discussion: {
|
||||
assignment: {
|
||||
lock_at: '2018-07-01T05:59:00Z',
|
||||
unlock_at: '2018-06-21T06:00:00Z',
|
||||
},
|
||||
id: '1',
|
||||
position: 1,
|
||||
published: true,
|
||||
title: 'Hello World',
|
||||
message: 'Foo bar bar baz boop beep bop Foo',
|
||||
posted_at: 'January 10, 2019 at 10:00 AM',
|
||||
can_unpublish: true,
|
||||
author: {
|
||||
id: '5',
|
||||
display_name: 'John Smith',
|
||||
html_url: '',
|
||||
avatar_image_url: null,
|
||||
},
|
||||
read_state: 'unread',
|
||||
unread_count: 0,
|
||||
discussion_subentry_count: 5,
|
||||
locked: false,
|
||||
html_url: '',
|
||||
user_count: 10,
|
||||
delayed_post_at: '2018-06-21T06:00:00Z',
|
||||
last_reply_at: new Date(2018, 1, 14, 0, 0, 0, 0),
|
||||
},
|
||||
})}
|
||||
/>
|
||||
)
|
||||
let node = tree.find('.discussion-availability')
|
||||
ok(node.exists())
|
||||
node = tree.find('.ic-discussion-row__content')
|
||||
ok(node.exists())
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('renders move-to in manage menu if permitted', () => {
|
||||
const tree = mount(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
displayManageMenu: true,
|
||||
onMoveDiscussion: () => {},
|
||||
})}
|
||||
/>
|
||||
)
|
||||
const manageMenu = tree.find('DiscussionManageMenu')
|
||||
const allKeys = manageMenu
|
||||
.props()
|
||||
.menuOptions()
|
||||
.map(option => option.key)
|
||||
equal(allKeys.length, 1)
|
||||
equal(allKeys[0], 'moveTo')
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('renders pin item in manage menu if permitted', () => {
|
||||
const tree = mount(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
displayManageMenu: true,
|
||||
displayPinMenuItem: true,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
const manageMenu = tree.find('DiscussionManageMenu')
|
||||
const allKeys = manageMenu
|
||||
.props()
|
||||
.menuOptions()
|
||||
.map(option => option.key)
|
||||
equal(allKeys.length, 1)
|
||||
equal(allKeys[0], 'togglepinned')
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('renders speedgrader link in manage menu if permitted', () => {
|
||||
ENV.show_additional_speed_grader_links = true
|
||||
const tree = mount(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
displayManageMenu: true,
|
||||
discussion: {
|
||||
assignment: {
|
||||
lock_at: '2018-07-01T05:59:00Z',
|
||||
unlock_at: '2018-06-21T06:00:00Z',
|
||||
id: '50',
|
||||
},
|
||||
id: '1',
|
||||
position: 1,
|
||||
published: true,
|
||||
title: 'Hello World',
|
||||
message: 'Foo bar bar baz boop beep bop Foo',
|
||||
posted_at: 'January 10, 2019 at 10:00 AM',
|
||||
can_unpublish: true,
|
||||
author: {
|
||||
id: '5',
|
||||
display_name: 'John Smith',
|
||||
html_url: '',
|
||||
avatar_image_url: null,
|
||||
},
|
||||
read_state: 'unread',
|
||||
unread_count: 0,
|
||||
discussion_subentry_count: 5,
|
||||
locked: false,
|
||||
html_url: '',
|
||||
user_count: 10,
|
||||
delayed_post_at: '2018-06-21T06:00:00Z',
|
||||
last_reply_at: new Date(2018, 1, 14, 0, 0, 0, 0),
|
||||
},
|
||||
})}
|
||||
/>
|
||||
)
|
||||
const manageMenu = tree.find('DiscussionManageMenu')
|
||||
const allKeys = manageMenu
|
||||
.props()
|
||||
.menuOptions()
|
||||
.map(option => option.key)
|
||||
equal(allKeys.length, 1)
|
||||
equal(allKeys[0], 'speed-grader-link')
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('renders duplicate item in manage menu if permitted', () => {
|
||||
const tree = mount(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
displayManageMenu: true,
|
||||
displayDuplicateMenuItem: true,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
const manageMenu = tree.find('DiscussionManageMenu')
|
||||
const allKeys = manageMenu
|
||||
.props()
|
||||
.menuOptions()
|
||||
.map(option => option.key)
|
||||
equal(allKeys.length, 1)
|
||||
equal(allKeys[0], 'duplicate')
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('renders delete item in manage menu if permitted', () => {
|
||||
const tree = mount(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
displayManageMenu: true,
|
||||
displayDeleteMenuItem: true,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
const manageMenu = tree.find('DiscussionManageMenu')
|
||||
const allKeys = manageMenu
|
||||
.props()
|
||||
.menuOptions()
|
||||
.map(option => option.key)
|
||||
equal(allKeys.length, 1)
|
||||
equal(allKeys[0], 'delete')
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('renders lock item in manage menu if permitted', () => {
|
||||
const tree = mount(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
displayManageMenu: true,
|
||||
displayLockMenuItem: true,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
const manageMenu = tree.find('DiscussionManageMenu')
|
||||
const allKeys = manageMenu
|
||||
.props()
|
||||
.menuOptions()
|
||||
.map(option => option.key)
|
||||
equal(allKeys.length, 1)
|
||||
equal(allKeys[0], 'togglelocked')
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('renders mastery paths menu item if permitted', () => {
|
||||
const tree = mount(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
displayManageMenu: true,
|
||||
discussion: {
|
||||
assignment_id: 2,
|
||||
},
|
||||
displayMasteryPathsMenuItem: true,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
const manageMenu = tree.find('DiscussionManageMenu')
|
||||
const allKeys = manageMenu
|
||||
.props()
|
||||
.menuOptions()
|
||||
.map(option => option.key)
|
||||
equal(allKeys.length, 1)
|
||||
equal(allKeys[0], 'masterypaths')
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('renders mastery paths link if permitted', () => {
|
||||
const tree = mount(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
displayManageMenu: true,
|
||||
discussion: {
|
||||
assignment_id: 2,
|
||||
},
|
||||
displayMasteryPathsLink: true,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
const node = tree.find('.discussion-index-mastery-paths-link')
|
||||
ok(node.exists())
|
||||
ok(node.text().includes('Mastery Paths'))
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('renders ltiTool menu if there are some', () => {
|
||||
const tree = mount(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
displayManageMenu: true,
|
||||
discussionTopicMenuTools: [
|
||||
{
|
||||
base_url: 'test.com',
|
||||
canvas_icon_class: 'icon-lti',
|
||||
icon_url: 'iconUrltest.com',
|
||||
title: 'discussion_topic_menu Text',
|
||||
},
|
||||
],
|
||||
})}
|
||||
/>
|
||||
)
|
||||
const manageMenu = tree.find('DiscussionManageMenu')
|
||||
const allKeys = manageMenu
|
||||
.props()
|
||||
.menuOptions()
|
||||
.map(option => option.key)
|
||||
equal(allKeys.length, 1)
|
||||
equal(allKeys[0], 'test.com')
|
||||
tree.unmount()
|
||||
})
|
||||
|
||||
test('renders multiple ltiTool menu if there are multiple', () => {
|
||||
const tree = mount(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
displayManageMenu: true,
|
||||
discussionTopicMenuTools: [
|
||||
{
|
||||
base_url: 'test.com',
|
||||
canvas_icon_class: 'icon-lti',
|
||||
icon_url: 'iconUrltest.com',
|
||||
title: 'discussion_topic_menu Text',
|
||||
},
|
||||
{
|
||||
base_url: 'test2.com',
|
||||
canvas_icon_class: 'icon-lti',
|
||||
icon_url: 'iconUrltest2.com',
|
||||
title: 'discussion_topic_menu otherText',
|
||||
},
|
||||
],
|
||||
})}
|
||||
/>
|
||||
)
|
||||
const manageMenu = tree.find('DiscussionManageMenu')
|
||||
const allKeys = manageMenu
|
||||
.props()
|
||||
.menuOptions()
|
||||
.map(option => option.key)
|
||||
equal(allKeys.length, 2)
|
||||
equal(allKeys[1], 'test2.com')
|
||||
tree.unmount()
|
||||
})
|
|
@ -663,7 +663,7 @@ class DiscussionRow extends Component {
|
|||
renderDragHandleIfAppropriate = () => {
|
||||
if (this.props.draggable && this.props.connectDragSource) {
|
||||
return (
|
||||
<div className="ic-item-row__drag-col">
|
||||
<div className="ic-item-row__drag-col" data-testid="ic-drag-handle-icon-container">
|
||||
<span>
|
||||
<Text color="secondary" size="large">
|
||||
<IconDragHandleLine />
|
||||
|
@ -836,7 +836,11 @@ class DiscussionRow extends Component {
|
|||
{maybeRenderMasteryPathsLink}
|
||||
{maybeRenderPeerReviewIcon}
|
||||
{actionsContent}
|
||||
<span ref={this.initializeMasterCourseIcon} className="ic-item-row__master-course-lock" />
|
||||
<span
|
||||
ref={this.initializeMasterCourseIcon}
|
||||
data-testid="ic-master-course-icon-container"
|
||||
className="ic-item-row__master-course-lock"
|
||||
/>
|
||||
{maybeDisplayManageMenu}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -887,7 +891,11 @@ class DiscussionRow extends Component {
|
|||
|
||||
renderBlueUnreadBadge() {
|
||||
if (this.props.discussion.read_state !== 'read') {
|
||||
return <Badge margin="0 small x-small 0" standalone={true} type="notification" />
|
||||
return (
|
||||
<div data-testid="ic-blue-unread-badge">
|
||||
<Badge margin="0 small x-small 0" standalone={true} type="notification" />
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<View display="block" margin="0 small x-small 0">
|
||||
|
|
|
@ -0,0 +1,766 @@
|
|||
/*
|
||||
* Copyright (C) 2021 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import {render, screen, waitFor} from '@testing-library/react'
|
||||
import userEvent, {PointerEventsCheckLevel} from '@testing-library/user-event'
|
||||
import {merge} from 'lodash'
|
||||
import {DiscussionRow} from '../DiscussionRow'
|
||||
|
||||
// We can't call the wrapped component because a lot of these tests are depending
|
||||
// on the class component instances. So we've got to cobble up enough of the date
|
||||
// formatter to send in as a prop.
|
||||
const dateFormatter = date => {
|
||||
const fmtr = Intl.DateTimeFormat('en').format
|
||||
try {
|
||||
if (date === null) return ''
|
||||
return fmtr(date instanceof Date ? date : new Date(date))
|
||||
} catch (e) {
|
||||
if (e instanceof RangeError) return ''
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
const user = userEvent.setup({pointerEventsCheck: PointerEventsCheckLevel.Never})
|
||||
|
||||
describe('DiscussionRow', () => {
|
||||
const makeProps = (props = {}) =>
|
||||
merge(
|
||||
{
|
||||
discussion: {
|
||||
id: '1',
|
||||
position: 1,
|
||||
published: true,
|
||||
title: 'Hello World',
|
||||
message: 'Foo bar bar baz boop beep bop Foo',
|
||||
posted_at: 'January 10, 2019 at 10:00 AM',
|
||||
can_unpublish: true,
|
||||
author: {
|
||||
id: '5',
|
||||
name: 'John Smith',
|
||||
display_name: 'John Smith',
|
||||
html_url: '',
|
||||
avatar_image_url: null,
|
||||
},
|
||||
subscribed: false,
|
||||
read_state: 'unread',
|
||||
unread_count: 0,
|
||||
discussion_subentry_count: 5,
|
||||
locked: false,
|
||||
html_url: '',
|
||||
user_count: 10,
|
||||
last_reply_at: new Date(2018, 1, 14, 0, 0, 0, 0),
|
||||
},
|
||||
canPublish: false,
|
||||
canReadAsAdmin: true,
|
||||
displayDeleteMenuItem: false,
|
||||
displayDuplicateMenuItem: false,
|
||||
displayLockMenuItem: false,
|
||||
displayManageMenu: false,
|
||||
displayPinMenuItem: false,
|
||||
displayDifferentiatedModulesTray: false,
|
||||
isMasterCourse: false,
|
||||
toggleSubscriptionState: () => {},
|
||||
cleanDiscussionFocus: () => {},
|
||||
duplicateDiscussion: () => {},
|
||||
updateDiscussion: () => {},
|
||||
masterCourseData: {},
|
||||
setCopyTo: () => {},
|
||||
setSendTo: () => {},
|
||||
setSendToOpen: () => {},
|
||||
deleteDiscussion: () => {},
|
||||
DIRECT_SHARE_ENABLED: false,
|
||||
contextType: '',
|
||||
dateFormatter,
|
||||
},
|
||||
props
|
||||
)
|
||||
|
||||
const openManageMenu = async (title) => {
|
||||
const menu = screen.getByText(`Manage options for ${title}`)
|
||||
expect(menu).toBeInTheDocument()
|
||||
await user.click(menu)
|
||||
const list = await waitFor(() => screen.getByRole('menu'))
|
||||
expect(list).toBeInTheDocument()
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
const oldEnv = window.ENV
|
||||
|
||||
afterEach(() => {
|
||||
window.ENV = oldEnv
|
||||
})
|
||||
|
||||
it('renders the DiscussionRow component', () => {
|
||||
expect(() => {
|
||||
render(<DiscussionRow {...makeProps()} />)
|
||||
}).not.toThrow()
|
||||
})
|
||||
|
||||
it('renders UnreadBadge if discussion has replies > 0', () => {
|
||||
const discussion = {discussion_subentry_count: 5}
|
||||
render(<DiscussionRow {...makeProps({discussion})} />)
|
||||
const nodes = screen.getAllByText('0 unread replies')
|
||||
expect(nodes.length).toBe(2)
|
||||
})
|
||||
|
||||
it('renders title as a link', () => {
|
||||
const discussion = {id: '1', locked: false, title: 'blerp', html_url: 'https://example.com'}
|
||||
render(<DiscussionRow {...makeProps({discussion})} />)
|
||||
const link = screen.getByTestId(`discussion-link-${discussion.id}`)
|
||||
expect(link.textContent.includes(discussion.title)).toBe(true)
|
||||
expect(link.tagName.toLowerCase()).toBe('a')
|
||||
expect(link.getAttribute('href')).toBe('https://example.com');
|
||||
})
|
||||
|
||||
it('when feature flag is off, anonymous title is plain text ', () => {
|
||||
window.ENV.discussion_anonymity_enabled = false
|
||||
const discussion = {id: '1', locked: false, title: 'blerp', anonymous_state: 'full_anonymity'}
|
||||
render(<DiscussionRow {...makeProps({discussion})} />)
|
||||
expect(screen.getByTestId(`discussion-title-${discussion.id}`)).toBeInTheDocument()
|
||||
expect(screen.queryByTestId(`discussion-link-${discussion.id}`)).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders Correct screen reader message for locking discussions', async () => {
|
||||
const updateDiscussionMock = jest.fn()
|
||||
const discussion = {locked: false, title: 'blerp'}
|
||||
render(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
displayManageMenu: true,
|
||||
displayLockMenuItem: true,
|
||||
updateDiscussion: updateDiscussionMock,
|
||||
discussion,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
|
||||
await openManageMenu(discussion.title)
|
||||
const lock = screen.getByText('Close discussion blerp for comments')
|
||||
await user.click(lock)
|
||||
|
||||
expect(updateDiscussionMock).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
successMessage: 'Lock discussion blerp succeeded',
|
||||
failMessage: 'Lock discussion blerp failed',
|
||||
}),
|
||||
expect.anything(),
|
||||
)
|
||||
})
|
||||
|
||||
it('renders Correct screen reader message for unlocking discussions', async () => {
|
||||
const updateDiscussionMock = jest.fn()
|
||||
const discussion = {locked: true, title: 'blerp'}
|
||||
render(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
displayManageMenu: true,
|
||||
displayLockMenuItem: true,
|
||||
updateDiscussion: updateDiscussionMock,
|
||||
discussion,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
|
||||
await openManageMenu(discussion.title)
|
||||
const unlock = screen.getByText('Open discussion blerp for comments')
|
||||
await user.click(unlock)
|
||||
|
||||
expect(updateDiscussionMock).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
successMessage: 'Unlock discussion blerp succeeded',
|
||||
failMessage: 'Unlock discussion blerp failed',
|
||||
}),
|
||||
expect.anything(),
|
||||
)
|
||||
})
|
||||
|
||||
it('does not render UnreadBadge if discussion has replies == 0', () => {
|
||||
const discussion = {discussion_subentry_count: 0}
|
||||
render(<DiscussionRow {...makeProps({discussion})} />)
|
||||
expect(screen.queryByText('0 unread replies')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders ReadBadge if discussion is unread', () => {
|
||||
const discussion = {read_state: 'unread'}
|
||||
render(<DiscussionRow {...makeProps({discussion})} />)
|
||||
expect(screen.getByTestId('ic-blue-unread-badge')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('does not render ReadBadge if discussion is read', () => {
|
||||
const discussion = {read_state: 'read'}
|
||||
render(<DiscussionRow {...makeProps({discussion})} />)
|
||||
expect(screen.queryByTestId('ic-blue-unread-badge')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders the subscription ToggleIcon', () => {
|
||||
render(<DiscussionRow {...makeProps()} />)
|
||||
expect(screen.getByText('Subscribe to Hello World')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('disables publish button when can_unpublish is false', () => {
|
||||
const discussion = {can_unpublish: false}
|
||||
render(<DiscussionRow {...makeProps({canPublish: true, discussion})} />)
|
||||
const button = screen.getByRole('button', {name: 'Unpublish Hello World'})
|
||||
expect(button.hasAttribute('disabled')).toBe(true)
|
||||
})
|
||||
|
||||
it('allows to publish even if you cannot unpublish', () => {
|
||||
const discussion = {can_unpublish: false, published: false}
|
||||
render(<DiscussionRow {...makeProps({canPublish: true, discussion})} />)
|
||||
const button = screen.getByRole('button', {name: 'Publish Hello World'})
|
||||
expect(button.hasAttribute('disabled')).toBe(false)
|
||||
})
|
||||
|
||||
it('renders the publish ToggleIcon', () => {
|
||||
const discussion = {published: false}
|
||||
render(<DiscussionRow {...makeProps({canPublish: true, discussion})} />)
|
||||
expect(screen.getByText('Publish Hello World')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('when feature flag is off, renders anonymous discussion lock explanation for read_as_admin', () => {
|
||||
window.ENV.discussion_anonymity_enabled = false
|
||||
const discussion = {locked: false, title: 'blerp', anonymous_state: 'full_anonymity'}
|
||||
render(<DiscussionRow {...makeProps({canReadAsAdmin: true, discussion})} />)
|
||||
expect(screen.getByText('Discussions/Announcements Redesign')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('when feature flag is off, renders anonymous discussion unavailable for students, etc.', () => {
|
||||
window.ENV.discussion_anonymity_enabled = false
|
||||
const discussion = {locked: false, title: 'blerp', anonymous_state: 'full_anonymity'}
|
||||
render(<DiscussionRow {...makeProps({canReadAsAdmin: false, discussion})} />)
|
||||
expect(screen.getByText('Unavailable')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('when feature flag is off, renders partially anonymous discussion unavailable for students, etc.', () => {
|
||||
window.ENV.discussion_anonymity_enabled = false
|
||||
const discussion = {locked: false, title: 'blerp', anonymous_state: 'partial_anonymity'}
|
||||
render(<DiscussionRow {...makeProps({canReadAsAdmin: false, discussion})} />)
|
||||
expect(screen.getByText('Unavailable')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders "Delayed until" date label if discussion is delayed', () => {
|
||||
const delayedDate = new Date()
|
||||
delayedDate.setYear(delayedDate.getFullYear() + 1)
|
||||
const discussion = {delayed_post_at: delayedDate.toISOString()}
|
||||
render(<DiscussionRow {...makeProps({discussion})} />)
|
||||
expect(screen.getAllByText('Not available until', {exact: false}).length).toBe(2)
|
||||
})
|
||||
|
||||
it('renders a last reply at date', () => {
|
||||
render(<DiscussionRow {...makeProps()} />)
|
||||
expect(screen.getAllByText('Last post at 2/14', {exact: false}).length).toBe(2)
|
||||
})
|
||||
|
||||
it('does not render last reply at date if there is none', () => {
|
||||
const discussion = {last_reply_at: ''}
|
||||
render(<DiscussionRow {...makeProps({discussion})} />)
|
||||
expect(screen.queryByText('Last post at', {exact: false})).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders available until if appropriate', () => {
|
||||
const futureDate = new Date()
|
||||
futureDate.setYear(futureDate.getFullYear() + 1)
|
||||
const discussion = {lock_at: futureDate}
|
||||
render(<DiscussionRow {...makeProps({discussion})} />)
|
||||
expect(screen.getAllByText('Available until', {exact: false}).length).toBe(2)
|
||||
// We need a relative date to ensure future-ness, so we can't really insist
|
||||
// on a given date element appearing this time
|
||||
})
|
||||
|
||||
it('renders locked at if appropriate', () => {
|
||||
const pastDate = new Date()
|
||||
pastDate.setYear(pastDate.getFullYear() - 1)
|
||||
const discussion = {lock_at: pastDate}
|
||||
render(<DiscussionRow {...makeProps({discussion})} />)
|
||||
expect(screen.getAllByText('No longer available', {exact: false}).length).toBe(2)
|
||||
// We need a relative date to ensure past-ness, so we can't really insist
|
||||
// on a given date element appearing this time
|
||||
})
|
||||
|
||||
it('renders nothing if currently available and no end date', () => {
|
||||
render(<DiscussionRow {...makeProps()} />)
|
||||
expect(screen.queryByText('Available until', {exact: false})).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('Not available until', {exact: false})).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('No longer available', {exact: false})).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders due date if graded with a due date', () => {
|
||||
const props = makeProps({
|
||||
discussion: {
|
||||
assignment: {
|
||||
due_at: '2018-07-01T05:59:00Z',
|
||||
},
|
||||
},
|
||||
})
|
||||
render(<DiscussionRow {...props} />)
|
||||
expect(screen.getAllByText('Due ', {exact: false}).length).toBe(2)
|
||||
expect(screen.queryByText('To do', {exact: false})).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders to do date if ungraded with a to do date', () => {
|
||||
const props = makeProps({
|
||||
discussion: {
|
||||
todo_date: '2018-07-01T05:59:00Z',
|
||||
},
|
||||
})
|
||||
render(<DiscussionRow {...props} />)
|
||||
expect(screen.getByText('To do ', {exact: false})).toBeInTheDocument()
|
||||
expect(screen.queryByText('Due', {exact: false})).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders neither a due or to do date if neither are available', () => {
|
||||
render(<DiscussionRow {...makeProps()} />)
|
||||
expect(screen.queryByText('Due', {exact: false})).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('To do', {exact: false})).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders the SectionsTooltip component', () => {
|
||||
const discussion = {user_count: 200}
|
||||
render(<DiscussionRow {...makeProps({discussion})} />)
|
||||
expect(screen.getByText('All sections', {exact: false})).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders the SectionsTooltip component with sections', () => {
|
||||
const discussion = {
|
||||
sections: [
|
||||
{id: 6, course_id: 1, name: 'section 4', user_count: 2},
|
||||
{id: 5, course_id: 1, name: 'section 2', user_count: 1},
|
||||
],
|
||||
}
|
||||
render(<DiscussionRow {...makeProps({discussion})} />)
|
||||
expect(screen.getByText('2 Sections')).toBeInTheDocument()
|
||||
expect(screen.getByText('section 4')).toBeInTheDocument()
|
||||
expect(screen.getByText('section 2')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('includes Anonymous Discussion prefix when discussion is anonymous', () => {
|
||||
window.ENV.discussion_anonymity_enabled = false
|
||||
const discussion = {
|
||||
sections: [
|
||||
{id: 6, course_id: 1, name: 'section 4', user_count: 2},
|
||||
{id: 5, course_id: 1, name: 'section 2', user_count: 1},
|
||||
],
|
||||
anonymous_state: 'full_anonymity',
|
||||
}
|
||||
render(<DiscussionRow {...makeProps({discussion})} />)
|
||||
expect(screen.getByText('Anonymous Discussion | 2 Sections')).toBeInTheDocument()
|
||||
expect(screen.getByText('section 4')).toBeInTheDocument()
|
||||
expect(screen.getByText('section 2')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('includes Partially Anonymous Discussion prefix when discussion is anonymous', () => {
|
||||
window.ENV.discussion_anonymity_enabled = false
|
||||
const discussion = {
|
||||
sections: [
|
||||
{id: 6, course_id: 1, name: 'section 4', user_count: 2},
|
||||
{id: 5, course_id: 1, name: 'section 2', user_count: 1},
|
||||
],
|
||||
anonymous_state: 'partial_anonymity',
|
||||
}
|
||||
render(<DiscussionRow {...makeProps({discussion})} />)
|
||||
expect(screen.getByText('Partially Anonymous Discussion | 2 Sections')).toBeInTheDocument()
|
||||
expect(screen.getByText('section 4')).toBeInTheDocument()
|
||||
expect(screen.getByText('section 2')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('does not render the SectionsTooltip component on a graded discussion', () => {
|
||||
const discussion = {user_count: 200, assignment: true}
|
||||
render(<DiscussionRow {...makeProps({discussion})} />)
|
||||
expect(screen.queryByText('All sections', {exact: false})).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('does not render the SectionsTooltip component on a group discussion', () => {
|
||||
const discussion = {user_count: 200, group_category_id: 13}
|
||||
render(<DiscussionRow {...makeProps({discussion})} />)
|
||||
expect(screen.queryByText('All sections', {exact: false})).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('does not render the SectionsTooltip component within a group context', () => {
|
||||
const discussion = {user_count: 200}
|
||||
render(<DiscussionRow {...makeProps({discussion, contextType: 'group'})} />)
|
||||
expect(screen.queryByText('All sections', {exact: false})).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('does not render the SectionsTooltip component in a blueprint course', () => {
|
||||
const discussion = {user_count: 200}
|
||||
render(<DiscussionRow {...makeProps({discussion, isMasterCourse: true})} />)
|
||||
expect(screen.queryByText('All sections', {exact: false})).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('does not render master course lock icon if masterCourseData is not provided', () => {
|
||||
const masterCourseData = null
|
||||
const ref = React.createRef()
|
||||
render(<DiscussionRow ref={ref} {...makeProps({masterCourseData})} />)
|
||||
expect(ref.current.masterCourseLock).toBeFalsy()
|
||||
const container = screen.getByTestId('ic-master-course-icon-container')
|
||||
expect(container.hasAttribute('data-tooltip')).toBe(false)
|
||||
})
|
||||
|
||||
it('renders master course lock icon if masterCourseData is provided', () => {
|
||||
const masterCourseData = {isMasterCourse: true, masterCourse: {id: '1'}}
|
||||
const ref = React.createRef()
|
||||
render(<DiscussionRow ref={ref} {...makeProps({masterCourseData})} />)
|
||||
expect(ref.current.masterCourseLock).toBeTruthy()
|
||||
const container = screen.getByTestId('ic-master-course-icon-container')
|
||||
expect(container.hasAttribute('data-tooltip')).toBe(true)
|
||||
})
|
||||
|
||||
it('renders drag icon', () => {
|
||||
render(<DiscussionRow {...makeProps({draggable: true})} />)
|
||||
expect(screen.getByTestId('ic-drag-handle-icon-container')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('does not render manage menu if not permitted', () => {
|
||||
render(<DiscussionRow {...makeProps({displayManageMenu: false})} />)
|
||||
expect(screen.queryByText('Manage options for Hello World')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('does not insert the manage menu list if we have not clicked it yet', () => {
|
||||
render(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
displayManageMenu: true,
|
||||
onMoveDiscussion: () => {},
|
||||
})}
|
||||
/>
|
||||
)
|
||||
// We still should show the menu thingy itself
|
||||
expect(screen.getByText('Manage options for Hello World')).toBeInTheDocument()
|
||||
expect(screen.queryByRole('menu')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('manage menu items do appear upon click', async () => {
|
||||
render(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
displayManageMenu: true,
|
||||
onMoveDiscussion: () => {},
|
||||
})}
|
||||
/>
|
||||
)
|
||||
|
||||
await openManageMenu('Hello World')
|
||||
expect(screen.getByText('Move To')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('does not render sharing menu options if not DIRECT_SHARE_ENABLED', async () => {
|
||||
const props = makeProps({displayManageMenu: true, DIRECT_SHARE_ENABLED: false})
|
||||
render(<DiscussionRow {...props} />)
|
||||
|
||||
await openManageMenu('Hello World')
|
||||
expect(screen.queryByText('Move To', {exact: false})).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('Send To', {exact: false})).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('only leaves pin/unpin open/close for comments, and delete when inaccessibleDueToAnonymity', async () => {
|
||||
window.ENV.discussion_anonymity_enabled = false
|
||||
const discussion = {locked: false, title: 'blerp', anonymous_state: 'full_anonymity'}
|
||||
render(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
discussion,
|
||||
displayManageMenu: true,
|
||||
DIRECT_SHARE_ENABLED: true,
|
||||
displayDeleteMenuItem: true,
|
||||
displayPinMenuItem: true,
|
||||
displayLockMenuItem: true,
|
||||
canPublish: true,
|
||||
published: false,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
|
||||
const list = await openManageMenu(discussion.title)
|
||||
const allKeys = list.querySelectorAll('li')
|
||||
expect(allKeys.length).toBe(3)
|
||||
expect(allKeys[0].textContent.includes('Close for comments')).toBe(true)
|
||||
expect(allKeys[1].textContent.includes('Pin')).toBe(true)
|
||||
expect(allKeys[2].textContent.includes('Delete')).toBe(true)
|
||||
|
||||
expect(screen.queryByText('Publish blerp')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('Subscribe to blerp')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders sharing menu options if DIRECT_SHARE_ENABLED', async () => {
|
||||
const props = makeProps({displayManageMenu: true, DIRECT_SHARE_ENABLED: true})
|
||||
render(<DiscussionRow {...props} />)
|
||||
|
||||
await openManageMenu('Hello World')
|
||||
expect(screen.getByText('Copy To', {exact: false})).toBeInTheDocument()
|
||||
expect(screen.getByText('Send To', {exact: false})).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('opens the copyTo tray when menu item is selected', async () => {
|
||||
const copyMock = jest.fn()
|
||||
const props = makeProps({
|
||||
displayManageMenu: true,
|
||||
DIRECT_SHARE_ENABLED: true,
|
||||
setCopyTo: copyMock,
|
||||
})
|
||||
render(<DiscussionRow {...props} />)
|
||||
|
||||
await openManageMenu('Hello World')
|
||||
const copyTo = screen.getByText('Copy To', {exact: false})
|
||||
await user.click(copyTo)
|
||||
|
||||
expect(copyMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
open: true,
|
||||
selection: {discussion_topics: [props.discussion.id]},
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('opens the sendTo tray when menu item is selected', async () => {
|
||||
const sendMock = jest.fn()
|
||||
const props = makeProps({
|
||||
displayManageMenu: true,
|
||||
DIRECT_SHARE_ENABLED: true,
|
||||
setSendTo: sendMock,
|
||||
})
|
||||
render(<DiscussionRow {...props} />)
|
||||
|
||||
await openManageMenu('Hello World')
|
||||
const sendToNode = screen.getByText('Send To', {exact: false})
|
||||
await user.click(sendToNode)
|
||||
|
||||
expect(sendMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
open: true,
|
||||
selection: {
|
||||
content_type: 'discussion_topic',
|
||||
content_id: props.discussion.id,
|
||||
},
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('renders availability information on graded discussions', () => {
|
||||
render(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
discussion: {
|
||||
assignment: {
|
||||
lock_at: '2018-07-01T05:59:00Z',
|
||||
unlock_at: '2018-06-21T06:00:00Z',
|
||||
},
|
||||
},
|
||||
})}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(screen.getByText('No longer available')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders move-to in manage menu if permitted', async () => {
|
||||
render(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
displayManageMenu: true,
|
||||
onMoveDiscussion: () => {},
|
||||
})}
|
||||
/>
|
||||
)
|
||||
|
||||
const list = await openManageMenu('Hello World')
|
||||
const allKeys = list.querySelectorAll('li')
|
||||
expect(allKeys.length).toBe(1)
|
||||
expect(allKeys[0].textContent.includes('Move To')).toBe(true)
|
||||
})
|
||||
|
||||
it('renders pin item in manage menu if permitted', async () => {
|
||||
render(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
displayManageMenu: true,
|
||||
displayPinMenuItem: true,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
|
||||
const list = await openManageMenu('Hello World')
|
||||
const allKeys = list.querySelectorAll('li')
|
||||
expect(allKeys.length).toBe(1)
|
||||
expect(allKeys[0].textContent.includes('Pin')).toBe(true)
|
||||
})
|
||||
|
||||
it('renders speedgrader link in manage menu if permitted', async () => {
|
||||
window.ENV.show_additional_speed_grader_links = true
|
||||
render(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
displayManageMenu: true,
|
||||
discussion: {
|
||||
assignment: {
|
||||
lock_at: '2018-07-01T05:59:00Z',
|
||||
unlock_at: '2018-06-21T06:00:00Z',
|
||||
id: '50',
|
||||
},
|
||||
},
|
||||
})}
|
||||
/>
|
||||
)
|
||||
|
||||
const list = await openManageMenu('Hello World')
|
||||
const allKeys = list.querySelectorAll('li')
|
||||
expect(allKeys.length).toBe(1)
|
||||
expect(allKeys[0].textContent.includes('SpeedGrader')).toBe(true)
|
||||
})
|
||||
|
||||
it('renders duplicate item in manage menu if permitted', async () => {
|
||||
render(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
displayManageMenu: true,
|
||||
displayDuplicateMenuItem: true,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
|
||||
const list = await openManageMenu('Hello World')
|
||||
const allKeys = list.querySelectorAll('li')
|
||||
expect(allKeys.length).toBe(1)
|
||||
expect(allKeys[0].textContent.includes('Duplicate')).toBe(true)
|
||||
})
|
||||
|
||||
it('renders delete item in manage menu if permitted', async () => {
|
||||
render(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
displayManageMenu: true,
|
||||
displayDeleteMenuItem: true,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
|
||||
const list = await openManageMenu('Hello World')
|
||||
const allKeys = list.querySelectorAll('li')
|
||||
expect(allKeys.length).toBe(1)
|
||||
expect(allKeys[0].textContent.includes('Delete')).toBe(true)
|
||||
})
|
||||
|
||||
it('renders lock item in manage menu if permitted', async () => {
|
||||
render(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
displayManageMenu: true,
|
||||
displayLockMenuItem: true,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
|
||||
const list = await openManageMenu('Hello World')
|
||||
const allKeys = list.querySelectorAll('li')
|
||||
expect(allKeys.length).toBe(1)
|
||||
expect(allKeys[0].textContent.includes('Close for comments')).toBe(true)
|
||||
})
|
||||
|
||||
it('renders mastery paths menu item if permitted', async () => {
|
||||
render(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
displayManageMenu: true,
|
||||
discussion: {
|
||||
assignment_id: 2,
|
||||
},
|
||||
displayMasteryPathsMenuItem: true,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
|
||||
const list = await openManageMenu('Hello World')
|
||||
const allKeys = list.querySelectorAll('li')
|
||||
expect(allKeys.length).toBe(1)
|
||||
expect(allKeys[0].textContent.includes('Mastery Paths')).toBe(true)
|
||||
})
|
||||
|
||||
it('renders mastery paths link if permitted', () => {
|
||||
render(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
displayManageMenu: true,
|
||||
discussion: {
|
||||
assignment_id: 2,
|
||||
},
|
||||
displayMasteryPathsLink: true,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(screen.getByText('Mastery Paths')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders ltiTool menu if there are some', async () => {
|
||||
render(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
displayManageMenu: true,
|
||||
discussionTopicMenuTools: [
|
||||
{
|
||||
base_url: 'test.com',
|
||||
canvas_icon_class: 'icon-lti',
|
||||
icon_url: 'iconUrltest.com',
|
||||
title: 'discussion_topic_menu Text',
|
||||
},
|
||||
],
|
||||
})}
|
||||
/>
|
||||
)
|
||||
|
||||
const list = await openManageMenu('Hello World')
|
||||
const allKeys = list.querySelectorAll('li')
|
||||
expect(allKeys.length).toBe(1)
|
||||
expect(allKeys[0].textContent.includes('discussion_topic_menu Text')).toBe(true)
|
||||
})
|
||||
|
||||
it('renders multiple ltiTool menu if there are multiple', async () => {
|
||||
render(
|
||||
<DiscussionRow
|
||||
{...makeProps({
|
||||
displayManageMenu: true,
|
||||
discussionTopicMenuTools: [
|
||||
{
|
||||
base_url: 'test.com',
|
||||
canvas_icon_class: 'icon-lti',
|
||||
icon_url: 'iconUrltest.com',
|
||||
title: 'discussion_topic_menu Text',
|
||||
},
|
||||
{
|
||||
base_url: 'test2.com',
|
||||
canvas_icon_class: 'icon-lti',
|
||||
icon_url: 'iconUrltest2.com',
|
||||
title: 'discussion_topic_menu otherText',
|
||||
},
|
||||
],
|
||||
})}
|
||||
/>
|
||||
)
|
||||
|
||||
const list = await openManageMenu('Hello World')
|
||||
const allKeys = list.querySelectorAll('li')
|
||||
expect(allKeys.length).toBe(2)
|
||||
expect(allKeys[0].textContent.includes('discussion_topic_menu Text')).toBe(true)
|
||||
expect(allKeys[1].textContent.includes('discussion_topic_menu otherText')).toBe(true)
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue