Commit 3a9ded90 by Tim Krones

Merge pull request #104 from open-craft/hide-feedback

Problem Builder: Add instance-wide option to hide global feedback and results for Long Answer blocks
parents 91f77e23 1a3f72ab
...@@ -60,7 +60,8 @@ log = logging.getLogger(__name__) ...@@ -60,7 +60,8 @@ log = logging.getLogger(__name__)
loader = ResourceLoader(__name__) loader = ResourceLoader(__name__)
_default_options_config = { _default_options_config = {
'pb_mcq_hide_previous_answer': False 'pb_mcq_hide_previous_answer': False,
'pb_hide_feedback_if_attempts_remain': False,
} }
...@@ -178,7 +179,7 @@ class BaseMentoringBlock( ...@@ -178,7 +179,7 @@ class BaseMentoringBlock(
""" """
Get value of a specific instance-wide `option`. Get value of a specific instance-wide `option`.
""" """
return self.get_options()[option] return self.get_options().get(option)
@XBlock.json_handler @XBlock.json_handler
def view(self, data, suffix=''): def view(self, data, suffix=''):
...@@ -563,9 +564,10 @@ class MentoringBlock(BaseMentoringBlock, StudioContainerXBlockMixin, StepParentM ...@@ -563,9 +564,10 @@ class MentoringBlock(BaseMentoringBlock, StudioContainerXBlockMixin, StepParentM
""" """
results = [] results = []
completed = True completed = True
show_message = bool(self.student_results) hide_feedback = self.get_option("pb_hide_feedback_if_attempts_remain") and not self.max_attempts_reached
show_message = (not hide_feedback) and bool(self.student_results)
# In standard mode, all children is visible simultaneously, so need collecting responses from all of them # In standard mode, all children are visible simultaneously, so need to collect results for all of them
for child in self.steps: for child in self.steps:
child_result = child.get_last_result() child_result = child.get_last_result()
results.append([child.name, child_result]) results.append([child.name, child_result])
......
...@@ -2,15 +2,10 @@ function AnswerBlock(runtime, element) { ...@@ -2,15 +2,10 @@ function AnswerBlock(runtime, element) {
return { return {
mode: null, mode: null,
init: function(options) { init: function(options) {
// register the child validator // Clear results and validate block when answer changes
$(':input', element).on('keyup', options.onChange); $(':input', element).on('keyup', options.onChange);
this.mode = options.mode; this.mode = options.mode;
var checkmark = $('.answer-checkmark', element);
var completed = $('.xblock-answer', element).data('completed');
if (completed === 'True' && this.mode === 'standard') {
checkmark.addClass('checkmark-correct icon-ok fa-check');
}
// In the LMS, the HTML of multiple units can be loaded at once, // In the LMS, the HTML of multiple units can be loaded at once,
// and the user can flip among them. If that happens, the answer in // and the user can flip among them. If that happens, the answer in
...@@ -26,15 +21,15 @@ function AnswerBlock(runtime, element) { ...@@ -26,15 +21,15 @@ function AnswerBlock(runtime, element) {
$('textarea', element).prop('disabled', true); $('textarea', element).prop('disabled', true);
}, },
handleSubmit: function(result) { handleSubmit: function(result, options) {
var checkmark = $('.answer-checkmark', element); var checkmark = $('.answer-checkmark', element);
this.clearResult(); this.clearResult();
if (this.mode === 'assessment') { if (options.hide_results || this.mode === 'assessment') {
// Display of checkmark would be redundant. // In assessment mode, display of checkmark would be redundant.
return return;
} }
if (result.status) { if (result.status) {
if (result.status === "correct") { if (result.status === "correct") {
...@@ -66,7 +61,7 @@ function AnswerBlock(runtime, element) { ...@@ -66,7 +61,7 @@ function AnswerBlock(runtime, element) {
var answer_length = input_value.length; var answer_length = input_value.length;
var data = input.data(); var data = input.data();
// an answer cannot be empty event if min_characters is 0 // An answer cannot be empty even if min_characters is 0
if (_.isNumber(data.min_characters)) { if (_.isNumber(data.min_characters)) {
var min_characters = _.max([data.min_characters, 1]); var min_characters = _.max([data.min_characters, 1]);
if (answer_length < min_characters) { if (answer_length < min_characters) {
......
...@@ -70,10 +70,6 @@ function MentoringBlock(runtime, element) { ...@@ -70,10 +70,6 @@ function MentoringBlock(runtime, element) {
function setContent(dom, content) { function setContent(dom, content) {
dom.html(''); dom.html('');
dom.append(content); dom.append(content);
var template = $('#light-child-template', dom).html();
if (template) {
dom.append(template);
}
} }
function renderAttempts() { function renderAttempts() {
......
...@@ -7,6 +7,8 @@ function MentoringStandardView(runtime, element, mentoring) { ...@@ -7,6 +7,8 @@ function MentoringStandardView(runtime, element, mentoring) {
function handleSubmitResults(response, disable_submit) { function handleSubmitResults(response, disable_submit) {
messagesDOM.empty().hide(); messagesDOM.empty().hide();
var hide_results = response.message === undefined;
var all_have_results = response.results.length > 0; var all_have_results = response.results.length > 0;
$.each(response.results || [], function(index, result_spec) { $.each(response.results || [], function(index, result_spec) {
var input = result_spec[0]; var input = result_spec[0];
...@@ -14,7 +16,8 @@ function MentoringStandardView(runtime, element, mentoring) { ...@@ -14,7 +16,8 @@ function MentoringStandardView(runtime, element, mentoring) {
var child = mentoring.getChildByName(input); var child = mentoring.getChildByName(input);
var options = { var options = {
max_attempts: response.max_attempts, max_attempts: response.max_attempts,
num_attempts: response.num_attempts num_attempts: response.num_attempts,
hide_results: hide_results,
}; };
callIfExists(child, 'handleSubmit', result, options); callIfExists(child, 'handleSubmit', result, options);
all_have_results = all_have_results && !$.isEmptyObject(result); all_have_results = all_have_results && !$.isEmptyObject(result);
...@@ -24,11 +27,12 @@ function MentoringStandardView(runtime, element, mentoring) { ...@@ -24,11 +27,12 @@ function MentoringStandardView(runtime, element, mentoring) {
$('.attempts', element).data('num_attempts', response.num_attempts); $('.attempts', element).data('num_attempts', response.num_attempts);
mentoring.renderAttempts(); mentoring.renderAttempts();
// Messages should only be displayed upon hitting 'submit', not on page reload if (!hide_results) {
mentoring.setContent(messagesDOM, response.message); mentoring.setContent(messagesDOM, response.message);
if (messagesDOM.html().trim()) { if (messagesDOM.html().trim()) {
messagesDOM.prepend('<div class="title1">' + mentoring.data.feedback_label + '</div>'); messagesDOM.prepend('<div class="title1">' + mentoring.data.feedback_label + '</div>');
messagesDOM.show(); messagesDOM.show();
}
} }
// Disable the submit button if we have just submitted new answers, // Disable the submit button if we have just submitted new answers,
......
...@@ -312,10 +312,6 @@ function MentoringWithStepsBlock(runtime, element) { ...@@ -312,10 +312,6 @@ function MentoringWithStepsBlock(runtime, element) {
function setContent(dom, content) { function setContent(dom, content) {
dom.html(''); dom.html('');
dom.append(content); dom.append(content);
var template = $('#light-child-template', dom).html();
if (template) {
dom.append(template);
}
} }
function publishEvent(data) { function publishEvent(data) {
......
...@@ -83,9 +83,15 @@ class ProblemBuilderQuestionnaireBlockTest(ProblemBuilderBaseTest): ...@@ -83,9 +83,15 @@ class ProblemBuilderQuestionnaireBlockTest(ProblemBuilderBaseTest):
def _get_choice(self, questionnaire, choice_index): def _get_choice(self, questionnaire, choice_index):
return questionnaire.find_elements_by_css_selector(".choices-list .choice")[choice_index] return questionnaire.find_elements_by_css_selector(".choices-list .choice")[choice_index]
def _get_answer_checkmark(self, answer):
return answer.find_element_by_xpath("parent::*").find_element_by_css_selector(".answer-checkmark")
def _get_messages_element(self, mentoring): def _get_messages_element(self, mentoring):
return mentoring.find_element_by_css_selector('.messages') return mentoring.find_element_by_css_selector('.messages')
def _get_submit(self, mentoring):
return mentoring.find_element_by_css_selector('.submit input.input-main')
def _get_controls(self, mentoring): def _get_controls(self, mentoring):
answer = self._get_xblock(mentoring, "feedback_answer_1").find_element_by_css_selector('.answer') answer = self._get_xblock(mentoring, "feedback_answer_1").find_element_by_css_selector('.answer')
mcq = self._get_xblock(mentoring, "feedback_mcq_2") mcq = self._get_xblock(mentoring, "feedback_mcq_2")
...@@ -94,16 +100,38 @@ class ProblemBuilderQuestionnaireBlockTest(ProblemBuilderBaseTest): ...@@ -94,16 +100,38 @@ class ProblemBuilderQuestionnaireBlockTest(ProblemBuilderBaseTest):
return answer, mcq, mrq, rating return answer, mcq, mrq, rating
def _assert_answer(self, answer, results_shown=True):
self.assertEqual(answer.get_attribute('value'), 'This is the answer')
answer_checkmark = self._get_answer_checkmark(answer)
self._assert_checkmark(answer_checkmark, shown=results_shown, checkmark_class='checkmark-correct')
def _assert_checkmark(self, checkmark, shown=True, checkmark_class=None): def _assert_checkmark(self, checkmark, shown=True, checkmark_class=None):
choice_result_classes = checkmark.get_attribute('class').split() result_classes = checkmark.get_attribute('class').split()
if shown: if shown:
self.assertTrue(checkmark.is_displayed()) self.assertTrue(checkmark.is_displayed())
self.assertIn(checkmark_class, choice_result_classes) self.assertIn(checkmark_class, result_classes)
else: else:
self.assertFalse(checkmark.is_displayed()) self.assertFalse(checkmark.is_displayed())
def _assert_feedback_showed(self, questionnaire, choice_index, expected_text, def _assert_mcq(self, mcq, previous_answer_shown=True):
click_choice_result=False, success=True): if previous_answer_shown:
self._assert_feedback_shown(mcq, 0, "Great!", click_choice_result=True)
else:
for i in range(3):
self._assert_feedback_hidden(mcq, i)
self._assert_not_checked(mcq, i)
def _assert_rating(self, rating, previous_answer_shown=True):
if previous_answer_shown:
self._assert_feedback_shown(rating, 3, "I love good grades.", click_choice_result=True)
else:
for i in range(5):
self._assert_feedback_hidden(rating, i)
self._assert_not_checked(rating, i)
def _assert_feedback_shown(
self, questionnaire, choice_index, expected_text, click_choice_result=False, success=True
):
""" """
Asserts that feedback for given element contains particular text Asserts that feedback for given element contains particular text
If `click_choice_result` is True - clicks on `choice-result` icon before checking feedback visibility: If `click_choice_result` is True - clicks on `choice-result` icon before checking feedback visibility:
...@@ -116,7 +144,7 @@ class ProblemBuilderQuestionnaireBlockTest(ProblemBuilderBaseTest): ...@@ -116,7 +144,7 @@ class ProblemBuilderQuestionnaireBlockTest(ProblemBuilderBaseTest):
feedback_popup = choice.find_element_by_css_selector(".choice-tips") feedback_popup = choice.find_element_by_css_selector(".choice-tips")
checkmark_class = 'checkmark-correct' if success else 'checkmark-incorrect' checkmark_class = 'checkmark-correct' if success else 'checkmark-incorrect'
self._assert_checkmark(choice_result, shown=True, checkmark_class=checkmark_class) self._assert_checkmark(choice_result, checkmark_class=checkmark_class)
self.assertTrue(feedback_popup.is_displayed()) self.assertTrue(feedback_popup.is_displayed())
self.assertEqual(feedback_popup.text, expected_text) self.assertEqual(feedback_popup.text, expected_text)
...@@ -124,25 +152,51 @@ class ProblemBuilderQuestionnaireBlockTest(ProblemBuilderBaseTest): ...@@ -124,25 +152,51 @@ class ProblemBuilderQuestionnaireBlockTest(ProblemBuilderBaseTest):
choice = self._get_choice(questionnaire, choice_index) choice = self._get_choice(questionnaire, choice_index)
choice_result = choice.find_element_by_css_selector('.choice-result') choice_result = choice.find_element_by_css_selector('.choice-result')
feedback_popup = choice.find_element_by_css_selector(".choice-tips") feedback_popup = choice.find_element_by_css_selector(".choice-tips")
choice_result_classes = choice_result.get_attribute('class').split() result_classes = choice_result.get_attribute('class').split()
self.assertTrue(choice_result.is_displayed()) self.assertTrue(choice_result.is_displayed())
self.assertFalse(feedback_popup.is_displayed()) self.assertFalse(feedback_popup.is_displayed())
self.assertNotIn('checkmark-correct', choice_result_classes) self.assertNotIn('checkmark-correct', result_classes)
self.assertNotIn('checkmark-incorrect', choice_result_classes) self.assertNotIn('checkmark-incorrect', result_classes)
def _assert_not_checked(self, questionnaire, choice_index): def _assert_not_checked(self, questionnaire, choice_index):
choice = self._get_choice(questionnaire, choice_index) choice = self._get_choice(questionnaire, choice_index)
choice_input = choice.find_element_by_css_selector('input') choice_input = choice.find_element_by_css_selector('input')
self.assertFalse(choice_input.is_selected()) self.assertFalse(choice_input.is_selected())
def _assert_mrq(self, mrq):
self._assert_feedback_shown(
mrq, 0, "This is something everyone has to like about this MRQ",
click_choice_result=True
)
self._assert_feedback_shown(
mrq, 1, "This is something everyone has to like about beauty",
click_choice_result=True, success=False
)
self._assert_feedback_shown(mrq, 2, "This MRQ is indeed very graceful", click_choice_result=True)
self._assert_feedback_shown(mrq, 3, "Nah, there aren't any!", click_choice_result=True, success=False)
def _assert_messages(self, messages, shown=True):
if shown:
self.assertTrue(messages.is_displayed())
self.assertEqual(messages.text, "FEEDBACK\nNot done yet")
else:
self.assertFalse(messages.is_displayed())
self.assertEqual(messages.text, "")
def _standard_filling(self, answer, mcq, mrq, rating): def _standard_filling(self, answer, mcq, mrq, rating):
# Long answer
answer.send_keys('This is the answer') answer.send_keys('This is the answer')
# MCQ
self.click_choice(mcq, "Yes") self.click_choice(mcq, "Yes")
# 1st, 3rd and 4th options, first three are correct, i.e. two mistakes: 2nd and 4th # MRQ: Select 1st, 3rd and 4th options
# First three options are required, so we're making two mistakes:
# - 2nd option should have been selected
# - 4th option should *not* have been selected
self.click_choice(mrq, "Its elegance") self.click_choice(mrq, "Its elegance")
self.click_choice(mrq, "Its gracefulness") self.click_choice(mrq, "Its gracefulness")
self.click_choice(mrq, "Its bugs") self.click_choice(mrq, "Its bugs")
# Rating
self.click_choice(rating, "4") self.click_choice(rating, "4")
# mcq and rating can't be reset easily, but it's not required; listing them here to keep method signature similar # mcq and rating can't be reset easily, but it's not required; listing them here to keep method signature similar
...@@ -153,47 +207,56 @@ class ProblemBuilderQuestionnaireBlockTest(ProblemBuilderBaseTest): ...@@ -153,47 +207,56 @@ class ProblemBuilderQuestionnaireBlockTest(ProblemBuilderBaseTest):
checkbox.click() checkbox.click()
def _standard_checks(self, answer, mcq, mrq, rating, messages): def _standard_checks(self, answer, mcq, mrq, rating, messages):
self.assertEqual(answer.get_attribute('value'), 'This is the answer') self.wait_until_visible(messages)
self._assert_feedback_showed(mcq, 0, "Great!", click_choice_result=True)
self._assert_feedback_showed(
mrq, 0, "This is something everyone has to like about this MRQ",
click_choice_result=True
)
self._assert_feedback_showed(
mrq, 1, "This is something everyone has to like about beauty",
click_choice_result=True, success=False
)
self._assert_feedback_showed(mrq, 2, "This MRQ is indeed very graceful", click_choice_result=True)
self._assert_feedback_showed(mrq, 3, "Nah, there aren't any!", click_choice_result=True, success=False)
self._assert_feedback_showed(rating, 3, "I love good grades.", click_choice_result=True)
self.assertTrue(messages.is_displayed())
self.assertEqual(messages.text, "FEEDBACK\nNot done yet")
def _feedback_customized_checks(self, answer, mcq, mrq, rating, messages): # Long answer: Previous answer and results visible
# Long answer: Previous answer and feedback visible self._assert_answer(answer)
self.assertEqual(answer.get_attribute('value'), 'This is the answer') # MCQ: Previous answer and results visible
# MCQ: Previous answer and feedback hidden self._assert_mcq(mcq)
for i in range(3):
self._assert_feedback_hidden(mcq, i)
self._assert_not_checked(mcq, i)
# MRQ: Previous answer and feedback visible # MRQ: Previous answer and feedback visible
self._assert_feedback_showed( self._assert_mrq(mrq)
mrq, 0, "This is something everyone has to like about this MRQ", # Rating: Previous answer and results visible
click_choice_result=True self._assert_rating(rating)
) # Messages visible
self._assert_feedback_showed( self._assert_messages(messages)
mrq, 1, "This is something everyone has to like about beauty",
click_choice_result=True, success=False def _mcq_hide_previous_answer_checks(self, answer, mcq, mrq, rating, messages):
) self.wait_until_visible(messages)
self._assert_feedback_showed(mrq, 2, "This MRQ is indeed very graceful", click_choice_result=True)
self._assert_feedback_showed(mrq, 3, "Nah, there aren't any!", click_choice_result=True, success=False) # Long answer: Previous answer and results visible
self._assert_answer(answer)
# MCQ: Previous answer and results hidden
self._assert_mcq(mcq, previous_answer_shown=False)
# MRQ: Previous answer and results visible
self._assert_mrq(mrq)
# Rating: Previous answer and results hidden
self._assert_rating(rating, previous_answer_shown=False)
# Messages visible
self._assert_messages(messages)
def _hide_feedback_checks(self, answer, mcq, mrq, rating, messages):
# Long answer: Previous answer visible and results hidden
self._assert_answer(answer, results_shown=False)
# MCQ: Previous answer and results visible
self._assert_mcq(mcq)
# MRQ: Previous answer and results visible
self._assert_mrq(mrq)
# Rating: Previous answer and results visible
self._assert_rating(rating)
# Messages hidden
self._assert_messages(messages, shown=False)
def _mcq_hide_previous_answer_hide_feedback_checks(self, answer, mcq, mrq, rating, messages):
# Long answer: Previous answer visible and results hidden
self._assert_answer(answer, results_shown=False)
# MCQ: Previous answer and results hidden
self._assert_mcq(mcq, previous_answer_shown=False)
# MRQ: Previous answer and results visible
self._assert_mrq(mrq)
# Rating: Previous answer and feedback hidden # Rating: Previous answer and feedback hidden
for i in range(5): self._assert_rating(rating, previous_answer_shown=False)
self._assert_feedback_hidden(rating, i) # Messages hidden
self._assert_not_checked(rating, i) self._assert_messages(messages, shown=False)
# Messages
self.assertTrue(messages.is_displayed())
self.assertEqual(messages.text, "FEEDBACK\nNot done yet")
def reload_student_view(self): def reload_student_view(self):
# Load another page (the home page), then go back to the page we want. This is the only reliable way to reload. # Load another page (the home page), then go back to the page we want. This is the only reliable way to reload.
...@@ -203,19 +266,20 @@ class ProblemBuilderQuestionnaireBlockTest(ProblemBuilderBaseTest): ...@@ -203,19 +266,20 @@ class ProblemBuilderQuestionnaireBlockTest(ProblemBuilderBaseTest):
def did_load_homepage(driver): def did_load_homepage(driver):
title = driver.find_element_by_css_selector('h1.title') title = driver.find_element_by_css_selector('h1.title')
return title and title.text == "XBlock scenarios" return title and title.text == "XBlock scenarios"
wait.until(did_load_homepage, u"Workbench home page should have loaded") wait.until(did_load_homepage, u"Workbench home page should have loaded")
mentoring = self.go_to_view("student_view") mentoring = self.go_to_view("student_view")
self.wait_until_visible(self._get_xblock(mentoring, "feedback_mcq_2")) submit = self._get_submit(mentoring)
self.wait_until_visible(submit)
return mentoring return mentoring
def test_feedbacks_and_messages_is_not_shown_on_first_load(self): def test_feedback_and_messages_not_shown_on_first_load(self):
mentoring = self.load_scenario("feedback_persistence.xml") mentoring = self.load_scenario("feedback_persistence.xml")
answer, mcq, mrq, rating = self._get_controls(mentoring) answer, mcq, mrq, rating = self._get_controls(mentoring)
messages = self._get_messages_element(mentoring) messages = self._get_messages_element(mentoring)
submit = mentoring.find_element_by_css_selector('.submit input.input-main') submit = self._get_submit(mentoring)
answer_checkmark = answer.find_element_by_xpath("parent::*").find_element_by_css_selector(".answer-checkmark")
answer_checkmark = self._get_answer_checkmark(answer)
self._assert_checkmark(answer_checkmark, shown=False) self._assert_checkmark(answer_checkmark, shown=False)
for i in range(3): for i in range(3):
self._assert_feedback_hidden(mcq, i) self._assert_feedback_hidden(mcq, i)
...@@ -227,31 +291,36 @@ class ProblemBuilderQuestionnaireBlockTest(ProblemBuilderBaseTest): ...@@ -227,31 +291,36 @@ class ProblemBuilderQuestionnaireBlockTest(ProblemBuilderBaseTest):
self.assertFalse(submit.is_enabled()) self.assertFalse(submit.is_enabled())
def test_persists_feedback_on_page_reload(self): def test_persists_feedback_on_page_reload(self):
mentoring = self.load_scenario("feedback_persistence.xml") options = {
answer, mcq, mrq, rating = self._get_controls(mentoring) 'pb_mcq_hide_previous_answer': False,
messages = self._get_messages_element(mentoring) 'pb_hide_feedback_if_attempts_remain': False
}
self._standard_filling(answer, mcq, mrq, rating) self._test_persistence(options, "_standard_checks")
self.click_submit(mentoring)
self._standard_checks(answer, mcq, mrq, rating, messages) def test_does_not_persist_feedback_on_page_reload_if_disabled(self):
options = {
# now, reload the page and do the same checks again 'pb_mcq_hide_previous_answer': False,
mentoring = self.reload_student_view() 'pb_hide_feedback_if_attempts_remain': True
answer, mcq, mrq, rating = self._get_controls(mentoring) }
messages = self._get_messages_element(mentoring) self._test_persistence(options, "_hide_feedback_checks")
submit = mentoring.find_element_by_css_selector('.submit input.input-main')
def test_does_not_persist_mcq_on_page_reload_if_disabled(self):
self._standard_checks(answer, mcq, mrq, rating, messages) options = {
# after reloading submit is disabled... 'pb_mcq_hide_previous_answer': True,
self.assertFalse(submit.is_enabled()) 'pb_hide_feedback_if_attempts_remain': False
}
# ...until some changes are done self._test_persistence(options, "_mcq_hide_previous_answer_checks")
self.click_choice(mrq, "Its elegance")
self.assertTrue(submit.is_enabled()) def test_does_not_persist_mcq_and_feedback_on_page_reload_if_disabled(self):
options = {
def test_does_not_persist_mcq_feedback_on_page_reload_if_disabled(self): 'pb_mcq_hide_previous_answer': True,
'pb_hide_feedback_if_attempts_remain': True
}
self._test_persistence(options, "_mcq_hide_previous_answer_hide_feedback_checks")
def _test_persistence(self, options, after_reload_checks):
with mock.patch("problem_builder.mentoring.MentoringBlock.get_options") as patched_options: with mock.patch("problem_builder.mentoring.MentoringBlock.get_options") as patched_options:
patched_options.return_value = {'pb_mcq_hide_previous_answer': True} patched_options.return_value = options
mentoring = self.load_scenario("feedback_persistence.xml") mentoring = self.load_scenario("feedback_persistence.xml")
answer, mcq, mrq, rating = self._get_controls(mentoring) answer, mcq, mrq, rating = self._get_controls(mentoring)
messages = self._get_messages_element(mentoring) messages = self._get_messages_element(mentoring)
...@@ -260,17 +329,19 @@ class ProblemBuilderQuestionnaireBlockTest(ProblemBuilderBaseTest): ...@@ -260,17 +329,19 @@ class ProblemBuilderQuestionnaireBlockTest(ProblemBuilderBaseTest):
self.click_submit(mentoring) self.click_submit(mentoring)
self._standard_checks(answer, mcq, mrq, rating, messages) self._standard_checks(answer, mcq, mrq, rating, messages)
# now, reload the page and see if previous answers and results for MCQs are hidden # Now reload the page...
mentoring = self.reload_student_view() mentoring = self.reload_student_view()
answer, mcq, mrq, rating = self._get_controls(mentoring) answer, mcq, mrq, rating = self._get_controls(mentoring)
messages = self._get_messages_element(mentoring) messages = self._get_messages_element(mentoring)
submit = mentoring.find_element_by_css_selector('.submit input.input-main') submit = self._get_submit(mentoring)
# ... and see if previous answers, results, feedback are shown/hidden correctly
getattr(self, after_reload_checks)(answer, mcq, mrq, rating, messages)
self._feedback_customized_checks(answer, mcq, mrq, rating, messages) # After reloading, submit is disabled...
# after reloading submit is disabled...
self.assertFalse(submit.is_enabled()) self.assertFalse(submit.is_enabled())
# ... until student answers MCQs again # ... until student makes changes
self.click_choice(mcq, "Maybe not") self.click_choice(mcq, "Maybe not")
self.click_choice(rating, "2") self.click_choice(rating, "2")
self.assertTrue(submit.is_enabled()) self.assertTrue(submit.is_enabled())
...@@ -291,18 +362,18 @@ class ProblemBuilderQuestionnaireBlockTest(ProblemBuilderBaseTest): ...@@ -291,18 +362,18 @@ class ProblemBuilderQuestionnaireBlockTest(ProblemBuilderBaseTest):
# precondition - verifying 100% score achieved # precondition - verifying 100% score achieved
self.assertEqual(answer.get_attribute('value'), 'This is the answer') self.assertEqual(answer.get_attribute('value'), 'This is the answer')
self._assert_feedback_showed(mcq, 0, "Great!", click_choice_result=True) self._assert_feedback_shown(mcq, 0, "Great!", click_choice_result=True)
self._assert_feedback_showed( self._assert_feedback_shown(
mrq, 0, "This is something everyone has to like about this MRQ", mrq, 0, "This is something everyone has to like about this MRQ",
click_choice_result=True click_choice_result=True
) )
self._assert_feedback_showed( self._assert_feedback_shown(
mrq, 1, "This is something everyone has to like about beauty", mrq, 1, "This is something everyone has to like about beauty",
click_choice_result=True click_choice_result=True
) )
self._assert_feedback_showed(mrq, 2, "This MRQ is indeed very graceful", click_choice_result=True) self._assert_feedback_shown(mrq, 2, "This MRQ is indeed very graceful", click_choice_result=True)
self._assert_feedback_showed(mrq, 3, "Nah, there aren't any!", click_choice_result=True) self._assert_feedback_shown(mrq, 3, "Nah, there aren't any!", click_choice_result=True)
self._assert_feedback_showed(rating, 3, "I love good grades.", click_choice_result=True) self._assert_feedback_shown(rating, 3, "I love good grades.", click_choice_result=True)
self.assertTrue(messages.is_displayed()) self.assertTrue(messages.is_displayed())
self.assertEqual(messages.text, "FEEDBACK\nAll Good") self.assertEqual(messages.text, "FEEDBACK\nAll Good")
...@@ -331,19 +402,20 @@ class ProblemBuilderQuestionnaireBlockTest(ProblemBuilderBaseTest): ...@@ -331,19 +402,20 @@ class ProblemBuilderQuestionnaireBlockTest(ProblemBuilderBaseTest):
self.click_submit(mentoring) self.click_submit(mentoring)
def assert_state(answer, mcq, mrq, rating, messages): def assert_state(answer, mcq, mrq, rating, messages):
self.wait_until_visible(messages)
self.assertEqual(answer.get_attribute('value'), 'This is the answer') self.assertEqual(answer.get_attribute('value'), 'This is the answer')
self._assert_feedback_showed(mcq, 0, "Great!", click_choice_result=True) self._assert_feedback_shown(mcq, 0, "Great!", click_choice_result=True)
self._assert_feedback_showed( self._assert_feedback_shown(
mrq, 0, "This is something everyone has to like about this MRQ", mrq, 0, "This is something everyone has to like about this MRQ",
click_choice_result=True click_choice_result=True
) )
self._assert_feedback_showed( self._assert_feedback_shown(
mrq, 1, "This is something everyone has to like about beauty", mrq, 1, "This is something everyone has to like about beauty",
click_choice_result=True, success=False click_choice_result=True, success=False
) )
self._assert_feedback_showed(mrq, 2, "This MRQ is indeed very graceful", click_choice_result=True) self._assert_feedback_shown(mrq, 2, "This MRQ is indeed very graceful", click_choice_result=True)
self._assert_feedback_showed(mrq, 3, "Nah, there aren't any!", click_choice_result=True) self._assert_feedback_shown(mrq, 3, "Nah, there aren't any!", click_choice_result=True)
self._assert_feedback_showed(rating, 3, "I love good grades.", click_choice_result=True) self._assert_feedback_shown(rating, 3, "I love good grades.", click_choice_result=True)
self.assertTrue(messages.is_displayed()) self.assertTrue(messages.is_displayed())
self.assertEqual(messages.text, "FEEDBACK\nNot done yet") self.assertEqual(messages.text, "FEEDBACK\nNot done yet")
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
<pb-tip values='["understand"]'><div id="test-custom-html">Really?</div></pb-tip> <pb-tip values='["understand"]'><div id="test-custom-html">Really?</div></pb-tip>
</pb-mcq> </pb-mcq>
<pb-mrq name="feedback_mrq_3" question="What do you like in this MRQ?" required_choices='["elegance","gracefulness","beauty"]'> <pb-mrq name="feedback_mrq_3" question="What do you like in this MRQ?" required_choices='["elegance","beauty","gracefulness"]'>
<pb-choice value="elegance">Its elegance</pb-choice> <pb-choice value="elegance">Its elegance</pb-choice>
<pb-choice value="beauty">Its beauty</pb-choice> <pb-choice value="beauty">Its beauty</pb-choice>
<pb-choice value="gracefulness">Its gracefulness</pb-choice> <pb-choice value="gracefulness">Its gracefulness</pb-choice>
......
import ddt import ddt
import unittest import unittest
from mock import MagicMock, Mock, patch from mock import MagicMock, Mock, PropertyMock, patch
from random import random from random import random
from xblock.field_data import DictFieldData from xblock.field_data import DictFieldData
...@@ -66,6 +66,27 @@ class TestMentoringBlock(unittest.TestCase): ...@@ -66,6 +66,27 @@ class TestMentoringBlock(unittest.TestCase):
self.assertIn('Unable to load child component', fragment.content) self.assertIn('Unable to load child component', fragment.content)
@ddt.data(
(True, True, True),
(True, False, False),
(False, False, True),
(False, False, True),
)
@ddt.unpack
def test_correctly_decides_to_show_or_hide_feedback_message(
self, pb_hide_feedback_if_attempts_remain, max_attempts_reached, expected_show_message
):
block = MentoringBlock(Mock(), DictFieldData({
'student_results': ['must', 'be', 'non-empty'],
}), Mock())
block.get_option = Mock(return_value=pb_hide_feedback_if_attempts_remain)
with patch(
'problem_builder.mentoring.MentoringBlock.max_attempts_reached', new_callable=PropertyMock
) as patched_max_attempts_reached:
patched_max_attempts_reached.return_value = max_attempts_reached
_, _, show_message = block._get_standard_results()
self.assertEqual(show_message, expected_show_message)
@ddt.ddt @ddt.ddt
class TestMentoringBlockTheming(unittest.TestCase): class TestMentoringBlockTheming(unittest.TestCase):
...@@ -140,13 +161,13 @@ class TestMentoringBlockTheming(unittest.TestCase): ...@@ -140,13 +161,13 @@ class TestMentoringBlockTheming(unittest.TestCase):
self.assertEqual(patched_load_unicode.call_count, len(locations)) self.assertEqual(patched_load_unicode.call_count, len(locations))
def test_student_view_calls_include_theme_files(self): def test_student_view_calls_include_theme_files(self):
self.service_mock.get_settings_bucket = Mock(return_value={}) self.block.get_xblock_settings = Mock(return_value={})
with patch.object(self.block, 'include_theme_files') as patched_include_theme_files: with patch.object(self.block, 'include_theme_files') as patched_include_theme_files:
fragment = self.block.student_view({}) fragment = self.block.student_view({})
patched_include_theme_files.assert_called_with(fragment) patched_include_theme_files.assert_called_with(fragment)
def test_author_preview_view_calls_include_theme_files(self): def test_author_preview_view_calls_include_theme_files(self):
self.service_mock.get_settings_bucket = Mock(return_value={}) self.block.get_xblock_settings = Mock(return_value={})
with patch.object(self.block, 'include_theme_files') as patched_include_theme_files: with patch.object(self.block, 'include_theme_files') as patched_include_theme_files:
fragment = self.block.author_preview_view({}) fragment = self.block.author_preview_view({})
patched_include_theme_files.assert_called_with(fragment) patched_include_theme_files.assert_called_with(fragment)
...@@ -190,17 +211,29 @@ class TestMentoringBlockOptions(unittest.TestCase): ...@@ -190,17 +211,29 @@ class TestMentoringBlockOptions(unittest.TestCase):
def test_get_option(self): def test_get_option(self):
random_key, random_value = random(), random() random_key, random_value = random(), random()
with patch.object(self.block, 'get_options') as patched_get_options: with patch.object(self.block, 'get_options') as patched_get_options:
# Happy path: Customizations contain expected key
patched_get_options.return_value = {random_key: random_value} patched_get_options.return_value = {random_key: random_value}
option = self.block.get_option(random_key) option = self.block.get_option(random_key)
patched_get_options.assert_called_once_with() patched_get_options.assert_called_once_with()
self.assertEqual(option, random_value) self.assertEqual(option, random_value)
with patch.object(self.block, 'get_options') as patched_get_options:
# Sad path: Customizations do not contain expected key
patched_get_options.return_value = {}
option = self.block.get_option(random_key)
patched_get_options.assert_called_once_with()
self.assertEqual(option, None)
def test_student_view_calls_get_option(self): def test_student_view_calls_get_option(self):
self.service_mock.get_settings_bucket = Mock(return_value={}) self.block.get_xblock_settings = Mock(return_value={})
with patch.object(self.block, 'get_option') as patched_get_option: with patch.object(self.block, 'get_option') as patched_get_option:
self.block.student_view({}) self.block.student_view({})
patched_get_option.assert_called_with('pb_mcq_hide_previous_answer') patched_get_option.assert_called_with('pb_mcq_hide_previous_answer')
def test_get_standard_results_calls_get_option(self):
with patch.object(self.block, 'get_option') as patched_get_option:
self.block._get_standard_results()
patched_get_option.assert_called_with('pb_hide_feedback_if_attempts_remain')
class TestMentoringBlockJumpToIds(unittest.TestCase): class TestMentoringBlockJumpToIds(unittest.TestCase):
def setUp(self): def setUp(self):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment