-
Notifications
You must be signed in to change notification settings - Fork 87
Validated WC cart object to prevent fatal error #4558
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+366
−0
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,160 @@ | ||
| <?php | ||
| /** | ||
| * Minimal WooCommerce stubs for the cart markup tests. | ||
| * | ||
| * `WooCommerce::$cart` starts out null, reproducing the "plugin loaded, cart | ||
| * missing" state; tests that need a usable cart assign a `WC_Cart` themselves. | ||
| * | ||
| * @package neve | ||
| */ | ||
|
|
||
| if ( ! defined( 'NEVE_TESTS_WC_CART_STUB' ) ) { | ||
| define( 'NEVE_TESTS_WC_CART_STUB', true ); | ||
| } | ||
|
|
||
| if ( ! class_exists( 'WooCommerce', false ) ) { | ||
| /** | ||
| * Stand-in for the WooCommerce main class, with no cart object. | ||
| */ | ||
| class WooCommerce { | ||
| /** | ||
| * The cart instance, null until a test assigns one. | ||
| * | ||
| * @var \WC_Cart|null | ||
| */ | ||
| public $cart = null; | ||
| } | ||
| } | ||
|
|
||
| if ( ! class_exists( 'WC_Cart', false ) ) { | ||
| /** | ||
| * Stand-in for the cart, carrying just the contents count and totals. | ||
| */ | ||
| class WC_Cart { | ||
| /** | ||
| * Number of items in the cart. | ||
| * | ||
| * @var int | ||
| */ | ||
| private $count; | ||
|
|
||
| /** | ||
| * Cart total, unformatted. | ||
| * | ||
| * @var string | ||
| */ | ||
| private $total; | ||
|
|
||
| /** | ||
| * Constructor. | ||
| * | ||
| * @param int $count number of items in the cart. | ||
| * @param string $total cart total, unformatted. | ||
| */ | ||
| public function __construct( $count = 0, $total = '0' ) { | ||
| $this->count = $count; | ||
| $this->total = $total; | ||
| } | ||
|
|
||
| /** | ||
| * Cart contents count. | ||
| * | ||
| * @return int | ||
| */ | ||
| public function get_cart_contents_count() { | ||
| return $this->count; | ||
| } | ||
|
|
||
| /** | ||
| * Cart contents total, unformatted. | ||
| * | ||
| * @return string | ||
| */ | ||
| public function get_cart_contents_total() { | ||
| return $this->total; | ||
| } | ||
|
|
||
| /** | ||
| * Cart total, formatted with the currency symbol. | ||
| * | ||
| * @return string | ||
| */ | ||
| public function get_cart_total() { | ||
| return '$' . $this->total; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if ( ! class_exists( 'WC_Widget_Cart', false ) ) { | ||
| /** | ||
| * Stand-in for the mini cart widget rendered inside the cart icon component. | ||
| */ | ||
| class WC_Widget_Cart extends WP_Widget { | ||
| /** | ||
| * Constructor. | ||
| */ | ||
| public function __construct() { | ||
| parent::__construct( 'neve_tests_wc_widget_cart', 'Cart', array( 'classname' => 'widget_shopping_cart' ) ); | ||
| } | ||
|
|
||
| /** | ||
| * Output the widget. | ||
| * | ||
| * @param array $args widget arguments. | ||
| * @param array $instance widget instance settings. | ||
| */ | ||
| public function widget( $args, $instance ) { | ||
| echo '<div class="widget_shopping_cart_content"></div>'; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if ( ! function_exists( 'WC' ) ) { | ||
| /** | ||
| * Return the stubbed WooCommerce instance. | ||
| * | ||
| * @return \WooCommerce | ||
| */ | ||
| function WC() { | ||
| static $instance = null; | ||
|
|
||
| if ( $instance === null ) { | ||
| $instance = new WooCommerce(); | ||
| } | ||
|
|
||
| return $instance; | ||
| } | ||
| } | ||
|
|
||
| if ( ! function_exists( 'wc_get_cart_url' ) ) { | ||
| /** | ||
| * Cart permalink stub. | ||
| * | ||
| * @return string | ||
| */ | ||
| function wc_get_cart_url() { | ||
| return home_url( '/cart/' ); | ||
| } | ||
| } | ||
|
|
||
| if ( ! function_exists( 'is_cart' ) ) { | ||
| /** | ||
| * The cart page is never the current request in these tests. | ||
| * | ||
| * @return bool | ||
| */ | ||
| function is_cart() { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| if ( ! function_exists( 'is_checkout' ) ) { | ||
| /** | ||
| * The checkout page is never the current request in these tests. | ||
| * | ||
| * @return bool | ||
| */ | ||
| function is_checkout() { | ||
| return false; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,188 @@ | ||
| <?php | ||
| /** | ||
| * Tests for the cart markup guards: the legacy primary-navigation cart item, the | ||
| * header/footer builder cart icon component and the cart total magic tags. | ||
| * | ||
| * Both entry points live in one test case on purpose. The null-cart scenario has | ||
| * to declare a global `WooCommerce` class, which cannot be undeclared afterwards, | ||
| * so the tests that need WooCommerce absent must be guaranteed to run first - | ||
| * something only method order inside a single class gives us. | ||
| * | ||
| * @package neve | ||
| */ | ||
|
|
||
| /** | ||
| * Class TestNeveCartGuards | ||
| */ | ||
| class TestNeveCartGuards extends WP_UnitTestCase { | ||
|
|
||
| /** | ||
| * Render the nav menu cart markup. | ||
| * | ||
| * @param bool $responsive whether to render the responsive variant. | ||
| * | ||
| * @return string | ||
| */ | ||
| private function render_nav_menu_cart( $responsive = false ) { | ||
| $header = new \Neve\Views\Header(); | ||
| $method = new ReflectionMethod( $header, 'get_nav_menu_cart' ); | ||
| $method->setAccessible( true ); | ||
|
|
||
| return $method->invoke( $header, $responsive ); | ||
| } | ||
|
|
||
| /** | ||
| * Render the builder cart icon component. | ||
| * | ||
| * @return string | ||
| */ | ||
| private function render_cart_icon_component() { | ||
| $reflection = new ReflectionClass( \HFG\Core\Components\CartIcon::class ); | ||
| $component = $reflection->newInstanceWithoutConstructor(); | ||
|
|
||
| ob_start(); | ||
| $component->render_component(); | ||
|
|
||
| return (string) ob_get_clean(); | ||
| } | ||
|
|
||
| /** | ||
| * Load the WooCommerce stubs. | ||
| */ | ||
| private function require_wc_stubs() { | ||
| require_once __DIR__ . '/stubs/woocommerce-cart.php'; | ||
| } | ||
|
|
||
| /** | ||
| * Skip when the cart state cannot be simulated. | ||
| */ | ||
| private function skip_unless_cart_is_stubbable() { | ||
| if ( defined( 'NEVE_TESTS_WC_CART_STUB' ) ) { | ||
| return; | ||
| } | ||
|
|
||
| if ( class_exists( 'WooCommerce', false ) || function_exists( 'WC' ) ) { | ||
| $this->markTestSkipped( 'A real WooCommerce instance is loaded; the cart state cannot be stubbed.' ); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Skip when WooCommerce is present, stub included. | ||
| */ | ||
| private function skip_unless_woocommerce_is_absent() { | ||
| if ( class_exists( 'WooCommerce', false ) ) { | ||
| $this->markTestSkipped( 'WooCommerce is loaded in this environment.' ); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Nothing is rendered when WooCommerce is not active at all. | ||
| */ | ||
| public function test_nav_menu_cart_is_empty_without_woocommerce() { | ||
| $this->skip_unless_woocommerce_is_absent(); | ||
|
|
||
| $this->assertSame( '', $this->render_nav_menu_cart() ); | ||
| $this->assertSame( '', $this->render_nav_menu_cart( true ) ); | ||
| } | ||
|
|
||
| /** | ||
| * The builder cart icon renders nothing when WooCommerce is not active at all. | ||
| */ | ||
| public function test_cart_icon_component_is_empty_without_woocommerce() { | ||
| $this->skip_unless_woocommerce_is_absent(); | ||
|
|
||
| $this->assertSame( '', $this->render_cart_icon_component() ); | ||
| } | ||
|
|
||
| /** | ||
| * Nothing is rendered when the WooCommerce cart object is not available. | ||
| */ | ||
| public function test_nav_menu_cart_is_empty_when_cart_object_is_missing() { | ||
| $this->skip_unless_cart_is_stubbable(); | ||
| $this->require_wc_stubs(); | ||
|
|
||
| $this->assertNull( WC()->cart, 'The stub must expose a null cart to reproduce the crash.' ); | ||
| $this->assertSame( '', $this->render_nav_menu_cart() ); | ||
| $this->assertSame( '', $this->render_nav_menu_cart( true ) ); | ||
| } | ||
|
|
||
| /** | ||
| * The builder cart icon renders nothing when the WooCommerce cart object is | ||
| * not available. | ||
| */ | ||
| public function test_cart_icon_component_is_empty_when_cart_object_is_missing() { | ||
| $this->skip_unless_cart_is_stubbable(); | ||
| $this->require_wc_stubs(); | ||
|
|
||
| $this->assertNull( WC()->cart, 'The stub must expose a null cart to reproduce the crash.' ); | ||
| $this->assertSame( '', $this->render_cart_icon_component() ); | ||
| } | ||
|
|
||
| /** | ||
| * The builder cart icon renders the cart markup when WooCommerce is active | ||
| * and the cart holds items. | ||
| */ | ||
| public function test_cart_icon_component_is_not_empty_when_cart_has_items() { | ||
| $this->skip_unless_cart_is_stubbable(); | ||
| $this->require_wc_stubs(); | ||
|
|
||
| $previous_cart = WC()->cart; | ||
| WC()->cart = new WC_Cart( 3 ); | ||
| register_widget( 'WC_Widget_Cart' ); | ||
|
|
||
| try { | ||
| $output = $this->render_cart_icon_component(); | ||
| } finally { | ||
| WC()->cart = $previous_cart; | ||
| unregister_widget( 'WC_Widget_Cart' ); | ||
| } | ||
|
|
||
| $this->assertNotSame( '', $output ); | ||
| $this->assertStringContainsString( 'menu-item-nav-cart', $output ); | ||
| $this->assertStringContainsString( 'cart-icon-wrapper', $output ); | ||
| $this->assertMatchesRegularExpression( '/class="cart-count">\s*3\s*</', $output, 'The cart contents count is rendered.' ); | ||
| $this->assertStringNotContainsString( 'cart-is-empty', $output ); | ||
| } | ||
|
|
||
| /** | ||
| * The cart total magic tags resolve to nothing when the WooCommerce cart | ||
| * object is not available. | ||
| */ | ||
| public function test_cart_magic_tags_are_empty_when_cart_object_is_missing() { | ||
| $this->skip_unless_cart_is_stubbable(); | ||
| $this->require_wc_stubs(); | ||
|
|
||
| $magic_tags = \HFG\Core\Magic_Tags::get_instance(); | ||
|
|
||
| $this->assertNull( WC()->cart, 'The stub must expose a null cart to reproduce the crash.' ); | ||
| $this->assertSame( '', $magic_tags->cart_total() ); | ||
| $this->assertSame( '', $magic_tags->cart_total_currency_symbol() ); | ||
| $this->assertSame( '', $magic_tags->do_magic_tags( '{cart_total}' ) ); | ||
| $this->assertSame( '', $magic_tags->do_magic_tags( '{cart_total_currency_symbol}' ) ); | ||
| } | ||
|
|
||
| /** | ||
| * The cart total magic tags resolve to the totals when WooCommerce is active | ||
| * and the cart holds items. | ||
| */ | ||
| public function test_cart_magic_tags_render_when_cart_has_items() { | ||
| $this->skip_unless_cart_is_stubbable(); | ||
| $this->require_wc_stubs(); | ||
|
|
||
| $magic_tags = \HFG\Core\Magic_Tags::get_instance(); | ||
| $previous_cart = WC()->cart; | ||
| WC()->cart = new WC_Cart( 3, '42' ); | ||
|
|
||
| try { | ||
| $total = $magic_tags->cart_total(); | ||
| $total_currency = $magic_tags->cart_total_currency_symbol(); | ||
| $parsed_total = $magic_tags->do_magic_tags( '{cart_total}' ); | ||
| } finally { | ||
| WC()->cart = $previous_cart; | ||
| } | ||
|
|
||
| $this->assertSame( '<span class="nv-cart-icon-total-plain">42</span>', $total ); | ||
| $this->assertSame( '<span class="nv-cart-icon-total-currency">$42</span>', $total_currency ); | ||
| $this->assertStringContainsString( '42', $parsed_total ); | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.