diff --git a/src/Hooks/CheckoutScriptHooks.php b/src/Hooks/CheckoutScriptHooks.php index 0fb775cf5..8ff9d2187 100644 --- a/src/Hooks/CheckoutScriptHooks.php +++ b/src/Hooks/CheckoutScriptHooks.php @@ -195,11 +195,19 @@ private function shouldShowDeliveryOptions(): bool $showDeliveryOptions = false; + $deliveryOptionsEnabledWhenOnBackorder = Settings::get( + CheckoutSettings::ENABLE_DELIVERY_OPTIONS_WHEN_NOT_IN_STOCK, + CheckoutSettings::ID + ); + foreach (WC()->cart->get_cart() as $cartItem) { /** @var WC_Product $product */ $product = $cartItem['data']; - if (! $product->is_virtual() && ! $product->is_on_backorder($cartItem['quantity'])) { + $productIsVirtual = $product->is_virtual(); + $productOnBackorder = $product->is_on_backorder($cartItem['quantity']); + + if (! $productIsVirtual && (! $productOnBackorder || $deliveryOptionsEnabledWhenOnBackorder)) { $showDeliveryOptions = true; break; } diff --git a/tests/Mock/MockWcCart.php b/tests/Mock/MockWcCart.php index b7e2d0a82..cefa53902 100644 --- a/tests/Mock/MockWcCart.php +++ b/tests/Mock/MockWcCart.php @@ -34,11 +34,24 @@ public function add_to_cart( array $variation = [], array $cartItemData = [] ): void { - for ($i = 0; $i < $quantity; $i++) { - $this->items[] = new WC_Product($productId); + $cartId = $this->generate_cart_id($productId, $variationId, $variation, $cartItemData); + $cartItemKey = $this->find_product_in_cart($cartId); + + if ($cartItemKey) { + $this->items[$cartItemKey]['quantity'] += $quantity; + } else { + $this->items[] = [ + 'data' => new WC_Product($productId), + 'quantity' => $quantity, + ]; } } + public function get_cart() + { + return $this->items; + } + /** * @return void */ @@ -53,13 +66,12 @@ public function empty_cart(): void public function get_shipping_packages(): array { // calculate weight of all products in cart - $weight = array_reduce( - $this->items, - static function (float $carry, WC_Product $item) { - return $carry + (float) $item->get_weight(); - }, - 0 - ); + $weight = 0; + foreach ($this->items as $item) { + /** @var \WC_Product $wcProduct */ + $wcProduct = $item['data']; + $weight += $wcProduct->get_weight() * $item['quantity']; + } if ($weight > 10) { return []; @@ -69,4 +81,72 @@ static function (float $carry, WC_Product $item) { 'flat_rate:0' => [], ]; } + + /** + * Generate a unique ID for the cart item being added. + * + * @param int $productId - id of the product the key is being generated for. + * @param int $variationId of the product the key is being generated for. + * @param array $variation data for the cart item. + * @param array $cartItemData other cart item data passed which affects this items uniqueness in the cart. + * + * @return string cart item key + */ + public function generate_cart_id( + int $productId, + int $variationId = 0, + array $variation = [], + array $cartItemData = [] + ): string { + $idParts = [$productId]; + + if ($variationId && 0 !== $variationId) { + $idParts[] = $variationId; + } + + if (is_array($variation) && ! empty($variation)) { + $variationKey = ''; + foreach ($variation as $key => $value) { + $variationKey .= trim($key) . trim($value); + } + $idParts[] = $variationKey; + } + + if (is_array($cartItemData) && ! empty($cartItemData)) { + $cartItemDataKey = ''; + foreach ($cartItemData as $key => $value) { + if (is_array($value) || is_object($value)) { + $value = http_build_query($value); + } + $cartItemDataKey .= trim($key) . trim($value); + } + $idParts[] = $cartItemDataKey; + } + + return apply_filters( + 'woocommerce_cart_id', + md5(implode('_', $idParts)) + ); + } + + /** + * Check if product is in the cart and return cart item key. + * Cart item key will be unique based on the item and its properties, such as variations. + * ONLY RETURNS A KEY! DOES NOT RETURN THE ITEM! + * + * @param mixed $cartId id of product to find in the cart. + * + * @return string cart item key + */ + public function find_product_in_cart($cartId = false): string + { + $thisItemsIsArray = is_array($this->items); + $itemAlreadyExists = isset($this->items[$cartId]); + + if ($cartId !== false && $thisItemsIsArray && $itemAlreadyExists) { + return $cartId; + } + + return ''; + } } diff --git a/tests/Mock/MockWpEnqueue.php b/tests/Mock/MockWpEnqueue.php new file mode 100644 index 000000000..e459fdb3f --- /dev/null +++ b/tests/Mock/MockWpEnqueue.php @@ -0,0 +1,93 @@ +put( + $handle, + array_merge($existing, [ + [ + 'src' => $src, + 'deps' => $deps, + 'ver' => $ver, + 'in_footer' => $in_footer, + ], + ]) + ); + } + + /** + * @return \MyParcelNL\Pdk\Base\Support\Collection + */ + public static function all(): Collection + { + return self::getQueuedItems(); + } + + /** + * @param string $tag + * + * @return array + */ + public static function get(string $tag): array + { + return self::getQueuedItems() + ->get($tag, []); + } + + public static function reset(): void + { + self::$queuedItems = new Collection(); + } + + public static function toArray(): array + { + return self::getQueuedItems() + ->map(static function (array $actions) { + return (new Collection(Arr::pluck($actions, 'function')))->map(static function ($function) { + if (! is_array($function)) { + return $function; + } + + return implode('::', [get_class($function[0]), $function[1]]); + }); + }) + ->toArray(); + } + + /** + * @return \MyParcelNL\Pdk\Base\Support\Collection + */ + private static function getQueuedItems(): Collection + { + if (null === self::$queuedItems) { + self::reset(); + } + + return self::$queuedItems; + } +} diff --git a/tests/Unit/Hooks/CheckoutScriptHooksTest.php b/tests/Unit/Hooks/CheckoutScriptHooksTest.php new file mode 100644 index 000000000..4d1703b3f --- /dev/null +++ b/tests/Unit/Hooks/CheckoutScriptHooksTest.php @@ -0,0 +1,83 @@ +withEnableDeliveryOptions($enableDeliveryOptions) + ->withEnableDeliveryOptionsWhenNotInStock($enableDeliveryOptionsWhenNotInStock) + ->store(); + + $product = wpFactory(WC_Product::class) + ->with($productData) + ->make(); + + WC()->cart->add_to_cart($product->get_id(), 2); + + /** @var \MyParcelNL\WooCommerce\Hooks\CheckoutScriptHooks $class */ + $class = Pdk::get(CheckoutScriptHooks::class); + + $class->enqueueFrontendScripts(); + + $all = + MockWpEnqueue::all() + ->all(); + + expect($all) + ->toHaveKeys($expected['toContain']) + ->and($all)->not->toHaveKeys($expected['notToContain']); + + MockWpEnqueue::reset(); + WC()->cart->empty_cart(); + } +) + ->with([ + 'enable all, in stock' => [ + 'enableDeliveryOptions' => true, + 'enableDeliveryOptionsWhenNotInStock' => true, + 'productData' => ['id' => 1, 'is_on_backorder' => false], + 'expected' => [ + 'toContain' => ['myparcelnl-delivery-options'], + 'notToContain' => [], + ], + ], + 'enable delivery options, on backorder' => [ + 'enableDeliveryOptions' => true, + 'enableDeliveryOptionsWhenNotInStock' => false, + 'productData' => ['id' => 1, 'is_on_backorder' => true], + 'expected' => [ + 'toContain' => [], + 'notToContain' => ['myparcelnl-delivery-options'], + ], + ], + 'enable all, on backorder' => [ + 'enableDeliveryOptions' => true, + 'enableDeliveryOptionsWhenNotInStock' => true, + 'productData' => ['id' => 1, 'is_on_backorder' => true], + 'expected' => [ + 'toContain' => ['myparcelnl-delivery-options'], + 'notToContain' => [], + ], + ], + ]); diff --git a/tests/__snapshots__/ProductSettingsMigrationTest__it_migrates_pre_v5.0.0_product_settings_with_data_set_simple_product_with_all_settings__1.json b/tests/__snapshots__/ProductSettingsMigrationTest__it_migrates_pre_v5.0.0_product_settings_with_data_set_simple_product_with_all_settings__1.json index 5bdb8013c..f551bf49a 100644 --- a/tests/__snapshots__/ProductSettingsMigrationTest__it_migrates_pre_v5.0.0_product_settings_with_data_set_simple_product_with_all_settings__1.json +++ b/tests/__snapshots__/ProductSettingsMigrationTest__it_migrates_pre_v5.0.0_product_settings_with_data_set_simple_product_with_all_settings__1.json @@ -51,8 +51,8 @@ "value": ["5.0.0-alpha.1"] } ], + "parentProduct": null, "product": { - "id": "product", "countryOfOrigin": "BB", "customsCode": "1234", "disableDeliveryOptions": -1, @@ -66,7 +66,7 @@ "exportSignature": -1, "fitInDigitalStamp": 7, "fitInMailbox": 7, + "id": "product", "packageType": "digital_stamp" - }, - "parentProduct": null + } } diff --git a/tests/mock_wp_functions.php b/tests/mock_wp_functions.php index 5eba63275..a76c5b3c1 100644 --- a/tests/mock_wp_functions.php +++ b/tests/mock_wp_functions.php @@ -6,6 +6,7 @@ use MyParcelNL\Pdk\Facade\Pdk; use MyParcelNL\WooCommerce\Tests\Exception\DieException; use MyParcelNL\WooCommerce\Tests\Mock\MockWpActions; +use MyParcelNL\WooCommerce\Tests\Mock\MockWpEnqueue; use MyParcelNL\WooCommerce\Tests\Mock\MockWpMeta; use MyParcelNL\WooCommerce\Tests\Mock\MockWpUser; use MyParcelNL\WooCommerce\Tests\Mock\WordPressOptions; @@ -151,3 +152,13 @@ function wp_unslash($value) { return $value; } + +function wp_enqueue_script($handle, $src = '', $deps = [], $ver = false, $in_footer = false) +{ + MockWpEnqueue::add($handle, $src, $deps, $ver, $in_footer); +} + +function wp_enqueue_style($handle, $src, $deps, $version, $media) +{ + MockWpEnqueue::add($handle, $src, $deps, $version, $media); +} \ No newline at end of file