You've already forked wc-bootstrap
Add PHPUnit test suite with Brain\Monkey (v0.1.6)
Add test infrastructure for isolated unit testing without WordPress/WooCommerce: - 27 tests (54 assertions) covering TemplateOverride and WooCommerceExtension - Brain\Monkey for WordPress function mocking, class stubs for TwigService and WC_Product - PHPUnit test job added to Gitea CI pipeline between lint and build-release - Test artifacts excluded from release packages Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
190
tests/Unit/TemplateOverrideTest.php
Normal file
190
tests/Unit/TemplateOverrideTest.php
Normal file
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WcBootstrap\Tests\Unit;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Brain\Monkey;
|
||||
use Brain\Monkey\Functions;
|
||||
use WcBootstrap\TemplateOverride;
|
||||
use WPBootstrap\Twig\TwigService;
|
||||
|
||||
class TemplateOverrideTest extends TestCase
|
||||
{
|
||||
private TemplateOverride $override;
|
||||
private string $templatePath;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
Monkey\setUp();
|
||||
|
||||
TwigService::reset();
|
||||
|
||||
// WC_BOOTSTRAP_PATH must point to the theme root so
|
||||
// resolveTwigTemplate() can locate files under templates/.
|
||||
$this->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 '<div>twig-rendered</div>';
|
||||
});
|
||||
|
||||
$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() => '<div>rendered</div>');
|
||||
|
||||
$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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user