You've already forked wc-composable-product
Add PHPUnit test suite, PSR-4 refactor, lint+test CI jobs (v1.3.1)
- 57 unit tests covering ProductType, StockManager, CartHandler, Plugin, Admin/ProductData, Admin/Settings using Brain Monkey + Mockery - WooCommerce class stubs for testing without WP installation - PHP lint and test jobs in release workflow (test gate blocks release) - PSR-4 namespace change: WC_Composable_Product -> Magdev\WcComposableProduct - PascalCase filenames for all classes under includes/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
75
tests/TestCase.php
Normal file
75
tests/TestCase.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/**
|
||||
* Base Test Case
|
||||
*
|
||||
* @package Magdev\WcComposableProduct\Tests
|
||||
*/
|
||||
|
||||
namespace Magdev\WcComposableProduct\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase as PHPUnitTestCase;
|
||||
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
|
||||
use Brain\Monkey;
|
||||
|
||||
/**
|
||||
* Base test case with Brain Monkey and Mockery integration.
|
||||
*
|
||||
* All test classes should extend this instead of PHPUnit\Framework\TestCase.
|
||||
*/
|
||||
abstract class TestCase extends PHPUnitTestCase
|
||||
{
|
||||
use MockeryPHPUnitIntegration;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
Monkey\setUp();
|
||||
|
||||
// Stub common WordPress translation functions (__(), _e(), esc_html__(), etc.)
|
||||
Monkey\Functions\stubTranslationFunctions();
|
||||
|
||||
// Stub common WordPress escaping functions (esc_html(), esc_attr(), etc.)
|
||||
Monkey\Functions\stubEscapeFunctions();
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
Monkey\tearDown();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Mockery mock of WC_Product with sensible defaults.
|
||||
*
|
||||
* @param array $overrides Method return value overrides
|
||||
* @return \Mockery\MockInterface
|
||||
*/
|
||||
protected function createProductMock(array $overrides = []): \Mockery\MockInterface
|
||||
{
|
||||
$defaults = [
|
||||
'get_id' => 100,
|
||||
'get_name' => 'Test Product',
|
||||
'get_type' => 'simple',
|
||||
'get_price' => '10.00',
|
||||
'get_regular_price' => '10.00',
|
||||
'get_price_html' => '<span>$10.00</span>',
|
||||
'get_permalink' => 'https://example.com/product/test',
|
||||
'get_image_id' => 1,
|
||||
'get_stock_quantity' => null,
|
||||
'get_stock_status' => 'instock',
|
||||
'is_purchasable' => true,
|
||||
'is_in_stock' => true,
|
||||
'managing_stock' => false,
|
||||
'backorders_allowed' => false,
|
||||
];
|
||||
|
||||
$config = array_merge($defaults, $overrides);
|
||||
$mock = \Mockery::mock('WC_Product');
|
||||
|
||||
foreach ($config as $method => $return) {
|
||||
$mock->shouldReceive($method)->andReturn($return)->byDefault();
|
||||
}
|
||||
|
||||
return $mock;
|
||||
}
|
||||
}
|
||||
108
tests/Unit/Admin/ProductDataTest.php
Normal file
108
tests/Unit/Admin/ProductDataTest.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin ProductData Tests
|
||||
*
|
||||
* @package Magdev\WcComposableProduct\Tests
|
||||
*/
|
||||
|
||||
namespace Magdev\WcComposableProduct\Tests\Unit\Admin;
|
||||
|
||||
use Magdev\WcComposableProduct\Tests\TestCase;
|
||||
use Magdev\WcComposableProduct\Admin\ProductData;
|
||||
use Brain\Monkey\Functions;
|
||||
|
||||
class ProductDataTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$_POST = [];
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
$_POST = [];
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function testConstructor_RegistersExpectedHooks(): void
|
||||
{
|
||||
$productData = new ProductData();
|
||||
|
||||
self::assertNotFalse(has_filter('woocommerce_product_data_tabs', 'Magdev\WcComposableProduct\Admin\ProductData->add_product_data_tab()'));
|
||||
self::assertNotFalse(has_action('woocommerce_product_data_panels', 'Magdev\WcComposableProduct\Admin\ProductData->add_product_data_panel()'));
|
||||
self::assertNotFalse(has_action('woocommerce_process_product_meta_composable', 'Magdev\WcComposableProduct\Admin\ProductData->save_product_data()'));
|
||||
self::assertNotFalse(has_action('woocommerce_product_options_general_product_data', 'Magdev\WcComposableProduct\Admin\ProductData->add_general_fields()'));
|
||||
}
|
||||
|
||||
public function testAddProductDataTab_AddsComposableTab(): void
|
||||
{
|
||||
$productData = new ProductData();
|
||||
$tabs = $productData->add_product_data_tab([]);
|
||||
|
||||
$this->assertArrayHasKey('composable', $tabs);
|
||||
$this->assertSame('composable_product_data', $tabs['composable']['target']);
|
||||
$this->assertContains('show_if_composable', $tabs['composable']['class']);
|
||||
$this->assertSame(21, $tabs['composable']['priority']);
|
||||
}
|
||||
|
||||
public function testSaveProductData_SavesAllFields(): void
|
||||
{
|
||||
$_POST = [
|
||||
'_composable_selection_limit' => '5',
|
||||
'_composable_pricing_mode' => 'fixed',
|
||||
'_composable_include_unpublished' => 'yes',
|
||||
'_composable_criteria_type' => 'tag',
|
||||
'_composable_categories' => ['1', '2'],
|
||||
'_composable_tags' => ['3', '4'],
|
||||
'_composable_skus' => 'SKU-1, SKU-2',
|
||||
];
|
||||
|
||||
Functions\expect('absint')->andReturnUsing(function ($val) {
|
||||
return abs((int) $val);
|
||||
});
|
||||
Functions\expect('sanitize_text_field')->andReturnUsing(function ($val) {
|
||||
return $val;
|
||||
});
|
||||
Functions\expect('sanitize_textarea_field')->andReturnUsing(function ($val) {
|
||||
return $val;
|
||||
});
|
||||
|
||||
Functions\expect('update_post_meta')->times(7);
|
||||
|
||||
$productData = new ProductData();
|
||||
$productData->save_product_data(42);
|
||||
}
|
||||
|
||||
public function testSaveProductData_DefaultsWhenPostEmpty(): void
|
||||
{
|
||||
// No POST data at all
|
||||
Functions\expect('absint')->andReturnUsing(function ($val) {
|
||||
return abs((int) $val);
|
||||
});
|
||||
Functions\expect('sanitize_text_field')->andReturnUsing(function ($val) {
|
||||
return $val;
|
||||
});
|
||||
Functions\expect('sanitize_textarea_field')->andReturnUsing(function ($val) {
|
||||
return $val;
|
||||
});
|
||||
|
||||
Functions\expect('update_post_meta')
|
||||
->with(42, '_composable_selection_limit', \Mockery::any())->once();
|
||||
Functions\expect('update_post_meta')
|
||||
->with(42, '_composable_pricing_mode', '')->once();
|
||||
Functions\expect('update_post_meta')
|
||||
->with(42, '_composable_include_unpublished', '')->once();
|
||||
Functions\expect('update_post_meta')
|
||||
->with(42, '_composable_criteria_type', 'category')->once();
|
||||
Functions\expect('update_post_meta')
|
||||
->with(42, '_composable_categories', [])->once();
|
||||
Functions\expect('update_post_meta')
|
||||
->with(42, '_composable_tags', [])->once();
|
||||
Functions\expect('update_post_meta')
|
||||
->with(42, '_composable_skus', '')->once();
|
||||
|
||||
$productData = new ProductData();
|
||||
$productData->save_product_data(42);
|
||||
}
|
||||
}
|
||||
84
tests/Unit/Admin/SettingsTest.php
Normal file
84
tests/Unit/Admin/SettingsTest.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin Settings Tests
|
||||
*
|
||||
* @package Magdev\WcComposableProduct\Tests
|
||||
*/
|
||||
|
||||
namespace Magdev\WcComposableProduct\Tests\Unit\Admin;
|
||||
|
||||
use Magdev\WcComposableProduct\Tests\TestCase;
|
||||
use Magdev\WcComposableProduct\Admin\Settings;
|
||||
use Brain\Monkey\Functions;
|
||||
|
||||
class SettingsTest extends TestCase
|
||||
{
|
||||
public function testConstructor_SetsIdAndLabel(): void
|
||||
{
|
||||
$settings = new Settings();
|
||||
|
||||
$this->assertSame('composable_products', $settings->get_id());
|
||||
}
|
||||
|
||||
public function testGetSettings_ReturnsExpectedFieldIds(): void
|
||||
{
|
||||
Functions\expect('apply_filters')
|
||||
->once()
|
||||
->with('wc_composable_settings', \Mockery::type('array'))
|
||||
->andReturnUsing(function ($hook, $settings) {
|
||||
return $settings;
|
||||
});
|
||||
|
||||
$settings = new Settings();
|
||||
$fields = $settings->get_settings();
|
||||
|
||||
// Extract all field IDs
|
||||
$ids = array_column($fields, 'id');
|
||||
|
||||
$this->assertContains('wc_composable_settings', $ids);
|
||||
$this->assertContains('wc_composable_default_limit', $ids);
|
||||
$this->assertContains('wc_composable_default_pricing', $ids);
|
||||
$this->assertContains('wc_composable_include_unpublished', $ids);
|
||||
$this->assertContains('wc_composable_show_images', $ids);
|
||||
$this->assertContains('wc_composable_show_prices', $ids);
|
||||
$this->assertContains('wc_composable_show_total', $ids);
|
||||
}
|
||||
|
||||
public function testGetSettings_HasCorrectFieldTypes(): void
|
||||
{
|
||||
Functions\expect('apply_filters')
|
||||
->once()
|
||||
->andReturnUsing(function ($hook, $settings) {
|
||||
return $settings;
|
||||
});
|
||||
|
||||
$settings = new Settings();
|
||||
$fields = $settings->get_settings();
|
||||
|
||||
// Index fields by ID for easy lookup
|
||||
$indexed = [];
|
||||
foreach ($fields as $field) {
|
||||
if (isset($field['id'])) {
|
||||
$indexed[$field['id']] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertSame('number', $indexed['wc_composable_default_limit']['type']);
|
||||
$this->assertSame('select', $indexed['wc_composable_default_pricing']['type']);
|
||||
$this->assertSame('checkbox', $indexed['wc_composable_include_unpublished']['type']);
|
||||
$this->assertSame('checkbox', $indexed['wc_composable_show_images']['type']);
|
||||
}
|
||||
|
||||
public function testGetSettings_AppliesFilter(): void
|
||||
{
|
||||
Functions\expect('apply_filters')
|
||||
->once()
|
||||
->with('wc_composable_settings', \Mockery::type('array'))
|
||||
->andReturnUsing(function ($hook, $settings) {
|
||||
return $settings;
|
||||
});
|
||||
|
||||
$settings = new Settings();
|
||||
$settings->get_settings();
|
||||
}
|
||||
}
|
||||
184
tests/Unit/CartHandlerTest.php
Normal file
184
tests/Unit/CartHandlerTest.php
Normal file
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
/**
|
||||
* CartHandler Tests
|
||||
*
|
||||
* @package Magdev\WcComposableProduct\Tests
|
||||
*/
|
||||
|
||||
namespace Magdev\WcComposableProduct\Tests\Unit;
|
||||
|
||||
use Magdev\WcComposableProduct\Tests\TestCase;
|
||||
use Magdev\WcComposableProduct\CartHandler;
|
||||
use Brain\Monkey\Functions;
|
||||
use Brain\Monkey\Actions;
|
||||
use Brain\Monkey\Filters;
|
||||
|
||||
class CartHandlerTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Clean up POST superglobal between tests
|
||||
$_POST = [];
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
$_POST = [];
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function testConstructor_RegistersExpectedHooks(): void
|
||||
{
|
||||
$handler = new CartHandler();
|
||||
|
||||
self::assertNotFalse(has_filter('woocommerce_add_to_cart_validation', 'Magdev\WcComposableProduct\CartHandler->validate_add_to_cart()'));
|
||||
self::assertNotFalse(has_filter('woocommerce_add_cart_item_data', 'Magdev\WcComposableProduct\CartHandler->add_cart_item_data()'));
|
||||
self::assertNotFalse(has_filter('woocommerce_get_cart_item_from_session', 'Magdev\WcComposableProduct\CartHandler->get_cart_item_from_session()'));
|
||||
self::assertNotFalse(has_filter('woocommerce_get_item_data', 'Magdev\WcComposableProduct\CartHandler->display_cart_item_data()'));
|
||||
self::assertNotFalse(has_action('woocommerce_before_calculate_totals', 'Magdev\WcComposableProduct\CartHandler->calculate_cart_item_price()'));
|
||||
self::assertNotFalse(has_action('woocommerce_single_product_summary', 'Magdev\WcComposableProduct\CartHandler->render_product_selector()'));
|
||||
self::assertNotFalse(has_filter('woocommerce_is_purchasable', 'Magdev\WcComposableProduct\CartHandler->hide_default_add_to_cart()'));
|
||||
}
|
||||
|
||||
public function testHideDefaultAddToCart_ReturnsFalseForComposable(): void
|
||||
{
|
||||
$handler = new CartHandler();
|
||||
$product = $this->createProductMock(['get_type' => 'composable']);
|
||||
|
||||
$result = $handler->hide_default_add_to_cart(true, $product);
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
public function testHideDefaultAddToCart_PassesThroughForSimple(): void
|
||||
{
|
||||
$handler = new CartHandler();
|
||||
$product = $this->createProductMock(['get_type' => 'simple']);
|
||||
|
||||
$result = $handler->hide_default_add_to_cart(true, $product);
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
public function testValidateAddToCart_PassesThroughForNonComposable(): void
|
||||
{
|
||||
$product = $this->createProductMock(['get_type' => 'simple']);
|
||||
Functions\expect('wc_get_product')->with(1)->andReturn($product);
|
||||
|
||||
$handler = new CartHandler();
|
||||
$result = $handler->validate_add_to_cart(true, 1, 1);
|
||||
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
public function testValidateAddToCart_ReturnsFalseWhenNoProductsSelected(): void
|
||||
{
|
||||
$product = $this->createProductMock(['get_type' => 'composable']);
|
||||
Functions\expect('wc_get_product')->with(1)->andReturn($product);
|
||||
Functions\expect('wc_add_notice')->once();
|
||||
|
||||
$_POST['composable_products'] = [];
|
||||
|
||||
$handler = new CartHandler();
|
||||
$result = $handler->validate_add_to_cart(true, 1, 1);
|
||||
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
public function testValidateAddToCart_ReturnsFalseWhenNoPostData(): void
|
||||
{
|
||||
$product = $this->createProductMock(['get_type' => 'composable']);
|
||||
Functions\expect('wc_get_product')->with(1)->andReturn($product);
|
||||
Functions\expect('wc_add_notice')->once();
|
||||
|
||||
// No $_POST['composable_products'] at all
|
||||
$handler = new CartHandler();
|
||||
$result = $handler->validate_add_to_cart(true, 1, 1);
|
||||
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
public function testAddCartItemData_AddsSelectionsForComposable(): void
|
||||
{
|
||||
$product = $this->createProductMock(['get_type' => 'composable']);
|
||||
Functions\expect('wc_get_product')->with(1)->andReturn($product);
|
||||
Functions\expect('absint')->andReturnUsing(function ($val) {
|
||||
return abs((int) $val);
|
||||
});
|
||||
|
||||
$_POST['composable_products'] = ['101', '102'];
|
||||
|
||||
$handler = new CartHandler();
|
||||
$result = $handler->add_cart_item_data([], 1);
|
||||
|
||||
$this->assertArrayHasKey('composable_products', $result);
|
||||
$this->assertSame([101, 102], $result['composable_products']);
|
||||
$this->assertArrayHasKey('unique_key', $result);
|
||||
}
|
||||
|
||||
public function testAddCartItemData_PassesThroughForNonComposable(): void
|
||||
{
|
||||
$product = $this->createProductMock(['get_type' => 'simple']);
|
||||
Functions\expect('wc_get_product')->with(1)->andReturn($product);
|
||||
|
||||
$handler = new CartHandler();
|
||||
$result = $handler->add_cart_item_data(['existing' => 'data'], 1);
|
||||
|
||||
$this->assertSame(['existing' => 'data'], $result);
|
||||
}
|
||||
|
||||
public function testGetCartItemFromSession_RestoresComposableProducts(): void
|
||||
{
|
||||
$handler = new CartHandler();
|
||||
$result = $handler->get_cart_item_from_session(
|
||||
['data' => 'test'],
|
||||
['composable_products' => [101, 102]]
|
||||
);
|
||||
|
||||
$this->assertSame([101, 102], $result['composable_products']);
|
||||
}
|
||||
|
||||
public function testGetCartItemFromSession_PassesThroughWithoutComposableData(): void
|
||||
{
|
||||
$handler = new CartHandler();
|
||||
$result = $handler->get_cart_item_from_session(
|
||||
['data' => 'test'],
|
||||
[]
|
||||
);
|
||||
|
||||
$this->assertArrayNotHasKey('composable_products', $result);
|
||||
}
|
||||
|
||||
public function testDisplayCartItemData_FormatsProductNames(): void
|
||||
{
|
||||
$mock1 = $this->createProductMock(['get_name' => 'Product A']);
|
||||
$mock2 = $this->createProductMock(['get_name' => 'Product B']);
|
||||
|
||||
Functions\expect('wc_get_product')
|
||||
->andReturnUsing(function ($id) use ($mock1, $mock2) {
|
||||
return match ($id) {
|
||||
101 => $mock1,
|
||||
102 => $mock2,
|
||||
default => false,
|
||||
};
|
||||
});
|
||||
|
||||
$handler = new CartHandler();
|
||||
$result = $handler->display_cart_item_data(
|
||||
[],
|
||||
['composable_products' => [101, 102]]
|
||||
);
|
||||
|
||||
$this->assertCount(1, $result);
|
||||
$this->assertStringContainsString('Product A', $result[0]['value']);
|
||||
$this->assertStringContainsString('Product B', $result[0]['value']);
|
||||
}
|
||||
|
||||
public function testDisplayCartItemData_ReturnsEmptyForNonComposable(): void
|
||||
{
|
||||
$handler = new CartHandler();
|
||||
$result = $handler->display_cart_item_data([], []);
|
||||
|
||||
$this->assertSame([], $result);
|
||||
}
|
||||
}
|
||||
72
tests/Unit/PluginTest.php
Normal file
72
tests/Unit/PluginTest.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin Tests
|
||||
*
|
||||
* @package Magdev\WcComposableProduct\Tests
|
||||
*/
|
||||
|
||||
namespace Magdev\WcComposableProduct\Tests\Unit;
|
||||
|
||||
use Magdev\WcComposableProduct\Tests\TestCase;
|
||||
use Magdev\WcComposableProduct\Plugin;
|
||||
use Brain\Monkey\Functions;
|
||||
|
||||
class PluginTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Reset the singleton instance between tests
|
||||
$reflection = new \ReflectionClass(Plugin::class);
|
||||
$property = $reflection->getProperty('instance');
|
||||
$property->setAccessible(true);
|
||||
$property->setValue(null, null);
|
||||
}
|
||||
|
||||
public function testInstance_ReturnsSingleton(): void
|
||||
{
|
||||
$instance1 = Plugin::instance();
|
||||
$instance2 = Plugin::instance();
|
||||
|
||||
$this->assertSame($instance1, $instance2);
|
||||
}
|
||||
|
||||
public function testInstance_ReturnsPluginClass(): void
|
||||
{
|
||||
$instance = Plugin::instance();
|
||||
|
||||
$this->assertInstanceOf(Plugin::class, $instance);
|
||||
}
|
||||
|
||||
public function testAddProductType_AddsComposableToTypes(): void
|
||||
{
|
||||
$plugin = Plugin::instance();
|
||||
$types = $plugin->add_product_type([]);
|
||||
|
||||
$this->assertArrayHasKey('composable', $types);
|
||||
}
|
||||
|
||||
public function testProductClass_ReturnsCustomClassForComposable(): void
|
||||
{
|
||||
$plugin = Plugin::instance();
|
||||
$class = $plugin->product_class('WC_Product', 'composable');
|
||||
|
||||
$this->assertSame('Magdev\WcComposableProduct\ProductType', $class);
|
||||
}
|
||||
|
||||
public function testProductClass_PassesThroughForOtherTypes(): void
|
||||
{
|
||||
$plugin = Plugin::instance();
|
||||
$class = $plugin->product_class('WC_Product', 'simple');
|
||||
|
||||
$this->assertSame('WC_Product', $class);
|
||||
}
|
||||
|
||||
public function testGetTwig_ReturnsTwigEnvironment(): void
|
||||
{
|
||||
$plugin = Plugin::instance();
|
||||
|
||||
$this->assertInstanceOf(\Twig\Environment::class, $plugin->get_twig());
|
||||
}
|
||||
}
|
||||
218
tests/Unit/ProductTypeTest.php
Normal file
218
tests/Unit/ProductTypeTest.php
Normal file
@@ -0,0 +1,218 @@
|
||||
<?php
|
||||
/**
|
||||
* ProductType Tests
|
||||
*
|
||||
* @package Magdev\WcComposableProduct\Tests
|
||||
*/
|
||||
|
||||
namespace Magdev\WcComposableProduct\Tests\Unit;
|
||||
|
||||
use Magdev\WcComposableProduct\Tests\TestCase;
|
||||
use Magdev\WcComposableProduct\ProductType;
|
||||
use Brain\Monkey\Functions;
|
||||
|
||||
class ProductTypeTest extends TestCase
|
||||
{
|
||||
private function createProductType(array $meta = []): ProductType
|
||||
{
|
||||
$product = new ProductType();
|
||||
foreach ($meta as $key => $value) {
|
||||
$product->update_meta_data($key, $value);
|
||||
}
|
||||
return $product;
|
||||
}
|
||||
|
||||
public function testGetType_ReturnsComposable(): void
|
||||
{
|
||||
$product = $this->createProductType();
|
||||
$this->assertSame('composable', $product->get_type());
|
||||
}
|
||||
|
||||
public function testIsPurchasable_ReturnsTrue(): void
|
||||
{
|
||||
$product = $this->createProductType();
|
||||
$this->assertTrue($product->is_purchasable());
|
||||
}
|
||||
|
||||
public function testIsSoldIndividually_ReturnsTrue(): void
|
||||
{
|
||||
$product = $this->createProductType();
|
||||
$this->assertTrue($product->is_sold_individually());
|
||||
}
|
||||
|
||||
public function testGetSelectionLimit_UsesProductMeta(): void
|
||||
{
|
||||
Functions\expect('absint')->once()->andReturnUsing(function ($val) {
|
||||
return abs((int) $val);
|
||||
});
|
||||
|
||||
$product = $this->createProductType(['_composable_selection_limit' => '3']);
|
||||
$this->assertSame(3, $product->get_selection_limit());
|
||||
}
|
||||
|
||||
public function testGetSelectionLimit_FallsBackToGlobalDefault(): void
|
||||
{
|
||||
Functions\expect('get_option')
|
||||
->once()
|
||||
->with('wc_composable_default_limit', 5)
|
||||
->andReturn(7);
|
||||
|
||||
Functions\expect('absint')->once()->andReturnUsing(function ($val) {
|
||||
return abs((int) $val);
|
||||
});
|
||||
|
||||
$product = $this->createProductType();
|
||||
$this->assertSame(7, $product->get_selection_limit());
|
||||
}
|
||||
|
||||
public function testGetSelectionLimit_FallsBackToHardDefault(): void
|
||||
{
|
||||
Functions\expect('get_option')
|
||||
->once()
|
||||
->with('wc_composable_default_limit', 5)
|
||||
->andReturn(5);
|
||||
|
||||
Functions\expect('absint')->once()->andReturnUsing(function ($val) {
|
||||
return abs((int) $val);
|
||||
});
|
||||
|
||||
$product = $this->createProductType();
|
||||
$this->assertSame(5, $product->get_selection_limit());
|
||||
}
|
||||
|
||||
public function testGetPricingMode_UsesProductMeta(): void
|
||||
{
|
||||
$product = $this->createProductType(['_composable_pricing_mode' => 'fixed']);
|
||||
$this->assertSame('fixed', $product->get_pricing_mode());
|
||||
}
|
||||
|
||||
public function testGetPricingMode_FallsBackToGlobalDefault(): void
|
||||
{
|
||||
Functions\expect('get_option')
|
||||
->once()
|
||||
->with('wc_composable_default_pricing', 'sum')
|
||||
->andReturn('sum');
|
||||
|
||||
$product = $this->createProductType();
|
||||
$this->assertSame('sum', $product->get_pricing_mode());
|
||||
}
|
||||
|
||||
public function testShouldIncludeUnpublished_PerProductYes(): void
|
||||
{
|
||||
$product = $this->createProductType(['_composable_include_unpublished' => 'yes']);
|
||||
$this->assertTrue($product->should_include_unpublished());
|
||||
}
|
||||
|
||||
public function testShouldIncludeUnpublished_PerProductNo(): void
|
||||
{
|
||||
$product = $this->createProductType(['_composable_include_unpublished' => 'no']);
|
||||
$this->assertFalse($product->should_include_unpublished());
|
||||
}
|
||||
|
||||
public function testShouldIncludeUnpublished_FallsBackToGlobalYes(): void
|
||||
{
|
||||
Functions\expect('get_option')
|
||||
->once()
|
||||
->with('wc_composable_include_unpublished', 'no')
|
||||
->andReturn('yes');
|
||||
|
||||
$product = $this->createProductType();
|
||||
$this->assertTrue($product->should_include_unpublished());
|
||||
}
|
||||
|
||||
public function testShouldIncludeUnpublished_FallsBackToGlobalNo(): void
|
||||
{
|
||||
Functions\expect('get_option')
|
||||
->once()
|
||||
->with('wc_composable_include_unpublished', 'no')
|
||||
->andReturn('no');
|
||||
|
||||
$product = $this->createProductType();
|
||||
$this->assertFalse($product->should_include_unpublished());
|
||||
}
|
||||
|
||||
public function testGetSelectionCriteria_DefaultsToCategory(): void
|
||||
{
|
||||
$product = $this->createProductType();
|
||||
$criteria = $product->get_selection_criteria();
|
||||
|
||||
$this->assertSame('category', $criteria['type']);
|
||||
$this->assertSame([], $criteria['categories']);
|
||||
$this->assertSame([], $criteria['tags']);
|
||||
$this->assertSame('', $criteria['skus']);
|
||||
}
|
||||
|
||||
public function testGetSelectionCriteria_UsesProductMeta(): void
|
||||
{
|
||||
$product = $this->createProductType([
|
||||
'_composable_criteria_type' => 'tag',
|
||||
'_composable_tags' => [5, 10],
|
||||
]);
|
||||
$criteria = $product->get_selection_criteria();
|
||||
|
||||
$this->assertSame('tag', $criteria['type']);
|
||||
$this->assertSame([5, 10], $criteria['tags']);
|
||||
}
|
||||
|
||||
public function testCalculateComposedPrice_FixedMode(): void
|
||||
{
|
||||
$product = $this->createProductType(['_composable_pricing_mode' => 'fixed']);
|
||||
|
||||
// Set regular price via the stub's data property
|
||||
$reflection = new \ReflectionClass($product);
|
||||
$dataProp = $reflection->getProperty('data');
|
||||
$dataProp->setAccessible(true);
|
||||
$data = $dataProp->getValue($product);
|
||||
$data['regular_price'] = '25.00';
|
||||
$dataProp->setValue($product, $data);
|
||||
|
||||
$price = $product->calculate_composed_price([101, 102]);
|
||||
$this->assertSame(25.0, $price);
|
||||
}
|
||||
|
||||
public function testCalculateComposedPrice_SumMode(): void
|
||||
{
|
||||
Functions\expect('get_option')
|
||||
->with('wc_composable_default_pricing', 'sum')
|
||||
->andReturn('sum');
|
||||
|
||||
$mock1 = $this->createProductMock(['get_price' => '5.00']);
|
||||
$mock2 = $this->createProductMock(['get_price' => '7.50']);
|
||||
|
||||
Functions\expect('wc_get_product')
|
||||
->andReturnUsing(function ($id) use ($mock1, $mock2) {
|
||||
return match ($id) {
|
||||
101 => $mock1,
|
||||
102 => $mock2,
|
||||
default => false,
|
||||
};
|
||||
});
|
||||
|
||||
$product = $this->createProductType();
|
||||
$price = $product->calculate_composed_price([101, 102]);
|
||||
|
||||
$this->assertSame(12.5, $price);
|
||||
}
|
||||
|
||||
public function testCalculateComposedPrice_SumMode_SkipsInvalidProducts(): void
|
||||
{
|
||||
Functions\expect('get_option')
|
||||
->with('wc_composable_default_pricing', 'sum')
|
||||
->andReturn('sum');
|
||||
|
||||
$mock1 = $this->createProductMock(['get_price' => '5.00']);
|
||||
|
||||
Functions\expect('wc_get_product')
|
||||
->andReturnUsing(function ($id) use ($mock1) {
|
||||
return match ($id) {
|
||||
101 => $mock1,
|
||||
default => false,
|
||||
};
|
||||
});
|
||||
|
||||
$product = $this->createProductType();
|
||||
$price = $product->calculate_composed_price([101, 999]);
|
||||
|
||||
$this->assertSame(5.0, $price);
|
||||
}
|
||||
}
|
||||
226
tests/Unit/StockManagerTest.php
Normal file
226
tests/Unit/StockManagerTest.php
Normal file
@@ -0,0 +1,226 @@
|
||||
<?php
|
||||
/**
|
||||
* StockManager Tests
|
||||
*
|
||||
* @package Magdev\WcComposableProduct\Tests
|
||||
*/
|
||||
|
||||
namespace Magdev\WcComposableProduct\Tests\Unit;
|
||||
|
||||
use Magdev\WcComposableProduct\Tests\TestCase;
|
||||
use Magdev\WcComposableProduct\StockManager;
|
||||
use Brain\Monkey\Functions;
|
||||
|
||||
class StockManagerTest extends TestCase
|
||||
{
|
||||
private StockManager $manager;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->manager = new StockManager();
|
||||
}
|
||||
|
||||
// --- validate_stock_availability ---
|
||||
|
||||
public function testValidateStock_ReturnsTrueWhenAllInStock(): void
|
||||
{
|
||||
$mock = $this->createProductMock([
|
||||
'managing_stock' => false,
|
||||
'is_in_stock' => true,
|
||||
]);
|
||||
|
||||
Functions\expect('wc_get_product')->with(1)->andReturn($mock);
|
||||
Functions\expect('wc_get_product')->with(2)->andReturn($mock);
|
||||
|
||||
$result = $this->manager->validate_stock_availability([1, 2]);
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
public function testValidateStock_ReturnsTrueWhenNotManagingStock(): void
|
||||
{
|
||||
$mock = $this->createProductMock(['managing_stock' => false]);
|
||||
|
||||
Functions\expect('wc_get_product')->with(1)->andReturn($mock);
|
||||
|
||||
$result = $this->manager->validate_stock_availability([1]);
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
public function testValidateStock_ReturnsErrorForOutOfStock(): void
|
||||
{
|
||||
$mock = $this->createProductMock([
|
||||
'managing_stock' => true,
|
||||
'is_in_stock' => false,
|
||||
'get_name' => 'Widget',
|
||||
]);
|
||||
|
||||
Functions\expect('wc_get_product')->with(1)->andReturn($mock);
|
||||
|
||||
$result = $this->manager->validate_stock_availability([1]);
|
||||
$this->assertIsString($result);
|
||||
$this->assertStringContainsString('Widget', $result);
|
||||
}
|
||||
|
||||
public function testValidateStock_ReturnsErrorForInsufficientQuantity(): void
|
||||
{
|
||||
$mock = $this->createProductMock([
|
||||
'managing_stock' => true,
|
||||
'is_in_stock' => true,
|
||||
'get_stock_quantity' => 1,
|
||||
'get_name' => 'Widget',
|
||||
'backorders_allowed' => false,
|
||||
]);
|
||||
|
||||
Functions\expect('wc_get_product')->with(1)->andReturn($mock);
|
||||
|
||||
$result = $this->manager->validate_stock_availability([1], 5);
|
||||
$this->assertIsString($result);
|
||||
$this->assertStringContainsString('Widget', $result);
|
||||
}
|
||||
|
||||
public function testValidateStock_PassesWhenBackordersAllowed(): void
|
||||
{
|
||||
// When stock_quantity is null the insufficient-stock check is skipped,
|
||||
// and the backorders_allowed() branch is reached.
|
||||
$mock = $this->createProductMock([
|
||||
'managing_stock' => true,
|
||||
'is_in_stock' => true,
|
||||
'get_stock_quantity' => null,
|
||||
'backorders_allowed' => true,
|
||||
]);
|
||||
|
||||
Functions\expect('wc_get_product')->with(1)->andReturn($mock);
|
||||
|
||||
$result = $this->manager->validate_stock_availability([1], 5);
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
public function testValidateStock_SkipsNullProducts(): void
|
||||
{
|
||||
Functions\expect('wc_get_product')->with(999)->andReturn(false);
|
||||
|
||||
$result = $this->manager->validate_stock_availability([999]);
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
// --- get_product_stock_info ---
|
||||
|
||||
public function testGetProductStockInfo_ReturnsCorrectStructure(): void
|
||||
{
|
||||
$mock = $this->createProductMock([
|
||||
'is_in_stock' => true,
|
||||
'get_stock_quantity' => 10,
|
||||
'backorders_allowed' => false,
|
||||
'get_stock_status' => 'instock',
|
||||
'managing_stock' => true,
|
||||
]);
|
||||
|
||||
Functions\expect('wc_get_product')->with(1)->andReturn($mock);
|
||||
|
||||
$info = $this->manager->get_product_stock_info(1);
|
||||
|
||||
$this->assertTrue($info['in_stock']);
|
||||
$this->assertSame(10, $info['stock_quantity']);
|
||||
$this->assertFalse($info['backorders_allowed']);
|
||||
$this->assertSame('instock', $info['stock_status']);
|
||||
$this->assertTrue($info['managing_stock']);
|
||||
$this->assertTrue($info['has_enough_stock']);
|
||||
}
|
||||
|
||||
public function testGetProductStockInfo_ReturnsFallbackForInvalidProduct(): void
|
||||
{
|
||||
Functions\expect('wc_get_product')->with(999)->andReturn(false);
|
||||
|
||||
$info = $this->manager->get_product_stock_info(999);
|
||||
|
||||
$this->assertFalse($info['in_stock']);
|
||||
$this->assertSame(0, $info['stock_quantity']);
|
||||
$this->assertFalse($info['backorders_allowed']);
|
||||
$this->assertSame('outofstock', $info['stock_status']);
|
||||
}
|
||||
|
||||
public function testGetProductStockInfo_HasEnoughStockTrueWhenNotManaging(): void
|
||||
{
|
||||
$mock = $this->createProductMock([
|
||||
'managing_stock' => false,
|
||||
'get_stock_quantity' => null,
|
||||
]);
|
||||
|
||||
Functions\expect('wc_get_product')->with(1)->andReturn($mock);
|
||||
|
||||
$info = $this->manager->get_product_stock_info(1, 100);
|
||||
$this->assertTrue($info['has_enough_stock']);
|
||||
}
|
||||
|
||||
public function testGetProductStockInfo_HasEnoughStockFalseWhenInsufficient(): void
|
||||
{
|
||||
$mock = $this->createProductMock([
|
||||
'managing_stock' => true,
|
||||
'get_stock_quantity' => 2,
|
||||
]);
|
||||
|
||||
Functions\expect('wc_get_product')->with(1)->andReturn($mock);
|
||||
|
||||
$info = $this->manager->get_product_stock_info(1, 5);
|
||||
$this->assertFalse($info['has_enough_stock']);
|
||||
}
|
||||
|
||||
// --- prevent_composable_stock_reduction ---
|
||||
|
||||
public function testPreventStockReduction_ReturnsFalseForComposableItem(): void
|
||||
{
|
||||
$productMock = $this->createProductMock(['get_type' => 'composable']);
|
||||
|
||||
$itemMock = \Mockery::mock('WC_Order_Item_Product');
|
||||
$itemMock->shouldReceive('get_product')->andReturn($productMock);
|
||||
|
||||
$orderMock = \Mockery::mock('WC_Order');
|
||||
$orderMock->shouldReceive('get_items')->andReturn([$itemMock]);
|
||||
|
||||
$result = $this->manager->prevent_composable_stock_reduction(true, $orderMock);
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
public function testPreventStockReduction_PassesThroughForNonComposable(): void
|
||||
{
|
||||
$productMock = $this->createProductMock(['get_type' => 'simple']);
|
||||
|
||||
$itemMock = \Mockery::mock('WC_Order_Item_Product');
|
||||
$itemMock->shouldReceive('get_product')->andReturn($productMock);
|
||||
|
||||
$orderMock = \Mockery::mock('WC_Order');
|
||||
$orderMock->shouldReceive('get_items')->andReturn([$itemMock]);
|
||||
|
||||
$result = $this->manager->prevent_composable_stock_reduction(true, $orderMock);
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
// --- store_selected_products_in_order ---
|
||||
|
||||
public function testStoreSelectedProducts_AddsMetaWhenPresent(): void
|
||||
{
|
||||
$itemMock = \Mockery::mock('WC_Order_Item_Product');
|
||||
$itemMock->shouldReceive('add_meta_data')
|
||||
->once()
|
||||
->with('_composable_products', [1, 2], true);
|
||||
|
||||
$this->manager->store_selected_products_in_order(
|
||||
$itemMock,
|
||||
'cart_key',
|
||||
['composable_products' => [1, 2]]
|
||||
);
|
||||
}
|
||||
|
||||
public function testStoreSelectedProducts_DoesNothingWithoutData(): void
|
||||
{
|
||||
$itemMock = \Mockery::mock('WC_Order_Item_Product');
|
||||
$itemMock->shouldNotReceive('add_meta_data');
|
||||
|
||||
$this->manager->store_selected_products_in_order(
|
||||
$itemMock,
|
||||
'cart_key',
|
||||
[]
|
||||
);
|
||||
}
|
||||
}
|
||||
33
tests/bootstrap.php
Normal file
33
tests/bootstrap.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/**
|
||||
* PHPUnit Bootstrap
|
||||
*
|
||||
* Sets up WooCommerce stubs, plugin constants, and the Composer autoloader
|
||||
* for running unit tests without a full WordPress installation.
|
||||
*
|
||||
* @package Magdev\WcComposableProduct\Tests
|
||||
*/
|
||||
|
||||
// Composer autoloader (loads Brain Monkey, Mockery, plugin classes)
|
||||
require_once dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
// Define WordPress constants that the plugin expects
|
||||
defined('ABSPATH') || define('ABSPATH', '/tmp/wordpress/');
|
||||
defined('WP_DEBUG') || define('WP_DEBUG', true);
|
||||
defined('DOING_AJAX') || define('DOING_AJAX', false);
|
||||
|
||||
// Define plugin constants
|
||||
defined('WC_COMPOSABLE_PRODUCT_VERSION') || define('WC_COMPOSABLE_PRODUCT_VERSION', '1.3.1');
|
||||
defined('WC_COMPOSABLE_PRODUCT_FILE') || define('WC_COMPOSABLE_PRODUCT_FILE', dirname(__DIR__) . '/wc-composable-product.php');
|
||||
defined('WC_COMPOSABLE_PRODUCT_PATH') || define('WC_COMPOSABLE_PRODUCT_PATH', dirname(__DIR__) . '/');
|
||||
defined('WC_COMPOSABLE_PRODUCT_URL') || define('WC_COMPOSABLE_PRODUCT_URL', 'https://example.com/wp-content/plugins/wc-composable-product/');
|
||||
defined('WC_COMPOSABLE_PRODUCT_BASENAME') || define('WC_COMPOSABLE_PRODUCT_BASENAME', 'wc-composable-product/wc-composable-product.php');
|
||||
|
||||
// Load WooCommerce class stubs (parent before child)
|
||||
require_once __DIR__ . '/stubs/class-wc-data.php';
|
||||
require_once __DIR__ . '/stubs/class-wc-product.php';
|
||||
require_once __DIR__ . '/stubs/class-wc-settings-page.php';
|
||||
require_once __DIR__ . '/stubs/class-wc-order.php';
|
||||
require_once __DIR__ . '/stubs/class-wc-order-item-product.php';
|
||||
require_once __DIR__ . '/stubs/class-wc-cart.php';
|
||||
require_once __DIR__ . '/stubs/class-wc-admin-settings.php';
|
||||
11
tests/stubs/class-wc-admin-settings.php
Normal file
11
tests/stubs/class-wc-admin-settings.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* Minimal WC_Admin_Settings stub for unit testing.
|
||||
*/
|
||||
class WC_Admin_Settings {
|
||||
public static function output_fields($options) {
|
||||
}
|
||||
|
||||
public static function save_fields($options) {
|
||||
}
|
||||
}
|
||||
15
tests/stubs/class-wc-cart.php
Normal file
15
tests/stubs/class-wc-cart.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* Minimal WC_Cart stub for unit testing.
|
||||
*/
|
||||
class WC_Cart {
|
||||
protected $cart_contents = [];
|
||||
|
||||
public function get_cart() {
|
||||
return $this->cart_contents;
|
||||
}
|
||||
|
||||
public function set_cart_contents($contents) {
|
||||
$this->cart_contents = $contents;
|
||||
}
|
||||
}
|
||||
35
tests/stubs/class-wc-data.php
Normal file
35
tests/stubs/class-wc-data.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* Minimal WC_Data stub for unit testing.
|
||||
*/
|
||||
class WC_Data {
|
||||
protected $id = 0;
|
||||
protected $data = [];
|
||||
protected $meta_data = [];
|
||||
|
||||
public function __construct($id = 0) {
|
||||
if (is_numeric($id) && $id > 0) {
|
||||
$this->id = (int) $id;
|
||||
}
|
||||
}
|
||||
|
||||
public function get_id() {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function set_id($id) {
|
||||
$this->id = (int) $id;
|
||||
}
|
||||
|
||||
public function get_meta($key, $single = true, $context = 'view') {
|
||||
return $this->meta_data[$key] ?? ($single ? '' : []);
|
||||
}
|
||||
|
||||
public function update_meta_data($key, $value, $meta_id = 0) {
|
||||
$this->meta_data[$key] = $value;
|
||||
}
|
||||
|
||||
public function save() {
|
||||
return $this->get_id();
|
||||
}
|
||||
}
|
||||
28
tests/stubs/class-wc-order-item-product.php
Normal file
28
tests/stubs/class-wc-order-item-product.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* Minimal WC_Order_Item_Product stub for unit testing.
|
||||
*/
|
||||
class WC_Order_Item_Product extends WC_Data {
|
||||
protected $product = null;
|
||||
protected $quantity = 1;
|
||||
|
||||
public function get_product() {
|
||||
return $this->product;
|
||||
}
|
||||
|
||||
public function set_product($product) {
|
||||
$this->product = $product;
|
||||
}
|
||||
|
||||
public function get_quantity() {
|
||||
return $this->quantity;
|
||||
}
|
||||
|
||||
public function set_quantity($quantity) {
|
||||
$this->quantity = $quantity;
|
||||
}
|
||||
|
||||
public function add_meta_data($key, $value, $unique = false) {
|
||||
$this->meta_data[$key] = $value;
|
||||
}
|
||||
}
|
||||
24
tests/stubs/class-wc-order.php
Normal file
24
tests/stubs/class-wc-order.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
* Minimal WC_Order stub for unit testing.
|
||||
*/
|
||||
class WC_Order extends WC_Data {
|
||||
protected $items = [];
|
||||
protected $order_notes = [];
|
||||
|
||||
public function get_items($type = '') {
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
public function set_items($items) {
|
||||
$this->items = $items;
|
||||
}
|
||||
|
||||
public function add_order_note($note) {
|
||||
$this->order_notes[] = $note;
|
||||
}
|
||||
|
||||
public function get_order_notes() {
|
||||
return $this->order_notes;
|
||||
}
|
||||
}
|
||||
88
tests/stubs/class-wc-product.php
Normal file
88
tests/stubs/class-wc-product.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/**
|
||||
* Minimal WC_Product stub for unit testing.
|
||||
*/
|
||||
class WC_Product extends WC_Data {
|
||||
protected $supports = [];
|
||||
protected $data = [
|
||||
'name' => '',
|
||||
'price' => '',
|
||||
'regular_price' => '',
|
||||
'status' => 'publish',
|
||||
];
|
||||
|
||||
public function get_type() {
|
||||
return 'simple';
|
||||
}
|
||||
|
||||
public function get_name($context = 'view') {
|
||||
return $this->data['name'] ?? '';
|
||||
}
|
||||
|
||||
public function get_price($context = 'view') {
|
||||
return $this->data['price'] ?? '';
|
||||
}
|
||||
|
||||
public function get_regular_price($context = 'view') {
|
||||
return $this->data['regular_price'] ?? '';
|
||||
}
|
||||
|
||||
public function get_price_html() {
|
||||
return '';
|
||||
}
|
||||
|
||||
public function get_permalink() {
|
||||
return '';
|
||||
}
|
||||
|
||||
public function get_image_id($context = 'view') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function get_stock_quantity($context = 'view') {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function get_stock_status($context = 'view') {
|
||||
return 'instock';
|
||||
}
|
||||
|
||||
public function get_children() {
|
||||
return [];
|
||||
}
|
||||
|
||||
public function is_type($type) {
|
||||
return $this->get_type() === $type;
|
||||
}
|
||||
|
||||
public function is_purchasable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function is_in_stock() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function managing_stock() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function backorders_allowed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function is_sold_individually() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function set_price($price) {
|
||||
$this->data['price'] = $price;
|
||||
}
|
||||
|
||||
public function set_stock_quantity($quantity) {
|
||||
}
|
||||
|
||||
public function supports($feature) {
|
||||
return in_array($feature, $this->supports, true);
|
||||
}
|
||||
}
|
||||
29
tests/stubs/class-wc-settings-page.php
Normal file
29
tests/stubs/class-wc-settings-page.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* Minimal WC_Settings_Page stub for unit testing.
|
||||
*/
|
||||
class WC_Settings_Page {
|
||||
protected $id = '';
|
||||
protected $label = '';
|
||||
|
||||
public function __construct() {
|
||||
}
|
||||
|
||||
public function get_id() {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function get_label() {
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
public function get_settings() {
|
||||
return [];
|
||||
}
|
||||
|
||||
public function output() {
|
||||
}
|
||||
|
||||
public function save() {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user