restore column header focus after dialog close
fixes: CNVS-37602 Test Plan: 1. Navigate to GradeZilla 2. Open an assignment column header menu 3. Press keys while the menu is open - When you press TAB: Observe that the menu closes and the next item in the normal grid navigation flow is focused - When you press SHIFT+TAB: Observe that the menu closes and the previous item in the normal grid navigation flow is focused - When you press ESC: Observe that the menu closes and the menu trigger is focused - Other keys should behave as before a. up/down arrow keys focus menu items b. left/right arrow keys expand/collapse flyouts - Nested flyouts should also trigger navigation upon these key presses 4. Select a menu item - If a modal appears: a. Observe that the focus is placed within the modal b. Observe the header menu trigger is refocused when the modal is closed - If a modal doesn't appear: a. Observe the header menu trigger is refocused 5. Repeat 2-4 with all column header types - Assignment Group column headers - Student column headers - Total Grade column header Change-Id: I7cd50a5bc2c598b5bf899f7a5d17998fc4f4ec04 Reviewed-on: https://gerrit.instructure.com/115864 Reviewed-by: Jeremy Neander <jneander@instructure.com> Tested-by: Jenkins Reviewed-by: Sheldon Leibole <sheldon@siimpl.io> QA-Review: Anju Reddy <areddy@instructure.com> Product-Review: Keith T. Garner <kgarner@instructure.com>
This commit is contained in:
parent
070f643531
commit
a05e102e68
|
@ -16,12 +16,12 @@ export default class AssignmentMuter {
|
|||
this.options = options
|
||||
}
|
||||
|
||||
show () {
|
||||
show (onClose) {
|
||||
if (this.options && this.options.openDialogInstantly) {
|
||||
if (this.assignment.muted) {
|
||||
this.confirmUnmute()
|
||||
} else {
|
||||
this.showDialog()
|
||||
this.showDialog(onClose)
|
||||
}
|
||||
} else {
|
||||
this.$link = $(this.$link)
|
||||
|
@ -31,7 +31,7 @@ export default class AssignmentMuter {
|
|||
if (this.assignment.muted) {
|
||||
this.confirmUnmute()
|
||||
} else {
|
||||
this.showDialog()
|
||||
this.showDialog(onClose)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ export default class AssignmentMuter {
|
|||
this.$link.text(this.assignment.muted ? I18n.t('Unmute Assignment') : I18n.t('Mute Assignment'))
|
||||
}
|
||||
|
||||
showDialog () {
|
||||
showDialog (onClose) {
|
||||
this.$dialog = $(mute_dialog_template()).dialog({
|
||||
buttons: [{
|
||||
text: I18n.t('Cancel'),
|
||||
|
@ -61,6 +61,7 @@ export default class AssignmentMuter {
|
|||
resizable: false,
|
||||
width: 400
|
||||
})
|
||||
this.$dialog.on('dialogclose', onClose)
|
||||
}
|
||||
|
||||
afterUpdate (serverResponse) {
|
||||
|
@ -92,7 +93,7 @@ export default class AssignmentMuter {
|
|||
click: () => this.$dialog.disableWhileLoading($.ajaxJSON(this.url, 'put', {status: false}, this.afterUpdate))
|
||||
}],
|
||||
open: () => setTimeout(() => this.$dialog.parent().find('.ui-dialog-titlebar-close').focus(), 100),
|
||||
close: () => this.$dialog.remove(),
|
||||
close: () => this.$dialog.dialog('close'),
|
||||
resizable: false,
|
||||
title: I18n.t('unmute_assignment', 'Unmute Assignment'),
|
||||
width: 400,
|
||||
|
|
|
@ -1420,13 +1420,15 @@ define [
|
|||
@grid.invalidate()
|
||||
@renderTotalGradeColumnHeader()
|
||||
|
||||
togglePointsOrPercentTotals: =>
|
||||
togglePointsOrPercentTotals: (cb) =>
|
||||
if UserSettings.contextGet('warned_about_totals_display')
|
||||
@switchTotalDisplay()
|
||||
cb() if typeof cb == 'function'
|
||||
else
|
||||
dialog_options =
|
||||
showing_points: @options.show_total_grade_as_points
|
||||
save: @switchTotalDisplay
|
||||
onClose: cb
|
||||
new GradeDisplayWarningDialog(dialog_options)
|
||||
|
||||
onUserFilterInput: (term) =>
|
||||
|
@ -2095,6 +2097,13 @@ define [
|
|||
@renderStudentColumnHeader()
|
||||
@renderTotalGradeColumnHeader()
|
||||
|
||||
# Column Header Helpers
|
||||
handleHeaderKeyDown: (e, columnId) =>
|
||||
@gridSupport.navigation.handleHeaderKeyDown e,
|
||||
region: 'header'
|
||||
cell: @grid.getColumnIndex(columnId)
|
||||
columnId: columnId
|
||||
|
||||
# Student Column Header
|
||||
|
||||
getStudentColumnSortBySetting: =>
|
||||
|
@ -2129,6 +2138,8 @@ define [
|
|||
addGradebookElement: @keyboardNav.addGradebookElement
|
||||
removeGradebookElement: @keyboardNav.removeGradebookElement
|
||||
onMenuClose: @handleColumnHeaderMenuClose
|
||||
onHeaderKeyDown: (e) =>
|
||||
@handleHeaderKeyDown(e, 'student')
|
||||
|
||||
renderStudentColumnHeader: =>
|
||||
mountPoint = @getColumnHeaderNode('student')
|
||||
|
@ -2138,6 +2149,7 @@ define [
|
|||
# Total Grade Column Header
|
||||
|
||||
freezeTotalGradeColumn: =>
|
||||
@totalColumnPositionChanged = true
|
||||
allColumns = @grid.getColumns()
|
||||
|
||||
# Remove total_grade column from aggregate section
|
||||
|
@ -2159,6 +2171,7 @@ define [
|
|||
@updateFrozenColumnsAndRenderGrid(allColumns)
|
||||
|
||||
moveTotalGradeColumnToEnd: =>
|
||||
@totalColumnPositionChanged = true
|
||||
allColumns = @grid.getColumns()
|
||||
|
||||
# Remove total_grade column from aggregate or frozen section as needed
|
||||
|
@ -2222,6 +2235,13 @@ define [
|
|||
@moveTotalGradeColumnToEnd()
|
||||
, 10)
|
||||
|
||||
totalColumnShouldFocus: ->
|
||||
if @totalColumnPositionChanged
|
||||
@totalColumnPositionChanged = false
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
getTotalGradeColumnHeaderProps: ->
|
||||
ref: (ref) =>
|
||||
@setHeaderComponentRef('total_grade', ref)
|
||||
|
@ -2231,6 +2251,9 @@ define [
|
|||
addGradebookElement: @keyboardNav.addGradebookElement
|
||||
removeGradebookElement: @keyboardNav.removeGradebookElement
|
||||
onMenuClose: @handleColumnHeaderMenuClose
|
||||
grabFocus: @totalColumnShouldFocus()
|
||||
onHeaderKeyDown: (e) =>
|
||||
@handleHeaderKeyDown(e, 'total_grade')
|
||||
|
||||
renderTotalGradeColumnHeader: =>
|
||||
return if @hideAggregateColumns()
|
||||
|
@ -2364,6 +2387,8 @@ define [
|
|||
removeGradebookElement: @keyboardNav.removeGradebookElement
|
||||
onMenuClose: @handleColumnHeaderMenuClose
|
||||
showUnpostedMenuItem: @options.new_gradebook_development_enabled
|
||||
onHeaderKeyDown: (e) =>
|
||||
@handleHeaderKeyDown(e, @getAssignmentColumnId(assignmentId))
|
||||
}
|
||||
|
||||
renderAssignmentColumnHeader: (assignmentId) =>
|
||||
|
@ -2406,6 +2431,8 @@ define [
|
|||
addGradebookElement: @keyboardNav.addGradebookElement
|
||||
removeGradebookElement: @keyboardNav.removeGradebookElement
|
||||
onMenuClose: @handleColumnHeaderMenuClose
|
||||
onHeaderKeyDown: (e) =>
|
||||
@handleHeaderKeyDown(e, @getAssignmentGroupColumnId(assignmentGroupId))
|
||||
}
|
||||
|
||||
renderAssignmentGroupColumnHeader: (assignmentGroupId) =>
|
||||
|
|
|
@ -38,7 +38,7 @@ define [
|
|||
class SetDefaultGradeDialog
|
||||
constructor: ({@assignment, @students, @context_id, @selected_section}) ->
|
||||
|
||||
show: =>
|
||||
show: (onClose) =>
|
||||
templateLocals =
|
||||
assignment: @assignment
|
||||
showPointsPossible: (@assignment.points_possible || @assignment.points_possible == '0') && @assignment.grading_type != "gpa_scale"
|
||||
|
@ -52,6 +52,7 @@ define [
|
|||
open: => @$dialog.find(".grading_box").focus()
|
||||
close: => @$dialog.remove()
|
||||
).fixDialogButtons()
|
||||
@$dialog.on 'dialogclose', onClose
|
||||
|
||||
$form = @$dialog
|
||||
$(".ui-dialog-titlebar-close").focus()
|
||||
|
@ -85,7 +86,7 @@ define [
|
|||
count: submissions.length)
|
||||
submittingDfd.resolve()
|
||||
$("#set_default_grade").focus()
|
||||
@$dialog.remove()
|
||||
@$dialog.dialog('close')
|
||||
|
||||
getStudents = =>
|
||||
if @selected_section
|
||||
|
|
|
@ -32,7 +32,7 @@ define [
|
|||
class CurveGradesDialog
|
||||
constructor: ({@assignment, @students, @context_url}) ->
|
||||
|
||||
show: =>
|
||||
show: (onClose) =>
|
||||
locals =
|
||||
assignment: @assignment
|
||||
action: "#{@context_url}/gradebook/update_submission"
|
||||
|
@ -90,6 +90,7 @@ define [
|
|||
close: => @$dialog.remove()
|
||||
.fixDialogButtons()
|
||||
|
||||
@$dialog.on 'dialogclose', onClose
|
||||
@$dialog.parent().find('.ui-dialog-titlebar-close').focus()
|
||||
@$dialog.find("#middle_score").bind "blur change keyup focus", @curve
|
||||
@$dialog.find("#assign_blanks").change @curve
|
||||
|
|
|
@ -36,13 +36,16 @@ define [
|
|||
buttons: [{
|
||||
text: I18n.t("grade_display_warning.cancel", "Cancel"), click: @cancel},
|
||||
{text: I18n.t("grade_display_warning.continue", "Continue"), click: @save}]
|
||||
close: =>
|
||||
@$dialog.remove()
|
||||
options.onClose() if typeof options.onClose == 'function'
|
||||
|
||||
save: () =>
|
||||
if @$dialog.find('#hide_warning').prop('checked')
|
||||
@options.save({ dontWarnAgain: true })
|
||||
else
|
||||
@options.save({ dontWarnAgain: false })
|
||||
@$dialog.remove()
|
||||
@$dialog.dialog('close')
|
||||
|
||||
cancel: () =>
|
||||
@$dialog.remove()
|
||||
@$dialog.dialog('close')
|
||||
|
|
|
@ -27,13 +27,13 @@ import 'compiled/jquery.rails_flash_notifications'
|
|||
return {
|
||||
isDisabled: !submissionsLoaded || gradingType === 'pass_fail' || pointsPossible == null || pointsPossible === 0,
|
||||
|
||||
onSelect () { // eslint-disable-line consistent-return
|
||||
onSelect (onClose) { // eslint-disable-line consistent-return
|
||||
if (!isAdmin && assignment.inClosedGradingPeriod) {
|
||||
return $.flashError(I18n.t('Unable to curve grades because this assignment is due in a closed ' +
|
||||
'grading period for at least one student'));
|
||||
}
|
||||
const dialog = new CurveGradesDialog({assignment, students, context_url: contextUrl});
|
||||
dialog.show();
|
||||
dialog.show(onClose);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ import ColumnHeader from 'jsx/gradezilla/default_gradebook/components/ColumnHead
|
|||
|
||||
class AssignmentColumnHeader extends ColumnHeader {
|
||||
static propTypes = {
|
||||
...ColumnHeader.propTypes,
|
||||
assignment: shape({
|
||||
courseId: string.isRequired,
|
||||
htmlUrl: string.isRequired,
|
||||
|
@ -92,8 +93,7 @@ class AssignmentColumnHeader extends ColumnHeader {
|
|||
onSelect: func.isRequired
|
||||
}).isRequired,
|
||||
onMenuClose: func.isRequired,
|
||||
showUnpostedMenuItem: bool.isRequired,
|
||||
...ColumnHeader.propTypes
|
||||
showUnpostedMenuItem: bool.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -117,6 +117,18 @@ class AssignmentColumnHeader extends ColumnHeader {
|
|||
}
|
||||
|
||||
bindAssignmentLink = (ref) => { this.assignmentLink = ref };
|
||||
|
||||
curveGrades = () => { this.invokeAndSkipFocus(this.props.curveGradesAction) };
|
||||
setDefaultGrades = () => { this.invokeAndSkipFocus(this.props.setDefaultGradeAction) };
|
||||
muteAssignment = () => { this.invokeAndSkipFocus(this.props.muteAssignmentAction) };
|
||||
downloadSubmissions = () => { this.invokeAndSkipFocus(this.props.downloadSubmissionsAction) };
|
||||
reuploadSubmissions = () => { this.invokeAndSkipFocus(this.props.reuploadSubmissionsAction) };
|
||||
|
||||
invokeAndSkipFocus (action) {
|
||||
this.setState({ skipFocusOnClose: true });
|
||||
action.onSelect(this.focusAtEnd);
|
||||
}
|
||||
|
||||
focusAtStart = () => { this.assignmentLink.focus() };
|
||||
|
||||
handleKeyDown = (event) => {
|
||||
|
@ -138,7 +150,9 @@ class AssignmentColumnHeader extends ColumnHeader {
|
|||
};
|
||||
|
||||
showMessageStudentsWhoDialog = () => {
|
||||
this.setState({ skipFocusOnClose: true });
|
||||
const settings = MessageStudentsWhoHelper.settings(this.props.assignment, this.activeStudentDetails());
|
||||
settings.onClose = this.focusAtEnd;
|
||||
window.messageStudents(settings);
|
||||
}
|
||||
|
||||
|
@ -266,7 +280,6 @@ class AssignmentColumnHeader extends ColumnHeader {
|
|||
</MenuItemGroup>
|
||||
</MenuItemFlyout>
|
||||
|
||||
|
||||
<MenuItem
|
||||
disabled={!this.props.submissionsLoaded}
|
||||
onSelect={this.showMessageStudentsWhoDialog}
|
||||
|
@ -276,21 +289,21 @@ class AssignmentColumnHeader extends ColumnHeader {
|
|||
|
||||
<MenuItem
|
||||
disabled={this.props.curveGradesAction.isDisabled}
|
||||
onSelect={this.props.curveGradesAction.onSelect}
|
||||
onSelect={this.curveGrades}
|
||||
>
|
||||
<span data-menu-item-id="curve-grades">{I18n.t('Curve Grades')}</span>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem
|
||||
disabled={this.props.setDefaultGradeAction.disabled}
|
||||
onSelect={this.props.setDefaultGradeAction.onSelect}
|
||||
onSelect={this.setDefaultGrades}
|
||||
>
|
||||
<span data-menu-item-id="set-default-grade">{I18n.t('Set Default Grade')}</span>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem
|
||||
disabled={this.props.muteAssignmentAction.disabled}
|
||||
onSelect={this.props.muteAssignmentAction.onSelect}
|
||||
onSelect={this.muteAssignment}
|
||||
>
|
||||
<span data-menu-item-id="assignment-muter">
|
||||
{this.props.assignment.muted ? I18n.t('Unmute Assignment') : I18n.t('Mute Assignment')}
|
||||
|
@ -306,14 +319,14 @@ class AssignmentColumnHeader extends ColumnHeader {
|
|||
|
||||
{
|
||||
!this.props.downloadSubmissionsAction.hidden &&
|
||||
<MenuItem onSelect={this.props.downloadSubmissionsAction.onSelect}>
|
||||
<MenuItem onSelect={this.downloadSubmissions}>
|
||||
<span data-menu-item-id="download-submissions">{I18n.t('Download Submissions')}</span>
|
||||
</MenuItem>
|
||||
}
|
||||
|
||||
{
|
||||
!this.props.reuploadSubmissionsAction.hidden &&
|
||||
<MenuItem onSelect={this.props.reuploadSubmissionsAction.onSelect}>
|
||||
<MenuItem onSelect={this.reuploadSubmissions}>
|
||||
<span data-menu-item-id="reupload-submissions">{I18n.t('Re-Upload Submissions')}</span>
|
||||
</MenuItem>
|
||||
}
|
||||
|
|
|
@ -23,12 +23,14 @@ import { func } from 'prop-types';
|
|||
export default class ColumnHeader extends React.Component {
|
||||
static propTypes = {
|
||||
addGradebookElement: func,
|
||||
removeGradebookElement: func
|
||||
removeGradebookElement: func,
|
||||
onHeaderKeyDown: func
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
addGradebookElement () {},
|
||||
removeGradebookElement () {}
|
||||
removeGradebookElement () {},
|
||||
onHeaderKeyDown () {}
|
||||
};
|
||||
|
||||
constructor (props) {
|
||||
|
@ -37,33 +39,30 @@ export default class ColumnHeader extends React.Component {
|
|||
this.handleKeyDown = this.handleKeyDown.bind(this);
|
||||
}
|
||||
|
||||
state = { menuShown: false };
|
||||
state = { menuShown: false, skipFocusOnClose: false };
|
||||
|
||||
bindOptionsMenuTrigger = (ref) => { this.optionsMenuTrigger = ref };
|
||||
|
||||
bindSortByMenuContent = (ref) => {
|
||||
bindFlyoutMenu = (ref, savedRef) => {
|
||||
// instructure-ui components return references to react components.
|
||||
// At this time the only way to get dom node refs is via findDOMNode.
|
||||
if (ref) {
|
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
this.props.addGradebookElement(ReactDOM.findDOMNode(ref));
|
||||
} else {
|
||||
const domNode = ReactDOM.findDOMNode(ref);
|
||||
this.props.addGradebookElement(domNode);
|
||||
domNode.addEventListener('keydown', this.handleMenuKeyDown);
|
||||
} else if (savedRef) {
|
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
this.props.removeGradebookElement(ReactDOM.findDOMNode(this.sortByMenuContent));
|
||||
this.props.removeGradebookElement(ReactDOM.findDOMNode(savedRef));
|
||||
}
|
||||
}
|
||||
|
||||
bindSortByMenuContent = (ref) => {
|
||||
this.bindFlyoutMenu(ref, this.sortByMenuContent);
|
||||
this.sortByMenuContent = ref;
|
||||
};
|
||||
|
||||
bindOptionsMenuContent = (ref) => {
|
||||
// Dealing with add/removeGradebookElement in a convoluted combination of
|
||||
// this method and onToggle rather than the simpler way of calling those
|
||||
// methods directly (like in bindSortByMenuContent) because this method is
|
||||
// called by PopoverMenu three times when opening the menu. First with a ref
|
||||
// to the content, then with null, then again with a ref to the content.
|
||||
// We MUST get the DOM node here, rather than in onToggle because by the
|
||||
// time onToggle is called when closing the menu, the component has already
|
||||
// been unmounted and an error will be thrown if you attempt access.
|
||||
// instructure-ui components return references to react components.
|
||||
// At this time the only way to get dom node refs is via findDOMNode.
|
||||
if (ref) {
|
||||
|
@ -71,6 +70,7 @@ export default class ColumnHeader extends React.Component {
|
|||
// eslint-disable-next-line react/no-find-dom-node
|
||||
this.optionsMenuContentDOMNode = ReactDOM.findDOMNode(ref);
|
||||
}
|
||||
this.bindFlyoutMenu(ref, this.optionsMenuContent);
|
||||
};
|
||||
|
||||
focusAtStart = () => {
|
||||
|
@ -85,18 +85,43 @@ export default class ColumnHeader extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
onToggle = (show) => {
|
||||
this.setState({ menuShown: show }, () => {
|
||||
if (show) {
|
||||
this.props.addGradebookElement(this.optionsMenuContentDOMNode);
|
||||
onToggle = (menuShown) => {
|
||||
const newState = { menuShown };
|
||||
let callback;
|
||||
|
||||
if (this.state.menuShown && !menuShown) {
|
||||
if (this.state.skipFocusOnClose) {
|
||||
newState.skipMenuOnClose = false;
|
||||
} else {
|
||||
this.props.removeGradebookElement(this.optionsMenuContentDOMNode);
|
||||
callback = this.focusAtEnd;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.state.menuShown && menuShown) {
|
||||
newState.skipFocusOnClose = false;
|
||||
}
|
||||
|
||||
this.setState(newState, () => {
|
||||
if (typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
|
||||
if (!menuShown) {
|
||||
this.optionsMenuContent = null;
|
||||
this.optionsMenuContentDOMNode = null;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleMenuKeyDown = (event) => {
|
||||
if (event.which === 9) { // Tab
|
||||
this.setState({ menuShown: false, skipFocusOnClose: true });
|
||||
this.props.onHeaderKeyDown(event);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
handleKeyDown (event) {
|
||||
if (document.activeElement === this.optionsMenuTrigger) {
|
||||
if (event.which === 13) { // Enter
|
||||
|
|
|
@ -86,8 +86,15 @@ export default class StudentColumnHeader extends ColumnHeader {
|
|||
this.props.onToggleEnrollmentFilter(enrollmentFilterKey);
|
||||
}
|
||||
|
||||
bindDisplayAsMenuContent = (ref) => { this.displayAsMenuContent = ref; };
|
||||
bindSecondaryInfoMenuContent = (ref) => { this.secondaryInfoMenuContent = ref; };
|
||||
bindDisplayAsMenuContent = (ref) => {
|
||||
this.displayAsMenuContent = ref;
|
||||
this.bindFlyoutMenu(ref, this.displayAsMenuContent);
|
||||
};
|
||||
|
||||
bindSecondaryInfoMenuContent = (ref) => {
|
||||
this.secondaryInfoMenuContent = ref;
|
||||
this.bindFlyoutMenu(ref, this.secondaryInfoMenuContent);
|
||||
};
|
||||
|
||||
render () {
|
||||
const {
|
||||
|
|
|
@ -66,13 +66,30 @@ class TotalGradeColumnHeader extends ColumnHeader {
|
|||
onMoveToBack: func.isRequired
|
||||
}).isRequired,
|
||||
onMenuClose: func.isRequired,
|
||||
grabFocus: bool,
|
||||
...ColumnHeader.propTypes
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
grabFocus: false,
|
||||
...ColumnHeader.defaultProps
|
||||
};
|
||||
|
||||
state = { menuShown: false, skipFocusOnClose: false };
|
||||
|
||||
switchGradeDisplay = () => { this.invokeAndSkipFocus(this.props.gradeDisplay) };
|
||||
|
||||
invokeAndSkipFocus (action) {
|
||||
this.setState({ skipFocusOnClose: true });
|
||||
action.onSelect(this.focusAtEnd);
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
if (this.props.grabFocus) {
|
||||
this.focusAtEnd();
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { sortBySetting, gradeDisplay, position } = this.props;
|
||||
const selectedSortSetting = sortBySetting.isSortColumn && sortBySetting.settingKey;
|
||||
|
@ -127,7 +144,7 @@ class TotalGradeColumnHeader extends ColumnHeader {
|
|||
!gradeDisplay.hidden &&
|
||||
<MenuItem
|
||||
disabled={this.props.gradeDisplay.disabled}
|
||||
onSelect={this.props.gradeDisplay.onSelect}
|
||||
onSelect={this.switchGradeDisplay}
|
||||
>
|
||||
<span data-menu-item-id="grade-display-switcher" style={nowrapStyle}>
|
||||
{displayAsPoints ? I18n.t('Display as Percentage') : I18n.t('Display as Points')}
|
||||
|
|
|
@ -27,11 +27,11 @@ import AssignmentMuter from 'compiled/AssignmentMuter'
|
|||
this.isDialogEnabled = this.isDialogEnabled.bind(this);
|
||||
}
|
||||
|
||||
showDialog () {
|
||||
showDialog (cb) {
|
||||
const assignmentMuter = new AssignmentMuter(
|
||||
null, this.assignment, this.url, null, { openDialogInstantly: true }
|
||||
);
|
||||
assignmentMuter.show();
|
||||
assignmentMuter.show(cb);
|
||||
}
|
||||
|
||||
isDialogEnabled () {
|
||||
|
|
|
@ -35,9 +35,9 @@ import 'jquery.instructure_misc_helpers'
|
|||
) && this.assignment.has_submitted_submissions;
|
||||
}
|
||||
|
||||
showDialog () {
|
||||
showDialog (cb) {
|
||||
this.submissionsDownloading(this.assignment.id);
|
||||
INST.downloadSubmissions(this.downloadUrl);
|
||||
INST.downloadSubmissions(this.downloadUrl, cb);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ import 'jquery.instructure_misc_helpers'
|
|||
return this.assignment.hasDownloadedSubmissions;
|
||||
}
|
||||
|
||||
getReuploadForm () {
|
||||
getReuploadForm (cb) {
|
||||
if (ReuploadSubmissionsDialogManager.reuploadForm) {
|
||||
return ReuploadSubmissionsDialogManager.reuploadForm;
|
||||
}
|
||||
|
@ -45,7 +45,12 @@ import 'jquery.instructure_misc_helpers'
|
|||
width: 400,
|
||||
modal: true,
|
||||
resizable: false,
|
||||
autoOpen: false
|
||||
autoOpen: false,
|
||||
close: () => {
|
||||
if (typeof cb === 'function') {
|
||||
cb();
|
||||
}
|
||||
}
|
||||
}
|
||||
).submit(function () {
|
||||
const data = $(this).getFormData();
|
||||
|
@ -64,8 +69,8 @@ import 'jquery.instructure_misc_helpers'
|
|||
return ReuploadSubmissionsDialogManager.reuploadForm;
|
||||
}
|
||||
|
||||
showDialog () {
|
||||
const form = this.getReuploadForm();
|
||||
showDialog (cb) {
|
||||
const form = this.getReuploadForm(cb);
|
||||
form.attr('action', this.reuploadUrl).dialog('open');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,11 +42,11 @@ import 'compiled/jquery.rails_flash_notifications'
|
|||
};
|
||||
}
|
||||
|
||||
showDialog () {
|
||||
showDialog (cb) {
|
||||
if (this.isAdmin || !this.assignment.inClosedGradingPeriod) {
|
||||
const dialog = new SetDefaultGradeDialog(this.getSetDefaultGradeDialogOptions());
|
||||
|
||||
dialog.show();
|
||||
dialog.show(cb);
|
||||
} else {
|
||||
$.flashError(I18n.t('Unable to set default grade because this ' +
|
||||
'assignment is due in a closed grading period for at least one student'));
|
||||
|
|
|
@ -95,7 +95,10 @@ import './jquery.instructure_misc_plugins' /* showIf */
|
|||
$message_students_dialog.dialog({
|
||||
width: 600,
|
||||
modal: true
|
||||
}).dialog('open').dialog('option', 'title', I18n.t("message_student", "Message Students for %{course_name}", {course_name: title}));
|
||||
})
|
||||
.dialog('open')
|
||||
.dialog('option', 'title', I18n.t('message_student', 'Message Students for %{course_name}', {course_name: title}))
|
||||
.on('dialogclose', settings.onClose);
|
||||
};
|
||||
|
||||
$(document).ready(function() {
|
||||
|
|
|
@ -24,57 +24,61 @@ import './jquery.ajaxJSON'
|
|||
import 'jqueryui/dialog'
|
||||
import 'jqueryui/progressbar'
|
||||
|
||||
INST.downloadSubmissions = function(url) {
|
||||
var cancelled = false;
|
||||
var title = ENV.SUBMISSION_DOWNLOAD_DIALOG_TITLE;
|
||||
title = title || I18n.t('#submissions.download_submissions',
|
||||
'Download Assignment Submissions');
|
||||
$("#download_submissions_dialog").dialog({
|
||||
title: title,
|
||||
close: function() {
|
||||
cancelled = true;
|
||||
}
|
||||
});
|
||||
$("#download_submissions_dialog .progress").progressbar({value: 0});
|
||||
var checkForChange = function() {
|
||||
if(cancelled || $("#download_submissions_dialog:visible").length == 0) { return; }
|
||||
$("#download_submissions_dialog .status_loader").css('visibility', 'visible');
|
||||
var lastProgress = null;
|
||||
$.ajaxJSON(url, 'GET', {}, function(data) {
|
||||
if(data && data.attachment) {
|
||||
var attachment = data.attachment;
|
||||
if(attachment.workflow_state == 'zipped') {
|
||||
$("#download_submissions_dialog .progress").progressbar('value', 100);
|
||||
var message = I18n.t("#submissions.finished_redirecting", "Finished! Redirecting to File...");
|
||||
var link = "<a href=\"" + htmlEscape(url) + "\"><b> " + htmlEscape(I18n.t("#submissions.click_to_download", "Click here to download %{size_of_file}", {size_of_file: attachment.readable_size})) + "</b></a>"
|
||||
$("#download_submissions_dialog .status").html(htmlEscape(message) + "<br>" + $.raw(link));
|
||||
$("#download_submissions_dialog .status_loader").css('visibility', 'hidden');
|
||||
location.href = url;
|
||||
return;
|
||||
INST.downloadSubmissions = function (url, onClose) {
|
||||
let cancelled = false;
|
||||
const title = ENV.SUBMISSION_DOWNLOAD_DIALOG_TITLE || I18n.t('Download Assignment Submissions');
|
||||
|
||||
$('#download_submissions_dialog').dialog({
|
||||
title,
|
||||
close () { cancelled = true; }
|
||||
}).on('dialogclose', onClose);
|
||||
$('#download_submissions_dialog .progress').progressbar({value: 0});
|
||||
const checkForChange = function () {
|
||||
if (cancelled || $('#download_submissions_dialog:visible').length === 0) { return; }
|
||||
$('#download_submissions_dialog .status_loader').css('visibility', 'visible');
|
||||
let lastProgress = null;
|
||||
$.ajaxJSON(url, 'GET', {}, (data) => {
|
||||
if (data && data.attachment) {
|
||||
const attachment = data.attachment;
|
||||
if (attachment.workflow_state === 'zipped') {
|
||||
$('#download_submissions_dialog .progress').progressbar('value', 100);
|
||||
const message = I18n.t('#submissions.finished_redirecting', 'Finished! Redirecting to File...');
|
||||
const linkText = I18n.t('Click here to download %{size_of_file}', {size_of_file: attachment.readable_size});
|
||||
const link = `<a href="${htmlEscape(url)}"><b>${htmlEscape(linkText)}</b></a>`;
|
||||
|
||||
$('#download_submissions_dialog .status').html(`${htmlEscape(message)}<br>${$.raw(link)}`);
|
||||
$('#download_submissions_dialog .status_loader').css('visibility', 'hidden');
|
||||
|
||||
location.href = url;
|
||||
return;
|
||||
} else {
|
||||
let progress = parseInt(attachment.file_state, 10);
|
||||
if (isNaN(progress)) { progress = 0; }
|
||||
progress += 5
|
||||
$('#download_submissions_dialog .progress').progressbar('value', progress);
|
||||
let message = null;
|
||||
if (progress >= 95) {
|
||||
message = I18n.t('#submissions.creating_zip', 'Creating zip file...');
|
||||
} else {
|
||||
var progress = parseInt(attachment.file_state, 10);
|
||||
if(isNaN(progress)) { progress = 0; }
|
||||
progress += 5
|
||||
$("#download_submissions_dialog .progress").progressbar('value', progress);
|
||||
var message = null;
|
||||
if(progress >= 95){
|
||||
message = I18n.t("#submissions.creating_zip", "Creating zip file...");
|
||||
} else {
|
||||
message = I18n.t("#submissions.gathering_files_progress", "Gathering Files (%{progress})...", {progress: I18n.toPercentage(progress)});
|
||||
}
|
||||
$("#download_submissions_dialog .status").text(message);
|
||||
if(progress <= 5 || progress == lastProgress) {
|
||||
$.ajaxJSON(url + "&compile=1", 'GET', {}, function() {}, function() {});
|
||||
}
|
||||
lastProgress = progress;
|
||||
message = I18n.t(
|
||||
'#submissions.gathering_files_progress',
|
||||
'Gathering Files (%{progress})...',
|
||||
{ progress: I18n.toPercentage(progress) }
|
||||
);
|
||||
}
|
||||
$('#download_submissions_dialog .status').text(message);
|
||||
if (progress <= 5 || progress === lastProgress) {
|
||||
$.ajaxJSON(`${url}&compile=1`, 'GET', {}, () => {}, () => {});
|
||||
}
|
||||
lastProgress = progress;
|
||||
}
|
||||
$("#download_submissions_dialog .status_loader").css('visibility', 'hidden');
|
||||
setTimeout(checkForChange, 3000);
|
||||
}, function(data) {
|
||||
$("#download_submissions_dialog .status_loader").css('visibility', 'hidden');
|
||||
setTimeout(checkForChange, 1000);
|
||||
});
|
||||
}
|
||||
checkForChange();
|
||||
}
|
||||
$('#download_submissions_dialog .status_loader').css('visibility', 'hidden');
|
||||
setTimeout(checkForChange, 3000);
|
||||
}, () => {
|
||||
$('#download_submissions_dialog .status_loader').css('visibility', 'hidden');
|
||||
setTimeout(checkForChange, 1000);
|
||||
});
|
||||
};
|
||||
checkForChange();
|
||||
};
|
||||
|
|
|
@ -46,3 +46,10 @@ define [
|
|||
deepEqual dialog.gradeIsExcused('F'), false
|
||||
#this test documents that we do not consider 'excused' to return true
|
||||
deepEqual dialog.gradeIsExcused('excused'), false
|
||||
|
||||
test 'when given callback for #show, invokes callback upon dialog close', ->
|
||||
callback = @stub()
|
||||
dialog = new SetDefaultGradeDialog({ @assignment })
|
||||
dialog.show(callback)
|
||||
$('button.ui-dialog-titlebar-close').click();
|
||||
equal(callback.callCount, 1)
|
||||
|
|
|
@ -2124,6 +2124,15 @@ test('when user is ignoring warnings, immediately toggles the total grade displa
|
|||
equal(this.gradebook.switchTotalDisplay.callCount, 1, 'toggles the total grade display');
|
||||
});
|
||||
|
||||
test('when user is ignoring warnings and a callback is given, immediately invokes callback', function () {
|
||||
const callback = this.stub();
|
||||
UserSettings.contextSet('warned_about_totals_display', true);
|
||||
|
||||
this.gradebook.togglePointsOrPercentTotals(callback);
|
||||
|
||||
equal(callback.callCount, 1);
|
||||
});
|
||||
|
||||
test('when user is not ignoring warnings, return a dialog', function () {
|
||||
UserSettings.contextSet('warned_about_totals_display', false);
|
||||
|
||||
|
@ -2143,6 +2152,16 @@ test('when user is not ignoring warnings, the dialog has a save property which i
|
|||
dialog.cancel();
|
||||
});
|
||||
|
||||
test('when user is not ignoring warnings, the dialog has a onClose property which is the callback function', function () {
|
||||
const callback = this.stub();
|
||||
this.stub(UserSettings, 'contextGet').withArgs('warned_about_totals_display').returns(false);
|
||||
const dialog = this.gradebook.togglePointsOrPercentTotals(callback);
|
||||
|
||||
equal(dialog.options.onClose, callback);
|
||||
|
||||
dialog.cancel();
|
||||
});
|
||||
|
||||
QUnit.module('Gradebook#showNotesColumn', {
|
||||
setup () {
|
||||
this.stub(DataLoader, 'getDataForColumn');
|
||||
|
@ -5470,6 +5489,13 @@ test('includes props for the "Position" settings', function () {
|
|||
strictEqual(typeof props.position.onMoveToBack, 'function', 'props include "onMoveToBack"');
|
||||
});
|
||||
|
||||
test('includes prop for focusing the column header', function () {
|
||||
const gradebook = this.createGradebook();
|
||||
this.stub(gradebook, 'totalColumnShouldFocus').returns(true);
|
||||
const props = gradebook.getTotalGradeColumnHeaderProps();
|
||||
equal(typeof props.grabFocus, 'boolean');
|
||||
});
|
||||
|
||||
QUnit.module('Gradebook#setStudentDisplay', {
|
||||
createGradebook (multipleSections = false) {
|
||||
const options = {};
|
||||
|
|
|
@ -184,16 +184,20 @@ test('renders a PopoverMenu with a trigger', function () {
|
|||
});
|
||||
|
||||
test('calls addGradebookElement prop on open', function () {
|
||||
notOk(this.props.addGradebookElement.called);
|
||||
|
||||
this.wrapper.find('.Gradebook__ColumnHeaderAction').simulate('click');
|
||||
|
||||
strictEqual(this.props.addGradebookElement.callCount, 1);
|
||||
ok(this.props.addGradebookElement.called);
|
||||
});
|
||||
|
||||
test('calls removeGradebookElement prop on close', function () {
|
||||
notOk(this.props.removeGradebookElement.called);
|
||||
|
||||
this.wrapper.find('.Gradebook__ColumnHeaderAction').simulate('click');
|
||||
this.wrapper.find('.Gradebook__ColumnHeaderAction').simulate('click');
|
||||
|
||||
strictEqual(this.props.removeGradebookElement.callCount, 1);
|
||||
ok(this.props.removeGradebookElement.called);
|
||||
});
|
||||
|
||||
test('calls onMenuClose prop on close', function () {
|
||||
|
@ -221,13 +225,6 @@ QUnit.module('AssignmentColumnHeader: Sort by Settings', {
|
|||
this.mountAndOpenOptions = mountAndOpenOptions;
|
||||
},
|
||||
|
||||
openSortByMenu (wrapper) {
|
||||
const menuContent = new ReactWrapper([wrapper.node.optionsMenuContent], wrapper.node);
|
||||
const flyout = menuContent.find('MenuItemFlyout');
|
||||
flyout.find('button').simulate('mouseOver');
|
||||
return new ReactWrapper([wrapper.node.sortByMenuContent], wrapper.node);
|
||||
},
|
||||
|
||||
teardown () {
|
||||
this.wrapper.unmount();
|
||||
}
|
||||
|
@ -257,6 +254,17 @@ test('clicking "Grade - Low to High" calls onSortByGradeAscending', function ()
|
|||
strictEqual(onSortByGradeAscending.callCount, 1);
|
||||
});
|
||||
|
||||
test('clicking "Grade - Low to High" focuses menu trigger', function () {
|
||||
const onSortByGradeAscending = this.stub();
|
||||
const props = defaultProps({ sortBySetting: { onSortByGradeAscending } });
|
||||
const menuItem = findMenuItem.call(this, props, 'Sort by', 'Grade - Low to High');
|
||||
const focusStub = this.stub(this.wrapper.instance(), 'focusAtEnd')
|
||||
|
||||
menuItem.simulate('click');
|
||||
|
||||
equal(focusStub.callCount, 1);
|
||||
});
|
||||
|
||||
test('"Grade - Low to High" is optionally disabled', function () {
|
||||
const props = defaultProps({ sortBySetting: { disabled: true } });
|
||||
const menuItem = findMenuItem.call(this, props, 'Sort by', 'Grade - Low to High');
|
||||
|
@ -282,6 +290,17 @@ test('clicking "Grade - High to Low" calls onSortByGradeDescending', function ()
|
|||
strictEqual(onSortByGradeDescending.callCount, 1);
|
||||
});
|
||||
|
||||
test('clicking "Grade - High to Low" focuses menu trigger', function () {
|
||||
const onSortByGradeDescending = this.stub();
|
||||
const props = defaultProps({ sortBySetting: { onSortByGradeDescending } });
|
||||
const menuItem = findMenuItem.call(this, props, 'Sort by', 'Grade - High to Low');
|
||||
const focusStub = this.stub(this.wrapper.instance(), 'focusAtEnd')
|
||||
|
||||
menuItem.simulate('click');
|
||||
|
||||
equal(focusStub.callCount, 1);
|
||||
});
|
||||
|
||||
test('"Grade - High to Low" is optionally disabled', function () {
|
||||
const props = defaultProps({ sortBySetting: { disabled: true } });
|
||||
const menuItem = findMenuItem.call(this, props, 'Sort by', 'Grade - High to Low');
|
||||
|
@ -307,6 +326,17 @@ test('clicking "Missing" calls onSortByMissing', function () {
|
|||
strictEqual(onSortByMissing.callCount, 1);
|
||||
});
|
||||
|
||||
test('clicking "Missing" focuses menu trigger', function () {
|
||||
const onSortByMissing = this.stub();
|
||||
const props = defaultProps({ sortBySetting: { onSortByMissing } });
|
||||
const menuItem = findMenuItem.call(this, props, 'Sort by', 'Missing');
|
||||
const focusStub = this.stub(this.wrapper.instance(), 'focusAtEnd')
|
||||
|
||||
menuItem.simulate('click');
|
||||
|
||||
equal(focusStub.callCount, 1);
|
||||
});
|
||||
|
||||
test('"Missing" is optionally disabled', function () {
|
||||
const props = defaultProps({ sortBySetting: { disabled: true } });
|
||||
const menuItem = findMenuItem.call(this, props, 'Sort by', 'Missing');
|
||||
|
@ -332,6 +362,17 @@ test('clicking "Late" calls onSortByLate', function () {
|
|||
strictEqual(onSortByLate.callCount, 1);
|
||||
});
|
||||
|
||||
test('clicking "Late" focuses menu trigger', function () {
|
||||
const onSortByLate = this.stub();
|
||||
const props = defaultProps({ sortBySetting: { onSortByLate } });
|
||||
const menuItem = findMenuItem.call(this, props, 'Sort by', 'Late');
|
||||
const focusStub = this.stub(this.wrapper.instance(), 'focusAtEnd')
|
||||
|
||||
menuItem.simulate('click');
|
||||
|
||||
equal(focusStub.callCount, 1);
|
||||
});
|
||||
|
||||
test('"Late" is optionally disabled', function () {
|
||||
const props = defaultProps({ sortBySetting: { disabled: true } });
|
||||
const menuItem = findMenuItem.call(this, props, 'Sort by', 'Late');
|
||||
|
@ -357,6 +398,17 @@ test('clicking "Unposted" calls onSortByUnposted', function () {
|
|||
strictEqual(onSortByUnposted.callCount, 1);
|
||||
});
|
||||
|
||||
test('clicking "Unposted" focuses menu trigger', function () {
|
||||
const onSortByUnposted = this.stub();
|
||||
const props = defaultProps({ sortBySetting: { onSortByUnposted } });
|
||||
const menuItem = findMenuItem.call(this, props, 'Sort by', 'Unposted');
|
||||
const focusStub = this.stub(this.wrapper.instance(), 'focusAtEnd')
|
||||
|
||||
menuItem.simulate('click');
|
||||
|
||||
equal(focusStub.callCount, 1);
|
||||
});
|
||||
|
||||
test('"Unposted" is optionally disabled', function () {
|
||||
const props = defaultProps({ sortBySetting: { disabled: true } });
|
||||
const menuItem = findMenuItem.call(this, props, 'Sort by', 'Unposted');
|
||||
|
@ -395,13 +447,16 @@ test('Curve Grades menu item is enabled when isDisabled is false', function () {
|
|||
notOk(menuItem.parentElement.parentElement.parentElement.getAttribute('aria-disabled'));
|
||||
});
|
||||
|
||||
test('onSelect is called when menu item is clicked', function () {
|
||||
test('clicking the menu item invokes onSelect with correct callback', function () {
|
||||
const onSelect = this.stub();
|
||||
const props = defaultProps({ curveGradesAction: { onSelect } });
|
||||
this.wrapper = mountAndOpenOptions(props);
|
||||
const menuItem = document.querySelector('[data-menu-item-id="curve-grades"]');
|
||||
|
||||
menuItem.click();
|
||||
|
||||
equal(onSelect.callCount, 1);
|
||||
equal(onSelect.getCall(0).args[0], this.wrapper.instance().focusAtEnd);
|
||||
});
|
||||
|
||||
test('the Curve Grades dialog has focus when it is invoked', function () {
|
||||
|
@ -457,14 +512,15 @@ test('disables the menu item when submissions are not loaded', function () {
|
|||
equal(menuItem.parentElement.parentElement.parentElement.getAttribute('aria-disabled'), 'true');
|
||||
});
|
||||
|
||||
test('clicking the menu item invokes the Message Students Who dialog', function () {
|
||||
test('clicking the menu item invokes the Message Students Who dialog with correct callback', function () {
|
||||
this.wrapper = mountAndOpenOptions(this.props);
|
||||
this.stub(window, 'messageStudents');
|
||||
|
||||
const messageStudents = this.stub(window, 'messageStudents');
|
||||
const menuItem = document.querySelector('[data-menu-item-id="message-students-who"]');
|
||||
|
||||
menuItem.click();
|
||||
|
||||
equal(window.messageStudents.callCount, 1);
|
||||
equal(messageStudents.callCount, 1);
|
||||
equal(messageStudents.getCall(0).args[0].onClose, this.wrapper.instance().focusAtEnd);
|
||||
});
|
||||
|
||||
QUnit.module('AssignmentColumnHeader: Mute/Unmute Assignment Action', {
|
||||
|
@ -505,7 +561,7 @@ test('disables the option when prop muteAssignmentAction.disabled is truthy', fu
|
|||
equal(specificMenuItem.parentElement.parentElement.parentElement.getAttribute('aria-disabled'), 'true');
|
||||
});
|
||||
|
||||
test('clicking the option invokes prop muteAssignmentAction.onSelect', function () {
|
||||
test('clicking the menu item invokes onSelect with correct callback', function () {
|
||||
this.props.muteAssignmentAction.onSelect = this.stub();
|
||||
this.wrapper = mountAndOpenOptions(this.props);
|
||||
|
||||
|
@ -513,6 +569,7 @@ test('clicking the option invokes prop muteAssignmentAction.onSelect', function
|
|||
specificMenuItem.click();
|
||||
|
||||
equal(this.props.muteAssignmentAction.onSelect.callCount, 1);
|
||||
equal(this.props.muteAssignmentAction.onSelect.getCall(0).args[0], this.wrapper.instance().focusAtEnd);
|
||||
});
|
||||
|
||||
test('the Assignment Muting dialog has focus when it is invoked', function () {
|
||||
|
@ -618,7 +675,7 @@ test('disables the menu item when the disabled prop is true', function () {
|
|||
equal(specificMenuItem.parentElement.parentElement.parentElement.getAttribute('aria-disabled'), 'true');
|
||||
});
|
||||
|
||||
test('clicking the menu item invokes the onSelect handler', function () {
|
||||
test('clicking the menu item invokes onSelect with correct callback', function () {
|
||||
this.props.setDefaultGradeAction.onSelect = this.stub();
|
||||
this.wrapper = mountAndOpenOptions(this.props);
|
||||
|
||||
|
@ -626,6 +683,7 @@ test('clicking the menu item invokes the onSelect handler', function () {
|
|||
specificMenuItem.click();
|
||||
|
||||
equal(this.props.setDefaultGradeAction.onSelect.callCount, 1);
|
||||
equal(this.props.setDefaultGradeAction.onSelect.getCall(0).args[0], this.wrapper.instance().focusAtEnd);
|
||||
});
|
||||
|
||||
test('the Set Default Grade dialog has focus when it is invoked', function () {
|
||||
|
@ -674,7 +732,7 @@ test('does not render the menu item when the hidden prop is true', function () {
|
|||
equal(specificMenuItem, null);
|
||||
});
|
||||
|
||||
test('clicking the menu item invokes the onSelect handler', function () {
|
||||
test('clicking the menu item invokes onSelect with correct callback', function () {
|
||||
this.props.downloadSubmissionsAction.onSelect = this.stub();
|
||||
this.wrapper = mountAndOpenOptions(this.props);
|
||||
|
||||
|
@ -682,6 +740,7 @@ test('clicking the menu item invokes the onSelect handler', function () {
|
|||
specificMenuItem.click();
|
||||
|
||||
equal(this.props.downloadSubmissionsAction.onSelect.callCount, 1);
|
||||
equal(this.props.downloadSubmissionsAction.onSelect.getCall(0).args[0], this.wrapper.instance().focusAtEnd);
|
||||
});
|
||||
|
||||
QUnit.module('AssignmentColumnHeader: Reupload Submissions Action', {
|
||||
|
@ -712,7 +771,7 @@ test('does not render the menu item when the hidden prop is true', function () {
|
|||
equal(specificMenuItem, null);
|
||||
});
|
||||
|
||||
test('clicking the menu item invokes the onSelect property', function () {
|
||||
test('clicking the menu item invokes the onSelect property with correct callback', function () {
|
||||
this.props.reuploadSubmissionsAction.onSelect = this.stub();
|
||||
this.wrapper = mountAndOpenOptions(this.props);
|
||||
|
||||
|
@ -720,6 +779,7 @@ test('clicking the menu item invokes the onSelect property', function () {
|
|||
specificMenuItem.click();
|
||||
|
||||
equal(this.props.reuploadSubmissionsAction.onSelect.callCount, 1);
|
||||
equal(this.props.reuploadSubmissionsAction.onSelect.getCall(0).args[0], this.wrapper.instance().focusAtEnd);
|
||||
});
|
||||
|
||||
QUnit.module('AssignmentColumnHeader#handleKeyDown', function (hooks) {
|
||||
|
|
|
@ -94,16 +94,20 @@ test('adds a class to the trigger when the PopoverMenu is opened', function () {
|
|||
});
|
||||
|
||||
test('calls addGradebookElement prop on open', function () {
|
||||
notOk(this.props.addGradebookElement.called);
|
||||
|
||||
this.wrapper.find('.Gradebook__ColumnHeaderAction').simulate('click');
|
||||
|
||||
strictEqual(this.props.addGradebookElement.callCount, 1);
|
||||
ok(this.props.addGradebookElement.called);
|
||||
});
|
||||
|
||||
test('calls removeGradebookElement prop on close', function () {
|
||||
notOk(this.props.removeGradebookElement.called);
|
||||
|
||||
this.wrapper.find('.Gradebook__ColumnHeaderAction').simulate('click');
|
||||
this.wrapper.find('.Gradebook__ColumnHeaderAction').simulate('click');
|
||||
|
||||
strictEqual(this.props.removeGradebookElement.callCount, 1);
|
||||
ok(this.props.removeGradebookElement.called);
|
||||
});
|
||||
|
||||
test('calls onMenuClose prop on close', function () {
|
||||
|
|
|
@ -111,16 +111,20 @@ test('renders a title for the More icon', function () {
|
|||
});
|
||||
|
||||
test('calls addGradebookElement prop on open', function () {
|
||||
notOk(this.props.addGradebookElement.called);
|
||||
|
||||
this.wrapper.find('.Gradebook__ColumnHeaderAction').simulate('click');
|
||||
|
||||
strictEqual(this.props.addGradebookElement.callCount, 1);
|
||||
ok(this.props.addGradebookElement.called);
|
||||
});
|
||||
|
||||
test('calls removeGradebookElement prop on close', function () {
|
||||
notOk(this.props.removeGradebookElement.called);
|
||||
|
||||
this.wrapper.find('.Gradebook__ColumnHeaderAction').simulate('click');
|
||||
this.wrapper.find('.Gradebook__ColumnHeaderAction').simulate('click');
|
||||
|
||||
strictEqual(this.props.removeGradebookElement.callCount, 1);
|
||||
ok(this.props.removeGradebookElement.called);
|
||||
});
|
||||
|
||||
test('calls onMenuClose prop on close', function () {
|
||||
|
|
|
@ -113,16 +113,20 @@ test('renders a title for the More icon based on the assignment name', function
|
|||
});
|
||||
|
||||
test('calls addGradebookElement prop on open', function () {
|
||||
notOk(this.props.addGradebookElement.called);
|
||||
|
||||
this.wrapper.find('.Gradebook__ColumnHeaderAction').simulate('click');
|
||||
|
||||
strictEqual(this.props.addGradebookElement.callCount, 1);
|
||||
ok(this.props.addGradebookElement.called);
|
||||
});
|
||||
|
||||
test('calls removeGradebookElement prop on close', function () {
|
||||
notOk(this.props.removeGradebookElement.called);
|
||||
|
||||
this.wrapper.find('.Gradebook__ColumnHeaderAction').simulate('click');
|
||||
this.wrapper.find('.Gradebook__ColumnHeaderAction').simulate('click');
|
||||
|
||||
strictEqual(this.props.removeGradebookElement.callCount, 1);
|
||||
ok(this.props.removeGradebookElement.called);
|
||||
});
|
||||
|
||||
test('calls onMenuClose prop on close', function () {
|
||||
|
|
|
@ -355,4 +355,26 @@ describe "Gradezilla" do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "assignment header focus" do
|
||||
before { Gradezilla.visit(@course)}
|
||||
let(:assignment) { @course.assignments.first }
|
||||
|
||||
it 'is placed on assignment header trigger upon sort' do
|
||||
Gradezilla.click_assignment_header_menu(assignment.id)
|
||||
Gradezilla.click_assignment_popover_sort_by('low-to-high')
|
||||
|
||||
check_element_has_focus Gradezilla.assignment_header_menu_trigger_element(assignment.title)
|
||||
end
|
||||
|
||||
%w[message-students-who curve-grades set-default-grade assignment-muter download-submissions].each do |dialog|
|
||||
it "is placed on assignment header trigger upon #{dialog} dialog close" do
|
||||
Gradezilla.click_assignment_header_menu(assignment.id)
|
||||
Gradezilla.click_assignment_header_menu_element(dialog)
|
||||
Gradezilla.close_open_dialog
|
||||
|
||||
check_element_has_focus Gradezilla.assignment_header_menu_trigger_element(assignment.title)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -592,6 +592,10 @@ class Gradezilla
|
|||
assignment_header_menu_element(id)
|
||||
end
|
||||
|
||||
def assignment_header_menu_trigger_element(assignment_name)
|
||||
assignment_header_cell_element(assignment_name).find_element(:css, '.Gradebook__ColumnHeaderAction')
|
||||
end
|
||||
|
||||
def assignment_header_menu_item_selector(item)
|
||||
menu_item_id = ""
|
||||
|
||||
|
@ -608,6 +612,10 @@ class Gradezilla
|
|||
".container_1 .slick-header-column[id*=assignment_#{assignment_id}] svg[name=IconMutedSolid]"
|
||||
end
|
||||
|
||||
def close_open_dialog
|
||||
fj('.ui-dialog-titlebar-close:visible').click
|
||||
end
|
||||
|
||||
def select_assignment_header_warning_icon
|
||||
assignment_header_warning_icon_element
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue