MDL-20636 numerical qtype: assorted changes

1. database upgrade to merge instructions into the questiontext field,
and remove the UNITDISPLAY option.

2. Changes to the validation in deferred feedback mode, so students
are warned about incomplete answers.

3. Using this to wark students if they enter an answer that is not
recognised as a number.
This commit is contained in:
Tim Hunt 2011-04-27 18:00:14 +01:00
parent 52ad7e0c1b
commit 5d2465c3f4
16 changed files with 441 additions and 90 deletions

View File

@ -101,8 +101,7 @@ abstract class backup_qtype_plugin extends backup_plugin {
// Define the elements
$options = new backup_nested_element('numerical_options');
$option = new backup_nested_element('numerical_option', array('id'), array(
'instructions', 'instructionsformat', 'showunits', 'unitsleft',
'unitgradingtype', 'unitpenalty'));
'showunits', 'unitsleft', 'unitgradingtype', 'unitpenalty'));
// Build the tree
$element->add_child($options);
@ -192,15 +191,4 @@ abstract class backup_qtype_plugin extends backup_plugin {
}
return $components;
}
/**
* Returns one array with filearea => mappingname elements for the qtype
*
* Used by {@link get_components_and_fileareas} to know about all the qtype
* files to be processed both in backup and restore.
*/
public static function get_qtype_fileareas() {
// By default, return empty array, only qtypes having own fileareas will override this
return array();
}
}

View File

@ -68,6 +68,33 @@ class qbehaviour_deferredfeedback extends question_behaviour_with_save {
}
}
/*
* Like the parent method, except that when a respones is gradable, but not
* completely, we move it to the invalid state.
*
* TODO refactor, to remove the duplication.
*/
public function process_save(question_attempt_pending_step $pendingstep) {
if ($this->qa->get_state()->is_finished()) {
return question_attempt::DISCARD;
} else if (!$this->qa->get_state()->is_active()) {
throw new coding_exception('Question is not active, cannot process_actions.');
}
if ($this->is_same_response($pendingstep)) {
return question_attempt::DISCARD;
}
if ($this->is_complete_response($pendingstep)) {
$pendingstep->set_state(question_state::$complete);
} else if ($this->question->is_gradable_response($pendingstep->get_qt_data())) {
$pendingstep->set_state(question_state::$invalid);
} else {
$pendingstep->set_state(question_state::$todo);
}
return question_attempt::KEEP;
}
public function summarise_action(question_attempt_step $step) {
if ($step->has_behaviour_var('comment')) {
return $this->summarise_manual_comment($step);

View File

@ -563,8 +563,6 @@ class qformat_gift_test extends UnitTestCase {
'options' => (object) array(
'id' => 123,
'question' => 666,
'instructions' => '',
'instructionsformat' => FORMAT_MOODLE,
'showunits' => 0,
'unitsleft' => 0,
'showunits' => 2,

View File

@ -198,7 +198,7 @@ class qtype_ddwtos_walkthrough_test extends qbehaviour_walkthrough_test_base {
$this->process_submission(array('p1' => '1', 'p2' => '2'));
// Verify.
$this->check_current_state(question_state::$todo);
$this->check_current_state(question_state::$invalid);
$this->check_current_mark(null);
$this->check_current_output(
$this->get_contains_drop_box_expectation('p1', 1, false),
@ -338,7 +338,7 @@ class qtype_ddwtos_walkthrough_test extends qbehaviour_walkthrough_test_base {
$this->process_submission(array('p1' => '1', 'p2' => '0', 'p3' => '0'));
// Verify.
$this->check_current_state(question_state::$todo);
$this->check_current_state(question_state::$invalid);
$this->check_current_mark(null);
$this->check_current_output(
$this->get_contains_drop_box_expectation('p1', 1, false),

View File

@ -124,7 +124,7 @@ class qtype_match_walkthrough_test extends qbehaviour_walkthrough_test_base {
'sub1' => $orderforchoice[2], 'sub2' => '0', 'sub3' => '0'));
// Verify.
$this->check_current_state(question_state::$todo);
$this->check_current_state(question_state::$invalid);
$this->check_current_mark(null);
$this->check_current_output(
$this->get_contains_select_expectation('sub0', $choices, 1, true),

View File

@ -83,6 +83,6 @@ class backup_qtype_numerical_plugin extends backup_qtype_plugin {
* files to be processed both in backup and restore.
*/
public static function get_qtype_fileareas() {
return array('instruction' => 'question_created');
return array();
}
}

View File

@ -22,10 +22,8 @@
<TABLE NAME="question_numerical_options" COMMENT="Options for questions of type numerical This table is also used by the calculated question type" PREVIOUS="question_numerical" NEXT="question_numerical_units">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" NEXT="question"/>
<FIELD NAME="question" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="id" NEXT="instructions"/>
<FIELD NAME="instructions" TYPE="text" LENGTH="small" NOTNULL="false" SEQUENCE="false" COMMENT="Feedback shown for any incorrect response." PREVIOUS="question" NEXT="instructionsformat"/>
<FIELD NAME="instructionsformat" TYPE="int" LENGTH="2" NOTNULL="true" UNSIGNED="false" DEFAULT="0" SEQUENCE="false" PREVIOUS="instructions" NEXT="showunits"/>
<FIELD NAME="showunits" TYPE="int" LENGTH="4" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="display units as multichoice" PREVIOUS="instructionsformat" NEXT="unitsleft"/>
<FIELD NAME="question" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="id" NEXT="showunits"/>
<FIELD NAME="showunits" TYPE="int" LENGTH="4" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="display units as multichoice" PREVIOUS="question" NEXT="unitsleft"/>
<FIELD NAME="unitsleft" TYPE="int" LENGTH="4" NOTNULL="true" UNSIGNED="false" DEFAULT="0" SEQUENCE="false" COMMENT="display the unit at left as in $1.00" PREVIOUS="showunits" NEXT="unitgradingtype"/>
<FIELD NAME="unitgradingtype" TYPE="int" LENGTH="4" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="0 no penalty, 1 response grade, 2 total grade" PREVIOUS="unitsleft" NEXT="unitpenalty"/>
<FIELD NAME="unitpenalty" TYPE="number" LENGTH="12" NOTNULL="true" UNSIGNED="true" DEFAULT="0.1" SEQUENCE="false" DECIMALS="7" PREVIOUS="unitgradingtype"/>

View File

@ -59,7 +59,7 @@ function xmldb_qtype_numerical_upgrade($oldversion) {
if (!$dbman->table_exists($table)) {
// $dbman->create_table doesnt return a result, we just have to trust it
$dbman->create_table($table);
}//else
}
upgrade_plugin_savepoint(true, 2009100100, 'qtype', 'numerical');
}
@ -96,5 +96,176 @@ function xmldb_qtype_numerical_upgrade($oldversion) {
upgrade_plugin_savepoint(true, 2009100101, 'qtype', 'numerical');
}
if ($oldversion < 2011042600) {
// Get rid of the instructions field by adding it to the qestion
// text. Also, if the unit was set to be displayed beside the input,
// deal with that within the question text too.
// The hard-coded constants used here are:
// 2 = the old qtype_numerical::UNITDISPLAY for ->showunits
// 3 = qtype_numerical::UNITNONE
$fs = get_file_storage();
$rs = $DB->get_recordset_sql('
SELECT q.id AS questionid,
q.questiontext,
q.questiontextformat,
qc.contextid,
qno.id AS qnoid,
qno.instructions,
qno.instructionsformat,
qno.showunits,
qno.unitsleft,
qnu.unit AS defaultunit
FROM {question} q
FROM {question_categories} qc ON qc.id = q.category
JOIN {question_numerical_options} qno ON qno.question = q.id
JOIN {question_numerical_units} qnu ON qnu.id = (
SELECT min(id)
FROM {question_numerical_units}
WHERE question = q.id AND ABS(multiplier - 1) < 0.0000000001)');
foreach ($rs as $numericaloptions) {
if ($numericaloptions->showunits != 2 && empty($numericaloptions->instructions)) {
// Nothing to do for this question.
continue;
}
$ishtml = qtype_numerical_convert_text_format($numericaloptions);
$response = '_______________';
if ($numericaloptions->showunits == 2) {
if ($numericaloptions->unitsleft) {
$response = $numericaloptions->defaultunit . ' _______________';
} else {
$response = '_______________ ' . $numericaloptions->defaultunit;
}
$DB->set_field('question_numerical_options', 'showunits', 3,
array('id' => $numericaloptions->qnoid));
}
if ($ishtml) {
$numericaloptions->questiontext .= '<p>' . $response . '</p>';
} else {
$numericaloptions->questiontext .= "\n\n" . $response;
}
if (!empty($numericaloptions->instructions)) {
if ($ishtml) {
$numericaloptions->questiontext .= $numericaloptions->instructions;
} else {
$numericaloptions->questiontext .= "\n\n" . $numericaloptions->instructions;
}
$oldfiles = $fs->get_area_files($numericaloptions->contextid,
'qtype_numerical', 'instruction', $numericaloptions->questionid, 'id', false);
foreach ($oldfiles as $oldfile) {
$filerecord = new stdClass();
$filerecord->component = 'question';
$filerecord->filearea = 'questiontext';
$fs->create_file_from_storedfile($filerecord, $oldfile);
}
if ($oldfiles) {
$fs->delete_area_files($numericaloptions->contextid,
'qtype_numerical', 'instruction', $numericaloptions->questionid);
}
}
$updaterecord = new stdClass();
$updaterecord->id = $numericaloptions->questionid;
$updaterecord->questiontext = $numericaloptions->questiontext;
$updaterecord->questiontextformat = $numericaloptions->questiontextformat;
$DB->update_record('question', $updaterecord);
}
$rs->close();
}
if ($oldversion < 2011042601) {
// Define field instructions to be dropped from question_numerical_options
$table = new xmldb_table('question_numerical_options');
$field = new xmldb_field('instructions');
// Conditionally launch drop field instructions
if ($dbman->field_exists($table, $field)) {
$dbman->drop_field($table, $field);
}
// numerical savepoint reached
upgrade_plugin_savepoint(true, 2011042601, 'qtype', 'numerical');
}
if ($oldversion < 2011042602) {
// Define field instructionsformat to be dropped from question_numerical_options
$table = new xmldb_table('question_numerical_options');
$field = new xmldb_field('instructionsformat');
// Conditionally launch drop field instructionsformat
if ($dbman->field_exists($table, $field)) {
$dbman->drop_field($table, $field);
}
// numerical savepoint reached
upgrade_plugin_savepoint(true, 2011042602, 'qtype', 'numerical');
}
return true;
}
/**
* Convert the ->questiontext and ->instructions fields to have the same text format.
* If they are already the same, do nothing. Otherwise, this method works by
* converting both to HTML.
* @param $numericaloptions the data to convert.
* @return bool true if the resulting fields are in HTML, as opposed to one of
* the text-based formats.
*/
function qtype_numerical_convert_text_format($numericaloptions) {
if ($numericaloptions->questiontextformat == $numericaloptions->instructionsformat) {
// Nothing to do:
return $numericaloptions->questiontextformat == FORMAT_HTML;
}
if ($numericaloptions->questiontextformat != FORMAT_HTML) {
$numericaloptions->questiontext = qtype_numerical_convert_to_html(
$numericaloptions->questiontext, $numericaloptions->questiontextformat);
$numericaloptions->questiontextformat = FORMAT_HTML;
}
if ($numericaloptions->instructionsformat != FORMAT_HTML) {
$numericaloptions->instructions = qtype_numerical_convert_to_html(
$numericaloptions->instructions, $numericaloptions->instructionsformat);
$numericaloptions->instructionsformat = FORMAT_HTML;
}
return true;
}
/**
* Convert some content to HTML.
* @param string $text the content to convert to HTML
* @param int $oldformat One of the FORMAT_... constants.
*/
function qtype_numerical_convert_to_html($text, $oldformat) {
switch ($oldformat) {
// Similar to format_text.
case FORMAT_PLAIN:
$text = s($text);
$text = str_replace(' ', '&nbsp; ', $text);
$text = nl2br($text);
return $text;
case FORMAT_MARKDOWN:
return markdown_to_html($text);
case FORMAT_MOODLE:
return text_to_html($text, null, $options['para'], $options['newlines']);
default:
throw new coding_exception('Unexpected text format when upgrading numerical questions.');
}
}

View File

@ -71,7 +71,6 @@ class qtype_numerical_edit_form extends question_edit_form {
$unitoptions = array(
qtype_numerical::UNITNONE => get_string('onlynumerical', 'qtype_numerical'),
qtype_numerical::UNITDISPLAY => get_string('oneunitshown', 'qtype_numerical'),
qtype_numerical::UNITOPTIONAL => get_string('manynumerical', 'qtype_numerical'),
qtype_numerical::UNITGRADED => get_string('unitgraded', 'qtype_numerical'),
);
@ -112,19 +111,12 @@ class qtype_numerical_edit_form extends question_edit_form {
get_string('unitposition', 'qtype_numerical'), $unitsleftoptions);
$mform->setDefault('unitsleft', 0);
$mform->addElement('editor', 'instructions',
get_string('instructions', 'qtype_numerical'), null, $this->editoroptions);
$mform->setType('instructions', PARAM_RAW);
$mform->addHelpButton('instructions', 'numericalinstructions', 'qtype_numerical');
$mform->disabledIf('penaltygrp', 'unitrole', 'eq', qtype_numerical::UNITNONE);
$mform->disabledIf('penaltygrp', 'unitrole', 'eq', qtype_numerical::UNITDISPLAY);
$mform->disabledIf('penaltygrp', 'unitrole', 'eq', qtype_numerical::UNITOPTIONAL);
$mform->disabledIf('unitsleft', 'unitrole', 'eq', qtype_numerical::UNITNONE);
$mform->disabledIf('multichoicedisplay', 'unitrole', 'eq', qtype_numerical::UNITNONE);
$mform->disabledIf('multichoicedisplay', 'unitrole', 'eq', qtype_numerical::UNITDISPLAY);
$mform->disabledIf('multichoicedisplay', 'unitrole', 'eq', qtype_numerical::UNITOPTIONAL);
}
@ -234,20 +226,6 @@ class qtype_numerical_edit_form extends question_edit_form {
$question->unitrole = $question->options->showunits;
}
// Instructions field.
$draftitemid = file_get_submitted_draft_itemid('instruction');
$question->instructions['text'] = file_prepare_draft_area(
$draftitemid, // draftid
$this->context->id, // context
'qtype_' . $this->qtype(), // component
'instruction', // filarea
!empty($question->id) ? (int) $question->id : null, // itemid
$this->fileoptions, // options
$question->options->instructions // text
);
$question->instructions['itemid'] = $draftitemid ;
$question->instructions['format'] = $question->options->instructionsformat;
return $question;
}

View File

@ -33,12 +33,13 @@ $string['answerno'] = 'Answer {$a}';
$string['decfractionofquestiongrade'] = 'as a fraction (0-1) of the question grade';
$string['decfractionofresponsegrade'] = 'as a fraction (0-1) of the response grade';
$string['decimalformat'] = 'decimals';
$string['editableunittext'] = 'a text input element';
$string['editableunittext'] = 'the text input element';
$string['editingnumerical'] = 'Editing a Numerical question';
$string['errornomultiplier'] = 'You must specify a multiplier for this unit.';
$string['errorrepeatedunit'] = 'You cannot have two units with the same name.';
$string['geometric'] = 'Geometric';
$string['instructions'] = 'Instructions ';
$string['invalidnumber'] = 'You must enter a valid number.';
$string['invalidnumbernounit'] = 'You must enter a valid number. Do not include a unit in your response.';
$string['invalidnumericanswer'] = 'One of the answers you entered was not a valid number.';
$string['invalidnumerictolerance'] = 'One of the tolerances you entered was not a valid number.';
$string['leftexample'] = 'on the left, for example $1.00 or £1.00';
@ -51,8 +52,6 @@ $string['numerical'] = 'Numerical';
$string['numerical_help'] = 'From the student perspective, a numerical question looks just like a short-answer question. The difference is that numerical answers are allowed to have an accepted error. This allows a fixed range of answers to be evaluated as one answer. For example, if the answer is 10 with an accepted error of 2, then any number between 8 and 12 will be accepted as correct. ';
$string['numerical_link'] = 'question/type/numerical';
$string['numericalsummary'] = 'Allows a numerical response, possibly with units, that is graded by comparing against various model answers, possibly with tolerances.';
$string['numericalinstructions'] = 'Instructions';
$string['numericalinstructions_help'] = 'Enter any instructions you would like to give the student about how to complete their response.';
$string['numericalmultiplier'] = 'Multiplier';
$string['numericalmultiplier_help'] = 'The multiplier is the factor by which the correct numerical response will be multiplied.
@ -100,7 +99,7 @@ $string['unitpenalty_help'] = 'The penalty is applied if
* the wrong unit name is entered into the unit input, or
* a unit is entered into the value input box';
$string['unitappliedpenalty'] = 'These marks include a penalty of {$a} for bad unit.';
$string['unitposition'] = 'Units are displayed';
$string['unitposition'] = 'Units go';
$string['unitnotselected'] = 'No unit selected';
$string['unithandling'] = 'Unit handling';
$string['validnumberformats'] = 'Valid number formats';

View File

@ -40,7 +40,7 @@ class qtype_numerical_question extends question_graded_automatically {
/** @var array of question_answer. */
public $answers = array();
/** @var int one of the constants UNITNONE, UNITDISPLAY, UNITSELECT or UNITINPUT. */
/** @var int one of the constants UNITNONE, UNITSELECT or UNITINPUT. */
public $unitdisplay;
/** @var int one of the constants UNITGRADEDOUTOFMARK or UNITGRADEDOUTOFMAX. */
public $unitgradingtype;
@ -76,16 +76,43 @@ class qtype_numerical_question extends question_graded_automatically {
}
}
public function is_complete_response(array $response) {
public function is_gradable_response(array $response) {
return array_key_exists('answer', $response) &&
($response['answer'] || $response['answer'] === '0' || $response['answer'] === 0);
}
public function get_validation_error(array $response) {
if ($this->is_gradable_response($response)) {
return '';
public function is_complete_response(array $response) {
if (!$this->is_gradable_response($response)) {
return false;
}
return get_string('pleaseenterananswer', 'qtype_numerical');
list($value, $unit) = $this->ap->apply_units($response['answer']);
if (is_null($value)) {
return false;
}
if ($this->unitdisplay == qtype_numerical::UNITNONE && $unit) {
return false;
}
return true;
}
public function get_validation_error(array $response) {
if (!$this->is_gradable_response($response)) {
return get_string('pleaseenterananswer', 'qtype_numerical');
}
list($value, $unit) = $this->ap->apply_units($response['answer']);
if (is_null($value)) {
return get_string('invalidnumber', 'qtype_numerical');
}
if ($this->unitdisplay == qtype_numerical::UNITNONE && $unit) {
return get_string('invalidnumbernounit', 'qtype_numerical');
}
return '';
}
public function is_same_response(array $prevresponse, array $newresponse) {
@ -129,7 +156,7 @@ class qtype_numerical_question extends question_graded_automatically {
}
protected function apply_unit_penalty($fraction, $unit) {
if (!empty($unit)) {
if (!empty($unit) && $this->ap->is_known_unit($unit)) {
return $fraction;
}
@ -178,9 +205,6 @@ class qtype_numerical_question extends question_graded_automatically {
} else if ($component == 'question' && $filearea == 'hint') {
return $this->check_hint_file_access($qa, $options, $args);
} else if ($component == 'qtype_numerical' && $filearea == 'instruction') {
return true;
} else {
return parent::check_file_access($qa, $options, $component, $filearea, $args, $forcedownload);
}

View File

@ -45,7 +45,6 @@ class qtype_numerical extends question_type {
const UNITSELECT = 1;
const UNITNONE = 3;
const UNITDISPLAY = 2;
const UNITGRADED = 1;
const UNITOPTIONAL = 0;
@ -79,7 +78,7 @@ class qtype_numerical extends question_type {
array('questionid' => $question->id), 'id ASC');
$this->get_numerical_units($question);
//get_numerical_options() need to know if there are units
// get_numerical_options() need to know if there are units
// to set correctly default values
$this->get_numerical_options($question);
@ -139,16 +138,12 @@ class qtype_numerical extends question_type {
$question->options->showunits = self::UNITNONE ;
}
$question->options->unitsleft = 0;
$question->options->instructions = '';
$question->options->instructionsformat = editors_get_preferred_format();
} else {
$question->options->unitgradingtype = $options->unitgradingtype;
$question->options->unitpenalty = $options->unitpenalty;
$question->options->showunits = $options->showunits;
$question->options->unitsleft = $options->unitsleft;
$question->options->instructions = $options->instructions;
$question->options->instructionsformat = $options->instructionsformat;
}
return true;
@ -267,7 +262,6 @@ class qtype_numerical extends question_type {
if (!$options) {
$options = new stdClass();
$options->question = $question->id;
$options->instructions = '';
$options->id = $DB->insert_record('question_numerical_options', $options);
}
@ -302,10 +296,6 @@ class qtype_numerical extends question_type {
$options->unitsleft = !empty($question->unitsleft);
$options->instructions = $this->import_or_save_files($question->instructions,
$question->context, 'qtype_'.$question->qtype , 'instruction', $question->id);
$options->instructionsformat = $question->instructions['format'];
$DB->update_record('question_numerical_options', $options);
// Report any problems.
@ -407,11 +397,6 @@ class qtype_numerical extends question_type {
$formatoptions->noclean = true;
$formatoptions->para = false;
$nameprefix = $question->name_prefix;
$component = 'qtype_' . $question->qtype;
// rewrite instructions text
$question->options->instructions = quiz_rewrite_question_urls(
$question->options->instructions, 'pluginfile.php', $context->id, $component,
'instruction', array($state->attempt, $state->question), $question->id);
/// Print question text and media
$questiontext = format_text($question->questiontext,
@ -672,9 +657,6 @@ class qtype_numerical extends question_type {
parent::move_files($questionid, $oldcontextid, $newcontextid);
$this->move_files_in_answers($questionid, $oldcontextid, $newcontextid);
$fs->move_area_files_to_new_context($oldcontextid,
$newcontextid, 'qtype_numerical', 'instruction', $questionid);
}
protected function delete_files($questionid, $contextid) {
@ -682,7 +664,6 @@ class qtype_numerical extends question_type {
parent::delete_files($questionid, $contextid);
$this->delete_files_in_answers($questionid, $contextid);
$fs->delete_area_files($contextid, 'qtype_numerical', 'instruction', $questionid);
}
}
@ -797,9 +778,6 @@ class qtype_numerical_answer_processor {
$unit = substr($response, strlen($matchedpart));
}
$unit = trim($unit);
if ($unit && !array_key_exists($unit, $this->units)) {
$unit = '';
}
return array($beforepoint, $decimals, $exponent, $unit);
}
@ -826,7 +804,7 @@ class qtype_numerical_answer_processor {
$numberstring .= 'e' . $exponent;
}
if ($unit) {
if ($unit && $this->is_known_unit($unit)) {
$value = $numberstring * $this->units[$unit];
} else {
$value = $numberstring * 1;
@ -855,4 +833,13 @@ class qtype_numerical_answer_processor {
return $answer . ' ' . $unit;
}
}
/**
* Is this unit recognised.
* @param string $unit the unit
* @return bool whether this is a unit we recognise.
*/
public function is_known_unit($unit) {
return array_key_exists($unit, $this->units);
}
}

View File

@ -77,7 +77,7 @@ class qtype_numerical_answer_processor_test extends UnitTestCase {
$ap->parse_response('1,000.00 m'));
$this->assertEqual(array(null, null, null, null), $ap->parse_response('frog'));
$this->assertEqual(array('3', '', '', ''), $ap->parse_response('3 frogs'));
$this->assertEqual(array('3', '', '', 'frogs'), $ap->parse_response('3 frogs'));
$this->assertEqual(array(null, null, null, null), $ap->parse_response('. m'));
$this->assertEqual(array(null, null, null, null), $ap->parse_response('.e8 m'));
$this->assertEqual(array(null, null, null, null), $ap->parse_response(','));
@ -92,7 +92,7 @@ class qtype_numerical_answer_processor_test extends UnitTestCase {
$this->assertEqual(array(299792458, 'c'), $ap->apply_units('1c'));
$this->assertEqual(array(0.44704, 'mph'), $ap->apply_units('0001.000 mph'));
$this->assertEqual(array(1, ''), $ap->apply_units('1 frogs'));
$this->assertEqual(array(1, 'frogs'), $ap->apply_units('1 frogs'));
$this->assertEqual(array(null, null), $ap->apply_units('. m/s'));
}
@ -120,6 +120,6 @@ class qtype_numerical_answer_processor_test extends UnitTestCase {
$this->assertEqual(array('100', '$'), $ap->apply_units('$100.'));
$this->assertEqual(array('100.00', '$'), $ap->apply_units('$100.00'));
$this->assertEqual(array('100', ''), $ap->apply_units('100'));
$this->assertEqual(array('100', ''), $ap->apply_units('frog 100'));
$this->assertEqual(array('100', 'frog'), $ap->apply_units('frog 100'));
}
}

View File

@ -39,7 +39,7 @@ class qtype_numerical_question_test extends UnitTestCase {
$this->assertFalse($question->is_complete_response(array()));
$this->assertTrue($question->is_complete_response(array('answer' => '0')));
$this->assertTrue($question->is_complete_response(array('answer' => 0)));
$this->assertTrue($question->is_complete_response(array('answer' => 'test')));
$this->assertFalse($question->is_complete_response(array('answer' => 'test')));
}
public function test_is_gradable_response() {

View File

@ -0,0 +1,181 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file contains overall tests of numerical questions.
*
* @package qtype
* @subpackage numerical
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/question/engine/simpletest/helpers.php');
require_once($CFG->dirroot . '/question/type/numerical/simpletest/helper.php');
/**
* Unit tests for the numerical question type.
*
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qtype_numerical_walkthrough_test extends qbehaviour_walkthrough_test_base {
public function test_interactive_currency() {
// Create a gapselect question.
$q = test_question_maker::make_question('numerical', 'currency');
$q->hints = array(
new question_hint(1, 'This is the first hint.', FORMAT_HTML),
new question_hint(2, 'This is the second hint.', FORMAT_HTML),
);
$this->start_attempt_at_question($q, '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_submit_button_expectation(true),
$this->get_does_not_contain_feedback_expectation(),
$this->get_does_not_contain_validation_error_expectation(),
$this->get_does_not_contain_try_again_button_expectation(),
$this->get_no_hint_visible_expectation());
// Submit blank.
$this->process_submission(array('-submit' => 1, 'answer' => ''));
// Verify.
$this->check_current_state(question_state::$invalid);
$this->check_current_mark(null);
$this->check_current_output(
$this->get_contains_marked_out_of_summary(),
$this->get_contains_submit_button_expectation(true),
$this->get_does_not_contain_feedback_expectation(),
$this->get_contains_validation_error_expectation(),
$this->get_does_not_contain_try_again_button_expectation(),
$this->get_no_hint_visible_expectation());
// Sumit something that does not look liek a number.
$this->process_submission(array('-submit' => 1, 'answer' => 'newt'));
// Verify.
$this->check_current_state(question_state::$invalid);
$this->check_current_mark(null);
$this->check_current_output(
$this->get_contains_marked_out_of_summary(),
$this->get_contains_submit_button_expectation(true),
$this->get_does_not_contain_feedback_expectation(),
$this->get_contains_validation_error_expectation(),
new PatternExpectation('/' .
preg_quote(get_string('invalidnumber', 'qtype_numerical') . '/')),
$this->get_does_not_contain_try_again_button_expectation(),
$this->get_no_hint_visible_expectation());
// Now get it right.
$this->process_submission(array('-submit' => 1, 'answer' => '$1332'));
// Verify.
$this->check_current_state(question_state::$gradedright);
$this->check_current_mark(3);
$this->check_current_output(
$this->get_contains_mark_summary(3),
$this->get_contains_submit_button_expectation(false),
$this->get_contains_correct_expectation(),
$this->get_does_not_contain_validation_error_expectation(),
$this->get_no_hint_visible_expectation());
$this->assertEqual('$1332',
$this->quba->get_response_summary($this->slot));
}
public function test_deferredfeedback_currency() {
// Create a gapselect question.
$q = test_question_maker::make_question('numerical', 'currency');
$this->start_attempt_at_question($q, '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_marked_out_of_summary(),
$this->get_does_not_contain_feedback_expectation(),
$this->get_does_not_contain_validation_error_expectation(),
$this->get_does_not_contain_try_again_button_expectation(),
$this->get_no_hint_visible_expectation());
// Submit blank.
$this->process_submission(array('answer' => ''));
// 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_does_not_contain_feedback_expectation(),
$this->get_does_not_contain_validation_error_expectation(),
$this->get_does_not_contain_try_again_button_expectation(),
$this->get_no_hint_visible_expectation());
// Sumit something that does not look like a number.
$this->process_submission(array('answer' => '$newt'));
// Verify.
$this->check_current_state(question_state::$invalid);
$this->check_current_mark(null);
$this->check_current_output(
$this->get_contains_marked_out_of_summary(),
$this->get_does_not_contain_feedback_expectation(),
$this->get_contains_validation_error_expectation(),
new PatternExpectation('/' .
preg_quote(get_string('invalidnumber', 'qtype_numerical') . '/')),
$this->get_does_not_contain_try_again_button_expectation(),
$this->get_no_hint_visible_expectation());
// Now put in the right answer but without a unit.
$this->process_submission(array('answer' => '1332'));
$this->check_current_state(question_state::$complete);
$this->check_current_mark(null);
$this->check_current_output(
$this->get_contains_marked_out_of_summary(),
$this->get_does_not_contain_feedback_expectation(),
$this->get_does_not_contain_validation_error_expectation(),
$this->get_does_not_contain_try_again_button_expectation(),
$this->get_no_hint_visible_expectation());
// Submit all and finish.
$this->process_submission(array('-finish' => '1'));
// Verify.
$this->check_current_state(question_state::$gradedpartial);
$this->check_current_mark(2.4);
$this->check_current_output(
$this->get_contains_mark_summary(2.4),
$this->get_contains_partcorrect_expectation(),
$this->get_does_not_contain_validation_error_expectation(),
$this->get_no_hint_visible_expectation());
$this->assertEqual('1332',
$this->quba->get_response_summary($this->slot));
}
// Todo. Test validation if you try to type a unit into a question that does
// not expect one.
}

View File

@ -26,5 +26,5 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2010090501;
$plugin->version = 2011042602;
$plugin->requires = 2010090501;