Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]

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