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 39 and 310]

   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 lib/classes/output/mustache_template_source_loader.php
  19   *
  20   * @package   core
  21   * @copyright 2018 Ryan Wyllie <ryan@moodle.com>
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  defined('MOODLE_INTERNAL') || die();
  26  
  27  use core\output\mustache_template_source_loader;
  28  
  29  /**
  30   * Unit tests for the Mustache source loader class.
  31   *
  32   * @package   core
  33   * @copyright 2018 Ryan Wyllie <ryan@moodle.com>
  34   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  class core_output_mustache_template_source_loader_testcase extends advanced_testcase {
  37      /**
  38       * Ensure that stripping comments from templates does not mutilate the template body.
  39       */
  40      public function test_strip_template_comments() {
  41  
  42          $templatebody = <<<'TBD'
  43          <h1>{{# str }} pluginname, mod_lemmings {{/ str }}</h1>
  44          <div>{{test}}</div>
  45          <div>{{{unescapedtest}}}</div>
  46          {{#lemmings}}
  47              <div>
  48                  <h2>{{name}}</h2>
  49                  {{> mod_lemmings/lemmingprofile }}
  50                  {{# pix }} t/edit, core, Edit Lemming {{/ pix }}
  51              </div>
  52          {{/lemmings}}
  53          {{^lemmings}}Sorry, no lemmings today{{/lemmings}}
  54          <div id="{{ uniqid }}-tab-container">
  55              {{# tabheader }}
  56                  <ul role="tablist" class="nav nav-tabs">
  57                      {{# iconlist }}
  58                          {{# icons }}
  59                              {{> core/pix_icon }}
  60                          {{/ icons }}
  61                      {{/ iconlist }}
  62                  </ul>
  63              {{/ tabheader }}
  64              {{# tabbody }}
  65                  <div class="tab-content">
  66                      {{# tabcontent }}
  67                          {{# tabs }}
  68                              {{> core/notification_info}}
  69                          {{/ tabs }}
  70                      {{/ tabcontent }}
  71                  </div>
  72              {{/ tabbody }}
  73          </div>
  74          {{#js}}
  75              require(['jquery','core/tabs'], function($, tabs) {
  76  
  77                  var container = $("#{{ uniqid }}-tab-container");
  78                  tabs.create(container);
  79              });
  80          {{/js}}
  81  TBD;
  82          $templatewithcomment = <<<TBC
  83          {{!
  84              This file is part of Moodle - http://moodle.org/
  85  
  86              Moodle is free software: you can redistribute it and/or modify
  87              it under the terms of the GNU General Public License as published by
  88              the Free Software Foundation, either version 3 of the License, or
  89              (at your option) any later version.
  90  
  91              Moodle is distributed in the hope that it will be useful,
  92              but WITHOUT ANY WARRANTY; without even the implied warranty of
  93              MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  94              GNU General Public License for more details.
  95  
  96              You should have received a copy of the GNU General Public License
  97              along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  98          }}
  99          {{!
 100              @template mod_lemmings/lemmings
 101  
 102              Lemmings template.
 103  
 104              The purpose of this template is to render a lot of lemmings.
 105  
 106              Classes required for JS:
 107              * none
 108  
 109              Data attributes required for JS:
 110              * none
 111  
 112              Context variables required for this template:
 113              * attributes Array of name / value pairs.
 114  
 115              Example context (json):
 116              {
 117                  "lemmings": [
 118                      { "name": "Lemmy Winks", "age" : 1, "size" : "big" },
 119                      { "name": "Rocky", "age" : 2, "size" : "small" }
 120                  ]
 121              }
 122  
 123          }}
 124          $templatebody
 125          {{!
 126              Here's some more comment text
 127              Note, there is no need to test bracketed variables inside comments as gherkin does not support that!
 128              See this issue: https://github.com/mustache/spec/issues/8
 129          }}
 130  TBC;
 131  
 132          $loader = new mustache_template_source_loader();
 133          $actual = phpunit_util::call_internal_method(
 134              $loader,
 135              'strip_template_comments',
 136              [$templatewithcomment],
 137              \core\output\mustache_template_source_loader::class
 138          );
 139          $this->assertEquals(trim($templatebody), trim($actual));
 140      }
 141  
 142      /**
 143       * Data provider for the test_load function.
 144       */
 145      public function test_load_test_cases() {
 146          $cache = [
 147              'core' => [
 148                  'test' => '{{! a comment }}The rest of the template'
 149              ]
 150          ];
 151          $loader = $this->build_loader_from_static_cache($cache);
 152  
 153          return [
 154              'with comments' => [
 155                  'loader' => $loader,
 156                  'component' => 'core',
 157                  'name' => 'test',
 158                  'includecomments' => true,
 159                  'expected' => '{{! a comment }}The rest of the template'
 160              ],
 161              'without comments' => [
 162                  'loader' => $loader,
 163                  'component' => 'core',
 164                  'name' => 'test',
 165                  'includecomments' => false,
 166                  'expected' => 'The rest of the template'
 167              ],
 168          ];
 169      }
 170  
 171      /**
 172       * Test the load function.
 173       *
 174       * @dataProvider test_load_test_cases()
 175       * @param mustache_template_source_loader $loader The loader
 176       * @param string $component The moodle component
 177       * @param string $name The template name
 178       * @param bool $includecomments Whether to strip comments
 179       * @param string $expected The expected output
 180       */
 181      public function test_load($loader, $component, $name, $includecomments, $expected) {
 182          $this->assertEquals($expected, $loader->load($component, $name, 'boost', $includecomments));
 183      }
 184  
 185      /**
 186       * Data provider for the load_with_dependencies function.
 187       */
 188      public function test_load_with_dependencies_test_cases() {
 189          // Create a bunch of templates that include one another in various ways. There is
 190          // multiple instances of recursive inclusions to test that the code doensn't get
 191          // stuck in an infinite loop.
 192          $foo = '{{! a comment }}{{> core/bar }}{{< test/bop }}{{/ test/bop}}{{#str}} help, core {{/str}}';
 193          $foo2 = '{{! a comment }}hello';
 194          $bar = '{{! a comment }}{{> core/baz }}';
 195          $baz = '{{! a comment }}{{#str}} hide, core {{/str}}';
 196          $bop = '{{! a comment }}{{< test/bim }}{{/ test/bim }}{{> core/foo }}';
 197          $bim = '{{! a comment }}{{< core/foo }}{{/ core/foo}}{{> test/foo }}';
 198          $foonocomment = '{{> core/bar }}{{< test/bop }}{{/ test/bop}}{{#str}} help, core {{/str}}';
 199          $foo2nocomment = 'hello';
 200          $barnocomment = '{{> core/baz }}';
 201          $baznocomment = '{{#str}} hide, core {{/str}}';
 202          $bopnocomment = '{{< test/bim }}{{/ test/bim }}{{> core/foo }}';
 203          $bimnocomment = '{{< core/foo }}{{/ core/foo}}{{> test/foo }}';
 204          $cache = [
 205              'core' => [
 206                  'foo' => $foo,
 207                  'bar' => $bar,
 208                  'baz' => $baz,
 209              ],
 210              'test' => [
 211                  'foo' => $foo2,
 212                  'bop' => $bop,
 213                  'bim' => $bim
 214              ]
 215          ];
 216          $loader = $this->build_loader_from_static_cache($cache);
 217  
 218          return [
 219              'no template includes w comments' => [
 220                  'loader' => $loader,
 221                  'component' => 'test',
 222                  'name' => 'foo',
 223                  'includecomments' => true,
 224                  'expected' => [
 225                      'templates' => [
 226                          'test' => [
 227                              'foo' => $foo2
 228                          ]
 229                      ],
 230                      'strings' => []
 231                  ]
 232              ],
 233              'no template includes w/o comments' => [
 234                  'loader' => $loader,
 235                  'component' => 'test',
 236                  'name' => 'foo',
 237                  'includecomments' => false,
 238                  'expected' => [
 239                      'templates' => [
 240                          'test' => [
 241                              'foo' => $foo2nocomment
 242                          ]
 243                      ],
 244                      'strings' => []
 245                  ]
 246              ],
 247              'no template includes with string w comments' => [
 248                  'loader' => $loader,
 249                  'component' => 'core',
 250                  'name' => 'baz',
 251                  'includecomments' => true,
 252                  'expected' => [
 253                      'templates' => [
 254                          'core' => [
 255                              'baz' => $baz
 256                          ]
 257                      ],
 258                      'strings' => [
 259                          'core' => [
 260                              'hide' => 'Hide'
 261                          ]
 262                      ]
 263                  ]
 264              ],
 265              'no template includes with string w/o comments' => [
 266                  'loader' => $loader,
 267                  'component' => 'core',
 268                  'name' => 'baz',
 269                  'includecomments' => false,
 270                  'expected' => [
 271                      'templates' => [
 272                          'core' => [
 273                              'baz' => $baznocomment
 274                          ]
 275                      ],
 276                      'strings' => [
 277                          'core' => [
 278                              'hide' => 'Hide'
 279                          ]
 280                      ]
 281                  ]
 282              ],
 283              'full with comments' => [
 284                  'loader' => $loader,
 285                  'component' => 'core',
 286                  'name' => 'foo',
 287                  'includecomments' => true,
 288                  'expected' => [
 289                      'templates' => [
 290                          'core' => [
 291                              'foo' => $foo,
 292                              'bar' => $bar,
 293                              'baz' => $baz
 294                          ],
 295                          'test' => [
 296                              'foo' => $foo2,
 297                              'bop' => $bop,
 298                              'bim' => $bim
 299                          ]
 300                      ],
 301                      'strings' => [
 302                          'core' => [
 303                              'help' => 'Help',
 304                              'hide' => 'Hide'
 305                          ]
 306                      ]
 307                  ]
 308              ],
 309              'full without comments' => [
 310                  'loader' => $loader,
 311                  'component' => 'core',
 312                  'name' => 'foo',
 313                  'includecomments' => false,
 314                  'expected' => [
 315                      'templates' => [
 316                          'core' => [
 317                              'foo' => $foonocomment,
 318                              'bar' => $barnocomment,
 319                              'baz' => $baznocomment
 320                          ],
 321                          'test' => [
 322                              'foo' => $foo2nocomment,
 323                              'bop' => $bopnocomment,
 324                              'bim' => $bimnocomment
 325                          ]
 326                      ],
 327                      'strings' => [
 328                          'core' => [
 329                              'help' => 'Help',
 330                              'hide' => 'Hide'
 331                          ]
 332                      ]
 333                  ]
 334              ]
 335          ];
 336      }
 337  
 338      /**
 339       * Test the load_with_dependencies function.
 340       *
 341       * @dataProvider test_load_with_dependencies_test_cases()
 342       * @param mustache_template_source_loader $loader The loader
 343       * @param string $component The moodle component
 344       * @param string $name The template name
 345       * @param bool $includecomments Whether to strip comments
 346       * @param string $expected The expected output
 347       */
 348      public function test_load_with_dependencies($loader, $component, $name, $includecomments, $expected) {
 349          $actual = $loader->load_with_dependencies($component, $name, 'boost', $includecomments);
 350          $this->assertEquals($expected, $actual);
 351      }
 352      /**
 353       * Data provider for the test_load function.
 354       */
 355      public function test_scan_template_source_for_dependencies_test_cases() {
 356          $foo = '{{! a comment }}{{> core/bar }}{{< test/bop }}{{/ test/bop}}{{#str}} help, core {{/str}}';
 357          $bar = '{{! a comment }}{{> core/baz }}';
 358          $baz = '{{! a comment }}{{#str}} hide, core {{/str}}';
 359          $bop = '{{! a comment }}hello';
 360          $multiline1 = <<<TEMPLATE
 361  {{! a comment }}{{#str}} authorreplyingprivatelytoauthor,
 362  mod_forum {{/str}}
 363  TEMPLATE;
 364          $multiline2 = <<<TEMPLATE
 365  {{! a comment }}{{#str}}
 366  authorreplyingprivatelytoauthor,
 367  mod_forum {{/str}}
 368  TEMPLATE;
 369          $multiline3 = <<<TEMPLATE
 370  {{! a comment }}{{#str}}
 371  authorreplyingprivatelytoauthor,
 372  mod_forum
 373  {{/str}}
 374  TEMPLATE;
 375          $multiline4 = <<<TEMPLATE
 376  {{! a comment }}{{#str}}
 377  authorreplyingprivatelytoauthor, mod_forum
 378  {{/str}}
 379  TEMPLATE;
 380          $multiline5 = <<<TEMPLATE
 381  {{! a comment }}{{#str}}
 382  hide
 383  {{/str}}
 384  TEMPLATE;
 385  
 386          $cache = [
 387              'core' => [
 388                  'foo' => $foo,
 389                  'bar' => $bar,
 390                  'baz' => $baz,
 391                  'bop' => $bop,
 392                  'multiline1' => $multiline1,
 393                  'multiline2' => $multiline2,
 394                  'multiline3' => $multiline3,
 395                  'multiline4' => $multiline4,
 396                  'multiline5' => $multiline5,
 397              ]
 398          ];
 399          $loader = $this->build_loader_from_static_cache($cache);
 400  
 401          return [
 402              'single template include' => [
 403                  'loader' => $loader,
 404                  'source' => $bar,
 405                  'expected' => [
 406                      'templates' => [
 407                          'core' => ['baz']
 408                      ],
 409                      'strings' => []
 410                  ]
 411              ],
 412              'single string include' => [
 413                  'loader' => $loader,
 414                  'source' => $baz,
 415                  'expected' => [
 416                      'templates' => [],
 417                      'strings' => [
 418                          'core' => ['hide']
 419                      ]
 420                  ]
 421              ],
 422              'no include' => [
 423                  'loader' => $loader,
 424                  'source' => $bop,
 425                  'expected' => [
 426                      'templates' => [],
 427                      'strings' => []
 428                  ]
 429              ],
 430              'all include' => [
 431                  'loader' => $loader,
 432                  'source' => $foo,
 433                  'expected' => [
 434                      'templates' => [
 435                          'core' => ['bar'],
 436                          'test' => ['bop']
 437                      ],
 438                      'strings' => [
 439                          'core' => ['help']
 440                      ]
 441                  ]
 442              ],
 443              'string: component on new line' => [
 444                  'loader' => $loader,
 445                  'source' => $multiline1,
 446                  'expected' => [
 447                      'templates' => [],
 448                      'strings' => [
 449                          'mod_forum' => ['authorreplyingprivatelytoauthor']
 450                      ]
 451                  ]
 452              ],
 453              'string: identifier on own line' => [
 454                  'loader' => $loader,
 455                  'source' => $multiline2,
 456                  'expected' => [
 457                      'templates' => [],
 458                      'strings' => [
 459                          'mod_forum' => ['authorreplyingprivatelytoauthor']
 460                      ]
 461                  ]
 462              ],
 463              'string: all parts on new lines' => [
 464                  'loader' => $loader,
 465                  'source' => $multiline3,
 466                  'expected' => [
 467                      'templates' => [],
 468                      'strings' => [
 469                          'mod_forum' => ['authorreplyingprivatelytoauthor']
 470                      ]
 471                  ]
 472              ],
 473              'string: id and component on own line' => [
 474                  'loader' => $loader,
 475                  'source' => $multiline4,
 476                  'expected' => [
 477                      'templates' => [],
 478                      'strings' => [
 479                          'mod_forum' => ['authorreplyingprivatelytoauthor']
 480                      ]
 481                  ]
 482              ],
 483              'string: no component' => [
 484                  'loader' => $loader,
 485                  'source' => $multiline5,
 486                  'expected' => [
 487                      'templates' => [],
 488                      'strings' => [
 489                          'core' => ['hide']
 490                      ]
 491                  ]
 492              ],
 493          ];
 494      }
 495  
 496      /**
 497       * Test the scan_template_source_for_dependencies function.
 498       *
 499       * @dataProvider test_scan_template_source_for_dependencies_test_cases()
 500       * @param mustache_template_source_loader $loader The loader
 501       * @param string $source The template to test
 502       * @param string $expected The expected output
 503       */
 504      public function test_scan_template_source_for_dependencies($loader, $source, $expected) {
 505          $actual = phpunit_util::call_internal_method(
 506              $loader,
 507              'scan_template_source_for_dependencies',
 508              [$source],
 509              \core\output\mustache_template_source_loader::class
 510          );
 511          $this->assertEquals($expected, $actual);
 512      }
 513  
 514      /**
 515       * Create an instance of mustache_template_source_loader which loads its templates
 516       * from the given cache rather than disk.
 517       *
 518       * @param array $cache A cache of templates
 519       * @return mustache_template_source_loader
 520       */
 521      private function build_loader_from_static_cache(array $cache) : mustache_template_source_loader {
 522          return new mustache_template_source_loader(function($component, $name, $themename) use ($cache) {
 523              return $cache[$component][$name];
 524          });
 525      }
 526  }