tokenize(); $this->assertCount(2, $tokens); // TEXT + EOF $this->assertEquals(TokenType::TEXT, $tokens[0]->type); $this->assertEquals("Hello World", $tokens[0]->value); } public function testTokenizesInterpolation(): void { $lexer = new Lexer("{{ var }}"); $tokens = $lexer->tokenize(); $this->assertCount(4, $tokens); // START, TEXT, END, EOF $this->assertEquals(TokenType::INTERPOLATION_START, $tokens[0]->type); $this->assertEquals(TokenType::TEXT, $tokens[1]->type); $this->assertEquals(" var ", $tokens[1]->value); $this->assertEquals(TokenType::INTERPOLATION_END, $tokens[2]->type); } public function testTokenizesRawInterpolation(): void { $lexer = new Lexer("{{{ raw }}}"); $tokens = $lexer->tokenize(); $this->assertCount(4, $tokens); $this->assertEquals(TokenType::RAW_START, $tokens[0]->type); $this->assertEquals(" raw ", $tokens[1]->value); $this->assertEquals(TokenType::RAW_END, $tokens[2]->type); } public function testTokenizesLogicTags(): void { $lexer = new Lexer("{( foreach item in items )}content{( endforeach )}"); $tokens = $lexer->tokenize(); $this->assertEquals(TokenType::LOGIC_START, $tokens[0]->type); $this->assertEquals(" foreach item in items ", $tokens[1]->value); $this->assertEquals(TokenType::LOGIC_END, $tokens[2]->type); $this->assertEquals("content", $tokens[3]->value); } public function testLogicTagConsumesTrailingNewline(): void { // Logic tag followed by newline should consume the newline $lexer = new Lexer("{( foreach )}\nLine 2"); $tokens = $lexer->tokenize(); // 0: START, 1: TEXT(" foreach "), 2: END, 3: TEXT("Line 2"), 4: EOF $this->assertEquals("Line 2", $tokens[3]->value); $this->assertEquals(TokenType::TEXT, $tokens[3]->type); $this->assertEquals(2, $tokens[3]->line); } public function testLogicTagConsumesTrailingNewlineWithHeredoc(): void { $template = <<