diff --git a/app/code/Magento/Analytics/etc/config.xml b/app/code/Magento/Analytics/etc/config.xml index 27d608ab46039..d3066e186b25a 100644 --- a/app/code/Magento/Analytics/etc/config.xml +++ b/app/code/Magento/Analytics/etc/config.xml @@ -23,5 +23,12 @@ + + + + analytics + + + diff --git a/app/code/Magento/Authorization/Model/Rules.php b/app/code/Magento/Authorization/Model/Rules.php index 07ce8e57e3933..44e254fac6201 100644 --- a/app/code/Magento/Authorization/Model/Rules.php +++ b/app/code/Magento/Authorization/Model/Rules.php @@ -25,28 +25,7 @@ class Rules extends \Magento\Framework\Model\AbstractModel { /** - * Class constructor - * - * @param \Magento\Framework\Model\Context $context - * @param \Magento\Framework\Registry $registry - * @param \Magento\Authorization\Model\ResourceModel\Rules $resource - * @param \Magento\Authorization\Model\ResourceModel\Rules\Collection $resourceCollection - * @param array $data - */ - public function __construct( - \Magento\Framework\Model\Context $context, - \Magento\Framework\Registry $registry, - \Magento\Authorization\Model\ResourceModel\Rules $resource, - \Magento\Authorization\Model\ResourceModel\Rules\Collection $resourceCollection, - array $data = [] - ) { - parent::__construct($context, $registry, $resource, $resourceCollection, $data); - } - - /** - * Class constructor - * - * @return void + * @inheritdoc */ protected function _construct() { @@ -54,15 +33,22 @@ protected function _construct() } /** + * Obsolete method of update + * * @return $this + * @deprecated Method was never implemented and used. */ public function update() { - $this->getResource()->update($this); + // phpcs:disable Magento2.Functions.DiscouragedFunction + trigger_error('Method was never implemented and used.', E_USER_DEPRECATED); + return $this; } /** + * Save authorization rule relation + * * @return $this */ public function saveRel() diff --git a/app/code/Magento/AwsS3/Driver/AwsS3.php b/app/code/Magento/AwsS3/Driver/AwsS3.php index 4f753210ca0f6..6b3da8c848fb7 100644 --- a/app/code/Magento/AwsS3/Driver/AwsS3.php +++ b/app/code/Magento/AwsS3/Driver/AwsS3.php @@ -304,6 +304,7 @@ public function getAbsolutePath($basePath, $path, $scheme = null) * Resolves relative path. * * @param string $path Absolute path + * @param bool $fixPath * @return string Relative path */ private function normalizeRelativePath(string $path, bool $fixPath = false): string @@ -358,7 +359,7 @@ public function isFile($path): bool return false; } - $path = $this->normalizeRelativePath($path, true);; + $path = $this->normalizeRelativePath($path, true); if ($this->adapter->has($path) && ($meta = $this->adapter->getMetadata($path))) { return ($meta['type'] ?? null) === self::TYPE_FILE; diff --git a/app/code/Magento/AwsS3/Driver/AwsS3Factory.php b/app/code/Magento/AwsS3/Driver/AwsS3Factory.php index 581c1c276c7e1..a4d3676bffa07 100644 --- a/app/code/Magento/AwsS3/Driver/AwsS3Factory.php +++ b/app/code/Magento/AwsS3/Driver/AwsS3Factory.php @@ -86,6 +86,10 @@ public function createConfigured( throw new DriverException(__('Bucket and region are required values')); } + if (!empty($config['http_handler'])) { + $config['http_handler'] = $this->objectManager->create($config['http_handler'])($config); + } + $client = new S3Client($config); $adapter = new AwsS3Adapter($client, $config['bucket'], $prefix); diff --git a/app/code/Magento/AwsS3/Model/HttpLoggerHandler.php b/app/code/Magento/AwsS3/Model/HttpLoggerHandler.php new file mode 100644 index 0000000000000..9971a81f155d4 --- /dev/null +++ b/app/code/Magento/AwsS3/Model/HttpLoggerHandler.php @@ -0,0 +1,61 @@ +directory = $filesystem->getDirectoryWrite(DirectoryList::VAR_DIR); + $this->file = $file; + } + + public function __invoke() + { + $this->directory->create(pathinfo($this->file, PATHINFO_DIRNAME)); + $localStream = $this->directory->getDriver()->fileOpen($this->directory->getAbsolutePath($this->file), 'a'); + $streamHandler = new StreamHandler($localStream, Logger::DEBUG, true, null, true); + $logger = new \Monolog\Logger('S3', [$streamHandler]); + $stack = HandlerStack::create(); + $stack->push( + Middleware::log( + $logger, + new MessageFormatter('{code}:{method}:{target} {error}') + ) + ); + return new GuzzleHandler(new Client(['handler' => $stack])); + } +} diff --git a/app/code/Magento/Bundle/Model/Sales/Order/Shipment/BundleShipmentTypeValidator.php b/app/code/Magento/Bundle/Model/Sales/Order/Shipment/BundleShipmentTypeValidator.php new file mode 100644 index 0000000000000..396f19e739347 --- /dev/null +++ b/app/code/Magento/Bundle/Model/Sales/Order/Shipment/BundleShipmentTypeValidator.php @@ -0,0 +1,51 @@ +isDummy(true)) { + return $result; + } + + $message = 'Cannot create shipment as bundle product "%1" has shipment type "%2". ' . + '%3 should be shipped instead.'; + + if ($item->getHasChildren() && $item->getProductType() === Type::TYPE_BUNDLE) { + $result[] = __( + $message, + $item->getSku(), + __('Separately'), + __('Bundle product options'), + ); + } + + if ($item->getParentItem() && $item->getParentItem()->getProductType() === Type::TYPE_BUNDLE) { + $result[] = __( + $message, + $item->getParentItem()->getSku(), + __('Together'), + __('Bundle product itself'), + ); + } + + return $result; + } +} diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddBundleItemsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddBundleItemsTest.xml index 26119c5267d86..39d026ac74731 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddBundleItemsTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddBundleItemsTest.xml @@ -59,6 +59,18 @@ + + + + 1 + $grabbedFirstBundleOptionQuantity + + + + 1 + $grabbedSecondBundleOptionQuantity + + @@ -108,6 +120,17 @@ + + + + {{BundleProduct.defaultQuantity}} + $grabbedFirstBundleOptionQuantityAfterUserInput + + + + {{BundleProduct.defaultQuantity}} + $grabbedSecondBundleOptionQuantityAfterUserInput + diff --git a/app/code/Magento/Bundle/etc/di.xml b/app/code/Magento/Bundle/etc/di.xml index 1cb7d835b30a5..1bb79b4b56d32 100644 --- a/app/code/Magento/Bundle/etc/di.xml +++ b/app/code/Magento/Bundle/etc/di.xml @@ -234,4 +234,11 @@ + + + + Magento\Bundle\Model\Sales\Order\Shipment\BundleShipmentTypeValidator + + + diff --git a/app/code/Magento/Bundle/i18n/en_US.csv b/app/code/Magento/Bundle/i18n/en_US.csv index ad9fad974e38f..99ef117fffadb 100644 --- a/app/code/Magento/Bundle/i18n/en_US.csv +++ b/app/code/Magento/Bundle/i18n/en_US.csv @@ -105,3 +105,6 @@ Select...,Select... Status,Status Thumbnail,Thumbnail Type,Type +"Cannot create shipment as bundle product ""%1"" has shipment type ""%2"". %3 should be shipped instead.","Cannot create shipment as bundle product ""%1"" has shipment type ""%2"". %3 should be shipped instead." +"Bundle product itself","Bundle product itself" +"Bundle product options","Bundle product options" diff --git a/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-option-qty.js b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-option-qty.js index e61def6e962a4..5904b20a5dabe 100644 --- a/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-option-qty.js +++ b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-option-qty.js @@ -17,6 +17,26 @@ define([ } }, + /** + * @inheritdoc + */ + setInitialValue: function () { + this.initialValue = this.getInitialValue(); + + if (this.initialValue === undefined || this.initialValue === '') { + this.initialValue = 1; + } + + if (this.value.peek() !== this.initialValue) { + this.value(this.initialValue); + } + + this.on('value', this.onUpdate.bind(this)); + this.isUseDefault(this.disabled()); + + return this; + }, + /** * @inheritdoc */ @@ -33,6 +53,5 @@ define([ return !this.visible() ? false : notEqual; } - }); }); diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/AbstractOptions.php b/app/code/Magento/Catalog/Block/Product/View/Options/AbstractOptions.php index de92546a8dd88..8655897fa5cad 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Options/AbstractOptions.php +++ b/app/code/Magento/Catalog/Block/Product/View/Options/AbstractOptions.php @@ -12,10 +12,11 @@ namespace Magento\Catalog\Block\Product\View\Options; -use Magento\Catalog\Pricing\Price\BasePrice; use Magento\Catalog\Pricing\Price\CalculateCustomOptionCatalogRule; use Magento\Catalog\Pricing\Price\CustomOptionPriceInterface; use Magento\Framework\App\ObjectManager; +use Magento\Framework\Pricing\Adjustment\CalculatorInterface; +use Magento\Framework\Pricing\PriceCurrencyInterface; /** * Product options section abstract block. @@ -55,24 +56,42 @@ abstract class AbstractOptions extends \Magento\Framework\View\Element\Template */ private $calculateCustomOptionCatalogRule; + /** + * @var CalculatorInterface + */ + private $calculator; + + /** + * @var PriceCurrencyInterface + */ + private $priceCurrency; + /** * @param \Magento\Framework\View\Element\Template\Context $context * @param \Magento\Framework\Pricing\Helper\Data $pricingHelper * @param \Magento\Catalog\Helper\Data $catalogData * @param array $data * @param CalculateCustomOptionCatalogRule|null $calculateCustomOptionCatalogRule + * @param CalculatorInterface|null $calculator + * @param PriceCurrencyInterface|null $priceCurrency */ public function __construct( \Magento\Framework\View\Element\Template\Context $context, \Magento\Framework\Pricing\Helper\Data $pricingHelper, \Magento\Catalog\Helper\Data $catalogData, array $data = [], - CalculateCustomOptionCatalogRule $calculateCustomOptionCatalogRule = null + CalculateCustomOptionCatalogRule $calculateCustomOptionCatalogRule = null, + CalculatorInterface $calculator = null, + PriceCurrencyInterface $priceCurrency = null ) { $this->pricingHelper = $pricingHelper; $this->_catalogHelper = $catalogData; $this->calculateCustomOptionCatalogRule = $calculateCustomOptionCatalogRule ?? ObjectManager::getInstance()->get(CalculateCustomOptionCatalogRule::class); + $this->calculator = $calculator + ?? ObjectManager::getInstance()->get(CalculatorInterface::class); + $this->priceCurrency = $priceCurrency + ?? ObjectManager::getInstance()->get(PriceCurrencyInterface::class); parent::__construct($context, $data); } @@ -188,7 +207,13 @@ protected function _formatPrice($value, $flag = true) } $context = [CustomOptionPriceInterface::CONFIGURATION_OPTION_FLAG => true]; - $optionAmount = $customOptionPrice->getCustomAmount($value['pricing_value'], null, $context); + $optionAmount = $isPercent + ? $this->calculator->getAmount( + $this->priceCurrency->roundPrice($value['pricing_value']), + $this->getProduct(), + null, + $context + ) : $customOptionPrice->getCustomAmount($value['pricing_value'], null, $context); $priceStr .= $this->getLayout()->getBlock('product.price.render.default')->renderAmount( $optionAmount, $customOptionPrice, diff --git a/app/code/Magento/Catalog/Model/Product/Image.php b/app/code/Magento/Catalog/Model/Product/Image.php index 842ee197f83fe..34ce5cad70b04 100644 --- a/app/code/Magento/Catalog/Model/Product/Image.php +++ b/app/code/Magento/Catalog/Model/Product/Image.php @@ -6,15 +6,13 @@ namespace Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product\Image\NotLoadInfoImageException; +use Magento\Catalog\Model\Product\Image\ParamsBuilder; use Magento\Catalog\Model\View\Asset\ImageFactory; use Magento\Catalog\Model\View\Asset\PlaceholderFactory; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\App\ObjectManager; -use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Image as MagentoImage; use Magento\Framework\Serialize\SerializerInterface; -use Magento\Catalog\Model\Product\Image\ParamsBuilder; -use Magento\Framework\Filesystem\Driver\File as FilesystemDriver; /** * Image operations @@ -202,11 +200,6 @@ class Image extends \Magento\Framework\Model\AbstractModel */ private $serializer; - /** - * @var FilesystemDriver - */ - private $filesystemDriver; - /** * Constructor * @@ -227,7 +220,6 @@ class Image extends \Magento\Framework\Model\AbstractModel * @param array $data * @param SerializerInterface $serializer * @param ParamsBuilder $paramsBuilder - * @param FilesystemDriver $filesystemDriver * @throws \Magento\Framework\Exception\FileSystemException * @SuppressWarnings(PHPMD.ExcessiveParameterList) * @SuppressWarnings(PHPMD.UnusedLocalVariable) @@ -249,8 +241,7 @@ public function __construct( \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [], SerializerInterface $serializer = null, - ParamsBuilder $paramsBuilder = null, - FilesystemDriver $filesystemDriver = null + ParamsBuilder $paramsBuilder = null ) { $this->_storeManager = $storeManager; $this->_catalogProductMediaConfig = $catalogProductMediaConfig; @@ -265,7 +256,6 @@ public function __construct( $this->viewAssetPlaceholderFactory = $viewAssetPlaceholderFactory; $this->serializer = $serializer ?: ObjectManager::getInstance()->get(SerializerInterface::class); $this->paramsBuilder = $paramsBuilder ?: ObjectManager::getInstance()->get(ParamsBuilder::class); - $this->filesystemDriver = $filesystemDriver ?: ObjectManager::getInstance()->get(FilesystemDriver::class); } /** @@ -675,12 +665,7 @@ public function getDestinationSubdir() public function isCached() { $path = $this->imageAsset->getPath(); - try { - $isCached = is_array($this->loadImageInfoFromCache($path)) || $this->filesystemDriver->isExists($path); - } catch (FileSystemException $e) { - $isCached = false; - } - return $isCached; + return is_array($this->loadImageInfoFromCache($path)) || $this->_mediaDirectory->isExist($path); } /** @@ -952,7 +937,7 @@ private function getImageSize($imagePath) */ private function saveImageInfoToCache(array $imageInfo, string $imagePath) { - $imagePath = $this->cachePrefix . $imagePath; + $imagePath = $this->cachePrefix . $imagePath; $this->_cacheManager->save( $this->serializer->serialize($imageInfo), $imagePath, @@ -968,7 +953,7 @@ private function saveImageInfoToCache(array $imageInfo, string $imagePath) */ private function loadImageInfoFromCache(string $imagePath) { - $imagePath = $this->cachePrefix . $imagePath; + $imagePath = $this->cachePrefix . $imagePath; $cacheData = $this->_cacheManager->load($imagePath); if (!$cacheData) { return false; diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontCustomOptionCheckboxByPriceActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontCustomOptionCheckboxByPriceActionGroup.xml new file mode 100644 index 0000000000000..ddd26e9ff0544 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontCustomOptionCheckboxByPriceActionGroup.xml @@ -0,0 +1,22 @@ + + + + + + + Validates that the provided price for Custom Option Checkbox is present on the Storefront Product page. + + + + + + + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPageGalleryChangingFullscreenImageByRibbonActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPageGalleryChangingFullscreenImageByRibbonActionGroup.xml new file mode 100644 index 0000000000000..6423bf5e319b7 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPageGalleryChangingFullscreenImageByRibbonActionGroup.xml @@ -0,0 +1,27 @@ + + + + + + + On the product page change main image by clicking on the images in the ribbon. Fullscreen + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPageGalleryChangingMainImageByRibbonActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPageGalleryChangingMainImageByRibbonActionGroup.xml new file mode 100644 index 0000000000000..b196956135043 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPageGalleryChangingMainImageByRibbonActionGroup.xml @@ -0,0 +1,27 @@ + + + + + + + Changing main image on product page media gallery by clicking on the images in the fotorama ribbon + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPageGalleryFullscreenThumbnailDragActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPageGalleryFullscreenThumbnailDragActionGroup.xml new file mode 100644 index 0000000000000..86803aed4cfb6 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPageGalleryFullscreenThumbnailDragActionGroup.xml @@ -0,0 +1,29 @@ + + + + + + + On the product page check functional of drag actions in the fotorama ribbon during fullscreen + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPageGalleryImageDimensionsActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPageGalleryImageDimensionsActionGroup.xml new file mode 100644 index 0000000000000..7b6a8e14455ca --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPageGalleryImageDimensionsActionGroup.xml @@ -0,0 +1,34 @@ + + + + + + + On the product page grab dimensions of the displayed product image, and image section. Assert that image is less or equals + + + + + + + + + + getImageHeight + getSectionHeight + + + getImageWidth + getSectionWidth + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup.xml new file mode 100644 index 0000000000000..6473f348648f2 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup.xml @@ -0,0 +1,25 @@ + + + + + + Check the expected image position in the fotorama ribbon on the product page + + + + + + + + + $grabSrcFromThumbnailImageByPosition + |{{image}}[_\d]*.{{extension}}| + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPageGalleryMainImageButtonsActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPageGalleryMainImageButtonsActionGroup.xml new file mode 100644 index 0000000000000..d4cc3097946be --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPageGalleryMainImageButtonsActionGroup.xml @@ -0,0 +1,28 @@ + + + + + + + Assert the buttons functionality "change image" on the product media gallery on the product page + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPageGalleryThumbnailDragActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPageGalleryThumbnailDragActionGroup.xml new file mode 100644 index 0000000000000..2e62d973ea090 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPageGalleryThumbnailDragActionGroup.xml @@ -0,0 +1,32 @@ + + + + + + + Check functional of drag actions in the thumbnail ribbon on the product page + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageGalleryDragMainImageBackActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageGalleryDragMainImageBackActionGroup.xml new file mode 100644 index 0000000000000..4925d6627a0b3 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageGalleryDragMainImageBackActionGroup.xml @@ -0,0 +1,19 @@ + + + + + + + Drag back main image in the media gallery of product page + + + + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageGalleryDragMainImageForwardActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageGalleryDragMainImageForwardActionGroup.xml new file mode 100644 index 0000000000000..a75a25e31717b --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageGalleryDragMainImageForwardActionGroup.xml @@ -0,0 +1,19 @@ + + + + + + + Drag forward main image in the media gallery of product page + + + + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ImageData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ImageData.xml index a2391dda54809..e1072001b56e5 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ImageData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ImageData.xml @@ -25,4 +25,52 @@ adobe-thumb jpg + + magento-small + adobe-small.jpg + adobe-small + jpg + + + magento-thumb + adobe-thumb.jpg + adobe-thumb + jpg + + + jpgimage + jpg.jpg + jpg + jpg + + + largeimage + large.jpg + large + jpg + + + magentoimage + magento.jpg + magento + jpg + + + magentostage + magentoStage.jpg + magentoStage + jpg + + + mediumimage + medium.jpg + medium + jpg + + + magentoimage + png.png + png + png + diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml index 716c4b07d2f1d..f4e468a939111 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml @@ -370,6 +370,7 @@ Yes magento-logo.png magento-logo + png MagentoLogo @@ -577,6 +578,10 @@ ProductOptionDropDownWithLongValuesTitle + + + ProductOptionCheckbox + ProductOptionValueDropdown diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/WYSIWYGConfigData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/WYSIWYGConfigData.xml index 7bb8cf5f4db37..3965cfa1f958b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/WYSIWYGConfigData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/WYSIWYGConfigData.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> - + diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductActionSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductActionSection.xml index 13ced1c0263e0..64f365217d7e4 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductActionSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductActionSection.xml @@ -14,5 +14,6 @@ + diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml index 447113ea65bb2..5efa094e2c35e 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml @@ -16,11 +16,21 @@ - - - + + + + + + + + + + + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGCatalogTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGCatalogTest.xml index bcdf6ad39124a..029c304873ce2 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGCatalogTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGCatalogTest.xml @@ -11,7 +11,7 @@ - + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml index acc22f2e611d6..5ea7253619ed9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml @@ -10,18 +10,20 @@ - - + <description value="Admin should be able to add image to WYSIWYG Editor on Product Page"/> <severity value="CRITICAL"/> - <testCaseId value="MAGETWO-84375"/> + <testCaseId value="MC-25763"/> + <group value="catalog"/> </annotations> + <before> <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="switchToTinyMCE4" /> </before> + <after> <actionGroup ref="DisabledWYSIWYGActionGroup" stepKey="disableWYSIWYG"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> @@ -32,8 +34,9 @@ <actionGroup ref="FillMainProductFormActionGroup" stepKey="fillBasicProductInfo" /> <click selector="{{AdminProductFormSection.contentTab}}" stepKey="clickContentTab" /> - <scrollTo selector="{{ProductDescriptionWYSIWYGToolbarSection.showHideBtn}}" y="-150" x="0" stepKey="scrollToDescription" /> + <scrollTo selector="{{ProductDescriptionWYSIWYGToolbarSection.showHideBtn}}" y="-150" x="0" stepKey="scrollToDescription"/> <waitForElementVisible selector="{{ProductDescriptionWYSIWYGToolbarSection.TinyMCE4}}" stepKey="waitForDescription" /> + <scrollTo selector="{{ProductDescriptionWYSIWYGToolbarSection.showHideBtn}}" y="-150" x="0" stepKey="scrollToDescriptionAgain"/> <click selector="{{ProductDescriptionWYSIWYGToolbarSection.InsertImageIcon}}" stepKey="clickInsertImageIcon1" /> <click selector="{{ProductDescriptionWYSIWYGToolbarSection.Browse}}" stepKey="clickBrowse1" /> <waitForLoadingMaskToDisappear stepKey="waitForBrowseModal" /> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml index b437d5fb0c868..283ed72e62faa 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml @@ -20,7 +20,7 @@ <before> <actionGroup ref="AdminLoginActionGroup" stepKey="loginGetFromGeneralFile"/> <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="switchToTinyMCE4" /> <createData stepKey="myProductAttributeCreation" entity="productAttributeWysiwyg"/> <createData stepKey="myProductAttributeSetAssign" entity="AddToDefaultSet"> <requiredEntity createDataKey="myProductAttributeCreation"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryToAnotherPositionInCategoryTreeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryToAnotherPositionInCategoryTreeTest.xml index 654ddb4d8d872..8eb3d9bbb8063 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryToAnotherPositionInCategoryTreeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryToAnotherPositionInCategoryTreeTest.xml @@ -38,23 +38,27 @@ <waitForPageLoad stepKey="waitForPageToLoad"/> <!-- Create three level deep sub Category --> <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickAddSubCategoryButton"/> + <waitForPageLoad stepKey="waitForAddSubCategoryClick1"/> + <waitForElementVisible selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" stepKey="waitForSubCategoryName1"/> <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{FirstLevelSubCat.name}}" stepKey="fillSubCategoryName"/> <actionGroup ref="AdminSaveCategoryActionGroup" stepKey="saveFirstLevelSubCategory"/> - <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSuccessMessage"/> + <waitForElementVisible selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSuccessMessage"/> <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategoryButtonAgain"/> + <waitForPageLoad stepKey="waitForAddSubCategoryClick2"/> + <waitForElementVisible selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" stepKey="waitForSubCategoryName2"/> <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{SecondLevelSubCat.name}}" stepKey="fillSecondLevelSubCategoryName"/> <actionGroup ref="AdminSaveCategoryActionGroup" stepKey="saveSecondLevelSubCategory"/> - <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSaveSuccessMessage"/> + <waitForElementVisible selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSaveSuccessMessage"/> <grabFromCurrentUrl stepKey="categoryId" regex="#\/([0-9]*)?\/$#" /> <!-- Move Category to another position in category tree, but click cancel button --> <dragAndDrop selector1="{{AdminCategorySidebarTreeSection.categoryInTree(SecondLevelSubCat.name)}}" selector2="{{AdminCategorySidebarTreeSection.categoryInTree('Default Category')}}" stepKey="moveCategory"/> - <see selector="{{AdminCategoryModalSection.message}}" userInput="This operation can take a long time" stepKey="seeWarningMessage"/> + <waitForText selector="{{AdminCategoryModalSection.message}}" userInput="This operation can take a long time" stepKey="seeWarningMessage"/> <click selector="{{AdminCategoryModalSection.cancel}}" stepKey="clickCancelButtonOnWarningPopup"/> <!-- Verify Category in store front page after clicking cancel button --> <amOnPage url="/$$createDefaultCategory.name$$/{{FirstLevelSubCat.name}}/{{SecondLevelSubCat.name}}.html" stepKey="seeTheCategoryInStoreFrontPage"/> <waitForPageLoad stepKey="waitForStoreFrontPageLoad"/> - <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(_defaultCategory.name)}}" stepKey="seeDefaultCategoryOnStoreNavigationBar"/> + <waitForElementVisible selector="{{StorefrontHeaderSection.NavigationCategoryByName(_defaultCategory.name)}}" stepKey="seeDefaultCategoryOnStoreNavigationBar"/> <dontSeeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(SimpleSubCategory.name)}}" stepKey="dontSeeSubCategoryOnStoreNavigationBar"/> <!-- Verify breadcrumbs in store front page after clicking cancel button --> <grabMultiple selector="{{StorefrontNavigationSection.categoryBreadcrumbs}}" stepKey="breadcrumbs"/> @@ -67,17 +71,18 @@ <actionGroup ref="AdminOpenCategoryPageActionGroup" stepKey="openTheAdminCategoryIndexPage"/> <actionGroup ref="AdminExpandCategoryTreeActionGroup" stepKey="clickOnExpandTree"/> <dragAndDrop selector1="{{AdminCategorySidebarTreeSection.categoryInTree(SecondLevelSubCat.name)}}" selector2="{{AdminCategorySidebarTreeSection.categoryInTree('Default Category')}}" stepKey="DragCategory"/> - <see selector="{{AdminCategoryModalSection.message}}" userInput="This operation can take a long time" stepKey="seeWarningMessageForOneMoreTime"/> + <waitForText selector="{{AdminCategoryModalSection.message}}" userInput="This operation can take a long time" stepKey="seeWarningMessageForOneMoreTime"/> + <waitForElementVisible selector="{{AdminCategoryModalSection.ok}}" stepKey="waitForOkButtonOnWarningPopup"/> <click selector="{{AdminCategoryModalSection.ok}}" stepKey="clickOkButtonOnWarningPopup"/> <waitForPageLoad stepKey="waitTheForPageToLoad"/> - <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You moved the category." stepKey="seeSuccessMoveMessage"/> + <waitForText selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You moved the category." stepKey="seeSuccessMoveMessage"/> <amOnPage url="/{{SimpleSubCategory.name}}.html" stepKey="seeCategoryNameInStoreFrontPage"/> <waitForPageLoad stepKey="waitForStoreFrontPageToLoad"/> <!-- Verify Category in store front after moving category to another position in category tree --> <amOnPage url="{{StorefrontCategoryPage.url(SecondLevelSubCat.name)}}" stepKey="amOnCategoryPage"/> <waitForPageLoad stepKey="waitForPageToBeLoaded"/> - <seeElement selector="{{StorefrontCategoryMainSection.CategoryTitle(SecondLevelSubCat.name)}}" stepKey="seeCategoryInTitle"/> - <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(SecondLevelSubCat.name)}}" stepKey="seeCategoryOnStoreNavigationBarAfterMove"/> + <waitForElementVisible selector="{{StorefrontCategoryMainSection.CategoryTitle(SecondLevelSubCat.name)}}" stepKey="seeCategoryInTitle"/> + <waitForElementVisible selector="{{StorefrontHeaderSection.NavigationCategoryByName(SecondLevelSubCat.name)}}" stepKey="seeCategoryOnStoreNavigationBarAfterMove"/> <!-- Verify breadcrumbs in store front page after moving category to another position in category tree --> <click selector="{{StorefrontHeaderSection.NavigationCategoryByName(SecondLevelSubCat.name)}}" stepKey="clickCategoryOnNavigation"/> <waitForPageLoad stepKey="waitForCategoryLoad"/> @@ -91,6 +96,7 @@ <amOnPage url="{{AdminUrlRewriteIndexPage.url}}" stepKey="openUrlRewriteIndexPage"/> <waitForPageLoad stepKey="waitForUrlRewritePageLoad"/> <click selector="{{AdminDataGridHeaderSection.filters}}" stepKey="openUrlRewriteGridFilters"/> + <waitForElementVisible selector="{{AdminDataGridHeaderSection.filterFieldInput('request_path')}}" stepKey="waitForCategoryUrlKey"/> <fillField selector="{{AdminDataGridHeaderSection.filterFieldInput('request_path')}}" userInput="{{SecondLevelSubCat.name_lwr}}.html" stepKey="fillCategoryUrlKey"/> <click selector="{{AdminDataGridHeaderSection.applyFilters}}" stepKey="clickOrderApplyFilters"/> <waitForPageLoad stepKey="waitForSearch"/> @@ -102,6 +108,7 @@ <see selector="{{AdminUrlRewriteIndexSection.gridCellByColumnRowNumber('2', 'Redirect Type')}}" userInput="No" stepKey="verifyTheRedirectTypeAfterMove"/> <!-- Verify before move Redirect Path displayed with associated Target Path and Redirect Type--> <click selector="{{AdminDataGridHeaderSection.filters}}" stepKey="openUrlRewriteGridFilters1"/> + <waitForElementVisible selector="{{AdminDataGridHeaderSection.filterFieldInput('request_path')}}" stepKey="waitForTheCategoryUrlKey"/> <fillField selector="{{AdminDataGridHeaderSection.filterFieldInput('request_path')}}" userInput="{{SecondLevelSubCat.name_lwr}}" stepKey="fillTheCategoryUrlKey"/> <click selector="{{AdminDataGridHeaderSection.applyFilters}}" stepKey="clickOrderApplyFilters1"/> <waitForPageLoad stepKey="waitForSearch1"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityDateTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityDateTest.xml index 26ff1bc45be9d..d1f7adb8a902c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityDateTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityDateTest.xml @@ -16,9 +16,6 @@ <severity value="BLOCKER"/> <testCaseId value="MC-10895"/> <group value="Catalog"/> - <skip> - <issueId value="MC-13817"/> - </skip> <group value="mtf_migrated"/> </annotations> @@ -35,8 +32,9 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> - <!--Generate date for use as default value, needs to be MM/d/YYYY --> + <!--Generate date for use as default value, needs to be MM/d/YYYY and mm/d/yy--> <generateDate date="now" format="m/j/Y" stepKey="generateDefaultDate"/> + <generateDate date="now" format="m/j/y" stepKey="generateDateCompressedFormat"/> <!--Navigate to Stores > Attributes > Product.--> <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="goToProductAttributes"/> @@ -57,7 +55,7 @@ <seeOptionIsSelected stepKey="assertInputType" selector="{{AttributePropertiesSection.InputType}}" userInput="{{dateProductAttribute.frontend_input}}"/> <seeOptionIsSelected stepKey="assertRequired" selector="{{AttributePropertiesSection.ValueRequired}}" userInput="{{dateProductAttribute.is_required_admin}}"/> <seeInField stepKey="assertAttrCode" selector="{{AdvancedAttributePropertiesSection.AttributeCode}}" userInput="{{dateProductAttribute.attribute_code}}"/> - <seeInField stepKey="assertDefaultValue" selector="{{AdvancedAttributePropertiesSection.DefaultValueDate}}" userInput="{$generateDefaultDate}"/> + <seeInField stepKey="assertDefaultValue" selector="{{AdvancedAttributePropertiesSection.DefaultValueDate}}" userInput="{$generateDateCompressedFormat}"/> <!--Go to New Product page, add Attribute and check values--> <amOnPage url="{{AdminProductCreatePage.url('4', 'simple')}}" stepKey="goToCreateSimpleProductPage"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml index fbb6893e92b1e..179ac35b9d206 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml @@ -10,6 +10,8 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EndToEndB2CLoggedInUserTest"> <before> + <actionGroup ref="DeleteAllProductsUsingProductGridActionGroup" after="loginAsAdmin" stepKey="deleteAllProducts"/> + <createData entity="ApiCategory" stepKey="createCategory"/> <createData entity="ApiSimpleProduct" stepKey="createSimpleProduct1"> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCheckCustomOptionPriceDifferentCurrencyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCheckCustomOptionPriceDifferentCurrencyTest.xml new file mode 100644 index 0000000000000..a8368fcd95035 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCheckCustomOptionPriceDifferentCurrencyTest.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCheckCustomOptionPriceDifferentCurrencyTest"> + <annotations> + <features value="Catalog"/> + <stories value="Custom options"/> + <title value="Check custom option price with different currency"/> + <description value="Check custom option price with different currency on the product page"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-38926"/> + <useCaseId value="MC-30626"/> + <group value="catalog"/> + </annotations> + <before> + <magentoCLI command="config:set {{SetAllowedCurrenciesConfigForUSD.path}} {{SetAllowedCurrenciesConfigForUSD.value}},{{SetAllowedCurrenciesConfigForEUR.value}}" stepKey="setCurrencyAllow"/> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + <field key="price">10</field> + </createData> + <updateData createDataKey="createProduct" entity="productWithCheckbox" stepKey="updateProductWithOptions"/> + </before> + <after> + <magentoCLI command="config:set {{SetAllowedCurrenciesConfigForUSD.path}} {{SetAllowedCurrenciesConfigForUSD.value}}" stepKey="setCurrencyAllow"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + </after> + <actionGroup ref="StorefrontOpenProductEntityPageActionGroup" stepKey="openProductPageOnStorefront"> + <argument name="product" value="$createProduct$"/> + </actionGroup> + <actionGroup ref="AssertStorefrontCustomOptionCheckboxByPriceActionGroup" stepKey="checkPriceProductOptionUSD"> + <argument name="price" value="12.3"/> + </actionGroup> + <actionGroup ref="StorefrontSwitchCurrencyActionGroup" stepKey="switchEURCurrency"> + <argument name="currency" value="EUR"/> + </actionGroup> + <actionGroup ref="AssertStorefrontCustomOptionCheckboxByPriceActionGroup" stepKey="checkPriceProductOptionEUR"> + <argument name="price" value="8.7"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductWithMediaGalleryBehaviorTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductWithMediaGalleryBehaviorTest.xml new file mode 100644 index 0000000000000..0fd5a7117167c --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductWithMediaGalleryBehaviorTest.xml @@ -0,0 +1,463 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontProductWithMediaGalleryBehaviorTest"> + <annotations> + <features value="Catalog"/> + <stories value="Storefront Gallery behaviour for Product with media"/> + <title value="Assert media behaviour for product with different media on storefront"/> + <description value="Assert media behaviour for product with different media on storefront"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-26305"/> + <group value="catalog"/> + <group value="productVideo"/> + <skip> + <issueId value="MC-33903"/> + </skip> + </annotations> + <before> + <createData entity="SimpleProduct2" stepKey="createProduct"/> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + <after> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> + </after> + <!--Add images and video to product--> + <actionGroup ref="AdminProductPageOpenByIdActionGroup" stepKey="openAdminProductEditPage"> + <argument name="productId" value="$createProduct.id$"/> + </actionGroup> + <actionGroup ref="AddProductImageActionGroup" stepKey="addBaseImage"> + <argument name="image" value="TestImageAdobe"/> + </actionGroup> + <actionGroup ref="AddProductImageActionGroup" stepKey="addImage1"> + <argument name="image" value="AdobeSmallImage"/> + </actionGroup> + <actionGroup ref="AddProductImageActionGroup" stepKey="addImage2"> + <argument name="image" value="AdobeThumbImage"/> + </actionGroup> + <actionGroup ref="AdminAddProductVideoWithPreviewActionGroup" stepKey="addVideo"> + <argument name="video" value="VimeoProductVideo"/> + <argument name="image" value="{{TestImageNew.file}}"/> + </actionGroup> + <actionGroup ref="AddProductImageActionGroup" stepKey="addImage4"> + <argument name="image" value="Magento2"/> + </actionGroup> + <actionGroup ref="AddProductImageActionGroup" stepKey="addImage5"> + <argument name="image" value="JpgImage"/> + </actionGroup> + <actionGroup ref="AddProductImageActionGroup" stepKey="addImage6"> + <argument name="image" value="LargeImage"/> + </actionGroup> + <actionGroup ref="AddProductImageActionGroup" stepKey="addImage7"> + <argument name="image" value="Magento2"/> + </actionGroup> + <actionGroup ref="AddProductImageActionGroup" stepKey="addImage8"> + <argument name="image" value="MagentoImage"/> + </actionGroup> + <actionGroup ref="AddProductImageActionGroup" stepKey="addImage9"> + <argument name="image" value="Magento3"/> + </actionGroup> + <actionGroup ref="AddProductImageActionGroup" stepKey="addImage10"> + <argument name="image" value="TestImageNew"/> + </actionGroup> + <actionGroup ref="AddProductImageActionGroup" stepKey="addImage11"> + <argument name="image" value="ProductImage"/> + </actionGroup> + <actionGroup ref="AddProductImageActionGroup" stepKey="addImage12"> + <argument name="image" value="MediumImage"/> + </actionGroup> + <actionGroup ref="AddProductImageActionGroup" stepKey="addImage13"> + <argument name="image" value="MediumImage"/> + </actionGroup> + <actionGroup ref="AddProductImageActionGroup" stepKey="addImage14"> + <argument name="image" value="PngImage"/> + </actionGroup> + <actionGroup ref="AddProductImageActionGroup" stepKey="addImage15"> + <argument name="image" value="Magento2"/> + </actionGroup> + <actionGroup ref="AddProductImageActionGroup" stepKey="addImage16"> + <argument name="image" value="Magento3"/> + </actionGroup> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProductForm"/> + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="goToStorefrontProductPage"> + <argument name="productUrl" value="$createProduct.custom_attributes[url_key]$"/> + </actionGroup> + <!--Assert positioning images in the ribbon Step 2.3--> + <waitForElementVisible selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="waitForThumbnailsAppear"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertImagePosition1"> + <argument name="image" value="{{TestImageAdobe.filename}}"/> + <argument name="extension" value="{{TestImageAdobe.file_extension}}"/> + <argument name="position" value="1"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertImagePosition2"> + <argument name="image" value="{{AdobeSmallImage.filename}}"/> + <argument name="extension" value="{{AdobeSmallImage.file_extension}}"/> + <argument name="position" value="2"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertImagePosition3"> + <argument name="image" value="{{AdobeThumbImage.filename}}"/> + <argument name="extension" value="{{AdobeThumbImage.file_extension}}"/> + <argument name="position" value="3"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertImagePosition4"> + <argument name="image" value="{{TestImageNew.filename}}"/> + <argument name="extension" value="{{TestImageNew.file_extension}}"/> + <argument name="position" value="4"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertImagePosition5"> + <argument name="image" value="{{Magento2.filename}}"/> + <argument name="extension" value="{{Magento2.file_extension}}"/> + <argument name="position" value="5"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertImagePosition6"> + <argument name="image" value="{{JpgImage.filename}}"/> + <argument name="extension" value="{{JpgImage.file_extension}}"/> + <argument name="position" value="6"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertImagePosition7"> + <argument name="image" value="{{LargeImage.filename}}"/> + <argument name="extension" value="{{LargeImage.file_extension}}"/> + <argument name="position" value="7"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertImagePosition8"> + <argument name="image" value="{{Magento2.filename}}"/> + <argument name="extension" value="{{Magento2.file_extension}}"/> + <argument name="position" value="8"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertImagePosition9"> + <argument name="image" value="{{MagentoImage.filename}}"/> + <argument name="extension" value="{{MagentoImage.file_extension}}"/> + <argument name="position" value="9"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertImagePosition10"> + <argument name="image" value="{{Magento3.filename}}"/> + <argument name="extension" value="{{Magento3.file_extension}}"/> + <argument name="position" value="10"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertImagePosition11"> + <argument name="image" value="{{TestImageNew.filename}}"/> + <argument name="extension" value="{{TestImageNew.file_extension}}"/> + <argument name="position" value="11"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertImagePosition12"> + <argument name="image" value="{{ProductImage.filename}}"/> + <argument name="extension" value="{{ProductImage.file_extension}}"/> + <argument name="position" value="12"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertImagePosition13"> + <argument name="image" value="{{MediumImage.filename}}"/> + <argument name="extension" value="{{MediumImage.file_extension}}"/> + <argument name="position" value="13"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertImagePosition14"> + <argument name="image" value="{{MediumImage.filename}}"/> + <argument name="extension" value="{{MediumImage.file_extension}}"/> + <argument name="position" value="14"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertImagePosition15"> + <argument name="image" value="{{PngImage.filename}}"/> + <argument name="extension" value="{{PngImage.file_extension}}"/> + <argument name="position" value="15"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertImagePosition16"> + <argument name="image" value="{{Magento2.filename}}"/> + <argument name="extension" value="{{Magento2.file_extension}}"/> + <argument name="position" value="16"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertImagePosition17"> + <argument name="image" value="{{Magento3.filename}}"/> + <argument name="extension" value="{{Magento3.file_extension}}"/> + <argument name="position" value="17"/> + </actionGroup> + <!--Assert fullscreen image isn't displayed. Step 2.1--> + <actionGroup ref="StorefrontAssertActiveProductImageActionGroup" stepKey="seeActiveBaseImage"> + <argument name="fileName" value="{{TestImageAdobe.filename}}"/> + </actionGroup> + <dontSeeElement selector="{{StorefrontProductMediaSection.mainImageForJsActionsFullscreen}}" stepKey="dontSeeFullscreenProductImage"/> + <!--Assert thumbnail drag actions. Steps 3-4--> + <actionGroup ref="AssertStorefrontProductPageGalleryThumbnailDragActionGroup" stepKey="assertThumbnailDragAction"> + <argument name="dragPointImage" value="{{TestImageNew.filename}}"/> + <argument name="currentImage" value="{{TestImageAdobe.filename}}"/> + <argument name="firstImage" value="{{TestImageAdobe.filename}}"/> + </actionGroup> + <!--Verify if looping is unavailable. Step 5--> + <dontSeeElement selector="{{StorefrontProductMediaSection.fotoramaPrevButton}}" stepKey="dontSeePrevButton"/> + <seeElement selector="{{StorefrontProductMediaSection.fotoramaNextButton}}" stepKey="seeNextButton"/> + <click selector="{{StorefrontProductMediaSection.fotoramaNextButton}}" stepKey="firstClickNextFotorama"/> + <seeElement selector="{{StorefrontProductMediaSection.productImageInFotorama(MagentoImage.filename)}}" stepKey="see9thImageInRibbon"/> + <click selector="{{StorefrontProductMediaSection.fotoramaNextButton}}" stepKey="secondClickNextFotorama"/> + <seeElement selector="{{StorefrontProductMediaSection.productImageInFotorama(MagentoImage.filename)}}" stepKey="seeLastImageInRibbon"/> + <seeElement selector="{{StorefrontProductMediaSection.productImage(TestImageAdobe.filename)}}" stepKey="seeActiveImageDontChangeAfterClickNext"/> + <dontSeeElement selector="{{StorefrontProductMediaSection.fotoramaNextButton}}" stepKey="dontSeeNextButtonAfterClickNext"/> + <click selector="{{StorefrontProductMediaSection.fotoramaPrevButton}}" stepKey="firstClickPrevFotorama"/> + <click selector="{{StorefrontProductMediaSection.fotoramaPrevButton}}" stepKey="secondClickPrevFotorama"/> + <seeElement selector="{{StorefrontProductMediaSection.productImage(TestImageAdobe.filename)}}" stepKey="seeActiveImageDefaultStay2"/> + <seeElement selector="{{StorefrontProductMediaSection.productImageInFotorama(TestImageAdobe.filename)}}" stepKey="seeFirstImageInRibbon"/> + <dontSeeElement selector="{{StorefrontProductMediaSection.fotoramaPrevButton}}" stepKey="dontSeePrevButtonAfterClick"/> + <!--Change image by thumbnail ribbon. Step 6--> + <actionGroup ref="AssertStorefrontProductPageGalleryChangingMainImageByRibbonActionGroup" stepKey="assertThumbnailClicking"> + <argument name="startImage" value="{{TestImageAdobe.filename}}"/> + <argument name="expectedImage" value="{{AdobeSmallImage.filename}}"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductImageAppearsOnProductPagePreviewActionGroup" stepKey="seeImageOnPreview"> + <argument name="productImage" value="{{TestImageAdobe.filename}}"/> + </actionGroup> + <!--Change image by image buttons. Step 7--> + <actionGroup ref="AssertStorefrontProductPageGalleryMainImageButtonsActionGroup" stepKey="assertButtonActions"> + <argument name="startImage" value="{{TestImageAdobe.filename}}"/> + <argument name="expectedImage" value="{{AdobeSmallImage.filename}}"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductImageAppearsOnProductPagePreviewActionGroup" stepKey="seeImageAfterButtonActions"> + <argument name="productImage" value="{{TestImageAdobe.filename}}"/> + </actionGroup> + <!--Check that images <= that image section. Step 7.4--> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertImageDimensions0"/> + <click selector="{{StorefrontProductMediaSection.imageNextButton}}" stepKey="clickOnNextImageButtonDimensionsAssert0"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertImageDimensions1"/> + <click selector="{{StorefrontProductMediaSection.imageNextButton}}" stepKey="clickOnNextImageButtonDimensionsAssert1"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertImageDimensions2"/> + <click selector="{{StorefrontProductMediaSection.imageNextButton}}" stepKey="clickOnNextImageButtonDimensionsAssert2"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertImageDimensions3"/> + <click selector="{{StorefrontProductMediaSection.imageNextButton}}" stepKey="clickOnNextImageButtonDimensionsAssert3"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertImageDimensions4"/> + <click selector="{{StorefrontProductMediaSection.imageNextButton}}" stepKey="clickOnNextImageButtonDimensionsAssert4"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertImageDimensions5"/> + <click selector="{{StorefrontProductMediaSection.imageNextButton}}" stepKey="clickOnNextImageButtonDimensionsAssert5"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertImageDimensions6"/> + <click selector="{{StorefrontProductMediaSection.imageNextButton}}" stepKey="clickOnNextImageButtonDimensionsAssert6"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertImageDimensions7"/> + <click selector="{{StorefrontProductMediaSection.imageNextButton}}" stepKey="clickOnNextImageButtonDimensionsAssert7"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertImageDimensions8"/> + <click selector="{{StorefrontProductMediaSection.imageNextButton}}" stepKey="clickOnNextImageButtonDimensionsAssert8"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertImageDimensions9"/> + <click selector="{{StorefrontProductMediaSection.imageNextButton}}" stepKey="clickOnNextImageButtonDimensionsAssert9"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertImageDimensions10"/> + <click selector="{{StorefrontProductMediaSection.imageNextButton}}" stepKey="clickOnNextImageButtonDimensionsAssert10"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertImageDimensions11"/> + <click selector="{{StorefrontProductMediaSection.imageNextButton}}" stepKey="clickOnNextImageButtonDimensionsAssert11"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertImageDimensions12"/> + <click selector="{{StorefrontProductMediaSection.imageNextButton}}" stepKey="clickOnNextImageButtonDimensionsAssert12"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertImageDimensions13"/> + <click selector="{{StorefrontProductMediaSection.imageNextButton}}" stepKey="clickOnNextImageButtonDimensionsAssert13"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertImageDimensions14"/> + <click selector="{{StorefrontProductMediaSection.imageNextButton}}" stepKey="clickOnNextImageButtonDimensionsAssert14"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertImageDimensions15"/> + <click selector="{{StorefrontProductMediaSection.imageNextButton}}" stepKey="clickOnNextImageButtonDimensionsAssert15"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertImageDimensions16"/> + <click selector="{{StorefrontProductMediaSection.imageNextButton}}" stepKey="clickOnNextImageButtonDimensionsAssert16"/> + <actionGroup ref="AssertStorefrontProductImageAppearsOnProductPagePreviewActionGroup" stepKey="seeImageAfterLoop"> + <argument name="productImage" value="{{TestImageAdobe.filename}}"/> + </actionGroup> + <!--Change image using the drag actions. Step 8--> + <actionGroup ref="StorefrontProductPageGalleryDragMainImageBackActionGroup" stepKey="dragBack"/> + <actionGroup ref="AssertStorefrontProductImageAppearsOnProductPagePreviewActionGroup" stepKey="seeImageAfterDragBack"> + <argument name="productImage" value="{{Magento3.filename}}"/> + </actionGroup> + <actionGroup ref="StorefrontProductPageGalleryDragMainImageForwardActionGroup" stepKey="dragForward"/> + <moveMouseOver selector="{{StorefrontProductMediaSection.mainImageForJsActions}}" stepKey="hoverOnImageAfterDragActions"/> + <actionGroup ref="AssertStorefrontProductImageAppearsOnProductPagePreviewActionGroup" stepKey="seeImageAfterDragForward"> + <argument name="productImage" value="{{TestImageAdobe.filename}}"/> + </actionGroup> + <!--Assert the image looping by image buttons. Step 9--> + <click selector="{{StorefrontProductMediaSection.imagePrevButton}}" stepKey="loopBack"/> + <actionGroup ref="AssertStorefrontProductImageAppearsOnProductPagePreviewActionGroup" stepKey="seeImageAfterLoopBack"> + <argument name="productImage" value="{{Magento3.filename}}"/> + </actionGroup> + <click selector="{{StorefrontProductMediaSection.imageNextButton}}" stepKey="loopForward"/> + <actionGroup ref="AssertStorefrontProductImageAppearsOnProductPagePreviewActionGroup" stepKey="seeImageAfterLoopForward"> + <argument name="productImage" value="{{TestImageAdobe.filename}}"/> + </actionGroup> + <!--Open the fullscreen image. Steps 10-11--> + <click selector="{{StorefrontProductMediaSection.imageNextButton}}" stepKey="setNonDefaultImage"/> + <click selector="{{StorefrontProductMediaSection.mainImageForJsActions}}" stepKey="openFullscreenImage"/> + <seeElement selector="{{StorefrontProductMediaSection.productImageFullscreen(AdobeSmallImage.filename)}}" stepKey="assertFullscreenImage"/> + <waitForElementVisible selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="waitForThumbnailsFullscreen"/> + <!--Assert positioning images in the ribbon Step 11.3--> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertFullscreenThumbnailImagePosition1"> + <argument name="image" value="{{TestImageAdobe.filename}}"/> + <argument name="extension" value="{{TestImageAdobe.file_extension}}"/> + <argument name="position" value="1"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertFullscreenThumbnailImagePosition2"> + <argument name="image" value="{{AdobeSmallImage.filename}}"/> + <argument name="extension" value="{{AdobeSmallImage.file_extension}}"/> + <argument name="position" value="2"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertFullscreenThumbnailImagePosition3"> + <argument name="image" value="{{AdobeThumbImage.filename}}"/> + <argument name="extension" value="{{AdobeThumbImage.file_extension}}"/> + <argument name="position" value="3"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertFullscreenThumbnailImagePosition4"> + <argument name="image" value="{{TestImageNew.filename}}"/> + <argument name="extension" value="{{TestImageNew.file_extension}}"/> + <argument name="position" value="4"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertFullscreenThumbnailImagePosition5"> + <argument name="image" value="{{Magento2.filename}}"/> + <argument name="extension" value="{{Magento2.file_extension}}"/> + <argument name="position" value="5"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertFullscreenThumbnailImagePosition6"> + <argument name="image" value="{{JpgImage.filename}}"/> + <argument name="extension" value="{{JpgImage.file_extension}}"/> + <argument name="position" value="6"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertFullscreenThumbnailImagePosition7"> + <argument name="image" value="{{LargeImage.filename}}"/> + <argument name="extension" value="{{LargeImage.file_extension}}"/> + <argument name="position" value="7"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertFullscreenThumbnailImagePosition8"> + <argument name="image" value="{{Magento2.filename}}"/> + <argument name="extension" value="{{Magento2.file_extension}}"/> + <argument name="position" value="8"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertFullscreenThumbnailImagePosition9"> + <argument name="image" value="{{MagentoImage.filename}}"/> + <argument name="extension" value="{{MagentoImage.file_extension}}"/> + <argument name="position" value="9"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertFullscreenThumbnailImagePosition10"> + <argument name="image" value="{{Magento3.filename}}"/> + <argument name="extension" value="{{Magento3.file_extension}}"/> + <argument name="position" value="10"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertFullscreenThumbnailImagePosition11"> + <argument name="image" value="{{TestImageNew.filename}}"/> + <argument name="extension" value="{{TestImageNew.file_extension}}"/> + <argument name="position" value="11"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertFullscreenThumbnailImagePosition12"> + <argument name="image" value="{{ProductImage.filename}}"/> + <argument name="extension" value="{{ProductImage.file_extension}}"/> + <argument name="position" value="12"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertFullscreenThumbnailImagePosition13"> + <argument name="image" value="{{MediumImage.filename}}"/> + <argument name="extension" value="{{MediumImage.file_extension}}"/> + <argument name="position" value="13"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertFullscreenThumbnailImagePosition14"> + <argument name="image" value="{{MediumImage.filename}}"/> + <argument name="extension" value="{{MediumImage.file_extension}}"/> + <argument name="position" value="14"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertFullscreenThumbnailImagePosition15"> + <argument name="image" value="{{PngImage.filename}}"/> + <argument name="extension" value="{{PngImage.file_extension}}"/> + <argument name="position" value="15"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertFullscreenThumbnailImagePosition16"> + <argument name="image" value="{{Magento2.filename}}"/> + <argument name="extension" value="{{Magento2.file_extension}}"/> + <argument name="position" value="16"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductPageGalleryImagePositionInThumbnailRibbonActionGroup" stepKey="assertFullscreenThumbnailImagePosition17"> + <argument name="image" value="{{Magento3.filename}}"/> + <argument name="extension" value="{{Magento3.file_extension}}"/> + <argument name="position" value="17"/> + </actionGroup> + <!--Assert the fullscreen thumbnail ribbon drag actions step 12--> + <actionGroup ref="AssertStorefrontProductPageGalleryFullscreenThumbnailDragActionGroup" stepKey="assertFullscreenThumbnailDragAction"> + <argument name="dragPointImage" value="{{TestImageNew.filename}}"/> + <argument name="currentImage" value="{{AdobeSmallImage.filename}}"/> + <argument name="firstImage" value="{{TestImageAdobe.filename}}"/> + </actionGroup> + <!--Change fullscreen image by clicking on thumbnail ribbon. Step 15--> + <actionGroup ref="AssertStorefrontProductPageGalleryChangingFullscreenImageByRibbonActionGroup" stepKey="assertThumbnailClickFullscreen"> + <argument name="startImage" value="{{AdobeSmallImage.filename}}"/> + <argument name="expectedImage" value="{{LargeImage.filename}}"/> + </actionGroup> + <!--Change fullscreen image using the image buttons. Steps 16 and 18--> + <click selector="{{StorefrontProductMediaSection.imageFullscreenPrevButton}}" stepKey="goToFirstImage"/> + <seeElement selector="{{StorefrontProductMediaSection.productImageFullscreen(TestImageAdobe.filename)}}" stepKey="seeFirstFullscreenImage"/> + <click selector="{{StorefrontProductMediaSection.imageFullscreenPrevButton}}" stepKey="loopToLastImage"/> + <seeElement selector="{{StorefrontProductMediaSection.productImageFullscreen(Magento3.filename)}}" stepKey="assertLastImageAfterLoop"/> + <click selector="{{StorefrontProductMediaSection.imageFullscreenNextButton}}" stepKey="loopToFirstImage"/> + <seeElement selector="{{StorefrontProductMediaSection.productImageFullscreen(TestImageAdobe.filename)}}" stepKey="assertFirstImageAfterLoop"/> + <click selector="{{StorefrontProductMediaSection.imageFullscreenNextButton}}" stepKey="clickOnNextImageButtonFullscreenDimensionsAssert0"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertFullscreenImageDimensions0"> + <argument name="source" value="{{StorefrontProductMediaSection.mainImageForJsActionsFullscreen}}"/> + </actionGroup> + <!-- Check that images <= that image section. Step 16.5--> + <click selector="{{StorefrontProductMediaSection.imageFullscreenNextButton}}" stepKey="clickOnNextImageButtonFullscreenDimensionsAssert1"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertFullscreenImageDimensions1"> + <argument name="source" value="{{StorefrontProductMediaSection.mainImageForJsActionsFullscreen}}"/> + </actionGroup> + <click selector="{{StorefrontProductMediaSection.imageFullscreenNextButton}}" stepKey="clickOnNextImageButtonFullscreenDimensionsAssert2"/> + <click selector="{{StorefrontProductMediaSection.fotoramaNextButtonVideo}}" stepKey="skipVideo"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertFullscreenImageDimensions3"> + <argument name="imageSource" value="{{StorefrontProductMediaSection.mainImageForJsActionsFullscreen}}"/> + </actionGroup> + <click selector="{{StorefrontProductMediaSection.imageFullscreenNextButton}}" stepKey="clickOnNextImageButtonFullscreenDimensionsAssert4"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertFullscreenImageDimensions4"> + <argument name="imageSource" value="{{StorefrontProductMediaSection.mainImageForJsActionsFullscreen}}"/> + </actionGroup> + <click selector="{{StorefrontProductMediaSection.imageFullscreenNextButton}}" stepKey="clickOnNextImageButtonFullscreenDimensionsAssert5"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertFullscreenImageDimensions5"> + <argument name="imageSource" value="{{StorefrontProductMediaSection.mainImageForJsActionsFullscreen}}"/> + </actionGroup> + <click selector="{{StorefrontProductMediaSection.imageFullscreenNextButton}}" stepKey="clickOnNextImageButtonFullscreenDimensionsAssert6"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertFullscreenImageDimensions6"> + <argument name="imageSource" value="{{StorefrontProductMediaSection.mainImageForJsActionsFullscreen}}"/> + </actionGroup> + <click selector="{{StorefrontProductMediaSection.imageFullscreenNextButton}}" stepKey="clickOnNextImageButtonFullscreenDimensionsAssert7"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertFullscreenImageDimensions7"> + <argument name="imageSource" value="{{StorefrontProductMediaSection.mainImageForJsActionsFullscreen}}"/> + </actionGroup> + <click selector="{{StorefrontProductMediaSection.imageFullscreenNextButton}}" stepKey="clickOnNextImageButtonFullscreenDimensionsAssert8"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertFullscreenImageDimensions8"> + <argument name="imageSource" value="{{StorefrontProductMediaSection.mainImageForJsActionsFullscreen}}"/> + </actionGroup> + <click selector="{{StorefrontProductMediaSection.imageFullscreenNextButton}}" stepKey="clickOnNextImageButtonFullscreenDimensionsAssert9"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertFullscreenImageDimensions9"> + <argument name="imageSource" value="{{StorefrontProductMediaSection.mainImageForJsActionsFullscreen}}"/> + </actionGroup> + <click selector="{{StorefrontProductMediaSection.imageFullscreenNextButton}}" stepKey="clickOnNextImageButtonFullscreenDimensionsAssert10"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertFullscreenImageDimensions10"> + <argument name="imageSource" value="{{StorefrontProductMediaSection.mainImageForJsActionsFullscreen}}"/> + </actionGroup> + <click selector="{{StorefrontProductMediaSection.imageFullscreenNextButton}}" stepKey="clickOnNextImageButtonFullscreenDimensionsAssert11"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertFullscreenImageDimensions11"> + <argument name="imageSource" value="{{StorefrontProductMediaSection.mainImageForJsActionsFullscreen}}"/> + </actionGroup> + <click selector="{{StorefrontProductMediaSection.imageFullscreenNextButton}}" stepKey="clickOnNextImageButtonFullscreenDimensionsAssert12"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertFullscreenImageDimensions12"> + <argument name="imageSource" value="{{StorefrontProductMediaSection.mainImageForJsActionsFullscreen}}"/> + </actionGroup> + <click selector="{{StorefrontProductMediaSection.imageFullscreenNextButton}}" stepKey="clickOnNextImageButtonFullscreenDimensionsAssert13"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertFullscreenImageDimensions13"> + <argument name="imageSource" value="{{StorefrontProductMediaSection.mainImageForJsActionsFullscreen}}"/> + </actionGroup> + <click selector="{{StorefrontProductMediaSection.imageFullscreenNextButton}}" stepKey="clickOnNextImageButtonFullscreenDimensionsAssert14"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertFullscreenImageDimensions14"> + <argument name="imageSource" value="{{StorefrontProductMediaSection.mainImageForJsActionsFullscreen}}"/> + </actionGroup> + <click selector="{{StorefrontProductMediaSection.imageFullscreenNextButton}}" stepKey="clickOnNextImageButtonFullscreenDimensionsAssert15"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertFullscreenImageDimensions15"> + <argument name="imageSource" value="{{StorefrontProductMediaSection.mainImageForJsActionsFullscreen}}"/> + </actionGroup> + <click selector="{{StorefrontProductMediaSection.imageFullscreenNextButton}}" stepKey="clickOnNextImageButtonFullscreenDimensionsAssert16"/> + <actionGroup ref="AssertStorefrontProductPageGalleryImageDimensionsActionGroup" stepKey="assertFullscreenImageDimensions16"> + <argument name="imageSource" value="{{StorefrontProductMediaSection.mainImageForJsActionsFullscreen}}"/> + </actionGroup> + <seeElement selector="{{StorefrontProductMediaSection.productImageFullscreen(TestImageAdobe.filename)}}" stepKey="assertFirstImageAfterSecondLoop"/> + <!-- TODO: Change fullscreen image by drag/swipe action: after MQE-2333 implementation --> + <!--Steps 19-20--> + <click selector="{{StorefrontProductMediaSection.imageFullscreenPrevButton}}" stepKey="selectLastImage"/> + <seeElement selector="{{StorefrontProductMediaSection.productImageFullscreen(Magento3.filename)}}" stepKey="assertLastImageFullscreen"/> + <click selector="{{StorefrontProductMediaSection.closeFullscreenImage}}" stepKey="closeFullScreenImage"/> + <actionGroup ref="AssertStorefrontProductImageAppearsOnProductPagePreviewActionGroup" stepKey="seeLastImageAfterFullscreen"> + <argument name="productImage" value="{{Magento3.filename}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyDefaultWYSIWYGToolbarOnProductTest/VerifyDefaultWYSIWYGToolbarOnProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyDefaultWYSIWYGToolbarOnProductTest/VerifyDefaultWYSIWYGToolbarOnProductTest.xml index 456abecc63ccb..662a251be3b20 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyDefaultWYSIWYGToolbarOnProductTest/VerifyDefaultWYSIWYGToolbarOnProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyDefaultWYSIWYGToolbarOnProductTest/VerifyDefaultWYSIWYGToolbarOnProductTest.xml @@ -20,7 +20,7 @@ <before> <actionGroup ref="AdminLoginActionGroup" stepKey="loginGetFromGeneralFile"/> <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4"/> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="switchToTinyMCE4"/> </before> <amOnPage url="{{AdminProductCreatePage.url(AddToDefaultSet.attributeSetId, 'simple')}}" stepKey="navigateToProduct"/> <waitForPageLoad stepKey="wait"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyDefaultWYSIWYGToolbarOnProductTest/VerifydefaultcontrolsonproductshortdescriptionTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyDefaultWYSIWYGToolbarOnProductTest/VerifydefaultcontrolsonproductshortdescriptionTest.xml index e929cbd752f81..d4bc095dbe9b1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyDefaultWYSIWYGToolbarOnProductTest/VerifydefaultcontrolsonproductshortdescriptionTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyDefaultWYSIWYGToolbarOnProductTest/VerifydefaultcontrolsonproductshortdescriptionTest.xml @@ -20,7 +20,7 @@ <before> <actionGroup ref="AdminLoginActionGroup" stepKey="loginGetFromGeneralFile"/> <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4"/> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="switchToTinyMCE4"/> </before> <amOnPage url="{{AdminProductCreatePage.url(AddToDefaultSet.attributeSetId, 'simple')}}" stepKey="navigateToProduct"/> <waitForPageLoad stepKey="wait"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnCatalogTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnCatalogTest.xml index d7e4f97ed0bc2..ee6ff0c224545 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnCatalogTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnCatalogTest.xml @@ -21,7 +21,7 @@ <before> <actionGroup ref="AdminLoginActionGroup" stepKey="loginGetFromGeneralFile"/> <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="switchToTinyMCE4" /> </before> <actionGroup ref="AdminOpenCategoryPageActionGroup" stepKey="navigateToNewCatalog"/> <waitForLoadingMaskToDisappear stepKey="wait2" /> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnProductTest.xml index cffc4af6fcbbd..f75053f495c4d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnProductTest.xml @@ -20,7 +20,7 @@ <before> <actionGroup ref="AdminLoginActionGroup" stepKey="loginGetFromGeneralFile"/> <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="switchToTinyMCE4" /> </before> <amOnPage url="{{AdminProductCreatePage.url(AddToDefaultSet.attributeSetId, 'simple')}}" stepKey="navigateToNewProduct"/> <waitForPageLoad stepKey="wait1"/> diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Export/ProductTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Export/ProductTest.php index 1ad82497119ba..a71d995f78a8c 100644 --- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Export/ProductTest.php +++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Export/ProductTest.php @@ -172,7 +172,8 @@ protected function setUp(): void $this->productFactory = $this->getMockBuilder( \Magento\Catalog\Model\ResourceModel\ProductFactory::class - )->addMethods(['getTypeId']) + )->disableOriginalConstructor() + ->addMethods(['getTypeId']) ->onlyMethods(['create']) ->getMock(); diff --git a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php index 080af5daa0322..22ad385dc2fc3 100644 --- a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php +++ b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php @@ -7,7 +7,10 @@ namespace Magento\CatalogSearch\Model\Layer\Filter; +use Magento\Catalog\Api\Data\ProductAttributeInterface; use Magento\Catalog\Model\Layer\Filter\AbstractFilter; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection; +use Magento\Framework\App\RequestInterface; /** * Layer attribute filter @@ -48,11 +51,10 @@ public function __construct( /** * Apply attribute option filter to product collection * - * @param \Magento\Framework\App\RequestInterface $request + * @param RequestInterface $request * @return $this - * @throws \Magento\Framework\Exception\LocalizedException */ - public function apply(\Magento\Framework\App\RequestInterface $request) + public function apply(RequestInterface $request) { $attributeValue = $request->getParam($this->_requestVar); if (empty($attributeValue) && !is_numeric($attributeValue)) { @@ -60,13 +62,16 @@ public function apply(\Magento\Framework\App\RequestInterface $request) } $attribute = $this->getAttributeModel(); - /** @var \Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection $productCollection */ + /** @var Collection $productCollection */ $productCollection = $this->getLayer() ->getProductCollection(); - $productCollection->addFieldToFilter($attribute->getAttributeCode(), $attributeValue); + $productCollection->addFieldToFilter( + $attribute->getAttributeCode(), + $this->convertAttributeValue($attribute, $attributeValue) + ); $labels = []; - foreach ((array) $attributeValue as $value) { + foreach ((array)$attributeValue as $value) { $label = $this->getOptionText($value); $labels[] = is_array($label) ? $label : [$label]; } @@ -76,6 +81,7 @@ public function apply(\Magento\Framework\App\RequestInterface $request) ->addFilter($this->_createItem($label, $attributeValue)); $this->setItems([]); // set items to disable show filtering + return $this; } @@ -88,7 +94,7 @@ public function apply(\Magento\Framework\App\RequestInterface $request) protected function _getItemsData() { $attribute = $this->getAttributeModel(); - /** @var \Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection $productCollection */ + /** @var Collection $productCollection */ $productCollection = $this->getLayer() ->getProductCollection(); $optionsFacetedData = $productCollection->getFacetedData($attribute->getAttributeCode()); @@ -163,6 +169,22 @@ private function getOptionCount($value, $optionsFacetedData) : 0; } + /** + * Convert attribute value according to its backend type. + * + * @param ProductAttributeInterface $attribute + * @param mixed $value + * @return int|string + */ + private function convertAttributeValue(ProductAttributeInterface $attribute, $value) + { + if ($attribute->getBackendType() === 'int') { + return (int)$value; + } + + return $value; + } + /** * @inheritdoc */ diff --git a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php index e2cb9174b5e97..7622f1d50ab60 100644 --- a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php +++ b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php @@ -7,7 +7,17 @@ namespace Magento\CatalogSearch\Model\Layer\Filter; +use Magento\Catalog\Model\Layer; use Magento\Catalog\Model\Layer\Filter\AbstractFilter; +use Magento\Catalog\Model\Layer\Filter\Item\DataBuilder; +use Magento\Catalog\Model\Layer\Filter\ItemFactory; +use Magento\Catalog\Model\ResourceModel\Layer\Filter\Decimal as ResourceDecimal; +use Magento\Catalog\Model\ResourceModel\Layer\Filter\DecimalFactory; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection as ProductCollection; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\Phrase; +use Magento\Framework\Pricing\PriceCurrencyInterface; +use Magento\Store\Model\StoreManagerInterface; /** * Layer decimal filter @@ -15,31 +25,31 @@ class Decimal extends AbstractFilter { /** - * @var \Magento\Framework\Pricing\PriceCurrencyInterface + * @var PriceCurrencyInterface */ private $priceCurrency; /** - * @var \Magento\Catalog\Model\ResourceModel\Layer\Filter\Decimal + * @var ResourceDecimal */ private $resource; /** - * @param \Magento\Catalog\Model\Layer\Filter\ItemFactory $filterItemFactory - * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @param \Magento\Catalog\Model\Layer $layer - * @param \Magento\Catalog\Model\Layer\Filter\Item\DataBuilder $itemDataBuilder - * @param \Magento\Catalog\Model\ResourceModel\Layer\Filter\DecimalFactory $filterDecimalFactory - * @param \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency + * @param ItemFactory $filterItemFactory + * @param StoreManagerInterface $storeManager + * @param Layer $layer + * @param DataBuilder $itemDataBuilder + * @param DecimalFactory $filterDecimalFactory + * @param PriceCurrencyInterface $priceCurrency * @param array $data */ public function __construct( - \Magento\Catalog\Model\Layer\Filter\ItemFactory $filterItemFactory, - \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Catalog\Model\Layer $layer, - \Magento\Catalog\Model\Layer\Filter\Item\DataBuilder $itemDataBuilder, - \Magento\Catalog\Model\ResourceModel\Layer\Filter\DecimalFactory $filterDecimalFactory, - \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency, + ItemFactory $filterItemFactory, + StoreManagerInterface $storeManager, + Layer $layer, + DataBuilder $itemDataBuilder, + DecimalFactory $filterDecimalFactory, + PriceCurrencyInterface $priceCurrency, array $data = [] ) { parent::__construct( @@ -56,11 +66,10 @@ public function __construct( /** * Apply price range filter * - * @param \Magento\Framework\App\RequestInterface $request + * @param RequestInterface $request * @return $this - * @throws \Magento\Framework\Exception\LocalizedException */ - public function apply(\Magento\Framework\App\RequestInterface $request) + public function apply(RequestInterface $request) { /** * Filter must be string: $fromPrice-$toPrice @@ -71,6 +80,8 @@ public function apply(\Magento\Framework\App\RequestInterface $request) } list($from, $to) = explode('-', $filter); + $from = (float)$from; + $to = (float)$to; $this->getLayer() ->getProductCollection() @@ -90,14 +101,12 @@ public function apply(\Magento\Framework\App\RequestInterface $request) * Get data array for building attribute filter items * * @return array - * @throws \Magento\Framework\Exception\LocalizedException - * @SuppressWarnings(PHPMD.NPathComplexity) */ protected function _getItemsData() { $attribute = $this->getAttributeModel(); - /** @var \Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection $productCollection */ + /** @var ProductCollection $productCollection */ $productCollection = $this->getLayer()->getProductCollection(); $productSize = $productCollection->getSize(); $facets = $productCollection->getFacetedData($attribute->getAttributeCode()); @@ -123,7 +132,7 @@ protected function _getItemsData() 'value' => $value, 'count' => $count, 'from' => $from, - 'to' => $to + 'to' => $to, ]; } @@ -135,7 +144,7 @@ protected function _getItemsData() * * @param float|string $fromPrice * @param float|string $toPrice - * @return \Magento\Framework\Phrase + * @return Phrase */ protected function renderRangeLabel($fromPrice, $toPrice) { @@ -146,6 +155,7 @@ protected function renderRangeLabel($fromPrice, $toPrice) if ($fromPrice != $toPrice) { $toPrice -= .01; } + return __('%1 - %2', $formattedFromPrice, $this->priceCurrency->format($toPrice)); } } diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/AttributeTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/AttributeTest.php index d90b612b7a1bc..6036798e446df 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/AttributeTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/AttributeTest.php @@ -13,6 +13,7 @@ use Magento\Catalog\Model\Layer\Filter\Item\DataBuilder; use Magento\Catalog\Model\Layer\Filter\ItemFactory; use Magento\Catalog\Model\Layer\State; +use Magento\Catalog\Model\ResourceModel\Eav\Attribute as EavAttribute; use Magento\Catalog\Model\ResourceModel\Layer\Filter\AttributeFactory; use Magento\CatalogSearch\Model\Layer\Filter\Attribute; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection; @@ -25,6 +26,8 @@ use PHPUnit\Framework\TestCase; /** + * Unit tests for \Magento\CatalogSearch\Model\Layer\Filter\Attribute class. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class AttributeTest extends TestCase @@ -34,36 +37,39 @@ class AttributeTest extends TestCase */ private $target; - /** @var AbstractFrontend|MockObject */ + /** @var AbstractFrontend|MockObject */ private $frontend; - /** @var Collection|MockObject */ + /** @var Collection|MockObject */ private $fulltextCollection; - /** @var State|MockObject */ + /** @var State|MockObject */ private $state; - /** @var \Magento\Eav\Model\Entity\Attribute|MockObject */ + /** @var EavAttribute|MockObject */ private $attribute; /** @var RequestInterface|MockObject */ private $request; - /** @var AttributeFactory|MockObject */ + /** @var AttributeFactory|MockObject */ private $filterAttributeFactory; - /** @var ItemFactory|MockObject */ + /** @var ItemFactory|MockObject */ private $filterItemFactory; - /** @var StoreManagerInterface|MockObject */ + /** @var StoreManagerInterface|MockObject */ private $storeManager; - /** @var Layer|MockObject */ + /** @var Layer|MockObject */ private $layer; /** @var DataBuilder|MockObject */ private $itemDataBuilder; + /** + * @inheritdoc + */ protected function setUp(): void { /** @var ItemFactory $filterItemFactory */ @@ -96,9 +102,7 @@ protected function setUp(): void ->setMethods(['addItemData', 'build']) ->getMock(); - $this->filterAttributeFactory = $this->getMockBuilder( - AttributeFactory::class - ) + $this->filterAttributeFactory = $this->getMockBuilder(AttributeFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); @@ -115,9 +119,14 @@ protected function setUp(): void ->disableOriginalConstructor() ->setMethods(['getOption', 'getSelectOptions']) ->getMock(); - $this->attribute = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute::class) + $this->attribute = $this->getMockBuilder(EavAttribute::class) ->disableOriginalConstructor() - ->setMethods(['getAttributeCode', 'getFrontend', 'getIsFilterable']) + ->onlyMethods([ + 'getAttributeCode', + 'getFrontend', + 'getIsFilterable', + 'getBackendType', + ]) ->getMock(); $this->request = $this->getMockBuilder(RequestInterface::class) @@ -146,15 +155,16 @@ protected function setUp(): void ); } - public function testApplyFilter() + /** + * @dataProvider attributeDataProvider + * @param array $attributeData + * @return void + */ + public function testApplyFilter(array $attributeData) { - $attributeCode = 'attributeCode'; - $attributeValue = 'attributeValue'; - $attributeLabel = 'attributeLabel'; - $this->attribute->expects($this->exactly(2)) ->method('getAttributeCode') - ->willReturn($attributeCode); + ->willReturn($attributeData['attribute_code']); $this->attribute->expects($this->atLeastOnce()) ->method('getFrontend') ->willReturn($this->frontend); @@ -163,45 +173,88 @@ public function testApplyFilter() $this->request->expects($this->once()) ->method('getParam') - ->with($attributeCode) - ->willReturn($attributeValue); + ->with($attributeData['attribute_code']) + ->willReturn($attributeData['attribute_value']); + + $this->attribute->expects($this->once()) + ->method('getBackendType') + ->willReturn($attributeData['backend_type']); $this->fulltextCollection->expects($this->once()) ->method('addFieldToFilter') - ->with($attributeCode, $attributeValue)->willReturnSelf(); + ->with( + $attributeData['attribute_code'], + $attributeData['attribute_value'] + ) + ->willReturnSelf(); $this->frontend->expects($this->once()) ->method('getOption') - ->with($attributeValue) - ->willReturn($attributeLabel); - - $filterItem = $this->createFilterItem(0, $attributeLabel, $attributeValue, 0); + ->with($attributeData['attribute_value']) + ->willReturn($attributeData['attribute_label']); + + $filterItem = $this->createFilterItem( + 0, + $attributeData['attribute_label'], + $attributeData['attribute_value'], + 0 + ); $filterItem->expects($this->once()) ->method('setFilter') - ->with($this->target)->willReturnSelf(); + ->with($this->target) + ->willReturnSelf(); $filterItem->expects($this->once()) ->method('setLabel') - ->with($attributeLabel)->willReturnSelf(); + ->with($attributeData['attribute_label']) + ->willReturnSelf(); $filterItem->expects($this->once()) ->method('setValue') - ->with($attributeValue)->willReturnSelf(); + ->with($attributeData['attribute_value']) + ->willReturnSelf(); $filterItem->expects($this->once()) ->method('setCount') - ->with(0)->willReturnSelf(); + ->with(0) + ->willReturnSelf(); $this->state->expects($this->once()) ->method('addFilter') - ->with($filterItem)->willReturnSelf(); + ->with($filterItem) + ->willReturnSelf(); $result = $this->target->apply($this->request); $this->assertEquals($this->target, $result); } + /** + * @return array + */ + public function attributeDataProvider(): array + { + return [ + 'Attribute with \'text\' backend type' => [ + [ + 'attribute_code' => 'attributeCode', + 'attribute_value' => 'attributeValue', + 'attribute_label' => 'attributeLabel', + 'backend_type' => 'text', + ], + ], + 'Attribute with \'int\' backend type' => [ + [ + 'attribute_code' => 'attributeCode', + 'attribute_value' => '0', + 'attribute_label' => 'attributeLabel', + 'backend_type' => 'int', + ], + ], + ]; + } + public function testGetItemsWithApply() { $attributeCode = 'attributeCode'; diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup.xml new file mode 100644 index 0000000000000..455d86d3709ac --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup"> + <annotations> + <description>Clicks the order number link from the checkout success page</description> + </annotations> + <click selector="{{CheckoutSuccessMainSection.orderLink}}" stepKey="clickOrderLink"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <waitForText selector="{{StorefrontCustomerAccountMainSection.pageTitle}}" userInput="Order #" stepKey="waitForPageTitle"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontClickProceedToCheckoutActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontClickProceedToCheckoutActionGroup.xml new file mode 100644 index 0000000000000..97073106c305e --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontClickProceedToCheckoutActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontClickProceedToCheckoutActionGroup"> + <annotations> + <description>Click Proceed to Checkout button.</description> + </annotations> + + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontOpenCheckoutPageInCustomStoreActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontOpenCheckoutPageInCustomStoreActionGroup.xml new file mode 100644 index 0000000000000..3a9ffade0a8a2 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontOpenCheckoutPageInCustomStoreActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontOpenCustomStoreCheckoutPageActionGroup" extends="StorefrontOpenCheckoutPageActionGroup"> + <annotations> + <description>Goes to the Storefront Checkout page in a custom store. Must have Add Store Code To Urls enabled</description> + </annotations> + <arguments> + <argument name="storeCode" defaultValue="{{customStoreEN.code}}" type="string"/> + </arguments> + <amOnPage url="{{StorefrontCustomStoreCheckoutPage.url(storeCode)}}" stepKey="openCheckoutPage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/Page/StorefrontCustomStoreCheckoutPage.xml b/app/code/Magento/Checkout/Test/Mftf/Page/StorefrontCustomStoreCheckoutPage.xml new file mode 100644 index 0000000000000..632687d049b1f --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Page/StorefrontCustomStoreCheckoutPage.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="StorefrontCustomStoreCheckoutPage" url="/{{storeCode}}/checkout" area="storefront" module="Magento_Checkout" parameterized="true"> + <section name="StorefrontCheckoutPageMessagesSection"/> + <section name="CheckoutShippingSection"/> + <section name="CheckoutShippingMethodsSection"/> + <section name="CheckoutOrderSummarySection"/> + <section name="CheckoutSuccessMainSection"/> + <section name="CheckoutPaymentSection"/> + <section name="SelectShippingBillingPopupSection"/> + </page> +</pages> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontCheckoutPaymentMethodSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontCheckoutPaymentMethodSection.xml index 40214b9c11fb0..dae6bfca9757d 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontCheckoutPaymentMethodSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontCheckoutPaymentMethodSection.xml @@ -13,8 +13,8 @@ <element name="checkPaymentMethodByName" type="radio" selector="//div[@id='checkout-payment-method-load']//div[@class='payment-method']//label//span[contains(., '{{methodName}}')]/../..//input" parameterized="true"/> <element name="billingAddressSameAsShipping" type="checkbox" selector=".payment-method._active [name='billing-address-same-as-shipping']"/> <element name="billingAddressSameAsShippingShared" type="checkbox" selector="#billing-address-same-as-shipping-shared"/> - <element name="paymentOnAccount" type="radio" selector="#companycredit"/> - <element name="paymentOnAccountLabel" type="text" selector="//span[text()='Payment on Account']"/> + <element name="paymentOnAccount" type="radio" selector="#companycredit" deprecated="Use StorefrontCheckoutPaymentSection.paymentOnAccount B2B repository"/> + <element name="paymentOnAccountLabel" type="text" selector="//span[text()='Payment on Account']" deprecated="Use StorefrontCheckoutPaymentSection.paymentOnAccountLabel in B2B repository"/> <element name="purchaseOrderNumber" type="input" selector="#po_number"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingDefaultAddressTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingDefaultAddressTest.xml index e90e1bf5a2e82..3d7f8e1dcfed4 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingDefaultAddressTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingDefaultAddressTest.xml @@ -54,8 +54,8 @@ <actionGroup ref="FillShippingZipForm" stepKey="fillShippingZipForm"> <argument name="address" value="US_Address_CA"/> </actionGroup> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> - <waitForPageLoad stepKey="waitForProceedToCheckout"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="clickProceedToCheckout"/> + <comment userInput="Adding the comment to replace waitForProceedToCheckout action for preserving Backward Compatibility" stepKey="waitForProceedToCheckout"/> <!-- Login as customer on checkout page --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNewAddressTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNewAddressTest.xml index e868f74d0253d..e6734cf9257ba 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNewAddressTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNewAddressTest.xml @@ -54,9 +54,8 @@ <actionGroup ref="FillShippingZipForm" stepKey="fillShippingZipForm"> <argument name="address" value="US_Address_CA"/> </actionGroup> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> - <waitForPageLoad stepKey="waitForProceedToCheckout"/> - + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="clickProceedToCheckout"/> + <comment userInput="Adding the comment to replace waitForProceedToCheckout action for preserving Backward Compatibility" stepKey="waitForProceedToCheckout"/> <!-- Login using Sign In link from checkout page --> <actionGroup ref="LoginAsCustomerUsingSignInLinkActionGroup" stepKey="customerLogin"> <argument name="customer" value="$$createCustomer$$"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml index 13968964436b4..8fcb8211c4625 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml @@ -54,8 +54,8 @@ <actionGroup ref="FillShippingZipForm" stepKey="fillShippingZipForm"> <argument name="address" value="US_Address_CA"/> </actionGroup> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> - <waitForPageLoad stepKey="waitForProceedToCheckout"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="clickProceedToCheckout"/> + <comment userInput="Adding the comment to replace waitForProceedToCheckout action for preserving Backward Compatibility" stepKey="waitForProceedToCheckout"/> <!-- Login as customer on checkout page --> <actionGroup ref="LoginAsCustomerOnCheckoutPageActionGroup" stepKey="customerLogin"> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonExistentCustomerGroupTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonExistentCustomerGroupTest.xml index 92dad56e81135..6b1d3a7ba66aa 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonExistentCustomerGroupTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonExistentCustomerGroupTest.xml @@ -66,8 +66,8 @@ <actionGroup ref="FillShippingZipForm" stepKey="fillShippingZipForm"> <argument name="address" value="US_Address_CA"/> </actionGroup> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> - <waitForPageLoad stepKey="waitForProceedToCheckout"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="clickProceedToCheckout"/> + <comment userInput="Adding the comment to replace waitForProceedToCheckout action for preserving Backward Compatibility" stepKey="waitForProceedToCheckout"/> <!-- Check that error does not appear and shipping methods are available to select --> <dontSee selector="{{CheckoutCartMessageSection.errorMessage}}" userInput="No such entity with id = $$createCustomerGroup.id$$" stepKey="assertErrorMessage"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutUsingSignInLinkTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutUsingSignInLinkTest.xml index 42d61abca845b..c7b1c441f1978 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutUsingSignInLinkTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutUsingSignInLinkTest.xml @@ -54,8 +54,8 @@ <actionGroup ref="FillShippingZipForm" stepKey="fillShippingZipForm"> <argument name="address" value="US_Address_CA"/> </actionGroup> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> - <waitForPageLoad stepKey="waitForProceedToCheckout"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="clickProceedToCheckout"/> + <comment userInput="Adding the comment to replace waitForProceedToCheckout action for preserving Backward Compatibility" stepKey="waitForProceedToCheckout"/> <!-- Login using Sign In link from checkout page --> <actionGroup ref="LoginAsCustomerUsingSignInLinkActionGroup" stepKey="customerLogin"> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutWithAllProductTypesTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutWithAllProductTypesTest.xml index a519aac72d1b5..ce8bfc37389fb 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutWithAllProductTypesTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutWithAllProductTypesTest.xml @@ -168,9 +168,8 @@ <actionGroup ref="FillShippingZipForm" stepKey="fillShippingZipForm"> <argument name="address" value="US_Address_CA"/> </actionGroup> - - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> - <waitForPageLoad stepKey="waitForProceedToCheckout"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="clickProceedToCheckout"/> + <comment userInput="Adding the comment to replace waitForProceedToCheckout action for preserving Backward Compatibility" stepKey="waitForProceedToCheckout"/> <actionGroup ref="FillCustomerSignInPopupFormActionGroup" stepKey="fillCustomerSignInPopupForm"> <argument name="customer" value="$$createCustomer$$"/> </actionGroup> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontFreeShippingRecalculationAfterCouponCodeAddedTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontFreeShippingRecalculationAfterCouponCodeAddedTest.xml index 20b94d0f4ec8a..f1ce2f25e6d60 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontFreeShippingRecalculationAfterCouponCodeAddedTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontFreeShippingRecalculationAfterCouponCodeAddedTest.xml @@ -7,9 +7,9 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StoreFrontFreeShippingRecalculationAfterCouponCodeAddedTest"> + <test name="StoreFrontFreeShippingRecalculationAfterCouponCodeAddedTest" deprecated="Use StoreFrontFreeShippingRecalculationAfterCouponCodeAppliedTest instead"> <annotations> - <title value="Checkout Free Shipping Recalculation after Coupon Code Added"/> + <title value="DEPRECATED. Checkout Free Shipping Recalculation after Coupon Code Added"/> <stories value="Checkout Free Shipping Recalculation after Coupon Code Added"/> <description value="User should be able to do checkout free shipping recalculation after adding coupon code"/> <features value="Checkout"/> @@ -17,6 +17,9 @@ <testCaseId value="MAGETWO-96537"/> <useCaseId value="MAGETWO-96431"/> <group value="Checkout"/> + <skip> + <issueId value="DEPRECATED">Use StoreFrontFreeShippingRecalculationAfterCouponCodeAppliedTest instead</issueId> + </skip> </annotations> <before> @@ -88,7 +91,7 @@ <see userInput="Your coupon was successfully applied." stepKey="seeSuccessMessage"/> <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder1"/> <waitForPageLoad stepKey="waitForError"/> - <see stepKey="seeShippingMethodError" userInput="The shipping method is missing. Select the shipping method and try again."/> + <seeElementInDOM selector="{{CheckoutHeaderSection.errorMessageContainsText('The shipping method is missing. Select the shipping method and try again.')}}" stepKey="seeShippingMethodError"/> <amOnPage stepKey="navigateToShippingPage" url="{{CheckoutShippingPage.url}}"/> <waitForPageLoad stepKey="waitForShippingPageLoad"/> <click stepKey="chooseFlatRateShipping" selector="{{CheckoutShippingMethodsSection.shippingMethodRowByName('Flat Rate')}}"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontFreeShippingRecalculationAfterCouponCodeAppliedTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontFreeShippingRecalculationAfterCouponCodeAppliedTest.xml new file mode 100644 index 0000000000000..bc9dddf53ad03 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontFreeShippingRecalculationAfterCouponCodeAppliedTest.xml @@ -0,0 +1,104 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StoreFrontFreeShippingRecalculationAfterCouponCodeAppliedTest"> + <annotations> + <features value="Checkout"/> + <stories value="Checkout Free Shipping Recalculation after Coupon Code Added"/> + <title value="Checkout Free Shipping Recalculation after Coupon Code Added"/> + <description value="User should be able to do checkout free shipping recalculation after adding coupon code"/> + <severity value="BLOCKER"/> + <testCaseId value="MC-28548"/> + <useCaseId value="MAGETWO-96431"/> + <group value="Checkout"/> + </annotations> + + <before> + <createData entity="Simple_US_Customer" stepKey="createCustomer"> + <field key="group_id">1</field> + </createData> + <createData entity="_defaultCategory" stepKey="defaultCategory"/> + <createData entity="_defaultProduct" stepKey="simpleProduct"> + <field key="price">90</field> + <requiredEntity createDataKey="defaultCategory"/> + </createData> + <!--It is default for FlatRate--> + <createData entity="FlatRateShippingMethodConfig" stepKey="enableFlatRate"/> + <createData entity="FreeShippingMethodsSettingConfig" stepKey="freeShippingMethodsSettingConfig"/> + <createData entity="MinimumOrderAmount90" stepKey="minimumOrderAmount90"/> + <actionGroup ref="CliCacheFlushActionGroup" stepKey="flushCache"> + <argument name="tags" value=""/> + </actionGroup> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminCartPriceRuleDeleteAllActionGroup" stepKey="deleteAllCartPriceRules"/> + <actionGroup ref="AdminCreateCartPriceRuleWithCouponCodeActionGroup" stepKey="createCartPriceRule"> + <argument name="ruleName" value="CatPriceRule"/> + <argument name="couponCode" value="CatPriceRule.coupon_code"/> + </actionGroup> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginToStoreFront"> + <argument name="Customer" value="$createCustomer$"/> + </actionGroup> + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductPage"> + <argument name="productUrl" value="$simpleProduct.custom_attributes[url_key]$"/> + </actionGroup> + </before> + + <after> + <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="defaultCategory" stepKey="deleteCategory"/> + <createData entity="DefaultShippingMethodsConfig" stepKey="defaultShippingMethodsConfig"/> + <createData entity="DefaultMinimumOrderAmount" stepKey="defaultMinimumOrderAmount"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + + <actionGroup ref="CliCacheFlushActionGroup" stepKey="flushCache"> + <argument name="tags" value=""/> + </actionGroup> + <actionGroup ref="AdminCartPriceRuleDeleteAllActionGroup" stepKey="deleteAllCartPriceRules"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + </after> + + <actionGroup ref="ApplyCartRuleOnStorefrontActionGroup" stepKey="applyCartRule"> + <argument name="product" value="$simpleProduct$"/> + <argument name="couponCode" value="{{CatPriceRule.coupon_code}}"/> + </actionGroup> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart1"/> + <waitForPageLoad stepKey="waitForpageLoad1"/> + <dontSee selector="{{CheckoutShippingMethodsSection.shippingMethodRowByName('Free')}}" stepKey="dontSeeFreeShipping"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToShoppingCartPage"/> + <conditionalClick selector="{{DiscountSection.DiscountTab}}" dependentSelector="{{DiscountSection.CouponInput}}" visible="false" stepKey="openDiscountTabIfClosed"/> + <waitForPageLoad stepKey="waitForCouponTabOpen1"/> + <click selector="{{DiscountSection.CancelCoupon}}" stepKey="cancelCoupon"/> + <waitForPageLoad stepKey="waitForCancel"/> + <see userInput='You canceled the coupon code.' stepKey="seeCancellationMessage"/> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart2"/> + <waitForPageLoad stepKey="waitForShippingMethods"/> + <click selector="{{CheckoutShippingMethodsSection.shippingMethodRowByName('Free')}}" stepKey="chooseFreeShipping"/> + <actionGroup ref="StorefrontCheckoutClickNextOnShippingStepActionGroup" stepKey="clickNextAfterFreeShippingMethodSelection"/> + <waitForPageLoad stepKey="waitForReviewAndPayments"/> + <actionGroup ref="StorefrontApplyDiscountCodeActionGroup" stepKey="applyCouponCode"> + <argument name="discountCode" value="{{CatPriceRule.coupon_code}}"/> + </actionGroup> + <!-- Assert order cannot be placed and error message will shown. --> + <actionGroup ref="AssertStorefrontOrderIsNotPlacedActionGroup" stepKey="seeShippingMethodError"> + <argument name="error" value="The shipping method is missing. Select the shipping method and try again."/> + </actionGroup> + <amOnPage stepKey="navigateToShippingPage" url="{{CheckoutShippingPage.url}}"/> + <waitForPageLoad stepKey="waitForShippingPageLoad"/> + + <click stepKey="chooseFlatRateShipping" selector="{{CheckoutShippingMethodsSection.shippingMethodRowByName('Flat Rate')}}"/> + <actionGroup ref="CheckoutSelectFlatRateShippingMethodActionGroup" stepKey="selectFlatRateShippingMethod"/> + <actionGroup ref="StorefrontCheckoutClickNextOnShippingStepActionGroup" stepKey="clickNextAfterFlatRateShippingMethodSelection"/> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectPaymentMethod"/> + <!-- Place Order --> + <actionGroup ref="CheckoutPlaceOrderActionGroup" stepKey="placeOrder"> + <argument name="orderNumberMessage" value="CONST.successCheckoutOrderNumberMessage"/> + <argument name="emailYouMessage" value="CONST.successCheckoutEmailYouMessage"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutForShowShippingMethodNoApplicableTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutForShowShippingMethodNoApplicableTest.xml index 22bc1260e5f33..3c06ca17cfedc 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutForShowShippingMethodNoApplicableTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutForShowShippingMethodNoApplicableTest.xml @@ -58,6 +58,6 @@ <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickNextButton"/> <!-- Assert order cannot be placed and error message will shown. --> <waitForPageLoad stepKey="waitForError"/> - <see stepKey="seeShippingMethodError" userInput="The shipping method is missing. Select the shipping method and try again."/> + <seeElementInDOM selector="{{CheckoutHeaderSection.errorMessageContainsText('The shipping method is missing. Select the shipping method and try again')}}" stepKey="seeShippingMethodError"/> </test> </tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndCreateCustomerAfterCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndCreateCustomerAfterCheckoutTest.xml index ef1f30e2d9c36..1ea3f118f9f23 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndCreateCustomerAfterCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndCreateCustomerAfterCheckoutTest.xml @@ -50,8 +50,8 @@ <!-- Fill the Estimate Shipping and Tax section --> <actionGroup ref="CheckoutFillEstimateShippingAndTaxActionGroup" stepKey="fillEstimateShippingAndTaxFields"/> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout"/> - <waitForPageLoad stepKey="waitForPageToLoad"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="goToCheckout"/> + <comment userInput="Adding the comment to replace waitForPageToLoad action for preserving Backward Compatibility" stepKey="waitForPageToLoad"/> <!-- Fill the guest form --> <actionGroup ref="FillGuestCheckoutShippingAddressFormActionGroup" stepKey="fillGuestShippingAddress"> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndProductWithTierPricesTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndProductWithTierPricesTest.xml index c724cf4986aa9..35328c58cdd49 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndProductWithTierPricesTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndProductWithTierPricesTest.xml @@ -65,8 +65,8 @@ <!-- Fill the Estimate Shipping and Tax section --> <actionGroup ref="CheckoutFillEstimateShippingAndTaxActionGroup" stepKey="fillEstimateShippingAndTaxFields"/> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout"/> - <waitForPageLoad stepKey="waitForPageToLoad"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="goToCheckout"/> + <comment userInput="Adding the comment to replace waitForPageToLoad action for preserving Backward Compatibility" stepKey="waitForPageToLoad"/> <!-- Fill the guest form --> <actionGroup ref="FillGuestCheckoutShippingAddressWithCountryActionGroup" stepKey="fillGuestForm"> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndRegisterCustomerAfterCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndRegisterCustomerAfterCheckoutTest.xml index 118205e912b5e..e50a1880dee3c 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndRegisterCustomerAfterCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndRegisterCustomerAfterCheckoutTest.xml @@ -52,8 +52,8 @@ <!-- Fill the Estimate Shipping and Tax section --> <actionGroup ref="CheckoutFillEstimateShippingAndTaxActionGroup" stepKey="fillEstimateShippingAndTaxFields"/> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout"/> - <waitForPageLoad stepKey="waitForPageToLoad"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="goToCheckout"/> + <comment userInput="Adding the comment to replace waitForPageToLoad action for preserving Backward Compatibility" stepKey="waitForPageToLoad"/> <!-- Fill the guest form --> <actionGroup ref="FillGuestCheckoutShippingAddressFormActionGroup" stepKey="fillGuestForm"> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithSpecialPriceProductsTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithSpecialPriceProductsTest.xml index 6abd62542e5c9..646983eafcf08 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithSpecialPriceProductsTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithSpecialPriceProductsTest.xml @@ -134,8 +134,8 @@ <!-- Fill the Estimate Shipping and Tax section --> <actionGroup ref="CheckoutFillEstimateShippingAndTaxActionGroup" stepKey="fillEstimateShippingAndTaxFields"/> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout"/> - <waitForPageLoad stepKey="waitForPageToLoad"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="goToCheckout"/> + <comment userInput="Adding the comment to replace waitForPageToLoad action for preserving Backward Compatibility" stepKey="waitForPageToLoad"/> <!-- Sign in using already existed customer details --> <fillField selector="{{CheckoutShippingSection.emailAddress}}" userInput="$$createCustomer.email$$" stepKey="fillEmailAddress"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutOnLoginWhenGuestCheckoutIsDisabledTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutOnLoginWhenGuestCheckoutIsDisabledTest.xml index feab271110356..bc24b8895d50a 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutOnLoginWhenGuestCheckoutIsDisabledTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutOnLoginWhenGuestCheckoutIsDisabledTest.xml @@ -53,7 +53,7 @@ <!-- Fill the Estimate Shipping and Tax section --> <actionGroup ref="CheckoutFillEstimateShippingAndTaxActionGroup" stepKey="fillEstimateShippingAndTaxFields"/> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="goToCheckout"/> <!--Fill the pop up sign form --> <actionGroup ref="StorefrontCustomerSignInPopUpActionGroup" stepKey="customerSignIn"> @@ -62,8 +62,8 @@ </actionGroup> <waitForElementVisible selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="waitProceedToCheckout"/> <scrollTo selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="scrollToGoToCheckout"/> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout1"/> - <waitForPageLoad stepKey="waitForShippingMethodSectionToLoad"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="goToCheckout1"/> + <comment userInput="Adding the comment to replace waitForShippingMethodSectionToLoad action for preserving Backward Compatibility" stepKey="waitForShippingMethodSectionToLoad"/> <actionGroup ref="StorefrontCheckoutClickNextButtonActionGroup" stepKey="clickOnNextButton"/> <!-- Verify order summary on payment page --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutWithNewCustomerRegistrationAndDisableGuestCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutWithNewCustomerRegistrationAndDisableGuestCheckoutTest.xml index 0d3bfe7b1fb84..526d23afa05e0 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutWithNewCustomerRegistrationAndDisableGuestCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutWithNewCustomerRegistrationAndDisableGuestCheckoutTest.xml @@ -50,8 +50,8 @@ <!--Open View and edit --> <actionGroup ref="ClickViewAndEditCartFromMiniCartActionGroup" stepKey="clickMiniCart"/> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout"/> - <waitForPageLoad stepKey="waitForPageToLoad"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="goToCheckout"/> + <comment userInput="Adding the comment to replace waitForPageToLoad action for preserving Backward Compatibility" stepKey="waitForPageToLoad"/> <!--Create an account--> <waitForElementVisible selector="{{StorefrontCustomerSignInPopupFormSection.createAnAccount}}" stepKey="waitForElementToBeVisible"/> @@ -86,7 +86,7 @@ <actionGroup ref="ClickViewAndEditCartFromMiniCartActionGroup" stepKey="openViewAndEditOption"/> <!-- Proceed to checkout --> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout1"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="goToCheckout1"/> <actionGroup ref="StorefrontCheckoutClickNextButtonActionGroup" stepKey="clickOnNextButton"/> <!-- Verify order summary on payment page --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerLoginDuringCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerLoginDuringCheckoutTest.xml index 27597a72fdb7f..4b98a1f177af8 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerLoginDuringCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerLoginDuringCheckoutTest.xml @@ -64,8 +64,8 @@ <!-- Go to Checkout page --> <actionGroup ref="ClickViewAndEditCartFromMiniCartActionGroup" stepKey="goToShoppingCartFromMinicart"/> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> - <waitForPageLoad stepKey="waitForProceedToCheckout"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="clickProceedToCheckout"/> + <comment userInput="Adding the comment to replace waitForProceedToCheckout action for preserving Backward Compatibility" stepKey="waitForProceedToCheckout"/> <!-- Input in field email and password for newly created customer; click Login button --> <actionGroup ref="LoginAsCustomerOnCheckoutPageActionGroup" stepKey="customerLogin"> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontDeleteSimpleProductFromMiniShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontDeleteSimpleProductFromMiniShoppingCartTest.xml index effd376ab4bfb..2d6f36c78edf6 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontDeleteSimpleProductFromMiniShoppingCartTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontDeleteSimpleProductFromMiniShoppingCartTest.xml @@ -54,6 +54,7 @@ <argument name="productName" value="$$simpleProduct.name$$"/> </actionGroup> <reloadPage stepKey="reloadPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> <!--Check the minicart is empty and verify AssertProductAbsentInMiniShoppingCart--> <actionGroup ref="AssertMiniCartEmptyActionGroup" stepKey="miniCartEnpty"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutUsingFreeShippingAndTaxesTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutUsingFreeShippingAndTaxesTest.xml index 033898bb90557..c03f10c3b2f38 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutUsingFreeShippingAndTaxesTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutUsingFreeShippingAndTaxesTest.xml @@ -179,9 +179,8 @@ <!--Select Free Shipping and proceed to checkout --> <click selector="{{AdminOrderFormPaymentSection.freeShippingOption}}" stepKey="selectFlatRateShippingMethod"/> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout"/> - <waitForPageLoad stepKey="waitForPageToLoad"/> - + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="goToCheckout"/> + <comment userInput="Adding the comment to replace waitForPageToLoad action for preserving Backward Compatibility" stepKey="waitForPageToLoad"/> <!-- Fill Guest form --> <actionGroup ref="FillGuestCheckoutShippingAddressFormActionGroup" stepKey="fillTheSignInForm"> <argument name="customer" value="Simple_US_Customer"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutWithCouponAndZeroSubtotalTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutWithCouponAndZeroSubtotalTest.xml index f910a9d47244f..c58e4ab7513af 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutWithCouponAndZeroSubtotalTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutWithCouponAndZeroSubtotalTest.xml @@ -49,8 +49,8 @@ <!-- Assert Discount and proceed to checkout --> <waitForElementVisible selector="{{CheckoutCartSummarySection.discountAmount}}" stepKey="waitForDiscountElement"/> <see selector="{{CheckoutCartSummarySection.discountAmount}}" userInput="-$50.00" stepKey="seeDiscountTotal"/> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout"/> - <waitForPageLoad stepKey="waitForPageToLoad"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="goToCheckout"/> + <comment userInput="Adding the comment to replace waitForPageToLoad action for preserving Backward Compatibility" stepKey="waitForPageToLoad"/> <!--Fill Customer Information --> <fillField selector="{{CheckoutShippingSection.emailAddress}}" userInput="{{Simple_US_Customer.email}}" stepKey="enterEmail"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontOnePageCheckoutDataWhenChangeQtyTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontOnePageCheckoutDataWhenChangeQtyTest.xml index 82324525bad24..321511e4e86d9 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontOnePageCheckoutDataWhenChangeQtyTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontOnePageCheckoutDataWhenChangeQtyTest.xml @@ -73,7 +73,7 @@ <actualResult type="const">$grabQty</actualResult> <expectedResult type="const">2</expectedResult> </assertEquals> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="clickProceedToCheckout"/> <!--Check that form is filled with customer data--> <grabValueFrom selector="{{CheckoutShippingSection.email}}" stepKey="grabEmail1"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontPersistentDataForGuestCustomerWithPhysicalQuoteTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontPersistentDataForGuestCustomerWithPhysicalQuoteTest.xml index a3c093d005371..3ab3a0b4ad3f7 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontPersistentDataForGuestCustomerWithPhysicalQuoteTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontPersistentDataForGuestCustomerWithPhysicalQuoteTest.xml @@ -57,7 +57,7 @@ <argument name="methodCode" value="flatrate"/> </actionGroup> <!-- 6. Go to Checkout --> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="clickProceedToCheckout"/> <actionGroup ref="StorefrontAssertCheckoutEstimateShippingInformationActionGroup" stepKey="assertCheckoutEstimateShippingInformationAfterGoingToCheckout"/> <actionGroup ref="StorefrontAssertCheckoutShippingMethodSelectedActionGroup" stepKey="assertFlatRateShippingMethodIsCheckedAfterGoingToCheckout"> <argument name="shippingMethod" value="Flat Rate"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductNameMinicartOnCheckoutPageDifferentStoreViewsTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductNameMinicartOnCheckoutPageDifferentStoreViewsTest.xml index f20d0b790edfa..91086a4b3788e 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductNameMinicartOnCheckoutPageDifferentStoreViewsTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductNameMinicartOnCheckoutPageDifferentStoreViewsTest.xml @@ -94,8 +94,8 @@ </assertStringContainsString> <!--Proceed to checkout and check product name in Order Summary area--> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="proceedToCheckout"/> - <waitForPageLoad stepKey="waitForShippingPageLoad"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="proceedToCheckout"/> + <comment userInput="Adding the comment to replace waitForShippingPageLoad action for preserving Backward Compatibility" stepKey="waitForShippingPageLoad"/> <click selector="{{CheckoutShippingGuestInfoSection.itemInCart}}" stepKey="clickItemInCart"/> <grabTextFrom selector="{{CheckoutShippingGuestInfoSection.productName}}" stepKey="grabProductNameShipping"/> <assertStringContainsString stepKey="assertProductNameShipping"> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductQuantityChangesInBackendAfterCustomerCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductQuantityChangesInBackendAfterCustomerCheckoutTest.xml index 3978389691b55..50f906fa9d2ca 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductQuantityChangesInBackendAfterCustomerCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductQuantityChangesInBackendAfterCustomerCheckoutTest.xml @@ -46,9 +46,8 @@ <!-- Fill the Estimate Shipping and Tax section --> <actionGroup ref="CheckoutFillEstimateShippingAndTaxActionGroup" stepKey="fillEstimateShippingAndTaxFields"/> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout"/> - <waitForPageLoad stepKey="waitForPageToLoad"/> - + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="goToCheckout"/> + <comment userInput="Adding the comment to replace clickAddProductToggle action for preserving Backward Compatibility" stepKey="waitForPageToLoad"/> <!-- Fill Customer Sign In Information --> <actionGroup ref="FillGuestCheckoutShippingAddressFormActionGroup" stepKey="fillGuestForm"> <argument name="customer" value="UKCustomer"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontRefreshPageDuringGuestCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontRefreshPageDuringGuestCheckoutTest.xml index 43b2262265841..99a9e0273430d 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontRefreshPageDuringGuestCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontRefreshPageDuringGuestCheckoutTest.xml @@ -42,8 +42,8 @@ <!-- Go to Checkout page --> <actionGroup ref="ClickViewAndEditCartFromMiniCartActionGroup" stepKey="goToShoppingCartFromMinicart"/> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> - <waitForPageLoad stepKey="waitForProceedToCheckout"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="clickProceedToCheckout"/> + <comment userInput="Adding the comment to replace waitForPageToLoad action for preserving Backward Compatibility" stepKey="waitForProceedToCheckout"/> <!-- Fill email field and addresses form and go next --> <actionGroup ref="GuestCheckoutFillingShippingSectionActionGroup" stepKey="guestCheckoutFillingShipping"> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUKCustomerCheckoutWithCouponTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUKCustomerCheckoutWithCouponTest.xml index d037718a1ec94..a8b8cf66f545b 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUKCustomerCheckoutWithCouponTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUKCustomerCheckoutWithCouponTest.xml @@ -77,16 +77,16 @@ <!-- Assert Discount and proceed to checkout --> <waitForElementVisible selector="{{CheckoutCartSummarySection.discountAmount}}" stepKey="waitForDiscountElement"/> <see selector="{{CheckoutCartSummarySection.discountAmount}}" userInput="-$15.00" stepKey="seeDiscountTotal"/> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout"/> - <waitForPageLoad stepKey="waitForPageToLoad1"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="goToCheckout"/> + <comment userInput="Adding the comment to replace waitForPageToLoad1 action for preserving Backward Compatibility" stepKey="waitForPageToLoad1"/> <!--Fill the pop up sign form --> <actionGroup ref="StorefrontCustomerSignInPopUpActionGroup" stepKey="customerSignIn"> <argument name="customerEmail" value="$$createCustomer.email$$"/> <argument name="customerPwd" value="$$createCustomer.password$$"/> </actionGroup> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout1"/> - <waitForPageLoad stepKey="waitForShippingMethodSectionToLoad"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="goToCheckout1"/> + <comment userInput="Adding the comment to replace waitForShippingMethodSectionToLoad action for preserving Backward Compatibility" stepKey="waitForShippingMethodSectionToLoad"/> <!-- Click and open order summary tab--> <conditionalClick selector="{{CheckoutOrderSummarySection.miniCartTab}}" dependentSelector="{{CheckoutOrderSummarySection.miniCartTabClosed}}" visible="true" stepKey="clickOnOrderSummaryTab"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUKGuestCheckoutWithConditionProductQuantityEqualsToOrderedQuantityTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUKGuestCheckoutWithConditionProductQuantityEqualsToOrderedQuantityTest.xml index 10eee5938b27d..dfb778bc2e027 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUKGuestCheckoutWithConditionProductQuantityEqualsToOrderedQuantityTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUKGuestCheckoutWithConditionProductQuantityEqualsToOrderedQuantityTest.xml @@ -43,8 +43,8 @@ <!-- Fill the Estimate Shipping and Tax section --> <actionGroup ref="CheckoutFillEstimateShippingAndTaxActionGroup" stepKey="fillEstimateShippingAndTaxFields"/> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout"/> - <waitForPageLoad stepKey="waitForPageToLoad"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="goToCheckout"/> + <comment userInput="Adding the comment to replace waitForPageToLoad action for preserving Backward Compatibility" stepKey="waitForPageToLoad"/> <!-- Fill Customer Sign In Information --> <actionGroup ref="FillGuestCheckoutShippingAddressFormActionGroup" stepKey="fillGuestForm"> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUSCustomerCheckoutWithCouponAndBankTransferPaymentMethodTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUSCustomerCheckoutWithCouponAndBankTransferPaymentMethodTest.xml index dc92c0c5ce9ee..7be123d9cb71f 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUSCustomerCheckoutWithCouponAndBankTransferPaymentMethodTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUSCustomerCheckoutWithCouponAndBankTransferPaymentMethodTest.xml @@ -56,8 +56,8 @@ <argument name="coupon" value="$$createCouponForCartPriceRule$$"/> </actionGroup> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout"/> - <waitForPageLoad stepKey="waitForPageToLoad"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="goToCheckout"/> + <comment userInput="Adding the comment to replace waitForPageToLoad action for preserving Backward Compatibility" stepKey="waitForPageToLoad"/> <!-- Fill the guest form --> <actionGroup ref="FillGuestCheckoutShippingAddressFormActionGroup" stepKey="fillGuestForm"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontVerifyGuestCheckoutUsingFreeShippingAndTaxesTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontVerifyGuestCheckoutUsingFreeShippingAndTaxesTest.xml index 49af0a285b5f4..38cc4fe8abcf3 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontVerifyGuestCheckoutUsingFreeShippingAndTaxesTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontVerifyGuestCheckoutUsingFreeShippingAndTaxesTest.xml @@ -135,8 +135,8 @@ <waitForPageLoad stepKey="waitForPageToReload"/> <see selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$9.72" stepKey="seeTaxAmountAfterLoadPage"/> <scrollTo selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="scrollToProceedToCheckout" /> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout"/> - <waitForPageLoad stepKey="waitForPageToLoad"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="goToCheckout"/> + <comment userInput="Adding the comment to replace waitForPageToLoad action for preserving Backward Compatibility" stepKey="waitForPageToLoad"/> <actionGroup ref="FillGuestCheckoutShippingAddressFormActionGroup" stepKey="fillTheSignInForm"> <argument name="customer" value="Simple_US_Customer"/> <argument name="customerAddress" value="US_Address_NY_Default_Shipping"/> diff --git a/app/code/Magento/Checkout/Test/Unit/Model/ShippingInformationManagementTest.php b/app/code/Magento/Checkout/Test/Unit/Model/ShippingInformationManagementTest.php index 8618626642421..6ff782d94e4eb 100644 --- a/app/code/Magento/Checkout/Test/Unit/Model/ShippingInformationManagementTest.php +++ b/app/code/Magento/Checkout/Test/Unit/Model/ShippingInformationManagementTest.php @@ -32,6 +32,7 @@ use Magento\Quote\Model\ShippingAssignmentFactory; use Magento\Quote\Model\ShippingFactory; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\MockObject\RuntimeException; use PHPUnit\Framework\TestCase; /** @@ -148,7 +149,7 @@ protected function setUp(): void 'importCustomerAddressData', 'save', 'getShippingRateByCode', - 'getShippingMethod' + 'getShippingMethod', ] ) ->disableOriginalConstructor() @@ -167,7 +168,7 @@ protected function setUp(): void 'collectTotals', 'getExtensionAttributes', 'setExtensionAttributes', - 'setBillingAddress' + 'setBillingAddress', ] ) ->disableOriginalConstructor() @@ -238,9 +239,7 @@ private function setShippingAssignmentsMocks($shippingMethod): void ->willReturn(null); $this->shippingAddressMock->expects($this->once()) ->method('setLimitCarrier'); - $this->cartExtensionMock = $this->getMockBuilder(CartExtension::class) - ->addMethods(['getShippingAssignments', 'setShippingAssignments']) - ->getMock(); + $this->cartExtensionMock = $this->getCartExtensionMock(); $this->cartExtensionFactoryMock->expects($this->once()) ->method('create') ->willReturn($this->cartExtensionMock); @@ -622,4 +621,21 @@ public function testSaveAddressInformation(): void $this->model->saveAddressInformation($cartId, $addressInformationMock) ); } + + /** + * Build cart extension mock. + * + * @return MockObject + */ + private function getCartExtensionMock(): MockObject + { + $mockBuilder = $this->getMockBuilder(CartExtension::class); + try { + $mockBuilder->addMethods(['getShippingAssignments', 'setShippingAssignments']); + } catch (RuntimeException $e) { + // CartExtension already generated. + } + + return $mockBuilder->getMock(); + } } diff --git a/app/code/Magento/Checkout/view/frontend/web/js/action/set-payment-information-extended.js b/app/code/Magento/Checkout/view/frontend/web/js/action/set-payment-information-extended.js index ae5b0914e83a6..43c8c5a3a4fe8 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/action/set-payment-information-extended.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/action/set-payment-information-extended.js @@ -30,7 +30,7 @@ define([ list[key] = filterTemplateData(value); } - if (key === '__disableTmpl') { + if (key === '__disableTmpl' || key === 'title') { delete list[key]; } }); diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/address-converter.js b/app/code/Magento/Checkout/view/frontend/web/js/model/address-converter.js index a59ea7101f16c..d296999c88b53 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/address-converter.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/address-converter.js @@ -27,7 +27,8 @@ define([ // clone address form data to new object var addressData = $.extend(true, {}, formData), region, - regionName = addressData.region; + regionName = addressData.region, + customAttributes; if (mageUtils.isObject(addressData.street)) { addressData.street = this.objectToArray(addressData.street); @@ -64,10 +65,20 @@ define([ addressData['custom_attributes'] = _.map( addressData['custom_attributes'], function (value, key) { - return { + customAttributes = { 'attribute_code': key, 'value': value }; + + if (typeof value === 'boolean') { + customAttributes = { + 'attribute_code': key, + 'value': value, + 'label': value === true ? 'Yes' : 'No' + }; + } + + return customAttributes; } ); } diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/cart/estimate-service.js b/app/code/Magento/Checkout/view/frontend/web/js/model/cart/estimate-service.js index fd12eed76ed50..71e6c39b4e319 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/cart/estimate-service.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/cart/estimate-service.js @@ -80,5 +80,4 @@ define([ quote.shippingAddress.subscribe(estimateTotalsAndUpdateRates); quote.shippingMethod.subscribe(estimateTotalsShipping); quote.billingAddress.subscribe(estimateTotalsBilling); - customerData.get('cart').subscribe(estimateTotalsShipping); }); diff --git a/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/GuestValidationTest.php b/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/GuestValidationTest.php index 01e4480956180..bb05d5636677c 100644 --- a/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/GuestValidationTest.php +++ b/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/GuestValidationTest.php @@ -20,6 +20,7 @@ use Magento\Quote\Api\Data\PaymentInterface; use Magento\Store\Model\ScopeInterface; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\MockObject\RuntimeException; use PHPUnit\Framework\TestCase; /** @@ -78,9 +79,7 @@ protected function setUp(): void $this->subjectMock = $this->getMockForAbstractClass(GuestPaymentInformationManagementInterface::class); $this->paymentMock = $this->getMockForAbstractClass(PaymentInterface::class); $this->addressMock = $this->getMockForAbstractClass(AddressInterface::class); - $this->extensionAttributesMock = $this->getMockBuilder(PaymentExtension::class) - ->addMethods(['getAgreementIds']) - ->getMock(); + $this->extensionAttributesMock = $this->getPaymentExtension(); $this->scopeConfigMock = $this->getMockForAbstractClass(ScopeConfigInterface::class); $this->checkoutAgreementsListMock = $this->createMock( CheckoutAgreementsListInterface::class @@ -165,4 +164,21 @@ public function testBeforeSavePaymentInformationAndPlaceOrderIfAgreementsNotVali "The order wasn't placed. First, agree to the terms and conditions, then try placing your order again." ); } + + /** + * Build payment extension mock. + * + * @return MockObject + */ + private function getPaymentExtension(): MockObject + { + $mockBuilder = $this->getMockBuilder(PaymentExtension::class); + try { + $mockBuilder->addMethods(['getAgreementIds']); + } catch (RuntimeException $e) { + // Payment extension already generated. + } + + return $mockBuilder->getMock(); + } } diff --git a/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/ValidationTest.php b/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/ValidationTest.php index 7dea366506d66..b7e5127df1f8b 100644 --- a/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/ValidationTest.php +++ b/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/ValidationTest.php @@ -22,6 +22,7 @@ use Magento\Quote\Model\Quote; use Magento\Store\Model\ScopeInterface; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\MockObject\RuntimeException; use PHPUnit\Framework\TestCase; /** @@ -96,9 +97,7 @@ protected function setUp(): void ->disableOriginalConstructor() ->getMock(); $this->quoteRepositoryMock = $this->getMockForAbstractClass(CartRepositoryInterface::class); - $this->extensionAttributesMock = $this->getMockBuilder(PaymentExtension::class) - ->addMethods(['getAgreementIds']) - ->getMock(); + $this->extensionAttributesMock = $this->getPaymentExtension(); $this->scopeConfigMock = $this->getMockForAbstractClass(ScopeConfigInterface::class); $this->checkoutAgreementsListMock = $this->createMock( CheckoutAgreementsListInterface::class @@ -232,4 +231,21 @@ public function testBeforeSavePaymentInformation() ->willReturn($this->extensionAttributesMock); $this->model->beforeSavePaymentInformation($this->subjectMock, $cartId, $this->paymentMock, $this->addressMock); } + + /** + * Build payment extension mock. + * + * @return MockObject + */ + private function getPaymentExtension(): MockObject + { + $mockBuilder = $this->getMockBuilder(PaymentExtension::class); + try { + $mockBuilder->addMethods(['getAgreementIds']); + } catch (RuntimeException $e) { + // Payment extension already generated. + } + + return $mockBuilder->getMock(); + } } diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/CliEnableTinyMCE4ActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/CliEnableTinyMCE4ActionGroup.xml new file mode 100644 index 0000000000000..9e49762f3de20 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/CliEnableTinyMCE4ActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="CliEnableTinyMCE4ActionGroup"> + <annotations> + <description>Enable Tiny MCE 4 by CLI command config:set</description> + </annotations> + + <magentoCLI command="config:set {{EnableTinyMCE4.path}} {{EnableTinyMCE4.value}}" stepKey="enableTinyMCE4"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml index c0424e09f8f76..e2111aac348cb 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml @@ -22,7 +22,7 @@ <createData entity="_defaultBlock" stepKey="createPreReqBlock" /> <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="switchToTinyMCE4" /> </before> <actionGroup ref="AssignBlockToCMSPage" stepKey="assignBlockToCMSPage"> <argument name="Block" value="$$createPreReqBlock$$"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGCMSTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGCMSTest.xml index 4f67b81446ae7..39f44a3270944 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGCMSTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGCMSTest.xml @@ -21,7 +21,7 @@ <createData entity="_defaultCmsPage" stepKey="createCMSPage" /> <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="switchToTinyMCE4" /> </before> <after> <actionGroup ref="NavigateToMediaGalleryActionGroup" stepKey="navigateToMediaGallery"/> @@ -33,7 +33,7 @@ <actionGroup ref="DisabledWYSIWYGActionGroup" stepKey="disableWYSIWYG"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> - + <actionGroup ref="NavigateToCreatedCMSPageActionGroup" stepKey="navigateToCreatedCMSPage"> <argument name="CMSPage" value="$$createCMSPage$$"/> </actionGroup> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGBlockTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGBlockTest.xml index 32bd75d373115..963844710dd7f 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGBlockTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGBlockTest.xml @@ -22,7 +22,7 @@ <createData entity="_defaultBlock" stepKey="createPreReqBlock" /> <actionGroup ref="AdminLoginActionGroup" stepKey="loginGetFromGeneralFile"/> <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="switchToTinyMCE4" /> </before> <!--Create Custom Variable--> <actionGroup ref="CreateCustomVariableActionGroup" stepKey="createCustomVariable" /> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGCMSTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGCMSTest.xml index 698f29a28598f..7f3d2537a9b0d 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGCMSTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGCMSTest.xml @@ -19,7 +19,7 @@ <before> <actionGroup ref="AdminLoginActionGroup" stepKey="loginGetFromGeneralFile"/> <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="switchToTinyMCE4" /> </before> <!--Create Custom Variable--> <actionGroup ref="CreateCustomVariableActionGroup" stepKey="createCustomVariable" /> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGBlockTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGBlockTest.xml index 887fe88533f74..9e28e81c2696d 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGBlockTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGBlockTest.xml @@ -22,7 +22,7 @@ <createData entity="_defaultBlock" stepKey="createPreReqBlock" /> <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="switchToTinyMCE4" /> </before> <actionGroup ref="NavigateToCreatedCMSBlockPageActionGroup" stepKey="navigateToCreatedCMSBlockPage"> <argument name="CMSBlockPage" value="$$createPreReqBlock$$"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSPageLinkTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSPageLinkTypeTest.xml index 509e1abe81ef6..a599d22eab298 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSPageLinkTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSPageLinkTypeTest.xml @@ -21,7 +21,7 @@ <before> <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="switchToTinyMCE4" /> </before> <!--Main test--> <actionGroup ref="AdminOpenCreateNewCMSPageActionGroup" stepKey="navigateToPage"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSStaticBlockTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSStaticBlockTypeTest.xml index cfb323683dc2c..1c9d7b38a40a4 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSStaticBlockTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSStaticBlockTypeTest.xml @@ -22,7 +22,7 @@ <createData entity="_defaultBlock" stepKey="createPreReqBlock" /> <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="switchToTinyMCE4" /> </before> <!--Main test--> <actionGroup ref="AdminOpenCreateNewCMSPageActionGroup" stepKey="navigateToPage"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml index d9ea67491e30a..4f78f6bcce5e0 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml @@ -21,7 +21,7 @@ <createData entity="_defaultCategory" stepKey="createPreReqCategory"/> <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="switchToTinyMCE4" /> <actionGroup ref="ConfigAdminAccountSharingActionGroup" stepKey="allowAdminShareAccount"/> </before> <!--Main test--> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml index 86f90e0e2a580..4ee5b0d263d40 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml @@ -25,7 +25,7 @@ </createData> <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="switchToTinyMCE4" /> </before> <!--Main test--> <actionGroup ref="AdminOpenCreateNewCMSPageActionGroup" stepKey="navigateToPage"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductListTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductListTypeTest.xml index dcb4c3dc11f3c..6c36913cbb593 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductListTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductListTypeTest.xml @@ -27,7 +27,7 @@ </createData> <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="switchToTinyMCE4" /> </before> <!--Main test--> <actionGroup ref="AdminOpenCreateNewCMSPageActionGroup" stepKey="navigateToPage"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyComparedProductsTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyComparedProductsTypeTest.xml index 6acf8ef18a332..440f63403c519 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyComparedProductsTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyComparedProductsTypeTest.xml @@ -26,7 +26,7 @@ </createData> <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="switchToTinyMCE4" /> </before> <actionGroup ref="AdminOpenCreateNewCMSPageActionGroup" stepKey="navigateToPage"/> <fillField selector="{{CmsNewPagePageBasicFieldsSection.pageTitle}}" userInput="{{_defaultCmsPage.title}}" stepKey="fillFieldTitle"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyViewedProductsTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyViewedProductsTypeTest.xml index 1ec4f7054e8c2..226292e6cdea4 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyViewedProductsTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyViewedProductsTypeTest.xml @@ -25,7 +25,7 @@ </createData> <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="switchToTinyMCE4" /> </before> <!--Main test--> <actionGroup ref="AdminOpenCreateNewCMSPageActionGroup" stepKey="navigateToPage"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminCheckCreateFolderEscapeAndEnterHandlesForWYSIWYGBlockTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminCheckCreateFolderEscapeAndEnterHandlesForWYSIWYGBlockTest.xml index c1d5f39d2a005..c5ab9d13b848a 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminCheckCreateFolderEscapeAndEnterHandlesForWYSIWYGBlockTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminCheckCreateFolderEscapeAndEnterHandlesForWYSIWYGBlockTest.xml @@ -23,7 +23,7 @@ <createData entity="_defaultBlock" stepKey="createPreReqBlock" /> <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="switchToTinyMCE4" /> </before> <after> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/CheckOrderOfProdsInWidgetOnCMSPageTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/CheckOrderOfProdsInWidgetOnCMSPageTest.xml index 484dc16faa3b9..c0e6a9cbd793d 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/CheckOrderOfProdsInWidgetOnCMSPageTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/CheckOrderOfProdsInWidgetOnCMSPageTest.xml @@ -22,7 +22,7 @@ <before> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="enableTinyMCE4"/> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="enableTinyMCE4"/> <waitForPageLoad stepKey="waitConfigToSave"/> <createData entity="ApiCategory" stepKey="createFirstCategory"/> <createData entity="ApiSimpleProduct" stepKey="product1"> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnBlockTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnBlockTest.xml index 58f3b9d5bd3b1..03e3097dbd720 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnBlockTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnBlockTest.xml @@ -22,7 +22,7 @@ <createData entity="_defaultCmsPage" stepKey="createPreReqCMSPage" /> <actionGroup ref="AdminLoginActionGroup" stepKey="loginGetFromGeneralFile"/> <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="switchToTinyMCE4" /> </before> <amOnPage url="{{CmsNewBlock.url}}" stepKey="amOnNewBlockPage"/> <waitForPageLoad stepKey="waitForPageLoad1"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnCMSPageTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnCMSPageTest.xml index 43615ac906b00..82e725de46249 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnCMSPageTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnCMSPageTest.xml @@ -21,7 +21,7 @@ <before> <actionGroup ref="AdminLoginActionGroup" stepKey="loginGetFromGeneralFile"/> <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="switchToTinyMCE4" /> </before> <amOnPage url="{{CmsPagesPage.url}}" stepKey="amOnPagePagesGrid"/> <waitForPageLoad stepKey="waitForPageLoad1"/> diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/SwitcherActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/SwitcherActionGroup.xml index f4f97b14682f3..c6b2605c73d8c 100644 --- a/app/code/Magento/Config/Test/Mftf/ActionGroup/SwitcherActionGroup.xml +++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/SwitcherActionGroup.xml @@ -10,9 +10,9 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="SwitchToVersion4ActionGroup"> <annotations> - <description>Goes to the 'Configuration' page for 'Content Management'. Sets the 'WYSIWYG Editor' to 'TinyMCE 4'. Clicks on the Save button. PLEASE NOTE: The value is Hardcoded.</description> + <description>DEPRECATED. Use CliEnableTinyMCE4 instead. Goes to the 'Configuration' page for 'Content Management'. Sets the 'WYSIWYG Editor' to 'TinyMCE 4'. Clicks on the Save button. PLEASE NOTE: The value is Hardcoded.</description> </annotations> - + <amOnPage url="{{ConfigurationStoresPage.url}}" stepKey="navigateToWYSIWYGConfigPage1"/> <waitForPageLoad stepKey="waitForConfigPageToLoad"/> <conditionalClick stepKey="expandWYSIWYGOptions" selector="{{ContentManagementSection.WYSIWYGOptions}}" dependentSelector="{{ContentManagementSection.CheckIfTabExpand}}" visible="true"/> diff --git a/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurations.php b/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurations.php index 76ad93564e96e..0180fd5bfba4b 100644 --- a/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurations.php +++ b/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurations.php @@ -115,9 +115,9 @@ protected function getConfigurations() if (isset($item['qty'])) { $result[$item['id']]['quantity_and_stock_status']['qty'] = $item['qty']; } - + // Changing product to simple on weight change - if (isset($item['weight']) && $item['weight'] >= 0) { + if (!empty($item['weight']) && $item['weight'] >= 0) { $result[$item['id']]['type_id'] = \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE; $result[$item['id']]['product_has_weight'] = WeightResolver::HAS_WEIGHT; } diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NoOptionAvailableToConfigureDisabledProductTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NoOptionAvailableToConfigureDisabledProductTest.xml index aa2c19ebc17f4..6e977a7749145 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NoOptionAvailableToConfigureDisabledProductTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NoOptionAvailableToConfigureDisabledProductTest.xml @@ -173,10 +173,10 @@ </actionGroup> <!-- Select shipping method --> <comment userInput="Select shipping method" stepKey="selectShippingMethod"/> - <click selector="{{AdminInvoicePaymentShippingSection.getShippingMethodAndRates}}" stepKey="openShippingMethod"/> - <waitForPageLoad stepKey="waitForShippingMethods"/> - <click selector="{{AdminInvoicePaymentShippingSection.shippingMethod}}" stepKey="chooseShippingMethod"/> - <waitForPageLoad stepKey="waitForShippingMethodLoad"/> + <actionGroup ref="AdminClickGetShippingMethodsAndRatesActionGroup" stepKey="openShippingMethod"/> + <comment userInput="Adding the comment to replace action for preserving Backward Compatibility" stepKey="waitForShippingMethods"/> + <actionGroup ref="AdminSelectFixedShippingMethodActionGroup" stepKey="chooseShippingMethod"/> + <comment userInput="Adding the comment to replace action for preserving Backward Compatibility" stepKey="waitForShippingMethodLoad"/> <actionGroup ref="AdminOrderClickSubmitOrderActionGroup" stepKey="clickSubmitOrder" /> <actionGroup ref="VerifyCreatedOrderInformationActionGroup" stepKey="checkOrderSuccessfullyCreated"/> </test> diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurationsTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurationsTest.php index 735560f00a11a..e5aa4a353f3b1 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurationsTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurationsTest.php @@ -119,6 +119,22 @@ private function getConfigurableMatrix() 'price' => '3.33', 'weight' => '5.55', ], + [ + 'newProduct' => false, + 'id' => 'product5', + 'status' => 'simple5_status', + 'sku' => 'simple5_sku', + 'name' => 'simple5_name', + 'price' => '3.33', + 'configurable_attribute' => 'simple5_configurable_attribute', + 'weight' => '', + 'media_gallery' => 'simple5_media_gallery', + 'swatch_image' => 'simple5_swatch_image', + 'small_image' => 'simple5_small_image', + 'thumbnail' => 'simple5_thumbnail', + 'image' => 'simple5_image', + 'was_changed' => true, + ], ]; } @@ -144,12 +160,26 @@ public function testAfterInitialize() ], 'product3' => [ 'quantity_and_stock_status' => ['qty' => '3'] - ] + ], + 'product5' => [ + 'status' => 'simple5_status', + 'sku' => 'simple5_sku', + 'name' => 'simple5_name', + 'price' => '3.33', + 'configurable_attribute' => 'simple5_configurable_attribute', + 'weight' => '', + 'media_gallery' => 'simple5_media_gallery', + 'swatch_image' => 'simple5_swatch_image', + 'small_image' => 'simple5_small_image', + 'thumbnail' => 'simple5_thumbnail', + 'image' => 'simple5_image', + ], ]; /** @var Product[]|MockObject[] $productMocks */ $productMocks = [ 'product2' => $this->getProductMock($configurations['product2'], true, true), 'product3' => $this->getProductMock($configurations['product3'], false, true), + 'product5' => $this->getProductMock($configurations['product5'], false, true), ]; $this->requestMock->expects(static::any()) @@ -169,7 +199,8 @@ public function testAfterInitialize() ->willReturnMap( [ ['product2', false, 0, false, $productMocks['product2']], - ['product3', false, 0, false, $productMocks['product3']] + ['product3', false, 0, false, $productMocks['product3']], + ['product5', false, 0, false, $productMocks['product5']], ] ); $this->variationHandlerMock->expects(static::any()) @@ -177,7 +208,8 @@ public function testAfterInitialize() ->willReturnMap( [ [$productMocks['product2'], $configurations['product2'], $configurations['product2']], - [$productMocks['product3'], $configurations['product3'], $configurations['product3']] + [$productMocks['product3'], $configurations['product3'], $configurations['product3']], + [$productMocks['product5'], $configurations['product5'], $configurations['product5']] ] ); diff --git a/app/code/Magento/Customer/Controller/Account/EditPost.php b/app/code/Magento/Customer/Controller/Account/EditPost.php index 6b59986f8ec5f..c2137f1b40019 100644 --- a/app/code/Magento/Customer/Controller/Account/EditPost.php +++ b/app/code/Magento/Customer/Controller/Account/EditPost.php @@ -31,6 +31,8 @@ use Magento\Framework\Exception\State\UserLockedException; use Magento\Customer\Controller\AbstractAccount; use Magento\Framework\Phrase; +use Magento\Framework\Filesystem; +use Magento\Framework\App\Filesystem\DirectoryList; /** * Customer edit account information controller @@ -94,6 +96,11 @@ class EditPost extends AbstractAccount implements CsrfAwareActionInterface, Http */ private $addressRegistry; + /** + * @var Filesystem + */ + private $filesystem; + /** * @param Context $context * @param Session $customerSession @@ -103,6 +110,7 @@ class EditPost extends AbstractAccount implements CsrfAwareActionInterface, Http * @param CustomerExtractor $customerExtractor * @param Escaper|null $escaper * @param AddressRegistry|null $addressRegistry + * @param Filesystem $filesystem */ public function __construct( Context $context, @@ -112,7 +120,8 @@ public function __construct( Validator $formKeyValidator, CustomerExtractor $customerExtractor, ?Escaper $escaper = null, - AddressRegistry $addressRegistry = null + AddressRegistry $addressRegistry = null, + Filesystem $filesystem = null ) { parent::__construct($context); $this->session = $customerSession; @@ -122,6 +131,7 @@ public function __construct( $this->customerExtractor = $customerExtractor; $this->escaper = $escaper ?: ObjectManager::getInstance()->get(Escaper::class); $this->addressRegistry = $addressRegistry ?: ObjectManager::getInstance()->get(AddressRegistry::class); + $this->filesystem = $filesystem ?: ObjectManager::getInstance()->get(Filesystem::class); } /** @@ -201,6 +211,14 @@ public function execute() $currentCustomerDataObject ); + $attributeToDelete = $this->_request->getParam('delete_attribute_value'); + if ($attributeToDelete !== null) { + $this->deleteCustomerFileAttribute( + $customerCandidateDataObject, + $attributeToDelete + ); + } + try { // whether a customer enabled change email option $this->processChangeEmailRequest($currentCustomerDataObject); @@ -388,4 +406,41 @@ private function disableAddressValidation($customer) $addressModel->setShouldIgnoreValidation(true); } } + + /** + * Removes file attribute from customer entity and file from filesystem + * + * @param CustomerInterface $customerCandidateDataObject + * @param string $attributeToDelete + * @return void + */ + private function deleteCustomerFileAttribute( + CustomerInterface $customerCandidateDataObject, + string $attributeToDelete + ) : void { + if ($attributeToDelete !== '') { + if (strpos($attributeToDelete, ',') !== false) { + $attributes = explode(',', $attributeToDelete); + } else { + $attributes[] = $attributeToDelete; + } + foreach ($attributes as $attr) { + $attributeValue = $customerCandidateDataObject->getCustomAttribute($attr); + if ($attributeValue!== null) { + if ($attributeValue->getValue() !== '') { + $mediaDirectory = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA); + $fileName = $attributeValue->getValue(); + $path = $mediaDirectory->getAbsolutePath('customer' . $fileName); + if ($fileName && $mediaDirectory->isFile($path)) { + $mediaDirectory->delete($path); + } + $customerCandidateDataObject->setCustomAttribute( + $attr, + '' + ); + } + } + } + } + } } diff --git a/app/code/Magento/Customer/Controller/Address/FormPost.php b/app/code/Magento/Customer/Controller/Address/FormPost.php index 25618e3129160..cae039ea975b8 100644 --- a/app/code/Magento/Customer/Controller/Address/FormPost.php +++ b/app/code/Magento/Customer/Controller/Address/FormPost.php @@ -24,6 +24,9 @@ use Magento\Framework\Exception\InputException; use Magento\Framework\Reflection\DataObjectProcessor; use Magento\Framework\View\Result\PageFactory; +use Magento\Framework\Filesystem; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Exception\NotFoundException; /** * Customer Address Form Post Controller @@ -47,6 +50,11 @@ class FormPost extends \Magento\Customer\Controller\Address implements HttpPostA */ private $customerAddressMapper; + /** + * @var Filesystem + */ + private $filesystem; + /** * @param Context $context * @param Session $customerSession @@ -61,6 +69,7 @@ class FormPost extends \Magento\Customer\Controller\Address implements HttpPostA * @param PageFactory $resultPageFactory * @param RegionFactory $regionFactory * @param HelperData $helperData + * @param Filesystem $filesystem * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -76,10 +85,12 @@ public function __construct( ForwardFactory $resultForwardFactory, PageFactory $resultPageFactory, RegionFactory $regionFactory, - HelperData $helperData + HelperData $helperData, + Filesystem $filesystem = null ) { $this->regionFactory = $regionFactory; $this->helperData = $helperData; + $this->filesystem = $filesystem ?: ObjectManager::getInstance()->get(Filesystem::class); parent::__construct( $context, $customerSession, @@ -150,7 +161,7 @@ protected function getExistingAddressData() if ($addressId = $this->getRequest()->getParam('id')) { $existingAddress = $this->_addressRepository->getById($addressId); if ($existingAddress->getCustomerId() !== $this->_getSession()->getCustomerId()) { - throw new \Exception(); + throw new NotFoundException(__('Address not found.')); } $existingAddressData = $this->getCustomerAddressMapper()->toFlatArray($existingAddress); } @@ -210,6 +221,9 @@ public function execute() try { $address = $this->_extractAddress(); + if ($this->_request->getParam('delete_attribute_value')) { + $address = $this->deleteAddressFileAttribute($address); + } $this->_addressRepository->save($address); $this->messageManager->addSuccessMessage(__('You saved the address.')); $url = $this->_buildUrl('*/*/index', ['_secure' => true]); @@ -249,4 +263,31 @@ private function getCustomerAddressMapper() } return $this->customerAddressMapper; } + + /** + * Removes file attribute from customer address and file from filesystem + * + * @param \Magento\Customer\Api\Data\AddressInterface $address + * @return mixed + */ + private function deleteAddressFileAttribute($address) + { + $attributeValue = $address->getCustomAttribute($this->_request->getParam('delete_attribute_value')); + if ($attributeValue!== null) { + if ($attributeValue->getValue() !== '') { + $mediaDirectory = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA); + $fileName = $attributeValue->getValue(); + $path = $mediaDirectory->getAbsolutePath('customer_address' . $fileName); + if ($fileName && $mediaDirectory->isFile($path)) { + $mediaDirectory->delete($path); + } + $address->setCustomAttribute( + $this->_request->getParam('delete_attribute_value'), + '' + ); + } + } + + return $address; + } } diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php index 192a0f1362ecd..cc8531214c7da 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Customer\Controller\Adminhtml\Index; use Magento\Backend\App\Action\Context; @@ -40,6 +41,7 @@ use Magento\Framework\Math\Random; use Magento\Framework\Reflection\DataObjectProcessor; use Magento\Framework\Registry; +use Magento\Framework\Validator\Exception; use Magento\Framework\View\Result\LayoutFactory; use Magento\Framework\View\Result\PageFactory; use Magento\Newsletter\Model\SubscriberFactory; @@ -243,10 +245,10 @@ protected function _extractData( /** * Saves default_billing and default_shipping flags for customer address * - * @deprecated 102.0.1 must be removed because addresses are save separately for now * @param array $addressIdList * @param array $extractedCustomerData * @return array + * @deprecated 102.0.1 must be removed because addresses are save separately for now */ protected function saveDefaultFlags(array $addressIdList, array &$extractedCustomerData) { @@ -286,9 +288,9 @@ protected function saveDefaultFlags(array $addressIdList, array &$extractedCusto /** * Reformat customer addresses data to be compatible with customer service interface * - * @deprecated 102.0.1 addresses are saved separately for now * @param array $extractedCustomerData * @return array + * @deprecated 102.0.1 addresses are saved separately for now */ protected function _extractCustomerAddressData(array &$extractedCustomerData) { @@ -318,6 +320,7 @@ public function execute() { $returnToEdit = false; $customerId = $this->getCurrentCustomerId(); + $customer = $this->customerDataFactory->create(); if ($this->getRequest()->getPostValue()) { try { @@ -335,8 +338,6 @@ public function execute() $customerData['id'] = $customerId; } - /** @var CustomerInterface $customer */ - $customer = $this->customerDataFactory->create(); $this->dataObjectHelper->populateWithArray( $customer, $customerData, @@ -353,7 +354,7 @@ public function execute() try { $this->customerAccountManagement->validateCustomerStoreIdByWebsiteId($customer); } catch (LocalizedException $exception) { - throw new LocalizedException(__("The Store View selected for sending Welcome email from". + throw new LocalizedException(__("The Store View selected for sending Welcome email from" . " is not related to the customer's associated website.")); } } @@ -361,7 +362,6 @@ public function execute() // Save customer if ($customerId) { $this->_customerRepository->save($customer); - $this->getEmailNotification()->credentialsChanged($customer, $currentCustomer->getEmail()); } else { $customer = $this->customerAccountManagement->createAccount($customer); @@ -386,13 +386,13 @@ public function execute() __('Something went wrong while saving the customer.') ); $returnToEdit = false; - } catch (\Magento\Framework\Validator\Exception $exception) { + } catch (Exception $exception) { $messages = $exception->getMessages(); if (empty($messages)) { $messages = $exception->getMessage(); } $this->_addSessionErrorMessages($messages); - $this->_getSession()->setCustomerFormData($this->retrieveFormattedFormData()); + $this->_getSession()->setCustomerFormData($this->retrieveFormattedFormData($customer)); $returnToEdit = true; } catch (AbstractAggregateException $exception) { $errors = $exception->getErrors(); @@ -401,18 +401,18 @@ public function execute() $messages[] = $error->getMessage(); } $this->_addSessionErrorMessages($messages); - $this->_getSession()->setCustomerFormData($this->retrieveFormattedFormData()); + $this->_getSession()->setCustomerFormData($this->retrieveFormattedFormData($customer)); $returnToEdit = true; } catch (LocalizedException $exception) { $this->_addSessionErrorMessages($exception->getMessage()); - $this->_getSession()->setCustomerFormData($this->retrieveFormattedFormData()); + $this->_getSession()->setCustomerFormData($this->retrieveFormattedFormData($customer)); $returnToEdit = true; } catch (\Exception $exception) { $this->messageManager->addExceptionMessage( $exception, __('Something went wrong while saving the customer.') ); - $this->_getSession()->setCustomerFormData($this->retrieveFormattedFormData()); + $this->_getSession()->setCustomerFormData($this->retrieveFormattedFormData($customer)); $returnToEdit = true; } } @@ -553,21 +553,16 @@ private function disableAddressValidation($customer) /** * Retrieve formatted form data * + * @param CustomerInterface $customer * @return array */ - private function retrieveFormattedFormData(): array + private function retrieveFormattedFormData(CustomerInterface $customer): array { $originalRequestData = $this->getRequest()->getPostValue(); + $customerData = $this->customerMapper->toFlatArray($customer); /* Customer data filtration */ if (isset($originalRequestData['customer'])) { - $customerData = $this->_extractData( - 'adminhtml_customer', - CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, - [], - 'customer' - ); - $customerData = array_intersect_key($customerData, $originalRequestData['customer']); $originalRequestData['customer'] = array_merge($originalRequestData['customer'], $customerData); } diff --git a/app/code/Magento/Customer/Model/Address/CustomAttributesProcessor.php b/app/code/Magento/Customer/Model/Address/CustomAttributesProcessor.php index d6e63e11ee453..0fd72a591899a 100644 --- a/app/code/Magento/Customer/Model/Address/CustomAttributesProcessor.php +++ b/app/code/Magento/Customer/Model/Address/CustomAttributesProcessor.php @@ -71,7 +71,7 @@ private function getAttributeLabels(array $customAttribute, string $customAttrib { $attributeOptionLabels = []; - if (!empty($customAttribute['value'])) { + if (isset($customAttribute['value']) && $customAttribute['value'] != null) { $customAttributeValues = explode(',', $customAttribute['value']); $attributeOptions = $this->attributeOptionManager->getItems( \Magento\Customer\Model\Indexer\Address\AttributeProvider::ENTITY, diff --git a/app/code/Magento/Customer/Model/Customer/DataProviderWithDefaultAddresses.php b/app/code/Magento/Customer/Model/Customer/DataProviderWithDefaultAddresses.php index 604295cc0c078..a3617ac4e4e79 100644 --- a/app/code/Magento/Customer/Model/Customer/DataProviderWithDefaultAddresses.php +++ b/app/code/Magento/Customer/Model/Customer/DataProviderWithDefaultAddresses.php @@ -8,15 +8,18 @@ use Magento\Customer\Model\Address; use Magento\Customer\Model\Customer; +use Magento\Customer\Model\CustomerFactory; use Magento\Customer\Model\ResourceModel\Customer\CollectionFactory as CustomerCollectionFactory; use Magento\Directory\Model\CountryFactory; use Magento\Eav\Model\Config; use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; use Magento\Eav\Model\Entity\Type; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Session\SessionManagerInterface; use Magento\Customer\Model\FileUploaderDataResolver; use Magento\Customer\Model\AttributeMetadataResolver; +use Magento\Ui\Component\Form\Element\Multiline; use Magento\Ui\DataProvider\AbstractDataProvider; /** @@ -66,6 +69,11 @@ class DataProviderWithDefaultAddresses extends AbstractDataProvider */ private $attributeMetadataResolver; + /** + * @var CustomerFactory + */ + private $customerFactory; + /** * @param string $name * @param string $primaryFieldName @@ -79,6 +87,7 @@ class DataProviderWithDefaultAddresses extends AbstractDataProvider * @param bool $allowToShowHiddenAttributes * @param array $meta * @param array $data + * @param CustomerFactory $customerFactory * @throws LocalizedException * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -94,7 +103,8 @@ public function __construct( AttributeMetadataResolver $attributeMetadataResolver, $allowToShowHiddenAttributes = true, array $meta = [], - array $data = [] + array $data = [], + CustomerFactory $customerFactory = null ) { parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data); $this->collection = $customerCollectionFactory->create(); @@ -107,6 +117,7 @@ public function __construct( $this->meta['customer']['children'] = $this->getAttributesMeta( $eavConfig->getEntityType('customer') ); + $this->customerFactory = $customerFactory ?: ObjectManager::getInstance()->get(CustomerFactory::class); } /** @@ -130,6 +141,7 @@ public function getData(): array $result['customer'], array_flip(self::$forbiddenCustomerFields) ); + $this->prepareCustomAttributeValue($result['customer']); unset($result['address']); $result['default_billing_address'] = $this->prepareDefaultAddress( @@ -142,9 +154,10 @@ public function getData(): array $this->loadedData[$customer->getId()] = $result; } - $data = $this->session->getCustomerFormData(); if (!empty($data)) { + $customer = $this->customerFactory->create(); + $this->fileUploaderDataResolver->overrideFileUploaderData($customer, $data['customer']); $customerId = $data['customer']['entity_id'] ?? null; $this->loadedData[$customerId] = $data; $this->session->unsCustomerFormData(); @@ -179,6 +192,24 @@ private function prepareDefaultAddress($address): array return $addressData; } + /*** + * Prepare values for Custom Attributes. + * + * @param array $data + * @return void + */ + private function prepareCustomAttributeValue(array &$data): void + { + foreach ($this->meta['customer']['children'] as $attributeName => $attributeMeta) { + if ($attributeMeta['arguments']['data']['config']['dataType'] === Multiline::NAME + && isset($data[$attributeName]) + && !is_array($data[$attributeName]) + ) { + $data[$attributeName] = explode("\n", $data[$attributeName]); + } + } + } + /** * Get attributes meta * diff --git a/app/code/Magento/Customer/Model/FileProcessor.php b/app/code/Magento/Customer/Model/FileProcessor.php index c596f8c313ab3..02bfe78be535c 100644 --- a/app/code/Magento/Customer/Model/FileProcessor.php +++ b/app/code/Magento/Customer/Model/FileProcessor.php @@ -7,6 +7,18 @@ namespace Magento\Customer\Model; +use Magento\Customer\Api\AddressMetadataInterface; +use Magento\Customer\Api\CustomerMetadataInterface; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\File\Mime; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\WriteInterface; +use Magento\Framework\Url\EncoderInterface; +use Magento\Framework\UrlInterface; +use Magento\MediaStorage\Model\File\Uploader; +use Magento\MediaStorage\Model\File\UploaderFactory; + /** * Processor class for work with uploaded files */ @@ -18,22 +30,22 @@ class FileProcessor const TMP_DIR = 'tmp'; /** - * @var \Magento\Framework\Filesystem\Directory\WriteInterface + * @var WriteInterface */ private $mediaDirectory; /** - * @var \Magento\MediaStorage\Model\File\UploaderFactory + * @var UploaderFactory */ private $uploaderFactory; /** - * @var \Magento\Framework\UrlInterface + * @var UrlInterface */ private $urlBuilder; /** - * @var \Magento\Framework\Url\EncoderInterface + * @var EncoderInterface */ private $urlEncoder; @@ -48,29 +60,29 @@ class FileProcessor private $allowedExtensions = []; /** - * @var \Magento\Framework\File\Mime + * @var Mime */ private $mime; /** - * @param \Magento\Framework\Filesystem $filesystem - * @param \Magento\MediaStorage\Model\File\UploaderFactory $uploaderFactory - * @param \Magento\Framework\UrlInterface $urlBuilder - * @param \Magento\Framework\Url\EncoderInterface $urlEncoder + * @param Filesystem $filesystem + * @param UploaderFactory $uploaderFactory + * @param UrlInterface $urlBuilder + * @param EncoderInterface $urlEncoder * @param string $entityTypeCode - * @param \Magento\Framework\File\Mime $mime + * @param Mime $mime * @param array $allowedExtensions */ public function __construct( - \Magento\Framework\Filesystem $filesystem, - \Magento\MediaStorage\Model\File\UploaderFactory $uploaderFactory, - \Magento\Framework\UrlInterface $urlBuilder, - \Magento\Framework\Url\EncoderInterface $urlEncoder, + Filesystem $filesystem, + UploaderFactory $uploaderFactory, + UrlInterface $urlBuilder, + EncoderInterface $urlEncoder, $entityTypeCode, - \Magento\Framework\File\Mime $mime, + Mime $mime, array $allowedExtensions = [] ) { - $this->mediaDirectory = $filesystem->getDirectoryWrite(\Magento\Framework\App\Filesystem\DirectoryList::MEDIA); + $this->mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA); $this->uploaderFactory = $uploaderFactory; $this->urlBuilder = $urlBuilder; $this->urlEncoder = $urlEncoder; @@ -91,8 +103,7 @@ public function getBase64EncodedData($fileName) $fileContent = $this->mediaDirectory->readFile($filePath); - $encodedContent = base64_encode($fileContent); - return $encodedContent; + return base64_encode($fileContent); } /** @@ -105,8 +116,7 @@ public function getStat($fileName) { $filePath = $this->entityTypeCode . '/' . ltrim($fileName, '/'); - $result = $this->mediaDirectory->stat($filePath); - return $result; + return $this->mediaDirectory->stat($filePath); } /** @@ -120,8 +130,7 @@ public function getMimeType($fileName) $filePath = $this->entityTypeCode . '/' . ltrim($fileName, '/'); $absoluteFilePath = $this->mediaDirectory->getAbsolutePath($filePath); - $result = $this->mime->getMimeType($absoluteFilePath); - return $result; + return $this->mime->getMimeType($absoluteFilePath); } /** @@ -134,8 +143,7 @@ public function isExist($fileName) { $filePath = $this->entityTypeCode . '/' . ltrim($fileName, '/'); - $result = $this->mediaDirectory->isExist($filePath); - return $result; + return $this->mediaDirectory->isExist($filePath); } /** @@ -149,13 +157,13 @@ public function getViewUrl($filePath, $type) { $viewUrl = ''; - if ($this->entityTypeCode == \Magento\Customer\Api\AddressMetadataInterface::ENTITY_TYPE_ADDRESS) { + if ($this->entityTypeCode == AddressMetadataInterface::ENTITY_TYPE_ADDRESS) { $filePath = $this->entityTypeCode . '/' . ltrim($filePath, '/'); - $viewUrl = $this->urlBuilder->getBaseUrl(['_type' => \Magento\Framework\UrlInterface::URL_TYPE_MEDIA]) + $viewUrl = $this->urlBuilder->getBaseUrl(['_type' => UrlInterface::URL_TYPE_MEDIA]) . $this->mediaDirectory->getRelativePath($filePath); } - if ($this->entityTypeCode == \Magento\Customer\Api\CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER) { + if ($this->entityTypeCode == CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER) { $viewUrl = $this->urlBuilder->getUrl( 'customer/index/viewfile', [$type => $this->urlEncoder->encode(ltrim($filePath, '/'))] @@ -170,11 +178,11 @@ public function getViewUrl($filePath, $type) * * @param string $fileId * @return \string[] - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ public function saveTemporaryFile($fileId) { - /** @var \Magento\MediaStorage\Model\File\Uploader $uploader */ + /** @var Uploader $uploader */ $uploader = $this->uploaderFactory->create(['fileId' => $fileId]); $uploader->setFilesDispersion(false); $uploader->setFilenamesCaseSensitivity(false); @@ -188,7 +196,7 @@ public function saveTemporaryFile($fileId) $result = $uploader->save($path); unset($result['path']); if (!$result) { - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( __('File can not be saved to the destination folder.') ); } @@ -201,28 +209,32 @@ public function saveTemporaryFile($fileId) * * @param string $fileName * @return string - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ public function moveTemporaryFile($fileName) { + if (!$this->isFileTemporary($fileName)) { + return $fileName; + } + $fileName = ltrim($fileName, '/'); - $dispersionPath = \Magento\MediaStorage\Model\File\Uploader::getDispersionPath($fileName); + $dispersionPath = Uploader::getDispersionPath($fileName); $destinationPath = $this->entityTypeCode . $dispersionPath; if (!$this->mediaDirectory->create($destinationPath)) { - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( __('Unable to create directory %1.', $destinationPath) ); } if (!$this->mediaDirectory->isWritable($destinationPath)) { - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( __('Destination folder is not writable or does not exists.') ); } - $destinationFileName = \Magento\MediaStorage\Model\File\Uploader::getNewFileName( + $destinationFileName = Uploader::getNewFileName( $this->mediaDirectory->getAbsolutePath($destinationPath) . '/' . $fileName ); @@ -238,8 +250,7 @@ public function moveTemporaryFile($fileName) ); } - $fileName = $dispersionPath . '/' . $destinationFileName; - return $fileName; + return $dispersionPath . '/' . $destinationFileName; } /** @@ -252,7 +263,20 @@ public function removeUploadedFile($fileName) { $filePath = $this->entityTypeCode . '/' . ltrim($fileName, '/'); - $result = $this->mediaDirectory->delete($filePath); - return $result; + return $this->mediaDirectory->delete($filePath); + } + + /** + * Verify if given file temporary. + * + * @param string $fileName + * @return bool + */ + private function isFileTemporary(string $fileName): bool + { + $tmpFile = $this->entityTypeCode . '/' . self::TMP_DIR . '/' . ltrim($fileName, '/'); + $destinationFile = $this->entityTypeCode . '/' . ltrim($fileName, '/'); + + return $this->mediaDirectory->isExist($tmpFile) && !$this->mediaDirectory->isExist($destinationFile); } } diff --git a/app/code/Magento/Customer/Model/FileUploader.php b/app/code/Magento/Customer/Model/FileUploader.php index c425ac06666c5..411ab37a1d740 100644 --- a/app/code/Magento/Customer/Model/FileUploader.php +++ b/app/code/Magento/Customer/Model/FileUploader.php @@ -100,14 +100,33 @@ public function validate() * @throws LocalizedException */ public function upload() + { + return $this->uploadFile(); + } + + /** + * File uploading process + * + * @param bool $useScope + * @return string[] + * @throws LocalizedException + */ + public function uploadFile($useScope = true) { /** @var FileProcessor $fileProcessor */ - $fileProcessor = $this->fileProcessorFactory->create([ - 'entityTypeCode' => $this->entityTypeCode, - 'allowedExtensions' => $this->getAllowedExtensions(), - ]); + $fileProcessor = $this->fileProcessorFactory->create( + [ + 'entityTypeCode' => $this->entityTypeCode, + 'allowedExtensions' => $this->getAllowedExtensions(), + ] + ); - $result = $fileProcessor->saveTemporaryFile($this->scope . '[' . $this->getAttributeCode() . ']'); + if ($useScope === true) { + $fileId = $this->scope . '[' . $this->getAttributeCode() . ']'; + } else { + $fileId = $this->getAttributeCode(); + } + $result = $fileProcessor->saveTemporaryFile($fileId); // Update tmp_name param. Required for attribute validation! $result['tmp_name'] = ltrim($result['file'], '/'); @@ -127,7 +146,14 @@ public function upload() */ private function getAttributeCode() { - return key($_FILES[$this->scope]['name']); + // phpcs:disable Magento2.Security.Superglobal + if (is_array($_FILES[$this->scope]['name'])) { + $code = key($_FILES[$this->scope]['name']); + } else { + $code = $this->scope; + } + // phpcs:enable Magento2.Security.Superglobal + return $code; } /** @@ -139,10 +165,16 @@ private function getData() { $data = []; + // phpcs:disable Magento2.Security.Superglobal $fileAttributes = $_FILES[$this->scope]; foreach ($fileAttributes as $attributeName => $attributeValue) { - $data[$attributeName] = $attributeValue[$this->getAttributeCode()]; + if (is_array($attributeValue)) { + $data[$attributeName] = $attributeValue[$this->getAttributeCode()]; + } else { + $data[$attributeName] = $attributeValue; + } } + // phpcs:enable Magento2.Security.Superglobal return $data; } @@ -160,9 +192,12 @@ private function getAllowedExtensions() foreach ($validationRules as $validationRule) { if ($validationRule->getName() == 'file_extensions') { $allowedExtensions = explode(',', $validationRule->getValue()); - array_walk($allowedExtensions, function (&$value) { - $value = strtolower(trim($value)); - }); + array_walk( + $allowedExtensions, + function (&$value) { + $value = strtolower(trim($value)); + } + ); break; } } diff --git a/app/code/Magento/Customer/Model/Metadata/Form/File.php b/app/code/Magento/Customer/Model/Metadata/Form/File.php index 1a1c48075fce5..17cfc0325ef41 100644 --- a/app/code/Magento/Customer/Model/Metadata/Form/File.php +++ b/app/code/Magento/Customer/Model/Metadata/Form/File.php @@ -13,6 +13,7 @@ use Magento\Framework\App\ObjectManager; use Magento\Framework\File\UploaderFactory; use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Io\File as IoFile; /** * Processes files that are save for customer. @@ -61,6 +62,11 @@ class File extends AbstractData */ protected $fileProcessorFactory; + /** + * @var IoFile|null + */ + private $ioFile; + /** * Constructor * @@ -76,6 +82,7 @@ class File extends AbstractData * @param Filesystem $fileSystem * @param UploaderFactory $uploaderFactory * @param \Magento\Customer\Model\FileProcessorFactory|null $fileProcessorFactory + * @param IoFile|null $ioFile * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -90,7 +97,8 @@ public function __construct( \Magento\MediaStorage\Model\File\Validator\NotProtectedExtension $fileValidator, Filesystem $fileSystem, UploaderFactory $uploaderFactory, - \Magento\Customer\Model\FileProcessorFactory $fileProcessorFactory = null + FileProcessorFactory $fileProcessorFactory = null, + IoFile $ioFile = null ) { parent::__construct($localeDate, $logger, $attribute, $localeResolver, $value, $entityTypeCode, $isAjax); $this->urlEncoder = $urlEncoder; @@ -98,8 +106,10 @@ public function __construct( $this->_fileSystem = $fileSystem; $this->uploaderFactory = $uploaderFactory; $this->fileProcessorFactory = $fileProcessorFactory ?: ObjectManager::getInstance() - ->get(\Magento\Customer\Model\FileProcessorFactory::class); + ->get(FileProcessorFactory::class); $this->fileProcessor = $this->fileProcessorFactory->create(['entityTypeCode' => $this->_entityTypeCode]); + $this->ioFile = $ioFile ?: ObjectManager::getInstance() + ->get(IoFile::class); } /** @@ -110,11 +120,17 @@ public function extractValue(\Magento\Framework\App\RequestInterface $request) { $extend = $this->_getRequestValue($request); + // phpcs:disable Magento2.Security.Superglobal $attrCode = $this->getAttribute()->getAttributeCode(); - if ($this->_requestScope || !isset($_FILES[$attrCode])) { + + // phpcs:disable Magento2.Security.Superglobal + $uploadedFile = $request->getParam($attrCode . '_uploaded'); + if ($uploadedFile) { + $value = $uploadedFile; + } elseif ($this->_requestScope || !isset($_FILES[$attrCode])) { $value = []; - if (strpos($this->_requestScope, '/') !== false) { - $scopes = explode('/', $this->_requestScope); + if (strpos($this->_requestScope, DIRECTORY_SEPARATOR) !== false) { + $scopes = explode(DIRECTORY_SEPARATOR, $this->_requestScope); $mainScope = array_shift($scopes); } else { $mainScope = $this->_requestScope; @@ -153,6 +169,7 @@ public function extractValue(\Magento\Framework\App\RequestInterface $request) $value = []; } } + // phpcs:enable Magento2.Security.Superglobal if (!empty($extend['delete'])) { $value['delete'] = true; @@ -171,7 +188,9 @@ protected function _validateByRules($value) { $label = $value['name']; $rules = $this->getAttribute()->getValidationRules(); - $extension = pathinfo($value['name'], PATHINFO_EXTENSION); + // phpcs:ignore Magento2.Functions.DiscouragedFunction + $pathInfo = $this->ioFile->getPathInfo($label); + $extension = $pathInfo['extension'] ?? null; $fileExtensions = ArrayObjectSearch::getArrayElementByName( $rules, 'file_extensions' @@ -219,12 +238,14 @@ protected function _validateByRules($value) */ protected function _isUploadedFile($filename) { + // phpcs:ignore Magento2.Functions.DiscouragedFunction if (is_uploaded_file($filename)) { return true; } // This case is required for file uploader UI component - $temporaryFile = FileProcessor::TMP_DIR . '/' . pathinfo($filename)['basename']; + $temporaryFile = FileProcessor::TMP_DIR . DIRECTORY_SEPARATOR . + $this->ioFile->getPathInfo($filename)['basename']; if ($this->fileProcessor->isExist($temporaryFile)) { return true; } @@ -290,10 +311,9 @@ public function compactValue($value) return $value; } - if (isset($value['file']) && !empty($value['file'])) { - if ($value['file'] == $this->_value) { - return $this->_value; - } + if ($value && is_string($value) && $this->fileProcessor->isExist($value)) { + $result = $value; + } elseif (isset($value['file']) && !empty($value['file'])) { $result = $this->processUiComponentValue($value); } else { $result = $this->processInputFieldValue($value); @@ -310,6 +330,9 @@ public function compactValue($value) */ protected function processUiComponentValue(array $value) { + if ($value['file'] == $this->_value) { + return $this->_value; + } $result = $this->fileProcessor->moveTemporaryFile($value['file']); return $result; } @@ -338,7 +361,8 @@ protected function processInputFieldValue($value) $result = $this->_value; if ($toDelete) { - $mediaDir->delete($this->_entityTypeCode . '/' . ltrim($this->_value, '/')); + $mediaDir->delete($this->_entityTypeCode . DIRECTORY_SEPARATOR . + ltrim($this->_value, DIRECTORY_SEPARATOR)); $result = ''; } @@ -363,7 +387,10 @@ protected function processInputFieldValue($value) */ public function restoreValue($value) { - return $this->_value; + if (!empty($this->_value)) { + return $this->_value; + } + return $this->compactValue($value); } /** diff --git a/app/code/Magento/Customer/Model/Metadata/Form/Image.php b/app/code/Magento/Customer/Model/Metadata/Form/Image.php index d023db1454906..b5bfe00c23384 100644 --- a/app/code/Magento/Customer/Model/Metadata/Form/Image.php +++ b/app/code/Magento/Customer/Model/Metadata/Form/Image.php @@ -16,15 +16,15 @@ use Magento\Framework\Api\ArrayObjectSearch; use Magento\Framework\Api\Data\ImageContentInterface; use Magento\Framework\Api\Data\ImageContentInterfaceFactory; +use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\File\UploaderFactory; use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\WriteFactory; use Magento\Framework\Filesystem\Directory\WriteInterface; use Magento\Framework\Filesystem\Io\File as IoFileSystem; -use Magento\Framework\App\Filesystem\DirectoryList; -use Magento\Framework\Filesystem\Directory\WriteFactory; use Magento\Framework\Locale\ResolverInterface; use Magento\Framework\Stdlib\DateTime\TimezoneInterface; use Magento\Framework\Url\EncoderInterface; @@ -54,8 +54,6 @@ class Image extends File private $mediaEntityTmpDirectory; /** - * Constructor - * * @param TimezoneInterface $localeDate * @param LoggerInterface $logger * @param AttributeMetadataInterface $attribute @@ -207,13 +205,11 @@ protected function _validateByRules($value) protected function processUiComponentValue(array $value) { if ($this->_entityTypeCode == AddressMetadataInterface::ENTITY_TYPE_ADDRESS) { - $result = $this->processCustomerAddressValue($value); - return $result; + return $this->processCustomerAddressValue($value); } if ($this->_entityTypeCode == CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER) { - $result = $this->processCustomerValue($value); - return $result; + return $this->processCustomerValue($value); } return $this->_value; @@ -267,6 +263,6 @@ protected function processCustomerValue(array $value) return $imageContentDataObject; } - return $this->_value; + return $this->_value ?: $value['file']; } } diff --git a/app/code/Magento/Customer/Model/Metadata/Form/Multiline.php b/app/code/Magento/Customer/Model/Metadata/Form/Multiline.php index 643a73302825d..c10052c631e58 100644 --- a/app/code/Magento/Customer/Model/Metadata/Form/Multiline.php +++ b/app/code/Magento/Customer/Model/Metadata/Form/Multiline.php @@ -10,7 +10,7 @@ class Multiline extends Text { /** - * {@inheritdoc} + * @inheritDoc */ public function extractValue(\Magento\Framework\App\RequestInterface $request) { @@ -24,7 +24,8 @@ public function extractValue(\Magento\Framework\App\RequestInterface $request) } /** - * {@inheritdoc} + * @inheritDoc + * * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function validateValue($value) @@ -43,7 +44,8 @@ public function validateValue($value) if (!is_array($value)) { $value = [$value]; } - for ($i = 0; $i < $attribute->getMultilineCount(); $i++) { + $multilineCount = $attribute->getMultilineCount(); + for ($i = 0; $i < $multilineCount; $i++) { if (!isset($value[$i])) { $value[$i] = null; } @@ -57,6 +59,7 @@ public function validateValue($value) if (!empty($value[$i])) { $result = parent::validateValue($value[$i]); if ($result !== true) { + // phpcs:ignore Magento2.Performance.ForeachArrayMerge $errors = array_merge($errors, $result); } } @@ -70,18 +73,20 @@ public function validateValue($value) } /** - * {@inheritdoc} + * @inheritDoc */ public function compactValue($value) { - if (!is_array($value)) { - $value = [$value]; + if (is_array($value)) { + $value = trim(implode("\n", $value)); } + $value = [$value]; + return parent::compactValue($value); } /** - * {@inheritdoc} + * @inheritDoc */ public function restoreValue($value) { @@ -89,7 +94,7 @@ public function restoreValue($value) } /** - * {@inheritdoc} + * @inheritDoc */ public function outputValue($format = \Magento\Customer\Model\Metadata\ElementFactory::OUTPUT_FORMAT_TEXT) { diff --git a/app/code/Magento/Customer/Observer/AfterAddressSaveObserver.php b/app/code/Magento/Customer/Observer/AfterAddressSaveObserver.php index af0a04827d30f..33290306e4843 100644 --- a/app/code/Magento/Customer/Observer/AfterAddressSaveObserver.php +++ b/app/code/Magento/Customer/Observer/AfterAddressSaveObserver.php @@ -141,7 +141,8 @@ public function execute(Observer $observer) if ($customerAddress->getVatId() == '' || !$this->_customerVat->isCountryInEU($customerAddress->getCountry()) ) { - $defaultGroupId = $this->_groupManagement->getDefaultGroup($customer->getStore())->getId(); + $defaultGroupId = $customer->getGroupId() ? $customer->getGroupId() : + $this->_groupManagement->getDefaultGroup($customer->getStore())->getId(); if (!$customer->getDisableAutoGroupChange() && $customer->getGroupId() != $defaultGroupId) { $customer->setGroupId($defaultGroupId); $customer->save(); @@ -216,8 +217,8 @@ protected function _canProcessAddress($address) protected function _isDefaultBilling($address) { return $address->getId() && $address->getId() == $address->getCustomer()->getDefaultBilling() - || $address->getIsPrimaryBilling() - || $address->getIsDefaultBilling(); + || $address->getIsPrimaryBilling() + || $address->getIsDefaultBilling(); } /** @@ -229,8 +230,8 @@ protected function _isDefaultBilling($address) protected function _isDefaultShipping($address) { return $address->getId() && $address->getId() == $address->getCustomer()->getDefaultShipping() - || $address->getIsPrimaryShipping() - || $address->getIsDefaultShipping(); + || $address->getIsPrimaryShipping() + || $address->getIsDefaultShipping(); } /** diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFillCustomerMainDataActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFillCustomerMainDataActionGroup.xml new file mode 100644 index 0000000000000..dc7d68faf362d --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFillCustomerMainDataActionGroup.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminFillCustomerMainDataActionGroup"> + <annotations> + <description>Fill customer main required data. Starts on customer creation/edit page.</description> + </annotations> + + <arguments> + <argument name="firstName" type="string" defaultValue="{{Simple_US_Customer.firstname}}"/> + <argument name="lastName" type="string" defaultValue="{{Simple_US_Customer.lastname}}"/> + <argument name="email" type="string" defaultValue="{{Simple_US_Customer.email}}"/> + </arguments> + <waitForElementVisible selector="{{AdminCustomerAccountInformationSection.firstName}}" stepKey="waitForCustomerPageLoad"/> + <fillField selector="{{AdminCustomerAccountInformationSection.firstName}}" userInput="{{firstName}}" stepKey="fillFirstName"/> + <fillField selector="{{AdminCustomerAccountInformationSection.lastName}}" userInput="{{lastName}}" stepKey="fillLastName"/> + <fillField selector="{{AdminCustomerAccountInformationSection.email}}" userInput="{{email}}" stepKey="fillEmail"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminOpenAccountInformationTabFromCustomerEditPageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminOpenAccountInformationTabFromCustomerEditPageActionGroup.xml new file mode 100644 index 0000000000000..e7d91fc187425 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminOpenAccountInformationTabFromCustomerEditPageActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOpenAccountInformationTabFromCustomerEditPageActionGroup"> + <annotations> + <description>Open Account Information tab from Customer's edit page</description> + </annotations> + <waitForElementVisible selector="{{AdminCustomerAccountInformationSection.accountInformationTab}}" stepKey="waitForAccountInformationTab"/> + <click selector="{{AdminCustomerAccountInformationSection.accountInformationTab}}" stepKey="clickAccountInformationTab"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSetCustomerActiveViaGridActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSetCustomerActiveViaGridActionGroup.xml new file mode 100644 index 0000000000000..132491da553a5 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSetCustomerActiveViaGridActionGroup.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminSuccessfullySetCustomerActiveViaGridActionGroup"> + <annotations> + <description>Sets a Customer to the Active status from the admin All Customers grid</description> + </annotations> + <arguments> + <argument name="customerEmail" type="string" defaultValue="{{Simple_US_CA_Customer.email}}"/> + </arguments> + <waitForElementVisible selector="{{AdminCustomerGridMainActionsSection.customerCheckbox(customerEmail)}}" stepKey="waitForCustomerRow"/> + <click selector="{{AdminCustomerGridMainActionsSection.customerCheckbox(customerEmail)}}" stepKey="selectCustomer"/> + <click selector="{{AdminCustomerGridMainActionsSection.actions}}" stepKey="clickActions"/> + <waitForElementVisible selector="{{AdminCustomerGridMainActionsSection.setActive}}" stepKey="waitForDropDownOpen"/> + <click selector="{{AdminCustomerGridMainActionsSection.setActive}}" stepKey="clickSetActive"/> + <waitForPageLoad stepKey="waitForLoad"/> + <waitForText selector="{{AdminMessagesSection.success}}" userInput="A total of 1 record(s) were updated." stepKey="seeSuccessMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSetCustomerInactiveViaGridActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSetCustomerInactiveViaGridActionGroup.xml new file mode 100644 index 0000000000000..b7d5c335aa8db --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSetCustomerInactiveViaGridActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminSuccessfullySetCustomerInactiveViaGridActionGroup" extends="AdminSuccessfullySetCustomerActiveViaGridActionGroup"> + <annotations> + <description>Sets a Customer to the Inactive status from the admin All Customers grid</description> + </annotations> + <waitForElementVisible selector="{{AdminCustomerGridMainActionsSection.setInactive}}" stepKey="waitForDropDownOpen"/> + <click selector="{{AdminCustomerGridMainActionsSection.setInactive}}" stepKey="clickSetActive"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCreateCustomerSaveActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCreateCustomerSaveActionGroup.xml new file mode 100644 index 0000000000000..d4ac7edffd47a --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCreateCustomerSaveActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontCreateCustomerSaveActionGroup"> + <click selector="{{StorefrontCustomerCreateFormSection.createAccountButton}}" stepKey="clickCreateAccountButton"/> + <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessageVisible"/> + <see selector="{{StorefrontMessagesSection.success}}" userInput="Thank you for registering with " stepKey="verifySuccessMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomStoreCustomerLogoutActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomStoreCustomerLogoutActionGroup.xml new file mode 100644 index 0000000000000..d7e25553da083 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomStoreCustomerLogoutActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontCustomStoreCustomerLogoutActionGroup" extends="StorefrontCustomerLogoutActionGroup"> + <annotations> + <description>Goes to the storefront Customer Logout page for a custom store. Must have Add Store Code To Urls enabled.</description> + </annotations> + <arguments> + <argument name="storeCode" defaultValue="{{customStoreEN.code}}" type="string"/> + </arguments> + <amOnPage url="{{StorefrontCustomStoreCustomerLogoutPage.url(storeCode)}}" stepKey="storefrontSignOut"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomStoreLoginActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomStoreLoginActionGroup.xml new file mode 100644 index 0000000000000..41a6abba1ff2b --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomStoreLoginActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontCustomStoreLoginActionGroup" extends="LoginToStorefrontActionGroup"> + <annotations> + <description>Goes to the storefront Customer Sign In page for a custom store. Logs in using the provided Customer. Must have Add Store Code To Urls enabled</description> + </annotations> + <arguments> + <argument name="storeCode" defaultValue="{{customStoreEN.code}}" type="string"/> + </arguments> + <amOnPage url="{{StorefrontCustomStoreCustomerSignInPage.url(storeCode)}}" stepKey="amOnSignInPage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomStoreNavigateToCustomerOrdersHistoryPageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomStoreNavigateToCustomerOrdersHistoryPageActionGroup.xml new file mode 100644 index 0000000000000..399b684ba8974 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomStoreNavigateToCustomerOrdersHistoryPageActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontCustomStoreNavigateToCustomerOrdersHistoryPageActionGroup" extends="StorefrontNavigateToCustomerOrdersHistoryPageActionGroup"> + <annotations> + <description>Goes to the storefront Customer Order History page for a custom store. Must have Add Store Code To Urls enabled</description> + </annotations> + <arguments> + <argument name="storeCode" defaultValue="{{customStoreEN.code}}" type="string"/> + </arguments> + <amOnPage url="{{StorefrontCustomStoreCustomerOrdersHistoryPage.url(storeCode)}}" stepKey="amOnTheCustomerPage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerSaveActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerSaveActionGroup.xml new file mode 100644 index 0000000000000..88c1d70bb9abb --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerSaveActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontCustomerSaveActionGroup"> + <annotations> + <description>Clicks on customer save button in dashboard, asserts success message</description> + </annotations> + <scrollToTopOfPage stepKey="scrollToTopOfThePage"/> + <click selector="{{StorefrontCustomerAccountInformationSection.saveButton}}" stepKey="saveCustomerOnStoreFront"/> + <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessageVisible"/> + <see selector="{{StorefrontMessagesSection.success}}" userInput="You saved the account information." stepKey="verifySuccessMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontGoToCustomerAddressesPageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontGoToCustomerAddressesPageActionGroup.xml new file mode 100644 index 0000000000000..ba942d9a1d904 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontGoToCustomerAddressesPageActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontGoToCustomerAddressesPageActionGroup"> + <annotations> + <description>Goes to the storefront customer addresses page</description> + </annotations> + <amOnPage url="{{StorefrontCustomerAddressesPage.url}}" stepKey="goToAddressPage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/ImageData.xml b/app/code/Magento/Customer/Test/Mftf/Data/ImageData.xml new file mode 100644 index 0000000000000..3abd51883434b --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Data/ImageData.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="SmallImage" type="imageFile"> + <data key="file">small.jpg</data> + <data key="name">small</data> + <data key="extension">jpg</data> + </entity> +</entities> diff --git a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomStoreCustomerLogoutPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomStoreCustomerLogoutPage.xml new file mode 100644 index 0000000000000..c4f215470a3b7 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomStoreCustomerLogoutPage.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="StorefrontCustomStoreCustomerLogoutPage" url="/{{storeCode}}/customer/account/logout/" area="storefront" module="Magento_Customer" parameterized="true"/> +</pages> diff --git a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomStoreCustomerSignInPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomStoreCustomerSignInPage.xml new file mode 100644 index 0000000000000..822aae12e8344 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomStoreCustomerSignInPage.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="StorefrontCustomStoreCustomerSignInPage" url="/{{storeCode}}/customer/account/login/" area="storefront" module="Magento_Customer" parameterized="true"> + <section name="StorefrontCustomerSignInFormSection" /> + <section name="StorefrontCustomerLoginMessagesSection"/> + </page> +</pages> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridMainActionsSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridMainActionsSection.xml index 44ab653259b55..a2d77a093db3d 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridMainActionsSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridMainActionsSection.xml @@ -17,5 +17,7 @@ <element name="ok" type="button" selector="//button[@data-role='action']//span[text()='OK']"/> <element name="addNewAddress" type="button" selector=".add-new-address-button" timeout="30"/> <element name="actions" type="text" selector=".admin__data-grid-header-row .action-select"/> + <element name="setActive" type="button" selector="//*[contains(@class, 'admin__data-grid-header')]//span[contains(@class,'action-menu-item') and text()='Set Active']" timeout="30"/> + <element name="setInactive" type="button" selector="//*[contains(@class, 'admin__data-grid-header')]//span[contains(@class,'action-menu-item') and text()='Set Inactive']" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridSection.xml index 9562a902b26da..a713a606426c8 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridSection.xml @@ -17,6 +17,7 @@ <element name="customerEditLinkByEmail" type="text" selector="//tr[@class='data-row' and //div[text()='{{customerEmail}}']]//a[@class='action-menu-item']" parameterized="true" timeout="30"/> <element name="customerGroupByEmail" type="text" selector="//tr[@class='data-row' and //div[text()='{{customerEmail}}']]//div[text()='{{customerGroup}}']" parameterized="true"/> <element name="customerGenderByEmail" type="text" selector="//tr[@class='data-row']//div[text()='{{customerEmail}}']/ancestor::tr/td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Gender')]/preceding-sibling::th) +1]" parameterized="true"/> + <element name="status" type="text" selector="//tr[@class='data-row' and //div[text()='{{customerEmail}}']]//div[contains(text(), '{{status}}')]" parameterized="true" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCreateFormSection/StoreFrontCustomerAdvancedAttributesSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCreateFormSection/StoreFrontCustomerAdvancedAttributesSection.xml index b2f96eb539c08..d03e088104807 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCreateFormSection/StoreFrontCustomerAdvancedAttributesSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCreateFormSection/StoreFrontCustomerAdvancedAttributesSection.xml @@ -20,5 +20,7 @@ <element name="yesNoOptionAttribute" type="select" selector="//select[@id='{{var}}']/option[2]" parameterized="true"/> <element name="selectedOption" type="text" selector="//select[@id='{{var}}']/option[@selected='selected']" parameterized="true"/> <element name="attributeLabel" type="text" selector="//span[text()='{{attributeLabel}}']" parameterized="true"/> + <element name="fileAttribute" type="file" selector="//input[@type='file' and @name='{{attributeCode}}']" parameterized="true" timeout="30"/> + <element name="customFileAttributeUploadButton" type="input" selector=".file-uploader-area input[name='{{attributeCode}}'] ~ .file-uploader-button" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderSection.xml index 61ce050aa3ef2..00703d8cc5235 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderSection.xml @@ -15,6 +15,8 @@ <element name="productCustomOptionsLink" type="text" selector="//strong[contains(@class, 'product-item-name') and normalize-space(.)='{{var1}}']/following-sibling::*[contains(@class, 'item-options')]/dt[normalize-space(.)='{{var2}}']/following-sibling::dd//a[text() = '{{var3}}']" parameterized="true"/> <element name="status" type="text" selector="//td[contains(concat(' ',normalize-space(@class),' '),' col status ')]"/> <element name="viewOrder" type="button" selector="//td[contains(concat(' ',normalize-space(@class),' '),' col actions ')]/a[contains(concat(' ',normalize-space(@class),' '),' action view ')]"/> + <element name="viewOrderByOrderNumber" type="button" parameterized="true" selector="//td[.='{{orderNumber}}']/ancestor::tr//a[contains(concat(' ',normalize-space(@class),' '),' action view ')]"/> + <element name="reorderByOrderNumber" type="button" parameterized="true" selector="//td[.='{{orderNumber}}']/ancestor::tr//a[contains(concat(' ',normalize-space(@class),' '),' action order ')]"/> <element name="tabRefund" type="button" selector="//a[text()='Refunds']"/> <element name="grandTotalRefund" type="text" selector="td[data-th='Grand Total'] > strong > span.price"/> <element name="currentPage" type="text" selector=".order-products-toolbar .pages .current span:nth-of-type(2)"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml index 7442a32d58b2d..5b6c4fd23e038 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml @@ -15,21 +15,24 @@ <title value="Admin should be able to create a customer"/> <description value="Admin should be able to create a customer"/> <severity value="BLOCKER"/> - <testCaseId value="MAGETWO-72095"/> + <testCaseId value="MC-28500"/> <group value="customer"/> <group value="create"/> </annotations> + <before> <actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindexCustomerGrid"> <argument name="indices" value="customer_grid"/> </actionGroup> </before> + <after> + <actionGroup ref="AdminClearCustomersFiltersActionGroup" stepKey="clearCustomersFilter"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> </after> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin1"/> - <amOnPage url="{{AdminCustomerPage.url}}" stepKey="navigateToCustomers"/> + <actionGroup ref="AdminClearCustomersFiltersActionGroup" stepKey="navigateToCustomers"/> <waitForPageLoad stepKey="waitForLoad1"/> <click selector="{{AdminCustomerGridMainActionsSection.addNewCustomer}}" stepKey="clickCreateCustomer"/> <fillField userInput="{{CustomerEntityOne.firstname}}" selector="{{AdminCustomerAccountInformationSection.firstName}}" stepKey="fillFirstName"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml index 8dcf494b3572b..824c898068342 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml @@ -21,10 +21,11 @@ </annotations> <before> <resetCookie userInput="PHPSESSID" stepKey="resetCookieForCart"/> + <actionGroup ref="AdminLoginActionGroup" after="resetCookieForCart" stepKey="loginAsAdmin"/> </before> <after> <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="logoutCustomer"/> - <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <comment userInput="No need this since it done in before section" stepKey="loginAsAdmin"/> <actionGroup ref="DeleteCustomerFromAdminActionGroup" stepKey="deleteCustomerFromAdmin"/> <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearProductsGridFilters"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Address/FormPostTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Address/FormPostTest.php index a10cfe207b822..37239884aeb4f 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Address/FormPostTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Address/FormPostTest.php @@ -495,7 +495,7 @@ public function testExecute( $this->request->expects($this->once()) ->method('isPost') ->willReturn(true); - $this->request->expects($this->exactly(3)) + $this->request->expects($this->exactly(4)) ->method('getParam') ->willReturnMap([ ['id', null, $addressId], diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php index 8e9cce7390831..0e586d0c424a5 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php @@ -722,7 +722,7 @@ public function testExecuteWithNewCustomerAndValidationException() ]; $extractedData = [ 'coolness' => false, - 'disable_auto_group_change' => 'false', + 'disable_auto_group_change' => 0, 'dob' => '1996-03-12', ]; @@ -731,10 +731,10 @@ public function testExecuteWithNewCustomerAndValidationException() AttributeMetadataInterface::class )->disableOriginalConstructor() ->getMock(); - $attributeMock->expects($this->exactly(2)) + $attributeMock->expects($this->once()) ->method('getAttributeCode') ->willReturn('coolness'); - $attributeMock->expects($this->exactly(2)) + $attributeMock->expects($this->once()) ->method('getFrontendInput') ->willReturn('int'); $attributes = [$attributeMock]; @@ -759,12 +759,12 @@ public function testExecuteWithNewCustomerAndValidationException() $objectMock = $this->getMockBuilder(DataObject::class) ->disableOriginalConstructor() ->getMock(); - $objectMock->expects($this->exactly(2)) + $objectMock->expects($this->once()) ->method('getData') ->with('customer') ->willReturn($postValue['customer']); - $this->objectFactoryMock->expects($this->exactly(2)) + $this->objectFactoryMock->expects($this->once()) ->method('create') ->with(['data' => $postValue]) ->willReturn($objectMock); @@ -773,19 +773,19 @@ public function testExecuteWithNewCustomerAndValidationException() Form::class )->disableOriginalConstructor() ->getMock(); - $customerFormMock->expects($this->exactly(2)) + $customerFormMock->expects($this->once()) ->method('extractData') ->with($this->requestMock, 'customer') ->willReturn($extractedData); - $customerFormMock->expects($this->exactly(2)) + $customerFormMock->expects($this->once()) ->method('compactData') ->with($extractedData) ->willReturn($extractedData); - $customerFormMock->expects($this->exactly(2)) + $customerFormMock->expects($this->once()) ->method('getAttributes') ->willReturn($attributes); - $this->formFactoryMock->expects($this->exactly(2)) + $this->formFactoryMock->expects($this->once()) ->method('create') ->with( CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, @@ -809,7 +809,10 @@ public function testExecuteWithNewCustomerAndValidationException() ->method('createAccount') ->with($customerMock, null, '') ->willThrowException(new \Magento\Framework\Validator\Exception(__('Validator Exception'))); - + $this->customerMapperMock->expects($this->once()) + ->method('toFlatArray') + ->with($customerMock) + ->willReturn($extractedData); $customerMock->expects($this->never()) ->method('getId'); @@ -876,7 +879,7 @@ public function testExecuteWithNewCustomerAndLocalizedException() ]; $extractedData = [ 'coolness' => false, - 'disable_auto_group_change' => 'false', + 'disable_auto_group_change' => 0, 'dob' => '1996-03-12', ]; @@ -885,10 +888,10 @@ public function testExecuteWithNewCustomerAndLocalizedException() AttributeMetadataInterface::class )->disableOriginalConstructor() ->getMock(); - $attributeMock->expects($this->exactly(2)) + $attributeMock->expects($this->once()) ->method('getAttributeCode') ->willReturn('coolness'); - $attributeMock->expects($this->exactly(2)) + $attributeMock->expects($this->once()) ->method('getFrontendInput') ->willReturn('int'); $attributes = [$attributeMock]; @@ -913,12 +916,12 @@ public function testExecuteWithNewCustomerAndLocalizedException() $objectMock = $this->getMockBuilder(DataObject::class) ->disableOriginalConstructor() ->getMock(); - $objectMock->expects($this->exactly(2)) + $objectMock->expects($this->once()) ->method('getData') ->with('customer') ->willReturn($postValue['customer']); - $this->objectFactoryMock->expects($this->exactly(2)) + $this->objectFactoryMock->expects($this->once()) ->method('create') ->with(['data' => $postValue]) ->willReturn($objectMock); @@ -928,19 +931,19 @@ public function testExecuteWithNewCustomerAndLocalizedException() Form::class )->disableOriginalConstructor() ->getMock(); - $customerFormMock->expects($this->exactly(2)) + $customerFormMock->expects($this->once()) ->method('extractData') ->with($this->requestMock, 'customer') ->willReturn($extractedData); - $customerFormMock->expects($this->exactly(2)) + $customerFormMock->expects($this->once()) ->method('compactData') ->with($extractedData) ->willReturn($extractedData); - $customerFormMock->expects($this->exactly(2)) + $customerFormMock->expects($this->once()) ->method('getAttributes') ->willReturn($attributes); - $this->formFactoryMock->expects($this->exactly(2)) + $this->formFactoryMock->expects($this->once()) ->method('create') ->with( CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, @@ -986,6 +989,11 @@ public function testExecuteWithNewCustomerAndLocalizedException() ->method('addMessage') ->with(new Error('Localized Exception')); + $this->customerMapperMock->expects($this->once()) + ->method('toFlatArray') + ->with($customerMock) + ->willReturn($extractedData); + $this->sessionMock->expects($this->once()) ->method('setCustomerFormData') ->with( @@ -1030,7 +1038,7 @@ public function testExecuteWithNewCustomerAndException() ]; $extractedData = [ 'coolness' => false, - 'disable_auto_group_change' => 'false', + 'disable_auto_group_change' => 0, 'dob' => '1996-03-12', ]; @@ -1039,10 +1047,10 @@ public function testExecuteWithNewCustomerAndException() AttributeMetadataInterface::class )->disableOriginalConstructor() ->getMock(); - $attributeMock->expects($this->exactly(2)) + $attributeMock->expects($this->once()) ->method('getAttributeCode') ->willReturn('coolness'); - $attributeMock->expects($this->exactly(2)) + $attributeMock->expects($this->once()) ->method('getFrontendInput') ->willReturn('int'); $attributes = [$attributeMock]; @@ -1067,12 +1075,12 @@ public function testExecuteWithNewCustomerAndException() $objectMock = $this->getMockBuilder(DataObject::class) ->disableOriginalConstructor() ->getMock(); - $objectMock->expects($this->exactly(2)) + $objectMock->expects($this->once()) ->method('getData') ->with('customer') ->willReturn($postValue['customer']); - $this->objectFactoryMock->expects($this->exactly(2)) + $this->objectFactoryMock->expects($this->once()) ->method('create') ->with(['data' => $postValue]) ->willReturn($objectMock); @@ -1081,19 +1089,19 @@ public function testExecuteWithNewCustomerAndException() Form::class )->disableOriginalConstructor() ->getMock(); - $customerFormMock->expects($this->exactly(2)) + $customerFormMock->expects($this->once()) ->method('extractData') ->with($this->requestMock, 'customer') ->willReturn($extractedData); - $customerFormMock->expects($this->exactly(2)) + $customerFormMock->expects($this->once()) ->method('compactData') ->with($extractedData) ->willReturn($extractedData); - $customerFormMock->expects($this->exactly(2)) + $customerFormMock->expects($this->once()) ->method('getAttributes') ->willReturn($attributes); - $this->formFactoryMock->expects($this->exactly(2)) + $this->formFactoryMock->expects($this->once()) ->method('create') ->with( CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, @@ -1141,6 +1149,11 @@ public function testExecuteWithNewCustomerAndException() ->method('addExceptionMessage') ->with($exception, __('Something went wrong while saving the customer.')); + $this->customerMapperMock->expects($this->once()) + ->method('toFlatArray') + ->with($customerMock) + ->willReturn($extractedData); + $this->sessionMock->expects($this->once()) ->method('setCustomerFormData') ->with( diff --git a/app/code/Magento/Customer/Test/Unit/Model/FileProcessorTest.php b/app/code/Magento/Customer/Test/Unit/Model/FileProcessorTest.php index e1c771d79694e..7a0522f6476f2 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/FileProcessorTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/FileProcessorTest.php @@ -313,10 +313,7 @@ public function testMoveTemporaryFileUnableToCreateDirectory() $destinationPath = 'customer/f/i'; - $this->mediaDirectory->expects($this->once()) - ->method('create') - ->with($destinationPath) - ->willReturn(false); + $this->configureMediaDirectoryMock($destinationPath, false); $model = $this->getModel(CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER); $model->moveTemporaryFile($filePath); @@ -331,10 +328,7 @@ public function testMoveTemporaryFileDestinationFolderDoesNotExists() $destinationPath = 'customer/f/i'; - $this->mediaDirectory->expects($this->once()) - ->method('create') - ->with($destinationPath) - ->willReturn(true); + $this->configureMediaDirectoryMock($destinationPath, true); $this->mediaDirectory->expects($this->once()) ->method('isWritable') ->with($destinationPath) @@ -350,10 +344,7 @@ public function testMoveTemporaryFile() $destinationPath = 'customer/f/i'; - $this->mediaDirectory->expects($this->once()) - ->method('create') - ->with($destinationPath) - ->willReturn(true); + $this->configureMediaDirectoryMock($destinationPath, true); $this->mediaDirectory->expects($this->once()) ->method('isWritable') ->with($destinationPath) @@ -390,10 +381,7 @@ public function testMoveTemporaryFileNewFileName() $destinationPath = 'customer/f/i'; - $this->mediaDirectory->expects($this->once()) - ->method('create') - ->with($destinationPath) - ->willReturn(true); + $this->configureMediaDirectoryMock($destinationPath, true); $this->mediaDirectory->expects($this->once()) ->method('isWritable') ->with($destinationPath) @@ -440,10 +428,7 @@ public function testMoveTemporaryFileWithException() $destinationPath = 'customer/f/i'; - $this->mediaDirectory->expects($this->once()) - ->method('create') - ->with($destinationPath) - ->willReturn(true); + $this->configureMediaDirectoryMock($destinationPath, true); $this->mediaDirectory->expects($this->once()) ->method('isWritable') ->with($destinationPath) @@ -486,4 +471,26 @@ public function testGetMimeType() $this->assertEquals($expected, $model->getMimeType($fileName)); } + + /** + * Configure media directory mock to create media directory. + * + * @param string $destinationPath + * @param bool $directoryCreated + */ + private function configureMediaDirectoryMock(string $destinationPath, bool $directoryCreated): void + { + $this->mediaDirectory->expects($this->at(0)) + ->method('isExist') + ->with('customer/tmp/filename.ext1') + ->willReturn(true); + $this->mediaDirectory->expects($this->at(1)) + ->method('isExist') + ->with('customer/filename.ext1') + ->willReturn(false); + $this->mediaDirectory->expects($this->once()) + ->method('create') + ->with($destinationPath) + ->willReturn($directoryCreated); + } } diff --git a/app/code/Magento/Customer/Test/Unit/Model/Metadata/Form/FileTest.php b/app/code/Magento/Customer/Test/Unit/Model/Metadata/Form/FileTest.php index d3a6e797c7d5c..3c5016df230f9 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Metadata/Form/FileTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Metadata/Form/FileTest.php @@ -103,29 +103,31 @@ public function testExtractValueNoRequestScope($expected, $attributeCode = '', $ $value = 'value'; $this->requestMock->expects( - $this->any() + $this->at(0) )->method( 'getParam' - )->willReturn( - ['delete' => $delete] + )->will( + $this->returnValue(['delete' => $delete]) ); $this->attributeMetadataMock->expects( $this->any() )->method( 'getAttributeCode' - )->willReturn( - $attributeCode + )->will( + $this->returnValue($attributeCode) ); if (!empty($attributeCode)) { $_FILES[$attributeCode] = ['attributeCodeValue']; } - $model = $this->initialize([ - 'value' => $value, - 'isAjax' => false, - 'entityTypeCode' => self::ENTITY_TYPE, - ]); + $model = $this->initialize( + [ + 'value' => $value, + 'isAjax' => false, + 'entityTypeCode' => self::ENTITY_TYPE, + ] + ); $this->assertEquals($expected, $model->extractValue($this->requestMock)); if (!empty($attributeCode)) { @@ -157,33 +159,35 @@ public function testExtractValueWithRequestScope($expected, $requestScope, $main $value = 'value'; $this->requestMock->expects( - $this->any() + $this->at(0) )->method( 'getParam' - )->willReturn( - ['delete' => true] + )->will( + $this->returnValue(['delete' => true]) ); $this->requestMock->expects( $this->any() )->method( 'getParams' - )->willReturn( - ['delete' => true] + )->will( + $this->returnValue(['delete' => true]) ); $this->attributeMetadataMock->expects( $this->any() )->method( 'getAttributeCode' - )->willReturn( - 'attributeCode' + )->will( + $this->returnValue('attributeCode') ); - $model = $this->initialize([ - 'value' => $value, - 'isAjax' => false, - 'entityTypeCode' => self::ENTITY_TYPE, - ]); + $model = $this->initialize( + [ + 'value' => $value, + 'isAjax' => false, + 'entityTypeCode' => self::ENTITY_TYPE, + ] + ); $model->setRequestScope($requestScope); @@ -229,22 +233,24 @@ public function testValidateValueNotToUpload($expected, $value, $isAjax = false, $this->any() )->method( 'isRequired' - )->willReturn( - $isRequired + )->will( + $this->returnValue($isRequired) ); $this->attributeMetadataMock->expects( $this->any() )->method( 'getStoreLabel' - )->willReturn( - 'attributeLabel' + )->will( + $this->returnValue('attributeLabel') ); - $model = $this->initialize([ - 'value' => $value, - 'isAjax' => $isAjax, - 'entityTypeCode' => self::ENTITY_TYPE, - ]); + $model = $this->initialize( + [ + 'value' => $value, + 'isAjax' => $isAjax, + 'entityTypeCode' => self::ENTITY_TYPE, + ] + ); $this->assertEquals($expected, $model->validateValue($value)); } @@ -272,39 +278,41 @@ public function testValidateValueToUpload($expected, $value, $parameters = []) { $parameters = array_merge(['uploaded' => true, 'valid' => true], $parameters); - $this->attributeMetadataMock->expects($this->any())->method('isRequired')->willReturn(false); + $this->attributeMetadataMock->expects($this->any())->method('isRequired')->will($this->returnValue(false)); $this->attributeMetadataMock->expects( $this->any() )->method( 'getStoreLabel' - )->willReturn( - 'File Input Field Label' + )->will( + $this->returnValue('File Input Field Label') ); $this->fileValidatorMock->expects( $this->any() )->method( 'getMessages' - )->willReturn( - ['Validation error message.'] + )->will( + $this->returnValue(['Validation error message.']) ); $this->fileValidatorMock->expects( $this->any() )->method( 'isValid' - )->willReturn( - $parameters['valid'] + )->will( + $this->returnValue($parameters['valid']) ); $this->fileProcessorMock->expects($this->any()) ->method('isExist') ->willReturn($parameters['uploaded']); - $model = $this->initialize([ - 'value' => $value, - 'isAjax' => false, - 'entityTypeCode' => self::ENTITY_TYPE, - ]); + $model = $this->initialize( + [ + 'value' => $value, + 'isAjax' => false, + 'entityTypeCode' => self::ENTITY_TYPE, + ] + ); $this->assertEquals($expected, $model->validateValue($value)); } @@ -331,24 +339,28 @@ public function validateValueToUploadDataProvider() public function testCompactValueIsAjax() { - $model = $this->initialize([ - 'value' => 'value', - 'isAjax' => true, - 'entityTypeCode' => self::ENTITY_TYPE, - ]); + $model = $this->initialize( + [ + 'value' => 'value', + 'isAjax' => true, + 'entityTypeCode' => self::ENTITY_TYPE, + ] + ); $this->assertSame($model, $model->compactValue('aValue')); } public function testCompactValueNoDelete() { - $this->attributeMetadataMock->expects($this->any())->method('isRequired')->willReturn(false); + $this->attributeMetadataMock->expects($this->any())->method('isRequired')->will($this->returnValue(false)); - $model = $this->initialize([ - 'value' => 'value', - 'isAjax' => false, - 'entityTypeCode' => self::ENTITY_TYPE, - ]); + $model = $this->initialize( + [ + 'value' => 'value', + 'isAjax' => false, + 'entityTypeCode' => self::ENTITY_TYPE, + ] + ); $this->fileProcessorMock->expects($this->once()) ->method('removeUploadedFile') @@ -360,10 +372,10 @@ public function testCompactValueNoDelete() public function testCompactValueDelete() { - $this->attributeMetadataMock->expects($this->any())->method('isRequired')->willReturn(false); + $this->attributeMetadataMock->expects($this->any())->method('isRequired')->will($this->returnValue(false)); $mediaDirMock = $this->getMockForAbstractClass( - WriteInterface::class + \Magento\Framework\Filesystem\Directory\WriteInterface::class ); $mediaDirMock->expects($this->once()) ->method('delete') @@ -372,13 +384,15 @@ public function testCompactValueDelete() $this->fileSystemMock->expects($this->once()) ->method('getDirectoryWrite') ->with(DirectoryList::MEDIA) - ->willReturn($mediaDirMock); + ->will($this->returnValue($mediaDirMock)); - $model = $this->initialize([ - 'value' => 'value', - 'isAjax' => false, - 'entityTypeCode' => self::ENTITY_TYPE, - ]); + $model = $this->initialize( + [ + 'value' => 'value', + 'isAjax' => false, + 'entityTypeCode' => self::ENTITY_TYPE, + ] + ); $this->assertSame('', $model->compactValue(['delete' => true])); } @@ -389,20 +403,20 @@ public function testCompactValueTmpFile() $expected = 'saved.file'; $mediaDirMock = $this->getMockForAbstractClass( - WriteInterface::class + \Magento\Framework\Filesystem\Directory\WriteInterface::class ); $this->fileSystemMock->expects($this->once()) ->method('getDirectoryWrite') ->with(DirectoryList::MEDIA) - ->willReturn($mediaDirMock); + ->will($this->returnValue($mediaDirMock)); $mediaDirMock->expects($this->any()) ->method('getAbsolutePath') - ->willReturnArgument(0); - $uploaderMock = $this->createMock(Uploader::class); + ->will($this->returnArgument(0)); + $uploaderMock = $this->createMock(\Magento\Framework\File\Uploader::class); $this->uploaderFactoryMock->expects($this->once()) ->method('create') ->with(['fileId' => $value]) - ->willReturn($uploaderMock); + ->will($this->returnValue($uploaderMock)); $uploaderMock->expects($this->once()) ->method('setFilesDispersion') ->with(true); @@ -417,13 +431,15 @@ public function testCompactValueTmpFile() ->with(self::ENTITY_TYPE, 'new.file'); $uploaderMock->expects($this->once()) ->method('getUploadedFileName') - ->willReturn($expected); + ->will($this->returnValue($expected)); - $model = $this->initialize([ - 'value' => null, - 'isAjax' => false, - 'entityTypeCode' => self::ENTITY_TYPE, - ]); + $model = $this->initialize( + [ + 'value' => null, + 'isAjax' => false, + 'entityTypeCode' => self::ENTITY_TYPE, + ] + ); $this->assertSame($expected, $model->compactValue($value)); } @@ -432,11 +448,13 @@ public function testRestoreValue() { $value = 'value'; - $model = $this->initialize([ - 'value' => $value, - 'isAjax' => false, - 'entityTypeCode' => self::ENTITY_TYPE, - ]); + $model = $this->initialize( + [ + 'value' => $value, + 'isAjax' => false, + 'entityTypeCode' => self::ENTITY_TYPE, + ] + ); $this->assertEquals($value, $model->restoreValue('aValue')); } @@ -447,11 +465,13 @@ public function testRestoreValue() */ public function testOutputValueNonJson($format) { - $model = $this->initialize([ - 'value' => 'value', - 'isAjax' => false, - 'entityTypeCode' => self::ENTITY_TYPE, - ]); + $model = $this->initialize( + [ + 'value' => 'value', + 'isAjax' => false, + 'entityTypeCode' => self::ENTITY_TYPE, + ] + ); $this->assertSame('', $model->outputValue($format)); } @@ -480,29 +500,31 @@ public function testOutputValueJson() )->method( 'encode' )->with( - $value - )->willReturn( - $urlKey + $this->equalTo($value) + )->will( + $this->returnValue($urlKey) ); $expected = ['value' => $value, 'url_key' => $urlKey]; - $model = $this->initialize([ - 'value' => $value, - 'isAjax' => false, - 'entityTypeCode' => self::ENTITY_TYPE, - ]); + $model = $this->initialize( + [ + 'value' => $value, + 'isAjax' => false, + 'entityTypeCode' => self::ENTITY_TYPE, + ] + ); $this->assertSame($expected, $model->outputValue(ElementFactory::OUTPUT_FORMAT_JSON)); } /** * @param array $data - * @return File + * @return \Magento\Customer\Model\Metadata\Form\File */ private function initialize(array $data) { - return new File( + return new \Magento\Customer\Model\Metadata\Form\File( $this->localeMock, $this->loggerMock, $this->attributeMetadataMock, @@ -528,22 +550,26 @@ public function testExtractValueFileUploaderUIComponent() ->method('getAttributeCode') ->willReturn($attributeCode); - $this->requestMock->expects($this->once()) + $this->requestMock->expects($this->at(0)) ->method('getParam') ->with($requestScope) - ->willReturn([ - $attributeCode => [ - [ - 'file' => $fileName, + ->willReturn( + [ + $attributeCode => [ + [ + 'file' => $fileName, + ], ], - ], - ]); - - $model = $this->initialize([ - 'value' => 'value', - 'isAjax' => false, - 'entityTypeCode' => self::ENTITY_TYPE, - ]); + ] + ); + + $model = $this->initialize( + [ + 'value' => 'value', + 'isAjax' => false, + 'entityTypeCode' => self::ENTITY_TYPE, + ] + ); $model->setRequestScope($requestScope); $result = $model->extractValue($this->requestMock); @@ -555,11 +581,13 @@ public function testCompactValueRemoveUiComponentValue() { $value = 'value'; - $model = $this->initialize([ - 'value' => $value, - 'isAjax' => false, - 'entityTypeCode' => self::ENTITY_TYPE, - ]); + $model = $this->initialize( + [ + 'value' => $value, + 'isAjax' => false, + 'entityTypeCode' => self::ENTITY_TYPE, + ] + ); $this->fileProcessorMock->expects($this->once()) ->method('removeUploadedFile') @@ -573,11 +601,13 @@ public function testCompactValueNoAction() { $value = 'value'; - $model = $this->initialize([ - 'value' => $value, - 'isAjax' => false, - 'entityTypeCode' => self::ENTITY_TYPE, - ]); + $model = $this->initialize( + [ + 'value' => $value, + 'isAjax' => false, + 'entityTypeCode' => self::ENTITY_TYPE, + ] + ); $this->assertEquals($value, $model->compactValue($value)); } @@ -588,11 +618,13 @@ public function testCompactValueUiComponent() 'file' => 'filename', ]; - $model = $this->initialize([ - 'value' => null, - 'isAjax' => false, - 'entityTypeCode' => self::ENTITY_TYPE, - ]); + $model = $this->initialize( + [ + 'value' => null, + 'isAjax' => false, + 'entityTypeCode' => self::ENTITY_TYPE, + ] + ); $this->fileProcessorMock->expects($this->once()) ->method('moveTemporaryFile') @@ -613,7 +645,7 @@ public function testCompactValueInputField() $uploadedFilename = 'filename.ext1'; $mediaDirectoryMock = $this->getMockBuilder( - WriteInterface::class + \Magento\Framework\Filesystem\Directory\WriteInterface::class ) ->getMockForAbstractClass(); $mediaDirectoryMock->expects($this->once()) @@ -627,9 +659,8 @@ public function testCompactValueInputField() ->willReturn($mediaDirectoryMock); $uploaderMock = $this->getMockBuilder( - Uploader::class - )->disableOriginalConstructor() - ->getMock(); + \Magento\Framework\File\Uploader::class + )->disableOriginalConstructor()->getMock(); $uploaderMock->expects($this->once()) ->method('setFilesDispersion') ->with(true) @@ -655,11 +686,13 @@ public function testCompactValueInputField() ->with(['fileId' => $value]) ->willReturn($uploaderMock); - $model = $this->initialize([ - 'value' => null, - 'isAjax' => false, - 'entityTypeCode' => self::ENTITY_TYPE, - ]); + $model = $this->initialize( + [ + 'value' => null, + 'isAjax' => false, + 'entityTypeCode' => self::ENTITY_TYPE, + ] + ); $this->assertEquals($uploadedFilename, $model->compactValue($value)); } @@ -674,7 +707,7 @@ public function testCompactValueInputFieldWithException() $originValue = 'origin'; $mediaDirectoryMock = $this->getMockBuilder( - WriteInterface::class + \Magento\Framework\Filesystem\Directory\WriteInterface::class )->getMockForAbstractClass(); $mediaDirectoryMock->expects($this->once()) ->method('delete') @@ -697,11 +730,13 @@ public function testCompactValueInputFieldWithException() ->with($exception) ->willReturnSelf(); - $model = $this->initialize([ - 'value' => $originValue, - 'isAjax' => false, - 'entityTypeCode' => self::ENTITY_TYPE, - ]); + $model = $this->initialize( + [ + 'value' => $originValue, + 'isAjax' => false, + 'entityTypeCode' => self::ENTITY_TYPE, + ] + ); $this->assertEquals('', $model->compactValue($value)); } diff --git a/app/code/Magento/Customer/Test/Unit/Model/Renderer/RegionTest.php b/app/code/Magento/Customer/Test/Unit/Model/Renderer/RegionTest.php index ae48926f12612..37e89c63c8957 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Renderer/RegionTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Renderer/RegionTest.php @@ -66,7 +66,8 @@ function (array $attributes) use ($elementMock): string { } ); $countryMock = $this->getMockBuilder(AbstractElement::class) - ->addMethods(['getValue', 'serialize']) + ->onlyMethods(['serialize']) + ->addMethods(['getValue']) ->disableOriginalConstructor() ->getMockForAbstractClass(); $countryMock->method('serialize')->willReturnCallback( diff --git a/app/code/Magento/Customer/Test/Unit/Observer/AfterAddressSaveObserverTest.php b/app/code/Magento/Customer/Test/Unit/Observer/AfterAddressSaveObserverTest.php index f72cbbc281e90..146cecb09351f 100644 --- a/app/code/Magento/Customer/Test/Unit/Observer/AfterAddressSaveObserverTest.php +++ b/app/code/Magento/Customer/Test/Unit/Observer/AfterAddressSaveObserverTest.php @@ -341,7 +341,7 @@ public function testAfterAddressSaveDefaultGroup( $customer->expects($this->once()) ->method('getDisableAutoGroupChange') ->willReturn(false); - $customer->expects($this->once()) + $customer->expects($this->exactly(2)) ->method('getGroupId') ->willReturn(null); $customer->expects($this->once()) diff --git a/app/code/Magento/Customer/Ui/Component/Listing/AttributeRepository.php b/app/code/Magento/Customer/Ui/Component/Listing/AttributeRepository.php index 0a89cb6ea1449..c409257a40123 100644 --- a/app/code/Magento/Customer/Ui/Component/Listing/AttributeRepository.php +++ b/app/code/Magento/Customer/Ui/Component/Listing/AttributeRepository.php @@ -143,9 +143,13 @@ protected function getOptionArray(array $options) { /** @var \Magento\Customer\Api\Data\OptionInterface $option */ foreach ($options as &$option) { + $value = $option->getValue(); + if (is_array($option->getOptions())) { + $value = $this->getOptionArray($option->getOptions()); + } $option = [ 'label' => (string)$option->getLabel(), - 'value' => $option->getValue(), + 'value' => $value, '__disableTmpl' => true ]; } diff --git a/app/code/Magento/Customer/ViewModel/Customer/Store.php b/app/code/Magento/Customer/ViewModel/Customer/Store.php index 1e6ca69e2d77a..1d6fa2b30912f 100644 --- a/app/code/Magento/Customer/ViewModel/Customer/Store.php +++ b/app/code/Magento/Customer/ViewModel/Customer/Store.php @@ -113,7 +113,12 @@ private function getStoreOptionsWithCurrentWebsiteId(): array if (!empty($this->dataPersistor->get('customer')['account'])) { $currentWebsiteId = (string)$this->dataPersistor->get('customer')['account']['website_id']; } else { - $currentWebsiteId = $this->storeManager->getDefaultStoreView()->getWebsiteId(); + $defaultStore = $this->storeManager->getDefaultStoreView(); + if (!$defaultStore) { + $stores = $this->storeManager->getStores(); + $defaultStore = array_shift($stores); + } + $currentWebsiteId = $defaultStore->getWebsiteId(); } foreach ($options as $key => $option) { diff --git a/app/code/Magento/Customer/etc/config.xml b/app/code/Magento/Customer/etc/config.xml index ab2020580a2eb..22596e0b901b2 100644 --- a/app/code/Magento/Customer/etc/config.xml +++ b/app/code/Magento/Customer/etc/config.xml @@ -103,5 +103,12 @@ <section_data_lifetime>60</section_data_lifetime> </online_customers> </customer> + <system> + <media_storage_configuration> + <allowed_resources> + <customer_address_folder>customer_address</customer_address_folder> + </allowed_resources> + </media_storage_configuration> + </system> </default> </config> diff --git a/app/code/Magento/Dhl/Model/Carrier.php b/app/code/Magento/Dhl/Model/Carrier.php index c5eb27b21e58b..9781b521597ba 100644 --- a/app/code/Magento/Dhl/Model/Carrier.php +++ b/app/code/Magento/Dhl/Model/Carrier.php @@ -7,22 +7,23 @@ namespace Magento\Dhl\Model; use Magento\Catalog\Model\Product\Type; +use Magento\Dhl\Model\Validator\XmlValidator; use Magento\Framework\App\ObjectManager; use Magento\Framework\App\ProductMetadataInterface; use Magento\Framework\Async\CallbackDeferred; +use Magento\Framework\HTTP\AsyncClient\HttpException; use Magento\Framework\HTTP\AsyncClient\HttpResponseDeferredInterface; use Magento\Framework\HTTP\AsyncClient\Request; use Magento\Framework\HTTP\AsyncClientInterface; use Magento\Framework\Module\Dir; -use Magento\Sales\Exception\DocumentValidationException; -use Magento\Sales\Model\Order\Shipment; +use Magento\Framework\Xml\Security; use Magento\Quote\Model\Quote\Address\RateRequest; use Magento\Quote\Model\Quote\Address\RateResult\Error; +use Magento\Sales\Exception\DocumentValidationException; +use Magento\Sales\Model\Order\Shipment; use Magento\Shipping\Model\Carrier\AbstractCarrier; use Magento\Shipping\Model\Rate\Result; use Magento\Shipping\Model\Rate\Result\ProxyDeferredFactory; -use Magento\Framework\Xml\Security; -use Magento\Dhl\Model\Validator\XmlValidator; /** * DHL International (API v1.4) @@ -1064,8 +1065,15 @@ protected function _getQuotes() function () use ($deferredResponses, $responseBodies) { //Loading rates not found in cache foreach ($deferredResponses as $deferredResponseData) { + $responseResult = null; + try { + $responseResult = $deferredResponseData['deferred']->get(); + } catch (HttpException $exception) { + $this->_logger->critical($exception); + } + $responseBody = $responseResult ? $responseResult->getBody() : ''; $responseBodies[] = [ - 'body' => $deferredResponseData['deferred']->get()->getBody(), + 'body' => $responseBody, 'date' => $deferredResponseData['date'], 'request' => $deferredResponseData['request'], 'from_cache' => false @@ -2028,7 +2036,8 @@ protected function _checkDomesticStatus($origCountryCode, $destCountryCode) $isDomestic = (string)$this->getCountryParams($destCountryCode)->getData('domestic'); if (($origCountry == $destCountry && $isDomestic) - || ($this->_carrierHelper->isCountryInEU($origCountryCode) + || ( + $this->_carrierHelper->isCountryInEU($origCountryCode) && $this->_carrierHelper->isCountryInEU($destCountryCode) ) ) { diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAccountDownloadableProductLinkAfterPartialRefundTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAccountDownloadableProductLinkAfterPartialRefundTest.xml index d82cc25b0eccf..b89aa7d126686 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAccountDownloadableProductLinkAfterPartialRefundTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAccountDownloadableProductLinkAfterPartialRefundTest.xml @@ -67,8 +67,8 @@ </actionGroup> <actionGroup ref="ClickViewAndEditCartFromMiniCartActionGroup" stepKey="goToShoppingCartFromMinicart"/> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> - <waitForPageLoad stepKey="waitForProceedToCheckout"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="clickProceedToCheckout"/> + <comment userInput="Adding the comment to replace waitForProceedToCheckout action for preserving Backward Compatibility" stepKey="waitForProceedToCheckout"/> <waitForElementVisible selector="{{CheckoutShippingSection.shipHereButton(UK_Not_Default_Address.street[0])}}" stepKey="waitForShipHereVisible"/> <click selector="{{CheckoutShippingSection.shipHereButton(UK_Not_Default_Address.street[0])}}" stepKey="clickShipHere"/> <click selector="{{CheckoutShippingGuestInfoSection.next}}" stepKey="clickNext"/> diff --git a/app/code/Magento/Downloadable/Test/Unit/Model/Link/UpdateHandlerTest.php b/app/code/Magento/Downloadable/Test/Unit/Model/Link/UpdateHandlerTest.php index 22cf4b9abf7ca..16b698adf57f0 100644 --- a/app/code/Magento/Downloadable/Test/Unit/Model/Link/UpdateHandlerTest.php +++ b/app/code/Magento/Downloadable/Test/Unit/Model/Link/UpdateHandlerTest.php @@ -15,6 +15,7 @@ use Magento\Downloadable\Model\Link\UpdateHandler; use Magento\Downloadable\Model\Product\Type; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\MockObject\RuntimeException; use PHPUnit\Framework\TestCase; /** @@ -56,7 +57,7 @@ protected function setUp(): void ->getMockForAbstractClass(); $this->linkMock = $this->getMockBuilder(LinkInterface::class) ->getMock(); - $this->productExtensionMock = $this->createMock(ProductExtensionInterface::class); + $this->productExtensionMock = $this->getProductExtensionMock(); $this->productExtensionMock->expects($this->once()) ->method('getDownloadableProductLinks') ->willReturn([$this->linkMock]); @@ -145,4 +146,22 @@ public function testExecuteNonDownloadable(): void $this->assertEquals($this->entityMock, $this->model->execute($this->entityMock)); } + + /** + * Build product extension mock. + * + * @return MockObject + */ + private function getProductExtensionMock(): MockObject + { + $mockBuilder = $this->getMockBuilder(ProductExtensionInterface::class) + ->disableOriginalConstructor(); + try { + $mockBuilder->addMethods(['getDownloadableProductLinks']); + } catch (RuntimeException $e) { + // ProductExtension already generated. + } + + return $mockBuilder->getMockForAbstractClass(); + } } diff --git a/app/code/Magento/Downloadable/Test/Unit/Model/Quote/Item/CartItemProcessorTest.php b/app/code/Magento/Downloadable/Test/Unit/Model/Quote/Item/CartItemProcessorTest.php index c177b117f19d0..8eaed8eebc05a 100644 --- a/app/code/Magento/Downloadable/Test/Unit/Model/Quote/Item/CartItemProcessorTest.php +++ b/app/code/Magento/Downloadable/Test/Unit/Model/Quote/Item/CartItemProcessorTest.php @@ -21,6 +21,7 @@ use Magento\Quote\Model\Quote\Item; use Magento\Quote\Model\Quote\ProductOptionFactory; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\MockObject\RuntimeException; use PHPUnit\Framework\TestCase; /** @@ -170,14 +171,12 @@ public function testProcessProductOptions() $this->optionFactoryMock->expects($this->once())->method('create')->willReturn($productOptionMock); $productOptionMock->expects($this->once())->method('getExtensionAttributes')->willReturn(null); - $extAttributeMock = $this->getMockBuilder(ProductOptionExtension::class) - ->addMethods(['setDownloadableOption']) - ->getMock(); + $extAttributeMock = $this->getProductOptionExtensionMock(); $this->objectHelperMock->expects($this->once())->method('populateWithArray')->with( $downloadableOptionMock, [ - 'downloadable_links' => $downloadableLinks + 'downloadable_links' => $downloadableLinks, ], DownloadableOptionInterface::class ); @@ -206,9 +205,7 @@ public function testProcessProductOptionsWhenItemDoesNotHaveDownloadableLinks() ->method('getOptionByCode') ->with('downloadable_link_ids'); - $extAttributeMock = $this->getMockBuilder(ProductOptionExtension::class) - ->addMethods(['setDownloadableOption']) - ->getMock(); + $extAttributeMock = $this->getProductOptionExtensionMock(); $productOptionMock = $this->getMockForAbstractClass(ProductOptionInterface::class); $productOptionMock->expects($this->any()) ->method('getExtensionAttributes') @@ -228,7 +225,7 @@ public function testProcessProductOptionsWhenItemDoesNotHaveDownloadableLinks() $this->objectHelperMock->expects($this->once())->method('populateWithArray')->with( $downloadableOptionMock, [ - 'downloadable_links' => $downloadableLinks + 'downloadable_links' => $downloadableLinks, ], DownloadableOptionInterface::class ); @@ -243,4 +240,21 @@ public function testProcessProductOptionsWhenItemDoesNotHaveDownloadableLinks() $this->assertEquals($cartItemMock, $this->model->processOptions($cartItemMock)); } + + /** + * Build product option extension mock. + * + * @return MockObject + */ + private function getProductOptionExtensionMock(): MockObject + { + $mockBuilder = $this->getMockBuilder(ProductOptionExtension::class); + try { + $mockBuilder->addMethods(['setDownloadableOption']); + } catch (RuntimeException $e) { + // ProductOptionExtension already generated. + } + + return $mockBuilder->getMock(); + } } diff --git a/app/code/Magento/Downloadable/Test/Unit/Model/Sample/UpdateHandlerTest.php b/app/code/Magento/Downloadable/Test/Unit/Model/Sample/UpdateHandlerTest.php index 0f8fe92e467ce..cc0427808ebc0 100644 --- a/app/code/Magento/Downloadable/Test/Unit/Model/Sample/UpdateHandlerTest.php +++ b/app/code/Magento/Downloadable/Test/Unit/Model/Sample/UpdateHandlerTest.php @@ -15,6 +15,7 @@ use Magento\Downloadable\Model\Product\Type; use Magento\Downloadable\Model\Sample\UpdateHandler; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\MockObject\RuntimeException; use PHPUnit\Framework\TestCase; /** @@ -56,7 +57,7 @@ protected function setUp(): void ->getMockForAbstractClass(); $this->sampleMock = $this->getMockBuilder(SampleInterface::class) ->getMock(); - $this->productExtensionMock = $this->createMock(ProductExtensionInterface::class); + $this->productExtensionMock = $this->getProductExtensionMock(); $this->productExtensionMock//->expects($this->once()) ->method('getDownloadableProductSamples') ->willReturn([$this->sampleMock]); @@ -145,4 +146,22 @@ public function testExecuteNonDownloadable(): void $this->assertEquals($this->entityMock, $this->model->execute($this->entityMock)); } + + /** + * Build product extension mock. + * + * @return MockObject + */ + private function getProductExtensionMock(): MockObject + { + $mockBuilder = $this->getMockBuilder(ProductExtensionInterface::class) + ->disableOriginalConstructor(); + try { + $mockBuilder->addMethods(['getDownloadableProductSamples']); + } catch (RuntimeException $e) { + // Product extension already generated. + } + + return $mockBuilder->getMockForAbstractClass(); + } } diff --git a/app/code/Magento/Downloadable/Test/Unit/Observer/IsAllowedGuestCheckoutObserverTest.php b/app/code/Magento/Downloadable/Test/Unit/Observer/IsAllowedGuestCheckoutObserverTest.php index 6040b301a60a8..ea4a2769a117b 100644 --- a/app/code/Magento/Downloadable/Test/Unit/Observer/IsAllowedGuestCheckoutObserverTest.php +++ b/app/code/Magento/Downloadable/Test/Unit/Observer/IsAllowedGuestCheckoutObserverTest.php @@ -91,13 +91,13 @@ protected function setUp(): void ->getMock(); $this->storeMock = $this->getMockBuilder(DataObject::class) + ->addMethods(['getId']) ->disableOriginalConstructor() ->getMock(); $this->storeManagerMock = $this->getMockForAbstractClass(StoreManagerInterface::class); - $this->storeManagerMock - ->method('getStore') - ->with(self::STUB_STORE_ID) + $this->storeManagerMock->method('getStore') + ->with($this->storeMock) ->willReturn($this->storeMock); $this->isAllowedGuestCheckoutObserver = (new ObjectManagerHelper($this)) diff --git a/app/code/Magento/Eav/Model/Attribute/Data/Image.php b/app/code/Magento/Eav/Model/Attribute/Data/Image.php index 24cd0f4fcf61f..d61a8b5fda5b1 100644 --- a/app/code/Magento/Eav/Model/Attribute/Data/Image.php +++ b/app/code/Magento/Eav/Model/Attribute/Data/Image.php @@ -5,6 +5,8 @@ */ namespace Magento\Eav\Model\Attribute\Data; +use Magento\Framework\Filesystem\ExtendedDriverInterface; + /** * EAV Entity Attribute Image File Data Model * @@ -21,24 +23,30 @@ class Image extends \Magento\Eav\Model\Attribute\Data\File * @return array * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) + * @throws \Magento\Framework\Exception\LocalizedException */ protected function _validateByRules($value) { $label = __($this->getAttribute()->getStoreLabel()); $rules = $this->getAttribute()->getValidateRules(); + $localStorage = !$this->_directory->getDriver() instanceof ExtendedDriverInterface; + $imageProp = $localStorage + ? @getimagesize($value['tmp_name']) + : $this->_directory->getDriver()->getMetadata($value['tmp_name']); + $allowImageTypes = ['gif', 'jpg', 'jpeg', 'png']; + if (!isset($imageProp['extension']) && isset($imageProp[2])) { + $extensionsMap = [1 => 'gif', 2 => 'jpg', 3 => 'png']; + $imageProp['extension'] = $extensionsMap[$imageProp[2]] ?? null; + } - $imageProp = @getimagesize($value['tmp_name']); - - $allowImageTypes = [1 => 'gif', 2 => 'jpg', 3 => 'png']; - - if (!isset($allowImageTypes[$imageProp[2]])) { + if (!\in_array($imageProp['extension'], $allowImageTypes, true)) { return [__('"%1" is not a valid image format', $label)]; } // modify image name $extension = pathinfo($value['name'], PATHINFO_EXTENSION); - if ($extension != $allowImageTypes[$imageProp[2]]) { - $value['name'] = pathinfo($value['name'], PATHINFO_FILENAME) . '.' . $allowImageTypes[$imageProp[2]]; + if ($extension !== $imageProp['extension']) { + $value['name'] = pathinfo($value['name'], PATHINFO_FILENAME) . '.' . $imageProp['extension']; } $errors = []; @@ -49,17 +57,17 @@ protected function _validateByRules($value) } } - if (!empty($rules['max_image_width'])) { - if ($rules['max_image_width'] < $imageProp[0]) { - $r = $rules['max_image_width']; - $errors[] = __('"%1" width exceeds allowed value of %2 px.', $label, $r); - } + $imageWidth = $imageProp['extra']['image-width'] ?? $imageProp[0]; + if (!empty($rules['max_image_width']) && !empty($imageWidth) + && ($rules['max_image_width'] < $imageWidth)) { + $r = $rules['max_image_width']; + $errors[] = __('"%1" width exceeds allowed value of %2 px.', $label, $r); } - if (!empty($rules['max_image_height'])) { - if ($rules['max_image_height'] < $imageProp[1]) { - $r = $rules['max_image_height']; - $errors[] = __('"%1" height exceeds allowed value of %2 px.', $label, $r); - } + $imageHeight = $imageProp['extra']['image-height'] ?? $imageProp[1]; + if (!empty($rules['max_image_height']) && !empty($imageHeight) + && ($rules['max_image_height'] < $imageHeight)) { + $r = $rules['max_image_height']; + $errors[] = __('"%1" height exceeds allowed value of %2 px.', $label, $r); } return $errors; diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/Elasticsearch.php b/app/code/Magento/Elasticsearch/Model/Adapter/Elasticsearch.php index 261f8d84b5baa..1b4b7ec322f3c 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/Elasticsearch.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/Elasticsearch.php @@ -6,11 +6,18 @@ namespace Magento\Elasticsearch\Model\Adapter; -use Magento\Catalog\Api\Data\ProductAttributeInterface; +use Elasticsearch\Common\Exceptions\Missing404Exception; +use Magento\AdvancedSearch\Model\Client\ClientInterface; use Magento\Catalog\Api\ProductAttributeRepositoryInterface; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\StaticField; +use Magento\Elasticsearch\Model\Adapter\Index\BuilderInterface; +use Magento\Elasticsearch\Model\Adapter\Index\IndexNameResolver; +use Magento\Elasticsearch\Model\Config; +use Magento\Elasticsearch\SearchAdapter\ConnectionManager; use Magento\Framework\App\ObjectManager; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Stdlib\ArrayManager; +use Psr\Log\LoggerInterface; /** * Elasticsearch adapter @@ -36,7 +43,7 @@ class Elasticsearch protected $connectionManager; /** - * @var \Magento\Elasticsearch\Model\Adapter\Index\IndexNameResolver + * @var IndexNameResolver */ protected $indexNameResolver; @@ -46,22 +53,22 @@ class Elasticsearch protected $fieldMapper; /** - * @var \Magento\Elasticsearch\Model\Config + * @var Config */ protected $clientConfig; /** - * @var \Magento\AdvancedSearch\Model\Client\ClientInterface + * @var ClientInterface */ protected $client; /** - * @var \Magento\Elasticsearch\Model\Adapter\Index\BuilderInterface + * @var BuilderInterface */ protected $indexBuilder; /** - * @var \Psr\Log\LoggerInterface + * @var LoggerInterface */ protected $logger; @@ -101,27 +108,27 @@ class Elasticsearch private $arrayManager; /** - * @param \Magento\Elasticsearch\SearchAdapter\ConnectionManager $connectionManager + * @param ConnectionManager $connectionManager * @param FieldMapperInterface $fieldMapper - * @param \Magento\Elasticsearch\Model\Config $clientConfig + * @param Config $clientConfig * @param Index\BuilderInterface $indexBuilder - * @param \Psr\Log\LoggerInterface $logger + * @param LoggerInterface $logger * @param Index\IndexNameResolver $indexNameResolver * @param BatchDataMapperInterface $batchDocumentDataMapper * @param array $options * @param ProductAttributeRepositoryInterface|null $productAttributeRepository * @param StaticField|null $staticFieldProvider * @param ArrayManager|null $arrayManager - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( - \Magento\Elasticsearch\SearchAdapter\ConnectionManager $connectionManager, + ConnectionManager $connectionManager, FieldMapperInterface $fieldMapper, - \Magento\Elasticsearch\Model\Config $clientConfig, - \Magento\Elasticsearch\Model\Adapter\Index\BuilderInterface $indexBuilder, - \Psr\Log\LoggerInterface $logger, - \Magento\Elasticsearch\Model\Adapter\Index\IndexNameResolver $indexNameResolver, + Config $clientConfig, + BuilderInterface $indexBuilder, + LoggerInterface $logger, + IndexNameResolver $indexNameResolver, BatchDataMapperInterface $batchDocumentDataMapper, $options = [], ProductAttributeRepositoryInterface $productAttributeRepository = null, @@ -146,7 +153,7 @@ public function __construct( $this->client = $this->connectionManager->getConnection($options); } catch (\Exception $e) { $this->logger->critical($e); - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( __('The search failed because of a search engine misconfiguration.') ); } @@ -156,14 +163,14 @@ public function __construct( * Retrieve Elasticsearch server status * * @return bool - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ public function ping() { try { $response = $this->client->ping(); } catch (\Exception $e) { - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( __('Could not ping search engine: %1', $e->getMessage()) ); } @@ -387,22 +394,12 @@ public function updateIndexMapping(int $storeId, string $mappedIndexerId, string return $this; } - $attribute = $this->productAttributeRepository->get($attributeCode); - $newAttributeMapping = $this->staticFieldProvider->getField($attribute); - $mappedAttributes = $this->getMappedAttributes($indexName); - - $attrToUpdate = array_diff_key($newAttributeMapping, $mappedAttributes); - if (!empty($attrToUpdate)) { - $settings['index']['mapping']['total_fields']['limit'] = $this - ->getMappingTotalFieldsLimit(array_merge($mappedAttributes, $attrToUpdate)); - $this->client->putIndexSettings($indexName, ['settings' => $settings]); - - $this->client->addFieldsMapping( - $attrToUpdate, - $indexName, - $this->clientConfig->getEntityType() - ); - $this->setMappedAttributes($indexName, $attrToUpdate); + try { + $this->updateMapping($attributeCode, $indexName); + } catch (Missing404Exception $e) { + unset($this->indexByCode[$mappedIndexerId . '_' . $storeId]); + $indexName = $this->getIndexFromAlias($storeId, $mappedIndexerId); + $this->updateMapping($attributeCode, $indexName); } return $this; @@ -505,4 +502,31 @@ private function getMappingTotalFieldsLimit(array $allAttributeTypes): int } return $count + self::MAPPING_TOTAL_FIELDS_BUFFER_LIMIT; } + + /** + * Perform index mapping update + * + * @param string $attributeCode + * @param string $indexName + * @return void + */ + private function updateMapping(string $attributeCode, string $indexName): void + { + $attribute = $this->productAttributeRepository->get($attributeCode); + $newAttributeMapping = $this->staticFieldProvider->getField($attribute); + $mappedAttributes = $this->getMappedAttributes($indexName); + $attrToUpdate = array_diff_key($newAttributeMapping, $mappedAttributes); + if (!empty($attrToUpdate)) { + $settings['index']['mapping']['total_fields']['limit'] = $this + ->getMappingTotalFieldsLimit(array_merge($mappedAttributes, $attrToUpdate)); + $this->client->putIndexSettings($indexName, ['settings' => $settings]); + + $this->client->addFieldsMapping( + $attrToUpdate, + $indexName, + $this->clientConfig->getEntityType() + ); + $this->setMappedAttributes($indexName, $attrToUpdate); + } + } } diff --git a/app/code/Magento/GiftMessage/Test/Unit/Model/Plugin/OrderGetTest.php b/app/code/Magento/GiftMessage/Test/Unit/Model/Plugin/OrderGetTest.php index 15c0e1b408ded..85bae4a42a8df 100644 --- a/app/code/Magento/GiftMessage/Test/Unit/Model/Plugin/OrderGetTest.php +++ b/app/code/Magento/GiftMessage/Test/Unit/Model/Plugin/OrderGetTest.php @@ -20,6 +20,7 @@ use Magento\Sales\Api\Data\OrderItemInterface; use Magento\Sales\Model\ResourceModel\Order\Collection; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\MockObject\RuntimeException; use PHPUnit\Framework\TestCase; /** @@ -106,18 +107,14 @@ protected function setUp(): void $this->orderMock = $this->createMock( OrderInterface::class ); - $this->orderExtensionMock = $this->getMockBuilder(OrderExtension::class) - ->addMethods(['getGiftMessage', 'setGiftMessage']) - ->getMock(); + $this->orderExtensionMock = $this->getOrderExtensionMock(); $this->giftMessageMock = $this->createMock( MessageInterface::class ); $this->orderItemMock = $this->createMock( OrderItemInterface::class ); - $this->orderItemExtensionMock = $this->getMockBuilder(OrderItemExtension::class) - ->addMethods(['setGiftMessage', 'getGiftMessage']) - ->getMock(); + $this->orderItemExtensionMock = $this->getOrderItemExtensionMock(); $this->orderRepositoryMock = $this->createMock( \Magento\Sales\Api\OrderRepositoryInterface::class ); @@ -272,4 +269,38 @@ public function testAfterGetList() $this->collectionMock->expects($this->once())->method('getItems')->willReturn([$this->orderMock]); $this->plugin->afterGetList($this->orderRepositoryMock, $this->collectionMock); } + + /** + * Build order extension mock. + * + * @return MockObject + */ + private function getOrderExtensionMock(): MockObject + { + $mockObject = $this->getMockBuilder(OrderExtension::class); + try { + $mockObject->addMethods(['getGiftMessage', 'setGiftMessage']); + } catch (RuntimeException $e) { + // Order extension already generated. + } + + return $mockObject->getMock(); + } + + /** + * Build order item extension mock. + * + * @return MockObject + */ + private function getOrderItemExtensionMock(): MockObject + { + $mockObject = $this->getMockBuilder(OrderItemExtension::class); + try { + $mockObject->addMethods(['getGiftMessage', 'setGiftMessage']); + } catch (RuntimeException $e) { + // Order extension already generated. + } + + return $mockObject->getMock(); + } } diff --git a/app/code/Magento/GiftMessage/Test/Unit/Model/Plugin/OrderSaveTest.php b/app/code/Magento/GiftMessage/Test/Unit/Model/Plugin/OrderSaveTest.php index f6eaa02d9eaab..51702f4349003 100644 --- a/app/code/Magento/GiftMessage/Test/Unit/Model/Plugin/OrderSaveTest.php +++ b/app/code/Magento/GiftMessage/Test/Unit/Model/Plugin/OrderSaveTest.php @@ -16,6 +16,7 @@ use Magento\Sales\Api\Data\OrderItemExtension; use Magento\Sales\Api\Data\OrderItemInterface; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\MockObject\RuntimeException; use PHPUnit\Framework\TestCase; class OrderSaveTest extends TestCase @@ -76,18 +77,14 @@ protected function setUp(): void $this->orderMock = $this->createMock( OrderInterface::class ); - $this->orderExtensionMock = $this->getMockBuilder(OrderExtension::class) - ->addMethods(['getGiftMessage', 'setGiftMessage']) - ->getMock(); + $this->orderExtensionMock = $this->getOrderExtensionMock(); $this->giftMessageMock = $this->createMock( MessageInterface::class ); $this->orderItemMock = $this->createMock( OrderItemInterface::class ); - $this->orderItemExtensionMock = $this->getMockBuilder(OrderItemExtension::class) - ->addMethods(['setGiftMessage', 'getGiftMessage']) - ->getMock(); + $this->orderItemExtensionMock = $this->getOrderItemExtensionMock(); $this->orderRepositoryMock = $this->createMock( \Magento\Sales\Api\OrderRepositoryInterface::class ); @@ -192,4 +189,38 @@ public function testAfterSaveIfItemGiftMessagesNotExist() ->willThrowException(new \Exception('Test message')); $this->plugin->afterSave($this->orderRepositoryMock, $this->orderMock); } + + /** + * Build order extension mock. + * + * @return MockObject + */ + private function getOrderExtensionMock(): MockObject + { + $mockBuilder = $this->getMockBuilder(OrderExtension::class); + try { + $mockBuilder->addMethods(['getGiftMessage', 'setGiftMessage']); + } catch (RuntimeException $e) { + // OrderExtension already generated. + } + + return $mockBuilder->getMock(); + } + + /** + * Build order item extension mock. + * + * @return MockObject + */ + private function getOrderItemExtensionMock(): MockObject + { + $mockBuilder = $this->getMockBuilder(OrderItemExtension::class); + try { + $mockBuilder->addMethods(['getGiftMessage', 'setGiftMessage']); + } catch (RuntimeException $e) { + // OrderItemExtension already generated. + } + + return $mockBuilder->getMock(); + } } diff --git a/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Form.php b/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Form.php index 87cd4cf346288..07cf6f8c733d4 100644 --- a/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Form.php +++ b/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Form.php @@ -248,6 +248,9 @@ protected function _prepareForm() .', e.g. <i>product_images</i>, <i>import_images/batch1</i>.<br><br>' .'For example, in case <i>product_images</i>, files should be placed into ' .'<i><Magento root directory>/' + .$this->imagesDirectoryProvider->getDirectoryRelativePath() . '/product_images</i> folder.<br>' + .'<br>If remote storage is enabled, in case <i>product_images</i>, files should be placed into ' + .'<i><Remote Storage>/' .$this->imagesDirectoryProvider->getDirectoryRelativePath() . '/product_images</i> folder.', ['i', 'br'] ) diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Delete.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Delete.php index e31f36e627a66..8c47412adc835 100644 --- a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Delete.php +++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Delete.php @@ -70,9 +70,9 @@ public function execute() return $resultRedirect; } - $directoryWrite = $this->filesystem->getDirectoryWrite(DirectoryList::VAR_EXPORT); + $directoryWrite = $this->filesystem->getDirectoryWrite(DirectoryList::VAR_IMPORT_EXPORT); try { - $directoryWrite->delete($directoryWrite->getAbsolutePath($fileName)); + $directoryWrite->delete($directoryWrite->getAbsolutePath() . 'export/' . $fileName); $this->messageManager->addSuccessMessage(__('File %1 deleted', $fileName)); } catch (ValidatorException $exception) { $this->messageManager->addErrorMessage( diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Download.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Download.php index 26ee257c42ff2..8e432e395bdf4 100644 --- a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Download.php +++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Download.php @@ -67,12 +67,13 @@ public function execute() return $resultRedirect; } try { - $directory = $this->filesystem->getDirectoryRead(DirectoryList::VAR_EXPORT); - if ($directory->isFile($fileName)) { + $path = 'export/' . $fileName; + $directory = $this->filesystem->getDirectoryRead(DirectoryList::VAR_IMPORT_EXPORT); + if ($directory->isFile($path)) { return $this->fileFactory->create( - $fileName, - $directory->readFile($fileName), - DirectoryList::VAR_EXPORT + $path, + $directory->readFile($path), + DirectoryList::VAR_IMPORT_EXPORT ); } $this->messageManager->addErrorMessage(__('%1 is not a valid file', $fileName)); diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/History/Download.php b/app/code/Magento/ImportExport/Controller/Adminhtml/History/Download.php index ba37cc8aacff9..9dcb2fdafb74f 100644 --- a/app/code/Magento/ImportExport/Controller/Adminhtml/History/Download.php +++ b/app/code/Magento/ImportExport/Controller/Adminhtml/History/Download.php @@ -62,7 +62,7 @@ public function execute() $this->fileFactory->create( $fileName, null, - DirectoryList::VAR_DIR, + DirectoryList::VAR_IMPORT_EXPORT, 'application/octet-stream', $reportHelper->getReportSize($fileName) ); diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Download.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Download.php index 9918ef8908956..5ff0dd9b63da9 100644 --- a/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Download.php +++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Download.php @@ -109,7 +109,7 @@ public function execute() $this->fileFactory->create( $fileName, null, - DirectoryList::VAR_DIR, + DirectoryList::VAR_IMPORT_EXPORT, 'application/octet-stream', $fileSize ); diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/ImportResult.php b/app/code/Magento/ImportExport/Controller/Adminhtml/ImportResult.php index 6da90efa4592c..4092879e23622 100644 --- a/app/code/Magento/ImportExport/Controller/Adminhtml/ImportResult.php +++ b/app/code/Magento/ImportExport/Controller/Adminhtml/ImportResult.php @@ -9,6 +9,8 @@ use Magento\ImportExport\Model\Import\Entity\AbstractEntity; use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface; use Magento\ImportExport\Model\History as ModelHistory; +use Magento\Framework\Escaper; +use Magento\Framework\App\ObjectManager; /** * Import controller @@ -37,22 +39,31 @@ abstract class ImportResult extends Import */ protected $reportHelper; + /** + * @var Escaper|null + */ + protected $escaper; + /** * @param \Magento\Backend\App\Action\Context $context * @param \Magento\ImportExport\Model\Report\ReportProcessorInterface $reportProcessor * @param \Magento\ImportExport\Model\History $historyModel * @param \Magento\ImportExport\Helper\Report $reportHelper + * @param Escaper|null $escaper */ public function __construct( \Magento\Backend\App\Action\Context $context, \Magento\ImportExport\Model\Report\ReportProcessorInterface $reportProcessor, \Magento\ImportExport\Model\History $historyModel, - \Magento\ImportExport\Helper\Report $reportHelper + \Magento\ImportExport\Helper\Report $reportHelper, + Escaper $escaper = null ) { parent::__construct($context); $this->reportProcessor = $reportProcessor; $this->historyModel = $historyModel; $this->reportHelper = $reportHelper; + $this->escaper = $escaper + ?? ObjectManager::getInstance()->get(Escaper::class); } /** @@ -69,28 +80,30 @@ protected function addErrorMessages( if ($errorAggregator->getErrorsCount()) { $message = ''; $counter = 0; + $escapedMessages = []; foreach ($this->getErrorMessages($errorAggregator) as $error) { - $message .= (++$counter) . '. ' . $error . '<br>'; + $escapedMessages[] = (++$counter) . '. ' . $this->escaper->escapeHtml($error); if ($counter >= self::LIMIT_ERRORS_MESSAGE) { break; } } if ($errorAggregator->hasFatalExceptions()) { foreach ($this->getSystemExceptions($errorAggregator) as $error) { - $message .= $error->getErrorMessage() + $escapedMessages[] = $this->escaper->escapeHtml($error->getErrorMessage()) . ' <a href="#" onclick="$(this).next().show();$(this).hide();return false;">' . __('Show more') . '</a><div style="display:none;">' . __('Additional data') . ': ' - . $error->getErrorDescription() . '</div>'; + . $this->escaper->escapeHtml($error->getErrorDescription()) . '</div>'; } } try { + $message .= implode('<br>', $escapedMessages); $resultBlock->addNotice( '<strong>' . __('Following Error(s) has been occurred during importing process:') . '</strong><br>' . '<div class="import-error-wrapper">' . __('Only the first 100 errors are shown. ') . '<a href="' . $this->createDownloadUrlImportHistoryFile($this->createErrorReport($errorAggregator)) . '">' . __('Download full report') . '</a><br>' - . '<div class="import-error-list">' . $resultBlock->escapeHtml($message) . '</div></div>' + . '<div class="import-error-list">' . $message . '</div></div>' ); } catch (\Exception $e) { foreach ($this->getErrorMessages($errorAggregator) as $errorMessage) { diff --git a/app/code/Magento/ImportExport/Helper/Report.php b/app/code/Magento/ImportExport/Helper/Report.php index 29d1928d837d6..d5be27a569942 100644 --- a/app/code/Magento/ImportExport/Helper/Report.php +++ b/app/code/Magento/ImportExport/Helper/Report.php @@ -40,7 +40,7 @@ public function __construct( \Magento\Framework\Filesystem $filesystem ) { $this->timeZone = $timeZone; - $this->varDirectory = $filesystem->getDirectoryWrite(DirectoryList::VAR_DIR); + $this->varDirectory = $filesystem->getDirectoryWrite(DirectoryList::VAR_IMPORT_EXPORT); parent::__construct($context); } diff --git a/app/code/Magento/ImportExport/Model/AbstractModel.php b/app/code/Magento/ImportExport/Model/AbstractModel.php index 87fde3fbb68a3..4010f3bfcae4c 100644 --- a/app/code/Magento/ImportExport/Model/AbstractModel.php +++ b/app/code/Magento/ImportExport/Model/AbstractModel.php @@ -56,7 +56,7 @@ public function __construct( array $data = [] ) { $this->_logger = $logger; - $this->_varDirectory = $filesystem->getDirectoryWrite(DirectoryList::VAR_DIR); + $this->_varDirectory = $filesystem->getDirectoryWrite(DirectoryList::VAR_IMPORT_EXPORT); parent::__construct($data); } diff --git a/app/code/Magento/ImportExport/Model/Export/Adapter/AbstractAdapter.php b/app/code/Magento/ImportExport/Model/Export/Adapter/AbstractAdapter.php index 8fbc91de5b211..1e292e849c9f8 100644 --- a/app/code/Magento/ImportExport/Model/Export/Adapter/AbstractAdapter.php +++ b/app/code/Magento/ImportExport/Model/Export/Adapter/AbstractAdapter.php @@ -46,7 +46,7 @@ abstract class AbstractAdapter public function __construct( \Magento\Framework\Filesystem $filesystem, $destination = null, - $destinationDirectoryCode = DirectoryList::VAR_DIR + $destinationDirectoryCode = DirectoryList::VAR_IMPORT_EXPORT ) { $this->_directoryHandle = $filesystem->getDirectoryWrite($destinationDirectoryCode); if (!$destination) { diff --git a/app/code/Magento/ImportExport/Model/Export/Consumer.php b/app/code/Magento/ImportExport/Model/Export/Consumer.php index 955f96fe3de2e..55ffbb9ab213d 100644 --- a/app/code/Magento/ImportExport/Model/Export/Consumer.php +++ b/app/code/Magento/ImportExport/Model/Export/Consumer.php @@ -70,8 +70,8 @@ public function process(ExportInfoInterface $exportInfo) try { $data = $this->exportManager->export($exportInfo); $fileName = $exportInfo->getFileName(); - $directory = $this->filesystem->getDirectoryWrite(DirectoryList::VAR_EXPORT); - $directory->writeFile($fileName, $data); + $directory = $this->filesystem->getDirectoryWrite(DirectoryList::VAR_IMPORT_EXPORT); + $directory->writeFile('export/' . $fileName, $data); $this->notifier->addMajor( __('Your export file is ready'), diff --git a/app/code/Magento/ImportExport/Model/Report/Csv.php b/app/code/Magento/ImportExport/Model/Report/Csv.php index e7ddef1008444..26f36c53cb7f8 100644 --- a/app/code/Magento/ImportExport/Model/Report/Csv.php +++ b/app/code/Magento/ImportExport/Model/Report/Csv.php @@ -83,7 +83,7 @@ public function createReport( } } - $directory = $this->filesystem->getDirectoryWrite(DirectoryList::VAR_DIR); + $directory = $this->filesystem->getDirectoryWrite(DirectoryList::VAR_IMPORT_EXPORT); $outputFileName = $this->generateOutputFileName($originalFileName); $directory->writeFile(Import::IMPORT_HISTORY_DIR . $outputFileName, $outputCsv->getContents()); @@ -136,7 +136,7 @@ protected function createSourceCsvModel($sourceFile) return $this->sourceCsvFactory->create( [ 'file' => $sourceFile, - 'directory' => $this->filesystem->getDirectoryWrite(DirectoryList::VAR_DIR), + 'directory' => $this->filesystem->getDirectoryWrite(DirectoryList::VAR_IMPORT_EXPORT), 'delimiter' => $this->reportHelper->getDelimiter(), ] ); diff --git a/app/code/Magento/ImportExport/Ui/DataProvider/ExportFileDataProvider.php b/app/code/Magento/ImportExport/Ui/DataProvider/ExportFileDataProvider.php index 71614bafd138e..e9df0a8127315 100644 --- a/app/code/Magento/ImportExport/Ui/DataProvider/ExportFileDataProvider.php +++ b/app/code/Magento/ImportExport/Ui/DataProvider/ExportFileDataProvider.php @@ -85,7 +85,7 @@ public function __construct( ); $this->fileIO = $fileIO ?: ObjectManager::getInstance()->get(File::class); - $this->directory = $filesystem->getDirectoryWrite(DirectoryList::VAR_EXPORT); + $this->directory = $filesystem->getDirectoryWrite(DirectoryList::VAR_IMPORT_EXPORT); } /** @@ -97,11 +97,11 @@ public function __construct( public function getData() { $emptyResponse = ['items' => [], 'totalRecords' => 0]; - if (!$this->directory->isExist($this->directory->getAbsolutePath())) { + if (!$this->directory->isExist($this->directory->getAbsolutePath() . 'export/')) { return $emptyResponse; } - $files = $this->getExportFiles($this->directory->getAbsolutePath()); + $files = $this->getExportFiles($this->directory->getAbsolutePath() . 'export/'); if (empty($files)) { return $emptyResponse; } @@ -128,14 +128,11 @@ public function getData() */ private function getPathToExportFile($file): string { - $directory = $this->fileSystem->getDirectoryRead(DirectoryList::VAR_EXPORT); $delimiter = '/'; $cutPath = explode( $delimiter, - $directory->getAbsolutePath() + $this->directory->getAbsolutePath() . 'export' ); - // remove . from dirname if file path is not absolute in the file system but just a file name - $file['dirname'] = $file['dirname'] !== '.' ? $file['dirname'] : ''; $filePath = explode( $delimiter, @@ -163,6 +160,7 @@ private function getExportFiles(string $directoryPath): array return []; } foreach ($files as $filePath) { + $filePath = $this->directory->getAbsolutePath($filePath); if ($this->directory->isFile($filePath)) { $fileModificationTime = $this->directory->stat($filePath)['mtime']; $sortedFiles[$fileModificationTime] = $filePath; diff --git a/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/StorefrontLoginAsCustomerBannerPresentOnAllPagesInSessionTest.xml b/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/StorefrontLoginAsCustomerBannerPresentOnAllPagesInSessionTest.xml index 4f85b9167fa54..be2749e64f65c 100644 --- a/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/StorefrontLoginAsCustomerBannerPresentOnAllPagesInSessionTest.xml +++ b/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/StorefrontLoginAsCustomerBannerPresentOnAllPagesInSessionTest.xml @@ -84,8 +84,8 @@ </actionGroup> <!-- Proceed to checkout and assert notification banner --> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> - <waitForPageLoad stepKey="waitForProceedToCheckout"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="clickProceedToCheckout"/> + <comment userInput="Adding the comment to replace waitForProceedToCheckout action for preserving Backward Compatibility" stepKey="waitForProceedToCheckout"/> <actionGroup ref="StorefrontAssertLoginAsCustomerNotificationBannerActionGroup" stepKey="assertNotificationBannerOnCheckoutPage"> <argument name="customerFullName" value="$$createCustomer.firstname$$ $$createCustomer.lastname$$"/> </actionGroup> diff --git a/app/code/Magento/MediaGalleryIntegration/Plugin/SaveImageInformation.php b/app/code/Magento/MediaGalleryIntegration/Plugin/SaveImageInformation.php index a999b9004d9e5..347e73663589e 100644 --- a/app/code/Magento/MediaGalleryIntegration/Plugin/SaveImageInformation.php +++ b/app/code/Magento/MediaGalleryIntegration/Plugin/SaveImageInformation.php @@ -85,12 +85,15 @@ public function __construct( */ public function afterSave(Uploader $subject, array $result): array { - if (!$this->config->isEnabled()) { + $mediaFolder = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA)->getAbsolutePath(); + + if (!$this->config->isEnabled() || substr($result['path'], 0, strlen($mediaFolder)) !== $mediaFolder) { return $result; } $path = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA) ->getRelativePath(rtrim($result['path'], '/') . '/' . ltrim($result['file'], '/')); + if (!$this->isApplicable($path)) { return $result; } diff --git a/app/code/Magento/Multishipping/Model/Cart/Controller/CartPlugin.php b/app/code/Magento/Multishipping/Model/Cart/Controller/CartPlugin.php index b450232395b88..ea3e765ebb5cc 100644 --- a/app/code/Magento/Multishipping/Model/Cart/Controller/CartPlugin.php +++ b/app/code/Magento/Multishipping/Model/Cart/Controller/CartPlugin.php @@ -13,6 +13,7 @@ use Magento\Framework\App\RequestInterface; use Magento\Framework\Exception\LocalizedException; use Magento\Multishipping\Model\Checkout\Type\Multishipping\State; +use Magento\Multishipping\Model\DisableMultishipping; use Magento\Quote\Api\CartRepositoryInterface; use Magento\Quote\Model\Quote; @@ -36,19 +37,27 @@ class CartPlugin */ private $addressRepository; + /** + * @var DisableMultishipping + */ + private $disableMultishipping; + /** * @param CartRepositoryInterface $cartRepository * @param Session $checkoutSession * @param AddressRepositoryInterface $addressRepository + * @param DisableMultishipping $disableMultishipping */ public function __construct( CartRepositoryInterface $cartRepository, Session $checkoutSession, - AddressRepositoryInterface $addressRepository + AddressRepositoryInterface $addressRepository, + DisableMultishipping $disableMultishipping ) { $this->cartRepository = $cartRepository; $this->checkoutSession = $checkoutSession; $this->addressRepository = $addressRepository; + $this->disableMultishipping = $disableMultishipping; } /** @@ -76,6 +85,9 @@ public function beforeDispatch(Cart $subject, RequestInterface $request) $shippingAddress->importCustomerAddressData($defaultCustomerAddress); } $this->cartRepository->save($quote); + } elseif ($this->disableMultishipping->execute($quote) && $this->isVirtualItemInQuote($quote)) { + $quote->setTotalsCollectedFlag(false); + $this->cartRepository->save($quote); } } @@ -88,4 +100,24 @@ private function isCheckoutComplete() : bool { return (bool) ($this->checkoutSession->getStepData(State::STEP_SHIPPING)['is_complete'] ?? true); } + + /** + * Checks whether quote has virtual items + * + * @param Quote $quote + * @return bool + */ + private function isVirtualItemInQuote(Quote $quote): bool + { + $items = $quote->getItems(); + if (!empty($items)) { + foreach ($items as $item) { + if ($item->getIsVirtual()) { + return true; + } + } + } + + return false; + } } diff --git a/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontDisableMultishippingModeCheckoutOnBackToCartTest.xml b/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontDisableMultishippingModeCheckoutOnBackToCartTest.xml new file mode 100644 index 0000000000000..8fa2c83fc6c0c --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontDisableMultishippingModeCheckoutOnBackToCartTest.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontDisableMultishippingModeCheckoutOnBackToCartTest"> + <annotations> + <features value="Multishipping"/> + <stories value="Multishipping"/> + <title value="Disable multishipping checkout on backing to cart"/> + <description value="Cart page summary block should count virtual product price after backing back to the cart from multishipping checkout."/> + <severity value="CRITICAL"/> + <testCaseId value="MC-39007"/> + <useCaseId value="MC-38825"/> + <group value="multishipping"/> + </annotations> + <before> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="VirtualProduct" stepKey="createVirtualProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="Simple_US_Customer_Multiple_Addresses" stepKey="createCustomerWithMultipleAddresses"/> + </before> + <after> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="createVirtualProduct" stepKey="deleteVirtualProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createCustomerWithMultipleAddresses" stepKey="deleteCustomer"/> + </after> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> + <argument name="Customer" value="$createCustomerWithMultipleAddresses$"/> + </actionGroup> + + <actionGroup ref="StorefrontOpenProductEntityPageActionGroup" stepKey="openVirtualProductPage"> + <argument name="product" value="$createVirtualProduct$"/> + </actionGroup> + <actionGroup ref="StorefrontAddProductToCartActionGroup" stepKey="addVirtualProductToCart"> + <argument name="product" value="$createVirtualProduct$"/> + <argument name="productCount" value="1"/> + </actionGroup> + + <actionGroup ref="StorefrontOpenProductEntityPageActionGroup" stepKey="openSimpleProductPage"> + <argument name="product" value="$createSimpleProduct$"/> + </actionGroup> + <actionGroup ref="StorefrontAddProductToCartActionGroup" stepKey="addSimpleProductToCart"> + <argument name="product" value="$createSimpleProduct$"/> + <argument name="productCount" value="2"/> + </actionGroup> + + <actionGroup ref="ClickViewAndEditCartFromMiniCartActionGroup" stepKey="goToShoppingCartFromMinicart"/> + <grabTextFrom selector="{{CheckoutPaymentSection.orderSummaryTotal}}" stepKey="grabTotal"/> + + <actionGroup ref="StorefrontGoCheckoutWithMultipleAddressesActionGroup" stepKey="goCheckoutWithMultipleAddresses"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goBackToShoppingCartPage"/> + <actionGroup ref="AssertStorefrontCheckoutPaymentSummaryTotalActionGroup" stepKey="assertSummaryTotal"> + <argument name="orderTotal" value="{$grabTotal}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Multishipping/Test/Unit/Model/Cart/Controller/CartPluginTest.php b/app/code/Magento/Multishipping/Test/Unit/Model/Cart/Controller/CartPluginTest.php index fec7a064a13ad..e5ec970ef8587 100644 --- a/app/code/Magento/Multishipping/Test/Unit/Model/Cart/Controller/CartPluginTest.php +++ b/app/code/Magento/Multishipping/Test/Unit/Model/Cart/Controller/CartPluginTest.php @@ -14,6 +14,7 @@ use Magento\Customer\Api\Data\CustomerInterface; use Magento\Framework\App\RequestInterface; use Magento\Multishipping\Model\Cart\Controller\CartPlugin; +use Magento\Multishipping\Model\DisableMultishipping; use Magento\Quote\Api\CartRepositoryInterface; use Magento\Quote\Model\Quote; use Magento\Quote\Model\Quote\Address; @@ -47,10 +48,12 @@ protected function setUp(): void $this->cartRepositoryMock = $this->getMockForAbstractClass(CartRepositoryInterface::class); $this->checkoutSessionMock = $this->createMock(Session::class); $this->addressRepositoryMock = $this->getMockForAbstractClass(AddressRepositoryInterface::class); + $disableMultishippingMock = $this->createMock(DisableMultishipping::class); $this->model = new CartPlugin( $this->cartRepositoryMock, $this->checkoutSessionMock, - $this->addressRepositoryMock + $this->addressRepositoryMock, + $disableMultishippingMock ); } @@ -65,33 +68,41 @@ public function testBeforeDispatch() 'getShippingAddress', 'getCustomer' ]); - $this->checkoutSessionMock->expects($this->once())->method('getQuote')->willReturn($quoteMock); + $this->checkoutSessionMock->method('getQuote') + ->willReturn($quoteMock); $addressMock = $this->createMock(Address::class); - $addressMock->expects($this->once())->method('getId')->willReturn($addressId); + $addressMock->method('getId') + ->willReturn($addressId); - $quoteMock->expects($this->once())->method('isMultipleShippingAddresses')->willReturn(true); - $quoteMock->expects($this->once())->method('getAllShippingAddresses')->willReturn([$addressMock]); - $quoteMock->expects($this->once())->method('removeAddress')->with($addressId)->willReturnSelf(); + $quoteMock->method('isMultipleShippingAddresses') + ->willReturn(true); + $quoteMock->method('getAllShippingAddresses') + ->willReturn([$addressMock]); + $quoteMock->method('removeAddress') + ->with($addressId)->willReturnSelf(); $shippingAddressMock = $this->createMock(Address::class); - $quoteMock->expects($this->once())->method('getShippingAddress')->willReturn($shippingAddressMock); + $quoteMock->method('getShippingAddress') + ->willReturn($shippingAddressMock); $customerMock = $this->getMockForAbstractClass(CustomerInterface::class); - $quoteMock->expects($this->once())->method('getCustomer')->willReturn($customerMock); - $customerMock->expects($this->once())->method('getDefaultShipping')->willReturn($customerAddressId); + $quoteMock->method('getCustomer') + ->willReturn($customerMock); + $customerMock->method('getDefaultShipping') + ->willReturn($customerAddressId); $customerAddressMock = $this->getMockForAbstractClass(AddressInterface::class); - $this->addressRepositoryMock->expects($this->once()) - ->method('getById') + $this->addressRepositoryMock->method('getById') ->with($customerAddressId) ->willReturn($customerAddressMock); - $shippingAddressMock->expects($this->once()) - ->method('importCustomerAddressData') + $shippingAddressMock->method('importCustomerAddressData') ->with($customerAddressMock) ->willReturnSelf(); - $this->cartRepositoryMock->expects($this->once())->method('save')->with($quoteMock); + $this->cartRepositoryMock->expects($this->once()) + ->method('save') + ->with($quoteMock); $this->model->beforeDispatch( $this->createMock(Cart::class), diff --git a/app/code/Magento/Multishipping/Test/Unit/Model/DisableMultishippingTest.php b/app/code/Magento/Multishipping/Test/Unit/Model/DisableMultishippingTest.php index 9882f8d1441aa..8d4013dd54e2c 100644 --- a/app/code/Magento/Multishipping/Test/Unit/Model/DisableMultishippingTest.php +++ b/app/code/Magento/Multishipping/Test/Unit/Model/DisableMultishippingTest.php @@ -11,6 +11,7 @@ use Magento\Quote\Api\Data\CartExtensionInterface; use Magento\Quote\Api\Data\CartInterface; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\MockObject\RuntimeException; use PHPUnit\Framework\TestCase; /** @@ -59,11 +60,7 @@ public function testExecuteWithMultishippingModeEnabled(bool $hasShippingAssignm ->method('setIsMultiShipping') ->with(0); - /** @var CartExtensionInterface|MockObject $extensionAttributesMock */ - $extensionAttributesMock = $this->getMockBuilder(CartExtensionInterface::class) - ->addMethods(['getShippingAssignments', 'setShippingAssignments']) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); + $extensionAttributesMock = $this->getCartExtensionMock(); $extensionAttributesMock->expects($this->once()) ->method('getShippingAssignments') @@ -90,7 +87,7 @@ public function executeWithMultishippingModeEnabledDataProvider(): array { return [ 'check_with_shipping_assignments' => [true], - 'check_without_shipping_assignments' => [false] + 'check_without_shipping_assignments' => [false], ]; } @@ -113,4 +110,22 @@ public function testExecuteWithMultishippingModeDisabled(): void $this->assertFalse($this->disableMultishippingModel->execute($this->quoteMock)); } + + /** + * Build cart extension mock. + * + * @return MockObject + */ + private function getCartExtensionMock(): MockObject + { + $mockBuilder = $this->getMockBuilder(CartExtensionInterface::class) + ->disableOriginalConstructor(); + try { + $mockBuilder->addMethods(['getShippingAssignments', 'setShippingAssignments']); + } catch (RuntimeException $e) { + // CartExtension already generated. + } + + return $mockBuilder->getMockForAbstractClass(); + } } diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddImageToWYSIWYGNewsletterTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddImageToWYSIWYGNewsletterTest.xml index e255f14a83661..a6cb2456c583f 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddImageToWYSIWYGNewsletterTest.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddImageToWYSIWYGNewsletterTest.xml @@ -20,7 +20,7 @@ <before> <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="switchToTinyMCE4" /> </before> <!--Create a newsletter template that contains an image--> <amOnPage url="{{NewsletterTemplateForm.url}}" stepKey="amOnNewsletterTemplatePage"/> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddVariableToWYSIWYGNewsletterTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddVariableToWYSIWYGNewsletterTest.xml index ff2e2a84a612e..d4bb8d358e21d 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddVariableToWYSIWYGNewsletterTest.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddVariableToWYSIWYGNewsletterTest.xml @@ -20,7 +20,7 @@ <before> <actionGroup ref="AdminLoginActionGroup" stepKey="loginGetFromGeneralFile"/> <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="switchToTinyMCE4" /> </before> <!--Create Custom Variable--> <actionGroup ref="CreateCustomVariableActionGroup" stepKey="createCustomVariable" /> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddWidgetToWYSIWYGNewsletterTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddWidgetToWYSIWYGNewsletterTest.xml index 73880f283677d..0fa16d275d6f4 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddWidgetToWYSIWYGNewsletterTest.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddWidgetToWYSIWYGNewsletterTest.xml @@ -20,7 +20,7 @@ <before> <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="switchToTinyMCE4" /> </before> <amOnPage url="{{NewsletterTemplateForm.url}}" stepKey="amOnNewsletterTemplatePage"/> <waitForElementVisible selector="{{BasicFieldNewsletterSection.templateName}}" stepKey="waitForTemplateName"/> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminMarketingNewsletterTemplateUpdateTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminMarketingNewsletterTemplateUpdateTest.xml index 1a0c90ff13c36..1d8565d7b2b78 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminMarketingNewsletterTemplateUpdateTest.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminMarketingNewsletterTemplateUpdateTest.xml @@ -15,6 +15,9 @@ <title value="Newsletter Updating Test"/> <description value="Admin should be able update created Newsletter Template"/> <severity value="MAJOR"/> + <testCaseId value="MC-39506"/> + <useCaseId value="MAGETWO-69597"/> + <group value="newsletter"/> <group value="reports"/> <group value="mtf_migrated"/> <group value="WYSIWYGDisabled"/> @@ -40,6 +43,7 @@ </actionGroup> <actionGroup ref="AdminMarketingOpenNewsletterTemplateFromGridActionGroup" stepKey="openCreatedNewsletterTemplate"/> </before> + <after> <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToNewsletterGridPage"> <argument name="menuUiId" value="{{AdminMenuMarketing.dataUiId}}"/> @@ -74,8 +78,8 @@ <argument name="subject" value="{{updatedNewsletter.subject}}"/> </actionGroup> <actionGroup ref="AdminSearchNewsletterTemplateOnGridActionGroup" stepKey="findUpdatedNewsletterTemplate"> - <argument name="name" value="Updated Newsletter Template"/> - <argument name="subject" value="Updated Newsletter Subject"/> + <argument name="name" value="{{updatedNewsletter.name}}"/> + <argument name="subject" value="{{updatedNewsletter.subject}}"/> </actionGroup> <actionGroup ref="AdminMarketingOpenNewsletterTemplateFromGridActionGroup" stepKey="openTemplate"/> <actionGroup ref="AssertAdminNewsletterTemplateFormActionGroup" stepKey="assertNewsletterForm"> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnNewsletterTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnNewsletterTest.xml index 3a247402c111d..7cfd4c67369c8 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnNewsletterTest.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnNewsletterTest.xml @@ -20,7 +20,7 @@ <before> <actionGroup ref="AdminLoginActionGroup" stepKey="loginGetFromGeneralFile"/> <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="switchToTinyMCE4" /> </before> <amOnPage url="{{NewsletterTemplateForm.url}}" stepKey="amOnNewsletterTemplatePage"/> <waitForPageLoad stepKey="waitForPageLoad1"/> diff --git a/app/code/Magento/PageCache/Test/Mftf/ActionGroup/AssertStorefrontAddToCartFormKeyValueIsNotCachedActionGroup.xml b/app/code/Magento/PageCache/Test/Mftf/ActionGroup/AssertStorefrontAddToCartFormKeyValueIsNotCachedActionGroup.xml new file mode 100644 index 0000000000000..6ef5f878023a1 --- /dev/null +++ b/app/code/Magento/PageCache/Test/Mftf/ActionGroup/AssertStorefrontAddToCartFormKeyValueIsNotCachedActionGroup.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStorefrontAddToCartFormKeyValueIsNotCachedActionGroup"> + <annotations> + <description>Assert that product page add to cart form key is different from cached value.</description> + </annotations> + <arguments> + <argument name="cachedValue" type="string"/> + </arguments> + + <grabValueFrom selector="{{StorefrontProductActionSection.inputFormKey}}" stepKey="grabUpdatedValue"/> + <assertRegExp stepKey="validateCachedFormKey"> + <expectedResult type="string">/\w{16}/</expectedResult> + <actualResult type="string">{{cachedValue}}</actualResult> + </assertRegExp> + <assertRegExp stepKey="validateUpdatedFormKey"> + <expectedResult type="string">/\w{16}/</expectedResult> + <actualResult type="variable">grabUpdatedValue</actualResult> + </assertRegExp> + <assertNotEquals stepKey="assertFormKeyUpdated"> + <expectedResult type="string">{{cachedValue}}</expectedResult> + <actualResult type="variable">grabUpdatedValue</actualResult> + </assertNotEquals> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/PageCache/Test/Mftf/Test/StorefrontCachedInputFormKeyValueUpdatedTest.xml b/app/code/Magento/PageCache/Test/Mftf/Test/StorefrontCachedInputFormKeyValueUpdatedTest.xml new file mode 100644 index 0000000000000..a9d77429e3248 --- /dev/null +++ b/app/code/Magento/PageCache/Test/Mftf/Test/StorefrontCachedInputFormKeyValueUpdatedTest.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCachedInputFormKeyValueUpdatedTest"> + <annotations> + <features value="PageCache"/> + <stories value="FormKey"/> + <title value="Form Key value should be updated by js script"/> + <description value="Form Key value should be updated by js script"/> + <testCaseId value="MC-39300"/> + <useCaseId value="MC-30171"/> + <severity value="AVERAGE"/> + <group value="pageCache"/> + </annotations> + <before> + <!-- Create Data --> + <createData entity="SimpleProduct2" stepKey="createProduct"/> + <actionGroup ref="CliCacheCleanActionGroup" stepKey="cleanCache"> + <argument name="tags" value="full_page"/> + </actionGroup> + </before> + <after> + <!-- Delete data --> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + </after> + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductPage"> + <argument name="productUrl" value="$createProduct.custom_attributes[url_key]$"/> + </actionGroup> + <grabValueFrom selector="{{StorefrontProductActionSection.inputFormKey}}" stepKey="grabCachedValue"/> + <resetCookie userInput="PHPSESSID" stepKey="resetSessionCookie"/> + <resetCookie userInput="form_key" stepKey="resetFormKeyCookie"/> + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="reopenProductPage"> + <argument name="productUrl" value="$createProduct.custom_attributes[url_key]$"/> + </actionGroup> + <actionGroup ref="AssertStorefrontAddToCartFormKeyValueIsNotCachedActionGroup" stepKey="assertValueIsUpdatedByScript"> + <argument name="cachedValue" value="{$grabCachedValue}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/PageCache/ViewModel/FormKeyProvider.php b/app/code/Magento/PageCache/ViewModel/FormKeyProvider.php new file mode 100644 index 0000000000000..26f6be43c627a --- /dev/null +++ b/app/code/Magento/PageCache/ViewModel/FormKeyProvider.php @@ -0,0 +1,41 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\PageCache\ViewModel; + +use Magento\Framework\View\Element\Block\ArgumentInterface; +use Magento\PageCache\Model\Config; + +/** + * Adds script to update form key from cookie after script rendering + */ +class FormKeyProvider implements ArgumentInterface +{ + /** + * @var Config + */ + private $config; + + /** + * @param Config $config + */ + public function __construct( + Config $config + ) { + $this->config = $config; + } + + /** + * Is full page cache enabled + * + * @return bool + */ + public function isFullPageCacheEnabled(): bool + { + return $this->config->isEnabled(); + } +} diff --git a/app/code/Magento/PageCache/view/frontend/layout/default.xml b/app/code/Magento/PageCache/view/frontend/layout/default.xml index 7e1fc9d31b864..3db4b1c4ae52e 100644 --- a/app/code/Magento/PageCache/view/frontend/layout/default.xml +++ b/app/code/Magento/PageCache/view/frontend/layout/default.xml @@ -10,6 +10,13 @@ <referenceBlock name="head.components"> <block class="Magento\Framework\View\Element\Js\Components" name="pagecache_page_head_components" template="Magento_PageCache::js/components.phtml"/> </referenceBlock> + <referenceBlock name="head.additional"> + <block name="form_key_provider" template="Magento_PageCache::form_key_provider.phtml"> + <arguments> + <argument name="form_key_provider" xsi:type="object">Magento\PageCache\ViewModel\FormKeyProvider</argument> + </arguments> + </block> + </referenceBlock> <referenceContainer name="content"> <block class="Magento\PageCache\Block\Javascript" template="Magento_PageCache::javascript.phtml" name="pageCache" as="pageCache"/> </referenceContainer> diff --git a/app/code/Magento/PageCache/view/frontend/requirejs-config.js b/app/code/Magento/PageCache/view/frontend/requirejs-config.js index 7a33e2748b916..59d4499092965 100644 --- a/app/code/Magento/PageCache/view/frontend/requirejs-config.js +++ b/app/code/Magento/PageCache/view/frontend/requirejs-config.js @@ -8,5 +8,6 @@ var config = { '*': { pageCache: 'Magento_PageCache/js/page-cache' } - } + }, + deps: ['Magento_PageCache/js/form-key-provider'] }; diff --git a/app/code/Magento/PageCache/view/frontend/templates/form_key_provider.phtml b/app/code/Magento/PageCache/view/frontend/templates/form_key_provider.phtml new file mode 100644 index 0000000000000..4f952002e458f --- /dev/null +++ b/app/code/Magento/PageCache/view/frontend/templates/form_key_provider.phtml @@ -0,0 +1,14 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +if ($block->getFormKeyProvider()->isFullPageCacheEnabled()): ?> + <script type="text/x-magento-init"> + { + "*": { + "Magento_PageCache/js/form-key-provider": {} + } + } + </script> +<?php endif; ?> diff --git a/app/code/Magento/PageCache/view/frontend/web/js/form-key-provider.js b/app/code/Magento/PageCache/view/frontend/web/js/form-key-provider.js new file mode 100644 index 0000000000000..c63d97840e946 --- /dev/null +++ b/app/code/Magento/PageCache/view/frontend/web/js/form-key-provider.js @@ -0,0 +1,93 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +define(function () { + 'use strict'; + + return function () { + var formKey, + inputElements, + inputSelector = 'input[name="form_key"]'; + + /** + * Set form_key cookie + * @private + */ + function setFormKeyCookie(value) { + var expires, + secure, + date = new Date(), + isSecure = !!window.cookiesConfig && window.cookiesConfig.secure; + + date.setTime(date.getTime() + 86400000); + expires = '; expires=' + date.toUTCString(); + secure = isSecure ? '; secure' : ''; + + document.cookie = 'form_key=' + (value || '') + expires + secure + '; path=/'; + } + + /** + * Retrieves form key from cookie + * @private + */ + function getFormKeyCookie() { + var cookie, + i, + nameEQ = 'form_key=', + cookieArr = document.cookie.split(';'); + + for (i = 0; i < cookieArr.length; i++) { + cookie = cookieArr[i]; + + while (cookie.charAt(0) === ' ') { + cookie = cookie.substring(1, cookie.length); + } + + if (cookie.indexOf(nameEQ) === 0) { + return cookie.substring(nameEQ.length, cookie.length); + } + } + + return null; + } + + /** + * Generate form key string + * @private + */ + function generateFormKeyString() { + var result = '', + length = 16, + chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + while (length--) { + result += chars[Math.round(Math.random() * (chars.length - 1))]; + } + + return result; + } + + /** + * Init form_key inputs with value + * @private + */ + function initFormKey() { + formKey = getFormKeyCookie(); + + if (!formKey) { + formKey = generateFormKeyString(); + setFormKeyCookie(formKey); + } + inputElements = document.querySelectorAll(inputSelector); + + if (inputElements.length) { + Array.prototype.forEach.call(inputElements, function (element) { + element.setAttribute('value', formKey); + }); + } + } + + initFormKey(); + }; +}); diff --git a/app/code/Magento/PageCache/view/frontend/web/js/page-cache.js b/app/code/Magento/PageCache/view/frontend/web/js/page-cache.js index 41a32ab8a49c8..d7214918c530d 100644 --- a/app/code/Magento/PageCache/view/frontend/web/js/page-cache.js +++ b/app/code/Magento/PageCache/view/frontend/web/js/page-cache.js @@ -7,9 +7,10 @@ define([ 'jquery', 'domReady', 'consoleLogger', + 'Magento_PageCache/js/form-key-provider', 'jquery-ui-modules/widget', 'mage/cookies' -], function ($, domReady, consoleLogger) { +], function ($, domReady, consoleLogger, formKeyInit) { 'use strict'; /** @@ -99,6 +100,7 @@ define([ /** * FormKey Widget - this widget is generating from key, saves it to cookie and + * @deprecated see Magento/PageCache/view/frontend/web/js/form-key-provider.js */ $.widget('mage.formKey', { options: { @@ -298,8 +300,7 @@ define([ }); domReady(function () { - $('body') - .formKey(); + formKeyInit(); }); return { diff --git a/app/code/Magento/Paypal/Model/Report/Settlement.php b/app/code/Magento/Paypal/Model/Report/Settlement.php index 462ca2f979420..f429fdaac4c34 100644 --- a/app/code/Magento/Paypal/Model/Report/Settlement.php +++ b/app/code/Magento/Paypal/Model/Report/Settlement.php @@ -595,6 +595,7 @@ public function getSftpCredentials($automaticMode = false) protected function _fileNameToDate($filename) { // Currently filenames look like STL-YYYYMMDD, so that is what we care about. + // phpcs:ignore Magento2.Functions.DiscouragedFunction $dateSnippet = substr(basename($filename), 4, 8); $result = substr($dateSnippet, 0, 4) . '-' . substr($dateSnippet, 4, 2) . '-' . substr($dateSnippet, 6, 2); return $result; @@ -603,13 +604,16 @@ protected function _fileNameToDate($filename) /** * Filter SFTP file list by filename format * + * Single Account format = STL-yyyymmdd.sequenceNumber.version.format + * Multiple Account format = STL-yyyymmdd.reportingWindow.sequenceNumber.totalFiles.version.format + * * @param array $list List of files as per $connection->rawls() * @return array Trimmed down list of files */ protected function _filterReportsList($list) { $result = []; - $pattern = '/^STL-(\d{8,8})\.(\d{2,2})\.(.{3,3})\.CSV$/'; + $pattern = '/^STL-(\d{8,8})\.((\d{2,2})|(([A-Z])\.(\d{2,2})\.(\d{2,2})))\.(.{3,3})\.CSV$/'; foreach ($list as $filename => $data) { if (preg_match($pattern, $filename)) { $result[$filename] = $data; diff --git a/app/code/Magento/Paypal/Test/Unit/Model/Report/SettlementTest.php b/app/code/Magento/Paypal/Test/Unit/Model/Report/SettlementTest.php new file mode 100644 index 0000000000000..ed4b35e9c08c9 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Unit/Model/Report/SettlementTest.php @@ -0,0 +1,73 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Paypal\Test\Unit\Model\Report; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\Paypal\Model\Report\Settlement; +use Magento\Framework\Filesystem\Io\Sftp; +use Magento\Framework\Filesystem\Directory\WriteInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Test for paypal settlement + */ +class SettlementTest extends TestCase +{ + /** + * @var Settlement + */ + private $settlement; + + /** + * @var WriteInterface|MockObject + */ + private $tmpDirectory; + + /** + * @inheritDoc + */ + protected function setUp(): void + { + $objectManagerHelper = new ObjectManagerHelper($this); + $this->tmpDirectory = $this->getMockBuilder(WriteInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->settlement = $objectManagerHelper->getObject( + Settlement::class, + [ + '_tmpDirectory' => $this->tmpDirectory + ] + ); + } + + /** + * Test for filter report list + * + * @return void + */ + public function testFilterReportList(): void + { + $this->tmpDirectory->method('getAbsolutePath') + ->willReturn(''); + /** @var Sftp|MockObject $connection */ + $connection = $this->getMockBuilder(Sftp::class) + ->onlyMethods(['rawls', 'read']) + ->disableOriginalConstructor() + ->getMock(); + $connection->method('rawls') + ->willReturn( + [ + 'STL-20201221.01.009.CSV' => 'Single account', + 'STL-20201221.H.01.01.009.CSV' => 'Multiple account', + ] + ); + $connection->expects($this->exactly(2))->method('read')->willReturn(false); + $this->settlement->fetchAndSave($connection); + } +} diff --git a/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/in-context/checkout-express.js b/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/in-context/checkout-express.js index 206355f5a9839..448c409da2716 100644 --- a/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/in-context/checkout-express.js +++ b/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/in-context/checkout-express.js @@ -85,7 +85,6 @@ define([ */ onClick: function () { additionalValidators.validate(); - this.selectPaymentMethod(); }, /** diff --git a/app/code/Magento/Persistent/Test/Mftf/Test/StorefrontCorrectWelcomeMessageAfterCustomerIsLoggedOutTest.xml b/app/code/Magento/Persistent/Test/Mftf/Test/StorefrontCorrectWelcomeMessageAfterCustomerIsLoggedOutTest.xml index 45ccab54de5f3..d6e1f2a247ebe 100644 --- a/app/code/Magento/Persistent/Test/Mftf/Test/StorefrontCorrectWelcomeMessageAfterCustomerIsLoggedOutTest.xml +++ b/app/code/Magento/Persistent/Test/Mftf/Test/StorefrontCorrectWelcomeMessageAfterCustomerIsLoggedOutTest.xml @@ -75,5 +75,8 @@ <see userInput="Welcome, $$createCustomerForPersistent.firstname$$ $$createCustomerForPersistent.lastname$$! Not you?" selector="{{StorefrontHeaderSection.welcomeMessage}}" stepKey="seePersistentWelcomeMessage"/> + <dontSee userInput="Not you? Not you?" + selector="{{StorefrontHeaderSection.welcomeMessage}}" + stepKey="verifyNotYouLinkNotDuplicated"/> </test> </tests> diff --git a/app/code/Magento/Persistent/Test/Unit/Model/QuoteManagerTest.php b/app/code/Magento/Persistent/Test/Unit/Model/QuoteManagerTest.php index 03d6ab02beb3c..ece36673c1e82 100644 --- a/app/code/Magento/Persistent/Test/Unit/Model/QuoteManagerTest.php +++ b/app/code/Magento/Persistent/Test/Unit/Model/QuoteManagerTest.php @@ -23,6 +23,7 @@ use Magento\Quote\Model\Quote\Address; use Magento\Quote\Model\Quote\ShippingAssignment\ShippingAssignmentProcessor; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\MockObject\RuntimeException; use PHPUnit\Framework\TestCase; /** @@ -94,7 +95,7 @@ protected function setUp(): void 'setCustomerData', 'clearQuote', 'clearStorage', - 'getQuote' + 'getQuote', ]) ->onlyMethods(['removePersistentCookie']) ->disableOriginalConstructor() @@ -115,7 +116,7 @@ protected function setUp(): void 'setCustomerLastname', 'setCustomerGroupId', 'setIsPersistent', - 'getCustomerId' + 'getCustomerId', ]) ->onlyMethods([ 'getId', @@ -132,7 +133,7 @@ protected function setUp(): void 'getExtensionAttributes', 'setExtensionAttributes', '__wakeup', - 'setCustomer' + 'setCustomer', ]) ->disableOriginalConstructor() ->getMock(); @@ -240,8 +241,7 @@ public function testSetGuest() ->method('removePersistentCookie')->willReturn($this->sessionMock); $this->quoteMock->expects($this->once())->method('isVirtual')->willReturn(false); $this->quoteMock->expects($this->once())->method('getItemsQty')->willReturn(1); - $extensionAttributes = $this->getMockBuilder(CartExtensionInterface::class) - ->getMockForAbstractClass(); + $extensionAttributes = $this->getExtensionAttributesMock(); $shippingAssignment = $this->createMock(ShippingAssignmentInterface::class); $extensionAttributes->expects($this->once()) ->method('setShippingAssignments') @@ -381,4 +381,21 @@ public function testConvertCustomerCartToGuestWithEmptyQuoteId() $this->quoteMock->expects($this->once())->method('getId')->willReturn(1); $this->model->convertCustomerCartToGuest(); } + + /** + * Build CartExtensionInterface mock. + * + * @return MockObject + */ + private function getExtensionAttributesMock(): MockObject + { + $extensionMockBuilder = $this->getMockBuilder(CartExtensionInterface::class); + try { + $extensionMockBuilder->addMethods(['setShippingAssignments']); + } catch (RuntimeException $e) { + // do nothing as CartExtensionInterface already generated and has 'setShippingAssignments' method. + } + + return $extensionMockBuilder->getMockForAbstractClass(); + } } diff --git a/app/code/Magento/Persistent/view/frontend/web/js/view/additional-welcome.js b/app/code/Magento/Persistent/view/frontend/web/js/view/additional-welcome.js index 8e69325860167..4875617ef5ea1 100644 --- a/app/code/Magento/Persistent/view/frontend/web/js/view/additional-welcome.js +++ b/app/code/Magento/Persistent/view/frontend/web/js/view/additional-welcome.js @@ -40,8 +40,8 @@ define([ $(this).attr('data-bind', html); $(this).html(html); - $(this).after(' <span><a ' + window.notYouLink + '>' + $t('Not you?') + '</a></span>'); }); + $(welcomeElems).append(' <span><a ' + window.notYouLink + '>' + $t('Not you?') + '</a>'); } } }, diff --git a/app/code/Magento/ProductVideo/Test/Unit/Model/Product/Attribute/Media/ExternalVideoEntryConverterTest.php b/app/code/Magento/ProductVideo/Test/Unit/Model/Product/Attribute/Media/ExternalVideoEntryConverterTest.php index a921bff76c8d6..a55416da4b532 100644 --- a/app/code/Magento/ProductVideo/Test/Unit/Model/Product/Attribute/Media/ExternalVideoEntryConverterTest.php +++ b/app/code/Magento/ProductVideo/Test/Unit/Model/Product/Attribute/Media/ExternalVideoEntryConverterTest.php @@ -20,6 +20,7 @@ use Magento\ProductVideo\Model\Product\Attribute\Media\ExternalVideoEntryConverter; use Magento\ProductVideo\Model\Product\Attribute\Media\VideoEntry; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\MockObject\RuntimeException; use PHPUnit\Framework\TestCase; /** @@ -81,7 +82,7 @@ protected function setUp(): void 'getContent', 'setContent', 'getExtensionAttributes', - 'setExtensionAttributes' + 'setExtensionAttributes', ] ); @@ -104,11 +105,7 @@ protected function setUp(): void ['create'] ); - $this->mediaGalleryEntryExtensionMock = $this->getMockBuilder(ProductAttributeMediaGalleryEntryExtension::class) - ->addMethods(['getVideoProvider']) - ->onlyMethods(['setVideoContent', 'getVideoContent']) - ->disableOriginalConstructor() - ->getMock(); + $this->mediaGalleryEntryExtensionMock = $this->getProductAttributeMediaGalleryEntryExtensionMock(); $this->mediaGalleryEntryExtensionMock->expects($this->any())->method('setVideoContent')->willReturn(null); $this->mediaGalleryEntryExtensionFactoryMock->expects($this->any())->method('create')->willReturn( @@ -123,7 +120,7 @@ protected function setUp(): void 'mediaGalleryEntryFactory' => $this->mediaGalleryEntryFactoryMock, 'dataObjectHelper' => $this->dataObjectHelperMock, 'videoEntryFactory' => $this->videoEntryFactoryMock, - 'mediaGalleryEntryExtensionFactory' => $this->mediaGalleryEntryExtensionFactoryMock + 'mediaGalleryEntryExtensionFactory' => $this->mediaGalleryEntryExtensionFactoryMock, ] ); } @@ -218,4 +215,28 @@ public function testConvertFrom() $result = $this->modelObject->convertFrom($this->mediaGalleryEntryMock); $this->assertEquals($expectedResult, $result); } + + /** + * Build ProductAttributeMediaGalleryEntryExtension mock. + * + * @return MockObject + */ + private function getProductAttributeMediaGalleryEntryExtensionMock(): MockObject + { + $mockBuilder = $this->getMockBuilder(ProductAttributeMediaGalleryEntryExtension::class) + ->disableOriginalConstructor(); + try { + $mockBuilder->addMethods( + [ + 'getVideoProvider', + 'setVideoContent', + 'getVideoContent', + ] + ); + } catch (RuntimeException $e) { + // ProductAttributeMediaGalleryEntryExtension already generated and has all necessary methods. + } + + return $mockBuilder->getMock(); + } } diff --git a/app/code/Magento/Quote/Model/Quote/Item/Processor.php b/app/code/Magento/Quote/Model/Quote/Item/Processor.php index c6bef1cc80bfb..477a6f2254d52 100644 --- a/app/code/Magento/Quote/Model/Quote/Item/Processor.php +++ b/app/code/Magento/Quote/Model/Quote/Item/Processor.php @@ -96,11 +96,12 @@ public function prepare(Item $item, DataObject $request, Product $candidate): vo } $item->addQty($candidate->getCartQty()); - $customPrice = $request->getCustomPrice(); if (!$item->getParentItem() || $item->getParentItem()->isChildrenCalculated()) { $item->setPrice($candidate->getFinalPrice()); } - if (!empty($customPrice)) { + + $customPrice = $request->getCustomPrice(); + if (!empty($customPrice) && !$candidate->getParentProductId()) { $item->setCustomPrice($customPrice); $item->setOriginalCustomPrice($customPrice); } diff --git a/app/code/Magento/Quote/Model/QuoteRepository.php b/app/code/Magento/Quote/Model/QuoteRepository.php index 1533194023e3e..b1bef834197aa 100644 --- a/app/code/Magento/Quote/Model/QuoteRepository.php +++ b/app/code/Magento/Quote/Model/QuoteRepository.php @@ -134,8 +134,8 @@ public function get($cartId, array $sharedStoreIds = []) { if (!isset($this->quotesById[$cartId])) { $quote = $this->loadQuote('loadByIdWithoutStore', 'cartId', $cartId, $sharedStoreIds); - $this->getLoadHandler()->load($quote); $this->quotesById[$cartId] = $quote; + $this->getLoadHandler()->load($quote); } return $this->quotesById[$cartId]; } diff --git a/app/code/Magento/Quote/Test/Unit/Model/ShippingAddressAssignmentTest.php b/app/code/Magento/Quote/Test/Unit/Model/ShippingAddressAssignmentTest.php index 00cb1d48e8889..868188c97b1fa 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/ShippingAddressAssignmentTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/ShippingAddressAssignmentTest.php @@ -16,6 +16,7 @@ use Magento\Quote\Model\Quote\ShippingAssignment\ShippingAssignmentProcessor; use Magento\Quote\Model\ShippingAddressAssignment; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\MockObject\RuntimeException; use PHPUnit\Framework\TestCase; class ShippingAddressAssignmentTest extends TestCase @@ -66,9 +67,7 @@ protected function setUp(): void ); $this->quoteMock = $this->createMock(Quote::class); $this->addressMock = $this->createMock(Address::class); - $this->extensionAttributeMock = $this->getMockBuilder(CartExtension::class) - ->addMethods(['setShippingAssignments']) - ->getMock(); + $this->extensionAttributeMock = $this->getCartExtensionMock(); $this->shippingAssignmentMock = $this->getMockForAbstractClass(ShippingAssignmentInterface::class); //shipping assignment processing @@ -113,4 +112,21 @@ public function testSetAddressUseForShippingFalse() $this->quoteMock->expects($this->once())->method('setShippingAddress')->with($addressMock); $this->model->setAddress($this->quoteMock, $this->addressMock, false); } + + /** + * Build cart extension mock. + * + * @return MockObject + */ + private function getCartExtensionMock(): MockObject + { + $mockBuilder = $this->getMockBuilder(CartExtension::class); + try { + $mockBuilder->addMethods(['setShippingAssignments']); + } catch (RuntimeException $e) { + // CartExtension already generated. + } + + return $mockBuilder->getMock(); + } } diff --git a/app/code/Magento/RemoteStorage/Model/Synchronizer.php b/app/code/Magento/RemoteStorage/Model/Synchronizer.php index 4276c7a1a2ffd..3b01e7af5ee54 100644 --- a/app/code/Magento/RemoteStorage/Model/Synchronizer.php +++ b/app/code/Magento/RemoteStorage/Model/Synchronizer.php @@ -8,13 +8,14 @@ namespace Magento\RemoteStorage\Model; use Generator; +use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Exception\ValidatorException; +use Magento\Framework\Filesystem\Directory\WriteInterface; use Magento\Framework\Filesystem\DriverPool; use Magento\Framework\Filesystem\Glob; use Magento\RemoteStorage\Driver\DriverPool as RemoteDriverPool; use Magento\RemoteStorage\Filesystem; -use Magento\Framework\Filesystem\Directory\WriteInterface; /** * Synchronize files from local filesystem. @@ -44,10 +45,12 @@ public function __construct(Filesystem $filesystem) public function execute(): Generator { foreach ($this->filesystem->getDirectoryCodes() as $directoryCode) { - $directory = $this->filesystem->getDirectoryWrite($directoryCode, DriverPool::FILE); - $remoteDirectory = $this->filesystem->getDirectoryWrite($directoryCode, RemoteDriverPool::REMOTE); + if ($this->isSynchronizationAllowed($directoryCode)) { + $directory = $this->filesystem->getDirectoryWrite($directoryCode, DriverPool::FILE); + $remoteDirectory = $this->filesystem->getDirectoryWrite($directoryCode, RemoteDriverPool::REMOTE); - yield from $this->copyRecursive($directory, $remoteDirectory, $directory->getAbsolutePath()); + yield from $this->copyRecursive($directory, $remoteDirectory, $directory->getAbsolutePath()); + } } } @@ -101,4 +104,18 @@ private function copyRecursive( yield from $this->copyRecursive($directory, $remoteDirectory, $childDirectory, $pattern, $flags); } } + + /** + * Check if synchronization is allowed. + * + * @param string $directoryCode + * @return bool + * @deprecated This method should be removed when MC-39280 is fixed + * and import export functionality is allocated inside var/import_export directory + */ + private function isSynchronizationAllowed(string $directoryCode): bool + { + // skip synchronization for import export + return $directoryCode !== DirectoryList::VAR_IMPORT_EXPORT; + } } diff --git a/app/code/Magento/RemoteStorage/Plugin/Image.php b/app/code/Magento/RemoteStorage/Plugin/Image.php index 013e3fd23e168..5c40bba238651 100644 --- a/app/code/Magento/RemoteStorage/Plugin/Image.php +++ b/app/code/Magento/RemoteStorage/Plugin/Image.php @@ -185,7 +185,7 @@ public function __destruct() private function copyFileToTmp(string $filePath): string { if ($this->fileExistsInTmp($filePath)) { - return $filePath; + return $this->tmpFiles[$filePath]; } $absolutePath = $this->remoteDirectoryWrite->getAbsolutePath($filePath); if ($this->remoteDirectoryWrite->isFile($absolutePath)) { @@ -223,7 +223,7 @@ private function storeTmpName(string $filePath): string */ private function fileExistsInTmp(string $filePath): bool { - return in_array($filePath, $this->tmpFiles, true); + return array_key_exists($filePath, $this->tmpFiles); } /** diff --git a/app/code/Magento/RemoteStorage/Test/Unit/Model/SynchronizerTest.php b/app/code/Magento/RemoteStorage/Test/Unit/Model/SynchronizerTest.php index 5c3ddb74bb0cf..4e373e7f9c77e 100644 --- a/app/code/Magento/RemoteStorage/Test/Unit/Model/SynchronizerTest.php +++ b/app/code/Magento/RemoteStorage/Test/Unit/Model/SynchronizerTest.php @@ -51,7 +51,7 @@ protected function setUp(): void public function testExecute(): void { $this->filesystemMock->method('getDirectoryCodes') - ->willReturn(['test']); + ->willReturn(['test', 'import_export']); $localDriver = $this->createMock(DriverInterface::class); $remoteDriver = $this->createMock(DriverInterface::class); @@ -63,7 +63,8 @@ public function testExecute(): void $remoteDirectory->method('getDriver') ->willReturn($remoteDriver); - $this->filesystemMock->method('getDirectoryWrite') + $this->filesystemMock->expects(self::exactly(2)) + ->method('getDirectoryWrite') ->willReturnMap([ ['test', DriverPool::FILE, $localDirectory], ['test', RemoteDriverPool::REMOTE, $remoteDirectory] diff --git a/app/code/Magento/RemoteStorage/etc/di.xml b/app/code/Magento/RemoteStorage/etc/di.xml index 04dcc5955ac41..6ba98cdd107e8 100644 --- a/app/code/Magento/RemoteStorage/etc/di.xml +++ b/app/code/Magento/RemoteStorage/etc/di.xml @@ -26,7 +26,7 @@ <arguments> <argument name="directoryCodes" xsi:type="array"> <item name="media" xsi:type="const">Magento\Framework\App\Filesystem\DirectoryList::MEDIA</item> - <item name="var_export" xsi:type="const">Magento\Framework\App\Filesystem\DirectoryList::VAR_EXPORT</item> + <item name="var_import_export" xsi:type="const">Magento\Framework\App\Filesystem\DirectoryList::VAR_IMPORT_EXPORT</item> </argument> </arguments> </virtualType> diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php index e6a209b541198..9bb71d837cade 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php @@ -6,11 +6,24 @@ namespace Magento\Sales\Block\Adminhtml\Order\Create\Form; +use Magento\Backend\Block\Template\Context; +use Magento\Backend\Model\Session\Quote; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Api\Data\CustomerInterface; use Magento\Customer\Api\GroupManagementInterface; +use Magento\Customer\Model\Metadata\Form; use Magento\Framework\Api\ExtensibleDataObjectConverter; use Magento\Framework\App\ObjectManager; use Magento\Framework\Data\Form\Element\AbstractElement; +use Magento\Framework\Data\FormFactory; +use Magento\Customer\Model\Metadata\FormFactory as MetadataFormFactory; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Phrase; use Magento\Framework\Pricing\PriceCurrencyInterface; +use Magento\Framework\Reflection\DataObjectProcessor; +use Magento\Sales\Model\AdminOrder\Create; +use Magento\Store\Model\ScopeInterface; /** * Create order account form @@ -25,46 +38,48 @@ class Account extends AbstractForm /** * Metadata form factory * - * @var \Magento\Customer\Model\Metadata\FormFactory + * @var MetadataFormFactory */ protected $_metadataFormFactory; /** * Customer repository * - * @var \Magento\Customer\Api\CustomerRepositoryInterface + * @var CustomerRepositoryInterface */ protected $customerRepository; /** - * @var \Magento\Framework\Api\ExtensibleDataObjectConverter + * @var ExtensibleDataObjectConverter */ protected $_extensibleDataObjectConverter; + private const XML_PATH_EMAIL_REQUIRED_CREATE_ORDER = 'customer/create_account/email_required_create_order'; + /** - * @param \Magento\Backend\Block\Template\Context $context - * @param \Magento\Backend\Model\Session\Quote $sessionQuote - * @param \Magento\Sales\Model\AdminOrder\Create $orderCreate + * @param Context $context + * @param Quote $sessionQuote + * @param Create $orderCreate * @param PriceCurrencyInterface $priceCurrency - * @param \Magento\Framework\Data\FormFactory $formFactory - * @param \Magento\Framework\Reflection\DataObjectProcessor $dataObjectProcessor - * @param \Magento\Customer\Model\Metadata\FormFactory $metadataFormFactory - * @param \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository + * @param FormFactory $formFactory + * @param DataObjectProcessor $dataObjectProcessor + * @param MetadataFormFactory $metadataFormFactory + * @param CustomerRepositoryInterface $customerRepository * @param ExtensibleDataObjectConverter $extensibleDataObjectConverter * @param array $data * @param GroupManagementInterface|null $groupManagement * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( - \Magento\Backend\Block\Template\Context $context, - \Magento\Backend\Model\Session\Quote $sessionQuote, - \Magento\Sales\Model\AdminOrder\Create $orderCreate, + Context $context, + Quote $sessionQuote, + Create $orderCreate, PriceCurrencyInterface $priceCurrency, - \Magento\Framework\Data\FormFactory $formFactory, - \Magento\Framework\Reflection\DataObjectProcessor $dataObjectProcessor, - \Magento\Customer\Model\Metadata\FormFactory $metadataFormFactory, - \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository, - \Magento\Framework\Api\ExtensibleDataObjectConverter $extensibleDataObjectConverter, + FormFactory $formFactory, + DataObjectProcessor $dataObjectProcessor, + MetadataFormFactory $metadataFormFactory, + CustomerRepositoryInterface $customerRepository, + ExtensibleDataObjectConverter $extensibleDataObjectConverter, array $data = [], ?GroupManagementInterface $groupManagement = null ) { @@ -103,7 +118,7 @@ public function getHeaderCssClass() /** * Return header text * - * @return \Magento\Framework\Phrase + * @return Phrase */ public function getHeaderText() { @@ -114,10 +129,12 @@ public function getHeaderText() * Prepare Form and add elements to form * * @return $this + * @throws LocalizedException + * @throws NoSuchEntityException */ protected function _prepareForm() { - /** @var \Magento\Customer\Model\Metadata\Form $customerForm */ + /** @var Form $customerForm */ $customerForm = $this->_metadataFormFactory->create('customer', 'adminhtml_checkout'); // prepare customer attributes to show @@ -170,6 +187,8 @@ protected function _addAdditionalFormElementData(AbstractElement $element) * Return Form Elements values * * @return array + * @throws LocalizedException + * @throws NoSuchEntityException */ public function getFormValues() { @@ -183,7 +202,7 @@ public function getFormValues() ? $this->_extensibleDataObjectConverter->toFlatArray( $customer, [], - \Magento\Customer\Api\Data\CustomerInterface::class + CustomerInterface::class ) : []; foreach ($this->getQuote()->getData() as $key => $value) { @@ -193,7 +212,7 @@ public function getFormValues() } if (array_key_exists('group_id', $data) && empty($data['group_id'])) { - $data['group_id'] = $this->groupManagement->getDefaultGroup($this->getQuote()->getStoreId())->getId(); + $data['group_id'] = $this->getSelectedGroupId(); } if ($this->getQuote()->getCustomerEmail()) { @@ -208,6 +227,8 @@ public function getFormValues() * * @param array $attributes * @return array + * @throws LocalizedException + * @throws NoSuchEntityException */ private function extractValuesFromAttributes(array $attributes): array { @@ -231,7 +252,24 @@ private function isEmailRequiredToCreateOrder() { return $this->_scopeConfig->getValue( self::XML_PATH_EMAIL_REQUIRED_CREATE_ORDER, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ScopeInterface::SCOPE_STORE ); } + + /** + * Retrieve selected group id + * + * @return string + * @throws LocalizedException + * @throws NoSuchEntityException + */ + private function getSelectedGroupId(): string + { + $selectedGroupId = $this->groupManagement->getDefaultGroup($this->getQuote()->getStoreId())->getId(); + $orderDetails = $this->getRequest()->getParam('order'); + if (!empty($orderDetails) && !empty($orderDetails['account']['group_id'])) { + $selectedGroupId = $orderDetails['account']['group_id']; + } + return $selectedGroupId; + } } diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Index.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Index.php index 634ecd50c6f9a..e6f470f5c1a85 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Index.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Index.php @@ -20,6 +20,12 @@ class Index extends \Magento\Sales\Controller\Adminhtml\Order\Create implements public function execute() { $this->_initSession(); + + // Clear existing order in session when creating a new order for a customer + if ($this->getRequest()->getParam('customer_id')) { + $this->_getSession()->setOrderId(null); + } + $this->_getOrderCreateModel()->initRuleData(); /** @var \Magento\Backend\Model\View\Result\Page $resultPage */ $resultPage = $this->resultPageFactory->create(); diff --git a/app/code/Magento/Sales/Model/AdminOrder/Create.php b/app/code/Magento/Sales/Model/AdminOrder/Create.php index 1f23e4480ec1c..3e708d1a0df02 100644 --- a/app/code/Magento/Sales/Model/AdminOrder/Create.php +++ b/app/code/Magento/Sales/Model/AdminOrder/Create.php @@ -674,7 +674,7 @@ public function initFromOrderItem(\Magento\Sales\Model\Order\Item $orderItem, $q if (in_array($option['option_type'], ['date', 'date_time', 'time', 'file'])) { $product->setSkipCheckRequiredOption(false); $formattedOptions[$option['option_id']] = - $buyRequest->getDataByKey('options')[$option['option_id']]; + $buyRequest->getDataByKey('options')[$option['option_id']]; continue; } @@ -1661,7 +1661,8 @@ public function setAccountData($accountData) // emulate request $request = $form->prepareRequest($accountData); - $data = $form->extractData($request); + $requestScope = $request->getPostValue() ? 'order/account' : null; + $data = $form->extractData($request, $requestScope); $data = $form->restoreData($data); $customer = $this->customerFactory->create(); $this->dataObjectHelper->populateWithArray( diff --git a/app/code/Magento/Sales/Model/AdminOrder/EmailSender.php b/app/code/Magento/Sales/Model/AdminOrder/EmailSender.php index 4e068eb571deb..2e9518600c27f 100644 --- a/app/code/Magento/Sales/Model/AdminOrder/EmailSender.php +++ b/app/code/Magento/Sales/Model/AdminOrder/EmailSender.php @@ -5,10 +5,13 @@ */ namespace Magento\Sales\Model\AdminOrder; -use Psr\Log\LoggerInterface as Logger; +use Magento\Framework\Exception\MailException; use Magento\Framework\Message\ManagerInterface; use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Email\Sender\InvoiceSender; use Magento\Sales\Model\Order\Email\Sender\OrderSender; +use Magento\Sales\Model\Order\Invoice; +use Psr\Log\LoggerInterface as Logger; /** * Class EmailSender @@ -30,21 +33,31 @@ class EmailSender */ protected $orderSender; + /** + * @var InvoiceSender + */ + private $invoiceSender; + /** * @param ManagerInterface $messageManager * @param Logger $logger * @param OrderSender $orderSender + * @param InvoiceSender $invoiceSender */ - public function __construct(ManagerInterface $messageManager, Logger $logger, OrderSender $orderSender) - { + public function __construct( + ManagerInterface $messageManager, + Logger $logger, + OrderSender $orderSender, + InvoiceSender $invoiceSender + ) { $this->messageManager = $messageManager; $this->logger = $logger; $this->orderSender = $orderSender; + $this->invoiceSender = $invoiceSender; } /** - * Send email about new order. - * Process mail exception + * Send email about new order and handle mail exception * * @param Order $order * @return bool @@ -53,7 +66,8 @@ public function send(Order $order) { try { $this->orderSender->send($order); - } catch (\Magento\Framework\Exception\MailException $exception) { + $this->sendInvoiceEmail($order); + } catch (MailException $exception) { $this->logger->critical($exception); $this->messageManager->addWarningMessage( __('You did not email your customer. Please check your email settings.') @@ -63,4 +77,19 @@ public function send(Order $order) return true; } + + /** + * Send email about invoice paying + * + * @param Order $order + */ + private function sendInvoiceEmail(Order $order): void + { + foreach ($order->getInvoiceCollection()->getItems() as $invoice) { + /** @var Invoice $invoice */ + if ($invoice->getState() === Invoice::STATE_PAID) { + $this->invoiceSender->send($invoice); + } + } + } } diff --git a/app/code/Magento/Sales/Model/EmailSenderHandler.php b/app/code/Magento/Sales/Model/EmailSenderHandler.php index 7c7005bf0da75..a201c1285ae49 100644 --- a/app/code/Magento/Sales/Model/EmailSenderHandler.php +++ b/app/code/Magento/Sales/Model/EmailSenderHandler.php @@ -5,7 +5,11 @@ */ namespace Magento\Sales\Model; +use Magento\Framework\App\Config\ValueFactory; +use Magento\Framework\App\Config\ValueInterface; +use Magento\Framework\App\ObjectManager; use Magento\Sales\Model\Order\Email\Container\IdentityInterface; +use Magento\Sales\Model\ResourceModel\Collection\AbstractCollection; /** * Sales emails sending @@ -32,7 +36,7 @@ class EmailSenderHandler /** * Entity collection model. * - * @var \Magento\Sales\Model\ResourceModel\Collection\AbstractCollection + * @var AbstractCollection */ protected $entityCollection; @@ -53,32 +57,50 @@ class EmailSenderHandler */ private $storeManager; + /** + * Config data factory + * + * @var ValueFactory + */ + private $configValueFactory; + + /** + * @var string + */ + private $modifyStartFromDate; + /** * @param \Magento\Sales\Model\Order\Email\Sender $emailSender * @param \Magento\Sales\Model\ResourceModel\EntityAbstract $entityResource - * @param \Magento\Sales\Model\ResourceModel\Collection\AbstractCollection $entityCollection + * @param AbstractCollection $entityCollection * @param \Magento\Framework\App\Config\ScopeConfigInterface $globalConfig * @param IdentityInterface|null $identityContainer - * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @throws \InvalidArgumentException + * @param \Magento\Store\Model\StoreManagerInterface|null $storeManager + * @param ValueFactory|null $configValueFactory + * @param string|null $modifyStartFromDate */ public function __construct( \Magento\Sales\Model\Order\Email\Sender $emailSender, \Magento\Sales\Model\ResourceModel\EntityAbstract $entityResource, - \Magento\Sales\Model\ResourceModel\Collection\AbstractCollection $entityCollection, + AbstractCollection $entityCollection, \Magento\Framework\App\Config\ScopeConfigInterface $globalConfig, IdentityInterface $identityContainer = null, - \Magento\Store\Model\StoreManagerInterface $storeManager = null + \Magento\Store\Model\StoreManagerInterface $storeManager = null, + ?ValueFactory $configValueFactory = null, + ?string $modifyStartFromDate = null ) { $this->emailSender = $emailSender; $this->entityResource = $entityResource; $this->entityCollection = $entityCollection; $this->globalConfig = $globalConfig; - $this->identityContainer = $identityContainer ?: \Magento\Framework\App\ObjectManager::getInstance() + $this->identityContainer = $identityContainer ?: ObjectManager::getInstance() ->get(\Magento\Sales\Model\Order\Email\Container\NullIdentity::class); - $this->storeManager = $storeManager ?: \Magento\Framework\App\ObjectManager::getInstance() + $this->storeManager = $storeManager ?: ObjectManager::getInstance() ->get(\Magento\Store\Model\StoreManagerInterface::class); + + $this->configValueFactory = $configValueFactory ?: ObjectManager::getInstance()->get(ValueFactory::class); + $this->modifyStartFromDate = $modifyStartFromDate ?: $this->modifyStartFromDate; } /** @@ -90,6 +112,7 @@ public function sendEmails() if ($this->globalConfig->getValue('sales_email/general/async_sending')) { $this->entityCollection->addFieldToFilter('send_email', ['eq' => 1]); $this->entityCollection->addFieldToFilter('email_sent', ['null' => true]); + $this->filterCollectionByStartFromDate($this->entityCollection); $this->entityCollection->setPageSize( $this->globalConfig->getValue('sales_email/general/sending_limit') ); @@ -126,7 +149,7 @@ public function sendEmails() * @throws \Magento\Framework\Exception\NoSuchEntityException */ private function getStores( - \Magento\Sales\Model\ResourceModel\Collection\AbstractCollection $entityCollection + AbstractCollection $entityCollection ): array { $stores = []; @@ -140,4 +163,26 @@ private function getStores( return $stores; } + + /** + * Filter collection by start from date + * + * @param AbstractCollection $collection + * @return void + */ + private function filterCollectionByStartFromDate(AbstractCollection $collection): void + { + /** @var $configValue ValueInterface */ + $configValue = $this->configValueFactory->create(); + $configValue->load('sales_email/general/async_sending', 'path'); + + if ($configValue->getId()) { + $startFromDate = date( + 'Y-m-d H:i:s', + strtotime($configValue->getUpdatedAt() . ' ' . $this->modifyStartFromDate) + ); + + $collection->addFieldToFilter('created_at', ['from' => $startFromDate]); + } + } } diff --git a/app/code/Magento/Sales/Model/Order/Shipment/ShipmentItemsValidator.php b/app/code/Magento/Sales/Model/Order/Shipment/ShipmentItemsValidator.php new file mode 100644 index 0000000000000..2112ef6066bd3 --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Shipment/ShipmentItemsValidator.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Model\Order\Shipment; + +use Magento\Framework\Exception\ConfigurationMismatchException; +use Magento\Sales\Model\ValidatorInterface; +use Magento\Sales\Model\ValidatorResultInterface; +use Magento\Sales\Model\ValidatorResultInterfaceFactory; + +/** + * Requested shipment items validation interface + */ +class ShipmentItemsValidator implements ShipmentItemsValidatorInterface +{ + /** + * @var ValidatorInterface[] + */ + private $validators; + + /** + * @var ValidatorResultInterfaceFactory + */ + private $validatorResultFactory; + + /** + * @param ValidatorResultInterfaceFactory $validatorResult + * @param ValidatorInterface[] $validators + */ + public function __construct(ValidatorResultInterfaceFactory $validatorResult, array $validators = []) + { + $this->validatorResultFactory = $validatorResult; + $this->validators = $validators; + } + + /** + * @inheritdoc + */ + public function validate(array $items): ValidatorResultInterface + { + $messages = []; + foreach ($this->validators as $validator) { + if (!$validator instanceof ValidatorInterface) { + throw new ConfigurationMismatchException( + __( + 'The "%1" validator is not an instance of the general validator interface.', + get_class($validator) + ) + ); + } + foreach ($items as $item) { + $messages[] = $validator->validate($item); + } + } + + return $this->validatorResultFactory->create(['messages' => array_merge([], ...$messages)]); + } +} diff --git a/app/code/Magento/Sales/Model/Order/Shipment/ShipmentItemsValidatorInterface.php b/app/code/Magento/Sales/Model/Order/Shipment/ShipmentItemsValidatorInterface.php new file mode 100644 index 0000000000000..557feacb2de48 --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Shipment/ShipmentItemsValidatorInterface.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Model\Order\Shipment; + +use Magento\Framework\Exception\ConfigurationMismatchException; +use Magento\Sales\Api\Data\OrderItemInterface; +use Magento\Sales\Model\ValidatorResultInterface; + +/** + * Represents shipment creation items validator interface + */ +interface ShipmentItemsValidatorInterface +{ + /** + * Validates shipment items creations + * + * @param OrderItemInterface[] $items + * @return ValidatorResultInterface + * @throws ConfigurationMismatchException + */ + public function validate(array $items): ValidatorResultInterface; +} diff --git a/app/code/Magento/Sales/Model/Order/Validation/ShipOrder.php b/app/code/Magento/Sales/Model/Order/Validation/ShipOrder.php index f043475d4ea8c..a90630d560ad4 100644 --- a/app/code/Magento/Sales/Model/Order/Validation/ShipOrder.php +++ b/app/code/Magento/Sales/Model/Order/Validation/ShipOrder.php @@ -5,16 +5,23 @@ */ namespace Magento\Sales\Model\Order\Validation; +use Magento\Framework\App\ObjectManager; use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\Data\OrderItemInterface; +use Magento\Sales\Api\Data\ShipmentCommentCreationInterface; +use Magento\Sales\Api\Data\ShipmentCreationArgumentsInterface; use Magento\Sales\Api\Data\ShipmentInterface; +use Magento\Sales\Api\Data\ShipmentItemCreationInterface; +use Magento\Sales\Model\Order\Shipment\ShipmentItemsValidatorInterface; use Magento\Sales\Model\Order\Shipment\Validation\QuantityValidator; use Magento\Sales\Model\Order\OrderValidatorInterface; use Magento\Sales\Model\Order\Shipment\ShipmentValidatorInterface; use Magento\Sales\Model\Order\Shipment\Validation\TrackValidator; +use Magento\Sales\Model\ValidatorResultInterface; use Magento\Sales\Model\ValidatorResultMerger; /** - * Class ShipOrder + * Ship order validation class */ class ShipOrder implements ShipOrderInterface { @@ -34,33 +41,42 @@ class ShipOrder implements ShipOrderInterface private $validatorResultMerger; /** - * ShipOrder constructor. - * + * @var ShipmentItemsValidatorInterface + */ + private $itemsValidator; + + /** * @param OrderValidatorInterface $orderValidator * @param ShipmentValidatorInterface $shipmentValidator * @param ValidatorResultMerger $validatorResultMerger + * @param ShipmentItemsValidatorInterface|null $itemsValidator */ public function __construct( OrderValidatorInterface $orderValidator, ShipmentValidatorInterface $shipmentValidator, - ValidatorResultMerger $validatorResultMerger + ValidatorResultMerger $validatorResultMerger, + ShipmentItemsValidatorInterface $itemsValidator = null ) { $this->orderValidator = $orderValidator; $this->shipmentValidator = $shipmentValidator; $this->validatorResultMerger = $validatorResultMerger; + $this->itemsValidator = $itemsValidator + ?? ObjectManager::getInstance()->get(ShipmentItemsValidatorInterface::class); } /** + * Order shipment validate + * * @param OrderInterface $order * @param ShipmentInterface $shipment * @param array $items * @param bool $notify * @param bool $appendComment - * @param \Magento\Sales\Api\Data\ShipmentCommentCreationInterface|null $comment + * @param ShipmentCommentCreationInterface|null $comment * @param array $tracks * @param array $packages - * @param \Magento\Sales\Api\Data\ShipmentCreationArgumentsInterface|null $arguments - * @return \Magento\Sales\Model\ValidatorResultInterface + * @param ShipmentCreationArgumentsInterface|null $arguments + * @return ValidatorResultInterface */ public function validate( $order, @@ -68,10 +84,10 @@ public function validate( array $items = [], $notify = false, $appendComment = false, - \Magento\Sales\Api\Data\ShipmentCommentCreationInterface $comment = null, + ShipmentCommentCreationInterface $comment = null, array $tracks = [], array $packages = [], - \Magento\Sales\Api\Data\ShipmentCreationArgumentsInterface $arguments = null + ShipmentCreationArgumentsInterface $arguments = null ) { $orderValidationResult = $this->orderValidator->validate( $order, @@ -87,6 +103,39 @@ public function validate( ] ); - return $this->validatorResultMerger->merge($orderValidationResult, $shipmentValidationResult); + $orderItems = $this->getRequestedOrderItems($items, $order); + $itemsValidationResult = $this->itemsValidator->validate($orderItems); + + return $this->validatorResultMerger->merge( + $orderValidationResult, + $shipmentValidationResult, + $itemsValidationResult->getMessages() + ); + } + + /** + * Return requested order items + * + * @param OrderItemInterface[] $items + * @param OrderInterface $order + * @return OrderItemInterface[] + */ + private function getRequestedOrderItems(array $items, OrderInterface $order): array + { + $requestedItemIds = array_reduce( + $items, + function (array $result, ShipmentItemCreationInterface $item): array { + $result[] = $item->getOrderItemId(); + return $result; + }, + [] + ); + + return array_filter( + $order->getAllItems(), + function (OrderItemInterface $orderItem) use ($requestedItemIds): bool { + return in_array($orderItem->getId(), $requestedItemIds); + } + ); } } diff --git a/app/code/Magento/Sales/Model/ValidatorResult.php b/app/code/Magento/Sales/Model/ValidatorResult.php index b326ebb4b8b37..feb93c02d5aae 100644 --- a/app/code/Magento/Sales/Model/ValidatorResult.php +++ b/app/code/Magento/Sales/Model/ValidatorResult.php @@ -6,14 +6,22 @@ namespace Magento\Sales\Model; /** - * Class ValidatorResult + * Validation result messages class */ class ValidatorResult implements ValidatorResultInterface { /** * @var \string[] */ - private $messages = []; + private $messages; + + /** + * @param array $messages + */ + public function __construct(array $messages = []) + { + $this->messages = $messages; + } /** * @inheritdoc @@ -24,7 +32,7 @@ public function addMessage($message) } /** - * @return bool + * @inheritdoc */ public function hasMessages() { @@ -32,7 +40,7 @@ public function hasMessages() } /** - * @return \string[] + * @inheritdoc */ public function getMessages() { diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminClickGetShippingMethodsAndRatesActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminClickGetShippingMethodsAndRatesActionGroup.xml new file mode 100644 index 0000000000000..790b0db584e4f --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminClickGetShippingMethodsAndRatesActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminClickGetShippingMethodsAndRatesActionGroup"> + <annotations> + <description>Click 'Get Shipping Methods And Rates' button on the admin create order page.</description> + </annotations> + + <click selector="{{AdminInvoicePaymentShippingSection.getShippingMethodAndRates}}" stepKey="openShippingMethod"/> + <waitForPageLoad stepKey="waitForShippingMethods"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOpenOrderCommentsHistoryActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOpenOrderCommentsHistoryActionGroup.xml new file mode 100644 index 0000000000000..a33abcc03c31f --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOpenOrderCommentsHistoryActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOpenOrderCommentsHistoryActionGroup"> + <annotations> + <description>Clicks on the 'Comments History' on Admin View Order page.</description> + </annotations> + <click selector="{{AdminOrderDetailsOrderViewSection.commentsHistory}}" stepKey="clickOnTabCommentsHistory"/> + <waitForPageLoad stepKey="waitForComments"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminSelectFixedShippingMethodActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminSelectFixedShippingMethodActionGroup.xml new file mode 100644 index 0000000000000..6b1f882eee5f7 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminSelectFixedShippingMethodActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminSelectFixedShippingMethodActionGroup"> + <annotations> + <description>Select Fixed Shipping Method on the admin create order page.</description> + </annotations> + + <click selector="{{AdminInvoicePaymentShippingSection.shippingMethod}}" stepKey="chooseShippingMethod"/> + <waitForPageLoad stepKey="waitForShippingMethodLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminSubmitOrderActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminSubmitOrderActionGroup.xml index f53e41386a389..3670839545e82 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminSubmitOrderActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminSubmitOrderActionGroup.xml @@ -9,6 +9,7 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AdminSubmitOrderActionGroup"> + <scrollTo selector="{{OrdersGridSection.submitOrder}}" stepKey="scrollToSubmitButton"/> <click selector="{{OrdersGridSection.submitOrder}}" stepKey="submitOrder"/> <waitForPageLoad stepKey="waitForPageLoad"/> <see stepKey="seeSuccessMessageForOrder" userInput="You created the order."/> diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/SubmitCreditMemoActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/SubmitCreditMemoActionGroup.xml index 2605adbfc91a7..5e47f5fae2b93 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/SubmitCreditMemoActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/SubmitCreditMemoActionGroup.xml @@ -9,9 +9,12 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="SubmitCreditMemoActionGroup"> + <arguments> + <argument name="refundButton" defaultValue="{{AdminCreditMemoTotalSection.submitRefundOfflineEnabled}}" type="string"/> + </arguments> <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="grabOrderId"/> - <waitForElementVisible selector="{{AdminCreditMemoTotalSection.submitRefundOfflineEnabled}}" stepKey="waitButtonEnabled"/> - <click selector="{{AdminCreditMemoTotalSection.submitRefundOfflineEnabled}}" stepKey="clickSubmitCreditMemo"/> + <waitForElementVisible selector="{{refundButton}}" stepKey="waitButtonEnabled"/> + <click selector="{{refundButton}}" stepKey="clickSubmitCreditMemo"/> <waitForElementVisible selector="{{AdminMessagesSection.success}}" stepKey="waitForMessageAppears"/> <see selector="{{AdminMessagesSection.success}}" userInput="You created the credit memo." stepKey="seeCreditMemoCreateSuccess"/> <seeInCurrentUrl url="{{AdminOrderDetailsPage.url}}$grabOrderId" stepKey="seeViewOrderPageCreditMemo"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Page/AdminShipmentPage.xml b/app/code/Magento/Sales/Test/Mftf/Page/AdminShipmentPage.xml index d35a8ab5c4538..41d58bb61942a 100644 --- a/app/code/Magento/Sales/Test/Mftf/Page/AdminShipmentPage.xml +++ b/app/code/Magento/Sales/Test/Mftf/Page/AdminShipmentPage.xml @@ -10,5 +10,6 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminShipmentPage" url="sales/shipment/" area="admin" module="Magento_Sales"> <section name="AdminShipmentGridSection"/> + <section name="AdminShipmentsGridFiltersSection"/> </page> </pages> diff --git a/app/code/Magento/Sales/Test/Mftf/Page/StorefrontCustomStoreCustomerOrdersHistoryPage.xml b/app/code/Magento/Sales/Test/Mftf/Page/StorefrontCustomStoreCustomerOrdersHistoryPage.xml new file mode 100644 index 0000000000000..8393d4b490924 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Page/StorefrontCustomStoreCustomerOrdersHistoryPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="StorefrontCustomStoreCustomerOrdersHistoryPage" url="/{{storeCode}}/sales/order/history/" module="Magento_Sales" area="storefront" parameterized="true"> + <section name="StorefrontCustomerOrdersGridSection"/> + </page> +</pages> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminShipmentsGridFiltersSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminShipmentsGridFiltersSection.xml new file mode 100644 index 0000000000000..ad1ec54e213ee --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminShipmentsGridFiltersSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminShipmentsGridFiltersSection"> + <element name="orderNumber" type="input" selector="input[name='order_increment_id']"/> + </section> +</sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceAndCheckInvoiceOrderTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceAndCheckInvoiceOrderTest.xml index de6e7ff22b7af..42faab9dc3f07 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceAndCheckInvoiceOrderTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceAndCheckInvoiceOrderTest.xml @@ -65,10 +65,10 @@ <actionGroup ref="SelectBankTransferPaymentMethodActionGroup" stepKey="selectPaymentMethod"/> <!-- Select shipping method --> - <click selector="{{AdminInvoicePaymentShippingSection.getShippingMethodAndRates}}" stepKey="openShippingMethod"/> - <waitForPageLoad stepKey="waitForShippingMethods"/> - <click selector="{{AdminInvoicePaymentShippingSection.shippingMethod}}" stepKey="chooseShippingMethod"/> - <waitForPageLoad stepKey="waitForShippingMethodLoad"/> + <actionGroup ref="AdminClickGetShippingMethodsAndRatesActionGroup" stepKey="openShippingMethod"/> + <comment userInput="Adding the comment to replace action for preserving Backward Compatibility" stepKey="waitForShippingMethods"/> + <actionGroup ref="AdminSelectFixedShippingMethodActionGroup" stepKey="chooseShippingMethod"/> + <comment userInput="Adding the comment to replace action for preserving Backward Compatibility" stepKey="waitForShippingMethodLoad"/> <!-- Submit order --> <actionGroup ref="AdminSubmitOrderActionGroup" stepKey="submitOrder"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceWithCashOnDeliveryPaymentMethodTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceWithCashOnDeliveryPaymentMethodTest.xml index 57e42e9b190e3..f8b171a587e09 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceWithCashOnDeliveryPaymentMethodTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceWithCashOnDeliveryPaymentMethodTest.xml @@ -62,10 +62,10 @@ </actionGroup> <!-- Select shipping method --> - <click selector="{{AdminInvoicePaymentShippingSection.getShippingMethodAndRates}}" stepKey="openShippingMethod"/> - <waitForPageLoad stepKey="waitForShippingMethods"/> - <click selector="{{AdminInvoicePaymentShippingSection.shippingMethod}}" stepKey="chooseShippingMethod"/> - <waitForPageLoad stepKey="waitForShippingMethodLoad"/> + <actionGroup ref="AdminClickGetShippingMethodsAndRatesActionGroup" stepKey="openShippingMethod"/> + <comment userInput="Adding the comment to replace action for preserving Backward Compatibility" stepKey="waitForShippingMethods"/> + <actionGroup ref="AdminSelectFixedShippingMethodActionGroup" stepKey="chooseShippingMethod"/> + <comment userInput="Adding the comment to replace action for preserving Backward Compatibility" stepKey="waitForShippingMethodLoad"/> <!-- Select cash on delivery payment method --> <actionGroup ref="SelectCashOnDeliveryPaymentMethodActionGroup" stepKey="selectPaymentMethod"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceWithShipmentAndCheckInvoicedOrderTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceWithShipmentAndCheckInvoicedOrderTest.xml index 10c6be60f5ba1..cfc42c98a5e71 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceWithShipmentAndCheckInvoicedOrderTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceWithShipmentAndCheckInvoicedOrderTest.xml @@ -59,10 +59,10 @@ </actionGroup> <!-- Select shipping method --> - <click selector="{{AdminInvoicePaymentShippingSection.getShippingMethodAndRates}}" stepKey="openShippingMethod"/> - <waitForPageLoad stepKey="waitForShippingMethods"/> - <click selector="{{AdminInvoicePaymentShippingSection.shippingMethod}}" stepKey="chooseShippingMethod"/> - <waitForPageLoad stepKey="waitForShippingMethodLoad"/> + <actionGroup ref="AdminClickGetShippingMethodsAndRatesActionGroup" stepKey="openShippingMethod"/> + <comment userInput="Adding the comment to replace action for preserving Backward Compatibility" stepKey="waitForShippingMethods"/> + <actionGroup ref="AdminSelectFixedShippingMethodActionGroup" stepKey="chooseShippingMethod"/> + <comment userInput="Adding the comment to replace action for preserving Backward Compatibility" stepKey="waitForShippingMethodLoad"/> <!-- Submit order --> <actionGroup ref="AdminSubmitOrderActionGroup" stepKey="submitOrder"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml index 77b119dd583de..367c50359701c 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml @@ -182,8 +182,8 @@ <!-- Select Free Shipping --> <waitForElementVisible selector="{{AdminInvoicePaymentShippingSection.getShippingMethodAndRates}}" stepKey="waitForShippingSection"/> - <click selector="{{AdminInvoicePaymentShippingSection.getShippingMethodAndRates}}" stepKey="openShippingMethod"/> - <waitForPageLoad stepKey="waitForShippingMethods"/> + <actionGroup ref="AdminClickGetShippingMethodsAndRatesActionGroup" stepKey="openShippingMethod"/> + <comment userInput="Adding the comment to replace action for preserving Backward Compatibility" stepKey="waitForShippingMethods"/> <click selector="{{AdminOrderFormPaymentSection.freeShippingOption}}" stepKey="chooseShippingMethod"/> <waitForPageLoad stepKey="waitForPageToLoad"/> <actionGroup ref="AdminOrderClickSubmitOrderActionGroup" stepKey="submitOrder" /> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedConfigurableProductOnOrderPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedConfigurableProductOnOrderPageTest.xml index 8e9e117d2d995..b0a63e7326cdf 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedConfigurableProductOnOrderPageTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedConfigurableProductOnOrderPageTest.xml @@ -84,10 +84,10 @@ </actionGroup> <!-- Select shipping method --> - <click selector="{{AdminInvoicePaymentShippingSection.getShippingMethodAndRates}}" stepKey="openShippingMethod"/> - <waitForPageLoad stepKey="waitForShippingMethods"/> - <click selector="{{AdminInvoicePaymentShippingSection.shippingMethod}}" stepKey="chooseShippingMethod"/> - <waitForPageLoad stepKey="waitForShippingMethodLoad"/> + <actionGroup ref="AdminClickGetShippingMethodsAndRatesActionGroup" stepKey="openShippingMethod"/> + <comment userInput="Adding the comment to replace action for preserving Backward Compatibility" stepKey="waitForShippingMethods"/> + <actionGroup ref="AdminSelectFixedShippingMethodActionGroup" stepKey="chooseShippingMethod"/> + <comment userInput="Adding the comment to replace action for preserving Backward Compatibility" stepKey="waitForShippingMethodLoad"/> <!-- Submit order --> <actionGroup ref="AdminSubmitOrderActionGroup" stepKey="submitOrder"/> diff --git a/app/code/Magento/Sales/Test/Unit/Model/AdminOrder/CreateTest.php b/app/code/Magento/Sales/Test/Unit/Model/AdminOrder/CreateTest.php index c587d2322c298..36f5b7c9f4cdd 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/AdminOrder/CreateTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/AdminOrder/CreateTest.php @@ -212,10 +212,15 @@ public function testSetAccountData() ->method('restoreData') ->willReturn(['group_id' => 1]); + $requestMock = $this->getMockBuilder(RequestInterface::class) + ->setMethods(['getPostValue']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $requestMock->expects($this->atLeastOnce())->method('getPostValue')->willReturn(null); $customerForm->method('prepareRequest') - ->willReturn($this->getMockForAbstractClass(RequestInterface::class)); + ->willReturn($requestMock); - $customer = $this->getMockForAbstractClass(CustomerInterface::class); + $customer = $this->createMock(CustomerInterface::class); $this->customerMapper->expects(self::atLeastOnce()) ->method('toFlatArray') ->willReturn(['group_id' => 1]); diff --git a/app/code/Magento/Sales/Test/Unit/Model/AdminOrder/EmailSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/AdminOrder/EmailSenderTest.php index 0e80ab7825748..584ce502c5efd 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/AdminOrder/EmailSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/AdminOrder/EmailSenderTest.php @@ -11,59 +11,81 @@ use Magento\Framework\Message\Manager; use Magento\Sales\Model\AdminOrder\EmailSender; use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Email\Sender\InvoiceSender; use Magento\Sales\Model\Order\Email\Sender\OrderSender; +use Magento\Sales\Model\Order\Invoice; +use Magento\Sales\Model\ResourceModel\Order\Invoice\Collection as InvoiceCollection; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; +/** + * Tests to sent order emails + */ class EmailSenderTest extends TestCase { /** - * @var MockObject + * @var LoggerInterface|MockObject */ - protected $orderMock; + private $loggerMock; /** - * @var MockObject + * @var Manager|MockObject */ - protected $loggerMock; + private $messageManagerMock; /** - * @var MockObject + * @var OrderSender|MockObject */ - protected $messageManagerMock; + private $orderSenderMock; /** - * @var EmailSender + * @var InvoiceSender|MockObject */ - protected $emailSender; + private $invoiceSenderMock; /** - * @var OrderSender + * @var EmailSender */ - protected $orderSenderMock; + private $emailSender; /** - * Test setup + * @inheritdoc */ protected function setUp(): void { $this->messageManagerMock = $this->createMock(Manager::class); $this->loggerMock = $this->getMockForAbstractClass(LoggerInterface::class); - $this->orderMock = $this->createMock(Order::class); $this->orderSenderMock = $this->createMock(OrderSender::class); + $this->invoiceSenderMock = $this->createMock(InvoiceSender::class); - $this->emailSender = new EmailSender($this->messageManagerMock, $this->loggerMock, $this->orderSenderMock); + $this->emailSender = new EmailSender( + $this->messageManagerMock, + $this->loggerMock, + $this->orderSenderMock, + $this->invoiceSenderMock + ); } /** - * testSendSuccess + * Test to send order emails */ public function testSendSuccess() { + $invoicePaid = $this->createMock(Invoice::class); + $invoicePaid->method('getState')->willReturn(Invoice::STATE_PAID); + $invoiceOpen = $this->createMock(Invoice::class); + $invoiceOpen->method('getState')->willReturn(Invoice::STATE_OPEN); + $order = $this->createOrderMock([$invoiceOpen, $invoicePaid]); + $this->orderSenderMock->expects($this->once()) - ->method('send'); - $this->assertTrue($this->emailSender->send($this->orderMock)); + ->method('send') + ->with($order); + $this->invoiceSenderMock->expects($this->once()) + ->method('send') + ->with($invoicePaid); + + $this->assertTrue($this->emailSender->send($order)); } /** @@ -71,6 +93,7 @@ public function testSendSuccess() */ public function testSendFailure() { + $orderMock = $this->createOrderMock(); $this->orderSenderMock->expects($this->once()) ->method('send') ->willThrowException(new MailException(__('test message'))); @@ -79,6 +102,22 @@ public function testSendFailure() $this->loggerMock->expects($this->once()) ->method('critical'); - $this->assertFalse($this->emailSender->send($this->orderMock)); + $this->assertFalse($this->emailSender->send($orderMock)); + } + + /** + * Create order mock + * + * @param array $invoiceCollection + * @return MockObject|Order + */ + private function createOrderMock(array $invoiceCollection = []): MockObject + { + $collection = $this->createMock(InvoiceCollection::class); + $collection->method('getItems')->willReturn($invoiceCollection); + $order = $this->createMock(Order::class); + $order->method('getInvoiceCollection')->willReturn($collection); + + return $order; } } diff --git a/app/code/Magento/Sales/Test/Unit/Model/EmailSenderHandlerTest.php b/app/code/Magento/Sales/Test/Unit/Model/EmailSenderHandlerTest.php index 386a919e41c90..757a026aa5d68 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/EmailSenderHandlerTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/EmailSenderHandlerTest.php @@ -7,7 +7,10 @@ namespace Magento\Sales\Test\Unit\Model; +use Magento\Config\Model\Config\Backend\Encrypted; use Magento\Framework\App\Config; +use Magento\Framework\App\Config\Value; +use Magento\Framework\App\Config\ValueFactory; use Magento\Framework\DB\Select; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Sales\Model\AbstractModel; @@ -71,6 +74,16 @@ class EmailSenderHandlerTest extends TestCase */ private $storeManagerMock; + /** + * @var ValueFactory|MockObject + */ + private $configValueFactory; + + /** + * @var string + */ + private $modifyStartFromDate = '-1 day'; + protected function setUp(): void { $objectManager = new ObjectManager($this); @@ -110,15 +123,21 @@ protected function setUp(): void StoreManagerInterface::class ); + $this->configValueFactory = $this->createMock( + ValueFactory::class + ); + $this->object = $objectManager->getObject( EmailSenderHandler::class, [ - 'emailSender' => $this->emailSender, - 'entityResource' => $this->entityResource, - 'entityCollection' => $this->entityCollection, - 'globalConfig' => $this->globalConfig, - 'identityContainer' => $this->identityContainerMock, - 'storeManager' => $this->storeManagerMock, + 'emailSender' => $this->emailSender, + 'entityResource' => $this->entityResource, + 'entityCollection' => $this->entityCollection, + 'globalConfig' => $this->globalConfig, + 'identityContainer' => $this->identityContainerMock, + 'storeManager' => $this->storeManagerMock, + 'configValueFactory' => $this->configValueFactory, + 'modifyStartFromDate' => $this->modifyStartFromDate ] ); } @@ -151,6 +170,13 @@ public function testExecute($configValue, $collectionItems, $emailSendingResult) ->method('addFieldToFilter') ->with('email_sent', ['null' => true]); + $nowDate = date('Y-m-d H:i:s'); + $fromDate = date('Y-m-d H:i:s', strtotime($nowDate . ' ' . $this->modifyStartFromDate)); + $this->entityCollection + ->expects($this->at(2)) + ->method('addFieldToFilter') + ->with('created_at', ['from' => $fromDate]); + $this->entityCollection ->expects($this->any()) ->method('addAttributeToSelect') @@ -175,6 +201,20 @@ public function testExecute($configValue, $collectionItems, $emailSendingResult) ->method('getItems') ->willReturn($collectionItems); + /** @var Value|Encrypted|MockObject $valueMock */ + $backendModelMock = $this->getMockBuilder(Value::class) + ->disableOriginalConstructor() + ->onlyMethods(['load', 'getId']) + ->addMethods(['getUpdatedAt']) + ->getMock(); + $backendModelMock->expects($this->once())->method('load')->willReturnSelf(); + $backendModelMock->expects($this->once())->method('getId')->willReturn(1); + $backendModelMock->expects($this->once())->method('getUpdatedAt')->willReturn($nowDate); + + $this->configValueFactory->expects($this->once()) + ->method('create') + ->willReturn($backendModelMock); + if ($collectionItems) { /** @var AbstractModel|MockObject $collectionItem */ diff --git a/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php b/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php index 2e51bd8d75b89..41c92da1cde5d 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php @@ -13,8 +13,8 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Payment\Api\Data\PaymentAdditionalInfoInterface; use Magento\Payment\Api\Data\PaymentAdditionalInfoInterfaceFactory; -use Magento\Sales\Api\Data\OrderExtension; use Magento\Sales\Api\Data\OrderExtensionFactory; +use Magento\Sales\Api\Data\OrderExtensionInterface; use Magento\Sales\Api\Data\OrderInterface; use Magento\Sales\Api\Data\OrderPaymentInterface; use Magento\Sales\Api\Data\OrderSearchResultInterfaceFactory as SearchResultFactory; @@ -29,6 +29,7 @@ use Magento\Tax\Api\Data\OrderTaxDetailsInterface; use Magento\Tax\Api\OrderTaxManagementInterface; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\MockObject\RuntimeException; use PHPUnit\Framework\TestCase; /** @@ -111,7 +112,7 @@ protected function setUp(): void 'collectionProcessor' => $this->collectionProcessor, 'orderExtensionFactory' => $this->orderExtensionFactoryMock, 'orderTaxManagement' => $this->orderTaxManagementMock, - 'paymentAdditionalInfoFactory' => $this->paymentAdditionalInfoFactory + 'paymentAdditionalInfoFactory' => $this->paymentAdditionalInfoFactory, ] ); } @@ -138,18 +139,7 @@ public function testGetList() ->disableOriginalConstructor() ->setMethods(['setKey', 'setValue'])->getMockForAbstractClass(); - $extensionAttributes = $this->getMockBuilder(OrderExtension::class) - ->addMethods( - [ - 'getShippingAssignments', - 'setShippingAssignments', - 'setConvertingFromQuote', - 'setAppliedTaxes', - 'setItemAppliedTaxes', - 'setPaymentAdditionalInfo' - ] - ) - ->getMock(); + $extensionAttributes = $this->getOrderExtensionMock(); $shippingAssignmentBuilder = $this->createMock( ShippingAssignmentBuilder::class ); @@ -188,9 +178,7 @@ public function testSave() ->disableOriginalConstructor() ->getMock(); $orderEntity = $this->createMock(Order::class); - $extensionAttributes = $this->getMockBuilder(OrderExtension::class) - ->addMethods(['getShippingAssignments']) - ->getMock(); + $extensionAttributes = $this->getOrderExtensionMock(); $shippingAssignment = $this->getMockBuilder(ShippingAssignment::class) ->disableOriginalConstructor() ->setMethods(['getShipping']) @@ -230,18 +218,7 @@ public function testGet() $paymentMock = $this->getMockBuilder(OrderPaymentInterface::class) ->disableOriginalConstructor()->getMockForAbstractClass(); $paymentMock->expects($this->once())->method('getAdditionalInformation')->willReturn($paymentInfo); - $orderExtension = $this->getMockBuilder(OrderExtension::class) - ->setMethods( - [ - 'getShippingAssignments', - 'setAppliedTaxes', - 'setConvertingFromQuote', - 'setItemAppliedTaxes', - 'setPaymentAdditionalInfo' - ] - ) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); + $orderExtension = $this->getOrderExtensionMock(); $orderExtension->expects($this->once())->method('getShippingAssignments')->willReturn(true); $orderExtension->expects($this->once())->method('setAppliedTaxes')->with($appliedTaxes); $orderExtension->expects($this->once())->method('setConvertingFromQuote')->with(true); @@ -266,4 +243,29 @@ public function testGet() $this->orderRepository->get($orderId); } + + /** + * Buld order extension mock. + * + * @return MockObject + */ + private function getOrderExtensionMock(): MockObject + { + $mockBuilder = $this->getMockBuilder(OrderExtensionInterface::class)->disableOriginalConstructor(); + try { + $mockBuilder->addMethods( + [ + 'getShippingAssignments', + 'setAppliedTaxes', + 'setConvertingFromQuote', + 'setItemAppliedTaxes', + 'setPaymentAdditionalInfo', + ] + ); + } catch (RuntimeException $e) { + // Order extension already generated. + } + + return $mockBuilder->getMockForAbstractClass(); + } } diff --git a/app/code/Magento/Sales/etc/di.xml b/app/code/Magento/Sales/etc/di.xml index b4dadfa944a5b..80bcf29789502 100644 --- a/app/code/Magento/Sales/etc/di.xml +++ b/app/code/Magento/Sales/etc/di.xml @@ -117,6 +117,7 @@ <preference for="Magento\Sales\Api\RefundInvoiceInterface" type="Magento\Sales\Model\RefundInvoice"/> <preference for="Magento\Sales\Model\ResourceModel\Provider\NotSyncedDataProviderInterface" type="Magento\Sales\Model\ResourceModel\Provider\NotSyncedDataProvider" /> <preference for="Magento\Sales\Model\ConfigInterface" type="Magento\Sales\Model\Config" /> + <preference for="Magento\Sales\Model\Order\Shipment\ShipmentItemsValidatorInterface" type="Magento\Sales\Model\Order\Shipment\ShipmentItemsValidator" /> <type name="Magento\Sales\Model\ResourceModel\Provider\NotSyncedDataProvider"> <arguments> <argument name="providers" xsi:type="array"> @@ -215,6 +216,11 @@ </argument> </arguments> </type> + <type name="Magento\Sales\Model\EmailSenderHandler"> + <arguments> + <argument name="modifyStartFromDate" xsi:type="string">-1 day</argument> + </arguments> + </type> <virtualType name="SalesOrderIndexGridSyncRemove" type="Magento\Sales\Observer\GridSyncRemoveObserver"> <arguments> <argument name="entityGrid" xsi:type="object">Magento\Sales\Model\ResourceModel\Order\Grid</argument> diff --git a/app/code/Magento/Sales/view/adminhtml/templates/order/create/form/address.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/create/form/address.phtml index 69b26d70e684a..80083569df889 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/order/create/form/address.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/order/create/form/address.phtml @@ -73,14 +73,14 @@ endif; ?> <input type="checkbox" id="order-shipping_same_as_billing" name="shipping_same_as_billing" class="admin__control-checkbox" <?php if ($block->getIsAsBilling()): ?>checked<?php endif; ?> /> + <label for="order-shipping_same_as_billing" class="admin__field-label"> + <?= $block->escapeHtml(__('Same As Billing Address')) ?> + </label> <?= /* @noEscape */ $secureRenderer->renderEventListenerAsTag( 'onclick', "order.setShippingAsBilling(this.checked)", 'input#order-shipping_same_as_billing' ) ?> - <label for="order-shipping_same_as_billing" class="admin__field-label"> - <?= $block->escapeHtml(__('Same As Billing Address')) ?> - </label> </div> <?php endif; ?> <div class="admin__field admin__field-select-from-existing-address"> diff --git a/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php b/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php index cf6301cb31a9c..62c1cc086048b 100644 --- a/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php +++ b/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php @@ -62,6 +62,7 @@ public function loadAttributeOptions() { $attributes = [ 'base_subtotal_with_discount' => __('Subtotal (Excl. Tax)'), + 'base_subtotal_total_incl_tax' => __('Subtotal (Incl. Tax)'), 'base_subtotal' => __('Subtotal'), 'total_qty' => __('Total Items Quantity'), 'weight' => __('Total Weight'), @@ -99,6 +100,7 @@ public function getInputType() { switch ($this->getAttribute()) { case 'base_subtotal': + case 'base_subtotal_total_incl_tax': case 'weight': case 'total_qty': return 'numeric'; diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCreateCartPriceRuleActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCreateCartPriceRuleActionGroup.xml index 57a2c17419532..f6a705ee368e9 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCreateCartPriceRuleActionGroup.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCreateCartPriceRuleActionGroup.xml @@ -19,6 +19,7 @@ <amOnPage url="{{AdminCartPriceRulesPage.url}}" stepKey="amOnCartPriceList"/> <waitForPageLoad stepKey="waitForPriceList"/> <click selector="{{AdminCartPriceRulesSection.addNewRuleButton}}" stepKey="clickAddNewRule"/> + <waitForElementVisible selector="{{AdminCartPriceRulesFormSection.ruleName}}" stepKey="waitRuleNameFieldAppeared"/> <fillField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{ruleName.name}}" stepKey="fillRuleName"/> <selectOption selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="{{ruleName.websites}}" stepKey="selectWebsites"/> <selectOption selector="{{AdminCartPriceRulesFormSection.customerGroups}}" parameterArray="[{{ruleName.customerGroups}}]" stepKey="selectCustomerGroup"/> @@ -26,6 +27,7 @@ <selectOption selector="{{AdminCartPriceRulesFormSection.apply}}" userInput="{{ruleName.apply}}" stepKey="selectActionType"/> <fillField selector="{{AdminCartPriceRulesFormSection.discountAmount}}" userInput="{{ruleName.discountAmount}}" stepKey="fillDiscountAmount"/> <click selector="{{AdminCartPriceRulesFormSection.save}}" stepKey="clickSaveButton"/> - <see selector="{{AdminCartPriceRulesFormSection.successMessage}}" userInput="You saved the rule." stepKey="seeSuccessMessage"/> + <waitForElementVisible selector="{{AdminMessagesSection.success}}" stepKey="waitForSuccessMessage"/> + <see selector="{{AdminMessagesSection.success}}" userInput="You saved the rule." stepKey="seeSuccessMessage"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontApplyDiscountCodeActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontApplyDiscountCodeActionGroup.xml index c8363a3df6221..5607512c862b3 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontApplyDiscountCodeActionGroup.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontApplyDiscountCodeActionGroup.xml @@ -12,7 +12,7 @@ <arguments> <argument name="discountCode" type="string"/> </arguments> - <click selector="{{DiscountSection.DiscountTab}}" stepKey="clickToAddDiscount"/> + <conditionalClick selector="{{DiscountSection.DiscountTab}}" dependentSelector="{{DiscountSection.CouponInput}}" visible="false" stepKey="clickToAddDiscount"/> <fillField selector="{{DiscountSection.DiscountInput}}" userInput="{{discountCode}}" stepKey="fillFieldDiscountCode"/> <click selector="{{DiscountSection.ApplyCodeBtn}}" stepKey="clickToApplyDiscount"/> <waitForElementVisible selector="{{DiscountSection.DiscountVerificationMsg}}" time="30" stepKey="waitForDiscountToBeAdded"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleAddressConditionsData.xml b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleAddressConditionsData.xml index cc695b347c4fb..727222213b118 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleAddressConditionsData.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleAddressConditionsData.xml @@ -10,6 +10,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="SalesRuleAddressConditions" type="SalesRuleConditionAttribute"> <data key="subtotal">Magento\SalesRule\Model\Rule\Condition\Address|base_subtotal</data> + <data key="base_subtotal_total_incl_tax">Magento\SalesRule\Model\Rule\Condition\Address|base_subtotal_total_incl_tax</data> <data key="totalItemsQty">Magento\SalesRule\Model\Rule\Condition\Address|total_qty</data> <data key="totalWeight">Magento\SalesRule\Model\Rule\Condition\Address|weight</data> <data key="shippingMethod">Magento\SalesRule\Model\Rule\Condition\Address|shipping_method</data> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleWithSubtotalInclTaxTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleWithSubtotalInclTaxTest.xml new file mode 100644 index 0000000000000..a52e8e10459e5 --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleWithSubtotalInclTaxTest.xml @@ -0,0 +1,90 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateCartPriceRuleWithSubtotalInclTaxTest"> + <annotations> + <stories value="Create Sales Rule"/> + <title value="Create Cart Price Rule with Subtotal Incl Tax"/> + <description value="Test that cart price rule with Subtotal Incl Tax works correctly"/> + <severity value="CRITICAL"/> + <useCaseId value="MC-37729"/> + <testCaseId value="MC-38971"/> + <group value="SalesRule"/> + </annotations> + <before> + <!--Login to backend--> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <!--Create tax rate for US-CA-*--> + <createData entity="defaultTaxRate" stepKey="taxRate"/> + <!--Create tax rule--> + <actionGroup ref="AdminCreateTaxRuleActionGroup" stepKey="createTaxRule"> + <argument name="taxRate" value="$$taxRate$$"/> + <argument name="taxRule" value="SimpleTaxRule"/> + </actionGroup> + <!--Create simple product--> + <createData entity="SimpleProduct2" stepKey="product"> + <field key="price">100</field> + </createData> + <!--Create cart price rule with no coupon and 50% discount--> + <createData entity="ApiCartRule" stepKey="createCartPriceRule"/> + <!--Add "subtotal incl tax > 100" condition to cart price rule--> + <amOnPage url="{{AdminCartPriceRuleEditPage.url($$createCartPriceRule.rule_id$$)}}" stepKey="openEditRule"/> + <actionGroup ref="SetCartAttributeConditionForCartPriceRuleActionGroup" stepKey="setCartAttributeConditionForCartPriceRule"> + <argument name="attributeName" value="{{SalesRuleAddressConditions.base_subtotal_total_incl_tax}}"/> + <argument name="operatorType" value="greater than"/> + <argument name="value" value="100"/> + </actionGroup> + </before> + <after> + <!--Delete tax rule--> + <actionGroup ref="AdminDeleteTaxRule" stepKey="deleteTaxRule"> + <argument name="taxRuleCode" value="{{SimpleTaxRule.code}}" /> + </actionGroup> + <!--Delete tax rate--> + <deleteData createDataKey="taxRate" stepKey="deleteTaxRate"/> + <!--Delete product--> + <deleteData createDataKey="product" stepKey="deleteProduct"/> + <!--Delete cart price rule--> + <deleteData createDataKey="createCartPriceRule" stepKey="deleteCartPriceRule"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + </after> + + <!--Open product --> + <actionGroup ref="StorefrontOpenProductEntityPageActionGroup" stepKey="openProduct2Page"> + <argument name="product" value="$$product$$"/> + </actionGroup> + <!--Add to cart --> + <actionGroup ref="StorefrontAddToTheCartActionGroup" stepKey="product2AddToCart"/> + <!--Click on mini cart--> + <actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="clickOnMiniCart"/> + <!--Click on view and edit cart link--> + <actionGroup ref="ClickViewAndEditCartFromMiniCartActionGroup" stepKey="goToShoppingCartFromMinicart"/> + <waitForPageLoad stepKey="waitForViewAndEditCartToOpen"/> + <!--Assert that tax and discount are not applied by default--> + <actionGroup ref="StorefrontCheckCartActionGroup" stepKey="AssertTaxAndDiscountIsNotApplied"> + <argument name="subtotal" value="$100.00"/> + <argument name="shipping" value="$5.00"/> + <argument name="shippingMethod" value="Flat Rate - Fixed"/> + <argument name="total" value="$105.00"/> + </actionGroup> + <dontSee selector="{{CheckoutCartSummarySection.discountAmount}}" stepKey="assertDiscountIsNotApplied"/> + <!--Open "Estimate Shipping and Tax" section and fill US-CA address --> + <actionGroup ref="StorefrontCartEstimateShippingAndTaxActionGroup" stepKey="fillEstimateShippingAndTaxSection"> + <argument name="estimateAddress" value="EstimateAddressCalifornia"/> + </actionGroup> + <!--Assert that tax and discount are applied by to total amount--> + <actionGroup ref="StorefrontCheckCartActionGroup" stepKey="AssertTaxAndDiscountIsApplied"> + <argument name="subtotal" value="$100.00"/> + <argument name="shipping" value="$5.00"/> + <argument name="shippingMethod" value="Flat Rate - Fixed"/> + <argument name="total" value="$60.00"/> + </actionGroup> + <see selector="{{CheckoutCartSummarySection.discountAmount}}" userInput="-$50.00" stepKey="assertDiscountIsApplied"/> + </test> +</tests> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml index 7a995b1feeeda..d76e9edb828bd 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml @@ -10,6 +10,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EndToEndB2CLoggedInUserTest"> <before> + <actionGroup ref="AdminCartPriceRuleDeleteAllActionGroup" after="loginAsAdmin" stepKey="deleteCartPriceRule"/> <createData entity="ApiSalesRule" stepKey="createSalesRule"/> <createData entity="ApiSalesRuleCoupon" stepKey="createSalesRuleCoupon"> <requiredEntity createDataKey="createSalesRule"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontAutoGeneratedCouponCodeTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontAutoGeneratedCouponCodeTest.xml index 74542be376c45..159f2a1a82ece 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontAutoGeneratedCouponCodeTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontAutoGeneratedCouponCodeTest.xml @@ -92,7 +92,7 @@ stepKey="waitForElementDiscountVisible"/> <!-- Step 8. Go to Checkout and Click Place Order button --> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="clickProceedToCheckout"/> <click selector="{{CheckoutShippingMethodsSection.checkShippingMethodByName('Flat Rate')}}" stepKey="selectFlatShippingMethod"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> @@ -132,7 +132,7 @@ stepKey="waitForElementDiscountVisible1"/> <!-- Step 14. Go to Checkout and Click Place Order button --> - <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout1"/> + <actionGroup ref="StorefrontClickProceedToCheckoutActionGroup" stepKey="clickProceedToCheckout1"/> <actionGroup ref="GuestCheckoutFillingShippingSectionActionGroup" stepKey="guestCheckoutFillingShippingSection"> <argument name="customerVar" value="CustomerEntityOne"/> <argument name="customerAddressVar" value="CustomerAddressSimple"/> diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Condition/AddressTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Condition/AddressTest.php new file mode 100644 index 0000000000000..70036c06922c5 --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Condition/AddressTest.php @@ -0,0 +1,65 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\SalesRule\Test\Unit\Model\Rule\Condition; + +use Magento\SalesRule\Model\Rule\Condition\Address; +use PHPUnit\Framework\TestCase; + +/** + * Test for address rule condition + */ +class AddressTest extends TestCase +{ + /** + * @var Address + */ + private $model; + + /** + * @inheritDoc + */ + protected function setUp(): void + { + parent::setUp(); + $context = $this->createMock(\Magento\Rule\Model\Condition\Context::class); + $directoryCountry = $this->createMock(\Magento\Directory\Model\Config\Source\Country::class); + $directoryAllregion = $this->createMock(\Magento\Directory\Model\Config\Source\Allregion::class); + $shippingAllmethods = $this->createMock(\Magento\Shipping\Model\Config\Source\Allmethods::class); + $paymentAllmethods = $this->createMock(\Magento\Payment\Model\Config\Source\Allmethods::class); + $this->model = new Address( + $context, + $directoryCountry, + $directoryAllregion, + $shippingAllmethods, + $paymentAllmethods + ); + } + + /** + * Test that all attributes are present in options list + */ + public function testLoadAttributeOptions(): void + { + $attributes = [ + 'base_subtotal_with_discount', + 'base_subtotal_total_incl_tax', + 'base_subtotal', + 'total_qty', + 'weight', + 'payment_method', + 'shipping_method', + 'postcode', + 'region', + 'region_id', + 'country_id', + ]; + + $this->model->loadAttributeOptions(); + $this->assertEquals($attributes, array_keys($this->model->getAttributeOption())); + } +} diff --git a/app/code/Magento/Search/Model/SynonymAnalyzer.php b/app/code/Magento/Search/Model/SynonymAnalyzer.php index 16d0b0b4ddcd9..f9d18179be4ee 100644 --- a/app/code/Magento/Search/Model/SynonymAnalyzer.php +++ b/app/code/Magento/Search/Model/SynonymAnalyzer.php @@ -137,10 +137,10 @@ private function getSearchPattern(array $words): string { $patterns = []; for ($lastItem = count($words); $lastItem > 0; $lastItem--) { - $words = array_map(function ($word) { + $safeRegexWords = array_map(function ($word) { return preg_quote($word, '/'); }, $words); - $phrase = implode("\s+", \array_slice($words, 0, $lastItem)); + $phrase = implode("\s+", \array_slice($safeRegexWords, 0, $lastItem)); $patterns[] = '^' . $phrase . ','; $patterns[] = ',' . $phrase . ','; $patterns[] = ',' . $phrase . '$'; diff --git a/app/code/Magento/Search/Test/Unit/Model/SynonymAnalyzerTest.php b/app/code/Magento/Search/Test/Unit/Model/SynonymAnalyzerTest.php index 9e6d087f72f99..70e93ca6b4404 100644 --- a/app/code/Magento/Search/Test/Unit/Model/SynonymAnalyzerTest.php +++ b/app/code/Magento/Search/Test/Unit/Model/SynonymAnalyzerTest.php @@ -84,4 +84,42 @@ public function testGetSynonymsForPhraseEmptyPhrase() $actual = $this->synonymAnalyzer->getSynonymsForPhrase($phrase); $this->assertEquals($expected, $actual); } + + /** + * @test + * + * Phrase that is long and has quotes in it scenario + */ + public function testLongQuotedPhrase() + { + $phrase = 'LSS 3/8"X3/4"X25\' EZ-PULL 1/2" INS SWEAT LINESET W/90 END BEND SUCTION LINE INSULATED'; + $expected = [ + 0 => [ 0 => "LSS" ], + 1 => [ 0 => "3/8\"X3/4\"X25'" ], + 2 => [ 0 => "EZ-PULL" ], + 3 => [ 0 => "1/2\"" ], + 4 => [ 0 => "INS" ], + 5 => [ 0 => "SWEAT" ], + 6 => [ 0 => "LINESET" ], + 7 => [ 0 => "W/90" ], + 8 => [ 0 => "END" ], + 9 => [ 0 => "BEND", 1 => "TWIST" ], + 10 => [ 0 => "SUCTION", 1 => "WEIGHT" ], + 11 => [ 0 => "LINE" ], + 12 => [ 0 => "INSULATED" ] + ]; + $this->synReaderModel->expects($this->once()) + ->method('loadByPhrase') + ->with($phrase) + ->willReturnSelf(); + $this->synReaderModel->expects($this->once()) + ->method('getData') + ->willReturn([ + ['synonyms' => 'BEND,TWIST'], + ['synonyms' => 'SUCTION,WEIGHT'], + ]); + + $actual = $this->synonymAnalyzer->getSynonymsForPhrase($phrase); + $this->assertEquals($expected, $actual); + } } diff --git a/app/code/Magento/Tinymce3/Test/Mftf/Test/AdminSwitchWYSIWYGOptionsTest.xml b/app/code/Magento/Tinymce3/Test/Mftf/Test/AdminSwitchWYSIWYGOptionsTest.xml index 0ba20d201f909..0bfdc0eed289c 100644 --- a/app/code/Magento/Tinymce3/Test/Mftf/Test/AdminSwitchWYSIWYGOptionsTest.xml +++ b/app/code/Magento/Tinymce3/Test/Mftf/Test/AdminSwitchWYSIWYGOptionsTest.xml @@ -77,7 +77,7 @@ <waitForPageLoad stepKey="wait6" /> <!--see widget on Storefront--> <see userInput="Hello TinyMCE3!" stepKey="seeContent2"/> - <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> + <actionGroup ref="CliEnableTinyMCE4ActionGroup" stepKey="switchToTinyMCE4" /> <after> <actionGroup ref="DisabledWYSIWYGActionGroup" stepKey="disableWYSIWYG"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> diff --git a/app/code/Magento/Ui/view/base/ui_component/etc/definition.xml b/app/code/Magento/Ui/view/base/ui_component/etc/definition.xml index f0a5f357f8a92..298ae22cb8904 100644 --- a/app/code/Magento/Ui/view/base/ui_component/etc/definition.xml +++ b/app/code/Magento/Ui/view/base/ui_component/etc/definition.xml @@ -141,7 +141,7 @@ </settings> </multiselect> <textarea class="Magento\Ui\Component\Form\Element\Textarea" component="Magento_Ui/js/form/element/textarea" template="ui/form/field"/> - <multiline class="Magento\Ui\Component\Form\Element\Multiline" component="Magento_Ui/js/form/components/group"/> + <multiline class="Magento\Ui\Component\Form\Element\Multiline" component="Magento_Ui/js/form/components/multiline"/> <range class="Magento\Ui\Component\Form\Element\Range" component="Magento_Ui/js/grid/filters/range"/> <fileUploader class="Magento\Ui\Component\Form\Element\DataType\Media" component="Magento_Ui/js/form/element/file-uploader" template="ui/form/element/uploader/uploader"/> <imageUploader class="Magento\Ui\Component\Form\Element\DataType\Media\Image" component="Magento_Ui/js/form/element/image-uploader" template="ui/form/element/uploader/image"> diff --git a/app/code/Magento/Ui/view/base/web/js/form/components/multiline.js b/app/code/Magento/Ui/view/base/web/js/form/components/multiline.js new file mode 100644 index 0000000000000..8c8816a0559a8 --- /dev/null +++ b/app/code/Magento/Ui/view/base/web/js/form/components/multiline.js @@ -0,0 +1,57 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** + * @api + */ +define([ + './group' +], function (Group) { + 'use strict'; + + return Group.extend({ + defaults: { + links: { + value: '${ $.provider }:${ $.dataScope }' + } + }, + + /** + * Initialize Multiline component. + * + * @returns {Object} + */ + initialize: function () { + return this._super() + ._prepareValue(); + }, + + /** + * {@inheritdoc} + */ + initObservable: function () { + this._super() + .observe('value'); + + return this; + }, + + /** + * Prepare value for Multiline options. + * + * @returns {Object} Chainable. + * @private + */ + _prepareValue: function () { + var value = this.value(); + + if (typeof value === 'string') { + this.value(value.split('\n')); + } + + return this; + } + }); +}); diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/date.js b/app/code/Magento/Ui/view/base/web/js/form/element/date.js index 1432372dd75a9..af9142745206b 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/date.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/date.js @@ -127,7 +127,7 @@ define([ if (this.options.showsTime) { shiftedValue = moment.tz(value, 'UTC').tz(this.storeTimeZone); } else { - shiftedValue = moment(value, this.outputDateFormat); + shiftedValue = moment(value, this.outputDateFormat, true); } if (!shiftedValue.isValid()) { diff --git a/app/code/Magento/Ui/view/base/web/js/grid/provider.js b/app/code/Magento/Ui/view/base/web/js/grid/provider.js index 2ba8bd73af910..021ae83eb1c7d 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/provider.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/provider.js @@ -14,7 +14,8 @@ define([ 'uiLayout', 'Magento_Ui/js/modal/alert', 'mage/translate', - 'uiElement' + 'uiElement', + 'Magento_Ui/js/grid/data-storage' ], function ($, _, utils, resolver, layout, alert, $t, Element) { 'use strict'; diff --git a/app/code/Magento/Ups/Model/Carrier.php b/app/code/Magento/Ups/Model/Carrier.php index 28a4a3a7e0b41..452f488050202 100644 --- a/app/code/Magento/Ups/Model/Carrier.php +++ b/app/code/Magento/Ups/Model/Carrier.php @@ -17,6 +17,7 @@ use Magento\Framework\Async\CallbackDeferred; use Magento\Framework\DataObject; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\HTTP\AsyncClient\HttpException; use Magento\Framework\HTTP\AsyncClient\HttpResponseDeferredInterface; use Magento\Framework\HTTP\AsyncClient\Request; use Magento\Framework\HTTP\AsyncClientInterface; @@ -33,6 +34,7 @@ use Magento\Shipping\Model\Rate\Result; use Magento\Shipping\Model\Rate\Result\ProxyDeferredFactory; use Magento\Shipping\Model\Rate\ResultFactory as RateFactory; +use Magento\Shipping\Model\Shipment\Request as Shipment; use Magento\Shipping\Model\Simplexml\Element; use Magento\Shipping\Model\Simplexml\ElementFactory; use Magento\Shipping\Model\Tracking\Result\ErrorFactory as TrackErrorFactory; @@ -40,7 +42,6 @@ use Magento\Shipping\Model\Tracking\ResultFactory as TrackFactory; use Magento\Store\Model\ScopeInterface; use Magento\Ups\Helper\Config; -use Magento\Shipping\Model\Shipment\Request as Shipment; use Psr\Log\LoggerInterface; use RuntimeException; use Throwable; @@ -811,10 +812,15 @@ protected function _getXmlQuotes() [ 'deferred' => new CallbackDeferred( function () use ($httpResponse) { - if ($httpResponse->get()->getStatusCode() >= 400) { - $xmlResponse = ''; - } else { - $xmlResponse = $httpResponse->get()->getBody(); + $responseResult = null; + $xmlResponse = ''; + try { + $responseResult = $httpResponse->get(); + } catch (HttpException $exception) { + $this->_logger->critical($exception); + } + if ($responseResult) { + $xmlResponse = $responseResult->getStatusCode() >= 400 ? '' : $responseResult->getBody(); } return $this->_parseXmlResponse($xmlResponse); diff --git a/app/code/Magento/Ups/etc/adminhtml/system.xml b/app/code/Magento/Ups/etc/adminhtml/system.xml index 3a1676d221977..6890e1bdaf870 100644 --- a/app/code/Magento/Ups/etc/adminhtml/system.xml +++ b/app/code/Magento/Ups/etc/adminhtml/system.xml @@ -13,9 +13,6 @@ <field id="access_license_number" translate="label" type="obscure" sortOrder="30" showInDefault="1" showInWebsite="1"> <label>Access License Number</label> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> - <depends> - <field id="carriers/ups/active">1</field> - </depends> </field> <field id="active" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Enabled for Checkout</label> @@ -89,9 +86,6 @@ <field id="password" translate="label" type="obscure" sortOrder="30" showInDefault="1" showInWebsite="1"> <label>Password</label> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> - <depends> - <field id="carriers/ups/active">1</field> - </depends> </field> <field id="pickup" translate="label" type="select" sortOrder="80" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Pickup Method</label> @@ -123,9 +117,6 @@ <field id="username" translate="label" type="obscure" sortOrder="30" showInDefault="1" showInWebsite="1"> <label>User ID</label> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> - <depends> - <field id="carriers/ups/active">1</field> - </depends> </field> <field id="negotiated_active" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Enable Negotiated Rates</label> diff --git a/app/code/Magento/Ups/view/adminhtml/templates/system/shipping/carrier_config.phtml b/app/code/Magento/Ups/view/adminhtml/templates/system/shipping/carrier_config.phtml index f068b0cf0079f..b6b7040a41bca 100644 --- a/app/code/Magento/Ups/view/adminhtml/templates/system/shipping/carrier_config.phtml +++ b/app/code/Magento/Ups/view/adminhtml/templates/system/shipping/carrier_config.phtml @@ -4,10 +4,17 @@ * See COPYING.txt for license details. */ -/** @var $upsModel \Magento\Ups\Helper\Config */ -/** @var $block \Magento\Ups\Block\Backend\System\CarrierConfig */ -/** @var \Magento\Framework\View\Helper\SecureHtmlRenderer $secureRenderer */ +use Magento\Framework\Escaper; +use Magento\Framework\Json\Helper\Data; +use Magento\Framework\View\Helper\SecureHtmlRenderer; +use Magento\Store\Model\Website; +use Magento\Ups\Block\Backend\System\CarrierConfig; +/** + * @var CarrierConfig $block + * @var Escaper $escaper + * @var SecureHtmlRenderer $secureRenderer + */ $upsCarrierConfig = $block->getCarrierConfig(); $orShipArr = $upsCarrierConfig->getCode('originShipment'); $defShipArr = $upsCarrierConfig->getCode('method'); @@ -15,26 +22,26 @@ $defShipArr = $upsCarrierConfig->getCode('method'); $sectionCode = $block->getRequest()->getParam('section'); $websiteCode = $block->getRequest()->getParam('website'); $storeCode = $block->getRequest()->getParam('store'); -/** @var \Magento\Framework\Json\Helper\Data $jsonHelper */ +/** @var Data $jsonHelper */ $jsonHelper = $block->getData('jsonHelper'); if (!$storeCode && $websiteCode) { - /** @var $web \Magento\Store\Model\Website */ + /** @var Website $web */ $web = $block->getWebsiteModel()->load($websiteCode); $storedAllowedMethods = explode(',', $web->getConfig('carriers/ups/allowed_methods')); - $storedOriginShipment = $block->escapeHtml($web->getConfig('carriers/ups/origin_shipment')); - $storedFreeShipment = $block->escapeHtml($web->getConfig('carriers/ups/free_method')); - $storedUpsType = $block->escapeHtml($web->getConfig('carriers/ups/type')); + $storedOriginShipment = $escaper->escapeHtml($web->getConfig('carriers/ups/origin_shipment')); + $storedFreeShipment = $escaper->escapeHtml($web->getConfig('carriers/ups/free_method')); + $storedUpsType = $escaper->escapeHtml($web->getConfig('carriers/ups/type')); } elseif ($storeCode) { $storedAllowedMethods = explode(',', $block->getConfig('carriers/ups/allowed_methods', $storeCode)); - $storedOriginShipment = $block->escapeHtml($block->getConfig('carriers/ups/origin_shipment', $storeCode)); - $storedFreeShipment = $block->escapeHtml($block->getConfig('carriers/ups/free_method', $storeCode)); - $storedUpsType = $block->escapeHtml($block->getConfig('carriers/ups/type', $storeCode)); + $storedOriginShipment = $escaper->escapeHtml($block->getConfig('carriers/ups/origin_shipment', $storeCode)); + $storedFreeShipment = $escaper->escapeHtml($block->getConfig('carriers/ups/free_method', $storeCode)); + $storedUpsType = $escaper->escapeHtml($block->getConfig('carriers/ups/type', $storeCode)); } else { $storedAllowedMethods = explode(',', $block->getConfig('carriers/ups/allowed_methods')); - $storedOriginShipment = $block->escapeHtml($block->getConfig('carriers/ups/origin_shipment')); - $storedFreeShipment = $block->escapeHtml($block->getConfig('carriers/ups/free_method')); - $storedUpsType = $block->escapeHtml($block->getConfig('carriers/ups/type')); + $storedOriginShipment = $escaper->escapeHtml($block->getConfig('carriers/ups/origin_shipment')); + $storedFreeShipment = $escaper->escapeHtml($block->getConfig('carriers/ups/free_method')); + $storedUpsType = $escaper->escapeHtml($block->getConfig('carriers/ups/type')); } ?> @@ -87,14 +94,16 @@ require(["prototype"], function(){ 'carriers_ups_origin_shipment','carriers_ups_negotiated_active','carriers_ups_shipper_number', 'carriers_ups_mode_xml','carriers_ups_include_taxes']; this.onlyUpsElements = ['carriers_ups_gateway_url']; + this.authUpsXmlElements = ['carriers_ups_username', + 'carriers_ups_password','carriers_ups_access_license_number']; script; $scriptString .= 'this.storedOriginShipment = \'' . /* @noEscape */ $storedOriginShipment . '\'; this.storedFreeShipment = \'' . /* @noEscape */ $storedFreeShipment . '\'; - this.storedUpsType = \'' . /* @noEscape */ $storedUpsType . '\';'; + this.storedUpsType = \'' . /* @noEscape */ $storedUpsType . '\';'; ?> -<?php $scriptString .= 'this.storedAllowedMethods = ' . /* @noEscape */ $jsonHelper->jsonEncode($storedAllowedMethods) . - '; +<?php $scriptString .= 'this.storedAllowedMethods = ' + . /* @noEscape */ $jsonHelper->jsonEncode($storedAllowedMethods) . '; this.originShipmentObj = ' . /* @noEscape */ $jsonHelper->jsonEncode($orShipArr) . '; this.originShipmentObj[\'default\'] = ' . /* @noEscape */ $jsonHelper->jsonEncode($defShipArr) . ';'; @@ -119,8 +128,9 @@ $scriptString .= <<<script script; -$scriptString .= 'freeMethod.insert(new Element(\'option\', {value:\'\'}).update(\'' . $block->escapeHtml(__('None')) . - '\'));'; +$scriptString .= 'freeMethod.insert(new Element(\'option\', {value:\'\'}).update(\'' + . $escaper->escapeHtml(__('None')) + . '\'));'; $scriptString .= <<<script var code, option; @@ -178,6 +188,9 @@ $scriptString .= <<<script } Event.observe($('carriers_ups_origin_shipment'), 'change', this.changeOriginShipment.bind(this)); showRowArrayElements(this.onlyUpsXmlElements); + if (\$F(this.carriersUpsActiveId) !== '1'){ + hideRowArrayElements(this.authUpsXmlElements); + } hideRowArrayElements(this.onlyUpsElements); this.changeOriginShipment(null, null); } diff --git a/app/code/Magento/Usps/Model/Carrier.php b/app/code/Magento/Usps/Model/Carrier.php index 85e0cf2f6999a..47bb68a10ad50 100644 --- a/app/code/Magento/Usps/Model/Carrier.php +++ b/app/code/Magento/Usps/Model/Carrier.php @@ -8,6 +8,8 @@ use Magento\Framework\App\ObjectManager; use Magento\Framework\Async\CallbackDeferred; +use Magento\Framework\DataObject; +use Magento\Framework\HTTP\AsyncClient\HttpException; use Magento\Framework\HTTP\AsyncClient\Request; use Magento\Framework\HTTP\AsyncClientInterface; use Magento\Framework\Xml\Security; @@ -25,19 +27,19 @@ */ class Carrier extends AbstractCarrierOnline implements \Magento\Shipping\Model\Carrier\CarrierInterface { - /** @deprecated */ + /** @deprecated Redundant dependency */ const CONTAINER_VARIABLE = 'VARIABLE'; - /** @deprecated */ + /** @deprecated Redundant dependency */ const CONTAINER_FLAT_RATE_BOX = 'FLAT RATE BOX'; - /** @deprecated */ + /** @deprecated Redundant dependency */ const CONTAINER_FLAT_RATE_ENVELOPE = 'FLAT RATE ENVELOPE'; - /** @deprecated */ + /** @deprecated Redundant dependency */ const CONTAINER_RECTANGULAR = 'RECTANGULAR'; - /** @deprecated */ + /** @deprecated Redundant dependency */ const CONTAINER_NONRECTANGULAR = 'NONRECTANGULAR'; /** @@ -149,6 +151,11 @@ class Carrier extends AbstractCarrierOnline implements \Magento\Shipping\Model\C */ private $proxyDeferredFactory; + /** + * @var DataObject + */ + private $_rawTrackRequest; + /** * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig * @param \Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory $rateErrorFactory @@ -560,7 +567,13 @@ protected function _getXmlQuotes() [ 'deferred' => new CallbackDeferred( function () use ($deferredResponse, $request, $debugData) { - $responseBody = $deferredResponse->get()->getBody(); + $responseResult = null; + try { + $responseResult = $deferredResponse->get(); + } catch (HttpException $exception) { + $this->_logger->critical($exception); + } + $responseBody = $responseResult ? $responseResult->getBody() : ''; $debugData['result'] = $responseBody; $this->_setCachedQuotes($request, $responseBody); $this->_debug($debugData); diff --git a/app/code/Magento/Vault/Plugin/PaymentVaultInformationManagement.php b/app/code/Magento/Vault/Plugin/PaymentVaultInformationManagement.php new file mode 100644 index 0000000000000..0d0a7fdfbd0e0 --- /dev/null +++ b/app/code/Magento/Vault/Plugin/PaymentVaultInformationManagement.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Vault\Plugin; + +use Magento\Checkout\Api\PaymentInformationManagementInterface; +use Magento\Quote\Api\Data\AddressInterface; +use Magento\Quote\Api\Data\PaymentInterface; +use Magento\Vault\Api\PaymentMethodListInterface; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Payment vault information management process + */ +class PaymentVaultInformationManagement +{ + /** + * @var PaymentMethodListInterface + */ + private $vaultPaymentMethodList; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * PaymentVaultInformationManagement constructor. + * + * @param PaymentMethodListInterface $vaultPaymentMethodList + * @param StoreManagerInterface $storeManager + */ + public function __construct( + PaymentMethodListInterface $vaultPaymentMethodList, + StoreManagerInterface $storeManager + ) { + $this->vaultPaymentMethodList = $vaultPaymentMethodList; + $this->storeManager = $storeManager; + } + + /** + * Set available vault method code without index to payment + * + * @param PaymentInformationManagementInterface $subject + * @param string $cartId + * @param PaymentInterface $paymentMethod + * @param AddressInterface|null $billingAddress + * @return void + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeSavePaymentInformation( + PaymentInformationManagementInterface $subject, + string $cartId, + PaymentInterface $paymentMethod, + AddressInterface $billingAddress = null + ): void { + $availableMethods = $this->vaultPaymentMethodList->getActiveList($this->storeManager->getStore()->getId()); + foreach ($availableMethods as $availableMethod) { + if (strpos($paymentMethod->getMethod(), $availableMethod->getCode()) !== false) { + $paymentMethod->setMethod($availableMethod->getCode()); + } + } + } +} diff --git a/app/code/Magento/Vault/Test/Unit/Plugin/PaymentVaultInformationManagementTest.php b/app/code/Magento/Vault/Test/Unit/Plugin/PaymentVaultInformationManagementTest.php new file mode 100644 index 0000000000000..3fd75f9ba6284 --- /dev/null +++ b/app/code/Magento/Vault/Test/Unit/Plugin/PaymentVaultInformationManagementTest.php @@ -0,0 +1,130 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Vault\Test\Unit\Plugin; + +use Magento\Checkout\Api\PaymentInformationManagementInterface; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Vault\Api\PaymentMethodListInterface; +use Magento\Vault\Plugin\PaymentVaultInformationManagement; +use Magento\Quote\Api\Data\PaymentInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Test for payment vault information management plugin + */ +class PaymentVaultInformationManagementTest extends TestCase +{ + /** + * @var StoreManagerInterface|MockObject + */ + private $storeManager; + + /** + * @var StoreInterface|MockObject + */ + private $store; + + /** + * @var PaymentMethodListInterface|MockObject + */ + private $paymentMethodList; + + /** + * @var PaymentVaultInformationManagement + */ + private $plugin; + + /** + * @var PaymentInformationManagementInterface|MockObject + */ + private $paymentInformationManagement; + + /** + * @var PaymentInterface|MockObject + */ + private $payment; + + /** + * @inheritDoc + */ + protected function setUp(): void + { + $this->storeManager = $this->getMockBuilder(StoreManagerInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['getStore']) + ->getMockForAbstractClass(); + $this->store = $this->getMockBuilder(StoreInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['getId']) + ->getMockForAbstractClass(); + $this->paymentMethodList = $this->getMockBuilder(PaymentMethodListInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['getActiveList']) + ->getMockForAbstractClass(); + $this->paymentInformationManagement = $this + ->getMockBuilder(PaymentInformationManagementInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->payment = $this->getMockBuilder(PaymentInterface::class) + ->onlyMethods(['setMethod']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->plugin = new PaymentVaultInformationManagement($this->paymentMethodList, $this->storeManager); + } + + /** + * Test payment method for vault before saving payment information + * + * @param string $requestPaymentMethodCode + * @param string $methodCode + * @dataProvider vaultPaymentMethodDataProvider + * + * @return void + */ + public function testBeforeSavePaymentInformation($requestPaymentMethodCode, $methodCode): void + { + $this->store->method('getId') + ->willReturn(1); + $this->storeManager->method('getStore') + ->willReturn($this->store); + $activeVaultMethod = $this->getMockBuilder(PaymentInterface::class) + ->disableOriginalConstructor() + ->addMethods(['getCode', 'getProviderCode']) + ->getMockForAbstractClass(); + $activeVaultMethod->method('getCode') + ->willReturn($methodCode); + $this->paymentMethodList->method('getActiveList') + ->willReturn([$activeVaultMethod]); + $this->payment->method('getMethod') + ->willReturn($requestPaymentMethodCode); + $this->payment->expects($this->once()) + ->method('setMethod') + ->with($methodCode); + + $this->plugin->beforeSavePaymentInformation( + $this->paymentInformationManagement, + '1', + $this->payment, + null + ); + } + + /** + * Data provider for BeforeSavePaymentInformation. + * + * @return array + */ + public function vaultPaymentMethodDataProvider(): array + { + return [ + ['braintree_cc_vault_01', 'braintree_cc_vault'], + ]; + } +} diff --git a/app/code/Magento/Vault/etc/di.xml b/app/code/Magento/Vault/etc/di.xml index 0192a783bd5a8..e02aa9277258c 100644 --- a/app/code/Magento/Vault/etc/di.xml +++ b/app/code/Magento/Vault/etc/di.xml @@ -53,4 +53,8 @@ </argument> </arguments> </type> + <type name="Magento\Checkout\Api\PaymentInformationManagementInterface"> + <plugin name="ProcessPaymentVaultInformationManagement" + type="Magento\Vault\Plugin\PaymentVaultInformationManagement"/> + </type> </config> diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipOrderTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipOrderTest.php index 64fc612120332..173e817c94c4a 100644 --- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipOrderTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipOrderTest.php @@ -3,9 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Sales\Service\V1; use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Type; use Magento\Framework\ObjectManagerInterface; use Magento\Sales\Api\Data\OrderItemInterface; use Magento\Sales\Api\ShipmentRepositoryInterface; @@ -105,7 +108,7 @@ public function testShipOrderStatusPreserve() $this->assertEquals($orderStatus, $order->getStatus()); $requestData = [ - 'orderId' => $order->getId() + 'orderId' => $order->getId(), ]; /** @var OrderItemInterface $item */ foreach ($order->getAllItems() as $item) { @@ -145,9 +148,9 @@ public function testShipOrder() [ 'track_number' => 'TEST_TRACK_0001', 'title' => 'Simple shipment track', - 'carrier_code' => 'UPS' - ] - ] + 'carrier_code' => 'UPS', + ], + ], ]; /** @var OrderItemInterface $item */ @@ -205,9 +208,9 @@ public function testShipOrderWithoutTrackingNumberReturnsError() 'tracks' => [ [ 'title' => 'Simple shipment track', - 'carrier_code' => 'UPS' - ] - ] + 'carrier_code' => 'UPS', + ], + ], ]; $this->_webApiCall($this->getServiceInfo($existingOrder), $requestData); @@ -231,9 +234,9 @@ public function testPartialShipOrderWithBundleShippedSeparately() [ 'track_number' => 'TEST_TRACK_0001', 'title' => 'Simple shipment track', - 'carrier_code' => 'UPS' - ] - ] + 'carrier_code' => 'UPS', + ], + ], ]; $shippedItemId = null; @@ -267,8 +270,9 @@ public function testPartialShipOrderWithBundleShippedSeparately() if ($item->getItemId() == $shippedItemId) { $this->assertEquals(1, $item->getQtyShipped()); continue; + } elseif ($item->getParentItem()) { + $this->assertEquals(0, $item->getQtyShipped()); } - $this->assertEquals(0, $item->getQtyShipped()); } } @@ -336,6 +340,116 @@ public function testPartialShipOrderWithTwoBundleShippedSeparatelyContainsSameSi } } + /** + * @magentoApiDataFixture Magento/Bundle/_files/order_with_bundle_shipped_separately.php + */ + public function testValidationShipTogetherWithBundleShippedSeparate() + { + $order = $this->getOrder('100000001'); + + $requestData = [ + 'orderId' => $order->getId(), + 'items' => [], + 'comment' => [ + 'comment' => 'Test Comment', + 'is_visible_on_front' => 1, + ], + 'tracks' => [], + ]; + + foreach ($order->getAllItems() as $item) { + if ($item->getProductType() === Type::TYPE_BUNDLE) { + $requestData['items'][] = [ + 'order_item_id' => $item->getItemId(), + 'qty' => $item->getQtyOrdered(), + ]; + break; + } + } + + try { + $this->_webApiCall($this->getServiceInfo($order), $requestData); + $this->fail('Expected exception was not raised'); + } catch (\Exception $exception) { + $this->assertExceptionMessage( + $exception, + 'Shipment Document Validation Error(s): ' + . 'You can\'t create a shipment without products. ' + . 'Cannot create shipment as bundle product "bundle-product" has shipment type "Separately". ' + . 'Bundle product options should be shipped instead.' + ); + } + + foreach ($order->getAllItems() as $item) { + if ($item->getProductType() === Type::TYPE_SIMPLE) { + $requestData['items'] = [ + [ + 'order_item_id' => $item->getItemId(), + 'qty' => $item->getQtyOrdered(), + ], + ]; + break; + } + } + + $this->_webApiCall($this->getServiceInfo($order), $requestData); + } + + /** + * @magentoApiDataFixture Magento/Bundle/_files/order_with_bundle_shipped_together.php + */ + public function testValidationShipTogetherWithBundleShippedTogether() + { + $order = $this->getOrder('100000001'); + + $requestData = [ + 'orderId' => $order->getId(), + 'items' => [], + 'comment' => [ + 'comment' => 'Test Comment', + 'is_visible_on_front' => 1, + ], + 'tracks' => [], + ]; + + foreach ($order->getAllItems() as $item) { + if ($item->getProductType() === Type::TYPE_SIMPLE) { + $requestData['items'][] = [ + 'order_item_id' => $item->getItemId(), + 'qty' => $item->getQtyOrdered(), + ]; + break; + } + } + + try { + $this->_webApiCall($this->getServiceInfo($order), $requestData); + $this->fail('Expected exception was not raised'); + } catch (\Exception $exception) { + $this->assertExceptionMessage( + $exception, + 'Shipment Document Validation Error(s): ' + . 'You can\'t create a shipment without products. ' + . 'Cannot create shipment as bundle product "bundle-product" has shipment type "Together". ' + . 'Bundle product itself should be shipped instead.' + ); + } + + foreach ($order->getAllItems() as $item) { + if ($item->getProductType() === Type::TYPE_BUNDLE) { + $requestData['items'] = [ + [ + 'order_item_id' => $item->getItemId(), + 'qty' => $item->getQtyOrdered(), + ], + ]; + break; + } + } + + $this->_webApiCall($this->getServiceInfo($order), $requestData); + } + /** * @param Order $order * @return array diff --git a/dev/tests/integration/framework/Magento/TestFramework/Application.php b/dev/tests/integration/framework/Magento/TestFramework/Application.php index af2b72e03e9be..43bb7852e2441 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Application.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Application.php @@ -731,6 +731,7 @@ protected function getCustomDirs() DirectoryList::TMP => [$path => "{$var}/tmp"], DirectoryList::UPLOAD => [$path => "{$var}/upload"], DirectoryList::PUB => [$path => "{$this->installDir}/pub"], + DirectoryList::VAR_IMPORT_EXPORT => [$path => "{$this->installDir}/var"], ]; return $customDirs; } diff --git a/dev/tests/integration/framework/Magento/TestFramework/HTTP/AsyncClientInterfaceMock.php b/dev/tests/integration/framework/Magento/TestFramework/HTTP/AsyncClientInterfaceMock.php index ab0020917a9e9..8f2ae2a7b0bdf 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/HTTP/AsyncClientInterfaceMock.php +++ b/dev/tests/integration/framework/Magento/TestFramework/HTTP/AsyncClientInterfaceMock.php @@ -38,6 +38,11 @@ class AsyncClientInterfaceMock implements AsyncClientInterface */ private $requests = []; + /** + * @var HttpResponseDeferredInterface + */ + private $mockDeferredResponse; + /** * AsyncClientInterfaceMock constructor. * @param GuzzleAsyncClient $client @@ -89,6 +94,19 @@ public function clearRequests() $this->lastRequest = null; } + /** + * Next responses will be as given. + * + * @param HttpResponseDeferredInterface $mockDeferredResponse + * @return self + */ + public function setDeferredResponseMock(HttpResponseDeferredInterface $mockDeferredResponse): self + { + $this->mockDeferredResponse = $mockDeferredResponse; + + return $this; + } + /** * @inheritDoc */ @@ -96,8 +114,8 @@ public function request(Request $request): HttpResponseDeferredInterface { $this->lastRequest = $request; $this->requests[] = $request; - if ($mockResponse = array_shift($this->mockResponses)) { - return new MockDeferredResponse($mockResponse); + if ($mockResponse = $this->mockDeferredResponse ?? array_shift($this->mockResponses)) { + return $this->mockDeferredResponse ?? new MockDeferredResponse($mockResponse); } return $this->client->request($request); diff --git a/dev/tests/integration/testsuite/Magento/Bundle/Model/Sales/AdminOrder/ReorderTest.php b/dev/tests/integration/testsuite/Magento/Bundle/Model/Sales/AdminOrder/ReorderTest.php new file mode 100644 index 0000000000000..6ca5ec22f5e14 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/Model/Sales/AdminOrder/ReorderTest.php @@ -0,0 +1,89 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Bundle\Model\Sales\AdminOrder; + +use Magento\Framework\ObjectManagerInterface; +use Magento\Quote\Model\Quote\Address\Rate; +use Magento\Quote\Model\Quote\Item as QuoteItem; +use Magento\Sales\Model\AdminOrder\Create; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Item as OrderItem; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Reorder with Bundle product integration tests. + * + * @see Create + * @magentoAppArea adminhtml + */ +class ReorderTest extends TestCase +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var Create + */ + private $model; + + /** + * @inheritDoc + */ + protected function setUp(): void + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->model =$this->objectManager->get(Create::class); + } + + /** + * Check Custom Price after reordering with Bundle product. + * + * @return void + * @magentoDataFixture Magento/Bundle/_files/order_item_with_bundle_and_options.php + */ + public function testReorderBundleProductWithCustomPrice(): void + { + $customPrice = 300; + /** @var $order Order */ + $order = $this->objectManager->create(Order::class); + $order->loadByIncrementId('100000001'); + $this->model->initFromOrder($order); + + /** @var QuoteItem[] $quoteItems */ + $quoteItems = $this->model->getQuote()->getAllItems(); + $firstQuoteItem = array_shift($quoteItems); + self::assertNull($firstQuoteItem->getParentItemId()); + self::assertEquals($customPrice, (int)$firstQuoteItem->getCustomPrice()); + foreach ($quoteItems as $quoteItem) { + self::assertEquals($firstQuoteItem->getId(), $quoteItem->getParentItemId()); + self::assertEquals(0, (int)$quoteItem->getCustomPrice()); + } + + $shippingMethod = 'freeshipping_freeshipping'; + /** @var Rate $rate */ + $rate = $this->objectManager->create(Rate::class); + $rate->setCode($shippingMethod); + $this->model->getQuote()->getShippingAddress()->addShippingRate($rate); + $this->model->setPaymentData(['method' => 'checkmo']); + $this->model->setIsValidate(true)->importPostData(['shipping_method' => $shippingMethod]); + $newOrder = $this->model->createOrder(); + + /** @var OrderItem[] $orderItems */ + $orderItems = $newOrder->getAllItems(); + $firstOrderItem = array_shift($orderItems); + self::assertNull($firstOrderItem->getParentItemId()); + self::assertEquals($customPrice, (int)$firstOrderItem->getPrice()); + foreach ($orderItems as $orderItem) { + self::assertEquals($firstOrderItem->getId(), $orderItem->getParentItemId()); + self::assertEquals(0, (int)$orderItem->getPrice()); + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/order_item_with_bundle_and_options.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/order_item_with_bundle_and_options.php index ae85671a684d8..e20bf232c70e1 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/_files/order_item_with_bundle_and_options.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/order_item_with_bundle_and_options.php @@ -48,6 +48,7 @@ 'bundle_option' => $bundleOptions, 'bundle_option_qty' => $bundleOptionsQty, 'qty' => 1, + 'custom_price' => 300, ]; /** @var \Magento\Sales\Model\Order\Item $orderItem */ @@ -58,7 +59,14 @@ $orderItem->setPrice($product->getPrice()); $orderItem->setRowTotal($product->getPrice()); $orderItem->setProductType($product->getTypeId()); -$orderItem->setProductOptions(['info_buyRequest' => $requestInfo]); +$orderItem->setProductOptions([ + 'info_buyRequest' => $requestInfo, + 'bundle_options' => [ + [ + 'value' => [['title' => $product->getName()]] + ] + ] +]); /** @var \Magento\Sales\Model\Order $order */ $order = $objectManager->create(\Magento\Sales\Model\Order::class); diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/order_with_bundle_shipped_separately.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/order_with_bundle_shipped_separately.php index b91d479cdf1ef..7d214e8ab45a7 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/_files/order_with_bundle_shipped_separately.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/order_with_bundle_shipped_separately.php @@ -3,32 +3,56 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - +declare(strict_types=1); + +use Magento\Bundle\Api\Data\LinkInterface; +use Magento\Bundle\Api\Data\LinkInterfaceFactory; +use Magento\Bundle\Api\Data\OptionInterfaceFactory; +use Magento\Bundle\Model\Option; +use Magento\Bundle\Model\Product\Price; +use Magento\Bundle\Model\Product\Type; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type\AbstractType; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Model\ProductFactory; +use Magento\Framework\ObjectManagerInterface; +use Magento\Sales\Api\OrderPaymentRepositoryInterface as PaymentFactory; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Address; +use Magento\Sales\Model\Order\Item; +use Magento\Sales\Model\Order\ItemFactory; +use Magento\Sales\Model\OrderFactory; +use Magento\Sales\Model\OrderRepository; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\Workaround\Override\Fixture\Resolver; Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/products.php'); -/** @var $objectManager \Magento\TestFramework\ObjectManager */ -$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); -/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ -$productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); +/** @var ObjectManagerInterface $objectManager */ +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); $sampleProduct = $productRepository->get('simple'); $secondSampleProduct = $productRepository->get('custom-design-simple-product'); +$productFactory = $objectManager->get(ProductFactory::class); -/** @var $product \Magento\Catalog\Model\Product */ -$product = $objectManager->create(\Magento\Catalog\Model\Product::class); -$product->setTypeId('bundle') +/** @var $product Product */ +$product = $productFactory->create(); +$product->setTypeId(Type::TYPE_CODE) ->setId(3) - ->setAttributeSetId(4) + ->setAttributeSetId($product->getDefaultAttributeSetId()) ->setWebsiteIds([1]) ->setName('Bundle Product') ->setSku('bundle-product') - ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) - ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) ->setPriceView(1) - ->setPriceType(1) - ->setShipmentType(1) + ->setPriceType(Price::PRICE_TYPE_FIXED) + ->setShipmentType(AbstractType::SHIPMENT_SEPARATELY) ->setPrice(10.0) ->setBundleOptionsData( [ @@ -46,7 +70,7 @@ 'required' => 1, 'delete' => '', ], - ] + ], ) ->setBundleSelectionsData( [ @@ -65,16 +89,15 @@ 'selection_can_change_qty' => 1, 'delete' => '', ], - ] - ] + ], + ], ); if ($product->getBundleOptionsData()) { $options = []; foreach ($product->getBundleOptionsData() as $key => $optionData) { if (!(bool)$optionData['delete']) { - $option = $objectManager->create(\Magento\Bundle\Api\Data\OptionInterfaceFactory::class) - ->create(['data' => $optionData]); + $option = $objectManager->get(OptionInterfaceFactory::class)->create(['data' => $optionData]); $option->setSku($product->getSku()); $option->setOptionId(null); @@ -83,9 +106,8 @@ if (!empty($bundleLinks[$key])) { foreach ($bundleLinks[$key] as $linkData) { if (!(bool)$linkData['delete']) { - /** @var \Magento\Bundle\Api\Data\LinkInterface$link */ - $link = $objectManager->create(\Magento\Bundle\Api\Data\LinkInterfaceFactory::class) - ->create(['data' => $linkData]); + /** @var LinkInterface $link */ + $link = $objectManager->get(LinkInterfaceFactory::class)->create(['data' => $linkData]); $linkProduct = $productRepository->getById($linkData['product_id']); $link->setSku($linkProduct->getSku()); $link->setQty($linkData['selection_qty']); @@ -104,26 +126,22 @@ $extension->setBundleProductOptions($options); $product->setExtensionAttributes($extension); } -$product->save(); - -$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$productRepository->save($product); $addressData = include __DIR__ . '/../../../Magento/Sales/_files/address_data.php'; -$billingAddress = $objectManager->create(\Magento\Sales\Model\Order\Address::class, ['data' => $addressData]); +$billingAddress = $objectManager->create(Address::class, ['data' => $addressData]); $billingAddress->setAddressType('billing'); $shippingAddress = clone $billingAddress; $shippingAddress->setId(null)->setAddressType('shipping'); -$payment = $objectManager->create(\Magento\Sales\Model\Order\Payment::class); -$payment->setMethod('checkmo'); +$paymentFactory = $objectManager->get(PaymentFactory::class); +$payment = $paymentFactory->create()->setMethod('checkmo'); -/** @var $product \Magento\Catalog\Model\Product */ -$product = $objectManager->create(\Magento\Catalog\Model\Product::class); -$product->load(3); +$product = $productRepository->getById(3); -/** @var $typeInstance \Magento\Bundle\Model\Product\Type */ +/** @var $typeInstance Type */ $typeInstance = $product->getTypeInstance(); $typeInstance->setStoreFilter($product->getStoreId(), $product); $optionCollection = $typeInstance->getOptionsCollection($product); @@ -132,7 +150,7 @@ $bundleOptionsQty = []; $optionsData = []; foreach ($optionCollection as $option) { - /** @var $option \Magento\Bundle\Model\Option */ + /** @var $option Option */ $selectionsCollection = $typeInstance->getSelectionsCollection([$option->getId()], $product); if ($option->isMultiSelection()) { $optionsData[$option->getId()] = array_column($selectionsCollection->toArray(), 'product_id'); @@ -151,41 +169,52 @@ 'qty' => 1, ]; +$orderItemFactory = $objectManager->get(ItemFactory::class); $orderItems = []; -/** @var \Magento\Sales\Model\Order\Item $orderItem */ -$orderItem = $objectManager->create(\Magento\Sales\Model\Order\Item::class); +/** @var Item $orderItem */ +$orderItem = $orderItemFactory->create(); $orderItem->setProductId($product->getId()); $orderItem->setQtyOrdered(1); $orderItem->setBasePrice($product->getPrice()); $orderItem->setPrice($product->getPrice()); $orderItem->setRowTotal($product->getPrice()); $orderItem->setProductType($product->getTypeId()); -$orderItem->setProductOptions(['info_buyRequest' => $requestInfo]); +$orderItem->setSku($product->getSku()); +$orderItem->setProductOptions([ + 'info_buyRequest' => $requestInfo, + 'shipment_type' => AbstractType::SHIPMENT_SEPARATELY +]); $orderItems[] = $orderItem; foreach ($optionsData as $optionId => $productId) { - /** @var $selectedProduct \Magento\Catalog\Model\Product */ - $selectedProduct = $objectManager->create(\Magento\Catalog\Model\Product::class); - $selectedProduct->load($productId); + /** @var $selectedProduct Product */ + $selectedProduct = $productRepository->getById($productId); - /** @var \Magento\Sales\Model\Order\Item $orderItem */ - $orderItem = $objectManager->create(\Magento\Sales\Model\Order\Item::class); + /** @var Item $orderItem */ + $orderItem = $orderItemFactory->create(); $orderItem->setProductId($productId); $orderItem->setQtyOrdered(1); $orderItem->setBasePrice($selectedProduct->getPrice()); $orderItem->setPrice($selectedProduct->getPrice()); $orderItem->setRowTotal($selectedProduct->getPrice()); $orderItem->setProductType($selectedProduct->getTypeId()); - $orderItem->setProductOptions(['info_buyRequest' => $requestInfo]); + $orderItem->setSku($selectedProduct->getSku()); + $orderItem->setProductOptions([ + 'info_buyRequest' => $requestInfo, + 'shipment_type' => AbstractType::SHIPMENT_SEPARATELY + ]); + $orderItem->setParentItem($orderItems[0]); $orderItems[] = $orderItem; } -/** @var \Magento\Sales\Model\Order $order */ -$order = $objectManager->create(\Magento\Sales\Model\Order::class); +$orderFactory = $objectManager->get(OrderFactory::class); +$orderRepository = $objectManager->get(OrderRepository::class); +/** @var Order $order */ +$order = $orderFactory->create(); $order->setIncrementId('100000001'); -$order->setState(\Magento\Sales\Model\Order::STATE_NEW); -$order->setStatus($order->getConfig()->getStateDefaultStatus(\Magento\Sales\Model\Order::STATE_NEW)); +$order->setState(Order::STATE_NEW); +$order->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_NEW)); $order->setCustomerIsGuest(true); $order->setCustomerEmail('customer@null.com'); $order->setCustomerFirstname('firstname'); @@ -199,8 +228,8 @@ $order->addItem($item); } -$order->setStoreId($objectManager->get(\Magento\Store\Model\StoreManagerInterface::class)->getStore()->getId()); +$order->setStoreId($objectManager->get(StoreManagerInterface::class)->getStore()->getId()); $order->setSubtotal(100); $order->setBaseSubtotal(100); $order->setBaseGrandTotal(100); -$order->save(); +$orderRepository->save($order); diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/order_with_bundle_shipped_together.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/order_with_bundle_shipped_together.php new file mode 100644 index 0000000000000..4cc8c96ae9697 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/order_with_bundle_shipped_together.php @@ -0,0 +1,232 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Bundle\Api\Data\LinkInterface; +use Magento\Bundle\Api\Data\LinkInterfaceFactory; +use Magento\Bundle\Api\Data\OptionInterfaceFactory; +use Magento\Bundle\Model\Option; +use Magento\Bundle\Model\Product\Price; +use Magento\Bundle\Model\Product\Type; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type\AbstractType; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Model\ProductFactory; +use Magento\Framework\ObjectManagerInterface; +use Magento\Sales\Api\OrderPaymentRepositoryInterface as PaymentFactory; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Address; +use Magento\Sales\Model\Order\Item; +use Magento\Sales\Model\Order\ItemFactory; +use Magento\Sales\Model\OrderFactory; +use Magento\Sales\Model\OrderRepository; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/products.php'); + +/** @var ObjectManagerInterface $objectManager */ +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +$sampleProduct = $productRepository->get('simple'); +$secondSampleProduct = $productRepository->get('custom-design-simple-product'); +$productFactory = $objectManager->get(ProductFactory::class); + +/** @var $product Product */ +$product = $productFactory->create(); +$product->setTypeId(Type::TYPE_CODE) + ->setId(3) + ->setAttributeSetId($product->getDefaultAttributeSetId()) + ->setWebsiteIds([1]) + ->setName('Bundle Product') + ->setSku('bundle-product') + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) + ->setPriceView(1) + ->setPriceType(Price::PRICE_TYPE_FIXED) + ->setShipmentType(AbstractType::SHIPMENT_TOGETHER) + ->setPrice(10.0) + ->setBundleOptionsData( + [ + [ + 'title' => 'Bundle Product Items', + 'default_title' => 'Bundle Product Items', + 'type' => 'select', + 'required' => 1, + 'delete' => '', + ], + [ + 'title' => 'Bundle Product Items Option 2', + 'default_title' => 'Bundle Product Items Option 2', + 'type' => 'select', + 'required' => 1, + 'delete' => '', + ], + ], + ) + ->setBundleSelectionsData( + [ + [ + [ + 'product_id' => $sampleProduct->getId(), + 'selection_qty' => 1, + 'selection_can_change_qty' => 1, + 'delete' => '', + ], + ], + [ + [ + 'product_id' => $secondSampleProduct->getId(), + 'selection_qty' => 1, + 'selection_can_change_qty' => 1, + 'delete' => '', + ], + ], + ], + ); + +if ($product->getBundleOptionsData()) { + $options = []; + foreach ($product->getBundleOptionsData() as $key => $optionData) { + if (!(bool)$optionData['delete']) { + $option = $objectManager->get(OptionInterfaceFactory::class)->create(['data' => $optionData]); + $option->setSku($product->getSku()); + $option->setOptionId(null); + + $links = []; + $bundleLinks = $product->getBundleSelectionsData(); + if (!empty($bundleLinks[$key])) { + foreach ($bundleLinks[$key] as $linkData) { + if (!(bool)$linkData['delete']) { + /** @var LinkInterface $link */ + $link = $objectManager->get(LinkInterfaceFactory::class)->create(['data' => $linkData]); + $linkProduct = $productRepository->getById($linkData['product_id']); + $link->setSku($linkProduct->getSku()); + $link->setQty($linkData['selection_qty']); + if (isset($linkData['selection_can_change_qty'])) { + $link->setCanChangeQuantity($linkData['selection_can_change_qty']); + } + $links[] = $link; + } + } + $option->setProductLinks($links); + $options[] = $option; + } + } + } + $extension = $product->getExtensionAttributes(); + $extension->setBundleProductOptions($options); + $product->setExtensionAttributes($extension); +} +$productRepository->save($product); + +$addressData = include __DIR__ . '/../../../Magento/Sales/_files/address_data.php'; + +$billingAddress = $objectManager->create(Address::class, ['data' => $addressData]); +$billingAddress->setAddressType('billing'); + +$shippingAddress = clone $billingAddress; +$shippingAddress->setId(null)->setAddressType('shipping'); + +$paymentFactory = $objectManager->get(PaymentFactory::class); +$payment = $paymentFactory->create()->setMethod('checkmo'); + +$product = $productRepository->getById(3); + +/** @var $typeInstance Type */ +$typeInstance = $product->getTypeInstance(); +$typeInstance->setStoreFilter($product->getStoreId(), $product); +$optionCollection = $typeInstance->getOptionsCollection($product); + +$bundleOptions = []; +$bundleOptionsQty = []; +$optionsData = []; +foreach ($optionCollection as $option) { + /** @var $option Option */ + $selectionsCollection = $typeInstance->getSelectionsCollection([$option->getId()], $product); + if ($option->isMultiSelection()) { + $optionsData[$option->getId()] = array_column($selectionsCollection->toArray(), 'product_id'); + $bundleOptions[$option->getId()] = array_column($selectionsCollection->toArray(), 'selection_id'); + } else { + $bundleOptions[$option->getId()] = $selectionsCollection->getFirstItem()->getSelectionId(); + $optionsData[$option->getId()] = $selectionsCollection->getFirstItem()->getProductId(); + } + $bundleOptionsQty[$option->getId()] = 1; +} + +$requestInfo = [ + 'product' => $product->getId(), + 'bundle_option' => $bundleOptions, + 'bundle_option_qty' => $bundleOptionsQty, + 'qty' => 1, +]; + +$orderItemFactory = $objectManager->get(ItemFactory::class); +$orderItems = []; +/** @var Item $orderItem */ +$orderItem = $orderItemFactory->create(); +$orderItem->setProductId($product->getId()); +$orderItem->setQtyOrdered(1); +$orderItem->setBasePrice($product->getPrice()); +$orderItem->setPrice($product->getPrice()); +$orderItem->setRowTotal($product->getPrice()); +$orderItem->setProductType($product->getTypeId()); +$orderItem->setSku($product->getSku()); +$orderItem->setProductOptions([ + 'info_buyRequest' => $requestInfo, + 'bundle_options' => [['value' => [['title' => $sampleProduct->getSku()]]]], +]); + +$orderItems[] = $orderItem; + +foreach ($optionsData as $optionId => $productId) { + /** @var $selectedProduct Product */ + $selectedProduct = $productRepository->getById($productId); + + /** @var Item $orderItem */ + $orderItem = $orderItemFactory->create(); + $orderItem->setProductId($productId); + $orderItem->setQtyOrdered(1); + $orderItem->setBasePrice($selectedProduct->getPrice()); + $orderItem->setPrice($selectedProduct->getPrice()); + $orderItem->setRowTotal($selectedProduct->getPrice()); + $orderItem->setProductType($selectedProduct->getTypeId()); + $orderItem->setSku($selectedProduct->getSku()); + $orderItem->setProductOptions(['info_buyRequest' => $requestInfo]); + $orderItem->setParentItem($orderItems[0]); + $orderItems[] = $orderItem; +} + +$orderFactory = $objectManager->get(OrderFactory::class); +$orderRepository = $objectManager->get(OrderRepository::class); +/** @var Order $order */ +$order = $orderFactory->create(); +$order->setIncrementId('100000001'); +$order->setState(Order::STATE_NEW); +$order->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_NEW)); +$order->setCustomerIsGuest(true); +$order->setCustomerEmail('customer@null.com'); +$order->setCustomerFirstname('firstname'); +$order->setCustomerLastname('lastname'); +$order->setBillingAddress($billingAddress); +$order->setShippingAddress($shippingAddress); +$order->setAddresses([$billingAddress, $shippingAddress]); +$order->setPayment($payment); + +foreach ($orderItems as $item) { + $order->addItem($item); +} + +$order->setStoreId($objectManager->get(StoreManagerInterface::class)->getStore()->getId()); +$order->setSubtotal(100); +$order->setBaseSubtotal(100); +$order->setBaseGrandTotal(100); +$orderRepository->save($order); diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/order_with_bundle_shipped_together_rollback.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/order_with_bundle_shipped_together_rollback.php new file mode 100644 index 0000000000000..4b113164d4ef2 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/order_with_bundle_shipped_together_rollback.php @@ -0,0 +1,11 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +Resolver::getInstance()->requireDataFixture('Magento/Bundle/_files/product_rollback.php'); +Resolver::getInstance()->requireDataFixture('Magento/Sales/_files/default_rollback.php'); diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/AttributeTest.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/AttributeTest.php index 463cb026a4c69..a0a445f19e17a 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/AttributeTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/AttributeTest.php @@ -3,17 +3,24 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\CatalogSearch\Model\Layer\Filter; +use Magento\Catalog\Api\Data\ProductAttributeInterface; +use Magento\Catalog\Model\Layer\Category as LayerCategory; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Request; +use PHPUnit\Framework\TestCase; + /** * Test class for \Magento\CatalogSearch\Model\Layer\Filter\Attribute. * * @magentoDataFixture Magento/Catalog/Model/Layer/Filter/_files/attribute_with_option.php */ -class AttributeTest extends \PHPUnit\Framework\TestCase +class AttributeTest extends TestCase { /** - * @var \Magento\CatalogSearch\Model\Layer\Filter\Attribute + * @var Attribute */ protected $_model; @@ -23,16 +30,18 @@ class AttributeTest extends \PHPUnit\Framework\TestCase protected $_attributeOptionId; /** - * @var \Magento\Catalog\Model\Layer + * @var mixed */ - protected $_layer; + private $request; + /** + * @inheritdoc + */ protected function setUp(): void { - /** @var $attribute \Magento\Catalog\Model\Entity\Attribute */ - $attribute = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Catalog\Model\Entity\Attribute::class - ); + $objectManager = Bootstrap::getObjectManager(); + /** @var ProductAttributeInterface $attribute */ + $attribute = $objectManager->get(ProductAttributeInterface::class); $attribute->loadByCode('catalog_product', 'attribute_with_option'); foreach ($attribute->getSource()->getAllOptions() as $optionInfo) { if ($optionInfo['label'] == 'Option Label') { @@ -41,51 +50,53 @@ protected function setUp(): void } } - $this->_layer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create(\Magento\Catalog\Model\Layer\Category::class); - $this->_model = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create(\Magento\CatalogSearch\Model\Layer\Filter\Attribute::class, ['layer' => $this->_layer]); + /** @var LayerCategory $layer */ + $layer = $objectManager->get(LayerCategory::class); + $this->request = $objectManager->get(Request::class); + $this->_model = $objectManager->create(Attribute::class, ['layer' => $layer]); $this->_model->setAttributeModel($attribute); $this->_model->setRequestVar('attribute'); } + /** + * @return void + */ public function testOptionIdNotEmpty() { $this->assertNotEmpty($this->_attributeOptionId, 'Fixture attribute option id.'); // just in case } + /** + * @return void + */ public function testApplyInvalid() { $this->assertEmpty($this->_model->getLayer()->getState()->getFilters()); - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $request = $objectManager->get(\Magento\TestFramework\Request::class); - $request->setParam('attribute', []); - $this->_model->apply($request); + $this->request->setParam('attribute', []); + $this->_model->apply($this->request); $this->assertEmpty($this->_model->getLayer()->getState()->getFilters()); } + /** + * @return void + */ public function testApply() { $this->assertEmpty($this->_model->getLayer()->getState()->getFilters()); - - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $request = $objectManager->get(\Magento\TestFramework\Request::class); - $request->setParam('attribute', $this->_attributeOptionId); - $this->_model->apply($request); + $this->request->setParam('attribute', $this->_attributeOptionId); + $this->_model->apply($this->request); $this->assertNotEmpty($this->_model->getLayer()->getState()->getFilters()); } /** - * @depends testApply + * @return void */ public function testGetItemsWithApply() { - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $request = $objectManager->get(\Magento\TestFramework\Request::class); - $request->setParam('attribute', $this->_attributeOptionId); - $this->_model->apply($request); + $this->request->setParam('attribute', $this->_attributeOptionId); + $this->_model->apply($this->request); $items = $this->_model->getItems(); $this->assertIsArray($items); diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/DecimalTest.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/DecimalTest.php index def90b0b5e97b..69e6a0ff87848 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/DecimalTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/DecimalTest.php @@ -3,8 +3,16 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\CatalogSearch\Model\Layer\Filter; +use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Catalog\Api\Data\ProductAttributeInterface; +use Magento\Catalog\Model\Layer\Category as LayerCategory; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Request; +use PHPUnit\Framework\TestCase; + /** * Test class for \Magento\CatalogSearch\Model\Layer\Filter\Decimal. * @@ -14,78 +22,83 @@ * @magentoDbIsolation enabled * @magentoAppIsolation enabled */ -class DecimalTest extends \PHPUnit\Framework\TestCase +class DecimalTest extends TestCase { /** - * @var \Magento\CatalogSearch\Model\Layer\Filter\Decimal + * @var Decimal */ protected $_model; + /** + * @var mixed + */ + private $request; + + /** + * @inheritdoc + */ protected function setUp(): void { - $category = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create( - \Magento\Catalog\Model\Category::class - ); - $category->load(4); + $objectManager = Bootstrap::getObjectManager(); + /** @var CategoryRepositoryInterface $categoryRepository */ + $categoryRepository = $objectManager->get(CategoryRepositoryInterface::class); + $category = $categoryRepository->get(4); - $layer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create( - \Magento\Catalog\Model\Layer\Category::class, - [ - 'data' => ['current_category' => $category] - ] - ); - - /** @var $attribute \Magento\Catalog\Model\Entity\Attribute */ - $attribute = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create( - \Magento\Catalog\Model\Entity\Attribute::class - ); + /** @var LayerCategory $layer */ + $layer = $objectManager->create( + LayerCategory::class, + ['data' => ['current_category' => $category]] + ); + /** @var ProductAttributeInterface $attribute */ + $attribute = $objectManager->get(ProductAttributeInterface::class); $attribute->loadByCode('catalog_product', 'weight'); - - $this->_model = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create(\Magento\CatalogSearch\Model\Layer\Filter\Decimal::class, ['layer' => $layer]); + $this->request = $objectManager->get(Request::class); + $this->_model = $objectManager->create(Decimal::class, ['layer' => $layer]); $this->_model->setAttributeModel($attribute); + $this->_model->setRequestVar('decimal'); } + /** + * @return void + */ public function testApplyNothing() { - $this->assertEmpty($this->_model->getData('range')); - /** @var $objectManager \Magento\TestFramework\ObjectManager */ - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - /** @var $request \Magento\TestFramework\Request */ - $request = $objectManager->get(\Magento\TestFramework\Request::class); - $this->_model->apply($request); - - $this->assertEmpty($this->_model->getData('range')); + $this->assertEmpty($this->_model->getLayer()->getState()->getFilters()); + $this->_model->apply($this->request); + $this->assertEmpty($this->_model->getLayer()->getState()->getFilters()); } + /** + * @return void + */ public function testApplyInvalid() { - $this->assertEmpty($this->_model->getData('range')); - /** @var $objectManager \Magento\TestFramework\ObjectManager */ - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - /** @var $request \Magento\TestFramework\Request */ - $request = $objectManager->get(\Magento\TestFramework\Request::class); - $request->setParam('decimal', 'non-decimal'); - $this->_model->apply($request); + $this->assertEmpty($this->_model->getLayer()->getState()->getFilters()); + $this->request->setParam('decimal', 'non-decimal'); + $this->_model->apply($this->request); - $this->assertEmpty($this->_model->getData('range')); + $filters = $this->_model->getLayer()->getState()->getFilters(); + $this->assertArrayHasKey(0, $filters); + $this->assertEquals( + '<span class="price">$0.00</span> - <span class="price">$0.00</span>', + (string)$filters[0]->getLabel() + ); } /** - * @return Decimal + * @return void */ public function testApply() { - /** @var $objectManager \Magento\TestFramework\ObjectManager */ - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - /** @var $request \Magento\TestFramework\Request */ - $request = $objectManager->get(\Magento\TestFramework\Request::class); - $request->setParam('decimal', '1-100'); - $this->_model->apply($request); + $this->assertEmpty($this->_model->getLayer()->getState()->getFilters()); + $this->request->setParam('decimal', '1-100'); + $this->_model->apply($this->request); - return $this->_model; + $filters = $this->_model->getLayer()->getState()->getFilters(); + $this->assertArrayHasKey(0, $filters); + $this->assertEquals( + '<span class="price">$1.00</span> - <span class="price">$99.99</span>', + (string)$filters[0]->getLabel() + ); } } diff --git a/dev/tests/integration/testsuite/Magento/Config/Block/System/Config/FormTest.php b/dev/tests/integration/testsuite/Magento/Config/Block/System/Config/FormTest.php index aae6ae770063f..a93adeee1307d 100644 --- a/dev/tests/integration/testsuite/Magento/Config/Block/System/Config/FormTest.php +++ b/dev/tests/integration/testsuite/Magento/Config/Block/System/Config/FormTest.php @@ -585,6 +585,13 @@ private function setEncryptedValue($encryptedValue) */ protected function tearDown(): void { + $reflection = new \ReflectionObject($this); + foreach ($reflection->getProperties() as $property) { + if (!$property->isStatic() && 0 !== strpos($property->getDeclaringClass()->getName(), 'PHPUnit')) { + $property->setAccessible(true); + $property->setValue($this, null); + } + } $this->setEncryptedValue('{ENCRYPTED_VALUE}'); $configResourceModel = Bootstrap::getObjectManager()->get(\Magento\Config\Model\ResourceModel\Config::class); diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Sales/AdminOrder/ReorderTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Sales/AdminOrder/ReorderTest.php new file mode 100644 index 0000000000000..9a02830ad74cf --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Sales/AdminOrder/ReorderTest.php @@ -0,0 +1,89 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Model\Sales\AdminOrder; + +use Magento\Framework\ObjectManagerInterface; +use Magento\Quote\Model\Quote\Address\Rate; +use Magento\Quote\Model\Quote\Item as QuoteItem; +use Magento\Sales\Model\AdminOrder\Create; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Item as OrderItem; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Reorder with Configurable product integration tests. + * + * @see Create + * @magentoAppArea adminhtml + */ +class ReorderTest extends TestCase +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var Create + */ + private $model; + + /** + * @inheritDoc + */ + protected function setUp(): void + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->model =$this->objectManager->get(Create::class); + } + + /** + * Check Custom Price after reordering with Configurable product. + * + * @return void + * @magentoDataFixture Magento/ConfigurableProduct/_files/order_item_with_configurable_and_options.php + */ + public function testReorderConfigurableProductWithCustomPrice(): void + { + $customPrice = 300; + /** @var $order Order */ + $order = $this->objectManager->create(Order::class); + $order->loadByIncrementId('100000001'); + $this->model->initFromOrder($order); + + /** @var QuoteItem[] $quoteItems */ + $quoteItems = $this->model->getQuote()->getAllItems(); + $firstQuoteItem = array_shift($quoteItems); + self::assertNull($firstQuoteItem->getParentItemId()); + self::assertEquals($customPrice, (int)$firstQuoteItem->getCustomPrice()); + foreach ($quoteItems as $quoteItem) { + self::assertEquals($firstQuoteItem->getId(), $quoteItem->getParentItemId()); + self::assertEquals(0, (int)$quoteItem->getCustomPrice()); + } + + $shippingMethod = 'freeshipping_freeshipping'; + /** @var Rate $rate */ + $rate = $this->objectManager->create(Rate::class); + $rate->setCode($shippingMethod); + $this->model->getQuote()->getShippingAddress()->addShippingRate($rate); + $this->model->setPaymentData(['method' => 'checkmo']); + $this->model->setIsValidate(true)->importPostData(['shipping_method' => $shippingMethod]); + $newOrder = $this->model->createOrder(); + + /** @var OrderItem[] $orderItems */ + $orderItems = $newOrder->getAllItems(); + $firstOrderItem = array_shift($orderItems); + self::assertNull($firstOrderItem->getParentItemId()); + self::assertEquals($customPrice, (int)$firstOrderItem->getPrice()); + foreach ($orderItems as $orderItem) { + self::assertEquals($firstOrderItem->getId(), $orderItem->getParentItemId()); + self::assertEquals(0, (int)$orderItem->getPrice()); + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/order_item_with_configurable_and_options.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/order_item_with_configurable_and_options.php index 47d86b34fc05e..d0956e460f709 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/order_item_with_configurable_and_options.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/order_item_with_configurable_and_options.php @@ -3,6 +3,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\TestFramework\Workaround\Override\Fixture\Resolver; Resolver::getInstance()->requireDataFixture('Magento/ConfigurableProduct/_files/product_configurable.php'); @@ -20,9 +23,11 @@ $payment = $objectManager->create(\Magento\Sales\Model\Order\Payment::class); $payment->setMethod('checkmo'); -/** @var $product \Magento\Catalog\Model\Product */ -$product = $objectManager->create(\Magento\Catalog\Model\Product::class); -$product->load(1); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +$product = $productRepository->get('configurable'); +/** @var ProductInterface $firstChildProduct */ +$firstChildProduct = current($product->getTypeInstance()->getUsedProducts($product)); /** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ $eavConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); @@ -38,6 +43,7 @@ 'super_attribute' => [ $attribute->getId() => $option->getId(), ], + 'custom_price' => 300, ]; /** @var \Magento\Sales\Model\Order $order */ $order = $objectManager->create(\Magento\Sales\Model\Order::class); @@ -54,7 +60,11 @@ $orderItem->setPrice($product->getPrice()); $orderItem->setRowTotal($product->getPrice()); $orderItem->setProductType($product->getTypeId()); -$orderItem->setProductOptions(['info_buyRequest' => $requestInfo]); +$orderItem->setProductOptions([ + 'info_buyRequest' => $requestInfo, + 'simple_sku' => $firstChildProduct->getSku(), + 'simple_name' => $firstChildProduct->getName(), +]); /** @var \Magento\Sales\Model\Order $order */ $order = $objectManager->create(\Magento\Sales\Model\Order::class); diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/ResourceModel/AddressRepositoryTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/ResourceModel/AddressRepositoryTest.php index f80d960be2942..3e7b59500b574 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Model/ResourceModel/AddressRepositoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/ResourceModel/AddressRepositoryTest.php @@ -3,18 +3,28 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Customer\Model\ResourceModel; use Magento\Customer\Api\AddressRepositoryInterface; use Magento\Customer\Api\CustomerRepositoryInterface; -use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Customer\Api\Data\AddressInterface; +use Magento\Customer\Api\Data\AddressInterfaceFactory; use Magento\Customer\Api\Data\RegionInterfaceFactory; +use Magento\Customer\Model\CustomerRegistry; +use Magento\Framework\Api\DataObjectHelper; +use Magento\Framework\Api\Filter; +use Magento\Framework\Api\FilterBuilder; +use Magento\Framework\Api\SearchCriteriaBuilder; use Magento\Framework\Api\SortOrder; +use Magento\Framework\Api\SortOrderBuilder; +use Magento\Framework\Config\CacheInterface; use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\NoSuchEntityException; -use Magento\Store\Api\Data\WebsiteInterface; -use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; /** * Class with integration tests for AddressRepository. @@ -23,7 +33,7 @@ * @SuppressWarnings(PHPMD.ExcessivePublicCount) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class AddressRepositoryTest extends \PHPUnit\Framework\TestCase +class AddressRepositoryTest extends TestCase { /** @var AddressRepositoryInterface */ private $repository; @@ -34,34 +44,41 @@ class AddressRepositoryTest extends \PHPUnit\Framework\TestCase /** @var \Magento\Customer\Model\Data\Address[] */ private $expectedAddresses; - /** @var \Magento\Customer\Api\Data\AddressInterfaceFactory */ + /** @var AddressInterfaceFactory */ private $addressFactory; - /** @var \Magento\Framework\Api\DataObjectHelper */ + /** @var DataObjectHelper */ private $dataObjectHelper; + /** @var CustomerRegistry */ private $customerRegistry; + /** @var StoreManagerInterface */ + private $storeManager; + + /** @var RegionInterfaceFactory */ + private $regionFactory; + + /** @var CustomerRepositoryInterface */ + private $customerRepository; + /** - * Set up. + * @inheritdoc */ protected function setUp(): void { - $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - - /* @var \Magento\Framework\Config\CacheInterface $cache */ - $cache = $this->objectManager->create(\Magento\Framework\Config\CacheInterface::class); + $this->objectManager = Bootstrap::getObjectManager(); + /* @var CacheInterface $cache */ + $cache = $this->objectManager->get(CacheInterface::class); $cache->remove('extension_attributes_config'); - - $this->repository = $this->objectManager->create(\Magento\Customer\Api\AddressRepositoryInterface::class); - $this->addressFactory = $this->objectManager->create( - \Magento\Customer\Api\Data\AddressInterfaceFactory::class - ); - $this->dataObjectHelper = $this->objectManager->create(\Magento\Framework\Api\DataObjectHelper::class); - $this->customerRegistry = $this->objectManager->get(\Magento\Customer\Model\CustomerRegistry::class); - - $regionFactory = $this->objectManager->get(RegionInterfaceFactory::class); - $region = $regionFactory->create() + $this->repository = $this->objectManager->get(AddressRepositoryInterface::class); + $this->addressFactory = $this->objectManager->get(AddressInterfaceFactory::class); + $this->dataObjectHelper = $this->objectManager->get(DataObjectHelper::class); + $this->customerRegistry = $this->objectManager->get(CustomerRegistry::class); + $this->storeManager = $this->objectManager->get(StoreManagerInterface::class); + $this->regionFactory = $this->objectManager->get(RegionInterfaceFactory::class); + $this->customerRepository = $this->objectManager->get(CustomerRepositoryInterface::class); + $region = $this->regionFactory->create() ->setRegionCode('AL') ->setRegion('Alabama') ->setRegionId(1); @@ -90,12 +107,11 @@ protected function setUp(): void ->setTelephone('3234676') ->setFirstname('John') ->setLastname('Smith'); - $this->expectedAddresses = [$address, $address2]; } /** - * Tear down. + * @inheritdoc */ protected function tearDown(): void { @@ -109,8 +125,9 @@ protected function tearDown(): void * @magentoDataFixture Magento/Customer/_files/customer_address.php * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php * @magentoAppIsolation enabled + * @return void */ - public function testSaveAddressChanges() + public function testSaveAddressChanges(): void { $address = $this->repository->getById(2); @@ -132,11 +149,19 @@ public function testSaveAddressChanges() * @magentoDataFixture Magento/Customer/_files/customer_address.php * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php * @magentoAppIsolation enabled + * @return void */ - public function testSaveAddressesIdSetButNotAlreadyExisting() + public function testSaveAddressesIdSetButNotAlreadyExisting(): void { - $this->expectException(\Magento\Framework\Exception\NoSuchEntityException::class); - $this->expectExceptionMessage('No such entity with addressId = 4200'); + $message = (string)__( + 'No such entity with %fieldName = %fieldValue', + [ + 'fieldName' => 'addressId', + 'fieldValue' => 4200, + ] + ); + $this->expectException(NoSuchEntityException::class); + $this->expectExceptionMessage($message); $proposedAddress = $this->_createSecondAddress()->setId(4200); $this->repository->save($proposedAddress); @@ -149,8 +174,9 @@ public function testSaveAddressesIdSetButNotAlreadyExisting() * @magentoDataFixture Magento/Customer/_files/customer_address.php * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php * @magentoAppIsolation enabled + * @return void */ - public function testGetAddressById() + public function testGetAddressById(): void { $addressId = 2; $address = $this->repository->getById($addressId); @@ -161,11 +187,19 @@ public function testGetAddressById() * Test for method get address by id with incorrect id. * * @magentoDataFixture Magento/Customer/_files/customer.php + * @return void */ - public function testGetAddressByIdBadAddressId() + public function testGetAddressByIdBadAddressId(): void { - $this->expectException(\Magento\Framework\Exception\NoSuchEntityException::class); - $this->expectExceptionMessage('No such entity with addressId = 12345'); + $message = (string)__( + 'No such entity with %fieldName = %fieldValue', + [ + 'fieldName' => 'addressId', + 'fieldValue' => 12345, + ] + ); + $this->expectException(NoSuchEntityException::class); + $this->expectExceptionMessage($message); $this->repository->getById(12345); } @@ -176,8 +210,9 @@ public function testGetAddressByIdBadAddressId() * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php * @magentoAppIsolation enabled + * @return void */ - public function testSaveNewAddress() + public function testSaveNewAddress(): void { $proposedAddress = $this->_createSecondAddress()->setCustomerId(1); @@ -204,8 +239,9 @@ public function testSaveNewAddress() * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php * @magentoAppIsolation enabled + * @return void */ - public function testSaveNewAddressWithAttributes() + public function testSaveNewAddressWithAttributes(): void { $proposedAddress = $this->_createFirstAddress() ->setCustomAttribute('firstname', 'Jane') @@ -231,8 +267,9 @@ public function testSaveNewAddressWithAttributes() * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php * @magentoAppIsolation enabled + * @return void */ - public function testSaveNewInvalidAddress() + public function testSaveNewInvalidAddress(): void { $address = $this->_createFirstAddress() ->setCustomAttribute('firstname', null) @@ -256,15 +293,19 @@ public function testSaveNewInvalidAddress() * * @return void */ - public function testSaveAddressesCustomerIdNotExist() + public function testSaveAddressesCustomerIdNotExist(): void { + $message = (string)__( + 'No such entity with %fieldName = %fieldValue', + [ + 'fieldName' => 'customerId', + 'fieldValue' => 4200, + ] + ); + $this->expectException(NoSuchEntityException::class); + $this->expectExceptionMessage($message); $proposedAddress = $this->_createSecondAddress()->setCustomerId(4200); - try { - $this->repository->save($proposedAddress); - $this->fail('Expected exception not thrown'); - } catch (NoSuchEntityException $nsee) { - $this->assertEquals('No such entity with customerId = 4200', $nsee->getMessage()); - } + $this->repository->save($proposedAddress); } /** @@ -272,15 +313,19 @@ public function testSaveAddressesCustomerIdNotExist() * * @return void */ - public function testSaveAddressesCustomerIdInvalid() + public function testSaveAddressesCustomerIdInvalid(): void { + $message = (string)__( + 'No such entity with %fieldName = %fieldValue', + [ + 'fieldName' => 'customerId', + 'fieldValue' => 'this_is_not_a_valid_id', + ] + ); + $this->expectException(NoSuchEntityException::class); + $this->expectExceptionMessage($message); $proposedAddress = $this->_createSecondAddress()->setCustomerId('this_is_not_a_valid_id'); - try { - $this->repository->save($proposedAddress); - $this->fail('Expected exception not thrown'); - } catch (NoSuchEntityException $nsee) { - $this->assertEquals('No such entity with customerId = this_is_not_a_valid_id', $nsee->getMessage()); - } + $this->repository->save($proposedAddress); } /** @@ -288,24 +333,26 @@ public function testSaveAddressesCustomerIdInvalid() * * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php + * @return void */ - public function testDeleteAddress() + public function testDeleteAddress(): void { $addressId = 1; // See that customer already has an address with expected addressId $addressDataObject = $this->repository->getById($addressId); $this->assertEquals($addressDataObject->getId(), $addressId); - // Delete the address from the customer $this->repository->delete($addressDataObject); - - // See that address is deleted - try { - $addressDataObject = $this->repository->getById($addressId); - $this->fail("Expected NoSuchEntityException not caught"); - } catch (NoSuchEntityException $exception) { - $this->assertEquals('No such entity with addressId = 1', $exception->getMessage()); - } + $message = (string)__( + 'No such entity with %fieldName = %fieldValue', + [ + 'fieldName' => 'addressId', + 'fieldValue' => 1, + ] + ); + $this->expectException(NoSuchEntityException::class); + $this->expectExceptionMessage($message); + $this->repository->getById($addressId); } /** @@ -313,8 +360,9 @@ public function testDeleteAddress() * * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php + * @return void */ - public function testDeleteAddressById() + public function testDeleteAddressById(): void { $addressId = 1; // See that customer already has an address with expected addressId @@ -323,50 +371,63 @@ public function testDeleteAddressById() // Delete the address from the customer $this->repository->deleteById($addressId); - - // See that address is deleted - try { - $addressDataObject = $this->repository->getById($addressId); - $this->fail("Expected NoSuchEntityException not caught"); - } catch (NoSuchEntityException $exception) { - $this->assertEquals('No such entity with addressId = 1', $exception->getMessage()); - } + $message = (string)__( + 'No such entity with %fieldName = %fieldValue', + [ + 'fieldName' => 'addressId', + 'fieldValue' => 1, + ] + ); + $this->expectException(NoSuchEntityException::class); + $this->expectExceptionMessage($message); + $this->repository->getById($addressId); } /** * Test delete address from customer with incorrect address id. * * @magentoDataFixture Magento/Customer/_files/customer.php + * @return void */ - public function testDeleteAddressFromCustomerBadAddressId() + public function testDeleteAddressFromCustomerBadAddressId(): void { - try { - $this->repository->deleteById(12345); - $this->fail("Expected NoSuchEntityException not caught"); - } catch (NoSuchEntityException $exception) { - $this->assertEquals('No such entity with addressId = 12345', $exception->getMessage()); - } + $message = (string)__( + 'No such entity with %fieldName = %fieldValue', + [ + 'fieldName' => 'addressId', + 'fieldValue' => 12345, + ] + ); + $this->expectException(NoSuchEntityException::class); + $this->expectExceptionMessage($message); + $this->repository->deleteById(12345); } /** * Test for searching addressed. * - * @param \Magento\Framework\Api\Filter[] $filters - * @param \Magento\Framework\Api\Filter[] $filterGroup - * @param \Magento\Framework\Api\SortOrder[] $filterOrders + * @param Filter[] $filters + * @param Filter[] $filterGroup + * @param SortOrder[] $filterOrders * @param array $expectedResult array of expected results indexed by ID * @param int $currentPage current page for search criteria * + * @return void * @dataProvider searchAddressDataProvider * * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php * @magentoAppIsolation enabled */ - public function testSearchAddresses($filters, $filterGroup, $filterOrders, $expectedResult, $currentPage) - { - /** @var \Magento\Framework\Api\SearchCriteriaBuilder $searchBuilder */ - $searchBuilder = $this->objectManager->create(\Magento\Framework\Api\SearchCriteriaBuilder::class); + public function testSearchAddresses( + $filters, + $filterGroup, + $filterOrders, + array $expectedResult, + int $currentPage + ): void { + /** @var SearchCriteriaBuilder $searchBuilder */ + $searchBuilder = $this->objectManager->create(SearchCriteriaBuilder::class); foreach ($filters as $filter) { $searchBuilder->addFilters([$filter]); } @@ -403,18 +464,18 @@ public function testSearchAddresses($filters, $filterGroup, $filterOrders, $expe * * @return array */ - public function searchAddressDataProvider() + public function searchAddressDataProvider(): array { /** - * @var \Magento\Framework\Api\FilterBuilder $filterBuilder + * @var FilterBuilder $filterBuilder */ - $filterBuilder = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create(\Magento\Framework\Api\FilterBuilder::class); + $filterBuilder = Bootstrap::getObjectManager() + ->create(FilterBuilder::class); /** - * @var \Magento\Framework\Api\SortOrderBuilder $orderBuilder + * @var SortOrderBuilder $orderBuilder */ - $orderBuilder = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create(\Magento\Framework\Api\SortOrderBuilder::class); + $orderBuilder = Bootstrap::getObjectManager() + ->create(SortOrderBuilder::class); return [ 'Address with postcode 75477' => [ [$filterBuilder->setField('postcode')->setValue('75477')->create()], @@ -482,13 +543,13 @@ public function searchAddressDataProvider() * Test for save addresses with restricted countries. * * @magentoDataFixture Magento/Customer/Fixtures/customer_sec_website.php + * @return void */ - public function testSaveAddressWithRestrictedCountries() + public function testSaveAddressWithRestrictedCountries(): void { - $website = $this->getWebsite('test'); - $customer = $this->getCustomer('customer.web@example.com', (int)$website->getId()); - $regionFactory = $this->objectManager->get(RegionInterfaceFactory::class); - $region = $regionFactory->create() + $website = $this->storeManager->getWebsite('test'); + $customer = $this->customerRepository->get('customer.web@example.com', (int)$website->getId()); + $region = $this->regionFactory->create() ->setRegionCode('CA') ->setRegion('California') ->setRegionId(12); @@ -501,7 +562,7 @@ public function testSaveAddressWithRestrictedCountries() 'country_id' => 'US', 'region' => $region, 'postcode' => 90230, - 'telephone' => '555655431' + 'telephone' => '555655431', ]; $address = $this->addressFactory->create(['data' => $addressData]); $saved = $this->repository->save($address); @@ -513,8 +574,9 @@ public function testSaveAddressWithRestrictedCountries() * * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php + * @return void */ - public function testSaveNewAddressWithExtraSpacesInPhone() + public function testSaveNewAddressWithExtraSpacesInPhone(): void { $proposedAddress = $this->_createSecondAddress() ->setCustomerId(1) @@ -528,8 +590,9 @@ public function testSaveNewAddressWithExtraSpacesInPhone() * Scenario for customer's default shipping and billing address saving and rollback. * * @magentoDataFixture Magento/Customer/_files/customer_without_addresses.php + * @return void */ - public function testCustomerAddressRelationSynchronisation() + public function testCustomerAddressRelationSynchronisation(): void { /** * Creating new address which is default shipping and billing for existing customer. @@ -544,7 +607,7 @@ public function testCustomerAddressRelationSynchronisation() /** * Customer registry should be updated with default shipping and billing addresses. */ - $customer = $this->getCustomer('customer@example.com', 1); + $customer = $this->customerRepository->get('customer@example.com', 1); $this->assertEquals($savedAddress->getId(), $customer->getDefaultShipping()); $this->assertEquals($savedAddress->getId(), $customer->getDefaultBilling()); @@ -557,21 +620,66 @@ public function testCustomerAddressRelationSynchronisation() /** * Customer's default shipping and billing addresses should be updated. */ - $customer = $this->getCustomer('customer@example.com', 1); + $customer = $this->customerRepository->get('customer@example.com', 1); $this->assertNull($customer->getDefaultShipping()); $this->assertNull($customer->getDefaultBilling()); } + /** + * Update Customer Address, with Alphanumeric Zip Code + * + * @magentoDataFixture Magento/Customer/_files/customer_one_address.php + * @return void + */ + public function testUpdateWithAlphanumericZipCode(): void + { + $region = $this->regionFactory->create() + ->setRegionCode('PH') + ->setRegion('Pinminnoch') + ->setRegionId(1); + $websiteId = (int)$this->storeManager->getWebsite('base')->getId(); + $customer = $this->customerRepository->get('customer_one_address@test.com', $websiteId); + $defaultBillingAddress = $customer->getDefaultBilling(); + $addressData = [ + AddressInterface::FIRSTNAME => 'Doe', + AddressInterface::LASTNAME => 'Doe', + AddressInterface::MIDDLENAME => 'Middle Name', + AddressInterface::SUFFIX => '_Suffix', + AddressInterface::PREFIX => 'Prefix', + AddressInterface::COMPANY => 'Company', + AddressInterface::STREET => ['Northgate Street, 39'], + AddressInterface::CITY => 'BICKTON', + AddressInterface::COUNTRY_ID => 'GB', + AddressInterface::REGION => $region, + AddressInterface::POSTCODE => 'KA26 1PF', + AddressInterface::TELEPHONE => '999-777-111-2345', + AddressInterface::VAT_ID => '987654321', + ]; + $customerAddress = $this->repository->getById((int)$defaultBillingAddress); + foreach ($addressData as $key => $value) { + $customerAddress->setData($key, $value); + } + $savedAddress = $this->repository->save($customerAddress); + $customerData = $savedAddress->__toArray(); + foreach ($addressData as $key => $value) { + if ($key === AddressInterface::REGION) { + $this->assertEquals($customerData[$key][AddressInterface::REGION], $value->getRegion()); + } else { + $this->assertEquals($value, $customerData[$key]); + } + } + } + /** * Helper function that returns an Address Data Object that matches the data from customer_address fixture * - * @return \Magento\Customer\Api\Data\AddressInterface + * @return AddressInterface */ - private function _createFirstAddress() + private function _createFirstAddress(): AddressInterface { $address = $this->addressFactory->create(); $this->dataObjectHelper->mergeDataObjects( - \Magento\Customer\Api\Data\AddressInterface::class, + AddressInterface::class, $address, $this->expectedAddresses[0] ); @@ -583,13 +691,13 @@ private function _createFirstAddress() /** * Helper function that returns an Address Data Object that matches the data from customer_two_address fixture * - * @return \Magento\Customer\Api\Data\AddressInterface + * @return AddressInterface */ - private function _createSecondAddress() + private function _createSecondAddress(): AddressInterface { $address = $this->addressFactory->create(); $this->dataObjectHelper->mergeDataObjects( - \Magento\Customer\Api\Data\AddressInterface::class, + AddressInterface::class, $address, $this->expectedAddresses[1] ); @@ -597,34 +705,4 @@ private function _createSecondAddress() $address->setRegion($this->expectedAddresses[1]->getRegion()); return $address; } - - /** - * Gets customer entity. - * - * @param string $email - * @param int $websiteId - * @return CustomerInterface - * @throws NoSuchEntityException - * @throws \Magento\Framework\Exception\LocalizedException - */ - private function getCustomer(string $email, int $websiteId): CustomerInterface - { - /** @var CustomerRepositoryInterface $repository */ - $repository = $this->objectManager->get(CustomerRepositoryInterface::class); - return $repository->get($email, $websiteId); - } - - /** - * Gets website entity. - * - * @param string $code - * @return WebsiteInterface - * @throws NoSuchEntityException - */ - private function getWebsite(string $code): WebsiteInterface - { - /** @var WebsiteRepositoryInterface $repository */ - $repository = $this->objectManager->get(WebsiteRepositoryInterface::class); - return $repository->get($code); - } } diff --git a/dev/tests/integration/testsuite/Magento/Customer/Ui/Component/Listing/AttributeRepositoryTest.php b/dev/tests/integration/testsuite/Magento/Customer/Ui/Component/Listing/AttributeRepositoryTest.php new file mode 100644 index 0000000000000..3982f9a76d57f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Ui/Component/Listing/AttributeRepositoryTest.php @@ -0,0 +1,65 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Ui\Component\Listing; + +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Test for \Magento\Customer\Ui\Component\Listing\AttributeRepository. + * + * @magentoAppArea adminhtml + */ +class AttributeRepositoryTest extends TestCase +{ + /** + * @var AttributeRepository + */ + private $model; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + $this->model = Bootstrap::getObjectManager()->create(AttributeRepository::class); + } + + /** + * Test for get store_id option array + * + * @magentoDataFixture Magento/Store/_files/core_fixturestore.php + * @return void + */ + public function testGetOptionArray(): void + { + $result = $this->model->getMetadataByCode('store_id'); + + $this->assertTrue(isset($result['options']['1']['value'])); + $this->assertEquals( + ['Default Store View', 'Fixture Store'], + $this->getStoreViewLabels($result['options'][1]['value']) + ); + } + + /** + * Returns prepared store view labels + * + * @param array $storeViewsData + * @return array + */ + private function getStoreViewLabels(array $storeViewsData): array + { + $result = []; + foreach ($storeViewsData as $storeView) { + $result[] = str_replace("\xc2\xa0", '', $storeView['label']); + } + + return $result; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Dhl/Model/CarrierTest.php b/dev/tests/integration/testsuite/Magento/Dhl/Model/CarrierTest.php index 7f9ca38353b2e..f9a1d2923e5be 100644 --- a/dev/tests/integration/testsuite/Magento/Dhl/Model/CarrierTest.php +++ b/dev/tests/integration/testsuite/Magento/Dhl/Model/CarrierTest.php @@ -9,10 +9,13 @@ use Magento\Framework\App\Config\ReinitableConfigInterface; use Magento\Framework\DataObject; +use Magento\Framework\HTTP\AsyncClient\HttpException; +use Magento\Framework\HTTP\AsyncClient\HttpResponseDeferredInterface; use Magento\Framework\HTTP\AsyncClient\Response; use Magento\Framework\HTTP\AsyncClientInterface; use Magento\Framework\Simplexml\Element; use Magento\Quote\Model\Quote\Address\RateRequest; +use Magento\Quote\Model\Quote\Address\RateResult\Error; use Magento\Shipping\Model\Shipment\Request; use Magento\Shipping\Model\Tracking\Result\Status; use Magento\Store\Model\ScopeInterface; @@ -475,6 +478,33 @@ public function testCollectRatesWithoutDimensions(?string $size, ?string $height $this->config->reinit(); } + /** + * Test get carriers rates if has HttpException. + * + * @magentoConfigFixture default_store carriers/dhl/active 1 + */ + public function testGetRatesWithHttpException(): void + { + $this->setDhlConfig(['showmethod' => 1]); + $requestData = $this->getRequestData(); + $deferredResponse = $this->getMockBuilder(HttpResponseDeferredInterface::class) + ->onlyMethods(['get']) + ->getMockForAbstractClass(); + $exception = new HttpException('Exception message'); + $deferredResponse->method('get')->willThrowException($exception); + $this->httpClient->setDeferredResponseMock($deferredResponse); + /** @var RateRequest $request */ + $request = Bootstrap::getObjectManager()->create(RateRequest::class, $requestData); + $this->dhlCarrier = Bootstrap::getObjectManager()->create(Carrier::class); + $resultRate = $this->dhlCarrier->collectRates($request)->getAllRates()[0]; + $error = Bootstrap::getObjectManager()->get(Error::class); + $error->setCarrier('dhl'); + $error->setCarrierTitle($this->dhlCarrier->getConfigData('title')); + $error->setErrorMessage($this->dhlCarrier->getConfigData('specificerrmsg')); + + $this->assertEquals($error, $resultRate); + } + /** * @return array */ diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Adapter/ElasticsearchTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Adapter/ElasticsearchTest.php new file mode 100644 index 0000000000000..986d98f0f2f14 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Adapter/ElasticsearchTest.php @@ -0,0 +1,194 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch\Model\Adapter; + +use Magento\AdvancedSearch\Model\Client\ClientInterface; +use Magento\Catalog\Model\ResourceModel\Eav\Attribute; +use Magento\Catalog\Setup\CategorySetup; +use Magento\Eav\Model\Entity\Attribute\Backend\ArrayBackend; +use Magento\Elasticsearch\Model\Adapter\Index\BuilderInterface; +use Magento\Elasticsearch\Model\Adapter\Index\IndexNameResolver; +use Magento\Elasticsearch\SearchAdapter\ConnectionManager; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Stdlib\ArrayManager; +use Magento\Indexer\Model\Indexer; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Elasticsearch adapter model test class + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class ElasticsearchTest extends TestCase +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var IndexNameResolver + */ + private $indexNameResolver; + + /** + * @var ClientInterface + */ + private $client; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var Elasticsearch + */ + private $adapter; + + /** + * @var BuilderInterface + */ + private $indexBuilder; + + /** + * @var ArrayManager + */ + private $arrayManager; + + /** + * @var string + */ + private $newIndex; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->indexNameResolver = $this->objectManager->get(IndexNameResolver::class); + $this->adapter = $this->objectManager->get(Elasticsearch::class); + $this->storeManager = $this->objectManager->create(StoreManagerInterface::class); + $connectionManager = $this->objectManager->create(ConnectionManager::class); + $this->client = $connectionManager->getConnection(); + $this->indexBuilder = $this->objectManager->get(BuilderInterface::class); + $this->arrayManager = $this->objectManager->get(ArrayManager::class); + $indexer = $this->objectManager->create(Indexer::class); + $indexer->load('catalogsearch_fulltext'); + $indexer->reindexAll(); + } + + /** + * @inheritdoc + */ + public function tearDown(): void + { + $this->deleteIndex($this->newIndex); + } + + /** + * Tests possibility to create mapping if adapter has obsolete index name in cache + * + * @magentoDataFixture Magento/Elasticsearch/_files/select_attribute.php + * @return void + */ + public function testRetryOnIndexNotFoundException(): void + { + $this->updateElasticsearchIndex(); + $this->createNewAttribute(); + $mapping = $this->client->getMapping(['index' => $this->newIndex]); + $pathField = $this->arrayManager->findPath('properties', $mapping); + $attributes = $this->arrayManager->get($pathField, $mapping, []); + $this->assertArrayHasKey('multiselect_attribute', $attributes); + } + + /** + * Prepare and save new attribute + * + * @return void + */ + public function createNewAttribute(): void + { + /** @var CategorySetup $installer */ + $installer = $this->objectManager->get(CategorySetup::class); + /** @var Attribute $attribute */ + $multiselectAttribute = $this->objectManager->get(Attribute::class); + $multiselectAttribute->setData( + [ + 'attribute_code' => 'multiselect_attribute', + 'entity_type_id' => $installer->getEntityTypeId('catalog_product'), + 'is_global' => 1, + 'is_user_defined' => 1, + 'frontend_input' => 'multiselect', + 'is_unique' => 0, + 'is_required' => 0, + 'is_searchable' => 1, + 'is_visible_in_advanced_search' => 0, + 'is_comparable' => 0, + 'is_filterable' => 1, + 'is_filterable_in_search' => 0, + 'is_used_for_promo_rules' => 0, + 'is_html_allowed_on_front' => 1, + 'is_visible_on_front' => 0, + 'used_in_product_listing' => 0, + 'used_for_sort_by' => 0, + 'frontend_label' => ['Multiselect Attribute'], + 'backend_type' => 'varchar', + 'backend_model' => ArrayBackend::class, + 'option' => [ + 'value' => [ + 'dog' => ['Dog'], + 'cat' => ['Cat'], + ], + 'order' => [ + 'dog' => 1, + 'cat' => 2, + ], + ], + ] + ); + $multiselectAttribute->save(); + } + + /** + * Prepare new index and delete old. Keep cache alive. + * + * @return void + */ + private function updateElasticsearchIndex(): void + { + $storeId = (int)$this->storeManager->getDefaultStoreView()->getId(); + $mappedIndexerId = 'product'; + $this->adapter->updateIndexMapping($storeId, $mappedIndexerId, 'select_attribute'); + $oldIndex = $this->indexNameResolver->getIndexFromAlias($storeId, $mappedIndexerId); + $this->newIndex = $oldIndex . '1'; + $this->deleteIndex($this->newIndex); + $this->indexBuilder->setStoreId($storeId); + $this->client->createIndex($this->newIndex, ['settings' => $this->indexBuilder->build()]); + $this->client->updateAlias( + $this->indexNameResolver->getIndexNameForAlias($storeId, $mappedIndexerId), + $this->newIndex, + $oldIndex + ); + $this->client->deleteIndex($oldIndex); + } + + /** + * Delete index by name if exists + * + * @param $newIndex + */ + private function deleteIndex($newIndex): void + { + if ($this->client->indexExists($newIndex)) { + $this->client->deleteIndex($newIndex); + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/Framework/File/UploaderTest.php b/dev/tests/integration/testsuite/Magento/Framework/File/UploaderTest.php index 3852fde042c87..293831f3850b3 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/File/UploaderTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/File/UploaderTest.php @@ -85,6 +85,51 @@ public function testUploadFileFromNotAllowedFolder(): void $this->uploaderFactory->create(['fileId' => $type]); } + /** + * Upload file test when `Old Media Gallery` is disabled + * + * @magentoConfigFixture system/media_gallery/enabled 1 + * @magentoAppArea adminhtml + * @dataProvider dirCodeDataProvider + * + * @param string $directoryCode + * @return void + */ + public function testUploadFileWhenOldMediaGalleryDisabled(string $directoryCode): void + { + $destinationDirectory = $this->filesystem->getDirectoryWrite($directoryCode); + $tmpDirectory = $this->filesystem->getDirectoryWrite(DirectoryList::SYS_TMP); + + $fileName = 'file.txt'; + $destinationDir = 'tmp'; + $filePath = $tmpDirectory->getAbsolutePath($fileName); + + $tmpDirectory->writeFile($fileName, 'some data'); + + $type = [ + 'tmp_name' => $filePath, + 'name' => $fileName, + ]; + + $uploader = $this->uploaderFactory->create(['fileId' => $type]); + $uploader->save($destinationDirectory->getAbsolutePath($destinationDir)); + + $this->assertTrue($destinationDirectory->isFile($destinationDir . DIRECTORY_SEPARATOR . $fileName)); + } + + /** + * DataProvider for testUploadFileWhenOldMediaGalleryDisabled + * + * @return array + */ + public function dirCodeDataProvider(): array + { + return [ + 'media destination' => [DirectoryList::MEDIA], + 'non-media destination' => [DirectoryList::VAR_DIR], + ]; + } + /** * @inheritdoc */ diff --git a/dev/tests/integration/testsuite/Magento/Framework/Session/SessionManagerTest.php b/dev/tests/integration/testsuite/Magento/Framework/Session/SessionManagerTest.php index d35d875ff8006..6a461c3007ca7 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Session/SessionManagerTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Session/SessionManagerTest.php @@ -115,6 +115,9 @@ class SessionManagerTest extends \PHPUnit\Framework\TestCase */ private $appState; + /** + * @inheritdoc + */ protected function setUp(): void { $this->sessionName = 'frontEndSession'; @@ -250,8 +253,6 @@ public function testIsValidForHost() $this->model->destroy(); } - /** - */ public function testStartAreaNotSet() { $this->expectException(\Magento\Framework\Exception\SessionException::class); @@ -283,17 +284,23 @@ public function testStartAreaNotSet() $this->model->start(); } - public function testConstructor() + /** + * @param string $saveMethod + * @dataProvider dataConstructor + * + * @return void + */ + public function testConstructor(string $saveMethod): void { global $mockPHPFunctions; $mockPHPFunctions = true; $deploymentConfigMock = $this->createMock(DeploymentConfig::class); $deploymentConfigMock->method('get') - ->willReturnCallback(function ($configPath) { + ->willReturnCallback(function ($configPath) use ($saveMethod) { switch ($configPath) { case Config::PARAM_SESSION_SAVE_METHOD: - return 'db'; + return $saveMethod; case Config::PARAM_SESSION_CACHE_LIMITER: return 'private_no_expire'; case Config::PARAM_SESSION_SAVE_PATH: @@ -313,13 +320,13 @@ public function testConstructor() 'sessionConfig' => $sessionConfig, ] ); - $this->assertEquals('db', $sessionConfig->getOption('session.save_handler')); + $this->assertEquals($saveMethod, $sessionConfig->getOption('session.save_handler')); $this->assertEquals('private_no_expire', $sessionConfig->getOption('session.cache_limiter')); $this->assertEquals('explicit_save_path', $sessionConfig->getOption('session.save_path')); $this->assertArrayHasKey('session.use_only_cookies', self::$isIniSetInvoked); $this->assertEquals('1', self::$isIniSetInvoked['session.use_only_cookies']); foreach ($sessionConfig->getOptions() as $option => $value) { - if ($option=='session.save_handler') { + if ($option === 'session.save_handler' && $value !== 'memcached') { $this->assertArrayNotHasKey('session.save_handler', self::$isIniSetInvoked); } else { $this->assertArrayHasKey($option, self::$isIniSetInvoked); @@ -329,6 +336,19 @@ public function testConstructor() $this->assertTrue(self::$isSessionSetSaveHandlerInvoked); } + /** + * @return array + */ + public function dataConstructor(): array + { + return [ + [Config::PARAM_SESSION_SAVE_METHOD =>'db'], + [Config::PARAM_SESSION_SAVE_METHOD =>'redis'], + [Config::PARAM_SESSION_SAVE_METHOD =>'memcached'], + [Config::PARAM_SESSION_SAVE_METHOD =>'user'], + ]; + } + private function initializeModel(): void { $this->model = $this->objectManager->create( diff --git a/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Export/File/DeleteTest.php b/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Export/File/DeleteTest.php index 53e90ebf76f66..e6f631758eb41 100644 --- a/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Export/File/DeleteTest.php +++ b/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Export/File/DeleteTest.php @@ -51,7 +51,7 @@ protected function setUp(): void $this->fileSystem = $this->_objectManager->get(Filesystem::class); $this->sourceFilePath = __DIR__ . '/../../Import/_files' . DIRECTORY_SEPARATOR . $this->fileName; //Refers to tests 'var' directory - $this->varDirectory = $this->fileSystem->getDirectoryRead(DirectoryList::VAR_DIR); + $this->varDirectory = $this->fileSystem->getDirectoryRead(DirectoryList::VAR_IMPORT_EXPORT); } /** diff --git a/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Export/File/DownloadTest.php b/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Export/File/DownloadTest.php index 2128516189474..277e6af871650 100644 --- a/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Export/File/DownloadTest.php +++ b/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Export/File/DownloadTest.php @@ -105,7 +105,7 @@ public function testExecute($file): void 'Incorrect response header "content-type"' ); $this->assertEquals( - 'attachment; filename="' . $this->fileName . '"', + 'attachment; filename="export/' . $this->fileName . '"', $contentDisposition->getFieldValue(), 'Incorrect response header "content-disposition"' ); diff --git a/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Import/_files/invalid_catalog_products.csv b/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Import/_files/invalid_catalog_products.csv new file mode 100644 index 0000000000000..f873a7c89ee53 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Import/_files/invalid_catalog_products.csv @@ -0,0 +1,3 @@ +sku,_store,_attribute_set,product_type,categories,_product_websites,color,cost,country_of_manufacture,created_at,custom_design,custom_design_from,custom_design_to,custom_layout_update,description,gallery,gift_message_available,gift_wrapping_available,gift_wrapping_price,has_options,image,image_label,is_returnable,manufacturer,meta_description,meta_keyword,meta_title,minimal_price,msrp,msrp_display_actual_price_type,name,news_from_date,news_to_date,options_container,page_layout,price,quantity_and_stock_status,related_tgtr_position_behavior,related_tgtr_position_limit,required_options,short_description,small_image,small_image_label,special_from_date,special_price,special_to_date,status,tax_class_id,thumbnail,thumbnail_label,updated_at,upsell_tgtr_position_behavior,upsell_tgtr_position_limit,url_key,url_path,visibility,weight,qty,min_qty,use_config_min_qty,is_qty_decimal,backorders,use_config_backorders,min_sale_qty,use_config_min_sale_qty,max_sale_qty,use_config_max_sale_qty,is_in_stock,notify_stock_qty,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,_related_sku,_related_position,_crosssell_sku,_crosssell_position,_upsell_sku,_upsell_position,_tier_price_website,_tier_price_customer_group,_tier_price_qty,_tier_price_price,_media_attribute_id,_media_image,_media_label,_media_position,_media_is_disabled,_associated_sku,_associated_default_qty,_associated_position +"",,Default,simple,,base,,,,"2014-12-25 19:52:47",,,,,,,,,,0,,,"No",,"simple product 1 ","simple product 1","simple product 1",,,,"simple product 1",,,"Block after Info Column",,100.0000,"In Stock",,,0,,,,,,,1,2,,,"2014-12-25 19:52:47",,,simple-product-1,,"catalog, search",,123.0000,0.0000,1,0,0,1,1.0000,1,0.0000,1,1,,1,1,0,1,0.0000,1,0,0,1,,,,,,,,,,,,,,,,,, +"",,Default,simple,,base,,,,"2014-12-25 19:53:14",,,,,,,,,,0,,,"No",,"simple product 2 ","simple product 2","simple product 2",,,,"simple product 2",,,"Block after Info Column",,200.0000,"In Stock",,,0,,,,,,,1,2,,,"2014-12-25 19:53:14",,,simple-product-2,,"catalog, search",,234.0000,0.0000,1,0,0,1,1.0000,1,0.0000,1,1,,1,1,0,1,0.0000,1,0,0,1,,,,,,,,,,,,,,,,,, diff --git a/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/ImportResultTest.php b/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/ImportResultTest.php new file mode 100644 index 0000000000000..37b5bcd81f68d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/ImportResultTest.php @@ -0,0 +1,91 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ImportExport\Controller\Adminhtml; + +use Magento\Framework\Filesystem\DirectoryList; +use Magento\Framework\HTTP\Adapter\FileTransferFactory; +use Magento\ImportExport\Model\Import; +use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface; +use Magento\ImportExport\Controller\Adminhtml\Import\HttpFactoryMock; + +/** + * Test for \Magento\ImportExport\Controller\Adminhtml\ImportResult class. + * + * @magentoAppArea adminhtml + */ +class ImportResultTest extends \Magento\TestFramework\TestCase\AbstractBackendController +{ + /** + * @param string $fileName + * @param string $mimeType + * @param string $delimiter + * @backupGlobals enabled + * @magentoDbIsolation enabled + * @dataProvider validationDataProvider + * @SuppressWarnings(PHPMD.Superglobals) + */ + public function testAddErrorMessages(string $fileName, string $mimeType, string $delimiter): void + { + $validationStrategy = ProcessingErrorAggregatorInterface::VALIDATION_STRATEGY_STOP_ON_ERROR; + + $this->getRequest()->setParam('isAjax', true); + $this->getRequest()->setMethod('POST'); + $_SERVER['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'; + + /** @var $formKey \Magento\Framework\Data\Form\FormKey */ + $formKey = $this->_objectManager->get(\Magento\Framework\Data\Form\FormKey::class); + $this->getRequest()->setPostValue('form_key', $formKey->getFormKey()); + $this->getRequest()->setPostValue('entity', 'catalog_product'); + $this->getRequest()->setPostValue('behavior', 'append'); + $this->getRequest()->setPostValue(Import::FIELD_NAME_VALIDATION_STRATEGY, $validationStrategy); + $this->getRequest()->setPostValue(Import::FIELD_NAME_ALLOWED_ERROR_COUNT, 0); + $this->getRequest()->setPostValue('_import_field_separator', $delimiter); + + /** @var \Magento\TestFramework\App\Filesystem $filesystem */ + $filesystem = $this->_objectManager->get(\Magento\Framework\Filesystem::class); + $tmpDir = $filesystem->getDirectoryWrite(DirectoryList::SYS_TMP); + $subDir = str_replace('\\', '_', __CLASS__); + $tmpDir->create($subDir); + $target = $tmpDir->getAbsolutePath("{$subDir}" . DIRECTORY_SEPARATOR . "{$fileName}"); + copy(__DIR__ . DIRECTORY_SEPARATOR . 'Import' . DIRECTORY_SEPARATOR . '_files' + . DIRECTORY_SEPARATOR . "{$fileName}", $target); + + $_FILES = [ + 'import_file' => [ + 'name' => $fileName, + 'type' => $mimeType, + 'tmp_name' => $target, + 'error' => 0, + 'size' => filesize($target) + ] + ]; + + $this->_objectManager->configure( + [ + 'preferences' => [FileTransferFactory::class => HttpFactoryMock::class] + ] + ); + + $this->dispatch('backend/admin/import/validate'); + $this->assertStringNotContainsString('<br>', $this->getResponse()->getBody()); + } + + /** + * @return array + */ + public function validationDataProvider(): array + { + return [ + [ + 'file_name' => 'invalid_catalog_products.csv', + 'mime-type' => 'text/csv', + 'delimiter' => ',', + ], + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/IntegrationTest.php b/dev/tests/integration/testsuite/Magento/IntegrationTest.php index 4626a10884290..26dec49c83b52 100644 --- a/dev/tests/integration/testsuite/Magento/IntegrationTest.php +++ b/dev/tests/integration/testsuite/Magento/IntegrationTest.php @@ -73,7 +73,10 @@ public static function suite($className) private static function getConfigurationFile(): string { $params = getopt('c:', ['configuration:']); - $longConfig = $params['configuration'] ?? ''; + $defaultConfigFile = file_exists(__DIR__ . '../../phpunit.xml') + ? __DIR__ . '/../../phpunit.xml' + : __DIR__ . '/../../phpunit.xml.dist'; + $longConfig = $params['configuration'] ?? $defaultConfigFile; $shortConfig = $params['c'] ?? ''; return $shortConfig ? $shortConfig : $longConfig; diff --git a/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AccountTest.php b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AccountTest.php index 861559acd8c20..b7e3ffcf9cd9d 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AccountTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AccountTest.php @@ -9,6 +9,7 @@ namespace Magento\Sales\Block\Adminhtml\Order\Create\Form; +use Magento\Backend\Block\Template\Context; use Magento\Backend\Model\Session\Quote as SessionQuote; use Magento\Customer\Api\CustomerRepositoryInterface; use Magento\Customer\Api\Data\AttributeMetadataInterface; @@ -17,6 +18,7 @@ use Magento\Customer\Model\Data\Option; use Magento\Customer\Model\Metadata\Form; use Magento\Customer\Model\Metadata\FormFactory; +use Magento\Framework\App\RequestInterface as Request; use Magento\Framework\View\LayoutInterface; use Magento\Quote\Model\Quote; use Magento\Store\Model\StoreManagerInterface; @@ -107,7 +109,7 @@ public function testGetFormWithCustomer() ); } - self::assertRegExp( + self::assertMatchesRegularExpression( '/<option value="'.$customerGroup.'".*?selected="selected"\>Wholesale\<\/option\>/is', $content, 'The Customer Group specified for the chosen customer should be selected.' @@ -150,13 +152,13 @@ public function testGetFormWithUserDefinedAttribute() $form->setUseContainer(true); $content = $form->toHtml(); - self::assertRegExp( + self::assertMatchesRegularExpression( '/\<option value="1".*?selected="selected"\>Yes\<\/option\>/is', $content, 'Default value for user defined custom attribute should be selected.' ); - self::assertRegExp( + self::assertMatchesRegularExpression( '/<option value="3".*?selected="selected"\>Retailer\<\/option\>/is', $content, 'The Customer Group specified for the chosen store should be selected.' @@ -203,6 +205,135 @@ public function testGetFormWithDefaultCustomerGroup() ); } + /** + * Test for get form with customer group based on vat id validation + * + * @dataProvider getDataForVatValidatedCustomer + * @param int $defaultCustomerGroupId + * @param int $vatValidatedCustomerGroupId + * @param array $customerDetails + * @param array $orderDetails + * @return void + */ + public function testGetFormWithVatValidatedCustomerGroup( + int $defaultCustomerGroupId, + int $vatValidatedCustomerGroupId, + array $customerDetails, + array $orderDetails + ): void { + $contextMock = $this->getMockBuilder(Context::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->getMock(); + $requestMock = $this->getMockBuilder(Request::class) + ->getMockForAbstractClass(); + $contextMock->expects($this->once()) + ->method('getRequest') + ->willReturn($requestMock); + $requestMock->expects($this->any()) + ->method('getParam') + ->willReturn($orderDetails); + + $quote = $this->objectManager->create(Quote::class); + $quote->setCustomerGroupId($defaultCustomerGroupId); + $quote->setData($customerDetails); + + $this->session = $this->getMockBuilder(SessionQuote::class) + ->disableOriginalConstructor() + ->setMethods(['getCustomerId', 'getQuote']) + ->getMock(); + $this->session->method('getQuote') + ->willReturn($quote); + $this->session->method('getCustomerId') + ->willReturn($customerDetails['customer_id']); + + $formFactory = $this->getFormFactoryMock(); + $this->objectManager->addSharedInstance($formFactory, FormFactory::class); + + /** @var LayoutInterface $layout */ + $layout = $this->objectManager->get(LayoutInterface::class); + $accountBlock = $layout->createBlock( + Account::class, + 'address_block' . rand(), + [ + 'context' => $contextMock, + 'sessionQuote' => $this->session + ] + ); + + $form = $accountBlock->getForm(); + + self::assertEquals( + $vatValidatedCustomerGroupId, + $form->getElement('group_id')->getValue(), + 'The Customer Group specified for the chosen customer should be selected.' + ); + } + + /** + * Data provider for vat validated customer group id + * + * @return array + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function getDataForVatValidatedCustomer(): array + { + return [ + 'Validated customer group id when its set in quote' => [ + 'defaultCustomerGroupId' => 0, + 'vatValidatedCustomerGroupId' => 3, + 'customerDetails' => [ + 'entity_id' => '35', + 'store_id' => 1, + 'created_at' => '2020-11-09 01:03:35', + 'updated_at' => '2020-11-09 05:44:07', + 'customer_id' => 1, + 'customer_tax_class_id' => '3', + 'customer_group_id' => 3, + 'customer_email' => 'test@test.com', + 'customer_prefix' => null, + 'customer_firstname' => null, + 'customer_middlename' => null, + 'customer_lastname' => null, + 'customer_suffix' => null, + 'customer_dob' => null, + ], + 'orderDetails' => [ + 'account' => [ + 'group_id' => 3, + 'email' => 'test@test.com' + ] + ] + ], + 'Validated customer group id when its set in request' => [ + 'defaultCustomerGroupId' => 0, + 'vatValidatedCustomerGroupId' => 3, + 'customerDetails' => [ + 'entity_id' => '35', + 'store_id' => 1, + 'created_at' => '2020-11-09 01:03:35', + 'updated_at' => '2020-11-09 05:44:07', + 'customer_id' => 1, + 'customer_tax_class_id' => '3', + 'customer_group_id' => null, + 'customer_email' => 'test@test.com', + 'customer_prefix' => null, + 'customer_firstname' => null, + 'customer_middlename' => null, + 'customer_lastname' => null, + 'customer_suffix' => null, + 'customer_dob' => null, + ], + 'orderDetails' => [ + 'account' => [ + 'group_id' => 3, + 'email' => 'test@test.com' + ] + ] + ] + ]; + } + /** * Creates a mock for Form object. * diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Create/IndexTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Create/IndexTest.php index 764c48b523968..a748183a9a026 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Create/IndexTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Create/IndexTest.php @@ -52,9 +52,17 @@ protected function setUp(): void public function testExecute(): void { $customerId = 1; + $editingOrderId = 10; + $this->getRequest()->setMethod(Http::METHOD_GET); $this->getRequest()->setParam('customer_id', $customerId); + $this->quoteSession->setOrderId($editingOrderId); + $this->assertEquals($editingOrderId, $this->quoteSession->getOrderId()); $this->dispatch('backend/sales/order_create/index'); + + // Check that existing order in session was cleared + $this->assertEquals(null, $this->quoteSession->getOrderId()); + $store = $this->storeManager->getStore(); $this->assertEquals($customerId, $this->quoteSession->getCustomerId()); $ruleData = $this->registry->registry('rule_data'); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_closed.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_closed.php new file mode 100644 index 0000000000000..1708d89881592 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_closed.php @@ -0,0 +1,148 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Customer\Api\Data\AddressInterface; +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Framework\DB\Transaction; +use Magento\OfflinePayments\Model\Checkmo; +use Magento\Sales\Api\CreditmemoItemRepositoryInterface; +use Magento\Sales\Api\CreditmemoRepositoryInterface; +use Magento\Sales\Api\Data\CreditmemoItemInterfaceFactory; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\Data\OrderInterfaceFactory; +use Magento\Sales\Api\Data\OrderItemInterface; +use Magento\Sales\Api\Data\OrderPaymentInterfaceFactory; +use Magento\Sales\Api\InvoiceManagementInterface; +use Magento\Sales\Api\Data\OrderItemInterfaceFactory; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Address; +use Magento\Sales\Model\Order\AddressFactory; +use Magento\Sales\Api\Data\OrderAddressInterfaceFactory; +use Magento\Sales\Model\Order\Creditmemo; +use Magento\Sales\Model\Order\CreditmemoFactory; +use Magento\Sales\Model\Order\ShipmentFactory; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/product_simple.php'); + +$objectManager = Bootstrap::getObjectManager(); +/** @var StoreManagerInterface $storeManager */ +$storeManager = $objectManager->get(StoreManagerInterface::class); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +$productRepository->cleanCache(); +/** @var OrderRepositoryInterface $orderRepository */ +$orderRepository = $objectManager->get(OrderRepositoryInterface::class); +/** @var InvoiceManagementInterface $invoiceService */ +$invoiceService = $objectManager->get(InvoiceManagementInterface::class); +/** @var ShipmentFactory $shipmentFactory */ +$shipmentFactory = $objectManager->get(ShipmentFactory::class); +/** @var CreditmemoFactory $creditmemoFactory */ +$creditmemoFactory = $objectManager->get(CreditmemoFactory::class); +/** @var CreditmemoItemInterfaceFactory $creditmemoItemFactory */ +$creditmemoItemFactory = $objectManager->get(CreditmemoItemInterfaceFactory::class); +/** @var CreditmemoRepositoryInterface $creditmemoRepository */ +$creditmemoRepository = $objectManager->get(CreditmemoRepositoryInterface::class); +/** @var CreditmemoItemRepositoryInterface $creditmemoItemRepository */ +$creditmemoItemRepository = $objectManager->get(CreditmemoItemRepositoryInterface::class); +$addressData = [ + AddressInterface::REGION => 'CA', + AddressInterface::REGION_ID => '12', + AddressInterface::POSTCODE => '11111', + AddressInterface::LASTNAME => 'lastname', + AddressInterface::FIRSTNAME => 'firstname', + AddressInterface::STREET => 'street', + AddressInterface::CITY => 'Los Angeles', + CustomerInterface::EMAIL => 'admin@example.com', + AddressInterface::TELEPHONE => '11111111', + AddressInterface::COUNTRY_ID => 'US', +]; +$product = $productRepository->get('simple'); +/** @var AddressFactory $addressFactory */ +$addressFactory = $objectManager->get(AddressFactory::class); +$billingAddress = $addressFactory->create(['data' => $addressData]); +$billingAddress->setAddressType(Address::TYPE_BILLING); +$shippingAddress = clone $billingAddress; +$shippingAddress->setId(null)->setAddressType(Address::TYPE_SHIPPING); +/** @var OrderPaymentInterfaceFactory $paymentFactory */ +$paymentFactory = $objectManager->get(OrderPaymentInterfaceFactory::class); +$payment = $paymentFactory->create(); +$payment->setMethod(Checkmo::PAYMENT_METHOD_CHECKMO_CODE) + ->setAdditionalInformation('last_trans_id', '11122') + ->setAdditionalInformation('metadata', ['type' => 'free', 'fraudulent' => false]); +/** @var OrderItemInterface $orderItem */ +$orderItem = $objectManager->get(OrderItemInterfaceFactory::class)->create(); +$orderItem->setProductId($product->getId()) + ->setQtyOrdered(2) + ->setBasePrice($product->getPrice()) + ->setPrice($product->getPrice()) + ->setRowTotal($product->getPrice()) + ->setProductType('simple') + ->setName($product->getName()) + ->setSku($product->getSku()) + ->setName('Test item'); +/** @var OrderInterface $order */ +$order = $objectManager->get(OrderInterfaceFactory::class)->create(); +$order->setIncrementId('100001111') + ->setState(Order::STATE_PROCESSING) + ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_PROCESSING)) + ->setSubtotal(100) + ->setGrandTotal(100) + ->setBaseSubtotal(100) + ->setBaseGrandTotal(100) + ->setOrderCurrencyCode('USD') + ->setBaseCurrencyCode('USD') + ->setCustomerIsGuest(true) + ->setCustomerEmail('customer@null.com') + ->setBillingAddress($billingAddress) + ->setShippingAddress($shippingAddress) + ->addItem($orderItem) + ->setPayment($payment) + ->setStoreId($storeManager->getStore('default')->getId()); +$orderRepository->save($order); + +$invoice = $invoiceService->prepareInvoice($order); +$invoice->register(); +$invoice->setIncrementId($order->getIncrementId()); +$order = $invoice->getOrder(); +$order->setIsInProcess(true); +$transactionSave = $objectManager->create(Transaction::class); +$transactionSave->addObject($invoice)->addObject($order)->save(); + +$items = []; +foreach ($order->getItems() as $item) { + $items[$item->getId()] = $item->getQtyOrdered(); +} + +$shipment = $objectManager->get(ShipmentFactory::class)->create($order, $items); +$shipment->register(); +$shipment->setIncrementId($order->getIncrementId()); +$transactionSave = $objectManager->create(Transaction::class); +$transactionSave->addObject($shipment)->addObject($order)->save(); +/** @var CreditmemoFactory $creditmemoFactory */ +$creditmemoFactory = $objectManager->get(CreditmemoFactory::class); +$creditmemo = $creditmemoFactory->createByOrder($order, $order->getData()); +$creditmemo->setOrder($order); +$creditmemo->setState(Creditmemo::STATE_OPEN); +$creditmemo->setIncrementId($order->getIncrementId()); +$creditmemoRepository->save($creditmemo); + +$orderItem->setName('Test item') + ->setQtyRefunded(2) + ->setQtyInvoiced(2) + ->setOriginalPrice($product->getPrice()); +$creditItem = $creditmemoItemFactory->create(); +$creditItem->setCreditmemo($creditmemo) + ->setName('Creditmemo item') + ->setOrderItemId($orderItem->getId()) + ->setQty(2) + ->setPrice($product->getPrice()); +$creditmemoItemRepository->save($creditItem); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_closed_rollback.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_closed_rollback.php new file mode 100644 index 0000000000000..4567294cec0a6 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_closed_rollback.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Registry; +use Magento\Sales\Api\CreditmemoRepositoryInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\Data\OrderInterfaceFactory; +use Magento\Sales\Api\InvoiceRepositoryInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Api\ShipmentRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +$objectManager = Bootstrap::getObjectManager(); +/** @var OrderRepositoryInterface $orderRepository */ +$orderRepository = $objectManager->get(OrderRepositoryInterface::class); +/** @var InvoiceRepositoryInterface $invoiceRepository */ +$invoiceRepository = $objectManager->get(InvoiceRepositoryInterface::class); +/** @var ShipmentRepositoryInterface $shipmentRepository */ +$shipmentRepository = $objectManager->get(ShipmentRepositoryInterface::class); +/** @var CreditmemoRepositoryInterface $creditmemoRepository */ +$creditmemoRepository = $objectManager->get(CreditmemoRepositoryInterface::class); +/** @var OrderInterface $order */ +$order = $objectManager->get(OrderInterfaceFactory::class)->create()->loadByIncrementId('100001111'); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +foreach ($order->getInvoiceCollection() as $invoice) { + $invoiceRepository->delete($invoice); +} + +$orderRepository->delete($order); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); + +$creditMemoGridAggregator = $objectManager->get(\CreditmemoGridAggregator::class); +$creditMemoGridAggregator->purge('100001111', 'order_increment_id'); + +Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/product_simple_rollback.php'); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_complete.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_complete.php new file mode 100644 index 0000000000000..bb8f028b41380 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_complete.php @@ -0,0 +1,116 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Customer\Api\Data\AddressInterface; +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Framework\DB\Transaction; +use Magento\OfflinePayments\Model\Checkmo; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\Data\OrderInterfaceFactory; +use Magento\Sales\Api\Data\OrderItemInterface; +use Magento\Sales\Api\Data\OrderPaymentInterfaceFactory; +use Magento\Sales\Api\InvoiceManagementInterface; +use Magento\Sales\Api\Data\OrderItemInterfaceFactory; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Address; +use Magento\Sales\Model\Order\AddressFactory; +use Magento\Sales\Model\Order\ShipmentFactory; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +Resolver::getInstance()->requireDataFixture('Magento/Sales/_files/default_rollback.php'); +Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/product_simple.php'); + +$objectManager = Bootstrap::getObjectManager(); +/** @var StoreManagerInterface $storeManager */ +$storeManager = $objectManager->get(StoreManagerInterface::class); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +$productRepository->cleanCache(); +/** @var OrderRepositoryInterface $orderRepository */ +$orderRepository = $objectManager->get(OrderRepositoryInterface::class); +/** @var InvoiceManagementInterface $invoiceService */ +$invoiceService = $objectManager->get(InvoiceManagementInterface::class); +/** @var ShipmentFactory $shipmentFactory */ +$shipmentFactory = $objectManager->get(ShipmentFactory::class); +$addressData = [ + AddressInterface::REGION => 'CA', + AddressInterface::REGION_ID => '12', + AddressInterface::POSTCODE => '11111', + AddressInterface::LASTNAME => 'lastname', + AddressInterface::FIRSTNAME => 'firstname', + AddressInterface::STREET => 'street', + AddressInterface::CITY => 'Los Angeles', + CustomerInterface::EMAIL => 'admin@example.com', + AddressInterface::TELEPHONE => '11111111', + AddressInterface::COUNTRY_ID => 'US', +]; +$product = $productRepository->get('simple'); +/** @var AddressFactory $addressFactory */ +$addressFactory = $objectManager->get(AddressFactory::class); +$billingAddress = $addressFactory->create(['data' => $addressData]); +$billingAddress->setAddressType(Address::TYPE_BILLING); +$shippingAddress = clone $billingAddress; +$shippingAddress->setId(null)->setAddressType(Address::TYPE_SHIPPING); +/** @var OrderPaymentInterfaceFactory $paymentFactory */ +$paymentFactory = $objectManager->get(OrderPaymentInterfaceFactory::class); +$payment = $paymentFactory->create(); +$payment->setMethod(Checkmo::PAYMENT_METHOD_CHECKMO_CODE) + ->setAdditionalInformation('last_trans_id', '11122') + ->setAdditionalInformation('metadata', ['type' => 'free', 'fraudulent' => false]); +/** @var OrderItemInterface $orderItem */ +$orderItem = $objectManager->get(OrderItemInterfaceFactory::class)->create(); +$orderItem->setProductId($product->getId()) + ->setQtyOrdered(2) + ->setBasePrice($product->getPrice()) + ->setPrice($product->getPrice()) + ->setRowTotal($product->getPrice()) + ->setProductType('simple') + ->setName($product->getName()) + ->setSku($product->getSku()) + ->setName('Test item'); +/** @var OrderInterface $order */ +$order = $objectManager->get(OrderInterfaceFactory::class)->create(); +$order->setIncrementId('100000333') + ->setState(Order::STATE_PROCESSING) + ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_PROCESSING)) + ->setSubtotal(100) + ->setGrandTotal(100) + ->setBaseSubtotal(100) + ->setBaseGrandTotal(100) + ->setOrderCurrencyCode('USD') + ->setBaseCurrencyCode('USD') + ->setCustomerIsGuest(true) + ->setCustomerEmail('customer@null.com') + ->setBillingAddress($billingAddress) + ->setShippingAddress($shippingAddress) + ->addItem($orderItem) + ->setPayment($payment) + ->setStoreId($storeManager->getStore('default')->getId()); +$orderRepository->save($order); + +$invoice = $invoiceService->prepareInvoice($order); +$invoice->register(); +$invoice->setIncrementId($order->getIncrementId()); +$order = $invoice->getOrder(); +$order->setIsInProcess(true); +$transactionSave = $objectManager->create(Transaction::class); +$transactionSave->addObject($invoice)->addObject($order)->save(); + +$items = []; +foreach ($order->getItems() as $item) { + $items[$item->getId()] = $item->getQtyOrdered(); +} + +$shipment = $objectManager->get(ShipmentFactory::class)->create($order, $items); +$shipment->register(); +$shipment->setIncrementId($order->getIncrementId()); +$transactionSave = $objectManager->create(Transaction::class); +$transactionSave->addObject($shipment)->addObject($order)->save(); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_complete_rollback.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_complete_rollback.php new file mode 100644 index 0000000000000..06a36c16b90f7 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_complete_rollback.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Registry; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\Data\OrderInterfaceFactory; +use Magento\Sales\Api\InvoiceRepositoryInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Api\ShipmentRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +$objectManager = Bootstrap::getObjectManager(); +/** @var OrderRepositoryInterface $orderRepository */ +$orderRepository = $objectManager->get(OrderRepositoryInterface::class); +/** @var InvoiceRepositoryInterface $invoiceRepository */ +$invoiceRepository = $objectManager->get(InvoiceRepositoryInterface::class); +/** @var ShipmentRepositoryInterface $shipmentRepository */ +$shipmentRepository = $objectManager->get(ShipmentRepositoryInterface::class); +/** @var OrderInterface $order */ +$order = $objectManager->get(OrderInterfaceFactory::class)->create()->loadByIncrementId('100000333'); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +foreach ($order->getInvoiceCollection() as $invoice) { + $invoiceRepository->delete($invoice); +} + +$orderRepository->delete($order); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); + +Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/product_simple_rollback.php'); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_state_hold_rollback.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_state_hold_rollback.php new file mode 100644 index 0000000000000..07d468289f5b4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_state_hold_rollback.php @@ -0,0 +1,10 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +Resolver::getInstance()->requireDataFixture('Magento/Sales/_files/order_rollback.php'); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_invoice_shipment_creditmemo.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_invoice_shipment_creditmemo.php new file mode 100644 index 0000000000000..c95b21c1433bf --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_invoice_shipment_creditmemo.php @@ -0,0 +1,149 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Customer\Api\Data\AddressInterface; +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Framework\DB\Transaction; +use Magento\OfflinePayments\Model\Checkmo; +use Magento\Sales\Api\CreditmemoItemRepositoryInterface; +use Magento\Sales\Api\CreditmemoRepositoryInterface; +use Magento\Sales\Api\Data\CreditmemoItemInterfaceFactory; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\Data\OrderInterfaceFactory; +use Magento\Sales\Api\Data\OrderItemInterface; +use Magento\Sales\Api\Data\OrderPaymentInterfaceFactory; +use Magento\Sales\Api\InvoiceManagementInterface; +use Magento\Sales\Api\Data\OrderItemInterfaceFactory; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Address; +use Magento\Sales\Model\Order\AddressFactory; +use Magento\Sales\Api\Data\OrderAddressInterfaceFactory; +use Magento\Sales\Model\Order\Creditmemo; +use Magento\Sales\Model\Order\CreditmemoFactory; +use Magento\Sales\Model\Order\ShipmentFactory; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +Resolver::getInstance()->requireDataFixture('Magento/Sales/_files/default_rollback.php'); +Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/product_simple.php'); + +$objectManager = Bootstrap::getObjectManager(); +/** @var StoreManagerInterface $storeManager */ +$storeManager = $objectManager->get(StoreManagerInterface::class); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +$productRepository->cleanCache(); +/** @var OrderRepositoryInterface $orderRepository */ +$orderRepository = $objectManager->get(OrderRepositoryInterface::class); +/** @var InvoiceManagementInterface $invoiceService */ +$invoiceService = $objectManager->get(InvoiceManagementInterface::class); +/** @var ShipmentFactory $shipmentFactory */ +$shipmentFactory = $objectManager->get(ShipmentFactory::class); +/** @var CreditmemoFactory $creditmemoFactory */ +$creditmemoFactory = $objectManager->get(CreditmemoFactory::class); +/** @var CreditmemoItemInterfaceFactory $creditmemoItemFactory */ +$creditmemoItemFactory = $objectManager->get(CreditmemoItemInterfaceFactory::class); +/** @var CreditmemoRepositoryInterface $creditmemoRepository */ +$creditmemoRepository = $objectManager->get(CreditmemoRepositoryInterface::class); +/** @var CreditmemoItemRepositoryInterface $creditmemoItemRepository */ +$creditmemoItemRepository = $objectManager->get(CreditmemoItemRepositoryInterface::class); +$addressData = [ + AddressInterface::REGION => 'CA', + AddressInterface::REGION_ID => '12', + AddressInterface::POSTCODE => '11111', + AddressInterface::LASTNAME => 'lastname', + AddressInterface::FIRSTNAME => 'firstname', + AddressInterface::STREET => 'street', + AddressInterface::CITY => 'Los Angeles', + CustomerInterface::EMAIL => 'admin@example.com', + AddressInterface::TELEPHONE => '11111111', + AddressInterface::COUNTRY_ID => 'US', +]; +$product = $productRepository->get('simple'); +/** @var AddressFactory $addressFactory */ +$addressFactory = $objectManager->get(AddressFactory::class); +$billingAddress = $addressFactory->create(['data' => $addressData]); +$billingAddress->setAddressType(Address::TYPE_BILLING); +$shippingAddress = clone $billingAddress; +$shippingAddress->setId(null)->setAddressType(Address::TYPE_SHIPPING); +/** @var OrderPaymentInterfaceFactory $paymentFactory */ +$paymentFactory = $objectManager->get(OrderPaymentInterfaceFactory::class); +$payment = $paymentFactory->create(); +$payment->setMethod(Checkmo::PAYMENT_METHOD_CHECKMO_CODE) + ->setAdditionalInformation('last_trans_id', '11122') + ->setAdditionalInformation('metadata', ['type' => 'free', 'fraudulent' => false]); +/** @var OrderItemInterface $orderItem */ +$orderItem = $objectManager->get(OrderItemInterfaceFactory::class)->create(); +$orderItem->setProductId($product->getId()) + ->setQtyOrdered(2) + ->setBasePrice($product->getPrice()) + ->setPrice($product->getPrice()) + ->setRowTotal($product->getPrice()) + ->setProductType('simple') + ->setName($product->getName()) + ->setSku($product->getSku()) + ->setName('Test item'); +/** @var OrderInterface $order */ +$order = $objectManager->get(OrderInterfaceFactory::class)->create(); +$order->setIncrementId('100000111') + ->setState(Order::STATE_PROCESSING) + ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_PROCESSING)) + ->setSubtotal(100) + ->setGrandTotal(100) + ->setBaseSubtotal(100) + ->setBaseGrandTotal(100) + ->setOrderCurrencyCode('USD') + ->setBaseCurrencyCode('USD') + ->setCustomerIsGuest(true) + ->setCustomerEmail('customer@null.com') + ->setBillingAddress($billingAddress) + ->setShippingAddress($shippingAddress) + ->addItem($orderItem) + ->setPayment($payment) + ->setStoreId($storeManager->getStore('default')->getId()); +$orderRepository->save($order); + +$invoice = $invoiceService->prepareInvoice($order); +$invoice->register(); +$invoice->setIncrementId($order->getIncrementId()); +$order = $invoice->getOrder(); +$order->setIsInProcess(true); +$transactionSave = $objectManager->create(Transaction::class); +$transactionSave->addObject($invoice)->addObject($order)->save(); + +$items = []; +foreach ($order->getItems() as $item) { + $items[$item->getId()] = $item->getQtyOrdered(); +} + +$shipment = $objectManager->get(ShipmentFactory::class)->create($order, $items); +$shipment->register(); +$shipment->setIncrementId($order->getIncrementId()); +$transactionSave = $objectManager->create(Transaction::class); +$transactionSave->addObject($shipment)->addObject($order)->save(); +/** @var CreditmemoFactory $creditmemoFactory */ +$creditmemoFactory = $objectManager->get(CreditmemoFactory::class); +$creditmemo = $creditmemoFactory->createByOrder($order, $order->getData()); +$creditmemo->setOrder($order); +$creditmemo->setState(Creditmemo::STATE_OPEN); +$creditmemo->setIncrementId($order->getIncrementId()); +$creditmemoRepository->save($creditmemo); + +$orderItem->setName('Test item') + ->setQtyRefunded(2) + ->setQtyInvoiced(2) + ->setOriginalPrice($product->getPrice()); +$creditItem = $creditmemoItemFactory->create(); +$creditItem->setCreditmemo($creditmemo) + ->setName('Creditmemo item') + ->setOrderItemId($orderItem->getId()) + ->setQty(2) + ->setPrice($product->getPrice()); +$creditmemoItemRepository->save($creditItem); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_invoice_shipment_creditmemo_rollback.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_invoice_shipment_creditmemo_rollback.php new file mode 100644 index 0000000000000..123d44c4610d0 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_invoice_shipment_creditmemo_rollback.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Registry; +use Magento\Sales\Api\CreditmemoRepositoryInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\Data\OrderInterfaceFactory; +use Magento\Sales\Api\InvoiceRepositoryInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Api\ShipmentRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +$objectManager = Bootstrap::getObjectManager(); +/** @var OrderRepositoryInterface $orderRepository */ +$orderRepository = $objectManager->get(OrderRepositoryInterface::class); +/** @var InvoiceRepositoryInterface $invoiceRepository */ +$invoiceRepository = $objectManager->get(InvoiceRepositoryInterface::class); +/** @var ShipmentRepositoryInterface $shipmentRepository */ +$shipmentRepository = $objectManager->get(ShipmentRepositoryInterface::class); +/** @var CreditmemoRepositoryInterface $creditmemoRepository */ +$creditmemoRepository = $objectManager->get(CreditmemoRepositoryInterface::class); +/** @var OrderInterface $order */ +$order = $objectManager->get(OrderInterfaceFactory::class)->create()->loadByIncrementId('100000111'); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +foreach ($order->getInvoiceCollection() as $invoice) { + $invoiceRepository->delete($invoice); +} + +$orderRepository->delete($order); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); + +$creditMemoGridAggregator = $objectManager->get(\CreditmemoGridAggregator::class); +$creditMemoGridAggregator->purge('100000111', 'order_increment_id'); + +Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/product_simple_rollback.php'); diff --git a/dev/tests/integration/testsuite/Magento/Store/_files/second_website_with_store_group_and_store.php b/dev/tests/integration/testsuite/Magento/Store/_files/second_website_with_store_group_and_store.php index a4e2b05b1a28c..ee677b35176d6 100644 --- a/dev/tests/integration/testsuite/Magento/Store/_files/second_website_with_store_group_and_store.php +++ b/dev/tests/integration/testsuite/Magento/Store/_files/second_website_with_store_group_and_store.php @@ -56,5 +56,6 @@ $storeResource->save($store); /* Refresh CatalogSearch index */ /** @var IndexerRegistry $indexerRegistry */ +$storeManager->reinitStores(); $indexerRegistry = $objectManager->get(IndexerRegistry::class); $indexerRegistry->get(Fulltext::INDEXER_ID)->reindexAll(); diff --git a/dev/tests/integration/testsuite/Magento/Ups/Model/CarrierTest.php b/dev/tests/integration/testsuite/Magento/Ups/Model/CarrierTest.php index 345c572b92861..7000fadd5154d 100644 --- a/dev/tests/integration/testsuite/Magento/Ups/Model/CarrierTest.php +++ b/dev/tests/integration/testsuite/Magento/Ups/Model/CarrierTest.php @@ -9,9 +9,12 @@ use Magento\Framework\App\Config\ReinitableConfigInterface; use Magento\Framework\DataObject; +use Magento\Framework\HTTP\AsyncClient\HttpException; +use Magento\Framework\HTTP\AsyncClient\HttpResponseDeferredInterface; use Magento\Framework\HTTP\AsyncClient\Response; use Magento\Framework\HTTP\AsyncClientInterface; use Magento\Quote\Model\Quote\Address\RateRequest; +use Magento\Quote\Model\Quote\Address\RateResult\Error; use Magento\TestFramework\Helper\Bootstrap; use Magento\Quote\Model\Quote\Address\RateRequestFactory; use Magento\TestFramework\HTTP\AsyncClientInterfaceMock; @@ -290,6 +293,50 @@ public function testRequestToShipment(): void $this->httpClient->clearRequests(); } + /** + * Test get carriers rates if has HttpException. + * + * @magentoConfigFixture default_store shipping/origin/country_id GB + * @magentoConfigFixture default_store carriers/ups/type UPS_XML + * @magentoConfigFixture default_store carriers/ups/active 1 + * @magentoConfigFixture default_store carriers/ups/shipper_number 12345 + * @magentoConfigFixture default_store carriers/ups/origin_shipment Shipments Originating in the European Union + * @magentoConfigFixture default_store carriers/ups/username user + * @magentoConfigFixture default_store carriers/ups/password pass + * @magentoConfigFixture default_store carriers/ups/access_license_number acn + * @magentoConfigFixture default_store currency/options/allow GBP,USD,EUR + * @magentoConfigFixture default_store currency/options/base GBP + */ + public function testGetRatesWithHttpException(): void + { + $deferredResponse = $this->getMockBuilder(HttpResponseDeferredInterface::class) + ->onlyMethods(['get']) + ->getMockForAbstractClass(); + $exception = new HttpException('Exception message'); + $deferredResponse->method('get')->willThrowException($exception); + $this->httpClient->setDeferredResponseMock($deferredResponse); + $request = Bootstrap::getObjectManager()->create( + RateRequest::class, + [ + 'data' => [ + 'dest_country' => 'GB', + 'dest_postal' => '01105', + 'product' => '11', + 'action' => 'Rate', + 'unit_measure' => 'KGS', + 'base_currency' => new DataObject(['code' => 'GBP']) + ] + ] + ); + $resultRate = $this->carrier->collectRates($request)->getAllRates()[0]; + $error = Bootstrap::getObjectManager()->get(Error::class); + $error->setCarrier('ups'); + $error->setCarrierTitle($this->carrier->getConfigData('title')); + $error->setErrorMessage($this->carrier->getConfigData('specificerrmsg')); + + $this->assertEquals($error, $resultRate); + } + /** * Extracts shipment request. * diff --git a/dev/tests/integration/testsuite/Magento/Usps/Model/CarrierTest.php b/dev/tests/integration/testsuite/Magento/Usps/Model/CarrierTest.php index 901db842acb7d..011381cb5917d 100644 --- a/dev/tests/integration/testsuite/Magento/Usps/Model/CarrierTest.php +++ b/dev/tests/integration/testsuite/Magento/Usps/Model/CarrierTest.php @@ -13,6 +13,9 @@ use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\HTTP\AsyncClientInterfaceMock; use PHPUnit\Framework\TestCase; +use Magento\Framework\HTTP\AsyncClient\HttpResponseDeferredInterface; +use Magento\Framework\HTTP\AsyncClient\HttpException; +use Magento\Quote\Model\Quote\Address\RateResult\Error; /** * Test for USPS integration. @@ -176,4 +179,72 @@ public function testCollectUnavailableRates(): void $rates = $this->carrier->collectRates($request); $this->assertCount(5, $rates->getAllRates()); } + + /** + * Test get carriers rates if has HttpException. + * + * @magentoConfigFixture default_store carriers/usps/allowed_methods 0_FCLE,0_FCL,0_FCP,1,2,3,4,6,7,13,16,17,22,23,25,27,28,33,34,35,36,37,42,43,53,55,56,57,61,INT_1,INT_2,INT_4,INT_6,INT_7,INT_8,INT_9,INT_10,INT_11,INT_12,INT_13,INT_14,INT_15,INT_16,INT_20,INT_26 + * @magentoConfigFixture default_store carriers/usps/showmethod 1 + * @magentoConfigFixture default_store carriers/usps/debug 1 + * @magentoConfigFixture default_store carriers/usps/userid test + * @magentoConfigFixture default_store carriers/usps/mode 0 + * @magentoConfigFixture default_store carriers/usps/active 1 + * @magentoConfigFixture default_store shipping/origin/country_id US + * @magentoConfigFixture default_store shipping/origin/postcode 90034 + * @magentoConfigFixture default_store carriers/usps/machinable true + */ + public function testGetRatesWithHttpException(): void + { + $deferredResponse = $this->getMockBuilder(HttpResponseDeferredInterface::class) + ->onlyMethods(['get']) + ->getMockForAbstractClass(); + $exception = new HttpException('Exception message'); + $deferredResponse->method('get')->willThrowException($exception); + $this->httpClient->setDeferredResponseMock($deferredResponse); + /** @var RateRequest $request */ + $request = Bootstrap::getObjectManager()->create( + RateRequest::class, + [ + 'data' => [ + 'dest_country_id' => 'US', + 'dest_region_code' => 'NY', + 'dest_street' => 'main st1', + 'dest_city' => 'New York', + 'dest_postcode' => '10029', + 'package_value' => '5', + 'package_value_with_discount' => '5', + 'package_weight' => '4.2657', + 'package_qty' => '1', + 'package_physical_value' => '5', + 'free_method_weight' => '5', + 'store_id' => '1', + 'website_id' => '1', + 'free_shipping' => '0', + 'limit_carrier' => 'null', + 'base_subtotal_incl_tax' => '5', + 'orig_country_id' => 'US', + 'country_id' => 'US', + 'region_id' => '12', + 'city' => 'Culver City', + 'postcode' => '90034', + 'usps_userid' => '213MAGEN6752', + 'usps_container' => 'VARIABLE', + 'usps_size' => 'REGULAR', + 'girth' => null, + 'height' => null, + 'length' => null, + 'width' => null, + ] + ] + ); + + $rates = $this->carrier->collectRates($request); + $resultRate = $rates->getAllRates()[0]; + $error = Bootstrap::getObjectManager()->get(Error::class); + $error->setCarrier('usps'); + $error->setCarrierTitle($this->carrier->getConfigData('title')); + $error->setErrorMessage($this->carrier->getConfigData('specificerrmsg')); + + $this->assertEquals($error, $resultRate); + } } diff --git a/dev/tests/integration/testsuite/Magento/Widget/Block/Adminhtml/Widget/InstanceTest.php b/dev/tests/integration/testsuite/Magento/Widget/Block/Adminhtml/Widget/InstanceTest.php index 57d4322ded9a3..9088dbfea6f85 100644 --- a/dev/tests/integration/testsuite/Magento/Widget/Block/Adminhtml/Widget/InstanceTest.php +++ b/dev/tests/integration/testsuite/Magento/Widget/Block/Adminhtml/Widget/InstanceTest.php @@ -118,13 +118,23 @@ public function gridFiltersDataProvider(): array 'filter' => base64_encode('sort_order=1'), ], 'expected_widgets' => [ - 'recently compared products' + 'recently compared products', + ], + ], + 'filter_by_title_and_luma_theme' => [ + 'filter' => [ + 'filter' => base64_encode( + 'title=cms page widget title&theme_id=' . $this->loadThemeIdByCode('Magento/luma') + ), + ], + 'expected_widgets' => [ + 'cms page widget title', ], ], - 'filter_by_multiple_filters' => [ + 'filter_by_title_and_blank_theme' => [ 'filter' => [ 'filter' => base64_encode( - 'type=Magento%5CCatalog%5CBlock%5CWidget%5CRecentlyCompared&sort_order=1' + 'title=recently compared products&theme_id=' . $this->loadThemeIdByCode('Magento/blank') ), ], 'expected_widgets' => [ @@ -270,7 +280,7 @@ private function assertWidgets($expectedWidgets, AbstractCollection $collection) $this->assertCount(count($expectedWidgets), $collection); foreach ($expectedWidgets as $widgetTitle) { $item = $collection->getItemByColumnValue('title', $widgetTitle); - $this->assertNotNull($item); + $this->assertNotNull($item, sprintf('Expected widget %s is not present in grid', $widgetTitle)); } } diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/action/set-payment-information-extended.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/action/set-payment-information-extended.test.js index 3a31672848d5c..deea9b087c3f9 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/action/set-payment-information-extended.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/action/set-payment-information-extended.test.js @@ -58,6 +58,7 @@ define([ deferral = new $.Deferred(), paymentData = { method: 'checkmo', + title: 'Method title', additionalData: null, __disableTmpl: { title: true diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/estimate-service.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/estimate-service.test.js index ce9c98c9d2560..3d8325a3ecd54 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/estimate-service.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/estimate-service.test.js @@ -150,12 +150,5 @@ define([ }); expect(mocks['Magento_Checkout/js/model/cart/totals-processor/default'].estimateTotals).toHaveBeenCalled(); }); - - it('test subscribe when cart data was changed', function () { - mocks['Magento_Customer/js/customer-data'].get('cart')({ - dataId: 2 - }); - expect(mocks['Magento_Checkout/js/model/cart/totals-processor/default'].estimateTotals).toHaveBeenCalled(); - }); }); }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/PageCache/frontend/js/form-key-provider.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/PageCache/frontend/js/form-key-provider.test.js new file mode 100644 index 0000000000000..162c00a9c0cca --- /dev/null +++ b/dev/tests/js/jasmine/tests/app/code/Magento/PageCache/frontend/js/form-key-provider.test.js @@ -0,0 +1,46 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/* eslint-disable max-nested-callbacks */ +define([ + 'jquery', + 'Magento_PageCache/js/form-key-provider' +], function ($, formKeyInit) { + 'use strict'; + + describe('Testing FormKey Provider', function () { + var inputContainer; + + beforeEach(function () { + inputContainer = document.createElement('input'); + inputContainer.setAttribute('value', ''); + inputContainer.setAttribute('name', 'form_key'); + document.querySelector('body').appendChild(inputContainer); + }); + + afterEach(function () { + $(inputContainer).remove(); + document.cookie = 'form_key= ; expires = Thu, 01 Jan 1970 00:00:00 GMT'; + }); + + it('sets value of input[form_key]', function () { + var expires, + date = new Date(); + + date.setTime(date.getTime() + 86400000); + expires = '; expires=' + date.toUTCString(); + document.cookie = 'form_key=FAKE_COOKIE' + expires + '; path=/'; + formKeyInit(); + expect($(inputContainer).val()).toEqual('FAKE_COOKIE'); + }); + + it('widget sets value to input[form_key] in case it empty', function () { + document.cookie = 'form_key= ; expires = Thu, 01 Jan 1970 00:00:00 GMT'; + formKeyInit(); + expect($(inputContainer).val()).toEqual(jasmine.any(String)); + expect($(inputContainer).val().length).toEqual(16); + }); + }); +}); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/PageCache/frontend/js/page-cache.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/PageCache/frontend/js/page-cache.test.js index 14e0523fd5151..f12d36e888f22 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/PageCache/frontend/js/page-cache.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/PageCache/frontend/js/page-cache.test.js @@ -106,13 +106,6 @@ define([ expect($.mage.cookies.set).toHaveBeenCalled(); expect(inputContainer.val()).toEqual(jasmine.any(String)); }); - - it('widget exists on load on body', function (done) { - $(function () { - expect($('body').data('mageFormKey')).toBeDefined(); - done(); - }); - }); }); describe('Testing PageCache Widget', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/components/multiline.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/components/multiline.test.js new file mode 100644 index 0000000000000..431d97605e318 --- /dev/null +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/components/multiline.test.js @@ -0,0 +1,65 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'uiRegistry', + 'Magento_Ui/js/form/components/multiline' +], function (registry, Constr) { + 'use strict'; + + describe('Magento_Ui/js/form/components/multiline', function () { + var obj, + dataScope = 'data', + providerName = 'provider', + prepareDataProvider = function (value) { // jscs:ignore jsDoc + registry.set(providerName, { + /** Stub */ + on: function () {}, + + /** Stub */ + set: function () {}, + + /** Stub */ + get: function () { + return value; + } + }); + }; + + describe('Verify process of preparing value for Multiline options', function () { + it('Check _prepareValue method', function () { + obj = new Constr({ + _prepareValue: jasmine.createSpy() + }); + + expect(obj._prepareValue).toHaveBeenCalled(); + }); + + it('Check array preparation', function () { + var value = ['some_array']; + + prepareDataProvider(value); + obj = new Constr({ + provider: providerName, + dataScope: dataScope + }); + + expect(obj.value().slice(0)).toEqual(value); + }); + + it('Check preparation of string value with line breaks', function () { + var value = 'first\n\nthird'; + + prepareDataProvider(value); + obj = new Constr({ + provider: providerName, + dataScope: dataScope + }); + + expect(obj.value()).toEqual(['first', '', 'third']); + }); + }); + }); +}); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/date.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/date.test.js index f9d379407fcbd..91f37e20cca87 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/date.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/date.test.js @@ -40,8 +40,12 @@ define([ name: '', index: '', dataScope: dataScope, + outputDateFormat: 'DD-MM-YYYY', + inputDateFormat: 'YYYY-MM-DD', + pickerDateTimeFormat: 'DD-MM-YYYY', options: { - showsTime: true + showsTime: false, + dateFormat: 'dd-MM-y' } }); utils = mageUtils; @@ -56,5 +60,10 @@ define([ expect(utils.convertToMomentFormat).toHaveBeenCalled(); }); + it('Check date will have correct value with different locales.', function () { + model.value('2020-11-28'); + expect(model.getPreview()).toBe('28-11-2020'); + }); + }); }); diff --git a/dev/tests/js/jasmine/tests/lib/mage/tinymce4Adapter.test.js b/dev/tests/js/jasmine/tests/lib/mage/tinymce4Adapter.test.js index 7af1a19e4b4c1..55eab905e8725 100644 --- a/dev/tests/js/jasmine/tests/lib/mage/tinymce4Adapter.test.js +++ b/dev/tests/js/jasmine/tests/lib/mage/tinymce4Adapter.test.js @@ -5,8 +5,9 @@ define([ 'wysiwygAdapter', - 'underscore' -], function (wysiwygAdapter, _) { + 'underscore', + 'tinymce4' +], function (wysiwygAdapter, _, tinyMCE4) { 'use strict'; var obj; @@ -38,4 +39,12 @@ define([ expect(_.size(obj.eventBus.arrEvents['open_browser_callback'])).toBe(1); }); }); + + describe('"triggerSave" method', function () { + it('Check method call.', function () { + spyOn(tinyMCE4, 'triggerSave'); + obj.triggerSave(); + expect(tinyMCE4.triggerSave).toHaveBeenCalled(); + }); + }); }); diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_methods.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_methods.php index ff8e7db0f4260..d6a4053448fc8 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_methods.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_methods.php @@ -2569,4 +2569,5 @@ 'Magento\Framework\MessageQueue\ConsumerInterface' ], ['isOrderIncrementIdUsed', 'Magento\Quote\Model\ResourceModel\Quote', 'Magento\Sales\Model\OrderIncrementIdChecker::isIncrementIdUsed'], + ['update', 'Magento\Authorization\Model\Rules', 'Magento\Authorization\Model\Rules::update'], ]; diff --git a/dev/tests/unit/phpunit.xml.dist b/dev/tests/unit/phpunit.xml.dist index 1793b0f698310..709bb7c1ea95b 100644 --- a/dev/tests/unit/phpunit.xml.dist +++ b/dev/tests/unit/phpunit.xml.dist @@ -15,6 +15,7 @@ <testsuites> <testsuite name="Magento_Unit_Tests_App_Code"> <directory>../../../app/code/*/*/Test/Unit</directory> + <directory>../../../vendor/magento/module-*/Test/Unit</directory> </testsuite> <testsuite name="Magento_Unit_Tests_Other"> <directory>../../../lib/internal/*/*/Test/Unit</directory> diff --git a/lib/internal/Magento/Framework/App/Filesystem/DirectoryList.php b/lib/internal/Magento/Framework/App/Filesystem/DirectoryList.php index fdf524348293b..ce150b00a6665 100644 --- a/lib/internal/Magento/Framework/App/Filesystem/DirectoryList.php +++ b/lib/internal/Magento/Framework/App/Filesystem/DirectoryList.php @@ -63,9 +63,9 @@ class DirectoryList extends \Magento\Framework\Filesystem\DirectoryList const VAR_EXPORT = 'var_export'; /** - * Storage of files which were imported. + * Storage of files for import/export. */ - const VAR_IMPORT = 'var_import'; + const VAR_IMPORT_EXPORT = 'import_export'; /** * Temporary files @@ -156,7 +156,8 @@ public static function getDefaultConfig() self::CONFIG => [parent::PATH => 'app/etc'], self::LIB_INTERNAL => [parent::PATH => 'lib/internal'], self::VAR_DIR => [parent::PATH => 'var'], - self::VAR_EXPORT => [parent::PATH => 'var/export', parent::URL_PATH => 'export'], + /** @deprecated */ + self::VAR_EXPORT => [parent::PATH => 'var/export'], self::CACHE => [parent::PATH => 'var/cache'], self::LOG => [parent::PATH => 'var/log'], self::DI => [parent::PATH => 'generated/metadata'], @@ -175,7 +176,7 @@ public static function getDefaultConfig() self::GENERATED => [parent::PATH => 'generated'], self::GENERATED_CODE => [parent::PATH => Io::DEFAULT_DIRECTORY], self::GENERATED_METADATA => [parent::PATH => 'generated/metadata'], - self::VAR_IMPORT => [parent::PATH => 'var/import', parent::URL_PATH => 'var/import'], + self::VAR_IMPORT_EXPORT => [parent::PATH => 'var', parent::URL_PATH => 'import_export'], ]; return parent::getDefaultConfig() + $result; } diff --git a/lib/internal/Magento/Framework/File/Uploader.php b/lib/internal/Magento/Framework/File/Uploader.php index c3246bfbf1e48..3ed8e274cfecd 100644 --- a/lib/internal/Magento/Framework/File/Uploader.php +++ b/lib/internal/Magento/Framework/File/Uploader.php @@ -13,6 +13,7 @@ use Magento\Framework\Filesystem\DriverInterface; use Magento\Framework\Filesystem\DriverPool; use Magento\Framework\Validation\ValidationException; +use Psr\Log\LoggerInterface; /** * File upload class @@ -133,6 +134,11 @@ class Uploader */ private $fileMime; + /** + * @var LoggerInterface + */ + private $logger; + /**#@+ * File upload type (multiple or single) */ @@ -329,24 +335,42 @@ protected function chmod($file) * @param string $tmpPath * @param string $destPath * @return bool - * @throws FileSystemException */ protected function _moveFile($tmpPath, $destPath) { $rootCode = DirectoryList::PUB; - if (strpos($destPath, $this->getDirectoryList()->getPath($rootCode)) !== 0) { - $rootCode = DirectoryList::ROOT; - } + try { + if (strpos($destPath, $this->getDirectoryList()->getPath($rootCode)) !== 0) { + $rootCode = DirectoryList::ROOT; + } - $destPath = str_replace($this->getDirectoryList()->getPath($rootCode), '', $destPath); - $directory = $this->getTargetDirectory()->getDirectoryWrite($rootCode); + $destPath = str_replace($this->getDirectoryList()->getPath($rootCode), '', $destPath); + $directory = $this->getTargetDirectory()->getDirectoryWrite($rootCode); - return $this->getFileDriver()->rename( - $tmpPath, - $directory->getAbsolutePath($destPath), - $directory->getDriver() - ); + return $this->getFileDriver()->rename( + $tmpPath, + $directory->getAbsolutePath($destPath), + $directory->getDriver() + ); + } catch (FileSystemException $exception) { + $this->getLogger()->critical($exception->getMessage()); + return false; + } + } + + /** + * Get logger instance. + * + * @deprecated + * @return LoggerInterface + */ + private function getLogger(): LoggerInterface + { + if (!$this->logger) { + $this->logger = ObjectManager::getInstance()->get(LoggerInterface::class); + } + return $this->logger; } /** diff --git a/lib/internal/Magento/Framework/Mview/Test/Unit/View/SubscriptionTest.php b/lib/internal/Magento/Framework/Mview/Test/Unit/View/SubscriptionTest.php index b91c0b525390f..697071d0d3067 100644 --- a/lib/internal/Magento/Framework/Mview/Test/Unit/View/SubscriptionTest.php +++ b/lib/internal/Magento/Framework/Mview/Test/Unit/View/SubscriptionTest.php @@ -323,4 +323,62 @@ public function testRemove() $this->model->remove(); } + + /** + * Test ignored columns for mview specified at the subscription level + * + * @return void + */ + public function testBuildStatementIgnoredColumnSubscriptionLevel(): void + { + $tableName = 'cataloginventory_stock_item'; + $ignoredColumnName = 'low_stock_date'; + $notIgnoredColumnName = 'backorders'; + $viewId = 'cataloginventory_stock'; + $ignoredData = [ + $viewId => [ + $tableName => [ + $ignoredColumnName => true, + $notIgnoredColumnName => false + ] + ] + ]; + + $this->connectionMock->expects($this->once()) + ->method('isTableExists') + ->willReturn(true); + $this->connectionMock->expects($this->once()) + ->method('describeTable') + ->willReturn([ + 'item_id' => ['COLUMN_NAME' => 'item_id'], + 'product_id' => ['COLUMN_NAME' => 'product_id'], + 'stock_id' => ['COLUMN_NAME' => 'stock_id'], + 'qty' => ['COLUMN_NAME' => 'qty'], + $ignoredColumnName => ['COLUMN_NAME' => $ignoredColumnName], + $notIgnoredColumnName => ['COLUMN_NAME' => $notIgnoredColumnName] + ]); + + $otherChangelogMock = $this->getMockForAbstractClass(ChangelogInterface::class); + $otherChangelogMock->expects($this->once()) + ->method('getViewId') + ->willReturn($viewId); + + $model = new Subscription( + $this->resourceMock, + $this->triggerFactoryMock, + $this->viewCollectionMock, + $this->viewMock, + $tableName, + 'columnName', + [], + $ignoredData + ); + + $method = new \ReflectionMethod($model, 'buildStatement'); + $method->setAccessible(true); + $statement = $method->invoke($model, Trigger::EVENT_UPDATE, $otherChangelogMock); + + $this->assertStringNotContainsString($ignoredColumnName, $statement); + $this->assertStringContainsString($notIgnoredColumnName, $statement); + } } diff --git a/lib/internal/Magento/Framework/Mview/View/Subscription.php b/lib/internal/Magento/Framework/Mview/View/Subscription.php index ddfa39f0a089f..510a4579d160b 100644 --- a/lib/internal/Magento/Framework/Mview/View/Subscription.php +++ b/lib/internal/Magento/Framework/Mview/View/Subscription.php @@ -11,9 +11,7 @@ use Magento\Framework\Mview\View\StateInterface; /** - * Class Subscription - * - * @package Magento\Framework\Mview\View + * Class Subscription for handling partial indexation triggers */ class Subscription implements SubscriptionInterface { @@ -57,13 +55,19 @@ class Subscription implements SubscriptionInterface protected $linkedViews = []; /** - * List of columns that can be updated in a subscribed table + * List of columns that can be updated in any subscribed table * without creating a new change log entry * * @var array */ private $ignoredUpdateColumns = []; + /** + * List of columns that can be updated in a specific subscribed table + * for a specific view without creating a new change log entry + */ + private $ignoredUpdateColumnsBySubscription = []; + /** * @var Resource */ @@ -77,6 +81,7 @@ class Subscription implements SubscriptionInterface * @param string $tableName * @param string $columnName * @param array $ignoredUpdateColumns + * @param array $ignoredUpdateColumnsBySubscription */ public function __construct( ResourceConnection $resource, @@ -85,7 +90,8 @@ public function __construct( \Magento\Framework\Mview\ViewInterface $view, $tableName, $columnName, - $ignoredUpdateColumns = [] + $ignoredUpdateColumns = [], + $ignoredUpdateColumnsBySubscription = [] ) { $this->connection = $resource->getConnection(); $this->triggerFactory = $triggerFactory; @@ -95,6 +101,7 @@ public function __construct( $this->columnName = $columnName; $this->resource = $resource; $this->ignoredUpdateColumns = $ignoredUpdateColumns; + $this->ignoredUpdateColumnsBySubscription = $ignoredUpdateColumnsBySubscription; } /** @@ -209,7 +216,14 @@ protected function buildStatement($event, $changelog) $describe = $this->connection->describeTable($tableName) ) { $columnNames = array_column($describe, 'COLUMN_NAME'); - $columnNames = array_diff($columnNames, $this->ignoredUpdateColumns); + $ignoredColumnsBySubscription = array_filter( + $this->ignoredUpdateColumnsBySubscription[$changelog->getViewId()][$this->getTableName()] ?? [] + ); + $ignoredColumns = array_merge( + $this->ignoredUpdateColumns, + array_keys($ignoredColumnsBySubscription) + ); + $columnNames = array_diff($columnNames, $ignoredColumns); if ($columnNames) { $columns = []; foreach ($columnNames as $columnName) { diff --git a/lib/internal/Magento/Framework/Session/SessionManager.php b/lib/internal/Magento/Framework/Session/SessionManager.php index 7e43a9f2d99c0..90b49a32a7851 100644 --- a/lib/internal/Magento/Framework/Session/SessionManager.php +++ b/lib/internal/Magento/Framework/Session/SessionManager.php @@ -619,7 +619,9 @@ private function initIniOptions() } foreach ($this->sessionConfig->getOptions() as $option => $value) { - if ($option=='session.save_handler') { + // Since PHP 7.2 it is explicitly forbidden to set the module name to "user". + // https://bugs.php.net/bug.php?id=77384 + if ($option === 'session.save_handler' && $value !== 'memcached') { continue; } else { $result = ini_set($option, $value); diff --git a/lib/web/mage/adminhtml/form.js b/lib/web/mage/adminhtml/form.js index 054594ff9e9f2..20ca1d505bb21 100644 --- a/lib/web/mage/adminhtml/form.js +++ b/lib/web/mage/adminhtml/form.js @@ -465,7 +465,7 @@ define([ // define whether the target should show up var shouldShowUp = true, idFrom, from, values, isInArray, isNegative, headElement, isInheritCheckboxChecked, target, inputs, - isAnInputOrSelect, currentConfig, rowElement, fromId, radioFrom; + isAnInputOrSelect, currentConfig, rowElement, fromId, radioFrom, targetArray, isChooser; for (idFrom in valuesFrom) { //eslint-disable-line guard-for-in from = $(idFrom); @@ -502,6 +502,12 @@ define([ // Account for the chooser style parameters. if (target === null && headElement.length === 0 && idTo.substring(0, 16) === 'options_fieldset') { + targetArray = $$('input[id*="' + idTo + '"]'); + isChooser = true; + + if (targetArray !== null && targetArray.length > 0) { + target = targetArray[0]; + } headElement = jQuery('.field-' + idTo).add('.field-chooser' + idTo); } @@ -526,7 +532,7 @@ define([ // don't touch hidden inputs (and Use Default inputs too), bc they may have custom logic if ((!item.type || item.type != 'hidden') && !($(item.id + '_inherit') && $(item.id + '_inherit').checked) && //eslint-disable-line !(currentConfig['can_edit_price'] != undefined && !currentConfig['can_edit_price']) && //eslint-disable-line - !item.getAttribute('readonly') //eslint-disable-line + !item.getAttribute('readonly') || isChooser //eslint-disable-line ) { item.disabled = false; jQuery(item).removeClass('ignore-validate'); diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js index d74838b0c26bf..fdb2e626e1263 100644 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js @@ -749,6 +749,13 @@ define([ */ addContentEditableAttributeBackToNonEditableNodes: function () { jQuery('.mceNonEditable', this.activeEditor().getDoc()).attr('contenteditable', false); + }, + + /** + * Calls the save method on all editor instances in the collection. + */ + triggerSave: function () { + tinyMCE4.triggerSave(); } };