diff --git a/app/code/Magento/Catalog/Model/Product/Pricing/Renderer/SalableResolver.php b/app/code/Magento/Catalog/Model/Product/Pricing/Renderer/SalableResolver.php new file mode 100644 index 0000000000000000000000000000000000000000..c76a5031e8c221bb160a4ed593c721f39c57ad63 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Pricing/Renderer/SalableResolver.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Model\Product\Pricing\Renderer; + +/** + * Resolvers check whether product available for sale or not + */ +class SalableResolver implements SalableResolverInterface +{ + /** + * Check whether product available for sale + * + * @param \Magento\Framework\Pricing\SaleableInterface $salableItem + * @return boolean + */ + public function isSalable(\Magento\Framework\Pricing\SaleableInterface $salableItem) + { + return $salableItem->getCanShowPrice() !== false && $salableItem->isSalable(); + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Pricing/Renderer/SalableResolverInterface.php b/app/code/Magento/Catalog/Model/Product/Pricing/Renderer/SalableResolverInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..d77015666358ea31135069ca95745ae40c443c2e --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Pricing/Renderer/SalableResolverInterface.php @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Model\Product\Pricing\Renderer; + +/** + * Interface resolver checks whether product available for sale + */ +interface SalableResolverInterface +{ + /** + * Check whether product available for sale + * + * @param \Magento\Framework\Pricing\SaleableInterface $salableItem + * @return boolean + */ + public function isSalable(\Magento\Framework\Pricing\SaleableInterface $salableItem); +} diff --git a/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php b/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php index c144daf8c71bd3dc035338af0283df31009e0126..05c1e3a79ce0318f6d4444e8f276318dae7b59dc 100644 --- a/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php +++ b/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php @@ -10,6 +10,11 @@ use Magento\Catalog\Pricing\Price; use Magento\Framework\Pricing\Render; use Magento\Framework\Pricing\Render\PriceBox as BasePriceBox; use Magento\Msrp\Pricing\Price\MsrpPrice; +use Magento\Catalog\Model\Product\Pricing\Renderer\SalableResolverInterface; +use Magento\Framework\View\Element\Template\Context; +use Magento\Framework\Pricing\SaleableInterface; +use Magento\Framework\Pricing\Price\PriceInterface; +use Magento\Framework\Pricing\Render\RendererPool; /** * Class for final_price rendering @@ -19,12 +24,38 @@ use Magento\Msrp\Pricing\Price\MsrpPrice; */ class FinalPriceBox extends BasePriceBox { + /** + * @var SalableResolverInterface + */ + private $salableResolver; + + /** + * @param Context $context + * @param SaleableInterface $saleableItem + * @param PriceInterface $price + * @param RendererPool $rendererPool + * @param array $data + * @param SalableResolverInterface $salableResolver + */ + public function __construct( + Context $context, + SaleableInterface $saleableItem, + PriceInterface $price, + RendererPool $rendererPool, + array $data = [], + SalableResolverInterface $salableResolver = null + ) { + parent::__construct($context, $saleableItem, $price, $rendererPool, $data); + $this->salableResolver = $salableResolver ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(SalableResolverInterface::class); + } + /** * @return string */ protected function _toHtml() { - if (!$this->getSaleableItem() || $this->getSaleableItem()->getCanShowPrice() === false) { + if (!$this->salableResolver->isSalable($this->getSaleableItem())) { return ''; } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Pricing/Renderer/SalableResolverTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Pricing/Renderer/SalableResolverTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7ef15b0781931d28e899942d47e5fa3dc851349c --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Pricing/Renderer/SalableResolverTest.php @@ -0,0 +1,60 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Test\Unit\Model\Product\Pricing\Renderer; + +class SalableResolverTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Catalog\Model\Product\Pricing\Renderer\SalableResolver + */ + protected $object; + + /** + * @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject + */ + protected $product; + + protected function setUp() + { + $this->product = $this->getMock( + \Magento\Catalog\Model\Product::class, + ['__wakeup', 'getCanShowPrice', 'isSalable'], + [], + '', + false + ); + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->object = $objectManager->getObject( + \Magento\Catalog\Model\Product\Pricing\Renderer\SalableResolver::class + ); + } + + public function testSalableItem() + { + $this->product->expects($this->any()) + ->method('getCanShowPrice') + ->willReturn(true); + + $this->product->expects($this->any())->method('isSalable')->willReturn(true); + + $result = $this->object->isSalable($this->product); + $this->assertTrue($result); + } + + public function testNotSalableItem() + { + $this->product->expects($this->any()) + ->method('getCanShowPrice') + ->willReturn(true); + + $this->product->expects($this->any())->method('isSalable')->willReturn(false); + + $result = $this->object->isSalable($this->product); + $this->assertFalse($result); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Pricing/Render/FinalPriceBoxTest.php b/app/code/Magento/Catalog/Test/Unit/Pricing/Render/FinalPriceBoxTest.php index bfe4e0c071bec556dc513682583c7f5b55108983..015a641a0df38d1337c99fa6ab8b06b405a76d11 100644 --- a/app/code/Magento/Catalog/Test/Unit/Pricing/Render/FinalPriceBoxTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Pricing/Render/FinalPriceBoxTest.php @@ -6,6 +6,8 @@ namespace Magento\Catalog\Test\Unit\Pricing\Render; +use Magento\Catalog\Model\Product\Pricing\Renderer\SalableResolverInterface; + /** * Class FinalPriceBoxTest * @@ -58,11 +60,16 @@ class FinalPriceBoxTest extends \PHPUnit_Framework_TestCase */ protected $price; + /** + * @var SalableResolverInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $salableResolverMock; + protected function setUp() { $this->product = $this->getMock( \Magento\Catalog\Model\Product::class, - ['getPriceInfo', '__wakeup', 'getCanShowPrice'], + ['getPriceInfo', '__wakeup', 'getCanShowPrice', 'isSalable'], [], '', false @@ -78,9 +85,7 @@ class FinalPriceBoxTest extends \PHPUnit_Framework_TestCase $this->priceBox = $this->getMock(\Magento\Framework\Pricing\Render\PriceBox::class, [], [], '', false); $this->logger = $this->getMock(\Psr\Log\LoggerInterface::class); - $this->layout->expects($this->any()) - ->method('getBlock') - ->will($this->returnValue($this->priceBox)); + $this->layout->expects($this->any())->method('getBlock')->willReturn($this->priceBox); $cacheState = $this->getMockBuilder(\Magento\Framework\App\Cache\StateInterface::class) ->getMockForAbstractClass(); @@ -93,12 +98,9 @@ class FinalPriceBoxTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->getMock(); - $urlBuilder = $this->getMockBuilder(\Magento\Framework\UrlInterface::class) - ->getMockForAbstractClass(); - - $store = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) - ->getMockForAbstractClass(); + $urlBuilder = $this->getMockBuilder(\Magento\Framework\UrlInterface::class)->getMockForAbstractClass(); + $store = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class)->getMockForAbstractClass(); $storeManager = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) ->setMethods(['getStore', 'getCode']) ->getMockForAbstractClass(); @@ -144,6 +146,10 @@ class FinalPriceBoxTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue(\Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE)); $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->salableResolverMock = $this->getMockBuilder(SalableResolverInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->object = $objectManager->getObject( \Magento\Catalog\Pricing\Render\FinalPriceBox::class, [ @@ -151,7 +157,8 @@ class FinalPriceBoxTest extends \PHPUnit_Framework_TestCase 'saleableItem' => $this->product, 'rendererPool' => $this->rendererPool, 'price' => $this->price, - 'data' => ['zone' => 'test_zone', 'list_category_page' => true] + 'data' => ['zone' => 'test_zone', 'list_category_page' => true], + 'salableResolver' => $this->salableResolverMock ] ); } @@ -169,6 +176,8 @@ class FinalPriceBoxTest extends \PHPUnit_Framework_TestCase ->with($this->equalTo($this->product)) ->will($this->returnValue(false)); + $this->salableResolverMock->expects($this->once())->method('isSalable')->with($this->product)->willReturn(true); + $result = $this->object->toHtml(); //assert price wrapper @@ -177,6 +186,18 @@ class FinalPriceBoxTest extends \PHPUnit_Framework_TestCase $this->assertRegExp('/[final_price]/', $result); } + public function testNotSalableItem() + { + $this->salableResolverMock + ->expects($this->once()) + ->method('isSalable') + ->with($this->product) + ->willReturn(false); + $result = $this->object->toHtml(); + + $this->assertEmpty($result); + } + public function testRenderMsrpEnabled() { $priceType = $this->getMock(\Magento\Msrp\Pricing\Price\MsrpPrice::class, [], [], '', false); @@ -211,6 +232,8 @@ class FinalPriceBoxTest extends \PHPUnit_Framework_TestCase ->with('msrp_price', $this->product, $arguments) ->will($this->returnValue($priceBoxRender)); + $this->salableResolverMock->expects($this->once())->method('isSalable')->with($this->product)->willReturn(true); + $result = $this->object->toHtml(); //assert price wrapper @@ -230,6 +253,8 @@ class FinalPriceBoxTest extends \PHPUnit_Framework_TestCase ->with($this->equalTo('msrp_price')) ->will($this->throwException(new \InvalidArgumentException())); + $this->salableResolverMock->expects($this->once())->method('isSalable')->with($this->product)->willReturn(true); + $result = $this->object->toHtml(); //assert price wrapper diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml index 6b39520ae021e9eb3bfafa20bda03e42c7db907a..047c3f2fc7cabb19974f7d20fdc77952f06ebcd6 100644 --- a/app/code/Magento/Catalog/etc/di.xml +++ b/app/code/Magento/Catalog/etc/di.xml @@ -48,6 +48,7 @@ <preference for="Magento\Catalog\Api\Data\CategorySearchResultsInterface" type="Magento\Framework\Api\SearchResults" /> <preference for="Magento\Catalog\Model\Config\Source\ProductPriceOptionsInterface" type="Magento\Catalog\Model\Config\Source\Product\Options\Price"/> <preference for="Magento\Catalog\Model\Indexer\Product\Flat\Table\BuilderInterface" type="Magento\Catalog\Model\Indexer\Product\Flat\Table\Builder"/> + <preference for="Magento\Catalog\Model\Product\Pricing\Renderer\SalableResolverInterface" type="Magento\Catalog\Model\Product\Pricing\Renderer\SalableResolver"/> <type name="Magento\Customer\Model\ResourceModel\Visitor"> <plugin name="catalogLog" type="Magento\Catalog\Model\Plugin\Log" /> </type> diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php index 68e82ed76a23fbdda404c32eb7406d3117b3cd0e..eb3040ad2f66866269c46febf3c014b9d01bf31b 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php @@ -64,11 +64,6 @@ class ConfigurablePriceResolver implements PriceResolverInterface $productPrice = $this->priceResolver->resolvePrice($subProduct); $price = $price ? min($price, $productPrice) : $productPrice; } - if ($price === null) { - throw new \Magento\Framework\Exception\LocalizedException( - __('Configurable product "%1" does not have sub-products', $product->getSku()) - ); - } return (float)$price; } diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/ConfigurablePriceResolverTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/ConfigurablePriceResolverTest.php index 8db61bb5e0a4322ff3d614348616dfc0e2f34e27..78ac3a309745825b20e09e0e097d9217d6691ac9 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/ConfigurablePriceResolverTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/ConfigurablePriceResolverTest.php @@ -51,24 +51,6 @@ class ConfigurablePriceResolverTest extends \PHPUnit_Framework_TestCase ); } - /** - * situation: There are no used products, thus there are no prices - * - * @expectedException \Magento\Framework\Exception\LocalizedException - */ - public function testResolvePriceWithNoPrices() - { - $product = $this->getMockBuilder( - \Magento\Catalog\Model\Product::class - )->disableOriginalConstructor()->getMock(); - - $product->expects($this->once())->method('getSku')->willReturn('Kiwi'); - - $this->lowestPriceOptionsProvider->expects($this->once())->method('getProducts')->willReturn([]); - - $this->resolver->resolvePrice($product); - } - /** * situation: one product is supplying the price, which could be a price of zero (0) * diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.xml b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.xml index 24f63035645f22ebbf051f2ab0cb8cbf48c59c93..14d8ecccd1dc34e5dbde389f2c74ff15fcca6c20 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.xml +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.xml @@ -120,7 +120,7 @@ <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal"/> </variation> <variation name="OnePageCheckoutTestVariation5" summary="Guest Checkout using Check/Money Order and Free Shipping with Prices/Taxes Verifications" ticketId="MAGETWO-12412"> - <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test</data> + <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test, stable:no</data> <data name="products/0" xsi:type="string">catalogProductSimple::product_10_dollar</data> <data name="products/1" xsi:type="string">configurableProduct::with_one_option</data> <data name="products/2" xsi:type="string">bundleProduct::bundle_fixed_100_dollar_product</data>