1441 |
ariadna |
1 |
<?php
|
|
|
2 |
// This file is part of Moodle - http://moodle.org/
|
|
|
3 |
//
|
|
|
4 |
// Moodle is free software: you can redistribute it and/or modify
|
|
|
5 |
// it under the terms of the GNU General Public License as published by
|
|
|
6 |
// the Free Software Foundation, either version 3 of the License, or
|
|
|
7 |
// (at your option) any later version.
|
|
|
8 |
//
|
|
|
9 |
// Moodle is distributed in the hope that it will be useful,
|
|
|
10 |
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
11 |
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
12 |
// GNU General Public License for more details.
|
|
|
13 |
//
|
|
|
14 |
// You should have received a copy of the GNU General Public License
|
|
|
15 |
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
|
|
16 |
|
|
|
17 |
namespace core_question;
|
|
|
18 |
|
|
|
19 |
use core_question\local\bank\question_bank_helper;
|
|
|
20 |
|
|
|
21 |
/**
|
|
|
22 |
* question bank helper class tests.
|
|
|
23 |
*
|
|
|
24 |
* @package core_question
|
|
|
25 |
* @copyright 2024 onwards Catalyst IT EU {@link https://catalyst-eu.net}
|
|
|
26 |
* @author Simon Adams <simon.adams@catalyst-eu.net>
|
|
|
27 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
28 |
* @covers \core_question\local\bank\question_bank_helper
|
|
|
29 |
*/
|
|
|
30 |
final class question_bank_helper_test extends \advanced_testcase {
|
|
|
31 |
|
|
|
32 |
/**
|
|
|
33 |
* Assert that at least 1 module type that shares questions exists and that mod_qbank is in the returned list.
|
|
|
34 |
*
|
|
|
35 |
* @return void
|
|
|
36 |
* @covers ::get_activity_types_with_shareable_questions
|
|
|
37 |
*/
|
|
|
38 |
public function test_get_shareable_modules(): void {
|
|
|
39 |
$openmods = question_bank_helper::get_activity_types_with_shareable_questions();
|
|
|
40 |
$this->assertGreaterThanOrEqual(1, count($openmods));
|
|
|
41 |
$this->assertContains('qbank', $openmods);
|
|
|
42 |
$this->assertNotContains('quiz', $openmods);
|
|
|
43 |
}
|
|
|
44 |
|
|
|
45 |
/**
|
|
|
46 |
* Assert that at least 1 module type that does not share questions exists and that mod_quiz is in the returned list.
|
|
|
47 |
*
|
|
|
48 |
* @return void
|
|
|
49 |
* @covers ::get_activity_types_with_private_questions
|
|
|
50 |
*/
|
|
|
51 |
public function test_get_private_modules(): void {
|
|
|
52 |
$closedmods = question_bank_helper::get_activity_types_with_private_questions();
|
|
|
53 |
$this->assertGreaterThanOrEqual(1, count($closedmods));
|
|
|
54 |
$this->assertContains('quiz', $closedmods);
|
|
|
55 |
$this->assertNotContains('qbank', $closedmods);
|
|
|
56 |
}
|
|
|
57 |
|
|
|
58 |
/**
|
|
|
59 |
* Setup some courses with quiz and qbank module instances and set different permissions for a user.
|
|
|
60 |
* Then assert that the correct results are returned from calls to the class methods.
|
|
|
61 |
*
|
|
|
62 |
* @covers ::get_activity_instances_with_shareable_questions
|
|
|
63 |
* @covers ::get_activity_instances_with_private_questions
|
|
|
64 |
* @return void
|
|
|
65 |
*/
|
|
|
66 |
public function test_get_instances(): void {
|
|
|
67 |
global $DB;
|
|
|
68 |
|
|
|
69 |
$this->resetAfterTest();
|
|
|
70 |
$user = self::getDataGenerator()->create_user();
|
|
|
71 |
$roles = $DB->get_records('role', [], '', 'shortname, id');
|
|
|
72 |
self::setUser($user);
|
|
|
73 |
|
|
|
74 |
$qgen = self::getDataGenerator()->get_plugin_generator('core_question');
|
|
|
75 |
$sharedmodgen = self::getDataGenerator()->get_plugin_generator('mod_qbank');
|
|
|
76 |
$privatemodgen = self::getDataGenerator()->get_plugin_generator('mod_quiz');
|
|
|
77 |
$category1 = self::getDataGenerator()->create_category();
|
|
|
78 |
$category2 = self::getDataGenerator()->create_category();
|
|
|
79 |
$course1 = self::getDataGenerator()->create_course(['category' => $category1->id]);
|
|
|
80 |
$course2 = self::getDataGenerator()->create_course(['category' => $category1->id]);
|
|
|
81 |
$course3 = self::getDataGenerator()->create_course(['category' => $category2->id]);
|
|
|
82 |
$course4 = self::getDataGenerator()->create_course(['category' => $category2->id]);
|
|
|
83 |
|
|
|
84 |
$sharedmod1 = $sharedmodgen->create_instance(['course' => $course1]);
|
|
|
85 |
$sharedmod1context = \context_module::instance($sharedmod1->cmid);
|
|
|
86 |
$sharedmod1qcat1 = question_get_default_category($sharedmod1context->id);
|
|
|
87 |
$sharedmod1qcat2 = $qgen->create_question_category(['contextid' => $sharedmod1context->id]);
|
|
|
88 |
$sharedmod1qcat2child = $qgen->create_question_category([
|
|
|
89 |
'contextid' => $sharedmod1context->id,
|
|
|
90 |
'parent' => $sharedmod1qcat2->id,
|
|
|
91 |
'name' => 'A, B, C',
|
|
|
92 |
]);
|
|
|
93 |
$privatemod1 = $privatemodgen->create_instance(['course' => $course1]);
|
|
|
94 |
$privatemod1context = \context_module::instance($privatemod1->cmid);
|
|
|
95 |
$privatemod1qcat1 = question_get_default_category($privatemod1context->id);
|
|
|
96 |
role_assign($roles['editingteacher']->id, $user->id, \context_module::instance($sharedmod1->cmid));
|
|
|
97 |
role_assign($roles['editingteacher']->id, $user->id, \context_module::instance($privatemod1->cmid));
|
|
|
98 |
|
|
|
99 |
$sharedmod2 = $sharedmodgen->create_instance(['course' => $course2]);
|
|
|
100 |
$sharedmod2context = \context_module::instance($sharedmod2->cmid);
|
|
|
101 |
$sharedmod2qcat1 = question_get_default_category($sharedmod2context->id);
|
|
|
102 |
$sharedmod2qcat2 = $qgen->create_question_category(['contextid' => $sharedmod2context->id]);
|
|
|
103 |
$sharedmod2qcat2child = $qgen->create_question_category([
|
|
|
104 |
'contextid' => $sharedmod2context->id,
|
|
|
105 |
'parent' => $sharedmod2qcat2->id,
|
|
|
106 |
]);
|
|
|
107 |
$privatemod2 = $privatemodgen->create_instance(['course' => $course2]);
|
|
|
108 |
$privatemod2context = \context_module::instance($privatemod2->cmid);
|
|
|
109 |
$privatemod1qcat1 = question_get_default_category($privatemod2context->id);
|
|
|
110 |
role_assign($roles['editingteacher']->id, $user->id, \context_module::instance($sharedmod2->cmid));
|
|
|
111 |
role_assign($roles['editingteacher']->id, $user->id, \context_module::instance($privatemod2->cmid));
|
|
|
112 |
|
|
|
113 |
// User doesn't have the capability on this one.
|
|
|
114 |
$sharedmod3 = $sharedmodgen->create_instance(['course' => $course3]);
|
|
|
115 |
$privatemod3 = $privatemodgen->create_instance(['course' => $course3]);
|
|
|
116 |
|
|
|
117 |
// Exclude this course in the results despite having the capability.
|
|
|
118 |
$sharedmod4 = $sharedmodgen->create_instance(['course' => $course4]);
|
|
|
119 |
role_assign($roles['editingteacher']->id, $user->id, \context_module::instance($sharedmod4->cmid));
|
|
|
120 |
|
|
|
121 |
$sharedbanks = question_bank_helper::get_activity_instances_with_shareable_questions(
|
|
|
122 |
[],
|
|
|
123 |
[$course4->id],
|
|
|
124 |
['moodle/question:add'],
|
|
|
125 |
true
|
|
|
126 |
);
|
|
|
127 |
|
|
|
128 |
$count = 0;
|
|
|
129 |
foreach ($sharedbanks as $courseinstance) {
|
|
|
130 |
// Must all be mod_qbanks.
|
|
|
131 |
$this->assertEquals('qbank', $courseinstance->cminfo->modname);
|
|
|
132 |
// Must have 2 categories each bank.
|
|
|
133 |
$this->assertCount(3, $courseinstance->questioncategories);
|
|
|
134 |
// Must not include the bank the user does not have access to.
|
|
|
135 |
$this->assertNotEquals($sharedmod3->name, $courseinstance->name);
|
|
|
136 |
$this->assertNotEquals($privatemod3->name, $courseinstance->name);
|
|
|
137 |
$count++;
|
|
|
138 |
}
|
|
|
139 |
// Expect count of 2 bank instances.
|
|
|
140 |
$this->assertEquals(2, $count);
|
|
|
141 |
|
|
|
142 |
$privatebanks = question_bank_helper::get_activity_instances_with_private_questions(
|
|
|
143 |
[$course1->id],
|
|
|
144 |
[],
|
|
|
145 |
['moodle/question:add'],
|
|
|
146 |
true
|
|
|
147 |
);
|
|
|
148 |
|
|
|
149 |
$count = 0;
|
|
|
150 |
foreach ($privatebanks as $courseinstance) {
|
|
|
151 |
// Must all be mod_quiz.
|
|
|
152 |
$this->assertEquals('quiz', $courseinstance->cminfo->modname);
|
|
|
153 |
// Must have 1 category in each bank.
|
|
|
154 |
$this->assertCount(1, $courseinstance->questioncategories);
|
|
|
155 |
// Must only include the bank from course 1.
|
|
|
156 |
$this->assertNotContains($courseinstance->cminfo->course, [$course2->id, $course3->id, $course4->id]);
|
|
|
157 |
$count++;
|
|
|
158 |
}
|
|
|
159 |
// Expect count of 1 bank instances.
|
|
|
160 |
$this->assertEquals(1, $count);
|
|
|
161 |
}
|
|
|
162 |
|
|
|
163 |
/**
|
|
|
164 |
* We should be able to filter sharable question bank instances by name.
|
|
|
165 |
*
|
|
|
166 |
* @covers ::get_activity_instances_with_shareable_questions
|
|
|
167 |
* @return void
|
|
|
168 |
*/
|
|
|
169 |
public function test_get_instances_by_name(): void {
|
|
|
170 |
global $DB;
|
|
|
171 |
|
|
|
172 |
$this->resetAfterTest();
|
|
|
173 |
$user = self::getDataGenerator()->create_user();
|
|
|
174 |
$roles = $DB->get_records('role', [], '', 'shortname, id');
|
|
|
175 |
self::setUser($user);
|
|
|
176 |
|
|
|
177 |
$sharedmodgen = self::getDataGenerator()->get_plugin_generator('mod_qbank');
|
|
|
178 |
$category1 = self::getDataGenerator()->create_category();
|
|
|
179 |
$course1 = self::getDataGenerator()->create_course(['category' => $category1->id]);
|
|
|
180 |
role_assign($roles['editingteacher']->id, $user->id, \core\context\course::instance($course1->id));
|
|
|
181 |
|
|
|
182 |
$sharedmods = [];
|
|
|
183 |
for ($i = 1; $i <= 21; $i++) {
|
|
|
184 |
$sharedmods[$i] = $sharedmodgen->create_instance(['course' => $course1, 'name' => "Shared bank {$i}"]);
|
|
|
185 |
}
|
|
|
186 |
$sharedmods[22] = $sharedmodgen->create_instance(['course' => $course1, 'name' => "Another bank"]);
|
|
|
187 |
|
|
|
188 |
// We get all banks with no parameters.
|
|
|
189 |
$allsharedbanks = question_bank_helper::get_activity_instances_with_shareable_questions();
|
|
|
190 |
$this->assertCount(22, $allsharedbanks);
|
|
|
191 |
|
|
|
192 |
// Searching for "2", we get the 4 banks with "2" in the name.
|
|
|
193 |
$twobanks = question_bank_helper::get_activity_instances_with_shareable_questions(search: '2');
|
|
|
194 |
$this->assertCount(4, $twobanks);
|
|
|
195 |
$this->assertEquals(
|
|
|
196 |
[$sharedmods[2]->cmid, $sharedmods[12]->cmid, $sharedmods[20]->cmid, $sharedmods[21]->cmid],
|
|
|
197 |
array_map(fn($bank) => $bank->modid, $twobanks),
|
|
|
198 |
);
|
|
|
199 |
|
|
|
200 |
// Searching for "Shared bank" with no limit, we should get all 21, but not "Another bank".
|
|
|
201 |
$sharedbanks = question_bank_helper::get_activity_instances_with_shareable_questions(search: 'Shared bank');
|
|
|
202 |
$this->assertCount(21, $sharedbanks);
|
|
|
203 |
$this->assertEmpty(array_filter($sharedbanks, fn($bank) => in_array($bank->name, ['Another bank'])));
|
|
|
204 |
|
|
|
205 |
// Searching for "Shared bank" with a limit of 20, we should get all except number 21 and "Another bank".
|
|
|
206 |
$limitedbanks = question_bank_helper::get_activity_instances_with_shareable_questions(search: 'Shared bank', limit: 20);
|
|
|
207 |
$this->assertCount(20, $limitedbanks);
|
|
|
208 |
$this->assertEmpty(array_filter($limitedbanks, fn($bank) => in_array($bank->name, ['Shared bank 21', 'Another bank'])));
|
|
|
209 |
}
|
|
|
210 |
|
|
|
211 |
/**
|
|
|
212 |
* Assert creating a default mod_qbank instance on a course provides the expected boilerplate settings.
|
|
|
213 |
*
|
|
|
214 |
* @return void
|
|
|
215 |
* @covers ::create_default_open_instance
|
|
|
216 |
*/
|
|
|
217 |
public function test_create_default_open_instance(): void {
|
|
|
218 |
global $DB;
|
|
|
219 |
|
|
|
220 |
$this->resetAfterTest();
|
|
|
221 |
self::setAdminUser();
|
|
|
222 |
|
|
|
223 |
$course = self::getDataGenerator()->create_course();
|
|
|
224 |
|
|
|
225 |
// Create the instance and assert default values.
|
|
|
226 |
question_bank_helper::create_default_open_instance($course, $course->fullname);
|
|
|
227 |
$modinfo = get_fast_modinfo($course);
|
|
|
228 |
$cminfos = $modinfo->get_instances_of('qbank');
|
|
|
229 |
$this->assertCount(1, $cminfos);
|
|
|
230 |
$cminfo = reset($cminfos);
|
|
|
231 |
$this->assertEquals($course->fullname, $cminfo->get_name());
|
|
|
232 |
$this->assertEquals(0, $cminfo->sectionnum);
|
|
|
233 |
$modrecord = $DB->get_record('qbank', ['id' => $cminfo->instance]);
|
|
|
234 |
$this->assertEquals(question_bank_helper::TYPE_STANDARD, $modrecord->type);
|
|
|
235 |
$this->assertEmpty($cminfo->idnumber);
|
|
|
236 |
$this->assertEmpty($cminfo->content);
|
|
|
237 |
|
|
|
238 |
// Create a system type bank.
|
|
|
239 |
question_bank_helper::create_default_open_instance($course, 'System bank 1', question_bank_helper::TYPE_SYSTEM);
|
|
|
240 |
|
|
|
241 |
// Try and create another system type bank.
|
|
|
242 |
question_bank_helper::create_default_open_instance($course, 'System bank 2', question_bank_helper::TYPE_SYSTEM);
|
|
|
243 |
|
|
|
244 |
$modinfo = get_fast_modinfo($course);
|
|
|
245 |
$cminfos = $modinfo->get_instances_of('qbank');
|
|
|
246 |
$cminfos = array_filter($cminfos, static function($cminfo) {
|
|
|
247 |
global $DB;
|
|
|
248 |
return $DB->record_exists('qbank', ['id' => $cminfo->instance, 'type' => question_bank_helper::TYPE_SYSTEM]);
|
|
|
249 |
});
|
|
|
250 |
|
|
|
251 |
// Can only be 1 system 'type' bank per course.
|
|
|
252 |
$this->assertCount(1, $cminfos);
|
|
|
253 |
$cminfo = reset($cminfos);
|
|
|
254 |
$this->assertEquals('System bank 1', $cminfo->get_name());
|
|
|
255 |
$moddata = $DB->get_record('qbank', ['id' => $cminfo->instance]);
|
|
|
256 |
$this->assertEquals(get_string('systembankdescription', 'question'), $moddata->intro);
|
|
|
257 |
$this->assertEquals(1, $cminfo->showdescription);
|
|
|
258 |
}
|
|
|
259 |
|
|
|
260 |
/**
|
|
|
261 |
* Create a default instance, passing a name that is too long for the database.
|
|
|
262 |
*
|
|
|
263 |
* @return void
|
|
|
264 |
* @throws \coding_exception
|
|
|
265 |
* @throws \dml_exception
|
|
|
266 |
* @throws \moodle_exception
|
|
|
267 |
*/
|
|
|
268 |
public function test_create_default_open_instance_with_long_name(): void {
|
|
|
269 |
$this->resetAfterTest();
|
|
|
270 |
self::setAdminUser();
|
|
|
271 |
|
|
|
272 |
$coursename = random_string(question_bank_helper::BANK_NAME_MAX_LENGTH);
|
|
|
273 |
$course = self::getDataGenerator()->create_course(['shortname' => $coursename]);
|
|
|
274 |
|
|
|
275 |
$this->expectExceptionMessage('The provided bankname is too long for the database field.');
|
|
|
276 |
question_bank_helper::create_default_open_instance(
|
|
|
277 |
$course,
|
|
|
278 |
get_string('defaultbank', 'core_question', ['coursename' => $coursename]),
|
|
|
279 |
);
|
|
|
280 |
}
|
|
|
281 |
|
|
|
282 |
/**
|
|
|
283 |
* Create a default instance, passing a multibyte-character name.
|
|
|
284 |
*
|
|
|
285 |
* The name has more bytes than the max length, but is within the character limit as they are multibyte characters.
|
|
|
286 |
*/
|
|
|
287 |
public function test_create_default_open_instance_with_multibyte_name(): void {
|
|
|
288 |
$this->resetAfterTest();
|
|
|
289 |
self::setAdminUser();
|
|
|
290 |
|
|
|
291 |
$coursename = '';
|
|
|
292 |
while (strlen($coursename) < question_bank_helper::BANK_NAME_MAX_LENGTH) {
|
|
|
293 |
$coursename .= '🙂';
|
|
|
294 |
}
|
|
|
295 |
$course = self::getDataGenerator()->create_course(['shortname' => '🙂']);
|
|
|
296 |
$bankname = get_string('defaultbank', 'core_question', ['coursename' => $coursename]);
|
|
|
297 |
$this->assertTrue(strlen($bankname) > question_bank_helper::BANK_NAME_MAX_LENGTH);
|
|
|
298 |
$this->assertTrue(\core_text::strlen($bankname) < question_bank_helper::BANK_NAME_MAX_LENGTH);
|
|
|
299 |
|
|
|
300 |
question_bank_helper::create_default_open_instance($course, $bankname);
|
|
|
301 |
|
|
|
302 |
$modinfo = get_fast_modinfo($course);
|
|
|
303 |
$cminfos = $modinfo->get_instances_of('qbank');
|
|
|
304 |
$this->assertCount(1, $cminfos);
|
|
|
305 |
$cminfo = reset($cminfos);
|
|
|
306 |
$this->assertEquals($bankname, $cminfo->get_name());
|
|
|
307 |
}
|
|
|
308 |
|
|
|
309 |
/**
|
|
|
310 |
* Assert that viewing a question bank logs the view for that user up to a maximum of 5 unique bank views.
|
|
|
311 |
*
|
|
|
312 |
* @return void
|
|
|
313 |
* @covers ::get_recently_used_open_banks
|
|
|
314 |
* @covers ::add_bank_context_to_recently_viewed
|
|
|
315 |
*/
|
|
|
316 |
public function test_recently_viewed_question_banks(): void {
|
|
|
317 |
$this->resetAfterTest();
|
|
|
318 |
|
|
|
319 |
$user = self::getDataGenerator()->create_user();
|
|
|
320 |
$course1 = self::getDataGenerator()->create_course();
|
|
|
321 |
$course2 = self::getDataGenerator()->create_course();
|
|
|
322 |
self::getDataGenerator()->enrol_user($user->id, $course1->id, 'editingteacher');
|
|
|
323 |
$banks = [];
|
|
|
324 |
$banks[] = self::getDataGenerator()->create_module('qbank', ['course' => $course1->id]);
|
|
|
325 |
$banks[] = self::getDataGenerator()->create_module('qbank', ['course' => $course1->id]);
|
|
|
326 |
$banks[] = self::getDataGenerator()->create_module('qbank', ['course' => $course1->id]);
|
|
|
327 |
$banks[] = self::getDataGenerator()->create_module('qbank', ['course' => $course2->id]);
|
|
|
328 |
$banks[] = self::getDataGenerator()->create_module('qbank', ['course' => $course2->id]);
|
|
|
329 |
$banks[] = self::getDataGenerator()->create_module('qbank', ['course' => $course2->id]);
|
|
|
330 |
|
|
|
331 |
self::setUser($user);
|
|
|
332 |
|
|
|
333 |
// Trigger bank view on each of them.
|
|
|
334 |
foreach ($banks as $bank) {
|
|
|
335 |
$cat = question_get_default_category(\context_module::instance($bank->cmid)->id, true);
|
|
|
336 |
$context = \context::instance_by_id($cat->contextid);
|
|
|
337 |
question_bank_helper::add_bank_context_to_recently_viewed($context);
|
|
|
338 |
}
|
|
|
339 |
|
|
|
340 |
$viewedorder = array_reverse($banks);
|
|
|
341 |
// Check that the courseid filter works.
|
|
|
342 |
$recentlyviewed = question_bank_helper::get_recently_used_open_banks($user->id, $course1->id);
|
|
|
343 |
$this->assertCount(3, $recentlyviewed);
|
|
|
344 |
// We should have the viewed banks in course 2.
|
|
|
345 |
$courseviewed = array_slice($banks, 3, 3);
|
|
|
346 |
$this->assertEqualsCanonicalizing(array_column($recentlyviewed, 'modid'), array_column($courseviewed, 'cmid'));
|
|
|
347 |
|
|
|
348 |
// Check that the capability filter works.
|
|
|
349 |
$recentlyviewed = question_bank_helper::get_recently_used_open_banks($user->id, havingcap: ['moodle/question:useall']);
|
|
|
350 |
$this->assertCount(2, $recentlyviewed);
|
|
|
351 |
// We should have the 2 most recently viewed banks in course 1.
|
|
|
352 |
$capabilityviewed = array_slice($banks, 1, 2);
|
|
|
353 |
$this->assertEqualsCanonicalizing(array_column($recentlyviewed, 'modid'), array_column($capabilityviewed, 'cmid'));
|
|
|
354 |
|
|
|
355 |
$recentlyviewed = question_bank_helper::get_recently_used_open_banks($user->id);
|
|
|
356 |
|
|
|
357 |
// We only keep a record of 5 maximum.
|
|
|
358 |
$this->assertCount(5, $recentlyviewed);
|
|
|
359 |
foreach ($recentlyviewed as $order => $record) {
|
|
|
360 |
$this->assertEquals($viewedorder[$order]->cmid, $record->modid);
|
|
|
361 |
}
|
|
|
362 |
|
|
|
363 |
// Now if we view one of those again it should get bumped to the front of the list.
|
|
|
364 |
$bank3cat = question_get_default_category(\context_module::instance($banks[2]->cmid)->id, true);
|
|
|
365 |
$bank3context = \context::instance_by_id($bank3cat->contextid);
|
|
|
366 |
question_bank_helper::add_bank_context_to_recently_viewed($bank3context);
|
|
|
367 |
|
|
|
368 |
$recentlyviewed = question_bank_helper::get_recently_used_open_banks($user->id);
|
|
|
369 |
|
|
|
370 |
// We should still have 5 maximum.
|
|
|
371 |
$this->assertCount(5, $recentlyviewed);
|
|
|
372 |
// The recently viewed on got bumped to the front.
|
|
|
373 |
$this->assertEquals($banks[2]->cmid, $recentlyviewed[0]->modid);
|
|
|
374 |
// The others got sorted accordingly behind it.
|
|
|
375 |
$this->assertEquals($banks[5]->cmid, $recentlyviewed[1]->modid);
|
|
|
376 |
$this->assertEquals($banks[4]->cmid, $recentlyviewed[2]->modid);
|
|
|
377 |
$this->assertEquals($banks[3]->cmid, $recentlyviewed[3]->modid);
|
|
|
378 |
$this->assertEquals($banks[1]->cmid, $recentlyviewed[4]->modid);
|
|
|
379 |
|
|
|
380 |
// Now create a quiz and trigger the bank view of it.
|
|
|
381 |
$quiz = self::getDataGenerator()->get_plugin_generator('mod_quiz')->create_instance(['course' => $course1]);
|
|
|
382 |
$quizcat = question_get_default_category(\context_module::instance($quiz->cmid)->id, true);
|
|
|
383 |
$quizcontext = \context::instance_by_id($quizcat->contextid);
|
|
|
384 |
question_bank_helper::add_bank_context_to_recently_viewed($quizcontext);
|
|
|
385 |
|
|
|
386 |
$recentlyviewed = question_bank_helper::get_recently_used_open_banks($user->id);
|
|
|
387 |
// We should still have 5 maximum.
|
|
|
388 |
$this->assertCount(5, $recentlyviewed);
|
|
|
389 |
|
|
|
390 |
// Make sure that we only store bank views for plugins that support FEATURE_PUBLISHES_QUESTIONS.
|
|
|
391 |
foreach ($recentlyviewed as $record) {
|
|
|
392 |
$this->assertNotEquals($quiz->cmid, $record->modid);
|
|
|
393 |
}
|
|
|
394 |
|
|
|
395 |
// Now delete one of the viewed bank modules and get the records again.
|
|
|
396 |
course_delete_module($banks[2]->cmid);
|
|
|
397 |
$recentlyviewed = question_bank_helper::get_recently_used_open_banks($user->id);
|
|
|
398 |
$this->assertCount(4, $recentlyviewed);
|
|
|
399 |
|
|
|
400 |
// Check the order was retained.
|
|
|
401 |
$this->assertEquals($banks[5]->cmid, $recentlyviewed[0]->modid);
|
|
|
402 |
$this->assertEquals($banks[4]->cmid, $recentlyviewed[1]->modid);
|
|
|
403 |
$this->assertEquals($banks[3]->cmid, $recentlyviewed[2]->modid);
|
|
|
404 |
$this->assertEquals($banks[1]->cmid, $recentlyviewed[3]->modid);
|
|
|
405 |
}
|
|
|
406 |
|
|
|
407 |
/**
|
|
|
408 |
* Assert that getting a default qbank instance on a course works with and without the "$createifnotexists" argument.
|
|
|
409 |
*
|
|
|
410 |
* @return void
|
|
|
411 |
* @covers ::get_default_open_instance_system_type
|
|
|
412 |
*/
|
|
|
413 |
public function test_get_default_open_instance_system_type(): void {
|
|
|
414 |
global $DB;
|
|
|
415 |
|
|
|
416 |
$this->resetAfterTest();
|
|
|
417 |
self::setAdminUser();
|
|
|
418 |
|
|
|
419 |
$course = self::getDataGenerator()->create_course();
|
|
|
420 |
$modinfo = get_fast_modinfo($course);
|
|
|
421 |
$qbanks = $modinfo->get_instances_of('qbank');
|
|
|
422 |
$this->assertCount(0, $qbanks);
|
|
|
423 |
$qbank = question_bank_helper::get_default_open_instance_system_type($course);
|
|
|
424 |
$this->assertNull($qbank);
|
|
|
425 |
$qbank = question_bank_helper::get_default_open_instance_system_type($course, true);
|
|
|
426 |
$this->assertEquals(get_string('systembank', 'question'), $qbank->get_name());
|
|
|
427 |
$modrecord = $DB->get_record('qbank', ['id' => $qbank->instance]);
|
|
|
428 |
$this->assertEquals(question_bank_helper::TYPE_SYSTEM, $modrecord->type);
|
|
|
429 |
// Create module other than a qbank with an ID that isn't used by a qbank yet.
|
|
|
430 |
do {
|
|
|
431 |
$wiki = self::getDataGenerator()->create_module('wiki', [
|
|
|
432 |
'course' => $course->id,
|
|
|
433 |
]);
|
|
|
434 |
} while ($DB->record_exists('qbank', ['id' => $wiki->id]));
|
|
|
435 |
// Swap the qbank instance record for one with the same ID as the wiki instance.
|
|
|
436 |
$newqbank = clone($modrecord);
|
|
|
437 |
$newqbank->id = $wiki->id;
|
|
|
438 |
$DB->insert_record_raw('qbank', $newqbank, customsequence: true);
|
|
|
439 |
$DB->delete_records('qbank', ['id' => $qbank->id]);
|
|
|
440 |
$DB->set_field('course_modules', 'instance', $newqbank->id, ['instance' => $qbank->instance]);
|
|
|
441 |
// Retry the above again.
|
|
|
442 |
\course_modinfo::purge_course_caches([$course->id]);
|
|
|
443 |
$qbank = question_bank_helper::get_default_open_instance_system_type($course);
|
|
|
444 |
$this->assertEquals(get_string('systembank', 'question'), $qbank->get_name());
|
|
|
445 |
$modrecord = $DB->get_record('qbank', ['id' => $qbank->instance]);
|
|
|
446 |
$this->assertEquals(question_bank_helper::TYPE_SYSTEM, $modrecord->type);
|
|
|
447 |
}
|
|
|
448 |
|
|
|
449 |
/**
|
|
|
450 |
* Assert that get_bank_name_string returns suitably truncated strings.
|
|
|
451 |
*
|
|
|
452 |
* @dataProvider bank_name_strings
|
|
|
453 |
* @param string $identifier
|
|
|
454 |
* @param string $component
|
|
|
455 |
* @param mixed $params
|
|
|
456 |
* @param string $expected
|
|
|
457 |
*/
|
|
|
458 |
public function test_get_bank_name_string(string $identifier, string $component, mixed $params, string $expected): void {
|
|
|
459 |
$this->assertEquals($expected, question_bank_helper::get_bank_name_string($identifier, $component, $params));
|
|
|
460 |
}
|
|
|
461 |
|
|
|
462 |
/**
|
|
|
463 |
* Get string examples with different parameter types and lengths.
|
|
|
464 |
*
|
|
|
465 |
* @return array[]
|
|
|
466 |
*/
|
|
|
467 |
public static function bank_name_strings(): array {
|
|
|
468 |
$longname = 'One two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen ' .
|
|
|
469 |
'eighteen nineteen twenty twenty-one twenty-two twenty-three twenty-four twenty-five twenty-six twenty-seven ' .
|
|
|
470 |
'twenty-eight twenty-nine thirty thirty-one';
|
|
|
471 |
return [
|
|
|
472 |
'String with no parameters' => [
|
|
|
473 |
'systembank',
|
|
|
474 |
'question',
|
|
|
475 |
null,
|
|
|
476 |
'System shared question bank',
|
|
|
477 |
],
|
|
|
478 |
'String with short string parameter' => [
|
|
|
479 |
'topfor',
|
|
|
480 |
'question',
|
|
|
481 |
'Test course',
|
|
|
482 |
'Top for Test course',
|
|
|
483 |
],
|
|
|
484 |
'String with long string parameter' => [
|
|
|
485 |
'topfor',
|
|
|
486 |
'question',
|
|
|
487 |
$longname,
|
|
|
488 |
'Top for One two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen ' .
|
|
|
489 |
'seventeen eighteen nineteen twenty twenty-one twenty-two twenty-three twenty-four twenty-five twenty-six ' .
|
|
|
490 |
'twenty-seven twenty-eight ...',
|
|
|
491 |
],
|
|
|
492 |
'String with short array parameter' => [
|
|
|
493 |
'defaultbank',
|
|
|
494 |
'question',
|
|
|
495 |
['coursename' => 'Test course'],
|
|
|
496 |
'Test course course question bank',
|
|
|
497 |
],
|
|
|
498 |
'String with long array parameter' => [
|
|
|
499 |
'defaultbank',
|
|
|
500 |
'question',
|
|
|
501 |
['coursename' => $longname],
|
|
|
502 |
'One two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen ' .
|
|
|
503 |
'eighteen nineteen twenty twenty-one twenty-two twenty-three twenty-four twenty-five twenty-six ' .
|
|
|
504 |
'twenty-seven twenty-eight ... course question bank',
|
|
|
505 |
],
|
|
|
506 |
'String with multiple long array parameters' => [
|
|
|
507 |
'markoutofmax',
|
|
|
508 |
'question',
|
|
|
509 |
['mark' => $longname, 'max' => $longname],
|
|
|
510 |
'Mark One two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen ' .
|
|
|
511 |
'eighteen ... out of One two three four five six seven eight nine ten eleven twelve thirteen fourteen ' .
|
|
|
512 |
'fifteen sixteen seventeen eighteen ...',
|
|
|
513 |
],
|
|
|
514 |
'Long lang string' => [
|
|
|
515 |
'howquestionsbehave_help',
|
|
|
516 |
'question',
|
|
|
517 |
null,
|
|
|
518 |
'Students can interact with the questions in the quiz in various different ways. For example, you may wish the ' .
|
|
|
519 |
'students to enter an answer to each question and then submit the entire quiz, before anything is graded or ' .
|
|
|
520 |
'they get any feedback. That would ...',
|
|
|
521 |
],
|
|
|
522 |
];
|
|
|
523 |
}
|
|
|
524 |
}
|