commit a2bb148ae33b1ff47aed65d65f55e25f2a69c009 Author: Tim Hunt Date: Fri Jan 28 19:07:33 2011 +0000 MDL-47494 ddimageortext: work-in-progress converting the ddwtos and gapselect qtypes. 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 00000000000..f900852b7c3 Binary files /dev/null and b/question/type/ddimageortext/icon.gif differ 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 &#xB0;C all aqueous basic solutions have hydrogen ion concentrations less than 10<sup>&#x2212;7</sup> mol litre<sup>&#x2212;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;