PageRenderTime 45ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/question/engine/simpletest/helpers.php

http://github.com/moodle/moodle
PHP | 757 lines | 497 code | 103 blank | 157 comment | 37 complexity | e9b6b0852e2b491ed9090de5649b3445 MD5 | raw file
Possible License(s): MIT, AGPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, Apache-2.0, LGPL-2.1, BSD-3-Clause
  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. * This file contains helper classes for testing the question engine.
  18. *
  19. * @package moodlecore
  20. * @subpackage questionengine
  21. * @copyright 2009 The Open University
  22. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23. */
  24. defined('MOODLE_INTERNAL') || die();
  25. require_once(dirname(__FILE__) . '/../lib.php');
  26. /**
  27. * Makes some protected methods of question_attempt public to facilitate testing.
  28. *
  29. * @copyright 2009 The Open University
  30. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  31. */
  32. class testable_question_attempt extends question_attempt {
  33. public function add_step($step) {
  34. parent::add_step($step);
  35. }
  36. public function set_min_fraction($fraction) {
  37. $this->minfraction = $fraction;
  38. }
  39. public function set_behaviour(question_behaviour $behaviour) {
  40. $this->behaviour = $behaviour;
  41. }
  42. }
  43. /**
  44. * Base class for question type test helpers.
  45. *
  46. * @copyright 2011 The Open University
  47. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  48. */
  49. abstract class question_test_helper {
  50. /**
  51. * @return array of example question names that can be passed as the $which
  52. * argument of {@link test_question_maker::make_question} when $qtype is
  53. * this question type.
  54. */
  55. abstract public function get_test_questions();
  56. }
  57. /**
  58. * This class creates questions of various types, which can then be used when
  59. * testing.
  60. *
  61. * @copyright 2009 The Open University
  62. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  63. */
  64. class test_question_maker {
  65. const STANDARD_OVERALL_CORRECT_FEEDBACK = 'Well done!';
  66. const STANDARD_OVERALL_PARTIALLYCORRECT_FEEDBACK =
  67. 'Parts, but only parts, of your response are correct.';
  68. const STANDARD_OVERALL_INCORRECT_FEEDBACK = 'That is not right at all.';
  69. /** @var array qtype => qtype test helper class. */
  70. protected static $testhelpers = array();
  71. /**
  72. * Just make a question_attempt at a question. Useful for unit tests that
  73. * need to pass a $qa to methods that call format_text. Probably not safe
  74. * to use for anything beyond that.
  75. * @param question_definition $question a question.
  76. * @param number $maxmark the max mark to set.
  77. * @return question_attempt the question attempt.
  78. */
  79. public function get_a_qa($question, $maxmark = 3) {
  80. return new question_attempt($question, 13, null, $maxmark);
  81. }
  82. /**
  83. * Initialise the common fields of a question of any type.
  84. */
  85. public static function initialise_a_question($q) {
  86. global $USER;
  87. $q->id = 0;
  88. $q->category = 0;
  89. $q->parent = 0;
  90. $q->questiontextformat = FORMAT_HTML;
  91. $q->generalfeedbackformat = FORMAT_HTML;
  92. $q->defaultmark = 1;
  93. $q->penalty = 0.3333333;
  94. $q->length = 1;
  95. $q->stamp = make_unique_id_code();
  96. $q->version = make_unique_id_code();
  97. $q->hidden = 0;
  98. $q->timecreated = time();
  99. $q->timemodified = time();
  100. $q->createdby = $USER->id;
  101. $q->modifiedby = $USER->id;
  102. }
  103. public static function initialise_question_data($qdata) {
  104. global $USER;
  105. $qdata->id = 0;
  106. $qdata->category = 0;
  107. $qdata->contextid = 0;
  108. $qdata->parent = 0;
  109. $qdata->questiontextformat = FORMAT_HTML;
  110. $qdata->generalfeedbackformat = FORMAT_HTML;
  111. $qdata->defaultmark = 1;
  112. $qdata->penalty = 0.3333333;
  113. $qdata->length = 1;
  114. $qdata->stamp = make_unique_id_code();
  115. $qdata->version = make_unique_id_code();
  116. $qdata->hidden = 0;
  117. $qdata->timecreated = time();
  118. $qdata->timemodified = time();
  119. $qdata->createdby = $USER->id;
  120. $qdata->modifiedby = $USER->id;
  121. $qdata->hints = array();
  122. }
  123. public static function initialise_question_form_data($qdata) {
  124. $formdata = new stdClass();
  125. $formdata->id = 0;
  126. $formdata->category = '0,0';
  127. $formdata->usecurrentcat = 1;
  128. $formdata->categorymoveto = '0,0';
  129. $formdata->tags = array();
  130. $formdata->penalty = 0.3333333;
  131. $formdata->questiontextformat = FORMAT_HTML;
  132. $formdata->generalfeedbackformat = FORMAT_HTML;
  133. }
  134. /**
  135. * Get the test helper class for a particular question type.
  136. * @param $qtype the question type name, e.g. 'multichoice'.
  137. * @return question_test_helper the test helper class.
  138. */
  139. public static function get_test_helper($qtype) {
  140. global $CFG;
  141. if (array_key_exists($qtype, self::$testhelpers)) {
  142. return self::$testhelpers[$qtype];
  143. }
  144. $file = get_plugin_directory('qtype', $qtype) . '/simpletest/helper.php';
  145. if (!is_readable($file)) {
  146. throw new coding_exception('Question type ' . $qtype .
  147. ' does not have test helper code.');
  148. }
  149. include_once($file);
  150. $class = 'qtype_' . $qtype . '_test_helper';
  151. if (!class_exists($class)) {
  152. throw new coding_exception('Class ' . $class . ' is not defined in ' . $file);
  153. }
  154. self::$testhelpers[$qtype] = new $class();
  155. return self::$testhelpers[$qtype];
  156. }
  157. /**
  158. * Call a method on a qtype_{$qtype}_test_helper class and return the result.
  159. *
  160. * @param string $methodtemplate e.g. 'make_{qtype}_question_{which}';
  161. * @param string $qtype the question type to get a test question for.
  162. * @param string $which one of the names returned by the get_test_questions
  163. * method of the relevant qtype_{$qtype}_test_helper class.
  164. * @param unknown_type $which
  165. */
  166. protected static function call_question_helper_method($methodtemplate, $qtype, $which = null) {
  167. $helper = self::get_test_helper($qtype);
  168. $available = $helper->get_test_questions();
  169. if (is_null($which)) {
  170. $which = reset($available);
  171. } else if (!in_array($which, $available)) {
  172. throw new coding_exception('Example question ' . $which . ' of type ' .
  173. $qtype . ' does not exist.');
  174. }
  175. $method = str_replace(array('{qtype}', '{which}'),
  176. array($qtype, $which), $methodtemplate);
  177. if (!method_exists($helper, $method)) {
  178. throw new coding_exception('Method ' . $method . ' does not exist on the' .
  179. $qtype . ' question type test helper class.');
  180. }
  181. return $helper->$method();
  182. }
  183. /**
  184. * Question types can provide a number of test question defintions.
  185. * They do this by creating a qtype_{$qtype}_test_helper class that extends
  186. * question_test_helper. The get_test_questions method returns the list of
  187. * test questions available for this question type.
  188. *
  189. * @param string $qtype the question type to get a test question for.
  190. * @param string $which one of the names returned by the get_test_questions
  191. * method of the relevant qtype_{$qtype}_test_helper class.
  192. * @return question_definition the requested question object.
  193. */
  194. public static function make_question($qtype, $which = null) {
  195. return self::call_question_helper_method('make_{qtype}_question_{which}',
  196. $qtype, $which);
  197. }
  198. /**
  199. * Like {@link make_question()} but returns the datastructure from
  200. * get_question_options instead of the question_definition object.
  201. *
  202. * @param string $qtype the question type to get a test question for.
  203. * @param string $which one of the names returned by the get_test_questions
  204. * method of the relevant qtype_{$qtype}_test_helper class.
  205. * @return stdClass the requested question object.
  206. */
  207. public static function get_question_data($qtype, $which = null) {
  208. return self::call_question_helper_method('get_{qtype}_question_data_{which}',
  209. $qtype, $which);
  210. }
  211. /**
  212. * Like {@link make_question()} but returns the data what would be saved from
  213. * the question editing form instead of the question_definition object.
  214. *
  215. * @param string $qtype the question type to get a test question for.
  216. * @param string $which one of the names returned by the get_test_questions
  217. * method of the relevant qtype_{$qtype}_test_helper class.
  218. * @return stdClass the requested question object.
  219. */
  220. public static function get_question_form_data($qtype, $which = null) {
  221. return self::call_question_helper_method('get_{qtype}_question_form_data_{which}',
  222. $qtype, $which);
  223. }
  224. /**
  225. * Makes a multichoice question with choices 'A', 'B' and 'C' shuffled. 'A'
  226. * is correct, defaultmark 1.
  227. * @return qtype_multichoice_single_question
  228. */
  229. public static function make_a_multichoice_single_question() {
  230. question_bank::load_question_definition_classes('multichoice');
  231. $mc = new qtype_multichoice_single_question();
  232. self::initialise_a_question($mc);
  233. $mc->name = 'Multi-choice question, single response';
  234. $mc->questiontext = 'The answer is A.';
  235. $mc->generalfeedback = 'You should have selected A.';
  236. $mc->qtype = question_bank::get_qtype('multichoice');
  237. $mc->shuffleanswers = 1;
  238. $mc->answernumbering = 'abc';
  239. $mc->answers = array(
  240. 13 => new question_answer(13, 'A', 1, 'A is right', FORMAT_HTML),
  241. 14 => new question_answer(14, 'B', -0.3333333, 'B is wrong', FORMAT_HTML),
  242. 15 => new question_answer(15, 'C', -0.3333333, 'C is wrong', FORMAT_HTML),
  243. );
  244. return $mc;
  245. }
  246. /**
  247. * Makes a multichoice question with choices 'A', 'B', 'C' and 'D' shuffled.
  248. * 'A' and 'C' is correct, defaultmark 1.
  249. * @return qtype_multichoice_multi_question
  250. */
  251. public static function make_a_multichoice_multi_question() {
  252. question_bank::load_question_definition_classes('multichoice');
  253. $mc = new qtype_multichoice_multi_question();
  254. self::initialise_a_question($mc);
  255. $mc->name = 'Multi-choice question, multiple response';
  256. $mc->questiontext = 'The answer is A and C.';
  257. $mc->generalfeedback = 'You should have selected A and C.';
  258. $mc->qtype = question_bank::get_qtype('multichoice');
  259. $mc->shuffleanswers = 1;
  260. $mc->answernumbering = 'abc';
  261. self::set_standard_combined_feedback_fields($mc);
  262. $mc->answers = array(
  263. 13 => new question_answer(13, 'A', 0.5, 'A is part of the right answer', FORMAT_HTML),
  264. 14 => new question_answer(14, 'B', -1, 'B is wrong', FORMAT_HTML),
  265. 15 => new question_answer(15, 'C', 0.5, 'C is part of the right answer', FORMAT_HTML),
  266. 16 => new question_answer(16, 'D', -1, 'D is wrong', FORMAT_HTML),
  267. );
  268. return $mc;
  269. }
  270. /**
  271. * Makes a matching question to classify 'Dog', 'Frog', 'Toad' and 'Cat' as
  272. * 'Mammal', 'Amphibian' or 'Insect'.
  273. * defaultmark 1. Stems are shuffled by default.
  274. * @return qtype_match_question
  275. */
  276. public static function make_a_matching_question() {
  277. question_bank::load_question_definition_classes('match');
  278. $match = new qtype_match_question();
  279. self::initialise_a_question($match);
  280. $match->name = 'Matching question';
  281. $match->questiontext = 'Classify the animals.';
  282. $match->generalfeedback = 'Frogs and toads are amphibians, the others are mammals.';
  283. $match->qtype = question_bank::get_qtype('match');
  284. $match->shufflestems = 1;
  285. self::set_standard_combined_feedback_fields($match);
  286. // Using unset to get 1-based arrays.
  287. $match->stems = array('', 'Dog', 'Frog', 'Toad', 'Cat');
  288. $match->stemformat = array('', FORMAT_HTML, FORMAT_HTML, FORMAT_HTML, FORMAT_HTML);
  289. $match->choices = array('', 'Mammal', 'Amphibian', 'Insect');
  290. $match->right = array('', 1, 2, 2, 1);
  291. unset($match->stems[0]);
  292. unset($match->stemformat[0]);
  293. unset($match->choices[0]);
  294. unset($match->right[0]);
  295. return $match;
  296. }
  297. /**
  298. * Makes a truefalse question with correct ansewer true, defaultmark 1.
  299. * @return qtype_essay_question
  300. */
  301. public static function make_an_essay_question() {
  302. question_bank::load_question_definition_classes('essay');
  303. $essay = new qtype_essay_question();
  304. self::initialise_a_question($essay);
  305. $essay->name = 'Essay question';
  306. $essay->questiontext = 'Write an essay.';
  307. $essay->generalfeedback = 'I hope you wrote an interesting essay.';
  308. $essay->penalty = 0;
  309. $essay->qtype = question_bank::get_qtype('essay');
  310. $essay->responseformat = 'editor';
  311. $essay->responsefieldlines = 15;
  312. $essay->attachments = 0;
  313. $essay->graderinfo = '';
  314. $essay->graderinfoformat = FORMAT_MOODLE;
  315. return $essay;
  316. }
  317. /**
  318. * Add some standard overall feedback to a question. You need to use these
  319. * specific feedback strings for the corresponding contains_..._feedback
  320. * methods in {@link qbehaviour_walkthrough_test_base} to works.
  321. * @param question_definition $q the question to add the feedback to.
  322. */
  323. public static function set_standard_combined_feedback_fields($q) {
  324. $q->correctfeedback = self::STANDARD_OVERALL_CORRECT_FEEDBACK;
  325. $q->correctfeedbackformat = FORMAT_HTML;
  326. $q->partiallycorrectfeedback = self::STANDARD_OVERALL_PARTIALLYCORRECT_FEEDBACK;
  327. $q->partiallycorrectfeedbackformat = FORMAT_HTML;
  328. $q->shownumcorrect = true;
  329. $q->incorrectfeedback = self::STANDARD_OVERALL_INCORRECT_FEEDBACK;
  330. $q->incorrectfeedbackformat = FORMAT_HTML;
  331. }
  332. }
  333. /**
  334. * Helper for tests that need to simulate records loaded from the database.
  335. *
  336. * @copyright 2009 The Open University
  337. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  338. */
  339. abstract class testing_db_record_builder {
  340. public static function build_db_records(array $table) {
  341. $columns = array_shift($table);
  342. $records = array();
  343. foreach ($table as $row) {
  344. if (count($row) != count($columns)) {
  345. throw new coding_exception("Row contains the wrong number of fields.");
  346. }
  347. $rec = new stdClass();
  348. foreach ($columns as $i => $name) {
  349. $rec->$name = $row[$i];
  350. }
  351. $records[] = $rec;
  352. }
  353. return $records;
  354. }
  355. }
  356. /**
  357. * Helper base class for tests that need to simulate records loaded from the
  358. * database.
  359. *
  360. * @copyright 2009 The Open University
  361. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  362. */
  363. class data_loading_method_test_base extends UnitTestCase {
  364. public function build_db_records(array $table) {
  365. return testing_db_record_builder::build_db_records($table);
  366. }
  367. }
  368. /**
  369. * Helper base class for tests that walk a question through a sequents of
  370. * interactions under the control of a particular behaviour.
  371. *
  372. * @copyright 2009 The Open University
  373. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  374. */
  375. class qbehaviour_walkthrough_test_base extends UnitTestCase {
  376. /** @var question_display_options */
  377. protected $displayoptions;
  378. /** @var question_usage_by_activity */
  379. protected $quba;
  380. /** @var unknown_type integer */
  381. protected $slot;
  382. public function setUp() {
  383. $this->displayoptions = new question_display_options();
  384. $this->quba = question_engine::make_questions_usage_by_activity('unit_test',
  385. get_context_instance(CONTEXT_SYSTEM));
  386. }
  387. public function tearDown() {
  388. $this->displayoptions = null;
  389. $this->quba = null;
  390. }
  391. protected function start_attempt_at_question($question, $preferredbehaviour,
  392. $maxmark = null, $variant = 1) {
  393. $this->quba->set_preferred_behaviour($preferredbehaviour);
  394. $this->slot = $this->quba->add_question($question, $maxmark);
  395. $this->quba->start_question($this->slot, $variant);
  396. }
  397. protected function process_submission($data) {
  398. $this->quba->process_action($this->slot, $data);
  399. }
  400. protected function manual_grade($comment, $mark) {
  401. $this->quba->manual_grade($this->slot, $comment, $mark);
  402. }
  403. protected function check_current_state($state) {
  404. $this->assertEqual($this->quba->get_question_state($this->slot), $state,
  405. 'Questions is in the wrong state: %s.');
  406. }
  407. protected function check_current_mark($mark) {
  408. if (is_null($mark)) {
  409. $this->assertNull($this->quba->get_question_mark($this->slot));
  410. } else {
  411. if ($mark == 0) {
  412. // PHP will think a null mark and a mark of 0 are equal,
  413. // so explicity check not null in this case.
  414. $this->assertNotNull($this->quba->get_question_mark($this->slot));
  415. }
  416. $this->assertWithinMargin($mark, $this->quba->get_question_mark($this->slot),
  417. 0.000001, 'Expected mark and actual mark differ: %s.');
  418. }
  419. }
  420. /**
  421. * @param $condition one or more Expectations. (users varargs).
  422. */
  423. protected function check_current_output() {
  424. $html = $this->quba->render_question($this->slot, $this->displayoptions);
  425. foreach (func_get_args() as $condition) {
  426. $this->assert($condition, $html);
  427. }
  428. }
  429. protected function get_question_attempt() {
  430. return $this->quba->get_question_attempt($this->slot);
  431. }
  432. protected function get_step_count() {
  433. return $this->get_question_attempt()->get_num_steps();
  434. }
  435. protected function check_step_count($expectednumsteps) {
  436. $this->assertEqual($expectednumsteps, $this->get_step_count());
  437. }
  438. protected function get_step($stepnum) {
  439. return $this->get_question_attempt()->get_step($stepnum);
  440. }
  441. protected function get_contains_question_text_expectation($question) {
  442. return new PatternExpectation('/' . preg_quote($question->questiontext) . '/');
  443. }
  444. protected function get_contains_general_feedback_expectation($question) {
  445. return new PatternExpectation('/' . preg_quote($question->generalfeedback) . '/');
  446. }
  447. protected function get_does_not_contain_correctness_expectation() {
  448. return new NoPatternExpectation('/class=\"correctness/');
  449. }
  450. protected function get_contains_correct_expectation() {
  451. return new PatternExpectation('/' . preg_quote(get_string('correct', 'question')) . '/');
  452. }
  453. protected function get_contains_partcorrect_expectation() {
  454. return new PatternExpectation('/' .
  455. preg_quote(get_string('partiallycorrect', 'question')) . '/');
  456. }
  457. protected function get_contains_incorrect_expectation() {
  458. return new PatternExpectation('/' . preg_quote(get_string('incorrect', 'question')) . '/');
  459. }
  460. protected function get_contains_standard_correct_combined_feedback_expectation() {
  461. return new PatternExpectation('/' .
  462. preg_quote(test_question_maker::STANDARD_OVERALL_CORRECT_FEEDBACK) . '/');
  463. }
  464. protected function get_contains_standard_partiallycorrect_combined_feedback_expectation() {
  465. return new PatternExpectation('/' .
  466. preg_quote(test_question_maker::STANDARD_OVERALL_PARTIALLYCORRECT_FEEDBACK) . '/');
  467. }
  468. protected function get_contains_standard_incorrect_combined_feedback_expectation() {
  469. return new PatternExpectation('/' .
  470. preg_quote(test_question_maker::STANDARD_OVERALL_INCORRECT_FEEDBACK) . '/');
  471. }
  472. protected function get_does_not_contain_feedback_expectation() {
  473. return new NoPatternExpectation('/class="feedback"/');
  474. }
  475. protected function get_does_not_contain_num_parts_correct() {
  476. return new NoPatternExpectation('/class="numpartscorrect"/');
  477. }
  478. protected function get_contains_num_parts_correct($num) {
  479. $a = new stdClass();
  480. $a->num = $num;
  481. return new PatternExpectation('/<div class="numpartscorrect">' .
  482. preg_quote(get_string('yougotnright', 'question', $a)) . '/');
  483. }
  484. protected function get_does_not_contain_specific_feedback_expectation() {
  485. return new NoPatternExpectation('/class="specificfeedback"/');
  486. }
  487. protected function get_contains_validation_error_expectation() {
  488. return new ContainsTagWithAttribute('div', 'class', 'validationerror');
  489. }
  490. protected function get_does_not_contain_validation_error_expectation() {
  491. return new NoPatternExpectation('/class="validationerror"/');
  492. }
  493. protected function get_contains_mark_summary($mark) {
  494. $a = new stdClass();
  495. $a->mark = format_float($mark, $this->displayoptions->markdp);
  496. $a->max = format_float($this->quba->get_question_max_mark($this->slot),
  497. $this->displayoptions->markdp);
  498. return new PatternExpectation('/' .
  499. preg_quote(get_string('markoutofmax', 'question', $a)) . '/');
  500. }
  501. protected function get_contains_marked_out_of_summary() {
  502. $max = format_float($this->quba->get_question_max_mark($this->slot),
  503. $this->displayoptions->markdp);
  504. return new PatternExpectation('/' .
  505. preg_quote(get_string('markedoutofmax', 'question', $max)) . '/');
  506. }
  507. protected function get_does_not_contain_mark_summary() {
  508. return new NoPatternExpectation('/<div class="grade">/');
  509. }
  510. protected function get_contains_checkbox_expectation($baseattr, $enabled, $checked) {
  511. $expectedattributes = $baseattr;
  512. $forbiddenattributes = array();
  513. $expectedattributes['type'] = 'checkbox';
  514. if ($enabled === true) {
  515. $forbiddenattributes['disabled'] = 'disabled';
  516. } else if ($enabled === false) {
  517. $expectedattributes['disabled'] = 'disabled';
  518. }
  519. if ($checked === true) {
  520. $expectedattributes['checked'] = 'checked';
  521. } else if ($checked === false) {
  522. $forbiddenattributes['checked'] = 'checked';
  523. }
  524. return new ContainsTagWithAttributes('input', $expectedattributes, $forbiddenattributes);
  525. }
  526. protected function get_contains_mc_checkbox_expectation($index, $enabled = null,
  527. $checked = null) {
  528. return $this->get_contains_checkbox_expectation(array(
  529. 'name' => $this->quba->get_field_prefix($this->slot) . $index,
  530. 'value' => 1,
  531. ), $enabled, $checked);
  532. }
  533. protected function get_contains_radio_expectation($baseattr, $enabled, $checked) {
  534. $expectedattributes = $baseattr;
  535. $forbiddenattributes = array();
  536. $expectedattributes['type'] = 'radio';
  537. if ($enabled === true) {
  538. $forbiddenattributes['disabled'] = 'disabled';
  539. } else if ($enabled === false) {
  540. $expectedattributes['disabled'] = 'disabled';
  541. }
  542. if ($checked === true) {
  543. $expectedattributes['checked'] = 'checked';
  544. } else if ($checked === false) {
  545. $forbiddenattributes['checked'] = 'checked';
  546. }
  547. return new ContainsTagWithAttributes('input', $expectedattributes, $forbiddenattributes);
  548. }
  549. protected function get_contains_mc_radio_expectation($index, $enabled = null, $checked = null) {
  550. return $this->get_contains_radio_expectation(array(
  551. 'name' => $this->quba->get_field_prefix($this->slot) . 'answer',
  552. 'value' => $index,
  553. ), $enabled, $checked);
  554. }
  555. protected function get_contains_hidden_expectation($name, $value = null) {
  556. $expectedattributes = array('type' => 'hidden', 'name' => s($name));
  557. if (!is_null($value)) {
  558. $expectedattributes['value'] = s($value);
  559. }
  560. return new ContainsTagWithAttributes('input', $expectedattributes);
  561. }
  562. protected function get_does_not_contain_hidden_expectation($name, $value = null) {
  563. $expectedattributes = array('type' => 'hidden', 'name' => s($name));
  564. if (!is_null($value)) {
  565. $expectedattributes['value'] = s($value);
  566. }
  567. return new DoesNotContainTagWithAttributes('input', $expectedattributes);
  568. }
  569. protected function get_contains_tf_true_radio_expectation($enabled = null, $checked = null) {
  570. return $this->get_contains_radio_expectation(array(
  571. 'name' => $this->quba->get_field_prefix($this->slot) . 'answer',
  572. 'value' => 1,
  573. ), $enabled, $checked);
  574. }
  575. protected function get_contains_tf_false_radio_expectation($enabled = null, $checked = null) {
  576. return $this->get_contains_radio_expectation(array(
  577. 'name' => $this->quba->get_field_prefix($this->slot) . 'answer',
  578. 'value' => 0,
  579. ), $enabled, $checked);
  580. }
  581. protected function get_contains_cbm_radio_expectation($certainty, $enabled = null,
  582. $checked = null) {
  583. return $this->get_contains_radio_expectation(array(
  584. 'name' => $this->quba->get_field_prefix($this->slot) . '-certainty',
  585. 'value' => $certainty,
  586. ), $enabled, $checked);
  587. }
  588. protected function get_contains_button_expectation($name, $value = null, $enabled = null) {
  589. $expectedattributes = array(
  590. 'type' => 'submit',
  591. 'name' => $name,
  592. );
  593. $forbiddenattributes = array();
  594. if (!is_null($value)) {
  595. $expectedattributes['value'] = $value;
  596. }
  597. if ($enabled === true) {
  598. $forbiddenattributes['disabled'] = 'disabled';
  599. } else if ($enabled === false) {
  600. $expectedattributes['disabled'] = 'disabled';
  601. }
  602. return new ContainsTagWithAttributes('input', $expectedattributes, $forbiddenattributes);
  603. }
  604. protected function get_contains_submit_button_expectation($enabled = null) {
  605. return $this->get_contains_button_expectation(
  606. $this->quba->get_field_prefix($this->slot) . '-submit', null, $enabled);
  607. }
  608. protected function get_tries_remaining_expectation($n) {
  609. return new PatternExpectation('/' .
  610. preg_quote(get_string('triesremaining', 'qbehaviour_interactive', $n)) . '/');
  611. }
  612. protected function get_invalid_answer_expectation() {
  613. return new PatternExpectation('/' .
  614. preg_quote(get_string('invalidanswer', 'question')) . '/');
  615. }
  616. protected function get_contains_try_again_button_expectation($enabled = null) {
  617. $expectedattributes = array(
  618. 'type' => 'submit',
  619. 'name' => $this->quba->get_field_prefix($this->slot) . '-tryagain',
  620. );
  621. $forbiddenattributes = array();
  622. if ($enabled === true) {
  623. $forbiddenattributes['disabled'] = 'disabled';
  624. } else if ($enabled === false) {
  625. $expectedattributes['disabled'] = 'disabled';
  626. }
  627. return new ContainsTagWithAttributes('input', $expectedattributes, $forbiddenattributes);
  628. }
  629. protected function get_does_not_contain_try_again_button_expectation() {
  630. return new NoPatternExpectation('/name="' .
  631. $this->quba->get_field_prefix($this->slot) . '-tryagain"/');
  632. }
  633. protected function get_contains_select_expectation($name, $choices,
  634. $selected = null, $enabled = null) {
  635. $fullname = $this->quba->get_field_prefix($this->slot) . $name;
  636. return new ContainsSelectExpectation($fullname, $choices, $selected, $enabled);
  637. }
  638. protected function get_mc_right_answer_index($mc) {
  639. $order = $mc->get_order($this->get_question_attempt());
  640. foreach ($order as $i => $ansid) {
  641. if ($mc->answers[$ansid]->fraction == 1) {
  642. return $i;
  643. }
  644. }
  645. $this->fail('This multiple choice question does not seem to have a right answer!');
  646. }
  647. protected function get_no_hint_visible_expectation() {
  648. return new NoPatternExpectation('/class="hint"/');
  649. }
  650. protected function get_contains_hint_expectation($hinttext) {
  651. // Does not currently verify hint text.
  652. return new ContainsTagWithAttribute('div', 'class', 'hint');
  653. }
  654. }