Skip to content

Commit 3933f65

Browse files
committed
Merge remote-tracking branch 'origin/MC-31023' into 2.4-develop-com-pr6
2 parents 7ab4f44 + c80d0e4 commit 3933f65

8 files changed

+642
-0
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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\TestFramework\Store;
9+
10+
use Magento\Store\Api\Data\StoreInterface;
11+
use Magento\Store\Model\StoreManagerInterface;
12+
13+
/**
14+
* Execute operation in specified store
15+
*/
16+
class ExecuteInStoreContext
17+
{
18+
/** @var StoreManagerInterface */
19+
private $storeManager;
20+
21+
/**
22+
* @param StoreManagerInterface $storeManager
23+
*/
24+
public function __construct(StoreManagerInterface $storeManager)
25+
{
26+
$this->storeManager = $storeManager;
27+
}
28+
29+
/**
30+
* Execute callback in store context
31+
*
32+
* @param null|string|bool|int|StoreInterface $store
33+
* @param callable $method
34+
* @param array $arguments
35+
* @return mixed
36+
*/
37+
public function execute($store, callable $method, ...$arguments)
38+
{
39+
$storeCode = $store instanceof StoreInterface
40+
? $store->getCode()
41+
: $this->storeManager->getStore($store)->getCode();
42+
$currentStore = $this->storeManager->getStore();
43+
44+
try {
45+
if ($currentStore->getCode() !== $storeCode) {
46+
$this->storeManager->setCurrentStore($storeCode);
47+
}
48+
49+
return $method(...array_values($arguments));
50+
} finally {
51+
if ($currentStore->getCode() !== $storeCode) {
52+
$this->storeManager->setCurrentStore($currentStore);
53+
}
54+
}
55+
}
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
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\ProductInterface;
11+
use Magento\Catalog\Api\ProductRepositoryInterface;
12+
use Magento\Catalog\Model\Product\Attribute\Source\Status;
13+
use Magento\Catalog\Model\ResourceModel\Product as ProductResource;
14+
use Magento\Framework\ObjectManagerInterface;
15+
use Magento\Framework\Serialize\SerializerInterface;
16+
use Magento\Framework\View\LayoutInterface;
17+
use Magento\Store\Model\StoreManagerInterface;
18+
use Magento\TestFramework\Helper\Bootstrap;
19+
use Magento\TestFramework\Store\ExecuteInStoreContext;
20+
use PHPUnit\Framework\TestCase;
21+
22+
/**
23+
* Class check configurable product options displaying per stores
24+
*
25+
* @magentoDbIsolation disabled
26+
*/
27+
class MultiStoreConfigurableViewOnProductPageTest extends TestCase
28+
{
29+
/** @var ObjectManagerInterface */
30+
private $objectManager;
31+
32+
/** @var ProductRepositoryInterface */
33+
private $productRepository;
34+
35+
/** @var StoreManagerInterface */
36+
private $storeManager;
37+
38+
/** @var LayoutInterface */
39+
private $layout;
40+
41+
/** @var SerializerInterface */
42+
private $serializer;
43+
44+
/** @var ProductResource */
45+
private $productResource;
46+
47+
/** @var ExecuteInStoreContext */
48+
private $executeInStoreContext;
49+
50+
/**
51+
* @inheritdoc
52+
*/
53+
protected function setUp()
54+
{
55+
parent::setUp();
56+
57+
$this->objectManager = Bootstrap::getObjectManager();
58+
$this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class);
59+
$this->productRepository->cleanCache();
60+
$this->storeManager = $this->objectManager->get(StoreManagerInterface::class);
61+
$this->layout = $this->objectManager->get(LayoutInterface::class);
62+
$this->serializer = $this->objectManager->get(SerializerInterface::class);
63+
$this->productResource = $this->objectManager->get(ProductResource::class);
64+
$this->executeInStoreContext = $this->objectManager->get(ExecuteInStoreContext::class);
65+
}
66+
67+
/**
68+
* @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_different_option_labeles_per_stores.php
69+
*
70+
* @dataProvider expectedLabelsDataProvider
71+
*
72+
* @param array $expectedStoreData
73+
* @param array $expectedSecondStoreData
74+
* @return void
75+
*/
76+
public function testMultiStoreLabelView(array $expectedStoreData, array $expectedSecondStoreData): void
77+
{
78+
$this->executeInStoreContext->execute('default', [$this, 'assertProductLabel'], $expectedStoreData);
79+
$this->executeInStoreContext->execute('fixturestore', [$this, 'assertProductLabel'], $expectedSecondStoreData);
80+
}
81+
82+
/**
83+
* @return array
84+
*/
85+
public function expectedLabelsDataProvider(): array
86+
{
87+
return [
88+
[
89+
'options_first_store' => [
90+
'simple_option_1_default_store' => [
91+
'label' => 'Option 1 Default Store',
92+
],
93+
'simple_option_2_default_store' => [
94+
'label' => 'Option 2 Default Store',
95+
],
96+
'simple_option_3_default_store' => [
97+
'label' => 'Option 3 Default Store',
98+
],
99+
],
100+
'options_second_store' => [
101+
'simple_option_1_default_store' => [
102+
'label' => 'Option 1 Second Store',
103+
],
104+
'simple_option_2_default_store' => [
105+
'label' => 'Option 2 Second Store',
106+
],
107+
'simple_option_3_default_store' => [
108+
'label' => 'Option 3 Second Store',
109+
],
110+
],
111+
],
112+
];
113+
}
114+
115+
/**
116+
* Assert configurable product labels config
117+
*
118+
* @param $expectedStoreData
119+
* @return void
120+
*/
121+
public function assertProductLabel($expectedStoreData): void
122+
{
123+
$product = $this->productRepository->get('configurable', false, null, true);
124+
$config = $this->getBlockConfig($product)['attributes'] ?? null;
125+
$this->assertNotNull($config);
126+
$this->assertAttributeConfig($expectedStoreData, reset($config));
127+
}
128+
129+
/**
130+
* @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_two_websites.php
131+
*
132+
* @dataProvider expectedProductDataProvider
133+
*
134+
* @param array $expectedProducts
135+
* @param array $expectedSecondStoreProducts
136+
* @return void
137+
*/
138+
public function testMultiStoreOptionsView(array $expectedProducts, array $expectedSecondStoreProducts): void
139+
{
140+
$this->prepareConfigurableProduct('configurable', 'fixture_second_store');
141+
$this->executeInStoreContext->execute('default', [$this, 'assertProductConfig'], $expectedProducts);
142+
$this->executeInStoreContext->execute(
143+
'fixture_second_store',
144+
[$this, 'assertProductConfig'],
145+
$expectedSecondStoreProducts
146+
);
147+
}
148+
149+
/**
150+
* @return array
151+
*/
152+
public function expectedProductDataProvider(): array
153+
{
154+
return [
155+
[
156+
'expected_store_products' => ['simple_option_1', 'simple_option_2'],
157+
'expected_second_store_products' => ['simple_option_2'],
158+
],
159+
];
160+
}
161+
162+
/**
163+
* Assert configurable product config
164+
*
165+
* @param $expectedProducts
166+
* @return void
167+
*/
168+
public function assertProductConfig($expectedProducts): void
169+
{
170+
$product = $this->productRepository->get('configurable', false, null, true);
171+
$config = $this->getBlockConfig($product)['index'] ?? null;
172+
$this->assertNotNull($config);
173+
$this->assertProducts($expectedProducts, $config);
174+
}
175+
176+
/**
177+
* Prepare configurable product to test
178+
*
179+
* @param string $sku
180+
* @param string $storeCode
181+
* @return void
182+
*/
183+
private function prepareConfigurableProduct(string $sku, string $storeCode): void
184+
{
185+
$product = $this->productRepository->get($sku, false, null, true);
186+
$productToUpdate = $product->getTypeInstance()->getUsedProductCollection($product)
187+
->setPageSize(1)->getFirstItem();
188+
$this->assertNotEmpty($productToUpdate->getData(), 'Configurable product does not have a child');
189+
$this->executeInStoreContext->execute($storeCode, [$this, 'setProductDisabled'], $productToUpdate);
190+
}
191+
192+
/**
193+
* Assert product options display per stores
194+
*
195+
* @param array $expectedProducts
196+
* @param array $config
197+
* @return void
198+
*/
199+
private function assertProducts(array $expectedProducts, array $config): void
200+
{
201+
$this->assertCount(count($expectedProducts), $config);
202+
$idsBySkus = $this->productResource->getProductsIdsBySkus($expectedProducts);
203+
204+
foreach ($idsBySkus as $productId) {
205+
$this->assertArrayHasKey($productId, $config);
206+
}
207+
}
208+
209+
/**
210+
* Set product status attribute to disabled
211+
*
212+
* @param ProductInterface $product
213+
* @param string $storeCode
214+
* @return void
215+
*/
216+
public function setProductDisabled(ProductInterface $product): void
217+
{
218+
$product->setStatus(Status::STATUS_DISABLED);
219+
$this->productRepository->save($product);
220+
}
221+
222+
/**
223+
* Get block config
224+
*
225+
* @param ProductInterface $product
226+
* @return array
227+
*/
228+
private function getBlockConfig(ProductInterface $product): array
229+
{
230+
$block = $this->layout->createBlock(Configurable::class);
231+
$block->setProduct($product);
232+
233+
return $this->serializer->unserialize($block->getJsonConfig());
234+
}
235+
236+
/**
237+
* Assert configurable product config
238+
*
239+
* @param array $expectedData
240+
* @param array $actualOptions
241+
* @return void
242+
*/
243+
private function assertAttributeConfig(array $expectedData, array $actualOptions): void
244+
{
245+
$skus = array_keys($expectedData);
246+
$idBySkuMap = $this->productResource->getProductsIdsBySkus($skus);
247+
array_walk($actualOptions['options'], function (&$option) {
248+
unset($option['id']);
249+
});
250+
foreach ($expectedData as $sku => &$option) {
251+
$option['products'] = [$idBySkuMap[$sku]];
252+
}
253+
$this->assertEquals(array_values($expectedData), $actualOptions['options']);
254+
}
255+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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\ProductAttributeInterface;
9+
use Magento\Catalog\Api\ProductAttributeRepositoryInterface;
10+
use Magento\Catalog\Model\ResourceModel\Eav\Attribute;
11+
use Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory;
12+
use Magento\Catalog\Setup\CategorySetup;
13+
use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface;
14+
use Magento\Store\Model\Store;
15+
use Magento\TestFramework\Helper\Bootstrap;
16+
17+
require __DIR__ . '/../../Store/_files/core_fixturestore.php';
18+
19+
$objectManager = Bootstrap::getObjectManager();
20+
$defaultInstalledStoreId = $storeManager->getStore('default')->getId();
21+
$secondStoreId = $storeManager->getStore('fixturestore')->getId();
22+
/** @var CategorySetup $installer */
23+
$installer = $objectManager->get(CategorySetup::class);
24+
/** @var Attribute $attribute */
25+
$attribute = $objectManager->get(AttributeFactory::class)->create();
26+
/** @var ProductAttributeRepositoryInterface $attributeRepository */
27+
$attributeRepository = $objectManager->get(ProductAttributeRepositoryInterface::class);
28+
$entityType = $installer->getEntityTypeId(ProductAttributeInterface::ENTITY_TYPE_CODE);
29+
if (!$attribute->loadByCode($entityType, 'different_labels_attribute')->getAttributeId()) {
30+
$attribute->setData(
31+
[
32+
'frontend_label' => ['Different option labels dropdown attribute'],
33+
'entity_type_id' => $entityType,
34+
'frontend_input' => 'select',
35+
'backend_type' => 'int',
36+
'is_required' => '0',
37+
'attribute_code' => 'different_labels_attribute',
38+
'is_global' => ScopedAttributeInterface::SCOPE_GLOBAL,
39+
'is_user_defined' => 1,
40+
'is_unique' => '0',
41+
'is_searchable' => '0',
42+
'is_comparable' => '0',
43+
'is_filterable' => '1',
44+
'is_filterable_in_search' => '0',
45+
'is_used_for_promo_rules' => '0',
46+
'is_html_allowed_on_front' => '1',
47+
'used_in_product_listing' => '1',
48+
'used_for_sort_by' => '0',
49+
'option' => [
50+
'value' => [
51+
'option_1' => [
52+
Store::DEFAULT_STORE_ID => 'Option 1',
53+
$defaultInstalledStoreId => 'Option 1 Default Store',
54+
$secondStoreId => 'Option 1 Second Store',
55+
],
56+
'option_2' => [
57+
Store::DEFAULT_STORE_ID => 'Option 2',
58+
$defaultInstalledStoreId => 'Option 2 Default Store',
59+
$secondStoreId => 'Option 2 Second Store',
60+
],
61+
'option_3' => [
62+
Store::DEFAULT_STORE_ID => 'Option 3',
63+
$defaultInstalledStoreId => 'Option 3 Default Store',
64+
$secondStoreId => 'Option 3 Second Store',
65+
],
66+
],
67+
'order' => [
68+
'option_1' => 1,
69+
'option_2' => 2,
70+
'option_3' => 3,
71+
],
72+
],
73+
]
74+
);
75+
$attributeRepository->save($attribute);
76+
$installer->addAttributeToGroup(
77+
ProductAttributeInterface::ENTITY_TYPE_CODE,
78+
'Default',
79+
'General',
80+
$attribute->getId()
81+
);
82+
}

0 commit comments

Comments
 (0)