PurePlateTest.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. <?php
  2. namespace Test\Michel\PurePlate;
  3. use ErrorException;
  4. use Michel\PurePlate\Engine;
  5. use Michel\UniTester\TestCase;
  6. final class PurePlateTest extends TestCase
  7. {
  8. private Engine $engine;
  9. private string $cacheDir = __DIR__ . '/cache';
  10. private string $tplDir = __DIR__ . '/tpl';
  11. protected function setUp(): void
  12. {
  13. if (!is_dir($this->cacheDir)) {
  14. mkdir($this->cacheDir);
  15. }
  16. if (!is_dir($this->tplDir)) {
  17. mkdir($this->tplDir);
  18. }
  19. $this->engine = new Engine($this->tplDir, true, $this->cacheDir);
  20. }
  21. protected function tearDown(): void
  22. {
  23. if (is_dir($this->cacheDir)) {
  24. array_map('unlink', glob("$this->cacheDir/*.*"));
  25. rmdir($this->cacheDir);
  26. }
  27. if (is_dir($this->tplDir)) {
  28. array_map('unlink', glob("$this->tplDir/*.*"));
  29. rmdir($this->tplDir);
  30. }
  31. }
  32. protected function execute(): void
  33. {
  34. $this->itRendersComment();
  35. $this->itRendersSimpleVariables();
  36. $this->itHandlesFiltersWithArguments();
  37. $this->itExecutesLogicBlocks();
  38. $this->itMapsErrorsToOriginalTemplateLine();
  39. $this->itHandlesComplexLogicAndNotOperator();
  40. $this->itHandlesForeachWithObjectsAndArrays();
  41. $this->itHandlesComplexNestedLogic();
  42. $this->itHandlesNestedForeachAndIf();
  43. $this->itHandlesIsNotEmptySyntax();
  44. $this->itHandlesNativeArraySyntax();
  45. }
  46. public function itRendersComment()
  47. {
  48. $tpl = "{# {% include 'flash_bag.html.twig' %}#}\n{# {% include 'flash_bag.html.twig' %}#}";
  49. file_put_contents($this->tplDir . '/comment.html', $tpl);
  50. $output = $this->engine->render('comment.html', []);
  51. $this->assertEquals('', trim($output));
  52. }
  53. /** @test */
  54. public function itRendersSimpleVariables()
  55. {
  56. $tpl = "Hello {{ user.name }}!";
  57. file_put_contents($this->tplDir . '/test.html', $tpl);
  58. $output = $this->engine->render('test.html', ['user' => (object)['name' => 'Michel']]);
  59. $this->assertEquals("Hello Michel!", trim($output));
  60. }
  61. /** @test */
  62. public function itHandlesFiltersWithArguments()
  63. {
  64. $tpl = "Total: {{ price | round(2) }} €";
  65. file_put_contents($this->tplDir . '/filter.html', $tpl);
  66. $output = $this->engine->render('filter.html', ['price' => 12.556]);
  67. $this->assertEquals("Total: 12.56 €", trim($output));
  68. }
  69. /** @test */
  70. public function itExecutesLogicBlocks()
  71. {
  72. $tpl = "{% if show %}YES{% else %}NO{% endif %}";
  73. file_put_contents($this->tplDir . '/logic.html', $tpl);
  74. $this->assertEquals("YES", trim($this->engine->render('logic.html', ['show' => true])));
  75. $this->assertEquals("NO", trim($this->engine->render('logic.html', ['show' => false])));
  76. }
  77. /** @test */
  78. public function itMapsErrorsToOriginalTemplateLine()
  79. {
  80. $tpl = "Line 1\nLine 2\n{{ undefined_var.property }}";
  81. file_put_contents($this->tplDir . '/error.html', $tpl);
  82. try {
  83. $this->engine->render('error.html', []);
  84. $this->fail("L'exception aurait dû être lancée.");
  85. } catch (ErrorException $e) {
  86. // Vérification du mapping de ligne magique /*L:3;F:...*/
  87. $this->assertEquals(3, $e->getLine());
  88. $this->assertStringContains($e->getFile(), 'error.html');
  89. }
  90. }
  91. /** @test */
  92. public function itHandlesComplexLogicAndNotOperator()
  93. {
  94. $tpl = "{% if not user.is_active %}Inactif{% endif %}";
  95. file_put_contents($this->tplDir . '/not.html', $tpl);
  96. $output = $this->engine->render('not.html', ['user' => (object)['is_active' => false]]);
  97. $this->assertEquals("Inactif", trim($output));
  98. }
  99. /** @test */
  100. public function itHandlesForeachWithObjectsAndArrays()
  101. {
  102. $tpl = "<ul>{% foreach users as user %}<li>{{ user.name }} ({{ user.role }})</li>{% endforeach %}</ul>";
  103. file_put_contents($this->tplDir . '/foreach.html', $tpl);
  104. $data = [
  105. 'users' => [
  106. (object)['name' => 'Alice', 'role' => 'Admin'],
  107. (object)['name' => 'Bob', 'role' => 'User'],
  108. ]
  109. ];
  110. $output = $this->engine->render('foreach.html', $data);
  111. $this->assertStringContains($output, 'Alice (Admin)');
  112. $this->assertStringContains($output, 'Bob (User)');
  113. }
  114. /** @test */
  115. public function itHandlesComplexNestedLogic()
  116. {
  117. $tpl = "{% if (user.age >= 18 and user.has_permit) or user.role == 'admin' %}ACCESS GRANTED{% endif %}";
  118. file_put_contents($this->tplDir . '/complex_logic.html', $tpl);
  119. $res1 = $this->engine->render('complex_logic.html', [
  120. 'user' => (object)['age' => 17, 'has_permit' => false, 'role' => 'admin']
  121. ]);
  122. $this->assertEquals("ACCESS GRANTED", trim($res1));
  123. $res2 = $this->engine->render('complex_logic.html', [
  124. 'user' => (object)['age' => 20, 'has_permit' => true, 'role' => 'user']
  125. ]);
  126. $this->assertEquals("ACCESS GRANTED", trim($res2));
  127. $res3 = $this->engine->render('complex_logic.html', [
  128. 'user' => (object)['age' => 16, 'has_permit' => true, 'role' => 'user']
  129. ]);
  130. $this->assertEquals("", trim($res3));
  131. }
  132. /** @test */
  133. public function itHandlesNestedForeachAndIf()
  134. {
  135. $tpl = "{% foreach categories as cat %}
  136. {{ cat.name }}:
  137. {% foreach cat.items as item %}
  138. {% if item.price > 10 %}{{ item.name }}{% endif %}
  139. {% endforeach %}
  140. {% endforeach %}";
  141. file_put_contents($this->tplDir . '/nested.html', $tpl);
  142. $data = [
  143. 'categories' => [
  144. (object)[
  145. 'name' => 'Tech',
  146. 'items' => [
  147. (object)['name' => 'Mouse', 'price' => 15],
  148. (object)['name' => 'Pad', 'price' => 5]
  149. ]
  150. ]
  151. ]
  152. ];
  153. $output = $this->engine->render('nested.html', $data);
  154. $this->assertStringContains($output, 'Tech:');
  155. $this->assertStringContains($output, 'Mouse');
  156. }
  157. /** @test */
  158. public function itHandlesIsNotEmptySyntax()
  159. {
  160. $tpl = "{% if tags is not empty %}TAGS: {{ tags | count }}{% else %}EMPTY{% endif %}";
  161. file_put_contents($this->tplDir . '/is_not_empty.html', $tpl);
  162. $res1 = $this->engine->render('is_not_empty.html', ['tags' => ['php', 'lexer']]);
  163. $this->assertStringContains('TAGS: 2', $res1);
  164. $res2 = $this->engine->render('is_not_empty.html', ['tags' => []]);
  165. $this->assertStringContains('EMPTY', $res2);
  166. }
  167. /** @test */
  168. public function itHandlesNativeArraySyntax()
  169. {
  170. $tpl = "First: {{ tags[0] }}
  171. Key: {{ config['env'] }}
  172. Count: {{ tags | count }}
  173. Dynamic: {% set idx = 1 %}{{ tags[idx] }}";
  174. file_put_contents($this->tplDir . '/arrays_native.html', $tpl);
  175. $data = [
  176. 'tags' => ['PHP', 'Lexer', 'Fast'],
  177. 'config' => ['env' => 'production']
  178. ];
  179. $output = $this->engine->render('arrays_native.html', $data);
  180. $this->assertStringContains($output, 'First: PHP');
  181. $this->assertStringContains($output, 'Key: production');
  182. $this->assertStringContains($output, 'Count: 3');
  183. $this->assertStringContains($output, 'Dynamic: Lexer');
  184. }
  185. }