Merge branch 'MDL-57636_master' of https://github.com/dasistwas/moodle
[moodle.git] / lib / tests / mustache_template_source_loader_test.php
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/>.
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  */
25 defined('MOODLE_INTERNAL') || die();
27 use core\output\mustache_template_source_loader;
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() {
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) {
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/
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.
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.
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
102             Lemmings template.
104             The purpose of this template is to render a lot of lemmings.
106             Classes required for JS:
107             * none
109             Data attributes required for JS:
110             * none
112             Context variables required for this template:
113             * attributes Array of name / value pairs.
115             Example context (json):
116             {
117                 "lemmings": [
118                     { "name": "Lemmy Winks", "age" : 1, "size" : "big" },
119                     { "name": "Rocky", "age" : 2, "size" : "small" }
120                 ]
121             }
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;
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     }
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);
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     }
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     }
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);
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     }
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         $cache = [
361             'core' => [
362                 'foo' => $foo,
363                 'bar' => $bar,
364                 'baz' => $baz,
365                 'bop' => $bop
366             ]
367         ];
368         $loader = $this->build_loader_from_static_cache($cache);
370         return [
371             'single template include' => [
372                 'loader' => $loader,
373                 'source' => $bar,
374                 'expected' => [
375                     'templates' => [
376                         'core' => ['baz']
377                     ],
378                     'strings' => []
379                 ]
380             ],
381             'single string include' => [
382                 'loader' => $loader,
383                 'source' => $baz,
384                 'expected' => [
385                     'templates' => [],
386                     'strings' => [
387                         'core' => ['hide']
388                     ]
389                 ]
390             ],
391             'no include' => [
392                 'loader' => $loader,
393                 'source' => $bop,
394                 'expected' => [
395                     'templates' => [],
396                     'strings' => []
397                 ]
398             ],
399             'all include' => [
400                 'loader' => $loader,
401                 'source' => $foo,
402                 'expected' => [
403                     'templates' => [
404                         'core' => ['bar'],
405                         'test' => ['bop']
406                     ],
407                     'strings' => [
408                         'core' => ['help']
409                     ]
410                 ]
411             ],
412         ];
413     }
415     /**
416      * Test the scan_template_source_for_dependencies function.
417      *
418      * @dataProvider test_scan_template_source_for_dependencies_test_cases()
419      * @param mustache_template_source_loader $loader The loader
420      * @param string $source The template to test
421      * @param string $expected The expected output
422      */
423     public function test_scan_template_source_for_dependencies($loader, $source, $expected) {
424         $actual = phpunit_util::call_internal_method(
425             $loader,
426             'scan_template_source_for_dependencies',
427             [$source],
428             \core\output\mustache_template_source_loader::class
429         );
430         $this->assertEquals($expected, $actual);
431     }
433     /**
434      * Create an instance of mustache_template_source_loader which loads its templates
435      * from the given cache rather than disk.
436      *
437      * @param array $cache A cache of templates
438      * @return mustache_template_source_loader
439      */
440     private function build_loader_from_static_cache(array $cache) : mustache_template_source_loader {
441         return new mustache_template_source_loader(function($component, $name, $themename) use ($cache) {
442             return $cache[$component][$name];
443         });
444     }