first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+340
View File
@@ -0,0 +1,340 @@
<?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/>.
/**
* Question behaviour for the old adaptive mode.
*
* @package qbehaviour
* @subpackage adaptive
* @copyright 2009 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Question behaviour for adaptive mode.
*
* This is the old version of interactive mode.
*
* @copyright 2009 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qbehaviour_adaptive extends question_behaviour_with_multiple_tries {
const IS_ARCHETYPAL = true;
public function is_compatible_question(question_definition $question) {
return $question instanceof question_automatically_gradable;
}
public function get_expected_data() {
if ($this->qa->get_state()->is_active()) {
return array('submit' => PARAM_BOOL);
}
return parent::get_expected_data();
}
public function get_state_string($showcorrectness) {
$laststep = $this->qa->get_last_step();
if ($laststep->has_behaviour_var('_try')) {
$state = question_state::graded_state_for_fraction(
$laststep->get_behaviour_var('_rawfraction'));
return $state->default_string(true);
}
$state = $this->qa->get_state();
if ($state == question_state::$todo) {
return get_string('notcomplete', 'qbehaviour_adaptive');
} else {
return parent::get_state_string($showcorrectness);
}
}
public function get_right_answer_summary() {
return $this->question->get_right_answer_summary();
}
public function adjust_display_options(question_display_options $options) {
// Save some bits so we can put them back later.
$save = clone($options);
// Do the default thing.
parent::adjust_display_options($options);
// Then, if they have just Checked an answer, show them the applicable bits of feedback.
if (!$this->qa->get_state()->is_finished() &&
$this->qa->get_last_behaviour_var('_try')) {
$options->feedback = $save->feedback;
$options->correctness = $save->correctness;
$options->numpartscorrect = $save->numpartscorrect;
}
}
public function process_action(question_attempt_pending_step $pendingstep) {
if ($pendingstep->has_behaviour_var('comment')) {
return $this->process_comment($pendingstep);
} else if ($pendingstep->has_behaviour_var('finish')) {
return $this->process_finish($pendingstep);
} else if ($pendingstep->has_behaviour_var('submit')) {
return $this->process_submit($pendingstep);
} else {
return $this->process_save($pendingstep);
}
}
public function summarise_action(question_attempt_step $step) {
if ($step->has_behaviour_var('comment')) {
return $this->summarise_manual_comment($step);
} else if ($step->has_behaviour_var('finish')) {
return $this->summarise_finish($step);
} else if ($step->has_behaviour_var('submit')) {
return $this->summarise_submit($step);
} else {
return $this->summarise_save($step);
}
}
public function process_save(question_attempt_pending_step $pendingstep) {
$status = parent::process_save($pendingstep);
$prevgrade = $this->qa->get_fraction();
if (!is_null($prevgrade)) {
$pendingstep->set_fraction($prevgrade);
}
$pendingstep->set_state(question_state::$todo);
return $status;
}
protected function adjusted_fraction($fraction, $prevtries) {
return $fraction - $this->question->penalty * $prevtries;
}
public function process_submit(question_attempt_pending_step $pendingstep) {
$status = $this->process_save($pendingstep);
$response = $pendingstep->get_qt_data();
if (!$this->question->is_complete_response($response)) {
$pendingstep->set_state(question_state::$invalid);
if ($this->qa->get_state() != question_state::$invalid) {
$status = question_attempt::KEEP;
}
return $status;
}
$prevstep = $this->qa->get_last_step_with_behaviour_var('_try');
$prevresponse = $prevstep->get_qt_data();
$prevtries = $this->qa->get_last_behaviour_var('_try', 0);
$prevbest = $pendingstep->get_fraction();
if (is_null($prevbest)) {
$prevbest = 0;
}
if ($this->question->is_same_response($response, $prevresponse)) {
return question_attempt::DISCARD;
}
list($fraction, $state) = $this->question->grade_response($response);
$pendingstep->set_fraction(max($prevbest, $this->adjusted_fraction($fraction, $prevtries)));
if ($prevstep->get_state() == question_state::$complete) {
$pendingstep->set_state(question_state::$complete);
} else if ($state == question_state::$gradedright) {
$pendingstep->set_state(question_state::$complete);
} else {
$pendingstep->set_state(question_state::$todo);
}
$pendingstep->set_behaviour_var('_try', $prevtries + 1);
$pendingstep->set_behaviour_var('_rawfraction', $fraction);
$pendingstep->set_new_response_summary($this->question->summarise_response($response));
return question_attempt::KEEP;
}
public function process_finish(question_attempt_pending_step $pendingstep) {
if ($this->qa->get_state()->is_finished()) {
return question_attempt::DISCARD;
}
$prevtries = $this->qa->get_last_behaviour_var('_try', 0);
$prevbest = $this->qa->get_fraction();
if (is_null($prevbest)) {
$prevbest = 0;
}
$laststep = $this->qa->get_last_step();
$response = $laststep->get_qt_data();
if (!$this->question->is_gradable_response($response)) {
$state = question_state::$gaveup;
$fraction = 0;
} else {
if ($laststep->has_behaviour_var('_try')) {
// Last answer was graded, we want to regrade it. Otherwise the answer
// has changed, and we are grading a new try.
$prevtries -= 1;
}
list($fraction, $state) = $this->question->grade_response($response);
$pendingstep->set_behaviour_var('_try', $prevtries + 1);
$pendingstep->set_behaviour_var('_rawfraction', $fraction);
$pendingstep->set_new_response_summary($this->question->summarise_response($response));
}
$pendingstep->set_state($state);
$pendingstep->set_fraction(max($prevbest, $this->adjusted_fraction($fraction, $prevtries)));
return question_attempt::KEEP;
}
/**
* Got the most recently graded step. This is mainly intended for use by the
* renderer.
* @return question_attempt_step the most recently graded step.
*/
public function get_graded_step() {
$step = $this->qa->get_last_step_with_behaviour_var('_try');
if ($step->has_behaviour_var('_try')) {
return $step;
} else {
return null;
}
}
/**
* Determine whether a question state represents an "improvable" result,
* that is, whether the user can still improve their score.
*
* @param question_state $state the question state.
* @return bool whether the state is improvable
*/
public function is_state_improvable(question_state $state) {
return $state == question_state::$todo;
}
/**
* @return qbehaviour_adaptive_mark_details the information about the current state-of-play, scoring-wise,
* for this adaptive attempt.
*/
public function get_adaptive_marks() {
// Try to find the last graded step.
$gradedstep = $this->get_graded_step();
if (is_null($gradedstep) || $this->qa->get_max_mark() == 0) {
// No score yet.
return new qbehaviour_adaptive_mark_details(question_state::$todo);
}
// Work out the applicable state.
if ($this->qa->get_state()->is_commented()) {
$state = $this->qa->get_state();
} else {
$state = question_state::graded_state_for_fraction(
$gradedstep->get_behaviour_var('_rawfraction'));
}
// Prepare the grading details.
$details = $this->adaptive_mark_details_from_step($gradedstep, $state, $this->qa->get_max_mark(), $this->question->penalty);
$details->improvable = $this->is_state_improvable($this->qa->get_state());
return $details;
}
/**
* Actually populate the qbehaviour_adaptive_mark_details object.
* @param question_attempt_step $gradedstep the step that holds the relevant mark details.
* @param question_state $state the state corresponding to $gradedstep.
* @param unknown_type $maxmark the maximum mark for this question_attempt.
* @param unknown_type $penalty the penalty for this question, as a fraction.
*/
protected function adaptive_mark_details_from_step(question_attempt_step $gradedstep,
question_state $state, $maxmark, $penalty) {
$details = new qbehaviour_adaptive_mark_details($state);
$details->maxmark = $maxmark;
$details->actualmark = $gradedstep->get_fraction() * $details->maxmark;
$details->rawmark = $gradedstep->get_behaviour_var('_rawfraction') * $details->maxmark;
$details->currentpenalty = $penalty * $details->maxmark;
$details->totalpenalty = $details->currentpenalty * $this->qa->get_last_behaviour_var('_try', 0);
$details->improvable = $this->is_state_improvable($gradedstep->get_state());
return $details;
}
}
/**
* This class encapsulates all the information about the current state-of-play
* scoring-wise. It is used to communicate between the beahviour and the renderer.
*
* @copyright 2012 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qbehaviour_adaptive_mark_details {
/** @var question_state the current state of the question. */
public $state;
/** @var float the maximum mark for this question. */
public $maxmark;
/** @var float the current mark for this question. */
public $actualmark;
/** @var float the raw mark for this question before penalties were applied. */
public $rawmark;
/** @var float the the amount of additional penalty this attempt attracted. */
public $currentpenalty;
/** @var float the total that will apply to future attempts. */
public $totalpenalty;
/** @var bool whether it is possible for this mark to be improved in future. */
public $improvable;
/**
* Constructor.
* @param question_state $state
*/
public function __construct($state, $maxmark = null, $actualmark = null, $rawmark = null,
$currentpenalty = null, $totalpenalty = null, $improvable = null) {
$this->state = $state;
$this->maxmark = $maxmark;
$this->actualmark = $actualmark;
$this->rawmark = $rawmark;
$this->currentpenalty = $currentpenalty;
$this->totalpenalty = $totalpenalty;
$this->improvable = $improvable;
}
/**
* Get the marks, formatted to a certain number of decimal places, in the
* form required by calls like get_string('gradingdetails', 'qbehaviour_adaptive', $a).
* @param int $markdp the number of decimal places required.
* @return array ready to substitute into language strings.
*/
public function get_formatted_marks($markdp) {
return array(
'max' => format_float($this->maxmark, $markdp),
'cur' => format_float($this->actualmark, $markdp),
'raw' => format_float($this->rawmark, $markdp),
'penalty' => format_float($this->currentpenalty, $markdp),
'totalpenalty' => format_float($this->totalpenalty, $markdp),
);
}
}
@@ -0,0 +1,43 @@
<?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/>.
/**
* Question behaviour type for adaptive behaviour.
*
* @package qbehaviour_adaptive
* @copyright 2012 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Question behaviour type information for adaptive behaviour.
*
* @copyright 2012 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qbehaviour_adaptive_type extends question_behaviour_type {
public function is_archetypal() {
return true;
}
public function allows_multiple_submitted_responses() {
return true;
}
}
@@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qbehaviour_adaptive.
*
* @package qbehaviour_adaptive
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qbehaviour_adaptive\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qbehaviour_adaptive implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
@@ -0,0 +1,41 @@
<?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/>.
/**
* Strings for component 'qbehaviour_adaptive', language 'en'.
*
* @package qbehaviour
* @subpackage adaptive
* @copyright 2009 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['disregardedwithoutpenalty'] = 'The submission was invalid, and has been disregarded without penalty.';
$string['gradingdetails'] = 'Marks for this submission: {$a->raw}/{$a->max}.';
$string['gradingdetailswithadjustment'] = 'Marks for this submission: {$a->raw}/{$a->max}. Accounting for previous tries, this gives <strong>{$a->cur}/{$a->max}</strong>.';
$string['gradingdetailswithadjustmentpenalty'] = 'Marks for this submission: {$a->raw}/{$a->max}. Accounting for previous tries, this gives <strong>{$a->cur}/{$a->max}</strong>. This submission attracted a penalty of {$a->penalty}.';
$string['gradingdetailswithadjustmenttotalpenalty'] = 'Marks for this submission: {$a->raw}/{$a->max}. Accounting for previous tries, this gives <strong>{$a->cur}/{$a->max}</strong>. This submission attracted a penalty of {$a->penalty}. Total penalties so far: {$a->totalpenalty}.';
$string['gradingdetailswithpenalty'] = 'Marks for this submission: {$a->raw}/{$a->max}. This submission attracted a penalty of {$a->penalty}.';
$string['gradingdetailswithtotalpenalty'] = 'Marks for this submission: {$a->raw}/{$a->max}. This submission attracted a penalty of {$a->penalty}. Total penalties so far: {$a->totalpenalty}.';
$string['notcomplete'] = 'Not complete';
$string['pluginname'] = 'Adaptive mode';
$string['privacy:metadata'] = 'The Adaptive mode question behaviour plugin does not store any personal data.';
// Old strings these are currently only used in the unit tests, to verify that the new
// strings give the same results as the old strings.
$string['gradingdetailsadjustment'] = 'Accounting for previous tries, this gives <strong>{$a->cur}/{$a->max}</strong>.';
$string['gradingdetailspenalty'] = 'This submission attracted a penalty of {$a}.';
$string['gradingdetailspenaltytotal'] = 'Total penalties so far: {$a}.';
+121
View File
@@ -0,0 +1,121 @@
<?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/>.
/**
* Renderer for outputting parts of a question belonging to the legacy
* adaptive behaviour.
*
* @package qbehaviour
* @subpackage adaptive
* @copyright 2009 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Renderer for outputting parts of a question belonging to the legacy
* adaptive behaviour.
*
* @copyright 2009 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qbehaviour_adaptive_renderer extends qbehaviour_renderer {
public function controls(question_attempt $qa, question_display_options $options) {
return $this->submit_button($qa, $options);
}
public function feedback(question_attempt $qa, question_display_options $options) {
// If the latest answer was invalid, display an informative message.
if ($qa->get_state() == question_state::$invalid) {
return html_writer::nonempty_tag('div', $this->disregarded_info(),
array('class' => 'gradingdetails'));
}
// Otherwise get the details.
return $this->render_adaptive_marks(
$qa->get_behaviour()->get_adaptive_marks(), $options);
}
/**
* Display the scoring information about an adaptive attempt.
* @param qbehaviour_adaptive_mark_details contains all the score details we need.
* @param question_display_options $options display options.
*/
public function render_adaptive_marks(qbehaviour_adaptive_mark_details $details, question_display_options $options) {
if ($details->state == question_state::$todo || $options->marks < question_display_options::MARK_AND_MAX) {
// No grades yet.
return '';
}
// Display the grading details from the last graded state.
$class = $details->state->get_feedback_class();
return html_writer::tag('div', get_string($class, 'question'),
array('class' => 'correctness badge ' . $class))
. html_writer::tag('div', $this->grading_details($details, $options),
array('class' => 'gradingdetails'));
}
/**
* Display the information about the penalty calculations.
* @param qbehaviour_adaptive_mark_details contains all the score details we need.
* @param question_display_options $options display options.
* @return string html fragment
*/
protected function grading_details(qbehaviour_adaptive_mark_details $details, question_display_options $options) {
$mark = $details->get_formatted_marks($options->markdp);
if ($details->currentpenalty == 0 && $details->totalpenalty == 0) {
return get_string('gradingdetails', 'qbehaviour_adaptive', $mark);
}
$output = '';
// Print details of grade adjustment due to penalties
if ($details->rawmark != $details->actualmark) {
if (!$details->improvable) {
return get_string('gradingdetailswithadjustment', 'qbehaviour_adaptive', $mark);
} else if ($details->totalpenalty > $details->currentpenalty) {
return get_string('gradingdetailswithadjustmenttotalpenalty', 'qbehaviour_adaptive', $mark);
} else {
return get_string('gradingdetailswithadjustmentpenalty', 'qbehaviour_adaptive', $mark);
}
} else {
if (!$details->improvable) {
return get_string('gradingdetails', 'qbehaviour_adaptive', $mark);
} else if ($details->totalpenalty > $details->currentpenalty) {
return get_string('gradingdetailswithtotalpenalty', 'qbehaviour_adaptive', $mark);
} else {
return get_string('gradingdetailswithpenalty', 'qbehaviour_adaptive', $mark);
}
}
return $output;
}
/**
* Display information about a disregarded (incomplete) response.
*/
protected function disregarded_info() {
return get_string('disregardedwithoutpenalty', 'qbehaviour_adaptive');
}
}
@@ -0,0 +1,63 @@
<?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/>.
namespace qbehaviour_adaptive;
use question_engine;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once(__DIR__ . '/../../../engine/lib.php');
require_once(__DIR__ . '/../../../engine/tests/helpers.php');
/**
* Unit tests for the adaptive behaviour type class.
*
* @package qbehaviour_adaptive
* @category test
* @copyright 2015 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behaviour_type_test extends \basic_testcase {
/** @var qbehaviour_adaptive_type */
protected $behaviourtype;
public function setUp(): void {
parent::setUp();
$this->behaviourtype = question_engine::get_behaviour_type('adaptive');
}
public function test_is_archetypal(): void {
$this->assertTrue($this->behaviourtype->is_archetypal());
}
public function test_get_unused_display_options(): void {
$this->assertEquals(array(),
$this->behaviourtype->get_unused_display_options());
}
public function test_can_questions_finish_during_the_attempt(): void {
$this->assertFalse($this->behaviourtype->can_questions_finish_during_the_attempt());
}
public function test_adjust_random_guess_score(): void {
$this->assertEquals(0, $this->behaviourtype->adjust_random_guess_score(0));
$this->assertEquals(1, $this->behaviourtype->adjust_random_guess_score(1));
}
}
@@ -0,0 +1,106 @@
<?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/>.
namespace qbehaviour_adaptive;
use qbehaviour_adaptive_mark_details;
use question_display_options;
use question_state;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once(__DIR__ . '/../../../engine/lib.php');
require_once(__DIR__ . '/../behaviour.php');
/**
* Unit tests for the adaptive behaviour the display of mark/penalty information.
*
* @package qbehaviour_adaptive
* @copyright 2012 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mark_display_test extends \basic_testcase {
/** @var qbehaviour_adaptive_renderer the renderer to test. */
protected $renderer;
/** @var question_display_options display options to use when rendering. */
protected $options;
protected function setUp(): void {
global $PAGE;
parent::setUp();
$this->renderer = $PAGE->get_renderer('qbehaviour_adaptive');
$this->options = new question_display_options();
}
public function test_blank_before_graded(): void {
$this->assertEquals('',
$this->renderer->render_adaptive_marks(new qbehaviour_adaptive_mark_details(
question_state::$todo), $this->options));
}
public function test_correct_no_penalty(): void {
$this->assertEquals('<div class="correctness badge correct">' . get_string('correct', 'question') . '</div>' .
'<div class="gradingdetails">' .
get_string('gradingdetails', 'qbehaviour_adaptive',
array('cur' => '1.00', 'raw' => '1.00', 'max' => '1.00')) . '</div>',
$this->renderer->render_adaptive_marks(new qbehaviour_adaptive_mark_details(
question_state::$gradedright, 1, 1, 1, 0, 0, false), $this->options));
}
public function test_partial_first_try(): void {
$this->assertEquals('<div class="correctness badge partiallycorrect">' . get_string('partiallycorrect', 'question') .
'</div><div class="gradingdetails">' .
get_string('gradingdetails', 'qbehaviour_adaptive',
array('cur' => '0.50', 'raw' => '0.50', 'max' => '1.00')) . ' ' .
get_string('gradingdetailspenalty', 'qbehaviour_adaptive', '0.10') . '</div>',
$this->renderer->render_adaptive_marks(new qbehaviour_adaptive_mark_details(
question_state::$gradedpartial, 1, 0.5, 0.5, 0.1, 0.1, true), $this->options));
}
public function test_partial_second_try(): void {
$mark = array('cur' => '0.80', 'raw' => '0.90', 'max' => '1.00');
$this->assertEquals('<div class="correctness badge partiallycorrect">' . get_string('partiallycorrect', 'question') .
'</div><div class="gradingdetails">' .
get_string('gradingdetails', 'qbehaviour_adaptive', $mark) . ' ' .
get_string('gradingdetailsadjustment', 'qbehaviour_adaptive', $mark) . ' ' .
get_string('gradingdetailspenalty', 'qbehaviour_adaptive', '0.10') . ' ' .
get_string('gradingdetailspenaltytotal', 'qbehaviour_adaptive', '0.20') . '</div>',
$this->renderer->render_adaptive_marks(new qbehaviour_adaptive_mark_details(
question_state::$gradedpartial, 1, 0.8, 0.9, 0.1, 0.2, true), $this->options));
}
public function test_correct_third_try(): void {
$mark = array('cur' => '0.80', 'raw' => '1.00', 'max' => '1.00');
$this->assertEquals('<div class="correctness badge partiallycorrect">' . get_string('partiallycorrect', 'question') .
'</div><div class="gradingdetails">' .
get_string('gradingdetails', 'qbehaviour_adaptive', $mark) . ' ' .
get_string('gradingdetailsadjustment', 'qbehaviour_adaptive', $mark) . '</div>',
$this->renderer->render_adaptive_marks(new qbehaviour_adaptive_mark_details(
question_state::$gradedpartial, 1, 0.8, 1.0, 0.1, 0.3, false), $this->options));
}
public function test_correct_third_try_if_we_dont_increase_penalties_for_wrong(): void {
$mark = array('cur' => '0.80', 'raw' => '1.00', 'max' => '1.00');
$this->assertEquals('<div class="correctness badge partiallycorrect">' . get_string('partiallycorrect', 'question') .
'</div><div class="gradingdetails">' .
get_string('gradingdetails', 'qbehaviour_adaptive', $mark) . ' ' .
get_string('gradingdetailsadjustment', 'qbehaviour_adaptive', $mark) . '</div>',
$this->renderer->render_adaptive_marks(new qbehaviour_adaptive_mark_details(
question_state::$gradedpartial, 1, 0.8, 1.0, 0, 0.2, false), $this->options));
}
}
@@ -0,0 +1,852 @@
<?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/>.
namespace qbehaviour_adaptive;
use question_state;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once(__DIR__ . '/../../../engine/lib.php');
require_once(__DIR__ . '/../../../engine/tests/helpers.php');
/**
* Unit tests for the adaptive behaviour.
*
* @package qbehaviour_adaptive
* @copyright 2009 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class walkthrough_test extends \qbehaviour_walkthrough_test_base {
protected function get_contains_penalty_info_expectation($penalty) {
$penaltyinfo = get_string('gradingdetailspenalty', 'qbehaviour_adaptive',
format_float($penalty, $this->displayoptions->markdp));
return new \question_pattern_expectation('/'.preg_quote($penaltyinfo, '/').'/');
}
protected function get_does_not_contain_penalty_info_expectation() {
$penaltyinfo = get_string('gradingdetailspenalty', 'qbehaviour_adaptive', 'XXXXX');
$penaltypattern = '/'.str_replace('XXXXX', '\\w*', preg_quote($penaltyinfo, '/')).'/';
return new \question_no_pattern_expectation($penaltypattern);
}
protected function get_contains_total_penalty_expectation($penalty) {
$penaltyinfo = get_string('gradingdetailspenaltytotal', 'qbehaviour_adaptive',
format_float($penalty, $this->displayoptions->markdp));
return new \question_pattern_expectation('/'.preg_quote($penaltyinfo, '/').'/');
}
protected function get_does_not_contain_total_penalty_expectation() {
$penaltyinfo = get_string('gradingdetailspenaltytotal', 'qbehaviour_adaptive', 'XXXXX');
$penaltypattern = '/'.str_replace('XXXXX', '\\w*', preg_quote($penaltyinfo, '/')).'/';
return new \question_no_pattern_expectation($penaltypattern);
}
protected function get_contains_disregarded_info_expectation() {
$penaltyinfo = get_string('disregardedwithoutpenalty', 'qbehaviour_adaptive');
return new \question_pattern_expectation('/'.preg_quote($penaltyinfo, '/').'/');
}
protected function get_does_not_contain_disregarded_info_expectation() {
$penaltyinfo = get_string('disregardedwithoutpenalty', 'qbehaviour_adaptive');
return new \question_no_pattern_expectation('/'.preg_quote($penaltyinfo, '/').'/');
}
public function test_adaptive_multichoice(): void {
// Create a multiple choice, single response question.
$mc = \test_question_maker::make_a_multichoice_single_question();
$mc->penalty = 0.3333333;
$this->start_attempt_at_question($mc, 'adaptive', 3);
$rightindex = $this->get_mc_right_answer_index($mc);
$wrongindex = ($rightindex + 1) % 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_question_text_expectation($mc),
$this->get_contains_mc_radio_expectation(0, true, false),
$this->get_contains_mc_radio_expectation(1, true, false),
$this->get_contains_mc_radio_expectation(2, true, false),
$this->get_contains_submit_button_expectation(true),
$this->get_does_not_contain_feedback_expectation());
// Process a submit.
$this->process_submission(array('answer' => $wrongindex, '-submit' => 1));
// Verify.
$this->check_current_state(question_state::$todo);
$this->check_current_mark(0);
$this->check_current_output(
$this->get_contains_mark_summary(0),
$this->get_contains_mc_radio_expectation($wrongindex, true, true),
$this->get_contains_mc_radio_expectation(($wrongindex + 1) % 3, true, false),
$this->get_contains_mc_radio_expectation(($wrongindex + 2) % 3, true, false),
$this->get_contains_incorrect_expectation(),
$this->get_contains_penalty_info_expectation(1.00),
$this->get_does_not_contain_total_penalty_expectation());
$this->assertMatchesRegularExpression('/B|C/',
$this->quba->get_response_summary($this->slot));
// Process a change of answer to the right one, but not sumbitted.
$this->process_submission(array('answer' => $rightindex));
// Verify.
$this->check_current_state(question_state::$todo);
$this->check_current_mark(0);
$this->check_current_output(
$this->get_contains_mark_summary(0),
$this->get_contains_mc_radio_expectation($rightindex, true, true),
$this->get_contains_mc_radio_expectation(($rightindex + 1) % 3, true, false),
$this->get_contains_mc_radio_expectation(($rightindex + 2) % 3, true, false));
$this->assertMatchesRegularExpression('/B|C/',
$this->quba->get_response_summary($this->slot));
// Now submit the right answer.
$this->process_submission(array('answer' => $rightindex, '-submit' => 1));
// Verify.
$this->check_current_state(question_state::$complete);
$this->check_current_mark(3 * (1 - $mc->penalty));
$this->check_current_output(
$this->get_contains_mark_summary(3 * (1 - $mc->penalty)),
$this->get_contains_mc_radio_expectation($rightindex, true, true),
$this->get_contains_mc_radio_expectation(($rightindex + 1) % 3, true, false),
$this->get_contains_mc_radio_expectation(($rightindex + 2) % 3, true, false),
$this->get_contains_correct_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_total_penalty_expectation());
$this->assertEquals('A',
$this->quba->get_response_summary($this->slot));
// Finish the attempt.
$this->quba->finish_all_questions();
// Verify.
$this->check_current_state(question_state::$gradedright);
$this->check_current_mark(3 * (1 - $mc->penalty));
$this->check_current_output(
$this->get_contains_mark_summary(3 * (1 - $mc->penalty)),
$this->get_contains_mc_radio_expectation($rightindex, false, true),
$this->get_contains_mc_radio_expectation(($rightindex + 1) % 3, false, false),
$this->get_contains_mc_radio_expectation(($rightindex + 2) % 3, false, false),
$this->get_contains_correct_expectation());
// Process a manual comment.
$this->manual_grade('Not good enough!', 1, FORMAT_HTML);
// Verify.
$this->check_current_state(question_state::$mangrpartial);
$this->check_current_mark(1);
$this->check_current_output(
$this->get_contains_mark_summary(1),
new \question_pattern_expectation('/' . preg_quote('Not good enough!', '/') . '/'));
// Now change the correct answer to the question, and regrade.
$mc->answers[13]->fraction = -0.33333333;
$mc->answers[14]->fraction = 1; // We don't know which "wrong" index we chose above!
$mc->answers[15]->fraction = 1; // Therefore, treat answers B and C with the same score.
$this->quba->regrade_all_questions();
// Verify.
$this->check_current_state(question_state::$mangrpartial);
$this->check_current_mark(1);
$this->check_current_output(
$this->get_contains_mark_summary(1),
$this->get_contains_partcorrect_expectation());
$autogradedstep = $this->get_step($this->get_step_count() - 2);
$this->assertEqualsWithDelta($autogradedstep->get_fraction(), 1, 0.0000001);
}
public function test_adaptive_multichoice2(): void {
// Create a multiple choice, multiple response question.
$mc = \test_question_maker::make_a_multichoice_multi_question();
$mc->penalty = 0.3333333;
$mc->shuffleanswers = 0;
$this->start_attempt_at_question($mc, 'adaptive', 2);
// 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_question_text_expectation($mc),
$this->get_contains_submit_button_expectation(true),
$this->get_does_not_contain_feedback_expectation());
// Process a submit.
$this->process_submission(array('choice0' => 1, 'choice2' => 1, '-submit' => 1));
// Verify.
$this->check_current_state(question_state::$complete);
$this->check_current_mark(2);
$this->check_current_output(
$this->get_contains_mark_summary(2),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_correct_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_total_penalty_expectation());
// Save the same correct answer again. Should not do anything.
$numsteps = $this->get_step_count();
$this->process_submission(array('choice0' => 1, 'choice2' => 1));
// Verify.
$this->check_step_count($numsteps);
$this->check_current_mark(2);
$this->check_current_state(question_state::$complete);
// Finish the attempt.
$this->quba->finish_all_questions();
// Verify.
$this->check_step_count($numsteps + 1);
$this->check_current_state(question_state::$gradedright);
$this->check_current_mark(2);
$this->check_current_output(
$this->get_contains_mark_summary(2),
$this->get_does_not_contain_submit_button_expectation(),
$this->get_contains_correct_expectation());
}
public function test_adaptive_shortanswer_partially_right(): void {
// Create a short answer question.
$sa = \test_question_maker::make_question('shortanswer');
$this->start_attempt_at_question($sa, 'adaptive');
// Check the initial state.
$this->check_current_state(question_state::$todo);
$this->check_current_mark(null);
$this->render();
$this->check_output_does_not_contain_text_input_with_class('answer', 'correct');
$this->check_output_does_not_contain_text_input_with_class('answer', 'partiallycorrect');
$this->check_output_does_not_contain_text_input_with_class('answer', 'incorrect');
$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());
// Submit a partially correct answer.
$this->process_submission(array('-submit' => 1, 'answer' => 'toad'));
// Verify.
$this->check_current_state(question_state::$todo);
$this->check_current_mark(0.8);
$this->render();
$this->check_output_contains_text_input_with_class('answer', 'partiallycorrect');
$this->check_current_output(
$this->get_contains_mark_summary(0.8),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_partcorrect_expectation(),
$this->get_contains_penalty_info_expectation(0.33),
$this->get_does_not_contain_total_penalty_expectation(),
$this->get_does_not_contain_validation_error_expectation());
// Submit an incorrect answer.
$this->process_submission(array('-submit' => 1, 'answer' => 'bumblebee'));
// Verify.
$this->check_current_state(question_state::$todo);
$this->check_current_mark(0.8);
$this->render();
$this->check_output_contains_text_input_with_class('answer', 'incorrect');
$this->check_current_output(
$this->get_contains_mark_summary(0.8),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_incorrect_expectation(),
$this->get_contains_penalty_info_expectation(0.33),
$this->get_contains_total_penalty_expectation(0.67),
$this->get_does_not_contain_validation_error_expectation());
// Submit a correct answer.
$this->process_submission(array('-submit' => 1, 'answer' => 'frog'));
// Verify.
$this->check_current_state(question_state::$complete);
$this->check_current_mark(0.8);
$this->render();
$this->check_output_contains_text_input_with_class('answer', 'correct');
$this->check_current_output(
$this->get_contains_mark_summary(0.8),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_correct_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_total_penalty_expectation(),
$this->get_does_not_contain_validation_error_expectation());
// Finish the attempt.
$this->quba->finish_all_questions();
// Verify.
$this->check_current_state(question_state::$gradedright);
$this->check_current_mark(0.8);
$this->check_current_output(
$this->get_contains_mark_summary(0.8),
$this->get_does_not_contain_submit_button_expectation(),
$this->get_contains_correct_expectation(),
$this->get_does_not_contain_validation_error_expectation());
}
public function test_adaptive_shortanswer_wrong_right_wrong(): void {
// Create a short answer question.
$sa = \test_question_maker::make_question('shortanswer');
$this->start_attempt_at_question($sa, 'adaptive', 6);
// 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());
// Submit a wrong answer.
$this->process_submission(array('-submit' => 1, 'answer' => 'hippopotamus'));
// Verify.
$this->check_current_state(question_state::$todo);
$this->check_current_mark(0);
$this->check_current_output(
$this->get_contains_mark_summary(0),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_incorrect_expectation(),
$this->get_contains_penalty_info_expectation(2.00),
$this->get_does_not_contain_total_penalty_expectation(),
$this->get_does_not_contain_validation_error_expectation());
// Submit the same wrong answer again. Nothing should change.
$this->process_submission(array('-submit' => 1, 'answer' => 'hippopotamus'));
// Verify.
$this->check_current_state(question_state::$todo);
$this->check_current_mark(0);
$this->check_current_output(
$this->get_contains_mark_summary(0),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_incorrect_expectation(),
$this->get_contains_penalty_info_expectation(2.00),
$this->get_does_not_contain_total_penalty_expectation(),
$this->get_does_not_contain_validation_error_expectation());
// Submit a correct answer.
$this->process_submission(array('-submit' => 1, 'answer' => 'frog'));
// Verify.
$this->check_current_state(question_state::$complete);
$this->check_current_mark(4.00);
$this->check_current_output(
$this->get_contains_mark_summary(4.00),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_correct_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_total_penalty_expectation(),
$this->get_does_not_contain_validation_error_expectation());
// Submit another incorrect answer.
$this->process_submission(array('-submit' => 1, 'answer' => 'bumblebee'));
// Verify.
$this->check_current_state(question_state::$complete);
$this->check_current_mark(4.00);
$this->check_current_output(
$this->get_contains_mark_summary(4.00),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_incorrect_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_total_penalty_expectation(),
$this->get_does_not_contain_validation_error_expectation());
// Finish the attempt.
$this->quba->finish_all_questions();
// Verify.
$this->check_current_state(question_state::$gradedwrong);
$this->check_current_mark(4.00);
$this->check_current_output(
$this->get_contains_mark_summary(4.00),
$this->get_does_not_contain_submit_button_expectation(),
$this->get_contains_incorrect_expectation(),
$this->get_does_not_contain_validation_error_expectation());
}
public function test_adaptive_shortanswer_invalid_after_complete(): void {
// Create a short answer question.
$sa = \test_question_maker::make_question('shortanswer');
$this->start_attempt_at_question($sa, 'adaptive');
// 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());
// Submit a wrong answer.
$this->process_submission(array('-submit' => 1, 'answer' => 'hippopotamus'));
// Verify.
$this->check_current_state(question_state::$todo);
$this->check_current_mark(0);
$this->check_current_output(
$this->get_contains_mark_summary(0),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_incorrect_expectation(),
$this->get_contains_penalty_info_expectation(0.33),
$this->get_does_not_contain_total_penalty_expectation(),
$this->get_does_not_contain_validation_error_expectation());
// Submit a correct answer.
$this->process_submission(array('-submit' => 1, 'answer' => 'frog'));
// Verify.
$this->check_current_state(question_state::$complete);
$this->check_current_mark(0.66666667);
$this->check_current_output(
$this->get_contains_mark_summary(0.67),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_correct_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_total_penalty_expectation(),
$this->get_does_not_contain_validation_error_expectation());
// Submit an empty answer.
$this->process_submission(array('-submit' => 1, 'answer' => ''));
// Verify.
$this->check_current_state(question_state::$invalid);
$this->check_current_mark(0.66666667);
$this->check_current_output(
$this->get_contains_mark_summary(0.67),
$this->get_contains_submit_button_expectation(true),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_total_penalty_expectation(),
$this->get_contains_validation_error_expectation());
// Submit another wrong answer.
$this->process_submission(array('-submit' => 1, 'answer' => 'bumblebee'));
// Verify.
$this->check_current_state(question_state::$complete);
$this->check_current_mark(0.66666667);
$this->check_current_output(
$this->get_contains_mark_summary(0.67),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_incorrect_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_total_penalty_expectation(),
$this->get_does_not_contain_validation_error_expectation());
// Finish the attempt.
$this->quba->finish_all_questions();
// Verify.
$this->check_current_state(question_state::$gradedwrong);
$this->check_current_mark(0.66666667);
$this->check_current_output(
$this->get_contains_mark_summary(0.67),
$this->get_does_not_contain_submit_button_expectation(),
$this->get_contains_incorrect_expectation(),
$this->get_does_not_contain_validation_error_expectation());
}
public function test_adaptive_shortanswer_zero_penalty(): void {
// Create a short answer question.
$sa = \test_question_maker::make_question('shortanswer');
// Disable penalties for this question.
$sa->penalty = 0;
$this->start_attempt_at_question($sa, 'adaptive');
// 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());
// Submit a wrong answer.
$this->process_submission(array('-submit' => 1, 'answer' => 'hippopotamus'));
// Verify.
$this->check_current_state(question_state::$todo);
$this->check_current_mark(0);
$this->check_current_output(
$this->get_contains_mark_summary(0),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_incorrect_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_total_penalty_expectation(),
$this->get_does_not_contain_validation_error_expectation());
// Submit a correct answer.
$this->process_submission(array('-submit' => 1, 'answer' => 'frog'));
// Verify.
$this->check_current_state(question_state::$complete);
$this->check_current_mark(1.0);
$this->check_current_output(
$this->get_contains_mark_summary(1.0),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_correct_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_total_penalty_expectation(),
$this->get_does_not_contain_validation_error_expectation());
// Finish the attempt.
$this->quba->finish_all_questions();
// Verify.
$this->check_current_state(question_state::$gradedright);
$this->check_current_mark(1.0);
$this->check_current_output(
$this->get_contains_mark_summary(1.0),
$this->get_does_not_contain_submit_button_expectation(),
$this->get_contains_correct_expectation(),
$this->get_does_not_contain_validation_error_expectation());
}
public function test_adaptive_shortanswer_try_to_submit_blank(): void {
// Create a short answer question with correct answer true.
$sa = \test_question_maker::make_question('shortanswer');
$this->start_attempt_at_question($sa, 'adaptive');
// 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());
// Submit with blank answer.
$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_correctness_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_total_penalty_expectation(),
$this->get_contains_validation_error_expectation(),
$this->get_contains_disregarded_info_expectation());
$this->assertNull($this->quba->get_response_summary($this->slot));
// Now get it wrong.
$this->process_submission(array('-submit' => 1, 'answer' => 'toad'));
// Verify.
$this->check_current_state(question_state::$todo);
$this->check_current_mark(0.8);
$this->check_current_output(
$this->get_contains_mark_summary(0.8),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_partcorrect_expectation(),
$this->get_contains_penalty_info_expectation(0.33),
$this->get_does_not_contain_total_penalty_expectation(),
$this->get_does_not_contain_validation_error_expectation());
// Now submit blank again.
$this->process_submission(array('-submit' => 1, 'answer' => ''));
// Verify.
$this->check_current_state(question_state::$invalid);
$this->check_current_mark(0.8);
$this->check_current_output(
$this->get_contains_mark_summary(0.8),
$this->get_contains_submit_button_expectation(true),
$this->get_does_not_contain_correctness_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_total_penalty_expectation(),
$this->get_contains_validation_error_expectation());
}
public function test_adaptive_numerical(): void {
// Create a numerical question.
$sa = \test_question_maker::make_question('numerical', 'pi');
$this->start_attempt_at_question($sa, 'adaptive');
// 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());
// Submit the correct answer.
$this->process_submission(array('-submit' => 1, 'answer' => '3.14'));
// Verify.
$this->check_current_state(question_state::$complete);
$this->check_current_mark(1);
$this->check_current_output(
$this->get_contains_mark_summary(1),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_correct_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_total_penalty_expectation(),
$this->get_does_not_contain_validation_error_expectation());
// Submit an incorrect answer.
$this->process_submission(array('-submit' => 1, 'answer' => '-5'));
// Verify.
$this->check_current_state(question_state::$complete);
$this->check_current_mark(1);
$this->check_current_output(
$this->get_contains_mark_summary(1),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_incorrect_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_total_penalty_expectation(),
$this->get_does_not_contain_validation_error_expectation());
// Finish the attempt.
$this->quba->finish_all_questions();
// Verify.
$this->check_current_state(question_state::$gradedwrong);
$this->check_current_mark(1);
$this->check_current_output(
$this->get_contains_mark_summary(1),
$this->get_does_not_contain_submit_button_expectation(),
$this->get_contains_incorrect_expectation(),
$this->get_does_not_contain_validation_error_expectation());
}
public function test_adaptive_numerical_invalid(): void {
// Create a numerical question.
$numq = \test_question_maker::make_question('numerical', 'pi');
$numq->penalty = 0.1;
$this->start_attempt_at_question($numq, 'adaptive');
// 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());
// Submit a non-numerical answer.
$this->process_submission(array('-submit' => 1, 'answer' => 'Pi'));
// Verify.
$this->check_current_state(question_state::$invalid);
$this->check_current_mark(null);
$this->check_current_output(
$this->get_contains_marked_out_of_summary(1),
$this->get_contains_submit_button_expectation(true),
$this->get_does_not_contain_correctness_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_total_penalty_expectation(),
$this->get_contains_validation_error_expectation(),
$this->get_contains_disregarded_info_expectation());
// Submit an incorrect answer.
$this->process_submission(array('-submit' => 1, 'answer' => '-5'));
// Verify.
$this->check_current_state(question_state::$todo);
$this->check_current_mark(0);
$this->check_current_output(
$this->get_contains_mark_summary(0),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_incorrect_expectation(),
$this->get_contains_penalty_info_expectation(0.1),
$this->get_does_not_contain_total_penalty_expectation(),
$this->get_does_not_contain_validation_error_expectation(),
$this->get_does_not_contain_disregarded_info_expectation());
// Submit another non-numerical answer.
$this->process_submission(array('-submit' => 1, 'answer' => 'Pi*2'));
// Verify.
$this->check_current_state(question_state::$invalid);
$this->check_current_mark(0);
$this->check_current_output(
$this->get_contains_mark_summary(0),
$this->get_contains_submit_button_expectation(true),
$this->get_does_not_contain_correctness_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_total_penalty_expectation(),
$this->get_contains_validation_error_expectation(),
$this->get_contains_disregarded_info_expectation());
// Submit the correct answer.
$this->process_submission(array('-submit' => 1, 'answer' => '3.14'));
// Verify.
$this->check_current_state(question_state::$complete);
$this->check_current_mark(0.9);
$this->check_current_output(
$this->get_contains_mark_summary(0.9),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_correct_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_total_penalty_expectation(),
$this->get_does_not_contain_validation_error_expectation(),
$this->get_does_not_contain_disregarded_info_expectation());
// Submit another non-numerical answer.
$this->process_submission(array('-submit' => 1, 'answer' => 'Pi/3'));
// Verify.
$this->check_current_state(question_state::$invalid);
$this->check_current_mark(0.9);
$this->check_current_output(
$this->get_contains_mark_summary(0.9),
$this->get_contains_submit_button_expectation(true),
$this->get_does_not_contain_correctness_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_total_penalty_expectation(),
$this->get_contains_validation_error_expectation(),
$this->get_contains_disregarded_info_expectation());
// Finish the attempt.
$this->quba->finish_all_questions();
// Verify.
$this->check_current_state(question_state::$gradedwrong);
$this->check_current_mark(0.9);
$this->check_current_output(
$this->get_contains_mark_summary(0.9),
$this->get_does_not_contain_submit_button_expectation(),
$this->get_contains_incorrect_expectation(),
$this->get_does_not_contain_validation_error_expectation(),
$this->get_does_not_contain_disregarded_info_expectation());
}
public function test_adaptive_multianswer(): void {
// Create a multianswer question.
$q = \test_question_maker::make_question('multianswer', 'twosubq');
// To simplify testing, multichoice subquestion's answers are not shuffled.
$q->subquestions[2]->shuffleanswers = 0;
$choices = array('0' => 'Bow-wow', '1' => 'Wiggly worm', '2' => 'Pussy-cat');
$this->start_attempt_at_question($q, 'adaptive', 12);
// Check the initial state.
$this->check_current_state(question_state::$todo);
$this->check_current_mark(null);
$this->assertEquals('adaptive',
$this->quba->get_question_attempt($this->slot)->get_behaviour_name());
$this->render();
$this->check_output_contains_text_input('sub1_answer', '', true);
$this->check_output_does_not_contain_text_input_with_class('sub1_answer', 'correct');
$this->check_output_does_not_contain_text_input_with_class('sub1_answer', 'partiallycorrect');
$this->check_output_does_not_contain_text_input_with_class('sub1_answer', 'incorrect');
$this->check_current_output(
$this->get_contains_marked_out_of_summary(),
$this->get_contains_submit_button_expectation(true),
$this->get_does_not_contain_validation_error_expectation(),
$this->get_does_not_contain_feedback_expectation());
$this->check_output_contains_selectoptions(
$this->get_contains_select_expectation('sub2_answer', $choices, null, true));
// Submit an invalid response.
$this->process_submission(array('sub1_answer' => '', 'sub2_answer' => 1, '-submit' => 1));
// Verify.
$this->check_current_state(question_state::$invalid);
$this->check_current_mark(null);
$this->check_output_contains_text_input('sub1_answer', '', true);
$this->check_current_output(
$this->get_contains_submit_button_expectation(true),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_total_penalty_expectation(),
$this->get_contains_disregarded_info_expectation());
$this->check_output_contains_selectoptions(
$this->get_contains_select_expectation('sub2_answer', $choices, 1, true));
// Check that extract responses will return the reset data.
$prefix = $this->quba->get_field_prefix($this->slot);
$this->assertEquals(array('sub2_answer' => 1),
$this->quba->extract_responses($this->slot, array($prefix . 'sub2_answer' => 1)));
// Submit an incorrect response.
$this->process_submission(array('sub1_answer' => 'Dog',
'sub2_answer' => 1, '-submit' => 1));
// Verify.
$this->check_current_state(question_state::$todo);
$this->check_current_mark(0);
$this->render();
$this->check_output_contains_text_input('sub1_answer', 'Dog', true);
$this->check_output_contains_text_input_with_class('sub1_answer', 'incorrect');
$this->check_current_output(
$this->get_contains_mark_summary(0),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_incorrect_expectation(),
$this->get_contains_penalty_info_expectation(4.00),
$this->get_does_not_contain_validation_error_expectation());
$this->check_output_contains_selectoptions(
$this->get_contains_select_expectation('sub2_answer', $choices, 1, true));
// Submit the right answer.
$this->process_submission(array('sub1_answer' => 'Owl', 'sub2_answer' => 2, '-submit' => 1));
// Verify.
$this->check_current_state(question_state::$complete);
$this->check_current_mark(8.00);
$this->render();
$this->check_output_contains_text_input('sub1_answer', 'Owl', true);
$this->check_output_contains_text_input_with_class('sub1_answer', 'correct');
$this->check_current_output(
$this->get_contains_mark_summary(8.00),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_correct_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_total_penalty_expectation(),
$this->get_does_not_contain_validation_error_expectation());
$this->check_output_contains_selectoptions(
$this->get_contains_select_expectation('sub2_answer', $choices, '2', true));
// Finish the attempt.
$this->quba->finish_all_questions();
// Verify.
$this->check_current_state(question_state::$gradedright);
$this->check_current_mark(8.00);
$this->render();
$this->check_output_contains_text_input('sub1_answer', 'Owl', false);
$this->check_output_contains_text_input_with_class('sub1_answer', 'correct');
$this->check_current_output(
$this->get_contains_mark_summary(8.00),
$this->get_does_not_contain_submit_button_expectation(),
$this->get_contains_correct_expectation(),
$this->get_does_not_contain_validation_error_expectation());
}
}
+33
View File
@@ -0,0 +1,33 @@
<?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/>.
/**
* Version information for the calculated question type.
*
* @package qbehaviour
* @subpackage adaptive
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'qbehaviour_adaptive';
$plugin->version = 2024042200;
$plugin->requires = 2024041600;
$plugin->maturity = MATURITY_STABLE;