1<?php
2
3/**
4 * Class AutoloaderTest
5 */
6class HandlebarsTest extends PHPUnit\Framework\TestCase
7{
8    /**
9     * Test handlebars autoloader
10     *
11     * @return void
12     */
13    public function testAutoLoad()
14    {
15        Handlebars\Autoloader::register(realpath(__DIR__ . '/../fixture/'));
16
17        $this->assertTrue(class_exists('Handlebars\\Test'));
18        $this->assertTrue(class_exists('Handlebars\\Example\\Test'));
19    }
20
21    /**
22     * Test basic tags
23     *
24     * @param string $src    handlebars source
25     * @param array  $data   data
26     * @param string $result expected data
27     *
28     * @dataProvider simpleTagdataProvider
29     *
30     * @return void
31     */
32    public function testBasicTags($src, $data, $result)
33    {
34        $loader = new \Handlebars\Loader\StringLoader();
35        $engine = new \Handlebars\Handlebars(array('loader' => $loader));
36        $this->assertEquals($result, $engine->render($src, $data));
37    }
38
39    /**
40     * Simple tag provider
41     *
42     * @return array
43     */
44    public function simpleTagdataProvider()
45    {
46        return array(
47            array(
48                '{{! This is comment}}',
49                array(),
50                ''
51            ),
52            array(
53                '{{data}}',
54                array('data' => 'result'),
55                'result'
56            ),
57            array(
58                '{{data.key}}',
59                array('data' => array('key' => 'result')),
60                'result'
61            ),
62        );
63    }
64
65
66    /**
67     * Test helpers (internal helpers)
68     *
69     * @param string $src    handlebars source
70     * @param array  $data   data
71     * @param string $result expected data
72     *
73     * @dataProvider internalHelpersdataProvider
74     *
75     * @return void
76     */
77    public function testSimpleHelpers($src, $data, $result)
78    {
79        $loader = new \Handlebars\Loader\StringLoader();
80        $helpers = new \Handlebars\Helpers();
81        $engine = new \Handlebars\Handlebars(array('loader' => $loader, 'helpers' => $helpers));
82
83        $this->assertEquals($result, $engine->render($src, $data));
84    }
85
86    /**
87     * Simple helpers provider
88     *
89     * @return array
90     */
91    public function internalHelpersdataProvider()
92    {
93        return [
94            [
95                '{{#if data}}Yes{{/if}}',
96                ['data' => true],
97                'Yes'
98            ],
99            [
100                '{{#if data}}Yes{{/if}}',
101                ['data' => false],
102                ''
103            ],
104            [
105                '{{#unless data}}OK{{/unless}}',
106                ['data' => false],
107                'OK'
108            ],
109            [
110                '{{#unless data}}OK {{else}}I believe{{/unless}}',
111                ['data' => true],
112                'I believe'
113            ],
114            [
115                '{{#with data}}{{key}}{{/with}}',
116                ['data' => ['key' => 'result']],
117                'result'
118            ],
119            [
120                '{{#each data}}{{this}}{{/each}}',
121                ['data' => [1, 2, 3, 4]],
122                '1234'
123            ],
124            [
125                '{{#each data[0:2]}}{{this}}{{/each}}',
126                ['data' => [1, 2, 3, 4]],
127                '12'
128            ],
129            [
130                '{{#each data[1:2]}}{{this}}{{/each}}',
131                ['data' => [1, 2, 3, 4]],
132                '23'
133            ],
134            [
135                '{{#upper data}}',
136                ['data' => "hello"],
137                'HELLO'
138            ],
139            [
140                '{{#lower data}}',
141                ['data' => "HELlO"],
142                'hello'
143            ],
144            [
145                '{{#capitalize data}}',
146                ['data' => "hello"],
147                'Hello'
148            ],
149            [
150                '{{#capitalize_words data}}',
151                ['data' => "hello world"],
152                'Hello World'
153            ],
154            [
155                '{{#reverse data}}',
156                ['data' => "hello"],
157                'olleh'
158            ],
159            [
160                "{{#inflect count 'album' 'albums' }}",
161                ["count" => 1],
162                'album'
163            ],
164            [
165                "{{#inflect count 'album' 'albums' }}",
166                ["count" => 10],
167                'albums'
168            ],
169            [
170                "{{#inflect count '%d album' '%d albums' }}",
171                ["count" => 1],
172                '1 album'
173            ],
174            [
175                "{{#inflect count '%d album' '%d albums' }}",
176                ["count" => 10],
177                '10 albums'
178            ],
179            [
180                "{{#default data 'OK' }}",
181                ["data" => "hello"],
182                'hello'
183            ],
184            [
185                "{{#default data 'OK' }}",
186                [],
187                'OK'
188            ],
189            [
190                "{{#truncate data 8 '...'}}",
191                ["data" => "Hello World! How are you?"],
192                'Hello Wo...'
193            ],
194            [
195                "{{#raw}}I'm raw {{data}}{{/raw}}",
196                ["data" => "raw to be included, but won't :)"],
197                "I'm raw {{data}}"
198            ],
199            [
200                "{{#repeat 3}}Yes {{/repeat}}",
201                [],
202                "Yes Yes Yes "
203            ],
204            [
205                "{{#repeat 4}}Nice {{data}} {{/repeat}}",
206                ["data" => "Daddy!"],
207                "Nice Daddy! Nice Daddy! Nice Daddy! Nice Daddy! "
208            ],
209            [
210                "{{#define test}}I'm Defined and Invoked{{/define}}{{#invoke test}}",
211                [],
212                "I'm Defined and Invoked"
213            ],
214        ];
215    }
216
217    /**
218     * @param string $src
219     * @param array $data
220     * @param string $result
221     * @param bool $enableDataVariables
222     * @dataProvider internalDataVariablesDataProvider
223     */
224    public function testDataVariables($src, $data, $result, $enableDataVariables)
225    {
226        $loader = new \Handlebars\Loader\StringLoader();
227        $helpers = new \Handlebars\Helpers();
228        $engine = new \Handlebars\Handlebars(array(
229            'loader' => $loader,
230            'helpers' => $helpers,
231            'enableDataVariables'=> $enableDataVariables,
232        ));
233
234        $this->assertEquals($result, $engine->render($src, $data));
235    }
236
237    public function testDataVariables1()
238    {
239        $object = new stdClass;
240        $object->{'@first'} = 'apple';
241        $object->{'@last'} = 'banana';
242        $object->{'@index'} = 'carrot';
243        $object->{'@unknown'} = 'zucchini';
244        $data = ['data' => [$object]];
245        $engine = new \Handlebars\Handlebars(array(
246            'loader' => new \Handlebars\Loader\StringLoader(),
247            'helpers' => new \Handlebars\Helpers(),
248            'enableDataVariables'=> false,
249        ));
250        $template = "{{#each data}}{{@first}}, {{@last}}, {{@index}}, {{@unknown}}{{/each}}";
251
252        $this->assertEquals('apple, banana, 0, zucchini', $engine->render($template, $data));
253    }
254
255    /**
256     * Data provider for data variables
257     * @return array
258     */
259    public function internalDataVariablesDataProvider()
260    {
261        // Build a standard set of objects to test against
262        $keyPropertyName = '@key';
263        $firstPropertyName = '@first';
264        $lastPropertyName = '@last';
265        $unknownPropertyName = '@unknown';
266        $objects = [];
267        foreach (['apple', 'banana', 'carrot', 'zucchini'] as $itemValue) {
268            $object = new stdClass();
269            $object->$keyPropertyName = $itemValue;
270            $object->$firstPropertyName = $itemValue;
271            $object->$lastPropertyName = $itemValue;
272            $object->$unknownPropertyName = $itemValue;
273            $objects[] = $object;
274        }
275
276        // Build a list of scenarios. These will be used later to build fanned out scenarios that will be used against
277        // the test. Each entry represents two different tests: (1) when enableDataVariables is enabled and (2) not enabled.
278        $scenarios = [
279            [
280                'src' => '{{#each data}}{{@index}}{{/each}}',
281                'data' => ['data' => ['apple', 'banana', 'carrot', 'zucchini']],
282                // @index should work the same regardless of the feature flag
283                'outputNotEnabled' => '0123',
284                'outputEnabled' => '0123',
285            ],
286            [
287                'src' => '{{#each data}}{{@key}}{{/each}}',
288                'data' => ['data' => ['apple', 'banana', 'carrot', 'zucchini']],
289                'outputNotEnabled' => '',
290                'outputEnabled' => '0123'
291            ],
292            [
293                'src' => '{{#each data}}{{#each this}}outer: {{@../key}},inner: {{@key}};{{/each}}{{/each}}',
294                'data' => ['data' => [['apple', 'banana'], ['carrot', 'zucchini']]],
295                'outputNotEnabled' => 'outer: ,inner: ;outer: ,inner: ;outer: ,inner: ;outer: ,inner: ;',
296                'outputEnabled' => 'outer: 0,inner: 0;outer: 0,inner: 1;outer: 1,inner: 0;outer: 1,inner: 1;',
297            ],
298            [
299                'src' => '{{#each data}}{{#if @first}}true{{else}}false{{/if}}{{/each}}',
300                'data' => ['data' => ['apple', 'banana', 'carrot', 'zucchini']],
301                'outputNotEnabled' => 'falsefalsefalsefalse',
302                'outputEnabled' => 'truefalsefalsefalse',
303            ],
304            [
305                'src' => '{{#each data}}{{@first}}{{/each}}',
306                'data' => ['data' => ['apple', 'banana', 'carrot', 'zucchini']],
307                'outputNotEnabled' => '',
308                'outputEnabled' => 'truefalsefalsefalse',
309            ],
310            [
311                'src' => '{{#each data}}{{#each this}}outer: {{@../first}},inner: {{@first}};{{/each}}{{/each}}',
312                'data' => ['data' => [['apple', 'banana'], ['carrot', 'zucchini']]],
313                'outputNotEnabled' => 'outer: ,inner: ;outer: ,inner: ;outer: ,inner: ;outer: ,inner: ;',
314                'outputEnabled' => 'outer: true,inner: true;outer: true,inner: false;outer: false,inner: true;outer: false,inner: false;',
315            ],
316            [
317                'src' => '{{#each data}}{{#if @last}}true{{else}}false{{/if}}{{/each}}',
318                'data' => ['data' => ['apple', 'banana', 'carrot', 'zucchini']],
319                'outputNotEnabled' => 'falsefalsefalsefalse',
320                'outputEnabled' => 'falsefalsefalsetrue'
321            ],
322            [
323                'src' => '{{#each data}}{{@last}}{{/each}}',
324                'data' => ['data' => ['apple', 'banana', 'carrot', 'zucchini']],
325                'outputNotEnabled' => '',
326                'outputEnabled' => 'falsefalsefalsetrue'
327            ],
328            [
329                'src' => '{{#each data}}{{#each this}}outer: {{@../last}},inner: {{@last}};{{/each}}{{/each}}',
330                'data' => ['data' => [['apple', 'banana'], ['carrot', 'zucchini']]],
331                'outputNotEnabled' => 'outer: ,inner: ;outer: ,inner: ;outer: ,inner: ;outer: ,inner: ;',
332                'outputEnabled' => 'outer: false,inner: false;outer: false,inner: true;outer: true,inner: false;outer: true,inner: true;'
333            ],
334            [
335                // @index variables are ignored and the data variable is used
336                'src' => '{{#each data}}{{@index}}{{/each}}',
337                'data' => ['data' => [['@index' => 'apple'], ['@index' => 'banana'], ['@index' => 'carrot'], ['@index' => 'zucchini']]],
338                'outputNotEnabled' => '0123',
339                'outputEnabled' => '0123'
340            ],
341            [
342                // @key variables are ignored and the data variable is used
343                'src' => '{{#each data}}{{@index}}{{/each}}',
344                'data' => ['data' => $objects],
345                'outputNotEnabled' => '0123',
346                'outputEnabled' => '0123'
347            ],
348            [
349                // @first variables are used when data variables are not enabled.
350                'src' => '{{#each data}}{{@first}}{{/each}}',
351                'data' => ['data' => $objects],
352                'outputNotEnabled' => 'applebananacarrotzucchini',
353                'outputEnabled' => 'truefalsefalsefalse'
354            ],
355            [
356                // @last variables are used when data variables are not enabled.
357                'src' => '{{#each data}}{{@last}}{{/each}}',
358                'data' => ['data' => $objects],
359                'outputNotEnabled' => 'applebananacarrotzucchini',
360                'outputEnabled' => 'falsefalsefalsetrue'
361            ],
362            [
363                // @unknown variables are used when data variables are not enabled however since "unknown" is not a valid
364                // value it should ignored.
365                'src' => '{{#each data}}{{@unknown}}{{/each}}',
366                'data' => ['data' => $objects],
367                'outputNotEnabled' => 'applebananacarrotzucchini',
368                'outputEnabled' => ''
369            ],
370            [
371                'src' => '{{#each data}}{{@key}}{{/each}}',
372                'data' => ['data' => ['fruit' => 'apple', '19' => 'banana', 'true' => 'carrot']],
373                'outputNotEnabled' => 'fruit19true',
374                'outputEnabled' => 'fruit19true',
375            ],
376        ];
377
378        // Build out a test case for when the enableDataVariables feature is enabled and when it's not
379        $fannedOutScenarios = [];
380        foreach ($scenarios as $scenario) {
381            $fannedOutScenarios['not enabled: ' . $scenario['src']] = [
382                $scenario['src'],
383                $scenario['data'],
384                $scenario['outputNotEnabled'],
385                false,
386            ];
387            $fannedOutScenarios['enabled: ' . $scenario['src']] = [
388                $scenario['src'],
389                $scenario['data'],
390                $scenario['outputEnabled'],
391                true,
392            ];
393        }
394        return $fannedOutScenarios;
395    }
396
397    /**
398     * Management helpers
399     */
400    public function testHelpersManagement()
401    {
402        $helpers = new \Handlebars\Helpers(array('test' => function () {
403        }), false);
404        $engine = new \Handlebars\Handlebars(array('helpers' => $helpers));
405        $this->assertTrue(is_callable($engine->getHelper('test')));
406        $this->assertTrue($engine->hasHelper('test'));
407        $engine->removeHelper('test');
408        $this->assertFalse($engine->hasHelper('test'));
409    }
410
411    /**
412     * Custom helper test
413     */
414    public function testCustomHelper()
415    {
416        $loader = new \Handlebars\Loader\StringLoader();
417        $engine = new \Handlebars\Handlebars(array('loader' => $loader));
418        $engine->addHelper('test', function () {
419            return 'Test helper is called';
420        });
421        $this->assertEquals('Test helper is called', $engine->render('{{#test}}', []));
422    }
423
424    /**
425     * @param $dir
426     *
427     * @return bool
428     */
429    private function delTree($dir)
430    {
431        $contents = scandir($dir);
432        if ($contents === false) {
433            return;
434        }
435        $files = array_diff($contents, array('.', '..'));
436        foreach ($files as $file) {
437            (is_dir("$dir/$file")) ? delTree("$dir/$file") : unlink("$dir/$file");
438        }
439
440        return rmdir($dir);
441    }
442
443    /**
444     * Its not a good test :) but ok
445     */
446    public function testCacheSystem()
447    {
448        $path = sys_get_temp_dir() . '/__cache__handlebars';
449
450        @$this->delTree($path);
451
452        $dummy = new \Handlebars\Cache\Disk($path);
453        $engine = new \Handlebars\Handlebars(array('cache' => $dummy));
454        $this->assertEquals(0, count(glob($path . '/*')));
455        $engine->render('test', array());
456        $this->assertEquals(1, count(glob($path . '/*')));
457    }
458}
459