canvas-lms/spec/selenium/discussions/discussions_edit_page_spec.rb

2213 lines
110 KiB
Ruby

# frozen_string_literal: true
#
# Copyright (C) 2014 - 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/>.
require_relative "../helpers/discussions_common"
require_relative "../helpers/items_assign_to_tray"
require_relative "../helpers/context_modules_common"
require_relative "../../helpers/k5_common"
require_relative "../dashboard/pages/k5_important_dates_section_page"
require_relative "../dashboard/pages/k5_dashboard_common_page"
require_relative "../common"
require_relative "pages/discussion_page"
require_relative "../assignments/page_objects/assignment_create_edit_page"
require_relative "../discussions/discussion_helpers"
require_relative "../../helpers/selective_release_common"
describe "discussions" do
include_context "in-process server selenium tests"
include DiscussionsCommon
include DiscussionHelpers
include ItemsAssignToTray
include ContextModulesCommon
include K5DashboardCommonPageObject
include K5Common
include K5ImportantDatesSectionPageObject
include SelectiveReleaseCommon
def create_graded_discussion(discussion_course, assignment_options = {})
default_assignment_options = {
name: "Default Assignment",
points_possible: 10,
assignment_group: discussion_course.assignment_groups.create!(name: "Default Assignment Group"),
only_visible_to_overrides: false
}
options = default_assignment_options.merge(assignment_options)
@discussion_assignment = discussion_course.assignments.create!(options)
all_graded_discussion_options = {
user: teacher,
title: "assignment topic title",
message: "assignment topic message",
discussion_type: "threaded",
assignment: @discussion_assignment,
}
discussion_course.discussion_topics.create!(all_graded_discussion_options)
end
let(:course) { course_model.tap(&:offer!) }
let(:teacher) { teacher_in_course(course:, name: "teacher", active_all: true).user }
let(:teacher_topic) { course.discussion_topics.create!(user: teacher, title: "teacher topic title", message: "teacher topic message") }
let(:assignment_group) { course.assignment_groups.create!(name: "assignment group") }
let(:group_category) { course.group_categories.create!(name: "group category") }
let(:assignment) do
course.assignments.create!(
name: "assignment",
points_possible: 10,
# submission_types: 'discussion_topic',
assignment_group:
)
end
let(:assignment_topic) do
course.discussion_topics.create!(user: teacher,
title: "assignment topic title",
message: "assignment topic message",
assignment:)
end
context "on the edit page" do
let(:url) { "/courses/#{course.id}/discussion_topics/#{topic.id}/edit" }
context "when :discussion_create feature flag is OFF" do
before do
Account.site_admin.disable_feature! :discussion_create
end
context "as a teacher" do
let(:topic) { teacher_topic }
before do
user_session(teacher)
stub_rcs_config
end
context "graded" do
let(:topic) { assignment_topic }
it "allows editing the assignment group", priority: "1" do
assign_group_2 = course.assignment_groups.create!(name: "Group 2")
get url
click_option("#assignment_group_id", assign_group_2.name)
expect_new_page_load { f(".form-actions button[type=submit]").click }
expect(topic.reload.assignment.assignment_group_id).to eq assign_group_2.id
end
it "allows editing the grading type", priority: "1" do
get url
click_option("#assignment_grading_type", "Letter Grade")
expect_new_page_load { f(".form-actions button[type=submit]").click }
expect(topic.reload.assignment.grading_type).to eq "letter_grade"
end
it "allows editing the group category", priority: "1" do
group_cat = course.group_categories.create!(name: "Groupies")
get url
f("#has_group_category").click
click_option("#assignment_group_category_id", group_cat.name)
expect_new_page_load { f(".form-actions button[type=submit]").click }
expect(topic.reload.group_category_id).to eq group_cat.id
end
it "allows editing the peer review", priority: "1" do
get url
f("#assignment_peer_reviews").click
expect_new_page_load { f(".form-actions button[type=submit]").click }
expect(topic.reload.assignment.peer_reviews).to be true
end
it "allows editing the due dates", priority: "1" do
differentiated_modules_off
get url
wait_for_tiny(f("textarea[name=message]"))
due_at = 3.days.from_now
unlock_at = 2.days.from_now
lock_at = 4.days.from_now
# set due_at, lock_at, unlock_at
replace_content(f(".date_field[data-date-type='due_at']"), format_date_for_view(due_at), tab_out: true)
replace_content(f(".date_field[data-date-type='unlock_at']"), format_date_for_view(unlock_at), tab_out: true)
replace_content(f(".date_field[data-date-type='lock_at']"), format_date_for_view(lock_at), tab_out: true)
wait_for_ajaximations
expect_new_page_load { f(".form-actions button[type=submit]").click }
a = DiscussionTopic.last.assignment
expect(a.due_at.to_date).to eq due_at.to_date
expect(a.unlock_at.to_date).to eq unlock_at.to_date
expect(a.lock_at.to_date).to eq lock_at.to_date
end
it "adds an attachment to a graded topic", priority: "1" do
get url
wait_for_tiny(f("textarea[name=message]"))
add_attachment_and_validate do
# should correctly save changes to the assignment
set_value f("#discussion_topic_assignment_points_possible"), "123"
end
assignment.reload
expect(assignment.points_possible).to eq 123
end
it "returns focus to add attachment when removed" do
get url
add_attachment_and_validate
get url
f(".removeAttachment").click
wait_for_ajaximations
check_element_has_focus(f("input[name=attachment]"))
end
it "warns user when leaving page unsaved", priority: "1" do
skip_if_safari(:alert)
title = "new title"
get url
wait_for_tiny(f("textarea[name=message]"))
replace_content(f("input[name=title]"), title)
fln("Home").click
expect(alert_present?).to be_truthy
driver.switch_to.alert.dismiss
end
context "with archived grading schemes enabled" do
before do
Account.site_admin.enable_feature!(:grading_scheme_updates)
Account.site_admin.enable_feature!(:archived_grading_schemes)
@course = course
@account = @course.account
@active_grading_standard = @course.grading_standards.create!(title: "Active Grading Scheme", data: { "A" => 0.9, "F" => 0 }, scaling_factor: 1.0, points_based: false, workflow_state: "active")
@archived_grading_standard = @course.grading_standards.create!(title: "Archived Grading Scheme", data: { "A" => 0.9, "F" => 0 }, scaling_factor: 1.0, points_based: false, workflow_state: "archived")
@account_grading_standard = @account.grading_standards.create!(title: "Account Grading Scheme", data: { "A" => 0.9, "F" => 0 }, scaling_factor: 1.0, points_based: false, workflow_state: "active")
discussion_assignment_options = {
name: "assignment",
points_possible: 10,
grading_type: "letter_grade",
assignment_group: course.assignment_groups.create!(name: "assignment group"),
only_visible_to_overrides: true,
}
discussion_assignment_peer_review_options = {
peer_reviews: true,
automatic_peer_reviews: true,
peer_reviews_due_at: 1.day.ago,
peer_review_count: 2,
}
discussion_assignment_options = discussion_assignment_options.merge(discussion_assignment_peer_review_options)
@graded_discussion = create_graded_discussion(course, discussion_assignment_options)
course_override_due_date = 5.days.from_now
course_section = course.course_sections.create!(name: "section alpha")
@graded_discussion.assignment.assignment_overrides.create!(set_type: "CourseSection", set_id: course_section.id, due_at: course_override_due_date)
@assignment = @graded_discussion.assignment
end
it "shows archived grading scheme if it is the course default twice, once to follow course default scheme and once to choose that scheme to use" do
@course.update!(grading_standard_id: @archived_grading_standard.id)
@course.reload
get "/courses/#{@course.id}/discussion_topics/#{@graded_discussion.id}/edit"
expect(f("[data-testid='grading-schemes-selector-dropdown']").attribute("title")).to eq(@archived_grading_standard.title + " (course default)")
f("[data-testid='grading-schemes-selector-dropdown']").click
expect(f("[data-testid='grading-schemes-selector-option-#{@course.grading_standard.id}']")).to include_text(@course.grading_standard.title)
end
it "shows archived grading scheme if it is the current assignment grading standard" do
@assignment.update!(grading_standard_id: @archived_grading_standard.id)
@assignment.reload
get "/courses/#{@course.id}/discussion_topics/#{@graded_discussion.id}/edit"
wait_for_ajaximations
expect(f("[data-testid='grading-schemes-selector-dropdown']").attribute("title")).to eq(@archived_grading_standard.title)
end
it "removes grading schemes from dropdown after archiving them but still shows them upon reopening the modal" do
get "/courses/#{@course.id}/discussion_topics/#{@graded_discussion.id}/edit"
wait_for_ajaximations
f("[data-testid='grading-schemes-selector-dropdown']").click
expect(f("[data-testid='grading-schemes-selector-option-#{@active_grading_standard.id}']")).to be_present
f("[data-testid='manage-all-grading-schemes-button']").click
wait_for_ajaximations
f("[data-testid='grading-scheme-#{@active_grading_standard.id}-archive-button']").click
wait_for_ajaximations
f("[data-testid='manage-all-grading-schemes-close-button']").click
wait_for_ajaximations
f("[data-testid='grading-schemes-selector-dropdown']").click
expect(f("[data-testid='grading-schemes-selector-dropdown-form']")).not_to contain_css("[data-testid='grading-schemes-selector-option-#{@active_grading_standard.id}']")
f("[data-testid='manage-all-grading-schemes-button']").click
wait_for_ajaximations
expect(f("[data-testid='grading-scheme-row-#{@active_grading_standard.id}']").text).to be_present
end
it "shows all archived schemes in the manage grading schemes modal" do
archived_gs1 = @course.grading_standards.create!(title: "Archived Grading Scheme 1", data: { "A" => 0.9, "F" => 0 }, scaling_factor: 1.0, points_based: false, workflow_state: "archived")
archived_gs2 = @course.grading_standards.create!(title: "Archived Grading Scheme 2", data: { "A" => 0.9, "F" => 0 }, scaling_factor: 1.0, points_based: false, workflow_state: "archived")
archived_gs3 = @course.grading_standards.create!(title: "Archived Grading Scheme 3", data: { "A" => 0.9, "F" => 0 }, scaling_factor: 1.0, points_based: false, workflow_state: "archived")
get "/courses/#{@course.id}/discussion_topics/#{@graded_discussion.id}/edit"
wait_for_ajaximations
f("[data-testid='manage-all-grading-schemes-button']").click
wait_for_ajaximations
expect(f("[data-testid='grading-scheme-#{archived_gs1.id}-name']")).to include_text(archived_gs1.title)
expect(f("[data-testid='grading-scheme-#{archived_gs2.id}-name']")).to include_text(archived_gs2.title)
expect(f("[data-testid='grading-scheme-#{archived_gs3.id}-name']")).to include_text(archived_gs3.title)
end
it "will still show the assignment grading scheme if you archive it on the edit page in the management modal and persist on reload" do
@assignment.update!(grading_standard_id: @active_grading_standard.id)
@assignment.reload
get "/courses/#{@course.id}/discussion_topics/#{@graded_discussion.id}/edit"
wait_for_ajaximations
expect(f("[data-testid='grading-schemes-selector-dropdown']").attribute("title")).to eq(@active_grading_standard.title)
f("[data-testid='manage-all-grading-schemes-button']").click
wait_for_ajaximations
f("[data-testid='grading-scheme-#{@active_grading_standard.id}-archive-button']").click
wait_for_ajaximations
f("[data-testid='manage-all-grading-schemes-close-button']").click
wait_for_ajaximations
expect(f("[data-testid='grading-schemes-selector-dropdown']").attribute("title")).to eq(@active_grading_standard.title)
get "/courses/#{@course.id}/assignments/#{@assignment.id}/edit"
wait_for_ajaximations
expect(f("[data-testid='grading-schemes-selector-dropdown']").attribute("title")).to eq(@active_grading_standard.title)
end
it "creates a discussion topic with selected grading scheme/standard" do
grading_standard = @course.grading_standards.create!(title: "Win/Lose", data: [["Winner", 0.94], ["Loser", 0]])
get "/courses/#{@course.id}/assignments/#{@assignment.id}/edit"
wait_for_ajaximations
f("[data-testid='grading-schemes-selector-dropdown']").click
f("[data-testid='grading-schemes-selector-option-#{grading_standard.id}']").click
f(".form-actions button[type=submit]").click
fj(".ui-button-text:contains('Continue')").click
a = DiscussionTopic.last.assignment
expect(a.grading_standard_id).to eq grading_standard.id
end
end
end
context "with a group attached" do
let(:graded_topic) { assignment_topic }
before do
@gc = GroupCategory.create(name: "Sharks", context: @course)
@student = student_in_course(course: @course, active_all: true).user
group = @course.groups.create!(group_category: @gc)
group.users << @student
end
it "group discussions with entries should lock and display the group name", priority: "1" do
topic.group_category = @gc
topic.save!
topic.child_topics[0].reply_from({ user: @student, text: "I feel pretty" })
@gc.destroy
get url
expect(f("#assignment_group_category_id")).to be_disabled
expect(get_value("#assignment_group_category_id")).to eq topic.group_category.id.to_s
end
it "prompts for creating a new group category if original group is deleted with no submissions", priority: "1" do
topic.group_category = @gc
topic.save!
@gc.destroy
get url
wait_for_ajaximations
expect(f("#assignment_group_category_id")).not_to be_displayed
end
context "graded" do
let(:topic) { assignment_topic }
it "locks and display the group name", priority: "1" do
topic.group_category = @gc
topic.save!
topic.reply_from({ user: @student, text: "I feel pretty" })
@gc.destroy
get url
expect(f("#assignment_group_category_id")).to be_disabled
expect(get_value("#assignment_group_category_id")).to eq topic.group_category.id.to_s
end
end
end
it "saves and display all changes", priority: "2" do
course.require_assignment_group
confirm(:off)
toggle(:on)
confirm(:on)
end
it "preserves query parameters in the URL when you CANCEL", :ignore_js_errors do
get "/courses/#{course.id}/discussion_topics/#{topic.id}/edit?embed=true"
force_click("button:contains('Cancel')")
wait_for_ajaximations
expect(driver.current_url).not_to include("edit")
expect(driver.current_url).to include("?embed=true")
end
it "shows correct date when saving" do
Timecop.freeze do
topic.lock_at = 5.days.ago
topic.save!
teacher.time_zone = "Hawaii"
teacher.save!
get url
f(".form-actions button[type=submit]").click
get url
expect(topic.reload.lock_at).to eq 5.days.ago.beginning_of_minute
end
end
it "toggles checkboxes when clicking their labels", priority: "1" do
get url
expect(is_checked("input[type=checkbox][name=threaded]")).not_to be_truthy
force_click_native("input#threaded")
expect(is_checked("input[type=checkbox][name=threaded]")).to be_truthy
end
context "locking" do
it "sets as active when removing existing delayed_post_at and lock_at dates", priority: "1" do
topic.delayed_post_at = 10.days.ago
topic.lock_at = 5.days.ago
topic.locked = true
topic.save!
get url
wait_for_tiny(f("textarea[name=message]"))
expect(f('input[type=text][name="delayed_post_at"]')).to be_displayed
f('input[type=text][name="delayed_post_at"]').clear
f('input[type=text][name="lock_at"]').clear
expect_new_page_load { f(".form-actions button[type=submit]").click }
topic.reload
expect(topic.delayed_post_at).to be_nil
expect(topic.lock_at).to be_nil
expect(topic.active?).to be_truthy
expect(topic.locked?).to be_falsey
end
it "is locked when delayed_post_at and lock_at are in past", priority: "2" do
topic.delayed_post_at = nil
topic.lock_at = nil
topic.workflow_state = "active"
topic.save!
get url
wait_for_tiny(f("textarea[name=message]"))
delayed_post_at = 10.days.ago
lock_at = 5.days.ago
replace_content(f('input[type=text][name="delayed_post_at"]'), format_date_for_view(delayed_post_at), tab_out: true)
replace_content(f('input[type=text][name="lock_at"]'), format_date_for_view(lock_at), tab_out: true)
expect_new_page_load { f(".form-actions button[type=submit]").click }
wait_for_ajaximations
topic.reload
expect(topic.delayed_post_at.to_date).to eq delayed_post_at.to_date
expect(topic.lock_at.to_date).to eq lock_at.to_date
expect(topic.locked?).to be_truthy
end
it "sets workflow to active when delayed_post_at in past and lock_at in future", priority: "2" do
topic.delayed_post_at = 5.days.from_now
topic.lock_at = 10.days.from_now
topic.workflow_state = "active"
topic.locked = false
topic.save!
get url
wait_for_tiny(f("textarea[name=message]"))
delayed_post_at = 5.days.ago
replace_content(f('input[type=text][name="delayed_post_at"]'), format_date_for_view(delayed_post_at), tab_out: true)
expect_new_page_load { f(".form-actions button[type=submit]").click }
wait_for_ajaximations
topic.reload
expect(topic.delayed_post_at.to_date).to eq delayed_post_at.to_date
expect(topic.active?).to be_truthy
expect(topic.locked?).to be_falsey
end
end
context "usage rights" do
before do
course.root_account.enable_feature!(:usage_rights_discussion_topics)
course.update!(usage_rights_required: true)
end
it "validates that usage rights are set" do
get url
_filename, fullpath, _data = get_file("testfile5.zip")
f("input[name=attachment]").send_keys(fullpath)
type_in_tiny("textarea[name=message]", "file attachment discussion")
f("#edit_discussion_form_buttons .btn-primary[type=submit]").click
wait_for_ajaximations
error_box = f("div[role='alert'] .error_text")
expect(error_box.text).to eq "You must set usage rights"
end
it "sets usage rights on file attachment" do
get url
_filename, fullpath, _data = get_file("testfile1.txt")
f("input[name=attachment]").send_keys(fullpath)
f("#usage_rights_control button").click
click_option(".UsageRightsSelectBox__container select", "own_copyright", :value)
f(".UsageRightsDialog__Footer-Actions button[type='submit']").click
expect_new_page_load { f(".form-actions button[type=submit]").click }
expect(topic.reload.attachment.usage_rights).not_to be_nil
end
it "displays usage rights on file attachment" do
usage_rights = @course.usage_rights.create!(
legal_copyright: "(C) 2012 Initrode",
use_justification: "own_copyright"
)
file = @course.attachments.create!(
display_name: "hey.txt",
uploaded_data: default_uploaded_data,
usage_rights:
)
file.usage_rights
topic.attachment = file
topic.save!
get url
expect(element_exists?("#usage_rights_control i.icon-files-copyright")).to be(true)
end
end
context "in paced course" do
let(:topic) { assignment_topic }
before do
course.enable_course_paces = true
course.save!
context_module = course.context_modules.create! name: "M"
assignment_topic.context_module_tags.create! context_module:, context: @course, tag_type: "context_module"
end
it "shows the course pacing notice on a graded discussion" do
get url
expect(Discussion.course_pacing_notice).to be_displayed
end
it "does not show the course pacing notice on a graded discussion when feature off in account" do
course.account.disable_feature!(:course_paces)
get url
expect(element_exists?(Discussion.course_pacing_notice_selector)).to be_falsey
end
end
context "anonymous topic" do
let(:topic) { course.discussion_topics.create!(user: teacher, title: "anonymous topic title", message: "anonymous topic message", anonymous_state: "full_anonymity") }
before do
Account.site_admin.enable_feature! :react_discussions_post
@student = student_in_course(course: @course, active_all: true).user
end
it "able to save" do
get url
expect_new_page_load { f(".form-actions button[type=submit]").click }
expect(fj("span:contains('anonymous topic title')")).to be_present
end
it "able to save anon, not graded, quick added from assignments", :ignore_js_errors do
get "/courses/#{course.id}/assignments"
f(".add_assignment").click
click_option(f('[name="submission_types"]'), "Discussion")
f(".create_assignment_dialog input[type=text]").send_keys("anon disc from assignment")
f(".more_options").click
f("input[type=radio][value=partial_anonymity]").click
f("input#use_for_grading").click
expect_new_page_load { f("button.save_and_publish").click }
end
it "allow to change the anonymity if there is no reply" do
get url
expect(f("input[value='full_anonymity']").selected?).to be_truthy
force_click_native("input[value='partial_anonymity']")
expect_new_page_load { f(".form-actions button[type=submit]").click }
end
it "should not allow to change the anonymity when there are replys" do
topic.reply_from({ user: @student, text: "I feel pretty" })
get url
expect(ff("input[name='anonymous_state'][disabled]").count).to eq 3
end
end
end
end
context "when :discussion_create feature flag is ON", :ignore_js_errors do
before do
Account.site_admin.enable_feature!(:discussion_create)
Account.site_admin.enable_feature!(:react_discussions_post)
user_session(teacher)
end
context "ungraded" do
before do
attachment_model
# TODO: Update to cover: graded, group discussions, file attachment, any other options later implemented
all_discussion_options_enabled = {
title: "value for title",
message: "value for message",
is_anonymous_author: false,
anonymous_state: "full_anonymity",
todo_date: "Thu, 12 Oct 2023 15:59:59.000000000 UTC +00:00",
allow_rating: true,
only_graders_can_rate: true,
podcast_enabled: true,
podcast_has_student_posts: true,
require_initial_post: true,
discussion_type: "side_comment",
delayed_post_at: "Tue, 10 Oct 2023 16:00:00.000000000 UTC +00:00",
lock_at: "Wed, 11 Nov 2023 15:59:59.999999000 UTC +00:00",
is_section_specific: false,
attachment: @attachment
}
@topic_all_options = course.discussion_topics.create!(all_discussion_options_enabled)
@topic_no_options = course.discussion_topics.create!(title: "no options enabled - topic", message: "test")
end
it "displays all selected options correctly" do
get "/courses/#{course.id}/discussion_topics/#{@topic_all_options.id}/edit"
expect(f("input[value='full_anonymity']").selected?).to be_truthy
expect(f("input[value='full_anonymity']").attribute("disabled")).to be_nil
expect(f("input[value='must-respond-before-viewing-replies']").selected?).to be_truthy
expect(f("input[value='enable-podcast-feed']").selected?).to be_truthy
expect(f("input[value='include-student-replies-in-podcast-feed']").selected?).to be_truthy
expect(f("input[value='allow-liking']").selected?).to be_truthy
expect(f("input[value='only-graders-can-like']").selected?).to be_truthy
expect(f("input[value='add-to-student-to-do']").selected?).to be_truthy
# Just checking for a value. Formatting and TZ differences between front-end and back-end
# makes an exact comparison too fragile.
unless Account.site_admin.feature_enabled?(:selective_release_ui_api)
expect(ff("input[placeholder='Select Date']")[0].attribute("value")).to be_truthy
expect(ff("input[placeholder='Select Date']")[1].attribute("value")).to be_truthy
end
end
it "does not display the grading and groups not supported in anonymous discussions message in the edit page" do
get "/courses/#{course.id}/discussion_topics/#{@topic_all_options.id}/edit"
expect(f("input[value='full_anonymity']").selected?).to be_truthy
expect(f("input[value='full_anonymity']").attribute("disabled")).to be_nil
expect(f("body")).not_to contain_jqcss("[data-testid=groups_grading_not_allowed]")
end
it "displays all unselected options correctly" do
get "/courses/#{course.id}/discussion_topics/#{@topic_no_options.id}/edit"
expect(f("input[value='full_anonymity']").selected?).to be_falsey
expect(f("input[value='full_anonymity']").attribute("disabled")).to be_nil
# There are less checks here because certain options are only visible if their parent input is selected
expect(f("input[value='must-respond-before-viewing-replies']").selected?).to be_falsey
expect(f("input[value='enable-podcast-feed']").selected?).to be_falsey
expect(f("input[value='allow-liking']").selected?).to be_falsey
expect(f("input[value='add-to-student-to-do']").selected?).to be_falsey
# Just checking for a value. Formatting and TZ differences between front-end and back-end
# makes an exact comparison too fragile.
unless Account.site_admin.feature_enabled?(:selective_release_ui_api)
expect(ff("input[placeholder='Select Date']")[0].attribute("value")).to eq("")
expect(ff("input[placeholder='Select Date']")[1].attribute("value")).to eq("")
end
end
context "usage rights" do
before do
course.root_account.enable_feature!(:usage_rights_discussion_topics)
course.update!(usage_rights_required: true)
usage_rights = @course.usage_rights.create! use_justification: "creative_commons", legal_copyright: "(C) 2014 XYZ Corp", license: "cc_by_nd"
@attachment.usage_rights = usage_rights
@attachment.save!
end
it "displays correct usage rights" do
get "/courses/#{course.id}/discussion_topics/#{@topic_all_options.id}/edit"
expect(f("button[data-testid='usage-rights-icon']")).to be_truthy
f("button[data-testid='usage-rights-icon']").find_element(css: "svg")
# Verify that the correct icon appears
expect(f("button[data-testid='usage-rights-icon']").find_element(css: "svg").attribute("name")).to eq "IconFilesCreativeCommons"
f("button[data-testid='usage-rights-icon']").click
expect(f("input[data-testid='usage-select']").attribute("value")).to eq "The material is licensed under Creative Commons"
expect(f("input[data-testid='cc-license-select']").attribute("value")).to eq "CC Attribution No Derivatives"
expect(f("input[data-testid='legal-copyright']").attribute("value")).to eq "(C) 2014 XYZ Corp"
end
end
it "saves all changes correctly" do
get "/courses/#{course.id}/discussion_topics/#{@topic_all_options.id}/edit"
replace_content(f("input[placeholder='Topic Title']"), "new title", { tab_out: true })
clear_tiny(f("#discussion-topic-message-body"), "discussion-topic-message-body_ifr")
type_in_tiny("#discussion-topic-message-body", "new message")
driver.action.move_to(f("span[data-testid='removable-item']")).perform
f("[data-testid='remove-button']").click
_, fullpath, _data = get_file("testfile5.zip")
f("[data-testid='attachment-input']").send_keys(fullpath)
unless Account.site_admin.feature_enabled?(:selective_release_ui_api)
f("button[title='Remove All Sections']").click
f("input[data-testid='section-select']").click
fj("li:contains('value for name')").click
end
# we can change anonymity on edit, if there is no reply
expect(ffj("fieldset:contains('Anonymous Discussion') input[type=radio]").count).to eq 3
force_click_native("input[value='must-respond-before-viewing-replies']")
force_click_native("input[value='enable-podcast-feed']")
expect(f("body")).not_to contain_jqcss("input[value='include-student-replies-in-podcast-feed']")
force_click_native("input[value='allow-liking']")
expect(f("body")).not_to contain_jqcss("input[value='only-graders-can-like']")
force_click_native("input[value='add-to-student-to-do']")
fj("button:contains('Save')").click
@topic_all_options.reload
expect(@topic_all_options.title).to eq "new title"
expect(@topic_all_options.message).to include "new message"
expect(@topic_all_options.attachment_id).to eq Attachment.last.id
unless Account.site_admin.feature_enabled?(:selective_release_ui_api)
expect(@topic_all_options.is_section_specific).to be_truthy
end
expect(@topic_all_options.require_initial_post).to be_falsey
expect(@topic_all_options.podcast_enabled).to be_falsey
expect(@topic_all_options.allow_rating).to be_falsey
expect(@topic_all_options.only_graders_can_rate).to be_falsey
expect(@topic_all_options.todo_date).to be_nil
end
# ignore js errors in unrelated discussion show page
it "preserves URL query parameters on CANCEL", :ignore_js_errors do
get "/courses/#{course.id}/discussion_topics/#{@topic_all_options.id}/edit?embed=true"
fj("button:contains('Cancel')").click
wait_for_ajaximations
expect(driver.current_url).not_to include("edit")
expect(driver.current_url).to include("?embed=true")
end
context "with selective_release_backend and selective_release_ui_api enabled" do
before :once do
Account.site_admin.enable_feature!(:selective_release_backend)
Account.site_admin.enable_feature!(:selective_release_ui_api)
end
it "does not show the assign to UI when the user does not have permission even if user can access edit page" do
# i.e., they have moderate_forum permission but not admin or unrestricted student enrollment
RoleOverride.create!(context: @course.account, permission: "moderate_forum", role: student_role, enabled: true)
student_in_course(active_all: true)
user_session(@student)
get "/courses/#{course.id}/discussion_topics/#{@topic_no_options.id}/edit"
expect(element_exists?(Discussion.assign_to_button_selector)).to be_truthy
enrollment = @course.enrollments.find_by(user: @student)
enrollment.update!(limit_privileges_to_course_section: true)
get "/courses/#{course.id}/discussion_topics/#{@topic_no_options.id}/edit"
expect(element_exists?(Discussion.assign_to_button_selector)).to be_falsey
end
it "does not display 'Assign To' section for an ungraded group discussion" do
group = course.groups.create!(name: "group")
group_ungraded = course.discussion_topics.create!(title: "no options enabled - topic", group_category: group.group_category)
get "/courses/#{course.id}/discussion_topics/#{group_ungraded.id}/edit"
expect(Discussion.select_date_input_exists?).to be_truthy
expect(element_exists?(Discussion.assign_to_button_selector)).to be_falsey
end
it "does not display 'Post To' section and Available From/Until inputs" do
get "/courses/#{course.id}/discussion_topics/#{@topic_no_options.id}/edit"
expect(Discussion.select_date_input_exists?).to be_falsey
expect(Discussion.section_selection_input_exists?).to be_falsey
end
it "updates overrides using 'Assign To' tray", :ignore_js_errors do
student1 = course.enroll_student(User.create!, enrollment_state: "active").user
available_from = 5.days.ago
available_until = 5.days.from_now
get "/courses/#{course.id}/discussion_topics/#{@topic_no_options.id}/edit"
Discussion.click_assign_to_button
wait_for_assign_to_tray_spinner
keep_trying_until { expect(item_tray_exists?).to be_truthy }
click_add_assign_to_card
expect(element_exists?(due_date_input_selector)).to be_falsey
select_module_item_assignee(1, student1.name)
update_available_date(1, format_date_for_view(available_from, "%-m/%-d/%Y"), true)
update_available_time(1, "8:00 AM", true)
update_until_date(1, format_date_for_view(available_until, "%-m/%-d/%Y"), true)
update_until_time(1, "9:00 PM", true)
click_save_button("Apply")
keep_trying_until { expect(element_exists?(module_item_edit_tray_selector)).to be_falsey }
Discussion.save_button.click
wait_for_ajaximations
@topic_no_options.reload
new_override = @topic_no_options.active_assignment_overrides.last
expect(new_override.set_type).to eq("ADHOC")
expect(new_override.set_id).to be_nil
expect(new_override.set.map(&:id)).to match_array([student1.id])
end
it "shows pending changes when overrides have been added", :ignore_js_errors, custom_timeout: 45 do
student1 = course.enroll_student(User.create!, enrollment_state: "active").user
available_from = 5.days.ago
available_until = 5.days.from_now
get "/courses/#{course.id}/discussion_topics/#{@topic_no_options.id}/edit"
Discussion.click_assign_to_button
wait_for_assign_to_tray_spinner
keep_trying_until { expect(item_tray_exists?).to be_truthy }
click_add_assign_to_card
select_module_item_assignee(1, student1.name)
update_available_date(1, format_date_for_view(available_from, "%-m/%-d/%Y"), true)
update_available_time(1, "8:00 AM", true)
update_until_date(1, format_date_for_view(available_until, "%-m/%-d/%Y"), true)
update_until_time(1, "9:00 PM", true)
click_save_button("Apply")
keep_trying_until { expect(element_exists?(module_item_edit_tray_selector)).to be_falsey }
expect(Discussion.pending_changes_pill_exists?).to be_truthy
end
it "shows no pending changes when override tray cancelled", :ignore_js_errors do
student1 = course.enroll_student(User.create!, enrollment_state: "active").user
available_from = 5.days.ago
available_until = 5.days.from_now
get "/courses/#{course.id}/discussion_topics/#{@topic_no_options.id}/edit"
Discussion.click_assign_to_button
wait_for_assign_to_tray_spinner
keep_trying_until { expect(item_tray_exists?).to be_truthy }
click_add_assign_to_card
select_module_item_assignee(1, student1.name)
update_available_date(1, format_date_for_view(available_from, "%-m/%-d/%Y"), true)
update_available_time(1, "8:00 AM", true)
update_until_date(1, format_date_for_view(available_until, "%-m/%-d/%Y"), true)
update_until_time(1, "9:00 PM", true)
click_cancel_button
keep_trying_until { expect(element_exists?(module_item_edit_tray_selector)).to be_falsey }
expect(Discussion.pending_changes_pill_exists?).to be_falsey
end
it "transitions from ungraded to graded and overrides are ok", :ignore_js_errors do
discussion_topic = DiscussionHelpers.create_discussion_topic(
course,
teacher,
"Teacher Discussion 1 Title",
"Teacher Discussion 1 message",
nil
)
student1 = course.enroll_student(User.create!, enrollment_state: "active").user
available_from = 5.days.ago
available_until = 5.days.from_now
discussion_topic.assignment_overrides.create!(set_type: "ADHOC",
unlock_at: available_from,
lock_at: available_until)
discussion_topic.assignment_overrides.last.assignment_override_students.create!(user: student1)
get "/courses/#{course.id}/discussion_topics/#{discussion_topic.id}/edit"
expect(is_checked(Discussion.graded_checkbox)).to be_falsey
Discussion.click_graded_checkbox
Discussion.save_button.click
wait_for_ajaximations
get "/courses/#{course.id}/discussion_topics/#{discussion_topic.id}/edit"
Discussion.click_assign_to_button
wait_for_assign_to_tray_spinner
keep_trying_until { expect(item_tray_exists?).to be_truthy }
expect(assign_to_due_date(1).attribute("value")).to eq("")
expect(assign_to_due_time(1).attribute("value")).to eq("")
expect(assign_to_available_from_date(1).attribute("value")).to eq(format_date_for_view(available_from, "%b %-e, %Y"))
expect(assign_to_available_from_time(1).attribute("value")).to eq(available_from.strftime("%-l:%M %p"))
expect(assign_to_until_date(1).attribute("value")).to eq(format_date_for_view(available_until, "%b %-e, %Y"))
expect(assign_to_until_time(1).attribute("value")).to eq(available_until.strftime("%-l:%M %p"))
end
it "transitions from graded to ungraded and overrides are ok", :ignore_js_errors do
discussion_assignment_options = {
name: "assignment",
points_possible: 10,
}
discussion_topic = create_graded_discussion(course, discussion_assignment_options)
student1 = course.enroll_student(User.create!, enrollment_state: "active").user
due_at = 3.days.from_now
available_from = 5.days.ago
available_until = 5.days.from_now
@discussion_assignment.assignment_overrides.create!(set_type: "ADHOC",
due_at:,
unlock_at: available_from,
lock_at: available_until)
@discussion_assignment.assignment_overrides.last.assignment_override_students.create!(user: student1)
get "/courses/#{course.id}/discussion_topics/#{discussion_topic.id}/edit"
expect(is_checked(Discussion.graded_checkbox)).to be_truthy
Discussion.click_graded_checkbox
Discussion.save_button.click
wait_for_ajaximations
get "/courses/#{course.id}/discussion_topics/#{discussion_topic.id}/edit"
Discussion.click_assign_to_button
wait_for_assign_to_tray_spinner
keep_trying_until { expect(item_tray_exists?).to be_truthy }
expect(assign_to_date_and_time[1].text).not_to include("Due Date")
expect(assign_to_available_from_date(1, true).attribute("value")).to eq(format_date_for_view(available_from, "%b %-e, %Y"))
expect(assign_to_available_from_time(1, true).attribute("value")).to eq(available_from.strftime("%-l:%M %p"))
expect(assign_to_until_date(1, true).attribute("value")).to eq(format_date_for_view(available_until, "%b %-e, %Y"))
expect(assign_to_until_time(1, true).attribute("value")).to eq(available_until.strftime("%-l:%M %p"))
end
it "does not recover a deleted card when adding an assignee", :ignore_js_errors do
# Bug fix of LX-1619
student1 = course.enroll_student(User.create!, enrollment_state: "active").user
get "/courses/#{course.id}/discussion_topics/#{@topic_all_options.id}/edit"
Discussion.click_assign_to_button
wait_for_assign_to_tray_spinner
keep_trying_until { expect(item_tray_exists?).to be_truthy }
click_add_assign_to_card
click_delete_assign_to_card(0)
select_module_item_assignee(0, student1.name)
expect(selected_assignee_options.count).to be(1)
end
end
end
context "ungraded group" do
it "displays the selected group category correctly" do
group_category = course.group_categories.create!(name: "group category 1")
discussion_with_group_category = {
title: "value for title",
message: "value for message",
group_category_id: group_category.id,
}
group_topic = course.discussion_topics.create!(discussion_with_group_category)
group_topic.update!(group_category_id: group_category.id)
get "/courses/#{course.id}/discussion_topics/#{group_topic.id}/edit"
expect(f("input[value='group-discussion']").selected?).to be_truthy
expect(f("input[placeholder='Select a group category']").attribute("title")).to eq group_category.name
end
end
context "announcement" do
before do
# TODO: Update to cover: file attachments and any other options later implemented
all_announcement_options = {
title: "value for title",
message: "value for message",
delayed_post_at: "Thu, 16 Nov 2023 17:00:00.000000000 UTC +00:00",
podcast_enabled: true,
podcast_has_student_posts: true,
require_initial_post: true,
discussion_type: "side_comment",
allow_rating: true,
only_graders_can_rate: true,
locked: false,
}
@announcement_all_options = course.announcements.create!(all_announcement_options)
# In this case, locked: true displays itself as an unchecked "allow participants to comment" option
@announcement_no_options = course.announcements.create!({ title: "no options", message: "nothing else", locked: true })
end
it "displays all selected options correctly" do
get "/courses/#{course.id}/discussion_topics/#{@announcement_all_options.id}/edit"
expect(f("input[value='enable-participants-commenting']").selected?).to be_truthy
expect(f("input[value='allow-liking']").selected?).to be_truthy
expect(f("input[value='only-graders-can-like']").selected?).to be_truthy
expect(f("input[value='enable-podcast-feed']").selected?).to be_truthy
expect(f("input[value='include-student-replies-in-podcast-feed']").selected?).to be_truthy
# Just checking for a value. Formatting and TZ differences between front-end and back-end
# makes an exact comparison too fragile.
expect(ff("input[placeholder='Select Date']")[0].attribute("value")).to be_truthy
end
it "displays all unselected options correctly" do
get "/courses/#{course.id}/discussion_topics/#{@announcement_no_options.id}/edit"
expect(f("input[value='enable-participants-commenting']").selected?).to be_falsey
expect(f("input[value='allow-liking']").selected?).to be_falsey
expect(f("input[value='enable-podcast-feed']").selected?).to be_falsey
end
end
context "graded" do
it "displays graded assignment options correctly when initially opening edit page with archived grading schemes disabled" do
Account.site_admin.disable_feature!(:archived_grading_schemes)
grading_standard = course.grading_standards.create!(title: "Win/Lose", data: [["Winner", 0.94], ["Loser", 0]])
# Create a grading standard and make sure it is selected
discussion_assignment_options = {
name: "assignment",
points_possible: 10,
grading_type: "letter_grade",
assignment_group: course.assignment_groups.create!(name: "assignment group"),
grading_standard_id: grading_standard.id,
only_visible_to_overrides: true,
}
discussion_assignment_peer_review_options = {
peer_reviews: true,
automatic_peer_reviews: true,
peer_reviews_due_at: 1.day.ago,
peer_review_count: 2,
}
discussion_assignment_options = discussion_assignment_options.merge(discussion_assignment_peer_review_options)
graded_discussion = create_graded_discussion(course, discussion_assignment_options)
course_override_due_date = 5.days.from_now
course_section = course.course_sections.create!(name: "section alpha")
graded_discussion.assignment.assignment_overrides.create!(set_type: "CourseSection", set_id: course_section.id, due_at: course_override_due_date)
get "/courses/#{course.id}/discussion_topics/#{graded_discussion.id}/edit"
# Grading scheme sub menu is selected
expect(fj("span:contains('#{grading_standard.title}')").present?).to be_truthy
expect(fj("span:contains('Manage All Grading Schemes')").present?).to be_truthy
# Graded checkbox
expect(is_checked(f("input[data-testid='graded-checkbox']"))).to be_truthy
# Points possible
expect(f("input[data-testid='points-possible-input']").attribute("value")).to eq "10"
# Grading type
expect(f("input[data-testid='display-grade-input']").attribute("value")).to eq "Letter Grade"
# Assignment Group
expect(f("input[data-testid='assignment-group-input']").attribute("value")).to eq "assignment group"
# Peer review checkboxes
expect(is_checked(f("input[data-testid='peer_review_manual']"))).to be_falsey
expect(is_checked(f("input[data-testid='peer_review_off']"))).to be_falsey
expect(is_checked(f("input[data-testid='peer_review_auto']"))).to be_truthy
# peer review count
expect(f("input[data-testid='peer-review-count-input']").attribute("value")).to eq "2"
# Peer review date
# Just checking for a value. Formatting and TZ differences between front-end and back-end
# makes an exact comparison too fragile.
expect(ff("input[placeholder='Select Date']")[0].attribute("value")).not_to be_empty
if Account.site_admin.feature_enabled?(:selective_release_ui_api)
expect(Discussion.assign_to_button).to be_displayed
Discussion.assign_to_button.click
expect(assign_to_in_tray("Remove #{course_section.name}")[0]).to be_displayed
else
expect(f("span[data-testid='assign-to-select-span']").present?).to be_truthy
expect(fj("span:contains('#{course_section.name}')").present?).to be_truthy
# Verify that the only_visible_to_overrides field is being respected
expect(f("body")).not_to contain_jqcss("span:contains('Everyone')")
# Just checking for a value. Formatting and TZ differences between front-end and back-end
# makes an exact comparison too fragile.
expect(f("input[placeholder='Select Assignment Due Date']").attribute("value")).not_to be_empty
end
end
it "allows settings a graded discussion to an ungraded discussion" do
graded_discussion = create_graded_discussion(course)
get "/courses/#{course.id}/discussion_topics/#{graded_discussion.id}/edit"
# Uncheck the "graded" checkbox
force_click_native('input[type=checkbox][value="graded"]')
fj("button:contains('Save')").click
expect(DiscussionTopic.last.assignment).to be_nil
end
it "sets the mark important dates checkbox for discussion edit when differentiated modules ff is off" do
feature_setup
graded_discussion = create_graded_discussion(course)
course_override_due_date = 5.days.from_now
course_section = course.course_sections.create!(name: "section alpha")
graded_discussion.assignment.assignment_overrides.create!(set_type: "CourseSection", set_id: course_section.id, due_at: course_override_due_date)
get "/courses/#{course.id}/discussion_topics/#{graded_discussion.id}/edit"
expect(mark_important_dates).to be_displayed
scroll_to_element(mark_important_dates)
click_mark_important_dates
Discussion.save_button.click
wait_for_ajaximations
assignment = Assignment.last
expect(assignment.important_dates).to be(true)
end
context "with archived grading schemes enabled" do
before do
Account.site_admin.enable_feature!(:grading_scheme_updates)
Account.site_admin.enable_feature!(:archived_grading_schemes)
@course = course
@account = @course.account
@active_grading_standard = @course.grading_standards.create!(title: "Active Grading Scheme", data: { "A" => 0.9, "F" => 0 }, scaling_factor: 1.0, points_based: false, workflow_state: "active")
@archived_grading_standard = @course.grading_standards.create!(title: "Archived Grading Scheme", data: { "A" => 0.9, "F" => 0 }, scaling_factor: 1.0, points_based: false, workflow_state: "archived")
@account_grading_standard = @account.grading_standards.create!(title: "Account Grading Scheme", data: { "A" => 0.9, "F" => 0 }, scaling_factor: 1.0, points_based: false, workflow_state: "active")
discussion_assignment_options = {
name: "assignment",
points_possible: 10,
grading_type: "letter_grade",
assignment_group: course.assignment_groups.create!(name: "assignment group"),
only_visible_to_overrides: true,
}
@graded_discussion = create_graded_discussion(course, discussion_assignment_options)
@assignment = @graded_discussion.assignment
end
it "shows archived grading scheme if it is the course default twice, once to follow course default scheme and once to choose that scheme to use" do
@course.update!(grading_standard_id: @archived_grading_standard.id)
@course.reload
get "/courses/#{@course.id}/discussion_topics/#{@graded_discussion.id}/edit"
wait_for_ajaximations
expect(f("[data-testid='grading-schemes-selector-dropdown']").attribute("title")).to eq(@archived_grading_standard.title + " (course default)")
f("[data-testid='grading-schemes-selector-dropdown']").click
expect(f("[data-testid='grading-schemes-selector-option-#{@course.grading_standard.id}']")).to include_text(@course.grading_standard.title)
end
it "shows archived grading scheme if it is the current assignment grading standard" do
@assignment.update!(grading_standard_id: @archived_grading_standard.id)
@assignment.reload
get "/courses/#{@course.id}/discussion_topics/#{@graded_discussion.id}/edit"
wait_for_ajaximations
expect(f("[data-testid='grading-schemes-selector-dropdown']").attribute("title")).to eq(@archived_grading_standard.title)
end
it "removes grading schemes from dropdown after archiving them but still shows them upon reopening the modal" do
get "/courses/#{@course.id}/discussion_topics/#{@graded_discussion.id}/edit"
wait_for_ajaximations
f("[data-testid='grading-schemes-selector-dropdown']").click
expect(f("[data-testid='grading-schemes-selector-option-#{@active_grading_standard.id}']")).to be_present
f("[data-testid='manage-all-grading-schemes-button']").click
wait_for_ajaximations
f("[data-testid='grading-scheme-#{@active_grading_standard.id}-archive-button']").click
wait_for_ajaximations
f("[data-testid='manage-all-grading-schemes-close-button']").click
wait_for_ajaximations
f("[data-testid='grading-schemes-selector-dropdown']").click
expect(f("[data-testid='grading-schemes-selector-dropdown-form']")).not_to contain_css("[data-testid='grading-schemes-selector-option-#{@active_grading_standard.id}']")
f("[data-testid='manage-all-grading-schemes-button']").click
wait_for_ajaximations
expect(f("[data-testid='grading-scheme-row-#{@active_grading_standard.id}']").text).to be_present
end
it "shows all archived schemes in the manage grading schemes modal" do
archived_gs1 = @course.grading_standards.create!(title: "Archived Grading Scheme 1", data: { "A" => 0.9, "F" => 0 }, scaling_factor: 1.0, points_based: false, workflow_state: "archived")
archived_gs2 = @course.grading_standards.create!(title: "Archived Grading Scheme 2", data: { "A" => 0.9, "F" => 0 }, scaling_factor: 1.0, points_based: false, workflow_state: "archived")
archived_gs3 = @course.grading_standards.create!(title: "Archived Grading Scheme 3", data: { "A" => 0.9, "F" => 0 }, scaling_factor: 1.0, points_based: false, workflow_state: "archived")
get "/courses/#{@course.id}/discussion_topics/#{@graded_discussion.id}/edit"
wait_for_ajaximations
f("[data-testid='manage-all-grading-schemes-button']").click
wait_for_ajaximations
expect(f("[data-testid='grading-scheme-#{archived_gs1.id}-name']")).to include_text(archived_gs1.title)
expect(f("[data-testid='grading-scheme-#{archived_gs2.id}-name']")).to include_text(archived_gs2.title)
expect(f("[data-testid='grading-scheme-#{archived_gs3.id}-name']")).to include_text(archived_gs3.title)
end
it "will still show the assignment grading scheme if you archive it on the edit page in the management modal and persist on reload" do
@assignment.update!(grading_standard_id: @active_grading_standard.id)
@assignment.reload
get "/courses/#{@course.id}/discussion_topics/#{@graded_discussion.id}/edit"
wait_for_ajaximations
expect(f("[data-testid='grading-schemes-selector-dropdown']").attribute("title")).to eq(@active_grading_standard.title)
f("[data-testid='manage-all-grading-schemes-button']").click
wait_for_ajaximations
f("[data-testid='grading-scheme-#{@active_grading_standard.id}-archive-button']").click
wait_for_ajaximations
f("[data-testid='manage-all-grading-schemes-close-button']").click
wait_for_ajaximations
expect(f("[data-testid='grading-schemes-selector-dropdown']").attribute("title")).to eq(@active_grading_standard.title)
get "/courses/#{@course.id}/assignments/#{@assignment.id}/edit"
wait_for_ajaximations
expect(f("[data-testid='grading-schemes-selector-dropdown']").attribute("title")).to eq(@active_grading_standard.title)
end
it "creates a discussion topic with selected grading scheme/standard" do
grading_standard = @course.grading_standards.create!(title: "Win/Lose", data: [["Winner", 0.94], ["Loser", 0]])
get "/courses/#{@course.id}/discussion_topics/#{@graded_discussion.id}/edit"
wait_for_ajaximations
f("[data-testid='grading-schemes-selector-dropdown']").click
f("[data-testid='grading-schemes-selector-option-#{grading_standard.id}']").click
f("[data-testid='save-button']").click
wait_for_ajaximations
expect_new_page_load { f("[data-testid='continue-button']").click }
a = DiscussionTopic.last.assignment
expect(a.grading_standard_id).to eq grading_standard.id
end
end
it "allows editing the assignment group for the graded discussion" do
assign_group_2 = course.assignment_groups.create!(name: "Group 2")
get "/courses/#{course.id}/discussion_topics/#{assignment_topic.id}/edit"
force_click_native("input[title='assignment group']")
wait_for(method: nil, timeout: 3) { fj("li:contains('Group 2')").present? }
fj("li:contains('Group 2')").click
fj("button:contains('Save')").click
expect(assignment.reload.assignment_group_id).to eq assign_group_2.id
end
it "allows editing the points possible, grading type, group category, and peer review for the graded discussion" do
pp_string = "80"
group_cat = course.group_categories.create!(name: "another group set")
get "/courses/#{course.id}/discussion_topics/#{assignment_topic.id}/edit"
# change points possible from 10 to 80. selenium's clear method does not completely remove the previous value
# so we use backspace instead
pp_string.each_char { f("input[data-testid='points-possible-input']").send_keys(:backspace) }
f("input[data-testid='points-possible-input']").send_keys(pp_string)
force_click_native("input[value='Points']")
fj("li:contains('Letter Grade')").click
force_click_native("input[data-testid='peer_review_manual']")
force_click_native("input[data-testid='group-discussion-checkbox']")
force_click_native("input[placeholder='Select a group category']")
fj("li:contains('#{group_cat.name}')").click
fj("button:contains('Save')").click
updated_assignment = assignment.reload
expect(updated_assignment.points_possible).to eq 80
expect(updated_assignment.grading_type).to eq "letter_grade"
expect(updated_assignment.peer_reviews).to be true
expect(updated_assignment.automatic_peer_reviews).to be false
expect(updated_assignment.peer_reviews_assign_at).to be_nil
expect(assignment.effective_group_category_id).to eq group_cat.id
end
it "adds an attachment to a graded discussion" do
get "/courses/#{course.id}/discussion_topics/#{assignment_topic.id}/edit"
_filename, fullpath, _data = get_file("testfile5.zip")
f("input[data-testid='attachment-input']").send_keys(fullpath)
fj("button:contains('Save')").click
expect(assignment_topic.reload.attachment_id).to eq Attachment.last.id
end
context "differentiated modules Feature Flag", :ignore_js_errors do
before do
differentiated_modules_on
@student1 = student_in_course(course:, active_all: true).user
@student2 = student_in_course(course:, active_all: true).user
@course_section = course.course_sections.create!(name: "section alpha")
@course_section_2 = course.course_sections.create!(name: "section Beta")
end
it "displays Everyone correctly", custom_timeout: 30 do
graded_discussion = create_graded_discussion(course)
due_date = "Sat, 06 Apr 2024 00:00:00.000000000 UTC +00:00"
unlock_at = "Fri, 05 Apr 2024 00:00:00.000000000 UTC +00:00"
lock_at = "Sun, 07 Apr 2024 00:00:00.000000000 UTC +00:00"
graded_discussion.assignment.update!(due_at: due_date, unlock_at:, lock_at:)
# Open page and assignTo tray
get "/courses/#{course.id}/discussion_topics/#{graded_discussion.id}/edit"
Discussion.assign_to_button.click
# Expect check card and override count/content
expect(module_item_assign_to_card.count).to eq 1
expect(selected_assignee_options.count).to eq 1
expect(selected_assignee_options.first.find("span").text).to eq "Everyone"
# Find the date inputs, extract their values, combine date and time values, and parse into DateTime objects
displayed_override_dates = all_displayed_assign_to_date_and_time
# Check that the due dates are correctly displayed
expect(displayed_override_dates.include?(DateTime.parse(due_date))).to be_truthy
expect(displayed_override_dates.include?(DateTime.parse(unlock_at))).to be_truthy
expect(displayed_override_dates.include?(DateTime.parse(lock_at))).to be_truthy
end
it "displays everyone and section and student overrides correctly", custom_timeout: 30 do
graded_discussion = create_graded_discussion(course)
# Create overrides
# Card 1 = ["Everyone else"], Set by: only_visible_to_overrides: false
# Card 2
graded_discussion.assignment.assignment_overrides.create!(set_type: "CourseSection", set_id: @course_section.id)
# Card 3
graded_discussion.assignment.assignment_overrides.create!(set_type: "CourseSection", set_id: @course_section_2.id)
# Card 4
graded_discussion.assignment.assignment_overrides.create!(set_type: "ADHOC")
graded_discussion.assignment.assignment_overrides.last.assignment_override_students.create!(user: @student1)
graded_discussion.assignment.assignment_overrides.last.assignment_override_students.create!(user: @student2)
# Open edit page and AssignTo Tray
get "/courses/#{course.id}/discussion_topics/#{graded_discussion.id}/edit"
Discussion.assign_to_button.click
# Check that displayed cards and overrides are correct
expect(module_item_assign_to_card.count).to eq 4
displayed_overrides = module_item_assign_to_card.map do |card|
card.find_all(assignee_selected_option_selector).map(&:text)
end
expected_overrides = generate_expected_overrides(graded_discussion.assignment)
expect(displayed_overrides).to match_array(expected_overrides)
end
it "displays visible to overrides only correctly", custom_timeout: 30 do
# The main difference in this test is that only_visible_to_overrides is true
discussion_assignment_options = {
only_visible_to_overrides: true,
}
graded_discussion = create_graded_discussion(course, discussion_assignment_options)
# Create overrides
# Card 1
graded_discussion.assignment.assignment_overrides.create!(set_type: "CourseSection", set_id: @course_section.id)
# Card 2
graded_discussion.assignment.assignment_overrides.create!(set_type: "CourseSection", set_id: @course_section_2.id)
# Card 3
graded_discussion.assignment.assignment_overrides.create!(set_type: "ADHOC")
graded_discussion.assignment.assignment_overrides.last.assignment_override_students.create!(user: @student1)
graded_discussion.assignment.assignment_overrides.last.assignment_override_students.create!(user: @student2)
# Open edit page and AssignTo Tray
get "/courses/#{course.id}/discussion_topics/#{graded_discussion.id}/edit"
Discussion.assign_to_button.click
# Check that displayed cards and overrides are correct
expect(module_item_assign_to_card.count).to eq 3
displayed_overrides = module_item_assign_to_card.map do |card|
card.find_all(assignee_selected_option_selector).map(&:text)
end
expected_overrides = generate_expected_overrides(graded_discussion.assignment)
expect(displayed_overrides).to match_array(expected_overrides)
end
it "allows adding overrides" do
graded_discussion = create_graded_discussion(course)
due_date = "Sat, 06 Apr 2024 00:00:00.000000000 UTC +00:00"
unlock_at = "Fri, 05 Apr 2024 00:00:00.000000000 UTC +00:00"
lock_at = "Sun, 07 Apr 2024 00:00:00.000000000 UTC +00:00"
graded_discussion.assignment.update!(due_at: due_date, unlock_at:, lock_at:)
course.reload
# Open page and assignTo tray
get "/courses/#{course.id}/discussion_topics/#{graded_discussion.id}/edit"
Discussion.assign_to_button.click
wait_for_assign_to_tray_spinner
keep_trying_until { expect(item_tray_exists?).to be_truthy }
click_add_assign_to_card
select_module_item_assignee(1, @student1.name)
click_save_button("Apply")
keep_trying_until { expect(element_exists?(module_item_edit_tray_selector)).to be_falsey }
expect(AssignmentCreateEditPage.pending_changes_pill_exists?).to be_truthy
Discussion.save_button.click
wait_for_ajaximations
assignment = Assignment.last
expect(assignment.assignment_overrides.active.count).to eq 1
end
it "allows removing overrides" do
graded_discussion = create_graded_discussion(course)
due_date = "Sat, 06 Apr 2024 00:00:00.000000000 UTC +00:00"
unlock_at = "Fri, 05 Apr 2024 00:00:00.000000000 UTC +00:00"
lock_at = "Sun, 07 Apr 2024 00:00:00.000000000 UTC +00:00"
graded_discussion.assignment.update!(due_at: due_date, unlock_at:, lock_at:)
# Create section override
course_section = course.course_sections.create!(name: "section alpha")
graded_discussion.assignment.assignment_overrides.create!(set_type: "CourseSection", set_id: course_section.id)
course.reload
# Open page and assignTo tray
get "/courses/#{course.id}/discussion_topics/#{graded_discussion.id}/edit"
Discussion.assign_to_button.click
wait_for_assign_to_tray_spinner
keep_trying_until { expect(item_tray_exists?).to be_truthy }
# Remove the section override
click_delete_assign_to_card(1)
click_save_button("Apply")
keep_trying_until { expect(element_exists?(module_item_edit_tray_selector)).to be_falsey }
expect(AssignmentCreateEditPage.pending_changes_pill_exists?).to be_truthy
Discussion.save_button.click
wait_for_ajaximations
assignment = Assignment.last
assignment.reload
expect(assignment.assignment_overrides.active.count).to eq 0
expect(assignment.only_visible_to_overrides).to be_falsey
end
it "displays module overrides correctly" do
graded_discussion = create_graded_discussion(course)
module1 = course.context_modules.create!(name: "Module 1")
graded_discussion.context_module_tags.create! context_module: module1, context: course, tag_type: "context_module"
override = module1.assignment_overrides.create!
override.assignment_override_students.create!(user: @student1)
# Open page and assignTo tray
get "/courses/#{course.id}/discussion_topics/#{graded_discussion.id}/edit"
Discussion.assign_to_button.click
wait_for_assign_to_tray_spinner
# Verify that Everyone tag does not appear
expect(module_item_assign_to_card.count).to eq 1
expect(module_item_assign_to_card[0].find_all(assignee_selected_option_selector).map(&:text)).to eq ["User"]
expect(inherited_from.last.text).to eq("Inherited from #{module1.name}")
# Update the inherited card due date, should remove inherited
update_due_date(0, "12/31/2022")
update_due_time(0, "5:00 PM")
click_save_button("Apply")
Discussion.assign_to_button.click
expect(f("body")).not_to contain_jqcss(inherited_from_selector)
click_save_button("Apply")
Discussion.save_button.click
Discussion.section_warning_continue_button.click
wait_for_ajaximations
# Expect the module override to be overridden and not appear
get "/courses/#{course.id}/discussion_topics/#{graded_discussion.id}/edit"
Discussion.assign_to_button.click
expect(module_item_assign_to_card.count).to eq 1
expect(f("body")).not_to contain_jqcss(inherited_from_selector)
end
it "displays module and course overrides correctly" do
graded_discussion = create_graded_discussion(course)
module1 = course.context_modules.create!(name: "Module 1")
graded_discussion.context_module_tags.create! context_module: module1, context: course, tag_type: "context_module"
override = module1.assignment_overrides.create!
override.assignment_override_students.create!(user: @student1)
graded_discussion.assignment.assignment_overrides.create!(set: course, due_at: 1.day.from_now)
# Open page and assignTo tray
get "/courses/#{course.id}/discussion_topics/#{graded_discussion.id}/edit"
Discussion.assign_to_button.click
wait_for_assign_to_tray_spinner
# Verify that Everyone tag does not appear
expect(module_item_assign_to_card.count).to eq 2
expect(module_item_assign_to_card[0].find_all(assignee_selected_option_selector).map(&:text)).to eq ["Everyone else"]
expect(module_item_assign_to_card[1].find_all(assignee_selected_option_selector).map(&:text)).to eq ["User"]
expect(inherited_from.last.text).to eq("Inherited from #{module1.name}")
end
it "creates a course override if everyone is added with a module override" do
graded_discussion = create_graded_discussion(course)
module1 = course.context_modules.create!(name: "Module 1")
graded_discussion.context_module_tags.create! context_module: module1, context: course, tag_type: "context_module"
override = module1.assignment_overrides.create!
override.assignment_override_students.create!(user: @student1)
# Open page and assignTo tray
get "/courses/#{course.id}/discussion_topics/#{graded_discussion.id}/edit"
Discussion.assign_to_button.click
wait_for_assign_to_tray_spinner
# Verify the module override is shown
expect(module_item_assign_to_card.count).to eq 1
expect(module_item_assign_to_card[0].find_all(assignee_selected_option_selector).map(&:text)).to eq ["User"]
expect(inherited_from.last.text).to eq("Inherited from #{module1.name}")
click_add_assign_to_card
select_module_item_assignee(1, "Everyone else")
click_save_button("Apply")
# Save the discussion without changing the inherited module override
Discussion.save_button.click
Discussion.section_warning_continue_button.click
wait_for_ajaximations
assignment = graded_discussion.assignment
assignment.reload
# Expect the existing override to be the module override
expect(assignment.assignment_overrides.active.count).to eq 1
expect(assignment.all_assignment_overrides.active.count).to eq 2
expect(assignment.assignment_overrides.first.set_type).to eq "Course"
expect(assignment.only_visible_to_overrides).to be_truthy
end
it "does not display module override if an unassigned override exists" do
graded_discussion = create_graded_discussion(course)
module1 = course.context_modules.create!(name: "Module 1")
graded_discussion.context_module_tags.create! context_module: module1, context: course, tag_type: "context_module"
override = module1.assignment_overrides.create!
override.assignment_override_students.create!(user: @student1)
unassigned_override = graded_discussion.assignment.assignment_overrides.create!
unassigned_override.assignment_override_students.create!(user: @student1)
unassigned_override.update(unassign_item: true)
assigned_override = graded_discussion.assignment.assignment_overrides.create!
assigned_override.assignment_override_students.create!(user: @student2)
# Open page and assignTo tray
get "/courses/#{course.id}/discussion_topics/#{graded_discussion.id}/edit"
Discussion.assign_to_button.click
wait_for_assign_to_tray_spinner
# Verify the module override is not shown
expect(module_item_assign_to_card.count).to eq 1
expect(module_item_assign_to_card[0].find_all(assignee_selected_option_selector).map(&:text)).to eq ["User"]
expect(module_item_assign_to_card[0]).not_to contain_css(inherited_from_selector)
end
it "does not create an override if the modules override is not updated" do
graded_discussion = create_graded_discussion(course)
module1 = course.context_modules.create!(name: "Module 1")
graded_discussion.context_module_tags.create! context_module: module1, context: course, tag_type: "context_module"
override = module1.assignment_overrides.create!
override.assignment_override_students.create!(user: @student1)
# Open page and assignTo tray
get "/courses/#{course.id}/discussion_topics/#{graded_discussion.id}/edit"
Discussion.assign_to_button.click
wait_for_assign_to_tray_spinner
# Verify the module override is shown
expect(module_item_assign_to_card.count).to eq 1
expect(module_item_assign_to_card[0].find_all(assignee_selected_option_selector).map(&:text)).to eq ["User"]
expect(inherited_from.last.text).to eq("Inherited from #{module1.name}")
click_save_button("Apply")
# Save the discussion without changing the inherited module override
Discussion.save_button.click
Discussion.section_warning_continue_button.click
wait_for_ajaximations
assignment = graded_discussion.assignment
assignment.reload
# Expect the existing override to be the module override
expect(assignment.assignment_overrides.active.count).to eq 0
expect(assignment.all_assignment_overrides.active.count).to eq 1
expect(assignment.all_assignment_overrides.first.context_module_id).to eq module1.id
expect(assignment.only_visible_to_overrides).to be_falsey
end
it "displays highighted cards correctly" do
graded_discussion = create_graded_discussion(course)
get "/courses/#{course.id}/discussion_topics/#{graded_discussion.id}/edit"
Discussion.assign_to_button.click
wait_for_assign_to_tray_spinner
# Expect there to be no highlighted cards
expect(module_item_assign_to_card.count).to eq 1
expect(f("body")).not_to contain_jqcss(highlighted_card_selector)
click_save_button("Apply")
# Expect that if no changes were made, that the apply button doens't highlight old cards
Discussion.assign_to_button.click
wait_for_assign_to_tray_spinner
expect(module_item_assign_to_card.count).to eq 1
expect(f("body")).not_to contain_jqcss(highlighted_card_selector)
# Expect highlighted card after making a change
update_due_date(0, "12/31/2022")
click_save_button("Apply")
Discussion.assign_to_button.click
wait_for_assign_to_tray_spinner
expect(highlighted_item_assign_to_card.count).to eq 1
end
it "cancels correctly" do
graded_discussion = create_graded_discussion(course)
# Open page and assignTo tray
get "/courses/#{course.id}/discussion_topics/#{graded_discussion.id}/edit"
Discussion.assign_to_button.click
wait_for_assign_to_tray_spinner
# Create a new card, don't apply it
click_add_assign_to_card
select_module_item_assignee(1, @student1.name)
expect(module_item_assign_to_card.count).to eq 2
cancel_button.click
# Reopen, expect new card to not be there
Discussion.assign_to_button.click
expect(module_item_assign_to_card.count).to eq 1
# Add a new card, apply it
click_add_assign_to_card
select_module_item_assignee(1, @student1.name)
click_save_button("Apply")
expect(AssignmentCreateEditPage.pending_changes_pill_exists?).to be_truthy
# Expect both cards to be there
Discussion.assign_to_button.click
expect(module_item_assign_to_card.count).to eq 2
end
it "sets the mark important dates checkbox for discussion edit" do
feature_setup
graded_discussion = create_graded_discussion(course)
get "/courses/#{course.id}/discussion_topics/#{graded_discussion.id}/edit"
Discussion.assign_to_button.click
wait_for_assign_to_tray_spinner
keep_trying_until { expect(item_tray_exists?).to be_truthy }
formatted_date = format_date_for_view(2.days.from_now(Time.zone.now), "%m/%d/%Y")
update_due_date(0, formatted_date)
update_due_time(0, "5:00 PM")
click_save_button("Apply")
expect(mark_important_dates).to be_displayed
scroll_to_element(mark_important_dates)
click_mark_important_dates
Discussion.save_button.click
wait_for_ajaximations
assignment = Assignment.last
expect(assignment.important_dates).to be(true)
end
it "does not show the assign to UI when the user does not have permission even if user can access edit page" do
# i.e., they have moderate_forum permission but not manage_assignments_edit
discussion = create_graded_discussion(course)
get "/courses/#{course.id}/discussion_topics/#{discussion.id}/edit"
expect(element_exists?(Discussion.assign_to_button_selector)).to be_truthy
RoleOverride.create!(context: @course.account, permission: "manage_assignments_edit", role: teacher_role, enabled: false)
get "/courses/#{course.id}/discussion_topics/#{discussion.id}/edit"
expect(element_exists?(Discussion.assign_to_button_selector)).to be_falsey
end
it "does not recover a deleted card when adding an assignee", :ignore_js_errors do
# Bug fix of LX-1619
discussion = create_graded_discussion(course)
get "/courses/#{course.id}/discussion_topics/#{discussion.id}/edit"
Discussion.click_assign_to_button
wait_for_assign_to_tray_spinner
keep_trying_until { expect(item_tray_exists?).to be_truthy }
click_add_assign_to_card
click_delete_assign_to_card(0)
select_module_item_assignee(0, @course_section_2.name)
expect(selected_assignee_options.count).to be(1)
end
context "checkpoints" do
it "shows reply to topic input on graded discussion with sub assignments" do
Account.site_admin.enable_feature!(:discussion_checkpoints)
@course.root_account.enable_feature!(:discussion_checkpoints)
assignment = @course.assignments.create!(
name: "Assignment",
submission_types: ["online_text_entry"],
points_possible: 20
)
assignment.update!(has_sub_assignments: true)
assignment.sub_assignments.create!(context: assignment.context, sub_assignment_tag: CheckpointLabels::REPLY_TO_TOPIC, points_possible: 10, due_at: 3.days.from_now)
assignment.sub_assignments.create!(context: assignment.context, sub_assignment_tag: CheckpointLabels::REPLY_TO_ENTRY, points_possible: 10, due_at: 5.days.from_now)
graded_discussion = @course.discussion_topics.create!(
title: "Graded Discussion",
discussion_type: "threaded",
posted_at: "2017-07-09 16:32:34",
user: @teacher,
assignment:,
reply_to_entry_required_count: 1
)
# Open page and assignTo tray
get "/courses/#{@course.id}/discussion_topics/#{graded_discussion.id}/edit"
Discussion.assign_to_button.click
wait_for_assign_to_tray_spinner
expect(module_item_assign_to_card.last).to contain_css(reply_to_topic_due_date_input_selector)
end
it "shows required replies input on graded discussion with sub assignments" do
Account.site_admin.enable_feature!(:discussion_checkpoints)
@course.root_account.enable_feature!(:discussion_checkpoints)
@student1 = student_in_course(course:, active_all: true).user
@student2 = student_in_course(course:, active_all: true).user
@course_section = course.course_sections.create!(name: "section alpha")
@course_section_2 = course.course_sections.create!(name: "section Beta")
# Open page and assignTo tray
get "/courses/#{@course.id}/discussion_topics/new"
title = "Graded Discussion Topic with letter grade type"
message = "replying to topic"
f("input[placeholder='Topic Title']").send_keys title
type_in_tiny("textarea", message)
force_click_native('input[type=checkbox][value="graded"]')
force_click_native('input[type=checkbox][value="checkpoints"]')
Discussion.assign_to_button.click
click_add_assign_to_card
click_delete_assign_to_card(0)
select_module_item_assignee(0, @course_section_2.name)
reply_to_topic_date = 3.days.from_now(Time.zone.now).to_date + 17.hours
reply_to_topic_date_formatted = format_date_for_view(reply_to_topic_date, "%m/%d/%Y")
update_reply_to_topic_date(0, reply_to_topic_date_formatted)
update_reply_to_topic_time(0, "5:00 PM")
# required replies
required_replies_date = 4.days.from_now(Time.zone.now).to_date + 17.hours
required_replies_date_formatted = format_date_for_view(required_replies_date, "%m/%d/%Y")
update_required_replies_date(0, required_replies_date_formatted)
update_required_replies_time(0, "5:00 PM")
# available from
available_from_date = 2.days.from_now(Time.zone.now).to_date + 17.hours
available_from_date_formatted = format_date_for_view(available_from_date, "%m/%d/%Y")
update_available_date(0, available_from_date_formatted, true, false)
update_available_time(0, "5:00 PM", true, false)
# available until
until_date = 5.days.from_now(Time.zone.now).to_date + 17.hours
until_date_formatted = format_date_for_view(until_date, "%m/%d/%Y")
update_until_date(0, until_date_formatted, true, false)
update_until_time(0, "5:00 PM", true, false)
click_save_button("Apply")
wait_for_assign_to_tray_spinner
fj("button:contains('Save')").click
Discussion.section_warning_continue_button.click
wait_for_ajaximations
graded_discussion = DiscussionTopic.last
sub_assignments = graded_discussion.assignment.sub_assignments
sub_assignment1 = sub_assignments.find_by(sub_assignment_tag: CheckpointLabels::REPLY_TO_TOPIC)
sub_assignment2 = sub_assignments.find_by(sub_assignment_tag: CheckpointLabels::REPLY_TO_ENTRY)
expect(graded_discussion.assignment.sub_assignments.count).to eq(2)
expect(format_date_for_view(sub_assignment1.assignment_overrides.active.first.due_at, "%m/%d/%Y")).to eq(reply_to_topic_date_formatted)
expect(format_date_for_view(sub_assignment2.assignment_overrides.active.first.due_at, "%m/%d/%Y")).to eq(required_replies_date_formatted)
# renders update
get "/courses/#{@course.id}/discussion_topics/#{graded_discussion.id}/edit"
Discussion.assign_to_button.click
displayed_override_dates = all_displayed_assign_to_date_and_time
# Check that the due dates are correctly displayed
expect(displayed_override_dates.include?(reply_to_topic_date)).to be_truthy
expect(displayed_override_dates.include?(required_replies_date)).to be_truthy
expect(displayed_override_dates.include?(available_from_date)).to be_truthy
expect(displayed_override_dates.include?(until_date)).to be_truthy
# updates dates and saves
reply_to_topic_date = 4.days.from_now(Time.zone.now).to_date + 17.hours
reply_to_topic_date_formatted = format_date_for_view(reply_to_topic_date, "%m/%d/%Y")
update_reply_to_topic_date(0, reply_to_topic_date_formatted)
update_reply_to_topic_time(0, "5:00 PM")
# required replies
required_replies_date = 5.days.from_now(Time.zone.now).to_date + 17.hours
required_replies_date_formatted = format_date_for_view(required_replies_date, "%m/%d/%Y")
update_required_replies_date(0, required_replies_date_formatted)
update_required_replies_time(0, "5:00 PM")
# available from
available_from_date = 3.days.from_now(Time.zone.now).to_date + 17.hours
available_from_date_formatted = format_date_for_view(available_from_date, "%m/%d/%Y")
update_available_date(0, available_from_date_formatted, true, false)
update_available_time(0, "5:00 PM", true, false)
# available until
until_date = 6.days.from_now(Time.zone.now).to_date + 17.hours
until_date_formatted = format_date_for_view(until_date, "%m/%d/%Y")
update_until_date(0, until_date_formatted, true, false)
update_until_time(0, "5:00 PM", true, false)
click_save_button("Apply")
wait_for_assign_to_tray_spinner
fj("button:contains('Save')").click
Discussion.section_warning_continue_button.click
wait_for_ajaximations
graded_discussion.reload
sub_assignments = graded_discussion.assignment.sub_assignments
sub_assignment1 = sub_assignments.find_by(sub_assignment_tag: CheckpointLabels::REPLY_TO_TOPIC)
sub_assignment2 = sub_assignments.find_by(sub_assignment_tag: CheckpointLabels::REPLY_TO_ENTRY)
expect(graded_discussion.assignment.sub_assignments.count).to eq(2)
expect(format_date_for_view(sub_assignment1.assignment_overrides.active.first.due_at, "%m/%d/%Y")).to eq(reply_to_topic_date_formatted)
expect(format_date_for_view(sub_assignment2.assignment_overrides.active.first.due_at, "%m/%d/%Y")).to eq(required_replies_date_formatted)
end
it "displays an error when the availability date is after the due date" do
Account.site_admin.enable_feature!(:discussion_checkpoints)
@course.root_account.enable_feature!(:discussion_checkpoints)
assignment = @course.assignments.create!(
name: "Assignment",
submission_types: ["online_text_entry"],
points_possible: 20
)
assignment.update!(has_sub_assignments: true)
assignment.sub_assignments.create!(context: assignment.context, sub_assignment_tag: CheckpointLabels::REPLY_TO_TOPIC, points_possible: 10, due_at: 3.days.from_now)
assignment.sub_assignments.create!(context: assignment.context, sub_assignment_tag: CheckpointLabels::REPLY_TO_ENTRY, points_possible: 10, due_at: 5.days.from_now)
graded_discussion = @course.discussion_topics.create!(
title: "Graded Discussion",
discussion_type: "threaded",
posted_at: "2017-07-09 16:32:34",
user: @teacher,
assignment:,
reply_to_entry_required_count: 1
)
# Open page and assignTo tray
get "/courses/#{@course.id}/discussion_topics/#{graded_discussion.id}/edit"
Discussion.assign_to_button.click
reply_to_topic_date_formatted = format_date_for_view(1.day.from_now(Time.zone.now).to_date, "%m/%d/%Y")
update_reply_to_topic_date(0, reply_to_topic_date_formatted)
update_reply_to_topic_time(0, "5:00 PM")
# available from
available_from_date_formatted = format_date_for_view(2.days.from_now(Time.zone.now).to_date, "%m/%d/%Y")
update_available_date(0, available_from_date_formatted, true, false)
update_available_time(0, "5:00 PM", true, false)
expect(assign_to_date_and_time[2].text).to include("Unlock date cannot be after reply to topic due date")
# correct reply to topic
reply_to_topic_date_formatted = format_date_for_view(3.days.from_now(Time.zone.now).to_date, "%m/%d/%Y")
update_reply_to_topic_date(0, reply_to_topic_date_formatted)
update_reply_to_topic_time(0, "5:00 PM")
# required replies
required_replies_date_formatted = format_date_for_view(1.day.from_now(Time.zone.now).to_date, "%m/%d/%Y")
update_required_replies_date(0, required_replies_date_formatted)
update_required_replies_time(0, "5:00 PM")
expect(assign_to_date_and_time[2].text).to include("Unlock date cannot be after required replies due date")
# available until
until_date = 5.days.from_now(Time.zone.now).to_date + 17.hours
until_date_formatted = format_date_for_view(until_date, "%m/%d/%Y")
update_until_date(0, until_date_formatted, true, false)
update_until_time(0, "5:00 PM", true, false)
reply_to_topic_date_formatted = format_date_for_view(6.days.from_now(Time.zone.now).to_date, "%m/%d/%Y")
update_reply_to_topic_date(0, reply_to_topic_date_formatted)
update_reply_to_topic_time(0, "5:00 PM")
expect(assign_to_date_and_time[3].text).to include("Lock date cannot be before reply to topic due date")
# correct reply to topic
reply_to_topic_date_formatted = format_date_for_view(3.days.from_now(Time.zone.now).to_date, "%m/%d/%Y")
update_reply_to_topic_date(0, reply_to_topic_date_formatted)
update_reply_to_topic_time(0, "5:00 PM")
# required replies
required_replies_date_formatted = format_date_for_view(6.days.from_now(Time.zone.now).to_date, "%m/%d/%Y")
update_required_replies_date(0, required_replies_date_formatted)
update_required_replies_time(0, "5:00 PM")
expect(assign_to_date_and_time[3].text).to include("Lock date cannot be before required replies due date")
end
end
context "post to sis" do
before do
course.account.set_feature_flag! "post_grades", "on"
course.account.set_feature_flag! :new_sis_integrations, "on"
course.account.settings[:sis_syncing] = { value: true, locked: false }
course.account.settings[:sis_require_assignment_due_date] = { value: true }
course.account.save!
end
it "blocks when enabled", :ignore_js_errors do
graded_discussion = create_graded_discussion(course)
get "/courses/#{course.id}/discussion_topics/#{graded_discussion.id}/edit"
Discussion.click_sync_to_sis_checkbox
Discussion.save_button.click
wait_for_ajaximations
expect(driver.current_url).to include("edit")
expect_instui_flash_message("Please set a due date or change your selection for the “Sync to SIS” option.")
Discussion.click_assign_to_button
wait_for_assign_to_tray_spinner
keep_trying_until { expect(item_tray_exists?).to be_truthy }
expect(assign_to_date_and_time[0].text).to include("Please add a due date")
update_due_date(0, format_date_for_view(Time.zone.now, "%-m/%-d/%Y"))
update_due_time(0, "11:59 PM")
click_save_button("Apply")
keep_trying_until { expect(element_exists?(module_item_edit_tray_selector)).to be_falsey }
expect_new_page_load { Discussion.save_button.click }
expect(driver.current_url).not_to include("edit")
expect(graded_discussion.reload.assignment.post_to_sis).to be_truthy
get "/courses/#{course.id}/discussion_topics/#{graded_discussion.id}/edit"
expect(is_checked(Discussion.sync_to_sis_checkbox_selector)).to be_truthy
end
it "does not block when disabled" do
graded_discussion = create_graded_discussion(course)
get "/courses/#{course.id}/discussion_topics/#{graded_discussion.id}/edit"
expect_new_page_load { Discussion.save_button.click }
expect(driver.current_url).not_to include("edit")
expect(graded_discussion.reload.assignment.post_to_sis).to be_falsey
get "/courses/#{course.id}/discussion_topics/#{graded_discussion.id}/edit"
expect(is_checked(Discussion.sync_to_sis_checkbox_selector)).to be_falsey
end
it "validates due date when user checks/unchecks the box", :ignore_js_errors do
graded_discussion = create_graded_discussion(course)
get "/courses/#{course.id}/discussion_topics/#{graded_discussion.id}/edit"
Discussion.click_assign_to_button
wait_for_assign_to_tray_spinner
keep_trying_until { expect(item_tray_exists?).to be_truthy }
expect(assign_to_date_and_time[0].text).not_to include("Please add a due date")
click_cancel_button
keep_trying_until { expect(element_exists?(module_item_edit_tray_selector)).to be_falsey }
Discussion.click_sync_to_sis_checkbox
Discussion.click_assign_to_button
wait_for_assign_to_tray_spinner
keep_trying_until { expect(item_tray_exists?).to be_truthy }
expect(assign_to_date_and_time[0].text).to include("Please add a due date")
update_due_date(0, format_date_for_view(Time.zone.now, "%-m/%-d/%Y"))
update_due_time(0, "11:59 PM")
click_save_button("Apply")
keep_trying_until { expect(element_exists?(module_item_edit_tray_selector)).to be_falsey }
expect_new_page_load { Discussion.save_button.click }
expect(driver.current_url).not_to include("edit")
expect(graded_discussion.reload.assignment.post_to_sis).to be_truthy
end
end
end
context "checkpoints" do
before do
course.root_account.enable_feature!(:discussion_checkpoints)
@checkpointed_discussion = DiscussionTopic.create_graded_topic!(course:, title: "checkpointed discussion")
Checkpoints::DiscussionCheckpointCreatorService.call(
discussion_topic: @checkpointed_discussion,
checkpoint_label: CheckpointLabels::REPLY_TO_TOPIC,
dates: [{ type: "everyone", due_at: 2.days.from_now }],
points_possible: 6
)
Checkpoints::DiscussionCheckpointCreatorService.call(
discussion_topic: @checkpointed_discussion,
checkpoint_label: CheckpointLabels::REPLY_TO_ENTRY,
dates: [{ type: "everyone", due_at: 2.days.from_now }],
points_possible: 7,
replies_required: 5
)
end
it "displays checkpoint settings values correctly when there are existing checkpoints" do
get "/courses/#{course.id}/discussion_topics/#{@checkpointed_discussion.id}/edit"
expect(f("input[data-testid='points-possible-input-reply-to-topic']").attribute("value")).to eq "6"
expect(f("input[data-testid='points-possible-input-reply-to-entry']").attribute("value")).to eq "7"
expect(f("input[data-testid='reply-to-entry-required-count']").attribute("value")).to eq "5"
end
it "allows for a discussion with checkpoints to be updated" do
get "/courses/#{course.id}/discussion_topics/#{@checkpointed_discussion.id}/edit"
f("input[data-testid='points-possible-input-reply-to-topic']").send_keys :backspace
f("input[data-testid='points-possible-input-reply-to-topic']").send_keys "5"
f("input[data-testid='reply-to-entry-required-count']").send_keys :backspace
f("input[data-testid='reply-to-entry-required-count']").send_keys "6"
f("input[data-testid='points-possible-input-reply-to-entry']").send_keys :backspace
f("input[data-testid='points-possible-input-reply-to-entry']").send_keys "7"
fj("button:contains('Save')").click
expect(DiscussionTopic.last.reply_to_entry_required_count).to eq 6
assignment = Assignment.last
sub_assignments = SubAssignment.where(parent_assignment_id: assignment.id)
sub_assignment1 = sub_assignments.find_by(sub_assignment_tag: CheckpointLabels::REPLY_TO_TOPIC)
sub_assignment2 = sub_assignments.find_by(sub_assignment_tag: CheckpointLabels::REPLY_TO_ENTRY)
expect(sub_assignment1.points_possible).to eq 5
expect(sub_assignment2.points_possible).to eq 7
end
it "deletes checkpoints if the checkpoint checkbox is unselected on an existing discussion with checkpoints" do
assignment = Assignment.last
expect(assignment.sub_assignments.count).to eq 2
get "/courses/#{course.id}/discussion_topics/#{@checkpointed_discussion.id}/edit"
force_click_native('input[type=checkbox][value="checkpoints"]')
fj("button:contains('Save')").click
expect(DiscussionTopic.last.reply_to_entry_required_count).to eq 0
expect(assignment.sub_assignments.count).to eq 0
expect(Assignment.last.has_sub_assignments).to be(false)
end
it "can edit a non-checkpointed discussion into a checkpointed discussion" do
graded_discussion = create_graded_discussion(course)
get "/courses/#{course.id}/discussion_topics/#{graded_discussion.id}/edit"
force_click_native('input[type=checkbox][value="checkpoints"]')
f("input[data-testid='points-possible-input-reply-to-topic']").send_keys :backspace
f("input[data-testid='points-possible-input-reply-to-topic']").send_keys "5"
f("input[data-testid='reply-to-entry-required-count']").send_keys :backspace
f("input[data-testid='reply-to-entry-required-count']").send_keys "6"
f("input[data-testid='points-possible-input-reply-to-entry']").send_keys :backspace
f("input[data-testid='points-possible-input-reply-to-entry']").send_keys "7"
fj("button:contains('Save')").click
assignment = Assignment.last
expect(assignment.has_sub_assignments?).to be true
expect(DiscussionTopic.last.reply_to_entry_required_count).to eq 6
sub_assignments = SubAssignment.where(parent_assignment_id: assignment.id)
sub_assignment1 = sub_assignments.find_by(sub_assignment_tag: CheckpointLabels::REPLY_TO_TOPIC)
sub_assignment2 = sub_assignments.find_by(sub_assignment_tag: CheckpointLabels::REPLY_TO_ENTRY)
expect(sub_assignment1.points_possible).to eq 5
expect(sub_assignment2.points_possible).to eq 7
end
it "deletes checkpoints if the graded checkbox is unselected on an exisitng discussion with checkpoints" do
assignment = Assignment.last
expect(assignment.sub_assignments.count).to eq 2
get "/courses/#{course.id}/discussion_topics/#{@checkpointed_discussion.id}/edit"
# Uncheck the "graded" checkbox
force_click_native('input[type=checkbox][value="graded"]')
fj("button:contains('Save')").click
# Expect the assignment an the checkpoints to no longer exist
expect(DiscussionTopic.last.reply_to_entry_required_count).to eq 0
expect(assignment.sub_assignments.count).to eq 0
expect(Assignment.last.has_sub_assignments).to be(false)
expect(DiscussionTopic.last.assignment).to be_nil
end
end
context "mastery paths aka cyoe ake conditional release" do
def create_assignment(course, title, points_possible = 10)
course.assignments.create!(
title: "#{title} #{SecureRandom.alphanumeric(10)}",
description: "General Assignment",
points_possible:,
submission_types: "online_text_entry",
workflow_state: "published"
)
end
def create_discussion(course, creator, workflow_state = "published")
discussion_assignment = create_assignment(@course, "Discussion Assignment", 10)
course.discussion_topics.create!(
user: creator,
title: "Discussion Topic #{SecureRandom.alphanumeric(10)}",
message: "Discussion topic message",
assignment: discussion_assignment,
workflow_state:
)
end
it "loads connected mastery paths immediately is requested in url" do
course_with_teacher_logged_in
@course.conditional_release = true
@course.save!
@trigger_assignment = create_assignment(@course, "Mastery Path Main Assignment", 10)
@set1_assmt1 = create_assignment(@course, "Set 1 Assessment 1", 10)
@set2_assmt1 = create_assignment(@course, "Set 2 Assessment 1", 10)
@set2_assmt2 = create_assignment(@course, "Set 2 Assessment 2", 10)
@set3a_assmt = create_assignment(@course, "Set 3a Assessment", 10)
@set3b_assmt = create_assignment(@course, "Set 3b Assessment", 10)
graded_discussion = create_discussion(@course, @teacher)
course_module = @course.context_modules.create!(name: "Mastery Path Module")
course_module.add_item(id: @trigger_assignment.id, type: "assignment")
course_module.add_item(id: @set1_assmt1.id, type: "assignment")
course_module.add_item(id: graded_discussion.id, type: "discussion_topic")
course_module.add_item(id: @set2_assmt1.id, type: "assignment")
course_module.add_item(id: @set2_assmt2.id, type: "assignment")
course_module.add_item(id: @set3a_assmt.id, type: "assignment")
course_module.add_item(id: @set3b_assmt.id, type: "assignment")
ranges = [
ConditionalRelease::ScoringRange.new(lower_bound: 0.7, upper_bound: 1.0, assignment_sets: [
ConditionalRelease::AssignmentSet.new(assignment_set_associations: [
ConditionalRelease::AssignmentSetAssociation.new(assignment_id: @set1_assmt1.id),
ConditionalRelease::AssignmentSetAssociation.new(assignment_id: graded_discussion.assignment_id)
])
]),
ConditionalRelease::ScoringRange.new(lower_bound: 0.4, upper_bound: 0.7, assignment_sets: [
ConditionalRelease::AssignmentSet.new(assignment_set_associations: [
ConditionalRelease::AssignmentSetAssociation.new(assignment_id: @set2_assmt1.id),
ConditionalRelease::AssignmentSetAssociation.new(assignment_id: @set2_assmt2.id)
])
]),
ConditionalRelease::ScoringRange.new(lower_bound: 0, upper_bound: 0.4, assignment_sets: [
ConditionalRelease::AssignmentSet.new(
assignment_set_associations: [ConditionalRelease::AssignmentSetAssociation.new(
assignment_id: @set3a_assmt.id
)]
),
ConditionalRelease::AssignmentSet.new(
assignment_set_associations: [ConditionalRelease::AssignmentSetAssociation.new(
assignment_id: @set3b_assmt.id
)]
)
])
]
@rule = @course.conditional_release_rules.create!(trigger_assignment: @trigger_assignment, scoring_ranges: ranges)
mp_discussion = @course.discussion_topics.create!(assignment: @trigger_assignment, title: "graded discussion")
get "/courses/#{@course.id}/discussion_topics/#{mp_discussion.id}/edit#mastery-paths-editor"
fj("div[role='tab']:contains('Mastery Paths')").click
ui_ranges = ff("div.cr-scoring-range")
expect(ui_ranges[0].text).to include @set1_assmt1.title
expect(ui_ranges[0].text).to include graded_discussion.title
expect(ui_ranges[1].text).to include @set2_assmt1.title
expect(ui_ranges[1].text).to include @set2_assmt2.title
expect(ui_ranges[2].text).to include @set3a_assmt.title
expect(ui_ranges[2].text).to include @set3b_assmt.title
end
end
end
end
end
end