Skip to content

Commit cf30cfb

Browse files
committed
MC-30642: Storefront: Configurable product prices
1 parent 06a0dd0 commit cf30cfb

File tree

4 files changed

+438
-0
lines changed

4 files changed

+438
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\ConfigurableProduct\Block\Product\View\Type;
9+
10+
use Magento\Catalog\Api\Data\ProductCustomOptionInterface;
11+
use Magento\Catalog\Api\Data\ProductInterface;
12+
use Magento\Catalog\Api\ProductRepositoryInterface;
13+
use Magento\Framework\ObjectManagerInterface;
14+
use Magento\Framework\Registry;
15+
use Magento\Framework\Serialize\SerializerInterface;
16+
use Magento\Framework\View\Result\Page;
17+
use Magento\TestFramework\Helper\Bootstrap;
18+
use PHPUnit\Framework\TestCase;
19+
20+
/**
21+
* Check configurable product price displaying
22+
*
23+
* @magentoDbIsolation enabled
24+
* @magentoAppIsolation enabled
25+
* @magentoAppArea frontend
26+
*/
27+
class ConfigurableProductPriceTest extends TestCase
28+
{
29+
/** @var ObjectManagerInterface */
30+
private $objectManager;
31+
32+
/** @var Registry */
33+
private $registry;
34+
35+
/** @var ProductRepositoryInterface */
36+
private $productRepository;
37+
38+
/** @var Page */
39+
private $page;
40+
41+
/** @var ProductCustomOptionInterface */
42+
private $productCustomOption;
43+
44+
/** @var SerializerInterface */
45+
private $json;
46+
47+
/**
48+
* @inheritdoc
49+
*/
50+
protected function setUp()
51+
{
52+
parent::setUp();
53+
54+
$this->objectManager = Bootstrap::getObjectManager();
55+
$this->registry = $this->objectManager->get(Registry::class);
56+
$this->page = $this->objectManager->get(Page::class);
57+
$this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class);
58+
$this->productRepository->cleanCache();
59+
$this->productCustomOption = $this->objectManager->get(ProductCustomOptionInterface::class);
60+
$this->json = $this->objectManager->get(SerializerInterface::class);
61+
}
62+
63+
/**
64+
* @inheritdoc
65+
*/
66+
protected function tearDown()
67+
{
68+
$this->registry->unregister('product');
69+
$this->registry->unregister('current_product');
70+
71+
parent::tearDown();
72+
}
73+
74+
/**
75+
* @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php
76+
*
77+
* @return void
78+
*/
79+
public function testConfigurablePrice(): void
80+
{
81+
$this->assertPrice($this->processPriceView('configurable'), 10.00);
82+
}
83+
84+
/**
85+
* @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable_disable_first_child.php
86+
*
87+
* @return void
88+
*/
89+
public function testConfigurablePriceWithDisabledFirstChild(): void
90+
{
91+
$this->assertPrice($this->processPriceView('configurable'), 20.00);
92+
}
93+
94+
/**
95+
* @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable_zero_qty_first_child.php
96+
*
97+
* @return void
98+
*/
99+
public function testConfigurablePriceWithOutOfStockFirstChild(): void
100+
{
101+
$this->assertPrice($this->processPriceView('configurable'), 20.00);
102+
}
103+
104+
/**
105+
* @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php
106+
* @magentoDataFixture Magento/CatalogRule/_files/rule_apply_as_percentage_of_original_not_logged_user.php
107+
* @magentoDbIsolation disabled
108+
*
109+
* @return void
110+
*/
111+
public function testConfigurablePriceWithCatalogRule(): void
112+
{
113+
$this->assertPrice($this->processPriceView('configurable'), 9.00);
114+
}
115+
116+
/**
117+
* @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable_with_custom_option_type_text.php
118+
*
119+
* @return void
120+
*/
121+
public function testConfigurablePriceWithCustomOption(): void
122+
{
123+
$product = $this->productRepository->get('configurable');
124+
$this->registerProduct($product);
125+
$this->preparePageLayout();
126+
$customOptionsBlock = $this->page->getLayout()
127+
->getChildBlock('product.info.options.wrapper', 'product_options');
128+
$option = $product->getOptions()[0] ?? null;
129+
$this->assertNotNull($option);
130+
$this->assertJsonConfig($customOptionsBlock->getJsonConfig(), '15', (int)$option->getId());
131+
$optionBlock = $customOptionsBlock->getChildBlock($this->productCustomOption->getGroupByType('area'));
132+
$optionPrice = $optionBlock->setProduct($product)->setOption($option)->getFormattedPrice();
133+
$this->assertEquals('+$15.00', preg_replace('/[\n\s]/', '', strip_tags($optionPrice)));
134+
}
135+
136+
/**
137+
* Register the product.
138+
*
139+
* @param ProductInterface $product
140+
* @return void
141+
*/
142+
private function registerProduct(ProductInterface $product): void
143+
{
144+
$this->registry->unregister('product');
145+
$this->registry->register('product', $product);
146+
$this->registry->unregister('current_product');
147+
$this->registry->register('current_product', $product);
148+
}
149+
150+
/**
151+
* Prepare configurable product page.
152+
*
153+
* @return void
154+
*/
155+
private function preparePageLayout(): void
156+
{
157+
$this->page->addHandle([
158+
'default',
159+
'catalog_product_view',
160+
'catalog_product_view_type_configurable',
161+
]);
162+
$this->page->getLayout()->generateXml();
163+
}
164+
165+
/**
166+
* Process view product final price block html.
167+
*
168+
* @param string $sku
169+
* @return string
170+
*/
171+
private function processPriceView(string $sku): string
172+
{
173+
$product = $this->productRepository->get($sku);
174+
$this->registerProduct($product);
175+
$this->preparePageLayout();
176+
177+
return $this->page->getLayout()->getBlock('product.price.final')->toHtml();
178+
}
179+
180+
/**
181+
* Assert that html contain price label and expected final price amount.
182+
*
183+
* @param string $priceBlockHtml
184+
* @param float $expectedPrice
185+
* @return void
186+
*/
187+
private function assertPrice(string $priceBlockHtml, float $expectedPrice): void
188+
{
189+
$regexp = '/<span class="price-label">As low as<\/span>.*';
190+
$regexp .= '<span.*data-price-amount="%s".*<span class="price">\$%.2f<\/span><\/span>/';
191+
$this->assertRegExp(
192+
sprintf($regexp, round($expectedPrice, 2), $expectedPrice),
193+
preg_replace('/[\n\r]/', '', $priceBlockHtml)
194+
);
195+
}
196+
197+
/**
198+
* Assert custom option price json config.
199+
*
200+
* @param string $config
201+
* @param string $expectedPrice
202+
* @param int $optionId
203+
* @return void
204+
*/
205+
private function assertJsonConfig(string $config, string $expectedPrice, int $optionId): void
206+
{
207+
$price = $this->json->unserialize($config)[$optionId]['prices']['finalPrice']['amount'] ?? null;
208+
$this->assertNotNull($price);
209+
$this->assertEquals($expectedPrice, $price);
210+
}
211+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
use Magento\Catalog\Api\Data\ProductCustomOptionInterface;
9+
use Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory;
10+
use Magento\Framework\ObjectManagerInterface;
11+
use Magento\TestFramework\Helper\Bootstrap;
12+
13+
require __DIR__ . '/product_configurable.php';
14+
15+
/** @var ObjectManagerInterface $objectManager */
16+
$objectManager = Bootstrap::getObjectManager();
17+
/** @var ProductCustomOptionInterfaceFactory $optionRepository */
18+
$optionRepository = $objectManager->get(ProductCustomOptionInterfaceFactory::class);
19+
20+
$createdOption = $optionRepository->create([
21+
'data' => [
22+
'is_require' => 0,
23+
'sku' => 'option-1',
24+
'title' => 'Option 1',
25+
'type' => ProductCustomOptionInterface::OPTION_TYPE_AREA,
26+
'price' => 15,
27+
'price_type' => 'fixed',
28+
]
29+
]);
30+
$createdOption->setProductSku($product->getSku());
31+
$product->setOptions([$createdOption]);
32+
$productRepository->save($product);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
require __DIR__ . '/product_configurable_rollback.php';

0 commit comments

Comments
 (0)