Sortable User Course List
closes OUT-6339 OUT-6346 flag=none Test Plan: - There are many cases that are handled by the automated and unit tests. The following steps are to ensure the page loads and behaves properly: - Open the User Courses page (/courses) - Observe that each column now has a sorting icon next to the column name - Observe that the Published column is default sorted in ascending order - Click through the different columns and observe that sorting by those columns and in descending order works as expected Change-Id: Ie522e17f161f2a4d0ed8c35f62c5006162de9bd5 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/346737 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Martin Yosifov <martin.yosifov@instructure.com> QA-Review: Martin Yosifov <martin.yosifov@instructure.com> Product-Review: Kyle Rosenbaum <krosenbaum@instructure.com>
This commit is contained in:
parent
e7faa44614
commit
bd6db735b8
|
@ -567,15 +567,50 @@ class CoursesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
@past_enrollments.sort_by! { |e| [e.course.published? ? 0 : 1, Canvas::ICU.collation_key(e.long_name)] }
|
||||
[@current_enrollments, @future_enrollments].each do |list|
|
||||
list.sort_by! do |e|
|
||||
[e.course.published? ? 0 : 1, e.active? ? 1 : 0, Canvas::ICU.collation_key(e.long_name)]
|
||||
end
|
||||
end
|
||||
@current_enrollments = sort_enrollments(@current_enrollments, "current")
|
||||
@past_enrollments = sort_enrollments(@past_enrollments, "past")
|
||||
@future_enrollments = sort_enrollments(@future_enrollments, "future")
|
||||
end
|
||||
helper_method :load_enrollments_for_index
|
||||
|
||||
def sort_enrollments(enrollments, type)
|
||||
sort_column = nil
|
||||
order = nil
|
||||
case type
|
||||
when "current"
|
||||
sort_column = params[:cc_sort]
|
||||
order = params[:cc_order]
|
||||
when "past"
|
||||
sort_column = params[:pc_sort]
|
||||
order = params[:pc_order]
|
||||
when "future"
|
||||
sort_column = params[:fc_sort]
|
||||
order = params[:fc_order]
|
||||
end
|
||||
sorted_enrollments = enrollments.sort_by! do |e|
|
||||
case sort_column
|
||||
when "favorite"
|
||||
@current_user.courses_with_primary_enrollment(:favorite_courses).map(&:id).include?(e.course_id) ? 0 : 1
|
||||
when "course"
|
||||
e.course.name
|
||||
when "nickname"
|
||||
nickname = e.course.nickname_for(@current_user, nil)
|
||||
[nickname.nil? ? 1 : 0, nickname]
|
||||
when "term"
|
||||
[e.course.enrollment_term.default_term? ? 1 : 0, e.course.enrollment_term.name]
|
||||
when "enrolled_as"
|
||||
e.readable_role_name
|
||||
else
|
||||
if type == "past"
|
||||
[e.course.published? ? 0 : 1, Canvas::ICU.collation_key(e.long_name)]
|
||||
else
|
||||
[e.course.published? ? 0 : 1, e.active? ? 1 : 0, Canvas::ICU.collation_key(e.long_name)]
|
||||
end
|
||||
end
|
||||
end
|
||||
(order == "desc") ? sorted_enrollments.reverse : sorted_enrollments
|
||||
end
|
||||
|
||||
def enrollments_for_index(type)
|
||||
instance_variable_get(:"@#{type}_enrollments")
|
||||
end
|
||||
|
|
|
@ -181,4 +181,24 @@ module CoursesHelper
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
def get_sorting_order(curr_col, sorted_col, order)
|
||||
"desc" if (sorted_col.nil? && curr_col == "published") || (curr_col == sorted_col && order != "desc")
|
||||
end
|
||||
|
||||
def get_sorting_icon(curr_col, sorted_col, order)
|
||||
if (curr_col == sorted_col) || (curr_col == "published" && sorted_col.nil?)
|
||||
"icon-mini-arrow-#{(order == "desc") ? "down" : "up"}"
|
||||
else
|
||||
"icon-mini-arrow-double"
|
||||
end
|
||||
end
|
||||
|
||||
def get_courses_params(table, col, params)
|
||||
params.permit(:cc_sort, :cc_order, :pc_sort, :pc_order, :fc_sort, :fc_order).merge({
|
||||
"#{table}_sort": col,
|
||||
"#{table}_order": get_sorting_order(col, params[:cc_sort], params[:cc_order]),
|
||||
focus: table
|
||||
})
|
||||
end
|
||||
end
|
||||
|
|
|
@ -24,16 +24,28 @@
|
|||
|
||||
/* Column widths are based on these cells */
|
||||
.course-list-star-column {
|
||||
width: 3%;
|
||||
width: 10%;
|
||||
&.course-list-favorite-icon {
|
||||
color: $ic-color-icon-disabled;
|
||||
}
|
||||
}
|
||||
.course-list-sort-icon {
|
||||
color: #C7CDD1;
|
||||
&.sorted {
|
||||
color: $ic-font-color-dark;
|
||||
}
|
||||
}
|
||||
.course-list-column-header {
|
||||
a {
|
||||
color: $ic-font-color-dark;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
.course-list-term-column,
|
||||
.course-list-enrolled-as-column {
|
||||
width: 15%;
|
||||
}
|
||||
.course-list-published-column {
|
||||
.course-list-published-column {
|
||||
width: 7%;
|
||||
}
|
||||
.course-list-nickname-column {
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
%>
|
||||
|
||||
<% js_bundle :courses %>
|
||||
|
||||
<%
|
||||
if k5_user?
|
||||
all_courses_title = t('All Subjects')
|
||||
|
@ -71,12 +73,38 @@
|
|||
<table id="my_courses_table" class="ic-Table ic-Table--bordered course-list-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<% if @show_star_column %><th scope="col" class="course-list-star-column"><span class="screenreader-only"><%= t ('Favorites') %></span></th><% end %>
|
||||
<th scope="col" class="course-list-course-title-column course-list-no-left-border"><%= course_label %></th>
|
||||
<th scope="col" class="course-list-nickname-column course-list-no-left-border"><%= t ('Nickname') %></th>
|
||||
<th scope="col" class="course-list-term-column course-list-no-left-border"><%= t ('Term') %></th>
|
||||
<th scope="col" class="course-list-enrolled-as-column course-list-no-left-border"><%= t ('Enrolled as') %></th>
|
||||
<th scope="col" class="course-list-published-column course-list-no-left-border"><%= t ('Published') %></th>
|
||||
<% if @show_star_column %>
|
||||
<th scope="col" class="course-list-star-column course-list-column-header course-list-no-left-border">
|
||||
<a href="<%= courses_path(get_courses_params("cc", "favorite", params)) %>" id="cc_favorite">
|
||||
<%= t('Favorite') %> <i class="<%= "course-list-sort-icon #{params[:cc_sort] == "favorite" ? "sorted" : ""}" %> <%= get_sorting_icon("favorite", params[:cc_sort], params[:cc_order]) %>"></i>
|
||||
</a>
|
||||
</th>
|
||||
<% end %>
|
||||
<th scope="col" class="course-list-course-title-column course-list-column-header course-list-no-left-border">
|
||||
<a href="<%= courses_path(get_courses_params("cc", "course", params)) %>" id="cc_course">
|
||||
<%= course_label %> <i class="<%= "course-list-sort-icon #{params[:cc_sort] == "course" ? "sorted" : ""}" %> <%= get_sorting_icon("course", params[:cc_sort], params[:cc_order]) %>"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col" class="course-list-nickname-column course-list-column-header course-list-no-left-border">
|
||||
<a href="<%= courses_path(get_courses_params("cc", "nickname", params)) %>" id="cc_nickname">
|
||||
<%= t ('Nickname') %> <i class="<%= "course-list-sort-icon #{params[:cc_sort] == "nickname" ? "sorted" : ""}" %> <%= get_sorting_icon("nickname", params[:cc_sort], params[:cc_order]) %>"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col" class="course-list-term-column course-list-column-header course-list-no-left-border">
|
||||
<a href="<%= courses_path(get_courses_params("cc", "term", params)) %>" id="cc_term">
|
||||
<%= t ('Term') %> <i class="<%= "course-list-sort-icon #{params[:cc_sort] == "term" ? "sorted" : ""}" %> <%= get_sorting_icon("term", params[:cc_sort], params[:cc_order]) %>"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col" class="course-list-enrolled-as-column course-list-column-header course-list-no-left-border">
|
||||
<a href="<%= courses_path(get_courses_params("cc", "enrolled_as", params)) %>" id="cc_enrolled_as">
|
||||
<%= t ('Enrolled as') %> <i class="<%= "course-list-sort-icon #{params[:cc_sort] == "enrolled_as" ? "sorted" : ""}" %> <%= get_sorting_icon("enrolled_as", params[:cc_sort], params[:cc_order]) %>"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col" class="course-list-published-column course-list-column-header course-list-no-left-border">
|
||||
<a href="<%= courses_path(get_courses_params("cc", "published", params)) %>" id="cc_published">
|
||||
<%= t ('Published') %> <i class="<%= "course-list-sort-icon #{(params[:cc_sort] == "published" || params[:cc_sort].nil?) ? "sorted" : ""}" %> <%= get_sorting_icon("published", params[:cc_sort], params[:cc_order]) %>"></i>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -95,12 +123,38 @@
|
|||
<table id="past_enrollments_table" class="ic-Table ic-Table--bordered course-list-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<% if @show_star_column %><th scope="col" class="course-list-star-column"><span class="screenreader-only"><%= t ('Favorites') %></span></th><% end %>
|
||||
<th scope="col" class="course-list-course-title-column course-list-no-left-border"><%= course_label %></th>
|
||||
<th scope="col" class="course-list-nickname-column course-list-no-left-border"><%= t ('Nickname') %></th>
|
||||
<th scope="col" class="course-list-term-column course-list-no-left-border"><%= t ('Term') %></th>
|
||||
<th scope="col" class="course-list-enrolled-as-column course-list-no-left-border"><%= t ('Enrolled as') %></th>
|
||||
<th scope="col" class="course-list-published-column course-list-no-left-border"><%= t ('Published') %></th>
|
||||
<% if @show_star_column %>
|
||||
<th scope="col" class="course-list-star-column course-list-column-header course-list-no-left-border">
|
||||
<a href="<%= courses_path(get_courses_params("pc", "favorite", params)) %>" id="pc_favorite">
|
||||
<%= t('Favorite') %> <i class="<%= "course-list-sort-icon #{params[:pc_sort] == "favorite" ? "sorted" : ""}" %> <%= get_sorting_icon("favorite", params[:pc_sort], params[:pc_order]) %>"></i>
|
||||
</a>
|
||||
</th>
|
||||
<% end %>
|
||||
<th scope="col" class="course-list-course-title-column course-list-column-header course-list-no-left-border">
|
||||
<a href="<%= courses_path(get_courses_params("pc", "course", params)) %>" id="pc_course">
|
||||
<%= course_label %> <i class="<%= "course-list-sort-icon #{params[:pc_sort] == "course" ? "sorted" : ""}" %> <%= get_sorting_icon("course", params[:pc_sort], params[:pc_order]) %>"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col" class="course-list-nickname-column course-list-column-header course-list-no-left-border">
|
||||
<a href="<%= courses_path(get_courses_params("pc", "nickname", params)) %>" id="pc_nickname">
|
||||
<%= t ('Nickname') %> <i class="<%= "course-list-sort-icon #{params[:pc_sort] == "nickname" ? "sorted" : ""}" %> <%= get_sorting_icon("nickname", params[:pc_sort], params[:pc_order]) %>"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col" class="course-list-term-column course-list-column-header course-list-no-left-border">
|
||||
<a href="<%= courses_path(get_courses_params("pc", "term", params)) %>" id="pc_term">
|
||||
<%= t ('Term') %> <i class="<%= "course-list-sort-icon #{params[:pc_sort] == "term" ? "sorted" : ""}" %> <%= get_sorting_icon("term", params[:pc_sort], params[:pc_order]) %>"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col" class="course-list-enrolled-as-column course-list-column-header course-list-no-left-border">
|
||||
<a href="<%= courses_path(get_courses_params("pc", "enrolled_as", params)) %>" id="pc_enrolled_as">
|
||||
<%= t ('Enrolled as') %> <i class="<%= "course-list-sort-icon #{params[:pc_sort] == "enrolled_as" ? "sorted" : ""}" %> <%= get_sorting_icon("enrolled_as", params[:pc_sort], params[:pc_order]) %>"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col" class="course-list-published-column course-list-column-header course-list-no-left-border">
|
||||
<a href="<%= courses_path(get_courses_params("pc", "published", params)) %>" id="pc_published">
|
||||
<%= t ('Published') %> <i class="<%= "course-list-sort-icon #{(params[:pc_sort] == "published" || params[:pc_sort].nil?) ? "sorted" : ""}" %> <%= get_sorting_icon("published", params[:pc_sort], params[:pc_order]) %>"></i>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -119,12 +173,38 @@
|
|||
<table id="future_enrollments_table" class="ic-Table ic-Table--bordered course-list-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<% if @show_star_column %><th scope="col" class="course-list-star-column"><span class="screenreader-only"><%= t ('Favorites') %></span></th><% end %>
|
||||
<th scope="col" class="course-list-course-title-column course-list-no-left-border"><%= course_label %></th>
|
||||
<th scope="col" class="course-list-nickname-column course-list-no-left-border"><%= t ('Nickname') %></th>
|
||||
<th scope="col" class="course-list-term-column course-list-no-left-border"><%= t ('Term') %></th>
|
||||
<th scope="col" class="course-list-enrolled-as-column course-list-no-left-border"><%= t ('Enrolled as') %></th>
|
||||
<th scope="col" class="course-list-published-column course-list-no-left-border"><%= t ('Published') %></th>
|
||||
<% if @show_star_column %>
|
||||
<th scope="col" class="course-list-star-column course-list-column-header course-list-no-left-border">
|
||||
<a href="<%= courses_path(get_courses_params("fc", "favorite", params)) %>" id="fc_favorite">
|
||||
<%= t('Favorite') %> <i class="<%= "course-list-sort-icon #{params[:fc_sort] == "favorite" ? "sorted" : ""}" %> <%= get_sorting_icon("favorite", params[:fc_sort], params[:fc_order]) %>"></i>
|
||||
</a>
|
||||
</th>
|
||||
<% end %>
|
||||
<th scope="col" class="course-list-course-title-column course-list-column-header course-list-no-left-border">
|
||||
<a href="<%= courses_path(get_courses_params("fc", "course", params)) %>" id="fc_course">
|
||||
<%= course_label %> <i class="<%= "course-list-sort-icon #{params[:fc_sort] == "course" ? "sorted" : ""}" %> <%= get_sorting_icon("course", params[:fc_sort], params[:fc_order]) %>"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col" class="course-list-nickname-column course-list-column-header course-list-no-left-border">
|
||||
<a href="<%= courses_path(get_courses_params("fc", "nickname", params)) %>" id="fc_nickname">
|
||||
<%= t ('Nickname') %> <i class="<%= "course-list-sort-icon #{params[:fc_sort] == "nickname" ? "sorted" : ""}" %> <%= get_sorting_icon("nickname", params[:fc_sort], params[:fc_order]) %>"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col" class="course-list-term-column course-list-column-header course-list-no-left-border">
|
||||
<a href="<%= courses_path(get_courses_params("fc", "term", params)) %>" id="fc_term">
|
||||
<%= t ('Term') %> <i class="<%= "course-list-sort-icon #{params[:fc_sort] == "term" ? "sorted" : ""}" %> <%= get_sorting_icon("term", params[:fc_sort], params[:fc_order]) %>"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col" class="course-list-enrolled-as-column course-list-column-header course-list-no-left-border">
|
||||
<a href="<%= courses_path(get_courses_params("fc", "enrolled_as", params)) %>" id="fc_enrolled_as">
|
||||
<%= t ('Enrolled as') %> <i class="<%= "course-list-sort-icon #{params[:fc_sort] == "enrolled_as" ? "sorted" : ""}" %> <%= get_sorting_icon("enrolled_as", params[:fc_sort], params[:fc_order]) %>"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col" class="course-list-published-column course-list-column-header course-list-no-left-border">
|
||||
<a href="<%= courses_path(get_courses_params("fc", "published", params)) %>" id="fc_published">
|
||||
<%= t ('Published') %> <i class="<%= "course-list-sort-icon #{(params[:fc_sort] == "published" || params[:fc_sort].nil?) ? "sorted" : ""}" %> <%= get_sorting_icon("published", params[:fc_sort], params[:fc_order]) %>"></i>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
|
@ -29,12 +29,12 @@ describe CoursesController do
|
|||
controller.instance_variable_set(:@domain_root_account, Account.default)
|
||||
end
|
||||
|
||||
def get_index(user = nil)
|
||||
def get_index(user: nil, index_params: {})
|
||||
user_session(user) if user
|
||||
user ||= @user
|
||||
controller.instance_variable_set(:@current_user, user)
|
||||
get "index", params: index_params
|
||||
controller.load_enrollments_for_index
|
||||
get "index"
|
||||
end
|
||||
|
||||
it "forces login" do
|
||||
|
@ -75,7 +75,7 @@ describe CoursesController do
|
|||
course_with_student_logged_in
|
||||
toggle_k5_setting(@course.account)
|
||||
|
||||
get_index @student
|
||||
get_index(user: @student)
|
||||
expect(assigns[:js_bundles].flatten).to include :k5_theme
|
||||
expect(assigns[:css_bundles].flatten).to include :k5_theme, :k5_font
|
||||
end
|
||||
|
@ -83,7 +83,7 @@ describe CoursesController do
|
|||
it "does not set k5_theme when k5 is off" do
|
||||
course_with_student_logged_in
|
||||
|
||||
get_index @student
|
||||
get_index(user: @student)
|
||||
expect(assigns[:js_bundles].flatten).not_to include :k5_theme
|
||||
expect(assigns[:css_bundles].flatten).not_to include :k5_theme, :k5_font
|
||||
end
|
||||
|
@ -93,7 +93,7 @@ describe CoursesController do
|
|||
toggle_k5_setting(@course.account)
|
||||
toggle_classic_font_setting(@course.account)
|
||||
|
||||
get_index @student
|
||||
get_index(user: @student)
|
||||
expect(assigns[:css_bundles].flatten).to include :k5_theme
|
||||
expect(assigns[:css_bundles].flatten).not_to include :k5_font
|
||||
end
|
||||
|
@ -139,6 +139,133 @@ describe CoursesController do
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples "sorting" do
|
||||
before do
|
||||
@course1 = (type == "future") ? Account.default.courses.create!(name: "A", start_at: 1.month.from_now, restrict_enrollments_to_course_dates: true) : Account.default.courses.create!(name: "A")
|
||||
@course2 = (type == "future") ? Account.default.courses.create!(name: "Z", start_at: 1.month.from_now, restrict_enrollments_to_course_dates: true) : Account.default.courses.create!(name: "Z")
|
||||
|
||||
# user is enrolled as a student in course 1
|
||||
enrollment1 = course_with_student user: @student, course: @course1
|
||||
enrollment1.invite!
|
||||
|
||||
# publish course 2
|
||||
@course2.offer!
|
||||
# user is enrolled as a ta in course 2
|
||||
enrollment2 = course_with_ta course: @course2, user: @student, active_all: true
|
||||
|
||||
term1 = @course1.root_account.enrollment_terms.create!(name: "Term 1")
|
||||
@course1.enrollment_term = term1
|
||||
@course1.save!
|
||||
|
||||
term2 = @course2.root_account.enrollment_terms.create!(name: "Term 2")
|
||||
@course2.enrollment_term = term2
|
||||
@course2.save!
|
||||
|
||||
@student.set_preference(:course_nicknames, @course1.id, "English")
|
||||
@student.set_preference(:course_nicknames, @course2.id, "Math")
|
||||
|
||||
@student.favorites.create!(context: @course2)
|
||||
|
||||
if type == "past"
|
||||
[enrollment1, enrollment2].each(&:complete!)
|
||||
end
|
||||
end
|
||||
|
||||
context "on published column" do
|
||||
it "lists unpublished courses after published" do
|
||||
user_session(@student)
|
||||
get_index
|
||||
expect(assigns["#{type}_enrollments"].map(&:course_id)).to eq [@course2.id, @course1.id]
|
||||
end
|
||||
|
||||
it "lists unpublished courses after published when descending order" do
|
||||
user_session(@student)
|
||||
get_index(index_params: { sort_column => "published", order_column => "desc" })
|
||||
expect(assigns["#{type}_enrollments"].map(&:course_id)).to eq [@course1.id, @course2.id]
|
||||
end
|
||||
end
|
||||
|
||||
context "on enrolled as column" do
|
||||
it "lists enrollment type alphabetically" do
|
||||
user_session(@student)
|
||||
get_index(index_params: { sort_column => "enrolled_as" })
|
||||
expect(assigns["#{type}_enrollments"].map(&:course_id)).to eq [@course1.id, @course2.id]
|
||||
end
|
||||
|
||||
it "lists enrollment type reverse alphabetically when descending order" do
|
||||
user_session(@student)
|
||||
get_index(index_params: { sort_column => "enrolled_as", order_column => "desc" })
|
||||
expect(assigns["#{type}_enrollments"].map(&:course_id)).to eq [@course2.id, @course1.id]
|
||||
end
|
||||
end
|
||||
|
||||
context "on term column" do
|
||||
it "lists terms alphabetically" do
|
||||
user_session(@student)
|
||||
get_index(index_params: { sort_column => "term" })
|
||||
expect(assigns["#{type}_enrollments"].map(&:course_id)).to eq [@course1.id, @course2.id]
|
||||
end
|
||||
|
||||
it "lists terms reverse alphabetically when descending order" do
|
||||
user_session(@student)
|
||||
get_index(index_params: { sort_column => "term", order_column => "desc" })
|
||||
expect(assigns["#{type}_enrollments"].map(&:course_id)).to eq [@course2.id, @course1.id]
|
||||
end
|
||||
end
|
||||
|
||||
context "on nickname column" do
|
||||
it "lists course nicknames alphabetically" do
|
||||
user_session(@student)
|
||||
get_index(index_params: { sort_column => "nickname" })
|
||||
expect(assigns["#{type}_enrollments"].map(&:course_id)).to eq [@course1.id, @course2.id]
|
||||
end
|
||||
|
||||
it "lists course nicknames reverse alphabetically when descending order" do
|
||||
user_session(@student)
|
||||
get_index(index_params: { sort_column => "nickname", order_column => "desc" })
|
||||
expect(assigns["#{type}_enrollments"].map(&:course_id)).to eq [@course2.id, @course1.id]
|
||||
end
|
||||
end
|
||||
|
||||
context "on course name column" do
|
||||
it "lists course names alphabetically" do
|
||||
user_session(@student)
|
||||
get_index(index_params: { sort_column => "course" })
|
||||
expect(assigns["#{type}_enrollments"].map(&:course_id)).to eq [@course1.id, @course2.id]
|
||||
end
|
||||
|
||||
it "lists course names reverse alphabetically when descending order" do
|
||||
user_session(@student)
|
||||
get_index(index_params: { sort_column => "course", order_column => "desc" })
|
||||
expect(assigns["#{type}_enrollments"].map(&:course_id)).to eq [@course2.id, @course1.id]
|
||||
end
|
||||
end
|
||||
|
||||
context "on favorites column" do
|
||||
it "lists favorited courses first" do
|
||||
user_session(@student)
|
||||
get_index(index_params: { sort_column => "favorite" })
|
||||
if type == "past"
|
||||
# Only active courses can be favorited. Therefore, we don't expect the sorting to affect the order.
|
||||
expect(assigns["#{type}_enrollments"].map(&:course_id)).to eq [@course1.id, @course2.id]
|
||||
else
|
||||
expect(assigns["#{type}_enrollments"].map(&:course_id)).to eq [@course2.id, @course1.id]
|
||||
end
|
||||
end
|
||||
|
||||
it "lists favorited courses last when descending order" do
|
||||
user_session(@student)
|
||||
get_index(index_params: { sort_column => "favorite", order_column => "desc" })
|
||||
if type == "past"
|
||||
# Only active courses can be favorited. Therefore, the list will just be reversed from its ascending order.
|
||||
expect(assigns["#{type}_enrollments"].map(&:course_id)).to eq [@course2.id, @course1.id]
|
||||
else
|
||||
expect(assigns["#{type}_enrollments"].map(&:course_id)).to eq [@course1.id, @course2.id]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "current_enrollments" do
|
||||
it "groups enrollments by course and type" do
|
||||
# enrollments with multiple sections of the same type should be de-duped
|
||||
|
@ -246,25 +373,6 @@ describe CoursesController do
|
|||
expect(assigns[:future_enrollments]).to be_empty
|
||||
end
|
||||
|
||||
describe "unpublished_courses" do
|
||||
it "lists unpublished courses after published" do
|
||||
# unpublished course
|
||||
course1 = Account.default.courses.create! name: "A"
|
||||
enrollment1 = course_with_student user: @student, course: course1
|
||||
enrollment1.invite!
|
||||
expect(course1).to be_unpublished
|
||||
|
||||
# published course
|
||||
course2 = Account.default.courses.create! name: "Z"
|
||||
course2.offer!
|
||||
course_with_student course: course2, user: @student, active_all: true
|
||||
|
||||
user_session(@student)
|
||||
get_index
|
||||
expect(assigns[:current_enrollments].map(&:course_id)).to eq [course2.id, course1.id]
|
||||
end
|
||||
end
|
||||
|
||||
context "as enrollment admin" do
|
||||
it "includes courses with no applicable start/end dates" do
|
||||
# no dates at all
|
||||
|
@ -293,6 +401,14 @@ describe CoursesController do
|
|||
expect(assigns[:future_enrollments]).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe "sorting" do
|
||||
include_examples "sorting" do
|
||||
let(:type) { "current" }
|
||||
let(:sort_column) { "cc_sort" }
|
||||
let(:order_column) { "cc_order" }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "past_enrollments" do
|
||||
|
@ -502,7 +618,7 @@ describe CoursesController do
|
|||
|
||||
course1.enrollment_term.update_attribute(:end_at, 1.month.ago)
|
||||
|
||||
get_index(@student)
|
||||
get_index(user: @student)
|
||||
expect(response).to be_successful
|
||||
expect(assigns[:past_enrollments]).to be_empty
|
||||
expect(assigns[:current_enrollments]).to be_empty
|
||||
|
@ -510,13 +626,13 @@ describe CoursesController do
|
|||
|
||||
observer = user_with_pseudonym(active_all: true)
|
||||
add_linked_observer(@student, observer)
|
||||
get_index(observer)
|
||||
get_index(user: observer)
|
||||
expect(response).to be_successful
|
||||
expect(assigns[:past_enrollments]).to be_empty
|
||||
expect(assigns[:current_enrollments]).to be_empty
|
||||
expect(assigns[:future_enrollments]).to be_empty
|
||||
|
||||
get_index(teacher)
|
||||
get_index(user: teacher)
|
||||
expect(response).to be_successful
|
||||
expect(assigns[:past_enrollments]).to eq [teacher_enrollment]
|
||||
expect(assigns[:current_enrollments]).to be_empty
|
||||
|
@ -565,6 +681,14 @@ describe CoursesController do
|
|||
expect(assigns[:past_enrollments].map(&:course_id)).to eq [course2.id, course1.id] # Z, then A
|
||||
end
|
||||
end
|
||||
|
||||
describe "sorting" do
|
||||
include_examples "sorting" do
|
||||
let(:type) { "past" }
|
||||
let(:sort_column) { "pc_sort" }
|
||||
let(:order_column) { "pc_order" }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "future_enrollments" do
|
||||
|
@ -698,6 +822,14 @@ describe CoursesController do
|
|||
expect(assigns[:future_enrollments].map(&:course_id)).to eq [course2.id, course1.id] # Z, then A
|
||||
end
|
||||
end
|
||||
|
||||
describe "sorting" do
|
||||
include_examples "sorting" do
|
||||
let(:type) { "future" }
|
||||
let(:sort_column) { "fc_sort" }
|
||||
let(:order_column) { "fc_order" }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "per-assignment permissions" do
|
||||
|
|
|
@ -270,4 +270,64 @@ describe CoursesHelper do
|
|||
expect(format_course_section_date).to eq "(no date)"
|
||||
end
|
||||
end
|
||||
|
||||
describe "sortable user course list helpers" do
|
||||
context "get_sorting_order" do
|
||||
it "returns 'desc' if we are sorting on the current col that is in ascending order" do
|
||||
expect(get_sorting_order("favorite", "favorite", nil)).to eq("desc")
|
||||
end
|
||||
|
||||
it "returns 'desc' if the default col is being sorted on in ascending order" do
|
||||
expect(get_sorting_order("published", nil, nil)).to eq("desc")
|
||||
end
|
||||
|
||||
it "returns nil if we are not sorting on the current col" do
|
||||
expect(get_sorting_order("enrolled_as", "favorite", nil)).to be_nil
|
||||
end
|
||||
|
||||
it "returns nil if we are sorting on the current col that is in descending order" do
|
||||
expect(get_sorting_order("favorite", "favorite", "desc")).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "get_sorting_icon" do
|
||||
it "returns the double arrow icon if we are not sorting on the given column" do
|
||||
expect(get_sorting_icon("favorite", "published", "desc")).to eq("icon-mini-arrow-double")
|
||||
end
|
||||
|
||||
it "returns the upward arrow icon if we are sorting on the given column in ascending order" do
|
||||
expect(get_sorting_icon("favorite", "favorite", nil)).to eq("icon-mini-arrow-up")
|
||||
end
|
||||
|
||||
it "returns the downward arrow icon if we are sorting on the given column in descending order" do
|
||||
expect(get_sorting_icon("favorite", "favorite", "desc")).to eq("icon-mini-arrow-down")
|
||||
end
|
||||
end
|
||||
|
||||
context "get_courses_params" do
|
||||
it "returns the correct params for the given table" do
|
||||
table = "cc"
|
||||
column = "favorite"
|
||||
old_params = ActionController::Parameters.new
|
||||
new_params = ActionController::Parameters.new(cc_sort: column, cc_order: nil, focus: table)
|
||||
expect(get_courses_params(table, column, old_params)).to eq(new_params.permit(:cc_sort, :cc_order, :focus))
|
||||
end
|
||||
|
||||
it "returns the correct params for the given table and params for other tables" do
|
||||
table = "cc"
|
||||
column = "favorite"
|
||||
old_params = ActionController::Parameters.new(pc_sort: "published")
|
||||
new_params = ActionController::Parameters.new(cc_sort: column, cc_order: nil, focus: table, pc_sort: "published")
|
||||
expect(get_courses_params(table, column, old_params)).to eq(new_params.permit(:cc_sort, :cc_order, :focus, :pc_sort))
|
||||
end
|
||||
|
||||
it "only returns permitted params" do
|
||||
table = "cc"
|
||||
column = "favorite"
|
||||
old_params = ActionController::Parameters.new(foo: "bar")
|
||||
new_params = ActionController::Parameters.new(cc_sort: column, cc_order: nil, focus: table)
|
||||
expect(get_courses_params(table, column, old_params)).to eq(new_params.permit(:cc_sort, :cc_order, :focus))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -177,4 +177,200 @@ describe "course index" do
|
|||
expect(fj('h2:contains("Create Course")')).to be_displayed
|
||||
end
|
||||
end
|
||||
|
||||
context "sorting" do
|
||||
it "by favorite column lists favorited courses first when ascending and last when descending" do
|
||||
favorite_course = @current_courses[0]
|
||||
@user.favorites.create!(context: favorite_course)
|
||||
get "/courses"
|
||||
# ascending
|
||||
favorites_column_header(current_enrollments_selector).click
|
||||
wait_for_ajaximations
|
||||
|
||||
sorted_courses = table_rows(current_enrollments_selector)
|
||||
# remove header row
|
||||
sorted_courses.shift
|
||||
expect(sorted_courses.first).to eq row_with_text(favorite_course.name)
|
||||
|
||||
# descending
|
||||
favorites_column_header(current_enrollments_selector).click
|
||||
wait_for_ajaximations
|
||||
|
||||
sorted_courses = table_rows(current_enrollments_selector)
|
||||
# remove header row
|
||||
sorted_courses.shift
|
||||
expect(sorted_courses.last).to eq row_with_text(favorite_course.name)
|
||||
end
|
||||
|
||||
it "by course column lists alphabetically when ascending and reverse when descending" do
|
||||
course_name = "Classic Course (Current)"
|
||||
|
||||
# Selenium is having trouble clicking the nickname column header, even though it
|
||||
# works fine in the UI. To test sorting, we just load the page with the
|
||||
# url params to test nickname column sorting.
|
||||
# Otherwise we would test it the same way as the other headers:
|
||||
# get "/courses"
|
||||
# title_column_header(current_enrollments_selector).click
|
||||
# wait_for_ajaximations
|
||||
get "/courses?cc_sort=course"
|
||||
|
||||
sorted_courses = table_rows(current_enrollments_selector)
|
||||
# remove header row
|
||||
sorted_courses.shift
|
||||
expect(sorted_courses.first).to eq row_with_text(course_name)
|
||||
|
||||
# descending
|
||||
get "/courses?cc_order=desc&cc_sort=course"
|
||||
|
||||
sorted_courses = table_rows(current_enrollments_selector)
|
||||
# remove header row
|
||||
sorted_courses.shift
|
||||
expect(sorted_courses.last).to eq row_with_text(course_name)
|
||||
end
|
||||
|
||||
it "by nickname column lists alphabetically when ascending and reverse when descending" do
|
||||
course_nickname = "Classic Course Nickname (Current)"
|
||||
course = @current_courses[0]
|
||||
@user.set_preference(:course_nicknames, course.id, course_nickname)
|
||||
|
||||
# Selenium is having trouble clicking the term column header, even though it
|
||||
# works fine in the UI. To test sorting, we just load the page with the
|
||||
# url params to test term column sorting.
|
||||
# Otherwise we would test it the same way as the other headers:
|
||||
# get "/courses"
|
||||
# nickname_column_header(current_enrollments_selector).click
|
||||
# wait_for_ajaximations
|
||||
get "/courses?cc_sort=nickname"
|
||||
|
||||
sorted_courses = table_rows(current_enrollments_selector)
|
||||
# remove header row
|
||||
sorted_courses.shift
|
||||
expect(sorted_courses.first).to eq row_with_text(course_nickname)
|
||||
|
||||
# descending
|
||||
get "/courses?cc_order=desc&cc_sort=nickname"
|
||||
|
||||
sorted_courses = table_rows(current_enrollments_selector)
|
||||
# remove header row
|
||||
sorted_courses.shift
|
||||
expect(sorted_courses.last).to eq row_with_text(course_nickname)
|
||||
end
|
||||
|
||||
it "by term column lists alphabetically when ascending and reverse when descending" do
|
||||
course = @current_courses[0]
|
||||
term = course.root_account.enrollment_terms.create!(name: "Term 1")
|
||||
course.enrollment_term = term
|
||||
course.save!
|
||||
|
||||
# Selenium is having trouble clicking the term column header, even though it
|
||||
# works fine in the UI. To test sorting, we just load the page with the
|
||||
# url params to test term column sorting.
|
||||
# Otherwise we would test it the same way as the other headers:
|
||||
# get "/courses"
|
||||
# term_column_header(current_enrollments_selector).click
|
||||
# wait_for_ajaximations
|
||||
get "/courses?cc_sort=term"
|
||||
|
||||
sorted_courses = table_rows(current_enrollments_selector)
|
||||
# remove header row
|
||||
sorted_courses.shift
|
||||
expect(sorted_courses.first).to eq row_with_text(course.name)
|
||||
|
||||
# descending
|
||||
get "/courses?cc_order=desc&cc_sort=term"
|
||||
|
||||
sorted_courses = table_rows(current_enrollments_selector)
|
||||
# remove header row
|
||||
sorted_courses.shift
|
||||
expect(sorted_courses.last).to eq row_with_text(course.name)
|
||||
end
|
||||
|
||||
it "by enrolled as column lists alphabetically when ascending and reverse when descending" do
|
||||
@current_courses << course_with_ta(course_name: "Classic Course 2 (Current)", account: @classic_account, user: @user, active_all: true).course
|
||||
get "/courses"
|
||||
# ascending
|
||||
enrolled_as_column_header(current_enrollments_selector).click
|
||||
wait_for_ajaximations
|
||||
|
||||
sorted_courses = table_rows(current_enrollments_selector)
|
||||
# remove header row
|
||||
sorted_courses.shift
|
||||
expect(sorted_courses.first.text.include?("Student")).to be true
|
||||
expect(sorted_courses.last.text.include?("TA")).to be true
|
||||
|
||||
# descending
|
||||
enrolled_as_column_header(current_enrollments_selector).click
|
||||
wait_for_ajaximations
|
||||
|
||||
sorted_courses = table_rows(current_enrollments_selector)
|
||||
# remove header row
|
||||
sorted_courses.shift
|
||||
expect(sorted_courses.first.text.include?("TA")).to be true
|
||||
expect(sorted_courses.last.text.include?("Student")).to be true
|
||||
end
|
||||
|
||||
it "by published column lists published first when ascending and non-published first when descending" do
|
||||
@current_courses << course_with_student(course_name: "Classic Course 2 (Current)", account: @classic_account, user: @user, active_all: true).course
|
||||
unpublished_course = @current_courses[2]
|
||||
unpublished_course.workflow_state = "created"
|
||||
unpublished_course.save!
|
||||
|
||||
get "/courses"
|
||||
# sorted by published (ascending) by default
|
||||
sorted_courses = table_rows(current_enrollments_selector)
|
||||
# remove header row
|
||||
sorted_courses.shift
|
||||
expect(sorted_courses.last).to eq row_with_text(unpublished_course.name)
|
||||
|
||||
# descending
|
||||
published_column_header(current_enrollments_selector).click
|
||||
wait_for_ajaximations
|
||||
|
||||
sorted_courses = table_rows(current_enrollments_selector)
|
||||
# remove header row
|
||||
sorted_courses.shift
|
||||
expect(sorted_courses.first).to eq row_with_text(unpublished_course.name)
|
||||
end
|
||||
|
||||
it "is independent across tables" do
|
||||
@current_courses << course_with_student(course_name: "Classic Course 2 (Current)", account: @classic_account, user: @user, active_all: true).course
|
||||
unpublished_course = @current_courses[2]
|
||||
unpublished_course.workflow_state = "created"
|
||||
unpublished_course.save!
|
||||
|
||||
@past_courses << course_with_ta(course_name: "Classic Course 3 (Current)", account: @classic_account, user: @user, active_all: true).course
|
||||
course_as_ta = @past_courses[2]
|
||||
course_as_ta.complete!
|
||||
|
||||
favorite_course = @future_courses[0]
|
||||
@user.favorites.create!(context: favorite_course)
|
||||
|
||||
get "/courses"
|
||||
|
||||
# sort the current courses by published (desc) column
|
||||
published_column_header(current_enrollments_selector).click
|
||||
wait_for_ajaximations
|
||||
|
||||
# sort the past courses by enrolled as column
|
||||
enrolled_as_column_header(past_enrollments_selector).click
|
||||
wait_for_ajaximations
|
||||
|
||||
# sort the future courses by favorites column
|
||||
favorites_column_header(future_enrollments_selector).click
|
||||
wait_for_ajaximations
|
||||
|
||||
sorted_current_courses = table_rows(current_enrollments_selector)
|
||||
sorted_past_courses = table_rows(past_enrollments_selector)
|
||||
sorted_future_courses = table_rows(future_enrollments_selector)
|
||||
|
||||
# remove header rows
|
||||
sorted_current_courses.shift
|
||||
sorted_past_courses.shift
|
||||
sorted_future_courses.shift
|
||||
|
||||
expect(sorted_current_courses.first).to eq row_with_text(unpublished_course.name)
|
||||
expect(sorted_past_courses.last).to eq row_with_text(course_as_ta.name)
|
||||
expect(sorted_future_courses.first).to eq row_with_text(favorite_course.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -77,4 +77,36 @@ module CourseIndexPage
|
|||
def favorite_icon(course_name)
|
||||
f(favorite_icon_selector(course_name))
|
||||
end
|
||||
|
||||
def favorites_column_header(table)
|
||||
table_header_row(table)[0]
|
||||
end
|
||||
|
||||
def title_column_header(table)
|
||||
table_header_row(table)[1]
|
||||
end
|
||||
|
||||
def nickname_column_header(table)
|
||||
table_header_row(table)[2]
|
||||
end
|
||||
|
||||
def term_column_header(table)
|
||||
table_header_row(table)[3]
|
||||
end
|
||||
|
||||
def enrolled_as_column_header(table)
|
||||
table_header_row(table)[4]
|
||||
end
|
||||
|
||||
def published_column_header(table)
|
||||
table_header_row(table)[5]
|
||||
end
|
||||
|
||||
def table_rows(table)
|
||||
ff("#{table} tr")
|
||||
end
|
||||
|
||||
def table_header_row(table)
|
||||
ff("#{table} tr th")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -82,6 +82,7 @@ const featureBundles: {
|
|||
course_statistics: () => import('./features/course_statistics/index'),
|
||||
course_wizard: () => import('./features/course_wizard/index'),
|
||||
course: () => import('./features/course/index'),
|
||||
courses: () => import('./features/courses/index'),
|
||||
dashboard: () => import('./features/dashboard/index'),
|
||||
deep_linking_response: () => import('./features/deep_linking_response/index'),
|
||||
developer_keys_v2: () => import('./features/developer_keys_v2/index'),
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (C) 2024 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import ready from '@instructure/ready'
|
||||
|
||||
ready(() => {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
const sortingTable = params.get('focus')
|
||||
if (sortingTable) {
|
||||
const sortingCol = params.get(sortingTable + '_sort')
|
||||
const focusedHeader = document.querySelector('a#' + sortingTable + '_' + sortingCol)
|
||||
if (focusedHeader) focusedHeader.focus()
|
||||
}
|
||||
})
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "@canvas-features/courses",
|
||||
"private": true,
|
||||
"version": "1.0.0"
|
||||
}
|
Loading…
Reference in New Issue