Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 39 and 311]

   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 qformat_gift;
  18  
  19  use qformat_gift;
  20  use question_bank;
  21  use question_check_specified_fields_expectation;
  22  
  23  defined('MOODLE_INTERNAL') || die();
  24  
  25  global $CFG;
  26  require_once($CFG->libdir . '/questionlib.php');
  27  require_once($CFG->dirroot . '/question/format.php');
  28  require_once($CFG->dirroot . '/question/format/gift/format.php');
  29  require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
  30  
  31  /**
  32   * Unit tests for the GIFT import/export format.
  33   *
  34   * @package   qformat_gift
  35   * @copyright 2010 The Open University
  36   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  class giftformat_test extends \question_testcase {
  39      public function assert_same_gift($expectedtext, $text) {
  40          $this->assertEquals(str_replace("\r\n", "\n", $expectedtext),
  41                  str_replace("\r\n", "\n", $text));
  42      }
  43  
  44      public function test_import_essay() {
  45          $gift = '
  46  // essay
  47  ::Q8:: How are you? {}';
  48          $lines = preg_split('/[\\n\\r]/', str_replace("\r\n", "\n", $gift));
  49  
  50          $importer = new qformat_gift();
  51          $q = $importer->readquestion($lines);
  52  
  53          $expectedq = (object) array(
  54              'name' => 'Q8',
  55              'questiontext' => 'How are you?',
  56              'questiontextformat' => FORMAT_MOODLE,
  57              'generalfeedback' => '',
  58              'generalfeedbackformat' => FORMAT_MOODLE,
  59              'qtype' => 'essay',
  60              'defaultmark' => 1,
  61              'penalty' => 0.3333333,
  62              'length' => 1,
  63              'responseformat' => 'editor',
  64              'responsefieldlines' => 15,
  65              'attachments' => 0,
  66              'graderinfo' => array(
  67                  'text' => '',
  68                  'format' => FORMAT_HTML,
  69                  'files' => array()),
  70          );
  71  
  72          $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
  73      }
  74  
  75      public function test_export_essay() {
  76          $qdata = (object) array(
  77              'id' => 666 ,
  78              'name' => 'Q8',
  79              'questiontext' => 'How are you?',
  80              'questiontextformat' => FORMAT_MOODLE,
  81              'generalfeedback' => '',
  82              'generalfeedbackformat' => FORMAT_MOODLE,
  83              'defaultmark' => 1,
  84              'penalty' => 0.3333333,
  85              'length' => 1,
  86              'qtype' => 'essay',
  87              'options' => (object) array(
  88                  'responseformat' => 'editor',
  89                  'responsefieldlines' => 15,
  90                  'attachments' => 0,
  91                  'graderinfo' => '',
  92                  'graderinfoformat' => FORMAT_HTML,
  93              ),
  94          );
  95  
  96          $exporter = new qformat_gift();
  97          $gift = $exporter->writequestion($qdata);
  98  
  99          $expectedgift = "// question: 666  name: Q8
 100  ::Q8::How are you?{}
 101  
 102  ";
 103  
 104          $this->assert_same_gift($expectedgift, $gift);
 105      }
 106  
 107      public function test_import_match() {
 108          $gift = '
 109  // question: 2  name: Moodle activities
 110  ::Moodle activities::[html]Match the <b>activity</b> to the description.{
 111      =[html]An activity supporting asynchronous discussions. -> Forum
 112      =[moodle]A teacher asks a question and specifies a choice of multiple responses. -> Choice
 113      =[plain]A bank of record entries which participants can add to. -> Database
 114      =[markdown]A collection of web pages that anyone can add to or edit. -> Wiki
 115      = -> Chat
 116  }';
 117          $lines = preg_split('/[\\n\\r]/', str_replace("\r\n", "\n", $gift));
 118  
 119          $importer = new qformat_gift();
 120          $q = $importer->readquestion($lines);
 121  
 122          $expectedq = (object) array(
 123              'name' => 'Moodle activities',
 124              'questiontext' => 'Match the <b>activity</b> to the description.',
 125              'questiontextformat' => FORMAT_HTML,
 126              'generalfeedback' => '',
 127              'generalfeedbackformat' => FORMAT_HTML,
 128              'qtype' => 'match',
 129              'defaultmark' => 1,
 130              'penalty' => 0.3333333,
 131              'length' => 1,
 132              'shuffleanswers' => '1',
 133              'correctfeedback' => array(
 134                  'text' => '',
 135                  'format' => FORMAT_HTML,
 136                  'files' => array(),
 137              ),
 138              'partiallycorrectfeedback' => array(
 139                  'text' => '',
 140                  'format' => FORMAT_HTML,
 141                  'files' => array(),
 142              ),
 143              'incorrectfeedback' => array(
 144                  'text' => '',
 145                  'format' => FORMAT_HTML,
 146                  'files' => array(),
 147              ),
 148              'subquestions' => array(
 149                  0 => array(
 150                      'text' => 'An activity supporting asynchronous discussions.',
 151                      'format' => FORMAT_HTML,
 152                      'files' => array(),
 153                  ),
 154                  1 => array(
 155                      'text' => 'A teacher asks a question and specifies a choice of multiple responses.',
 156                      'format' => FORMAT_MOODLE,
 157                      'files' => array(),
 158                  ),
 159                  2 => array(
 160                      'text' => 'A bank of record entries which participants can add to.',
 161                      'format' => FORMAT_PLAIN,
 162                      'files' => array(),
 163                  ),
 164                  3 => array(
 165                      'text' => 'A collection of web pages that anyone can add to or edit.',
 166                      'format' => FORMAT_MARKDOWN,
 167                      'files' => array(),
 168                  ),
 169                  4 => array(
 170                      'text' => '',
 171                      'format' => FORMAT_HTML,
 172                      'files' => array(),
 173                  ),
 174              ),
 175              'subanswers' => array(
 176                  0 => 'Forum',
 177                  1 => 'Choice',
 178                  2 => 'Database',
 179                  3 => 'Wiki',
 180                  4 => 'Chat',
 181              ),
 182          );
 183  
 184          // Repeated test for better failure messages.
 185          $this->assertEquals($expectedq->subquestions, $q->subquestions);
 186          $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
 187      }
 188  
 189      public function test_export_match() {
 190          $qdata = (object) array(
 191              'id' => 666 ,
 192              'name' => 'Moodle activities',
 193              'questiontext' => 'Match the <b>activity</b> to the description.',
 194              'questiontextformat' => FORMAT_HTML,
 195              'generalfeedback' => '',
 196              'generalfeedbackformat' => FORMAT_HTML,
 197              'defaultmark' => 1,
 198              'penalty' => 0.3333333,
 199              'length' => 1,
 200              'qtype' => 'match',
 201              'options' => (object) array(
 202                  'id' => 123,
 203                  'question' => 666,
 204                  'shuffleanswers' => 1,
 205                  'subquestions' => array(
 206                      42 => (object) array(
 207                          'id' => 1234,
 208                          'code' => 12341234,
 209                          'question' => 666,
 210                          'questiontext' => '<div class="frog">An activity supporting asynchronous discussions.</div>',
 211                          'questiontextformat' => FORMAT_HTML,
 212                          'answertext' => 'Forum',
 213                      ),
 214                      43 => (object) array(
 215                          'id' => 1234,
 216                          'code' => 12341234,
 217                          'question' => 666,
 218                          'questiontext' => 'A teacher asks a question and specifies a choice of multiple responses.',
 219                          'questiontextformat' => FORMAT_MOODLE,
 220                          'answertext' => 'Choice',
 221                      ),
 222                      44 => (object) array(
 223                          'id' => 1234,
 224                          'code' => 12341234,
 225                          'question' => 666,
 226                          'questiontext' => 'A bank of record entries which participants can add to.',
 227                          'questiontextformat' => FORMAT_PLAIN,
 228                          'answertext' => 'Database',
 229                      ),
 230                      45 => (object) array(
 231                          'id' => 1234,
 232                          'code' => 12341234,
 233                          'question' => 666,
 234                          'questiontext' => 'A collection of web pages that anyone can add to or edit.',
 235                          'questiontextformat' => FORMAT_MARKDOWN,
 236                          'answertext' => 'Wiki',
 237                      ),
 238                      46 => (object) array(
 239                          'id' => 1234,
 240                          'code' => 12341234,
 241                          'question' => 666,
 242                          'questiontext' => '',
 243                          'questiontextformat' => FORMAT_MARKDOWN,
 244                          'answertext' => 'Chat',
 245                      ),
 246                  ),
 247              ),
 248          );
 249  
 250          $exporter = new qformat_gift();
 251          $gift = $exporter->writequestion($qdata);
 252  
 253          $expectedgift = "// question: 666  name: Moodle activities
 254  ::Moodle activities::[html]Match the <b>activity</b> to the description.{
 255  \t=<div class\\=\"frog\">An activity supporting asynchronous discussions.</div> -> Forum
 256  \t=[moodle]A teacher asks a question and specifies a choice of multiple responses. -> Choice
 257  \t=[plain]A bank of record entries which participants can add to. -> Database
 258  \t=[markdown]A collection of web pages that anyone can add to or edit. -> Wiki
 259  \t= -> Chat
 260  }
 261  
 262  ";
 263  
 264          $this->assert_same_gift($expectedgift, $gift);
 265      }
 266  
 267      /**
 268       * Test import of multichoice question in GIFT format
 269       *
 270       * @dataProvider numberingstyle_provider
 271       *
 272       * @param string $numberingstyle multichoice numbering style to set for qtype_multichoice
 273       *
 274       */
 275      public function test_import_multichoice($numberingstyle) {
 276          $this->resetAfterTest(true);
 277  
 278          set_config('answernumbering', $numberingstyle, 'qtype_multichoice');
 279          $gift = "
 280  // multiple choice with specified feedback for right and wrong answers
 281  ::Q2:: What's between orange and green in the spectrum?
 282  {
 283      =yellow # right; good!
 284      ~red # [html]wrong, it's yellow
 285      ~[plain]blue # wrong, it's yellow
 286  }";
 287          $lines = preg_split('/[\\n\\r]/', str_replace("\r\n", "\n", $gift));
 288  
 289          $importer = new qformat_gift();
 290          $q = $importer->readquestion($lines);
 291  
 292          $expectedq = (object) array(
 293              'name' => 'Q2',
 294              'questiontext' => "What's between orange and green in the spectrum?",
 295              'questiontextformat' => FORMAT_MOODLE,
 296              'generalfeedback' => '',
 297              'generalfeedbackformat' => FORMAT_MOODLE,
 298              'qtype' => 'multichoice',
 299              'defaultmark' => 1,
 300              'penalty' => 0.3333333,
 301              'length' => 1,
 302              'single' => 1,
 303              'shuffleanswers' => '1',
 304              'answernumbering' => $numberingstyle,
 305              'correctfeedback' => array(
 306                  'text' => '',
 307                  'format' => FORMAT_MOODLE,
 308                  'files' => array(),
 309              ),
 310              'partiallycorrectfeedback' => array(
 311                  'text' => '',
 312                  'format' => FORMAT_MOODLE,
 313                  'files' => array(),
 314              ),
 315              'incorrectfeedback' => array(
 316                  'text' => '',
 317                  'format' => FORMAT_MOODLE,
 318                  'files' => array(),
 319              ),
 320              'answer' => array(
 321                  0 => array(
 322                      'text' => 'yellow',
 323                      'format' => FORMAT_MOODLE,
 324                      'files' => array(),
 325                  ),
 326                  1 => array(
 327                      'text' => 'red',
 328                      'format' => FORMAT_MOODLE,
 329                      'files' => array(),
 330                  ),
 331                  2 => array(
 332                      'text' => 'blue',
 333                      'format' => FORMAT_PLAIN,
 334                      'files' => array(),
 335                  ),
 336              ),
 337              'fraction' => array(1, 0, 0),
 338              'feedback' => array(
 339                  0 => array(
 340                      'text' => 'right; good!',
 341                      'format' => FORMAT_MOODLE,
 342                      'files' => array(),
 343                  ),
 344                  1 => array(
 345                      'text' => "wrong, it's yellow",
 346                      'format' => FORMAT_HTML,
 347                      'files' => array(),
 348                  ),
 349                  2 => array(
 350                      'text' => "wrong, it's yellow",
 351                      'format' => FORMAT_MOODLE,
 352                      'files' => array(),
 353                  ),
 354              ),
 355          );
 356  
 357          // Repeated test for better failure messages.
 358          $this->assertEquals($expectedq->answer, $q->answer);
 359          $this->assertEquals($expectedq->feedback, $q->feedback);
 360          $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
 361      }
 362  
 363      /**
 364       * Return a list of numbering styles (see question/type/multichoice/questiontype.php
 365       * for valid choices)
 366       *
 367       * @return array Array of 1-element arrays of qtype_multichoice numbering styles
 368       */
 369      public function numberingstyle_provider() {
 370          return [
 371              ['abc'],
 372              ['ABCD'],
 373              ['123'],
 374              ['iii'],
 375              ['IIII'],
 376              ['none']
 377          ];
 378      }
 379  
 380      public function test_import_multichoice_multi() {
 381          $gift = "
 382  // multiple choice, multiple response with specified feedback for right and wrong answers
 383  ::colours:: What's between orange and green in the spectrum?
 384  {
 385      ~%50%yellow # right; good!
 386      ~%-100%red # [html]wrong
 387      ~%50%off-beige # right; good!
 388      ~%-100%[plain]blue # wrong
 389  }";
 390          $lines = preg_split('/[\\n\\r]/', str_replace("\r\n", "\n", $gift));
 391  
 392          $importer = new qformat_gift();
 393          $q = $importer->readquestion($lines);
 394  
 395          $expectedq = (object) array(
 396              'name' => 'colours',
 397              'questiontext' => "What's between orange and green in the spectrum?",
 398              'questiontextformat' => FORMAT_MOODLE,
 399              'generalfeedback' => '',
 400              'generalfeedbackformat' => FORMAT_MOODLE,
 401              'qtype' => 'multichoice',
 402              'defaultmark' => 1,
 403              'penalty' => 0.3333333,
 404              'length' => 1,
 405              'single' => 0,
 406              'shuffleanswers' => '1',
 407              'answernumbering' => 'abc',
 408              'correctfeedback' => array(
 409                  'text' => '',
 410                  'format' => FORMAT_MOODLE,
 411                  'files' => array(),
 412              ),
 413              'partiallycorrectfeedback' => array(
 414                  'text' => '',
 415                  'format' => FORMAT_MOODLE,
 416                  'files' => array(),
 417              ),
 418              'incorrectfeedback' => array(
 419                  'text' => '',
 420                  'format' => FORMAT_MOODLE,
 421                  'files' => array(),
 422              ),
 423              'answer' => array(
 424                  0 => array(
 425                      'text' => 'yellow',
 426                      'format' => FORMAT_MOODLE,
 427                      'files' => array(),
 428                  ),
 429                  1 => array(
 430                      'text' => 'red',
 431                      'format' => FORMAT_MOODLE,
 432                      'files' => array(),
 433                  ),
 434                  2 => array(
 435                      'text' => 'off-beige',
 436                      'format' => FORMAT_MOODLE,
 437                      'files' => array(),
 438                  ),
 439                  3 => array(
 440                      'text' => 'blue',
 441                      'format' => FORMAT_PLAIN,
 442                      'files' => array(),
 443                  ),
 444              ),
 445              'fraction' => array(0.5, -1, 0.5, -1),
 446              'feedback' => array(
 447                  0 => array(
 448                      'text' => 'right; good!',
 449                      'format' => FORMAT_MOODLE,
 450                      'files' => array(),
 451                  ),
 452                  1 => array(
 453                      'text' => "wrong",
 454                      'format' => FORMAT_HTML,
 455                      'files' => array(),
 456                  ),
 457                  2 => array(
 458                      'text' => "right; good!",
 459                      'format' => FORMAT_MOODLE,
 460                      'files' => array(),
 461                  ),
 462                  3 => array(
 463                      'text' => "wrong",
 464                      'format' => FORMAT_MOODLE,
 465                      'files' => array(),
 466                  ),
 467              ),
 468          );
 469  
 470          // Repeated test for better failure messages.
 471          $this->assertEquals($expectedq->answer, $q->answer);
 472          $this->assertEquals($expectedq->feedback, $q->feedback);
 473          $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
 474      }
 475  
 476      public function test_import_multichoice_multi_tricky() {
 477          $gift = "
 478  // multiple choice, multiple response with specified feedback for right and wrong answers
 479  ::colours:: What's between orange and green in the spectrum?
 480  {
 481      ~%100%yellow # right; good!
 482      ~%-50%red # wrong
 483      ~%-50%blue # wrong
 484  }";
 485          $lines = preg_split('/[\\n\\r]/', str_replace("\r\n", "\n", $gift));
 486  
 487          $importer = new qformat_gift();
 488          $q = $importer->readquestion($lines);
 489  
 490          $expectedq = (object) array(
 491                  'name' => 'colours',
 492                  'questiontext' => "What's between orange and green in the spectrum?",
 493                  'questiontextformat' => FORMAT_MOODLE,
 494                  'generalfeedback' => '',
 495                  'generalfeedbackformat' => FORMAT_MOODLE,
 496                  'qtype' => 'multichoice',
 497                  'defaultmark' => 1,
 498                  'penalty' => 0.3333333,
 499                  'length' => 1,
 500                  'single' => 0,
 501                  'shuffleanswers' => '1',
 502                  'answernumbering' => 'abc',
 503                  'correctfeedback' => array(
 504                          'text' => '',
 505                          'format' => FORMAT_MOODLE,
 506                          'files' => array(),
 507                  ),
 508                  'partiallycorrectfeedback' => array(
 509                          'text' => '',
 510                          'format' => FORMAT_MOODLE,
 511                          'files' => array(),
 512                  ),
 513                  'incorrectfeedback' => array(
 514                          'text' => '',
 515                          'format' => FORMAT_MOODLE,
 516                          'files' => array(),
 517                  ),
 518                  'answer' => array(
 519                          0 => array(
 520                                  'text' => 'yellow',
 521                                  'format' => FORMAT_MOODLE,
 522                                  'files' => array(),
 523                          ),
 524                          1 => array(
 525                                  'text' => 'red',
 526                                  'format' => FORMAT_MOODLE,
 527                                  'files' => array(),
 528                          ),
 529                          2 => array(
 530                                  'text' => 'blue',
 531                                  'format' => FORMAT_MOODLE,
 532                                  'files' => array(),
 533                          ),
 534                  ),
 535                  'fraction' => array(1, -0.5, -0.5),
 536                  'feedback' => array(
 537                          0 => array(
 538                                  'text' => 'right; good!',
 539                                  'format' => FORMAT_MOODLE,
 540                                  'files' => array(),
 541                          ),
 542                          1 => array(
 543                                  'text' => "wrong",
 544                                  'format' => FORMAT_MOODLE,
 545                                  'files' => array(),
 546                          ),
 547                          2 => array(
 548                                  'text' => "wrong",
 549                                  'format' => FORMAT_MOODLE,
 550                                  'files' => array(),
 551                          ),
 552                  ),
 553          );
 554  
 555          // Repeated test for better failure messages.
 556          $this->assertEquals($expectedq->answer, $q->answer);
 557          $this->assertEquals($expectedq->feedback, $q->feedback);
 558          $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
 559      }
 560  
 561      public function test_export_multichoice() {
 562          $qdata = (object) array(
 563              'id' => 666 ,
 564              'name' => 'Q8',
 565              'questiontext' => "What's between orange and green in the spectrum?",
 566              'questiontextformat' => FORMAT_MOODLE,
 567              'generalfeedback' => '',
 568              'generalfeedbackformat' => FORMAT_MOODLE,
 569              'defaultmark' => 1,
 570              'penalty' => 0.3333333,
 571              'length' => 1,
 572              'qtype' => 'multichoice',
 573              'options' => (object) array(
 574                  'single' => 1,
 575                  'shuffleanswers' => '1',
 576                  'answernumbering' => 'abc',
 577                  'correctfeedback' => '',
 578                  'correctfeedbackformat' => FORMAT_MOODLE,
 579                  'partiallycorrectfeedback' => '',
 580                  'partiallycorrectfeedbackformat' => FORMAT_MOODLE,
 581                  'incorrectfeedback' => '',
 582                  'incorrectfeedbackformat' => FORMAT_MOODLE,
 583                  'answers' => array(
 584                      123 => (object) array(
 585                          'id' => 123,
 586                          'answer' => 'yellow',
 587                          'answerformat' => FORMAT_MOODLE,
 588                          'fraction' => 1,
 589                          'feedback' => 'right; good!',
 590                          'feedbackformat' => FORMAT_MOODLE,
 591                      ),
 592                      124 => (object) array(
 593                          'id' => 124,
 594                          'answer' => 'red',
 595                          'answerformat' => FORMAT_MOODLE,
 596                          'fraction' => 0,
 597                          'feedback' => "wrong, it's yellow",
 598                          'feedbackformat' => FORMAT_HTML,
 599                      ),
 600                      125 => (object) array(
 601                          'id' => 125,
 602                          'answer' => 'blue',
 603                          'answerformat' => FORMAT_PLAIN,
 604                          'fraction' => 0,
 605                          'feedback' => "wrong, it's yellow",
 606                          'feedbackformat' => FORMAT_MOODLE,
 607                      ),
 608                  ),
 609              ),
 610          );
 611  
 612          $exporter = new qformat_gift();
 613          $gift = $exporter->writequestion($qdata);
 614  
 615          $expectedgift = "// question: 666  name: Q8
 616  ::Q8::What's between orange and green in the spectrum?{
 617  \t=yellow#right; good!
 618  \t~red#[html]wrong, it's yellow
 619  \t~[plain]blue#wrong, it's yellow
 620  }
 621  
 622  ";
 623  
 624          $this->assert_same_gift($expectedgift, $gift);
 625      }
 626  
 627      public function test_export_multichoice_multi_tricky() {
 628          $qdata = (object) array(
 629              'id' => 666 ,
 630              'name' => 'Q8',
 631              'questiontext' => "What's between orange and green in the spectrum?",
 632              'questiontextformat' => FORMAT_MOODLE,
 633              'generalfeedback' => '',
 634              'generalfeedbackformat' => FORMAT_MOODLE,
 635              'defaultmark' => 1,
 636              'penalty' => 0.3333333,
 637              'length' => 1,
 638              'qtype' => 'multichoice',
 639              'options' => (object) array(
 640                  'single' => 0,
 641                  'shuffleanswers' => '1',
 642                  'answernumbering' => 'abc',
 643                  'correctfeedback' => '',
 644                  'correctfeedbackformat' => FORMAT_MOODLE,
 645                  'partiallycorrectfeedback' => '',
 646                  'partiallycorrectfeedbackformat' => FORMAT_MOODLE,
 647                  'incorrectfeedback' => '',
 648                  'incorrectfeedbackformat' => FORMAT_MOODLE,
 649                  'answers' => array(
 650                      123 => (object) array(
 651                          'id' => 123,
 652                          'answer' => 'yellow',
 653                          'answerformat' => FORMAT_MOODLE,
 654                          'fraction' => 1,
 655                          'feedback' => 'right; good!',
 656                          'feedbackformat' => FORMAT_MOODLE,
 657                      ),
 658                      124 => (object) array(
 659                          'id' => 124,
 660                          'answer' => 'red',
 661                          'answerformat' => FORMAT_MOODLE,
 662                          'fraction' => -0.5,
 663                          'feedback' => "wrong, it's yellow",
 664                          'feedbackformat' => FORMAT_MOODLE,
 665                      ),
 666                      125 => (object) array(
 667                          'id' => 125,
 668                          'answer' => 'blue',
 669                          'answerformat' => FORMAT_MOODLE,
 670                          'fraction' => -0.5,
 671                          'feedback' => "wrong, it's yellow",
 672                          'feedbackformat' => FORMAT_MOODLE,
 673                      ),
 674                  ),
 675              ),
 676          );
 677  
 678          $exporter = new qformat_gift();
 679          $gift = $exporter->writequestion($qdata);
 680  
 681          $expectedgift = "// question: 666  name: Q8
 682  ::Q8::What's between orange and green in the spectrum?{
 683  \t~%100%yellow#right; good!
 684  \t~%-50%red#wrong, it's yellow
 685  \t~%-50%blue#wrong, it's yellow
 686  }
 687  
 688  ";
 689  
 690          $this->assert_same_gift($expectedgift, $gift);
 691      }
 692  
 693      public function test_import_numerical() {
 694          $gift = "
 695  // math range question
 696  ::Q5:: What is a number from 1 to 5? {#3:2~#Completely wrong}";
 697          $lines = preg_split('/[\\n\\r]/', str_replace("\r\n", "\n", $gift));
 698  
 699          $importer = new qformat_gift();
 700          $q = $importer->readquestion($lines);
 701  
 702          $expectedq = (object) array(
 703              'name' => 'Q5',
 704              'questiontext' => "What is a number from 1 to 5?",
 705              'questiontextformat' => FORMAT_MOODLE,
 706              'generalfeedback' => '',
 707              'generalfeedbackformat' => FORMAT_MOODLE,
 708              'qtype' => 'numerical',
 709              'defaultmark' => 1,
 710              'penalty' => 0.3333333,
 711              'length' => 1,
 712              'answer' => array(
 713                  '3',
 714                  '*',
 715              ),
 716              'fraction' => array(1, 0),
 717              'feedback' => array(
 718                  0 => array(
 719                      'text' => '',
 720                      'format' => FORMAT_MOODLE,
 721                      'files' => array(),
 722                  ),
 723                  1 => array(
 724                      'text' => "Completely wrong",
 725                      'format' => FORMAT_MOODLE,
 726                      'files' => array(),
 727                  ),
 728              ),
 729              'tolerance' => array(2, ''),
 730          );
 731  
 732          // Repeated test for better failure messages.
 733          $this->assertEquals($expectedq->answer, $q->answer);
 734          $this->assertEquals($expectedq->fraction, $q->fraction);
 735          $this->assertEquals($expectedq->feedback, $q->feedback);
 736          $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
 737      }
 738  
 739      public function test_export_numerical() {
 740          $qdata = (object) array(
 741              'id' => 666 ,
 742              'name' => 'Q5',
 743              'questiontext' => "What is a number from 1 to 5?",
 744              'questiontextformat' => FORMAT_MOODLE,
 745              'generalfeedback' => '',
 746              'generalfeedbackformat' => FORMAT_MOODLE,
 747              'defaultmark' => 1,
 748              'penalty' => 1,
 749              'length' => 1,
 750              'qtype' => 'numerical',
 751              'options' => (object) array(
 752                  'id' => 123,
 753                  'question' => 666,
 754                  'unitsleft' => 0,
 755                  'showunits' => 2,
 756                  'unitgradingtype' => 0,
 757                  'unitpenalty' => 0,
 758                  'answers' => array(
 759                      1 => (object) array(
 760                          'id' => 123,
 761                          'answer' => '3',
 762                          'answerformat' => 0,
 763                          'fraction' => 1,
 764                          'tolerance' => 2,
 765                          'feedback' => '',
 766                          'feedbackformat' => FORMAT_MOODLE,
 767                      ),
 768                      2 => (object) array(
 769                          'id' => 124,
 770                          'answer' => '*',
 771                          'answerformat' => 0,
 772                          'fraction' => 0,
 773                          'tolerance' => 0,
 774                          'feedback' => "Completely wrong",
 775                          'feedbackformat' => FORMAT_MOODLE,
 776                      ),
 777                  ),
 778              ),
 779          );
 780  
 781          $exporter = new qformat_gift();
 782          $gift = $exporter->writequestion($qdata);
 783  
 784          $expectedgift = "// question: 666  name: Q5
 785  ::Q5::What is a number from 1 to 5?{#
 786  \t=%100%3:2#
 787  \t~#Completely wrong
 788  }
 789  
 790  ";
 791  
 792          $this->assert_same_gift($expectedgift, $gift);
 793      }
 794  
 795      public function test_import_shortanswer() {
 796          $gift = "
 797  // question: 666  name: Shortanswer
 798  ::Shortanswer::Which is the best animal?{
 799      =Frog#Good!
 800      =%50%Cat#What is it with Moodlers and cats?
 801      =%0%*#Completely wrong
 802  }";
 803          $lines = preg_split('/[\\n\\r]/', str_replace("\r\n", "\n", $gift));
 804  
 805          $importer = new qformat_gift();
 806          $q = $importer->readquestion($lines);
 807  
 808          $expectedq = (object) array(
 809              'name' => 'Shortanswer',
 810              'questiontext' => "Which is the best animal?",
 811              'questiontextformat' => FORMAT_MOODLE,
 812              'generalfeedback' => '',
 813              'generalfeedbackformat' => FORMAT_MOODLE,
 814              'qtype' => 'shortanswer',
 815              'defaultmark' => 1,
 816              'penalty' => 0.3333333,
 817              'length' => 1,
 818              'answer' => array(
 819                  'Frog',
 820                  'Cat',
 821                  '*',
 822              ),
 823              'fraction' => array(1, 0.5, 0),
 824              'feedback' => array(
 825                  0 => array(
 826                      'text' => 'Good!',
 827                      'format' => FORMAT_MOODLE,
 828                      'files' => array(),
 829                  ),
 830                  1 => array(
 831                      'text' => "What is it with Moodlers and cats?",
 832                      'format' => FORMAT_MOODLE,
 833                      'files' => array(),
 834                  ),
 835                  2 => array(
 836                      'text' => "Completely wrong",
 837                      'format' => FORMAT_MOODLE,
 838                      'files' => array(),
 839                  ),
 840              ),
 841          );
 842  
 843          // Repeated test for better failure messages.
 844          $this->assertEquals($expectedq->answer, $q->answer);
 845          $this->assertEquals($expectedq->fraction, $q->fraction);
 846          $this->assertEquals($expectedq->feedback, $q->feedback);
 847          $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
 848      }
 849  
 850      public function test_import_shortanswer_with_general_feedback() {
 851          $gift = "
 852  // question: 666  name: Shortanswer
 853  ::Shortanswer::Which is the best animal?{
 854      =Frog#Good!
 855      =%50%Cat#What is it with Moodlers and cats?
 856      =%0%*#Completely wrong
 857      ####[html]Here is some general feedback!
 858  }";
 859          $lines = preg_split('/[\\n\\r]/', str_replace("\r\n", "\n", $gift));
 860  
 861          $importer = new qformat_gift();
 862          $q = $importer->readquestion($lines);
 863  
 864          $expectedq = (object) array(
 865              'name' => 'Shortanswer',
 866              'questiontext' => "Which is the best animal?",
 867              'questiontextformat' => FORMAT_MOODLE,
 868              'generalfeedback' => 'Here is some general feedback!',
 869              'generalfeedbackformat' => FORMAT_HTML,
 870              'qtype' => 'shortanswer',
 871              'defaultmark' => 1,
 872              'penalty' => 0.3333333,
 873              'length' => 1,
 874              'answer' => array(
 875                  'Frog',
 876                  'Cat',
 877                  '*',
 878              ),
 879              'fraction' => array(1, 0.5, 0),
 880              'feedback' => array(
 881                  0 => array(
 882                      'text' => 'Good!',
 883                      'format' => FORMAT_MOODLE,
 884                      'files' => array(),
 885                  ),
 886                  1 => array(
 887                      'text' => "What is it with Moodlers and cats?",
 888                      'format' => FORMAT_MOODLE,
 889                      'files' => array(),
 890                  ),
 891                  2 => array(
 892                      'text' => "Completely wrong",
 893                      'format' => FORMAT_MOODLE,
 894                      'files' => array(),
 895                  ),
 896              ),
 897          );
 898  
 899          // Repeated test for better failure messages.
 900          $this->assertEquals($expectedq->answer, $q->answer);
 901          $this->assertEquals($expectedq->fraction, $q->fraction);
 902          $this->assertEquals($expectedq->feedback, $q->feedback);
 903          $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
 904      }
 905  
 906      public function test_export_shortanswer() {
 907          $qdata = (object) array(
 908              'id' => 666 ,
 909              'name' => 'Shortanswer',
 910              'questiontext' => "Which is the best animal?",
 911              'questiontextformat' => FORMAT_MOODLE,
 912              'generalfeedback' => '',
 913              'generalfeedbackformat' => FORMAT_MOODLE,
 914              'defaultmark' => 1,
 915              'penalty' => 1,
 916              'length' => 1,
 917              'qtype' => 'shortanswer',
 918              'options' => (object) array(
 919                  'id' => 123,
 920                  'questionid' => 666,
 921                  'usecase' => 1,
 922                  'answers' => array(
 923                      1 => (object) array(
 924                          'id' => 1,
 925                          'answer' => 'Frog',
 926                          'answerformat' => 0,
 927                          'fraction' => 1,
 928                          'feedback' => 'Good!',
 929                          'feedbackformat' => FORMAT_MOODLE,
 930                      ),
 931                      2 => (object) array(
 932                          'id' => 2,
 933                          'answer' => 'Cat',
 934                          'answerformat' => 0,
 935                          'fraction' => 0.5,
 936                          'feedback' => "What is it with Moodlers and cats?",
 937                          'feedbackformat' => FORMAT_MOODLE,
 938                      ),
 939                      3 => (object) array(
 940                          'id' => 3,
 941                          'answer' => '*',
 942                          'answerformat' => 0,
 943                          'fraction' => 0,
 944                          'feedback' => "Completely wrong",
 945                          'feedbackformat' => FORMAT_MOODLE,
 946                      ),
 947                  ),
 948              ),
 949          );
 950  
 951          $exporter = new qformat_gift();
 952          $gift = $exporter->writequestion($qdata);
 953  
 954          $expectedgift = "// question: 666  name: Shortanswer
 955  ::Shortanswer::Which is the best animal?{
 956  \t=%100%Frog#Good!
 957  \t=%50%Cat#What is it with Moodlers and cats?
 958  \t=%0%*#Completely wrong
 959  }
 960  
 961  ";
 962  
 963          $this->assert_same_gift($expectedgift, $gift);
 964      }
 965  
 966      public function test_export_shortanswer_with_general_feedback() {
 967          $qdata = (object) array(
 968              'id' => 666 ,
 969              'name' => 'Shortanswer',
 970              'questiontext' => "Which is the best animal?",
 971              'questiontextformat' => FORMAT_MOODLE,
 972              'generalfeedback' => 'Here is some general feedback!',
 973              'generalfeedbackformat' => FORMAT_HTML,
 974              'defaultmark' => 1,
 975              'penalty' => 1,
 976              'length' => 1,
 977              'qtype' => 'shortanswer',
 978              'options' => (object) array(
 979                  'id' => 123,
 980                  'questionid' => 666,
 981                  'usecase' => 1,
 982                  'answers' => array(
 983                      1 => (object) array(
 984                          'id' => 1,
 985                          'answer' => 'Frog',
 986                          'answerformat' => 0,
 987                          'fraction' => 1,
 988                          'feedback' => 'Good!',
 989                          'feedbackformat' => FORMAT_MOODLE,
 990                      ),
 991                      2 => (object) array(
 992                          'id' => 2,
 993                          'answer' => 'Cat',
 994                          'answerformat' => 0,
 995                          'fraction' => 0.5,
 996                          'feedback' => "What is it with Moodlers and cats?",
 997                          'feedbackformat' => FORMAT_MOODLE,
 998                      ),
 999                      3 => (object) array(
1000                          'id' => 3,
1001                          'answer' => '*',
1002                          'answerformat' => 0,
1003                          'fraction' => 0,
1004                          'feedback' => "Completely wrong",
1005                          'feedbackformat' => FORMAT_MOODLE,
1006                      ),
1007                  ),
1008              ),
1009          );
1010  
1011          $exporter = new qformat_gift();
1012          $gift = $exporter->writequestion($qdata);
1013  
1014          $expectedgift = "// question: 666  name: Shortanswer
1015  ::Shortanswer::Which is the best animal?{
1016  \t=%100%Frog#Good!
1017  \t=%50%Cat#What is it with Moodlers and cats?
1018  \t=%0%*#Completely wrong
1019  \t####[html]Here is some general feedback!
1020  }
1021  
1022  ";
1023  
1024          $this->assert_same_gift($expectedgift, $gift);
1025      }
1026  
1027      public function test_import_truefalse() {
1028          $gift = "
1029  // true/false
1030  ::Q1:: 42 is the Absolute Answer to everything.{
1031  FALSE#42 is the Ultimate Answer.#You gave the right answer.}";
1032          $lines = preg_split('/[\\n\\r]/', str_replace("\r\n", "\n", $gift));
1033  
1034          $importer = new qformat_gift();
1035          $q = $importer->readquestion($lines);
1036  
1037          $expectedq = (object) array(
1038              'name' => 'Q1',
1039              'questiontext' => "42 is the Absolute Answer to everything.",
1040              'questiontextformat' => FORMAT_MOODLE,
1041              'generalfeedback' => '',
1042              'generalfeedbackformat' => FORMAT_MOODLE,
1043              'qtype' => 'truefalse',
1044              'defaultmark' => 1,
1045              'penalty' => 1,
1046              'length' => 1,
1047              'correctanswer' => 0,
1048              'feedbacktrue' => array(
1049                  'text' => '42 is the Ultimate Answer.',
1050                  'format' => FORMAT_MOODLE,
1051                  'files' => array(),
1052              ),
1053              'feedbackfalse' => array(
1054                  'text' => 'You gave the right answer.',
1055                  'format' => FORMAT_MOODLE,
1056                  'files' => array(),
1057              ),
1058          );
1059  
1060          $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
1061      }
1062  
1063      public function test_import_truefalse_true_answer1() {
1064          $gift = "// name 0-11
1065  ::2-08 TSL::TSL is blablabla.{T}";
1066          $lines = preg_split('/[\\n\\r]/', str_replace("\r\n", "\n", $gift));
1067  
1068          $importer = new qformat_gift();
1069          $q = $importer->readquestion($lines);
1070  
1071          $expectedq = (object) array(
1072              'name' => '2-08 TSL',
1073              'questiontext' => "TSL is blablabla.",
1074              'questiontextformat' => FORMAT_MOODLE,
1075              'generalfeedback' => '',
1076              'generalfeedbackformat' => FORMAT_MOODLE,
1077              'qtype' => 'truefalse',
1078              'defaultmark' => 1,
1079              'penalty' => 1,
1080              'length' => 1,
1081              'correctanswer' => 1,
1082              'feedbacktrue' => array(
1083                  'text' => '',
1084                  'format' => FORMAT_MOODLE,
1085                  'files' => array(),
1086              ),
1087              'feedbackfalse' => array(
1088                  'text' => '',
1089                  'format' => FORMAT_MOODLE,
1090                  'files' => array(),
1091              ),
1092          );
1093  
1094          $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
1095      }
1096  
1097      public function test_import_truefalse_true_answer2() {
1098          $gift = "// name 0-11
1099  ::2-08 TSL::TSL is blablabla.{TRUE}";
1100          $lines = preg_split('/[\\n\\r]/', str_replace("\r\n", "\n", $gift));
1101  
1102          $importer = new qformat_gift();
1103          $q = $importer->readquestion($lines);
1104  
1105          $expectedq = (object) array(
1106              'name' => '2-08 TSL',
1107              'questiontext' => "TSL is blablabla.",
1108              'questiontextformat' => FORMAT_MOODLE,
1109              'generalfeedback' => '',
1110              'generalfeedbackformat' => FORMAT_MOODLE,
1111              'qtype' => 'truefalse',
1112              'defaultmark' => 1,
1113              'penalty' => 1,
1114              'length' => 1,
1115              'correctanswer' => 1,
1116              'feedbacktrue' => array(
1117                  'text' => '',
1118                  'format' => FORMAT_MOODLE,
1119                  'files' => array(),
1120              ),
1121              'feedbackfalse' => array(
1122                  'text' => '',
1123                  'format' => FORMAT_MOODLE,
1124                  'files' => array(),
1125              ),
1126          );
1127  
1128          $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
1129      }
1130  
1131      public function test_export_truefalse() {
1132          $qdata = (object) array(
1133              'id' => 666 ,
1134              'name' => 'Q1',
1135              'questiontext' => "42 is the Absolute Answer to everything.",
1136              'questiontextformat' => FORMAT_MOODLE,
1137              'generalfeedback' => '',
1138              'generalfeedbackformat' => FORMAT_MOODLE,
1139              'defaultmark' => 1,
1140              'penalty' => 1,
1141              'length' => 1,
1142              'qtype' => 'truefalse',
1143              'options' => (object) array(
1144                  'id' => 123,
1145                  'question' => 666,
1146                  'trueanswer' => 1,
1147                  'falseanswer' => 2,
1148                  'answers' => array(
1149                      1 => (object) array(
1150                          'id' => 123,
1151                          'answer' => 'True',
1152                          'answerformat' => 0,
1153                          'fraction' => 1,
1154                          'feedback' => 'You gave the right answer.',
1155                          'feedbackformat' => FORMAT_MOODLE,
1156                      ),
1157                      2 => (object) array(
1158                          'id' => 124,
1159                          'answer' => 'False',
1160                          'answerformat' => 0,
1161                          'fraction' => 0,
1162                          'feedback' => "42 is the Ultimate Answer.",
1163                          'feedbackformat' => FORMAT_HTML,
1164                      ),
1165                  ),
1166              ),
1167          );
1168  
1169          $exporter = new qformat_gift();
1170          $gift = $exporter->writequestion($qdata);
1171  
1172          $expectedgift = "// question: 666  name: Q1
1173  ::Q1::42 is the Absolute Answer to everything.{TRUE#[html]42 is the Ultimate Answer.#You gave the right answer.}
1174  
1175  ";
1176  
1177          $this->assert_same_gift($expectedgift, $gift);
1178      }
1179  
1180      public function test_export_backslash() {
1181          // There was a bug (MDL-34171) where \\ was getting exported as \\, not
1182          // \\\\, and on import, \\ in converted to \.
1183          // We need \\\\ in the test code, because of PHPs string escaping rules.
1184          $qdata = (object) array(
1185              'id' => 666 ,
1186              'name' => 'backslash',
1187              'questiontext' => 'A \\ B \\\\ C',
1188              'questiontextformat' => FORMAT_MOODLE,
1189              'generalfeedback' => '',
1190              'generalfeedbackformat' => FORMAT_MOODLE,
1191              'defaultmark' => 1,
1192              'penalty' => 0.3333333,
1193              'length' => 1,
1194              'qtype' => 'essay',
1195              'options' => (object) array(
1196                  'responseformat' => 'editor',
1197                  'responsefieldlines' => 15,
1198                  'attachments' => 0,
1199                  'graderinfo' => '',
1200                  'graderinfoformat' => FORMAT_HTML,
1201              ),
1202          );
1203  
1204          $exporter = new qformat_gift();
1205          $gift = $exporter->writequestion($qdata);
1206  
1207          $expectedgift = "// question: 666  name: backslash
1208  ::backslash::A \\\\ B \\\\\\\\ C{}
1209  
1210  ";
1211  
1212          $this->assert_same_gift($expectedgift, $gift);
1213      }
1214  
1215      public function test_import_backslash() {
1216          // There was a bug (MDL-34171) where \\ in the import was getting changed
1217          // to \. This test checks for that.
1218          // We need \\\\ in the test code, because of PHPs string escaping rules.
1219          $gift = '
1220  // essay
1221  ::double backslash:: A \\\\ B \\\\\\\\ C{}';
1222          $lines = preg_split('/[\\n\\r]/', str_replace("\r\n", "\n", $gift));
1223  
1224          $importer = new qformat_gift();
1225          $q = $importer->readquestion($lines);
1226  
1227          $expectedq = (object) array(
1228              'name' => 'double backslash',
1229              'questiontext' => 'A \\ B \\\\ C',
1230              'questiontextformat' => FORMAT_MOODLE,
1231              'generalfeedback' => '',
1232              'generalfeedbackformat' => FORMAT_MOODLE,
1233              'qtype' => 'essay',
1234              'defaultmark' => 1,
1235              'penalty' => 0.3333333,
1236              'length' => 1,
1237              'responseformat' => 'editor',
1238              'responsefieldlines' => 15,
1239              'attachments' => 0,
1240              'graderinfo' => array(
1241                  'text' => '',
1242                  'format' => FORMAT_HTML,
1243                  'files' => array()),
1244          );
1245  
1246          $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
1247      }
1248  
1249      public function test_import_pre_content() {
1250          $gift = '
1251  ::Q001::[html]<p>What would running the test method print?</p>
1252  <pre>
1253      public void test() \{
1254          method1();
1255          method2();
1256          method3();
1257      \}
1258  </pre>
1259  {}';
1260          $lines = preg_split('/[\\n\\r]/', str_replace("\r\n", "\n", $gift));
1261  
1262          $importer = new qformat_gift();
1263          $q = $importer->readquestion($lines);
1264  
1265          $expectedq = (object) array(
1266              'name' => 'Q001',
1267              'questiontext' => '<p>What would running the test method print?</p>
1268  <pre>
1269      public void test() {
1270          method1();
1271          method2();
1272          method3();
1273      }
1274  </pre>',
1275              'questiontextformat' => FORMAT_HTML,
1276              'generalfeedback' => '',
1277              'generalfeedbackformat' => FORMAT_HTML,
1278              'qtype' => 'essay',
1279              'defaultmark' => 1,
1280              'penalty' => 0.3333333,
1281              'length' => 1,
1282              'responseformat' => 'editor',
1283              'responsefieldlines' => 15,
1284              'attachments' => 0,
1285              'graderinfo' => array(
1286                  'text' => '',
1287                  'format' => FORMAT_HTML,
1288                  'files' => array()),
1289          );
1290  
1291          $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
1292      }
1293  
1294      public function test_import_question_with_tags() {
1295          $gift = '
1296  // This question is to test importing tags: [tag:tag] [tag:other-tag].
1297  // And an idnumber: [id:myid].
1298  ::Question name:: How are you? {}';
1299          $lines = preg_split('/[\\n\\r]/', str_replace("\r\n", "\n", $gift));
1300  
1301          $importer = new qformat_gift();
1302          $q = $importer->readquestion($lines);
1303  
1304          $expectedq = (object) array(
1305              'name' => 'Question name',
1306              'questiontext' => 'How are you?',
1307              'questiontextformat' => FORMAT_MOODLE,
1308              'generalfeedback' => '',
1309              'generalfeedbackformat' => FORMAT_MOODLE,
1310              'qtype' => 'essay',
1311              'defaultmark' => 1,
1312              'penalty' => 0.3333333,
1313              'length' => 1,
1314              'responseformat' => 'editor',
1315              'responsefieldlines' => 15,
1316              'attachments' => 0,
1317              'graderinfo' => array(
1318                  'text' => '',
1319                  'format' => FORMAT_HTML,
1320                  'files' => array()),
1321              'tags' => ['tag', 'other-tag'],
1322              'idnumber' => 'myid',
1323          );
1324  
1325          $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
1326      }
1327  
1328      /**
1329       * Data provider for test_extract_idnumber_and_tags_from_comment.
1330       *
1331       * @return array the test cases.
1332       */
1333      public function extract_idnumber_and_tags_from_comment_testcases() {
1334          return [
1335              'blank comment' => ['', [], ''],
1336              'nothing in comment' => ['', [], '// A basic comment.'],
1337              'idnumber only' => ['frog', [], '// A comment with [id:frog] <-- an idnumber.'],
1338              'tags only' => ['', ['frog', 'toad'], '// Look tags: [tag:frog] [tag:toad].'],
1339              'everything' => ['four', ['add', 'basic'], '// [tag:add] [tag:basic] [id:four]'],
1340              'everything mixed up' => ['four', ['basic', 'add'],
1341                      "// [tag:  basic] Here is  \n// a [id:   four   ] que[tag:add   ]stion."],
1342              'split over line' => ['', [], "// Ceci n\'est pas une [tag:\n\\ frog]."],
1343              'escape ] idnumber' => ['i]d', [], '// [id:i\]d].'],
1344              'escape ] tag' => ['', ['t]ag'], '// [tag:t\]ag].'],
1345          ];
1346      }
1347  
1348      /**
1349       * Test extract_idnumber_and_tags_from_comment.
1350       *
1351       * @dataProvider extract_idnumber_and_tags_from_comment_testcases
1352       * @param string $expectedidnumber the expected idnumber.
1353       * @param array $expectedtags the expected tags.
1354       * @param string $comment the comment to parse.
1355       */
1356      public function test_extract_idnumber_and_tags_from_comment(
1357              string $expectedidnumber, array $expectedtags, string $comment) {
1358          $importer = new qformat_gift();
1359  
1360          list($idnumber, $tags) = $importer->extract_idnumber_and_tags_from_comment($comment);
1361          $this->assertSame($expectedidnumber, $idnumber);
1362          $this->assertSame($expectedtags, $tags);
1363      }
1364  
1365      public function test_export_question_with_tags_and_idnumber() {
1366          $this->resetAfterTest();
1367  
1368          // Create a question with tags.
1369          $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
1370          $category = $generator->create_question_category();
1371          $question = $generator->create_question('truefalse', null,
1372                  ['category' => $category->id, 'idnumber' => 'myid']);
1373          \core_tag_tag::set_item_tags('core_question', 'question', $question->id,
1374                  \context::instance_by_id($category->contextid), ['tag1', 'tag2'], 0);
1375  
1376          // Export it.
1377          $questiondata = question_bank::load_question_data($question->id);
1378          $exporter = new qformat_gift();
1379          $exporter->course = get_course(SITEID);
1380          $gift = $exporter->writequestion($questiondata);
1381  
1382          // Verify.
1383          $expectedgift = "// question: {$question->id}  name: True/false question
1384  // [id:myid] [tag:tag1] [tag:tag2]
1385  ::True/false question::[html]The answer is true.{TRUE#This is the wrong answer.#This is the right answer.####You should have selected true.}
1386  
1387  ";
1388  
1389          $this->assert_same_gift($expectedgift, $gift);
1390      }
1391  }