mirror of
https://github.com/moodle/moodle.git
synced 2025-08-04 16:36:37 +02:00
MDL-39980 question engine: Attempt on last loses response files
When doing Each attempt builds on last, we need to copy any response files into a draft file area, and then re-save them. While writing the unit test for this, I had to deal with a todo in the question engine so that questions with files in the response could be unit-tested. I also found an fixed a bug with qtype_essay_question::is_same_response and fixed some notices in the existing essay/manual graded unit tests.
This commit is contained in:
parent
838d78a9ff
commit
afb1b3d03b
8 changed files with 221 additions and 18 deletions
|
@ -38,7 +38,7 @@ require_once(dirname(__FILE__) . '/../../../engine/tests/helpers.php');
|
|||
* @copyright 2009 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class qbehaviour_manualgraded_walkthrough_test extends qbehaviour_walkthrough_test_base {
|
||||
class qbehaviour_manualgraded_walkthrough_testcase extends qbehaviour_walkthrough_test_base {
|
||||
public function test_manual_graded_essay() {
|
||||
|
||||
// Create an essay question.
|
||||
|
@ -56,7 +56,7 @@ class qbehaviour_manualgraded_walkthrough_test extends qbehaviour_walkthrough_te
|
|||
$this->get_does_not_contain_feedback_expectation());
|
||||
|
||||
// Simulate some data submitted by the student.
|
||||
$this->process_submission(array('answer' => 'This is my wonderful essay!', 'answerformat' => FORMAT_PLAIN));
|
||||
$this->process_submission(array('answer' => 'This is my wonderful essay!', 'answerformat' => FORMAT_HTML));
|
||||
|
||||
// Verify.
|
||||
$this->check_current_state(question_state::$complete);
|
||||
|
@ -68,16 +68,16 @@ class qbehaviour_manualgraded_walkthrough_test extends qbehaviour_walkthrough_te
|
|||
|
||||
// Process the same data again, check it does not create a new step.
|
||||
$numsteps = $this->get_step_count();
|
||||
$this->process_submission(array('answer' => 'This is my wonderful essay!', 'answerformat' => FORMAT_PLAIN));
|
||||
$this->process_submission(array('answer' => 'This is my wonderful essay!', 'answerformat' => FORMAT_HTML));
|
||||
$this->check_step_count($numsteps);
|
||||
|
||||
// Process different data, check it creates a new step.
|
||||
$this->process_submission(array('answer' => ''));
|
||||
$this->process_submission(array('answer' => '', 'answerformat' => FORMAT_HTML));
|
||||
$this->check_step_count($numsteps + 1);
|
||||
$this->check_current_state(question_state::$todo);
|
||||
|
||||
// Change back, check it creates a new step.
|
||||
$this->process_submission(array('answer' => 'This is my wonderful essay!', 'answerformat' => FORMAT_PLAIN));
|
||||
$this->process_submission(array('answer' => 'This is my wonderful essay!', 'answerformat' => FORMAT_HTML));
|
||||
$this->check_step_count($numsteps + 2);
|
||||
|
||||
// Finish the attempt.
|
||||
|
@ -206,7 +206,7 @@ class qbehaviour_manualgraded_walkthrough_test extends qbehaviour_walkthrough_te
|
|||
$this->check_current_mark(null);
|
||||
|
||||
// Simulate some data submitted by the student.
|
||||
$this->process_submission(array('answer' => 'This is my wonderful essay!', 'answerformat' => FORMAT_PLAIN));
|
||||
$this->process_submission(array('answer' => 'This is my wonderful essay!', 'answerformat' => FORMAT_HTML));
|
||||
|
||||
// Verify.
|
||||
$this->check_current_state(question_state::$complete);
|
||||
|
@ -283,7 +283,7 @@ class qbehaviour_manualgraded_walkthrough_test extends qbehaviour_walkthrough_te
|
|||
$this->get_does_not_contain_feedback_expectation());
|
||||
|
||||
// Simulate some data submitted by the student.
|
||||
$this->process_submission(array('answer' => 'This is my wonderful essay!', 'answerformat' => FORMAT_PLAIN));
|
||||
$this->process_submission(array('answer' => 'This is my wonderful essay!', 'answerformat' => FORMAT_HTML));
|
||||
|
||||
// Verify.
|
||||
$this->check_current_state(question_state::$complete);
|
||||
|
|
|
@ -1379,6 +1379,35 @@ class question_file_loader implements question_response_files {
|
|||
public function get_files() {
|
||||
return $this->step->get_qt_files($this->name, $this->contextid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy these files into a draft area, and return the corresponding
|
||||
* {@link question_file_saver} that can save them again.
|
||||
*
|
||||
* This is used by {@link question_attempt::start_based_on()}, which is used
|
||||
* (for example) by the quizzes 'Each attempt builds on last' feature.
|
||||
*
|
||||
* @return question_file_saver that can re-save these files again.
|
||||
*/
|
||||
public function get_question_file_saver() {
|
||||
|
||||
// Value will be either a plain MD5 hash, or some real content, followed
|
||||
// by an MD5 hash in a HTML comment. We only want the value in the latter case.
|
||||
if (preg_match('/\s*<!-- File hash: [0-9a-zA-Z]{32} -->\s*$/', $this->value)) {
|
||||
$value = preg_replace('/\s*<!-- File hash: [0-9a-zA-Z]{32} -->\s*$/', '', $this->value);
|
||||
|
||||
} else if (preg_match('/^[0-9a-zA-Z]{32}$/', $this->value)) {
|
||||
$value = null;
|
||||
|
||||
} else {
|
||||
throw new coding_exception('$value passed to question_file_loader::get_question_file_saver' .
|
||||
' was not of the expected form.');
|
||||
}
|
||||
|
||||
list($draftid, $text) = $this->step->prepare_response_files_draft_itemid_with_text(
|
||||
$this->name, $this->contextid, $value);
|
||||
return new question_file_saver($draftid, 'question', 'response_' . $this->name, $text);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -923,7 +923,13 @@ class question_attempt {
|
|||
* @return array name => value pairs.
|
||||
*/
|
||||
protected function get_resume_data() {
|
||||
return $this->behaviour->get_resume_data();
|
||||
$resumedata = $this->behaviour->get_resume_data();
|
||||
foreach ($resumedata as $name => $value) {
|
||||
if ($value instanceof question_file_loader) {
|
||||
$resumedata[$name] = $value->get_question_file_saver();
|
||||
}
|
||||
}
|
||||
return $resumedata;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -975,11 +981,12 @@ class question_attempt {
|
|||
*/
|
||||
protected function process_response_files($name, $draftidname, $postdata = null, $text = null) {
|
||||
if ($postdata) {
|
||||
// There can be no files with test data (at the moment).
|
||||
return null;
|
||||
// For simulated posts, get the draft itemid from there.
|
||||
$draftitemid = $this->get_submitted_var($draftidname, PARAM_INT, $postdata);
|
||||
} else {
|
||||
$draftitemid = file_get_submitted_draft_itemid($draftidname);
|
||||
}
|
||||
|
||||
$draftitemid = file_get_submitted_draft_itemid($draftidname);
|
||||
if (!$draftitemid) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -106,7 +106,7 @@ class question_attempt_step {
|
|||
global $USER;
|
||||
|
||||
if (!is_array($data)) {
|
||||
echo format_backtrace(debug_backtrace());
|
||||
throw new coding_exception('$data must be an array when constructing a question_attempt_step.');
|
||||
}
|
||||
$this->state = question_state::$unprocessed;
|
||||
$this->data = $data;
|
||||
|
|
|
@ -87,12 +87,12 @@ class qtype_essay_question extends question_with_responses {
|
|||
|
||||
public function is_same_response(array $prevresponse, array $newresponse) {
|
||||
if (array_key_exists('answer', $prevresponse) && $prevresponse['answer'] !== $this->responsetemplate) {
|
||||
$value1 = $prevresponse['answer'];
|
||||
$value1 = (string) $prevresponse['answer'];
|
||||
} else {
|
||||
$value1 = '';
|
||||
}
|
||||
if (array_key_exists('answer', $newresponse) && $newresponse['answer'] !== $this->responsetemplate) {
|
||||
$value2 = $newresponse['answer'];
|
||||
$value2 = (string) $newresponse['answer'];
|
||||
} else {
|
||||
$value2 = '';
|
||||
}
|
||||
|
|
|
@ -78,6 +78,29 @@ class qtype_essay_test_helper extends question_test_helper {
|
|||
return $q;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the data what would be received from the editing form for an essay
|
||||
* question using the HTML editor allowing embedded files as input, and up
|
||||
* to three attachments.
|
||||
*
|
||||
* @return stdClass the data that would be returned by $form->get_gata();
|
||||
*/
|
||||
public function get_essay_question_form_data_editorfilepicker() {
|
||||
$fromform = new stdClass();
|
||||
|
||||
$fromform->name = 'Essay question with filepicker and attachments';
|
||||
$fromform->questiontext = array('text' => 'Please write a story about a frog.', 'format' => FORMAT_HTML);
|
||||
$fromform->defaultmark = 1.0;
|
||||
$fromform->generalfeedback = array('text' => 'I hope your story had a beginning, a middle and an end.', 'format' => FORMAT_HTML);
|
||||
$fromform->responseformat = 'editorfilepicker';
|
||||
$fromform->responsefieldlines = 10;
|
||||
$fromform->attachments = 3;
|
||||
$fromform->graderinfo = array('text' => '', 'format' => FORMAT_HTML);
|
||||
$fromform->responsetemplate = array('text' => '', 'format' => FORMAT_HTML);
|
||||
|
||||
return $fromform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes an essay question using plain text input.
|
||||
* @return qtype_essay_question
|
||||
|
|
|
@ -36,7 +36,7 @@ require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
|
|||
* @copyright 2009 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class qtype_essay_question_test extends advanced_testcase {
|
||||
class qtype_essay_question_testcase extends advanced_testcase {
|
||||
public function test_get_question_summary() {
|
||||
$essay = test_question_maker::make_an_essay_question();
|
||||
$essay->questiontext = 'Hello <img src="http://example.com/globe.png" alt="world" />';
|
||||
|
@ -46,8 +46,8 @@ class qtype_essay_question_test extends advanced_testcase {
|
|||
public function test_summarise_response() {
|
||||
$longstring = str_repeat('0123456789', 50);
|
||||
$essay = test_question_maker::make_an_essay_question();
|
||||
$this->assertEquals($longstring,
|
||||
$essay->summarise_response(array('answer' => $longstring, 'answerformat' => FORMAT_PLAIN)));
|
||||
$this->assertEquals($longstring, $essay->summarise_response(
|
||||
array('answer' => $longstring, 'answerformat' => FORMAT_HTML)));
|
||||
}
|
||||
|
||||
public function test_is_same_response() {
|
||||
|
|
|
@ -35,7 +35,7 @@ require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
|
|||
* @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 {
|
||||
class qtype_essay_walkthrough_testcase extends qbehaviour_walkthrough_test_base {
|
||||
|
||||
protected function check_contains_textarea($name, $content = '', $height = 10) {
|
||||
$fieldname = $this->quba->get_field_prefix($this->slot) . $name;
|
||||
|
@ -50,6 +50,28 @@ class qtype_essay_walkthrough_test extends qbehaviour_walkthrough_test_base {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method: Store a test file with a given name and contents in a
|
||||
* draft file area.
|
||||
*
|
||||
* @param int $usercontextid user context id.
|
||||
* @param int $draftitemid draft item id.
|
||||
* @param string $filename filename.
|
||||
* @param string $contents file contents.
|
||||
*/
|
||||
protected function save_file_to_draft_area($usercontextid, $draftitemid, $filename, $contents) {
|
||||
$fs = get_file_storage();
|
||||
|
||||
$filerecord = new stdClass();
|
||||
$filerecord->contextid = $usercontextid;
|
||||
$filerecord->component = 'user';
|
||||
$filerecord->filearea = 'draft';
|
||||
$filerecord->itemid = $draftitemid;
|
||||
$filerecord->filepath = '/';
|
||||
$filerecord->filename = $filename;
|
||||
$fs->create_file_from_string($filerecord, $contents);
|
||||
}
|
||||
|
||||
public function test_deferred_feedback_html_editor() {
|
||||
|
||||
// Create an essay question.
|
||||
|
@ -204,4 +226,126 @@ class qtype_essay_walkthrough_test extends qbehaviour_walkthrough_test_base {
|
|||
$this->get_contains_question_text_expectation($q),
|
||||
$this->get_contains_general_feedback_expectation($q));
|
||||
}
|
||||
|
||||
public function test_deferred_feedback_html_editor_with_files_attempt_on_last() {
|
||||
global $CFG, $USER;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
$usercontextid = context_user::instance($USER->id)->id;
|
||||
$fs = get_file_storage();
|
||||
|
||||
// Create an essay question in the DB.
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$cat = $generator->create_question_category();
|
||||
$question = $generator->create_question('essay', 'editorfilepicker', array('category' => $cat->id));
|
||||
|
||||
// Start attempt at the question.
|
||||
$q = question_bank::load_question($question->id);
|
||||
$this->start_attempt_at_question($q, 'deferredfeedback', 1);
|
||||
|
||||
$this->check_current_state(question_state::$todo);
|
||||
$this->check_current_mark(null);
|
||||
$this->check_step_count(1);
|
||||
|
||||
// Process a response and check the expected result.
|
||||
// First we need to get the draft item ids.
|
||||
$this->render();
|
||||
if (!preg_match('/env=editor&.*?itemid=(\d+)&/', $this->currentoutput, $matches)) {
|
||||
throw new coding_exception('Editor draft item id not found.');
|
||||
}
|
||||
$editordraftid = $matches[1];
|
||||
if (!preg_match('/env=filemanager&action=browse&.*?itemid=(\d+)&/', $this->currentoutput, $matches)) {
|
||||
throw new coding_exception('File manager draft item id not found.');
|
||||
}
|
||||
$attachementsdraftid = $matches[1];
|
||||
|
||||
$this->save_file_to_draft_area($usercontextid, $editordraftid, 'smile.txt', ':-)');
|
||||
$this->save_file_to_draft_area($usercontextid, $attachementsdraftid, 'greeting.txt', 'Hello world!');
|
||||
$this->process_submission(array(
|
||||
'answer' => 'Here is a picture: <img src="' . $CFG->wwwroot .
|
||||
"/draftfile.php/{$usercontextid}/user/draft/{$editordraftid}/smile.txt" .
|
||||
'" alt="smile">.',
|
||||
'answerformat' => FORMAT_HTML,
|
||||
'answer:itemid' => $editordraftid,
|
||||
'attachments' => $attachementsdraftid));
|
||||
|
||||
$this->check_current_state(question_state::$complete);
|
||||
$this->check_current_mark(null);
|
||||
$this->check_step_count(2);
|
||||
$this->save_quba();
|
||||
|
||||
// Save the same response again, and verify no new step is created.
|
||||
$this->load_quba();
|
||||
|
||||
$this->render();
|
||||
if (!preg_match('/env=editor&.*?itemid=(\d+)&/', $this->currentoutput, $matches)) {
|
||||
throw new coding_exception('Editor draft item id not found.');
|
||||
}
|
||||
$editordraftid = $matches[1];
|
||||
if (!preg_match('/env=filemanager&action=browse&.*?itemid=(\d+)&/', $this->currentoutput, $matches)) {
|
||||
throw new coding_exception('File manager draft item id not found.');
|
||||
}
|
||||
$attachementsdraftid = $matches[1];
|
||||
|
||||
$this->process_submission(array(
|
||||
'answer' => 'Here is a picture: <img src="' . $CFG->wwwroot .
|
||||
"/draftfile.php/{$usercontextid}/user/draft/{$editordraftid}/smile.txt" .
|
||||
'" alt="smile">.',
|
||||
'answerformat' => FORMAT_HTML,
|
||||
'answer:itemid' => $editordraftid,
|
||||
'attachments' => $attachementsdraftid));
|
||||
|
||||
$this->check_current_state(question_state::$complete);
|
||||
$this->check_current_mark(null);
|
||||
$this->check_step_count(2);
|
||||
|
||||
// Now submit all and finish.
|
||||
$this->finish();
|
||||
$this->check_current_state(question_state::$needsgrading);
|
||||
$this->check_current_mark(null);
|
||||
$this->check_step_count(3);
|
||||
$this->save_quba();
|
||||
|
||||
// Now start a new attempt based on the old one.
|
||||
$this->load_quba();
|
||||
$oldqa = $this->get_question_attempt();
|
||||
|
||||
$q = question_bank::load_question($question->id);
|
||||
$this->quba = question_engine::make_questions_usage_by_activity('unit_test',
|
||||
context_system::instance());
|
||||
$this->quba->set_preferred_behaviour('deferredfeedback');
|
||||
$this->slot = $this->quba->add_question($q, 1);
|
||||
$this->quba->start_question_based_on($this->slot, $oldqa);
|
||||
|
||||
$this->check_current_state(question_state::$complete);
|
||||
$this->check_current_mark(null);
|
||||
$this->check_step_count(1);
|
||||
$this->save_quba();
|
||||
|
||||
// Now save the same response again, and ensure that a new step is not created.
|
||||
$this->load_quba();
|
||||
|
||||
$this->render();
|
||||
if (!preg_match('/env=editor&.*?itemid=(\d+)&/', $this->currentoutput, $matches)) {
|
||||
throw new coding_exception('Editor draft item id not found.');
|
||||
}
|
||||
$editordraftid = $matches[1];
|
||||
if (!preg_match('/env=filemanager&action=browse&.*?itemid=(\d+)&/', $this->currentoutput, $matches)) {
|
||||
throw new coding_exception('File manager draft item id not found.');
|
||||
}
|
||||
$attachementsdraftid = $matches[1];
|
||||
|
||||
$this->process_submission(array(
|
||||
'answer' => 'Here is a picture: <img src="' . $CFG->wwwroot .
|
||||
"/draftfile.php/{$usercontextid}/user/draft/{$editordraftid}/smile.txt" .
|
||||
'" alt="smile">.',
|
||||
'answerformat' => FORMAT_HTML,
|
||||
'answer:itemid' => $editordraftid,
|
||||
'attachments' => $attachementsdraftid));
|
||||
|
||||
$this->check_current_state(question_state::$complete);
|
||||
$this->check_current_mark(null);
|
||||
$this->check_step_count(1);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue