1<?php 2 3/* 4 * This file is part of Twig. 5 * 6 * (c) Fabien Potencier 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12use Twig\Environment; 13 14class Twig_Tests_Extension_CoreTest extends \PHPUnit\Framework\TestCase 15{ 16 /** 17 * @dataProvider getRandomFunctionTestData 18 */ 19 public function testRandomFunction(array $expectedInArray, $value1, $value2 = null) 20 { 21 $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); 22 for ($i = 0; $i < 100; ++$i) { 23 $this->assertTrue(\in_array(twig_random($env, $value1, $value2), $expectedInArray, true)); // assertContains() would not consider the type 24 } 25 } 26 27 public function getRandomFunctionTestData() 28 { 29 return [ 30 'array' => [ 31 ['apple', 'orange', 'citrus'], 32 ['apple', 'orange', 'citrus'], 33 ], 34 'Traversable' => [ 35 ['apple', 'orange', 'citrus'], 36 new ArrayObject(['apple', 'orange', 'citrus']), 37 ], 38 'unicode string' => [ 39 ['Ä', '€', 'é'], 40 'Ä€é', 41 ], 42 'numeric but string' => [ 43 ['1', '2', '3'], 44 '123', 45 ], 46 'integer' => [ 47 range(0, 5, 1), 48 5, 49 ], 50 'float' => [ 51 range(0, 5, 1), 52 5.9, 53 ], 54 'negative' => [ 55 [0, -1, -2], 56 -2, 57 ], 58 'min max int' => [ 59 range(50, 100), 60 50, 61 100, 62 ], 63 'min max float' => [ 64 range(-10, 10), 65 -9.5, 66 9.5, 67 ], 68 'min null' => [ 69 range(0, 100), 70 null, 71 100, 72 ], 73 ]; 74 } 75 76 public function testRandomFunctionWithoutParameter() 77 { 78 $max = mt_getrandmax(); 79 80 for ($i = 0; $i < 100; ++$i) { 81 $val = twig_random(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock())); 82 $this->assertTrue(\is_int($val) && $val >= 0 && $val <= $max); 83 } 84 } 85 86 public function testRandomFunctionReturnsAsIs() 87 { 88 $this->assertSame('', twig_random(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()), '')); 89 $this->assertSame('', twig_random(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock(), ['charset' => null]), '')); 90 91 $instance = new \stdClass(); 92 $this->assertSame($instance, twig_random(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()), $instance)); 93 } 94 95 /** 96 * @expectedException \Twig\Error\RuntimeError 97 */ 98 public function testRandomFunctionOfEmptyArrayThrowsException() 99 { 100 twig_random(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()), []); 101 } 102 103 public function testRandomFunctionOnNonUTF8String() 104 { 105 if (!\function_exists('iconv') && !\function_exists('mb_convert_encoding')) { 106 $this->markTestSkipped('needs iconv or mbstring'); 107 } 108 109 $twig = new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()); 110 $twig->setCharset('ISO-8859-1'); 111 112 $text = twig_convert_encoding('Äé', 'ISO-8859-1', 'UTF-8'); 113 for ($i = 0; $i < 30; ++$i) { 114 $rand = twig_random($twig, $text); 115 $this->assertTrue(\in_array(twig_convert_encoding($rand, 'UTF-8', 'ISO-8859-1'), ['Ä', 'é'], true)); 116 } 117 } 118 119 public function testReverseFilterOnNonUTF8String() 120 { 121 if (!\function_exists('iconv') && !\function_exists('mb_convert_encoding')) { 122 $this->markTestSkipped('needs iconv or mbstring'); 123 } 124 125 $twig = new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()); 126 $twig->setCharset('ISO-8859-1'); 127 128 $input = twig_convert_encoding('Äé', 'ISO-8859-1', 'UTF-8'); 129 $output = twig_convert_encoding(twig_reverse_filter($twig, $input), 'UTF-8', 'ISO-8859-1'); 130 131 $this->assertEquals($output, 'éÄ'); 132 } 133 134 /** 135 * @dataProvider provideCustomEscaperCases 136 */ 137 public function testCustomEscaper($expected, $string, $strategy) 138 { 139 $twig = new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()); 140 $twig->getExtension('\Twig\Extension\CoreExtension')->setEscaper('foo', 'foo_escaper_for_test'); 141 142 $this->assertSame($expected, twig_escape_filter($twig, $string, $strategy)); 143 } 144 145 public function provideCustomEscaperCases() 146 { 147 return [ 148 ['fooUTF-8', 'foo', 'foo'], 149 ['UTF-8', null, 'foo'], 150 ['42UTF-8', 42, 'foo'], 151 ]; 152 } 153 154 /** 155 * @expectedException \Twig\Error\RuntimeError 156 */ 157 public function testUnknownCustomEscaper() 158 { 159 twig_escape_filter(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()), 'foo', 'bar'); 160 } 161 162 /** 163 * @dataProvider provideTwigFirstCases 164 */ 165 public function testTwigFirst($expected, $input) 166 { 167 $twig = new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()); 168 $this->assertSame($expected, twig_first($twig, $input)); 169 } 170 171 public function provideTwigFirstCases() 172 { 173 $i = [1 => 'a', 2 => 'b', 3 => 'c']; 174 175 return [ 176 ['a', 'abc'], 177 [1, [1, 2, 3]], 178 ['', null], 179 ['', ''], 180 ['a', new CoreTestIterator($i, array_keys($i), true, 3)], 181 ]; 182 } 183 184 /** 185 * @dataProvider provideTwigLastCases 186 */ 187 public function testTwigLast($expected, $input) 188 { 189 $twig = new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()); 190 $this->assertSame($expected, twig_last($twig, $input)); 191 } 192 193 public function provideTwigLastCases() 194 { 195 $i = [1 => 'a', 2 => 'b', 3 => 'c']; 196 197 return [ 198 ['c', 'abc'], 199 [3, [1, 2, 3]], 200 ['', null], 201 ['', ''], 202 ['c', new CoreTestIterator($i, array_keys($i), true)], 203 ]; 204 } 205 206 /** 207 * @dataProvider provideArrayKeyCases 208 */ 209 public function testArrayKeysFilter(array $expected, $input) 210 { 211 $this->assertSame($expected, twig_get_array_keys_filter($input)); 212 } 213 214 public function provideArrayKeyCases() 215 { 216 $array = ['a' => 'a1', 'b' => 'b1', 'c' => 'c1']; 217 $keys = array_keys($array); 218 219 return [ 220 [$keys, $array], 221 [$keys, new CoreTestIterator($array, $keys)], 222 [$keys, new CoreTestIteratorAggregate($array, $keys)], 223 [$keys, new CoreTestIteratorAggregateAggregate($array, $keys)], 224 [[], null], 225 [['a'], new \SimpleXMLElement('<xml><a></a></xml>')], 226 ]; 227 } 228 229 /** 230 * @dataProvider provideInFilterCases 231 */ 232 public function testInFilter($expected, $value, $compare) 233 { 234 $this->assertSame($expected, twig_in_filter($value, $compare)); 235 } 236 237 public function provideInFilterCases() 238 { 239 $array = [1, 2, 'a' => 3, 5, 6, 7]; 240 $keys = array_keys($array); 241 242 return [ 243 [true, 1, $array], 244 [true, '3', $array], 245 [true, '3', 'abc3def'], 246 [true, 1, new CoreTestIterator($array, $keys, true, 1)], 247 [true, '3', new CoreTestIterator($array, $keys, true, 3)], 248 [true, '3', new CoreTestIteratorAggregateAggregate($array, $keys, true, 3)], 249 [false, 4, $array], 250 [false, 4, new CoreTestIterator($array, $keys, true)], 251 [false, 4, new CoreTestIteratorAggregateAggregate($array, $keys, true)], 252 [false, 1, 1], 253 [true, 'b', new \SimpleXMLElement('<xml><a>b</a></xml>')], 254 ]; 255 } 256 257 /** 258 * @dataProvider provideSliceFilterCases 259 */ 260 public function testSliceFilter($expected, $input, $start, $length = null, $preserveKeys = false) 261 { 262 $twig = new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()); 263 $this->assertSame($expected, twig_slice($twig, $input, $start, $length, $preserveKeys)); 264 } 265 266 public function provideSliceFilterCases() 267 { 268 $i = ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4]; 269 $keys = array_keys($i); 270 271 return [ 272 [['a' => 1], $i, 0, 1, true], 273 [['a' => 1], $i, 0, 1, false], 274 [['b' => 2, 'c' => 3], $i, 1, 2], 275 [[1], [1, 2, 3, 4], 0, 1], 276 [[2, 3], [1, 2, 3, 4], 1, 2], 277 [[2, 3], new CoreTestIterator($i, $keys, true), 1, 2], 278 [['c' => 3, 'd' => 4], new CoreTestIteratorAggregate($i, $keys, true), 2, null, true], 279 [$i, new CoreTestIterator($i, $keys, true), 0, \count($keys) + 10, true], 280 [[], new CoreTestIterator($i, $keys, true), \count($keys) + 10], 281 ['de', 'abcdef', 3, 2], 282 [[], new \SimpleXMLElement('<items><item>1</item><item>2</item></items>'), 3], 283 [[], new \ArrayIterator([1, 2]), 3], 284 ]; 285 } 286} 287 288function foo_escaper_for_test(Environment $env, $string, $charset) 289{ 290 return $string.$charset; 291} 292 293final class CoreTestIteratorAggregate implements \IteratorAggregate 294{ 295 private $iterator; 296 297 public function __construct(array $array, array $keys, $allowAccess = false, $maxPosition = false) 298 { 299 $this->iterator = new CoreTestIterator($array, $keys, $allowAccess, $maxPosition); 300 } 301 302 public function getIterator() 303 { 304 return $this->iterator; 305 } 306} 307 308final class CoreTestIteratorAggregateAggregate implements \IteratorAggregate 309{ 310 private $iterator; 311 312 public function __construct(array $array, array $keys, $allowValueAccess = false, $maxPosition = false) 313 { 314 $this->iterator = new CoreTestIteratorAggregate($array, $keys, $allowValueAccess, $maxPosition); 315 } 316 317 public function getIterator() 318 { 319 return $this->iterator; 320 } 321} 322 323final class CoreTestIterator implements Iterator 324{ 325 private $position; 326 private $array; 327 private $arrayKeys; 328 private $allowValueAccess; 329 private $maxPosition; 330 331 public function __construct(array $values, array $keys, $allowValueAccess = false, $maxPosition = false) 332 { 333 $this->array = $values; 334 $this->arrayKeys = $keys; 335 $this->position = 0; 336 $this->allowValueAccess = $allowValueAccess; 337 $this->maxPosition = false === $maxPosition ? \count($values) + 1 : $maxPosition; 338 } 339 340 public function rewind() 341 { 342 $this->position = 0; 343 } 344 345 public function current() 346 { 347 if ($this->allowValueAccess) { 348 return $this->array[$this->key()]; 349 } 350 351 throw new \LogicException('Code should only use the keys, not the values provided by iterator.'); 352 } 353 354 public function key() 355 { 356 return $this->arrayKeys[$this->position]; 357 } 358 359 public function next() 360 { 361 ++$this->position; 362 if ($this->position === $this->maxPosition) { 363 throw new \LogicException(sprintf('Code should not iterate beyond %d.', $this->maxPosition)); 364 } 365 } 366 367 public function valid() 368 { 369 return isset($this->arrayKeys[$this->position]); 370 } 371} 372