cacheDir)) { mkdir($this->cacheDir); } if (!is_dir($this->tplDir)) { mkdir($this->tplDir); } $this->engine = new Engine($this->tplDir, true, $this->cacheDir); } protected function tearDown(): void { if (is_dir($this->cacheDir)) { array_map('unlink', glob("$this->cacheDir/*.*")); rmdir($this->cacheDir); } if (is_dir($this->tplDir)) { array_map('unlink', glob("$this->tplDir/*.*")); rmdir($this->tplDir); } } protected function execute(): void { $this->itRendersComment(); $this->itRendersSimpleVariables(); $this->itHandlesFiltersWithArguments(); $this->itExecutesLogicBlocks(); $this->itMapsErrorsToOriginalTemplateLine(); $this->itHandlesComplexLogicAndNotOperator(); $this->itHandlesForeachWithObjectsAndArrays(); $this->itHandlesComplexNestedLogic(); $this->itHandlesNestedForeachAndIf(); $this->itHandlesIsNotEmptySyntax(); $this->itHandlesNativeArraySyntax(); } public function itRendersComment() { $tpl = "{# {% include 'flash_bag.html.twig' %}#}\n{# {% include 'flash_bag.html.twig' %}#}"; file_put_contents($this->tplDir . '/comment.html', $tpl); $output = $this->engine->render('comment.html', []); $this->assertEquals('', trim($output)); } /** @test */ public function itRendersSimpleVariables() { $tpl = "Hello {{ user.name }}!"; file_put_contents($this->tplDir . '/test.html', $tpl); $output = $this->engine->render('test.html', ['user' => (object)['name' => 'Michel']]); $this->assertEquals("Hello Michel!", trim($output)); } /** @test */ public function itHandlesFiltersWithArguments() { $tpl = "Total: {{ price | round(2) }} €"; file_put_contents($this->tplDir . '/filter.html', $tpl); $output = $this->engine->render('filter.html', ['price' => 12.556]); $this->assertEquals("Total: 12.56 €", trim($output)); } /** @test */ public function itExecutesLogicBlocks() { $tpl = "{% if show %}YES{% else %}NO{% endif %}"; file_put_contents($this->tplDir . '/logic.html', $tpl); $this->assertEquals("YES", trim($this->engine->render('logic.html', ['show' => true]))); $this->assertEquals("NO", trim($this->engine->render('logic.html', ['show' => false]))); } /** @test */ public function itMapsErrorsToOriginalTemplateLine() { $tpl = "Line 1\nLine 2\n{{ undefined_var.property }}"; file_put_contents($this->tplDir . '/error.html', $tpl); try { $this->engine->render('error.html', []); $this->fail("L'exception aurait dû être lancée."); } catch (ErrorException $e) { // Vérification du mapping de ligne magique /*L:3;F:...*/ $this->assertEquals(3, $e->getLine()); $this->assertStringContains($e->getMessage(), 'error.html'); } } /** @test */ public function itHandlesComplexLogicAndNotOperator() { $tpl = "{% if not user.is_active %}Inactif{% endif %}"; file_put_contents($this->tplDir . '/not.html', $tpl); $output = $this->engine->render('not.html', ['user' => (object)['is_active' => false]]); $this->assertEquals("Inactif", trim($output)); } /** @test */ public function itHandlesForeachWithObjectsAndArrays() { $tpl = ""; file_put_contents($this->tplDir . '/foreach.html', $tpl); $data = [ 'users' => [ (object)['name' => 'Alice', 'role' => 'Admin'], (object)['name' => 'Bob', 'role' => 'User'], ] ]; $output = $this->engine->render('foreach.html', $data); $this->assertStringContains($output, 'Alice (Admin)'); $this->assertStringContains($output, 'Bob (User)'); } /** @test */ public function itHandlesComplexNestedLogic() { $tpl = "{% if (user.age >= 18 and user.has_permit) or user.role == 'admin' %}ACCESS GRANTED{% endif %}"; file_put_contents($this->tplDir . '/complex_logic.html', $tpl); $res1 = $this->engine->render('complex_logic.html', [ 'user' => (object)['age' => 17, 'has_permit' => false, 'role' => 'admin'] ]); $this->assertEquals("ACCESS GRANTED", trim($res1)); $res2 = $this->engine->render('complex_logic.html', [ 'user' => (object)['age' => 20, 'has_permit' => true, 'role' => 'user'] ]); $this->assertEquals("ACCESS GRANTED", trim($res2)); $res3 = $this->engine->render('complex_logic.html', [ 'user' => (object)['age' => 16, 'has_permit' => true, 'role' => 'user'] ]); $this->assertEquals("", trim($res3)); } /** @test */ public function itHandlesNestedForeachAndIf() { $tpl = "{% foreach categories as cat %} {{ cat.name }}: {% foreach cat.items as item %} {% if item.price > 10 %}{{ item.name }}{% endif %} {% endforeach %} {% endforeach %}"; file_put_contents($this->tplDir . '/nested.html', $tpl); $data = [ 'categories' => [ (object)[ 'name' => 'Tech', 'items' => [ (object)['name' => 'Mouse', 'price' => 15], (object)['name' => 'Pad', 'price' => 5] ] ] ] ]; $output = $this->engine->render('nested.html', $data); $this->assertStringContains($output, 'Tech:'); $this->assertStringContains($output, 'Mouse'); } /** @test */ public function itHandlesIsNotEmptySyntax() { $tpl = "{% if tags is not empty %}TAGS: {{ tags | count }}{% else %}EMPTY{% endif %}"; file_put_contents($this->tplDir . '/is_not_empty.html', $tpl); $res1 = $this->engine->render('is_not_empty.html', ['tags' => ['php', 'lexer']]); $this->assertStringContains('TAGS: 2', $res1); $res2 = $this->engine->render('is_not_empty.html', ['tags' => []]); $this->assertStringContains('EMPTY', $res2); } /** @test */ public function itHandlesNativeArraySyntax() { $tpl = "First: {{ tags[0] }} Key: {{ config['env'] }} Count: {{ tags | count }} Dynamic: {% set idx = 1 %}{{ tags[idx] }}"; file_put_contents($this->tplDir . '/arrays_native.html', $tpl); $data = [ 'tags' => ['PHP', 'Lexer', 'Fast'], 'config' => ['env' => 'production'] ]; $output = $this->engine->render('arrays_native.html', $data); $this->assertStringContains($output, 'First: PHP'); $this->assertStringContains($output, 'Key: production'); $this->assertStringContains($output, 'Count: 3'); $this->assertStringContains($output, 'Dynamic: Lexer'); } }