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