run prettier on ui/ and spec/

Change-Id: If7c95860bab3791a5be1dea1961d83dbb6a5dd50
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/347401
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Spencer Olson <solson@instructure.com>
QA-Review: Aaron Shafovaloff <ashafovaloff@instructure.com>
Product-Review: Aaron Shafovaloff <ashafovaloff@instructure.com>
This commit is contained in:
Aaron Shafovaloff 2024-05-15 06:27:48 -06:00
parent 08441503cf
commit 94eec36cb6
134 changed files with 876 additions and 710 deletions

View File

@ -324,7 +324,8 @@ describe('FindReplaceTray', () => {
expect(fakePlugin.next).toHaveBeenCalledTimes(3) expect(fakePlugin.next).toHaveBeenCalledTimes(3)
}) })
it('is incremented when enter pressed on find input', async () => { // fickle test - LF-1605
it.skip('is incremented when enter pressed on find input', async () => {
const {user} = renderComponent() const {user} = renderComponent()
const findInput = screen.getByTestId('find-text-input') const findInput = screen.getByTestId('find-text-input')
await type(user, findInput, 'a') await type(user, findInput, 'a')

View File

@ -128,23 +128,13 @@ QUnit.module('DueDateCalendarPicker', suiteHooks => {
test('forwards properties to label', () => { test('forwards properties to label', () => {
props.labelClasses = 'special-label' props.labelClasses = 'special-label'
mountComponent() mountComponent()
ok( ok(wrapper.container.querySelector('label').className.match(/special-label/))
wrapper
.container.querySelector('label')
.className
.match(/special-label/)
)
}) })
test('forwards properties to input', () => { test('forwards properties to input', () => {
props.name = 'special-name' props.name = 'special-name'
mountComponent() mountComponent()
ok( ok(wrapper.container.querySelector('input').name.match(/special-name/))
wrapper
.container.querySelector('input')
.name
.match(/special-name/)
)
}) })
test('label and input reference each other', () => { test('label and input reference each other', () => {

View File

@ -69,7 +69,7 @@ test('state triggered', function () {
}) })
test('render', async function () { test('render', async function () {
const user = userEvent.setup({ delay: null }) const user = userEvent.setup({delay: null})
const clock = sinon.useFakeTimers() const clock = sinon.useFakeTimers()
sinon.stub(CourseEpubExportStore, 'create') sinon.stub(CourseEpubExportStore, 'create')

View File

@ -78,7 +78,10 @@ QUnit.module('GradebookGrid AssignmentGroupColumnHeader', suiteHooks => {
function mountComponent(overrides) { function mountComponent(overrides) {
// eslint-disable-next-line react/no-render-return-value // eslint-disable-next-line react/no-render-return-value
component = ReactDOM.render(<AssignmentGroupColumnHeader {...props} {...overrides} />, $container) component = ReactDOM.render(
<AssignmentGroupColumnHeader {...props} {...overrides} />,
$container
)
} }
function getOptionsMenuTrigger() { function getOptionsMenuTrigger() {

View File

@ -2908,13 +2908,20 @@ QUnit.module('SpeedGrader', rootHooks => {
userSettings.get.withArgs('eg_sort_by').returns('randomize') userSettings.get.withArgs('eg_sort_by').returns('randomize')
sandbox sandbox
.stub(Math, 'random') .stub(Math, 'random')
.onFirstCall().returns(0.3) .onFirstCall()
.onSecondCall().returns(0.1) .returns(0.3)
.onThirdCall().returns(0.4) .onSecondCall()
.onCall(3).returns(0.2) .returns(0.1)
.onCall(4).returns(0.7) .onThirdCall()
.onCall(5).returns(0.5) .returns(0.4)
.onCall(6).returns(0.6) .onCall(3)
.returns(0.2)
.onCall(4)
.returns(0.7)
.onCall(5)
.returns(0.5)
.onCall(6)
.returns(0.6)
SpeedGrader.EG.jsonReady() SpeedGrader.EG.jsonReady()
const ids = window.jsonData.studentsWithSubmissions.map(student => student.id) const ids = window.jsonData.studentsWithSubmissions.map(student => student.id)
deepEqual(ids, ['1102', '1104', '1101', '1103', '1106', '1107', '1105']) deepEqual(ids, ['1102', '1104', '1101', '1103', '1106', '1107', '1105'])
@ -2924,13 +2931,20 @@ QUnit.module('SpeedGrader', rootHooks => {
userSettings.get.withArgs('eg_sort_by').returns('submission_status_randomize') userSettings.get.withArgs('eg_sort_by').returns('submission_status_randomize')
sandbox sandbox
.stub(Math, 'random') .stub(Math, 'random')
.onFirstCall().returns(0.3) .onFirstCall()
.onSecondCall().returns(0.1) .returns(0.3)
.onThirdCall().returns(0.4) .onSecondCall()
.onCall(3).returns(0.2) .returns(0.1)
.onCall(4).returns(0.7) .onThirdCall()
.onCall(5).returns(0.5) .returns(0.4)
.onCall(6).returns(0.6) .onCall(3)
.returns(0.2)
.onCall(4)
.returns(0.7)
.onCall(5)
.returns(0.5)
.onCall(6)
.returns(0.6)
SpeedGrader.EG.jsonReady() SpeedGrader.EG.jsonReady()
const ids = window.jsonData.studentsWithSubmissions.map(student => student.id) const ids = window.jsonData.studentsWithSubmissions.map(student => student.id)
deepEqual(ids, ['1101', '1103', '1102', '1104', '1106', '1107', '1105']) deepEqual(ids, ['1101', '1103', '1102', '1104', '1106', '1107', '1105'])

View File

@ -74,7 +74,7 @@ export default function NewCourseModal({terms, children}) {
return return
} }
const successHandler = (createdCourse) => { const successHandler = createdCourse => {
closeModal() closeModal()
showFlashAlert({ showFlashAlert({
type: 'success', type: 'success',
@ -88,7 +88,9 @@ export default function NewCourseModal({terms, children}) {
}) })
} }
const errorHandler = showFlashError(I18n.t('Something went wrong creating the course. Please try again.')) const errorHandler = showFlashError(
I18n.t('Something went wrong creating the course. Please try again.')
)
CoursesStore.create({course: data}, successHandler, errorHandler) CoursesStore.create({course: data}, successHandler, errorHandler)
} }

View File

@ -72,8 +72,11 @@ export default class UsersPane extends React.Component {
this.unsubscribe = this.props.store.subscribe(this.handleStateChange) this.unsubscribe = this.props.store.subscribe(this.handleStateChange)
// make page reflect what the querystring params asked for // make page reflect what the querystring params asked for
const {search_term, role_filter_id, include_deleted_users} = {...UsersToolbar.defaultProps, ...this.props.queryParams} const {search_term, role_filter_id, include_deleted_users} = {
const bool_include_deleted_users = (include_deleted_users === 'true') ...UsersToolbar.defaultProps,
...this.props.queryParams,
}
const bool_include_deleted_users = include_deleted_users === 'true'
this.props.store.dispatch( this.props.store.dispatch(
UserActions.updateSearchFilter({ UserActions.updateSearchFilter({
search_term, search_term,

View File

@ -120,7 +120,9 @@ describe('Account Course User Search UsersList View', function (hooks) {
it(`sorting by ${columnID} ${sortOrder} puts ${expectedArrow}-arrow on ${label} only`, () => { it(`sorting by ${columnID} ${sortOrder} puts ${expectedArrow}-arrow on ${label} only`, () => {
const wrapper = render(<UsersList {...props} />) const wrapper = render(<UsersList {...props} />)
expect(wrapper.container.querySelectorAll(`[name="IconMiniArrow${unexpectedArrow}"]`).length).toEqual(0) expect(
wrapper.container.querySelectorAll(`[name="IconMiniArrow${unexpectedArrow}"]`).length
).toEqual(0)
const icons = wrapper.container.querySelectorAll(`[name="IconMiniArrow${expectedArrow}"]`) const icons = wrapper.container.querySelectorAll(`[name="IconMiniArrow${expectedArrow}"]`)
expect(icons.length).toEqual(1) expect(icons.length).toEqual(1)
const header = icons[0].closest('[data-testid="UsersListHeader"]') const header = icons[0].closest('[data-testid="UsersListHeader"]')
@ -139,9 +141,11 @@ describe('Account Course User Search UsersList View', function (hooks) {
}} }}
/> />
) )
const header = Array.from(wrapper const header = Array.from(
.container.querySelectorAll('[data-testid="UsersListHeader"]')) wrapper.container.querySelectorAll('[data-testid="UsersListHeader"]')
.filter(n => n.textContent.includes(label))[0].querySelector('button') )
.filter(n => n.textContent.includes(label))[0]
.querySelector('button')
const user = userEvent.setup({delay: null}) const user = userEvent.setup({delay: null})
await user.click(header) await user.click(header)
expect(sortSpy.calledOnce).toBeTruthy() expect(sortSpy.calledOnce).toBeTruthy()

View File

@ -34,8 +34,8 @@ describe('UsersToolbar', () => {
beforeEach(() => { beforeEach(() => {
old_env = window.ENV old_env = window.ENV
window.ENV = { window.ENV = {
PERMISSIONS: { can_edit_users: true }, PERMISSIONS: {can_edit_users: true},
FEATURES: { granular_permissions_manage_users: true }, FEATURES: {granular_permissions_manage_users: true},
} }
}) })

View File

@ -21,7 +21,6 @@ import React from 'react'
import {render} from '@testing-library/react' import {render} from '@testing-library/react'
import AnnouncementEmptyState from '../AnnouncementEmptyState' import AnnouncementEmptyState from '../AnnouncementEmptyState'
const renderComponent = (props = {}) => { const renderComponent = (props = {}) => {
const defaultProps = { const defaultProps = {
canCreate: true, canCreate: true,

View File

@ -27,30 +27,30 @@ import sinon from 'sinon'
import AnnouncementsIndex from '../AnnouncementsIndex' import AnnouncementsIndex from '../AnnouncementsIndex'
const announcements = [ const announcements = [
{ {
id: '1',
position: 2,
published: true,
title: 'hello world',
message: 'lorem ipsum foo bar baz',
posted_at: new Date().toString(),
author: {
id: '1', id: '1',
position: 2, display_name: 'John Doe',
published: true, name: 'John Doe',
title: 'hello world', html_url: 'http://example.org/user/5',
message: 'lorem ipsum foo bar baz',
posted_at: new Date().toString(),
author: {
id: '1',
display_name: 'John Doe',
name: 'John Doe',
html_url: 'http://example.org/user/5',
},
read_state: 'read',
unread_count: 0,
discussion_subentry_count: 0,
locked: false,
user_count: 2,
html_url: 'http://example.org/announcement/5',
permissions: {
delete: true,
},
}, },
] read_state: 'read',
unread_count: 0,
discussion_subentry_count: 0,
locked: false,
user_count: 2,
html_url: 'http://example.org/announcement/5',
permissions: {
delete: true,
},
},
]
const makeProps = (props = {}) => const makeProps = (props = {}) =>
_.merge( _.merge(

View File

@ -19,7 +19,7 @@
import '@instructure/canvas-theme' import '@instructure/canvas-theme'
import React from 'react' import React from 'react'
import {render} from '@testing-library/react' import {render} from '@testing-library/react'
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event'
import RSSFeedList from '../RSSFeedList' import RSSFeedList from '../RSSFeedList'
const defaultFeeds = () => [ const defaultFeeds = () => [
@ -68,18 +68,18 @@ const renderComponent = (props = {}) => {
test('renders the RSSFeedList component', () => { test('renders the RSSFeedList component', () => {
const feeds = defaultFeeds() const feeds = defaultFeeds()
const tree = renderComponent({ hasLoadedFeed: true, feeds }) const tree = renderComponent({hasLoadedFeed: true, feeds})
expect(tree.getByText(feeds[0].display_name)).toBeInTheDocument() expect(tree.getByText(feeds[0].display_name)).toBeInTheDocument()
}) })
test('renders the RSSFeedList component loading indicator when loading', () => { test('renders the RSSFeedList component loading indicator when loading', () => {
const tree = renderComponent({ hasLoadedFeed: false }) const tree = renderComponent({hasLoadedFeed: false})
expect(tree.getByText('Adding RSS Feed')).toBeInTheDocument() expect(tree.getByText('Adding RSS Feed')).toBeInTheDocument()
}) })
test('renders the RSSFeedList component with 5 rows for 5 feeds', () => { test('renders the RSSFeedList component with 5 rows for 5 feeds', () => {
const feeds = defaultFeeds() const feeds = defaultFeeds()
const tree = renderComponent({ hasLoadedFeed: true, feeds }) const tree = renderComponent({hasLoadedFeed: true, feeds})
feeds.forEach(feed => { feeds.forEach(feed => {
expect(tree.getByText(feed.display_name)).toBeInTheDocument() expect(tree.getByText(feed.display_name)).toBeInTheDocument()
}) })
@ -90,7 +90,7 @@ test('calls getExternalFeeds when feed has not been loaded', () => {
const mockGetExternalFeeds = jest.fn() const mockGetExternalFeeds = jest.fn()
renderComponent({ renderComponent({
hasLoadedFeed: false, hasLoadedFeed: false,
getExternalFeeds: mockGetExternalFeeds getExternalFeeds: mockGetExternalFeeds,
}) })
expect(mockGetExternalFeeds).toHaveBeenCalledTimes(1) expect(mockGetExternalFeeds).toHaveBeenCalledTimes(1)
@ -100,7 +100,7 @@ test('does not call getExternalFeeds when feed has been loaded', () => {
const mockGetExternalFeeds = jest.fn() const mockGetExternalFeeds = jest.fn()
renderComponent({ renderComponent({
hasLoadedFeed: true, hasLoadedFeed: true,
getExternalFeeds: mockGetExternalFeeds getExternalFeeds: mockGetExternalFeeds,
}) })
expect(mockGetExternalFeeds).not.toHaveBeenCalled() expect(mockGetExternalFeeds).not.toHaveBeenCalled()

View File

@ -25,7 +25,7 @@ import {Spinner} from '@instructure/ui-spinner'
import {View} from '@instructure/ui-view' import {View} from '@instructure/ui-view'
import AnnouncementRow from '@canvas/announcements/react/components/AnnouncementRow' import AnnouncementRow from '@canvas/announcements/react/components/AnnouncementRow'
import ready from '@instructure/ready' import ready from '@instructure/ready'
import { captureException } from '@sentry/react' import {captureException} from '@sentry/react'
const I18n = useI18nScope('announcements_on_home_page') const I18n = useI18nScope('announcements_on_home_page')

View File

@ -167,7 +167,8 @@ function EditView() {
this.handleGradingTypeChange = this.handleGradingTypeChange.bind(this) this.handleGradingTypeChange = this.handleGradingTypeChange.bind(this)
this.handleRestrictFileUploadsChange = this.handleRestrictFileUploadsChange.bind(this) this.handleRestrictFileUploadsChange = this.handleRestrictFileUploadsChange.bind(this)
this.renderDefaultExternalTool = this.renderDefaultExternalTool.bind(this) this.renderDefaultExternalTool = this.renderDefaultExternalTool.bind(this)
this.renderAssignmentSubmissionTypeSelectionLaunchButton = this.renderAssignmentSubmissionTypeSelectionLaunchButton.bind(this) this.renderAssignmentSubmissionTypeSelectionLaunchButton =
this.renderAssignmentSubmissionTypeSelectionLaunchButton.bind(this)
this.defaultExternalToolName = this.defaultExternalToolName.bind(this) this.defaultExternalToolName = this.defaultExternalToolName.bind(this)
this.defaultExternalToolUrl = this.defaultExternalToolUrl.bind(this) this.defaultExternalToolUrl = this.defaultExternalToolUrl.bind(this)
this.defaultExternalToolEnabled = this.defaultExternalToolEnabled.bind(this) this.defaultExternalToolEnabled = this.defaultExternalToolEnabled.bind(this)
@ -1026,7 +1027,7 @@ EditView.prototype.handlePlacementExternalToolSelect = function (selection) {
this.$externalToolsIframeHeight.val('') this.$externalToolsIframeHeight.val('')
} }
this.renderAssignmentSubmissionTypeSelectionLaunchButton() this.renderAssignmentSubmissionTypeSelectionLaunchButton()
} }
EditView.prototype.handleSubmissionTypeSelectionLaunch = function () { EditView.prototype.handleSubmissionTypeSelectionLaunch = function () {

View File

@ -28,7 +28,7 @@ export default function AssignmentSubmissionTypeSelectionLaunchButton(props) {
const {title, description, icon_url: iconUrl} = tool const {title, description, icon_url: iconUrl} = tool
return ( return (
<BaseButton <BaseButton
id="assignment_submission_type_selection_launch_button" id="assignment_submission_type_selection_launch_button"
display="block" display="block"
color="secondary" color="secondary"
@ -37,19 +37,19 @@ export default function AssignmentSubmissionTypeSelectionLaunchButton(props) {
withBorder={true} withBorder={true}
onClick={onClick} onClick={onClick}
renderIcon={iconUrl ? <img src={iconUrl} width="28px" height="28px" /> : undefined} renderIcon={iconUrl ? <img src={iconUrl} width="28px" height="28px" /> : undefined}
> >
<View> <View>
<Text as="div" id="title_text"> <Text as="div" id="title_text">
{title} {title}
</Text>
{description && (
<Text weight="light" size="small">
<TruncateText as="div" id="desc_text" maxLines={1}>
{description}
</TruncateText>
</Text> </Text>
{description && ( )}
<Text weight="light" size="small"> </View>
<TruncateText as="div" id="desc_text" maxLines={1}> </BaseButton>
{description}
</TruncateText>
</Text>
)}
</View>
</BaseButton>
) )
} }

View File

@ -23,13 +23,13 @@ import AssignmentSubmissionTypeSelectionLaunchButton from '../AssignmentSubmissi
const tool = { const tool = {
title: 'Tool Title', title: 'Tool Title',
description: 'The tool description.', description: 'The tool description.',
icon_url: 'https://www.example.com/icon.png' icon_url: 'https://www.example.com/icon.png',
} }
describe('AssignmentSubmissionTypeSelectionLaunchButton', () => { describe('AssignmentSubmissionTypeSelectionLaunchButton', () => {
it('renders a button to launch the tool', () => { it('renders a button to launch the tool', () => {
const wrapper = render(<AssignmentSubmissionTypeSelectionLaunchButton tool={tool} />) const wrapper = render(<AssignmentSubmissionTypeSelectionLaunchButton tool={tool} />)
expect(wrapper.getByRole('button', { name: `${tool.title} ${tool.description}` })).toBeTruthy() expect(wrapper.getByRole('button', {name: `${tool.title} ${tool.description}`})).toBeTruthy()
}) })
it('renders an icon, a title, description', () => { it('renders an icon, a title, description', () => {

View File

@ -29,11 +29,10 @@ const renderComponent = (props = {}) => {
courseId: 1, courseId: 1,
toolName: 'Awesome Tool', toolName: 'Awesome Tool',
previouslySelected: false, previouslySelected: false,
}; }
return render(<DefaultToolForm {...defaultProps} {...props} />) return render(<DefaultToolForm {...defaultProps} {...props} />)
} }
describe('DefaultToolForm', () => { describe('DefaultToolForm', () => {
beforeEach(() => { beforeEach(() => {
jest.spyOn(axios, 'get').mockResolvedValue({data: []}) jest.spyOn(axios, 'get').mockResolvedValue({data: []})
@ -41,13 +40,13 @@ describe('DefaultToolForm', () => {
it('renders a button to launch the tool', () => { it('renders a button to launch the tool', () => {
const wrapper = renderComponent() const wrapper = renderComponent()
expect(wrapper.getByRole('button', { name: 'Add Content' })).toBeInTheDocument() expect(wrapper.getByRole('button', {name: 'Add Content'})).toBeInTheDocument()
}) })
it('launches the tool when the button is clicked', async () => { it('launches the tool when the button is clicked', async () => {
SelectContentDialog.Events.onContextExternalToolSelect = jest.fn() SelectContentDialog.Events.onContextExternalToolSelect = jest.fn()
const wrapper = renderComponent() const wrapper = renderComponent()
await userEvent.click(wrapper.getByRole('button', { name: 'Add Content' })) await userEvent.click(wrapper.getByRole('button', {name: 'Add Content'}))
expect(SelectContentDialog.Events.onContextExternalToolSelect).toHaveBeenCalled() expect(SelectContentDialog.Events.onContextExternalToolSelect).toHaveBeenCalled()
SelectContentDialog.Events.onContextExternalToolSelect.mockRestore() SelectContentDialog.Events.onContextExternalToolSelect.mockRestore()
}) })
@ -59,7 +58,7 @@ describe('DefaultToolForm', () => {
it('sets the button text', () => { it('sets the button text', () => {
const wrapper = renderComponent({toolButtonText: 'Custom Button Text'}) const wrapper = renderComponent({toolButtonText: 'Custom Button Text'})
expect(wrapper.getByRole('button', { name: 'Custom Button Text' })).toBeInTheDocument() expect(wrapper.getByRole('button', {name: 'Custom Button Text'})).toBeInTheDocument()
}) })
it('renders the success message if previouslySelected is true', () => { it('renders the success message if previouslySelected is true', () => {
@ -85,7 +84,11 @@ describe('DefaultToolForm', () => {
it('renders an error message', async () => { it('renders an error message', async () => {
const wrapper = renderComponent({previouslySelected: true}) const wrapper = renderComponent({previouslySelected: true})
await waitFor(() => expect(wrapper.getByText('The tool is not installed in the course or account')).toBeInTheDocument()) await waitFor(() =>
expect(
wrapper.getByText('The tool is not installed in the course or account')
).toBeInTheDocument()
)
}) })
}) })
}) })

View File

@ -48,7 +48,7 @@ describe('GradeSummary AcceptGradesButton', () => {
const sr_label = within(btn).getByText('Accept grades by Jackie Chan') const sr_label = within(btn).getByText('Accept grades by Jackie Chan')
const visible_label = within(btn).getByText('Accept') const visible_label = within(btn).getByText('Accept')
expect(sr_label.className.includes('screenReaderContent')).toBe(true) expect(sr_label.className.includes('screenReaderContent')).toBe(true)
expect(visible_label).toHaveAttribute('aria-hidden', 'true'); expect(visible_label).toHaveAttribute('aria-hidden', 'true')
}) })
test('is not disabled', () => { test('is not disabled', () => {
@ -110,7 +110,7 @@ describe('GradeSummary AcceptGradesButton', () => {
}) })
test('is labeled with "Accepted"', () => { test('is labeled with "Accepted"', () => {
expect(screen.getByRole('button', { name: 'Accepted' })).toBeInTheDocument() expect(screen.getByRole('button', {name: 'Accepted'})).toBeInTheDocument()
}) })
test('is disabled', () => { test('is disabled', () => {
@ -134,7 +134,7 @@ describe('GradeSummary AcceptGradesButton', () => {
const sr_label = within(btn).getByText('Accept grades by Jackie Chan') const sr_label = within(btn).getByText('Accept grades by Jackie Chan')
const visible_label = within(btn).getByText('Accept') const visible_label = within(btn).getByText('Accept')
expect(sr_label.className.includes('screenReaderContent')).toBe(true) expect(sr_label.className.includes('screenReaderContent')).toBe(true)
expect(visible_label).toHaveAttribute('aria-hidden', 'true'); expect(visible_label).toHaveAttribute('aria-hidden', 'true')
}) })
test('is not disabled', () => { test('is not disabled', () => {
@ -161,7 +161,7 @@ describe('GradeSummary AcceptGradesButton', () => {
const sr_label = within(btn).getByText('Accept grades by Jackie Chan') const sr_label = within(btn).getByText('Accept grades by Jackie Chan')
const visible_label = within(btn).getByText('Accept') const visible_label = within(btn).getByText('Accept')
expect(sr_label.className.includes('screenReaderContent')).toBe(true) expect(sr_label.className.includes('screenReaderContent')).toBe(true)
expect(visible_label).toHaveAttribute('aria-hidden', 'true'); expect(visible_label).toHaveAttribute('aria-hidden', 'true')
}) })
test('is disabled', () => { test('is disabled', () => {

View File

@ -38,7 +38,9 @@ describe('GradeSummary Grid', () => {
return speedGraderUrl('1201', '2301', {anonymousStudents: false, studentId}) return speedGraderUrl('1201', '2301', {anonymousStudents: false, studentId})
} }
afterEach(() => { jest.clearAllMocks() }) afterEach(() => {
jest.clearAllMocks()
})
beforeEach(() => { beforeEach(() => {
props = { props = {
@ -141,20 +143,22 @@ describe('GradeSummary Grid', () => {
test('sends disabledCustomGrade to each GridRow', () => { test('sends disabledCustomGrade to each GridRow', () => {
render(<Grid {...props} />) render(<Grid {...props} />)
const rowCalls = GridRow.mock.calls.filter((call => !call[0].disabledCustomGrade)) const rowCalls = GridRow.mock.calls.filter(call => !call[0].disabledCustomGrade)
expect(rowCalls.length).toBe(4) expect(rowCalls.length).toBe(4)
}) })
test('sends finalGrader to each GridRow', () => { test('sends finalGrader to each GridRow', () => {
render(<Grid {...props} />) render(<Grid {...props} />)
const rowCalls = GridRow.mock.calls.filter((call => !!call[0].finalGrader)) const rowCalls = GridRow.mock.calls.filter(call => !!call[0].finalGrader)
expect(rowCalls.length).toBe(4) expect(rowCalls.length).toBe(4)
}) })
test('sends graders to each GridRow', () => { test('sends graders to each GridRow', () => {
render(<Grid {...props} />) render(<Grid {...props} />)
const rowCalls = GridRow.mock.calls const rowCalls = GridRow.mock.calls
expect(rowCalls.map(row => row[0].graders.map(grader => grader.graderName)).flat()).toStrictEqual([ expect(
rowCalls.map(row => row[0].graders.map(grader => grader.graderName)).flat()
).toStrictEqual([
'Miss Frizzle', 'Miss Frizzle',
'Mr. Keating', 'Mr. Keating',
'Miss Frizzle', 'Miss Frizzle',
@ -168,7 +172,7 @@ describe('GradeSummary Grid', () => {
test('sends onGradeSelect to each GridRow', () => { test('sends onGradeSelect to each GridRow', () => {
render(<Grid {...props} />) render(<Grid {...props} />)
const rowCalls = GridRow.mock.calls.filter((call => !!call[0].onGradeSelect)) const rowCalls = GridRow.mock.calls.filter(call => !!call[0].onGradeSelect)
expect(rowCalls.length).toBe(4) expect(rowCalls.length).toBe(4)
}) })
@ -180,13 +184,15 @@ describe('GradeSummary Grid', () => {
test('sends student-specific select provisional grade statuses to each GridRow', () => { test('sends student-specific select provisional grade statuses to each GridRow', () => {
render(<Grid {...props} />) render(<Grid {...props} />)
const rowCalls = GridRow.mock.calls.filter((call => call[0].selectProvisionalGradeStatus == STARTED)) const rowCalls = GridRow.mock.calls.filter(
call => call[0].selectProvisionalGradeStatus == STARTED
)
expect(rowCalls.length).toBe(1) expect(rowCalls.length).toBe(1)
}) })
test('sends the related row to each GridRow', () => { test('sends the related row to each GridRow', () => {
render(<Grid {...props} />) render(<Grid {...props} />)
const rowCalls = GridRow.mock.calls.filter((call => !!call[0].row)) const rowCalls = GridRow.mock.calls.filter(call => !!call[0].row)
expect(rowCalls.length).toBe(4) expect(rowCalls.length).toBe(4)
}) })
}) })

View File

@ -84,7 +84,9 @@ describe('GradeSummary GridRow', () => {
test('includes a cell for each grader', () => { test('includes a cell for each grader', () => {
renderComponent() renderComponent()
const cells = screen.getAllByRole('cell').filter(cell => cell.className.match(/GradesGrid__ProvisionalGradeCell/)) const cells = screen
.getAllByRole('cell')
.filter(cell => cell.className.match(/GradesGrid__ProvisionalGradeCell/))
expect(cells.length).toBe(props.graders.length) expect(cells.length).toBe(props.graders.length)
}) })

View File

@ -17,7 +17,7 @@
*/ */
import React from 'react' import React from 'react'
import { render, screen, fireEvent } from '@testing-library/react' import {render, screen, fireEvent} from '@testing-library/react'
import { import {
FAILURE, FAILURE,
@ -60,7 +60,7 @@ describe('GradeSummary ReleaseButton', () => {
test('is labeled with "Releasing Grades"', () => { test('is labeled with "Releasing Grades"', () => {
render(<ReleaseButton {...props} />) render(<ReleaseButton {...props} />)
expect(screen.getByRole('button', { name: 'Releasing Grades' })).toBeInTheDocument() expect(screen.getByRole('button', {name: 'Releasing Grades'})).toBeInTheDocument()
}) })
test('does not call the onClick prop when clicked', () => { test('does not call the onClick prop when clicked', () => {
@ -83,7 +83,7 @@ describe('GradeSummary ReleaseButton', () => {
test('is labeled with "Grades Released"', () => { test('is labeled with "Grades Released"', () => {
render(<ReleaseButton {...props} />) render(<ReleaseButton {...props} />)
expect(screen.getByRole('button', { name: 'Grades Released' })).toBeInTheDocument() expect(screen.getByRole('button', {name: 'Grades Released'})).toBeInTheDocument()
}) })
}) })
@ -94,7 +94,7 @@ describe('GradeSummary ReleaseButton', () => {
test('is labeled with "Release Grades"', () => { test('is labeled with "Release Grades"', () => {
render(<ReleaseButton {...props} />) render(<ReleaseButton {...props} />)
expect(screen.getByRole('button', { name: 'Release Grades' })).toBeInTheDocument() expect(screen.getByRole('button', {name: 'Release Grades'})).toBeInTheDocument()
}) })
test('calls the onClick prop when clicked', () => { test('calls the onClick prop when clicked', () => {

View File

@ -786,13 +786,17 @@ export default AssignmentListItemView = (function () {
} }
json.submission = submissionJSON json.submission = submissionJSON
let grade = submission.get('grade') let grade = submission.get('grade')
// it should skip this logic if it is a pass/fail assignment or if the // it should skip this logic if it is a pass/fail assignment or if the
// grading type is letter grade and the grade represents the letter grade // grading type is letter grade and the grade represents the letter grade
// and the score represents the numerical grade // and the score represents the numerical grade
// this is usually how the grade is stored when the assignment is letter grade // this is usually how the grade is stored when the assignment is letter grade
// but this does not happen when points possible is 0, then the grade is not saved as a letter grade // but this does not happen when points possible is 0, then the grade is not saved as a letter grade
// and needs to be converted // and needs to be converted
if (json.restrict_quantitative_data && gradingType !== 'pass_fail' && !(gradingType === 'letter_grade' && String(grade) !== String(score))) { if (
json.restrict_quantitative_data &&
gradingType !== 'pass_fail' &&
!(gradingType === 'letter_grade' && String(grade) !== String(score))
) {
gradingType = 'letter_grade' gradingType = 'letter_grade'
if (json.pointsPossible === 0 && json.submission.score < 0) { if (json.pointsPossible === 0 && json.submission.score < 0) {
grade = json.submission.score grade = json.submission.score

View File

@ -16,7 +16,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React from 'react' import React from 'react'
import { render, screen, fireEvent, cleanup} from '@testing-library/react' import {render, screen, fireEvent, cleanup} from '@testing-library/react'
import '@testing-library/jest-dom/extend-expect' import '@testing-library/jest-dom/extend-expect'
import Actions from '../actions/IndexMenuActions' import Actions from '../actions/IndexMenuActions'
import IndexMenu from '../IndexMenu' import IndexMenu from '../IndexMenu'
@ -66,7 +66,7 @@ const generateProps = (overrides, initialState = {}) => {
} }
} }
const renderComponent = (overrides={}, initialState={}) => { const renderComponent = (overrides = {}, initialState = {}) => {
return render(<IndexMenu {...generateProps(overrides, initialState)} />) return render(<IndexMenu {...generateProps(overrides, initialState)} />)
} }
@ -74,10 +74,10 @@ describe('AssignmentsIndexMenu', () => {
let oldEnv let oldEnv
beforeEach(() => { beforeEach(() => {
oldEnv = { ...window.ENV } oldEnv = {...window.ENV}
Actions.apiGetLaunches.mockReturnValue({ Actions.apiGetLaunches.mockReturnValue({
type: 'STUB_API_GET_TOOLS', type: 'STUB_API_GET_TOOLS',
}) })
}) })
@ -98,8 +98,8 @@ describe('AssignmentsIndexMenu', () => {
it('renders a bulk edit option if property is specified', () => { it('renders a bulk edit option if property is specified', () => {
const requestBulkEditFn = jest.fn() const requestBulkEditFn = jest.fn()
const {getAllByText} = renderComponent({ requestBulkEdit: requestBulkEditFn }) const {getAllByText} = renderComponent({requestBulkEdit: requestBulkEditFn})
const menuitem = getAllByText("Edit Assignment Dates") const menuitem = getAllByText('Edit Assignment Dates')
expect(menuitem.length).toBe(1) expect(menuitem.length).toBe(1)
// click menuitem: // click menuitem:
fireEvent.click(menuitem[0]) fireEvent.click(menuitem[0])
@ -109,7 +109,7 @@ describe('AssignmentsIndexMenu', () => {
it('does not render a bulk edit option if property is not specified', () => { it('does not render a bulk edit option if property is not specified', () => {
const {queryByText} = renderComponent() const {queryByText} = renderComponent()
// expect no element with text "Edit Assignment Dates": // expect no element with text "Edit Assignment Dates":
expect(queryByText("Edit Assignment Dates")).toBeNull() expect(queryByText('Edit Assignment Dates')).toBeNull()
}) })
it("doesn't show an LTI tool modal if modalIsOpen is not true", () => { it("doesn't show an LTI tool modal if modalIsOpen is not true", () => {
@ -118,13 +118,16 @@ describe('AssignmentsIndexMenu', () => {
}) })
it('shows the LTI tool modal state if modalIsOpen is true', () => { it('shows the LTI tool modal state if modalIsOpen is true', () => {
renderComponent({}, { renderComponent(
modalIsOpen: true, {},
selectedTool: { {
placements: {course_assignments_menu: {title: 'foo'}}, modalIsOpen: true,
definition_id: 100, selectedTool: {
}, placements: {course_assignments_menu: {title: 'foo'}},
}) definition_id: 100,
},
}
)
expect(screen.queryByRole('dialog')).not.toBeNull() expect(screen.queryByRole('dialog')).not.toBeNull()
}) })
@ -199,7 +202,7 @@ describe('AssignmentsIndexMenu', () => {
] ]
mockWindowLocationReload = jest.fn() mockWindowLocationReload = jest.fn()
const mockLocation = { ...window.location, reload: mockWindowLocationReload } const mockLocation = {...window.location, reload: mockWindowLocationReload}
jest.spyOn(window, 'location', 'get').mockImplementation(() => mockLocation) jest.spyOn(window, 'location', 'get').mockImplementation(() => mockLocation)
}) })
@ -232,7 +235,7 @@ describe('AssignmentsIndexMenu', () => {
// that here. // that here.
it('reloads the page when assignment_index_menu receives and LTI 1.1 externalContentReady message', () => { it('reloads the page when assignment_index_menu receives and LTI 1.1 externalContentReady message', () => {
const {container} = renderComponent({sisName: "test1"}) const {container} = renderComponent({sisName: 'test1'})
makeMountPointUsedForTray(container) makeMountPointUsedForTray(container)
openTray(container) openTray(container)
@ -245,14 +248,14 @@ describe('AssignmentsIndexMenu', () => {
it('clears the window listener handler when unmounted', () => { it('clears the window listener handler when unmounted', () => {
let container let container
container = renderComponent({sisName: "test2"}).container container = renderComponent({sisName: 'test2'}).container
makeMountPointUsedForTray(container) makeMountPointUsedForTray(container)
openTray(container) openTray(container)
cleanup() cleanup()
expect(mockWindowLocationReload).toHaveBeenCalledTimes(0) expect(mockWindowLocationReload).toHaveBeenCalledTimes(0)
container = renderComponent({sisName: "test2"}).container container = renderComponent({sisName: 'test2'}).container
makeMountPointUsedForTray(container) makeMountPointUsedForTray(container)
openTray(container) openTray(container)

View File

@ -225,7 +225,7 @@ function renderItemAssignToTray(open, returnFocusTo, itemProps) {
) )
} }
function renderDrawerLayout (tool, onDismiss) { function renderDrawerLayout(tool, onDismiss) {
const mountPoint = document.getElementById('drawer-layout-mount-point') const mountPoint = document.getElementById('drawer-layout-mount-point')
const pageContent = document.getElementById('application') const pageContent = document.getElementById('application')
@ -238,7 +238,7 @@ function renderDrawerLayout (tool, onDismiss) {
pageContentTitle="" pageContentTitle=""
pageContentMinWidth="40rem" pageContentMinWidth="40rem"
pageContentHeight={window.innerHeight} pageContentHeight={window.innerHeight}
acceptedResourceTypes={["assignment"]} acceptedResourceTypes={['assignment']}
targetResourceType="assignment" targetResourceType="assignment"
allowItemSelection={false} allowItemSelection={false}
selectableItems={[]} selectableItems={[]}
@ -253,7 +253,9 @@ function renderDrawerLayout (tool, onDismiss) {
} }
ready(() => { ready(() => {
if (!document.getElementById('drawer-layout-mount-point')) { return } if (!document.getElementById('drawer-layout-mount-point')) {
return
}
let tool = null let tool = null
const onDismiss = () => { const onDismiss = () => {

View File

@ -16,22 +16,22 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React, {useState} from 'react' import React, {useState} from 'react'
import { arrayOf, bool } from 'prop-types' import {arrayOf, bool} from 'prop-types'
import CanvasSelect from '@canvas/instui-bindings/react/Select' import CanvasSelect from '@canvas/instui-bindings/react/Select'
import { fillAssessment } from '@canvas/rubrics/react/helpers' import {fillAssessment} from '@canvas/rubrics/react/helpers'
import { useScope as useI18nScope } from '@canvas/i18n' import {useScope as useI18nScope} from '@canvas/i18n'
import { ProficiencyRating } from '@canvas/assignments/graphql/student/ProficiencyRating' import {ProficiencyRating} from '@canvas/assignments/graphql/student/ProficiencyRating'
import { Rubric } from '@canvas/assignments/graphql/student/Rubric' import {Rubric} from '@canvas/assignments/graphql/student/Rubric'
import { RubricAssessment } from '@canvas/assignments/graphql/student/RubricAssessment' import {RubricAssessment} from '@canvas/assignments/graphql/student/RubricAssessment'
import { RubricAssociation } from '@canvas/assignments/graphql/student/RubricAssociation' import {RubricAssociation} from '@canvas/assignments/graphql/student/RubricAssociation'
import RubricComponent from '@canvas/rubrics/react/Rubric' import RubricComponent from '@canvas/rubrics/react/Rubric'
import { Text } from '@instructure/ui-text' import {Text} from '@instructure/ui-text'
import { ToggleDetails } from '@instructure/ui-toggle-details' import {ToggleDetails} from '@instructure/ui-toggle-details'
import { Alert } from '@instructure/ui-alerts' import {Alert} from '@instructure/ui-alerts'
import { View } from '@instructure/ui-view' import {View} from '@instructure/ui-view'
import useStore from './stores/index' import useStore from './stores/index'
import { RubricAssessmentTray, TraditionalView } from '@canvas/rubrics/react/RubricAssessment' import {RubricAssessmentTray, TraditionalView} from '@canvas/rubrics/react/RubricAssessment'
import { Button } from '@instructure/ui-buttons' import {Button} from '@instructure/ui-buttons'
const I18n = useI18nScope('assignments_2') const I18n = useI18nScope('assignments_2')
@ -59,7 +59,7 @@ export default function RubricTab(props) {
} }
const onAssessmentChange = updatedAssessment => { const onAssessmentChange = updatedAssessment => {
const newState = { displayedAssessment: updatedAssessment } const newState = {displayedAssessment: updatedAssessment}
if (enhancedRubricsEnabled) { if (enhancedRubricsEnabled) {
newState['isSavingRubricAssessment'] = true newState['isSavingRubricAssessment'] = true
setRubricTrayOpen(false) setRubricTrayOpen(false)
@ -70,7 +70,7 @@ export default function RubricTab(props) {
const assessmentSelectorChanged = assessmentId => { const assessmentSelectorChanged = assessmentId => {
const assessment = findAssessmentById(assessmentId) const assessment = findAssessmentById(assessmentId)
const filledAssessment = fillAssessment(props.rubric, assessment || {}) const filledAssessment = fillAssessment(props.rubric, assessment || {})
useStore.setState({ displayedAssessment: filledAssessment }) useStore.setState({displayedAssessment: filledAssessment})
} }
const hasSubmittedAssessment = props.assessments?.some( const hasSubmittedAssessment = props.assessments?.some(
@ -82,7 +82,7 @@ export default function RubricTab(props) {
return { return {
...data, ...data,
criterionId: data.criterion_id, criterionId: data.criterion_id,
points: typeof points === 'number' ? points : points.value points: typeof points === 'number' ? points : points.value,
} }
}) })
@ -100,7 +100,7 @@ export default function RubricTab(props) {
criteria={props.rubric.criteria} criteria={props.rubric.criteria}
hidePoints={hidePoints} hidePoints={hidePoints}
isPreviewMode={true} isPreviewMode={true}
onUpdateAssessmentData={() => { }} onUpdateAssessmentData={() => {}}
rubricTitle={props.rubric.title} rubricTitle={props.rubric.title}
rubricAssessmentData={rubricAssessmentData} rubricAssessmentData={rubricAssessmentData}
/> />
@ -128,75 +128,74 @@ export default function RubricTab(props) {
</Alert> </Alert>
)} )}
{ {showEnhancedRubricPeerReview ? (
showEnhancedRubricPeerReview ? ( <View as="div" margin="small 0 0 0">
<View as="div" margin="small 0 0 0"> <Button onClick={() => setRubricTrayOpen(!rubricTrayOpen)}>
<Button onClick={() => setRubricTrayOpen(!rubricTrayOpen)}> {hasSubmittedAssessment ? I18n.t('View Rubric') : I18n.t('Fill Out Rubric')}
{hasSubmittedAssessment ? I18n.t('View Rubric') : I18n.t('Fill Out Rubric')} </Button>
</Button> <RubricAssessmentTray
<RubricAssessmentTray hidePoints={hidePoints}
hidePoints={hidePoints} isOpen={rubricTrayOpen}
isOpen={rubricTrayOpen} isPreviewMode={hasSubmittedAssessment}
isPreviewMode={hasSubmittedAssessment} isPeerReview={true}
isPeerReview={true} onDismiss={() => setRubricTrayOpen(false)}
onDismiss={() => setRubricTrayOpen(false)} rubricAssessmentData={rubricAssessmentData}
rubricAssessmentData={rubricAssessmentData} rubric={props.rubric}
rubric={props.rubric} onSubmit={assessment => {
onSubmit={(assessment) => { const updatedState = {
const updatedState = { score: assessment.reduce((prev, curr) => prev + curr.points, 0),
score: assessment.reduce((prev, curr) => prev + curr.points, 0), data: assessment.map(criterionAssessment => {
data: assessment.map(criterionAssessment => { const {points} = criterionAssessment
const {points} = criterionAssessment const valid = !Number.isNaN(points)
const valid = !Number.isNaN(points) return {
return { ...criterionAssessment,
...criterionAssessment, points: {
points: { text: points.toString(),
text: points.toString(), valid,
valid, value: points,
value: points },
} }
}}) }),
} }
onAssessmentChange(updatedState) onAssessmentChange(updatedState)
}} }}
/> />
</View> </View>
) : ( ) : (
<ToggleDetails <ToggleDetails
defaultExpanded={true} defaultExpanded={true}
fluidWidth={true} fluidWidth={true}
data-testid="fill-out-rubric-toggle" data-testid="fill-out-rubric-toggle"
summary={ summary={
<Text weight="bold"> <Text weight="bold">
{props.peerReviewModeEnabled ? I18n.t('Fill Out Rubric') : I18n.t('View Rubric')} {props.peerReviewModeEnabled ? I18n.t('Fill Out Rubric') : I18n.t('View Rubric')}
</Text> </Text>
} }
> >
{!props.peerReviewModeEnabled && !!props.assessments?.length && ( {!props.peerReviewModeEnabled && !!props.assessments?.length && (
<div style={{ marginBottom: '22px', width: '325px' }}> <div style={{marginBottom: '22px', width: '325px'}}>
<CanvasSelect <CanvasSelect
label={I18n.t('Select Grader')} label={I18n.t('Select Grader')}
value={displayedAssessment?._id} value={displayedAssessment?._id}
data-testid="select-grader-dropdown" data-testid="select-grader-dropdown"
onChange={(e, optionValue) => assessmentSelectorChanged(optionValue)} onChange={(e, optionValue) => assessmentSelectorChanged(optionValue)}
> >
{props.assessments.map(assessment => ( {props.assessments.map(assessment => (
<CanvasSelect.Option <CanvasSelect.Option
key={assessment._id} key={assessment._id}
value={assessment._id} value={assessment._id}
id={assessment._id} id={assessment._id}
> >
{formatAssessor(assessment.assessor)} {formatAssessor(assessment.assessor)}
</CanvasSelect.Option> </CanvasSelect.Option>
))} ))}
</CanvasSelect> </CanvasSelect>
</div> </div>
)} )}
{renderRubricPreview()} {renderRubricPreview()}
</ToggleDetails> </ToggleDetails>
) )}
}
</View> </View>
</div> </div>
) )

View File

@ -276,10 +276,10 @@ function StudentContent(props) {
// TODO: Move the button provider up one level // TODO: Move the button provider up one level
return ( return (
<div data-testid="assignments-2-student-view" style={{marginTop: '-36px'}}> <div data-testid="assignments-2-student-view" style={{marginTop: '-36px'}}>
<View <View
as='div' as="div"
position='sticky' position="sticky"
stacking='above' stacking="above"
background="primary" background="primary"
style={{top: 0}} style={{top: 0}}
padding="large 0 0 0" padding="large 0 0 0"

View File

@ -292,7 +292,6 @@ const SubmissionManager = ({
setOnFailure(I18n.t('Invalid Rubric Submission')) setOnFailure(I18n.t('Invalid Rubric Submission'))
} }
} }
}, [isSavingRubricAssessment]) }, [isSavingRubricAssessment])
const isRubricComplete = assessment => { const isRubricComplete = assessment => {

View File

@ -26,7 +26,6 @@ import getSampleData from './getSampleData'
import sinon from 'sinon' import sinon from 'sinon'
describe('AssociationsTable component', () => { describe('AssociationsTable component', () => {
const focusManager = new FocusManager() const focusManager = new FocusManager()
focusManager.before = document.body focusManager.before = document.body
@ -52,15 +51,21 @@ describe('AssociationsTable component', () => {
const rows = tree.container.querySelectorAll('tr[data-testid="associations-course-row"]') const rows = tree.container.querySelectorAll('tr[data-testid="associations-course-row"]')
expect(rows.length).toEqual(props.existingAssociations.length) expect(rows.length).toEqual(props.existingAssociations.length)
expect(rows[0].querySelectorAll('td')[0].textContent).toEqual(props.existingAssociations[0].name) expect(rows[0].querySelectorAll('td')[0].textContent).toEqual(
expect(rows[1].querySelectorAll('td')[0].textContent).toEqual(props.existingAssociations[1].name) props.existingAssociations[0].name
)
expect(rows[1].querySelectorAll('td')[0].textContent).toEqual(
props.existingAssociations[1].name
)
}) })
test('calls onRemoveAssociations when association remove button is clicked', async () => { test('calls onRemoveAssociations when association remove button is clicked', async () => {
const props = defaultProps() const props = defaultProps()
props.onRemoveAssociations = sinon.spy() props.onRemoveAssociations = sinon.spy()
const tree = render(<AssociationsTable {...props} />) const tree = render(<AssociationsTable {...props} />)
const button = tree.container.querySelectorAll('tr[data-testid="associations-course-row"] button') const button = tree.container.querySelectorAll(
'tr[data-testid="associations-course-row"] button'
)
await userEvent.click(button[0]) await userEvent.click(button[0])
expect(props.onRemoveAssociations.callCount).toEqual(1) expect(props.onRemoveAssociations.callCount).toEqual(1)

View File

@ -23,7 +23,6 @@ import BlueprintAssociations from '../BlueprintAssociations'
import getSampleData from './getSampleData' import getSampleData from './getSampleData'
describe('BlueprintAssociations component', () => { describe('BlueprintAssociations component', () => {
const defaultProps = () => ({ const defaultProps = () => ({
courses: [], courses: [],
existingAssociations: [], existingAssociations: [],

View File

@ -23,7 +23,7 @@ import {shallow} from 'enzyme'
import BlueprintSidebar from '../BlueprintSidebar' import BlueprintSidebar from '../BlueprintSidebar'
import sinon from 'sinon' import sinon from 'sinon'
describe('BlueprintSidebar', (hooks) => { describe('BlueprintSidebar', hooks => {
let clock let clock
let wrapper let wrapper

View File

@ -51,15 +51,21 @@ describe('CoursePickerTable component', () => {
const rows = tree.container.querySelectorAll('tr[data-testid="bca-table__course-row"]') const rows = tree.container.querySelectorAll('tr[data-testid="bca-table__course-row"]')
expect(rows.length).toEqual(props.courses.length) expect(rows.length).toEqual(props.courses.length)
expect(rows[0].querySelectorAll('td')[0].textContent).toEqual(`Toggle select course ${props.courses[0].name}`) expect(rows[0].querySelectorAll('td')[0].textContent).toEqual(
expect(rows[1].querySelectorAll('td')[0].textContent).toEqual(`Toggle select course ${props.courses[1].name}`) `Toggle select course ${props.courses[0].name}`
)
expect(rows[1].querySelectorAll('td')[0].textContent).toEqual(
`Toggle select course ${props.courses[1].name}`
)
}) })
test('calls onSelectedChanged when courses are selected', async () => { test('calls onSelectedChanged when courses are selected', async () => {
const props = defaultProps() const props = defaultProps()
props.onSelectedChanged = sinon.spy() props.onSelectedChanged = sinon.spy()
const tree = render(<CoursePickerTable {...props} />) const tree = render(<CoursePickerTable {...props} />)
const checkbox = tree.container.querySelectorAll('[data-testid="bca-table__course-row"] input[type="checkbox"]')[0] const checkbox = tree.container.querySelectorAll(
'[data-testid="bca-table__course-row"] input[type="checkbox"]'
)[0]
await userEvent.click(checkbox) await userEvent.click(checkbox)
expect(props.onSelectedChanged.callCount).toEqual(1) expect(props.onSelectedChanged.callCount).toEqual(1)
@ -71,7 +77,9 @@ describe('CoursePickerTable component', () => {
props.selectedCourses = ['1'] props.selectedCourses = ['1']
props.onSelectedChanged = sinon.spy() props.onSelectedChanged = sinon.spy()
const tree = render(<CoursePickerTable {...props} />) const tree = render(<CoursePickerTable {...props} />)
const checkbox = tree.container.querySelectorAll('[data-testid="bca-table__course-row"] input[type="checkbox"]')[0] const checkbox = tree.container.querySelectorAll(
'[data-testid="bca-table__course-row"] input[type="checkbox"]'
)[0]
await userEvent.click(checkbox) await userEvent.click(checkbox)
expect(props.onSelectedChanged.callCount).toEqual(1) expect(props.onSelectedChanged.callCount).toEqual(1)
@ -83,7 +91,9 @@ describe('CoursePickerTable component', () => {
props.onSelectedChanged = sinon.spy() props.onSelectedChanged = sinon.spy()
const tree = render(<CoursePickerTable {...props} />) const tree = render(<CoursePickerTable {...props} />)
const checkbox = tree.container.querySelectorAll('.btps-table__header-wrapper input[type="checkbox"]')[0] const checkbox = tree.container.querySelectorAll(
'.btps-table__header-wrapper input[type="checkbox"]'
)[0]
await userEvent.click(checkbox) await userEvent.click(checkbox)
expect(props.onSelectedChanged.callCount).toEqual(1) expect(props.onSelectedChanged.callCount).toEqual(1)
@ -96,7 +106,9 @@ describe('CoursePickerTable component', () => {
const tree = render(<CoursePickerTable {...props} ref={ref} />) const tree = render(<CoursePickerTable {...props} ref={ref} />)
const instance = ref.current const instance = ref.current
const check = tree.container.querySelectorAll('[data-testid="bca-table__course-row"] input[type="checkbox"]')[0] const check = tree.container.querySelectorAll(
'[data-testid="bca-table__course-row"] input[type="checkbox"]'
)[0]
check.focus = sinon.spy() check.focus = sinon.spy()
instance.handleFocusLoss(0) instance.handleFocusLoss(0)
@ -109,7 +121,9 @@ describe('CoursePickerTable component', () => {
const tree = render(<CoursePickerTable {...props} ref={ref} />) const tree = render(<CoursePickerTable {...props} ref={ref} />)
const instance = ref.current const instance = ref.current
const check = tree.container.querySelectorAll('[data-testid="bca-table__course-row"] input[type="checkbox"]')[1] const check = tree.container.querySelectorAll(
'[data-testid="bca-table__course-row"] input[type="checkbox"]'
)[1]
check.focus = sinon.spy() check.focus = sinon.spy()
instance.handleFocusLoss(2) instance.handleFocusLoss(2)
@ -123,7 +137,9 @@ describe('CoursePickerTable component', () => {
const tree = render(<CoursePickerTable {...props} ref={ref} />) const tree = render(<CoursePickerTable {...props} ref={ref} />)
const instance = ref.current const instance = ref.current
const check = tree.container.querySelectorAll('.bca-table__select-all input[type="checkbox"]')[0] const check = tree.container.querySelectorAll(
'.bca-table__select-all input[type="checkbox"]'
)[0]
check.focus = sinon.spy() check.focus = sinon.spy()
instance.handleFocusLoss(1) instance.handleFocusLoss(1)

View File

@ -51,7 +51,7 @@ const defaultProps = () => ({
contentRef: cr => { contentRef: cr => {
sidebarContentRef = cr sidebarContentRef = cr
}, },
routeTo: () => {} routeTo: () => {},
}) })
function mockStore(initialState) { function mockStore(initialState) {
@ -102,7 +102,9 @@ describe('Course Sidebar component', () => {
// associations // associations
expect(rows.eq(0).find('button#mcSidebarAsscBtn').size()).toBeTruthy() expect(rows.eq(0).find('button#mcSidebarAsscBtn').size()).toBeTruthy()
expect(rows.eq(0).text().trim()).toEqual(`Associations${initialState.existingAssociations.length}`) expect(rows.eq(0).text().trim()).toEqual(
`Associations${initialState.existingAssociations.length}`
)
// sync history // sync history
expect(rows.eq(1).find('button#mcSyncHistoryBtn').size()).toBeTruthy() expect(rows.eq(1).find('button#mcSyncHistoryBtn').size()).toBeTruthy()
@ -202,7 +204,7 @@ describe('Course Sidebar component', () => {
tree.unmount() tree.unmount()
}) })
test('renders Sync button if has associations and has unsynced changes', async() => { test('renders Sync button if has associations and has unsynced changes', async () => {
const props = defaultProps() const props = defaultProps()
const state = {...initialState} const state = {...initialState}
const tree = render(connect(props, state)) const tree = render(connect(props, state))

View File

@ -26,7 +26,6 @@ import MigrationStates from '@canvas/blueprint-courses/react/migrationStates'
const noop = () => {} const noop = () => {}
describe('MigrationOptions component', () => { describe('MigrationOptions component', () => {
const defaultProps = { const defaultProps = {
migrationStatus: MigrationStates.states.unknown, migrationStatus: MigrationStates.states.unknown,
willSendNotification: false, willSendNotification: false,

View File

@ -24,7 +24,6 @@ import userEvent from '@testing-library/user-event'
import MigrationSync from '../MigrationSync' import MigrationSync from '../MigrationSync'
describe('MigrationSync component', () => { describe('MigrationSync component', () => {
const defaultProps = () => ({ const defaultProps = () => ({
migrationStatus: 'void', migrationStatus: 'void',
hasCheckedMigration: true, hasCheckedMigration: true,

View File

@ -23,16 +23,15 @@ import SyncHistory from '../SyncHistory'
import getSampleData from './getSampleData' import getSampleData from './getSampleData'
describe('SyncHistory component', () => { describe('SyncHistory component', () => {
const defaultProps = () => ({
const defaultProps = () => ({ loadHistory: () => {},
loadHistory: () => {}, isLoadingHistory: false,
isLoadingHistory: false, hasLoadedHistory: false,
hasLoadedHistory: false, loadAssociations: () => {},
loadAssociations: () => {}, isLoadingAssociations: false,
isLoadingAssociations: false, hasLoadedAssociations: false,
hasLoadedAssociations: false, migrations: getSampleData().history,
migrations: getSampleData().history, })
})
test('renders the SyncHistory component', () => { test('renders the SyncHistory component', () => {
const tree = shallow(<SyncHistory {...defaultProps()} />) const tree = shallow(<SyncHistory {...defaultProps()} />)

View File

@ -92,7 +92,6 @@ function connect(props = {...defaultProps}) {
} }
describe('UnsyncedChanges component', () => { describe('UnsyncedChanges component', () => {
test('renders the UnsyncedChanges component', () => { test('renders the UnsyncedChanges component', () => {
const tree = render(connect()) const tree = render(connect())
let node = tree.container.querySelector('.bcs__unsynced-item__table') let node = tree.container.querySelector('.bcs__unsynced-item__table')

View File

@ -153,7 +153,9 @@ CalendarHeader.prototype._selectAgenda = function (_event) {
CalendarHeader.prototype._triggerWeek = function (_event) { CalendarHeader.prototype._triggerWeek = function (_event) {
if (ENV.FEATURES?.instui_header) { if (ENV.FEATURES?.instui_header) {
document.dispatchEvent(new CustomEvent('calendar:header:select_view', {detail: {viewName: 'week'}})) document.dispatchEvent(
new CustomEvent('calendar:header:select_view', {detail: {viewName: 'week'}})
)
} }
return this.trigger('week') return this.trigger('week')
@ -161,7 +163,9 @@ CalendarHeader.prototype._triggerWeek = function (_event) {
CalendarHeader.prototype._triggerMonth = function (_event) { CalendarHeader.prototype._triggerMonth = function (_event) {
if (ENV.FEATURES?.instui_header) { if (ENV.FEATURES?.instui_header) {
document.dispatchEvent(new CustomEvent('calendar:header:select_view', {detail: {viewName: 'month'}})) document.dispatchEvent(
new CustomEvent('calendar:header:select_view', {detail: {viewName: 'month'}})
)
} }
return this.trigger('month') return this.trigger('month')
@ -169,9 +173,11 @@ CalendarHeader.prototype._triggerMonth = function (_event) {
CalendarHeader.prototype._triggerAgenda = function (_event) { CalendarHeader.prototype._triggerAgenda = function (_event) {
if (ENV.FEATURES?.instui_header) { if (ENV.FEATURES?.instui_header) {
document.dispatchEvent(new CustomEvent('calendar:header:select_view', {detail: {viewName: 'agenda'}})) document.dispatchEvent(
new CustomEvent('calendar:header:select_view', {detail: {viewName: 'agenda'}})
)
} }
return this.trigger('agenda') return this.trigger('agenda')
} }
@ -199,7 +205,7 @@ CalendarHeader.prototype._handleKeyDownEvent = function (event) {
} }
} }
CalendarHeader.prototype._loadObjects = function(options = null) { CalendarHeader.prototype._loadObjects = function (options = null) {
this.navigator = new CalendarNavigator({ this.navigator = new CalendarNavigator({
el: this.$navigator, el: this.$navigator,
size: options?.size, size: options?.size,
@ -211,15 +217,15 @@ CalendarHeader.prototype._loadObjects = function(options = null) {
this.connectEvents() this.connectEvents()
} }
CalendarHeader.prototype.afterRender = function() { CalendarHeader.prototype.afterRender = function () {
if (!ENV.FEATURES?.instui_header) { if (!ENV.FEATURES?.instui_header) {
return this._loadObjects() return this._loadObjects()
} }
ReactDOM.render( ReactDOM.render(
<CalendarHeaderComponent <CalendarHeaderComponent
bridge={{ bridge={{
onLoadReady: (options) => { onLoadReady: options => {
if (this.navigator) return if (this.navigator) return
this.$calendarViewButtons = this.$el.find('.calendar_view_buttons') this.$calendarViewButtons = this.$el.find('.calendar_view_buttons')
@ -230,8 +236,9 @@ CalendarHeader.prototype.afterRender = function() {
this._loadObjects(options) this._loadObjects(options)
}, },
onChangeSelectViewMode: (viewName) => this.selectView(viewName), onChangeSelectViewMode: viewName => this.selectView(viewName),
}} />, }}
/>,
this.$el.find('#calendar_header_component')[0] this.$el.find('#calendar_header_component')[0]
) )
} }

View File

@ -45,8 +45,7 @@ if (ENV.FEATURES?.instui_header) {
'click .navigation_title': '_onTitleClick', 'click .navigation_title': '_onTitleClick',
'keyclick .navigation_title': '_onTitleClick', 'keyclick .navigation_title': '_onTitleClick',
} }
} } else {
else {
CalendarNavigator.prototype.els = { CalendarNavigator.prototype.els = {
'.navigation_title': '$title', '.navigation_title': '$title',
'.navigation_title_text': '$titleText', '.navigation_title_text': '$titleText',
@ -70,7 +69,7 @@ CalendarNavigator.prototype.initialize = function () {
CalendarNavigator.__super__.initialize.apply(this, arguments) CalendarNavigator.__super__.initialize.apply(this, arguments)
this.render() this.render()
this._savedButtonsVisibility = true this._savedButtonsVisibility = true
// use debounce to make the aria-live updates nicer // use debounce to make the aria-live updates nicer
this._flashDateSuggestion = debounce(this._flashDateSuggestion, 1500) this._flashDateSuggestion = debounce(this._flashDateSuggestion, 1500)
} }
@ -86,7 +85,7 @@ CalendarNavigator.prototype.hide = function () {
return this.show(false) return this.show(false)
} }
CalendarNavigator.prototype.setTitle = function (new_text) { CalendarNavigator.prototype.setTitle = function (new_text) {
this.$titleText.attr('aria-label', new_text + ' click to change') this.$titleText.attr('aria-label', new_text + ' click to change')
return this.$titleText.text(new_text) return this.$titleText.text(new_text)
} }
@ -194,7 +193,7 @@ CalendarNavigator.prototype._onPickerClose = function () {
return this.hidePicker() return this.hidePicker()
} }
CalendarNavigator.prototype._loadDateField = function () { CalendarNavigator.prototype._loadDateField = function () {
// make sure our jquery key handler is called first // make sure our jquery key handler is called first
this.$dateField.keydown(this._onDateFieldKey) this.$dateField.keydown(this._onDateFieldKey)
this.$dateField.date_field({ this.$dateField.date_field({
@ -217,7 +216,7 @@ CalendarNavigator.prototype.afterRender = function () {
} }
ReactDOM.render( ReactDOM.render(
<CalendarNavigatorComponent <CalendarNavigatorComponent
size={this.options.size || 'large'} size={this.options.size || 'large'}
bridge={{ bridge={{
navigatePrev: () => this.trigger('navigatePrev'), navigatePrev: () => this.trigger('navigatePrev'),
@ -225,7 +224,7 @@ CalendarNavigator.prototype.afterRender = function () {
navigateNext: () => this.trigger('navigateNext'), navigateNext: () => this.trigger('navigateNext'),
onLoadReady: () => { onLoadReady: () => {
// class.prototype.els is disabled with instui_header FF // class.prototype.els is disabled with instui_header FF
// because els initializes with the template BEFORE react is injected in the DOM // because els initializes with the template BEFORE react is injected in the DOM
// so we need to redefine the els vars each time the component is rendered // so we need to redefine the els vars each time the component is rendered
if (this._reactViewAlreadyLoaded) { if (this._reactViewAlreadyLoaded) {
@ -235,24 +234,25 @@ CalendarNavigator.prototype.afterRender = function () {
dateFieldValue: this.$dateField.val(), dateFieldValue: this.$dateField.val(),
} }
} }
this.$title = this.$el.find('.navigation_title') this.$title = this.$el.find('.navigation_title')
this.$titleText = this.$el.find('.navigation_title_text') this.$titleText = this.$el.find('.navigation_title_text')
this.$buttons = this.$el.find('.navigation_buttons') this.$buttons = this.$el.find('.navigation_buttons')
this.$dateField = this.$el.find('.date_field') this.$dateField = this.$el.find('.date_field')
this.$dateWrapper = this.$el.find('.date_field_wrapper') this.$dateWrapper = this.$el.find('.date_field_wrapper')
this._reactViewAlreadyLoaded = true this._reactViewAlreadyLoaded = true
this._loadDateField() this._loadDateField()
if (this._restorePreviousData) { if (this._restorePreviousData) {
this.setTitle(this._restorePreviousData.titleTextValue) this.setTitle(this._restorePreviousData.titleTextValue)
this.$dateField.val(this._restorePreviousData.dateFieldValue) this.$dateField.val(this._restorePreviousData.dateFieldValue)
this.$buttons.toggle(this._savedButtonsVisibility) this.$buttons.toggle(this._savedButtonsVisibility)
} }
}, },
}} />, }}
/>,
this.$el.find('#calendar_navigator_component')[0] this.$el.find('#calendar_navigator_component')[0]
) )
} }

View File

@ -49,7 +49,7 @@ const start = () => {
initializeDelayed(header) initializeDelayed(header)
} }
const initializeDelayed = (header) => { const initializeDelayed = header => {
const calendar = new Calendar( const calendar = new Calendar(
'#calendar-app', '#calendar-app',
ENV.CALENDAR.CONTEXTS, ENV.CALENDAR.CONTEXTS,

View File

@ -176,7 +176,7 @@ describe('Other Calendars modal ', () => {
expect(fetchMock.calls(markAsSeenUrl)).toHaveLength(1) expect(fetchMock.calls(markAsSeenUrl)).toHaveLength(1)
}) })
it('does not try to mark the feature as seen if it is already seen', async() => { it('does not try to mark the feature as seen if it is already seen', async () => {
const {getByTestId} = render(<AccountCalendarsModal {...getProps({featureSeen: true})} />) const {getByTestId} = render(<AccountCalendarsModal {...getProps({featureSeen: true})} />)
const addCalendarButton = getByTestId('add-other-calendars-button') const addCalendarButton = getByTestId('add-other-calendars-button')
await openModal(addCalendarButton) await openModal(addCalendarButton)

View File

@ -21,13 +21,11 @@ import {shallow} from 'enzyme'
import {render} from '@testing-library/react' import {render} from '@testing-library/react'
import FindAppointmentApp from '../FindAppointment' import FindAppointmentApp from '../FindAppointment'
const courses = [ const courses = [
{id: 1, name: 'testCourse1', asset_string: 'thing1'}, {id: 1, name: 'testCourse1', asset_string: 'thing1'},
{id: 2, name: 'testCourse2', asset_string: 'thing2'}, {id: 2, name: 'testCourse2', asset_string: 'thing2'},
] ]
describe('FindAppointmentApp', () => { describe('FindAppointmentApp', () => {
test('renders the FindAppoint component', () => { test('renders the FindAppoint component', () => {
const store = { const store = {
getState() { getState() {

View File

@ -220,7 +220,8 @@ class ContextSelector extends React.Component {
} }
renderSections(context) { renderSections(context) {
const filteredSections = context.sections?.filter(section => section.can_create_appointment_groups) ?? [] const filteredSections =
context.sections?.filter(section => section.can_create_appointment_groups) ?? []
return ( return (
<div <div
id={`${context.asset_string}_sections`} id={`${context.asset_string}_sections`}
@ -265,10 +266,11 @@ class ContextSelector extends React.Component {
} }
renderListItems() { renderListItems() {
const filteredContexts = this.props.contexts.filter(context => context const filteredContexts = this.props.contexts.filter(
.can_create_appointment_groups || context =>
context.sections?.some(section => section context.can_create_appointment_groups ||
.can_create_appointment_groups)) context.sections?.some(section => section.can_create_appointment_groups)
)
return ( return (
<div> <div>
{filteredContexts.map(context => { {filteredContexts.map(context => {

View File

@ -31,13 +31,13 @@ const COURSE_1 = {
id: '1', id: '1',
asset_string: 'course_section_1', asset_string: 'course_section_1',
name: 'testsection', name: 'testsection',
can_create_appointment_groups: true can_create_appointment_groups: true,
}, },
{ {
id: '3', id: '3',
asset_string: 'course_section_3', asset_string: 'course_section_3',
name: 'testsection3', name: 'testsection3',
can_create_appointment_groups: true can_create_appointment_groups: true,
}, },
], ],
} }
@ -52,7 +52,7 @@ const COURSE_2 = {
id: '2', id: '2',
asset_string: 'course_section_2', asset_string: 'course_section_2',
name: 'testsection2', name: 'testsection2',
can_create_appointment_groups: true can_create_appointment_groups: true,
}, },
], ],
} }

View File

@ -32,8 +32,8 @@ let props = {
date: '2016-10-26', date: '2016-10-26',
startTime: '10:00', startTime: '10:00',
endTime: '15:00', endTime: '15:00',
} },
} },
], ],
onChange() {}, onChange() {},
} }
@ -109,7 +109,9 @@ describe('TimeBlockSelector', () => {
newRow.timeData.startTime = new Date('Oct 26 2016 11:00') newRow.timeData.startTime = new Date('Oct 26 2016 11:00')
newRow.timeData.endTime = new Date('Oct 26 2016 16:00') newRow.timeData.endTime = new Date('Oct 26 2016 16:00')
ref.current.handleSetData(ref.current.state.timeBlockRows[1].slotEventId, newRow) ref.current.handleSetData(ref.current.state.timeBlockRows[1].slotEventId, newRow)
expect(ref.current.state.timeBlockRows[0].timeData.endTime).toEqual(new Date('Oct 26 2016 16:00')) expect(ref.current.state.timeBlockRows[0].timeData.endTime).toEqual(
new Date('Oct 26 2016 16:00')
)
}) })
test('calls onChange when there are modifications made', async () => { test('calls onChange when there are modifications made', async () => {

View File

@ -24,7 +24,7 @@ import {
postMessageExternalContentReady, postMessageExternalContentReady,
postMessageExternalContentCancel, postMessageExternalContentCancel,
} from '@canvas/external-tools/messages' } from '@canvas/external-tools/messages'
import { captureException } from '@sentry/react' import {captureException} from '@sentry/react'
const I18n = useI18nScope('content_migrations') const I18n = useI18nScope('content_migrations')

View File

@ -31,7 +31,10 @@ class IndexView extends PaginatedCollectionView {
// needed to render the react component at the top of the page // needed to render the react component at the top of the page
// in the right lifecycle method of backbone // in the right lifecycle method of backbone
afterRender() { afterRender() {
return ReactDOM.render(<ProgressionModuleHeader bridge={this.collection} />, document.getElementById('progression-module-header-root')) return ReactDOM.render(
<ProgressionModuleHeader bridge={this.collection} />,
document.getElementById('progression-module-header-root')
)
} }
} }
@ -58,7 +61,7 @@ ready(() => {
modules_url: ENV.MODULES_URL, modules_url: ENV.MODULES_URL,
autoFetch: true, autoFetch: true,
}) })
if (!ENV.RESTRICTED_LIST) { if (!ENV.RESTRICTED_LIST) {
// attach the view's scroll container once it's populated // attach the view's scroll container once it's populated
students.fetch({ students.fetch({
@ -73,7 +76,7 @@ ready(() => {
// we need to have the backbone view in the dom before we can render the react component // we need to have the backbone view in the dom before we can render the react component
indexView.$el.appendTo($('#content')) indexView.$el.appendTo($('#content'))
indexView.render() indexView.render()
if (ENV.RESTRICTED_LIST && ENV.VISIBLE_STUDENTS.length === 1) { if (ENV.RESTRICTED_LIST && ENV.VISIBLE_STUDENTS.length === 1) {

View File

@ -142,15 +142,13 @@ ready(() => {
) )
} }
const defaultDueTimeContainer = document.getElementById( const defaultDueTimeContainer = document.getElementById('default_due_time_container')
'default_due_time_container'
)
if (defaultDueTimeContainer) { if (defaultDueTimeContainer) {
ReactDOM.render( ReactDOM.render(
<Suspense fallback={<Loading />}> <Suspense fallback={<Loading />}>
<CourseDefaultDueTime/> <CourseDefaultDueTime />
</Suspense>, </Suspense>,
defaultDueTimeContainer defaultDueTimeContainer
) )
} }

View File

@ -87,7 +87,7 @@ export default class DashboardOptionsMenu extends React.Component {
<Menu <Menu
trigger={ trigger={
this.props.responsiveSize == 'small' ? ( this.props.responsiveSize == 'small' ? (
<Button <Button
elementRef={this.props.menuButtonRef} elementRef={this.props.menuButtonRef}
screenReaderLabel={I18n.t('Dashboard Options')} screenReaderLabel={I18n.t('Dashboard Options')}
display="block" display="block"

View File

@ -169,11 +169,19 @@ describe('Dashboard Options Menu', () => {
) )
dashboardMenu.handleColorOverlayOptionSelect(false) dashboardMenu.handleColorOverlayOptionSelect(false)
expect(document.getElementsByClassName('ic-DashboardCard__header_hero')[0].style.opacity).toEqual('0') expect(
expect(document.getElementsByClassName('ic-DashboardCard__header-button-bg')[0].style.opacity).toEqual('1') document.getElementsByClassName('ic-DashboardCard__header_hero')[0].style.opacity
).toEqual('0')
expect(
document.getElementsByClassName('ic-DashboardCard__header-button-bg')[0].style.opacity
).toEqual('1')
dashboardMenu.handleColorOverlayOptionSelect(true) dashboardMenu.handleColorOverlayOptionSelect(true)
expect(document.getElementsByClassName('ic-DashboardCard__header_hero')[0].style.opacity).toEqual('0.6') expect(
expect(document.getElementsByClassName('ic-DashboardCard__header-button-bg')[0].style.opacity).toEqual('0') document.getElementsByClassName('ic-DashboardCard__header_hero')[0].style.opacity
).toEqual('0.6')
expect(
document.getElementsByClassName('ic-DashboardCard__header-button-bg')[0].style.opacity
).toEqual('0')
}) })
}) })

View File

@ -74,7 +74,9 @@ describe('DevelopersKeyApp', () => {
visible: true, visible: true,
}, },
], ],
list: list || [{id: '1', api_key: 'abc12345678', created_at: '2012-06-07T20:36:50Z', visible: true,}], list: list || [
{id: '1', api_key: 'abc12345678', created_at: '2012-06-07T20:36:50Z', visible: true},
],
nextPage: 'http://...', nextPage: 'http://...',
inheritedNextPage: 'http://...', inheritedNextPage: 'http://...',
}, },
@ -315,7 +317,9 @@ describe('DevelopersKeyApp', () => {
const component = renderComponent() const component = renderComponent()
const componentNode = ReactDOM.findDOMNode(component) const componentNode = ReactDOM.findDOMNode(component)
expect(componentNode.querySelector('div[role="tab"][aria-selected="true"]').textContent).toEqual('Account') expect(
componentNode.querySelector('div[role="tab"][aria-selected="true"]').textContent
).toEqual('Account')
}) })
test('renders the inherited keys tab', () => { test('renders the inherited keys tab', () => {

View File

@ -160,7 +160,7 @@ describe('NewKeyForm', () => {
test('populates the key notes when lti key', () => { test('populates the key notes when lti key', () => {
const textarea = formFieldOfTypeAndName(developerKey, 'textarea', 'notes') const textarea = formFieldOfTypeAndName(developerKey, 'textarea', 'notes')
expect(textarea.value).toEqual(developerKey.notes); expect(textarea.value).toEqual(developerKey.notes)
}) })
test('renders the tool configuration form if isLtiKey is true', () => { test('renders the tool configuration form if isLtiKey is true', () => {
@ -240,9 +240,13 @@ describe('NewKeyForm', () => {
/> />
) )
const match1 = wrapper.container.innerHTML.match(new RegExp(/<span class=.*>Redirect URIs:<\/span>/)) const match1 = wrapper.container.innerHTML.match(
new RegExp(/<span class=.*>Redirect URIs:<\/span>/)
)
expect(match1).toBeFalsy() expect(match1).toBeFalsy()
const match2 = wrapper.container.innerHTML.match(new RegExp(/<span class=.*>* Redirect URIs:<\/span>/)) const match2 = wrapper.container.innerHTML.match(
new RegExp(/<span class=.*>* Redirect URIs:<\/span>/)
)
expect(match2).toBeTruthy() expect(match2).toBeTruthy()
}) })
}) })

View File

@ -140,11 +140,16 @@ export const ItemAssignToTrayWrapper = () => {
inputObj.student_ids.forEach(id => { inputObj.student_ids.forEach(id => {
outputObj.assignedList.push('user_' + id) outputObj.assignedList.push('user_' + id)
}) })
} else if(inputObj.course_id) { } else if (inputObj.course_id) {
outputObj.assignedList.push('course_' + inputObj.course_id) outputObj.assignedList.push('course_' + inputObj.course_id)
} }
if (!inputObj.course_section_id && !inputObj.course_id && !inputObj.student_ids && !inputObj.noop_id) { if (
!inputObj.course_section_id &&
!inputObj.course_id &&
!inputObj.student_ids &&
!inputObj.noop_id
) {
outputObj.assignedList.push('everyone') outputObj.assignedList.push('everyone')
} }

View File

@ -175,7 +175,7 @@ describe('AssignmentDueDatesManager', () => {
window.ENV.K5_SUBJECT_COURSE = false window.ENV.K5_SUBJECT_COURSE = false
setup({}) setup({})
const importantDates = screen.queryByTestId('important-dates-checkbox'); const importantDates = screen.queryByTestId('important-dates-checkbox')
expect(importantDates).not.toBeInTheDocument() expect(importantDates).not.toBeInTheDocument()
}) })

View File

@ -19,7 +19,7 @@
import {render} from '@testing-library/react' import {render} from '@testing-library/react'
import React from 'react' import React from 'react'
import { CheckpointsSettings } from '../CheckpointsSettings' import {CheckpointsSettings} from '../CheckpointsSettings'
import {GradedDiscussionDueDatesContext} from '../../../util/constants' import {GradedDiscussionDueDatesContext} from '../../../util/constants'
@ -75,7 +75,7 @@ describe('CheckpointsSettings', () => {
describe('Additional Replies Required', () => { describe('Additional Replies Required', () => {
it('displays the correct additional replies required passed from the useContext', () => { it('displays the correct additional replies required passed from the useContext', () => {
const {getByTestId} = setup({ const {getByTestId} = setup({
replyToEntryRequiredCount: 5 replyToEntryRequiredCount: 5,
}) })
expect(getByTestId('reply-to-entry-required-count')).toHaveValue('5') expect(getByTestId('reply-to-entry-required-count')).toHaveValue('5')
}) })

View File

@ -25,7 +25,7 @@ const defaultProps = {
pointsPossible: 10, pointsPossible: 10,
setPointsPossible: () => {}, setPointsPossible: () => {},
pointsPossibleLabel: 'Points Possible', pointsPossibleLabel: 'Points Possible',
pointsPossibleDataTestId: 'points-possible-input' pointsPossibleDataTestId: 'points-possible-input',
} }
const renderPointsPossible = () => { const renderPointsPossible = () => {
@ -40,7 +40,12 @@ describe('PointsPossible', () => {
it('does not allow negative values on decrement', () => { it('does not allow negative values on decrement', () => {
const mockSetPointsPossible = jest.fn() const mockSetPointsPossible = jest.fn()
const {getByTestId} = render( const {getByTestId} = render(
<PointsPossible {...defaultProps} pointsPossible={0} setPointsPossible={mockSetPointsPossible}/>) <PointsPossible
{...defaultProps}
pointsPossible={0}
setPointsPossible={mockSetPointsPossible}
/>
)
// Assuming your decrement button has a test id of 'decrement-button', adjust if necessary // Assuming your decrement button has a test id of 'decrement-button', adjust if necessary
const input = getByTestId('points-possible-input') const input = getByTestId('points-possible-input')

View File

@ -455,7 +455,7 @@ export default function DiscussionTopicForm({
setTitleValidationMessages, setTitleValidationMessages,
setAvailabilityValidationMessages, setAvailabilityValidationMessages,
shouldShowPostToSectionOption, shouldShowPostToSectionOption,
sectionIdsToPostTo, sectionIdsToPostTo
) )
) { ) {
const payload = createSubmitPayload(shouldPublish) const payload = createSubmitPayload(shouldPublish)
@ -758,7 +758,7 @@ export default function DiscussionTopicForm({
</View> </View>
<Tooltip renderTip={checkpointsToolTipText} on={['hover', 'focus']} color="primary"> <Tooltip renderTip={checkpointsToolTipText} on={['hover', 'focus']} color="primary">
<div <div
style={{display: "inline-block", marginLeft: theme.spacing.xxSmall}} style={{display: 'inline-block', marginLeft: theme.spacing.xxSmall}}
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
tabIndex="0" tabIndex="0"
> >

View File

@ -18,7 +18,7 @@
import React from 'react' import React from 'react'
import {fireEvent, render} from '@testing-library/react' import {fireEvent, render} from '@testing-library/react'
import {MissingSectionsWarningModal} from "../MissingSectionsWarningModal"; import {MissingSectionsWarningModal} from '../MissingSectionsWarningModal'
const defaultProps = { const defaultProps = {
sections: [ sections: [

View File

@ -43,13 +43,13 @@ const GradedDiscussionDueDateDefaultValues = {
gradedDiscussionRefMap: new Map(), gradedDiscussionRefMap: new Map(),
setGradedDiscussionRefMap: () => {}, setGradedDiscussionRefMap: () => {},
pointsPossibleReplyToTopic: 0, pointsPossibleReplyToTopic: 0,
setPointsPossibleReplyToTopic: (points) => {}, setPointsPossibleReplyToTopic: points => {},
pointsPossibleReplyToEntry: 0, pointsPossibleReplyToEntry: 0,
setPointsPossibleReplyToEntry: (points) => {}, setPointsPossibleReplyToEntry: points => {},
replyToEntryRequiredCount: 1, replyToEntryRequiredCount: 1,
setReplyToEntryRequiredCount: (count) => {}, setReplyToEntryRequiredCount: count => {},
importantDates: false, importantDates: false,
setImportantDates: (newImportantDatesValue) => {}, setImportantDates: newImportantDatesValue => {},
} }
export const GradedDiscussionDueDatesContext = React.createContext( export const GradedDiscussionDueDatesContext = React.createContext(

View File

@ -126,7 +126,10 @@ export default class DiscussionsIndex extends Component {
renderSpinner(title) { renderSpinner(title) {
return ( return (
<div className="discussions-v2__spinnerWrapper" data-testid="discussions-index-spinner-container"> <div
className="discussions-v2__spinnerWrapper"
data-testid="discussions-index-spinner-container"
>
<Spinner size="large" renderTitle={title} /> <Spinner size="large" renderTitle={title} />
<Text size="small" as="p"> <Text size="small" as="p">
{title} {title}

View File

@ -37,9 +37,7 @@ describe('DiscussionBackgrounds', () => {
it('renders correct student view for the pinnedDiscussionBackground with manage_content true', () => { it('renders correct student view for the pinnedDiscussionBackground with manage_content true', () => {
render(pinnedDiscussionBackground(defaultProps)) render(pinnedDiscussionBackground(defaultProps))
expect(screen.getByText('You currently have no pinned discussions')).toBeInTheDocument() expect(screen.getByText('You currently have no pinned discussions')).toBeInTheDocument()
expect( expect(screen.getByText('To pin a discussion to the top', {exact: false})).toBeInTheDocument()
screen.getByText('To pin a discussion to the top', {exact: false})
).toBeInTheDocument()
}) })
it('renders correct student view for the pinnedDiscussionBackground with manage_content false', () => { it('renders correct student view for the pinnedDiscussionBackground with manage_content false', () => {

View File

@ -91,7 +91,7 @@ describe('DiscussionRow', () => {
props props
) )
const openManageMenu = async (title) => { const openManageMenu = async title => {
const menu = screen.getByText(`Manage options for ${title}`) const menu = screen.getByText(`Manage options for ${title}`)
expect(menu).toBeInTheDocument() expect(menu).toBeInTheDocument()
await user.click(menu) await user.click(menu)
@ -126,7 +126,7 @@ describe('DiscussionRow', () => {
const link = screen.getByTestId(`discussion-link-${discussion.id}`) const link = screen.getByTestId(`discussion-link-${discussion.id}`)
expect(link.textContent.includes(discussion.title)).toBe(true) expect(link.textContent.includes(discussion.title)).toBe(true)
expect(link.tagName.toLowerCase()).toBe('a') expect(link.tagName.toLowerCase()).toBe('a')
expect(link.getAttribute('href')).toBe('https://example.com'); expect(link.getAttribute('href')).toBe('https://example.com')
}) })
it('when feature flag is off, anonymous title is plain text ', () => { it('when feature flag is off, anonymous title is plain text ', () => {
@ -162,7 +162,7 @@ describe('DiscussionRow', () => {
successMessage: 'Lock discussion blerp succeeded', successMessage: 'Lock discussion blerp succeeded',
failMessage: 'Lock discussion blerp failed', failMessage: 'Lock discussion blerp failed',
}), }),
expect.anything(), expect.anything()
) )
}) })
@ -191,7 +191,7 @@ describe('DiscussionRow', () => {
successMessage: 'Unlock discussion blerp succeeded', successMessage: 'Unlock discussion blerp succeeded',
failMessage: 'Unlock discussion blerp failed', failMessage: 'Unlock discussion blerp failed',
}), }),
expect.anything(), expect.anything()
) )
}) })

View File

@ -135,7 +135,7 @@ describe('DiscussionIndex', () => {
}) })
it('does not render pinned discussions in studentView if there are no pinned discussions', () => { it('does not render pinned discussions in studentView if there are no pinned discussions', () => {
const overrideProps = {closedForCommentsDiscussions: [],} const overrideProps = {closedForCommentsDiscussions: []}
renderConnectedComponent(overrideProps) renderConnectedComponent(overrideProps)
expect(screen.getAllByTestId('discussion-connected-container').length).toBe(2) expect(screen.getAllByTestId('discussion-connected-container').length).toBe(2)
}) })

View File

@ -59,7 +59,7 @@ describe('IndexHeader', () => {
it('renders the search input', () => { it('renders the search input', () => {
const props = makeProps() const props = makeProps()
render(<IndexHeader {...props} />) render(<IndexHeader {...props} />)
expect(screen.getByPlaceholderText('Search by title or author...')).toBeInTheDocument(); expect(screen.getByPlaceholderText('Search by title or author...')).toBeInTheDocument()
}) })
it('renders the filter input', () => { it('renders the filter input', () => {

View File

@ -341,7 +341,8 @@ const DiscussionTopicManager = props => {
<Responsive <Responsive
match="media" match="media"
query={responsiveQuerySizes({mobile: true, desktop: true})} query={responsiveQuerySizes({mobile: true, desktop: true})}
props={{mobile: { props={{
mobile: {
viewPortWidth: '100vw', viewPortWidth: '100vw',
}, },
desktop: { desktop: {

View File

@ -16,35 +16,39 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { IconMoreSolid } from "@instructure/ui-icons" import {IconMoreSolid} from '@instructure/ui-icons'
import { Button } from "@instructure/ui-buttons" import {Button} from '@instructure/ui-buttons'
import {useScope as useI18nScope} from '@canvas/i18n' import {useScope as useI18nScope} from '@canvas/i18n'
import { useContext, useEffect, useState } from "react" import {useContext, useEffect, useState} from 'react'
import React from 'react' import React from 'react'
import { DiscussionManagerUtilityContext } from "../../utils/constants" import {DiscussionManagerUtilityContext} from '../../utils/constants'
import { Menu } from "@instructure/ui-menu" import {Menu} from '@instructure/ui-menu'
import { View } from "@instructure/ui-view" import {View} from '@instructure/ui-view'
const I18n = useI18nScope('discussions_posts') const I18n = useI18nScope('discussions_posts')
export const MoreMenuButton = () => { export const MoreMenuButton = () => {
const [showMenu, setShowMenu] = useState(false); const [showMenu, setShowMenu] = useState(false)
const { translationLanguages, setShowTranslationControl } = useContext(DiscussionManagerUtilityContext); const {translationLanguages, setShowTranslationControl} = useContext(
DiscussionManagerUtilityContext
)
const [translationOptionText, setTranslationOptionText] = useState(I18n.t('Translate Text')) const [translationOptionText, setTranslationOptionText] = useState(I18n.t('Translate Text'))
const [hideTranslateText, setHideTranslateText] = useState(false); const [hideTranslateText, setHideTranslateText] = useState(false)
const toggleTranslateText = () => { const toggleTranslateText = () => {
// Update local state // Update local state
setHideTranslateText(!hideTranslateText); setHideTranslateText(!hideTranslateText)
setTranslationOptionText(hideTranslateText ? I18n.t('Translate Text') : I18n.t('Hide Translate Text')) setTranslationOptionText(
hideTranslateText ? I18n.t('Translate Text') : I18n.t('Hide Translate Text')
)
// Update context // Update context
setShowTranslationControl(!hideTranslateText) setShowTranslationControl(!hideTranslateText)
setShowMenu(false) setShowMenu(false)
} }
let menuOptions = []; let menuOptions = []
if (translationLanguages.current.length > 0) { if (translationLanguages.current.length > 0) {
menuOptions.push({ text: translationOptionText, onClick: toggleTranslateText }) menuOptions.push({text: translationOptionText, onClick: toggleTranslateText})
} }
return ( return (
@ -57,9 +61,13 @@ export const MoreMenuButton = () => {
} }
withArrow={false} withArrow={false}
> >
{ menuOptions.map(({text, onClick}) => { {menuOptions.map(({text, onClick}) => {
return <Menu.Item key={text} onClick={onClick}>{text}</Menu.Item> return (
}) } <Menu.Item key={text} onClick={onClick}>
{text}
</Menu.Item>
)
})}
</Menu> </Menu>
) )
} }

View File

@ -20,7 +20,7 @@ import {AlertManagerContext} from '@canvas/alerts/react/AlertManager'
import {render, fireEvent} from '@testing-library/react' import {render, fireEvent} from '@testing-library/react'
import React from 'react' import React from 'react'
import {DiscussionPostToolbar} from '../DiscussionPostToolbar' import {DiscussionPostToolbar} from '../DiscussionPostToolbar'
import { DiscussionManagerUtilityContext } from '../../../utils/constants' import {DiscussionManagerUtilityContext} from '../../../utils/constants'
import {updateUserDiscussionsSplitscreenViewMock} from '../../../../graphql/Mocks' import {updateUserDiscussionsSplitscreenViewMock} from '../../../../graphql/Mocks'
import {ChildTopic} from '../../../../graphql/ChildTopic' import {ChildTopic} from '../../../../graphql/ChildTopic'
import {waitFor} from '@testing-library/dom' import {waitFor} from '@testing-library/dom'
@ -68,10 +68,8 @@ const setup = (props, mocks) => {
<AlertManagerContext.Provider <AlertManagerContext.Provider
value={{setOnFailure: onFailureStub, setOnSuccess: onSuccessStub}} value={{setOnFailure: onFailureStub, setOnSuccess: onSuccessStub}}
> >
<DiscussionManagerUtilityContext.Provider <DiscussionManagerUtilityContext.Provider value={{translationLanguages: {current: []}}}>
value={{translationLanguages: {current: []}}} <DiscussionPostToolbar {...props} />
>
<DiscussionPostToolbar {...props} />
</DiscussionManagerUtilityContext.Provider> </DiscussionManagerUtilityContext.Provider>
</AlertManagerContext.Provider> </AlertManagerContext.Provider>
</MockedProvider> </MockedProvider>
@ -252,21 +250,21 @@ describe('DiscussionPostToolbar', () => {
describe('Discussion Summary', () => { describe('Discussion Summary', () => {
it('should render the discussion summary button if user can summarize and summary is not enabled', () => { it('should render the discussion summary button if user can summarize and summary is not enabled', () => {
ENV.user_can_summarize = true ENV.user_can_summarize = true
const { queryByTestId } = setup({ isSummaryEnabled: false }) const {queryByTestId} = setup({isSummaryEnabled: false})
expect(queryByTestId('summarize-button')).toBeTruthy() expect(queryByTestId('summarize-button')).toBeTruthy()
}) })
it('should not render the discussion summary button if summary is enabled', () => { it('should not render the discussion summary button if summary is enabled', () => {
ENV.user_can_summarize = true ENV.user_can_summarize = true
const { queryByTestId } = setup({ isSummaryEnabled: true }) const {queryByTestId} = setup({isSummaryEnabled: true})
expect(queryByTestId('summarize-button')).toBeNull() expect(queryByTestId('summarize-button')).toBeNull()
}) })
it('should not render the discussion summary button if user can not summarize', () => { it('should not render the discussion summary button if user can not summarize', () => {
ENV.user_can_summarize = false ENV.user_can_summarize = false
const { queryByTestId } = setup({ isSummaryEnabled: false }) const {queryByTestId} = setup({isSummaryEnabled: false})
expect(queryByTestId('summarize-button')).toBeNull() expect(queryByTestId('summarize-button')).toBeNull()
}) })

View File

@ -16,26 +16,26 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { DiscussionEdit } from '../DiscussionEdit/DiscussionEdit' import {DiscussionEdit} from '../DiscussionEdit/DiscussionEdit'
import { useScope as useI18nScope } from '@canvas/i18n' import {useScope as useI18nScope} from '@canvas/i18n'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import React, { useContext, useEffect, useState } from 'react' import React, {useContext, useEffect, useState} from 'react'
import { getDisplayName, responsiveQuerySizes, getTranslation } from '../../utils' import {getDisplayName, responsiveQuerySizes, getTranslation} from '../../utils'
import { DiscussionManagerUtilityContext, SearchContext } from '../../utils/constants' import {DiscussionManagerUtilityContext, SearchContext} from '../../utils/constants'
import { SearchSpan } from '../SearchSpan/SearchSpan' import {SearchSpan} from '../SearchSpan/SearchSpan'
import { AccessibleContent } from '@instructure/ui-a11y-content' import {AccessibleContent} from '@instructure/ui-a11y-content'
import { Responsive } from '@instructure/ui-responsive' import {Responsive} from '@instructure/ui-responsive'
import { Text } from '@instructure/ui-text' import {Text} from '@instructure/ui-text'
import { Flex } from '@instructure/ui-flex' import {Flex} from '@instructure/ui-flex'
import { Spinner } from '@instructure/ui-spinner' import {Spinner} from '@instructure/ui-spinner'
import theme from '@instructure/canvas-theme' import theme from '@instructure/canvas-theme'
import { View } from '@instructure/ui-view' import {View} from '@instructure/ui-view'
const I18n = useI18nScope('discussion_posts') const I18n = useI18nScope('discussion_posts')
export function PostMessage({ ...props }) { export function PostMessage({...props}) {
const { searchTerm } = useContext(SearchContext) const {searchTerm} = useContext(SearchContext)
useEffect(() => { useEffect(() => {
if (ENV.SEQUENCE !== undefined && props.isTopic) { if (ENV.SEQUENCE !== undefined && props.isTopic) {
@ -53,7 +53,7 @@ export function PostMessage({ ...props }) {
heading = 'h' + depth.toString() heading = 'h' + depth.toString()
} }
const { translateTargetLanguage } = useContext(DiscussionManagerUtilityContext) const {translateTargetLanguage} = useContext(DiscussionManagerUtilityContext)
const [translatedTitle, setTranslatedTitle] = useState(props.title) const [translatedTitle, setTranslatedTitle] = useState(props.title)
const [translatedMessage, setTranslatedMessage] = useState(props.message) const [translatedMessage, setTranslatedMessage] = useState(props.message)
const [isTranslating, setIsTranslating] = useState(false) const [isTranslating, setIsTranslating] = useState(false)
@ -66,13 +66,18 @@ export function PostMessage({ ...props }) {
} }
getTranslation(translatedTitle, translateTargetLanguage, setTranslatedTitle, setIsTranslating) getTranslation(translatedTitle, translateTargetLanguage, setTranslatedTitle, setIsTranslating)
getTranslation(translatedMessage, translateTargetLanguage, setTranslatedMessage, setIsTranslating) getTranslation(
translatedMessage,
translateTargetLanguage,
setTranslatedMessage,
setIsTranslating
)
}, [translateTargetLanguage]) }, [translateTargetLanguage])
return ( return (
<Responsive <Responsive
match="media" match="media"
query={responsiveQuerySizes({ mobile: true, desktop: true })} query={responsiveQuerySizes({mobile: true, desktop: true})}
props={{ props={{
mobile: { mobile: {
titleMargin: '0', titleMargin: '0',
@ -101,9 +106,14 @@ export function PostMessage({ ...props }) {
padding={props.isTopic ? 'small 0 0 0' : '0'} padding={props.isTopic ? 'small 0 0 0' : '0'}
> >
<Text size={responsiveProps.titleTextSize} weight={responsiveProps.titleTextWeight}> <Text size={responsiveProps.titleTextSize} weight={responsiveProps.titleTextWeight}>
<AccessibleContent alt={I18n.t('Discussion Topic: %{title}', { title: translatedTitle })}> <AccessibleContent
{translateTargetLanguage ? alt={I18n.t('Discussion Topic: %{title}', {title: translatedTitle})}
<span lang={translateTargetLanguage}>{translatedTitle}</span> : translatedTitle} >
{translateTargetLanguage ? (
<span lang={translateTargetLanguage}>{translatedTitle}</span>
) : (
translatedTitle
)}
</AccessibleContent> </AccessibleContent>
</Text> </Text>
</View> </View>
@ -118,14 +128,16 @@ export function PostMessage({ ...props }) {
</Text> </Text>
</View> </View>
)} )}
{isTranslating && <Flex justifyItems="start"> {isTranslating && (
<Flex.Item> <Flex justifyItems="start">
<Spinner renderTitle={I18n.t('Translating')} size="x-small" /> <Flex.Item>
</Flex.Item> <Spinner renderTitle={I18n.t('Translating')} size="x-small" />
<Flex.Item margin="0 0 0 x-small"> </Flex.Item>
<Text>{I18n.t('Translating Text')}</Text> <Flex.Item margin="0 0 0 x-small">
</Flex.Item> <Text>{I18n.t('Translating Text')}</Text>
</Flex>} </Flex.Item>
</Flex>
)}
{props.isEditing ? ( {props.isEditing ? (
<View display="inline-block" margin="small none none none" width="100%"> <View display="inline-block" margin="small none none none" width="100%">
<DiscussionEdit <DiscussionEdit

View File

@ -103,5 +103,5 @@ SearchSpan.propTypes = {
/** /**
* Language code if the span has been translated * Language code if the span has been translated
*/ */
lang: PropTypes.string lang: PropTypes.string,
} }

View File

@ -1,15 +1,17 @@
import React, { useContext, useState } from 'react' import React, {useContext, useState} from 'react'
import { SimpleSelect } from '@instructure/ui-simple-select'; import {SimpleSelect} from '@instructure/ui-simple-select'
import { View } from '@instructure/ui-view'; import {View} from '@instructure/ui-view'
import { DiscussionManagerUtilityContext } from '../../utils/constants'; import {DiscussionManagerUtilityContext} from '../../utils/constants'
// TODO: Translate the language controls into the canvas target locale. // TODO: Translate the language controls into the canvas target locale.
export const TranslationControls = () => { export const TranslationControls = () => {
const heading = `Translate Discussion` const heading = `Translate Discussion`
const { translationLanguages, setTranslateTargetLanguage } = useContext(DiscussionManagerUtilityContext) const {translationLanguages, setTranslateTargetLanguage} = useContext(
DiscussionManagerUtilityContext
)
const [language, setLanguage] = useState(translationLanguages.current[0].name) const [language, setLanguage] = useState(translationLanguages.current[0].name)
const handleSelect = (e, { id, value }) => { const handleSelect = (e, {id, value}) => {
setLanguage(value) setLanguage(value)
// Also set global language in context // Also set global language in context
@ -18,19 +20,12 @@ export const TranslationControls = () => {
return ( return (
<View as="div" margin="x-small 0 0"> <View as="div" margin="x-small 0 0">
<SimpleSelect <SimpleSelect renderLabel={heading} value={language} onChange={handleSelect} width="360px">
renderLabel={heading} {translationLanguages.current.map(({id, name}) => {
value={language} return (
onChange={handleSelect} <SimpleSelect.Option key={id} id={id} value={name}>
width='360px' {name}
> </SimpleSelect.Option>
{translationLanguages.current.map(({ id, name }) => {
return (<SimpleSelect.Option
key={id}
id={id}
value={name}>
{name}
</SimpleSelect.Option>
) )
})} })}
</SimpleSelect> </SimpleSelect>

View File

@ -83,7 +83,9 @@ export const DiscussionThreadContainer = props => {
const {searchTerm, filter, allThreadsStatus, expandedThreads, setExpandedThreads} = const {searchTerm, filter, allThreadsStatus, expandedThreads, setExpandedThreads} =
useContext(SearchContext) useContext(SearchContext)
const {setOnFailure, setOnSuccess} = useContext(AlertManagerContext) const {setOnFailure, setOnSuccess} = useContext(AlertManagerContext)
const {replyFromId, setReplyFromId, usedThreadingToolbarChildRef} = useContext(DiscussionManagerUtilityContext) const {replyFromId, setReplyFromId, usedThreadingToolbarChildRef} = useContext(
DiscussionManagerUtilityContext
)
const [expandReplies, setExpandReplies] = useState( const [expandReplies, setExpandReplies] = useState(
defaultExpandedReplies(props.discussionEntry._id) defaultExpandedReplies(props.discussionEntry._id)
) )
@ -537,23 +539,22 @@ export const DiscussionThreadContainer = props => {
} }
: null : null
} }
onMarkThreadAsRead={readState => { onMarkThreadAsRead={readState => {
window['ENV'].discussions_deep_link = { window['ENV'].discussions_deep_link = {
root_entry_id: props.discussionEntry.rootEntryId, root_entry_id: props.discussionEntry.rootEntryId,
parent_id: props.discussionEntry.parentId, parent_id: props.discussionEntry.parentId,
entry_id: props.discussionEntry._id entry_id: props.discussionEntry._id,
}
updateDiscussionThreadReadState({
variables: {
discussionEntryId: props.discussionEntry.rootEntryId
? props.discussionEntry.rootEntryId
: props.discussionEntry.id,
read: readState,
},
})
props.setHighlightEntryId(props.discussionEntry._id)
} }
} updateDiscussionThreadReadState({
variables: {
discussionEntryId: props.discussionEntry.rootEntryId
? props.discussionEntry.rootEntryId
: props.discussionEntry.id,
read: readState,
},
})
props.setHighlightEntryId(props.discussionEntry._id)
}}
/> />
) : null ) : null
} }

View File

@ -27,7 +27,10 @@ import {fireEvent, render} from '@testing-library/react'
import {getSpeedGraderUrl} from '../../../utils' import {getSpeedGraderUrl} from '../../../utils'
import {MockedProvider} from '@apollo/react-testing' import {MockedProvider} from '@apollo/react-testing'
import React from 'react' import React from 'react'
import {updateDiscussionEntryParticipantMock, updateDiscussionThreadReadStateMock} from '../../../../graphql/Mocks' import {
updateDiscussionEntryParticipantMock,
updateDiscussionThreadReadStateMock,
} from '../../../../graphql/Mocks'
import {User} from '../../../../graphql/User' import {User} from '../../../../graphql/User'
import {waitFor} from '@testing-library/dom' import {waitFor} from '@testing-library/dom'
@ -186,18 +189,18 @@ describe('DiscussionThreadContainer', () => {
window.location = {assign: jest.fn()} window.location = {assign: jest.fn()}
const setHighlightEntryId = jest.fn() const setHighlightEntryId = jest.fn()
const {getByTestId, getAllByText} = setup( const {getByTestId, getAllByText} = setup(
defaultProps({propOverrides: {setHighlightEntryId: setHighlightEntryId}}), defaultProps({propOverrides: {setHighlightEntryId: setHighlightEntryId}}),
updateDiscussionThreadReadStateMock({ updateDiscussionThreadReadStateMock({
discussionEntryId: 'DiscussionEntry-default-mock', discussionEntryId: 'DiscussionEntry-default-mock',
read: false read: false,
}) })
) )
fireEvent.click(getByTestId('thread-actions-menu')) fireEvent.click(getByTestId('thread-actions-menu'))
expect(getAllByText('Mark Thread as Unread').length).toBe(1) expect(getAllByText('Mark Thread as Unread').length).toBe(1)
expect(getAllByText('Mark Thread as Read').length).toBe(1) expect(getAllByText('Mark Thread as Read').length).toBe(1)
fireEvent.click(getAllByText('Mark Thread as Unread')[0]) fireEvent.click(getAllByText('Mark Thread as Unread')[0])
expect(setHighlightEntryId.mock.calls.length).toBe(1) expect(setHighlightEntryId.mock.calls.length).toBe(1)
expect(setHighlightEntryId).toHaveBeenCalledWith('DiscussionEntry-default-mock') expect(setHighlightEntryId).toHaveBeenCalledWith('DiscussionEntry-default-mock')

View File

@ -419,7 +419,7 @@ export const DiscussionTopicContainer = ({createDiscussionEntry, ...props}) => {
color="secondary" color="secondary"
data-testid="discussion-topic-closed-for-comments" data-testid="discussion-topic-closed-for-comments"
> >
{I18n.t("This topic is closed for comments.")} {I18n.t('This topic is closed for comments.')}
</Text> </Text>
)} )}
{props.discussionTopic.permissions?.reply && !expandedReply && ( {props.discussionTopic.permissions?.reply && !expandedReply && (
@ -500,24 +500,24 @@ export const DiscussionTopicContainer = ({createDiscussionEntry, ...props}) => {
</View> </View>
</Flex.Item> </Flex.Item>
{props.isSummaryEnabled && ( {props.isSummaryEnabled && (
<Flex.Item> <Flex.Item>
<View <View
as="div" as="div"
borderWidth={responsiveProps?.border?.width} borderWidth={responsiveProps?.border?.width}
borderRadius={responsiveProps?.border?.radius} borderRadius={responsiveProps?.border?.radius}
borderStyle="solid" borderStyle="solid"
borderColor="primary" borderColor="primary"
padding="small" padding="small"
margin="0 0 small 0" margin="0 0 small 0"
> >
<Flex direction="column" padding={responsiveProps?.container?.padding}> <Flex direction="column" padding={responsiveProps?.container?.padding}>
<DiscussionSummary <DiscussionSummary
onDisableSummaryClick={() => props.setIsSummaryEnabled(false)} onDisableSummaryClick={() => props.setIsSummaryEnabled(false)}
showButtonText={!matches.includes('mobile')} showButtonText={!matches.includes('mobile')}
/> />
</Flex> </Flex>
</View> </View>
</Flex.Item> </Flex.Item>
)} )}
</Flex> </Flex>
</Highlight> </Highlight>

View File

@ -397,14 +397,14 @@ describe('DiscussionTopicContainer', () => {
const container = setup({ const container = setup({
discussionTopic: Discussion.mock({permissions: DiscussionPermissions.mock({reply: false})}), discussionTopic: Discussion.mock({permissions: DiscussionPermissions.mock({reply: false})}),
}) })
expect(await container.findByText('This is a Discussion Topic Message')).toBeInTheDocument() expect(await container.findByText('This is a Discussion Topic Message')).toBeInTheDocument()
expect(await container.findByTestId('discussion-topic-closed-for-comments')).toBeInTheDocument() expect(await container.findByTestId('discussion-topic-closed-for-comments')).toBeInTheDocument()
}) })
it('does not renders "discussion topic closed for comments" message if user has reply permission true', () => { it('does not renders "discussion topic closed for comments" message if user has reply permission true', () => {
const container = setup({discussionTopic: Discussion.mock()}) const container = setup({discussionTopic: Discussion.mock()})
expect(container.queryByTestId('discussion-topic-closed-for-comments')).toBeNull() expect(container.queryByTestId('discussion-topic-closed-for-comments')).toBeNull()
}) })
@ -818,7 +818,7 @@ describe('DiscussionTopicContainer', () => {
describe('Discussion Summary', () => { describe('Discussion Summary', () => {
it('renders a summary', () => { it('renders a summary', () => {
const { queryByTestId } = setup({ const {queryByTestId} = setup({
discussionTopic: Discussion.mock(), discussionTopic: Discussion.mock(),
isSummaryEnabled: true, isSummaryEnabled: true,
}) })
@ -826,11 +826,11 @@ describe('DiscussionTopicContainer', () => {
}) })
it('does not render a summary', () => { it('does not render a summary', () => {
const { queryAllByTestId } = setup({ const {queryAllByTestId} = setup({
discussionTopic: Discussion.mock(), discussionTopic: Discussion.mock(),
isSummaryEnabled: false, isSummaryEnabled: false,
}) })
expect( queryAllByTestId(/summary-.*/) ).toEqual([]) expect(queryAllByTestId(/summary-.*/)).toEqual([])
}) })
}) })
}) })

View File

@ -20,14 +20,18 @@ import {Discussion} from '../../../graphql/Discussion'
import {DiscussionPostToolbar} from '../../components/DiscussionPostToolbar/DiscussionPostToolbar' import {DiscussionPostToolbar} from '../../components/DiscussionPostToolbar/DiscussionPostToolbar'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import React, {useContext, useEffect, useState} from 'react' import React, {useContext, useEffect, useState} from 'react'
import {DiscussionManagerUtilityContext, SEARCH_TERM_DEBOUNCE_DELAY, SearchContext} from '../../utils/constants' import {
DiscussionManagerUtilityContext,
SEARCH_TERM_DEBOUNCE_DELAY,
SearchContext,
} from '../../utils/constants'
import {View} from '@instructure/ui-view' import {View} from '@instructure/ui-view'
import {ScreenReaderContent} from '@instructure/ui-a11y-content' import {ScreenReaderContent} from '@instructure/ui-a11y-content'
import { TranslationControls } from '../../components/TranslationControls/TranslationControls' import {TranslationControls} from '../../components/TranslationControls/TranslationControls'
export const DiscussionTopicToolbarContainer = props => { export const DiscussionTopicToolbarContainer = props => {
const {searchTerm, filter, sort, setSearchTerm, setFilter, setSort} = useContext(SearchContext) const {searchTerm, filter, sort, setSearchTerm, setFilter, setSort} = useContext(SearchContext)
const {showTranslationControl} = useContext(DiscussionManagerUtilityContext); const {showTranslationControl} = useContext(DiscussionManagerUtilityContext)
const [currentSearchValue, setCurrentSearchValue] = useState(searchTerm || '') const [currentSearchValue, setCurrentSearchValue] = useState(searchTerm || '')
useEffect(() => { useEffect(() => {

View File

@ -260,9 +260,10 @@ export default class EditCalendarEventView extends Backbone.View {
} }
renderHeaderComponent() { renderHeaderComponent() {
const title = this.model.id == null ? const title =
I18n.t('Create New Calendar Event') : this.model.id == null
I18n.t('Edit %{title}', {title: this.model.get('title')}) ? I18n.t('Create New Calendar Event')
: I18n.t('Edit %{title}', {title: this.model.get('title')})
ReactDOM.render( ReactDOM.render(
<EditCalendarEventHeader title={title} />, <EditCalendarEventHeader title={title} />,

View File

@ -161,9 +161,7 @@ ready(() => {
// module item navigation from PLAT-1687 // module item navigation from PLAT-1687
const sequenceFooterHeight = $('#sequence_footer').outerHeight(true) || 0 const sequenceFooterHeight = $('#sequence_footer').outerHeight(true) || 0
toolResizer.resize_tool_content_wrapper( toolResizer.resize_tool_content_wrapper(
$window.height() - $window.height() - canvas_chrome_height - sequenceFooterHeight
canvas_chrome_height -
sequenceFooterHeight
) )
} }
} }

View File

@ -209,17 +209,18 @@ const AssignmentTable = ({
) )
: null : null
})} })}
{!hideTotalRow && totalRow( {!hideTotalRow &&
queryData, totalRow(
calculateOnlyGradedAssignments, queryData,
getCurrentOrFinalGrade(
getGradingPeriodID() === '0',
calculateOnlyGradedAssignments, calculateOnlyGradedAssignments,
courseGrades?.current, getCurrentOrFinalGrade(
courseGrades?.final, getGradingPeriodID() === '0',
activeWhatIfScores calculateOnlyGradedAssignments,
) courseGrades?.current,
)} courseGrades?.final,
activeWhatIfScores
)
)}
</Table.Body> </Table.Body>
</Table> </Table>
) )

View File

@ -639,11 +639,13 @@ describe('util', () => {
}) })
it('should return "Submitted" status when submission has been submitted', () => { it('should return "Submitted" status when submission has been submitted', () => {
const submittedAt = (new Date()).toISOString() const submittedAt = new Date().toISOString()
const assignment = { const assignment = {
dueAt: getTime(false), dueAt: getTime(false),
submissionsConnection: { submissionsConnection: {
nodes: [Submission.mock({state: 'submitted', submittedAt, gradingStatus: 'needs_grading'})], nodes: [
Submission.mock({state: 'submitted', submittedAt, gradingStatus: 'needs_grading'}),
],
}, },
} }
expect(getDisplayStatus(assignment)).toEqual(DateHelper.formatDatetimeForDisplay(submittedAt)) expect(getDisplayStatus(assignment)).toEqual(DateHelper.formatDatetimeForDisplay(submittedAt))

View File

@ -237,7 +237,6 @@ describe('GradebookGrid CompleteIncompleteGradeInput', () => {
waitFor(() => expect(getGradeInfo().grade).toBe('complete')) waitFor(() => expect(getGradeInfo().grade).toBe('complete'))
}) })
test('sets score to points possible when "Complete" is clicked', async () => { test('sets score to points possible when "Complete" is clicked', async () => {
await openAndClick('Open Complete/Incomplete menu') await openAndClick('Open Complete/Incomplete menu')
waitFor(() => expect(getGradeInfo().score).toBe(10)) waitFor(() => expect(getGradeInfo().score).toBe(10))

View File

@ -781,7 +781,9 @@ describe('GradebookGrid AssignmentGradeInput', () => {
}) })
test('returns false when the input adds only whitespace', () => { test('returns false when the input adds only whitespace', () => {
fireEvent.change(wrapper.container.querySelector('input'), {target: {value: ' Excused '}}) fireEvent.change(wrapper.container.querySelector('input'), {
target: {value: ' Excused '},
})
expect(hasGradeChanged()).toBeFalsy() expect(hasGradeChanged()).toBeFalsy()
}) })
}) })

View File

@ -131,7 +131,9 @@ class SearchResultsComponent extends Component {
<Table.Head> <Table.Head>
<Table.Row> <Table.Row>
{colHeaders.map(header => ( {colHeaders.map(header => (
<Table.ColHeader key={`${header}-column`} id={`${header}-column`}>{header}</Table.ColHeader> <Table.ColHeader key={`${header}-column`} id={`${header}-column`}>
{header}
</Table.ColHeader>
))} ))}
</Table.Row> </Table.Row>
</Table.Head> </Table.Head>

View File

@ -60,7 +60,7 @@ describe('MessageBody', () => {
it('renders signature when inboxSettingsFeature prop is true', () => { it('renders signature when inboxSettingsFeature prop is true', () => {
const props = createProps({ const props = createProps({
inboxSettingsFeature: true, inboxSettingsFeature: true,
signature: 'My signature' signature: 'My signature',
}) })
render(<MessageBody {...props} />) render(<MessageBody {...props} />)
const textArea = document.querySelectorAll('textarea')[0].value const textArea = document.querySelectorAll('textarea')[0].value
@ -71,7 +71,7 @@ describe('MessageBody', () => {
it('does not render signature when inboxSettingsFeature prop is false', () => { it('does not render signature when inboxSettingsFeature prop is false', () => {
const props = createProps({ const props = createProps({
inboxSettingsFeature: false, inboxSettingsFeature: false,
signature: 'My signature' signature: 'My signature',
}) })
render(<MessageBody {...props} />) render(<MessageBody {...props} />)
const textArea = document.querySelectorAll('textarea')[0].value const textArea = document.querySelectorAll('textarea')[0].value

View File

@ -53,7 +53,7 @@ import {Tooltip} from '@instructure/ui-tooltip'
import InboxSettingsModalContainer, { import InboxSettingsModalContainer, {
SAVE_SETTINGS_OK, SAVE_SETTINGS_OK,
SAVE_SETTINGS_FAIL, SAVE_SETTINGS_FAIL,
LOAD_SETTINGS_FAIL LOAD_SETTINGS_FAIL,
} from './InboxSettingsModalContainer/InboxSettingsModalContainer' } from './InboxSettingsModalContainer/InboxSettingsModalContainer'
const I18n = useI18nScope('conversations_2') const I18n = useI18nScope('conversations_2')
@ -732,7 +732,8 @@ const CanvasInbox = () => {
margin="none" margin="none"
renderIcon={IconComposeLine} renderIcon={IconComposeLine}
onClick={() => { onClick={() => {
if (/#filter=type=submission_comments/.test(window.location.hash)) window.location.hash = '#filter=type=inbox' if (/#filter=type=submission_comments/.test(window.location.hash))
window.location.hash = '#filter=type=inbox'
setComposeModal(true) setComposeModal(true)
}} }}
testid="compose" testid="compose"
@ -877,9 +878,7 @@ const CanvasInbox = () => {
</Flex.Item> </Flex.Item>
</Flex> </Flex>
{inboxSettingsFeature && inboxSettingsModal && ( {inboxSettingsFeature && inboxSettingsModal && (
<InboxSettingsModalContainer <InboxSettingsModalContainer onDismissWithAlert={handleDismissWithAlert} />
onDismissWithAlert={handleDismissWithAlert}
/>
)} )}
<ComposeModalManager <ComposeModalManager
conversation={selectedConversations[0]} conversation={selectedConversations[0]}

View File

@ -64,9 +64,7 @@ const ComposeModalContainer = props => {
const [includeObserversMessages, setIncludeObserversMessages] = useState(null) const [includeObserversMessages, setIncludeObserversMessages] = useState(null)
const [activeSignature, setActiveSignature] = useState() const [activeSignature, setActiveSignature] = useState()
const { const {loading: inboxSettingsLoading} = useQuery(INBOX_SETTINGS_QUERY, {
loading: inboxSettingsLoading
} = useQuery(INBOX_SETTINGS_QUERY, {
onCompleted: data => { onCompleted: data => {
let signature let signature
if (data?.myInboxSettings?.useSignature) { if (data?.myInboxSettings?.useSignature) {
@ -78,7 +76,7 @@ const ComposeModalContainer = props => {
setOnFailure(I18n.t('There was an error while loading inbox settings')) setOnFailure(I18n.t('There was an error while loading inbox settings'))
dismiss() dismiss()
}, },
skip: !props.inboxSettingsFeature || !props.open skip: !props.inboxSettingsFeature || !props.open,
}) })
const [ const [
@ -443,10 +441,7 @@ const ComposeModalContainer = props => {
onExited={resetState} onExited={resetState}
data-testid={responsiveProps.dataTestId} data-testid={responsiveProps.dataTestId}
> >
<ModalHeader <ModalHeader onDismiss={dismiss} headerTitle={props?.submissionCommentsHeader} />
onDismiss={dismiss}
headerTitle={props?.submissionCommentsHeader}
/>
<ModalBody <ModalBody
attachments={[...attachments, ...attachmentsToUpload]} attachments={[...attachments, ...attachmentsToUpload]}
bodyMessages={bodyMessages} bodyMessages={bodyMessages}

View File

@ -19,7 +19,7 @@
import React from 'react' import React from 'react'
import InboxSettingsModalContainer, { import InboxSettingsModalContainer, {
SAVE_SETTINGS_OK, SAVE_SETTINGS_OK,
SAVE_SETTINGS_FAIL SAVE_SETTINGS_FAIL,
} from '../InboxSettingsModalContainer' } from '../InboxSettingsModalContainer'
import {fireEvent, render, waitFor} from '@testing-library/react' import {fireEvent, render, waitFor} from '@testing-library/react'
import {within} from '@testing-library/dom' import {within} from '@testing-library/dom'
@ -84,15 +84,11 @@ describe('InboxSettingsModalContainer', () => {
server.close() server.close()
}) })
const setup = ({ const setup = ({onDismissWithAlert = onDismissWithAlertMock} = {}) =>
onDismissWithAlert = onDismissWithAlertMock
} = {}) =>
render( render(
<ApolloProvider client={mswClient}> <ApolloProvider client={mswClient}>
<AlertManagerContext.Provider value={{setOnFailure: jest.fn(), setOnSuccess: jest.fn()}}> <AlertManagerContext.Provider value={{setOnFailure: jest.fn(), setOnSuccess: jest.fn()}}>
<InboxSettingsModalContainer <InboxSettingsModalContainer onDismissWithAlert={onDismissWithAlert} />
onDismissWithAlert={onDismissWithAlert}
/>
</AlertManagerContext.Provider> </AlertManagerContext.Provider>
</ApolloProvider> </ApolloProvider>
) )
@ -171,7 +167,7 @@ describe('InboxSettingsModalContainer', () => {
fireEvent.click(getByText('15').closest('button')) fireEvent.click(getByText('15').closest('button'))
fireEvent.click(getByLabelText(new RegExp('End Date'))) fireEvent.click(getByLabelText(new RegExp('End Date')))
fireEvent.click(getByText('16').closest('button')) fireEvent.click(getByText('16').closest('button'))
await waitFor(() => { await waitFor(() => {
fireEvent.click(getByText('Save')) fireEvent.click(getByText('Save'))
expect(getAllByText('Date cannot be in the past').length).toBe(2) expect(getAllByText('Date cannot be in the past').length).toBe(2)
}) })

View File

@ -233,11 +233,11 @@ describe('CanvasInbox App Container', () => {
...window.ENV, ...window.ENV,
CONVERSATIONS: { CONVERSATIONS: {
...window.ENV.CONVERSATIONS, ...window.ENV.CONVERSATIONS,
INBOX_SETTINGS_ENABLED: true INBOX_SETTINGS_ENABLED: true,
} },
} }
const {getByTestId} = setup() const {getByTestId} = setup()
expect(getByTestId("inbox-settings-in-header")).toBeInTheDocument() expect(getByTestId('inbox-settings-in-header')).toBeInTheDocument()
}) })
it('should redirect to inbox when submission_comments and click on Compose button', async () => { it('should redirect to inbox when submission_comments and click on Compose button', async () => {
@ -245,8 +245,8 @@ describe('CanvasInbox App Container', () => {
...window.ENV, ...window.ENV,
CONVERSATIONS: { CONVERSATIONS: {
...window.ENV.CONVERSATIONS, ...window.ENV.CONVERSATIONS,
INBOX_SETTINGS_ENABLED: true INBOX_SETTINGS_ENABLED: true,
} },
} }
const container = setup() const container = setup()
await waitForApolloLoading() await waitForApolloLoading()

View File

@ -84,7 +84,7 @@ describe('ComposeModalContainer', () => {
conversation, conversation,
selectedIds = ['1'], selectedIds = ['1'],
isSubmissionCommentsType = false, isSubmissionCommentsType = false,
inboxSettingsFeature = false inboxSettingsFeature = false,
} = {}) => } = {}) =>
render( render(
<ApolloProvider client={mswClient}> <ApolloProvider client={mswClient}>

View File

@ -507,7 +507,7 @@ OutcomeGradebookView.prototype.setOutcomeOrder = function () {
url: this._assignOrderUrl(course_id), url: this._assignOrderUrl(course_id),
type: 'POST', type: 'POST',
data: JSON.stringify(outcomes), data: JSON.stringify(outcomes),
contentType: 'application/json; charset=utf-8' contentType: 'application/json; charset=utf-8',
}) })
return Grid.View.redrawHeader(this.grid, Grid.averageFn) return Grid.View.redrawHeader(this.grid, Grid.averageFn)

View File

@ -62,7 +62,7 @@ const ExportCSVButton = ({courseId, gradebookFilters}) => {
filename={`course-${courseId}-gradebook-export.csv`} filename={`course-${courseId}-gradebook-export.csv`}
data-testid="csv-link" data-testid="csv-link"
> >
<span ref={csvElementRef}/> <span ref={csvElementRef} />
</CSVLink> </CSVLink>
</> </>
) )

View File

@ -23,9 +23,9 @@ import ExportCSVButton from '../ExportCSVButton'
describe('ExportCSVButton', () => { describe('ExportCSVButton', () => {
const defaultProps = (props = {}) => { const defaultProps = (props = {}) => {
return { return {
courseId: "1", courseId: '1',
gradebookFilters: [], gradebookFilters: [],
...props ...props,
} }
} }
@ -34,4 +34,4 @@ describe('ExportCSVButton', () => {
expect(getByTestId('export-button')).toBeInTheDocument() expect(getByTestId('export-button')).toBeInTheDocument()
expect(getByTestId('csv-link')).toBeInTheDocument() expect(getByTestId('csv-link')).toBeInTheDocument()
}) })
}) })

View File

@ -37,7 +37,6 @@ describe('CollaborationsToolLaunch screenreader functionality', () => {
ENV.LTI_LAUNCH_FRAME_ALLOWANCES = undefined ENV.LTI_LAUNCH_FRAME_ALLOWANCES = undefined
}) })
test('shows beginning info alert and adds styles to iframe', () => { test('shows beginning info alert and adds styles to iframe', () => {
const ref = React.createRef() const ref = React.createRef()
const wrapper = render(<CollaborationsToolLaunch ref={ref} />) const wrapper = render(<CollaborationsToolLaunch ref={ref} />)
@ -95,11 +94,15 @@ describe('CollaborationsToolLaunch screenreader functionality', () => {
ref.current.setState({toolLaunchUrl: 'http://localhost:3000/messages/blti'}) ref.current.setState({toolLaunchUrl: 'http://localhost:3000/messages/blti'})
expect(ref.current.state.beforeExternalContentAlertClass).toEqual('screenreader-only') expect(ref.current.state.beforeExternalContentAlertClass).toEqual('screenreader-only')
expect(ref.current.state.afterExternalContentAlertClass).toEqual('screenreader-only') expect(ref.current.state.afterExternalContentAlertClass).toEqual('screenreader-only')
expect(wrapper.container.querySelector('.tool_launch').getAttribute('allow')).toEqual(ENV.LTI_LAUNCH_FRAME_ALLOWANCES.join('; ')) expect(wrapper.container.querySelector('.tool_launch').getAttribute('allow')).toEqual(
ENV.LTI_LAUNCH_FRAME_ALLOWANCES.join('; ')
)
}) })
test("sets the 'data-lti-launch' attribute on the iframe", () => { test("sets the 'data-lti-launch' attribute on the iframe", () => {
const wrapper = render(<CollaborationsToolLaunch />) const wrapper = render(<CollaborationsToolLaunch />)
expect(wrapper.container.querySelector('.tool_launch').getAttribute('data-lti-launch')).toEqual('true') expect(wrapper.container.querySelector('.tool_launch').getAttribute('data-lti-launch')).toEqual(
'true'
)
}) })
}) })

View File

@ -21,7 +21,6 @@ import {render} from '@testing-library/react'
import GettingStartedCollaborations from '../GettingStartedCollaborations' import GettingStartedCollaborations from '../GettingStartedCollaborations'
describe('GettingStartedCollaborations', () => { describe('GettingStartedCollaborations', () => {
function setEnvironment(roles, context) { function setEnvironment(roles, context) {
ENV.context_asset_string = context ENV.context_asset_string = context
ENV.current_user_roles = roles ENV.current_user_roles = roles
@ -46,7 +45,9 @@ describe('GettingStartedCollaborations', () => {
'Collaborations are web-based tools to work collaboratively on tasks like taking notes or grouped papers. Get started by clicking on the "+ Collaboration" button.' 'Collaborations are web-based tools to work collaboratively on tasks like taking notes or grouped papers. Get started by clicking on the "+ Collaboration" button.'
const expectedLinkText = 'Learn more about collaborations' const expectedLinkText = 'Learn more about collaborations'
expect(expectedHeader).toEqual(wrapper.container.querySelector('.ic-Action-header__Heading').textContent) expect(expectedHeader).toEqual(
wrapper.container.querySelector('.ic-Action-header__Heading').textContent
)
expect(expectedContent).toEqual(wrapper.container.querySelector('p').textContent) expect(expectedContent).toEqual(wrapper.container.querySelector('p').textContent)
expect(expectedLinkText).toEqual(wrapper.container.querySelector('a').textContent) expect(expectedLinkText).toEqual(wrapper.container.querySelector('a').textContent)
}) })
@ -61,7 +62,9 @@ describe('GettingStartedCollaborations', () => {
'Collaborations are web-based tools to work collaboratively on tasks like taking notes or grouped papers. Get started by adding a collaboration app.' 'Collaborations are web-based tools to work collaboratively on tasks like taking notes or grouped papers. Get started by adding a collaboration app.'
const expectedLinkText = 'Learn more about collaborations' const expectedLinkText = 'Learn more about collaborations'
expect(expectedHeader).toEqual(wrapper.container.querySelector('.ic-Action-header__Heading').textContent) expect(expectedHeader).toEqual(
wrapper.container.querySelector('.ic-Action-header__Heading').textContent
)
expect(expectedContent).toEqual(wrapper.container.querySelector('p').textContent) expect(expectedContent).toEqual(wrapper.container.querySelector('p').textContent)
expect(expectedLinkText).toEqual(wrapper.container.querySelector('a').textContent) expect(expectedLinkText).toEqual(wrapper.container.querySelector('a').textContent)
}) })
@ -75,7 +78,9 @@ describe('GettingStartedCollaborations', () => {
const expectedContent = const expectedContent =
'You have no Collaboration apps configured. Talk to your teacher to get some set up.' 'You have no Collaboration apps configured. Talk to your teacher to get some set up.'
expect(expectedHeader).toEqual(wrapper.container.querySelector('.ic-Action-header__Heading').textContent) expect(expectedHeader).toEqual(
wrapper.container.querySelector('.ic-Action-header__Heading').textContent
)
expect(expectedContent).toEqual(wrapper.container.querySelector('p').textContent) expect(expectedContent).toEqual(wrapper.container.querySelector('p').textContent)
}) })
@ -89,7 +94,9 @@ describe('GettingStartedCollaborations', () => {
'Collaborations are web-based tools to work collaboratively on tasks like taking notes or grouped papers. Get started by clicking on the "+ Collaboration" button.' 'Collaborations are web-based tools to work collaboratively on tasks like taking notes or grouped papers. Get started by clicking on the "+ Collaboration" button.'
const expectedLinkText = 'Learn more about collaborations' const expectedLinkText = 'Learn more about collaborations'
expect(expectedHeader).toEqual(wrapper.container.querySelector('.ic-Action-header__Heading').textContent) expect(expectedHeader).toEqual(
wrapper.container.querySelector('.ic-Action-header__Heading').textContent
)
expect(expectedContent).toEqual(wrapper.container.querySelector('p').textContent) expect(expectedContent).toEqual(wrapper.container.querySelector('p').textContent)
expect(expectedLinkText).toEqual(wrapper.container.querySelector('a').textContent) expect(expectedLinkText).toEqual(wrapper.container.querySelector('a').textContent)
}) })
@ -106,7 +113,9 @@ describe('GettingStartedCollaborations', () => {
'Collaborations are web-based tools to work collaboratively on tasks like taking notes or grouped papers. Talk to your teacher to get started.' 'Collaborations are web-based tools to work collaboratively on tasks like taking notes or grouped papers. Talk to your teacher to get started.'
const expectedLinkText = 'Learn more about collaborations' const expectedLinkText = 'Learn more about collaborations'
expect(expectedHeader).toEqual(wrapper.container.querySelector('.ic-Action-header__Heading').textContent) expect(expectedHeader).toEqual(
wrapper.container.querySelector('.ic-Action-header__Heading').textContent
)
expect(expectedContent).toEqual(wrapper.container.querySelector('p').textContent) expect(expectedContent).toEqual(wrapper.container.querySelector('p').textContent)
expect(expectedLinkText).toEqual(wrapper.container.querySelector('a').textContent) expect(expectedLinkText).toEqual(wrapper.container.querySelector('a').textContent)
}) })

View File

@ -29,7 +29,6 @@ import {isValidDeepLinkingEvent} from '@canvas/deep-linking/DeepLinking'
import processSingleContentItem from '@canvas/deep-linking/processors/processSingleContentItem' import processSingleContentItem from '@canvas/deep-linking/processors/processSingleContentItem'
import {handleExternalContentMessages} from '@canvas/external-tools/messages' import {handleExternalContentMessages} from '@canvas/external-tools/messages'
const attachListeners = () => { const attachListeners = () => {
// LTI 1.3 deep linking handler // LTI 1.3 deep linking handler
window.addEventListener('message', async event => { window.addEventListener('message', async event => {
@ -54,9 +53,9 @@ const attachListeners = () => {
// called by LTI 1.1 content item handler // called by LTI 1.1 content item handler
handleExternalContentMessages({ handleExternalContentMessages({
ready: (data) => { ready: data => {
store.dispatch(actions.externalContentReady(data)) store.dispatch(actions.externalContentReady(data))
} },
}) })
} }

View File

@ -26,7 +26,7 @@ import {TextInput} from '@instructure/ui-text-input'
import {showFlashSuccess} from '@canvas/alerts/react/FlashAlert' import {showFlashSuccess} from '@canvas/alerts/react/FlashAlert'
import CanvasModal from '@canvas/instui-bindings/react/Modal' import CanvasModal from '@canvas/instui-bindings/react/Modal'
import doFetchApi from '@canvas/do-fetch-api-effect' import doFetchApi from '@canvas/do-fetch-api-effect'
import { captureException } from '@sentry/react' import {captureException} from '@sentry/react'
const I18n = useI18nScope('groups') const I18n = useI18nScope('groups')

View File

@ -31,7 +31,7 @@ import {TextArea} from '@instructure/ui-text-area'
import {showFlashSuccess} from '@canvas/alerts/react/FlashAlert' import {showFlashSuccess} from '@canvas/alerts/react/FlashAlert'
import CanvasModal from '@canvas/instui-bindings/react/Modal' import CanvasModal from '@canvas/instui-bindings/react/Modal'
import doFetchApi from '@canvas/do-fetch-api-effect' import doFetchApi from '@canvas/do-fetch-api-effect'
import { captureException } from '@sentry/react' import {captureException} from '@sentry/react'
const I18n = useI18nScope('groups') const I18n = useI18nScope('groups')

View File

@ -29,7 +29,10 @@ import {
outcomeGroupsMocks, outcomeGroupsMocks,
} from '@canvas/outcomes/mocks/Outcomes' } from '@canvas/outcomes/mocks/Outcomes'
import {createCache} from '@canvas/apollo' import {createCache} from '@canvas/apollo'
import {showOutcomesImporter, showOutcomesImporterIfInProgress} from '@canvas/outcomes/react/OutcomesImporter' import {
showOutcomesImporter,
showOutcomesImporterIfInProgress,
} from '@canvas/outcomes/react/OutcomesImporter'
import {courseMocks, groupDetailMocks, groupMocks} from '@canvas/outcomes/mocks/Management' import {courseMocks, groupDetailMocks, groupMocks} from '@canvas/outcomes/mocks/Management'
jest.mock('@canvas/outcomes/react/OutcomesImporter', () => ({ jest.mock('@canvas/outcomes/react/OutcomesImporter', () => ({
@ -123,7 +126,7 @@ describe('OutcomeManagement', () => {
const modal = await findByTestId('createOutcomeModal') const modal = await findByTestId('createOutcomeModal')
expect(within(modal).getByText('Course folder 0')).not.toBeNull() expect(within(modal).getByText('Course folder 0')).not.toBeNull()
expect(within(modal).getByText('Group 200 folder 0')).not.toBeNull() expect(within(modal).getByText('Group 200 folder 0')).not.toBeNull()
}, 7500) // Increase time to 7.5 seconds }, 7500) // Increase time to 7.5 seconds
const sharedExamples = () => { const sharedExamples = () => {
beforeEach(() => { beforeEach(() => {

View File

@ -76,4 +76,4 @@ export const unmount = function () {
ReactDOM.unmountComponentAtNode(container) ReactDOM.unmountComponentAtNode(container)
container = undefined container = undefined
} }
} }

View File

@ -72,4 +72,4 @@ export const unmount = function () {
ReactDOM.unmountComponentAtNode(container) ReactDOM.unmountComponentAtNode(container)
container = undefined container = undefined
} }
} }

View File

@ -93,7 +93,8 @@ export default class RosterUserView extends Backbone.View {
} }
json.canRemoveUsers = every(this.model.get('enrollments'), e => e.can_be_removed) json.canRemoveUsers = every(this.model.get('enrollments'), e => e.can_be_removed)
json.canResendInvitation = json.canResendInvitation =
!json.isInactive && (ENV.FEATURES.granular_permissions_manage_users !json.isInactive &&
(ENV.FEATURES.granular_permissions_manage_users
? some(this.model.get('enrollments'), en => ? some(this.model.get('enrollments'), en =>
ENV.permissions.active_granular_enrollment_permissions.includes(en.type) ENV.permissions.active_granular_enrollment_permissions.includes(en.type)
) )

View File

@ -26,10 +26,22 @@ const I18n = useI18nScope('SmartSearch')
export default function IndexingProgress({progress}) { export default function IndexingProgress({progress}) {
return ( return (
<div> <div>
<Text>{I18n.t('Please wait a moment while we get Smart Search ready for this course. This only needs to happen once.')}</Text><br/> <Text>
<Text fontStyle="italic">{I18n.t('You can leave this page and come back, and we will keep working in the background.')}</Text> {I18n.t(
<ProgressBar screenReaderLabel={I18n.t('Indexing in progress')} valueNow={progress} valueMax={100} /> 'Please wait a moment while we get Smart Search ready for this course. This only needs to happen once.'
)}
</Text>
<br />
<Text fontStyle="italic">
{I18n.t(
'You can leave this page and come back, and we will keep working in the background.'
)}
</Text>
<ProgressBar
screenReaderLabel={I18n.t('Indexing in progress')}
valueNow={progress}
valueMax={100}
/>
</div> </div>
) )
} }

Some files were not shown because too many files have changed in this diff Show More