Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/MC-31023' into 2.4-develop-com-pr6
Browse files Browse the repository at this point in the history
  • Loading branch information
ysapiga committed Feb 4, 2020
2 parents 7ab4f44 + c80d0e4 commit 3933f65
Show file tree
Hide file tree
Showing 8 changed files with 642 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\TestFramework\Store;

use Magento\Store\Api\Data\StoreInterface;
use Magento\Store\Model\StoreManagerInterface;

/**
* Execute operation in specified store
*/
class ExecuteInStoreContext
{
/** @var StoreManagerInterface */
private $storeManager;

/**
* @param StoreManagerInterface $storeManager
*/
public function __construct(StoreManagerInterface $storeManager)
{
$this->storeManager = $storeManager;
}

/**
* Execute callback in store context
*
* @param null|string|bool|int|StoreInterface $store
* @param callable $method
* @param array $arguments
* @return mixed
*/
public function execute($store, callable $method, ...$arguments)
{
$storeCode = $store instanceof StoreInterface
? $store->getCode()
: $this->storeManager->getStore($store)->getCode();
$currentStore = $this->storeManager->getStore();

try {
if ($currentStore->getCode() !== $storeCode) {
$this->storeManager->setCurrentStore($storeCode);
}

return $method(...array_values($arguments));
} finally {
if ($currentStore->getCode() !== $storeCode) {
$this->storeManager->setCurrentStore($currentStore);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\ConfigurableProduct\Block\Product\View\Type;

use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Model\Product\Attribute\Source\Status;
use Magento\Catalog\Model\ResourceModel\Product as ProductResource;
use Magento\Framework\ObjectManagerInterface;
use Magento\Framework\Serialize\SerializerInterface;
use Magento\Framework\View\LayoutInterface;
use Magento\Store\Model\StoreManagerInterface;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\TestFramework\Store\ExecuteInStoreContext;
use PHPUnit\Framework\TestCase;

/**
* Class check configurable product options displaying per stores
*
* @magentoDbIsolation disabled
*/
class MultiStoreConfigurableViewOnProductPageTest extends TestCase
{
/** @var ObjectManagerInterface */
private $objectManager;

/** @var ProductRepositoryInterface */
private $productRepository;

/** @var StoreManagerInterface */
private $storeManager;

/** @var LayoutInterface */
private $layout;

/** @var SerializerInterface */
private $serializer;

/** @var ProductResource */
private $productResource;

/** @var ExecuteInStoreContext */
private $executeInStoreContext;

/**
* @inheritdoc
*/
protected function setUp()
{
parent::setUp();

$this->objectManager = Bootstrap::getObjectManager();
$this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class);
$this->productRepository->cleanCache();
$this->storeManager = $this->objectManager->get(StoreManagerInterface::class);
$this->layout = $this->objectManager->get(LayoutInterface::class);
$this->serializer = $this->objectManager->get(SerializerInterface::class);
$this->productResource = $this->objectManager->get(ProductResource::class);
$this->executeInStoreContext = $this->objectManager->get(ExecuteInStoreContext::class);
}

/**
* @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_different_option_labeles_per_stores.php
*
* @dataProvider expectedLabelsDataProvider
*
* @param array $expectedStoreData
* @param array $expectedSecondStoreData
* @return void
*/
public function testMultiStoreLabelView(array $expectedStoreData, array $expectedSecondStoreData): void
{
$this->executeInStoreContext->execute('default', [$this, 'assertProductLabel'], $expectedStoreData);
$this->executeInStoreContext->execute('fixturestore', [$this, 'assertProductLabel'], $expectedSecondStoreData);
}

/**
* @return array
*/
public function expectedLabelsDataProvider(): array
{
return [
[
'options_first_store' => [
'simple_option_1_default_store' => [
'label' => 'Option 1 Default Store',
],
'simple_option_2_default_store' => [
'label' => 'Option 2 Default Store',
],
'simple_option_3_default_store' => [
'label' => 'Option 3 Default Store',
],
],
'options_second_store' => [
'simple_option_1_default_store' => [
'label' => 'Option 1 Second Store',
],
'simple_option_2_default_store' => [
'label' => 'Option 2 Second Store',
],
'simple_option_3_default_store' => [
'label' => 'Option 3 Second Store',
],
],
],
];
}

/**
* Assert configurable product labels config
*
* @param $expectedStoreData
* @return void
*/
public function assertProductLabel($expectedStoreData): void
{
$product = $this->productRepository->get('configurable', false, null, true);
$config = $this->getBlockConfig($product)['attributes'] ?? null;
$this->assertNotNull($config);
$this->assertAttributeConfig($expectedStoreData, reset($config));
}

/**
* @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_two_websites.php
*
* @dataProvider expectedProductDataProvider
*
* @param array $expectedProducts
* @param array $expectedSecondStoreProducts
* @return void
*/
public function testMultiStoreOptionsView(array $expectedProducts, array $expectedSecondStoreProducts): void
{
$this->prepareConfigurableProduct('configurable', 'fixture_second_store');
$this->executeInStoreContext->execute('default', [$this, 'assertProductConfig'], $expectedProducts);
$this->executeInStoreContext->execute(
'fixture_second_store',
[$this, 'assertProductConfig'],
$expectedSecondStoreProducts
);
}

/**
* @return array
*/
public function expectedProductDataProvider(): array
{
return [
[
'expected_store_products' => ['simple_option_1', 'simple_option_2'],
'expected_second_store_products' => ['simple_option_2'],
],
];
}

/**
* Assert configurable product config
*
* @param $expectedProducts
* @return void
*/
public function assertProductConfig($expectedProducts): void
{
$product = $this->productRepository->get('configurable', false, null, true);
$config = $this->getBlockConfig($product)['index'] ?? null;
$this->assertNotNull($config);
$this->assertProducts($expectedProducts, $config);
}

/**
* Prepare configurable product to test
*
* @param string $sku
* @param string $storeCode
* @return void
*/
private function prepareConfigurableProduct(string $sku, string $storeCode): void
{
$product = $this->productRepository->get($sku, false, null, true);
$productToUpdate = $product->getTypeInstance()->getUsedProductCollection($product)
->setPageSize(1)->getFirstItem();
$this->assertNotEmpty($productToUpdate->getData(), 'Configurable product does not have a child');
$this->executeInStoreContext->execute($storeCode, [$this, 'setProductDisabled'], $productToUpdate);
}

/**
* Assert product options display per stores
*
* @param array $expectedProducts
* @param array $config
* @return void
*/
private function assertProducts(array $expectedProducts, array $config): void
{
$this->assertCount(count($expectedProducts), $config);
$idsBySkus = $this->productResource->getProductsIdsBySkus($expectedProducts);

foreach ($idsBySkus as $productId) {
$this->assertArrayHasKey($productId, $config);
}
}

/**
* Set product status attribute to disabled
*
* @param ProductInterface $product
* @param string $storeCode
* @return void
*/
public function setProductDisabled(ProductInterface $product): void
{
$product->setStatus(Status::STATUS_DISABLED);
$this->productRepository->save($product);
}

/**
* Get block config
*
* @param ProductInterface $product
* @return array
*/
private function getBlockConfig(ProductInterface $product): array
{
$block = $this->layout->createBlock(Configurable::class);
$block->setProduct($product);

return $this->serializer->unserialize($block->getJsonConfig());
}

/**
* Assert configurable product config
*
* @param array $expectedData
* @param array $actualOptions
* @return void
*/
private function assertAttributeConfig(array $expectedData, array $actualOptions): void
{
$skus = array_keys($expectedData);
$idBySkuMap = $this->productResource->getProductsIdsBySkus($skus);
array_walk($actualOptions['options'], function (&$option) {
unset($option['id']);
});
foreach ($expectedData as $sku => &$option) {
$option['products'] = [$idBySkuMap[$sku]];
}
$this->assertEquals(array_values($expectedData), $actualOptions['options']);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

use Magento\Catalog\Api\Data\ProductAttributeInterface;
use Magento\Catalog\Api\ProductAttributeRepositoryInterface;
use Magento\Catalog\Model\ResourceModel\Eav\Attribute;
use Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory;
use Magento\Catalog\Setup\CategorySetup;
use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface;
use Magento\Store\Model\Store;
use Magento\TestFramework\Helper\Bootstrap;

require __DIR__ . '/../../Store/_files/core_fixturestore.php';

$objectManager = Bootstrap::getObjectManager();
$defaultInstalledStoreId = $storeManager->getStore('default')->getId();
$secondStoreId = $storeManager->getStore('fixturestore')->getId();
/** @var CategorySetup $installer */
$installer = $objectManager->get(CategorySetup::class);
/** @var Attribute $attribute */
$attribute = $objectManager->get(AttributeFactory::class)->create();
/** @var ProductAttributeRepositoryInterface $attributeRepository */
$attributeRepository = $objectManager->get(ProductAttributeRepositoryInterface::class);
$entityType = $installer->getEntityTypeId(ProductAttributeInterface::ENTITY_TYPE_CODE);
if (!$attribute->loadByCode($entityType, 'different_labels_attribute')->getAttributeId()) {
$attribute->setData(
[
'frontend_label' => ['Different option labels dropdown attribute'],
'entity_type_id' => $entityType,
'frontend_input' => 'select',
'backend_type' => 'int',
'is_required' => '0',
'attribute_code' => 'different_labels_attribute',
'is_global' => ScopedAttributeInterface::SCOPE_GLOBAL,
'is_user_defined' => 1,
'is_unique' => '0',
'is_searchable' => '0',
'is_comparable' => '0',
'is_filterable' => '1',
'is_filterable_in_search' => '0',
'is_used_for_promo_rules' => '0',
'is_html_allowed_on_front' => '1',
'used_in_product_listing' => '1',
'used_for_sort_by' => '0',
'option' => [
'value' => [
'option_1' => [
Store::DEFAULT_STORE_ID => 'Option 1',
$defaultInstalledStoreId => 'Option 1 Default Store',
$secondStoreId => 'Option 1 Second Store',
],
'option_2' => [
Store::DEFAULT_STORE_ID => 'Option 2',
$defaultInstalledStoreId => 'Option 2 Default Store',
$secondStoreId => 'Option 2 Second Store',
],
'option_3' => [
Store::DEFAULT_STORE_ID => 'Option 3',
$defaultInstalledStoreId => 'Option 3 Default Store',
$secondStoreId => 'Option 3 Second Store',
],
],
'order' => [
'option_1' => 1,
'option_2' => 2,
'option_3' => 3,
],
],
]
);
$attributeRepository->save($attribute);
$installer->addAttributeToGroup(
ProductAttributeInterface::ENTITY_TYPE_CODE,
'Default',
'General',
$attribute->getId()
);
}
Loading

0 comments on commit 3933f65

Please sign in to comment.