From a2bb148ae33b1ff47aed65d65f55e25f2a69c009 Mon Sep 17 00:00:00 2001
From: Tim Hunt
Date: Fri, 28 Jan 2011 19:07:33 +0000
Subject: [PATCH] MDL-47494 ddimageortext: work-in-progress converting the
ddwtos and gapselect qtypes.
---
question/type/ddimageortext/db/install.xml | 23 +
question/type/ddimageortext/db/upgrade.php | 83 +++
.../type/ddimageortext/edit_ddwtos_form.php | 69 ++
question/type/ddimageortext/icon.gif | Bin 0 -> 867 bytes
.../ddimageortext/lang/en/qtype_ddwtos.php | 13 +
question/type/ddimageortext/question.php | 59 ++
question/type/ddimageortext/questiontype.php | 224 ++++++
question/type/ddimageortext/renderer.php | 181 +++++
question/type/ddimageortext/script.js | 562 +++++++++++++++
.../type/ddimageortext/simpletest/helper.php | 97 +++
.../ddimageortext/simpletest/testquestion.php | 246 +++++++
.../simpletest/testquestiontype.php | 470 ++++++++++++
.../simpletest/testwalkthrough.php | 680 ++++++++++++++++++
question/type/ddimageortext/styles.css | 67 ++
question/type/ddimageortext/version.php | 4 +
15 files changed, 2778 insertions(+)
create mode 100755 question/type/ddimageortext/db/install.xml
create mode 100755 question/type/ddimageortext/db/upgrade.php
create mode 100755 question/type/ddimageortext/edit_ddwtos_form.php
create mode 100755 question/type/ddimageortext/icon.gif
create mode 100755 question/type/ddimageortext/lang/en/qtype_ddwtos.php
create mode 100755 question/type/ddimageortext/question.php
create mode 100755 question/type/ddimageortext/questiontype.php
create mode 100755 question/type/ddimageortext/renderer.php
create mode 100755 question/type/ddimageortext/script.js
create mode 100755 question/type/ddimageortext/simpletest/helper.php
create mode 100755 question/type/ddimageortext/simpletest/testquestion.php
create mode 100755 question/type/ddimageortext/simpletest/testquestiontype.php
create mode 100755 question/type/ddimageortext/simpletest/testwalkthrough.php
create mode 100755 question/type/ddimageortext/styles.css
create mode 100755 question/type/ddimageortext/version.php
diff --git a/question/type/ddimageortext/db/install.xml b/question/type/ddimageortext/db/install.xml
new file mode 100755
index 00000000000..065853d37b1
--- /dev/null
+++ b/question/type/ddimageortext/db/install.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/question/type/ddimageortext/db/upgrade.php b/question/type/ddimageortext/db/upgrade.php
new file mode 100755
index 00000000000..91d665a1685
--- /dev/null
+++ b/question/type/ddimageortext/db/upgrade.php
@@ -0,0 +1,83 @@
+question = $ddanswer->questionid;
+ $answer->answer = addslashes($ddanswer->answer);
+ $answer->fraction = 1;
+
+ $feedback = new stdClass;
+ $feedback->draggroup = $ddanswer->draggroup;
+ $feedback->infinite = $ddanswer->infinite;
+ $answer->feedback = serialize($feedback);
+
+ if(!insert_record('question_answers', $answer)){
+ notify('move_question_ddanswers_to_question_answers(): cannot insert row into question_answer table.');
+ return false;
+ }
+ }
+ }
+
+ // Drop table
+ $result = $result && drop_table($table);
+ }
+
+ if ($result && $oldversion < 2010042800) {
+
+ /// Rename field correctresponsesfeedback on table question_ddwtos to shownumcorrect
+ $table = new XMLDBTable('question_ddwtos');
+ $field = new XMLDBField('correctresponsesfeedback');
+ $field->setAttributes(XMLDB_TYPE_INTEGER, '2', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0', 'incorrectfeedback');
+
+ /// Launch rename field correctresponsesfeedback
+ $result = $result && rename_field($table, $field, 'shownumcorrect');
+ }
+
+ return $result;
+}
diff --git a/question/type/ddimageortext/edit_ddwtos_form.php b/question/type/ddimageortext/edit_ddwtos_form.php
new file mode 100755
index 00000000000..dec87f43e86
--- /dev/null
+++ b/question/type/ddimageortext/edit_ddwtos_form.php
@@ -0,0 +1,69 @@
+.
+
+
+/**
+ * Defines the editing form for the drag-and-drop words into sentences question type.
+ *
+ * @package qtype_ddwtos
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once($CFG->dirroot . '/question/type/sddl/edit_form_base.php');
+
+
+/**
+ * Drag-and-drop words into sentences editing form definition.
+ *
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_ddwtos_edit_form extends qtype_gapselect_edit_form_base {
+
+ function qtype() {
+ return 'ddwtos';
+ }
+
+ protected function default_values_from_feedback_field($feedback, $key){
+ $feedback = unserialize($feedback);
+ $draggroup = $feedback->draggroup;
+ $infinite = $feedback->infinite;
+
+ $default_values = array();
+ $default_values['choices['.$key.'][draggroup]'] = $draggroup;
+ $default_values['choices['.$key.'][infinite]'] = $infinite;
+ return $default_values;
+ }
+
+ protected function repeated_options(){
+ $repeatedoptions = array();
+ $repeatedoptions['draggroup']['default'] = '1';
+ $repeatedoptions['infinite']['default'] = 0;
+ return $repeatedoptions;
+ }
+
+ protected function choice_group(&$mform, $grouparray){
+ $options = array();
+ for ($i = 1; $i <= 8; $i += 1) {
+ $options[$i] = $i;
+ }
+ $grouparray[] =& $mform->createElement('select', 'draggroup', get_string('group', 'qtype_ddwtos'), $options);
+ $grouparray[] =& $mform->createElement('checkbox', 'infinite', ' ', get_string('infinite', 'qtype_ddwtos'), null, array('size'=>1, 'class'=>'tweakcss'));
+ return $grouparray;
+ }
+}
diff --git a/question/type/ddimageortext/icon.gif b/question/type/ddimageortext/icon.gif
new file mode 100755
index 0000000000000000000000000000000000000000..f900852b7c387ee8fdbd61f26d031376af7966ac
GIT binary patch
literal 867
zcmZ?wbhEHb6krfw_|Cxa_wV1|zkmPy`Sa`7ub)4E{`m3ZhYug#ym|Bb_3IZeUOa#P
z{PE+*4<9~Uy?XV+g$pN7o?KE=Vr6AzW@ct+XsDr~p{}m3q@*M+E-oY_#K*_Sz`*eT
z|9>Dk3PwX$HPm=1$P=VpEuqZW<8gNHhqm80(b
xSm4OqE2-`F=Ep@v_ij1IFrOJl4^K?jjg9*f@oC|qel5YIniHFsH#IO=0{~))R22XK
literal 0
HcmV?d00001
diff --git a/question/type/ddimageortext/lang/en/qtype_ddwtos.php b/question/type/ddimageortext/lang/en/qtype_ddwtos.php
new file mode 100755
index 00000000000..34b17378121
--- /dev/null
+++ b/question/type/ddimageortext/lang/en/qtype_ddwtos.php
@@ -0,0 +1,13 @@
+.
+
+
+/**
+ * Drag-and-drop words into sentences question definition class.
+ *
+ * @package qtype_ddwtos
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once($CFG->dirroot . '/question/type/gapselect/questionbase.php');
+
+/**
+ * Represents a drag-and-drop words into sentences question.
+ *
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_ddwtos_question extends qtype_gapselect_question_base {
+//is actually exactly the same.
+}
+
+
+/**
+ * Represents one of the choices (draggable boxes).
+ *
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_ddwtos_choice {
+ public $text;
+ public $draggroup;
+ public $isinfinite;
+
+ public function __construct($text, $draggroup = 1, $isinfinite = false) {
+ $this->text = $text;
+ $this->draggroup = $draggroup;
+ $this->isinfinite = $isinfinite;
+ }
+ public function choice_group(){
+ return $this->draggroup;
+ }
+}
diff --git a/question/type/ddimageortext/questiontype.php b/question/type/ddimageortext/questiontype.php
new file mode 100755
index 00000000000..b72e89a2414
--- /dev/null
+++ b/question/type/ddimageortext/questiontype.php
@@ -0,0 +1,224 @@
+.
+
+
+/**
+ * Question type class for the drag-and-drop words into sentences question type.
+ *
+ * @package qtype_ddwtos
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+require_once($CFG->libdir . '/questionlib.php');
+require_once($CFG->dirroot . '/question/engine/lib.php');
+require_once($CFG->dirroot . '/question/format/xml/format.php');
+
+require_once($CFG->dirroot . '/question/type/gapselect/questiontypebase.php');
+
+/**
+ * The drag-and-drop words into sentences question type class.
+ *
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_ddwtos extends qtype_gapselect_base {
+ protected function feedback_serialization($choice){
+ $output = new stdClass;
+ $output->draggroup = $choice['draggroup'];
+ $output->infinite = !empty($choice['infinite']);
+ return serialize($output);
+ }
+
+ protected function feedback_unserialization($choicedata){
+ $options = unserialize($choicedata->feedback);
+ return new qtype_ddwtos_choice($choicedata->answer, $options->draggroup, $options->infinite);
+ }
+
+
+ protected function feedback_unserialize_as_array($feedback){
+ $feedbackobj = unserialize($feedback);
+ return array('draggroup'=> $feedbackobj->draggroup, 'infinite'=> $feedbackobj->infinite);
+ }
+
+ protected function choice_group_key(){
+ return 'draggroup';
+ }
+
+ public function requires_qtypes() {
+ return array('sddl');
+ }
+
+ public function import_from_xml($data, $question, $format, $extra=null) {
+ if (!isset($data['@']['type']) || $data['@']['type'] != 'ddwtos') {
+ return false;
+ }
+
+ $question = $format->import_headers($data);
+ $question->qtype = 'ddwtos';
+
+ $question->shuffleanswers = $format->trans_single(
+ $format->getpath($data, array('#', 'shuffleanswers', 0, '#'), 1));
+
+ if (!empty($data['#']['dragbox'])) {
+ // Modern XML format.
+ $dragboxes = $data['#']['dragbox'];
+ $question->answer = array();
+ $question->draggroup = array();
+ $question->infinite = array();
+
+ foreach ($data['#']['dragbox'] as $dragboxxml) {
+ $question->choices[] = array(
+ 'answer' => $format->getpath($dragboxxml, array('#', 'text', 0, '#'), '', true),
+ 'draggroup' => $format->getpath($dragboxxml, array('#', 'group', 0, '#'), 1),
+ 'infinite' => array_key_exists('infinite', $dragboxxml['#']),
+ );
+ }
+
+ } else {
+ // Legacy format containing PHP serialisation.
+ foreach ($data['#']['answer'] as $answerxml) {
+ $ans = $format->import_answer($answerxml);
+ $options = unserialize(stripslashes($ans->feedback));
+ $question->choices[] = array(
+ 'answer' => $ans->answer,
+ 'draggroup' => $options->draggroup,
+ 'infinite' => $options->infinite,
+ );
+ }
+ }
+
+ $format->import_combined_feedback($question, $data, true);
+ $format->import_hints($question, $data, true);
+
+ return $question;
+ }
+
+ function export_to_xml($question, $format, $extra = null) {
+ $output = '';
+
+ $output .= ' ' . $question->options->shuffleanswers . "\n";
+
+ $output .= $format->write_combined_feedback($question->options);
+
+ foreach ($question->options->answers as $answer) {
+ $options = unserialize($answer->feedback);
+
+ $output .= " \n";
+ $output .= $format->writetext($answer->answer, 3);
+ $output .= " {$options->draggroup}\n";
+ if ($options->infinite) {
+ $output .= " \n";
+ }
+ $output .= " \n";
+ }
+
+ return $output;
+ }
+
+ /*
+ * Backup the data in the question
+ *
+ * This is used in question/backuplib.php
+ */
+ public function backup($bf, $preferences, $question, $level = 6) {
+ $status = true;
+ $ddwtos = get_records("question_ddwtos", "questionid", $question, "id");
+
+ //If there are ddwtos
+ if ($ddwtos) {
+ //Iterate over each ddwtos
+ foreach ($ddwtos as $ddws) {
+ $status = fwrite ($bf,start_tag("DDWORDSSENTENCES",$level,true));
+ //Print oumultiresponse contents
+ fwrite ($bf,full_tag("SHUFFLEANSWERS",$level+1,false,$ddws->shuffleanswers));
+ fwrite ($bf,full_tag("CORRECTFEEDBACK",$level+1,false,$ddws->correctfeedback));
+ fwrite ($bf,full_tag("PARTIALLYCORRECTFEEDBACK",$level+1,false,$ddws->partiallycorrectfeedback));
+ fwrite ($bf,full_tag("INCORRECTFEEDBACK",$level+1,false,$ddws->incorrectfeedback));
+ fwrite ($bf,full_tag("SHOWNUMCORRECT",$level+1,false,$ddws->shownumcorrect));
+ $status = fwrite ($bf,end_tag("DDWORDSSENTENCES",$level,true));
+ }
+
+ //Now print question_answers
+ $status = question_backup_answers($bf,$preferences,$question);
+ }
+ return $status;
+ }
+
+ /**
+ * Restores the data in the question (This is used in question/restorelib.php)
+ *
+ */
+ public function restore($old_question_id,$new_question_id,$info,$restore) {
+ $status = true;
+
+ //Get the ddwtos array
+ $ddwtos = $info['#']['DDWORDSSENTENCES'];
+
+ //Iterate over oumultiresponses
+ for($i = 0; $i < sizeof($ddwtos); $i++) {
+ $mul_info = $ddwtos[$i];
+
+ //Now, build the question_ddwtos record structure
+ $ddwtos = new stdClass;
+ $ddwtos->questionid = $new_question_id;
+ $ddwtos->shuffleanswers = isset($mul_info['#']['SHUFFLEANSWERS']['0']['#'])?backup_todb($mul_info['#']['SHUFFLEANSWERS']['0']['#']):'';
+ if (array_key_exists("CORRECTFEEDBACK", $mul_info['#'])) {
+ $ddwtos->correctfeedback = backup_todb($mul_info['#']['CORRECTFEEDBACK']['0']['#']);
+ } else {
+ $ddwtos->correctfeedback = '';
+ }
+ if (array_key_exists("PARTIALLYCORRECTFEEDBACK", $mul_info['#'])) {
+ $ddwtos->partiallycorrectfeedback = backup_todb($mul_info['#']['PARTIALLYCORRECTFEEDBACK']['0']['#']);
+ } else {
+ $ddwtos->partiallycorrectfeedback = '';
+ }
+ if (array_key_exists("INCORRECTFEEDBACK", $mul_info['#'])) {
+ $ddwtos->incorrectfeedback = backup_todb($mul_info['#']['INCORRECTFEEDBACK']['0']['#']);
+ } else {
+ $ddwtos->incorrectfeedback = '';
+ }
+ if (array_key_exists('SHOWNUMCORRECT', $mul_info['#'])) {
+ $ddwtos->shownumcorrect = backup_todb($mul_info['#']['SHOWNUMCORRECT']['0']['#']);
+ } else if (array_key_exists('CORRECTRESPONSESFEEDBACK', $mul_info['#'])) {
+ $ddwtos->shownumcorrect = backup_todb($mul_info['#']['CORRECTRESPONSESFEEDBACK']['0']['#']);
+ } else {
+ $ddwtos->shownumcorrect = 0;
+ }
+
+ $newid = insert_record ("question_ddwtos",$ddwtos);
+
+ //Do some output
+ if (($i+1) % 50 == 0) {
+ if (!defined('RESTORE_SILENTLY')) {
+ echo ".";
+ if (($i+1) % 1000 == 0) {
+ echo "
";
+ }
+ }
+ backup_flush(300);
+ }
+
+ if (!$newid) {
+ $status = false;
+ }
+ }
+ return $status;
+ }
+
+}
diff --git a/question/type/ddimageortext/renderer.php b/question/type/ddimageortext/renderer.php
new file mode 100755
index 00000000000..8f4362fed1b
--- /dev/null
+++ b/question/type/ddimageortext/renderer.php
@@ -0,0 +1,181 @@
+.
+
+
+/**
+ * Drag-and-drop words into sentences question renderer class.
+ *
+ * @package qtype_ddwtos
+ * @copyright 2010 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once($CFG->dirroot . '/question/type/gapselect/rendererbase.php');
+
+
+/**
+ * Generates the output for drag-and-drop words into sentences questions.
+ *
+ * @copyright 2010 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_ddwtos_renderer extends qtype_elements_embedded_in_question_text_renderer {
+
+ protected function qtext_classname(){
+ return 'qtext ddwtos_questionid_for_javascript';
+ }
+
+ protected function post_qtext_elements(question_attempt $qa, question_display_options $options){
+ $result = '';
+ $question = $qa->get_question();
+ $dragboxs = '';
+ foreach ($question->choices as $group => $choices) {
+ $dragboxs .= $this->drag_boxes($qa, $group,
+ $question->get_ordered_choices($group), $options);
+ }
+ $result .= html_writer::tag('div', $dragboxs,
+ array('class' => 'answercontainer'));
+ // We abuse the clear_wrong method to output the hidden form fields we
+ // want irrespective of whether we are actually clearing the wrong
+ // bits of the response.
+ if (!$options->clearwrong) {
+ $result .= $this->clear_wrong($qa, false);
+ }
+ return $result;
+ }
+
+ /**
+ * Modify the contents of a drag/drop box to fix some IE-related problem.
+ * Unfortunately I don't have more details than that.
+ * @param string $string the box contents.
+ * @return string the box contents modified.
+ */
+ protected function dodgy_ie_fix($string) {
+ return ' ' . $string . ' ';
+ }
+
+ protected function embedded_element(question_attempt $qa, $place, question_display_options $options) {
+ $question = $qa->get_question();
+ $group = $question->places[$place];
+ $boxcontents = $this->dodgy_ie_fix(' ');
+
+ $value = $qa->get_last_qt_var($question->field($place));
+
+ $attributes = array(
+ 'id' => $this->box_id($qa, 'p' . $place, $group),
+ 'class' => 'slot group' . $group
+ );
+
+ if ($options->readonly) {
+ $attributes['class'] .= ' readonly';
+ } else {
+ $attributes['tabindex'] = '0';
+ }
+
+ $feedbackimage = '';
+ if ($options->correctness) {
+ $response = $qa->get_last_qt_data();
+ $fieldname = $question->field($place);
+ if (array_key_exists($fieldname, $response)) {
+ $fraction = (int) ($response[$fieldname] == $question->get_right_choice_for($place));
+ $attributes['class'] .= ' ' . $this->feedback_class($fraction);
+ $feedbackimage = $this->feedback_image($fraction);
+ }
+ }
+
+ return html_writer::tag('span', $boxcontents, $attributes) . ' ' . $feedbackimage;
+ }
+
+ protected function drag_boxes($qa, $group, $choices, question_display_options $options) {
+ $readonly = '';
+ if ($options->readonly) {
+ $readonly = ' readonly';
+ }
+
+ $boxes = '';
+ foreach ($choices as $key => $choice) {
+ //Bug 8632 - long text entry causes bug in drag and drop field in IE
+ $content = str_replace('-', '‑', $choice->text);
+ $content = $this->dodgy_ie_fix(str_replace(' ', ' ', $content));
+
+ $infinite = '';
+ if ($choice->isinfinite) {
+ $infinite = ' infinite';
+ }
+
+ $boxes .= html_writer::tag('span', $content, array(
+ 'id' => $this->box_id($qa, $key, $choice->draggroup),
+ 'class' => 'player group' . $choice->draggroup . $infinite . $readonly)) . ' ';
+ }
+
+ return html_writer::nonempty_tag('div', $boxes, array('class' => 'answertext'));
+ }
+
+
+ public function head_code(question_attempt $qa) {
+ require_js(array('yui_dom-event', 'yui_dragdrop'));
+ return parent::head_code($qa);
+ }
+
+ /**
+ * Actually, this question type abuses this method to always ouptut the
+ * hidden fields it needs.
+ */
+ public function clear_wrong(question_attempt $qa, $reallyclear = true) {
+ $question = $qa->get_question();
+ $response = $qa->get_last_qt_data();
+
+ if (!empty($response) && $reallyclear) {
+ $cleanresponse = $question->clear_wrong_from_response($response);
+ } else {
+ $cleanresponse = $response;
+ }
+
+ $output = '';
+ foreach ($question->places as $place => $group) {
+ $fieldname = $question->field($place);
+ if (array_key_exists($fieldname, $response)) {
+ $value = $response[$fieldname];
+ } else {
+ $value = '0';
+ }
+ if (array_key_exists($fieldname, $cleanresponse)) {
+ $cleanvalue = $cleanresponse[$fieldname];
+ } else {
+ $cleanvalue = '0';
+ }
+ if ($cleanvalue != $value) {
+ $output .= html_writer::empty_tag('input', array(
+ 'type' => 'hidden',
+ 'id' => $this->box_id($qa, 'p' . $place, $group) . '_hidden',
+ 'value' => s($value))) .
+ html_writer::empty_tag('input', array(
+ 'type' => 'hidden',
+ 'name' => $qa->get_qt_field_name($fieldname),
+ 'value' => s($cleanvalue)));
+ } else {
+ $output .= html_writer::empty_tag('input', array(
+ 'type' => 'hidden',
+ 'id' => $this->box_id($qa, 'p' . $place, $group) . '_hidden',
+ 'name' => $qa->get_qt_field_name($fieldname),
+ 'value' => s($value)));
+ }
+ }
+ return $output;
+ }
+
+}
diff --git a/question/type/ddimageortext/script.js b/question/type/ddimageortext/script.js
new file mode 100755
index 00000000000..0f0a4807959
--- /dev/null
+++ b/question/type/ddimageortext/script.js
@@ -0,0 +1,562 @@
+/**
+ * JavaScript objects, functions as well as usage of some YUI library for
+ * enabling drag and drop interaction for dran-anddrop words into sentences
+ * (ddwtos)
+ *
+ * @package qtype_ddwtos
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+//global variables
+var ddwtos_currentzindex = 10;
+
+
+/*
+ * The way it seems to be, if there are more than one of this type of question
+ * in a quiz, then this file is shared between them. Therefore it has to cope
+ * with ALL the questions of this type on the page.
+ */
+(function() {
+
+ // start of App object SHARED BY ALL QUESTIONS OF THIS TYPE IN THE QUIZ /////
+ YAHOO.example.DDApp = {
+ init : function() {
+ var questionspans = YAHOO.util.Dom.getElementsByClassName("ddwtos_questionid_for_javascript");
+
+ // we need this loop in case of more than one of this qtype on one page
+ for (var i = 0; i < questionspans.length; i++) {
+ // The Questions object should now contain a QuestionDataObject
+ //object for each question of this type in the quiz.
+ Questions[questionspans[i].id] = new QuestionDataObject(questionspans[i].id);
+ }
+
+ // populate the arrays "slots" and "players" for each question
+ var tempSlots = YAHOO.util.Dom.getElementsByClassName("slot", "span");
+ var tempPlayers = YAHOO.util.Dom.getElementsByClassName("player", "span");
+
+ //ie7 zoom message
+ ie7_zoom_message();
+
+ for (var i = 0; i < tempSlots.length; i++) {
+ var name_prefix = tempSlots[i].id.split("_")[0] + "_";
+ var q = Questions[name_prefix];
+ var g = getGroupForThis(tempSlots[i].id);
+
+ var ddtarget = new YAHOO.util.DDTarget(tempSlots[i].id, g);
+ q.tempSlots.push(tempSlots[i]);
+ q.slots.push(ddtarget);
+ }
+
+ for (var i = 0; i < tempPlayers.length; i++) {
+ var name_prefix = tempPlayers[i].id.split("_")[0] + "_";
+ var q = Questions[name_prefix];
+ var g = getGroupForThis(tempPlayers[i].id);
+ var ddplayer = new YAHOO.example.DDPlayer(tempPlayers[i].id, g);
+ q.tempPlayers.push(tempPlayers[i]);
+ q.players.push(ddplayer);
+ }
+
+ for (var i = 0; i < questionspans.length; i++) {
+ var q = Questions[questionspans[i].id];
+ var groupwidth = getWidthForAllGroups(q.tempPlayers);
+ for (var j = 0; j < q.tempSlots.length; j++) {
+ var g = getGroupForThis(q.tempSlots[j].id);
+ setWidth(q.tempSlots[j], groupwidth[g]);
+ }
+ for (var j = 0; j < q.tempPlayers.length; j++) {
+ var g = getGroupForThis(q.tempPlayers[j].id);
+ setWidth(q.tempPlayers[j], groupwidth[g]);
+ }
+
+ // set responses for all slots
+ setResponsesForAllSlots(q.tempSlots, q.tempPlayers);
+ }
+ }
+ };
+ // end of App object ////////////////////////////////////////////////////////
+
+ // beginning of Player object (the draggable item) //////////////////////////
+ YAHOO.example.DDPlayer = function(id, sGroup, config) {
+ YAHOO.example.DDPlayer.superclass.constructor.apply(this, arguments);
+ this.initPlayer(id, sGroup, config);
+ };
+
+ YAHOO.extend(YAHOO.example.DDPlayer, YAHOO.util.DD, {
+ TYPE :"DDPlayer",
+
+ initPlayer : function(id, sGroup, config) {
+ this.isTarget = false;
+ this.currentPos = YAHOO.util.Dom.getXY(this.getEl());
+
+ },
+
+ //Abstract method called after a drag/drop object is clicked and the drag or mousedown time thresholds have beeen met.
+ startDrag : function(x, y) {
+ YAHOO.util.Dom.setStyle(this.getEl(), "zIndex", ddwtos_currentzindex++);
+ YAHOO.util.Dom.removeClass(this.getEl(), 'placed');
+
+ if (is_infinite(this.getEl()) && !this.slot){
+ var currentplayer = this.getEl().id.replace(/_clone[0-9]+$/, '');
+ var ddplayer = YAHOO.util.DragDropMgr.getDDById(currentplayer);
+ clone_player(ddplayer);
+ }
+
+ if (this.slot) { // dragging starts from a slot
+ var hiddenElement = document.getElementById(this.slot.getEl().id + '_hidden');
+ hiddenElement.value = '';
+ this.slot.player = null;
+ this.slot = null;
+ }
+ },
+
+ //Abstract method called when this item is dropped on another DragDrop obj
+ onDragDrop : function(e, id) {
+ // get the drag and drop object that was targeted
+ var target = YAHOO.util.DragDropMgr.getDDById(id);
+ var dragged = this.getEl();
+
+ //get the question-prefix of slot and player and check whether they belong to the same question
+ var slotprefix = target.id.split("_")[0] + "_";
+ var playerprefix = dragged.id.split("_")[0] + "_";
+ if (slotprefix != playerprefix){
+ var p = YAHOO.util.DragDropMgr.getDDById(dragged.id);
+ p.startDrag(0,0);
+ p.onInvalidDrop(null);
+ return;
+ }
+
+ show_element(this.getEl());
+
+ if (target.player) { // there's a player already there
+ var oldplayer = target.player;
+ oldplayer.startDrag(0,0);
+ oldplayer.onInvalidDrop(null);
+ }
+
+ YAHOO.util.DragDropMgr.moveToEl(dragged, target.getEl());
+ this.slot = target;
+ target.player = this;
+ YAHOO.util.Dom.setXY(target.player.getEl(), YAHOO.util.Dom.getXY(target.getEl()));
+ YAHOO.util.Dom.addClass(this.getEl(), 'placed');
+ if (YAHOO.util.Dom.hasClass(target.getEl(), 'readonly')) {
+ if (YAHOO.util.Dom.hasClass(target.getEl(), 'correct')) {
+ YAHOO.util.Dom.addClass(this.getEl(), 'correct');
+ } else if (YAHOO.util.Dom.hasClass(target.getEl(), 'incorrect')) {
+ YAHOO.util.Dom.addClass(this.getEl(), 'incorrect');
+ }
+ }
+
+ // set value
+ var hiddenElement = document.getElementById(id + '_hidden');
+ hiddenElement.value = this.getEl().id.split("_")[1];
+ },
+
+ //Abstract method called when this item is dropped on an area with no drop target
+ onInvalidDrop : function(e) {
+ YAHOO.util.Dom.setXY(this.getEl(), YAHOO.util.Dom.getXY(this.originalplayer.getEl()));
+ YAHOO.util.Dom.removeClass(this.getEl(), 'placed');
+ }
+ });
+ // end of Player object (the draggable item) ////////////////////////////////
+
+ YAHOO.util.Event.onDOMReady(YAHOO.example.DDApp.init, YAHOO.example.DDApp, true);
+
+
+ // Objects///////////////////////////////////////////////////////////////////
+ var Questions = new Object();
+
+ function QuestionDataObject(theid) {
+ this.id = theid;
+ this.tempSlots = [];
+ this.tempPlayers = [];
+ this.slots = [];
+ this.players = [];
+ }
+
+ // template for the slot object
+ function SlotObject(id, group, currentvalue, values, callback) {
+ this.id = id;
+ this.group = group;
+ this.currentvalue = currentvalue;
+ this.values = values;
+ this.callback = callback;
+ }
+ // End of Objects ///////////////////////////////////////////////////////////
+
+ // functions ////////////////////////////////////////////////////////////////
+
+ function clone_player(p) {
+ var el = p.getEl();
+ var newNode = document.createElement('div');
+ newNode.className = el.className;
+ newNode.innerHTML = el.innerHTML;
+
+ if (!p.clones) {
+ p.clones = [];
+ }
+ newNode.id = el.id + '_clone' + p.clones.length; // _clone0 for first
+
+ newNode.style.position = 'absolute';
+
+ var region = YAHOO.util.Dom.getRegion(el);
+ var height = region.bottom - region.top;
+ var width = region.right - region.left;
+
+ // -2 is becuase get_region includes the border, but style.width/height does not.
+ newNode.style.height = (height - 2) + "px";
+ newNode.style.width = (width - 2) + "px";
+
+ el.parentNode.parentNode.appendChild(newNode);
+ YAHOO.util.Dom.setXY(newNode, YAHOO.util.Dom.getXY(el));
+
+ var g = getGroupForThis(el.id);
+ var p2 = new YAHOO.example.DDPlayer(newNode.id, g);
+ p2.originalplayer = p;
+
+ hide_element(el);
+ show_element(newNode);
+
+ if (is_readonly(newNode)) {
+ p2.lock();
+ }
+
+ p.clones[p.clones.length] = p2;
+ return p2;
+ }
+
+ function clone_players(players){
+ for (var i = 0; i < players.length; i++) {
+ var p = YAHOO.util.DragDropMgr.getDDById(players[i].id);
+ clone_player(p);
+ }
+ }
+
+ function is_readonly(el){
+ return YAHOO.util.Dom.hasClass(el, 'readonly');
+ }
+
+ function is_infinite(el){
+ return YAHOO.util.Dom.hasClass(el, 'infinite');
+ }
+
+ function show_element(el){
+ YAHOO.util.Dom.setStyle(el, 'visibility', 'visible');
+ }
+
+ function hide_element(el){
+ YAHOO.util.Dom.setStyle(el, 'visibility', 'hidden');
+ }
+
+ function list_of_slots_and_players(slots, players) {
+ this.slots = slots;
+ this.players = players;
+ }
+
+ function set_xy_after_resize(e, slotsandplayerobj){
+ setTimeout(function() {
+ set_xy_after_resize_actual(e,slotsandplayerobj);
+ }, 0);
+ }
+
+ function set_xy_after_resize_actual(e, slotsandplayerobj) {
+ var slots = slotsandplayerobj.slots;
+ var players = slotsandplayerobj.players;
+ for (var i = 0; i < players.length; i++) {
+ var original = YAHOO.util.DragDropMgr.getDDById(players[i].id);
+ for (var index in original.clones) {
+ var c = original.clones[index];
+ if (c.slot) {
+ //player is in slot
+ YAHOO.util.Dom.setXY(c.getEl(), YAHOO.util.Dom.getXY(c.slot.getEl()));
+ } else {
+ //player is not in slot
+ YAHOO.util.Dom.setXY(c.getEl(), YAHOO.util.Dom.getXY(c.originalplayer.getEl()));
+ }
+ }
+ }
+ }
+
+ function setResponsesForAllSlots(slots, players) {
+ clone_players(players);
+
+ for (var i = 0; i < slots.length; i++) {
+ var slot = slots[i];
+
+ var hiddenElement = document.getElementById(slot.id + '_hidden');
+ if (!hiddenElement) continue;
+
+ // get group
+ var group = getGroupForThis(slot.id);
+
+ // get array of values
+ var values = getValuesForThisSlot(slot.id, players);
+
+ var currentvalue = parseInt(hiddenElement.value);
+ // if slot is occupied
+ if (currentvalue) {
+ // Find player
+ var idbits = hiddenElement.id.split('_');
+ idbits[1] = currentvalue;
+ idbits.pop();
+ var newid = idbits.join('_');
+ var original = YAHOO.util.DragDropMgr.getDDById(newid);
+ var index = original.clones.length - 1;
+ original.clones[index].startDrag(0, 0);
+ original.clones[index].onDragDrop(null, slot.id);
+ }
+ YAHOO.util.Event.addListener(slot.id, "focus", setFocus);
+ YAHOO.util.Event.addListener(slot.id, "blur", setBlur);
+
+ YAHOO.util.Event.addListener(slot.id, "mousedown", mouseDown);
+
+ var myobj = new SlotObject(slot.id, group, currentvalue, values, funCallKeys);
+
+ // event keydown
+ YAHOO.util.Event.addListener(slot.id, "keydown", funCallKeys, myobj);
+ }
+
+ //resize
+ var listofslotsandplayers = new list_of_slots_and_players(slots, players);
+ YAHOO.util.Event.addListener(window, "resize", set_xy_after_resize, listofslotsandplayers);
+ YAHOO.util.Event.addListener(window, "load", set_xy_after_resize, listofslotsandplayers);
+ }
+
+ function getValuesForThisSlot(slotid, players) {
+ var gslot = getGroupForThis(slotid);
+ var values = [];
+ var j = 0;
+ for (var i = 0; i < players.length; i++) {
+ var pElement = players[i];
+ var gplayer = getGroupForThis(pElement.id);
+
+ // from the same group
+ if (gslot == gplayer) {
+ values[j++] = pElement.id;
+ }
+ }
+ return values;
+ }
+
+ function getWidthForAllGroups(allplayers) {
+ var widtharray = [];
+ for (var i = 0; i < allplayers.length; i++) {
+ var g = getGroupForThis(allplayers[i].id);
+ var width = getWidthForThisElement(allplayers[i]);
+ if (!widtharray[g] || width > widtharray[g]) {
+ widtharray[g] = width;
+ }
+ }
+ return widtharray;
+ }
+
+ function getWidthForThisGroup(allplayers, group) {
+ var tempwidth = 0;
+ for (var i = 0; i < allplayers.length; i++) {
+ var g = getGroupForThis(allplayers[i].id);
+ if (group != g) {
+ continue;
+ }
+ var width = getWidthForThisElement(allplayers[i]);
+ if (width > tempwidth) {
+ tempwidth = width;
+ }
+ }
+ return tempwidth;
+ }
+
+ function getWidthForThisElement(el) {
+ var region = YAHOO.util.Dom.getRegion(el);
+ return region.right - region.left;
+ }
+
+ function setWidth(el, gwidth) {
+ var width = getWidthForThisElement(el);
+ var remainder = (gwidth - width) + 10;
+
+ // IE8 does not rewrap lines when the padding changes, so this
+ // change uses different layout for that browser version only.
+ if (navigator.appVersion.indexOf('MSIE 8') != -1) {
+ var region = YAHOO.util.Dom.getRegion(el);
+ var height = region.bottom - region.top;
+ el.style.display = 'inline-block';
+ el.style.width = gwidth + 'px';
+ el.style.height = height + 'px';
+ return;
+ }
+
+ YAHOO.util.Dom.setStyle(el, 'padding-right', Math.floor((remainder + 1) / 2) + 'px');
+ YAHOO.util.Dom.setStyle(el, 'padding-left', Math.floor(remainder / 2) + 'px');
+ }
+
+ function funCallKeys(e, slotobj) {
+ //disable the key access when readonly
+ if (is_readonly(document.getElementById(slotobj.values[0]))) {
+ return;
+ }
+
+ var evt = e || window.event;
+ var key = evt.keyCode;
+
+ switch (key) {
+ case 39: // arrow right (forwards)
+ case 40: // arrow down (forwards)
+ case 32: // space (forwards)
+ changeObject(slotobj, 1);
+ return false; //this has to return false because of IE
+
+ case 37: // arrow left (backwards)
+ case 38: // arrow up (backwards)
+ changeObject(slotobj, -1);
+ return false; //this has to return false because of IE
+
+ case 66: // B (backwards)
+ case 98: // b (backwards)
+ case 80: // P (previous)
+ case 112: // p (previous)
+ changeObject(slotobj, -1);
+ break;
+
+ case 13: // cariage return (forwards)
+ case 70: // F (forwards)
+ case 102: // f (forwards)
+ case 78: // N (next)
+ case 110: // n (next)
+ changeObject(slotobj, 1);
+ break;
+
+ case 27: // escape (empty the drop box)
+ changeObject(slotobj, 0);
+ default:
+ return true;
+ }
+ return true;
+ }
+
+ function changeObject(slotobj, direction) {
+ // Prevent infinite loop if there are no values for this slot
+ if (slotobj.values.length == 0) {
+ return;
+ }
+
+ if (direction == 0) {
+ call_YUI_startDrag_onInvalidDrop(slotobj);
+ return;
+ }
+
+ var hiddenElement = document.getElementById(slotobj.id + '_hidden');
+
+ // Get current position in values list
+ var selectedIndex = -1;
+ for(var i = 0; i < slotobj.values.length; i++) {
+ if (slotobj.values[i].split("_")[1] == hiddenElement.value) {
+ selectedIndex = i;
+ break;
+ }
+ }
+
+ var currentIndex = selectedIndex;
+ while (true) {
+ // Get new position in values list
+ selectedIndex += direction;
+ if (selectedIndex > slotobj.values.length) {
+ selectedIndex-= slotobj.values.length + 1;
+ }
+ if (selectedIndex < 0) {
+ selectedIndex += slotobj.values.length + 1;
+ }
+
+ //empty the slot at the beginning or the end of the players list
+ if (selectedIndex == slotobj.values.length) {
+ call_YUI_startDrag_onInvalidDrop(slotobj);
+ return;
+ }
+
+ // If we loop round back to the current one then there are
+ // no more options, so stop
+ if (selectedIndex == currentIndex) {
+ break;
+ }
+
+ // Check the item at the new position is not used
+ var original = YAHOO.util.DragDropMgr.getDDById(slotobj.values[selectedIndex]);
+ var index = original.clones.length - 1;
+ if (!original.clones[index].slot) {
+ // This one is not in a slot so we can use it
+ original.clones[index].startDrag(0, 0);
+ original.clones[index].onDragDrop(null, slotobj.id);
+ break;
+ }
+ }
+ }
+
+ function call_YUI_startDrag_onInvalidDrop(slotobj){
+ var target = YAHOO.util.DragDropMgr.getDDById(slotobj.id);
+ if (!target.player) {
+ return;
+ }
+ var player = target.player;
+
+ // Find player and call YUI methods
+ player.startDrag(0, 0);
+ player.onInvalidDrop(null);
+ }
+
+ function getGroupForThis(str) {
+ var g = str.split("_")[2];
+ return g;
+ }
+
+ function mouseDown() {
+ }
+
+ function setFocus() {
+ YAHOO.util.Dom.addClass(this, 'focussed');
+ }
+
+ function setBlur() {
+ YAHOO.util.Dom.removeClass(this, 'focussed');
+ }
+
+ function ie7_zoom_message (){
+ var browser = navigator.appVersion;
+ if (browser.indexOf('MSIE 7') > -1){
+ var b = document.body.getBoundingClientRect();
+ var magnifactor = (b.right - b.left)/document.body.clientWidth;
+ if (magnifactor < 0.9 || magnifactor > 1.1){
+
+ var answers = YAHOO.util.Dom.getElementsByClassName("answercontainer", "div");
+ for(var i=0; i.
+
+
+/**
+ * Test helper class for the drag-and-drop words into sentences question type.
+ *
+ * @package qtype_ddwtos
+ * @copyright 2010 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_ddwtos_test_helper {
+ /**
+ * @return qtype_ddwtos_question
+ */
+ public static function make_a_ddwtos_question() {
+ question_bank::load_question_definition_classes('ddwtos');
+ $dd = new qtype_ddwtos_question();
+
+ test_question_maker::initialise_a_question($dd);
+
+ $dd->name = 'Drag-and-drop words into sentences question';
+ $dd->questiontext = 'The [[1]] brown [[2]] jumped over the [[3]] dog.';
+ $dd->generalfeedback = 'This sentence uses each letter of the alphabet.';
+ $dd->qtype = question_bank::get_qtype('ddwtos');
+
+ $dd->shufflechoices = true;
+
+ test_question_maker::set_standard_combined_feedback_fields($dd);
+
+ $dd->choices = array(
+ 1 => array(
+ 1 => new qtype_ddwtos_choice('quick', 1),
+ 2 => new qtype_ddwtos_choice('slow', 1)),
+ 2 => array(
+ 1 => new qtype_ddwtos_choice('fox', 2),
+ 2 => new qtype_ddwtos_choice('dog', 2)),
+ 3 => array(
+ 1 => new qtype_ddwtos_choice('lazy', 3),
+ 2 => new qtype_ddwtos_choice('assiduous', 3)),
+ );
+
+ $dd->places = array(1 => 1, 2 => 2, 3 => 3);
+ $dd->rightchoices = array(1 => 1, 2 => 1, 3 => 1);
+ $dd->textfragments = array('The ', ' brown ', ' jumped over the ', ' dog.');
+
+ return $dd;
+ }
+
+ /**
+ * @return qtype_ddwtos_question
+ */
+ public static function make_a_maths_ddwtos_question() {
+ question_bank::load_question_definition_classes('ddwtos');
+ $dd = new qtype_ddwtos_question();
+
+ test_question_maker::initialise_a_question($dd);
+
+ $dd->name = 'Drag-and-drop words into sentences question';
+ $dd->questiontext = 'Fill in the operators to make this equation work: ' .
+ '7 [[1]] 11 [[2]] 13 [[1]] 17 [[2]] 19 = 3';
+ $dd->generalfeedback = 'This sentence uses each letter of the alphabet.';
+ $dd->qtype = question_bank::get_qtype('ddwtos');
+
+ $dd->shufflechoices = true;
+
+ test_question_maker::set_standard_combined_feedback_fields($dd);
+
+ $dd->choices = array(
+ 1 => array(
+ 1 => new qtype_ddwtos_choice('+', 1, true),
+ 2 => new qtype_ddwtos_choice('-', 1, true),
+ 3 => new qtype_ddwtos_choice('*', 1, true),
+ 4 => new qtype_ddwtos_choice('/', 1, true),
+ ));
+
+ $dd->places = array(1 => 1, 2 => 1, 3 => 1, 4 => 1);
+ $dd->rightchoices = array(1 => 1, 2 => 2, 3 => 1, 4 => 2);
+ $dd->textfragments = array('7 ', ' 11 ', ' 13 ', ' 17 ', ' 19 = 3');
+
+ return $dd;
+ }
+}
diff --git a/question/type/ddimageortext/simpletest/testquestion.php b/question/type/ddimageortext/simpletest/testquestion.php
new file mode 100755
index 00000000000..db8ae7c9166
--- /dev/null
+++ b/question/type/ddimageortext/simpletest/testquestion.php
@@ -0,0 +1,246 @@
+.
+
+
+/**
+ * Unit tests for the drag-and-drop words into sentences question definition class.
+ *
+ * @package qtype_ddwtos
+ * @copyright 2010 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once($CFG->dirroot . '/question/engine/simpletest/helpers.php');
+require_once($CFG->dirroot . '/question/type/ddwtos/simpletest/helper.php');
+
+
+/**
+ * Unit tests for the matching question definition class.
+ *
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_ddwtos_question_test extends UnitTestCase {
+
+ public function test_get_question_summary() {
+ $dd = qtype_ddwtos_test_helper::make_a_ddwtos_question();
+ $this->assertEqual('The [[1]] brown [[2]] jumped over the [[3]] dog.; [[1]] -> {quick / slow}; [[2]] -> {fox / dog}; [[3]] -> {lazy / assiduous}',
+ $dd->get_question_summary());
+ }
+
+ public function test_get_question_summary_maths() {
+ $dd = qtype_ddwtos_test_helper::make_a_maths_ddwtos_question();
+ $this->assertEqual('Fill in the operators to make this equation work: ' .
+ '7 [[1]] 11 [[2]] 13 [[1]] 17 [[2]] 19 = 3; [[1]] -> {+ / - / * / /}',
+ $dd->get_question_summary());
+ }
+
+ public function test_summarise_response() {
+ $dd = qtype_ddwtos_test_helper::make_a_ddwtos_question();
+ $dd->shufflechoices = false;
+ $dd->init_first_step(new question_attempt_step());
+
+ $this->assertEqual('{quick} {fox} {lazy}',
+ $dd->summarise_response(array('p1' => '1', 'p2' => '1', 'p3' => '1')));
+ }
+
+ public function test_summarise_response_maths() {
+ $dd = qtype_ddwtos_test_helper::make_a_maths_ddwtos_question();
+ $dd->shufflechoices = false;
+ $dd->init_first_step(new question_attempt_step());
+
+ $this->assertEqual('{+} {-} {+} {-}',
+ $dd->summarise_response(array('p1' => '1', 'p2' => '2', 'p3' => '1', 'p4' => '2')));
+ }
+
+ public function test_get_random_guess_score() {
+ $dd = qtype_ddwtos_test_helper::make_a_ddwtos_question();
+ $this->assertEqual(0.5, $dd->get_random_guess_score());
+ }
+
+ public function test_get_random_guess_score_maths() {
+ $dd = qtype_ddwtos_test_helper::make_a_maths_ddwtos_question();
+ $this->assertEqual(0.25, $dd->get_random_guess_score());
+ }
+
+ public function test_get_right_choice_for() {
+ $dd = qtype_ddwtos_test_helper::make_a_ddwtos_question();
+ $dd->shufflechoices = false;
+ $dd->init_first_step(new question_attempt_step());
+
+ $this->assertEqual(1, $dd->get_right_choice_for(1));
+ $this->assertEqual(1, $dd->get_right_choice_for(2));
+ }
+
+ public function test_get_right_choice_for_maths() {
+ $dd = qtype_ddwtos_test_helper::make_a_maths_ddwtos_question();
+ $dd->shufflechoices = false;
+ $dd->init_first_step(new question_attempt_step());
+
+ $this->assertEqual(1, $dd->get_right_choice_for(1));
+ $this->assertEqual(2, $dd->get_right_choice_for(2));
+ }
+
+ public function test_clear_wrong_from_response() {
+ $dd = qtype_ddwtos_test_helper::make_a_maths_ddwtos_question();
+ $dd->shufflechoices = false;
+ $dd->init_first_step(new question_attempt_step());
+
+ $initialresponse = array('p1' => '1', 'p2' => '1', 'p3' => '1', 'p4' => '1');
+ $this->assertEqual(array('p1' => '1', 'p2' => '0', 'p3' => '1', 'p4' => '0'),
+ $dd->clear_wrong_from_response($initialresponse));
+ }
+
+ public function test_get_num_parts_right() {
+ $dd = qtype_ddwtos_test_helper::make_a_ddwtos_question();
+ $dd->shufflechoices = false;
+ $dd->init_first_step(new question_attempt_step());
+
+ $this->assertEqual(array(2, 3),
+ $dd->get_num_parts_right(array('p1' => '1', 'p2' => '1', 'p3' => '2')));
+ $this->assertEqual(array(3, 3),
+ $dd->get_num_parts_right(array('p1' => '1', 'p2' => '1', 'p3' => '1')));
+ }
+
+ public function test_get_num_parts_right_maths() {
+ $dd = qtype_ddwtos_test_helper::make_a_maths_ddwtos_question();
+ $dd->shufflechoices = false;
+ $dd->init_first_step(new question_attempt_step());
+
+ $this->assertEqual(array(2, 4),
+ $dd->get_num_parts_right(array('p1' => '1', 'p2' => '1', 'p3' => '1', 'p4' => '1')));
+ }
+
+ public function test_get_expected_data() {
+ $dd = qtype_ddwtos_test_helper::make_a_ddwtos_question();
+ $dd->init_first_step(new question_attempt_step());
+
+ $this->assertEqual(array('p1' => PARAM_INT, 'p2' => PARAM_INT, 'p3' => PARAM_INT),
+ $dd->get_expected_data());
+ }
+
+ public function test_get_correct_response() {
+ $dd = qtype_ddwtos_test_helper::make_a_ddwtos_question();
+ $dd->shufflechoices = false;
+ $dd->init_first_step(new question_attempt_step());
+
+ $this->assertEqual(array('p1' => '1', 'p2' => '1', 'p3' => '1'),
+ $dd->get_correct_response());
+ }
+
+ public function test_get_correct_response_maths() {
+ $dd = qtype_ddwtos_test_helper::make_a_maths_ddwtos_question();
+ $dd->shufflechoices = false;
+ $dd->init_first_step(new question_attempt_step());
+
+ $this->assertEqual(array('p1' => '1', 'p2' => '2', 'p3' => '1', 'p4' => '2'),
+ $dd->get_correct_response());
+ }
+
+ public function test_is_same_response() {
+ $dd = qtype_ddwtos_test_helper::make_a_ddwtos_question();
+ $dd->init_first_step(new question_attempt_step());
+
+ $this->assertTrue($dd->is_same_response(
+ array(),
+ array('p1' => '0', 'p2' => '0', 'p3' => '0')));
+
+ $this->assertFalse($dd->is_same_response(
+ array(),
+ array('p1' => '1', 'p2' => '0', 'p3' => '0')));
+
+ $this->assertFalse($dd->is_same_response(
+ array('p1' => '0', 'p2' => '0', 'p3' => '0'),
+ array('p1' => '1', 'p2' => '0', 'p3' => '0')));
+
+ $this->assertTrue($dd->is_same_response(
+ array('p1' => '1', 'p2' => '2', 'p3' => '3'),
+ array('p1' => '1', 'p2' => '2', 'p3' => '3')));
+
+ $this->assertFalse($dd->is_same_response(
+ array('p1' => '1', 'p2' => '2', 'p3' => '3'),
+ array('p1' => '1', 'p2' => '2', 'p3' => '2')));
+ }
+ public function test_is_complete_response() {
+ $dd = qtype_ddwtos_test_helper::make_a_ddwtos_question();
+ $dd->init_first_step(new question_attempt_step());
+
+ $this->assertFalse($dd->is_complete_response(array()));
+ $this->assertFalse($dd->is_complete_response(
+ array('p1' => '1', 'p2' => '1', 'p3' => '0')));
+ $this->assertFalse($dd->is_complete_response(array('p1' => '1')));
+ $this->assertTrue($dd->is_complete_response(
+ array('p1' => '1', 'p2' => '1', 'p3' => '1')));
+ }
+
+ public function test_is_gradable_response() {
+ $dd = qtype_ddwtos_test_helper::make_a_ddwtos_question();
+ $dd->init_first_step(new question_attempt_step());
+
+ $this->assertFalse($dd->is_gradable_response(array()));
+ $this->assertFalse($dd->is_gradable_response(
+ array('p1' => '0', 'p2' => '0', 'p3' => '0')));
+ $this->assertTrue($dd->is_gradable_response(
+ array('p1' => '1', 'p2' => '1', 'p3' => '0')));
+ $this->assertTrue($dd->is_gradable_response(array('p1' => '1')));
+ $this->assertTrue($dd->is_gradable_response(
+ array('p1' => '1', 'p2' => '1', 'p3' => '1')));
+ }
+
+ public function test_grading() {
+ $dd = qtype_ddwtos_test_helper::make_a_ddwtos_question();
+ $dd->shufflechoices = false;
+ $dd->init_first_step(new question_attempt_step());
+
+ $this->assertEqual(array(1, question_state::$gradedright),
+ $dd->grade_response(array('p1' => '1', 'p2' => '1', 'p3' => '1')));
+ $this->assertEqual(array(1/3, question_state::$gradedpartial),
+ $dd->grade_response(array('p1' => '1')));
+ $this->assertEqual(array(0, question_state::$gradedwrong),
+ $dd->grade_response(array('p1' => '2', 'p2' => '2', 'p3' => '2')));
+ }
+
+ public function test_grading_maths() {
+ $dd = qtype_ddwtos_test_helper::make_a_maths_ddwtos_question();
+ $dd->shufflechoices = false;
+ $dd->init_first_step(new question_attempt_step());
+
+ $this->assertEqual(array(1, question_state::$gradedright),
+ $dd->grade_response(array('p1' => '1', 'p2' => '2', 'p3' => '1', 'p4' => '2')));
+ $this->assertEqual(array(0.5, question_state::$gradedpartial),
+ $dd->grade_response(array('p1' => '1', 'p2' => '1', 'p3' => '1', 'p4' => '1')));
+ $this->assertEqual(array(0, question_state::$gradedwrong),
+ $dd->grade_response(array('p1' => '0', 'p2' => '1', 'p3' => '2', 'p4' => '1')));
+ }
+
+ public function test_classify_response() {
+ $dd = qtype_ddwtos_test_helper::make_a_ddwtos_question();
+ $dd->shufflechoices = false;
+ $dd->init_first_step(new question_attempt_step());
+
+ $this->assertEqual(array(
+ 1 => new question_classified_response(1, 'quick', 1),
+ 2 => new question_classified_response(2, 'dog', 0),
+ 3 => new question_classified_response(1, 'lazy', 1),
+ ), $dd->classify_response(array('p1' => '1', 'p2' => '2', 'p3' => '1')));
+ $this->assertEqual(array(
+ 1 => question_classified_response::no_response(),
+ 2 => new question_classified_response(1, 'fox', 1),
+ 3 => new question_classified_response(2, 'assiduous', 0),
+ ), $dd->classify_response(array('p1' => '0', 'p2' => '1', 'p3' => '2')));
+ }
+}
diff --git a/question/type/ddimageortext/simpletest/testquestiontype.php b/question/type/ddimageortext/simpletest/testquestiontype.php
new file mode 100755
index 00000000000..0acfa17b174
--- /dev/null
+++ b/question/type/ddimageortext/simpletest/testquestiontype.php
@@ -0,0 +1,470 @@
+.
+
+
+/**
+ * Unit tests for the drag-and-drop words into sentences question definition class.
+ *
+ * @package qtype_ddwtos
+ * @copyright 2010 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+require_once($CFG->dirroot . '/question/engine/simpletest/helpers.php');
+require_once($CFG->dirroot . '/question/type/ddwtos/simpletest/helper.php');
+
+
+/**
+ * Unit tests for the drag-and-drop words into sentences question definition class.
+ *
+ * @copyright 2010 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_ddwtos_test extends UnitTestCase {
+ /** @var qtype_ddwtos instance of the question type class to test. */
+ protected $qtype;
+
+ public function setUp() {
+ $this->qtype = question_bank::get_qtype('ddwtos');;
+ }
+
+ public function tearDown() {
+ $this->qtype = null;
+ }
+
+ public function assert_same_xml($expectedxml, $xml) {
+ $this->assertEqual(str_replace("\r\n", "\n", $expectedxml),
+ str_replace("\r\n", "\n", $xml));
+ }
+
+ /**
+ * @return object the data to construct a question like
+ * {@link qtype_ddwtos_test_helper::make_a_ddwtos_question()}.
+ */
+ protected function get_test_question_data() {
+ global $USER;
+
+ $dd = new stdClass;
+ $dd->id = 0;
+ $dd->category = 0;
+ $dd->parent = 0;
+ $dd->questiontextformat = FORMAT_HTML;
+ $dd->defaultmark = 1;
+ $dd->penalty = 0.3333333;
+ $dd->length = 1;
+ $dd->stamp = make_unique_id_code();
+ $dd->version = make_unique_id_code();
+ $dd->hidden = 0;
+ $dd->timecreated = time();
+ $dd->timemodified = time();
+ $dd->createdby = $USER->id;
+ $dd->modifiedby = $USER->id;
+
+ $dd->name = 'Drag-and-drop words into sentences question';
+ $dd->questiontext = 'The [[1]] brown [[2]] jumped over the [[3]] dog.';
+ $dd->generalfeedback = 'This sentence uses each letter of the alphabet.';
+ $dd->qtype = 'ddwtos';
+
+ $dd->options->shuffleanswers = true;
+
+ test_question_maker::set_standard_combined_feedback_fields($dd->options);
+
+ $dd->options->answers = array(
+ (object) array('answer' => 'quick', 'feedback' => 'O:8:"stdClass":2:{s:9:"draggroup";s:1:"1";s:8:"infinite";i:0;}'),
+ (object) array('answer' => 'fox', 'feedback' => 'O:8:"stdClass":2:{s:9:"draggroup";s:1:"2";s:8:"infinite";i:0;}'),
+ (object) array('answer' => 'lazy', 'feedback' => 'O:8:"stdClass":2:{s:9:"draggroup";s:1:"3";s:8:"infinite";i:0;}'),
+ (object) array('answer' => 'assiduous', 'feedback' => 'O:8:"stdClass":2:{s:9:"draggroup";s:1:"3";s:8:"infinite";i:0;}'),
+ (object) array('answer' => 'dog', 'feedback' => 'O:8:"stdClass":2:{s:9:"draggroup";s:1:"2";s:8:"infinite";i:0;}'),
+ (object) array('answer' => 'slow', 'feedback' => 'O:8:"stdClass":2:{s:9:"draggroup";s:1:"1";s:8:"infinite";i:0;}'),
+ );
+
+ return $dd;
+ }
+
+ public function test_name() {
+ $this->assertEqual($this->qtype->name(), 'ddwtos');
+ }
+
+ public function test_can_analyse_responses() {
+ $this->assertTrue($this->qtype->can_analyse_responses());
+ }
+
+ public function test_initialise_question_instance() {
+ $qdata = $this->get_test_question_data();
+
+ $expected = qtype_ddwtos_test_helper::make_a_ddwtos_question();
+ $expected->stamp = $qdata->stamp;
+ $expected->version = $qdata->version;
+
+ $q = $this->qtype->make_question($qdata);
+
+ $this->assertEqual($expected, $q);
+ }
+
+ public function test_get_random_guess_score() {
+ $q = $this->get_test_question_data();
+ $this->assertWithinMargin(0.5, $this->qtype->get_random_guess_score($q), 0.0000001);
+ }
+
+ public function test_get_possible_responses() {
+ $q = $this->get_test_question_data();
+
+ $this->assertEqual(array(
+ 1 => array(
+ 1 => new question_possible_response('quick', 1),
+ 2 => new question_possible_response('slow', 0),
+ null => question_possible_response::no_response()),
+ 2 => array(
+ 1 => new question_possible_response('fox', 1),
+ 2 => new question_possible_response('dog', 0),
+ null => question_possible_response::no_response()),
+ 3 => array(
+ 1 => new question_possible_response('lazy', 1),
+ 2 => new question_possible_response('assiduous', 0),
+ null => question_possible_response::no_response()),
+ ), $this->qtype->get_possible_responses($q));
+ }
+
+ public function test_xml_import() {
+ $xml = '
+
+ A drag-and-drop question
+
+
+ Put these in order: [[1]], [[2]], [[3]].
+
+
+ The answer is Alpha, Beta, Gamma.
+
+ 3
+ 0.3333333
+ 0
+ 1
+
+ Your answer is correct.
]]>
+
+
+ Your answer is partially correct.]]>
+
+
+ Your answer is incorrect.]]>
+
+
+
+ Alpha
+ 1
+
+
+ Beta
+ 1
+
+
+ Gamma
+ 1
+
+
+
+ Try again.
+
+
+
+ These are the first three letters of the Greek alphabet.
+
+
+
+ ';
+ $xmldata = xmlize($xml);
+
+ $importer = new qformat_xml();
+ $q = $importer->try_importing_using_qtypes(
+ $xmldata['question'], null, null, 'ddwtos');
+
+ $expectedq = new stdClass;
+ $expectedq->qtype = 'ddwtos';
+ $expectedq->name = 'A drag-and-drop question';
+ $expectedq->questiontext = 'Put these in order: [[1]], [[2]], [[3]].';
+ $expectedq->questiontextformat = FORMAT_MOODLE;
+ $expectedq->generalfeedback = 'The answer is Alpha, Beta, Gamma.';
+ $expectedq->defaultmark = 3;
+ $expectedq->length = 1;
+ $expectedq->penalty = 0.3333333;
+
+ $expectedq->shuffleanswers = 1;
+ $expectedq->correctfeedback = 'Your answer is correct.
';
+ $expectedq->partiallycorrectfeedback = 'Your answer is partially correct.
';
+ $expectedq->shownumcorrect = true;
+ $expectedq->incorrectfeedback = 'Your answer is incorrect.
';
+
+ $expectedq->choices = array(
+ array('answer' => 'Alpha', 'draggroup' => 1, 'infinite' => false),
+ array('answer' => 'Beta', 'draggroup' => 1, 'infinite' => false),
+ array('answer' => 'Gamma', 'draggroup' => 1, 'infinite' => true),
+ );
+
+ $expectedq->hint = array('Try again.', 'These are the first three letters of the Greek alphabet.');
+ $expectedq->hintshownumcorrect = array(true, true);
+ $expectedq->hintclearwrong = array(false, true);
+
+ $this->assert(new CheckSpecifiedFieldsExpectation($expectedq), $q);
+ }
+
+ public function test_xml_import_legacy() {
+ $xml = '
+
+ QDandD1 Base definition
+
+
+ <p>Drag and drop the words from the list below to fill the blank spaces and correctly complete the sentence.</p> <p>At 25°C all aqueous basic solutions have [[1]] ion concentrations less than [[8]]<br />mol litre<sup>-1</sup> and pH values [[9]] than [[6]].</p> <!--DONOTCLEAN-->
+
+
+
+ <p>At 25 °C all aqueous basic solutions have hydrogen ion concentrations less than 10<sup>−7</sup> mol litre<sup>−1</sup> and pH values greater than 7.</p> <p>See Section 9 of S103 <em class="italic">Discovering Science</em> Block 8.</p>
+
+ 1
+ 0.33
+ 0
+ 0
+ false
+
+ 1
+ hydrogen
+
+ O:8:"stdClass":2:{s:9:"draggroup";s:1:"1";s:8:"infinite";i:0;}
+
+
+
+ 0
+ positive
+
+ O:8:"stdClass":2:{s:9:"draggroup";s:1:"1";s:8:"infinite";i:0;}
+
+
+
+ 0
+ hydroxide
+
+ O:8:"stdClass":2:{s:9:"draggroup";s:1:"1";s:8:"infinite";i:0;}
+
+
+
+ 0
+ negative
+
+ O:8:"stdClass":2:{s:9:"draggroup";s:1:"1";s:8:"infinite";i:0;}
+
+
+
+ 0
+ 10<sup>7</sup>
+
+ O:8:"stdClass":2:{s:9:"draggroup";s:1:"2";s:8:"infinite";i:0;}
+
+
+
+ 1
+ 7
+
+ O:8:"stdClass":2:{s:9:"draggroup";s:1:"2";s:8:"infinite";i:0;}
+
+
+
+ 0
+ 1
+
+ O:8:"stdClass":2:{s:9:"draggroup";s:1:"2";s:8:"infinite";i:0;}
+
+
+
+ 1
+ 10<sup>-7</sup>
+
+ O:8:"stdClass":2:{s:9:"draggroup";s:1:"2";s:8:"infinite";i:0;}
+
+
+
+ 1
+ greater
+
+ O:8:"stdClass":2:{s:9:"draggroup";s:1:"3";s:8:"infinite";i:0;}
+
+
+
+ 0
+ less
+
+ O:8:"stdClass":2:{s:9:"draggroup";s:1:"3";s:8:"infinite";i:0;}
+
+
+
+ Your answer is correct.
+
+ 1
+
+ Your answer is partially correct.
+
+
+ Your answer is incorrect.
+
+ 0
+ 0.33
+
+ 1
+ 0
+
+ You may wish to read Section 9 of <em class="italic">Discovering Science</em> Block 8.
+
+
+
+ 1
+ 1
+
+ Any incorrect choices will be removed before your final try.
+
+
+ ';
+ $xmldata = xmlize($xml);
+
+ $importer = new qformat_xml();
+ $q = $importer->try_importing_using_qtypes(
+ $xmldata['question'], null, null, 'ddwtos');
+
+ $expectedq = new stdClass;
+ $expectedq->qtype = 'ddwtos';
+ $expectedq->name = 'QDandD1 Base definition';
+ $expectedq->questiontext = 'Drag and drop the words from the list below to fill the blank spaces and correctly complete the sentence.
At 25°C all aqueous basic solutions have [[1]] ion concentrations less than [[8]]
mol litre-1 and pH values [[9]] than [[6]].
';
+ $expectedq->questiontextformat = FORMAT_HTML;
+ $expectedq->generalfeedback = 'At 25 °C all aqueous basic solutions have hydrogen ion concentrations less than 10−7 mol litre−1 and pH values greater than 7.
See Section 9 of S103 Discovering Science Block 8.
';
+ $expectedq->defaultmark = 1;
+ $expectedq->length = 1;
+ $expectedq->penalty = 0.3333333;
+
+ $expectedq->shuffleanswers = 0;
+ $expectedq->correctfeedback = 'Your answer is correct.';
+ $expectedq->partiallycorrectfeedback = 'Your answer is partially correct.';
+ $expectedq->shownumcorrect = true;
+ $expectedq->incorrectfeedback = 'Your answer is incorrect.';
+
+ $expectedq->choices = array(
+ array('answer' => 'hydrogen', 'draggroup' => 1, 'infinite' => false),
+ array('answer' => 'positive', 'draggroup' => 1, 'infinite' => false),
+ array('answer' => 'hydroxide', 'draggroup' => 1, 'infinite' => false),
+ array('answer' => 'negative', 'draggroup' => 1, 'infinite' => false),
+ array('answer' => '107', 'draggroup' => 2, 'infinite' => false),
+ array('answer' => '7', 'draggroup' => 2, 'infinite' => false),
+ array('answer' => '1', 'draggroup' => 2, 'infinite' => false),
+ array('answer' => '10-7', 'draggroup' => 2, 'infinite' => false),
+ array('answer' => 'greater', 'draggroup' => 3, 'infinite' => false),
+ array('answer' => 'less', 'draggroup' => 3, 'infinite' => false),
+ );
+
+ $expectedq->hint = array(
+ 'You may wish to read Section 9 of Discovering Science Block 8.',
+ 'Any incorrect choices will be removed before your final try.'
+ );
+ $expectedq->hintshownumcorrect = array(true, true);
+ $expectedq->hintclearwrong = array(false, true);
+
+ $this->assert(new CheckSpecifiedFieldsExpectation($expectedq), $q);
+ }
+
+ public function test_xml_export() {
+ $qdata = new stdClass;
+ $qdata->id = 123;
+ $qdata->qtype = 'ddwtos';
+ $qdata->name = 'A drag-and-drop question';
+ $qdata->questiontext = 'Put these in order: [[1]], [[2]], [[3]].';
+ $qdata->questiontextformat = FORMAT_MOODLE;
+ $qdata->generalfeedback = 'The answer is Alpha, Beta, Gamma.';
+ $qdata->defaultmark = 3;
+ $qdata->length = 1;
+ $qdata->penalty = 0.3333333;
+ $qdata->hidden = 0;
+
+ $qdata->options->shuffleanswers = 1;
+ $qdata->options->correctfeedback = 'Your answer is correct.
';
+ $qdata->options->partiallycorrectfeedback = 'Your answer is partially correct.
';
+ $qdata->options->shownumcorrect = true;
+ $qdata->options->incorrectfeedback = 'Your answer is incorrect.
';
+
+ $qdata->options->answers = array(
+ new question_answer('Alpha', 0, 'O:8:"stdClass":2:{s:9:"draggroup";s:1:"1";s:8:"infinite";b:0;}'),
+ new question_answer('Beta', 0, 'O:8:"stdClass":2:{s:9:"draggroup";s:1:"1";s:8:"infinite";b:0;}'),
+ new question_answer('Gamma', 0, 'O:8:"stdClass":2:{s:9:"draggroup";s:1:"1";s:8:"infinite";b:1;}'),
+ );
+
+ $qdata->hints = array(
+ new question_hint_with_parts('Try again.', true, false),
+ new question_hint_with_parts('These are the first three letters of the Greek alphabet.', true, true),
+ );
+
+ $exporter = new qformat_xml();
+ $xml = $exporter->writequestion($qdata);
+
+ $expectedxml = '
+
+
+ A drag-and-drop question
+
+
+ Put these in order: [[1]], [[2]], [[3]].
+
+
+ The answer is Alpha, Beta, Gamma.
+
+ 3
+ 0.3333333
+ 0
+ 1
+
+ Your answer is correct.]]>
+
+
+ Your answer is partially correct.]]>
+
+
+ Your answer is incorrect.]]>
+
+
+
+ Alpha
+ 1
+
+
+ Beta
+ 1
+
+
+ Gamma
+ 1
+
+
+
+ Try again.
+
+
+
+ These are the first three letters of the Greek alphabet.
+
+
+
+
+';
+
+ $this->assert_same_xml($expectedxml, $xml);
+ }
+}
diff --git a/question/type/ddimageortext/simpletest/testwalkthrough.php b/question/type/ddimageortext/simpletest/testwalkthrough.php
new file mode 100755
index 00000000000..4a0f2820fc9
--- /dev/null
+++ b/question/type/ddimageortext/simpletest/testwalkthrough.php
@@ -0,0 +1,680 @@
+.
+
+
+/**
+ * This file contains tests that walks a question through the interactive
+ * behaviour.
+ *
+ * @package qtype_ddwtos
+ * @copyright 2010 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+require_once($CFG->dirroot . '/question/engine/simpletest/helpers.php');
+require_once($CFG->dirroot . '/question/type/ddwtos/simpletest/helper.php');
+
+
+class qtype_ddwtos_walkthrough_test extends qbehaviour_walkthrough_test_base {
+
+ protected function get_contains_drop_box_expectation($place, $group, $readonly, $stateclass = '0') {
+ $qa = $this->quba->get_question_attempt($this->slot);
+
+ $expectedattrs = array(
+ 'id' => $qa->get_qt_field_name($place . '_' . $group),
+ );
+ $class = 'slot group' . $group;
+ if ($readonly) {
+ $class .= ' readonly';
+ } else {
+ $expectedattrs['tabindex'] = 0;
+ }
+ if ($stateclass) {
+ $class .= ' ' . $stateclass;
+ }
+ $expectedattrs['class'] = $class;
+
+ return new ContainsTagWithAttributes('span', $expectedattrs);
+ }
+
+ public function test_interactive_behaviour() {
+
+ // Create a drag-and-drop question.
+ $dd = qtype_ddwtos_test_helper::make_a_ddwtos_question();
+ $dd->hints = array(
+ new question_hint_with_parts('This is the first hint.', false, false),
+ new question_hint_with_parts('This is the second hint.', true, true),
+ );
+ $dd->shufflechoices = false;
+ $this->start_attempt_at_question($dd, 'interactive', 3);
+
+ // Check the initial state.
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, false),
+ $this->get_contains_drop_box_expectation('p2', 2, false),
+ $this->get_contains_drop_box_expectation('p3', 3, false),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p1', '0'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p2', '0'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p3', '0'),
+ $this->get_contains_submit_button_expectation(true),
+ $this->get_does_not_contain_feedback_expectation(),
+ $this->get_tries_remaining_expectation(3),
+ $this->get_no_hint_visible_expectation());
+
+ // Save the wrong answer.
+ $this->process_submission(array('p1' => '2', 'p2' => '2', 'p3' => '2'));
+
+ // Verify.
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, false),
+ $this->get_contains_drop_box_expectation('p2', 2, false),
+ $this->get_contains_drop_box_expectation('p3', 3, false),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p1', '2'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p2', '2'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p3', '2'),
+ $this->get_contains_submit_button_expectation(true),
+ $this->get_does_not_contain_correctness_expectation(),
+ $this->get_does_not_contain_feedback_expectation(),
+ $this->get_tries_remaining_expectation(3),
+ $this->get_no_hint_visible_expectation());
+
+ // Submit the wrong answer.
+ $this->process_submission(array('p1' => '2', 'p2' => '2', 'p3' => '2', '-submit' => 1));
+
+ // Verify.
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, true),
+ $this->get_contains_drop_box_expectation('p2', 2, true),
+ $this->get_contains_drop_box_expectation('p3', 3, true),
+ $this->get_contains_submit_button_expectation(false),
+ $this->get_contains_try_again_button_expectation(true),
+ $this->get_does_not_contain_correctness_expectation(),
+ new PatternExpectation('/' . preg_quote(get_string('notcomplete', 'qbehaviour_interactive')) . '/'),
+ $this->get_contains_hint_expectation('This is the first hint'));
+
+ // Do try again.
+ $this->process_submission(array('-tryagain' => 1));
+
+ // Verify.
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, false),
+ $this->get_contains_drop_box_expectation('p2', 2, false),
+ $this->get_contains_drop_box_expectation('p3', 3, false),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p1', '2'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p2', '2'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p3', '2'),
+ $this->get_contains_submit_button_expectation(true),
+ $this->get_does_not_contain_correctness_expectation(),
+ $this->get_does_not_contain_feedback_expectation(),
+ $this->get_tries_remaining_expectation(2),
+ $this->get_no_hint_visible_expectation());
+
+ // Submit the right answer.
+ $this->process_submission(array('p1' => '1', 'p2' => '1', 'p3' => '1', '-submit' => 1));
+
+ // Verify.
+ $this->check_current_state(question_state::$gradedright);
+ $this->check_current_mark(2);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, true, 'correct'),
+ $this->get_contains_drop_box_expectation('p2', 2, true, 'correct'),
+ $this->get_contains_drop_box_expectation('p3', 3, true, 'correct'),
+ $this->get_contains_submit_button_expectation(false),
+ $this->get_contains_correct_expectation(),
+ $this->get_no_hint_visible_expectation());
+
+ // Check regrading does not mess anything up.
+ $this->quba->regrade_all_questions();
+
+ // Verify.
+ $this->check_current_state(question_state::$gradedright);
+ $this->check_current_mark(2);
+ }
+
+ public function test_deferred_feedback() {
+
+ // Create a drag-and-drop question.
+ $dd = qtype_ddwtos_test_helper::make_a_ddwtos_question();
+ $dd->shufflechoices = false;
+ $this->start_attempt_at_question($dd, 'deferredfeedback', 3);
+
+ // Check the initial state.
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, false),
+ $this->get_contains_drop_box_expectation('p2', 2, false),
+ $this->get_contains_drop_box_expectation('p3', 3, false),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p1', '0'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p2', '0'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p3', '0'),
+ $this->get_does_not_contain_feedback_expectation());
+
+ // Save a partial answer.
+ $this->process_submission(array('p1' => '1', 'p2' => '2'));
+
+ // Verify.
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, false),
+ $this->get_contains_drop_box_expectation('p2', 2, false),
+ $this->get_contains_drop_box_expectation('p3', 3, false),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p1', '1'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p2', '2'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p3', '0'),
+ $this->get_does_not_contain_correctness_expectation(),
+ $this->get_does_not_contain_feedback_expectation());
+
+ // Save the right answer.
+ $this->process_submission(array('p1' => '1', 'p2' => '1', 'p3' => '1'));
+
+ // Verify.
+ $this->check_current_state(question_state::$complete);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, false),
+ $this->get_contains_drop_box_expectation('p2', 2, false),
+ $this->get_contains_drop_box_expectation('p3', 3, false),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p1', '1'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p2', '1'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p3', '1'),
+ $this->get_does_not_contain_correctness_expectation(),
+ $this->get_does_not_contain_feedback_expectation());
+
+ // Finish the attempt.
+ $this->quba->finish_all_questions();
+
+ // Verify.
+ $this->check_current_state(question_state::$gradedright);
+ $this->check_current_mark(3);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, true, 'correct'),
+ $this->get_contains_drop_box_expectation('p2', 2, true, 'correct'),
+ $this->get_contains_drop_box_expectation('p3', 3, true, 'correct'),
+ $this->get_contains_correct_expectation());
+
+ // Change the right answer a bit.
+ $dd->rightchoices[2] = 2;
+
+ // Check regrading does not mess anything up.
+ $this->quba->regrade_all_questions();
+
+ // Verify.
+ $this->check_current_state(question_state::$gradedpartial);
+ $this->check_current_mark(2);
+ }
+
+ public function test_deferred_feedback_unanswered() {
+
+ // Create a drag-and-drop question.
+ $dd = qtype_ddwtos_test_helper::make_a_ddwtos_question();
+ $dd->shufflechoices = false;
+ $this->start_attempt_at_question($dd, 'deferredfeedback', 3);
+
+ // Check the initial state.
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, false),
+ $this->get_contains_drop_box_expectation('p2', 2, false),
+ $this->get_contains_drop_box_expectation('p3', 3, false),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p1', '0'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p2', '0'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p3', '0'),
+ $this->get_does_not_contain_correctness_expectation(),
+ $this->get_does_not_contain_feedback_expectation());
+ $this->check_step_count(1);
+
+ // Save a blank response.
+ $this->process_submission(array('p1' => '0', 'p2' => '0', 'p3' => '0'));
+
+ // Verify.
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, false),
+ $this->get_contains_drop_box_expectation('p2', 2, false),
+ $this->get_contains_drop_box_expectation('p3', 3, false),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p1', '0'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p2', '0'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p3', '0'),
+ $this->get_does_not_contain_correctness_expectation(),
+ $this->get_does_not_contain_feedback_expectation());
+ $this->check_step_count(1);
+
+ // Finish the attempt.
+ $this->quba->finish_all_questions();
+
+ // Verify.
+ $this->check_current_state(question_state::$gaveup);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, true),
+ $this->get_contains_drop_box_expectation('p2', 2, true),
+ $this->get_contains_drop_box_expectation('p3', 3, true));
+ }
+
+ public function test_deferred_feedback_partial_answer() {
+
+ // Create a drag-and-drop question.
+ $dd = qtype_ddwtos_test_helper::make_a_ddwtos_question();
+ $dd->shufflechoices = false;
+ $this->start_attempt_at_question($dd, 'deferredfeedback', 3);
+
+ // Check the initial state.
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, false),
+ $this->get_contains_drop_box_expectation('p2', 2, false),
+ $this->get_contains_drop_box_expectation('p3', 3, false),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p1', '0'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p2', '0'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p3', '0'),
+ $this->get_does_not_contain_correctness_expectation(),
+ $this->get_does_not_contain_feedback_expectation());
+
+ // Save a partial response.
+ $this->process_submission(array('p1' => '1', 'p2' => '0', 'p3' => '0'));
+
+ // Verify.
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, false),
+ $this->get_contains_drop_box_expectation('p2', 2, false),
+ $this->get_contains_drop_box_expectation('p3', 3, false),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p1', '1'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p2', '0'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p3', '0'),
+ $this->get_does_not_contain_correctness_expectation(),
+ $this->get_does_not_contain_feedback_expectation());
+
+ // Finish the attempt.
+ $this->quba->finish_all_questions();
+
+ // Verify.
+ $this->check_current_state(question_state::$gradedpartial);
+ $this->check_current_mark(1);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, true, 'correct'),
+ $this->get_contains_drop_box_expectation('p2', 2, true, 'incorrect'),
+ $this->get_contains_drop_box_expectation('p3', 3, true, 'incorrect'),
+ $this->get_contains_partcorrect_expectation());
+ }
+
+ public function test_interactive_grading() {
+
+ // Create a drag-and-drop question.
+ $dd = qtype_ddwtos_test_helper::make_a_ddwtos_question();
+ $dd->hints = array(
+ new question_hint_with_parts('This is the first hint.', true, true),
+ new question_hint_with_parts('This is the second hint.', true, true),
+ );
+ $dd->shufflechoices = false;
+ $this->start_attempt_at_question($dd, 'interactive', 9);
+
+ // Check the initial state.
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->assertEqual('interactivecountback',
+ $this->quba->get_question_attempt($this->slot)->get_behaviour_name());
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, false),
+ $this->get_contains_drop_box_expectation('p2', 2, false),
+ $this->get_contains_drop_box_expectation('p3', 3, false),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p1', '0'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p2', '0'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p3', '0'),
+ $this->get_contains_submit_button_expectation(true),
+ $this->get_does_not_contain_feedback_expectation(),
+ $this->get_tries_remaining_expectation(3),
+ $this->get_does_not_contain_num_parts_correct(),
+ $this->get_no_hint_visible_expectation());
+
+ // Submit an response with the first two parts right.
+ $this->process_submission(array('p1' => '1', 'p2' => '1', 'p3' => '2', '-submit' => 1));
+
+ // Verify.
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, true),
+ $this->get_contains_drop_box_expectation('p2', 2, true),
+ $this->get_contains_drop_box_expectation('p3', 3, true),
+ $this->get_contains_submit_button_expectation(false),
+ $this->get_contains_try_again_button_expectation(true),
+ $this->get_does_not_contain_correctness_expectation(),
+ new PatternExpectation('/' . preg_quote(get_string('notcomplete', 'qbehaviour_interactive')) . '/'),
+ $this->get_contains_hint_expectation('This is the first hint'),
+ $this->get_contains_num_parts_correct(2),
+ $this->get_contains_standard_partiallycorrect_combined_feedback_expectation(),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p1', '1'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p2', '1'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p3', '0'));
+
+ // Check that extract responses will return the reset data.
+ $prefix = $this->quba->get_field_prefix($this->slot);
+ $this->assertEqual(array('p1' => '1', 'p2' => '1'),
+ $this->quba->extract_responses($this->slot, array($prefix . 'p1' => '1', $prefix . 'p2' => '1', '-tryagain' => 1)));
+
+ // Do try again.
+ $this->process_submission(array('p1' => '1', 'p2' => '1', '-tryagain' => 1));
+
+ // Verify.
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, false),
+ $this->get_contains_drop_box_expectation('p2', 2, false),
+ $this->get_contains_drop_box_expectation('p3', 3, false),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p1', '1'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p2', '1'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p3', '0'),
+ $this->get_contains_submit_button_expectation(true),
+ $this->get_does_not_contain_try_again_button_expectation(),
+ $this->get_does_not_contain_correctness_expectation(),
+ $this->get_does_not_contain_feedback_expectation(),
+ $this->get_tries_remaining_expectation(2),
+ $this->get_no_hint_visible_expectation());
+
+ // Submit an response with the first and last parts right.
+ $this->process_submission(array('p1' => '1', 'p2' => '2', 'p3' => '1', '-submit' => 1));
+
+ // Verify.
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, true),
+ $this->get_contains_drop_box_expectation('p2', 2, true),
+ $this->get_contains_drop_box_expectation('p3', 3, true),
+ $this->get_contains_submit_button_expectation(false),
+ $this->get_contains_try_again_button_expectation(true),
+ $this->get_does_not_contain_correctness_expectation(),
+ new PatternExpectation('/' . preg_quote(get_string('notcomplete', 'qbehaviour_interactive')) . '/'),
+ $this->get_contains_hint_expectation('This is the second hint'),
+ $this->get_contains_num_parts_correct(2),
+ $this->get_contains_standard_partiallycorrect_combined_feedback_expectation(),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p1', '1'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p2', '0'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p3', '1'));
+
+ // Do try again.
+ $this->process_submission(array('p1' => '1', 'p3' => '1', '-tryagain' => 1));
+
+ // Verify.
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, false),
+ $this->get_contains_drop_box_expectation('p2', 2, false),
+ $this->get_contains_drop_box_expectation('p3', 3, false),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p1', '1'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p2', '0'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p3', '1'),
+ $this->get_contains_submit_button_expectation(true),
+ $this->get_does_not_contain_try_again_button_expectation(),
+ $this->get_does_not_contain_correctness_expectation(),
+ $this->get_does_not_contain_feedback_expectation(),
+ $this->get_tries_remaining_expectation(1),
+ $this->get_no_hint_visible_expectation());
+
+ // Submit the right answer.
+ $this->process_submission(array('p1' => '1', 'p2' => '1', 'p3' => '1', '-submit' => 1));
+
+ // Verify.
+ $this->check_current_state(question_state::$gradedright);
+ $this->check_current_mark(6);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, true, 'correct'),
+ $this->get_contains_drop_box_expectation('p2', 2, true, 'correct'),
+ $this->get_contains_drop_box_expectation('p3', 3, true, 'correct'),
+ $this->get_contains_submit_button_expectation(false),
+ $this->get_does_not_contain_try_again_button_expectation(),
+ $this->get_contains_correct_expectation(),
+ $this->get_no_hint_visible_expectation(),
+ $this->get_does_not_contain_num_parts_correct(),
+ $this->get_contains_standard_correct_combined_feedback_expectation(),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p1', '1'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p2', '1'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p3', '1'));
+ }
+
+ public function test_interactive_correct_no_submit() {
+
+ // Create a drag-and-drop question.
+ $dd = qtype_ddwtos_test_helper::make_a_ddwtos_question();
+ $dd->hints = array(
+ new question_hint_with_parts('This is the first hint.', false, false),
+ new question_hint_with_parts('This is the second hint.', true, true),
+ );
+ $dd->shufflechoices = false;
+ $this->start_attempt_at_question($dd, 'interactive', 3);
+
+ // Check the initial state.
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, false),
+ $this->get_contains_drop_box_expectation('p2', 2, false),
+ $this->get_contains_drop_box_expectation('p3', 3, false),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p1', '0'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p2', '0'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p3', '0'),
+ $this->get_contains_submit_button_expectation(true),
+ $this->get_does_not_contain_feedback_expectation(),
+ $this->get_tries_remaining_expectation(3),
+ $this->get_no_hint_visible_expectation());
+
+ // Save the right answer.
+ $this->process_submission(array('p1' => '1', 'p2' => '1', 'p3' => '1'));
+
+ // Finish the attempt without clicking check.
+ $this->quba->finish_all_questions();
+
+ // Verify.
+ $this->check_current_state(question_state::$gradedright);
+ $this->check_current_mark(3);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, true, 'correct'),
+ $this->get_contains_drop_box_expectation('p2', 2, true, 'correct'),
+ $this->get_contains_drop_box_expectation('p3', 3, true, 'correct'),
+ $this->get_contains_submit_button_expectation(false),
+ $this->get_contains_correct_expectation(),
+ $this->get_no_hint_visible_expectation());
+
+ // Check regrading does not mess anything up.
+ $this->quba->regrade_all_questions();
+
+ // Verify.
+ $this->check_current_state(question_state::$gradedright);
+ $this->check_current_mark(3);
+ }
+
+ public function test_interactive_partial_no_submit() {
+
+ // Create a drag-and-drop question.
+ $dd = qtype_ddwtos_test_helper::make_a_ddwtos_question();
+ $dd->hints = array(
+ new question_hint_with_parts('This is the first hint.', false, false),
+ new question_hint_with_parts('This is the second hint.', true, true),
+ );
+ $dd->shufflechoices = false;
+ $this->start_attempt_at_question($dd, 'interactive', 3);
+
+ // Check the initial state.
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, false),
+ $this->get_contains_drop_box_expectation('p2', 2, false),
+ $this->get_contains_drop_box_expectation('p3', 3, false),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p1', '0'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p2', '0'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p3', '0'),
+ $this->get_contains_submit_button_expectation(true),
+ $this->get_does_not_contain_feedback_expectation(),
+ $this->get_tries_remaining_expectation(3),
+ $this->get_no_hint_visible_expectation());
+
+ // Save the a partially right answer.
+ $this->process_submission(array('p1' => '1', 'p2' => '2', 'p3' => '2'));
+
+ // Finish the attempt without clicking check.
+ $this->quba->finish_all_questions();
+
+ // Verify.
+ $this->check_current_state(question_state::$gradedpartial);
+ $this->check_current_mark(1);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, true, 'correct'),
+ $this->get_contains_drop_box_expectation('p2', 2, true, 'incorrect'),
+ $this->get_contains_drop_box_expectation('p3', 3, true, 'incorrect'),
+ $this->get_contains_submit_button_expectation(false),
+ $this->get_contains_partcorrect_expectation(),
+ $this->get_no_hint_visible_expectation());
+
+ // Check regrading does not mess anything up.
+ $this->quba->regrade_all_questions();
+
+ // Verify.
+ $this->check_current_state(question_state::$gradedpartial);
+ $this->check_current_mark(1);
+ }
+
+ public function test_interactive_no_right_clears() {
+
+ // Create a drag-and-drop question.
+ $dd = qtype_ddwtos_test_helper::make_a_ddwtos_question();
+ $dd->hints = array(
+ new question_hint_with_parts('This is the first hint.', false, true),
+ new question_hint_with_parts('This is the second hint.', true, true),
+ );
+ $dd->shufflechoices = false;
+ $this->start_attempt_at_question($dd, 'interactive', 3);
+
+ // Check the initial state.
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ $this->get_contains_marked_out_of_summary(),
+ $this->get_contains_drop_box_expectation('p1', 1, false),
+ $this->get_contains_drop_box_expectation('p2', 2, false),
+ $this->get_contains_drop_box_expectation('p3', 3, false),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p1', '0'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p2', '0'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p3', '0'),
+ $this->get_contains_submit_button_expectation(true),
+ $this->get_does_not_contain_feedback_expectation(),
+ $this->get_tries_remaining_expectation(3),
+ $this->get_no_hint_visible_expectation());
+
+ // Save the a completely wrong answer.
+ $this->process_submission(array('p1' => '2', 'p2' => '2', 'p3' => '2', '-submit' => 1));
+
+ // Verify.
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ $this->get_contains_marked_out_of_summary(),
+ $this->get_contains_drop_box_expectation('p1', 1, true),
+ $this->get_contains_drop_box_expectation('p2', 2, true),
+ $this->get_contains_drop_box_expectation('p3', 3, true),
+ $this->get_contains_submit_button_expectation(false),
+ $this->get_contains_hint_expectation('This is the first hint'));
+
+ // Do try again.
+ $this->process_submission(array('p1' => '0', 'p2' => '0', 'p3' => '0', '-tryagain' => 1));
+
+ // Check that all the wrong answers have been cleared.
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ $this->get_contains_marked_out_of_summary(),
+ $this->get_contains_drop_box_expectation('p1', 1, false),
+ $this->get_contains_drop_box_expectation('p2', 2, false),
+ $this->get_contains_drop_box_expectation('p3', 3, false),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p1', '0'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p2', '0'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p3', '0'),
+ $this->get_contains_submit_button_expectation(true),
+ $this->get_does_not_contain_feedback_expectation(),
+ $this->get_tries_remaining_expectation(2),
+ $this->get_no_hint_visible_expectation());
+ }
+
+ public function test_display_of_right_answer_when_shuffled() {
+
+ // Create a drag-and-drop question.
+ $dd = qtype_ddwtos_test_helper::make_a_ddwtos_question();
+ $this->start_attempt_at_question($dd, 'deferredfeedback', 3);
+
+ // Check the initial state.
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, false),
+ $this->get_contains_drop_box_expectation('p2', 2, false),
+ $this->get_contains_drop_box_expectation('p3', 3, false),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p1', '0'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p2', '0'),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p3', '0'),
+ $this->get_does_not_contain_feedback_expectation());
+
+ // Save a partial answer.
+ $this->process_submission($dd->get_correct_response());
+
+ // Verify.
+ $this->check_current_state(question_state::$complete);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, false),
+ $this->get_contains_drop_box_expectation('p2', 2, false),
+ $this->get_contains_drop_box_expectation('p3', 3, false),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p1', $dd->get_right_choice_for(1)),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p2', $dd->get_right_choice_for(2)),
+ $this->get_contains_hidden_expectation($this->quba->get_field_prefix($this->slot) . 'p3', $dd->get_right_choice_for(3)),
+ $this->get_does_not_contain_correctness_expectation(),
+ $this->get_does_not_contain_feedback_expectation());
+
+ // Finish the attempt.
+ $this->quba->finish_all_questions();
+
+ // Verify.
+ $this->displayoptions->rightanswer = question_display_options::VISIBLE;
+ $this->assertEqual('{quick} {fox} {lazy}', $dd->get_right_answer_summary());
+ $this->check_current_state(question_state::$gradedright);
+ $this->check_current_mark(3);
+ $this->check_current_output(
+ $this->get_contains_drop_box_expectation('p1', 1, true, 'correct'),
+ $this->get_contains_drop_box_expectation('p2', 2, true, 'correct'),
+ $this->get_contains_drop_box_expectation('p3', 3, true, 'correct'),
+ $this->get_contains_correct_expectation(),
+ new PatternExpectation('/' . preg_quote('The [quick] brown [fox] jumped over the [lazy] dog.') . '/'));
+
+ }
+}
diff --git a/question/type/ddimageortext/styles.css b/question/type/ddimageortext/styles.css
new file mode 100755
index 00000000000..991b59e5788
--- /dev/null
+++ b/question/type/ddimageortext/styles.css
@@ -0,0 +1,67 @@
+.que.ddwtos .qtext {
+ line-height:2em;
+ margin-top: 1px;
+ margin-bottom: 0.5em;
+ display: block;
+}
+
+.que.ddwtos .answercontainer {
+ line-height: 2em;
+ margin-bottom:1em;
+ display: block;
+}
+
+.que.ddwtos .answertext {
+ padding-bottom: 0.5em;
+}
+
+.que.ddwtos .slot {
+ display: inline-block;
+ text-align: center;
+ border: 1px solid #000000;
+}
+.que.ddwtos .player {
+ display: inline-block;
+ text-align: center;
+ cursor: move;
+ border: 1px solid #000000;
+}
+.que.ddwtos .player.placed { /* Lets the focus rectangle be visible in IE. */
+ background: transparent;
+}
+
+.que.ddwtos .readonly {
+ cursor: default;
+}
+
+.que.ddwtos span.incorrect {
+ background-color: #faa;
+}
+.que.ddwtos span.correct {
+ background-color: #afa;
+}
+
+.que.ddwtos .group1 {
+ background-color: #FFFFFF;
+}
+.que.ddwtos .group2 {
+ background-color: #DCDCDC;
+}
+.que.ddwtos .group3 {
+ background-color: #B0C4DE;
+}
+.que.ddwtos .group4 {
+ background-color: #D8BFD8;
+}
+.que.ddwtos .group5 {
+ background-color: #87CEFA;
+}
+.que.ddwtos .group6 {
+ background-color: #DAA520;
+}
+.que.ddwtos .group7 {
+ background-color: #FFD700;
+}
+.que.ddwtos .group8 {
+ background-color: #F0E68C;
+}
diff --git a/question/type/ddimageortext/version.php b/question/type/ddimageortext/version.php
new file mode 100755
index 00000000000..243100003cd
--- /dev/null
+++ b/question/type/ddimageortext/version.php
@@ -0,0 +1,4 @@
+version = 2010042800;
+$plugin->requires = 2007101000;