templatePath = dirname(__DIR__, 2) . '/'; if (!defined('WC_BOOTSTRAP_PATH')) { define('WC_BOOTSTRAP_PATH', $this->templatePath); } $this->override = new TemplateOverride(); } protected function tearDown(): void { TwigService::reset(); Monkey\tearDown(); parent::tearDown(); } // ── register() ────────────────────────────────────────────── public function testRegisterAddsHooksWhenTwigServiceExists(): void { $calls = []; Functions\when('add_action')->alias(function () use (&$calls): void { $calls[] = func_get_args(); }); $this->override->register(); $this->assertCount(2, $calls); $this->assertSame('woocommerce_before_template_part', $calls[0][0]); $this->assertSame('woocommerce_after_template_part', $calls[1][0]); } // ── beforeTemplatePart / afterTemplatePart ─────────────────── public function testBeforeTemplatePartRendersAndBuffersWhenTwigTemplateExists(): void { // Use a real template file that exists in the theme. // cart/cart.php -> cart/cart.html.twig $templateName = 'cart/cart.php'; TwigService::setRenderCallback(function (string $tpl, array $ctx): string { return '
twig-rendered
'; }); $this->override->beforeTemplatePart($templateName, '', '', []); // Output buffer should be active — the PHP template output is being captured. $level = ob_get_level(); $this->assertGreaterThan(0, $level); // Now simulate the PHP template echoing something. echo 'php-output-should-be-discarded'; $this->override->afterTemplatePart($templateName, '', '', []); // The Twig output was already echoed before the buffer, so we // just verify the buffer was cleaned (level decreased). $this->assertSame($level - 1, ob_get_level()); } public function testBeforeTemplatePartSkipsWhenNoTwigTemplate(): void { $levelBefore = ob_get_level(); // Use a template name that has no Twig override. $this->override->beforeTemplatePart('nonexistent/template.php', '', '', []); // No buffer should have been started. $this->assertSame($levelBefore, ob_get_level()); } public function testAfterTemplatePartIgnoresUnbufferedTemplate(): void { $levelBefore = ob_get_level(); // Calling after without a matching before should be safe. $this->override->afterTemplatePart('cart/cart.php', '', '', []); $this->assertSame($levelBefore, ob_get_level()); } public function testNestedTemplatesHandledCorrectly(): void { // Both templates must exist as Twig files. $outer = 'cart/cart.php'; $inner = 'cart/cart-empty.php'; TwigService::setRenderCallback(fn() => '
rendered
'); $levelBefore = ob_get_level(); $this->override->beforeTemplatePart($outer, '', '', []); $outerLevel = ob_get_level(); $this->override->beforeTemplatePart($inner, '', '', []); $innerLevel = ob_get_level(); $this->assertSame($outerLevel + 1, $innerLevel); // Close inner first (stack order). $this->override->afterTemplatePart($inner, '', '', []); $this->assertSame($outerLevel, ob_get_level()); $this->override->afterTemplatePart($outer, '', '', []); $this->assertSame($levelBefore, ob_get_level()); } public function testBeforeTemplatePartPassesArgsAndProductToTwig(): void { $templateName = 'cart/cart.php'; $captured = null; TwigService::setRenderCallback(function (string $tpl, array $ctx) use (&$captured): string { $captured = $ctx; return ''; }); $args = ['foo' => 'bar']; $this->override->beforeTemplatePart($templateName, '', '', $args); // Clean up the buffer. $this->override->afterTemplatePart($templateName, '', '', $args); $this->assertIsArray($captured); $this->assertSame('bar', $captured['foo']); } public function testBeforeTemplatePartInjectsGlobalProduct(): void { $templateName = 'cart/cart.php'; $captured = null; $product = new \WC_Product(42); $GLOBALS['product'] = $product; TwigService::setRenderCallback(function (string $tpl, array $ctx) use (&$captured): string { $captured = $ctx; return ''; }); $this->override->beforeTemplatePart($templateName, '', '', []); $this->override->afterTemplatePart($templateName, '', '', []); $this->assertSame($product, $captured['product']); unset($GLOBALS['product']); } public function testBeforeTemplatePartFallsBackOnRenderException(): void { $templateName = 'cart/cart.php'; TwigService::setRenderCallback(function (): string { throw new \RuntimeException('Twig error'); }); $levelBefore = ob_get_level(); // Should not throw — falls back silently and lets PHP render. @$this->override->beforeTemplatePart($templateName, '', '', []); // No buffer started on failure. $this->assertSame($levelBefore, ob_get_level()); } }