extension = new WooCommerceExtension(); } protected function tearDown(): void { Monkey\tearDown(); parent::tearDown(); } // ── getFunctions / getFilters ─────────────────────────────── public function testGetFunctionsReturnsNonEmptyArray(): void { $functions = $this->extension->getFunctions(); $this->assertNotEmpty($functions); $this->assertContainsOnlyInstancesOf(TwigFunction::class, $functions); } public function testGetFiltersReturnsNonEmptyArray(): void { $filters = $this->extension->getFilters(); $this->assertNotEmpty($filters); $this->assertContainsOnlyInstancesOf(TwigFilter::class, $filters); } public function testGetFunctionsContainsExpectedNames(): void { $names = array_map( fn(TwigFunction $f) => $f->getName(), $this->extension->getFunctions() ); $expected = [ 'do_action', 'apply_filters', 'wp_nonce_field', 'fn', 'wc_get_cart_url', 'wc_get_checkout_url', 'wc_get_template', 'wc_print_notices', 'woocommerce_form_field', ]; foreach ($expected as $name) { $this->assertContains($name, $names, "Missing Twig function: {$name}"); } } public function testGetFiltersContainsExpectedNames(): void { $names = array_map( fn(TwigFilter $f) => $f->getName(), $this->extension->getFilters() ); $expected = [ 'esc_html', 'esc_attr', 'esc_url', 'sanitize_title', 'wpautop', 'wp_kses_post', 'wptexturize', 'do_shortcode', ]; foreach ($expected as $name) { $this->assertContains($name, $names, "Missing Twig filter: {$name}"); } } // ── callFunction() whitelist ──────────────────────────────── public function testCallFunctionWithAllowedFunctionSucceeds(): void { // Define a stub for an allowed function. Functions\when('_n')->alias(fn($single, $plural, $count) => $count === 1 ? $single : $plural); $result = $this->extension->callFunction('_n', 'item', 'items', 1); $this->assertSame('item', $result); } public function testCallFunctionWithDisallowedFunctionThrows(): void { $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('is not allowed'); $this->extension->callFunction('exec', 'whoami'); } public function testCallFunctionWithNonExistentAllowedFunctionThrows(): void { // 'WC' is allowed but won't exist in the test environment. $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('does not exist'); $this->extension->callFunction('WC'); } // ── doAction() ────────────────────────────────────────────── public function testDoActionCapturesOutput(): void { Functions\when('do_action')->alias(function (string $tag): void { echo "
{$tag}
"; }); $result = $this->extension->doAction('woocommerce_before_cart'); $this->assertSame('
woocommerce_before_cart
', $result); } public function testDoActionReturnsEmptyWhenNoOutput(): void { Functions\when('do_action')->justReturn(null); $result = $this->extension->doAction('some_silent_hook'); $this->assertSame('', $result); } // ── applyFilters() ────────────────────────────────────────── public function testApplyFiltersDelegatesToWordPress(): void { Functions\when('apply_filters')->alias(function (string $tag, ...$args) { return strtoupper($args[0]); }); $result = $this->extension->applyFilters('the_title', 'hello'); $this->assertSame('HELLO', $result); } // ── callUserFunc() ────────────────────────────────────────── public function testCallUserFuncCapturesCallbackOutput(): void { $result = $this->extension->callUserFunc(function (): void { echo 'tab-content'; }); $this->assertSame('tab-content', $result); } public function testCallUserFuncPassesArguments(): void { $result = $this->extension->callUserFunc(function (string $key): void { echo "key={$key}"; }, 'description'); $this->assertSame('key=description', $result); } // ── setupProductData() ────────────────────────────────────── public function testSetupProductDataSetsGlobalProduct(): void { $product = new \WC_Product(99); Functions\when('get_post')->justReturn((object) ['ID' => 99]); Functions\when('setup_postdata')->justReturn(true); $result = $this->extension->setupProductData($product); $this->assertSame('', $result); $this->assertSame($product, $GLOBALS['product']); unset($GLOBALS['product'], $GLOBALS['post']); } public function testSetupProductDataSkipsPostdataWhenPostNotFound(): void { $product = new \WC_Product(1); Functions\when('get_post')->justReturn(null); $result = $this->extension->setupProductData($product); $this->assertSame('', $result); $this->assertSame($product, $GLOBALS['product']); unset($GLOBALS['product']); } // ── Output-capture wrappers ───────────────────────────────── public function testWcPrintNoticesCapturesOutput(): void { Functions\when('wc_print_notices')->alias(function (): void { echo '
Notice
'; }); $result = $this->extension->wcPrintNotices(); $this->assertStringContainsString('woocommerce-message', $result); } public function testWcDisplayItemMetaReturnsString(): void { $item = new \stdClass(); Functions\when('wc_display_item_meta')->alias(function ($item, array $args): string { return $args['echo'] === false ? 'meta' : ''; }); $result = $this->extension->wcDisplayItemMeta($item, []); $this->assertSame('meta', $result); } public function testWcQueryStringFormFieldsReturnsHtml(): void { Functions\when('wc_query_string_form_fields')->alias(function ($values, array $exclude, string $key, bool $return): string { return $return ? '' : ''; }); $result = $this->extension->wcQueryStringFormFields(null, [], ''); $this->assertSame('', $result); } public function testWoocommerceFormFieldReturnsHtml(): void { Functions\when('woocommerce_form_field')->alias(function (string $key, array $args, $value): string { return $args['return'] === true ? "

{$key}

" : ''; }); $result = $this->extension->woocommerceFormField('billing_first_name', [], null); $this->assertStringContainsString('billing_first_name', $result); } public function testWcGetTemplateCapturesOutput(): void { Functions\when('wc_get_template')->alias(function (string $name): void { echo "
{$name}
"; }); $result = $this->extension->wcGetTemplate('order/order-details.php', []); $this->assertSame('
order/order-details.php
', $result); } }