From 53291c7256286f597cc557d0a2f1fa2e6171f3a5 Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Mon, 4 Feb 2013 12:54:10 +0000 Subject: [PATCH] MDL-37847 plain text essays were screwing up HTML special chars. This was incorrect use of PARAM_CLEANHTML for these inputs. This fix also adds some unit tests to try to verify that this does not break again in future. --- question/engine/tests/helpers.php | 16 +- question/type/essay/question.php | 4 +- question/type/essay/tests/helper.php | 100 +++++++++++ .../type/essay/tests/walkthrough_test.php | 156 ++++++++++++++++++ 4 files changed, 274 insertions(+), 2 deletions(-) create mode 100644 question/type/essay/tests/helper.php create mode 100644 question/type/essay/tests/walkthrough_test.php diff --git a/question/engine/tests/helpers.php b/question/engine/tests/helpers.php index a03d0eacdbbfa..703ee8d906550 100644 --- a/question/engine/tests/helpers.php +++ b/question/engine/tests/helpers.php @@ -619,8 +619,14 @@ abstract class qbehaviour_walkthrough_test_base extends question_testcase { protected $displayoptions; /** @var question_usage_by_activity */ protected $quba; - /** @var unknown_type integer */ + /** @var integer */ + protected $slot; + /** + * @var string after {@link render()} has been called, this contains the + * display of the question in its current state. + */ + protected $currentoutput = ''; protected function setUp() { parent::setUp(); @@ -670,6 +676,14 @@ protected function check_current_mark($mark) { } } + /** + * Generate the HTML rendering of the question in its current state in + * $this->currentoutput so that it can be verified. + */ + protected function render() { + $this->currentoutput = $this->quba->render_question($this->slot, $this->displayoptions); + } + /** * @param $condition one or more Expectations. (users varargs). */ diff --git a/question/type/essay/question.php b/question/type/essay/question.php index 3f3c2c4e284f1..ef9b6bf9ce4c6 100644 --- a/question/type/essay/question.php +++ b/question/type/essay/question.php @@ -56,8 +56,10 @@ public function get_format_renderer(moodle_page $page) { public function get_expected_data() { if ($this->responseformat == 'editorfilepicker') { $expecteddata = array('answer' => question_attempt::PARAM_CLEANHTML_FILES); - } else { + } else if ($this->responseformat == 'editor') { $expecteddata = array('answer' => PARAM_CLEANHTML); + } else { + $expecteddata = array('answer' => PARAM_RAW); } $expecteddata['answerformat'] = PARAM_FORMAT; if ($this->attachments != 0) { diff --git a/question/type/essay/tests/helper.php b/question/type/essay/tests/helper.php new file mode 100644 index 0000000000000..e7484aafe06a9 --- /dev/null +++ b/question/type/essay/tests/helper.php @@ -0,0 +1,100 @@ +. + +/** + * Test helpers for the essay question type. + * + * @package qtype_essay + * @copyright 2013 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +defined('MOODLE_INTERNAL') || die(); + + +/** + * Test helper class for the essay question type. + * + * @copyright 2013 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class qtype_essay_test_helper extends question_test_helper { + public function get_test_questions() { + return array('editor', 'editorfilepicker', 'plain', 'monospaced'); + } + + /** + * Helper method to reduce duplication. + * @return qtype_essay_question + */ + protected function initialise_essay_question() { + question_bank::load_question_definition_classes('essay'); + $q = new qtype_essay_question(); + test_question_maker::initialise_a_question($q); + $q->name = 'Essay question (HTML editor)'; + $q->questiontext = 'Please write a story about a frog.'; + $q->generalfeedback = 'I hope your story had a beginning, a middle and an end.'; + $q->responseformat = 'editor'; + $q->responsefieldlines = 10; + $q->attachments = 0; + $q->graderinfo = ''; + $q->graderinfoformat = FORMAT_HTML; + $q->qtype = question_bank::get_qtype('essay'); + + return $q; + } + + /** + * Makes an essay question using the HTML editor as input. + * @return qtype_essay_question + */ + public function make_essay_question_editor() { + return $this->initialise_essay_question(); + } + + /** + * Makes an essay question using the HTML editor allowing embedded files as + * input, and up to three attachments. + * @return qtype_essay_question + */ + public function make_essay_question_editorfilepicker() { + $q = $this->initialise_essay_question(); + $q->responseformat = 'editorfilepicker'; + $q->attachments = 3; + return $q; + } + + /** + * Makes an essay question using plain text input. + * @return qtype_essay_question + */ + public function make_essay_question_plain() { + $q = $this->initialise_essay_question(); + $q->responseformat = 'plain'; + return $q; + } + + /** + * Makes an essay question using monospaced input. + * @return qtype_essay_question + */ + public function make_essay_question_monospaced() { + $q = $this->initialise_essay_question(); + $q->responseformat = 'monospaced'; + return $q; + } +} diff --git a/question/type/essay/tests/walkthrough_test.php b/question/type/essay/tests/walkthrough_test.php new file mode 100644 index 0000000000000..301edd3c6fd1a --- /dev/null +++ b/question/type/essay/tests/walkthrough_test.php @@ -0,0 +1,156 @@ +. + +/** + * This file contains tests that walks essay questions through some attempts. + * + * @package qtype_essay + * @copyright 2013 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->dirroot . '/question/engine/tests/helpers.php'); + + +/** + * Unit tests for the essay question type. + * + * @copyright 2013 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class qtype_essay_walkthrough_test extends qbehaviour_walkthrough_test_base { + + protected function check_contains_textarea($name, $content = '', $height = 10) { + $fieldname = $this->quba->get_field_prefix($this->slot) . $name; + + $this->assertTag(array('tag' => 'textarea', + 'attributes' => array('cols' => '60', 'rows' => $height, + 'name' => $fieldname)), + $this->currentoutput); + + if ($content) { + $this->assertRegExp('/' . preg_quote(s($content), '/') . '/', $this->currentoutput); + } + } + + public function test_deferred_feedback_html_editor() { + + // Create a matching question. + $q = test_question_maker::make_question('essay', 'editor'); + $this->start_attempt_at_question($q, 'deferredfeedback', 1); + + $prefix = $this->quba->get_field_prefix($this->slot); + $fieldname = $prefix . 'answer'; + $response = '

The cat sat on the mat. Then it ate a frog.

'; + + // Check the initial state. + $this->check_current_state(question_state::$todo); + $this->check_current_mark(null); + $this->render(); + $this->check_contains_textarea('answer', ''); + $this->check_current_output( + $this->get_contains_question_text_expectation($q), + $this->get_does_not_contain_feedback_expectation()); + $this->check_step_count(1); + + // Save a response. + $this->quba->process_all_actions(null, array( + 'slots' => $this->slot, + $fieldname => $response, + $fieldname . 'format' => FORMAT_HTML, + $prefix . ':sequencecheck' => '1', + )); + + // Verify. + $this->check_current_state(question_state::$complete); + $this->check_current_mark(null); + $this->check_step_count(2); + $this->render(); + $this->check_contains_textarea('answer', $response); + $this->check_current_output( + $this->get_contains_question_text_expectation($q), + $this->get_does_not_contain_feedback_expectation()); + $this->check_step_count(2); + + // Finish the attempt. + $this->quba->finish_all_questions(); + + // Verify. + $this->check_current_state(question_state::$needsgrading); + $this->check_current_mark(null); + $this->render(); + $this->assertRegExp('/' . preg_quote($response, '/') . '/', $this->currentoutput); + $this->check_current_output( + $this->get_contains_question_text_expectation($q), + $this->get_contains_general_feedback_expectation($q)); + } + + public function test_deferred_feedback_plain_text() { + + // Create a matching question. + $q = test_question_maker::make_question('essay', 'plain'); + $this->start_attempt_at_question($q, 'deferredfeedback', 1); + + $prefix = $this->quba->get_field_prefix($this->slot); + $fieldname = $prefix . 'answer'; + $response = "x < 1\nx > 0\nFrog & Toad were friends."; + + // Check the initial state. + $this->check_current_state(question_state::$todo); + $this->check_current_mark(null); + $this->render(); + $this->check_contains_textarea('answer', ''); + $this->check_current_output( + $this->get_contains_question_text_expectation($q), + $this->get_does_not_contain_feedback_expectation()); + $this->check_step_count(1); + + // Save a response. + $this->quba->process_all_actions(null, array( + 'slots' => $this->slot, + $fieldname => $response, + $fieldname . 'format' => FORMAT_HTML, + $prefix . ':sequencecheck' => '1', + )); + + // Verify. + $this->check_current_state(question_state::$complete); + $this->check_current_mark(null); + $this->check_step_count(2); + $this->render(); + $this->check_contains_textarea('answer', $response); + $this->check_current_output( + $this->get_contains_question_text_expectation($q), + $this->get_does_not_contain_feedback_expectation()); + $this->check_step_count(2); + + // Finish the attempt. + $this->quba->finish_all_questions(); + + // Verify. + $this->check_current_state(question_state::$needsgrading); + $this->check_current_mark(null); + $this->render(); + $this->assertRegExp('/' . preg_quote(s($response), '/') . '/', $this->currentoutput); + $this->check_current_output( + $this->get_contains_question_text_expectation($q), + $this->get_contains_general_feedback_expectation($q)); + } +}