2
0

PurePlateTest.php 7.3 KB

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