From c4e2b67c2268cbe2cb17ecff4b12077cc8996893 Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Tue, 12 Jan 2021 17:45:44 +0000 Subject: [PATCH] MDL-68597 qtype_essay: show the word count on the review screen --- question/type/essay/lang/en/qtype_essay.php | 3 + question/type/essay/question.php | 59 +++++++++++++++---- question/type/essay/renderer.php | 2 + question/type/essay/tests/question_test.php | 57 ++++++++++++++++-- .../type/essay/tests/walkthrough_test.php | 12 ++++ 5 files changed, 116 insertions(+), 17 deletions(-) diff --git a/question/type/essay/lang/en/qtype_essay.php b/question/type/essay/lang/en/qtype_essay.php index e3a84f24834..58d6745355a 100644 --- a/question/type/essay/lang/en/qtype_essay.php +++ b/question/type/essay/lang/en/qtype_essay.php @@ -69,3 +69,6 @@ $string['responseisrequired'] = 'Require the student to enter text'; $string['responsetemplate'] = 'Response template'; $string['responsetemplateheader'] = 'Response Template'; $string['responsetemplate_help'] = 'Any text entered here will be displayed in the response input box when a new attempt at the question starts.'; +$string['wordcount'] = 'Word count: {$a}'; +$string['wordcounttoofew'] = 'Word count: {$a->count}, less than the required {$a->limit} words.'; +$string['wordcounttoomuch'] = 'Word count: {$a->count}, more than the limit of {$a->limit} words.'; diff --git a/question/type/essay/question.php b/question/type/essay/question.php index 204abaea191..38db4aecde6 100644 --- a/question/type/essay/question.php +++ b/question/type/essay/question.php @@ -252,19 +252,52 @@ class qtype_essay_question extends question_with_responses { if (!$this->responserequired) { return null; } - if ((isset($this->minwordlimit) && $this->minwordlimit > 0) || - (isset($this->maxwordlimit) && $this->maxwordlimit > 0)) { - // Count the number of words in the response string. - $responsewords = count_words($responsestring); - if (isset($this->minwordlimit) && $this->minwordlimit > $responsewords) { - return get_string('minwordlimitboundary', 'qtype_essay', - ['limit' => $this->minwordlimit, 'count' => $responsewords]); - } - if (isset($this->maxwordlimit) && $this->maxwordlimit < $responsewords) { - return get_string('maxwordlimitboundary', 'qtype_essay', - ['limit' => $this->maxwordlimit, 'count' => $responsewords]); - } + if (!$this->minwordlimit && !$this->maxwordlimit) { + // This question does not care about the word count. + return null; + } + + // Count the number of words in the response string. + $count = count_words($responsestring); + if ($this->maxwordlimit && $count > $this->maxwordlimit) { + return get_string('maxwordlimitboundary', 'qtype_essay', + ['limit' => $this->maxwordlimit, 'count' => $count]); + } else if ($count < $this->minwordlimit) { + return get_string('minwordlimitboundary', 'qtype_essay', + ['limit' => $this->minwordlimit, 'count' => $count]); + } else { + return null; + } + } + + /** + * If this question uses word counts, then return a display of the current + * count, and whether it is within limit, for when the question is being reviewed. + * + * @param array $response responses, as returned by + * {@see question_attempt_step::get_qt_data()}. + * @return string If relevant to this question, a display of the word count. + */ + public function get_word_count_message_for_review(array $response): string { + if (!$this->minwordlimit && !$this->maxwordlimit) { + // This question does not care about the word count. + return ''; + } + + if (!array_key_exists('answer', $response) || ($response['answer'] === '')) { + // No response. + return ''; + } + + $count = count_words($response['answer']); + if ($this->maxwordlimit && $count > $this->maxwordlimit) { + return get_string('wordcounttoomuch', 'qtype_essay', + ['limit' => $this->maxwordlimit, 'count' => $count]); + } else if ($count < $this->minwordlimit) { + return get_string('wordcounttoofew', 'qtype_essay', + ['limit' => $this->minwordlimit, 'count' => $count]); + } else { + return get_string('wordcount', 'qtype_essay', $count); } - return null; } } diff --git a/question/type/essay/renderer.php b/question/type/essay/renderer.php index 0a1e0dedf9c..91b07de9733 100644 --- a/question/type/essay/renderer.php +++ b/question/type/essay/renderer.php @@ -55,6 +55,8 @@ class qtype_essay_renderer extends qtype_renderer { } else { $answer = $responseoutput->response_area_read_only('answer', $qa, $step, $question->responsefieldlines, $options->context); + $answer .= html_writer::nonempty_tag('p', $question->get_word_count_message_for_review($step->get_qt_data())); + } $files = ''; diff --git a/question/type/essay/tests/question_test.php b/question/type/essay/tests/question_test.php index ffd583a525b..b42f2417256 100644 --- a/question/type/essay/tests/question_test.php +++ b/question/type/essay/tests/question_test.php @@ -186,8 +186,8 @@ class qtype_essay_question_test extends advanced_testcase { $this->assertTrue($essay->is_complete_response($response)); // Unset the minwordlimit/maxwordlimit variables to avoid the extra check in is_complete_response() for further tests. - unset($essay->minwordlimit); - unset($essay->maxwordlimit); + $essay->minwordlimit = null; + $essay->maxwordlimit = null; // Test the case where two files are required. $essay->attachmentsrequired = 2; @@ -284,8 +284,10 @@ class qtype_essay_question_test extends advanced_testcase { /** * Test get_validation_error when users submit their input text. * + * (The tests are done with a fixed 14-word response.) + * * @dataProvider get_min_max_wordlimit_test_cases() - * @param int $responserequired whether reponse required (yes = 1, no = 0) + * @param int $responserequired whether response required (yes = 1, no = 0) * @param int $minwordlimit minimum word limit * @param int $maxwordlimit maximum word limit * @param string $expected error message | null @@ -303,8 +305,10 @@ class qtype_essay_question_test extends advanced_testcase { /** * Data provider for get_validation_error test. + * + * @return array the test cases. */ - public function get_min_max_wordlimit_test_cases() { + public function get_min_max_wordlimit_test_cases(): array { return [ 'text input required, min/max word limit not set' => [1, 0, 0, ''], 'text input required, min/max word limit valid (within the boundaries)' => [1, 10, 25, ''], @@ -315,4 +319,49 @@ class qtype_essay_question_test extends advanced_testcase { 'text input not required, min/max word limit not set' => [0, 5, 12, ''], ]; } + + /** + * Test get_word_count_message_for_review when users submit their input text. + * + * (The tests are done with a fixed 14-word response.) + * + * @dataProvider get_word_count_message_for_review_test_cases() + * @param int|null $minwordlimit minimum word limit + * @param int|null $maxwordlimit maximum word limit + * @param string $expected error message | null + */ + public function test_get_word_count_message_for_review(?int $minwordlimit, ?int $maxwordlimit, string $expected): void { + $question = test_question_maker::make_an_essay_question(); + $question->minwordlimit = $minwordlimit; + $question->maxwordlimit = $maxwordlimit; + + $response = ['answer' => 'One two three four five six seven eight nine ten eleven twelve thirteen fourteen.']; + $this->assertEquals($expected, $question->get_word_count_message_for_review($response)); + } + + /** + * Data provider for test_get_word_count_message_for_review. + * + * @return array the test cases. + */ + public function get_word_count_message_for_review_test_cases() { + return [ + 'No limit' => + [null, null, ''], + 'min and max, answer within range' => + [10, 25, get_string('wordcount', 'qtype_essay', 14)], + 'min and max, answer too short' => + [15, 25, get_string('wordcounttoofew', 'qtype_essay', ['count' => 14, 'limit' => 15])], + 'min and max, answer too long' => + [5, 12, get_string('wordcounttoomuch', 'qtype_essay', ['count' => 14, 'limit' => 12])], + 'min only, answer within range' => + [14, null, get_string('wordcount', 'qtype_essay', 14)], + 'min only, answer too short' => + [15, null, get_string('wordcounttoofew', 'qtype_essay', ['count' => 14, 'limit' => 15])], + 'max only, answer within range' => + [null, 14, get_string('wordcount', 'qtype_essay', 14)], + 'max only, answer too short' => + [null, 13, get_string('wordcounttoomuch', 'qtype_essay', ['count' => 14, 'limit' => 13])], + ]; + } } diff --git a/question/type/essay/tests/walkthrough_test.php b/question/type/essay/tests/walkthrough_test.php index 1d6c3e1dcda..2aa234eb51e 100644 --- a/question/type/essay/tests/walkthrough_test.php +++ b/question/type/essay/tests/walkthrough_test.php @@ -715,5 +715,17 @@ class qtype_essay_walkthrough_testcase extends qbehaviour_walkthrough_test_base 'attempting to submit 8 words. Please shorten your response and try again.', $this->currentoutput); + // Now submit all and finish. + $this->finish(); + + // Verify. + $this->check_current_state(question_state::$needsgrading); + $this->check_current_mark(null); + $this->render(); + $this->check_current_output( + $this->get_contains_question_text_expectation($q), + $this->get_contains_general_feedback_expectation($q)); + $this->assertStringContainsString('Word count: 8, more than the limit of 7 words.', + $this->currentoutput); } }