diff --git a/.htaccess b/.htaccess index 71a5cf708dbc5..e07a564bc0ab6 100644 --- a/.htaccess +++ b/.htaccess @@ -37,29 +37,6 @@ DirectoryIndex index.php - -############################################ -## adjust memory limit - - php_value memory_limit 756M - php_value max_execution_time 18000 - -############################################ -## disable automatic session start -## before autoload was initialized - - php_flag session.auto_start off - -############################################ -## enable resulting html compression - - #php_flag zlib.output_compression on - -########################################### -## disable user agent verification to not break multiple image upload - - php_flag suhosin.session.cryptua off - ############################################ ## adjust memory limit diff --git a/app/code/Magento/Catalog/Model/Product/ProductFrontendAction/Synchronizer.php b/app/code/Magento/Catalog/Model/Product/ProductFrontendAction/Synchronizer.php index ec2e697ccc849..b736700a4481d 100644 --- a/app/code/Magento/Catalog/Model/Product/ProductFrontendAction/Synchronizer.php +++ b/app/code/Magento/Catalog/Model/Product/ProductFrontendAction/Synchronizer.php @@ -20,6 +20,8 @@ * * Service which allows to sync product widget information, such as product id with db. In order to reuse this info * on different devices + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class Synchronizer { @@ -94,6 +96,7 @@ public function __construct( * * @param string $namespace * @return int + * @throws \Magento\Framework\Exception\LocalizedException */ private function getLifeTimeByNamespace($namespace) { @@ -119,6 +122,7 @@ private function getLifeTimeByNamespace($namespace) * @param array $productsData (product action data, that came from frontend) * @param string $typeId namespace (type of action) * @return array + * @throws \Magento\Framework\Exception\LocalizedException */ private function filterNewestActions(array $productsData, $typeId) { @@ -166,6 +170,7 @@ private function getProductIdsByActions(array $actions) * @param array $productsData * @param string $typeId * @return void + * @throws \Exception */ public function syncActions(array $productsData, $typeId) { @@ -189,8 +194,7 @@ public function syncActions(array $productsData, $typeId) foreach ($collection as $item) { $this->entityManager->delete($item); } - - foreach ($productsData as $productId => $productData) { + foreach ($productsData as $productData) { /** @var ProductFrontendActionInterface $action */ $action = $this->productFrontendActionFactory->create( [ @@ -198,7 +202,7 @@ public function syncActions(array $productsData, $typeId) 'visitor_id' => $customerId ? null : $visitorId, 'customer_id' => $this->session->getCustomerId(), 'added_at' => $productData['added_at'], - 'product_id' => $productId, + 'product_id' => $productData['product_id'], 'type_id' => $typeId ] ] diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index 78cdac8e8a8a0..9a455c12e6055 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -1911,6 +1911,7 @@ protected function _productLimitationJoinPrice() * @param bool $joinLeft * @return $this * @see \Magento\Catalog\Model\ResourceModel\Product\Collection::_productLimitationJoinPrice() + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function _productLimitationPrice($joinLeft = false) { @@ -1929,14 +1930,14 @@ protected function _productLimitationPrice($joinLeft = false) $connection = $this->getConnection(); $select = $this->getSelect(); - $joinCond = join( - ' AND ', - [ - 'price_index.entity_id = e.entity_id', - $connection->quoteInto('price_index.website_id = ?', $filters['website_id']), - $connection->quoteInto('price_index.customer_group_id = ?', $filters['customer_group_id']) - ] - ); + $joinCondArray = []; + $joinCondArray[] = 'price_index.entity_id = e.entity_id'; + $joinCondArray[] = $connection->quoteInto('price_index.customer_group_id = ?', $filters['customer_group_id']); + // Add website condition only if it's different from admin scope + if (((int) $filters['website_id']) !== Store::DEFAULT_STORE_ID) { + $joinCondArray[] = $connection->quoteInto('price_index.website_id = ?', $filters['website_id']); + } + $joinCond = join(' AND ', $joinCondArray); $fromPart = $select->getPart(\Magento\Framework\DB\Select::FROM); if (!isset($fromPart['price_index'])) { diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/ProductFrontendAction/SynchronizerTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/ProductFrontendAction/SynchronizerTest.php index 38bed83cb9504..8b70899cd26d3 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/ProductFrontendAction/SynchronizerTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/ProductFrontendAction/SynchronizerTest.php @@ -82,15 +82,15 @@ public function testFilterProductActions() { $typeId = 'recently_compared_product'; $productsData = [ - 1 => [ + 'website-1-1' => [ 'added_at' => 12, 'product_id' => 1, ], - 2 => [ + 'website-1-2' => [ 'added_at' => 13, 'product_id' => '2', ], - 3 => [ + 'website-2-3' => [ 'added_at' => 14, 'product_id' => 3, ] diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml index 5a31f3d125c81..24cae93ca61c0 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml @@ -4,11 +4,15 @@ * See COPYING.txt for license details. */ ?> - + -escapeHtml($block->getCustomAttributes()) ?> - src="escapeUrl($block->getImageUrl()) ?>" - width="escapeHtmlAttr($block->getWidth()) ?>" - height="escapeHtmlAttr($block->getHeight()) ?>" - alt="stripTags($block->getLabel(), null, true) ?>" /> +escapeHtml($block->getCustomAttributes()) ?> + src="escapeUrl($block->getImageUrl()) ?>" + loading="lazy" + width="escapeHtmlAttr($block->getWidth()) ?>" + height="escapeHtmlAttr($block->getHeight()) ?>" + alt="escapeHtmlAttr($block->getLabel()) ?>" /> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml index 33f7620f1a1f5..e8ddabce504ea 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml @@ -4,16 +4,20 @@ * See COPYING.txt for license details. */ ?> - + + style="width:escapeHtmlAttr($block->getWidth()) ?>px;"> - escapeHtmlAttr($block->getCustomAttributes()) ?> - src="escapeUrl($block->getImageUrl()) ?>" - max-width="escapeHtmlAttr($block->getWidth()) ?>" - max-height="escapeHtmlAttr($block->getHeight()) ?>" - alt="stripTags($block->getLabel(), null, true) ?>"/> + escapeHtmlAttr($block->getCustomAttributes()) ?> + src="escapeUrl($block->getImageUrl()) ?>" + loading="lazy" + width="escapeHtmlAttr($block->getWidth()) ?>" + height="escapeHtmlAttr($block->getHeight()) ?>" + alt="escapeHtmlAttr($block->getLabel()) ?>"/> diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index ae5f0f5d79e2a..d1bce7aaaf951 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -307,6 +307,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity // Can't add new translated strings in patch release 'invalidLayoutUpdate' => 'Invalid format.', 'insufficientPermissions' => 'Invalid format.', + ValidatorInterface::ERROR_SKU_MARGINAL_WHITESPACES => 'SKU contains marginal whitespaces' ]; //@codingStandardsIgnoreEnd diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php index f41596ad185a6..f13b603003898 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php @@ -87,6 +87,8 @@ interface RowValidatorInterface extends \Magento\Framework\Validator\ValidatorIn const ERROR_DUPLICATE_MULTISELECT_VALUES = 'duplicatedMultiselectValues'; + const ERROR_SKU_MARGINAL_WHITESPACES = 'skuMarginalWhitespaces'; + /** * Value that means all entities (e.g. websites, groups etc.) */ diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php index 4b7416f6ad9a6..b2eca68db4d1c 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php @@ -10,7 +10,7 @@ use Magento\Catalog\Model\Product\Attribute\Backend\Sku; /** - * Class Validator + * Product import model validator * * @api * @since 100.0.2 @@ -72,8 +72,12 @@ protected function textValidation($attrCode, $type) $val = $this->string->cleanString($this->_rowData[$attrCode]); if ($type == 'text') { $valid = $this->string->strlen($val) < Product::DB_MAX_TEXT_LENGTH; - } else if ($attrCode == Product::COL_SKU) { + } elseif ($attrCode == Product::COL_SKU) { $valid = $this->string->strlen($val) <= SKU::SKU_MAX_LENGTH; + if ($this->string->strlen($val) !== $this->string->strlen(trim($val))) { + $this->_addMessages([RowValidatorInterface::ERROR_SKU_MARGINAL_WHITESPACES]); + return false; + } } else { $valid = $this->string->strlen($val) < Product::DB_MAX_VARCHAR_LENGTH; } @@ -359,5 +363,7 @@ public function init($context) foreach ($this->validators as $validator) { $validator->init($context); } + + return $this; } } diff --git a/app/code/Magento/CatalogWidget/Model/Rule/Condition/Product.php b/app/code/Magento/CatalogWidget/Model/Rule/Condition/Product.php index eca994de0892f..f9340a495de65 100644 --- a/app/code/Magento/CatalogWidget/Model/Rule/Condition/Product.php +++ b/app/code/Magento/CatalogWidget/Model/Rule/Condition/Product.php @@ -11,6 +11,7 @@ use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\ProductCategoryList; +use Magento\Catalog\Model\ResourceModel\Product\Collection; use Magento\Store\Model\Store; /** @@ -122,46 +123,34 @@ protected function _addSpecialAttributes(array &$attributes) /** * Add condition to collection * - * @param \Magento\Catalog\Model\ResourceModel\Product\Collection $collection + * @param Collection $collection * @return $this */ public function addToCollection($collection) { $attribute = $this->getAttributeObject(); + $attributeCode = $attribute->getAttributeCode(); + if ($attributeCode !== 'price' || !$collection->getLimitationFilters()->isUsingPriceIndex()) { + if ($collection->isEnabledFlat()) { + if ($attribute->isEnabledInFlat()) { + $alias = array_keys($collection->getSelect()->getPart('from'))[0]; + $this->joinedAttributes[$attributeCode] = $alias . '.' . $attributeCode; + } else { + $alias = 'at_' . $attributeCode; + if (!in_array($alias, array_keys($collection->getSelect()->getPart('from')))) { + $collection->joinAttribute($attributeCode, "catalog_product/$attributeCode", 'entity_id'); + } - if ($collection->isEnabledFlat()) { - if ($attribute->isEnabledInFlat()) { - $alias = array_keys($collection->getSelect()->getPart('from'))[0]; - $this->joinedAttributes[$attribute->getAttributeCode()] = $alias . '.' . $attribute->getAttributeCode(); - } else { - $alias = 'at_' . $attribute->getAttributeCode(); - if (!in_array($alias, array_keys($collection->getSelect()->getPart('from')))) { - $collection->joinAttribute( - $attribute->getAttributeCode(), - 'catalog_product/'.$attribute->getAttributeCode(), - 'entity_id' - ); + $this->joinedAttributes[$attributeCode] = $alias . '.value'; } - - $this->joinedAttributes[$attribute->getAttributeCode()] = $alias . '.value'; + } elseif ($attributeCode !== 'category_ids' && !$attribute->isStatic()) { + $this->addAttributeToCollection($attribute, $collection); + $attributes = $this->getRule()->getCollectedAttributes(); + $attributes[$attributeCode] = true; + $this->getRule()->setCollectedAttributes($attributes); } - return $this; } - if ('category_ids' == $attribute->getAttributeCode() || $attribute->isStatic()) { - return $this; - } - - if ($attribute->getBackend() && $attribute->isScopeGlobal()) { - $this->addGlobalAttribute($attribute, $collection); - } else { - $this->addNotGlobalAttribute($attribute, $collection); - } - - $attributes = $this->getRule()->getCollectedAttributes(); - $attributes[$attribute->getAttributeCode()] = true; - $this->getRule()->setCollectedAttributes($attributes); - return $this; } @@ -169,12 +158,12 @@ public function addToCollection($collection) * Adds Attributes that belong to Global Scope * * @param \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute - * @param \Magento\Catalog\Model\ResourceModel\Product\Collection $collection + * @param Collection $collection * @return $this */ protected function addGlobalAttribute( \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute, - \Magento\Catalog\Model\ResourceModel\Product\Collection $collection + Collection $collection ) { switch ($attribute->getBackendType()) { case 'decimal': @@ -207,12 +196,12 @@ protected function addGlobalAttribute( * Adds Attributes that don't belong to Global Scope * * @param \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute - * @param \Magento\Catalog\Model\ResourceModel\Product\Collection $collection + * @param Collection $collection * @return $this */ protected function addNotGlobalAttribute( \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute, - \Magento\Catalog\Model\ResourceModel\Product\Collection $collection + Collection $collection ) { $storeId = $this->storeManager->getStore()->getId(); $values = $collection->getAllAttributeValues($attribute); @@ -255,6 +244,8 @@ public function getMappedSqlField() $result = parent::getMappedSqlField(); } elseif (isset($this->joinedAttributes[$this->getAttribute()])) { $result = $this->joinedAttributes[$this->getAttribute()]; + } elseif ($this->getAttribute() === 'price') { + $result = 'price_index.min_price'; } elseif ($this->getAttributeObject()->isStatic()) { $result = $this->getAttributeObject()->getAttributeCode(); } elseif ($this->getValueParsed()) { @@ -267,11 +258,27 @@ public function getMappedSqlField() /** * @inheritdoc * - * @param \Magento\Catalog\Model\ResourceModel\Product\Collection $productCollection + * @param Collection $productCollection * @return $this */ public function collectValidatedAttributes($productCollection) { return $this->addToCollection($productCollection); } + + /** + * Add attribute to collection based on scope + * + * @param \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute + * @param Collection $collection + * @return void + */ + private function addAttributeToCollection($attribute, $collection): void + { + if ($attribute->getBackend() && $attribute->isScopeGlobal()) { + $this->addGlobalAttribute($attribute, $collection); + } else { + $this->addNotGlobalAttribute($attribute, $collection); + } + } } diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Directive.php b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Directive.php index 97d0b35a2354f..a3370b2666264 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Directive.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Directive.php @@ -21,6 +21,7 @@ use Magento\Framework\Controller\Result\RawFactory; use Magento\Backend\App\Action\Context; use Magento\Framework\App\ObjectManager; +use Magento\Framework\Filesystem\Driver\File; /** * Process template text for wysiwyg editor. @@ -67,6 +68,11 @@ class Directive extends Action implements HttpGetActionInterface */ private $filter; + /** + * @var File + */ + private $file; + /** * Constructor * @@ -77,6 +83,7 @@ class Directive extends Action implements HttpGetActionInterface * @param LoggerInterface|null $logger * @param Config|null $config * @param Filter|null $filter + * @param File|null $file */ public function __construct( Context $context, @@ -85,7 +92,8 @@ public function __construct( AdapterFactory $adapterFactory = null, LoggerInterface $logger = null, Config $config = null, - Filter $filter = null + Filter $filter = null, + File $file = null ) { parent::__construct($context); $this->urlDecoder = $urlDecoder; @@ -94,6 +102,7 @@ public function __construct( $this->logger = $logger ?: ObjectManager::getInstance()->get(LoggerInterface::class); $this->config = $config ?: ObjectManager::getInstance()->get(Config::class); $this->filter = $filter ?: ObjectManager::getInstance()->get(Filter::class); + $this->file = $file ?: ObjectManager::getInstance()->get(File::class); } /** @@ -127,6 +136,15 @@ public function execute() $this->logger->warning($e); } } + $mimeType = $image->getMimeType(); + unset($image); + // To avoid issues with PNG images with alpha blending we return raw file + // after validation as an image source instead of generating the new PNG image + // with image adapter + $content = $this->file->fileGetContents($imagePath); + $resultRaw->setHeader('Content-Type', $mimeType); + $resultRaw->setContents($content); + return $resultRaw; } } diff --git a/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Wysiwyg/DirectiveTest.php b/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Wysiwyg/DirectiveTest.php index 5fea276225622..be9f6b8d8ccd5 100644 --- a/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Wysiwyg/DirectiveTest.php +++ b/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Wysiwyg/DirectiveTest.php @@ -5,100 +5,123 @@ */ namespace Magento\Cms\Test\Unit\Controller\Adminhtml\Wysiwyg; +use Magento\Backend\App\Action\Context; +use Magento\Cms\Controller\Adminhtml\Wysiwyg\Directive; +use Magento\Cms\Model\Template\Filter; +use Magento\Cms\Model\Wysiwyg\Config; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\App\ResponseInterface; +use Magento\Framework\Controller\Result\Raw; +use Magento\Framework\Controller\Result\RawFactory; +use Magento\Framework\Filesystem\Driver\File; +use Magento\Framework\Image\Adapter\AdapterInterface; +use Magento\Framework\Image\AdapterFactory; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\Url\DecoderInterface; +use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject; +use Psr\Log\LoggerInterface; + /** * @covers \Magento\Cms\Controller\Adminhtml\Wysiwyg\Directive * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class DirectiveTest extends \PHPUnit\Framework\TestCase +class DirectiveTest extends TestCase { const IMAGE_PATH = 'pub/media/wysiwyg/image.jpg'; /** - * @var \Magento\Cms\Controller\Adminhtml\Wysiwyg\Directive + * @var Directive */ protected $wysiwygDirective; /** - * @var \Magento\Backend\App\Action\Context|\PHPUnit_Framework_MockObject_MockObject + * @var Context|PHPUnit_Framework_MockObject_MockObject */ protected $actionContextMock; /** - * @var \Magento\Framework\App\RequestInterface|\PHPUnit_Framework_MockObject_MockObject + * @var RequestInterface|PHPUnit_Framework_MockObject_MockObject */ protected $requestMock; /** - * @var \Magento\Framework\Url\DecoderInterface|\PHPUnit_Framework_MockObject_MockObject + * @var DecoderInterface|PHPUnit_Framework_MockObject_MockObject */ protected $urlDecoderMock; /** - * @var \Magento\Framework\ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ObjectManagerInterface|PHPUnit_Framework_MockObject_MockObject */ protected $objectManagerMock; /** - * @var \Magento\Cms\Model\Template\Filter|\PHPUnit_Framework_MockObject_MockObject + * @var Filter|PHPUnit_Framework_MockObject_MockObject */ protected $templateFilterMock; /** - * @var \Magento\Framework\Image\AdapterFactory|\PHPUnit_Framework_MockObject_MockObject + * @var AdapterFactory|PHPUnit_Framework_MockObject_MockObject */ protected $imageAdapterFactoryMock; /** - * @var \Magento\Framework\Image\Adapter\AdapterInterface|\PHPUnit_Framework_MockObject_MockObject + * @var AdapterInterface|PHPUnit_Framework_MockObject_MockObject */ protected $imageAdapterMock; /** - * @var \Magento\Framework\App\ResponseInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ResponseInterface|PHPUnit_Framework_MockObject_MockObject */ protected $responseMock; /** - * @var \Magento\Cms\Model\Wysiwyg\Config|\PHPUnit_Framework_MockObject_MockObject + * @var File|PHPUnit_Framework_MockObject_MockObject + */ + protected $fileMock; + + /** + * @var Config|PHPUnit_Framework_MockObject_MockObject */ protected $wysiwygConfigMock; /** - * @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var LoggerInterface|PHPUnit_Framework_MockObject_MockObject */ protected $loggerMock; /** - * @var \Magento\Framework\Controller\Result\RawFactory|\PHPUnit_Framework_MockObject_MockObject + * @var RawFactory|PHPUnit_Framework_MockObject_MockObject */ protected $rawFactoryMock; /** - * @var \Magento\Framework\Controller\Result\Raw|\PHPUnit_Framework_MockObject_MockObject + * @var Raw|PHPUnit_Framework_MockObject_MockObject */ protected $rawMock; protected function setUp() { - $this->actionContextMock = $this->getMockBuilder(\Magento\Backend\App\Action\Context::class) + $this->actionContextMock = $this->getMockBuilder(Context::class) ->disableOriginalConstructor() ->getMock(); - $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\RequestInterface::class) + $this->requestMock = $this->getMockBuilder(RequestInterface::class) ->disableOriginalConstructor() ->getMock(); - $this->urlDecoderMock = $this->getMockBuilder(\Magento\Framework\Url\DecoderInterface::class) + $this->urlDecoderMock = $this->getMockBuilder(DecoderInterface::class) ->disableOriginalConstructor() ->getMock(); - $this->objectManagerMock = $this->getMockBuilder(\Magento\Framework\ObjectManagerInterface::class) + $this->objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class) ->disableOriginalConstructor() ->getMock(); - $this->templateFilterMock = $this->getMockBuilder(\Magento\Cms\Model\Template\Filter::class) + $this->templateFilterMock = $this->getMockBuilder(Filter::class) ->disableOriginalConstructor() ->getMock(); - $this->imageAdapterFactoryMock = $this->getMockBuilder(\Magento\Framework\Image\AdapterFactory::class) + $this->imageAdapterFactoryMock = $this->getMockBuilder(AdapterFactory::class) ->disableOriginalConstructor() ->getMock(); - $this->imageAdapterMock = $this->getMockBuilder(\Magento\Framework\Image\Adapter\AdapterInterface::class) + $this->imageAdapterMock = $this->getMockBuilder(AdapterInterface::class) ->disableOriginalConstructor() ->setMethods( [ @@ -117,21 +140,25 @@ protected function setUp() ] ) ->getMock(); - $this->responseMock = $this->getMockBuilder(\Magento\Framework\App\ResponseInterface::class) + $this->responseMock = $this->getMockBuilder(ResponseInterface::class) ->disableOriginalConstructor() ->setMethods(['setHeader', 'setBody', 'sendResponse']) ->getMock(); - $this->wysiwygConfigMock = $this->getMockBuilder(\Magento\Cms\Model\Wysiwyg\Config::class) + $this->fileMock = $this->getMockBuilder(File::class) ->disableOriginalConstructor() + ->setMethods(['fileGetContents']) ->getMock(); - $this->loggerMock = $this->getMockBuilder(\Psr\Log\LoggerInterface::class) + $this->wysiwygConfigMock = $this->getMockBuilder(Config::class) ->disableOriginalConstructor() ->getMock(); - $this->rawFactoryMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\RawFactory::class) + $this->loggerMock = $this->getMockBuilder(LoggerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->rawFactoryMock = $this->getMockBuilder(RawFactory::class) ->setMethods(['create']) ->disableOriginalConstructor() ->getMock(); - $this->rawMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Raw::class) + $this->rawMock = $this->getMockBuilder(Raw::class) ->disableOriginalConstructor() ->getMock(); @@ -145,9 +172,9 @@ protected function setUp() ->method('getObjectManager') ->willReturn($this->objectManagerMock); - $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $objectManager = new ObjectManager($this); $this->wysiwygDirective = $objectManager->getObject( - \Magento\Cms\Controller\Adminhtml\Wysiwyg\Directive::class, + Directive::class, [ 'context' => $this->actionContextMock, 'urlDecoder' => $this->urlDecoderMock, @@ -155,7 +182,8 @@ protected function setUp() 'adapterFactory' => $this->imageAdapterFactoryMock, 'logger' => $this->loggerMock, 'config' => $this->wysiwygConfigMock, - 'filter' => $this->templateFilterMock + 'filter' => $this->templateFilterMock, + 'file' => $this->fileMock, ] ); } @@ -172,23 +200,29 @@ public function testExecute() $this->imageAdapterMock->expects($this->once()) ->method('open') ->with(self::IMAGE_PATH); - $this->imageAdapterMock->expects($this->once()) + $this->imageAdapterMock->expects($this->atLeastOnce()) ->method('getMimeType') ->willReturn($mimeType); - $this->rawMock->expects($this->once()) + $this->rawMock->expects($this->atLeastOnce()) ->method('setHeader') ->with('Content-Type', $mimeType) ->willReturnSelf(); - $this->rawMock->expects($this->once()) + $this->rawMock->expects($this->atLeastOnce()) ->method('setContents') ->with($imageBody) ->willReturnSelf(); $this->imageAdapterMock->expects($this->once()) ->method('getImage') ->willReturn($imageBody); + $this->fileMock->expects($this->once()) + ->method('fileGetContents') + ->willReturn($imageBody); $this->rawFactoryMock->expects($this->any()) ->method('create') ->willReturn($this->rawMock); + $this->imageAdapterFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($this->imageAdapterMock); $this->assertSame( $this->rawMock, @@ -217,20 +251,23 @@ public function testExecuteException() $this->imageAdapterMock->expects($this->at(1)) ->method('open') ->with($placeholderPath); - $this->imageAdapterMock->expects($this->once()) + $this->imageAdapterMock->expects($this->atLeastOnce()) ->method('getMimeType') ->willReturn($mimeType); - $this->rawMock->expects($this->once()) + $this->rawMock->expects($this->atLeastOnce()) ->method('setHeader') ->with('Content-Type', $mimeType) ->willReturnSelf(); - $this->rawMock->expects($this->once()) + $this->rawMock->expects($this->atLeastOnce()) ->method('setContents') ->with($imageBody) ->willReturnSelf(); - $this->imageAdapterMock->expects($this->once()) + $this->imageAdapterMock->expects($this->any()) ->method('getImage') ->willReturn($imageBody); + $this->fileMock->expects($this->once()) + ->method('fileGetContents') + ->willReturn($imageBody); $this->loggerMock->expects($this->once()) ->method('warning') ->with($exception); @@ -238,6 +275,10 @@ public function testExecuteException() ->method('create') ->willReturn($this->rawMock); + $this->imageAdapterFactoryMock->expects($this->exactly(1)) + ->method('create') + ->willReturn($this->imageAdapterMock); + $this->assertSame( $this->rawMock, $this->wysiwygDirective->execute() @@ -297,6 +338,20 @@ public function testExecuteWithDeletedImage() ->method('warning') ->with($exception); + $this->rawMock->expects($this->once()) + ->method('setHeader') + ->with('Content-Type', null) + ->willReturnSelf(); + + $this->rawMock->expects($this->once()) + ->method('setContents') + ->with(null) + ->willReturnSelf(); + + $this->rawFactoryMock->expects($this->any()) + ->method('create') + ->willReturn($this->rawMock); + $this->wysiwygDirective->execute(); } } diff --git a/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/PageActionsTest.php b/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/PageActionsTest.php index 53d8ee5220768..781d6d31246ca 100644 --- a/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/PageActionsTest.php +++ b/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/PageActionsTest.php @@ -3,91 +3,112 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Cms\Test\Unit\Ui\Component\Listing\Column; use Magento\Cms\Ui\Component\Listing\Column\PageActions; +use Magento\Cms\ViewModel\Page\Grid\UrlBuilder; use Magento\Framework\Escaper; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\UrlInterface; +use Magento\Framework\View\Element\UiComponent\ContextInterface; +use Magento\Framework\View\Element\UiComponent\Processor; +use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject as MockObject; /** * Test for Magento\Cms\Ui\Component\Listing\Column\PageActions class. */ -class PageActionsTest extends \PHPUnit\Framework\TestCase +class PageActionsTest extends TestCase { - public function testPrepareItemsByPageId() + + /** + * @var UrlInterface|MockObject + */ + private $urlBuilderMock; + + /** + * @var UrlBuilder|MockObject + */ + private $scopeUrlBuilderMock; + + /** + * @var ContextInterface|MockObject + */ + private $contextMock; + + /** + * @var Processor|MockObject + */ + private $processorMock; + + /** + * @var Escaper|MockObject + */ + private $escaperMock; + + /** + * @var PageActions + */ + private $model; + + /** + * @inheritDoc + */ + public function setUp() { - $pageId = 1; - // Create Mocks and SUT - $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - /** @var \PHPUnit_Framework_MockObject_MockObject $urlBuilderMock */ - $urlBuilderMock = $this->getMockBuilder(\Magento\Framework\UrlInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $contextMock = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponent\ContextInterface::class) + $this->urlBuilderMock = $this->createMock(UrlInterface::class); + $this->scopeUrlBuilderMock = $this->createMock(UrlBuilder::class); + $this->processorMock = $this->createMock(Processor::class); + $this->contextMock = $this->getMockBuilder(ContextInterface::class) ->getMockForAbstractClass(); - $processor = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponent\Processor::class) + $this->escaperMock = $this->getMockBuilder(Escaper::class) ->disableOriginalConstructor() + ->setMethods(['escapeHtml']) ->getMock(); - $contextMock->expects($this->never())->method('getProcessor')->willReturn($processor); - /** @var \Magento\Cms\Ui\Component\Listing\Column\PageActions $model */ - $model = $objectManager->getObject( - \Magento\Cms\Ui\Component\Listing\Column\PageActions::class, + $objectManager = new ObjectManager($this); + + $this->model = $objectManager->getObject( + PageActions::class, [ - 'urlBuilder' => $urlBuilderMock, - 'context' => $contextMock, + 'urlBuilder' => $this->urlBuilderMock, + 'context' => $this->contextMock, + 'scopeUrlBuilder' => $this->scopeUrlBuilderMock ] ); - $escaper = $this->getMockBuilder(Escaper::class) - ->disableOriginalConstructor() - ->setMethods(['escapeHtml']) - ->getMock(); - $objectManager->setBackwardCompatibleProperty($model, 'escaper', $escaper); - - // Define test input and expectations - $title = 'page title'; - $items = [ - 'data' => [ - 'items' => [ - [ - 'page_id' => $pageId, - 'title' => $title - ] - ] - ] - ]; - $name = 'item_name'; - $expectedItems = [ - [ - 'page_id' => $pageId, - 'title' => $title, - $name => [ - 'edit' => [ - 'href' => 'test/url/edit', - 'label' => __('Edit'), - '__disableTmpl' => true, - ], - 'delete' => [ - 'href' => 'test/url/delete', - 'label' => __('Delete'), - 'confirm' => [ - 'title' => __('Delete %1', $title), - 'message' => __('Are you sure you want to delete a %1 record?', $title), - '__disableTmpl' => true, - ], - 'post' => true, - '__disableTmpl' => true, - ], - ], - ], - ]; + $objectManager->setBackwardCompatibleProperty($this->model, 'escaper', $this->escaperMock); + } - $escaper->expects(static::once()) + /** + * Verify Prepare Items by page Id. + * + * @dataProvider configDataProvider + * @param int $pageId + * @param string $title + * @param string $name + * @param array $items + * @param array $expectedItems + * @return void + */ + public function testPrepareItemsByPageId( + int $pageId, + string $title, + string $name, + array $items, + array $expectedItems + ):void { + $this->contextMock->expects($this->never()) + ->method('getProcessor') + ->willReturn($this->processorMock); + $this->escaperMock->expects(static::once()) ->method('escapeHtml') ->with($title) ->willReturn($title); // Configure mocks and object data - $urlBuilderMock->expects($this->any()) + $this->urlBuilderMock->expects($this->any()) ->method('getUrl') ->willReturnMap( [ @@ -107,9 +128,77 @@ public function testPrepareItemsByPageId() ], ] ); - $model->setName($name); - $items = $model->prepareDataSource($items); + + $this->scopeUrlBuilderMock->expects($this->any()) + ->method('getUrl') + ->willReturn('test/url/view'); + + $this->model->setName($name); + $items = $this->model->prepareDataSource($items); // Run test $this->assertEquals($expectedItems, $items['data']['items']); } + + /** + * Data provider for testPrepareItemsByPageId + * + * @return array + */ + public function configDataProvider():array + { + $pageId = 1; + $title = 'page title'; + $identifier = 'page_identifier'; + $name = 'item_name'; + + return [ + [ + 'pageId' => $pageId, + 'title' => $title, + 'name' => $name, + 'items' => [ + 'data' => [ + 'items' => [ + [ + 'page_id' => $pageId, + 'title' => $title, + 'identifier' => $identifier + ] + ] + ] + ], + 'expectedItems' => [ + [ + 'page_id' => $pageId, + 'title' => $title, + 'identifier' => $identifier, + $name => [ + 'edit' => [ + 'href' => 'test/url/edit', + 'label' => __('Edit'), + '__disableTmpl' => true, + ], + 'delete' => [ + 'href' => 'test/url/delete', + 'label' => __('Delete'), + 'confirm' => [ + 'title' => __('Delete %1', $title), + 'message' => __('Are you sure you want to delete a %1 record?', $title), + '__disableTmpl' => true, + ], + 'post' => true, + '__disableTmpl' => true, + ], + 'preview' => [ + 'href' => 'test/url/view', + 'label' => __('View'), + '__disableTmpl' => true, + 'target' => '_blank' + ] + ], + ], + ] + ] + ]; + } } diff --git a/app/code/Magento/Cms/Ui/Component/Listing/Column/PageActions.php b/app/code/Magento/Cms/Ui/Component/Listing/Column/PageActions.php index fa3756abfded4..0c6000bdbab84 100644 --- a/app/code/Magento/Cms/Ui/Component/Listing/Column/PageActions.php +++ b/app/code/Magento/Cms/Ui/Component/Listing/Column/PageActions.php @@ -14,7 +14,7 @@ use Magento\Ui\Component\Listing\Columns\Column; /** - * Class PageActions + * Class prepare Page Actions */ class PageActions extends Column { @@ -111,6 +111,7 @@ public function prepareDataSource(array $dataSource) ), 'label' => __('View'), '__disableTmpl' => true, + 'target' => '_blank' ]; } } diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml index 3d797e62c806a..847ee728d5e78 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml @@ -61,6 +61,8 @@ + + diff --git a/app/code/Magento/Customer/Model/Indexer/Processor.php b/app/code/Magento/Customer/Model/Indexer/Processor.php new file mode 100644 index 0000000000000..6b44b674b405a --- /dev/null +++ b/app/code/Magento/Customer/Model/Indexer/Processor.php @@ -0,0 +1,16 @@ + - + - + + diff --git a/app/code/Magento/CustomerImportExport/Model/Import/CustomerComposite.php b/app/code/Magento/CustomerImportExport/Model/Import/CustomerComposite.php index 0f4c5f82bfe1d..4a22dc83a1f31 100644 --- a/app/code/Magento/CustomerImportExport/Model/Import/CustomerComposite.php +++ b/app/code/Magento/CustomerImportExport/Model/Import/CustomerComposite.php @@ -6,6 +6,7 @@ namespace Magento\CustomerImportExport\Model\Import; use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface; +use Magento\Customer\Model\Indexer\Processor; /** * Import entity customer combined model @@ -148,6 +149,11 @@ class CustomerComposite extends \Magento\ImportExport\Model\Import\AbstractEntit */ protected $masterAttributeCode = 'email'; + /** + * @var Processor + */ + private $indexerProcessor; + /** * @param \Magento\Framework\Stdlib\StringUtils $string * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig @@ -158,6 +164,7 @@ class CustomerComposite extends \Magento\ImportExport\Model\Import\AbstractEntit * @param \Magento\CustomerImportExport\Model\ResourceModel\Import\CustomerComposite\DataFactory $dataFactory * @param \Magento\CustomerImportExport\Model\Import\CustomerFactory $customerFactory * @param \Magento\CustomerImportExport\Model\Import\AddressFactory $addressFactory + * @param Processor $indexerProcessor * @param array $data * @throws \Magento\Framework\Exception\LocalizedException * @@ -173,6 +180,7 @@ public function __construct( \Magento\CustomerImportExport\Model\ResourceModel\Import\CustomerComposite\DataFactory $dataFactory, \Magento\CustomerImportExport\Model\Import\CustomerFactory $customerFactory, \Magento\CustomerImportExport\Model\Import\AddressFactory $addressFactory, + Processor $indexerProcessor, array $data = [] ) { parent::__construct($string, $scopeConfig, $importFactory, $resourceHelper, $resource, $errorAggregator, $data); @@ -230,6 +238,7 @@ public function __construct( } else { $this->_nextCustomerId = $resourceHelper->getNextAutoincrement($this->_customerEntity->getEntityTable()); } + $this->indexerProcessor = $indexerProcessor; } /** @@ -273,11 +282,12 @@ protected function _importData() $this->countItemsCreated += $this->_customerEntity->getCreatedItemsCount(); $this->countItemsUpdated += $this->_customerEntity->getUpdatedItemsCount(); $this->countItemsDeleted += $this->_customerEntity->getDeletedItemsCount(); - if ($this->getBehavior() != \Magento\ImportExport\Model\Import::BEHAVIOR_DELETE) { - return $result && $this->_addressEntity->setCustomerAttributes($this->_customerAttributes)->importData(); + $result = $result && $this->_addressEntity->setCustomerAttributes($this->_customerAttributes)->importData(); + } + if ($result) { + $this->indexerProcessor->markIndexerAsInvalid(); } - return $result; } diff --git a/app/code/Magento/CustomerImportExport/Test/Unit/Model/Import/CustomerCompositeTest.php b/app/code/Magento/CustomerImportExport/Test/Unit/Model/Import/CustomerCompositeTest.php index 1b900c2139588..7aff0f911c2b0 100644 --- a/app/code/Magento/CustomerImportExport/Test/Unit/Model/Import/CustomerCompositeTest.php +++ b/app/code/Magento/CustomerImportExport/Test/Unit/Model/Import/CustomerCompositeTest.php @@ -15,7 +15,7 @@ use Magento\ImportExport\Model\Import\Source\Csv; /** - * Customer composite test + * The test for Customer composite model * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -88,6 +88,12 @@ class CustomerCompositeTest extends \PHPUnit\Framework\TestCase */ protected $errorFactory; + /** + * @var \Magento\Customer\Model\Indexer\Processor + * |\PHPUnit\Framework\MockObject\MockObject + */ + private $indexerProcessor; + /** * Expected prepared data after method CustomerComposite::_prepareRowForDb * @@ -141,6 +147,7 @@ protected function setUp() ->getMock(); $this->_scopeConfigMock = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class); + $this->indexerProcessor = $this->createMock(\Magento\Customer\Model\Indexer\Processor::class); } /** @@ -159,6 +166,7 @@ protected function _createModelMock($data) $this->_dataFactory, $this->_customerFactory, $this->_addressFactory, + $this->indexerProcessor, $data ); } diff --git a/app/code/Magento/Multishipping/Model/Cart/CartTotalRepositoryPlugin.php b/app/code/Magento/Multishipping/Model/Cart/CartTotalRepositoryPlugin.php index 732bdee314f7c..bb225a5c46228 100644 --- a/app/code/Magento/Multishipping/Model/Cart/CartTotalRepositoryPlugin.php +++ b/app/code/Magento/Multishipping/Model/Cart/CartTotalRepositoryPlugin.php @@ -33,21 +33,21 @@ public function __construct( } /** - * Overwrite the CartTotalRepository quoteTotal and update the shipping price + * Check multishipping update shipping price after get cart total * - * @param CartTotalRepository $subject - * @param Totals $quoteTotals - * @param String $cartId - * @return Totals - * @throws \Magento\Framework\Exception\NoSuchEntityException + * @param CartTotalRepository $subject + * @param Totals $quoteTotals + * @param int $cartId + * @return Totals + * @throws \Magento\Framework\Exception\NoSuchEntityException * @SuppressWarnings(PHPMD.UnusedLocalVariable) * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function afterGet( CartTotalRepository $subject, Totals $quoteTotals, - String $cartId - ) { + $cartId + ) : Totals { $quote = $this->quoteRepository->getActive($cartId); if ($quote->getIsMultiShipping()) { $shippingMethod = $quote->getShippingAddress()->getShippingMethod(); diff --git a/app/code/Magento/Multishipping/Test/Unit/Model/Cart/CartTotalRepositoryPluginTest.php b/app/code/Magento/Multishipping/Test/Unit/Model/Cart/CartTotalRepositoryPluginTest.php index 73b0b9ef3ca7a..8362699efbd45 100644 --- a/app/code/Magento/Multishipping/Test/Unit/Model/Cart/CartTotalRepositoryPluginTest.php +++ b/app/code/Magento/Multishipping/Test/Unit/Model/Cart/CartTotalRepositoryPluginTest.php @@ -6,76 +6,145 @@ namespace Magento\Multishipping\Test\Unit\Model\Cart; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Model\Cart\CartTotalRepository; +use Magento\Quote\Model\Cart\Totals as QuoteTotals; +use Magento\Quote\Model\Quote\Address as QuoteAddress; +use Magento\Quote\Model\Quote\Address\Rate as QuoteAddressRate; +use Magento\Multishipping\Model\Cart\CartTotalRepositoryPlugin; +use Magento\Store\Model\Store; +use PHPUnit\Framework\MockObject\MockObject; + class CartTotalRepositoryPluginTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\Multishipping\Model\Cart\CartTotalRepositoryPlugin + * Stub cart id + */ + private const STUB_CART_ID = 10; + + /** + * Stub shipping method + */ + private const STUB_SHIPPING_METHOD = 'flatrate_flatrate'; + + /** + * Stub shipping price + */ + private const STUB_SHIPPING_PRICE = '10.00'; + + /** + * @var CartTotalRepositoryPlugin */ private $modelRepository; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var CartTotalRepository|MockObject + */ + private $quoteTotalRepositoryMock; + + /** + * @var CartRepositoryInterface|MockObject */ private $quoteRepositoryMock; - protected function setUp() - { - $this->quoteRepositoryMock = $this->createMock(\Magento\Quote\Api\CartRepositoryInterface::class); - $this->modelRepository = new \Magento\Multishipping\Model\Cart\CartTotalRepositoryPlugin( - $this->quoteRepositoryMock - ); - } + /** + * @var QuoteTotals|MockObject + */ + private $quoteTotalsMock; /** - * Test quotTotal from cartRepository after get($cartId) function is called + * @var QuoteAddress|MockObject */ - public function testAfterGet() + private $shippingAddressMock; + + /** + * @var QuoteAddressRate|MockObject + */ + private $shippingRateMock; + + /** + * @var Store|MockObject + */ + private $storeMock; + + protected function setUp() { - $cartId = "10"; - $shippingMethod = 'flatrate_flatrate'; - $shippingPrice = '10.00'; - $quoteMock = $this->createPartialMock( - \Magento\Quote\Model\Cart\Totals::class, + $objectManager = new ObjectManager($this); + $this->quoteTotalsMock = $this->createPartialMock( + QuoteTotals::class, [ - 'getStore', - 'getShippingAddress', - 'getIsMultiShipping' + 'getStore', + 'getShippingAddress', + 'getIsMultiShipping' ] ); - $this->quoteRepositoryMock->expects($this->once())->method('getActive')->with($cartId)->willReturn($quoteMock); - $quoteMock->expects($this->once())->method('getIsMultiShipping')->willReturn(true); - $shippingAddressMock = $this->createPartialMock( - \Magento\Quote\Model\Quote\Address::class, + $this->shippingAddressMock = $this->createPartialMock( + QuoteAddress::class, [ - 'getShippingMethod', - 'getShippingRateByCode', - 'getShippingAmount' + 'getShippingMethod', + 'getShippingRateByCode', + 'getShippingAmount' ] ); - $quoteMock->expects($this->any())->method('getShippingAddress')->willReturn($shippingAddressMock); - - $shippingAddressMock->expects($this->once())->method('getShippingMethod')->willReturn($shippingMethod); - $shippingAddressMock->expects($this->any())->method('getShippingAmount')->willReturn($shippingPrice); - $shippingRateMock = $this->createPartialMock( - \Magento\Quote\Model\Quote\Address\Rate::class, + $this->shippingRateMock = $this->createPartialMock( + QuoteAddressRate::class, [ - 'getPrice' + 'getPrice' ] ); - $shippingAddressMock->expects($this->once())->method('getShippingRateByCode')->willReturn($shippingRateMock); - - $shippingRateMock->expects($this->once())->method('getPrice')->willReturn($shippingPrice); - - $storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class) + $this->storeMock = $this->getMockBuilder(Store::class) ->disableOriginalConstructor() ->getMock(); - $quoteMock->expects($this->any())->method('getStore')->willReturn($storeMock); - $storeMock->expects($this->any())->method('getBaseCurrency')->willReturnSelf(); + $this->quoteRepositoryMock = $this->createMock(CartRepositoryInterface::class); + $this->quoteTotalRepositoryMock = $this->createMock(CartTotalRepository::class); + $this->modelRepository = $objectManager->getObject(CartTotalRepositoryPlugin::class, [ + 'quoteRepository' => $this->quoteRepositoryMock + ]); + } + + /** + * Test quoteTotal from cartRepository after get($cartId) function is called + */ + public function testAfterGetQuoteTotalAddedShippingPrice() + { + $this->quoteRepositoryMock->expects($this->once()) + ->method('getActive') + ->with(self::STUB_CART_ID) + ->willReturn($this->quoteTotalsMock); + $this->quoteTotalsMock->expects($this->once()) + ->method('getIsMultiShipping') + ->willReturn(true); + $this->quoteTotalsMock->expects($this->any()) + ->method('getShippingAddress') + ->willReturn($this->shippingAddressMock); + + $this->shippingAddressMock->expects($this->once()) + ->method('getShippingMethod') + ->willReturn(self::STUB_SHIPPING_METHOD); + $this->shippingAddressMock->expects($this->any()) + ->method('getShippingAmount') + ->willReturn(self::STUB_SHIPPING_PRICE); + + $this->shippingAddressMock->expects($this->once()) + ->method('getShippingRateByCode') + ->willReturn($this->shippingRateMock); + + $this->shippingRateMock->expects($this->once()) + ->method('getPrice') + ->willReturn(self::STUB_SHIPPING_PRICE); + + $this->quoteTotalsMock->expects($this->any()) + ->method('getStore') + ->willReturn($this->storeMock); + $this->storeMock->expects($this->any()) + ->method('getBaseCurrency') + ->willReturnSelf(); $this->modelRepository->afterGet( - $this->createMock(\Magento\Quote\Model\Cart\CartTotalRepository::class), - $quoteMock, - $cartId + $this->quoteTotalRepositoryMock, + $this->quoteTotalsMock, + self::STUB_CART_ID ); } } diff --git a/app/code/Magento/Multishipping/view/frontend/layout/multishipping_checkout_customer_address.xml b/app/code/Magento/Multishipping/view/frontend/layout/multishipping_checkout_customer_address.xml index fee3cb790a522..449f5feeafd9c 100644 --- a/app/code/Magento/Multishipping/view/frontend/layout/multishipping_checkout_customer_address.xml +++ b/app/code/Magento/Multishipping/view/frontend/layout/multishipping_checkout_customer_address.xml @@ -13,6 +13,7 @@ Magento\Customer\Block\DataProviders\AddressAttributeData Magento\Customer\Block\DataProviders\PostCodesPatternsAttributeData + Magento\Customer\ViewModel\Address diff --git a/app/code/Magento/Newsletter/Block/Adminhtml/Subscriber.php b/app/code/Magento/Newsletter/Block/Adminhtml/Subscriber.php index 4d5165db68736..d05657c727192 100644 --- a/app/code/Magento/Newsletter/Block/Adminhtml/Subscriber.php +++ b/app/code/Magento/Newsletter/Block/Adminhtml/Subscriber.php @@ -11,13 +11,18 @@ */ namespace Magento\Newsletter\Block\Adminhtml; +use Magento\Backend\Block\Template; +use Magento\Backend\Block\Template\Context; use Magento\Newsletter\Model\ResourceModel\Queue\Collection; +use Magento\Newsletter\Model\ResourceModel\Queue\CollectionFactory; /** + * Newsletter Subscriber block + * * @api * @since 100.0.2 */ -class Subscriber extends \Magento\Backend\Block\Template +class Subscriber extends Template { /** * Queue collection @@ -32,34 +37,24 @@ class Subscriber extends \Magento\Backend\Block\Template protected $_template = 'Magento_Newsletter::subscriber/list.phtml'; /** - * @var \Magento\Newsletter\Model\ResourceModel\Queue\CollectionFactory + * @var CollectionFactory */ protected $_collectionFactory; /** - * @param \Magento\Backend\Block\Template\Context $context - * @param \Magento\Newsletter\Model\ResourceModel\Queue\CollectionFactory $collectionFactory + * @param Context $context + * @param CollectionFactory $collectionFactory * @param array $data */ public function __construct( - \Magento\Backend\Block\Template\Context $context, - \Magento\Newsletter\Model\ResourceModel\Queue\CollectionFactory $collectionFactory, + Context $context, + CollectionFactory $collectionFactory, array $data = [] ) { $this->_collectionFactory = $collectionFactory; parent::__construct($context, $data); } - /** - * Prepares block to render - * - * @return $this - */ - protected function _beforeToHtml() - { - return parent::_beforeToHtml(); - } - /** * Return queue collection with loaded neversent queues * @@ -68,7 +63,7 @@ protected function _beforeToHtml() public function getQueueCollection() { if ($this->_queueCollection === null) { - /** @var $this->_queueCollection \Magento\Newsletter\Model\ResourceModel\Queue\Collection */ + /** @var $this->_queueCollection Collection */ $this->_queueCollection = $this ->_collectionFactory ->create() diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertAdminItemOrderedErrorActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertAdminItemOrderedErrorActionGroup.xml new file mode 100644 index 0000000000000..a2f35b9c5fca8 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertAdminItemOrderedErrorActionGroup.xml @@ -0,0 +1,21 @@ + + + + + + + Assert that item in "Item Ordered" grid has an error/notice + + + + + + + + + diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertAdminItemOrderedErrorNotVisibleActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertAdminItemOrderedErrorNotVisibleActionGroup.xml new file mode 100644 index 0000000000000..83bac652d7dff --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertAdminItemOrderedErrorNotVisibleActionGroup.xml @@ -0,0 +1,21 @@ + + + + + + + Assert that item in "Item Ordered" grid does not have an error/notice + + + + + + + + + diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsOrderedSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsOrderedSection.xml index e3417e7c662b9..4437f6e6775f2 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsOrderedSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsOrderedSection.xml @@ -18,5 +18,6 @@ + diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminAddSelectedProductToOrderTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminAddSelectedProductToOrderTest.xml new file mode 100644 index 0000000000000..ca74eca88308a --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminAddSelectedProductToOrderTest.xml @@ -0,0 +1,78 @@ + + + + + + + + + + <description value="Trying to add selected products to order in Admin when requested qty more than available"/> + <useCaseId value="MC-29184"/> + <testCaseId value="MC-31589"/> + <severity value="MAJOR"/> + <group value="sales"/> + <group value="catalogInventory"/> + </annotations> + <before> + <createData entity="Simple_US_Customer" stepKey="simpleCustomer"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <deleteData createDataKey="simpleCustomer" stepKey="deleteSimpleCustomer"/> + <deleteData createDataKey="simpleProduct" stepKey="deleteSimpleProduct"/> + <actionGroup ref="logout" stepKey="logoutFromAdmin"/> + </after> + + <!-- Initiate create new order --> + <actionGroup ref="NavigateToNewOrderPageExistingCustomerActionGroup" stepKey="navigateToNewOrderPageWithExistingCustomer"> + <argument name="customer" value="$simpleCustomer$"/> + </actionGroup> + <!-- Add to order maximum available quantity - 1 --> + <executeJS function="return {{SimpleProduct2.quantity}} - 1" stepKey="maxQtyMinusOne"/> + <actionGroup ref="AddSimpleProductToOrderActionGroup" stepKey="addProductToOrderWithMaxQtyMinusOne"> + <argument name="product" value="$simpleProduct$"/> + <argument name="productQty" value="{$maxQtyMinusOne}"/> + </actionGroup> + <!-- Check that there is no error or notice --> + <actionGroup ref="AssertAdminItemOrderedErrorNotVisibleActionGroup" stepKey="assertNoticeAbsent"> + <argument name="productName" value="$simpleProduct.name$"/> + <argument name="messageType" value="notice"/> + </actionGroup> + <actionGroup ref="AssertAdminItemOrderedErrorNotVisibleActionGroup" stepKey="assertErrorAbsent"> + <argument name="productName" value="$simpleProduct.name$"/> + <argument name="messageType" value="error"/> + </actionGroup> + <!-- Add to order maximum available quantity --> + <actionGroup ref="AddSimpleProductToOrderActionGroup" stepKey="addProductToOrder"> + <argument name="product" value="$simpleProduct$"/> + <argument name="productQty" value="1"/> + </actionGroup> + <!-- Check that there is no error or notice --> + <actionGroup ref="AssertAdminItemOrderedErrorNotVisibleActionGroup" stepKey="assertNoticeAbsentAgain"> + <argument name="productName" value="$simpleProduct.name$"/> + <argument name="messageType" value="notice"/> + </actionGroup> + <actionGroup ref="AssertAdminItemOrderedErrorNotVisibleActionGroup" stepKey="assertErrorAbsentAgain"> + <argument name="productName" value="$simpleProduct.name$"/> + <argument name="messageType" value="error"/> + </actionGroup> + <!-- Add to order one more quantity --> + <actionGroup ref="AddSimpleProductToOrderActionGroup" stepKey="addProductToOrderAgain"> + <argument name="product" value="$simpleProduct$"/> + <argument name="productQty" value="1"/> + </actionGroup> + <!-- Check that error remains --> + <actionGroup ref="AssertAdminItemOrderedErrorActionGroup" stepKey="assertProductErrorRemains"> + <argument name="productName" value="$simpleProduct.name$"/> + <argument name="messageType" value="notice"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Widget/Test/Mftf/Test/ProductsListWidgetTest.xml b/app/code/Magento/Widget/Test/Mftf/Test/ProductsListWidgetTest.xml index fbad9ab271bda..df9b724783372 100644 --- a/app/code/Magento/Widget/Test/Mftf/Test/ProductsListWidgetTest.xml +++ b/app/code/Magento/Widget/Test/Mftf/Test/ProductsListWidgetTest.xml @@ -6,8 +6,7 @@ */ --> -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="ProductsListWidgetTest"> <annotations> <features value="Widget"/> @@ -61,8 +60,10 @@ <click selector="{{CmsPagesPageActionsSection.select(_newDefaultCmsPage.title)}}" stepKey="clickSelect" /> <waitForElementVisible selector="{{CmsPagesPageActionsSection.edit(_newDefaultCmsPage.title)}}" stepKey="waitForEditLink" /> <click selector="{{CmsPagesPageActionsSection.preview(_newDefaultCmsPage.title)}}" stepKey="clickEdit" /> + <switchToNextTab stepKey="switchToNextTab"/> <waitForPageLoad stepKey="waitForCMSPage"/> <seeInTitle userInput="{{_newDefaultCmsPage.title}}" stepKey="seePageTitle"/> <see userInput="{{_newDefaultCmsPage.title}}" stepKey="seeProduct"/> + <closeTab stepKey="closeCurrentTab"/> </test> </tests> diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_checkbox_options.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_checkbox_options.php index f9636890e61f6..4b581a5cbf5d6 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_checkbox_options.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_checkbox_options.php @@ -34,7 +34,7 @@ $bundleProduct->setTypeId(Type::TYPE_BUNDLE) ->setAttributeSetId($bundleProduct->getDefaultAttributeSetId()) ->setWebsiteIds([$baseWebsiteId]) - ->setName('Bundle Product') + ->setName('Bundle Product Checkbox Options') ->setSku('bundle-product-checkbox-options') ->setVisibility(Visibility::VISIBILITY_BOTH) ->setStatus(Status::STATUS_ENABLED) diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_checkbox_required_option.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_checkbox_required_option.php index 453b531f75b2d..1cc9ede5d71e4 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_checkbox_required_option.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_checkbox_required_option.php @@ -33,7 +33,7 @@ $bundleProduct->setTypeId(Type::TYPE_BUNDLE) ->setAttributeSetId($bundleProduct->getDefaultAttributeSetId()) ->setWebsiteIds([$baseWebsiteId]) - ->setName('Bundle Product') + ->setName('Bundle Product Checkbox Required Option') ->setSku('bundle-product-checkbox-required-option') ->setVisibility(Visibility::VISIBILITY_BOTH) ->setStatus(Status::STATUS_ENABLED) diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_checkbox_required_options.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_checkbox_required_options.php index 9b84d1236c5c9..5bb6fe6973287 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_checkbox_required_options.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_checkbox_required_options.php @@ -34,7 +34,7 @@ $bundleProduct->setTypeId(Type::TYPE_BUNDLE) ->setAttributeSetId($product->getDefaultAttributeSetId()) ->setWebsiteIds([$baseWebsiteId]) - ->setName('Bundle Product') + ->setName('Bundle Product Checkbox Required Options') ->setSku('bundle-product-checkbox-required-options') ->setVisibility(Visibility::VISIBILITY_BOTH) ->setStatus(Status::STATUS_ENABLED) diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_dropdown_options.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_dropdown_options.php index 06f6473802ee2..62c80abc6415f 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_dropdown_options.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_dropdown_options.php @@ -34,7 +34,7 @@ $bundleProduct->setTypeId(Type::TYPE_BUNDLE) ->setAttributeSetId($product->getDefaultAttributeSetId()) ->setWebsiteIds([$baseWebsiteId]) - ->setName('Bundle Product') + ->setName('Bundle Product Dropdown options') ->setSku('bundle-product-dropdown-options') ->setVisibility(Visibility::VISIBILITY_BOTH) ->setStatus(Status::STATUS_ENABLED) diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_dropdown_required_options.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_dropdown_required_options.php index 1789f472f968d..4093c9ff057e7 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_dropdown_required_options.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_dropdown_required_options.php @@ -34,7 +34,7 @@ $bundleProduct->setTypeId(Type::TYPE_BUNDLE) ->setAttributeSetId($product->getDefaultAttributeSetId()) ->setWebsiteIds([$baseWebsiteId]) - ->setName('Bundle Product') + ->setName('Bundle Product Dropdown Required options') ->setSku('bundle-product-dropdown-required-options') ->setVisibility(Visibility::VISIBILITY_BOTH) ->setStatus(Status::STATUS_ENABLED) diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_multiselect_options.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_multiselect_options.php index a5667b89f8bf4..29b7710c47040 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_multiselect_options.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_multiselect_options.php @@ -34,7 +34,7 @@ $bundleProduct->setTypeId(Type::TYPE_BUNDLE) ->setAttributeSetId($product->getDefaultAttributeSetId()) ->setWebsiteIds([$baseWebsiteId]) - ->setName('Bundle Product') + ->setName('Bundle Product Multiselect Options') ->setSku('bundle-product-multiselect-options') ->setVisibility(Visibility::VISIBILITY_BOTH) ->setStatus(Status::STATUS_ENABLED) diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_multiselect_required_option.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_multiselect_required_option.php index 7789045f6f7ef..0cde1e65c9e54 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_multiselect_required_option.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_multiselect_required_option.php @@ -33,7 +33,7 @@ $bundleProduct->setTypeId(Type::TYPE_BUNDLE) ->setAttributeSetId($product->getDefaultAttributeSetId()) ->setWebsiteIds([$baseWebsiteId]) - ->setName('Bundle Product') + ->setName('Bundle Product Multiselect Required Option') ->setSku('bundle-product-multiselect-required-option') ->setVisibility(Visibility::VISIBILITY_BOTH) ->setStatus(Status::STATUS_ENABLED) diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_multiselect_required_options.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_multiselect_required_options.php index 65bb49f3b6122..f18494d08215c 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_multiselect_required_options.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_multiselect_required_options.php @@ -34,7 +34,7 @@ $bundleProduct->setTypeId(Type::TYPE_BUNDLE) ->setAttributeSetId($product->getDefaultAttributeSetId()) ->setWebsiteIds([$baseWebsiteId]) - ->setName('Bundle Product') + ->setName('Bundle Product Multiselect Required Options') ->setSku('bundle-product-multiselect-required-options') ->setVisibility(Visibility::VISIBILITY_BOTH) ->setStatus(Status::STATUS_ENABLED) diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_radio_options.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_radio_options.php index def31b48b2172..23a5713e46eb0 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_radio_options.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_radio_options.php @@ -34,7 +34,7 @@ $bundleProduct->setTypeId(Type::TYPE_BUNDLE) ->setAttributeSetId($product->getDefaultAttributeSetId()) ->setWebsiteIds([$baseWebsiteId]) - ->setName('Bundle Product') + ->setName('Bundle Product Radio Options') ->setSku('bundle-product-radio-options') ->setVisibility(Visibility::VISIBILITY_BOTH) ->setStatus(Status::STATUS_ENABLED) diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_radio_required_option.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_radio_required_option.php index c659387e09dcc..1472986023e21 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_radio_required_option.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_radio_required_option.php @@ -33,7 +33,7 @@ $bundleProduct->setTypeId(Type::TYPE_BUNDLE) ->setAttributeSetId($product->getDefaultAttributeSetId()) ->setWebsiteIds([$baseWebsiteId]) - ->setName('Bundle Product') + ->setName('Bundle Product Radio Required Option') ->setSku('bundle-product-radio-required-option') ->setVisibility(Visibility::VISIBILITY_BOTH) ->setStatus(Status::STATUS_ENABLED) diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_radio_required_options.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_radio_required_options.php index ec28bf556b69c..cb703902a0b7a 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_radio_required_options.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/bundle_product_radio_required_options.php @@ -34,7 +34,7 @@ $bundleProduct->setTypeId(Type::TYPE_BUNDLE) ->setAttributeSetId($product->getDefaultAttributeSetId()) ->setWebsiteIds([$baseWebsiteId]) - ->setName('Bundle Product') + ->setName('Bundle Product Radio Required Options') ->setSku('bundle-product-radio-required-options') ->setVisibility(Visibility::VISIBILITY_BOTH) ->setStatus(Status::STATUS_ENABLED) diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/dynamic_and_fixed_bundle_products_in_category.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/dynamic_and_fixed_bundle_products_in_category.php new file mode 100644 index 0000000000000..9a6cae37fbe25 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/dynamic_and_fixed_bundle_products_in_category.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\CategoryLinkManagementInterface; +use Magento\Catalog\Helper\DefaultCategory; + +require __DIR__ . '/product.php'; +require __DIR__ . '/bundle_product_dropdown_options.php'; +require __DIR__ . '/../../Catalog/_files/category.php'; + +/** @var CategoryLinkManagementInterface $categoryLinkManagement */ +$categoryLinkManagement = $objectManager->create(CategoryLinkManagementInterface::class); +/** @var DefaultCategory $categoryHelper */ +$categoryHelper = $objectManager->get(DefaultCategory::class); +$categoryLinkManagement->assignProductToCategories('bundle-product', [2, $category->getId()]); +$categoryLinkManagement->assignProductToCategories( + 'bundle-product-dropdown-options', + [$categoryHelper->getId(), $category->getId()] +); diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/dynamic_and_fixed_bundle_products_in_category_rollback.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/dynamic_and_fixed_bundle_products_in_category_rollback.php new file mode 100644 index 0000000000000..58eb8cacde815 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/dynamic_and_fixed_bundle_products_in_category_rollback.php @@ -0,0 +1,10 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/product_rollback.php'; +require __DIR__ . '/bundle_product_dropdown_options_rollback.php'; +require __DIR__ . '/../../Catalog/_files/category_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/ProductFrontendAction/SynchronizerTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/ProductFrontendAction/SynchronizerTest.php index 3ea30005e9f6c..5cf53349480f1 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/ProductFrontendAction/SynchronizerTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/ProductFrontendAction/SynchronizerTest.php @@ -40,20 +40,24 @@ protected function setUp() * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php * * @return void + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function testSyncActions(): void { $actionsType = 'recently_viewed_product'; + $productScope = 'website'; + $scopeId = 1; $product1 = $this->productRepository->get('simple'); $product2 = $this->productRepository->get('simple2'); $product1Id = $product1->getId(); $product2Id = $product2->getId(); $productsData = [ - $product1Id => [ + $productScope . '-' . $scopeId . '-' . $product1Id => [ 'added_at' => '1576582660', 'product_id' => $product1Id, ], - $product2Id => [ + $productScope . '-' . $scopeId . '-' . $product2Id => [ 'added_at' => '1576587153', 'product_id' => $product2Id, ], @@ -71,8 +75,9 @@ public function testSyncActions(): void ); foreach ($synchronizedCollection as $item) { - $this->assertArrayHasKey($item->getProductId(), $productsData); - $this->assertEquals($productsData[$item->getProductId()]['added_at'], $item->getAddedAt()); + $productScopeId = $productScope . '-' . $scopeId . '-' . $item->getProductId(); + $this->assertArrayHasKey($productScopeId, $productsData); + $this->assertEquals($productsData[$productScopeId]['added_at'], $item->getAddedAt()); } } @@ -81,6 +86,8 @@ public function testSyncActions(): void * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php * * @return void + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function testSyncActionsWithoutActionsType(): void { diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php index 302534679d073..93684b1e7a070 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php @@ -2416,6 +2416,16 @@ public function validateRowDataProvider() 'behavior' => Import::BEHAVIOR_REPLACE, 'expectedResult' => true, ], + [ + 'row' => ['sku' => 'sku with whitespace ', + 'name' => 'Test', + 'product_type' => 'simple', + '_attribute_set' => 'Default', + 'price' => 10.20, + ], + 'behavior' => Import::BEHAVIOR_ADD_UPDATE, + 'expectedResult' => false, + ], ]; } diff --git a/dev/tests/integration/testsuite/Magento/CatalogWidget/Block/Product/ProductListTest.php b/dev/tests/integration/testsuite/Magento/CatalogWidget/Block/Product/ProductListTest.php index f11083dd2ba91..8bcd0001b8119 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogWidget/Block/Product/ProductListTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogWidget/Block/Product/ProductListTest.php @@ -256,4 +256,80 @@ public function testCreateAnchorCollection() "Anchor root category does not contain products of it's children." ); } + + /** + * Test that price rule condition works correctly + * + * @magentoDbIsolation disabled + * @magentoDataFixture Magento/Catalog/_files/category_with_different_price_products.php + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + * @param string $operator + * @param int $value + * @param array $matches + * @dataProvider priceFilterDataProvider + */ + public function testPriceFilter(string $operator, int $value, array $matches) + { + $encodedConditions = '^[`1`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Combine`, + `aggregator`:`all`,`value`:`1`,`new_child`:``^], + `1--1`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Product`, + `attribute`:`price`, + `operator`:`' . $operator . '`,`value`:`' . $value . '`^]^]'; + + $this->block->setData('conditions_encoded', $encodedConditions); + + $productCollection = $this->block->createCollection(); + $productCollection->load(); + $skus = array_map( + function ($item) { + return $item['sku']; + }, + $productCollection->getItems() + ); + $this->assertEquals($matches, $skus, '', 0.0, 10, true); + } + + public function priceFilterDataProvider(): array + { + return [ + [ + '>', + 10, + [ + 'simple1001', + ] + ], + [ + '>=', + 10, + [ + 'simple1000', + 'simple1001', + 'configurable', + ] + ], + [ + '<', + 10, + [] + ], + [ + '<', + 20, + [ + 'simple1000', + 'configurable', + ] + ], + [ + '<=', + 20, + [ + 'simple1000', + 'simple1001', + 'configurable', + ] + ], + ]; + } } diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/Orders/RenderOrdersTabTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/Orders/RenderOrdersTabTest.php new file mode 100644 index 0000000000000..1d18814487bf8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/Orders/RenderOrdersTabTest.php @@ -0,0 +1,433 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Block\Adminhtml\Edit\Tab\Orders; + +use Magento\Customer\Block\Adminhtml\Edit\Tab\Orders; +use Magento\Customer\Controller\RegistryConstants; +use Magento\Directory\Model\Currency; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Locale\CurrencyInterface; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Registry; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; +use Magento\Framework\View\Element\UiComponent\DataProvider\Document; +use Magento\Framework\View\LayoutInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Store\Model\System\Store; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Helper\Xpath; +use PHPUnit\Framework\TestCase; + +/** + * Test cases related to check that orders tab with customer orders + * grid correctly renders and contains all necessary data. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @magentoAppArea adminhtml + * @magentoDbIsolation enabled + */ +class RenderOrdersTabTest extends TestCase +{ + private const PATHS_TO_TABLE_BODY = [ + "//div[contains(@data-grid-id, 'customer_orders_grid')]", + "//table[contains(@class, 'data-grid')]", + "//tbody", + ]; + + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var Store + */ + private $store; + + /** + * @var LayoutInterface + */ + private $layout; + + /** + * @var CurrencyInterface + */ + private $currency; + + /** + * @var TimezoneInterface + */ + private $timezone; + + /** + * @var Registry + */ + private $registry; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var Orders + */ + private $ordersGridBlock; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->store = $this->objectManager->get(Store::class); + $this->layout = $this->objectManager->get(LayoutInterface::class); + $this->currency = $this->objectManager->get(CurrencyInterface::class); + $this->timezone = $this->objectManager->get(TimezoneInterface::class); + $this->registry = $this->objectManager->get(Registry::class); + $this->scopeConfig = $this->objectManager->get(ScopeConfigInterface::class); + parent::setUp(); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->registry->unregister(RegistryConstants::CURRENT_CUSTOMER_ID); + parent::tearDown(); + } + + /** + * Assert that customer orders tab renders with message "We couldn't find any records." + * when customer doesn't have any orders. + * + * @magentoDataFixture Magento/Customer/_files/customer.php + * + * @return void + */ + public function testRenderBlockWithoutOrders(): void + { + $this->processCheckOrdersGridByCustomerId(1, 0); + } + + /** + * Assert that customer orders tab renders without message "We couldn't find any records." + * and contains rendered order item when customer has one order. + * + * @magentoDataFixture Magento/Sales/_files/order_with_customer.php + * + * @return void + */ + public function testRenderBlockWithOneOrder(): void + { + $this->processCheckOrdersGridByCustomerId(1, 1); + } + + /** + * Assert that customer orders tab renders without message "We couldn't find any records." + * and contains rendered orders items when customer has few orders. + * + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDataFixture Magento/Sales/_files/orders_with_customer.php + * + * @return void + */ + public function testRenderBlockWithFewOrders(): void + { + $this->processCheckOrdersGridByCustomerId(1, 5); + } + + /** + * Render orders grid and assert that all data rendered as expected. + * + * @param int $customerId + * @param int $expectedOrderCount + * @return void + */ + private function processCheckOrdersGridByCustomerId(int $customerId, int $expectedOrderCount): void + { + $this->registerCustomerId($customerId); + $ordersGridHtml = $this->getOrdersGridHtml(); + $orderItemsData = $this->getOrderGridItemsData(); + $this->assertOrdersCount($expectedOrderCount, $ordersGridHtml); + $this->assertIsEmptyGridMessageArrears($ordersGridHtml, $expectedOrderCount === 0); + $this->checkOrderItemsFields($orderItemsData, $ordersGridHtml); + } + + /** + * Add customer id to registry. + * + * @param int $customerId + * @return void + */ + private function registerCustomerId(int $customerId): void + { + $this->registry->unregister(RegistryConstants::CURRENT_CUSTOMER_ID); + $this->registry->register(RegistryConstants::CURRENT_CUSTOMER_ID, $customerId); + } + + /** + * Render customer orders tab. + * + * @return string + */ + private function getOrdersGridHtml(): string + { + $this->ordersGridBlock = $this->layout->createBlock(Orders::class); + + return $this->ordersGridBlock->toHtml(); + } + + /** + * Check that rendered html contains all provided order items. + * + * @param array $orderItemsData + * @param string $html + * @return void + */ + private function checkOrderItemsFields(array $orderItemsData, string $html): void + { + foreach ($orderItemsData as $itemOrder => $orderItemData) { + $this->assertViewOrderUrl($itemOrder, $orderItemData['order_id'], $html); + $this->assertReorderUrl($itemOrder, $orderItemData['order_id'], $html); + $this->assertStoreViewLabels($itemOrder, $orderItemData['store_view_labels'], $html); + unset($orderItemData['order_id'], $orderItemData['store_view_labels']); + $this->assertColumnsValues($itemOrder, $orderItemData, $html); + } + } + + /** + * Assert that field store_id contains all provided store codes. + * + * @param int $itemOrder + * @param array $storeViewLabels + * @param string $html + * @return void + */ + private function assertStoreViewLabels(int $itemOrder, array $storeViewLabels, string $html): void + { + if (empty($storeViewLabels)) { + return; + } + + $elementPaths = array_merge(self::PATHS_TO_TABLE_BODY, [ + "//tr[{$itemOrder}]", + "//td[contains(@class, 'store_id') and %s]", + ]); + $storeLabelsPaths = []; + foreach ($storeViewLabels as $labelIndex => $storeViewLabel) { + $storeLabelsPaths[] = "contains(text()[{$labelIndex}], '{$storeViewLabel}')"; + } + $checkStoreViewsXPath = sprintf(implode('', $elementPaths), implode(' and ', $storeLabelsPaths)); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath($checkStoreViewsXPath, $html), + sprintf("Some store view label not found. Labels: %s. Html: %s", implode(', ', $storeViewLabels), $html) + ); + } + + /** + * Assert that columns values as expected. + * + * @param int $itemOrder + * @param array $columnsData + * @param string $html + * @return void + */ + private function assertColumnsValues(int $itemOrder, array $columnsData, string $html): void + { + $elementPaths = array_merge(self::PATHS_TO_TABLE_BODY, [ + "//tr[{$itemOrder}]", + "//td[contains(@class, '%s') and contains(text(), '%s')]", + ]); + $elementXPathTemplate = implode('', $elementPaths); + foreach ($columnsData as $columnName => $columnValue) { + $preparedXPath = sprintf($elementXPathTemplate, $columnName, $columnValue); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath($preparedXPath, $html), + sprintf("Column %s doesn't have value %s. Html: %s", $columnName, $columnValue, $html) + ); + } + } + + /** + * Assert that rendered html contains URL to reorder by order id. + * + * @param int $itemOrder + * @param int $orderId + * @param string $html + * @return void + */ + private function assertReorderUrl(int $itemOrder, int $orderId, string $html): void + { + $urlLabel = (string)__('Reorder'); + $elementPaths = array_merge(self::PATHS_TO_TABLE_BODY, [ + "//tr[{$itemOrder}]", + "//td[contains(@class, 'action')]", + "//a[contains(@href, 'sales/order_create/reorder/order_id/$orderId') and contains(text(), '{$urlLabel}')]", + ]); + $reorderUrlXPath = implode('', $elementPaths); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath($reorderUrlXPath, $html), + sprintf('Reorder URL is not as expected. Html: %s', $html) + ); + } + + /** + * Assert that rendered html contains URL to order view by order id. + * + * @param int $itemOrder + * @param int $orderId + * @param string $html + * @return void + */ + private function assertViewOrderUrl(int $itemOrder, int $orderId, string $html): void + { + $elementPaths = array_merge(self::PATHS_TO_TABLE_BODY, [ + "//tr[{$itemOrder}][contains(@title, 'sales/order/view/order_id/{$orderId}')]", + ]); + $viewOrderUrlXPath = implode('', $elementPaths); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath($viewOrderUrlXPath, $html), + sprintf('URL to view order is not as expected. Html: %s', $html) + ); + } + + /** + * Assert that provided orders count and count in html are equals. + * + * @param int $expectedOrdersCount + * @param string $html + * @return void + */ + private function assertOrdersCount(int $expectedOrdersCount, string $html): void + { + $elementPaths = [ + "//div[contains(@data-grid-id, 'customer_orders_grid')]", + "//div[contains(@class, 'grid-header-row')]", + "//div[contains(@class, 'control-support-text')]", + sprintf("//span[contains(@id, 'grid-total-count') and contains(text(), '%s')]", $expectedOrdersCount), + ]; + $ordersCountXPath = implode('', $elementPaths); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath($ordersCountXPath, $html), + sprintf('Provided count and count in html are not equals. Html: %s', $html) + ); + } + + /** + * Assert that grid contains or not contains message "We couldn't find any records.". + * + * @param string $html + * @param bool $isMessageAppears + * @return void + */ + private function assertIsEmptyGridMessageArrears(string $html, bool $isMessageAppears = false): void + { + $gridText = (string)__("We couldn't find any records."); + $elementPaths = array_merge(self::PATHS_TO_TABLE_BODY, [ + "//tr[contains(@class, 'tr-no-data')]", + "//td[contains(@class, 'empty-text') and contains(text(), \"{$gridText}\")]", + ]); + $emptyTextXPath = implode('', $elementPaths); + $this->assertEquals( + $isMessageAppears ? 1 : 0, + Xpath::getElementsCountForXpath($emptyTextXPath, $html), + sprintf('Message "We couldn\'t find any records." not found in html. Html: %s', $html) + ); + } + + /** + * Build array with rendered orders for check that all contained data appears. + * + * @return array + */ + private function getOrderGridItemsData(): array + { + $orders = []; + $orderNumber = 1; + /** @var Document $order */ + foreach ($this->ordersGridBlock->getCollection() as $order) { + $orderGrandTotal = $this->prepareGrandTotal( + $order->getData('grand_total'), + $order->getData('order_currency_code') + ); + $orders[$orderNumber] = [ + 'order_id' => (int)$order->getData(OrderInterface::ENTITY_ID), + 'increment_id' => $order->getData(OrderInterface::INCREMENT_ID), + 'created_at' => $this->prepareCreatedAtDate($order->getData(OrderInterface::CREATED_AT)), + 'billing_name' => $order->getData('billing_name'), + 'shipping_name' => $order->getData('shipping_name'), + 'grand_total' => $orderGrandTotal, + 'store_view_labels' => $this->prepareStoreViewLabels([$order->getData(OrderInterface::STORE_ID)]), + ]; + $orderNumber++; + } + + return $orders; + } + + /** + * Normalize created at date. + * + * @param string $createdAt + * @return string + */ + private function prepareCreatedAtDate(string $createdAt): string + { + $date = new \DateTime($createdAt); + + return $this->timezone->formatDateTime($date, \IntlDateFormatter::MEDIUM, \IntlDateFormatter::MEDIUM); + } + + /** + * Normalize grand total. + * + * @param string $grandTotal + * @param string|null $orderCurrencyCode + * @return string + */ + private function prepareGrandTotal(string $grandTotal, ?string $orderCurrencyCode = null): string + { + $resultGrandTotal = sprintf("%f", (float)$grandTotal * 1.0); + $orderCurrencyCode = $orderCurrencyCode ?: + $this->scopeConfig->getValue(Currency::XML_PATH_CURRENCY_BASE, 'default'); + + return $this->currency->getCurrency($orderCurrencyCode)->toCurrency($resultGrandTotal); + } + + /** + * Normalize store ids. + * + * @param array $orderStoreIds + * @return array + */ + private function prepareStoreViewLabels(array $orderStoreIds): array + { + $result = []; + $storeStructure = $this->store->getStoresStructure(false, $orderStoreIds); + $textIndex = 0; + foreach ($storeStructure as $website) { + $textIndex++; + foreach ($website['children'] as $group) { + $textIndex++; + foreach ($group['children'] as $store) { + $textIndex++; + $result[$textIndex] = $store['label']; + } + } + } + + return $result; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/NewsletterTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/NewsletterTest.php new file mode 100644 index 0000000000000..8778a00e81499 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/NewsletterTest.php @@ -0,0 +1,89 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Block; + +use Magento\Customer\Model\Session; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\View\LayoutInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Helper\Xpath; +use PHPUnit\Framework\TestCase; + +/** + * Class check newsletter subscription block behavior + * + * @see \Magento\Customer\Block\Newsletter + * @magentoAppArea frontend + * @magentoDbIsolation enabled + * @magentoDataFixture Magento/Customer/_files/customer.php + */ +class NewsletterTest extends TestCase +{ + private const LABEL_XPATH = "//form[contains(@class, 'form-newsletter-manage')]" + . "//span[contains(text(), 'Subscription option')]"; + private const CHECKBOX_XPATH = "//form[contains(@class, 'form-newsletter-manage')]" + . "//input[@type='checkbox' and @name='is_subscribed']"; + private const CHECKBOX_TITLE_XPATH = "//form[contains(@class, 'form-newsletter-manage')]" + . "//label/span[contains(text(), 'General Subscription')]"; + private const SAVE_BUTTON_XPATH = "//form[contains(@class, 'form-newsletter-manage')]" + . "//button[@type='submit']/span[contains(text(), 'Save')]"; + + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var LayoutInterface */ + private $layout; + + /** @var Newsletter */ + private $block; + + /** @var Session */ + private $customerSession; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->layout = $this->objectManager->get(LayoutInterface::class); + $this->block = $this->layout->createBlock(Newsletter::class); + $this->customerSession = $this->objectManager->get(Session::class); + } + + /** + * @return void + */ + public function testSubscriptionCheckbox(): void + { + $this->customerSession->loginById(1); + $html = $this->block->toHtml(); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath(self::LABEL_XPATH, $html), + 'Subscription label is not present on the page' + ); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath(self::CHECKBOX_XPATH, $html), + 'Subscription checkbox is not present on the page' + ); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath(self::CHECKBOX_TITLE_XPATH, $html), + 'Subscription checkbox label is not present on the page' + ); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath(self::SAVE_BUTTON_XPATH, $html), + 'Subscription save button is not present on the page' + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Address/Delete/DeleteAddressTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Address/Delete/DeleteAddressTest.php new file mode 100644 index 0000000000000..86a443d7aa3e1 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Address/Delete/DeleteAddressTest.php @@ -0,0 +1,217 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Controller\Address\Delete; + +use Magento\Customer\Api\AddressRepositoryInterface; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Api\Data\AddressInterface; +use Magento\Customer\Model\Session; +use Magento\Framework\App\Request\Http; +use Magento\Framework\Escaper; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Message\MessageInterface; +use Magento\TestFramework\TestCase\AbstractController; + +/** + * Test cases related to check that customer address correctly deleted on frontend + * or wasn't deleted and proper error message appears. + * + * @magentoAppArea frontend + * @magentoDbIsolation enabled + * + * @see \Magento\Customer\Controller\Address\Delete::execute + */ +class DeleteAddressTest extends AbstractController +{ + /** + * @var Escaper + */ + private $escaper; + + /** + * @var Session + */ + private $customerSession; + + /** + * @var AddressRepositoryInterface + */ + private $addressRepository; + + /** + * @var CustomerRepositoryInterface + */ + private $customerRepository; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->escaper = $this->_objectManager->get(Escaper::class); + $this->customerSession = $this->_objectManager->get(Session::class); + $this->addressRepository = $this->_objectManager->get(AddressRepositoryInterface::class); + $this->customerRepository = $this->_objectManager->get(CustomerRepositoryInterface::class); + } + + /** + * Assert that customer address deleted successfully. + * + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDataFixture Magento/Customer/_files/customer_address.php + * + * @return void + */ + public function testSuccessDeleteExistCustomerAddress(): void + { + $customer = $this->customerRepository->get('customer@example.com'); + $customerAddresses = $customer->getAddresses() ?? []; + $this->assertCount(1, $customerAddresses); + /** @var AddressInterface $currentCustomerAddress */ + $currentCustomerAddress = reset($customerAddresses); + $this->customerSession->setCustomerId($customer->getId()); + $this->performAddressDeleteRequest((int)$currentCustomerAddress->getId()); + $this->checkRequestPerformedSuccessfully(); + $customer = $this->customerRepository->get('customer@example.com'); + $this->assertCount(0, $customer->getAddresses() ?? []); + try { + $this->addressRepository->getById((int)$currentCustomerAddress->getId()); + $this->fail('Customer address is not deleted.'); + } catch (LocalizedException $e) { + //Do nothing, this block mean that address deleted successfully from DB. + } + } + + /** + * Check that customer address will not be deleted if we don't pass address ID parameter. + * + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDataFixture Magento/Customer/_files/customer_address.php + * + * @return void + */ + public function testDeleteWithoutParam(): void + { + $customer = $this->customerRepository->get('customer@example.com'); + $customerAddresses = $customer->getAddresses() ?? []; + $this->assertCount(1, $customerAddresses); + /** @var AddressInterface $currentCustomerAddress */ + $currentCustomerAddress = reset($customerAddresses); + $this->customerSession->setCustomerId($customer->getId()); + $this->performAddressDeleteRequest(); + $this->assertRedirect($this->stringContains('customer/address/index')); + $customer = $this->customerRepository->get('customer@example.com'); + $this->assertCount(1, $customer->getAddresses() ?? []); + $this->checkAddressWasntDeleted((int)$currentCustomerAddress->getId()); + } + + /** + * Check that customer address will not be deleted if customer id in address and in session are not equals. + * + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDataFixture Magento/Customer/_files/customer_address.php + * @magentoDataFixture Magento/Customer/_files/customer_with_uk_address.php + * + * @return void + */ + public function testDeleteDifferentCustomerAddress(): void + { + $firstCustomer = $this->customerRepository->get('customer@example.com'); + $customerAddresses = $firstCustomer->getAddresses() ?? []; + $this->assertCount(1, $customerAddresses); + /** @var AddressInterface $currentCustomerAddress */ + $currentCustomerAddress = reset($customerAddresses); + $this->customerSession->setCustomerId('1'); + $secondCustomer = $this->customerRepository->get('customer_uk_address@test.com'); + $secondCustomerAddresses = $secondCustomer->getAddresses() ?? []; + /** @var AddressInterface $secondCustomerAddress */ + $secondCustomerAddress = reset($secondCustomerAddresses); + $this->performAddressDeleteRequest((int)$secondCustomerAddress->getId()); + $this->checkRequestPerformedWithError(true); + $firstCustomer = $this->customerRepository->get('customer@example.com'); + $this->assertCount(1, $firstCustomer->getAddresses() ?? []); + $this->checkAddressWasntDeleted((int)$currentCustomerAddress->getId()); + } + + /** + * Check that error message appear if we try to delete non-exits address. + * + * @magentoDataFixture Magento/Customer/_files/customer.php + * + * @return void + */ + public function testDeleteNonExistAddress(): void + { + $customer = $this->customerRepository->get('customer@example.com'); + $this->customerSession->setCustomerId($customer->getId()); + $this->performAddressDeleteRequest(999); + $this->checkRequestPerformedWithError(); + } + + /** + * Perform delete request by provided address id. + * + * @param int|null $processAddressId + * @return void + */ + private function performAddressDeleteRequest(?int $processAddressId = null): void + { + $this->getRequest()->setMethod(Http::METHOD_POST); + if (null !== $processAddressId) { + $this->getRequest()->setPostValue(['id' => $processAddressId]); + } + $this->dispatch('customer/address/delete'); + } + + /** + * Check that delete address request performed successfully + * (proper success message and redirect to customer/address/index are appear). + * + * @return void + */ + private function checkRequestPerformedSuccessfully(): void + { + $this->assertRedirect($this->stringContains('customer/address/index')); + $this->assertSessionMessages( + $this->equalTo([(string)__('You deleted the address.')]), + MessageInterface::TYPE_SUCCESS + ); + } + + /** + * Check that delete address request performed with error. + * (proper error messages and redirect to customer/address/edit are appear). + * + * @param bool $isNeedEscapeMessage + * @return void + */ + private function checkRequestPerformedWithError(bool $isNeedEscapeMessage = false): void + { + $message = (string)__("We can't delete the address right now."); + if ($isNeedEscapeMessage) { + $message = $this->escaper->escapeHtml($message); + } + $this->assertSessionMessages($this->contains($message), MessageInterface::TYPE_ERROR); + } + + /** + * Assert that customer address wasn't deleted. + * + * @param int $addressId + * @return void + */ + private function checkAddressWasntDeleted(int $addressId): void + { + try { + $this->addressRepository->getById($addressId); + } catch (LocalizedException $e) { + $this->fail('Expects that customer address will not be deleted.'); + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Address/FormPost/CreateAddressTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Address/FormPost/CreateAddressTest.php new file mode 100644 index 0000000000000..1e152008043a7 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Address/FormPost/CreateAddressTest.php @@ -0,0 +1,391 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Controller\Address\FormPost; + +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Api\Data\AddressInterface; +use Magento\Customer\Api\Data\RegionInterface; +use Magento\Customer\Model\CustomerRegistry; +use Magento\Customer\Model\Session; +use Magento\Framework\App\Request\Http; +use Magento\Framework\Escaper; +use Magento\Framework\Message\MessageInterface; +use Magento\TestFramework\Directory\Model\GetRegionIdByName; +use Magento\TestFramework\TestCase\AbstractController; + +/** + * Test cases related to check that customer address correctly created from + * customer account page on frontend or wasn't create and proper error message appears. + * + * @magentoDataFixture Magento/Customer/_files/customer_no_address.php + * + * @magentoAppArea frontend + * @magentoDbIsolation enabled + * + * @see \Magento\Customer\Controller\Address\FormPost::execute + */ +class CreateAddressTest extends AbstractController +{ + /** + * POST static data for create customer address via controller on frontend. + */ + private const STATIC_POST_ADDRESS_DATA = [ + AddressInterface::TELEPHONE => '+380505282812', + AddressInterface::POSTCODE => 75477, + AddressInterface::COUNTRY_ID => 'US', + 'custom_region_name' => 'Alabama', + AddressInterface::CITY => 'CityM', + AddressInterface::STREET => [ + 'Green str, 67', + ], + AddressInterface::FIRSTNAME => 'John', + AddressInterface::LASTNAME => 'Smith', + ]; + + /** + * @var Escaper + */ + private $escaper; + + /** + * @var Session + */ + private $customerSession; + + /** + * @var CustomerRegistry + */ + private $customerRegistry; + + /** + * @var GetRegionIdByName + */ + private $getRegionIdByName; + + /** + * @var CustomerRepositoryInterface + */ + private $customerRepository; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->escaper = $this->_objectManager->get(Escaper::class); + $this->customerSession = $this->_objectManager->get(Session::class); + $this->customerRegistry = $this->_objectManager->get(CustomerRegistry::class); + $this->getRegionIdByName = $this->_objectManager->get(GetRegionIdByName::class); + $this->customerRepository = $this->_objectManager->get(CustomerRepositoryInterface::class); + $this->customerSession->setCustomerId('5'); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->customerSession->setCustomerId(null); + $this->customerRegistry->removeByEmail('customer5@example.com'); + parent::tearDown(); + } + + /** + * Assert that default or non-default customer address successfully created via controller on frontend. + * + * @dataProvider postDataForSuccessCreateDefaultAddressDataProvider + * + * @param array $postData + * @param bool $isShippingDefault + * @param bool $isBillingDefault + * @return void + */ + public function testAddressSuccessfullyCreatedAsDefaultForCustomer( + array $postData, + bool $isShippingDefault, + bool $isBillingDefault + ): void { + $customer = $this->customerRepository->get('customer5@example.com'); + $this->assertNull($customer->getDefaultShipping(), 'Customer already have default shipping address'); + $this->assertNull($customer->getDefaultBilling(), 'Customer already have default billing address'); + $this->assertEmpty($customer->getAddresses(), 'Customer already has address'); + $this->performRequestWithData($postData); + $this->checkRequestPerformedSuccessfully(); + $customer = $this->customerRepository->get('customer5@example.com'); + $customerAddresses = $customer->getAddresses(); + $this->assertCount(1, $customerAddresses); + /** @var AddressInterface $address */ + $address = reset($customerAddresses); + $expectedShippingId = $isShippingDefault ? $address->getId() : null; + $expectedBillingId = $isBillingDefault ? $address->getId() : null; + $this->assertEquals($expectedShippingId, $customer->getDefaultShipping()); + $this->assertEquals($expectedBillingId, $customer->getDefaultBilling()); + } + + /** + * Data provider which contain proper POST data for create default or non-default customer address. + * + * @return array + */ + public function postDataForSuccessCreateDefaultAddressDataProvider(): array + { + return [ + 'any_addresses_are_default' => [ + array_merge( + self::STATIC_POST_ADDRESS_DATA, + [AddressInterface::DEFAULT_SHIPPING => 0, AddressInterface::DEFAULT_BILLING => 0] + ), + false, + false, + ], + 'shipping_address_is_default' => [ + array_merge( + self::STATIC_POST_ADDRESS_DATA, + [AddressInterface::DEFAULT_SHIPPING => 1, AddressInterface::DEFAULT_BILLING => 0] + ), + true, + false, + ], + 'billing_address_is_default' => [ + array_merge( + self::STATIC_POST_ADDRESS_DATA, + [AddressInterface::DEFAULT_SHIPPING => 0, AddressInterface::DEFAULT_BILLING => 1] + ), + false, + true, + ], + 'all_addresses_are_default' => [ + array_merge( + self::STATIC_POST_ADDRESS_DATA, + [AddressInterface::DEFAULT_SHIPPING => 1, AddressInterface::DEFAULT_BILLING => 1] + ), + true, + true, + ], + ]; + } + + /** + * Assert that customer address successfully created via controller on frontend. + * + * @dataProvider postDataForSuccessCreateAddressDataProvider + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testAddressSuccessfullyCreatedForCustomer(array $postData, array $expectedData): void + { + if (isset($expectedData['custom_region_name'])) { + $expectedData[AddressInterface::REGION_ID] = $this->getRegionIdByName->execute( + $expectedData['custom_region_name'], + $expectedData[AddressInterface::COUNTRY_ID] + ); + unset($expectedData['custom_region_name']); + } + $this->performRequestWithData($postData); + $this->checkRequestPerformedSuccessfully(); + $customer = $this->customerRepository->get('customer5@example.com'); + $customerAddresses = $customer->getAddresses(); + $this->assertCount(1, $customerAddresses); + /** @var AddressInterface $address */ + $address = reset($customerAddresses); + $createdAddressData = $address->__toArray(); + foreach ($expectedData as $fieldCode => $expectedValue) { + $this->assertArrayHasKey($fieldCode, $createdAddressData, "Field $fieldCode wasn't found."); + $this->assertEquals($expectedValue, $createdAddressData[$fieldCode]); + } + } + + /** + * Data provider which contain proper POST data for create customer address. + * + * @return array + */ + public function postDataForSuccessCreateAddressDataProvider(): array + { + return [ + 'required_fields_valid_data' => [ + self::STATIC_POST_ADDRESS_DATA, + [ + AddressInterface::TELEPHONE => '+380505282812', + AddressInterface::COUNTRY_ID => 'US', + AddressInterface::POSTCODE => 75477, + 'custom_region_name' => 'Alabama', + AddressInterface::FIRSTNAME => 'John', + AddressInterface::LASTNAME => 'Smith', + AddressInterface::STREET => ['Green str, 67'], + AddressInterface::CITY => 'CityM', + ], + ], + 'required_field_empty_postcode_for_uk' => [ + array_replace( + self::STATIC_POST_ADDRESS_DATA, + [AddressInterface::POSTCODE => '', AddressInterface::COUNTRY_ID => 'GB'] + ), + [ + AddressInterface::COUNTRY_ID => 'GB', + AddressInterface::POSTCODE => null, + ], + ], + 'required_field_empty_region_id_for_ua' => [ + array_replace( + self::STATIC_POST_ADDRESS_DATA, + [AddressInterface::REGION_ID => '', AddressInterface::COUNTRY_ID => 'UA'] + ), + [ + AddressInterface::COUNTRY_ID => 'UA', + AddressInterface::REGION => [ + RegionInterface::REGION => null, + RegionInterface::REGION_CODE => null, + RegionInterface::REGION_ID => 0, + ], + ], + ], + 'required_field_street_as_array' => [ + array_replace(self::STATIC_POST_ADDRESS_DATA, [AddressInterface::STREET => ['', 'Green str, 67']]), + [AddressInterface::STREET => ['Green str, 67']], + ], + 'field_company_name' => [ + array_merge(self::STATIC_POST_ADDRESS_DATA, [AddressInterface::COMPANY => 'My company']), + [AddressInterface::COMPANY => 'My company'], + ], + 'field_vat_number' => [ + array_merge(self::STATIC_POST_ADDRESS_DATA, [AddressInterface::VAT_ID => 'My VAT number']), + [AddressInterface::VAT_ID => 'My VAT number'], + ], + ]; + } + + /** + * Assert that customer address wasn't created via controller on frontend + * when POST data broken. + * + * @dataProvider postDataForCreateAddressWithErrorDataProvider + * + * @param array $postData + * @param array $expectedSessionMessages + * @return void + */ + public function testAddressWasntCreatedForCustomer(array $postData, array $expectedSessionMessages): void + { + $this->performRequestWithData($postData); + $this->checkRequestPerformedWithInputValidationErrors($expectedSessionMessages); + } + + /** + * Data provider which contain broken POST data for create customer address with error. + * + * @return array + */ + public function postDataForCreateAddressWithErrorDataProvider(): array + { + return [ + 'empty_post_data' => [ + [], + [ + 'One or more input exceptions have occurred.', + '"firstname" is required. Enter and try again.', + '"lastname" is required. Enter and try again.', + '"street" is required. Enter and try again.', + '"city" is required. Enter and try again.', + '"telephone" is required. Enter and try again.', + '"postcode" is required. Enter and try again.', + '"countryId" is required. Enter and try again.', + ] + ], + 'required_field_empty_telephone' => [ + array_replace(self::STATIC_POST_ADDRESS_DATA, [AddressInterface::TELEPHONE => '']), + ['"telephone" is required. Enter and try again.'], + ], + 'required_field_empty_postcode_for_us' => [ + array_replace(self::STATIC_POST_ADDRESS_DATA, [AddressInterface::POSTCODE => '']), + ['"postcode" is required. Enter and try again.'], + ], +// TODO: Uncomment this variation after fix issue https://jira.corp.magento.com/browse/MC-31031 +// 'required_field_empty_region_id_for_us' => [ +// array_replace(self::STATIC_POST_ADDRESS_DATA, [AddressInterface::REGION_ID => '']), +// ['"regionId" is required. Enter and try again.'], +// ], + 'required_field_empty_firstname' => [ + array_replace(self::STATIC_POST_ADDRESS_DATA, [AddressInterface::FIRSTNAME => '']), + ['"firstname" is required. Enter and try again.'], + ], + 'required_field_empty_lastname' => [ + array_replace(self::STATIC_POST_ADDRESS_DATA, [AddressInterface::LASTNAME => '']), + ['"lastname" is required. Enter and try again.'], + ], + 'required_field_empty_street_as_string' => [ + array_replace(self::STATIC_POST_ADDRESS_DATA, [AddressInterface::STREET => '']), + ['"street" is required. Enter and try again.'], + ], + 'required_field_empty_street_as_array' => [ + array_replace(self::STATIC_POST_ADDRESS_DATA, [AddressInterface::STREET => []]), + ['"street" is required. Enter and try again.'], + ], + 'required_field_empty_city' => [ + array_replace(self::STATIC_POST_ADDRESS_DATA, [AddressInterface::CITY => '']), + ['"city" is required. Enter and try again.'], + ], + ]; + } + + /** + * Perform request with provided POST data. + * + * @param array $postData + * @return void + */ + private function performRequestWithData(array $postData): void + { + if (isset($postData['custom_region_name'])) { + $postData[AddressInterface::REGION_ID] = $this->getRegionIdByName->execute( + $postData['custom_region_name'], + $postData[AddressInterface::COUNTRY_ID] + ); + unset($postData['custom_region_name']); + } + + $this->getRequest()->setPostValue($postData)->setMethod(Http::METHOD_POST); + $this->dispatch('customer/address/formPost'); + } + + /** + * Check that save address request performed successfully + * (proper success message and redirect to customer/address/index are appear). + * + * @return void + */ + private function checkRequestPerformedSuccessfully(): void + { + $this->assertRedirect($this->stringContains('customer/address/index')); + $this->assertSessionMessages( + $this->equalTo([(string)__('You saved the address.')]), + MessageInterface::TYPE_SUCCESS + ); + } + + /** + * Check that save address request performed with input validation errors + * (proper error messages and redirect to customer/address/edit are appear). + * + * @param array $expectedSessionMessages + * @return void + */ + private function checkRequestPerformedWithInputValidationErrors(array $expectedSessionMessages): void + { + $this->assertRedirect($this->stringContains('customer/address/edit')); + foreach ($expectedSessionMessages as $expectedMessage) { + $this->assertSessionMessages( + $this->contains($this->escaper->escapeHtml((string)__($expectedMessage))), + MessageInterface::TYPE_ERROR + ); + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Address/FormPost/UpdateAddressTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Address/FormPost/UpdateAddressTest.php new file mode 100644 index 0000000000000..fb18cc45511c8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Address/FormPost/UpdateAddressTest.php @@ -0,0 +1,403 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Controller\Address\FormPost; + +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Api\Data\AddressInterface; +use Magento\Customer\Api\Data\RegionInterface; +use Magento\Customer\Model\AddressRegistry; +use Magento\Customer\Model\CustomerRegistry; +use Magento\Customer\Model\Session; +use Magento\Framework\App\Request\Http; +use Magento\Framework\Escaper; +use Magento\Framework\Message\MessageInterface; +use Magento\TestFramework\Directory\Model\GetRegionIdByName; +use Magento\TestFramework\TestCase\AbstractController; + +/** + * Test cases related to check that customer address correctly updated from + * customer account page on frontend or wasn't updated and proper error message appears. + * + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDataFixture Magento/Customer/_files/customer_address.php + * + * @magentoAppArea frontend + * @magentoDbIsolation enabled + * + * @see \Magento\Customer\Controller\Address\FormPost::execute + */ +class UpdateAddressTest extends AbstractController +{ + /** + * POST static data for update customer address via controller on frontend. + */ + private const STATIC_POST_ADDRESS_DATA = [ + AddressInterface::TELEPHONE => 9548642, + AddressInterface::POSTCODE => 95556, + AddressInterface::COUNTRY_ID => 'US', + 'custom_region_name' => 'Arkansas', + AddressInterface::CITY => 'Mukachevo', + AddressInterface::STREET => [ + 'Yellow str, 228', + ], + AddressInterface::FIRSTNAME => 'Foma', + AddressInterface::LASTNAME => 'Kiniaev', + ]; + + /** + * @var Escaper + */ + private $escaper; + + /** + * @var Session + */ + private $customerSession; + + /** + * @var AddressRegistry + */ + private $addressRegistry; + + /** + * @var CustomerRegistry + */ + private $customerRegistry; + + /** + * @var GetRegionIdByName + */ + private $getRegionIdByName; + + /** + * @var CustomerRepositoryInterface + */ + private $customerRepository; + + /** + * @var array + */ + private $processedAddressesIds = []; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->escaper = $this->_objectManager->get(Escaper::class); + $this->customerSession = $this->_objectManager->get(Session::class); + $this->addressRegistry = $this->_objectManager->get(AddressRegistry::class); + $this->customerRegistry = $this->_objectManager->get(CustomerRegistry::class); + $this->getRegionIdByName = $this->_objectManager->get(GetRegionIdByName::class); + $this->customerRepository = $this->_objectManager->get(CustomerRepositoryInterface::class); + $this->customerSession->setCustomerId('1'); + $this->processedAddressesIds[] = 1; + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->customerSession->setCustomerId(null); + $this->customerRegistry->removeByEmail('customer@example.com'); + foreach ($this->processedAddressesIds as $addressesId) { + $this->addressRegistry->remove($addressesId); + } + parent::tearDown(); + } + + /** + * Assert that default customer address successfully changed via controller on frontend. + * + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php + * + * @dataProvider postDataForSuccessCreateDefaultAddressDataProvider + * + * @param array $postData + * @param int $expectedShippingId + * @param int $expectedBillingId + * @return void + */ + public function testAddressSuccessfullyCreatedAsDefaultForCustomer( + array $postData, + int $expectedShippingId, + int $expectedBillingId + ): void { + $this->processedAddressesIds = [1, 2]; + $customer = $this->customerRepository->get('customer@example.com'); + $this->assertEquals(1, $customer->getDefaultShipping(), "Customer doesn't have shipping address"); + $this->assertEquals(1, $customer->getDefaultBilling(), "Customer doesn't have billing address"); + $this->performRequestWithData($postData, 2); + $this->checkRequestPerformedSuccessfully(); + $customer = $this->customerRepository->get('customer@example.com'); + $this->assertEquals($expectedShippingId, $customer->getDefaultShipping()); + $this->assertEquals($expectedBillingId, $customer->getDefaultBilling()); + } + + /** + * Data provider which contain proper POST data for change default customer address. + * + * @return array + */ + public function postDataForSuccessCreateDefaultAddressDataProvider(): array + { + return [ + 'any_addresses_are_default' => [ + array_merge( + self::STATIC_POST_ADDRESS_DATA, + [AddressInterface::DEFAULT_SHIPPING => 2, AddressInterface::DEFAULT_BILLING => 2] + ), + 2, + 2, + ], + 'shipping_address_is_default' => [ + array_merge( + self::STATIC_POST_ADDRESS_DATA, + [AddressInterface::DEFAULT_BILLING => 2] + ), + 1, + 2, + ], + 'billing_address_is_default' => [ + array_merge( + self::STATIC_POST_ADDRESS_DATA, + [AddressInterface::DEFAULT_SHIPPING => 2] + ), + 2, + 1, + ], + ]; + } + + /** + * Assert that customer address successfully updated via controller on frontend. + * + * @dataProvider postDataForSuccessUpdateAddressDataProvider + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testAddressSuccessfullyUpdatedForCustomer(array $postData, array $expectedData): void + { + if (isset($expectedData['custom_region_name'])) { + $expectedData[AddressInterface::REGION_ID] = $this->getRegionIdByName->execute( + $expectedData['custom_region_name'], + $expectedData[AddressInterface::COUNTRY_ID] + ); + unset($expectedData['custom_region_name']); + } + $this->performRequestWithData($postData, 1); + $this->checkRequestPerformedSuccessfully(); + $customer = $this->customerRepository->get('customer@example.com'); + $customerAddresses = $customer->getAddresses(); + $this->assertCount(1, $customerAddresses); + /** @var AddressInterface $address */ + $address = reset($customerAddresses); + $createdAddressData = $address->__toArray(); + foreach ($expectedData as $fieldCode => $expectedValue) { + if (null === $expectedValue) { + $this->assertArrayNotHasKey($fieldCode, $createdAddressData); + continue; + } + $this->assertArrayHasKey($fieldCode, $createdAddressData, "Field $fieldCode wasn't found."); + $this->assertEquals($expectedValue, $createdAddressData[$fieldCode]); + } + } + + /** + * Data provider which contain proper POST data for update customer address. + * + * @return array + */ + public function postDataForSuccessUpdateAddressDataProvider(): array + { + return [ + 'required_fields_valid_data' => [ + self::STATIC_POST_ADDRESS_DATA, + [ + AddressInterface::TELEPHONE => 9548642, + AddressInterface::COUNTRY_ID => 'US', + AddressInterface::POSTCODE => 95556, + 'custom_region_name' => 'Arkansas', + AddressInterface::FIRSTNAME => 'Foma', + AddressInterface::LASTNAME => 'Kiniaev', + AddressInterface::STREET => ['Yellow str, 228'], + AddressInterface::CITY => 'Mukachevo', + ], + ], + 'required_field_empty_postcode_for_uk' => [ + array_replace( + self::STATIC_POST_ADDRESS_DATA, + [AddressInterface::POSTCODE => '', AddressInterface::COUNTRY_ID => 'GB'] + ), + [ + AddressInterface::COUNTRY_ID => 'GB', + AddressInterface::POSTCODE => null, + ], + ], + 'required_field_empty_region_id_for_ua' => [ + array_replace( + self::STATIC_POST_ADDRESS_DATA, + [AddressInterface::REGION_ID => '', AddressInterface::COUNTRY_ID => 'UA'] + ), + [ + AddressInterface::COUNTRY_ID => 'UA', + AddressInterface::REGION => [ + RegionInterface::REGION => null, + RegionInterface::REGION_CODE => null, + RegionInterface::REGION_ID => 0, + ], + ], + ], + 'required_field_street_as_array' => [ + array_replace(self::STATIC_POST_ADDRESS_DATA, [AddressInterface::STREET => ['', 'Green str, 67']]), + [AddressInterface::STREET => ['Green str, 67']], + ], + 'field_company_name' => [ + array_merge(self::STATIC_POST_ADDRESS_DATA, [AddressInterface::COMPANY => 'My company']), + [AddressInterface::COMPANY => 'My company'], + ], + 'field_vat_number' => [ + array_merge(self::STATIC_POST_ADDRESS_DATA, [AddressInterface::VAT_ID => 'My VAT number']), + [AddressInterface::VAT_ID => 'My VAT number'], + ], + ]; + } + + /** + * Assert that customer address wasn't updated via controller on frontend + * when POST data broken. + * + * @dataProvider postDataForUpdateAddressWithErrorDataProvider + * + * @param array $postData + * @param array $expectedSessionMessages + * @return void + */ + public function testAddressWasntUpdatedForCustomer(array $postData, array $expectedSessionMessages): void + { + $this->performRequestWithData($postData, 1); + $this->checkRequestPerformedWithInputValidationErrors($expectedSessionMessages); + } + + /** + * Data provider which contain broken POST data for update customer address with error. + * + * @return array + */ + public function postDataForUpdateAddressWithErrorDataProvider(): array + { + return [ + 'empty_post_data' => [ + [], + [ + 'One or more input exceptions have occurred.', + '"firstname" is required. Enter and try again.', + '"lastname" is required. Enter and try again.', + '"street" is required. Enter and try again.', + '"city" is required. Enter and try again.', + '"telephone" is required. Enter and try again.', + '"postcode" is required. Enter and try again.', + '"countryId" is required. Enter and try again.', + ] + ], + 'required_field_empty_telephone' => [ + array_replace(self::STATIC_POST_ADDRESS_DATA, [AddressInterface::TELEPHONE => '']), + ['"telephone" is required. Enter and try again.'], + ], + 'required_field_empty_postcode_for_us' => [ + array_replace(self::STATIC_POST_ADDRESS_DATA, [AddressInterface::POSTCODE => '']), + ['"postcode" is required. Enter and try again.'], + ], +// TODO: Uncomment this variation after fix issue https://jira.corp.magento.com/browse/MC-31031 +// 'required_field_empty_region_id_for_us' => [ +// array_replace(self::STATIC_POST_ADDRESS_DATA, [AddressInterface::REGION_ID => '']), +// ['"regionId" is required. Enter and try again.'], +// ], + 'required_field_empty_firstname' => [ + array_replace(self::STATIC_POST_ADDRESS_DATA, [AddressInterface::FIRSTNAME => '']), + ['"firstname" is required. Enter and try again.'], + ], + 'required_field_empty_lastname' => [ + array_replace(self::STATIC_POST_ADDRESS_DATA, [AddressInterface::LASTNAME => '']), + ['"lastname" is required. Enter and try again.'], + ], + 'required_field_empty_street_as_string' => [ + array_replace(self::STATIC_POST_ADDRESS_DATA, [AddressInterface::STREET => '']), + ['"street" is required. Enter and try again.'], + ], + 'required_field_empty_street_as_array' => [ + array_replace(self::STATIC_POST_ADDRESS_DATA, [AddressInterface::STREET => []]), + ['"street" is required. Enter and try again.'], + ], + 'required_field_empty_city' => [ + array_replace(self::STATIC_POST_ADDRESS_DATA, [AddressInterface::CITY => '']), + ['"city" is required. Enter and try again.'], + ], + ]; + } + + /** + * Perform request with provided POST data. + * + * @param array $postData + * @param int $processAddressId + * @return void + */ + private function performRequestWithData(array $postData, int $processAddressId): void + { + $postData[AddressInterface::ID] = $processAddressId; + if (isset($postData['custom_region_name'])) { + $postData[AddressInterface::REGION_ID] = $this->getRegionIdByName->execute( + $postData['custom_region_name'], + $postData[AddressInterface::COUNTRY_ID] + ); + unset($postData['custom_region_name']); + } + + $this->getRequest()->setPostValue($postData)->setMethod(Http::METHOD_POST); + $this->dispatch('customer/address/formPost'); + } + + /** + * Check that save address request performed successfully + * (proper success message and redirect to customer/address/index are appear). + * + * @return void + */ + private function checkRequestPerformedSuccessfully(): void + { + $this->assertRedirect($this->stringContains('customer/address/index')); + $this->assertSessionMessages( + $this->equalTo([(string)__('You saved the address.')]), + MessageInterface::TYPE_SUCCESS + ); + } + + /** + * Check that save address request performed with input validation errors + * (proper error messages and redirect to customer/address/edit are appear). + * + * @param array $expectedSessionMessages + * @return void + */ + private function checkRequestPerformedWithInputValidationErrors(array $expectedSessionMessages): void + { + $this->assertRedirect($this->stringContains('customer/address/edit')); + foreach ($expectedSessionMessages as $expectedMessage) { + $this->assertSessionMessages( + $this->contains($this->escaper->escapeHtml((string)__($expectedMessage))), + MessageInterface::TYPE_ERROR + ); + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Section/LoadTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Section/LoadTest.php index 3db22b8379850..0869832091fec 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Section/LoadTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Section/LoadTest.php @@ -3,19 +3,104 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Customer\Controller\Section; -class LoadTest extends \Magento\TestFramework\TestCase\AbstractController +use Magento\Customer\Model\Session; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\Serialize\SerializerInterface; +use Magento\TestFramework\TestCase\AbstractController; +use Magento\Framework\Escaper; + +/** + * Load customer data test class. + * + * @magentoDbIsolation enabled + * @magentoAppArea frontend + */ +class LoadTest extends AbstractController { - public function testLoadInvalidSection() + /** @var Session */ + private $customerSession; + + /** @var SerializerInterface */ + private $json; + + /** @var Escaper */ + private $escaper; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->customerSession = $this->_objectManager->get(Session::class); + $this->json = $this->_objectManager->get(SerializerInterface::class); + $this->escaper = $this->_objectManager->get(Escaper::class); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->customerSession->setCustomerId(null); + + parent::tearDown(); + } + + /** + * @return void + */ + public function testLoadInvalidSection(): void { - $expected = [ - 'message' => 'The "section<invalid" section source isn't supported.', - ]; + $message = $this->escaper->escapeHtml('The "section<invalid" section source isn\'t supported.'); + $expected = ['message' => $message]; $this->dispatch( '/customer/section/load/?sections=section<invalid&force_new_section_timestamp=false&_=147066166394' ); - self::assertEquals(json_encode($expected), $this->getResponse()->getBody()); + $this->assertEquals($this->json->serialize($expected), $this->getResponse()->getBody()); + } + + /** + * @magentoConfigFixture current_store wishlist/wishlist_link/use_qty 1 + * @magentoDataFixture Magento/Wishlist/_files/wishlist_with_product_qty_three.php + * + * @return void + */ + public function testWishListCounterUseQty(): void + { + $this->customerSession->setCustomerId(1); + $response = $this->performWishListSectionRequest(); + $this->assertEquals('3 items', $response['wishlist']['counter']); + } + + /** + * @magentoConfigFixture current_store wishlist/wishlist_link/use_qty 0 + * @magentoDataFixture Magento/Wishlist/_files/wishlist_with_product_qty_three.php + * + * @return void + */ + public function testWishListCounterNotUseQty(): void + { + $this->customerSession->setCustomerId(1); + $response = $this->performWishListSectionRequest(); + $this->assertEquals('1 item', $response['wishlist']['counter']); + } + + /** + * Perform wish list section request. + * + * @return array + */ + private function performWishListSectionRequest(): array + { + $this->getRequest()->setParam('sections', 'wishlist')->setMethod(HttpRequest::METHOD_GET); + $this->dispatch('customer/section/load'); + + return $this->json->unserialize($this->getResponse()->getBody()); } } diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/new_customer.php b/dev/tests/integration/testsuite/Magento/Customer/_files/new_customer.php new file mode 100644 index 0000000000000..2e298a7e508a3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/new_customer.php @@ -0,0 +1,44 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Customer\Api\AccountManagementInterface; +use Magento\Customer\Api\CustomerMetadataInterface; +use Magento\Customer\Model\Data\CustomerFactory; +use Magento\Customer\Model\GroupManagement; +use Magento\Eav\Model\AttributeRepository; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var AccountManagementInterface $accountManagement */ +$accountManagement = $objectManager->get(AccountManagementInterface::class); +$customerFactory = $objectManager->get(CustomerFactory::class); +$customerFactory->create(); +/** @var StoreManagerInterface $storeManager */ +$storeManager = $objectManager->get(StoreManagerInterface::class); +$website = $storeManager->getWebsite('base'); +/** @var GroupManagement $groupManagement */ +$groupManagement = $objectManager->get(GroupManagement::class); +$defaultStoreId = $website->getDefaultStore()->getId(); +/** @var AttributeRepository $attributeRepository */ +$attributeRepository = $objectManager->get(AttributeRepository::class); +$gender = $attributeRepository->get(CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, 'gender') + ->getSource()->getOptionId('Male'); +$customer = $customerFactory->create(); +$customer->setWebsiteId($website->getId()) + ->setEmail('new_customer@example.com') + ->setGroupId($groupManagement->getDefaultGroup($defaultStoreId)->getId()) + ->setStoreId($defaultStoreId) + ->setPrefix('Mr.') + ->setFirstname('John') + ->setMiddlename('A') + ->setLastname('Smith') + ->setSuffix('Esq.') + ->setDefaultBilling(1) + ->setDefaultShipping(1) + ->setGender($gender); +$accountManagement->createAccount($customer, 'Qwert12345'); diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/new_customer_rollback.php b/dev/tests/integration/testsuite/Magento/Customer/_files/new_customer_rollback.php new file mode 100644 index 0000000000000..48da309f6878f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/new_customer_rollback.php @@ -0,0 +1,33 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Registry; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); +/** @var CustomerRepositoryInterface $customerRepository */ +$customerRepository = $objectManager->get(CustomerRepositoryInterface::class); +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class); +$websiteId = $websiteRepository->get('base')->getId(); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +try { + $customer = $customerRepository->get('new_customer@example.com', $websiteId); + $customerRepository->delete($customer); +} catch (NoSuchEntityException $e) { + //customer already deleted +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/unconfirmed_customer.php b/dev/tests/integration/testsuite/Magento/Customer/_files/unconfirmed_customer.php index 3aad592ad34ef..e27010dc042ca 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/_files/unconfirmed_customer.php +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/unconfirmed_customer.php @@ -7,7 +7,7 @@ use Magento\Customer\Api\AccountManagementInterface; use Magento\Customer\Api\CustomerMetadataInterface; -use \Magento\Customer\Model\Data\CustomerFactory; +use Magento\Customer\Model\Data\CustomerFactory; use Magento\Eav\Model\AttributeRepository; use Magento\Framework\Math\Random; use Magento\Store\Api\WebsiteRepositoryInterface; diff --git a/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/AbstractFiltersTest.php b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/AbstractFiltersTest.php index cf39757cb8264..fc0c23b346fe2 100644 --- a/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/AbstractFiltersTest.php +++ b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/AbstractFiltersTest.php @@ -140,7 +140,7 @@ protected function getSearchFiltersAndAssert( $this->updateAttribute($attributeData); $this->updateProducts($products, $this->getAttributeCode()); $this->clearInstanceAndReindexSearch(); - $this->navigationBlock->getRequest()->setParams(['q' => 'Simple Product']); + $this->navigationBlock->getRequest()->setParams(['q' => $this->getSearchString()]); $this->navigationBlock->setLayout($this->layout); $filter = $this->getFilterByCode($this->navigationBlock->getFilters(), $this->getAttributeCode()); @@ -293,4 +293,14 @@ protected function createNavigationBlockInstance(): void $this->navigationBlock = $this->objectManager->create(CategoryNavigationBlock::class); } } + + /** + * Returns search query for filters on search page. + * + * @return string + */ + protected function getSearchString(): string + { + return 'Simple Product'; + } } diff --git a/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/Bundle/MultiselectFilterTest.php b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/Bundle/MultiselectFilterTest.php new file mode 100644 index 0000000000000..518a0a19f857f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/Bundle/MultiselectFilterTest.php @@ -0,0 +1,109 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LayeredNavigation\Block\Navigation\Category\Bundle; + +use Magento\Catalog\Model\Layer\Resolver; +use Magento\Framework\Module\Manager; +use Magento\LayeredNavigation\Block\Navigation\AbstractFiltersTest; +use Magento\Catalog\Model\Layer\Filter\AbstractFilter; + +/** + * Provides tests for custom multiselect filter in navigation block on category page with bundle products. + * + * @magentoAppArea frontend + * @magentoAppIsolation enabled + * @magentoDbIsolation disabled + */ +class MultiselectFilterTest extends AbstractFiltersTest +{ + /** + * @var Manager + */ + private $moduleManager; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->moduleManager = $this->objectManager->get(Manager::class); + //This check is needed because LayeredNavigation independent of Magento_Bundle + if (!$this->moduleManager->isEnabled('Magento_Bundle')) { + $this->markTestSkipped('Magento_Bundle module disabled.'); + } + } + + /** + * @magentoDataFixture Magento/Catalog/_files/multiselect_attribute.php + * @magentoDataFixture Magento/Bundle/_files/dynamic_and_fixed_bundle_products_in_category.php + * @dataProvider getFiltersWithCustomAttributeDataProvider + * @param array $products + * @param array $attributeData + * @param array $expectation + * @return void + */ + public function testGetFiltersWithCustomAttribute(array $products, array $attributeData, array $expectation): void + { + $this->getCategoryFiltersAndAssert($products, $attributeData, $expectation, 'Category 1'); + } + + /** + * @return array + */ + public function getFiltersWithCustomAttributeDataProvider(): array + { + return [ + 'not_used_in_navigation' => [ + 'products_data' => [], + 'attribute_data' => ['is_filterable' => 0], + 'expectation' => [], + ], + 'used_in_navigation_with_results' => [ + 'products_data' => [ + 'bundle-product' => 'Option 1', + 'bundle-product-dropdown-options' => 'Option 2', + ], + 'attribute_data' => ['is_filterable' => AbstractFilter::ATTRIBUTE_OPTIONS_ONLY_WITH_RESULTS], + 'expectation' => [ + ['label' => 'Option 1', 'count' => 1], + ['label' => 'Option 2', 'count' => 1], + ], + ], + 'used_in_navigation_without_results' => [ + 'products_data' => [ + 'bundle-product' => 'Option 1', + 'bundle-product-dropdown-options' => 'Option 2', + ], + 'attribute_data' => ['is_filterable' => 2], + 'expectation' => [ + ['label' => 'Option 1', 'count' => 1], + ['label' => 'Option 2', 'count' => 1], + ['label' => 'Option 3', 'count' => 0], + ['label' => 'Option 4 "!@#$%^&*', 'count' => 0], + ], + ], + ]; + } + + /** + * @inheritdoc + */ + protected function getLayerType(): string + { + return Resolver::CATALOG_LAYER_CATEGORY; + } + + /** + * @inheritdoc + */ + protected function getAttributeCode(): string + { + return 'multiselect_attribute'; + } +} diff --git a/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/Bundle/PriceFilterTest.php b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/Bundle/PriceFilterTest.php new file mode 100644 index 0000000000000..3817c5efc86fc --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/Bundle/PriceFilterTest.php @@ -0,0 +1,96 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LayeredNavigation\Block\Navigation\Category\Bundle; + +use Magento\Catalog\Model\Layer\Resolver; +use Magento\Framework\Module\Manager; +use Magento\LayeredNavigation\Block\Navigation\AbstractFiltersTest; +use Magento\Catalog\Model\Layer\Filter\AbstractFilter; +use Magento\Catalog\Model\Layer\Filter\Item; + +/** + * Provides price filter tests for bundle products in navigation block on category page. + * + * @magentoAppArea frontend + * @magentoAppIsolation enabled + * @magentoDbIsolation disabled + */ +class PriceFilterTest extends AbstractFiltersTest +{ + /** + * @var Manager + */ + private $moduleManager; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->moduleManager = $this->objectManager->get(Manager::class); + //This check is needed because LayeredNavigation independent of Magento_Bundle + if (!$this->moduleManager->isEnabled('Magento_Bundle')) { + $this->markTestSkipped('Magento_Bundle module disabled.'); + } + } + + /** + * @magentoDataFixture Magento/Bundle/_files/dynamic_and_fixed_bundle_products_in_category.php + * @magentoConfigFixture current_store catalog/layered_navigation/price_range_calculation manual + * @magentoConfigFixture current_store catalog/layered_navigation/price_range_step 10 + * @return void + */ + public function testGetFilters(): void + { + $this->getCategoryFiltersAndAssert( + ['bundle-product' => 20.00], + ['is_filterable' => '1'], + [ + ['label' => '$10.00 - $19.99', 'value' => '10-20', 'count' => 1], + ['label' => '$20.00 and above', 'value' => '20-', 'count' => 1], + ], + 'Category 1' + ); + } + + /** + * @inheritdoc + */ + protected function getLayerType(): string + { + return Resolver::CATALOG_LAYER_CATEGORY; + } + + /** + * @inheritdoc + */ + protected function getAttributeCode(): string + { + return 'price'; + } + + /** + * @inheritdoc + */ + protected function prepareFilterItems(AbstractFilter $filter): array + { + $items = []; + /** @var Item $item */ + foreach ($filter->getItems() as $item) { + $item = [ + 'label' => strip_tags(__($item->getData('label'))->render()), + 'value' => $item->getData('value'), + 'count' => $item->getData('count'), + ]; + $items[] = $item; + } + + return $items; + } +} diff --git a/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/Bundle/SelectFilterTest.php b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/Bundle/SelectFilterTest.php new file mode 100644 index 0000000000000..7b3be2afbdbdb --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/Bundle/SelectFilterTest.php @@ -0,0 +1,108 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LayeredNavigation\Block\Navigation\Category\Bundle; + +use Magento\Catalog\Model\Layer\Resolver; +use Magento\Framework\Module\Manager; +use Magento\LayeredNavigation\Block\Navigation\AbstractFiltersTest; +use Magento\Catalog\Model\Layer\Filter\AbstractFilter; + +/** + * Provides tests for custom select filter for bundle products in navigation block on category page. + * + * @magentoAppArea frontend + * @magentoAppIsolation enabled + * @magentoDbIsolation disabled + */ +class SelectFilterTest extends AbstractFiltersTest +{ + /** + * @var Manager + */ + private $moduleManager; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->moduleManager = $this->objectManager->get(Manager::class); + //This check is needed because LayeredNavigation independent of Magento_Bundle + if (!$this->moduleManager->isEnabled('Magento_Bundle')) { + $this->markTestSkipped('Magento_Bundle module disabled.'); + } + } + + /** + * @magentoDataFixture Magento/Catalog/_files/product_dropdown_attribute.php + * @magentoDataFixture Magento/Bundle/_files/dynamic_and_fixed_bundle_products_in_category.php + * @dataProvider getFiltersWithCustomAttributeDataProvider + * @param array $products + * @param array $attributeData + * @param array $expectation + * @return void + */ + public function testGetFiltersWithCustomAttribute(array $products, array $attributeData, array $expectation): void + { + $this->getCategoryFiltersAndAssert($products, $attributeData, $expectation, 'Category 1'); + } + + /** + * @return array + */ + public function getFiltersWithCustomAttributeDataProvider(): array + { + return [ + 'not_used_in_navigation' => [ + 'products_data' => [], + 'attribute_data' => ['is_filterable' => 0], + 'expectation' => [], + ], + 'used_in_navigation_with_results' => [ + 'products_data' => [ + 'bundle-product' => 'Option 1', + 'bundle-product-dropdown-options' => 'Option 2', + ], + 'attribute_data' => ['is_filterable' => AbstractFilter::ATTRIBUTE_OPTIONS_ONLY_WITH_RESULTS], + 'expectation' => [ + ['label' => 'Option 1', 'count' => 1], + ['label' => 'Option 2', 'count' => 1], + ], + ], + 'used_in_navigation_without_results' => [ + 'products_data' => [ + 'bundle-product' => 'Option 1', + 'bundle-product-dropdown-options' => 'Option 2', + ], + 'attribute_data' => ['is_filterable' => 2], + 'expectation' => [ + ['label' => 'Option 1', 'count' => 1], + ['label' => 'Option 2', 'count' => 1], + ['label' => 'Option 3', 'count' => 0], + ], + ], + ]; + } + + /** + * @inheritdoc + */ + protected function getLayerType(): string + { + return Resolver::CATALOG_LAYER_CATEGORY; + } + + /** + * @inheritdoc + */ + protected function getAttributeCode(): string + { + return 'dropdown_attribute'; + } +} diff --git a/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Search/Bundle/PriceFilterTest.php b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Search/Bundle/PriceFilterTest.php new file mode 100644 index 0000000000000..435dd29e16dfa --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Search/Bundle/PriceFilterTest.php @@ -0,0 +1,55 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LayeredNavigation\Block\Navigation\Search\Bundle; + +use Magento\Catalog\Model\Layer\Resolver; +use Magento\LayeredNavigation\Block\Navigation\Category\Bundle\PriceFilterTest as CategoryFilterTest; + +/** + * Provides price filter tests for bundle product in navigation block on search page. + * + * @magentoAppArea frontend + * @magentoAppIsolation enabled + * @magentoDbIsolation disabled + */ +class PriceFilterTest extends CategoryFilterTest +{ + /** + * @magentoDataFixture Magento/Bundle/_files/dynamic_and_fixed_bundle_products_in_category.php + * @magentoConfigFixture current_store catalog/layered_navigation/price_range_calculation manual + * @magentoConfigFixture current_store catalog/layered_navigation/price_range_step 10 + * @return void + */ + public function testGetFilters(): void + { + $this->getSearchFiltersAndAssert( + ['bundle-product' => 20.00], + ['is_filterable_in_search' => 1], + [ + ['label' => '$10.00 - $19.99', 'value' => '10-20', 'count' => 1], + ['label' => '$20.00 and above', 'value' => '20-', 'count' => 1], + ] + ); + } + + /** + * @inheritdoc + */ + protected function getLayerType(): string + { + return Resolver::CATALOG_LAYER_SEARCH; + } + + /** + * @inheritdoc + */ + protected function getSearchString(): string + { + return 'Bundle'; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/Block/Account/LinkTest.php b/dev/tests/integration/testsuite/Magento/Newsletter/Block/Account/LinkTest.php new file mode 100644 index 0000000000000..6dac22bd49384 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Newsletter/Block/Account/LinkTest.php @@ -0,0 +1,79 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Newsletter\Block\Account; + +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\View\Result\Page; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Checks Newsletter Subscriptions link displaying in account dashboard + * + * @magentoAppArea frontend + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + */ +class LinkTest extends TestCase +{ + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var Page */ + private $page; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->page = $this->objectManager->create(Page::class); + } + + /** + * @return void + */ + public function testNewsletterLink(): void + { + $this->preparePage(); + $block = $this->page->getLayout()->getBlock('customer-account-navigation-newsletter-subscriptions-link'); + $this->assertNotFalse($block); + $html = $block->toHtml(); + $this->assertContains('newsletter/manage/', $html); + $this->assertEquals('Newsletter Subscriptions', strip_tags($html)); + } + + /** + * @magentoConfigFixture current_store newsletter/general/active 0 + * + * @return void + */ + public function testNewsletterLinkDisabled(): void + { + $this->preparePage(); + $block = $this->page->getLayout()->getBlock('customer-account-navigation-newsletter-subscriptions-link'); + $this->assertFalse($block); + } + + /** + * Prepare page before render + * + * @return void + */ + private function preparePage(): void + { + $this->page->addHandle([ + 'default', + 'customer_account', + ]); + $this->page->getLayout()->generateXml(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/Controller/Manage/SaveTest.php b/dev/tests/integration/testsuite/Magento/Newsletter/Controller/Manage/SaveTest.php new file mode 100644 index 0000000000000..87b022c942318 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Newsletter/Controller/Manage/SaveTest.php @@ -0,0 +1,157 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Newsletter\Controller\Manage; + +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Model\CustomerRegistry; +use Magento\Customer\Model\Session; +use Magento\Framework\Data\Form\FormKey; +use Magento\Framework\Message\MessageInterface; +use Magento\Newsletter\Model\Plugin\CustomerPlugin; +use Magento\TestFramework\TestCase\AbstractController; + +/** + * Class checks customer subscription + * + * @magentoDbIsolation enabled + */ +class SaveTest extends AbstractController +{ + /** @var Session */ + protected $customerSession; + + /** @var CustomerRepositoryInterface */ + private $customerRepository; + + /** @var FormKey */ + private $formKey; + + /** @var CustomerRegistry */ + private $customerRegistry; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->customerSession = $this->_objectManager->get(Session::class); + $this->customerRepository = $this->_objectManager->get(CustomerRepositoryInterface::class); + $this->formKey = $this->_objectManager->get(FormKey::class); + $this->customerRegistry = $this->_objectManager->get(CustomerRegistry::class); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->customerSession->logout(); + + parent::tearDown(); + } + + /** + * @magentoDataFixture Magento/Customer/_files/new_customer.php + * + * @dataProvider subscriptionDataProvider + * + * @param bool $isSubscribed + * @param string $expectedMessage + * @return void + */ + public function testSaveAction(bool $isSubscribed, string $expectedMessage): void + { + $this->loginCustomer('new_customer@example.com'); + $this->_objectManager->removeSharedInstance(CustomerPlugin::class); + $this->dispatchSaveAction($isSubscribed); + $this->assertSuccessSubscription($expectedMessage); + } + + /** + * @return array + */ + public function subscriptionDataProvider(): array + { + return [ + 'subscribe_customer' => [ + 'is_subscribed' => true, + 'expected_message' => 'We have saved your subscription.', + ], + 'unsubscribe_customer' => [ + 'is_subscribed' => false, + 'expected_message' => 'We have updated your subscription.', + ], + ]; + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer_confirmation_config_enable.php + * @magentoDataFixture Magento/Customer/_files/new_customer.php + * + * @return void + */ + public function testSubscribeWithEnabledConfirmation(): void + { + $this->loginCustomer('new_customer@example.com'); + $this->dispatchSaveAction(true); + $this->assertSuccessSubscription('A confirmation request has been sent.'); + } + + /** + * @magentoDataFixture Magento/Newsletter/_files/customer_with_subscription.php + * + * @return void + */ + public function testUnsubscribeSubscribedCustomer(): void + { + $this->loginCustomer('new_customer@example.com'); + $this->_objectManager->removeSharedInstance(CustomerPlugin::class); + $this->dispatchSaveAction(false); + $this->assertSuccessSubscription('We have removed your newsletter subscription.'); + } + + /** + * Dispatch save action with parameters + * + * @param string $isSubscribed + * @return void + */ + private function dispatchSaveAction(bool $isSubscribed): void + { + $this->_objectManager->removeSharedInstance(CustomerPlugin::class); + $this->getRequest()->setParam('form_key', $this->formKey->getFormKey()) + ->setParam('is_subscribed', $isSubscribed); + $this->dispatch('newsletter/manage/save'); + } + + /** + * Login customer by email + * + * @param string $email + * @return void + */ + private function loginCustomer(string $email): void + { + $customer = $this->customerRepository->get($email); + $this->customerSession->loginById($customer->getId()); + } + + /** + * Assert that action was successfully done + * + * @param string $expectedMessage + * @return void + */ + private function assertSuccessSubscription(string $expectedMessage): void + { + $this->assertRedirect($this->stringContains('customer/account/')); + $this->assertSessionMessages($this->equalTo([(string)__($expectedMessage)]), MessageInterface::TYPE_SUCCESS); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/Controller/ManageTest.php b/dev/tests/integration/testsuite/Magento/Newsletter/Controller/ManageTest.php deleted file mode 100644 index 175c1c7c6c668..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Newsletter/Controller/ManageTest.php +++ /dev/null @@ -1,98 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Newsletter\Controller; - -/** - * @magentoDbIsolation enabled - */ -class ManageTest extends \Magento\TestFramework\TestCase\AbstractController -{ - /** - * @var \Magento\Customer\Model\Session - */ - protected $customerSession; - - /** - * @var \Magento\Framework\Session\Generic - */ - protected $coreSession; - - /** - * Test setup - */ - protected function setUp() - { - parent::setUp(); - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $this->customerSession = $objectManager->get(\Magento\Customer\Model\Session::class); - $this->customerSession->setCustomerId(1); - $this->coreSession = $objectManager->get(\Magento\Framework\Session\Generic::class); - $this->coreSession->setData('_form_key', 'formKey'); - } - - /** - * test tearDown - */ - protected function tearDown() - { - $this->customerSession->setCustomerId(null); - $this->coreSession->unsetData('_form_key'); - } - - /** - * @magentoDataFixture Magento/Customer/_files/customer.php - */ - public function testSaveAction() - { - $this->getRequest() - ->setParam('form_key', 'formKey') - ->setParam('is_subscribed', '1'); - $this->dispatch('newsletter/manage/save'); - - $this->assertRedirect($this->stringContains('customer/account/')); - - /** - * Check that errors - */ - $this->assertSessionMessages($this->isEmpty(), \Magento\Framework\Message\MessageInterface::TYPE_ERROR); - - /** - * Check that success message - */ - $this->assertSessionMessages( - $this->equalTo(['We have saved your subscription.']), - \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS - ); - } - - /** - * @magentoDataFixture Magento/Customer/_files/customer.php - */ - public function testSaveActionRemoveSubscription() - { - - $this->getRequest() - ->setParam('form_key', 'formKey') - ->setParam('is_subscribed', '0'); - $this->dispatch('newsletter/manage/save'); - - $this->assertRedirect($this->stringContains('customer/account/')); - - /** - * Check that errors - */ - $this->assertSessionMessages($this->isEmpty(), \Magento\Framework\Message\MessageInterface::TYPE_ERROR); - - /** - * Check that success message - */ - $this->assertSessionMessages( - $this->equalTo(['We have updated your subscription.']), - \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS - ); - } -} diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/Controller/Subscriber/NewActionTest.php b/dev/tests/integration/testsuite/Magento/Newsletter/Controller/Subscriber/NewActionTest.php new file mode 100644 index 0000000000000..0f07d8b31d13b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Newsletter/Controller/Subscriber/NewActionTest.php @@ -0,0 +1,255 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Newsletter\Controller\Subscriber; + +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Model\Session; +use Magento\Customer\Model\Url; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Newsletter\Model\ResourceModel\Subscriber as SubscriberResource; +use Magento\Newsletter\Model\ResourceModel\Subscriber\CollectionFactory; +use Magento\TestFramework\TestCase\AbstractController; +use Zend\Stdlib\Parameters; + +/** + * Class checks subscription behaviour from frontend + * + * @magentoDbIsolation enabled + * @see \Magento\Newsletter\Controller\Subscriber\NewAction + */ +class NewActionTest extends AbstractController +{ + /** @var Session */ + private $session; + + /** @var CollectionFactory */ + private $subscriberCollectionFactory; + + /** @var SubscriberResource */ + private $subscriberResource; + + /** @var string|null */ + private $subscriberToDelete; + + /** @var CustomerRepositoryInterface */ + private $customerRepository; + + /** @var Url */ + private $customerUrl; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->session = $this->_objectManager->get(Session::class); + $this->subscriberCollectionFactory = $this->_objectManager->get(CollectionFactory::class); + $this->subscriberResource = $this->_objectManager->get(SubscriberResource::class); + $this->customerRepository = $this->_objectManager->get(CustomerRepositoryInterface::class); + $this->customerUrl = $this->_objectManager->get(Url::class); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + if ($this->subscriberToDelete) { + $this->deleteSubscriber($this->subscriberToDelete); + } + + parent::tearDown(); + } + + /** + * @dataProvider subscribersDataProvider + * + * @param string $email + * @param string $expectedMessage + * @return void + */ + public function testNewAction(string $email, string $expectedMessage): void + { + $this->subscriberToDelete = $email ? $email : null; + $this->prepareRequest($email); + $this->dispatch('newsletter/subscriber/new'); + + $this->performAsserts($expectedMessage); + } + + /** + * @return array + */ + public function subscribersDataProvider(): array + { + return [ + 'without_email' => [ + 'email' => '', + 'message' => '', + ], + 'with_unused_email' => [ + 'email' => 'not_used@example.com', + 'message' => 'Thank you for your subscription.', + ], + 'with_invalid_email' => [ + 'email' => 'invalid_email.com', + 'message' => 'Please enter a valid email address.' + ], + ]; + } + + /** + * @magentoDataFixture Magento/Customer/_files/new_customer.php + * + * @return void + */ + public function testNewActionUsedEmail(): void + { + $this->prepareRequest('new_customer@example.com'); + $this->dispatch('newsletter/subscriber/new'); + + $this->performAsserts('Thank you for your subscription.'); + } + + /** + * @magentoDataFixture Magento/Customer/_files/new_customer.php + * + * @return void + */ + public function testNewActionOwnerEmail(): void + { + $this->prepareRequest('new_customer@example.com'); + $this->session->loginById(1); + $this->dispatch('newsletter/subscriber/new'); + + $this->performAsserts('Thank you for your subscription.'); + } + + /** + * @magentoDataFixture Magento/Newsletter/_files/customer_with_subscription.php + * + * @return void + */ + public function testAlreadyExistEmail(): void + { + $this->prepareRequest('new_customer@example.com'); + $this->dispatch('newsletter/subscriber/new'); + + $this->performAsserts('This email address is already subscribed.'); + } + + /** + * @magentoConfigFixture current_store newsletter/subscription/allow_guest_subscribe 0 + * + * @return void + */ + public function testWithNotAllowedGuestSubscription(): void + { + $message = sprintf( + 'Sorry, but the administrator denied subscription for guests. Please <a href="%s">register</a>.', + $this->customerUrl->getRegisterUrl() + ); + $this->subscriberToDelete = 'guest@example.com'; + $this->prepareRequest('guest@example.com'); + $this->dispatch('newsletter/subscriber/new'); + + $this->performAsserts($message); + } + + /** + * @magentoConfigFixture current_store newsletter/subscription/allow_guest_subscribe 0 + * + * @magentoDataFixture Magento/Customer/_files/new_customer.php + * + * @return void + */ + public function testCustomerSubscribeUnrelatedEmailWithNotAllowedGuestSubscription(): void + { + $this->markTestSkipped('Blocked by MC-31662'); + $this->subscriberToDelete = 'guest@example.com'; + $this->session->loginById($this->customerRepository->get('new_customer@example.com')->getId()); + $this->prepareRequest('guest@example.com'); + $this->dispatch('newsletter/subscriber/new'); + //ToDo message need to be specified after bug MC-31662 fixing + $this->performAsserts(''); + } + + /** + * @magentoConfigFixture current_store newsletter/subscription/confirm 1 + * + * @return void + */ + public function testWithRequiredConfirmation(): void + { + $this->subscriberToDelete = 'guest@example.com'; + $this->prepareRequest('guest@example.com'); + $this->dispatch('newsletter/subscriber/new'); + + $this->performAsserts('The confirmation request has been sent.'); + } + + /** + * @magentoDataFixture Magento/Newsletter/_files/three_subscribers.php + * + * @return void + */ + public function testWithEmailAssignedToAnotherCustomer(): void + { + $this->session->loginById(1); + $this->prepareRequest('customer2@search.example.com'); + $this->dispatch('newsletter/subscriber/new'); + + $this->performAsserts('This email address is already assigned to another user.'); + } + + /** + * Prepare request + * + * @param string $email + * @return void + */ + private function prepareRequest(string $email): void + { + $parameters = $this->_objectManager->create(Parameters::class); + $parameters->set('HTTP_REFERER', 'http://localhost/testRedirect'); + $this->getRequest()->setServer($parameters); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); + $this->getRequest()->setPostValue(['email' => $email]); + } + + /** + * Assert session message and expected redirect + * + * @param string $message + * @return void + */ + private function performAsserts(string $message): void + { + if ($message) { + $this->assertSessionMessages($this->equalTo([(string)__($message)])); + } + $this->assertRedirect($this->equalTo('http://localhost/testRedirect')); + } + + /** + * Delete subscribers by email + * + * @param string $email + * @return void + */ + private function deleteSubscriber(string $email): void + { + $collection = $this->subscriberCollectionFactory->create(); + $item = $collection->addFieldToFilter('subscriber_email', $email)->setPageSize(1)->getFirstItem(); + if ($item->getId()) { + $this->subscriberResource->delete($item); + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/Controller/SubscriberTest.php b/dev/tests/integration/testsuite/Magento/Newsletter/Controller/SubscriberTest.php index bf19d6ddefc36..ec6ae77c385fd 100644 --- a/dev/tests/integration/testsuite/Magento/Newsletter/Controller/SubscriberTest.php +++ b/dev/tests/integration/testsuite/Magento/Newsletter/Controller/SubscriberTest.php @@ -23,59 +23,6 @@ */ class SubscriberTest extends AbstractController { - public function testNewAction() - { - $this->getRequest()->setMethod('POST'); - - $this->dispatch('newsletter/subscriber/new'); - - $this->assertSessionMessages($this->isEmpty()); - $this->assertRedirect($this->anything()); - } - - /** - * @magentoDbIsolation enabled - */ - public function testNewActionUnusedEmail() - { - $this->getRequest()->setMethod('POST'); - $this->getRequest()->setPostValue(['email' => 'not_used@example.com']); - - $this->dispatch('newsletter/subscriber/new'); - - $this->assertSessionMessages($this->equalTo(['Thank you for your subscription.'])); - $this->assertRedirect($this->anything()); - } - - /** - * @magentoDataFixture Magento/Customer/_files/customer.php - */ - public function testNewActionUsedEmail() - { - $this->getRequest()->setMethod('POST'); - $this->getRequest()->setPostValue(['email' => 'customer@example.com']); - - $this->dispatch('newsletter/subscriber/new'); - - $this->assertSessionMessages($this->equalTo(['Thank you for your subscription.'])); - $this->assertRedirect($this->anything()); - } - - /** - * @magentoDataFixture Magento/Customer/_files/customer.php - */ - public function testNewActionOwnerEmail() - { - $this->getRequest()->setMethod('POST'); - $this->getRequest()->setPostValue(['email' => 'customer@example.com']); - $this->login(1); - - $this->dispatch('newsletter/subscriber/new'); - - $this->assertSessionMessages($this->equalTo(['Thank you for your subscription.'])); - $this->assertRedirect($this->anything()); - } - /** * Check that Customer still subscribed for newsletters emails after registration. * @@ -149,18 +96,4 @@ private function fillRequestWithAccountDataAndFormKey($email) ->setPostValue('create_address', true) ->setParam('form_key', Bootstrap::getObjectManager()->get(FormKey::class)->getFormKey()); } - - /** - * Login the user - * - * @param string $customerId Customer to mark as logged in for the session - * @return void - */ - protected function login($customerId) - { - /** @var \Magento\Customer\Model\Session $session */ - $session = Bootstrap::getObjectManager() - ->get(\Magento\Customer\Model\Session::class); - $session->loginById($customerId); - } } diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php b/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php index bdcbdc035d2b0..06c8902f45897 100644 --- a/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php +++ b/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php @@ -3,104 +3,175 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Newsletter\Model; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Framework\ObjectManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\Mail\Template\TransportBuilderMock; +use PHPUnit\Framework\TestCase; /** - * \Magento\Newsletter\Model\Subscriber tests + * Class checks subscription behavior. + * + * @see \Magento\Newsletter\Model\Subscriber */ -class SubscriberTest extends \PHPUnit\Framework\TestCase +class SubscriberTest extends TestCase { + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var SubscriberFactory */ + private $subscriberFactory; + + /** @var TransportBuilderMock */ + private $transportBuilder; + + /** @var CustomerRepositoryInterface */ + private $customerRepository; + /** - * @var Subscriber + * @inheritdoc */ - private $model; - protected function setUp() { - $this->model = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Newsletter\Model\Subscriber::class - ); + $this->objectManager = Bootstrap::getObjectManager(); + $this->subscriberFactory = $this->objectManager->get(SubscriberFactory::class); + $this->transportBuilder = $this->objectManager->get(TransportBuilderMock::class); + $this->customerRepository = $this->objectManager->get(CustomerRepositoryInterface::class); } /** - * @magentoDataFixture Magento/Newsletter/_files/subscribers.php * @magentoConfigFixture current_store newsletter/subscription/confirm 1 + * + * @magentoDataFixture Magento/Newsletter/_files/subscribers.php + * + * @return void */ - public function testEmailConfirmation() + public function testEmailConfirmation(): void { - $this->model->subscribe('customer_confirm@example.com'); - /** @var TransportBuilderMock $transportBuilder */ - $transportBuilder = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->get(\Magento\TestFramework\Mail\Template\TransportBuilderMock::class); + $subscriber = $this->subscriberFactory->create(); + $subscriber->subscribe('customer_confirm@example.com'); // confirmationCode 'ysayquyajua23iq29gxwu2eax2qb6gvy' is taken from fixture $this->assertContains( - '/newsletter/subscriber/confirm/id/' . $this->model->getSubscriberId() + '/newsletter/subscriber/confirm/id/' . $subscriber->getSubscriberId() . '/code/ysayquyajua23iq29gxwu2eax2qb6gvy', - $transportBuilder->getSentMessage()->getBody()->getParts()[0]->getRawContent() + $this->transportBuilder->getSentMessage()->getBody()->getParts()[0]->getRawContent() ); - $this->assertEquals(Subscriber::STATUS_NOT_ACTIVE, $this->model->getSubscriberStatus()); + $this->assertEquals(Subscriber::STATUS_NOT_ACTIVE, $subscriber->getSubscriberStatus()); } /** * @magentoDataFixture Magento/Newsletter/_files/subscribers.php + * + * @return void */ - public function testLoadByCustomerId() + public function testLoadByCustomerId(): void { - $this->assertSame($this->model, $this->model->loadByCustomerId(1)); - $this->assertEquals('customer@example.com', $this->model->getSubscriberEmail()); + $subscriber = $this->subscriberFactory->create(); + $this->assertSame($subscriber, $subscriber->loadByCustomerId(1)); + $this->assertEquals('customer@example.com', $subscriber->getSubscriberEmail()); } /** * @magentoDataFixture Magento/Newsletter/_files/subscribers.php - * @magentoAppArea frontend + * + * @magentoAppArea frontend + * + * @return void */ - public function testUnsubscribeSubscribe() + public function testUnsubscribeSubscribe(): void { - // Unsubscribe and verify - $this->assertSame($this->model, $this->model->loadByCustomerId(1)); - $this->assertEquals($this->model, $this->model->unsubscribe()); - $this->assertEquals(Subscriber::STATUS_UNSUBSCRIBED, $this->model->getSubscriberStatus()); - + $subscriber = $this->subscriberFactory->create(); + $this->assertSame($subscriber, $subscriber->loadByCustomerId(1)); + $this->assertEquals($subscriber, $subscriber->unsubscribe()); + $this->assertContains( + 'You have been unsubscribed from the newsletter.', + $this->transportBuilder->getSentMessage()->getRawMessage() + ); + $this->assertEquals(Subscriber::STATUS_UNSUBSCRIBED, $subscriber->getSubscriberStatus()); // Subscribe and verify - $this->assertEquals(Subscriber::STATUS_SUBSCRIBED, $this->model->subscribe('customer@example.com')); - $this->assertEquals(Subscriber::STATUS_SUBSCRIBED, $this->model->getSubscriberStatus()); + $this->assertEquals(Subscriber::STATUS_SUBSCRIBED, $subscriber->subscribe('customer@example.com')); + $this->assertEquals(Subscriber::STATUS_SUBSCRIBED, $subscriber->getSubscriberStatus()); + $this->assertContains( + 'You have been successfully subscribed to our newsletter.', + $this->transportBuilder->getSentMessage()->getRawMessage() + ); } /** * @magentoDataFixture Magento/Newsletter/_files/subscribers.php - * @magentoAppArea frontend + * + * @magentoAppArea frontend + * + * @return void */ - public function testUnsubscribeSubscribeByCustomerId() + public function testUnsubscribeSubscribeByCustomerId(): void { + $subscriber = $this->subscriberFactory->create(); // Unsubscribe and verify - $this->assertSame($this->model, $this->model->unsubscribeCustomerById(1)); - $this->assertEquals(Subscriber::STATUS_UNSUBSCRIBED, $this->model->getSubscriberStatus()); - + $this->assertSame($subscriber, $subscriber->unsubscribeCustomerById(1)); + $this->assertEquals(Subscriber::STATUS_UNSUBSCRIBED, $subscriber->getSubscriberStatus()); + $this->assertContains( + 'You have been unsubscribed from the newsletter.', + $this->transportBuilder->getSentMessage()->getRawMessage() + ); // Subscribe and verify - $this->assertSame($this->model, $this->model->subscribeCustomerById(1)); - $this->assertEquals(Subscriber::STATUS_SUBSCRIBED, $this->model->getSubscriberStatus()); + $this->assertSame($subscriber, $subscriber->subscribeCustomerById(1)); + $this->assertEquals(Subscriber::STATUS_SUBSCRIBED, $subscriber->getSubscriberStatus()); + $this->assertContains( + 'You have been successfully subscribed to our newsletter.', + $this->transportBuilder->getSentMessage()->getRawMessage() + ); } /** - * @magentoDataFixture Magento/Newsletter/_files/subscribers.php * @magentoConfigFixture current_store newsletter/subscription/confirm 1 + * + * @magentoDataFixture Magento/Newsletter/_files/subscribers.php + * + * @return void */ - public function testConfirm() + public function testConfirm(): void { + $subscriber = $this->subscriberFactory->create(); $customerEmail = 'customer_confirm@example.com'; - $this->model->subscribe($customerEmail); - $this->model->loadByEmail($customerEmail); - $this->model->confirm($this->model->getSubscriberConfirmCode()); - - $transportBuilder = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( - \Magento\TestFramework\Mail\Template\TransportBuilderMock::class - ); - + $subscriber->subscribe($customerEmail); + $subscriber->loadByEmail($customerEmail); + $subscriber->confirm($subscriber->getSubscriberConfirmCode()); $this->assertContains( 'You have been successfully subscribed to our newsletter.', - $transportBuilder->getSentMessage()->getBody()->getParts()[0]->getRawContent() + $this->transportBuilder->getSentMessage()->getRawMessage() ); } + + /** + * @magentoDataFixture Magento/Customer/_files/customer_confirmation_config_enable.php + * @magentoDataFixture Magento/Newsletter/_files/newsletter_unconfirmed_customer.php + * + * @return void + */ + public function testSubscribeUnconfirmedCustomerWithSubscription(): void + { + $customer = $this->customerRepository->get('unconfirmedcustomer@example.com'); + $subscriber = $this->subscriberFactory->create(); + $subscriber->subscribeCustomerById($customer->getId()); + $this->assertEquals(Subscriber::STATUS_SUBSCRIBED, $subscriber->getStatus()); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer_confirmation_config_enable.php + * @magentoDataFixture Magento/Customer/_files/unconfirmed_customer.php + * + * @return void + */ + public function testSubscribeUnconfirmedCustomerWithoutSubscription(): void + { + $customer = $this->customerRepository->get('unconfirmedcustomer@example.com'); + $subscriber = $this->subscriberFactory->create(); + $subscriber->subscribeCustomerById($customer->getId()); + $this->assertEquals(Subscriber::STATUS_UNCONFIRMED, $subscriber->getStatus()); + } } diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/_files/customer_with_subscription.php b/dev/tests/integration/testsuite/Magento/Newsletter/_files/customer_with_subscription.php new file mode 100644 index 0000000000000..f700889881e8d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Newsletter/_files/customer_with_subscription.php @@ -0,0 +1,13 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Newsletter\Model\SubscriberFactory; + +require __DIR__ . '/../../../Magento/Customer/_files/new_customer.php'; + +$subscriberFactory = $objectManager->get(SubscriberFactory::class); +$subscriberFactory->create()->subscribe('new_customer@example.com'); diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/_files/customer_with_subscription_rollback.php b/dev/tests/integration/testsuite/Magento/Newsletter/_files/customer_with_subscription_rollback.php new file mode 100644 index 0000000000000..145f6a9b43bbb --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Newsletter/_files/customer_with_subscription_rollback.php @@ -0,0 +1,8 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/../../../Magento/Customer/_files/new_customer_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/_files/newsletter_unconfirmed_customer.php b/dev/tests/integration/testsuite/Magento/Newsletter/_files/newsletter_unconfirmed_customer.php new file mode 100644 index 0000000000000..405ac1c67bd7b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Newsletter/_files/newsletter_unconfirmed_customer.php @@ -0,0 +1,14 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Newsletter\Model\SubscriberFactory; + +require __DIR__ . '/../../../Magento/Customer/_files/unconfirmed_customer.php'; + +/** @var SubscriberFactory $subscriberFactory */ +$subscriberFactory = $objectManager->get(SubscriberFactory::class); +$subscriberFactory->create()->subscribe('unconfirmedcustomer@example.com'); diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/_files/newsletter_unconfirmed_customer_rollback.php b/dev/tests/integration/testsuite/Magento/Newsletter/_files/newsletter_unconfirmed_customer_rollback.php new file mode 100644 index 0000000000000..5742526988187 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Newsletter/_files/newsletter_unconfirmed_customer_rollback.php @@ -0,0 +1,8 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/../../../Magento/Customer/_files/unconfirmed_customer_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/orders_with_customer.php b/dev/tests/integration/testsuite/Magento/Sales/_files/orders_with_customer.php index 1a0a94b0ca951..4c962d5527c49 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/_files/orders_with_customer.php +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/orders_with_customer.php @@ -24,7 +24,6 @@ 'base_grand_total' => 120.00, 'store_id' => 1, 'website_id' => 1, - 'payment' => $payment ], [ 'increment_id' => '100000003', @@ -36,7 +35,6 @@ 'total_paid' => 130.00, 'store_id' => 0, 'website_id' => 0, - 'payment' => $payment ], [ 'increment_id' => '100000004', @@ -47,7 +45,6 @@ 'subtotal' => 140.00, 'store_id' => 1, 'website_id' => 1, - 'payment' => $payment ], [ 'increment_id' => '100000005', @@ -59,7 +56,6 @@ 'total_paid' => 150.00, 'store_id' => 1, 'website_id' => 1, - 'payment' => $payment ], [ 'increment_id' => '100000006', @@ -71,7 +67,6 @@ 'total_paid' => 160.00, 'store_id' => 1, 'website_id' => 1, - 'payment' => $payment ], ]; @@ -79,6 +74,8 @@ $orderRepository = $objectManager->create(OrderRepositoryInterface::class); /** @var array $orderData */ foreach ($orders as $orderData) { + $newPayment = clone $payment; + $newPayment->setId(null); /** @var $order \Magento\Sales\Model\Order */ $order = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( \Magento\Sales\Model\Order::class @@ -108,7 +105,8 @@ ->setCustomerId(1) ->setCustomerEmail('customer@example.com') ->setBillingAddress($billingAddress) - ->setShippingAddress($shippingAddress); + ->setShippingAddress($shippingAddress) + ->setPayment($newPayment); $orderRepository->save($order); } diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Block/Account/LinkTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Block/Account/LinkTest.php new file mode 100644 index 0000000000000..f4d66b2ff8cde --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Block/Account/LinkTest.php @@ -0,0 +1,79 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Wishlist\Block\Account; + +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\View\Result\Page; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Checks My Wish List link displaying in account dashboard + * + * @magentoAppArea frontend + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + */ +class LinkTest extends TestCase +{ + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var Page */ + private $page; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->page = $this->objectManager->create(Page::class); + } + + /** + * @return void + */ + public function testNewsletterLink(): void + { + $this->preparePage(); + $block = $this->page->getLayout()->getBlock('customer-account-navigation-wish-list-link'); + $this->assertNotFalse($block); + $html = $block->toHtml(); + $this->assertContains('wishlist/', $html); + $this->assertEquals('My Wish List', strip_tags($html)); + } + + /** + * @magentoConfigFixture current_store wishlist/general/active 0 + * + * @return void + */ + public function testNewsletterLinkDisabled(): void + { + $this->preparePage(); + $block = $this->page->getLayout()->getBlock('customer-account-navigation-wish-list-link'); + $this->assertFalse($block); + } + + /** + * Prepare page before render + * + * @return void + */ + private function preparePage(): void + { + $this->page->addHandle([ + 'default', + 'customer_account', + ]); + $this->page->getLayout()->generateXml(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Block/Catalog/Product/ProductList/Item/AddTo/WishlistTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Block/Catalog/Product/ProductList/Item/AddTo/WishlistTest.php new file mode 100644 index 0000000000000..36bd4dd4f312e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Block/Catalog/Product/ProductList/Item/AddTo/WishlistTest.php @@ -0,0 +1,69 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Wishlist\Block\Catalog\Product\ProductList\Item\AddTo; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\View\LayoutInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Checks add to wishlist button on category page. + * + * @magentoAppArea frontend + * @magentoDbIsolation enabled + * @magentoAppIsolation disabled + */ +class WishlistTest extends TestCase +{ + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var Wishlist */ + private $block; + + /** @var ProductRepositoryInterface */ + private $productRepository; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); + $this->block = $this->objectManager->get(LayoutInterface::class) + ->createBlock(Wishlist::class)->setTemplate('Magento_Wishlist::catalog/product/list/addto/wishlist.phtml'); + } + + /** + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * + * @return void + */ + public function testAddToWishListVisible(): void + { + $product = $this->productRepository->get('simple2'); + $html = $this->block->setProduct($product)->toHtml(); + $this->assertEquals('Add to Wish List', trim(strip_tags($html))); + } + + /** + * @magentoConfigFixture current_store wishlist/general/active 0 + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * + * @return void + */ + public function testAddToWishListNotVisible(): void + { + $product = $this->productRepository->get('simple2'); + $this->assertEmpty($this->block->setProduct($product)->toHtml()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Block/Catalog/Product/View/AddTo/WishlistTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Block/Catalog/Product/View/AddTo/WishlistTest.php new file mode 100644 index 0000000000000..8d0226bfe9a2b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Block/Catalog/Product/View/AddTo/WishlistTest.php @@ -0,0 +1,108 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Wishlist\Block\Catalog\Product\View\AddTo; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Registry; +use Magento\Framework\View\LayoutInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Helper\Xpath; +use PHPUnit\Framework\TestCase; + +/** + * Checks add to wishlist button on product page. + * + * @magentoAppArea frontend + * @magentoDbIsolation enabled + * @magentoAppIsolation disabled + */ +class WishlistTest extends TestCase +{ + private const ADD_TO_WISHLIST_XPATH = "//a[@data-action='add-to-wishlist']" + . "/span[contains(text(), 'Add to Wish List')]"; + + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var Registry */ + private $registry; + + /** @var Wishlist */ + private $block; + + /** @var ProductRepositoryInterface */ + private $productRepository; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->registry = $this->objectManager->get(Registry::class); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); + $this->block = $this->objectManager->get(LayoutInterface::class) + ->createBlock(Wishlist::class)->setTemplate('Magento_Wishlist::catalog/product/view/addto/wishlist.phtml'); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->registry->unregister('product'); + + parent::tearDown(); + } + + /** + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * + * @return void + */ + public function testAddToWishListVisible(): void + { + $product = $this->productRepository->get('simple2'); + $this->registerProduct($product); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath(self::ADD_TO_WISHLIST_XPATH, $this->block->toHtml()) + ); + } + + /** + * @magentoConfigFixture current_store wishlist/general/active 0 + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * + * @return void + */ + public function testAddToWishListNotVisible(): void + { + $product = $this->productRepository->get('simple2'); + $this->registerProduct($product); + $this->assertEquals( + 0, + Xpath::getElementsCountForXpath(self::ADD_TO_WISHLIST_XPATH, $this->block->toHtml()) + ); + } + + /** + * Register the product. + * + * @param ProductInterface $product + * @return void + */ + private function registerProduct(ProductInterface $product): void + { + $this->registry->unregister('product'); + $this->registry->register('product', $product); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Block/Customer/SidebarTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Block/Customer/SidebarTest.php new file mode 100644 index 0000000000000..6e6e88ab73019 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Block/Customer/SidebarTest.php @@ -0,0 +1,94 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Wishlist\Block\Customer; + +use Magento\Framework\App\ProductMetadata; +use Magento\Framework\App\ProductMetadataInterface; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\View\Result\Page; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Helper\Xpath; +use PHPUnit\Framework\TestCase; + +/** + * Class test sidebar wish list block. + * + * @magentoAppArea frontend + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + */ +class SidebarTest extends TestCase +{ + private const BLOCK_NAME = 'wishlist_sidebar'; + + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var Page */ + private $page; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $productMetadataInterface = $this->objectManager->get(ProductMetadataInterface::class); + if ($productMetadataInterface->getEdition() !== ProductMetadata::EDITION_NAME) { + $this->markTestSkipped('Skipped, because this logic is rewritten on EE.'); + } + $this->page = $this->objectManager->create(Page::class); + } + + /** + * @magentoConfigFixture current_store wishlist/general/show_in_sidebar 1 + * + * @return void + */ + public function testSidebarWishListVisible(): void + { + $this->preparePageLayout(); + $block = $this->page->getLayout()->getBlock(self::BLOCK_NAME); + $this->assertNotFalse($block); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath( + "//div[contains(@class, 'block-wishlist')]//strong[contains(text(), 'My Wish List')]", + $block->toHtml() + ) + ); + } + + /** + * @magentoConfigFixture current_store wishlist/general/show_in_sidebar 0 + * + * @return void + */ + public function testSidebarWishListNotVisible(): void + { + $this->preparePageLayout(); + $this->assertFalse( + $this->page->getLayout()->getBlock(self::BLOCK_NAME), + 'Sidebar wish list should not be visible.' + ); + } + + /** + * Prepare category page. + * + * @return void + */ + private function preparePageLayout(): void + { + $this->page->addHandle([ + 'default', + 'catalog_category_view', + ]); + $this->page->getLayout()->generateXml(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Block/Customer/WishlistTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Block/Customer/WishlistTest.php new file mode 100644 index 0000000000000..944d2ac6faada --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Block/Customer/WishlistTest.php @@ -0,0 +1,108 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Wishlist\Block\Customer; + +use Magento\Customer\Model\Session; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\View\Result\Page; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Helper\Xpath; +use PHPUnit\Framework\TestCase; + +/** + * Class test my wish list on customer account page. + * + * @magentoAppArea frontend + * @magentoDbIsolation enabled + * @magentoAppIsolation disabled + */ +class WishlistTest extends TestCase +{ + private const ITEMS_COUNT_XPATH = "//div[contains(@class, 'pager')]//span[contains(@class, 'toolbar-number')" + . " and contains(text(), '%s Item')]"; + + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var Page */ + private $page; + + /** @var Session */ + private $customerSession; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->page = $this->objectManager->create(Page::class); + $this->customerSession = $this->objectManager->get(Session::class); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->customerSession->setCustomerId(null); + + parent::tearDown(); + } + + /** + * @magentoConfigFixture current_store wishlist/wishlist_link/use_qty 0 + * @magentoDataFixture Magento/Wishlist/_files/wishlist_with_product_qty_three.php + * + * @return void + */ + public function testDisplayNumberOfItemsInWishList(): void + { + $this->customerSession->setCustomerId(1); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath(sprintf(self::ITEMS_COUNT_XPATH, 1), $this->getWishListPagerBlockHtml()) + ); + } + + /** + * @magentoConfigFixture current_store wishlist/wishlist_link/use_qty 1 + * @magentoDataFixture Magento/Wishlist/_files/wishlist_with_product_qty_three.php + * + * @return void + */ + public function testDisplayItemQuantitiesInWishList(): void + { + $this->markTestSkipped('Test is blocked by issue MC-31595'); + $this->customerSession->setCustomerId(1); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath(sprintf(self::ITEMS_COUNT_XPATH, 3), $this->getWishListPagerBlockHtml()) + ); + } + + /** + * Get wish list pager block html. + * + * @return string + */ + private function getWishListPagerBlockHtml(): string + { + $this->page->addHandle([ + 'default', + 'wishlist_index_index', + ]); + $this->page->getLayout()->generateXml(); + /** @var Wishlist $customerWishlistBlock */ + $customerWishlistBlock = $this->page->getLayout()->getBlock('customer.wishlist'); + + return $customerWishlistBlock->getChildBlock('wishlist_item_pager')->toHtml(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Block/LinkTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Block/LinkTest.php new file mode 100644 index 0000000000000..b7f124f7c1f6d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Block/LinkTest.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Wishlist\Block; + +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\View\LayoutInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Class test link my wish list in customer menu. + * + * @magentoAppArea frontend + * @magentoDbIsolation enabled + * @magentoAppIsolation disabled + */ +class LinkTest extends TestCase +{ + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var Link */ + private $block; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->block = $this->objectManager->get(LayoutInterface::class)->createBlock(Link::class); + } + + /** + * @return void + */ + public function testWishListLinkVisible(): void + { + $this->assertContains('My Wish List', strip_tags($this->block->toHtml())); + } + + /** + * @magentoConfigFixture current_store wishlist/general/active 0 + * + * @return void + */ + public function testWishListLinkNotVisible(): void + { + $this->assertEmpty($this->block->toHtml()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_with_product_qty_three.php b/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_with_product_qty_three.php new file mode 100644 index 0000000000000..11752d5ba39f3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_with_product_qty_three.php @@ -0,0 +1,18 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Wishlist\Model\WishlistFactory; +use Magento\Framework\Serialize\SerializerInterface; + +require __DIR__ . '/../../../Magento/Customer/_files/customer.php'; +require __DIR__ . '/../../../Magento/Catalog/_files/product_simple.php'; + +$json = $objectManager->get(SerializerInterface::class); +$wishlistFactory = $objectManager->get(WishlistFactory::class); +$wishlist = $wishlistFactory->create(); +$wishlist->loadByCustomerId($customer->getId(), true); +$wishlist->addNewItem($product, $json->serialize(['qty' => 3])); diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_with_product_qty_three_rollback.php b/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_with_product_qty_three_rollback.php new file mode 100644 index 0000000000000..24bbccd5739f4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_with_product_qty_three_rollback.php @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/../../../Magento/Catalog/_files/product_simple_rollback.php'; +require __DIR__ . '/../../../Magento/Customer/_files/customer_rollback.php'; diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php index fa0d365061858..60855043a8c4e 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php @@ -154,6 +154,13 @@ class DependencyTest extends \PHPUnit\Framework\TestCase */ private static $routesWhitelist = null; + /** + * Redundant dependencies whitelist + * + * @var array|null + */ + private static $redundantDependenciesWhitelist = null; + /** * @var RouteMapper */ @@ -185,6 +192,7 @@ public static function setUpBeforeClass() self::_prepareMapLayoutHandles(); self::getLibraryWhiteLists(); + self::getRedundantDependenciesWhiteLists(); self::_initDependencies(); self::_initThemes(); @@ -206,6 +214,26 @@ private static function getLibraryWhiteLists() } } + /** + * Initialize redundant dependencies whitelist + * + * @return array + */ + private static function getRedundantDependenciesWhiteLists(): array + { + if (is_null(self::$redundantDependenciesWhitelist)) { + $redundantDependenciesWhitelistFilePattern = + realpath(__DIR__) . '/_files/dependency_test/whitelist/redundant_dependencies_*.php'; + $redundantDependenciesWhitelist = []; + foreach (glob($redundantDependenciesWhitelistFilePattern) as $fileName) { + //phpcs:ignore Magento2.Performance.ForeachArrayMerge + $redundantDependenciesWhitelist = array_merge($redundantDependenciesWhitelist, include $fileName); + } + self::$redundantDependenciesWhitelist = $redundantDependenciesWhitelist; + } + return self::$redundantDependenciesWhitelist; + } + /** * Initialize default themes */ @@ -532,6 +560,9 @@ public function testRedundant() foreach (array_keys(self::$mapDependencies) as $module) { $result = []; $redundant = $this->_getDependencies($module, self::TYPE_HARD, self::MAP_TYPE_REDUNDANT); + if (isset(self::$redundantDependenciesWhitelist[$module])) { + $redundant = array_diff($redundant, self::$redundantDependenciesWhitelist[$module]); + } if (!empty($redundant)) { $result[] = sprintf( "\r\nModule %s: %s [%s]", diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/copyright/blacklist.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/copyright/blacklist.php index 242e4ebb22a54..48eb64ffea27e 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/copyright/blacklist.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/copyright/blacklist.php @@ -10,4 +10,5 @@ '/setup\/src\/Zend\/Mvc\/Controller\/LazyControllerAbstractFactory\.php/', '/app\/code\/(?!Magento)[^\/]*/', '#dev/tests/setup-integration/testsuite/Magento/Developer/_files/\S*\.xml$#', + '/lib\/internal\/Magento\/Framework\/File\/Test\/Unit\/_files\/blank.html$/' ]; diff --git a/lib/internal/Magento/Framework/File/Mime.php b/lib/internal/Magento/Framework/File/Mime.php index ed370b1beae54..148f43d47cfd4 100644 --- a/lib/internal/Magento/Framework/File/Mime.php +++ b/lib/internal/Magento/Framework/File/Mime.php @@ -78,6 +78,18 @@ class Mime 'svg' => 'image/svg+xml', ]; + /** + * List of generic MIME types + * + * The file mime type should be detected by the file's extension if the native mime type is one of the listed below. + * + * @var array + */ + private $genericMimeTypes = [ + 'application/x-empty', + 'inode/x-empty', + ]; + /** * Get mime type of a file * @@ -120,7 +132,11 @@ private function getNativeMimeType(string $file): string $extension = $this->getFileExtension($file); $result = mime_content_type($file); if (isset($this->mimeTypes[$extension], $this->defineByExtensionList[$extension]) - && (strpos($result, 'text/') === 0 || strpos($result, 'image/svg') === 0) + && ( + strpos($result, 'text/') === 0 + || strpos($result, 'image/svg') === 0 + || in_array($result, $this->genericMimeTypes, true) + ) ) { $result = $this->mimeTypes[$extension]; } diff --git a/lib/internal/Magento/Framework/File/Test/Unit/MimeTest.php b/lib/internal/Magento/Framework/File/Test/Unit/MimeTest.php index 1a964c141dd34..0ae1542a2c0e1 100644 --- a/lib/internal/Magento/Framework/File/Test/Unit/MimeTest.php +++ b/lib/internal/Magento/Framework/File/Test/Unit/MimeTest.php @@ -5,6 +5,9 @@ */ namespace Magento\Framework\File\Test\Unit; +/** + * Test mime type utility for correct + */ class MimeTest extends \PHPUnit\Framework\TestCase { /** @@ -12,6 +15,9 @@ class MimeTest extends \PHPUnit\Framework\TestCase */ private $object; + /** + * @inheritDoc + */ protected function setUp() { $this->object = new \Magento\Framework\File\Mime(); @@ -42,12 +48,13 @@ public function testGetMimeType($file, $expectedType) /** * @return array */ - public function getMimeTypeDataProvider() + public function getMimeTypeDataProvider(): array { return [ 'javascript' => [__DIR__ . '/_files/javascript.js', 'application/javascript'], 'weird extension' => [__DIR__ . '/_files/file.weird', 'application/octet-stream'], 'weird uppercase extension' => [__DIR__ . '/_files/UPPERCASE.WEIRD', 'application/octet-stream'], + 'generic mime type' => [__DIR__ . '/_files/blank.html', 'text/html'], ]; } } diff --git a/lib/internal/Magento/Framework/File/Test/Unit/_files/blank.html b/lib/internal/Magento/Framework/File/Test/Unit/_files/blank.html new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/lib/internal/Magento/Framework/Image/Adapter/Gd2.php b/lib/internal/Magento/Framework/Image/Adapter/Gd2.php index caa080c02e255..df236faf8173b 100644 --- a/lib/internal/Magento/Framework/Image/Adapter/Gd2.php +++ b/lib/internal/Magento/Framework/Image/Adapter/Gd2.php @@ -75,16 +75,6 @@ public function open($filename) $this->_getCallback('create', null, sprintf('Unsupported image format. File: %s', $this->_fileName)), $this->_fileName ); - $fileType = $this->getImageType(); - if (in_array($fileType, [IMAGETYPE_PNG, IMAGETYPE_GIF])) { - $this->_keepTransparency = true; - if ($this->_imageHandler) { - $isAlpha = $this->checkAlpha($this->_fileName); - if ($isAlpha) { - $this->_fillBackgroundColor($this->_imageHandler); - } - } - } } /**