Commit 7f723723 authored by 0815-xyz's avatar 0815-xyz
Browse files

Initial commit: Transfer from MOODLE_404_KNIGHT

parents
<?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 mod_adaptivequiz\local;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot.'/mod/adaptivequiz/locallib.php');
use advanced_testcase;
use context_module;
use mod_adaptivequiz\local\attempt\attempt_state;
use stdClass;
/**
* Tests for the attempt class.
*
* @package mod_adaptivequiz
* @copyright 2013 Remote-Learner {@link http://www.remote-learner.ca/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @covers \mod_adaptivequiz\local\attempt
*/
class attempt_test extends advanced_testcase {
/** @var stdClass $activityinstance adaptivequiz activity instance object */
protected $activityinstance = null;
/** @var stdClass $cm a partially completed course module object */
protected $cm = null;
/** @var stdClass $user a user object */
protected $user = null;
/**
* This function loads data into the PHPUnit tables for testing
*/
protected function setup_test_data_xml() {
$this->dataset_from_files(
[__DIR__.'/../fixtures/mod_adaptivequiz_adaptiveattempt.xml']
)->to_database();
}
/**
* This function creates a default user and activity instance using generator classes
* The activity parameters created are are follows:
* lowest difficulty level: 1
* highest difficulty level: 10
* minimum question attempts: 2
* maximum question attempts: 10
* standard error: 1.1
* starting level: 5
* question category ids: 1
* course id: 2
* @return void
*/
protected function setup_generator_data() {
// Create test user.
$this->user = $this->getDataGenerator()->create_user();
$this->setUser($this->user);
$this->setAdminUser();
// Create activity.
$generator = $this->getDataGenerator()->get_plugin_generator('mod_adaptivequiz');
$options = array(
'highestlevel' => 10,
'lowestlevel' => 1,
'minimumquestions' => 2,
'maximumquestions' => 10,
'standarderror' => 1.1,
'startinglevel' => 5,
'questionpool' => array(1),
'course' => 1
);
$this->activityinstance = $generator->create_instance($options);
$this->cm = new stdClass();
$this->cm->id = $this->activityinstance->cmid;
}
/**
* This function tests retrieving an adaptivequiz attempt record
*/
public function test_get_attempt() {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$dummy = new stdClass();
$dummy->id = 220;
$userid = 2;
$attempt = new attempt($dummy, $userid);
$data = $attempt->get_attempt();
$expected = new stdClass();
$expected->id = '1';
$expected->instance = '220';
$expected->userid = '2';
$expected->uniqueid = '1110';
$expected->attemptstate = attempt_state::IN_PROGRESS;
$expected->questionsattempted = '0';
$expected->attemptstopcriteria = '';
$expected->standarderror = '1.00000';
$expected->difficultysum = '0.0000000';
$expected->measure = '0.00000';
$expected->timemodified = '0';
$expected->timecreated = '0';
$this->assertEquals($expected, $data);
}
/*
* @test
*/
public function it_fails_to_set_quba_for_an_attempt_with_an_invalid_argument(): void {
$this->resetAfterTest(true);
$this->setup_generator_data();
$this->activityinstance->context = context_module::instance($this->cm->id);
$adaptiveattempt = new attempt($this->activityinstance, $this->user->id);
$this->expectException('coding_exception');
$adaptiveattempt->set_quba(new stdClass());
}
/**
* This function tests the accessor methods for question_usage_by_activity ($quba) property
*/
public function test_set_get_quba() {
$this->resetAfterTest(true);
$this->setup_generator_data();
$this->activityinstance->context = context_module::instance($this->cm->id);
$adaptiveattempt = new attempt($this->activityinstance, $this->user->id);
// Test a non initialized quba.
$this->assertNull($adaptiveattempt->get_quba());
// Test a property initializes quba.
$mockquba = $this->getMockBuilder('question_usage_by_activity')
->disableOriginalConstructor()
->getMock();
$adaptiveattempt->set_quba($mockquba);
$this->assertInstanceOf('question_usage_by_activity', $adaptiveattempt->get_quba());
}
/**
* This function tests whether the user's attempt has exceeded the maximum number of questions attempted
*/
public function test_max_questions_answered() {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$dummy = new stdClass();
$dummy->id = 220;
$dummy->maximumquestions = 20;
$userid = 2;
$attempt = new attempt($dummy, $userid);
$attempt->get_attempt();
$this->assertEquals(false, $attempt->max_questions_answered());
$dummy->id = 221;
$attempt = new attempt($dummy, $userid);
$attempt->get_attempt();
$this->assertEquals(false, $attempt->max_questions_answered());
$dummy->id = 222;
$attempt = new attempt($dummy, $userid);
$attempt->get_attempt();
$this->assertEquals(true, $attempt->max_questions_answered());
$dummy->id = 223;
$attempt = new attempt($dummy, $userid);
$attempt->get_attempt();
$this->assertEquals(true, $attempt->max_questions_answered());
}
/**
* This function tests whether the user's attempt has met the minimum number of questions attempted
*/
public function test_min_questions_answered() {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$dummy = new stdClass();
$dummy->id = 220;
$dummy->minimumquestions = 21;
$userid = 2;
$attempt = new attempt($dummy, $userid);
$attempt->get_attempt();
$this->assertEquals(false, $attempt->min_questions_answered());
$dummy->id = 221;
$attempt = new attempt($dummy, $userid);
$attempt->get_attempt();
$this->assertEquals(false, $attempt->min_questions_answered());
$dummy->id = 222;
$attempt = new attempt($dummy, $userid);
$attempt->get_attempt();
$this->assertEquals(false, $attempt->min_questions_answered());
$dummy->id = 223;
$attempt = new attempt($dummy, $userid);
$attempt->get_attempt();
$this->assertEquals(true, $attempt->min_questions_answered());
}
/**
* This function test that one value is returned or an empty array is returned
*/
public function test_return_random_question() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$dummy->id = 220;
$dummy->minimumquestions = 21;
$userid = 2;
$adaptiveattempt = new attempt($dummy, $userid);
$result = $adaptiveattempt->return_random_question(array());
$this->assertEquals(0, $result);
$result = (string) $adaptiveattempt->return_random_question(
[1 => 'quest 1', 2 => 'quest 2', 3 => 'quest 3', 4 => 'quest 4']
);
$this->assertMatchesRegularExpression('/[1-4]/', $result);
}
/**
* This function tests the creation of a question_usage_by_activity object for an attempt
*/
public function test_initialize_quba() {
$this->resetAfterTest(true);
$this->setup_generator_data();
$this->activityinstance->context = context_module::instance($this->cm->id);
$adaptiveattempt = new attempt($this->activityinstance, $this->user->id);
$adaptiveattempt->get_attempt();
$quba = $adaptiveattempt->initialize_quba();
$this->assertInstanceOf('question_usage_by_activity', $quba);
}
/**
* This function tests the re-using of the question_usage_by_activity object, due to an incomplete attempt
*/
public function test_initialize_quba_with_existing_attempt_uniqueid() {
global $DB;
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$param = array('id' => 330);
$activityinstance = $DB->get_record('adaptivequiz', $param);
$adaptiveattempt = new attempt($activityinstance, 2);
$adaptiveattempt->get_attempt();
$quba = $adaptiveattempt->initialize_quba();
$this->assertInstanceOf('question_usage_by_activity', $quba);
// Check if the uniqueid of the adaptive attempt equals the id of the object loaded by quba class.
$this->assertEquals(330, $quba->get_id());
}
/**
* This function tests retrieving the last question viewed by the student for a given attempt
*/
public function test_find_last_quest_used_by_attempt() {
global $DB;
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$param = array('id' => 330);
$activityinstance = $DB->get_record('adaptivequiz', $param);
$adaptiveattempt = new attempt($activityinstance, 2);
$adaptiveattempt->get_attempt();
$quba = $adaptiveattempt->initialize_quba();
$result = $adaptiveattempt->find_last_quest_used_by_attempt($quba);
$this->assertEquals(2, $result);
}
/**
* @test
*/
public function it_fails_to_find_the_last_question_used_by_attempt_with_an_invalid_argument() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$adaptiveattempt = new attempt($dummy, 2);
$this->expectException('coding_exception');
$adaptiveattempt->find_last_quest_used_by_attempt($dummy);
}
/**
* This function tests retrieving the last question viewed by the student for a given attempt, but using failing data
*/
public function test_find_last_umarked_question_using_bad_data() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$adaptiveattempt = $this->getMockBuilder(attempt::class)
->disableOriginalConstructor()
->getMock();
// Test passing an invalid object instance.
$result = $adaptiveattempt->find_last_quest_used_by_attempt($dummy);
$this->assertEquals(0, $result);
// Test getting an empty slot value.
$mockquba = $this->getMockBuilder('question_usage_by_activity')
->disableOriginalConstructor()
->getMock();
$result = $adaptiveattempt->find_last_quest_used_by_attempt($mockquba);
$this->assertEquals(0, $result);
}
/**
* This function tests whether the user submitted an answer to the question
*/
public function test_was_answer_submitted_to_question_with_graded_right() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$adaptiveattempt = new attempt($dummy, 1);
$mockquba = $this->getMockBuilder('question_usage_by_activity')
->disableOriginalConstructor()
->getMock();
$mockstate = $this->createMock('question_state_gradedright', array(), array(), '', false);
$mockquba->expects($this->once())
->method('get_question_state')
->with(1)
->will($this->returnValue($mockstate));
$mockquba->expects($this->never())
->method('get_id');
$result = $adaptiveattempt->was_answer_submitted_to_question($mockquba, 1);
$this->assertTrue($result);
}
/**
* This function tests whether the user submitted an answer to the question
*/
public function test_was_answer_submitted_to_question_with_graded_wrong() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$adaptiveattempt = new attempt($dummy, 1);
$mockquba = $this->getMockBuilder('question_usage_by_activity')
->disableOriginalConstructor()
->getMock();
$mockstate = $this->createMock('question_state_gradedwrong', array(), array(), '', false);
$mockquba->expects($this->once())
->method('get_question_state')
->with(1)
->will($this->returnValue($mockstate));
$mockquba->expects($this->never())
->method('get_id');
$result = $adaptiveattempt->was_answer_submitted_to_question($mockquba, 1);
$this->assertTrue($result);
}
/**
* This function tests whether the user submitted an answer to the question
*/
public function test_was_answer_submitted_to_question_with_graded_partial() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$adaptiveattempt = new attempt($dummy, 1);
$mockquba = $this->getMockBuilder('question_usage_by_activity')
->disableOriginalConstructor()
->getMock();
$mockstate = $this->createMock('question_state_gradedpartial', array(), array(), '', false);
$mockquba->expects($this->once())
->method('get_question_state')
->with(1)
->will($this->returnValue($mockstate));
$mockquba->expects($this->never())
->method('get_id');
$result = $adaptiveattempt->was_answer_submitted_to_question($mockquba, 1);
$this->assertTrue($result);
}
/**
* This function tests whether the user submitted an answer to the question
*/
public function test_was_answer_submitted_to_question_with_graded_gaveup() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$adaptiveattempt = new attempt($dummy, 1);
$mockquba = $this->getMockBuilder('question_usage_by_activity')
->disableOriginalConstructor()
->getMock();
$mockstate = $this->createMock('question_state_gaveup', array(), array(), '', false);
$mockquba->expects($this->once())
->method('get_question_state')
->with(1)
->will($this->returnValue($mockstate));
$mockquba->expects($this->never())
->method('get_id');
$result = $adaptiveattempt->was_answer_submitted_to_question($mockquba, 1);
$this->assertTrue($result);
}
/**
* This function tests whether the user submitted an answer to the question
*/
public function test_was_answer_submitted_to_question_with_graded_todo() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$adaptiveattempt = new attempt($dummy, 1);
$mockquba = $this->getMockBuilder('question_usage_by_activity')
->disableOriginalConstructor()
->getMock();
$mockstate = $this->createMock('question_state_todo', array(), array(), '', false);
$mockquba->expects($this->once())
->method('get_question_state')
->with(1)
->will($this->returnValue($mockstate));
$mockquba->expects($this->once())
->method('get_id')
->will($this->returnValue(1));
$result = $adaptiveattempt->was_answer_submitted_to_question($mockquba, 1);
$this->assertFalse($result);
}
/**
* This function tests the accessor method for $slot
*/
public function test_set_get_question_slot_number() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$userid = 1;
$adaptiveattempt = new attempt($dummy, $userid);
$adaptiveattempt->set_question_slot_number(2);
$result = $adaptiveattempt->get_question_slot_number();
$this->assertEquals(2, $result);
}
/**
* This function tests the accessor method for $slot
*/
public function test_set_get_question_level() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$userid = 1;
$adaptiveattempt = new attempt($dummy, $userid);
$adaptiveattempt->set_level(2);
$result = $adaptiveattempt->get_level();
$this->assertEquals(2, $result);
}
/**
* This function tests results returned from get_question_mark()
*/
public function test_get_question_mark_with_quba_return_float() {
$this->resetAfterTest(true);
// Test quba returning a mark of 1.0.
$mockquba = $this->createMock('question_usage_by_activity', array(), array(), '', false);
$mockquba->expects($this->once())
->method('get_question_mark')
->will($this->returnValue(1.0));
$dummy = new stdClass();
$userid = 1;
$adaptiveattempt = new attempt($dummy, $userid);
$result = $adaptiveattempt->get_question_mark($mockquba, 1);
$this->assertEquals(1.0, $result);
}
/**
* This function tests results returned from get_question_mark()
*/
public function test_get_question_mark_with_quba_return_non_float() {
$this->resetAfterTest(true);
// Test quba returning a non float value.
$mockqubatwo = $this->createMock('question_usage_by_activity', array(), array(), '', false);
$mockqubatwo->expects($this->once())
->method('get_question_mark')
->will($this->returnValue(0));
$dummy = new stdClass();
$userid = 1;
$adaptiveattempttwo = new attempt($dummy, $userid);
$result = $adaptiveattempttwo->get_question_mark($mockqubatwo, 1);
$this->assertEquals(0, $result);
}
/**
* This function tests results returned from get_question_mark()
*/
public function test_get_question_mark_with_quba_return_non_null() {
$this->resetAfterTest(true);
// Test quba returning null.
$mockqubathree = $this->createMock('question_usage_by_activity', array(), array(), '', false);
$mockqubathree->expects($this->once())
->method('get_question_mark')
->will($this->returnValue(null));
$dummy = new stdClass();
$userid = 1;
$adaptiveattemptthree = new attempt($dummy, $userid);
$result = $adaptiveattemptthree->get_question_mark($mockqubathree, 1);
$this->assertEquals(0, $result);
}
/**
* This function tests what happens when the maximum number of questions have been answered
*/
public function test_start_attempt_max_num_of_quest_answered() {
$this->resetAfterTest(true);
$attempt = $this->createPartialMock(attempt::class,
['get_attempt', 'level_in_bounds', 'max_questions_answered']);
$attempt->method('get_attempt')->willReturn(new stdClass());
$attempt->method('level_in_bounds')->willReturn(true);
$attempt->method('max_questions_answered')->willReturn(true);
$this->assertFalse($attempt->start_attempt());
}
/**
* This function tests what happens when a question slot number is not found, but the number of submitted answers is greater
* than zero.
*/
public function test_start_attempt_quest_slot_empty_quest_submit_greater_than_one() {
$dummyadaptivequiz = new stdClass();
$dummyadaptivequiz->lowestlevel = 1;
$dummyadaptivequiz->highestlevel = 100;
$mockattemptthree = $this
->getMockBuilder(attempt::class)
->onlyMethods(
['get_attempt', 'max_questions_answered', 'initialize_quba', 'find_last_quest_used_by_attempt',
'level_in_bounds']
)
->setConstructorArgs(
[$dummyadaptivequiz, 1]
)
->getMock();
$dummyattempt = new stdClass();
$dummyattempt->questionsattempted = 1;
$mockattemptthree->expects($this->once())
->method('get_attempt')
->will($this->returnValue($dummyattempt));
$mockattemptthree->expects($this->once())
->method('level_in_bounds')
->will($this->returnValue(true));
$mockattemptthree->expects($this->once())
->method('max_questions_answered')
->will($this->returnValue(false));
$mockattemptthree->expects($this->once())
->method('initialize_quba');
$mockattemptthree->expects($this->once())
->method('find_last_quest_used_by_attempt')
->will($this->returnValue(0));
$this->assertFalse($mockattemptthree->start_attempt());
}
/**
* This function tests the return values for level_in_bounds()
*/
public function test_level_in_bounds() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$dummy->lowestlevel = 5;
$dummy->highestlevel = 10;
$userid = 1;
$adaptiveattempt = new attempt($dummy, $userid);
$adaptivequiz = $adaptiveattempt->get_adaptivequiz();
$result = $adaptiveattempt->level_in_bounds(6, $adaptivequiz);
$this->assertTrue($result);
}
/**
* This function tests retrieving of question ids
*/
public function test_get_all_questions_in_attempt() {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$dummy = new stdClass();
$userid = 1;
$adaptiveattempt = new attempt($dummy, $userid);
$questids = $adaptiveattempt->get_all_questions_in_attempt(330);
$this->assertEquals(2, count($questids));
$this->assertEquals(array('1' => 1, '2' => 2), $questids);
}
/**
* @test
*/
public function it_can_check_if_a_user_has_a_completed_attempt_on_a_quiz(): void {
global $DB;
$this->resetAfterTest();
$this->setup_test_data_xml();
$uniqueid = 330;
$adaptivequizid = 330;
$cmid = 5;
$userid = 2;
$adaptivequiz = $DB->get_record('adaptivequiz', ['id' => $adaptivequizid]);
$context = context_module::instance($cmid);
adaptivequiz_complete_attempt($uniqueid, $adaptivequiz, $context, $userid, '', '');
$this->assertTrue(attempt::user_has_completed_on_quiz($adaptivequizid, $userid));
}
}
<?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/>.
/**
* PHPUnit tests for catalgo class.
*
* @copyright 2013 Remote-Learner {@link http://www.remote-learner.ca/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\local;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot.'/mod/adaptivequiz/locallib.php');
use advanced_testcase;
use coding_exception;
use question_usage_by_activity;
use stdClass;
/**
* @group mod_adaptivequiz
* @covers \mod_adaptivequiz\local\catalgo
*/
class catalgo_test extends advanced_testcase {
/**
* This function loads data into the PHPUnit tables for testing
*
* @throws coding_exception
*/
protected function setup_test_data_xml() {
$this->dataset_from_files(
[__DIR__.'/../fixtures/mod_adaptivequiz_catalgo.xml']
)->to_database();
}
/**
* This function tests instantiating the catalgo class without an instance of question_usage_by_activity.
*/
public function test_init_catalgo_no_quba_object_instance() {
$this->resetAfterTest(true);
$this->expectException('coding_exception');
$algo = new catalgo(new stdClass(), 1, true, 1);
}
/**
* This function tests instantiating the catalgo class with a non-positive integer and throwing an exception.
*/
public function test_init_catalgo_negative_int_throw_except() {
$this->resetAfterTest(true);
$mockquba = $this->createMock('question_usage_by_activity', array(), array(), '', false);
$this->expectException('coding_exception');
$algo = new catalgo($mockquba, -1);
}
/**
* This function tests instantiating the catalgo class without setting the level argument.
*/
public function test_init_catalgo_no_level_throw_except() {
$this->resetAfterTest(true);
$mockquba = $this->createMock('question_usage_by_activity', array(), array(), '', false);
$this->expectException('coding_exception');
$algo = new catalgo($mockquba, 1, true);
}
/**
* This fuction tests the retrieval of an attempt record
*/
public function test_retrieve_attempt_record() {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$mockquba = $this->createMock('question_usage_by_activity', array(), array(), '', false);
$algo = new catalgo($mockquba, 1, true, 1);
$result = $algo->retrieve_attempt_record(1);
$expected = new stdClass();
$expected->id = 1;
$expected->questionsattempted = 0;
$expected->difficultysum = 99;
$expected->standarderror = 1.2;
$expected->lowestlevel = 1;
$expected->highestlevel = 100;
$expected->measure = 2.222;
$this->assertEquals($expected, $result);
}
/**
* This function tests the retrieval of using illegible attempt id.
*/
public function test_retrieve_illegit_attempt_record_throw_except() {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$mockquba = $this->createMock('question_usage_by_activity', array(), array(), '', false);
$algo = new catalgo($mockquba, 1, true, 1);
$this->expectException('dml_missing_record_exception');
$result = $algo->retrieve_attempt_record(511);
}
/**
* This function tests was_answer_submitted_to_question() returning a false instead of a true
*/
public function test_quest_was_marked_correct_no_submit_prev_quest_fail() {
$this->resetAfterTest(true);
$mockcatalgo = $this->createPartialMock(catalgo::class,
['find_last_quest_used_by_attempt', 'was_answer_submitted_to_question', 'get_question_mark']);
$mockcatalgo->expects($this->once())
->method('find_last_quest_used_by_attempt')
->willReturn(99);
$mockcatalgo->expects($this->once())
->method('was_answer_submitted_to_question')
->with(99)
->willReturn(false);
$mockcatalgo->expects($this->never())
->method('get_question_mark');
$result = $mockcatalgo->question_was_marked_correct();
$this->assertFalse($result);
}
/**
* This function tests was_answer_submitted_to_question() returning a 0 instead of a slot number
*/
public function test_quest_was_marked_correct_zero_slot_number_fail() {
$this->resetAfterTest(true);
$mockcatalgo = $this->createPartialMock(catalgo::class,
['find_last_quest_used_by_attempt', 'get_question_mark']);
$mockcatalgo->expects($this->once())
->method('find_last_quest_used_by_attempt')
->willReturn(0);
$mockcatalgo->expects($this->never())
->method('get_question_mark');
$result = $mockcatalgo->question_was_marked_correct();
$this->assertNull($result);
}
/**
* This function tests get_question_mark() returning null
*/
public function test_quest_was_marked_correct_mark_is_null_fail() {
$this->resetAfterTest(true);
$mockcatalgo = $this->createPartialMock(catalgo::class,
['find_last_quest_used_by_attempt', 'was_answer_submitted_to_question', 'get_question_mark']);
$mockcatalgo->expects($this->once())
->method('find_last_quest_used_by_attempt')
->willReturn(99);
$mockcatalgo->expects($this->once())
->method('was_answer_submitted_to_question')
->with(99)
->willReturn(true);
$mockcatalgo->expects($this->once())
->method('get_question_mark')
->withAnyParameters()
->willReturn(null);
$result = $mockcatalgo->question_was_marked_correct();
$this->assertNull($result);
}
/**
* This function tests get_question_mark() returning a mark of zero
*/
public function test_quest_was_marked_correct_mark_zero() {
$this->resetAfterTest(true);
$mockcatalgo = $this->createPartialMock(catalgo::class,
['find_last_quest_used_by_attempt', 'was_answer_submitted_to_question', 'get_question_mark']);
$mockcatalgo->expects($this->once())
->method('find_last_quest_used_by_attempt')
->willReturn(99);
$mockcatalgo->expects($this->once())
->method('was_answer_submitted_to_question')
->with(99)
->willReturn(true);
$mockcatalgo->expects($this->once())
->method('get_question_mark')
->withAnyParameters()
->willReturn(0.00);
$result = $mockcatalgo->question_was_marked_correct();
$this->assertFalse($result);
}
/**
* This function tests get_question_mark() returning a mark of greater than zero
*/
public function test_quest_was_marked_correct_mark_non_zero() {
$this->resetAfterTest(true);
$mockcatalgo = $this->createPartialMock(catalgo::class,
['find_last_quest_used_by_attempt', 'was_answer_submitted_to_question', 'get_question_mark']);
$mockcatalgo->expects($this->once())
->method('find_last_quest_used_by_attempt')
->willReturn(99);
$mockcatalgo->expects($this->once())
->method('was_answer_submitted_to_question')
->with(99)
->willReturn(true);
$mockcatalgo->expects($this->once())
->method('get_question_mark')
->withAnyParameters()
->willReturn(0.10);
$result = $mockcatalgo->question_was_marked_correct();
$this->assertTrue($result);
}
/**
* This function tests retrieve_standard_error(), retrieving the standard error value set for the activity.
*/
public function test_retrieve_standard_error() {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$mockquba = $this->createMock(question_usage_by_activity::class);
$algo = new catalgo($mockquba, 1, true, 1);
$result = $algo->retrieve_standard_error(1);
$this->assertEquals(9.9, $result);
}
/**
* This function tests retrieve_standard_error() with illegible attempt id.
*/
public function test_retrieve_standard_error_throw_excep() {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$mockquba = $this->createMock('question_usage_by_activity');
$algo = new catalgo($mockquba, 1, true, 1);
$this->expectException('dml_missing_record_exception');
$result = $algo->retrieve_standard_error(511);
}
/**
* This function tests compute_next_difficulty()
* Setting 0 as the lowest level and 100 as the highest level
*/
public function test_compute_next_difficulty_zero_min_one_hundred_max() {
$this->resetAfterTest(true);
$mockquba = $this->createMock(question_usage_by_activity::class);
$catalgo = new catalgo($mockquba, 1, true, 1);
$dummy = new stdClass();
$dummy->lowestlevel = 0;
$dummy->highestlevel = 100;
// Test the next difficulty level shown to the student if the student got a level 30 question wrong,
// having attempted 1 question.
$result = $catalgo->compute_next_difficulty(30, 1, false, $dummy);
$this->assertEquals(5, $result);
// Test the next difficulty level shown to the student if the student got a level 30 question right,
// having attempted 1 question.
$result = $catalgo->compute_next_difficulty(30, 1, true, $dummy);
$this->assertEquals(76, $result);
// Test the next difficulty level shown to the student if the student got a level 80 question wrong,
// having attempted 2 questions.
$result = $catalgo->compute_next_difficulty(80, 2, false, $dummy);
$this->assertEquals(60, $result);
// Test the next difficulty level shown to the student if the student got a level 80 question right,
// having attempted 2 question.
$result = $catalgo->compute_next_difficulty(80, 2, true, $dummy);
$this->assertEquals(92, $result);
}
/**
* This function tests compute_next_difficulty()
* Setting 1 as the lowest level and 10 as the highest level
*/
public function test_compute_next_difficulty_one_min_ten_max_compute_infinity() {
$this->resetAfterTest(true);
$mockquba = $this->createMock(question_usage_by_activity::class);
$catalgo = new catalgo($mockquba, 1, true, 1);
$dummy = new stdClass();
$dummy->lowestlevel = 1;
$dummy->highestlevel = 10;
$result = $catalgo->compute_next_difficulty(1, 2, false, $dummy);
$this->assertEquals(1, $result);
$result = $catalgo->compute_next_difficulty(10, 2, true, $dummy);
$this->assertEquals(10, $result);
}
/**
* This function tests results returned from get_question_mark()
*/
public function test_get_question_mark() {
$this->resetAfterTest(true);
// Test quba returning a mark of 1.0.
$mockquba = $this->createMock(question_usage_by_activity::class);
$mockquba->expects($this->once())
->method('get_question_mark')
->will($this->returnValue(1.0));
$catalgo = new catalgo($mockquba, 1, true, 1);
$result = $catalgo->get_question_mark($mockquba, 1);
$this->assertEquals(1.0, $result);
// Test quba returning a non float value.
$mockqubatwo = $this->createMock('question_usage_by_activity');
$mockqubatwo->expects($this->once())
->method('get_question_mark')
->will($this->returnValue(1));
$catalgo = new catalgo($mockqubatwo, 1, true, 1);
$result = $catalgo->get_question_mark($mockqubatwo, 1);
$this->assertNull($result);
}
/**
* This function tests the return data from compute_right_answers()
*/
public function test_compute_right_answers() {
$this->resetAfterTest(true);
// Test use case where user got all 5 question correct.
$mockquba = $this->createPartialMock('question_usage_by_activity', ['get_slots', 'get_question_mark']);
$mockquba->expects($this->exactly(5))
->method('get_question_mark')
->willReturn(1.0);
$mockquba->expects($this->once())
->method('get_slots')
->willReturn([1, 2, 3, 4, 5]);
$catalgo = new catalgo($mockquba, 1, true, 1);
$result = $catalgo->compute_right_answers($mockquba);
$this->assertEquals(5, $result);
}
/**
* This function tests the return data from compute_right_answers()
*/
public function test_compute_right_answers_none_correct() {
$this->resetAfterTest(true);
// Test use case where user got all 5 question incorrect.
$mockquba = $this->createMock('question_usage_by_activity');
$mockquba->expects($this->exactly(5))
->method('get_question_mark')
->will($this->returnValue(0));
$mockquba->expects($this->once())
->method('get_slots')
->will($this->returnValue(array(1, 2, 3, 4, 5)));
$catalgo = new catalgo($mockquba, 1, true, 1);
$result = $catalgo->compute_right_answers($mockquba);
$this->assertEquals(0, $result);
}
/**
* This function tests the return data from compute_right_answers() when get_question_mark() returns a null
*/
public function test_compute_right_answers_null_return_from_get_question_mark() {
$this->resetAfterTest(true);
$mockquba = $this->createMock('question_usage_by_activity');
$mockquba->expects($this->once())
->method('get_question_mark')
->will($this->returnValue(null));
$mockquba->expects($this->once())
->method('get_slots')
->will($this->returnValue(array(1)));
$catalgo = new catalgo($mockquba, 1, true, 1);
$result = $catalgo->compute_right_answers($mockquba);
$this->assertEquals(0, $result);
}
/**
* This function tests the return data from compute_right_answers()
*/
public function test_compute_wrong_answers() {
$this->resetAfterTest(true);
// Test use case where user got all 5 question incorrect.
$mockquba = $this->createMock('question_usage_by_activity');
$mockquba->expects($this->exactly(5))
->method('get_question_mark')
->will($this->returnValue(0));
$mockquba->expects($this->once())
->method('get_slots')
->will($this->returnValue(array(1, 2, 3, 4, 5)));
$catalgo = new catalgo($mockquba, 1, true, 1);
$result = $catalgo->compute_wrong_answers($mockquba);
$this->assertEquals(5, $result);
}
/**
* This function tests the return data from compute_right_answers()
*/
public function test_compute_wrong_answers_all_correct() {
$this->resetAfterTest(true);
// Test use case where user got all 5 question correct.
$mockquba = $this->createMock('question_usage_by_activity');
$mockquba->expects($this->exactly(5))
->method('get_question_mark')
->will($this->returnValue(1.0));
$mockquba->expects($this->once())
->method('get_slots')
->will($this->returnValue(array(1, 2, 3, 4, 5)));
$catalgo = new catalgo($mockquba, 1, true, 1);
$result = $catalgo->compute_wrong_answers($mockquba);
$this->assertEquals(0, $result);
}
/**
* This function tests the return data from compute_wrong_answers() when get_question_mark() returns a null
*/
public function test_compute_wrong_answers_null_return_from_get_question_mark() {
$this->resetAfterTest(true);
$mockquba = $this->createMock('question_usage_by_activity');
$mockquba->expects($this->once())
->method('get_question_mark')
->will($this->returnValue(null));
$mockquba->expects($this->once())
->method('get_slots')
->will($this->returnValue(array(1)));
$catalgo = new catalgo($mockquba, 1, true, 1);
$result = $catalgo->compute_wrong_answers($mockquba);
$this->assertEquals(1, $result);
}
/**
* This function tests the return data from estimate_measure()
*/
public function test_estimate_measure() {
$this->resetAfterTest(true);
$mockquba = $this->createMock('question_usage_by_activity', array(), array(), '', false);
// Test an attempt with the following details:
// sum of difficulty - 20, number of questions attempted - 10, number of correct answers - 7,
// number of incorrect answers - 3.
$catalgo = new catalgo($mockquba, 1, true, 1);
$result = $catalgo->estimate_measure(20, 10, 7, 3);
$this->assertEquals(2.8473, $result);
}
/**
* This function tests the return data from estimate_standard_error()
*/
public function test_estimate_standard_error() {
$this->resetAfterTest(true);
$mockquba = $this->createMock('question_usage_by_activity', array(), array(), '', false);
// Test an attempt with the following details;
// sum of questions attempted - 10, number of correct answers - 7, number of incorrect answers - 3.
$catalgo = new catalgo($mockquba, 1, true, 1);
$result = $catalgo->estimate_standard_error(10, 7, 3);
$this->assertEquals(0.69007, $result);
}
/**
* This function tests the return data from perform_calculation_steps(), where question_was_marked_correct() returns null
*/
public function test_perform_calc_steps_marked_correct_return_null_fail() {
$this->resetAfterTest(true);
$mockcatalgo = $this->createPartialMock(catalgo::class, ['retrieve_attempt_record',
'question_was_marked_correct', 'compute_next_difficulty', 'compute_right_answers', 'compute_wrong_answers',
'estimate_measure', 'estimate_standard_error', 'retrieve_standard_error',
'standard_error_within_parameters']);
$dummyattempt = new stdClass();
$dummyattempt->questionsattempted = 10;
$dummyattempt->difficultysum = 20;
$mockcatalgo->expects($this->once())
->method('retrieve_attempt_record')
->willReturn($dummyattempt);
$mockcatalgo->expects($this->once())
->method('question_was_marked_correct')
->willReturn(null); // Questions marked correctly returns null.
$mockcatalgo->expects($this->never())
->method('compute_next_difficulty');
$mockcatalgo->expects($this->never())
->method('compute_right_answers');
$mockcatalgo->expects($this->never())
->method('compute_wrong_answers');
$mockcatalgo->expects($this->never())
->method('estimate_measure');
$mockcatalgo->expects($this->never())
->method('estimate_standard_error');
$mockcatalgo->expects($this->never())
->method('retrieve_standard_error');
$mockcatalgo->expects($this->never())
->method('standard_error_within_parameters');
$result = $mockcatalgo->perform_calculation_steps();
$this->assertEquals(0, $result);
}
/**
* This function tests the return data from perform_calculation_steps(), where compute_right_answers() returns 0,
* but the next difficulty number is returned.
*/
public function test_perform_calc_steps_right_ans_ret_zero_but_return_non_zero() {
$this->resetAfterTest(true);
$mockcatalgo = $this->createPartialMock(catalgo::class, ['retrieve_attempt_record',
'question_was_marked_correct', 'compute_next_difficulty', 'compute_right_answers', 'compute_wrong_answers',
'retrieve_standard_error']);
$dummyattempt = new stdClass();
$dummyattempt->questionsattempted = 10;
$dummyattempt->difficultysum = 20;
$mockcatalgo->expects($this->once())
->method('retrieve_attempt_record')
->willReturn($dummyattempt);
$mockcatalgo->expects($this->once())
->method('question_was_marked_correct')
->willReturn(true);
$mockcatalgo->expects($this->once())
->method('compute_next_difficulty')
->willReturn(30);
$mockcatalgo->expects($this->once())
->method('compute_right_answers')
->willReturn(0); // Right answers is set to zero.
$mockcatalgo->expects($this->once())
->method('compute_wrong_answers')
->willReturn(10); // Wrong answers is set to 10.
$mockcatalgo->expects($this->once())
->method('retrieve_standard_error')
->willReturn(1.45095);
$result = $mockcatalgo->perform_calculation_steps();
$this->assertNotEquals(0, $result);
}
/**
* This function tests the return data from perform_calculation_steps(), where compute_wrong_answers() returns 0,
* but the next difficulty number is returned.
*/
public function test_perform_calc_steps_wrong_ans_return_zero_but_return_non_zero() {
$this->resetAfterTest(true);
$mockcatalgo = $this->createPartialMock(catalgo::class, ['retrieve_attempt_record', 'question_was_marked_correct',
'compute_next_difficulty', 'compute_right_answers', 'compute_wrong_answers', 'retrieve_standard_error']);
$dummyattempt = new stdClass();
$dummyattempt->questionsattempted = 10;
$dummyattempt->difficultysum = 20;
$mockcatalgo->expects($this->once())
->method('retrieve_attempt_record')
->willReturn($dummyattempt);
$mockcatalgo->expects($this->any())
->method('question_was_marked_correct')
->willReturn(true);
$mockcatalgo->expects($this->once())
->method('compute_next_difficulty')
->willReturn(30);
$mockcatalgo->expects($this->once())
->method('compute_right_answers')
->willReturn(10); // Right answers is set to 10.
$mockcatalgo->expects($this->once())
->method('compute_wrong_answers')
->willReturn(0); // Wrong answers is set to zero.
$mockcatalgo->expects($this->once())
->method('retrieve_standard_error')
->willReturn(1.45095);
$result = $mockcatalgo->perform_calculation_steps();
$this->assertNotEquals(0, $result);
}
/**
* This function tests the return data from perform_calculation_steps(), where questions attempted is set to 0
*/
public function test_perform_calc_steps_quest_attempted_return_zero_fail() {
$this->resetAfterTest(true);
$mockcatalgo = $this->createPartialMock(catalgo::class, ['retrieve_attempt_record',
'question_was_marked_correct', 'compute_next_difficulty', 'compute_right_answers', 'compute_wrong_answers',
'estimate_measure', 'estimate_standard_error', 'retrieve_standard_error',
'standard_error_within_parameters']);
$dummyattempt = new stdClass();
$dummyattempt->questionsattempted = 0; // Questions attempted is set to zero.
$dummyattempt->difficultysum = 20;
$mockcatalgo->expects($this->once())
->method('retrieve_attempt_record')
->willReturn($dummyattempt);
$mockcatalgo->expects($this->once())
->method('question_was_marked_correct')
->willReturn(true);
$mockcatalgo->expects($this->once())
->method('compute_next_difficulty')
->willReturn(30);
$mockcatalgo->expects($this->once())
->method('compute_right_answers')
->willReturn(1);
$mockcatalgo->expects($this->once())
->method('compute_wrong_answers')
->willReturn(1);
$mockcatalgo->expects($this->never())
->method('estimate_measure');
$mockcatalgo->expects($this->never())
->method('estimate_standard_error');
$mockcatalgo->expects($this->never())
->method('retrieve_standard_error');
$mockcatalgo->expects($this->never())
->method('standard_error_within_parameters');
$result = $mockcatalgo->perform_calculation_steps();
$this->assertEquals(0, $result);
}
/**
* This function tests the return data from perform_calculation_steps(), where the sum of correct and incorrect answers
* does not equal the sum of questions attempted.
*/
public function test_perform_calc_steps_sum_corr_and_incorr_not_equl_sum_quest_attempt_fail() {
$this->resetAfterTest(true);
$mockcatalgo = $this->createPartialMock(catalgo::class, ['retrieve_attempt_record', 'question_was_marked_correct',
'compute_next_difficulty', 'compute_right_answers', 'compute_wrong_answers', 'estimate_measure',
'estimate_standard_error', 'retrieve_standard_error', 'standard_error_within_parameters']);
$dummyattempt = new stdClass();
$dummyattempt->questionsattempted = 3; // Sum of question attempted.
$dummyattempt->difficultysum = 20;
$mockcatalgo->expects($this->once())
->method('retrieve_attempt_record')
->willReturn($dummyattempt);
$mockcatalgo->expects($this->once())
->method('question_was_marked_correct')
->willReturn(true);
$mockcatalgo->expects($this->once())
->method('compute_next_difficulty')
->willReturn(30);
$mockcatalgo->expects($this->once())
->method('compute_right_answers')
->willReturn(1); // Sum of right answers.
$mockcatalgo->expects($this->once())
->method('compute_wrong_answers')
->willReturn(1); // Sum of wrong answers.
$mockcatalgo->expects($this->never())
->method('estimate_measure');
$mockcatalgo->expects($this->never())
->method('estimate_standard_error');
$mockcatalgo->expects($this->never())
->method('retrieve_standard_error');
$mockcatalgo->expects($this->never())
->method('standard_error_within_parameters');
$result = $mockcatalgo->perform_calculation_steps();
$this->assertEquals(0, $result);
}
/**
* This function tests the return data from perform_calculation_steps(), where the user answered
* the last question correctly and the attempt has not met the minimum stopping criteria.
*/
public function test_perform_calculation_steps_nostop_correct_answer() {
$this->resetAfterTest(true);
$mockquba = $this->createMock(question_usage_by_activity::class);
// Minimum stopping criteria has not been met.
$mockcatalgo = $this
->getMockBuilder(catalgo::class)
->onlyMethods(
['retrieve_attempt_record', 'question_was_marked_correct', 'compute_next_difficulty',
'compute_right_answers', 'compute_wrong_answers', 'estimate_measure', 'estimate_standard_error',
'retrieve_standard_error', 'standard_error_within_parameters']
)
->setConstructorArgs(
[$mockquba, 1, false, 50]
)
->getMock();
$dummyattempt = new stdClass();
$dummyattempt->questionsattempted = 1;
$dummyattempt->difficultysum = 50;
$mockcatalgo->expects($this->once())
->method('retrieve_attempt_record')
->willReturn($dummyattempt);
$mockcatalgo->expects($this->once())
->method('question_was_marked_correct')
->willReturn(true); // Last attempted question marked correctly.
$mockcatalgo->expects($this->once())
->method('compute_next_difficulty')
->willReturn(52);
$mockcatalgo->expects($this->never())
->method('compute_right_answers');
$mockcatalgo->expects($this->never())
->method('compute_wrong_answers');
$mockcatalgo->expects($this->never())
->method('estimate_measure');
$mockcatalgo->expects($this->never())
->method('estimate_standard_error');
$mockcatalgo->expects($this->never())
->method('retrieve_standard_error');
$mockcatalgo->expects($this->never())
->method('standard_error_within_parameters');
$result = $mockcatalgo->perform_calculation_steps();
$this->assertEquals(52, $result);
}
/**
* This function tests the return data from perform_calculation_steps(), where the user answered
* the last question incorrectly and the attempt has not met the minimum stopping criteria.
*/
public function test_perform_calculation_steps_nostop_incorrect_answer() {
$this->resetAfterTest(true);
$mockquba = $this->createMock(question_usage_by_activity::class);
// Minimum stopping criteria has not been met.
$mockcatalgo = $this
->getMockBuilder(catalgo::class)
->onlyMethods(
['retrieve_attempt_record', 'question_was_marked_correct', 'compute_next_difficulty',
'compute_right_answers', 'compute_wrong_answers', 'estimate_measure', 'estimate_standard_error',
'retrieve_standard_error', 'standard_error_within_parameters']
)
->setConstructorArgs(
[$mockquba, 1, false, 50]
)
->getMock();
$dummyattempt = new stdClass();
$dummyattempt->questionsattempted = 1;
$dummyattempt->difficultysum = 50;
$mockcatalgo->expects($this->once())
->method('retrieve_attempt_record')
->willReturn($dummyattempt);
$mockcatalgo->expects($this->once())
->method('question_was_marked_correct')
->willReturn(false); // Last attempted question marked incorrectly.
$mockcatalgo->expects($this->once())
->method('compute_next_difficulty')
->willReturn(48);
$mockcatalgo->expects($this->never())
->method('compute_right_answers');
$mockcatalgo->expects($this->never())
->method('compute_wrong_answers');
$mockcatalgo->expects($this->never())
->method('estimate_measure');
$mockcatalgo->expects($this->never())
->method('estimate_standard_error');
$mockcatalgo->expects($this->never())
->method('retrieve_standard_error');
$mockcatalgo->expects($this->never())
->method('standard_error_within_parameters');
$result = $mockcatalgo->perform_calculation_steps();
$this->assertEquals(48, $result);
}
/**
* This function tests the return data from perform_calculation_steps(), where the attempt has met
* all the criteria to determine the standard error and the function runs from beginning to end.
*/
public function test_perform_calculation_steps_stop_no_fail() {
$this->resetAfterTest(true);
$mockquba = $this->createMock(question_usage_by_activity::class);
// Minimum stopping criteria has been met.
$mockcatalgo = $this
->getMockBuilder(catalgo::class)
->onlyMethods(
['retrieve_attempt_record', 'question_was_marked_correct', 'compute_next_difficulty', 'compute_right_answers',
'compute_wrong_answers', 'retrieve_standard_error', 'standard_error_within_parameters']
)
->setConstructorArgs(
[$mockquba, 1, true, 50]
)
->getMock();
$dummyattempt = new stdClass();
$dummyattempt->questionsattempted = 2;
$dummyattempt->difficultysum = 50;
$mockcatalgo->expects($this->once())
->method('retrieve_attempt_record')
->with(1)
->willReturn($dummyattempt);
$mockcatalgo->expects($this->once())
->method('question_was_marked_correct')
->willReturn(true);
$mockcatalgo->expects($this->once())
->method('compute_next_difficulty')
->with(50, 2, true, $dummyattempt)
->willReturn(48);
$mockcatalgo->expects($this->once())
->method('compute_right_answers')
->withAnyParameters()
->willReturn(1);
$mockcatalgo->expects($this->once())
->method('compute_wrong_answers')
->withAnyParameters()
->willReturn(1);
$mockcatalgo->expects($this->once())
->method('retrieve_standard_error')
->with(1)
->willReturn(5);
$mockcatalgo->expects($this->once())
->method('standard_error_within_parameters')
->withAnyParameters() // Second parameter (is not rounded) and has decimal with many decimal places.
->willReturn(1.0);
$result = $mockcatalgo->perform_calculation_steps();
$this->assertEquals(48, $result);
}
/**
* This function tests the return value from standard_error_within_parameters().
*/
public function test_standard_error_within_parameters_return_true_then_false() {
$this->resetAfterTest(true);
$mockquba = $this->createMock('question_usage_by_activity');
$catalgo = new catalgo($mockquba, 1, true, 1);
$result = $catalgo->standard_error_within_parameters(0.02, 0.1);
$this->assertTrue($result);
$result = $catalgo->standard_error_within_parameters(0.01, 0.002);
$this->assertFalse($result);
}
/**
* This function tests the return value from get_current_diff_level().
*/
public function test_get_current_diff_level_using_level_zero() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$dummy->lowestlevel = 1;
$dummy->highestlevel = 20;
$mockquba = $this->createMock(question_usage_by_activity::class);
$mockcatalgo = $this
->getMockBuilder(catalgo::class)
->onlyMethods(
['return_current_diff_level']
)
->setConstructorArgs(
[$mockquba, 1, true, 50]
)
->getMock();
$mockcatalgo->expects($this->never())
->method('return_current_diff_level');
$this->expectException('coding_exception');
$mockcatalgo->get_current_diff_level($mockquba, 0, $dummy);
}
/**
* This function tests the return value from get_current_diff_level()
*/
public function test_get_current_diff_level_using_no_quba() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$dummy->lowestlevel = 1;
$dummy->highestlevel = 20;
$mockquba = $this->createMock(question_usage_by_activity::class);
$mockcatalgo = $this
->getMockBuilder(catalgo::class)
->onlyMethods(
['return_current_diff_level']
)
->setConstructorArgs(
[$mockquba, 1, true, 50]
)
->getMock();
$mockcatalgo->expects($this->never())
->method('return_current_diff_level');
$this->expectException('coding_exception');
$mockcatalgo->get_current_diff_level($dummy, 1, $dummy);
}
/**
* This function tests the return value from get_current_diff_level()
*/
public function test_get_current_diff_level_using_no_attempt_obj() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$mockquba = $this->createMock(question_usage_by_activity::class);
$mockcatalgo = $this
->getMockBuilder(catalgo::class)
->onlyMethods(
['return_current_diff_level']
)
->setConstructorArgs(
[$mockquba, 1, true, 50]
)
->getMock();
$mockcatalgo->expects($this->never())
->method('return_current_diff_level');
$this->expectException('coding_exception');
$mockcatalgo->get_current_diff_level($mockquba, 1, $dummy);
}
/**
* This function tests the return value from get_current_diff_level()
*/
public function test_get_current_diff_level_using_attempt_obj_missing_lowestlevel() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$dummy->highestlevel = 20;
$mockquba = $this->createMock(question_usage_by_activity::class);
$mockcatalgo = $this
->getMockBuilder(catalgo::class)
->onlyMethods(
['return_current_diff_level']
)
->setConstructorArgs(
[$mockquba, 1, true, 50]
)
->getMock();
$mockcatalgo->expects($this->never())
->method('return_current_diff_level');
$this->expectException('coding_exception');
$mockcatalgo->get_current_diff_level($mockquba, 1, $dummy);
}
/**
* This function tests the return value from get_current_diff_level()
*/
public function test_get_current_diff_level_using_attempt_obj_missing_highestlevel() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$dummy->lowestlevel = 20;
$mockquba = $this->createMock(question_usage_by_activity::class);
$mockcatalgo = $this
->getMockBuilder(catalgo::class)
->onlyMethods(
['return_current_diff_level']
)
->setConstructorArgs(
[$mockquba, 1, true, 50]
)
->getMock();
$mockcatalgo->expects($this->never())
->method('return_current_diff_level');
$this->expectException('coding_exception');
$mockcatalgo->get_current_diff_level($mockquba, 1, $dummy);
}
/**
* This function tests the return value from get_current_diff_level()
*/
public function test_get_current_diff_level() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$dummy->lowestlevel = 20;
$dummy->highestlevel = 21;
$mockquba = $this->createMock(question_usage_by_activity::class);
$mockcatalgo = $this
->getMockBuilder(catalgo::class)
->onlyMethods(
['return_current_diff_level']
)
->setConstructorArgs(
[$mockquba, 1, true, 50]
)
->getMock();
$mockcatalgo->expects($this->once())
->method('return_current_diff_level')
->willReturn(3);
$result = $mockcatalgo->get_current_diff_level($mockquba, 1, $dummy);
$this->assertEquals(3, $result);
}
/**
* This function tests the output from convert_percent_to_logit()
*/
public function test_convert_percent_to_logit_using_param_less_than_zero() {
$this->resetAfterTest(true);
$this->expectException('coding_exception');
$result = catalgo::convert_percent_to_logit(-1);
}
/**
* This function tests the output from convert_percent_to_logit()
*/
public function test_convert_percent_to_logit_using_param_greater_than_decimal_five() {
$this->resetAfterTest(true);
$this->expectException('coding_exception');
catalgo::convert_percent_to_logit(0.51);
}
/**
* This function tests the output from convert_percent_to_logit()
*/
public function test_convert_percent_to_logit() {
$this->resetAfterTest(true);
$result = catalgo::convert_percent_to_logit(0.05);
$result = round($result, 1);
$this->assertEquals(0.2, $result);
}
/**
* This function tests the output from convert_logit_to_percent()
*/
public function test_convert_logit_to_percent_using_param_less_than_zero() {
$this->resetAfterTest(true);
$this->expectException('coding_exception');
catalgo::convert_logit_to_percent(-1);
}
/**
* This function tests the output from convert_logit_to_percent()
*/
public function test_convert_logit_to_percent() {
$this->resetAfterTest(true);
$result = catalgo::convert_logit_to_percent(0.2);
$result = round($result, 2);
$this->assertEquals(0.05, $result);
}
/**
* This function tests the output from map_logit_to_scale()
*/
public function test_map_logit_to_scale() {
$this->resetAfterTest(true);
$result = catalgo::map_logit_to_scale(-0.6, 16, 1);
$result = round($result, 1);
$this->assertEquals(6.3, $result);
}
}
<?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/>.
/**
* fetch question PHPUnit tests
*
* @copyright 2013 Remote-Learner {@link http://www.remote-learner.ca/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\local;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot.'/mod/adaptivequiz/locallib.php');
use advanced_testcase;
use coding_exception;
use mod_adaptivequiz\local\repository\questions_number_per_difficulty;
use stdClass;
/**
* @group mod_adaptivequiz
* @covers \mod_adaptivequiz\local\fetchquestion
*/
class fetchquestion_test extends advanced_testcase {
/** @var stdClass $activityinstance adaptivequiz activity instance object */
protected $activityinstance = null;
/** @var stdClass $cm a partially completed course module object */
protected $cm = null;
/** @var stdClass $user a user object */
protected $user = null;
/**
* This function loads data into the PHPUnit tables for testing
*
* @return void
* @throws coding_exception
*/
protected function setup_test_data_xml() {
$this->dataset_from_files(
[__DIR__.'/../fixtures/mod_adaptivequiz_findquestion.xml']
)->to_database();
}
/**
* This function creates a default user and activity instance using generator classes
* The activity parameters created are are follows:
* lowest difficulty level: 1
* highest difficulty level: 10
* minimum question attempts: 2
* maximum question attempts: 10
* standard error: 1.1
* starting level: 5
* question category ids: 1
* course id: 2
* @return void
*/
protected function setup_generator_data() {
// Create test user.
$this->user = $this->getDataGenerator()->create_user();
$this->setUser($this->user);
$this->setAdminUser();
// Create activity.
$generator = $this->getDataGenerator()->get_plugin_generator('mod_adaptivequiz');
$options = array(
'highestlevel' => 10,
'lowestlevel' => 1,
'minimumquestions' => 2,
'maximumquestions' => 10,
'standarderror' => 1.1,
'startinglevel' => 5,
'questionpool' => array(1),
'course' => 2
);
$this->activityinstance = $generator->create_instance($options);
$this->cm = new stdClass();
$this->cm->id = $this->activityinstance->cmid;
}
/**
* This function creates a default user and activity instance using generator classes (using a different question category)
* The activity parameters created are are follows:
* lowest difficulty level: 1
* highest difficulty level: 10
* minimum question attempts: 2
* maximum question attempts: 10
* standard error: 1.1
* starting level: 5
* question category ids: 1
* course id: 2
* @return void
*/
protected function setup_generator_data_two() {
// Create test user.
$this->user = $this->getDataGenerator()->create_user();
$this->setUser($this->user);
$this->setAdminUser();
// Create activity.
$generator = $this->getDataGenerator()->get_plugin_generator('mod_adaptivequiz');
$options = array(
'highestlevel' => 10,
'lowestlevel' => 1,
'minimumquestions' => 2,
'maximumquestions' => 10,
'standarderror' => 1.1,
'startinglevel' => 5,
'questionpool' => array(4),
'course' => 2
);
$this->activityinstance = $generator->create_instance($options);
$this->cm = new stdClass();
$this->cm->id = $this->activityinstance->cmid;
}
/**
* This function tests the retrieval of using illegible tag ids.
* @see setup_generator_data() for detail of activity instance.
*/
public function test_find_questions_fail_tag_ids() {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$this->setup_generator_data();
$attempt = $this
->getMockBuilder(fetchquestion::class)
->onlyMethods(
['retrieve_question_categories']
)
->setConstructorArgs(
[$this->activityinstance, 1, 1, 100]
)
->getMock();
$attempt->expects($this->exactly(2))
->method('retrieve_question_categories')
->willReturn(
[1 => 1, 2 => 2, 3 => 3]
);
$data = $attempt->find_questions_with_tags(
[99]
);
$this->assertEquals(0, count($data));
$data = $attempt->find_questions_with_tags([]);
$this->assertEquals(0, count($data));
}
/**
* This function tests the retrieval of questions using an empty set of question categories.
* @see setup_generator_data() for detail of activity instance.
*/
public function test_find_questions_fail_question_cat() {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$this->setup_generator_data();
$mockclass = $this
->getMockBuilder(fetchquestion::class)
->onlyMethods(
['retrieve_question_categories']
)
->setConstructorArgs(
[$this->activityinstance, 1, 1, 100]
)
->getMock();
$mockclass->expects($this->exactly(2))
->method('retrieve_question_categories')
->willReturn([]);
// Call class method with illegible tag id.
$data = $mockclass->find_questions_with_tags(
[99]
);
$this->assertEquals(0, count($data));
// Call class method with legit tag id.
$data = $mockclass->find_questions_with_tags(
[1]
);
$this->assertEquals(0, count($data));
}
/**
* This function tests the retrieval of questions using the exclude parameter
* @see setup_generator_data() for detail of activity instance
* @return void
*/
public function test_find_questions_exclude() {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$this->setup_generator_data();
$mockclass = $this
->getMockBuilder(fetchquestion::class)
->onlyMethods(
['retrieve_question_categories']
)
->setConstructorArgs(
[$this->activityinstance, 1, 1, 100]
)
->getMock();
$mockclass->expects($this->once())
->method('retrieve_question_categories')
->willReturn(
[1 => 1, 2 => 2, 3 => 3]
);
$data = $mockclass->find_questions_with_tags([1], [1]);
$this->assertEquals(0, count($data));
}
/**
* This functions tests the accessor methods for the $level class variable.
*/
public function test_get_set_level() {
$this->resetAfterTest(true);
$dummyclass = new stdClass();
$fetchquestion = new fetchquestion($dummyclass, 99, 1, 100);
$this->assertEquals(99, $fetchquestion->get_level());
$fetchquestion->set_level(22);
$this->assertEquals(22, $fetchquestion->get_level());
$this->expectException('coding_exception');
$fetchquestion->set_level(-22);
}
/**
* @test
*/
public function it_fails_when_instantiated_with_a_zero_difficulty_level(): void {
$this->resetAfterTest(true);
$this->expectException('coding_exception');
(new fetchquestion(new stdClass(), 0, 1, 100, ['phpunittag_']));
}
/**
* @test
*/
public function it_fails_when_instantiated_with_a_negative_difficulty_level(): void {
$this->resetAfterTest(true);
$this->expectException('coding_exception');
(new fetchquestion(new stdClass(), -11, 1, 100, ['phpunittag_']));
}
/**
* @test
*/
public function it_fails_when_instantiated_with_a_difficulty_level_as_a_string(): void {
$this->resetAfterTest(true);
$this->expectException('coding_exception');
(new fetchquestion(new stdClass(), 'asdf', 1, 100, ['phpunittag_']));
}
/**
* This functions tests the retrevial of tag ids with an associated difficulty level
* but using legit data.
*/
public function test_retrieve_tag() {
$this->resetAfterTest();
$this->setup_test_data_xml();
$dummyclass = new stdClass();
$fetchquestion = new fetchquestion($dummyclass, 5, 1, 100, ['phpunittag_']);
$data = $fetchquestion->retrieve_tag(5);
$this->assertEquals(2, count($data));
$this->assertEquals([0 => 1, 1 => 2], $data);
$fetchquestion2 = new fetchquestion($dummyclass, 888, 1, 100, ['phpunittag_']);
$data = $fetchquestion2->retrieve_tag(888);
$this->assertEquals(0, count($data));
}
/**
* This function test output from fetch_question() where initalize_tags_with_quest_count() returns an empty array
*/
public function test_fetch_question_initalize_tags_with_quest_count_return_empty_array() {
$this->resetAfterTest(true);
$mockclass = $this
->getMockBuilder(fetchquestion::class)
->onlyMethods(
['initalize_tags_with_quest_count', 'retrieve_tag', 'find_questions_with_tags']
)
->setConstructorArgs(
[new stdClass(), 5, 1, 100]
)
->getMock();
$mockclass->expects($this->once())
->method('initalize_tags_with_quest_count')
->willReturn([]);
$mockclass->expects($this->never())
->method('retrieve_tag');
$mockclass->expects($this->never())
->method('find_questions_with_tags');
$result = $mockclass->fetch_questions();
$this->assertEquals([], $result);
}
/**
* This function test output from fetch_question() where the initial requested level has available questions
*/
public function test_fetch_question_requested_level_has_questions() {
$this->resetAfterTest(true);
$mockclass = $this
->getMockBuilder(fetchquestion::class)
->onlyMethods(
['initalize_tags_with_quest_count', 'retrieve_tag', 'find_questions_with_tags']
)
->setConstructorArgs(
[new stdClass(), 5, 1, 100]
)
->getMock();
$mockclass->expects($this->once())
->method('initalize_tags_with_quest_count')
->with([], ['adpq_'], '1', '100')
->willReturn(
[5 => 2]
);
$mockclass->expects($this->once())
->method('retrieve_tag')
->with(5)
->willReturn(
[11]
);
$mockclass->expects($this->once())
->method('find_questions_with_tags')
->with(array(11), array())
->willReturn(
[22]
);
$result = $mockclass->fetch_questions();
$this->assertEquals([22], $result);
}
/**
* This function test output from fetch_question() where one level higher than requested level has available
* questions.
*/
public function test_fetch_question_one_level_higher_has_questions() {
$this->resetAfterTest(true);
$mockclass = $this
->getMockBuilder(fetchquestion::class)
->onlyMethods(
['initalize_tags_with_quest_count', 'retrieve_tag', 'find_questions_with_tags']
)
->setConstructorArgs(
[new stdClass(), 5, 1, 100]
)
->getMock();
$mockclass->expects($this->once())
->method('initalize_tags_with_quest_count')
->with([], ['adpq_'], '1', '100')
->willReturn(
[5 => 0, 6 => 1]
);
$mockclass->expects($this->once())
->method('retrieve_tag')
->with(6)
->willReturn(
[11]
);
$mockclass->expects($this->once())
->method('find_questions_with_tags')
->with([11], [])
->willReturn(
[22]
);
$result = $mockclass->fetch_questions();
$this->assertEquals([22], $result);
}
/**
* This function test output from fetch_question() where five levels higher than requested level has available
* questions.
*/
public function test_fetch_question_five_levels_higher_has_questions() {
$this->resetAfterTest(true);
$mockclass = $this
->getMockBuilder(fetchquestion::class)
->onlyMethods(
['initalize_tags_with_quest_count', 'retrieve_tag', 'find_questions_with_tags']
)
->setConstructorArgs(
[new stdClass(), 5, 1, 100]
)
->getMock();
$mockclass->expects($this->once())
->method('initalize_tags_with_quest_count')
->with([], ['adpq_'], '1', '100')
->willReturn(
[1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0, 7 => 0, 8 => 0, 9 => 0, 10 => 2]
);
$mockclass->expects($this->once())
->method('retrieve_tag')
->with(10)
->willReturn(
[11]
);
$mockclass->expects($this->once())
->method('find_questions_with_tags')
->with([11], [])
->willReturn(
[22]
);
$result = $mockclass->fetch_questions();
$this->assertEquals([22], $result);
}
/**
* This function test output from fetch_question() where four levels lower than requested level has available
* questions.
*/
public function test_fetch_question_four_levels_lower_has_questions() {
$this->resetAfterTest(true);
$mockclass = $this
->getMockBuilder(fetchquestion::class)
->onlyMethods(
['initalize_tags_with_quest_count', 'retrieve_tag', 'find_questions_with_tags']
)
->setConstructorArgs(
[new stdClass(), 5, 1, 100]
)
->getMock();
$mockclass->expects($this->once())
->method('initalize_tags_with_quest_count')
->with([], ['adpq_'], '1', '100')
->willReturn(
[1 => 1, 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0, 7 => 0, 8 => 0, 9 => 0, 10 => 0]
);
$mockclass->expects($this->once())
->method('retrieve_tag')
->with(1)
->willReturn(
[11]
);
$mockclass->expects($this->once())
->method('find_questions_with_tags')
->with([11], [])
->willReturn([22]);
$result = $mockclass->fetch_questions();
$this->assertEquals([22], $result);
}
/**
* This function test output from fetch_question() where searching for a question goes outside
* the min and max boundaries and stops the searching.
*/
public function test_fetch_question_search_outside_min_max_bounds() {
$this->resetAfterTest(true);
$mockclass = $this
->getMockBuilder(fetchquestion::class)
->onlyMethods(
['initalize_tags_with_quest_count', 'retrieve_tag', 'find_questions_with_tags']
)
->setConstructorArgs(
[new stdClass(), 50, 49, 51]
)
->getMock();
$mockclass->expects($this->once())
->method('initalize_tags_with_quest_count')
->with([], ['adpq_'], 49, 51)
->willReturn(
[48 => 1, 52 => 1]
);
$mockclass->expects($this->never())
->method('retrieve_tag');
$mockclass->expects($this->never())
->method('find_questions_with_tags');
$result = $mockclass->fetch_questions();
$this->assertEquals([], $result);
}
/**
* @test
*/
public function it_retrieves_all_tag_ids(): void {
$this->resetAfterTest();
$this->setup_test_data_xml();
$fetchquestion = new fetchquestion(new stdClass(), 5, 1, 100);
$result = $fetchquestion->retrieve_all_tag_ids(1, 100, ADAPTIVEQUIZ_QUESTION_TAG);
$this->assertEquals(
[5 => '1', 6 => '4', 7 => '5', 8 => '6', 9 => '7', 10 => '8'],
$result
);
}
/**
* @test
*/
public function it_throws_an_exception_when_retrieves_all_tag_ids_for_an_empty_tag_prefix(): void {
$fetchquestion = new fetchquestion(new stdClass(), 5, 1, 100);
$this->expectException('invalid_parameter_exception');
$fetchquestion->retrieve_all_tag_ids(1, 5, '');
}
/**
* This is a data provider for
* @return $data - an array with arrays of data
*/
public function constructor_throw_coding_exception_provider() {
$data = array(
array(0, 1, 100),
array(1, 100, 100),
array(1, 100, 99)
);
return $data;
}
/**
* This function tests throwing an exception by passing incorrect parameters
*
* @param int $level the difficulty level
* @param int $min the minimum level of the attempt
* @param int $max the maximum level of the attempt
* @dataProvider constructor_throw_coding_exception_provider
*/
public function test_constructor_throw_coding_exception($level, $min, $max) {
$this->resetAfterTest(true);
$this->expectException('coding_exception');
(new fetchquestion(new stdClass(), $level, $min, $max));
}
/**
* This function tests the output from initalize_tags_with_quest_count()
*/
public function test_initalize_tags_with_quest_count() {
$this->resetAfterTest();
$mockclass = $this
->getMockBuilder(fetchquestion::class)
->onlyMethods(
['retrieve_question_categories', 'retrieve_all_tag_ids', 'retrieve_tags_with_question_count']
)
->setConstructorArgs(
[new stdClass(), 1, 1, 100]
)
->getMock();
$mockclass->expects($this->once())
->method('retrieve_question_categories')
->willReturn(
[1 => 1, 2 => 2, 3 => 3]
);
$mockclass->expects($this->exactly(2))
->method('retrieve_all_tag_ids')
->withAnyParameters()
->willReturn(
[4 => 4, 5 => 5, 6 => 6]
);
$mockclass->expects($this->exactly(2))
->method('retrieve_tags_with_question_count')
->withAnyParameters()
->willReturn(
[
new questions_number_per_difficulty(1, 8),
new questions_number_per_difficulty(2, 3),
new questions_number_per_difficulty(5, 10),
]
);
$result = $mockclass->initalize_tags_with_quest_count([], ['test1_', 'test2_'], 1, 100);
$this->assertEquals([1 => 16, 2 => 6, 5 => 20], $result);
}
/**
* This function tests the output from initalize_tags_with_quest_count(), passing an already built difficulty question
* sum structure, forcing a rebuild.
*/
public function test_initalize_tags_with_quest_count_pre_built_quest_sum_struct_rebuild_true() {
$this->resetAfterTest();
$mockclass = $this
->getMockBuilder(fetchquestion::class)
->onlyMethods(
['retrieve_question_categories', 'retrieve_all_tag_ids', 'retrieve_tags_with_question_count']
)
->setConstructorArgs(
[new stdClass(), 1, 1, 100]
)
->getMock();
$mockclass->expects($this->once())
->method('retrieve_question_categories')
->willReturn(
[1 => 1, 2 => 2, 3 => 3]
);
$mockclass->expects($this->exactly(2))
->method('retrieve_all_tag_ids')
->withAnyParameters()
->willReturn(
[4 => 4, 5 => 5, 6 => 6]
);
$mockclass->expects($this->exactly(2))
->method('retrieve_tags_with_question_count')
->withAnyParameters()
->willReturn(
[
new questions_number_per_difficulty(1, 8),
new questions_number_per_difficulty(2, 3),
new questions_number_per_difficulty(5, 10),
]
);
$result = $mockclass->initalize_tags_with_quest_count([1, 2, 3, 4], ['test1_', 'test2_'], 1, 100, true);
$this->assertEquals([1 => 16, 2 => 6, 5 => 20], $result);
}
/**
* This function tests the output from decrement_question_sum_from_difficulty().
*/
public function test_decrement_question_sum_from_difficulty() {
$this->resetAfterTest(true);
$dummyclass = new stdClass();
$result = array(1 => 12);
$expected = array(1 => 11);
$fetchquestion = new fetchquestion($dummyclass, 1, 1, 2);
$result = $fetchquestion->decrement_question_sum_from_difficulty($result, 1);
$this->assertEquals($expected, $result);
}
/**
* This function tests the output from decrement_question_sum_from_difficulty(), using a key that doesn't exist.
*/
public function test_decrement_question_sum_from_difficulty_user_missing_key() {
$this->resetAfterTest(true);
$dummyclass = new stdClass();
$result = array(1 => 12);
$expected = array(1 => 12);
$fetchquestion = new fetchquestion($dummyclass, 1, 1, 2);
$result = $fetchquestion->decrement_question_sum_from_difficulty($result, 2);
$this->assertEquals($expected, $result);
}
}
<?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/>.
/**
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\local\report;
use basic_testcase;
use stdClass;
/**
* @covers \mod_adaptivequiz\local\report\questions_difficulty_range
*/
class questions_difficulty_range_test extends basic_testcase {
public function test_it_can_be_created_from_activity_record(): void {
$record = new stdClass();
$record->lowestlevel = 5;
$record->highestlevel = 25;
$range = questions_difficulty_range::from_activity_instance($record);
$this->assertEquals(5, $range->lowest_level());
$this->assertEquals(25, $range->highest_level());
}
}
<?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/>.
/**
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\local\report\users_attempts\user_preferences;
use basic_testcase;
use mod_adaptivequiz\local\report\users_attempts\filter\filter_options;
/**
* @covers \mod_adaptivequiz\local\report\users_attempts\user_preferences\filter_user_preferences
*/
class filter_user_preferences_test extends basic_testcase {
public function test_it_acquires_correct_default_values_when_unexpected_parameters_provided(): void {
$filter = filter_user_preferences::from_array([]);
$this->assertEquals(filter_options::users_option_default(), $filter->users());
$this->assertEquals(filter_options::INCLUDE_INACTIVE_ENROLMENTS_DEFAULT,
$filter->include_inactive_enrolments());
$filter = filter_user_preferences::from_array(['users' => 42, 'includeinactiveenrolments' => 5]);
$this->assertEquals(filter_options::users_option_default(), $filter->users());
$this->assertEquals(filter_options::INCLUDE_INACTIVE_ENROLMENTS_DEFAULT,
$filter->include_inactive_enrolments());
}
public function test_it_can_be_converted_to_array(): void {
$filterasarray = ['users' => filter_options::users_option_default(), 'includeinactiveenrolments' => 1];
$filter = filter_user_preferences::from_array($filterasarray);
$this->assertEquals($filterasarray, $filter->as_array());
}
}
<?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/>.
/**
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\local\report\users_attempts\user_preferences;
use advanced_testcase;
/**
* @covers \mod_adaptivequiz\local\report\users_attempts\user_preferences\user_preferences_repository
*/
class user_preferences_repository_test extends advanced_testcase {
public function test_it_stores_and_fetches_preferences(): void {
$this->resetAfterTest();
$preferences = user_preferences::from_array(['perpage' => 20, 'showinitialsbar' => 0]);
user_preferences_repository::save($preferences);
$this->assertEquals($preferences, user_preferences_repository::get());
}
public function test_it_avoids_querying_database_when_not_needed(): void {
global $DB;
$this->resetAfterTest();
$preferences = user_preferences::from_array(['perpage' => 20, 'showinitialsbar' => 0]);
// Check it will not query database to fetch preference after saving them.
$queriescountbefore = $DB->perf_get_reads();
user_preferences_repository::save($preferences);
user_preferences_repository::get();
$this->assertEquals($queriescountbefore, $DB->perf_get_reads());
// Check it will not query the database for subsequent fetches of preferences.
user_preferences_repository::get();
$this->assertEquals($queriescountbefore, $DB->perf_get_reads());
}
}
<?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/>.
/**
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\local\report\users_attempts\user_preferences;
use basic_testcase;
use mod_adaptivequiz\local\report\users_attempts\filter\filter_options;
use stdClass;
/**
* @covers \mod_adaptivequiz\local\report\users_attempts\user_preferences\user_preferences
*/
class user_preferences_test extends basic_testcase {
public function test_it_acquires_correct_default_values_when_provided_values_are_not_in_valid_range(): void {
$preferences = user_preferences::from_array(
['perpage' => 100, 'showinitialsbar' => 22, 'persistentfilter' => -1]
);
$this->assertEquals(15, $preferences->rows_per_page());
$this->assertTrue($preferences->show_initials_bar());
$this->assertFalse($preferences->persistent_filter());
$this->assertNull($preferences->filter());
$preferences = user_preferences::from_array([]);
$this->assertEquals(15, $preferences->rows_per_page());
$this->assertTrue($preferences->show_initials_bar());
$this->assertFalse($preferences->persistent_filter());
$this->assertNull($preferences->filter());
$preferencesobject = new stdClass();
$preferencesobject->perpage = -25;
$preferencesobject->showinitialsbar = 100;
$preferencesobject->persistentfilter = "12";
$preferences = user_preferences::from_plain_object($preferencesobject);
$this->assertEquals(15, $preferences->rows_per_page());
$this->assertTrue($preferences->show_initials_bar());
$this->assertFalse($preferences->persistent_filter());
$this->assertNull($preferences->filter());
}
public function test_it_can_acquire_and_loose_filter_preferences_while_preserving_previously_set_preferences(): void {
$preferences = user_preferences::from_array(['perpage' => 10, 'showinitialsbar' => 0, 'persistentfilter' => 1]);
$pfilterpreferencesarray = ['users' => filter_options::users_option_default(),
'includeinactiveenrolments' => filter_options::INCLUDE_INACTIVE_ENROLMENTS_DEFAULT];
$preferences = $preferences->with_filter_preference(filter_user_preferences::from_array($pfilterpreferencesarray));
$this->assertEquals(10, $preferences->rows_per_page());
$this->assertEquals(0, $preferences->show_initials_bar());
$this->assertEquals(1, $preferences->persistent_filter());
$this->assertEquals(filter_user_preferences::from_array($pfilterpreferencesarray), $preferences->filter());
$preferences = $preferences->without_filter_preference();
$this->assertEquals(10, $preferences->rows_per_page());
$this->assertEquals(0, $preferences->show_initials_bar());
$this->assertEquals(1, $preferences->persistent_filter());
$this->assertEquals(null, $preferences->filter());
}
public function test_it_can_be_converted_to_array(): void {
$preferences = user_preferences::defaults();
$this->assertEquals(['perpage' => 15, 'showinitialsbar' => 1, 'persistentfilter' => 0, 'filter' => null],
$preferences->as_array());
}
}
<?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/>.
/**
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\local\repository;
use advanced_testcase;
use coding_exception;
use context;
use context_course;
use core_question\local\bank\question_version_status;
use core_question_generator;
use core_tag_tag;
use dml_missing_record_exception;
use question_bank;
use stdClass;
/**
* @covers \mod_adaptivequiz\local\repository\questions_repository
*/
class questions_repository_test extends advanced_testcase {
public function test_it_can_count_adaptive_questions_in_pool(): void {
$this->resetAfterTest();
$generator = $this->getDataGenerator();
/** @var core_question_generator $questionsgenerator */
$questionsgenerator = $generator->get_plugin_generator('core_question');
$course = $generator->create_course();
$questionscat1 = $questionsgenerator->create_question_category(
['contextid' => context_course::instance($course->id)->id]
);
$question1 = $questionsgenerator->create_question('truefalse', null, ['category' => $questionscat1->id]);
$questionsgenerator->create_question_tag(
['questionid' => $question1->id, 'tag' => 'adpq_1']
);
$question2 = $questionsgenerator->create_question('truefalse', null, ['category' => $questionscat1->id]);
$questionsgenerator->create_question_tag(
['questionid' => $question2->id, 'tag' => 'adpq_2']
);
$question3 = $questionsgenerator->create_question('truefalse', null, ['category' => $questionscat1->id]);
$questionsgenerator->create_question_tag(
['questionid' => $question3->id, 'tag' => 'adpq_001']
);
$questionscat2 = $questionsgenerator->create_question_category(
['contextid' => context_course::instance($course->id)->id]
);
$this->assertEquals(1, questions_repository::count_adaptive_questions_in_pool_with_level(
[$questionscat1->id, $questionscat2->id], 1
));
$questionsgenerator->create_question('truefalse', null, ['category' => $questionscat2->id]);
$questionsgenerator->create_question_tag(
['questionid' => $question2->id, 'tag' => 'truefalse_1']
);
$this->assertEquals(1, questions_repository::count_adaptive_questions_in_pool_with_level(
[$questionscat1->id, $questionscat2->id], 1
));
$questionscat3 = $questionsgenerator->create_question_category(
['contextid' => context_course::instance($course->id)->id]
);
$this->assertEquals(0, questions_repository::count_adaptive_questions_in_pool_with_level([$questionscat3->id], 1));
}
public function test_it_can_count_questions_number_per_difficulty(): void {
$this->resetAfterTest();
$generator = $this->getDataGenerator();
/** @var core_question_generator $questionsgenerator */
$questionsgenerator = $generator->get_plugin_generator('core_question');
$course = $generator->create_course();
$category1 = $questionsgenerator->create_question_category(
['contextid' => context_course::instance($course->id)->id]
);
$category2 = $questionsgenerator->create_question_category(
['contextid' => context_course::instance($course->id)->id]
);
$question1 = $questionsgenerator->create_question('truefalse', null, ['category' => $category1->id]);
$question1tag = $this->create_question_tag($question1->id, 'adpq_1');
$question2 = $questionsgenerator->create_question('truefalse', null, ['category' => $category2->id]);
$question2tag = $this->create_question_tag($question2->id, 'adpq_4');
$question3 = $questionsgenerator->create_question('truefalse', null, ['category' => $category1->id]);
// Update one of the questions to populate several versions in the database.
$questionsgenerator->update_question($question3, null, ['name' => 'New question version']);
$question3tag = $this->create_question_tag($question3->id, 'adpq_4');
$draftquestion = $questionsgenerator->create_question('truefalse', null,
['category' => $category1->id, 'status' => question_version_status::QUESTION_STATUS_DRAFT]);
$draftquestiontag = $this->create_question_tag($draftquestion->id, 'adpq_5');
$hiddenquestion = $questionsgenerator->create_question('truefalse', null,
['category' => $category1->id, 'status' => question_version_status::QUESTION_STATUS_HIDDEN]);
$hiddenquestiontag = $this->create_question_tag($hiddenquestion->id, 'adpq_6');
self::assertEquals([
new questions_number_per_difficulty(1, 1),
new questions_number_per_difficulty(4, 2),
],
questions_repository::count_questions_number_per_difficulty(
[$question1tag->id, $question2tag->id, $question3tag->id, $draftquestiontag->id, $hiddenquestiontag->id],
[$category1->id, $category2->id]
)
);
}
public function test_it_finds_questions_with_tags(): void {
$this->resetAfterTest();
// It returns empty array when no tags or no question categories specified.
self::assertEquals([], questions_repository::find_questions_with_tags([], [1, 2], []));
self::assertEquals([], questions_repository::find_questions_with_tags([1, 2], [], []));
$generator = $this->getDataGenerator();
/** @var core_question_generator $questionsgenerator */
$questionsgenerator = $generator->get_plugin_generator('core_question');
$course = $generator->create_course();
$category1 = $questionsgenerator->create_question_category(
['contextid' => context_course::instance($course->id)->id]
);
$category2 = $questionsgenerator->create_question_category(
['contextid' => context_course::instance($course->id)->id]
);
$question1 = $questionsgenerator->create_question('truefalse', null,
['name' => 'Question 1', 'category' => $category1->id]);
$question1tag = $this->create_question_tag($question1->id, 'adpq_1');
$question2 = $questionsgenerator->create_question('truefalse', null,
['name' => 'Question 2', 'category' => $category2->id]);
// Update one of the questions to populate several versions in the database.
$question2 = $questionsgenerator->update_question($question2, null, ['name' => 'New question version']);
$question2tag = $this->create_question_tag($question2->id, 'adpq_4');
$draftquestion = $questionsgenerator->create_question('truefalse', null,
['category' => $category1->id, 'status' => question_version_status::QUESTION_STATUS_DRAFT]);
$draftquestiontag = $this->create_question_tag($draftquestion->id, 'adpq_5');
$hiddenquestion = $questionsgenerator->create_question('truefalse', null,
['category' => $category1->id, 'status' => question_version_status::QUESTION_STATUS_HIDDEN]);
$hiddenquestiontag = $this->create_question_tag($hiddenquestion->id, 'adpq_6');
$tagidlist = [$question1tag->id, $question2tag->id, $draftquestiontag->id, $hiddenquestiontag->id];
$categoryidlist = [$category1->id, $category2->id];
$result = questions_repository::find_questions_with_tags($tagidlist, $categoryidlist, []);
$expectedresult = [];
$expectedrecord = new stdClass;
$expectedrecord->id = $question1->id;
$expectedrecord->name = $question1->name;
$expectedresult[$question1->id] = $expectedrecord;
$expectedrecord = new stdClass;
$expectedrecord->id = $question2->id;
$expectedrecord->name = $question2->name;
$expectedresult[$question2->id] = $expectedrecord;
self::assertEquals($expectedresult, $result);
// It can handle excluded questions.
$result = questions_repository::find_questions_with_tags($tagidlist, $categoryidlist, [$question1->id]);
$expectedresult = [];
$expectedrecord = new stdClass;
$expectedrecord->id = $question2->id;
$expectedrecord->name = $question2->name;
$expectedresult[$question2->id] = $expectedrecord;
self::assertEquals($expectedresult, $result);
}
/**
* @throws dml_missing_record_exception
* @throws coding_exception
*/
private function create_question_tag(int $questionid, string $tagname): core_tag_tag {
$question = question_bank::load_question($questionid);
core_tag_tag::add_item_tag('core_question', 'question', $question->id,
context::instance_by_id($question->contextid), $tagname);
$tagbyname = core_tag_tag::get_by_name(0, $tagname);
if (!$tagbyname) {
throw new coding_exception('Could not find the tag by name, please, check data initialization steps.');
}
return $tagbyname;
}
}
<?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/>.
/**
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\local\repository;
use advanced_testcase;
use coding_exception;
use context_course;
use core_question_generator;
use core_tag_tag;
/**
* @covers \mod_adaptivequiz\local\repository\tags_repository
*/
class tags_repository_test extends advanced_testcase {
public function test_it_gets_question_level_to_tag_id_mapping_by_tag_names(): void {
$this->resetAfterTest();
$this->assertEquals(
[],
tags_repository::get_question_level_to_tag_id_mapping_by_tag_names(
['adpq_5', 'adpq_6']
)
);
$generator = $this->getDataGenerator();
/** @var core_question_generator $questionsgenerator */
$questionsgenerator = $generator->get_plugin_generator('core_question');
$course = $generator->create_course();
$questionscat = $questionsgenerator->create_question_category(
['contextid' => context_course::instance($course->id)->id]
);
$question1 = $questionsgenerator->create_question('shortanswer', null, ['category' => $questionscat->id]);
$questionsgenerator->create_question_tag(
['questionid' => $question1->id, 'tag' => 'adpq_5']
);
$question2 = $questionsgenerator->create_question('shortanswer', null, ['category' => $questionscat->id]);
$questionsgenerator->create_question_tag(
['questionid' => $question2->id, 'tag' => 'adpq_6']
);
$question3 = $questionsgenerator->create_question('shortanswer', null, ['category' => $questionscat->id]);
$questionsgenerator->create_question_tag(
['questionid' => $question3->id, 'tag' => 'adpq_7']
);
core_tag_tag::add_item_tag('core', 'course', $course->id, context_course::instance($course->id), 'adpq_5');
$map = tags_repository::get_question_level_to_tag_id_mapping_by_tag_names(
['adpq_5', 'adpq_6']
);
$questionstags = core_tag_tag::get_items_tags('core_question', 'question', [$question1->id, $question2->id]);
$tagidlist = array_map(function(array $itemtags): int {
return $itemtags[array_key_first($itemtags)]->id;
}, $questionstags);
$this->assertEquals(2, count($map));
$this->assertEquals(
[5 => array_shift($tagidlist), 6 => array_shift($tagidlist)],
$map
);
}
public function test_it_fails_to_get_question_level_to_tag_id_mapping_for_an_empty_array_of_tag_names(): void {
$this->expectException(coding_exception::class);
tags_repository::get_question_level_to_tag_id_mapping_by_tag_names([]);
}
public function test_it_gets_tag_id_list_by_tag_names(): void {
$this->resetAfterTest();
$this->assertEquals(
[],
tags_repository::get_tag_id_list_by_tag_names(
['adpq_10', 'adpq_11']
)
);
$generator = $this->getDataGenerator();
/** @var core_question_generator $questionsgenerator */
$questionsgenerator = $generator->get_plugin_generator('core_question');
$course = $generator->create_course();
$questionscat = $questionsgenerator->create_question_category(
['contextid' => context_course::instance($course->id)->id]
);
$question1 = $questionsgenerator->create_question('truefalse', null, ['category' => $questionscat->id]);
$questionsgenerator->create_question_tag(
['questionid' => $question1->id, 'tag' => 'adpq_10']
);
$question2 = $questionsgenerator->create_question('shortanswer', null, ['category' => $questionscat->id]);
$questionsgenerator->create_question_tag(
['questionid' => $question2->id, 'tag' => 'adpq_11']
);
$question3 = $questionsgenerator->create_question('truefalse', null, ['category' => $questionscat->id]);
$questionsgenerator->create_question_tag(
['questionid' => $question3->id, 'tag' => 'adpq_12']
);
core_tag_tag::add_item_tag('core', 'course', $course->id, context_course::instance($course->id), 'adpq_10');
$tagidlist = tags_repository::get_tag_id_list_by_tag_names(
['adpq_10', 'adpq_11']
);
$questionstags = core_tag_tag::get_items_tags('core_question', 'question', [$question1->id, $question2->id]);
$itemtagidlist = array_map(function(array $itemtags): int {
return $itemtags[array_key_first($itemtags)]->id;
}, $questionstags);
$this->assertEquals(2, count($tagidlist));
$this->assertEquals(
[0 => array_shift($itemtagidlist), 1 => array_shift($itemtagidlist)],
$tagidlist
);
}
public function test_it_fails_to_get_a_list_of_tag_id_for_an_empty_array_of_tag_names(): void {
$this->expectException(coding_exception::class);
tags_repository::get_tag_id_list_by_tag_names([]);
}
}
<?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 mod_adaptivequiz;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot .'/mod/adaptivequiz/locallib.php');
use advanced_testcase;
use context_course;
use context_module;
use mod_adaptivequiz\event\attempt_completed;
use mod_adaptivequiz\local\attempt\attempt_state;
/**
* Adaptive locallib.php PHPUnit tests.
*
* @package mod_adaptivequiz
* @copyright 2013 Remote-Learner {@link http://www.remote-learner.ca/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class locallib_test extends advanced_testcase {
/**
* This functions loads data via the tests/fixtures/mod_adaptivequiz.xml file
*/
protected function setup_test_data_xml() {
$this->dataset_from_files(
[__DIR__.'/fixtures/mod_adaptivequiz.xml']
)->to_database();
}
/**
* Provide input data to the parameters of the test_count_user_previous_attempts_fail() method.
* @return $data an array with arrays of data
*/
public function fail_attempt_data() {
$data = array(
array(99, 99),
array(99, 3),
array(13, 99),
);
return $data;
}
/**
* Provide input data to the parameters of the test_allowed_attempt_fail() method.
* @return $data an array with arrays of data
*/
public function attempts_allowed_data_fail() {
$data = array(
array(99, 100),
array(99, 99),
);
return $data;
}
/**
* Provide input data to the parameters of the test_allowed_attempt() method.
* @return $data an array with arrays of data
*/
public function attempts_allowed_data() {
$data = array(
array(99, 98),
array(0, 99),
);
return $data;
}
/**
* Provide input data to the parameters of the test_adaptivequiz_construct_view_report_orderby() method.
*/
public function view_reports_data(): array {
return [
['firstname', 'ASC'],
['firstname', 'DESC'],
['lastname', 'ASC'],
['lastname', 'DESC'],
['attempts', 'ASC'],
['attempts', 'DESC'],
['stderror', 'ASC'],
['stderror', 'DESC'],
];
}
/**
* Test retrieving an array of question categories.
*
* @covers ::adaptivequiz_get_question_categories
*/
public function test_get_question_categories() {
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$coursecontext = context_course::instance($course->id);
adaptivequiz_make_default_categories($coursecontext);
// Test it returns data for both course and activity contexts.
$data = adaptivequiz_get_question_categories($coursecontext);
$this->assertEquals(1, count($data));
$questioncategory = $this->getDataGenerator()
->get_plugin_generator('core_question')
->create_question_category(['name' => 'My category']);
$adaptivequiz = $this->getDataGenerator()
->get_plugin_generator('mod_adaptivequiz')
->create_instance([
'course' => $course->id,
'questionpool' => [$questioncategory->id],
]);
$cm = get_coursemodule_from_instance('adaptivequiz', $adaptivequiz->id);
$activitycontext = context_module::instance($cm->id);
$data = adaptivequiz_get_question_categories($activitycontext);
$this->assertEquals(2, count($data));
}
/**
* Test retrieving question categories used by the activity instance.
*
* @covers ::adaptivequiz_get_selected_question_cateogires
*/
public function test_get_selected_question_cateogires() {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$data = adaptivequiz_get_selected_question_cateogires(12);
$this->assertEquals(6, count($data));
}
/**
* This function tests failing conditions for counting user's previous attempts
* that have been marked as completed.
*
* @dataProvider fail_attempt_data
* @param int $instanceid activity instance id
* @param int $userid user id
* @covers ::adaptivequiz_count_user_previous_attempts
*/
public function test_count_user_previous_attempts_fail($instanceid, $userid) {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$result = adaptivequiz_count_user_previous_attempts($instanceid, $userid);
$this->assertEquals(0, $result);
}
/**
* This function tests a non-failing conditions for counting user's previous attempts
* that have been marked as completed.
*
* @covers ::adaptivequiz_count_user_previous_attempts
*/
public function test_count_user_previous_attempts_inprogress() {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$result = adaptivequiz_count_user_previous_attempts(13, 3);
$this->assertEquals(1, $result);
}
/**
* This function tests failing conditions for determining whether a user is allowed
* further attemtps at the activity.
*
* @dataProvider attempts_allowed_data_fail
* @param int $maxattempts the maximum number of attempts allowed
* @param int $attempts the number of attempts taken thus far
* @covers ::adaptivequiz_allowed_attempt
*/
public function test_allowed_attempt_no_more_attempts_allowed($maxattempts, $attempts) {
$data = adaptivequiz_allowed_attempt($maxattempts, $attempts);
$this->assertFalse($data);
}
/**
* This function tests failing conditions for determining whether a user is allowed
* further attemtps at the activity.
*
* @dataProvider attempts_allowed_data
* @param int $maxattempts the maximum number of attempts allowed
* @param int $attempts the number of attempts taken thus far
* @covers ::adaptivequiz_allowed_attempt
*/
public function test_allowed_attempt($maxattempts, $attempts) {
$data = adaptivequiz_allowed_attempt($maxattempts, $attempts);
$this->assertTrue($data);
}
/**
* This function tests adaptivequiz_uniqueid_part_of_attempt().
*
* @covers ::adaptivequiz_uniqueid_part_of_attempt
*/
public function test_adaptivequiz_uniqueid_part_of_attempt() {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
// Assert that there exists a record where the uniqueid, activity instance and userid all match up.
$result = adaptivequiz_uniqueid_part_of_attempt(3, 1, 2);
$this->assertTrue($result);
$result = adaptivequiz_uniqueid_part_of_attempt(1, 1, 1);
$this->assertFalse($result);
}
/**
* This function tests the updating of the attempt data.
*
* @covers ::adaptivequiz_update_attempt_data
*/
public function test_adaptivequiz_update_attempt_data() {
global $DB;
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$result = adaptivequiz_update_attempt_data(3, 13, 3, 50, 0.002, 0.99);
$record = $DB->get_record('adaptivequiz_attempt', ['id' => 2]);
$this->assertTrue($result);
$this->assertEquals(51, $record->difficultysum);
$this->assertEquals(1, $record->questionsattempted);
$this->assertEquals(0.002, $record->standarderror);
$this->assertEquals(0.99, $record->measure);
}
/**
* This function tests the updating of the attempt data.
*
* @covers ::adaptivequiz_update_attempt_data
*/
public function test_adaptivequiz_update_attempt_data_using_infinite_value() {
global $DB;
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$result = adaptivequiz_update_attempt_data(3, 13, 3, -INF, 0.02, 0.1);
$record = $DB->get_record('adaptivequiz_attempt', ['id' => 2]);
$this->assertFalse($result);
}
/**
* This function tests completing an attempt.
*
* @covers ::adaptivequiz_complete_attempt
*/
public function test_adaptivequiz_complete_attempt() {
global $DB;
$this->resetAfterTest();
$this->setup_test_data_xml();
$adaptivequizid = 1;
$cmid = 5;
$userid = 2;
$attemptid = 1;
$adaptivequiz = $DB->get_record('adaptivequiz', ['id' => $adaptivequizid]);
$context = context_module::instance($cmid);
adaptivequiz_complete_attempt(3, $adaptivequiz, $context, $userid, '1', 'php unit test');
$attempt = $DB->get_record('adaptivequiz_attempt', ['id' => $attemptid]);
$this->assertEquals('php unit test', $attempt->attemptstopcriteria);
$this->assertEquals(attempt_state::COMPLETED, $attempt->attemptstate);
$this->assertEquals('1.00000', $attempt->standarderror);
}
/**
* @test
* @covers ::adaptivequiz_complete_attempt
*/
public function event_is_triggered_on_attempt_completion(): void {
global $DB;
$this->resetAfterTest();
$this->setup_test_data_xml();
$eventsink = $this->redirectEvents();
// This comes from fixtures unless we switch over to using generators.
$adaptivequizid = 1;
$cmid = 5;
$userid = 2;
$attemptid = 1;
$attempt = $DB->get_record('adaptivequiz_attempt', ['id' => $attemptid]);
$adaptivequiz = $DB->get_record('adaptivequiz', ['id' => $adaptivequizid]);
$context = context_module::instance($cmid);
adaptivequiz_complete_attempt(3, $adaptivequiz, $context, $userid, '1', 'php unit test');
$events = $eventsink->get_events();
$attemptcompletedevent = null;
foreach ($events as $event) {
if ($event instanceof attempt_completed) {
$attemptcompletedevent = $event;
break;
}
}
$this->assertNotNull($attemptcompletedevent,
sprintf('Failed asserting that event %s was triggered.', attempt_completed::class));
$this->assertEquals($attemptid, $event->objectid);
$this->assertEquals($context, $attemptcompletedevent->get_context());
$this->assertEquals($userid, $event->userid);
$this->assertEquals($attempt, $event->get_record_snapshot('adaptivequiz_attempt', $attemptid));
$this->assertEquals($adaptivequiz, $event->get_record_snapshot('adaptivequiz', $adaptivequizid));
}
/**
* This function tests checking if the minimum number of questions have been attempted.
*
* @covers ::adaptivequiz_min_attempts_reached
*/
public function test_adaptivequiz_min_attempts_reached() {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$result = adaptivequiz_min_attempts_reached(3, 13, 3);
$this->assertFalse($result);
$result = adaptivequiz_min_attempts_reached(4, 13, 4);
$this->assertTrue($result);
}
}
<?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/>.
/**
* PHPUnit tests for the renderer class.
*
* @copyright 2013 Remote-Learner {@link http://www.remote-learner.ca/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot.'/mod/adaptivequiz/locallib.php');
require_once($CFG->dirroot.'/mod/adaptivequiz/renderer.php');
require_once($CFG->dirroot.'/tag/lib.php');
use advanced_testcase;
use mod_adaptivequiz_renderer;
use moodle_page;
use moodle_url;
use stdClass;
/**
* @group mod_adaptivequiz
* @covers \mod_adaptivequiz_renderer
*/
class renderer_test extends advanced_testcase {
/**
* This function tests the output from the get_js_module.
*/
public function test_adaptivequiz_get_js_module(): void {
$dummypage = new moodle_page();
$target = 'mod_adaptivequiz';
$renderer = new mod_adaptivequiz_renderer($dummypage, $target);
$output = $renderer->adaptivequiz_get_js_module();
$this->assertArrayHasKey('name', $output);
$this->assertContains('mod_adaptivequiz', $output);
$this->assertArrayHasKey('fullpath', $output);
$this->assertContains('/mod/adaptivequiz/module.js', $output);
$this->assertArrayHasKey('requires', $output);
$this->assertEquals(
['base', 'dom', 'event-delegate', 'event-key', 'core_question_engine', 'moodle-core-formchangechecker'],
$output['requires']
);
$this->assertArrayHasKey('strings', $output);
}
/**
* This function tests how init_metadata() handlss an integer
*/
public function test_init_metadata_with_integer() {
$dummypage = new moodle_page();
$target = 'mod_adaptivequiz';
$renderer = new mod_adaptivequiz_renderer($dummypage, $target);
$mockquba = $this->createPartialMock('question_usage_by_activity', ['render_question_head_html']);
$mockquba->expects($this->once())
->method('render_question_head_html')
->willReturn('');
// Only testing that the mock object's method is called once.
$renderer->init_metadata($mockquba, 1);
}
/**
* This function tests the output from print_form_and_button()
*/
public function test_print_form_and_button() {
$dummypage = new moodle_page();
$target = 'mod_adaptivequiz';
$renderer = new mod_adaptivequiz_renderer($dummypage, $target);
$url = new moodle_url('/test/phpunittest/test.php', array('cmid' => 99));
$text = 'phpunit test button';
$output = $renderer->print_form_and_button($url, $text);
$this->assertStringContainsString('<form', $output);
$this->assertStringContainsString('<input', $output);
$this->assertStringContainsString('type="submit"', $output);
$this->assertStringContainsString('/test/phpunittest/test.php', $output);
$this->assertStringContainsString('cmid=99', $output);
$this->assertStringContainsString('phpunit test button', $output);
$this->assertStringContainsString('<center>', $output);
$this->assertStringContainsString('</center>', $output);
$this->assertStringContainsString('</form>', $output);
}
/**
* This function tests the output from format_report_table_headers()
*/
public function test_format_report_table_headers() {
$dummypage = new moodle_page();
$target = 'mod_adaptivequiz';
$renderer = new mod_adaptivequiz_renderer($dummypage, $target);
$dummycm = new stdClass();
$dummycm->id = 99;
$output = $renderer->format_report_table_headers($dummycm, 'stderror', 'ASC');
$this->assertEquals(6, count($output));
$this->assertStringContainsString('/mod/adaptivequiz/viewreport.php', $output[0]);
$this->assertStringContainsString('sort=firstname&amp;sortdir=ASC', $output[0]);
$this->assertStringContainsString('sort=lastname&amp;sortdir=ASC', $output[0]);
$this->assertStringContainsString('/mod/adaptivequiz/viewreport.php', $output[1]);
$this->assertStringContainsString('sort=email&amp;sortdir=ASC', $output[1]);
$this->assertStringContainsString('/mod/adaptivequiz/viewreport.php', $output[2]);
$this->assertStringContainsString('sort=attempts&amp;sortdir=ASC', $output[2]);
$this->assertStringContainsString('/mod/adaptivequiz/viewreport.php', $output[3]);
$this->assertStringContainsString('sort=measure&amp;sortdir=ASC', $output[3]);
$this->assertStringContainsString('/mod/adaptivequiz/viewreport.php', $output[4]);
$this->assertStringContainsString('sort=stderror&amp;sortdir=DESC', $output[4]);
$this->assertStringContainsString('/mod/adaptivequiz/viewreport.php', $output[5]);
$this->assertStringContainsString('sort=timemodified&amp;sortdir=ASC', $output[5]);
}
}
<?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/>.
/**
* Plugin basic info.
*
* @package mod_adaptivequiz
* @copyright 2013 Remote-Learner {@link http://www.remote-learner.ca/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2025072001;
$plugin->release = '2.4.0';
$plugin->maturity = MATURITY_STABLE;
$plugin->requires = 2024042200;
$plugin->cron = 0;
$plugin->component = 'mod_adaptivequiz';
<?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/>.
/**
* Adaptive testing main view page script.
*
* @package mod_adaptivequiz
* @copyright 2013 Remote-Learner {@link http://www.remote-learner.ca/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../config.php');
require_once($CFG->libdir . '/tablelib.php');
require_once($CFG->dirroot.'/mod/adaptivequiz/locallib.php');
use mod_adaptivequiz\local\report\questions_difficulty_range;
use mod_adaptivequiz\local\report\users_attempts\filter\filter;
use mod_adaptivequiz\local\report\users_attempts\filter\filter_form;
use mod_adaptivequiz\local\report\users_attempts\filter\filter_options;
use mod_adaptivequiz\local\report\users_attempts\user_preferences\filter_user_preferences;
use mod_adaptivequiz\local\user_attempts_table;
use mod_adaptivequiz\local\report\users_attempts\users_attempts_table;
use mod_adaptivequiz\local\report\users_attempts\user_preferences\user_preferences_form;
use mod_adaptivequiz\local\report\users_attempts\user_preferences\user_preferences_repository;
use mod_adaptivequiz\local\report\users_attempts\user_preferences\user_preferences;
use mod_adaptivequiz\output\user_attempt_summary;
$id = optional_param('id', 0, PARAM_INT);
$downloadusersattempts = optional_param('download', '', PARAM_ALPHA);
$n = optional_param('n', 0, PARAM_INT);
$resetfilter = optional_param('resetfilter', 0, PARAM_INT);
if ($id) {
$cm = get_coursemodule_from_id('adaptivequiz', $id, 0, false, MUST_EXIST);
$course = $DB->get_record('course', ['id' => $cm->course], '*', MUST_EXIST);
$adaptivequiz = $DB->get_record('adaptivequiz', ['id' => $cm->instance], '*', MUST_EXIST);
} else if ($n) {
$adaptivequiz = $DB->get_record('adaptivequiz', ['id' => $n], '*', MUST_EXIST);
$course = $DB->get_record('course', ['id' => $adaptivequiz->course], '*', MUST_EXIST);
$cm = get_coursemodule_from_instance('adaptivequiz', $adaptivequiz->id, $course->id, false, MUST_EXIST);
} else {
throw new moodle_exception('invalidarguments');
}
require_login($course, true, $cm);
$context = context_module::instance($cm->id);
$PAGE->set_url('/mod/adaptivequiz/view.php', ['id' => $cm->id]);
$PAGE->set_context($context);
$PAGE->add_body_class('limitedwidth');
/** @var mod_adaptivequiz_renderer $renderer */
$renderer = $PAGE->get_renderer('mod_adaptivequiz');
$canviewattemptsreport = has_capability('mod/adaptivequiz:viewreport', $context);
if ($canviewattemptsreport) {
$reportuserprefs = user_preferences_repository::get();
$reportuserprefsform = new user_preferences_form($PAGE->url->out());
if ($prefsformdata = $reportuserprefsform->get_data()) {
$reportuserprefs = user_preferences::from_plain_object($prefsformdata);
if (!$reportuserprefs->persistent_filter() && $reportuserprefs->has_filter_preference()) {
$reportuserprefs = $reportuserprefs->without_filter_preference();
}
user_preferences_repository::save($reportuserprefs);
}
$reportuserprefsform->set_data($reportuserprefs->as_array());
$filter = filter::from_vars($adaptivequiz->id, groups_get_activity_group($cm, true));
if ($resetfilter) {
$filter->fill_from_array(['users' => filter_options::users_option_default(),
'includeinactiveenrolments' => filter_options::INCLUDE_INACTIVE_ENROLMENTS_DEFAULT]);
}
$reportfilterform = new filter_form($PAGE->url->out(), ['actionurl' => $PAGE->url]);
if ($reportuserprefs->persistent_filter() && $reportuserprefs->has_filter_preference()) {
$filter->fill_from_preference($reportuserprefs->filter());
$reportfilterform->set_data($reportuserprefs->filter()->as_array());
}
if ($resetfilter) {
$filterdefaultsarray = ['users' => filter_options::users_option_default(),
'includeinactiveenrolments' => filter_options::INCLUDE_INACTIVE_ENROLMENTS_DEFAULT];
$filter->fill_from_array($filterdefaultsarray);
if ($reportuserprefs->persistent_filter()) {
user_preferences_repository::save(
$reportuserprefs->with_filter_preference(filter_user_preferences::from_array($filterdefaultsarray))
);
}
$reportfilterform->set_data($filterdefaultsarray);
}
if ($filterformdata = $reportfilterform->get_data()) {
$filter->fill_from_array((array) $filterformdata);
if ($reportuserprefs->persistent_filter()) {
user_preferences_repository::save(
$reportuserprefs->with_filter_preference(filter_user_preferences::from_array((array) $filterformdata))
);
}
}
$attemptsreporttable = new users_attempts_table($renderer, $cm->id,
questions_difficulty_range::from_activity_instance($adaptivequiz), $PAGE->url, $context, $filter);
$attemptsreporttable->is_downloading($downloadusersattempts,
get_string('reportattemptsdownloadfilename', 'adaptivequiz', format_string($adaptivequiz->name)));
if ($attemptsreporttable->is_downloading()) {
$attemptsreporttable->out(1, false);
exit;
}
}
$event = \mod_adaptivequiz\event\course_module_viewed::create([
'objectid' => $PAGE->cm->instance,
'context' => $PAGE->context,
]);
$event->add_record_snapshot('course', $PAGE->course);
$event->add_record_snapshot($PAGE->cm->modname, $adaptivequiz);
$event->trigger();
$PAGE->set_title(format_string($adaptivequiz->name));
$PAGE->set_heading(format_string($course->fullname));
echo $OUTPUT->header();
if (has_capability('mod/folder:managefiles', $context)) {
//KNIGHT: Capability managefiles checks if the current user is either an admin or a teacher (to show question check reminder)
$completedattemptscount = adaptivequiz_count_user_previous_attempts($adaptivequiz->id, $USER->id);
if($adaptivequiz->questionchecktrigger > 0 && $adaptivequiz->questionschecked == 0 && $completedattemptscount >= $adaptivequiz->questionchecktrigger) {
$notification = \core\notification::add(get_string('questioncheckmessage', 'adaptivequiz'), \core\output\notification::NOTIFY_INFO);
}
}
// if (has_capability('mod/adaptivequiz:attempt', $context)) {
// $completedattemptscount = adaptivequiz_count_user_previous_attempts($adaptivequiz->id, $USER->id);
// echo $renderer->container_start('attempt-controls-or-notification-container pb-3');
// echo $renderer->attempt_controls_or_notification($cm->id,
// adaptivequiz_allowed_attempt($adaptivequiz->attempts, $completedattemptscount), $adaptivequiz->browsersecurity);
// echo $renderer->container_end();
// $allattemptscount = $DB->count_records('adaptivequiz_attempt',
// ['instance' => $adaptivequiz->id, 'userid' => $USER->id]);
// // KNIGHT: user attempts table will only be shown if the teacher has selected 'Yes' for the option 'Show attempt results to students'.
// if($adaptivequiz->showownattemptresult || $adaptivequiz->showabilitymeasure){
// echo $renderer->heading(get_string('attemptsuserprevious', 'adaptivequiz'), 3);
// if($allattemptscount){
// $attemptstable = new user_attempts_table($renderer);
// $attemptstable->init($PAGE->url, $adaptivequiz, $USER->id);
// $attemptstable->out(10, false);
// } else {
// echo html_writer::div(get_string('attemptsusernoprevious', 'adaptivequiz'), 'alert alert-info text-center');
// }
// }
// }
if (has_capability('mod/adaptivequiz:attempt', $context)) {
$completedattemptscount = adaptivequiz_count_user_previous_attempts($adaptivequiz->id, $USER->id);
echo $renderer->container_start('attempt-controls-or-notification-container pb-3');
echo $renderer->attempt_controls_or_notification(
$cm->id,
adaptivequiz_allowed_attempt($adaptivequiz->attempts, $completedattemptscount),
$adaptivequiz->browsersecurity
);
echo $renderer->container_end();
// KNIGHT: Only show attempt summary or history if teacher enabled the setting.
if ($adaptivequiz->showownattemptresult || $adaptivequiz->showabilitymeasure) {
$allattemptscount = $DB->count_records('adaptivequiz_attempt', [
'instance' => $adaptivequiz->id,
'userid' => $USER->id
]);
if ($allattemptscount) {
// 📝 Summary of latest attempt
$sql = 'SELECT id, attemptstate, measure, timemodified
FROM {adaptivequiz_attempt}
WHERE instance = ? AND userid = ?
ORDER BY timemodified DESC';
if ($userattempts = $DB->get_records_sql($sql, [$adaptivequiz->id, $USER->id], 0, 1)) {
$userattempt = $userattempts[array_key_first($userattempts)];
echo $renderer->heading(get_string('attempt_summary', 'adaptivequiz'), 3, 'text-center');
echo $renderer->render(user_attempt_summary::from_db_records($userattempt, $adaptivequiz));
}
// Full attempt table (for multiple attempts)
echo $renderer->heading(get_string('attemptsuserprevious', 'adaptivequiz'), 3);
$attemptstable = new user_attempts_table($renderer);
$attemptstable->init($PAGE->url, $adaptivequiz, $USER->id);
$attemptstable->out(10, false);
} else {
echo html_writer::div(get_string('attemptsusernoprevious', 'adaptivequiz'), 'alert alert-info text-center');
}
}
}
if ($canviewattemptsreport) {
echo $renderer->heading(get_string('activityreports', 'adaptivequiz'), '3', 'text-center');
groups_print_activity_menu($cm, new moodle_url('/mod/adaptivequiz/view.php', ['id' => $cm->id]));
echo $renderer->container_start('usersattemptstable-wrapper');
$attemptsreporttable->out($reportuserprefs->rows_per_page(), $reportuserprefs->show_initials_bar());
echo $renderer->container_end();
$reportuserprefsform->display();
$reportfilterform->display();
$resetfilterul = $PAGE->url;
$resetfilterul->param('resetfilter', 1);
echo $renderer->reset_users_attempts_filter_action($resetfilterul);
}
echo $OUTPUT->footer();
<?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/>.
/**
* Adaptive quiz view attempt report script.
*
* @copyright 2013 onwards Remote-Learner {@link http://www.remote-learner.ca/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(dirname(__FILE__).'/../../config.php');
require_once($CFG->dirroot.'/mod/adaptivequiz/locallib.php');
use mod_adaptivequiz\local\report\individual_user_attempts\filter as user_attempts_table;
use mod_adaptivequiz\local\report\questions_difficulty_range;
use mod_adaptivequiz\local\report\individual_user_attempts\table as individual_user_attempts_table;
$id = required_param('cmid', PARAM_INT);
$userid = required_param('userid', PARAM_INT);
$cm = get_coursemodule_from_id('adaptivequiz', $id, 0, false, MUST_EXIST);
$adaptivequiz = $DB->get_record('adaptivequiz', ['id' => $cm->instance], '*', MUST_EXIST);
$course = $DB->get_record('course', ['id' => $cm->course], '*', MUST_EXIST);
$user = $DB->get_record('user', ['id' => $userid], '*', MUST_EXIST);
require_login($course, true, $cm);
$context = context_module::instance($cm->id);
require_capability('mod/adaptivequiz:viewreport', $context);
$PAGE->set_context($context);
$PAGE->set_url('/mod/adaptivequiz/viewattemptreport.php', ['cmid' => $cm->id, 'userid' => $user->id]);
$a = new stdClass();
$a->quizname = format_string($adaptivequiz->name);
$a->username = fullname($user);
$title = get_string('reportindividualuserattemptpageheading', 'adaptivequiz', $a);
$PAGE->set_title($title);
$PAGE->set_heading(format_string($course->fullname));
/** @var mod_adaptivequiz_renderer $renderer */
$renderer = $PAGE->get_renderer('mod_adaptivequiz');
$header = $renderer->print_header();
$footer = $renderer->print_footer();
echo $header;
echo $renderer->heading($title);
$attemptstable = new individual_user_attempts_table(
$renderer,
user_attempts_table::from_vars($cm->instance, $user->id),
$PAGE->url,
questions_difficulty_range::from_activity_instance($adaptivequiz),
$cm->id
);
$attemptstable->out(20, false);
echo $footer;
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment