Fix a11y with the ignore feature of the To Do list

fixes CNVS-19205

Test Plan:
   - Add a submission to a course that needs grading.
   - As a teacher of the course go to your dashboard
   - In the to do list, use a SR to access the menu that
     pops up with the Ignore "X".
   - You should be able to navigate it without problem
   - The same behavior should occur from the course
     home page.
   - For keyboard only, focus should go to the previous
     ignore "X" if it is there.
   - If a previous ignore "X" isn't available, it should
     go to the view calendar link.

Change-Id: I8e8297dc41ac8af69131ef748f25f169541c2958
Reviewed-on: https://gerrit.instructure.com/50278
Reviewed-by: Dan Minkevitch <dan@instructure.com>
Tested-by: Jenkins
QA-Review: Clare Strong <clare@instructure.com>
Product-Review: Clay Diffrient <cdiffrient@instructure.com>
This commit is contained in:
Clay Diffrient 2015-03-12 15:05:56 -06:00
parent a9512faebf
commit 5c5baf1113
7 changed files with 190 additions and 22 deletions

View File

@ -138,4 +138,19 @@ module DashboardHelper
end
end
def todo_ignore_api_url(activity_type, item, force_permanent = false)
activity_symbol = activity_type.to_sym
permanent = (activity_symbol != :grading || force_permanent) ? 1 : nil
api_v1_users_todo_ignore_url(item.asset_string, activity_type, { permanent: permanent })
end
def todo_link_classes(activity_type)
activity_symbol = activity_type.to_sym
(activity_symbol == :grading) ? 'al-trigger disable_item_link' : 'disable_item_link disable-todo-item-link'
end
end

View File

@ -47,10 +47,7 @@ cache(safe_cache_key([@current_user, contexts, 'a_need_grading']), cache_opts) d
<%= t 'need_grading_count', { one: '1 needs grading', other: '%{count} need grading' }, count: Assignments::NeedsGradingCountQuery.new(assignment, @current_user).count %>
</em>
</a>
<a class='disable_item_link grading' title="<%= t('links.title.ignore', %{Ignore this assignment}) %>" href="#" data-api-href="<%= api_v1_users_todo_ignore_url(assignment.asset_string, 'grading') %>">
<span class="screenreader-only"><%= t('images.alt.ignore', 'ignore') %></span>
<i class="icon-x"></i>
</a>
<%= render :partial => 'shared/ignore_option_list', :locals => {:type => 'grading', :item => assignment } %>
</li>
<% end %>
<% needs_submitting.each_with_index do |assignment, i| %>
@ -77,10 +74,7 @@ cache(safe_cache_key([@current_user, contexts, 'a_need_grading']), cache_opts) d
<b><%= assignment.submission_action_string %></b>
<em><%= translated_due_date(assignment) %></em>
</a>
<a class='disable_item_link submitting' title="<%= t('links.title.ignore', %{Ignore this assignment}) %>" href="#" data-api-href="<%= api_v1_users_todo_ignore_url(assignment.asset_string, 'submitting') %>">
<span class="screenreader-only"><%= t('images.alt.ignore', 'ignore') %></span>
<i class="icon-x"></i>
</a>
<%= render :partial => 'shared/ignore_option_list', :locals => {:type => 'submitting', :item => assignment } %>
</li>
<% end %>
<% needs_reviewing.each_with_index do |assessment_request, i| %>
@ -103,10 +97,7 @@ cache(safe_cache_key([@current_user, contexts, 'a_need_grading']), cache_opts) d
<%= t('no_peer_review_due_date', 'due: No Due Date') %>
<% end %>
</em>
<a class='disable_item_link submitting' title="<%= t('links.title.ignore_review', %{Ignore this review}) %>" href="#" data-api-href="<%= api_v1_users_todo_ignore_url(assessment_request.asset_string, 'reviewing') %>">
<span class="screenreader-only"><%= t('images.alt.ignore', 'ignore') %></span>
<i class="icon-x"></i>
</a>
<%= render :partial => 'shared/ignore_option_list', :locals => {:type => 'reviewing', :item => assessment_request} %>
</li>
<% end %>
<% end %>

View File

@ -0,0 +1,23 @@
<div class="IgnoreButton al-dropbown__container">
<a class="<%= todo_link_classes(type) %>"
aria-haspopup="true"
title="<%= t('Ignore this assignment') %>"
href="#"
data-api-href="<%= todo_ignore_api_url(type, item) %>"
data-popup-within="body"
>
<i class="icon-x"></i>
<span class="screenreader-only"><%= t('Ignore') %></span>
</a>
<% if type.to_sym == :grading %>
<ul id="ignore_dropdown" class="al-options" role="menu" tabindex="0" aria-hidden="true" aria-expanded="false" aria-activedescendant="ignore_dropdown">
<li role="presentation">
<a href="#" id="ignore_forever" class="icon-trash disable-todo-item-link" tabindex="-1" role="menuitem" data-api-href="<%= todo_ignore_api_url(type, item, true) %>" title="<%= t('Ignore Forever') %>"><%= t('Ignore Forever') %></a>
</li>
<li role="presentation">
<a href="#" id="ignore_until_submission" class="icon-star disable-todo-item-link" tabindex="-1" role="menuitem" data-api-href="<%= todo_ignore_api_url(type, item) %>" title="<%= t('Ignore Until New Submission') %>"><%= t('Ignore Until New Submission') %></a>
</li>
</ul>
<% end %>
</div>

View File

@ -863,9 +863,13 @@ define([
}
return false;
});
$("#right-side, #topic_list").delegate('.disable_item_link', 'click', function(event) {
$('#right-side').on('click', '.disable-todo-item-link', function (event) {
event.preventDefault();
var $item = $(this).parents("li, div.topic_message");
var $item = $(this).parents("li, div.topic_message").last();
var $prevItem = $(this).closest('.to-do-list > li').prev()
var toFocus = ($prevItem.find('.al-trigger').length && $prevItem.find('.al-trigger')) ||
$('.event-list-view-calendar')
var url = $(this).data('api-href');
function remove(delete_url) {
$item.confirmDelete({
@ -874,20 +878,16 @@ define([
success: function() {
$(this).slideUp(function() {
$(this).remove();
toFocus.focus();
});
}
});
}
if($(this).hasClass('grading')) {
options = {}
options['<span class="ui-icon ui-icon-trash">&nbsp;</span> ' + htmlEscape(I18n.t('ignore_forever', 'Ignore Forever'))] = function() { remove(url + "?permanent=1"); };
options['<span class="ui-icon ui-icon-star">&nbsp;</span> ' + htmlEscape(I18n.t('ignore_until_new_submission', 'Ignore Until New Submission'))] = function() { remove(url); };
$(this).dropdownList({ options: options });
} else {
remove(url + "?permanent=1");
}
remove(url);
});
// in 2 seconds (to give time for everything else to load), find all the external links and add give them
// the external link look and behavior (force them to open in a new tab)
setTimeout(function() {

View File

@ -19,6 +19,16 @@ define([
'jquery' /* $ */
], function($) {
/**
* TL;DR: Remove this file when possible.
*
* Please note that this file is considered very legacy. All instances of it
* should be evaluated and replaced with the newer and far better looking
* KyleMenu. The only other place that I've seen it in used is the file
* attendance.js which is on the chopping block at some point in the future.
* Once that file has been axed, then this file should be able to be removed.
*/
// Simple dropdown list. Takes the list of attributes specified in "options" and displays them
// in a menu anchored to the selected element.
$.fn.dropdownList = function(options) {

View File

@ -36,6 +36,114 @@ describe "dashboard" do
end
end
it "should be able to ignore an assignment to grade permanently" do
assignment = assignment_model({:submission_types => 'online_text_entry', :course => @course})
student = user_with_pseudonym(:active_user => true, :username => 'student@example.com', :password => 'qwerty')
student2 = user_with_pseudonym(:active_user => true, :username => 'student2@example.com', :password => 'qwerty')
@course.enroll_user(student, "StudentEnrollment", :enrollment_state => 'active')
@course.enroll_user(student2, "StudentEnrollment", :enrollment_state => 'active')
assignment.reload
assignment.submit_homework(student, {:submission_type => 'online_text_entry', :body => 'ABC'})
assignment.reload
enable_cache do
get "/"
f('.to-do-list .disable_item_link').click
wait_for_ajaximations
f('#ignore_forever').click
wait_for_ajaximations
expect(f('.to-do-list > li')).to be_nil
get "/"
expect(f('.to-do-list')).to be_nil
end
assignment.reload
assignment.submit_homework(student2, {:submission_type => 'online_text_entry', :body => 'ABC'})
assignment.reload
enable_cache do
get "/"
expect(f('.to-do-list')).to be_nil
end
end
it "should be able to ignore an assignment until the next submission" do
assignment = assignment_model({:submission_types => 'online_text_entry', :course => @course})
student = user_with_pseudonym(:active_user => true, :username => 'student@example.com', :password => 'qwerty')
student2 = user_with_pseudonym(:active_user => true, :username => 'student2@example.com', :password => 'qwerty')
@course.enroll_user(student, "StudentEnrollment", :enrollment_state => 'active')
@course.enroll_user(student2, "StudentEnrollment", :enrollment_state => 'active')
assignment.reload
assignment.submit_homework(student, {:submission_type => 'online_text_entry', :body => 'ABC'})
assignment.reload
enable_cache do
get "/"
f('.to-do-list .disable_item_link').click
wait_for_ajaximations
f('#ignore_until_submission').click
wait_for_ajaximations
expect(f('.to-do-list > li')).to be_nil
get "/"
expect(f('.to-do-list')).to be_nil
end
assignment.reload
assignment.submit_homework(student2, {:submission_type => 'online_text_entry', :body => 'ABC'})
assignment.reload
enable_cache do
get "/"
expect(f('.to-do-list > li')).to include_text('Grade ' + assignment.title)
end
end
describe "Todo Ignore Options Focus Management" do
before :each do
assignment = assignment_model({:submission_types => 'online_text_entry', :course => @course})
@student = user_with_pseudonym(:active_user => true, :username => 'student@example.com', :password => 'qwerty')
@course.enroll_user(@student, "StudentEnrollment", :enrollment_state => 'active')
assignment.submit_homework(@student, {:submission_type => 'online_text_entry', :body => 'ABC'})
end
it "should focus on the previous ignore link after ignoring a todo item" do
assignment2 = assignment_model({:submission_types => 'online_text_entry', :course => @course})
assignment2.submit_homework(@student, {:submission_type => 'online_text_entry', :body => 'Number2'})
enable_cache do
get "/"
all_todo_links = ff('.to-do-list .disable_item_link')
all_todo_links.last.click
wait_for_ajaximations
ff('#ignore_forever').last.click
wait_for_ajaximations
check_element_has_focus(all_todo_links.first)
end
end
it "should focus on the 'View Calendar' link if there are no other todo items" do
enable_cache do
get "/"
f('.to-do-list .disable_item_link').click
wait_for_ajaximations
f('#ignore_forever').click
wait_for_ajaximations
check_element_has_focus(f('.event-list-view-calendar'))
end
end
end
it "should not display assignment to grade in to do list for a designer" do
course_with_designer_logged_in(:active_all => true)
assignment = assignment_model({:submission_types => 'online_text_entry', :course => @course})

View File

@ -59,5 +59,26 @@ describe "dashboard" do
expect(f('.to-do-list')).to be_nil
expect(f('.coming_up')).to_not include_text(assignment.title)
end
it "should allow to do list items to be ignored" do
notification_model(:name => 'Assignment Due Date Changed')
notification_policy_model(:notification_id => @notification.id)
assignment = assignment_model({:submission_types => 'online_text_entry', :course => @course})
assignment.due_at = Time.now + 60
assignment.created_at = 1.month.ago
assignment.save!
get "/"
expect(f('.to-do-list > li')).to include_text(assignment.submission_action_string)
f('.to-do-list .disable_item_link').click
wait_for_ajaximations
expect(f('.to-do-list > li')).to be_nil
get "/"
expect(f('.to-do-list')).to be_nil
end
end
end