diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/TierPrice.php b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/TierPrice.php index b1f99bb1fc05f..2ad96cfeab1d9 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/TierPrice.php +++ b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/TierPrice.php @@ -3,15 +3,25 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing\Validator; use Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing; +use Magento\CatalogImportExport\Model\Import\Product; use Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface; +use Magento\CatalogImportExport\Model\Import\Product\StoreResolver; +use Magento\CatalogImportExport\Model\Import\Product\Validator\AbstractImportValidator; +use Magento\CatalogImportExport\Model\Import\Product\Validator\AbstractPrice; +use Magento\Customer\Api\GroupRepositoryInterface; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\Exception\LocalizedException; -class TierPrice extends \Magento\CatalogImportExport\Model\Import\Product\Validator\AbstractPrice +class TierPrice extends AbstractPrice { /** - * @var \Magento\CatalogImportExport\Model\Import\Product\StoreResolver + * @var StoreResolver */ protected $storeResolver; @@ -27,21 +37,26 @@ class TierPrice extends \Magento\CatalogImportExport\Model\Import\Product\Valida ]; /** - * @param \Magento\Customer\Api\GroupRepositoryInterface $groupRepository - * @param \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder - * @param \Magento\CatalogImportExport\Model\Import\Product\StoreResolver $storeResolver + * @param GroupRepositoryInterface $groupRepository + * @param SearchCriteriaBuilder $searchCriteriaBuilder + * @param StoreResolver $storeResolver */ public function __construct( - \Magento\Customer\Api\GroupRepositoryInterface $groupRepository, - \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder, - \Magento\CatalogImportExport\Model\Import\Product\StoreResolver $storeResolver + GroupRepositoryInterface $groupRepository, + SearchCriteriaBuilder $searchCriteriaBuilder, + StoreResolver $storeResolver ) { $this->storeResolver = $storeResolver; parent::__construct($groupRepository, $searchCriteriaBuilder); } /** - * {@inheritdoc} + * Initialize method + * + * @param Product $context + * + * @return RowValidatorInterface|AbstractImportValidator|void + * @throws LocalizedException */ public function init($context) { @@ -52,7 +67,10 @@ public function init($context) } /** + * Add decimal error + * * @param string $attribute + * * @return void */ protected function addDecimalError($attribute) @@ -83,12 +101,12 @@ public function getCustomerGroups() } /** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * Validation * * @param mixed $value * @return bool * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ public function isValid($value) { @@ -133,6 +151,7 @@ public function isValid($value) * Check if at list one value and length are valid * * @param array $value + * * @return bool */ protected function isValidValueAndLength(array $value) @@ -150,6 +169,7 @@ protected function isValidValueAndLength(array $value) * Check if value has empty columns * * @param array $value + * * @return bool */ protected function hasEmptyColumns(array $value) diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/TierPriceType.php b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/TierPriceType.php index 6aa59e6227a05..71b5271a90fa2 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/TierPriceType.php +++ b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/TierPriceType.php @@ -4,28 +4,24 @@ * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing\Validator; use Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing; use Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface; +use Magento\CatalogImportExport\Model\Import\Product\Validator\AbstractImportValidator; /** * Class TierPriceType validates tier price type. */ -class TierPriceType extends \Magento\CatalogImportExport\Model\Import\Product\Validator\AbstractImportValidator +class TierPriceType extends AbstractImportValidator { - /** - * {@inheritdoc} - */ - public function init($context) - { - return parent::init($context); - } - /** * Validate tier price type. * * @param array $value + * * @return bool */ public function isValid($value) diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/Website.php b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/Website.php index 0f3f8b3389c7d..93c63dcbcab28 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/Website.php +++ b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/Website.php @@ -3,49 +3,47 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing\Validator; use Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing; -use Magento\CatalogImportExport\Model\Import\Product\Validator\AbstractImportValidator; use Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface; +use Magento\CatalogImportExport\Model\Import\Product\StoreResolver; +use Magento\CatalogImportExport\Model\Import\Product\Validator\AbstractImportValidator; +use Magento\Store\Model\Website as WebsiteModel; class Website extends AbstractImportValidator implements RowValidatorInterface { /** - * @var \Magento\CatalogImportExport\Model\Import\Product\StoreResolver + * @var StoreResolver */ protected $storeResolver; /** - * @var \Magento\Store\Model\Website + * @var WebsiteModel */ protected $websiteModel; /** - * @param \Magento\CatalogImportExport\Model\Import\Product\StoreResolver $storeResolver - * @param \Magento\Store\Model\Website $websiteModel + * @param StoreResolver $storeResolver + * @param WebsiteModel $websiteModel */ public function __construct( - \Magento\CatalogImportExport\Model\Import\Product\StoreResolver $storeResolver, - \Magento\Store\Model\Website $websiteModel + StoreResolver $storeResolver, + WebsiteModel $websiteModel ) { $this->storeResolver = $storeResolver; $this->websiteModel = $websiteModel; } - /** - * {@inheritdoc} - */ - public function init($context) - { - return parent::init($context); - } - /** * Validate by website type * * @param array $value * @param string $websiteCode + * * @return bool */ protected function isWebsiteValid($value, $websiteCode) @@ -62,7 +60,8 @@ protected function isWebsiteValid($value, $websiteCode) /** * Validate value * - * @param mixed $value + * @param array $value + * * @return bool */ public function isValid($value) @@ -85,6 +84,7 @@ public function isValid($value) */ public function getAllWebsitesValue() { - return AdvancedPricing::VALUE_ALL_WEBSITES . ' ['.$this->websiteModel->getBaseCurrency()->getCurrencyCode().']'; + return AdvancedPricing::VALUE_ALL_WEBSITES . + ' [' . $this->websiteModel->getBaseCurrency()->getCurrencyCode() . ']'; } } diff --git a/app/code/Magento/Authorization/Test/Unit/Model/ResourceModel/RulesTest.php b/app/code/Magento/Authorization/Test/Unit/Model/ResourceModel/RulesTest.php index 6560b3be3b947..cbd9012e1a80d 100644 --- a/app/code/Magento/Authorization/Test/Unit/Model/ResourceModel/RulesTest.php +++ b/app/code/Magento/Authorization/Test/Unit/Model/ResourceModel/RulesTest.php @@ -182,7 +182,7 @@ public function testSaveRelNoResources() /** * Test LocalizedException throw case. */ - public function testLocalizedExceptionOccurance() + public function testLocalizedExceptionOccurrence() { $this->expectException(LocalizedException::class); $this->expectExceptionMessage("TestException"); @@ -212,7 +212,7 @@ public function testLocalizedExceptionOccurance() /** * Test generic exception throw case. */ - public function testGenericExceptionOccurance() + public function testGenericExceptionOccurrence() { $exception = new \Exception('GenericException'); diff --git a/app/code/Magento/Backup/Test/Mftf/ActionGroup/deleteBackupActionGroup.xml b/app/code/Magento/Backup/Test/Mftf/ActionGroup/deleteBackupActionGroup.xml deleted file mode 100644 index b879a2aa9647a..0000000000000 --- a/app/code/Magento/Backup/Test/Mftf/ActionGroup/deleteBackupActionGroup.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDefaultImageBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDefaultImageBundleProductTest.xml index 55038b0c68c44..3588bd360004d 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDefaultImageBundleProductTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDefaultImageBundleProductTest.xml @@ -25,7 +25,7 @@ - + diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAttributeSetSelectionTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAttributeSetSelectionTest.xml index 06a05e7a29cd9..6bb4c3e0e1a5f 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAttributeSetSelectionTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAttributeSetSelectionTest.xml @@ -22,7 +22,7 @@ - + diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminBasicBundleProductAttributesTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminBasicBundleProductAttributesTest.xml index b9fb1c72e079f..56dad8b214d0f 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminBasicBundleProductAttributesTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminBasicBundleProductAttributesTest.xml @@ -21,7 +21,7 @@ - + diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteABundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteABundleProductTest.xml index 51c30ef86242c..e9b91a0efb595 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteABundleProductTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteABundleProductTest.xml @@ -25,7 +25,7 @@ - + diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminEditRelatedBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminEditRelatedBundleProductTest.xml index 4ba5d0f66e096..61a9c7c975324 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminEditRelatedBundleProductTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminEditRelatedBundleProductTest.xml @@ -31,7 +31,7 @@ - + diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminFilterProductListByBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminFilterProductListByBundleProductTest.xml index f8914656cc32b..45cbb7d83bb2d 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminFilterProductListByBundleProductTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminFilterProductListByBundleProductTest.xml @@ -25,7 +25,7 @@ - + diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminMassDeleteBundleProductsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminMassDeleteBundleProductsTest.xml index 2c1fcb6d7de42..a995a629092c6 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminMassDeleteBundleProductsTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminMassDeleteBundleProductsTest.xml @@ -29,7 +29,7 @@ - + diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminRemoveDefaultImageBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminRemoveDefaultImageBundleProductTest.xml index ab1d4bb5ce68a..e0e0fe571fe32 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminRemoveDefaultImageBundleProductTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminRemoveDefaultImageBundleProductTest.xml @@ -29,7 +29,7 @@ - + diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductWithTierPriceInCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductWithTierPriceInCartTest.xml index b5812817b5640..def24c86e1730 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductWithTierPriceInCartTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductWithTierPriceInCartTest.xml @@ -34,7 +34,7 @@ - + diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/CurrencyChangingBundleProductInCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/CurrencyChangingBundleProductInCartTest.xml index ada91d068efcf..b25139835de59 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/CurrencyChangingBundleProductInCartTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/CurrencyChangingBundleProductInCartTest.xml @@ -40,7 +40,7 @@ - + diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/MassEnableDisableBundleProductsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/MassEnableDisableBundleProductsTest.xml index fd94ca93b1600..5ebb50b161284 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/MassEnableDisableBundleProductsTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/MassEnableDisableBundleProductsTest.xml @@ -29,7 +29,7 @@ - + @@ -130,8 +130,7 @@ - - + diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/NewBundleProductSelectionTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/NewBundleProductSelectionTest.xml index efef033f9d974..e722caaf090c5 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/NewBundleProductSelectionTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/NewBundleProductSelectionTest.xml @@ -22,7 +22,7 @@ - + diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAdminEditDataTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAdminEditDataTest.xml index 8e8df1f4f16f0..1fe16f068e2e6 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAdminEditDataTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAdminEditDataTest.xml @@ -25,7 +25,7 @@ - + diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleAddToCartSuccessTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleAddToCartSuccessTest.xml index e6f8834336683..7231f61b28899 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleAddToCartSuccessTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleAddToCartSuccessTest.xml @@ -24,7 +24,7 @@ - + diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleCartTest.xml index 966082739aa68..2152717fad8da 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleCartTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleCartTest.xml @@ -25,7 +25,7 @@ - + diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGridTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGridTest.xml index 871bf71d1f876..3532e8f4fdd54 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGridTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGridTest.xml @@ -29,7 +29,7 @@ - + diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCustomerSelectAndSetBundleOptionsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCustomerSelectAndSetBundleOptionsTest.xml index 77c561f311280..565af8b3dbfdb 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCustomerSelectAndSetBundleOptionsTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCustomerSelectAndSetBundleOptionsTest.xml @@ -30,7 +30,7 @@ - + diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml index 161d308044b4a..262c216218c7f 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml @@ -25,7 +25,7 @@ - + diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontGoToDetailsPageWhenAddingToCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontGoToDetailsPageWhenAddingToCartTest.xml index add819e2d3f14..f0836229bb653 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontGoToDetailsPageWhenAddingToCartTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontGoToDetailsPageWhenAddingToCartTest.xml @@ -26,7 +26,7 @@ - + diff --git a/app/code/Magento/Bundle/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/BundleTest.php b/app/code/Magento/Bundle/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/BundleTest.php index 2d86f130767c8..0092c894ac44a 100644 --- a/app/code/Magento/Bundle/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/BundleTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/BundleTest.php @@ -150,13 +150,13 @@ public function testAfterInitializeIfBundleAnsCustomOptionsAndBundleSelectionsEx $this->productMock->expects($this->once()) ->method('getBundleOptionsData') ->willReturn(['option_1' => ['delete' => 1]]); - $extentionAttribute = $this->getMockBuilder(ProductExtensionInterface::class) + $extensionAttribute = $this->getMockBuilder(ProductExtensionInterface::class) ->disableOriginalConstructor() ->setMethods(['setBundleProductOptions']) ->getMockForAbstractClass(); - $extentionAttribute->expects($this->once())->method('setBundleProductOptions')->with([]); - $this->productMock->expects($this->once())->method('getExtensionAttributes')->willReturn($extentionAttribute); - $this->productMock->expects($this->once())->method('setExtensionAttributes')->with($extentionAttribute); + $extensionAttribute->expects($this->once())->method('setBundleProductOptions')->with([]); + $this->productMock->expects($this->once())->method('getExtensionAttributes')->willReturn($extensionAttribute); + $this->productMock->expects($this->once())->method('setExtensionAttributes')->with($extensionAttribute); $this->model->afterInitialize($this->subjectMock, $this->productMock); } @@ -191,14 +191,14 @@ public function testAfterInitializeIfBundleOptionsNotExist(): void ['affect_bundle_product_selections', null, false], ]; $this->requestMock->expects($this->any())->method('getPost')->willReturnMap($valueMap); - $extentionAttribute = $this->getMockBuilder(ProductExtensionInterface::class) + $extensionAttribute = $this->getMockBuilder(ProductExtensionInterface::class) ->disableOriginalConstructor() ->setMethods(['setBundleProductOptions']) ->getMockForAbstractClass(); - $extentionAttribute->expects($this->once())->method('setBundleProductOptions')->with([]); + $extensionAttribute->expects($this->once())->method('setBundleProductOptions')->with([]); $this->productMock->expects($this->any())->method('getCompositeReadonly')->willReturn(false); - $this->productMock->expects($this->once())->method('getExtensionAttributes')->willReturn($extentionAttribute); - $this->productMock->expects($this->once())->method('setExtensionAttributes')->with($extentionAttribute); + $this->productMock->expects($this->once())->method('getExtensionAttributes')->willReturn($extensionAttribute); + $this->productMock->expects($this->once())->method('setExtensionAttributes')->with($extensionAttribute); $this->productMock->expects($this->once())->method('setCanSaveBundleSelections')->with(false); $this->model->afterInitialize($this->subjectMock, $this->productMock); diff --git a/app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php b/app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php index 771b5c53b3347..b7041051591d8 100644 --- a/app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php @@ -11,6 +11,7 @@ use Magento\Bundle\Model\Product\Type; use Magento\Bundle\Model\ResourceModel\BundleFactory; use Magento\Bundle\Model\ResourceModel\Option\Collection; +use Magento\CatalogRule\Model\ResourceModel\Product\CollectionProcessor; use Magento\Bundle\Model\ResourceModel\Selection\Collection as SelectionCollection; use Magento\Bundle\Model\ResourceModel\Selection\CollectionFactory; use Magento\Bundle\Model\Selection; @@ -42,6 +43,8 @@ use PHPUnit\Framework\TestCase; /** + * Test for bundle product type + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class TypeTest extends TestCase @@ -116,6 +119,11 @@ class TypeTest extends TestCase */ private $arrayUtility; + /** + * @var CollectionProcessor|MockObject + */ + private $catalogRuleProcessor; + /** * @return void */ @@ -172,20 +180,20 @@ protected function setUp(): void ->setMethods(['create']) ->disableOriginalConstructor() ->getMock(); - $this->serializer = $this->getMockBuilder(Json::class) ->setMethods(null) ->disableOriginalConstructor() ->getMock(); - $this->metadataPool = $this->getMockBuilder(MetadataPool::class) ->disableOriginalConstructor() ->getMock(); - $this->arrayUtility = $this->getMockBuilder(ArrayUtils::class) ->setMethods(['flatten']) ->disableOriginalConstructor() ->getMock(); + $this->catalogRuleProcessor = $this->getMockBuilder(CollectionProcessor::class) + ->disableOriginalConstructor() + ->getMock(); $objectHelper = new ObjectManager($this); $this->model = $objectHelper->getObject( @@ -1542,7 +1550,7 @@ public function testPrepareForCartAdvancedSpecifyProductOptions() $this->parentClass($group, $option, $buyRequest, $product); - $product->expects($this->once()) + $product->expects($this->any()) ->method('getSkipCheckRequiredOption') ->willReturn(true); $buyRequest->expects($this->once()) @@ -2424,9 +2432,6 @@ protected function parentClass($group, $option, $buyRequest, $product) $group->expects($this->once()) ->method('setProcessMode') ->willReturnSelf(); - $group->expects($this->once()) - ->method('validateUserValue') - ->willReturnSelf(); $group->expects($this->once()) ->method('prepareForCart') ->willReturn('someString'); diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest/CaptchaWithDisabledGuestCheckoutTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest/CaptchaWithDisabledGuestCheckoutTest.xml index bfea4e99996c3..9e99fa96ee766 100644 --- a/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest/CaptchaWithDisabledGuestCheckoutTest.xml +++ b/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest/CaptchaWithDisabledGuestCheckoutTest.xml @@ -34,9 +34,7 @@ - - - + diff --git a/app/code/Magento/Catalog/Api/ProductAttributeOptionUpdateInterface.php b/app/code/Magento/Catalog/Api/ProductAttributeOptionUpdateInterface.php new file mode 100644 index 0000000000000..c783033b6d7b7 --- /dev/null +++ b/app/code/Magento/Catalog/Api/ProductAttributeOptionUpdateInterface.php @@ -0,0 +1,33 @@ +eavOptionManagement = $eavOptionManagement; + $this->eavOptionUpdate = $eavOptionUpdate; } /** @@ -33,7 +47,7 @@ public function __construct( public function getItems($attributeCode) { return $this->eavOptionManagement->getItems( - \Magento\Catalog\Api\Data\ProductAttributeInterface::ENTITY_TYPE_CODE, + ProductAttributeInterface::ENTITY_TYPE_CODE, $attributeCode ); } @@ -44,8 +58,21 @@ public function getItems($attributeCode) public function add($attributeCode, $option) { return $this->eavOptionManagement->add( - \Magento\Catalog\Api\Data\ProductAttributeInterface::ENTITY_TYPE_CODE, + ProductAttributeInterface::ENTITY_TYPE_CODE, + $attributeCode, + $option + ); + } + + /** + * @inheritdoc + */ + public function update(string $attributeCode, int $optionId, AttributeOptionInterface $option): bool + { + return $this->eavOptionUpdate->update( + ProductAttributeInterface::ENTITY_TYPE_CODE, $attributeCode, + $optionId, $option ); } @@ -60,7 +87,7 @@ public function delete($attributeCode, $optionId) } return $this->eavOptionManagement->delete( - \Magento\Catalog\Api\Data\ProductAttributeInterface::ENTITY_TYPE_CODE, + ProductAttributeInterface::ENTITY_TYPE_CODE, $attributeCode, $optionId ); diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/File.php b/app/code/Magento/Catalog/Model/Product/Option/Type/File.php index 9f1eae207e116..425c81a5a9c43 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Type/File.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Type/File.php @@ -16,8 +16,9 @@ /** * Catalog product option file type * - * @author Magento Core Team + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class File extends \Magento\Catalog\Model\Product\Option\Type\DefaultType { @@ -181,6 +182,7 @@ protected function _getProcessingParams() /** * Returns file info array if we need to get file from already existing file. + * * Or returns null, if we need to get file from uploaded array. * * @return null|array @@ -262,7 +264,6 @@ public function validateUserValue($values) . "Make sure the options are entered and try again." ) ); - break; default: $this->setUserValue(null); break; @@ -330,7 +331,11 @@ public function prepareForCart() public function getFormattedOptionValue($optionValue) { if ($this->_formattedOptionValue === null) { - $value = $this->serializer->unserialize($optionValue); + try { + $value = $this->serializer->unserialize($optionValue); + } catch (\InvalidArgumentException $e) { + return $optionValue; + } if ($value === null) { return $optionValue; } @@ -476,13 +481,13 @@ public function copyQuoteToOrder() try { $value = $this->serializer->unserialize($quoteOption->getValue()); if (!isset($value['quote_path'])) { - throw new \Exception(); + return $this; } $quotePath = $value['quote_path']; $orderPath = $value['order_path']; if (!$this->mediaDirectory->isFile($quotePath) || !$this->mediaDirectory->isReadable($quotePath)) { - throw new \Exception(); + return $this; } if ($this->_coreFileStorageDatabase->checkDbUsage()) { @@ -524,6 +529,8 @@ protected function _getOptionDownloadUrl($route, $params) } /** + * Prepare size + * * @param array $value * @return string */ diff --git a/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php b/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php index e6804d9246faa..5afac9636d7b4 100644 --- a/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php +++ b/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php @@ -9,15 +9,18 @@ use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\App\ObjectManager; /** - * @api * Abstract model for product type implementation + * + * phpcs:disable Magento2.Classes.AbstractApi + * @api + * @since 100.0.2 * @SuppressWarnings(PHPMD.ExcessivePublicCount) * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - * @since 100.0.2 */ abstract class AbstractType { @@ -207,7 +210,7 @@ public function __construct( $this->_filesystem = $filesystem; $this->_logger = $logger; $this->productRepository = $productRepository; - $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance() + $this->serializer = $serializer ?: ObjectManager::getInstance() ->get(\Magento\Framework\Serialize\Serializer\Json::class); } @@ -355,6 +358,7 @@ public function isSalable($product) /** * Prepare product and its configuration to be added to some products list. + * * Perform standard preparation process and then prepare options belonging to specific product type. * * @param \Magento\Framework\DataObject $buyRequest @@ -440,6 +444,7 @@ public function processConfiguration( /** * Initialize product(s) for add to cart process. + * * Advanced version of func to prepare product for cart - processMode can be specified there. * * @param \Magento\Framework\DataObject $buyRequest @@ -476,6 +481,7 @@ public function prepareForCart(\Magento\Framework\DataObject $buyRequest, $produ * @throws \Magento\Framework\Exception\LocalizedException * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) + * phpcs:disable Generic.Metrics.NestingLevel */ public function processFileQueue() { @@ -492,6 +498,7 @@ public function processFileQueue() /** @var $uploader \Zend_File_Transfer_Adapter_Http */ $uploader = isset($queueOptions['uploader']) ? $queueOptions['uploader'] : null; + // phpcs:ignore Magento2.Functions.DiscouragedFunction $path = dirname($dst); try { @@ -529,9 +536,11 @@ public function processFileQueue() return $this; } + //phpcs:enable /** * Add file to File Queue + * * @param array $queueOptions Array of File Queue * (eg. ['operation'=>'move', * 'src_name'=>'filename', @@ -572,6 +581,7 @@ public function getSpecifyOptionMessage() * @param string $processMode * @return array * @throws LocalizedException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function _prepareOptions(\Magento\Framework\DataObject $buyRequest, $product, $processMode) { @@ -583,6 +593,7 @@ protected function _prepareOptions(\Magento\Framework\DataObject $buyRequest, $p } if ($options !== null) { $results = []; + $optionsFromRequest = $buyRequest->getOptions(); foreach ($options as $option) { /* @var $option \Magento\Catalog\Model\Product\Option */ try { @@ -590,8 +601,14 @@ protected function _prepareOptions(\Magento\Framework\DataObject $buyRequest, $p ->setOption($option) ->setProduct($product) ->setRequest($buyRequest) - ->setProcessMode($processMode) - ->validateUserValue($buyRequest->getOptions()); + ->setProcessMode($processMode); + + if ($product->getSkipCheckRequiredOption() !== true) { + $group->validateUserValue($optionsFromRequest); + } elseif ($optionsFromRequest !== null && isset($optionsFromRequest[$option->getId()])) { + $transport->options[$option->getId()] = $optionsFromRequest[$option->getId()]; + } + } catch (LocalizedException $e) { $results[] = $e->getMessage(); continue; @@ -643,8 +660,7 @@ public function checkProductBuyState($product) } /** - * Prepare additional options/information for order item which will be - * created from this product + * Prepare additional options/information for order item which will be created from this product * * @param \Magento\Catalog\Model\Product $product * @return array @@ -900,7 +916,7 @@ public function getStoreFilter($product) /** * Set store filter for associated products * - * @param $store int|\Magento\Store\Model\Store + * @param int|\Magento\Store\Model\Store $store * @param \Magento\Catalog\Model\Product $product * @return $this */ @@ -913,6 +929,7 @@ public function setStoreFilter($store, $product) /** * Allow for updates of children qty's + * * (applicable for complicated product types. As default returns false) * * @param \Magento\Catalog\Model\Product $product @@ -940,6 +957,7 @@ public function prepareQuoteItemQty($qty, $product) /** * Implementation of product specify logic of which product needs to be assigned to option. + * * For example if product which was added to option already removed from catalog. * * @param \Magento\Catalog\Model\Product $optionProduct @@ -979,6 +997,7 @@ public function setConfig($config) /** * Retrieve additional searchable data from type instance + * * Using based on product id and store_id data * * @param \Magento\Catalog\Model\Product $product @@ -999,6 +1018,7 @@ public function getSearchableData($product) /** * Retrieve products divided into groups required to purchase + * * At least one product in each group has to be purchased * * @param \Magento\Catalog\Model\Product $product @@ -1092,6 +1112,8 @@ public function getIdentities(\Magento\Catalog\Model\Product $product) } /** + * Get Associated Products + * * @param \Magento\Catalog\Model\Product\Type\AbstractType $product * @return array * @SuppressWarnings(PHPMD.UnusedFormalParameter) diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductCatalogPageOpenActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductCatalogPageOpenActionGroup.xml new file mode 100644 index 0000000000000..f25f73977bf4e --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductCatalogPageOpenActionGroup.xml @@ -0,0 +1,19 @@ + + + + + + + Goes to the Admin Product Catalog Page grid page. + + + + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProperUrlIsShownActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProperUrlIsShownActionGroup.xml new file mode 100644 index 0000000000000..6fb7f68f64320 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProperUrlIsShownActionGroup.xml @@ -0,0 +1,21 @@ + + + + + + + Validate that the URL path is correct + + + + + + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageSimpleProductTest.xml index 92f24fe76502d..110cfa0cd83a7 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageSimpleProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageSimpleProductTest.xml @@ -22,7 +22,7 @@ - + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageVirtualProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageVirtualProductTest.xml index 7cf388914207b..dc215ac51f075 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageVirtualProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageVirtualProductTest.xml @@ -22,7 +22,7 @@ - + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddInStockProductToTheCartTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddInStockProductToTheCartTest.xml index 52da8c70a3bc8..a41d0836a2ded 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddInStockProductToTheCartTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddInStockProductToTheCartTest.xml @@ -78,9 +78,7 @@ - - - + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeGroupTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeGroupTest.xml index 04955807d7618..68e6040277247 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeGroupTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeGroupTest.xml @@ -9,6 +9,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <description value="Attribute value should be preserved after changing attribute group"/> <severity value="CRITICAL"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryFromProductPageTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryFromProductPageTest.xml index b94c12d1d7a39..b96052d2d0685 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryFromProductPageTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryFromProductPageTest.xml @@ -27,7 +27,7 @@ <after> <!-- Delete the created category --> <actionGroup ref="DeleteMostRecentCategoryActionGroup" stepKey="getRidOfCreatedCategory"/> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> </after> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml index 7b2c67b205ea8..19df6e29f36a2 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml @@ -105,7 +105,7 @@ <seeElement selector="{{AdminProductFormSection.attributeLabelByText(ProductAttributeFrontendLabel.label)}}" stepKey="seeAttributeLabelInProductForm"/> <!--Verify Product Attribute in Attribute Form --> - <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="navigateToProductAttributeGrid"/> + <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="navigateToProductAttributeGrid"/> <fillField selector="{{AdminProductAttributeGridSection.FilterByAttributeCode}}" userInput="{{newProductAttribute.attribute_code}}" stepKey="setAttributeCode"/> <click selector="{{AdminProductAttributeGridSection.Search}}" stepKey="searchForAttributeFromTheGrid"/> <waitForPageLoad stepKey="waitForPageLoad" /> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDatetimeProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDatetimeProductAttributeTest.xml index 37dc7de910917..4c57504b60ad7 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDatetimeProductAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDatetimeProductAttributeTest.xml @@ -29,8 +29,7 @@ <!-- Generate the datetime default value --> <generateDate date="now" format="n/j/y g:i A" stepKey="generateDefaultValue"/> <!-- Create new datetime product attribute --> - <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="goToProductAttributes"/> - <waitForPageLoad stepKey="waitForPageLoadAttributes"/> + <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="goToProductAttributes"/> <actionGroup ref="CreateProductAttributeWithDatetimeFieldActionGroup" stepKey="createAttribute"> <argument name="attribute" value="DatetimeProductAttribute"/> <argument name="date" value="{$generateDefaultValue}"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml index fef69edde23e8..0bfa5b925483e 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml @@ -60,7 +60,7 @@ <actionGroup ref="SaveAttributeSetActionGroup" stepKey="saveAttributeSet"/> <!-- Go to Product Attribute Grid page --> - <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="navigateToProductAttributeGrid"/> + <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="navigateToProductAttributeGrid"/> <fillField selector="{{AdminProductAttributeGridSection.FilterByAttributeCode}}" userInput="$$attribute.attribute_code$$" stepKey="fillAttrCodeField" /> <click selector="{{AdminProductAttributeGridSection.Search}}" stepKey="clickSearchBtn" /> <click selector="{{AdminProductAttributeGridSection.FirstRow}}" stepKey="chooseFirstRow" /> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryAndUpdateAsInactiveTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryAndUpdateAsInactiveTest.xml index f8346f5a9dd5c..bf9aa5509fa50 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryAndUpdateAsInactiveTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryAndUpdateAsInactiveTest.xml @@ -37,9 +37,8 @@ <magentoCLI stepKey="setFlatCatalogCategory" command="config:set catalog/frontend/flat_catalog_category 1"/> <!--Open Index Management Page and Select Index mode "Update by Schedule" --> <magentoCLI stepKey="setIndexerMode" command="indexer:set-mode" arguments="schedule" /> - <!-- Run cron twice --> - <magentoCLI command="cron:run" arguments="--group=index" stepKey="runCron1"/> - <magentoCLI command="cron:run" arguments="--group=index" stepKey="runCron2"/> + <!-- Run cron --> + <magentoCron stepKey="runIndexCronJobs" groups="index"/> </before> <after> <magentoCLI stepKey="setFlatCatalogCategory" command="config:set catalog/frontend/flat_catalog_category 0 "/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryTest.xml index 0aa89bdfd45b6..3f54f60f382e4 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryTest.xml @@ -37,9 +37,8 @@ <magentoCLI stepKey="setFlatCatalogCategory" command="config:set catalog/frontend/flat_catalog_category 1"/> <!--Open Index Management Page and Select Index mode "Update by Schedule" --> <magentoCLI stepKey="setIndexerMode" command="indexer:set-mode" arguments="schedule" /> - <!-- Run cron twice --> - <magentoCLI command="cron:run" arguments="--group=index" stepKey="runCron1"/> - <magentoCLI command="cron:run" arguments="--group=index" stepKey="runCron2"/> + <!-- Run cron --> + <magentoCron stepKey="runIndexCronJobs" groups="index"/> </before> <after> <magentoCLI stepKey="setFlatCatalogCategory" command="config:set catalog/frontend/flat_catalog_category 0 "/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveInMenuFlatCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveInMenuFlatCategoryTest.xml index 171d15fe6ed4f..df9d28637f727 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveInMenuFlatCategoryTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveInMenuFlatCategoryTest.xml @@ -37,9 +37,8 @@ <magentoCLI stepKey="setFlatCatalogCategory" command="config:set catalog/frontend/flat_catalog_category 1"/> <!--Open Index Management Page and Select Index mode "Update by Schedule" --> <magentoCLI stepKey="setIndexerMode" command="indexer:set-mode" arguments="schedule" /> - <!-- Run cron twice --> - <magentoCLI command="cron:run" arguments="--group=index" stepKey="runCron1"/> - <magentoCLI command="cron:run" arguments="--group=index" stepKey="runCron2"/> + <!-- Run cron --> + <magentoCron stepKey="runIndexCronJobs" groups="index"/> </before> <after> <magentoCLI stepKey="setFlatCatalogCategory" command="config:set catalog/frontend/flat_catalog_category 0 "/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateMultipleSelectProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateMultipleSelectProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml index caacfde89d1cb..174bfc3e18ba5 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateMultipleSelectProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateMultipleSelectProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml @@ -64,7 +64,7 @@ <actionGroup ref="SaveAttributeSetActionGroup" stepKey="saveAttributeSet"/> <!-- Go to Product Attribute Grid page --> - <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="navigateToProductAttributeGrid"/> + <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="navigateToProductAttributeGrid"/> <fillField selector="{{AdminProductAttributeGridSection.FilterByAttributeCode}}" userInput="$$attribute.attribute_code$$" stepKey="fillAttrCodeField" /> <click selector="{{AdminProductAttributeGridSection.Search}}" stepKey="clickSearchBtn" /> <click selector="{{AdminProductAttributeGridSection.FirstRow}}" stepKey="chooseFirstRow" /> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeFromProductPageTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeFromProductPageTest.xml index 7fdab11d0a050..7154b7b27650d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeFromProductPageTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeFromProductPageTest.xml @@ -100,7 +100,7 @@ <seeElement selector="{{AdminProductFormSection.attributeLabelByText(ProductAttributeFrontendLabel.label)}}" stepKey="seeAttributeLabelInProductForm"/> <!--Verify Product Attribute in Attribute Form --> - <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="navigateToProductAttributeGrid"/> + <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="navigateToProductAttributeGrid"/> <fillField selector="{{AdminProductAttributeGridSection.FilterByAttributeCode}}" userInput="{{newProductAttribute.attribute_code}}" stepKey="setAttributeCode"/> <click selector="{{AdminProductAttributeGridSection.Search}}" stepKey="searchForAttributeFromTheGrid"/> <waitForPageLoad stepKey="waitForPageLoad" /> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductWithCountryOfManufactureAttributeSKUMaskTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductWithCountryOfManufactureAttributeSKUMaskTest.xml index c378ca5b2c27a..5d88d687754e0 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductWithCountryOfManufactureAttributeSKUMaskTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductWithCountryOfManufactureAttributeSKUMaskTest.xml @@ -31,8 +31,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="openProductCatalogPage"/> - <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage"/> <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="clickAddProductToggle"/> <waitForPageLoad stepKey="waitForProductToggleToSelectSimpleProduct"/> <click selector="{{AdminProductGridActionSection.addSimpleProduct}}" stepKey="clickSimpleProductFromDropDownList"/> @@ -49,8 +48,7 @@ <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertSimpleProductSaveSuccessMessage"/> <!-- Search created simple product(from above step) in the grid page to verify sku masked as name and country of manufacture --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchCreatedSimpleProduct"/> - <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPageToSearchCreatedSimpleProduct"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{nameAndAttributeSkuMaskSimpleProduct.name}}-{{nameAndAttributeSkuMaskSimpleProduct.country_of_manufacture_label}}" stepKey="fillSkuFilterFieldWithNameAndCountryOfManufactureInput" /> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductFillingRequiredFieldsOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductFillingRequiredFieldsOnlyTest.xml index 9d3a47cd115aa..c82c038b75d74 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductFillingRequiredFieldsOnlyTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductFillingRequiredFieldsOnlyTest.xml @@ -25,8 +25,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> - <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage"/> <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="clickAddProductToggle"/> <waitForPageLoad stepKey="waitForProductToggleToSelectProduct"/> <click selector="{{AdminProductGridActionSection.addVirtualProduct}}" stepKey="clickVirtualProduct"/> @@ -42,8 +41,7 @@ <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSuccessMessage"/> <!-- Verify we see created virtual product(from the above step) on the product grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage1"/> - <waitForPageLoad stepKey="waitForProductCatalogPage1"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage1"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickSelector"/> <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFilter"/> <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{virtualProductWithRequiredFields.name}}" stepKey="fillProductName1"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductOutOfStockWithTierPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductOutOfStockWithTierPriceTest.xml index 842f93b49c14a..9747ae6314b7d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductOutOfStockWithTierPriceTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductOutOfStockWithTierPriceTest.xml @@ -31,8 +31,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> - <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage"/> <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="clickAddProductToggle"/> <waitForPageLoad stepKey="waitForProductToggleToSelectProduct"/> <click selector="{{AdminProductGridActionSection.addVirtualProduct}}" stepKey="clickVirtualProduct"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml index 8bb3391b5240b..f119c995d3a61 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml @@ -31,8 +31,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> - <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage"/> <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="clickAddProductToggle"/> <waitForPageLoad stepKey="waitForProductToggleToSelectProduct"/> <click selector="{{AdminProductGridActionSection.addVirtualProduct}}" stepKey="clickVirtualProduct"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceTest.xml index faae6a371db24..16da9c0642edc 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceTest.xml @@ -27,8 +27,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> - <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage"/> <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="clickAddProductToggle"/> <waitForPageLoad stepKey="waitForProductToggleToSelectProduct"/> <click selector="{{AdminProductGridActionSection.addVirtualProduct}}" stepKey="clickVirtualProduct"/> @@ -60,8 +59,7 @@ <!-- Verify we see success message --> <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSuccessMessage"/> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage1"/> - <waitForPageLoad stepKey="waitForProductCatalogPage1"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage1"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="checkRetailCustomerTaxClass" /> <fillField selector="{{AdminProductGridFilterSection.keywordSearch}}" userInput="{{virtualProductBigQty.name}}" stepKey="fillProductName1"/> <click selector="{{AdminProductGridFilterSection.keywordSearchButton}}" stepKey="clickKeywordSearchButton"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithoutManageStockTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithoutManageStockTest.xml index 3b5a8d8e753da..14f228375bfaa 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithoutManageStockTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithoutManageStockTest.xml @@ -27,8 +27,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> - <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage"/> <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="clickAddProductToggle"/> <waitForPageLoad stepKey="waitForProductToggleToSelectProduct"/> <click selector="{{AdminProductGridActionSection.addVirtualProduct}}" stepKey="clickVirtualProduct"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSystemProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSystemProductAttributeTest.xml index 22c6bf061f274..b7e037b323ee2 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSystemProductAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSystemProductAttributeTest.xml @@ -23,8 +23,7 @@ <after> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> - <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="navigateToProductAttribute"/> - <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="navigateToProductAttribute"/> <click selector="{{AdminProductAttributeGridSection.ResetFilter}}" stepKey="resetFiltersOnGrid"/> <fillField selector="{{AdminProductAttributeGridSection.FilterByAttributeCode}}" userInput="{{newsFromDate.attribute_code}}" stepKey="setAttributeCode"/> <click selector="{{AdminProductAttributeGridSection.Search}}" stepKey="searchForAttributeFromTheGrid"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml index ebbfdc4d72f40..f0d670cd35471 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml @@ -50,8 +50,7 @@ <amOnPage url="{{_defaultProduct.name}}.html" stepKey="navigateToProductPage"/> <waitForPageLoad stepKey="waitForPageLoad5"/> <see userInput="Text Area" stepKey="seeText2" /> - <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="navigateToProductAttributeGrid2"/> - <waitForPageLoad stepKey="waitForPageLoad6"/> + <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="navigateToProductAttributeGrid2"/> <click selector="{{AdminProductAttributeGridSection.AttributeCode($$myProductAttributeCreation.attribute_code$$)}}" stepKey="navigateToAttributeEditPage2" /> <waitForPageLoad stepKey="waitForPageLoad7" /> <seeOptionIsSelected selector="{{AttributePropertiesSection.InputType}}" userInput="Text Area" stepKey="seeTextAreaSelected" /> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml index f6b2a74eca0f0..f10288bea36d9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml @@ -23,8 +23,7 @@ <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> <!--Clear product grid--> <comment userInput="Clear product grid" stepKey="commentClearProductGrid"/> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="goToProductCatalog"/> - <waitForPageLoad stepKey="waitForProductIndexPage"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="goToProductCatalog"/> <actionGroup ref="ResetProductGridToDefaultViewActionGroup" stepKey="resetProductGridToDefaultView"/> <actionGroup ref="DeleteProductsIfTheyExistActionGroup" stepKey="deleteProductIfTheyExist"/> <createData stepKey="category1" entity="SimpleSubCategory"/> @@ -37,8 +36,8 @@ </createData> </before> <after> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="goToProductCatalog"/> - <waitForPageLoad stepKey="waitForProductIndexPage"/> + + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="goToProductCatalog"/> <click selector="{{AdminDataGridPaginationSection.previousPage}}" stepKey="clickPrevPageOrderGrid"/> <actionGroup ref="AdminDataGridDeleteCustomPerPageActionGroup" stepKey="deleteCustomAddedPerPage"> <argument name="perPage" value="ProductPerPage.productCount"/> @@ -49,8 +48,7 @@ <deleteData stepKey="deleteProduct2" createDataKey="product2"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="goToProductCatalog"/> - <waitForPageLoad stepKey="waitForProductIndexPage"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="goToProductCatalog"/> <actionGroup ref="AdminDataGridSelectCustomPerPageActionGroup" stepKey="select1OrderPerPage"> <argument name="perPage" value="ProductPerPage.productCount"/> </actionGroup> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassChangeProductsStatusTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassChangeProductsStatusTest.xml index 0214f9141b903..abec993167fa9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassChangeProductsStatusTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassChangeProductsStatusTest.xml @@ -30,7 +30,7 @@ </createData> </before> <after> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> <deleteData createDataKey="createProductOne" stepKey="deleteProductOne"/> <deleteData createDataKey="createProductTwo" stepKey="deleteProductTwo"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesMissingRequiredFieldTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesMissingRequiredFieldTest.xml index e4d69e9169613..7809dbbe498da 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesMissingRequiredFieldTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesMissingRequiredFieldTest.xml @@ -30,7 +30,7 @@ </createData> </before> <after> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> <deleteData createDataKey="createProductOne" stepKey="deleteProductOne"/> <deleteData createDataKey="createProductTwo" stepKey="deleteProductTwo"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest/AdminMassUpdateProductAttributesStoreViewScopeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest/AdminMassUpdateProductAttributesStoreViewScopeTest.xml index 08b2d924e2a5e..6014176f0702c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest/AdminMassUpdateProductAttributesStoreViewScopeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest/AdminMassUpdateProductAttributesStoreViewScopeTest.xml @@ -31,7 +31,7 @@ <deleteData createDataKey="createProductTwo" stepKey="deleteProductTwo"/> <deleteData createDataKey="createProductThree" stepKey="deleteProductThree"/> <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="AdminDeleteStoreViewActionGroup"/> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> </after> <!-- Search and select products --> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByDateAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByDateAttributeTest.xml index d47730a99308b..d4d32ae47b827 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByDateAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByDateAttributeTest.xml @@ -26,8 +26,7 @@ <deleteData createDataKey="createSimpleProductWithDate" stepKey="deleteProduct"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> </after> - <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="navigateToProductAttribute"/> - <waitForPageLoad stepKey="wait1"/> + <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="navigateToProductAttribute"/> <click selector="{{AdminProductAttributeGridSection.ResetFilter}}" stepKey="resetFiltersOnGrid"/> <fillField selector="{{AdminProductAttributeGridSection.GridFilterFrontEndLabel}}" userInput="Set Product as New from Date" stepKey="setAttributeLabel"/> <click selector="{{AdminProductAttributeGridSection.Search}}" stepKey="searchForAttributeFromGrid"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductStatusAttributeDisabledByDefaultTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductStatusAttributeDisabledByDefaultTest.xml index ae63158990b96..e681feb77c380 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductStatusAttributeDisabledByDefaultTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductStatusAttributeDisabledByDefaultTest.xml @@ -23,8 +23,7 @@ </before> <after> - <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="navigateToProductAttribute"/> - <waitForPageLoad stepKey="wait1"/> + <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="navigateToProductAttribute"/> <click selector="{{AdminProductAttributeGridSection.ResetFilter}}" stepKey="resetFiltersOnGrid1"/> <fillField selector="{{AdminProductAttributeGridSection.GridFilterFrontEndLabel}}" userInput="Enable Product" stepKey="setAttributeLabel1"/> <click selector="{{AdminProductAttributeGridSection.Search}}" stepKey="searchForAttributeFromTheGrid1"/> @@ -37,8 +36,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> </after> - <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="navigateToProductAttribute"/> - <waitForPageLoad stepKey="wait1"/> + <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="navigateToProductAttribute"/> <click selector="{{AdminProductAttributeGridSection.ResetFilter}}" stepKey="resetFiltersOnGrid"/> <fillField selector="{{AdminProductAttributeGridSection.GridFilterFrontEndLabel}}" userInput="Enable Product" stepKey="setAttributeLabel"/> <click selector="{{AdminProductAttributeGridSection.Search}}" stepKey="searchForAttributeFromTheGrid"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultImageSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultImageSimpleProductTest.xml index 00eaa623e2bca..1d216b6eda359 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultImageSimpleProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultImageSimpleProductTest.xml @@ -22,7 +22,7 @@ <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> </before> <after> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> </after> <!--Create product--> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultImageVirtualProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultImageVirtualProductTest.xml index 6cc1b256e5ec9..2ff76520cee38 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultImageVirtualProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultImageVirtualProductTest.xml @@ -22,7 +22,7 @@ <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> </before> <after> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> </after> <!--Create product--> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleSetEditRelatedProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleSetEditRelatedProductsTest.xml index 534924e0f70c9..2396bea2768a7 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleSetEditRelatedProductsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleSetEditRelatedProductsTest.xml @@ -35,7 +35,7 @@ <actionGroup ref="DeleteProductUsingProductGridActionGroup" stepKey="deleteProduct"> <argument name="product" value="SimpleProduct3"/> </actionGroup> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> <deleteData createDataKey="simpleProduct0" stepKey="deleteSimpleProduct0"/> <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryUrlKeyWithStoreViewTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryUrlKeyWithStoreViewTest.xml index 6a12b991bd225..3bbe8722d8bfc 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryUrlKeyWithStoreViewTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryUrlKeyWithStoreViewTest.xml @@ -32,16 +32,12 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> - <!--Open Store Page --> - <amOnPage url="{{AdminSystemStorePage.url}}" stepKey="amOnAdminSystemStorePage"/> - <waitForPageLoad stepKey="waitForSystemStorePage"/> - <!--Create Custom Store --> - <click selector="{{AdminStoresMainActionsSection.createStoreButton}}" stepKey="selectCreateStore"/> - <fillField userInput="{{customStore.name}}" selector="{{AdminNewStoreGroupSection.storeGrpNameTextField}}" stepKey="fillStoreName"/> - <fillField userInput="{{customStore.code}}" selector="{{AdminNewStoreGroupSection.storeGrpCodeTextField}}" stepKey="fillStoreCode"/> - <selectOption userInput="{{NewRootCategory.name}}" selector="{{AdminNewStoreGroupSection.storeRootCategoryDropdown}}" stepKey="selectStoreStatus"/> - <click selector="{{AdminStoresMainActionsSection.saveButton}}" stepKey="clickSaveStoreButton"/> + <actionGroup ref="CreateCustomStoreActionGroup" stepKey="createCustomStore"> + <argument name="website" value="{{_defaultWebsite.name}}"/> + <argument name="store" value="{{customStore.name}}"/> + <argument name="rootCategory" value="$$rootCategory.name$$"/> + </actionGroup> <!--Create Store View--> <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView"> @@ -50,34 +46,37 @@ </actionGroup> <!--Verify Category in Store View--> - <amOnPage url="/{{NewRootCategory.name}}/{{SimpleRootSubCategory.name}}.html" stepKey="seeTheCategoryInStoreFront"/> - <waitForPageLoad stepKey="waitForSystemStorePage1"/> - <click selector="{{StorefrontFooterSection.switchStoreButton}}" stepKey="ClickSwitchStoreButtonOnDefaultStore"/> - <click selector="{{StorefrontFooterSection.storeLink(customStore.name)}}" stepKey="SelectSecondStoreToSwitchOn"/> - <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(SimpleRootSubCategory.name)}}" stepKey="seeCatergoryInStoreFront"/> - <click selector="{{StorefrontHeaderSection.NavigationCategoryByName(SimpleRootSubCategory.name)}}" stepKey="selectCategory"/> - <waitForPageLoad stepKey="waitForProductToLoad"/> + <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="goToHomepage"/> + <actionGroup ref="StorefrontSwitchStoreActionGroup" stepKey="switchToCustomStore"> + <argument name="storeName" value="{{customStore.name}}"/> + </actionGroup> + <actionGroup ref="StorefrontAssertCategoryNameIsShownInMenuActionGroup" stepKey="seeCatergoryInStoreFront"> + <argument name="categoryName" value="{{SimpleRootSubCategory.name}}"/> + </actionGroup> + <actionGroup ref="StorefrontGoToCategoryPageActionGroup" stepKey="selectCategory"> + <argument name="categoryName" value="$$category.name$$"/> + </actionGroup> <!--Update URL Key--> - <actionGroup ref="AdminOpenCategoryPageActionGroup" stepKey="openAdminCategoryIndexPage"/> - <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="clickOnExpandTree"/> - <click selector="{{AdminCategorySidebarTreeSection.categoryInTree(SimpleRootSubCategory.name)}}" stepKey="selectCategory1"/> - <scrollTo selector="{{AdminCategorySEOSection.SectionHeader}}" stepKey="scrollToSearchEngineOptimization"/> - <click selector="{{AdminCategorySEOSection.SectionHeader}}" stepKey="openSeoSection"/> - <clearField selector="{{AdminCategorySEOSection.UrlKeyInput}}" stepKey="clearUrlKeyField"/> - <fillField selector="{{AdminCategorySEOSection.UrlKeyInput}}" userInput="newurlkey" stepKey="enterURLKey"/> - <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveCategoryAfterFirstSeoUpdate"/> - <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="assertSuccessMessage"/> - <waitForPageLoad stepKey="waitForPageToLoad"/> + <actionGroup ref="NavigateToCreatedCategoryActionGroup" stepKey="openCreatedSubCategory"> + <argument name="Category" value="$$category$$"/> + </actionGroup> + <actionGroup ref="ChangeSeoUrlKeyActionGroup" stepKey="changeSeoUrlKey"> + <argument name="value" value="newurlkey"/> + </actionGroup> <!--Open Category Store Front Page--> - <amOnPage url="/{{NewRootCategory.name}}/{{SimpleRootSubCategory.name}}.html" stepKey="seeTheCategoryInStoreFront1"/> - <waitForPageLoad stepKey="waitForSystemStorePage3"/> - <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(SimpleRootSubCategory.name)}}" stepKey="seeCategoryOnNavigation1"/> - <click selector="{{StorefrontHeaderSection.NavigationCategoryByName(SimpleRootSubCategory.name)}}" stepKey="selectCategory2"/> - <waitForPageLoad stepKey="waitForProductToLoad1"/> + <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="openHomepage"/> + <actionGroup ref="StorefrontAssertCategoryNameIsShownInMenuActionGroup" stepKey="seeCatergoryNameInStoreFront"> + <argument name="categoryName" value="{{SimpleRootSubCategory.name}}"/> + </actionGroup> + <actionGroup ref="StorefrontGoToCategoryPageActionGroup" stepKey="openCategory"> + <argument name="categoryName" value="{{SimpleRootSubCategory.name}}"/> + </actionGroup> <!--Verify Updated URLKey is present--> - <seeInCurrentUrl stepKey="verifyUpdatedUrlKey" url="newurlkey.html"/> + <actionGroup ref="StorefrontAssertProperUrlIsShownActionGroup" stepKey="seeUpdatedUrlkey"> + <argument name="urlPath" value="newurlkey.html"/> + </actionGroup> </test> </tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryAndAddProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryAndAddProductsTest.xml index 1950b385c4a68..011eae0572a9a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryAndAddProductsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryAndAddProductsTest.xml @@ -38,9 +38,8 @@ <magentoCLI stepKey="setFlatCatalogCategory" command="config:set catalog/frontend/flat_catalog_category 1"/> <!--Open Index Management Page and Select Index mode "Update by Schedule" --> <magentoCLI stepKey="setIndexerMode" command="indexer:set-mode" arguments="schedule" /> - <!-- Run cron twice --> - <magentoCLI command="cron:run" stepKey="runCron1"/> - <magentoCLI command="cron:run" stepKey="runCron2"/> + <!-- Run cron --> + <magentoCron stepKey="runAllCronJobs"/> </before> <after> <magentoCLI stepKey="setFlatCatalogCategory" command="config:set catalog/frontend/flat_catalog_category 0 "/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductNameToVerifyDataOverridingOnStoreViewLevelTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductNameToVerifyDataOverridingOnStoreViewLevelTest.xml index 6edffb923d540..1f4024f47bdf3 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductNameToVerifyDataOverridingOnStoreViewLevelTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductNameToVerifyDataOverridingOnStoreViewLevelTest.xml @@ -42,8 +42,7 @@ </after> <!-- Search default simple product in grid --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> - <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage"/> <actionGroup ref="FilterProductGridBySku2ActionGroup" stepKey="filterProductGrid"> <argument name="sku" value="$$initialSimpleProduct.sku$$"/> </actionGroup> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductPriceToVerifyDataOverridingOnStoreViewLevelTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductPriceToVerifyDataOverridingOnStoreViewLevelTest.xml index e954de90ef542..10b488c9848ba 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductPriceToVerifyDataOverridingOnStoreViewLevelTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductPriceToVerifyDataOverridingOnStoreViewLevelTest.xml @@ -42,8 +42,7 @@ </after> <!-- Search default simple product in grid --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> - <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage"/> <actionGroup ref="FilterProductGridBySku2ActionGroup" stepKey="filterProductGrid"> <argument name="sku" value="$$initialSimpleProduct.sku$$"/> </actionGroup> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductTieredPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductTieredPriceTest.xml index f5b0fb8054dc1..522f0c712754f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductTieredPriceTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductTieredPriceTest.xml @@ -40,8 +40,7 @@ </after> <!-- Search default simple product in the grid --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> - <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage"/> <actionGroup ref="FilterProductGridBySku2ActionGroup" stepKey="filterProductGrid"> <argument name="sku" value="$$initialSimpleProduct.sku$$"/> </actionGroup> @@ -85,8 +84,7 @@ <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertSimpleProductSaveSuccessMessage"/> <!-- Search updated simple product(from above step) in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedSimpleProduct"/> - <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPageToSearchUpdatedSimpleProduct"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{simpleProductTierPrice300InStock.name}}" stepKey="fillSimpleProductNameInNameFilter"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockDisabledProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockDisabledProductTest.xml index d20594461173b..74774f936cba8 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockDisabledProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockDisabledProductTest.xml @@ -34,8 +34,7 @@ </after> <!-- Search default simple product in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> - <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage"/> <actionGroup ref="FilterProductGridBySku2ActionGroup" stepKey="filterProductGrid"> <argument name="sku" value="$$initialSimpleProduct.sku$$"/> </actionGroup> @@ -60,8 +59,7 @@ <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertSimpleProductSaveSuccessMessage"/> <!-- Search updated simple product(from above step) in the grid --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedSimpleProduct"/> - <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPageToSearchUpdatedSimpleProduct"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{simpleProductDisabled.name}}" stepKey="fillSimpleProductNameInNameFilter"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockEnabledFlatTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockEnabledFlatTest.xml index 5fa7acbeb8de9..9819ba61c21b7 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockEnabledFlatTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockEnabledFlatTest.xml @@ -38,8 +38,7 @@ </after> <!-- Search default simple product in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> - <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage"/> <actionGroup ref="FilterProductGridBySku2ActionGroup" stepKey="filterProductGrid"> <argument name="sku" value="$$initialSimpleProduct.sku$$"/> </actionGroup> @@ -79,8 +78,7 @@ <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertSimpleProductSaveSuccessMessage"/> <!-- Search updated simple product(from above step) in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedSimpleProduct"/> - <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPageToSearchUpdatedSimpleProduct"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{simpleProductEnabledFlat.name}}" stepKey="fillSimpleProductNameInNameFilter"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockNotVisibleIndividuallyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockNotVisibleIndividuallyTest.xml index 4b21d1337e9b7..7dcad35c34d13 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockNotVisibleIndividuallyTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockNotVisibleIndividuallyTest.xml @@ -36,8 +36,7 @@ </after> <!-- Search default simple product in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> - <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage"/> <actionGroup ref="FilterProductGridBySku2ActionGroup" stepKey="filterProductGrid"> <argument name="sku" value="$$initialSimpleProduct.sku$$"/> </actionGroup> @@ -70,8 +69,7 @@ <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertSimpleProductSaveSuccessMessage"/> <!-- Search updated simple product(from above step) in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedSimpleProduct"/> - <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPageToSearchUpdatedSimpleProduct"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{simpleProductNotVisibleIndividually.name}}" stepKey="fillSimpleProductNameInNameFilter"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockUnassignFromCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockUnassignFromCategoryTest.xml index 4256f93ea41d1..3450a8fb6e017 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockUnassignFromCategoryTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockUnassignFromCategoryTest.xml @@ -34,8 +34,7 @@ </after> <!--Search default simple product in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> - <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage"/> <actionGroup ref="FilterProductGridBySku2ActionGroup" stepKey="filterProductGrid"> <argument name="sku" value="$$initialSimpleProduct.sku$$"/> </actionGroup> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogAndSearchTest.xml index 58db163bed720..2a260c606c71f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogAndSearchTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogAndSearchTest.xml @@ -36,8 +36,7 @@ </after> <!-- Search default simple product in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> - <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage"/> <actionGroup ref="FilterProductGridBySku2ActionGroup" stepKey="filterProductGrid"> <argument name="sku" value="$$initialSimpleProduct.sku$$"/> </actionGroup> @@ -70,8 +69,7 @@ <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertSimpleProductSaveSuccessMessage"/> <!-- Search updated simple product(from above step) in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedSimpleProduct"/> - <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPageToSearchUpdatedSimpleProduct"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{simpleProductRegularPrice245InStock.name}}" stepKey="fillSimpleProductNameInNameFilter"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogOnlyTest.xml index 5e9a48f659d6b..db7d0f7adbec6 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogOnlyTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogOnlyTest.xml @@ -36,8 +36,7 @@ </after> <!-- Search default simple product in the grid --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> - <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage"/> <actionGroup ref="FilterProductGridBySku2ActionGroup" stepKey="filterProductGrid"> <argument name="sku" value="$$initialSimpleProduct.sku$$"/> </actionGroup> @@ -70,8 +69,7 @@ <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertSimpleProductSaveSuccessMessage"/> <!-- Search updated simple product(from above step) in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedSimpleProduct"/> - <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPageToSearchUpdatedSimpleProduct"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{simpleProductRegularPrice32501InStock.name}}" stepKey="fillSimpleProductNameInNameFilter"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInSearchOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInSearchOnlyTest.xml index 3d37b54dfa439..0a892b004f150 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInSearchOnlyTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInSearchOnlyTest.xml @@ -36,8 +36,7 @@ </after> <!-- Search default simple product in the grid --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> - <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage"/> <actionGroup ref="FilterProductGridBySku2ActionGroup" stepKey="filterProductGrid"> <argument name="sku" value="$$initialSimpleProduct.sku$$"/> </actionGroup> @@ -70,8 +69,7 @@ <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertSimpleProductSaveSuccessMessage"/> <!-- Search updated simple product(from above step) in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedSimpleProduct"/> - <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPageToSearchUpdatedSimpleProduct"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{simpleProductRegularPrice325InStock.name}}" stepKey="fillSimpleProductNameInNameFilter"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml index 855a2b1d9b0cc..48b223b1b0567 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml @@ -36,8 +36,7 @@ </after> <!-- Search default simple product in the grid --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> - <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage"/> <actionGroup ref="FilterProductGridBySku2ActionGroup" stepKey="filterProductGrid"> <argument name="sku" value="$$initialSimpleProduct.sku$$"/> </actionGroup> @@ -83,8 +82,7 @@ <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertSimpleProductSaveSuccessMessage"/> <!--Search updated simple product(from above step) in the grid page--> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedSimpleProduct"/> - <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPageToSearchUpdatedSimpleProduct"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{simpleProductRegularPriceCustomOptions.name}}" stepKey="fillSimpleProductNameInNameFilter"/> @@ -150,9 +148,7 @@ <!-- Verify added Product in cart --> <selectOption selector="{{StorefrontProductPageSection.customOptionDropDown}}" userInput="{{simpleProductCustomizableOption.option_0_title}} +$98.00" stepKey="selectCustomOption"/> <fillField selector="{{StorefrontProductPageSection.qtyInput}}" userInput="1" stepKey="fillProductQuantity"/> - <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="clickOnAddToCartButton"/> - <waitForPageLoad stepKey="waitForProductToAddInCart"/> - <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> + <actionGroup ref="StorefrontClickAddToCartOnProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage"/> <seeElement selector="{{StorefrontProductPageSection.successMsg}}" stepKey="seeYouAddedSimpleprod4ToYourShoppingCartSuccessSaveMessage"/> <seeElement selector="{{StorefrontMinicartSection.quantity(1)}}" stepKey="seeAddedProductQuantityInCart"/> <actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="clickOnMiniCart"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceOutOfStockTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceOutOfStockTest.xml index af836efcf6be6..98096f2702e12 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceOutOfStockTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceOutOfStockTest.xml @@ -36,8 +36,7 @@ </after> <!-- Search default simple product in the grid --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> - <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage"/> <actionGroup ref="FilterProductGridBySku2ActionGroup" stepKey="filterProductGrid"> <argument name="sku" value="$$initialSimpleProduct.sku$$"/> </actionGroup> @@ -69,8 +68,7 @@ <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertSimpleProductSaveSuccessMessage"/> <!-- Search updated simple product(from above step) in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedSimpleProduct"/> - <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPageToSearchUpdatedSimpleProduct"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{simpleProductRegularPrice32503OutOfStock.name}}" stepKey="fillSimpleProductNameInNameFilter"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockVisibleInCategoryOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockVisibleInCategoryOnlyTest.xml index 595f9bcd489ec..f77138c6e2b8a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockVisibleInCategoryOnlyTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockVisibleInCategoryOnlyTest.xml @@ -37,8 +37,7 @@ </after> <!-- Search default virtual product in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage1"/> - <waitForPageLoad stepKey="waitForProductCatalogPage1"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage1"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAllFilter" /> <fillField selector="{{AdminProductGridFilterSection.keywordSearch}}" userInput="$$initialVirtualProduct.name$$" stepKey="fillVirtualProductNameInKeywordSearch"/> <click selector="{{AdminProductGridFilterSection.keywordSearchButton}}" stepKey="clickKeywordSearchButton"/> @@ -81,8 +80,7 @@ <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSaveSuccessMessage"/> <!-- Search updated virtual product(from above step) in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedVirtualProduct"/> - <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPageToSearchUpdatedVirtualProduct"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{updateVirtualProductRegularPrice.name}}" stepKey="fillVirtualProductNameInNameFilter"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml index 458d02d61426d..1bc66cca8d5fa 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml @@ -34,8 +34,7 @@ </after> <!-- Search default virtual product in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage1"/> - <waitForPageLoad stepKey="waitForProductCatalogPage1"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage1"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAllFilter" /> <fillField selector="{{AdminProductGridFilterSection.keywordSearch}}" userInput="$$initialVirtualProduct.name$$" stepKey="fillVirtualProductNameInKeywordSearch"/> <click selector="{{AdminProductGridFilterSection.keywordSearchButton}}" stepKey="clickKeywordSearchButton"/> @@ -126,8 +125,7 @@ <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSaveSuccessMessage"/> <!-- Search updated virtual product(from above step) in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedVirtualProduct"/> - <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPageToSearchUpdatedVirtualProduct"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{updateVirtualProductRegularPriceInStock.name}}" stepKey="fillVirtualProductNameInNameFilter"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryAndSearchTest.xml index 6d6ff0b3b1b89..8316d71f7b7da 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryAndSearchTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryAndSearchTest.xml @@ -37,8 +37,7 @@ </after> <!-- Search default virtual product in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage1"/> - <waitForPageLoad stepKey="waitForProductCatalogPage1"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage1"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAllFilter" /> <fillField selector="{{AdminProductGridFilterSection.keywordSearch}}" userInput="$$initialVirtualProduct.name$$" stepKey="fillVirtualProductNameInKeywordSearch"/> <click selector="{{AdminProductGridFilterSection.keywordSearchButton}}" stepKey="clickKeywordSearchButton"/> @@ -62,8 +61,7 @@ <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSaveSuccessMessage"/> <!-- Search updated virtual product(from above step) in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedVirtualProduct"/> - <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPageToSearchUpdatedVirtualProduct"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.name}}" stepKey="fillVirtualProductNameInNameFilter"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryOnlyTest.xml index d5ae971d87695..6e346f38a2836 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryOnlyTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryOnlyTest.xml @@ -37,8 +37,7 @@ </after> <!-- Search default virtual product in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage1"/> - <waitForPageLoad stepKey="waitForProductCatalogPage1"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage1"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickclearAllFilter" /> <fillField selector="{{AdminProductGridFilterSection.keywordSearch}}" userInput="$$initialVirtualProduct.name$$" stepKey="fillVirtualProductNameInKeywordSearch"/> <click selector="{{AdminProductGridFilterSection.keywordSearchButton}}" stepKey="clickKeywordSearchButton"/> @@ -70,8 +69,7 @@ <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSaveSuccessMessage"/> <!-- Search updated virtual product(from above step) in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedVirtualProduct"/> - <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPageToSearchUpdatedVirtualProduct"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.name}}" stepKey="fillVirtualProductNameInNameFilter"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInSearchOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInSearchOnlyTest.xml index 314df67d43d00..57cf6f6e45d97 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInSearchOnlyTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInSearchOnlyTest.xml @@ -35,8 +35,7 @@ </after> <!-- Search default virtual product in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage1"/> - <waitForPageLoad stepKey="waitForProductCatalogPage1"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage1"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAllFilter" /> <fillField selector="{{AdminProductGridFilterSection.keywordSearch}}" userInput="$$initialVirtualProduct.name$$" stepKey="fillVirtualProductNameInKeywordSearch"/> <click selector="{{AdminProductGridFilterSection.keywordSearchButton}}" stepKey="clickKeywordSearchButton"/> @@ -60,8 +59,7 @@ <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSaveSuccessMessage"/> <!-- Search updated virtual product(from above step) in the grid --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedVirtualProduct"/> - <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPageToSearchUpdatedVirtualProduct"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{updateVirtualProductRegularPrice99OutOfStock.name}}" stepKey="fillVirtualProductNameInNameFilter"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest.xml index d0f4fc8882e3f..d1aa7a31ed297 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest.xml @@ -37,8 +37,7 @@ </after> <!-- Search default virtual product in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> - <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAllFilter" /> <fillField selector="{{AdminProductGridFilterSection.keywordSearch}}" userInput="$$initialVirtualProduct.name$$" stepKey="fillVirtualProductNameInKeywordSearch"/> <click selector="{{AdminProductGridFilterSection.keywordSearchButton}}" stepKey="clickKeywordSearchButton"/> @@ -76,8 +75,7 @@ <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSaveSuccessMessage"/> <!-- Search updated virtual product(from above step) in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedVirtualProduct"/> - <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPageToSearchUpdatedVirtualProduct"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{updateVirtualProductSpecialPrice.name}}" stepKey="fillVirtualProductNameInNameFilter"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceOutOfStockVisibleInCategoryAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceOutOfStockVisibleInCategoryAndSearchTest.xml index 2234d6f338b62..ba8ccbe932bfa 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceOutOfStockVisibleInCategoryAndSearchTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceOutOfStockVisibleInCategoryAndSearchTest.xml @@ -37,8 +37,7 @@ </after> <!-- Search default virtual product in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage1"/> - <waitForPageLoad stepKey="waitForProductCatalogPage1"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage1"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAllFilter" /> <fillField selector="{{AdminProductGridFilterSection.keywordSearch}}" userInput="$$initialVirtualProduct.name$$" stepKey="fillVirtualProductNameInKeywordSearch"/> <click selector="{{AdminProductGridFilterSection.keywordSearchButton}}" stepKey="clickKeywordSearchButton"/> @@ -75,8 +74,7 @@ <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSaveSuccessMessage"/> <!-- Search updated virtual product with special price(out of stock) in the grid --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedVirtualProduct"/> - <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPageToSearchUpdatedVirtualProduct"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{updateVirtualProductSpecialPriceOutOfStock.name}}" stepKey="fillVirtualProductNameInNameFilter"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryOnlyTest.xml index 8f0861fe33371..e43b04bb5e08b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryOnlyTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryOnlyTest.xml @@ -37,8 +37,7 @@ </after> <!-- Search default virtual product in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage1"/> - <waitForPageLoad stepKey="waitForProductCatalogPage1"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage1"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAllFilter" /> <fillField selector="{{AdminProductGridFilterSection.keywordSearch}}" userInput="$$initialVirtualProduct.name$$" stepKey="fillVirtualProductNameInKeywordSearch"/> <click selector="{{AdminProductGridFilterSection.keywordSearchButton}}" stepKey="clickKeywordSearchButton"/> @@ -81,8 +80,7 @@ <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSaveSuccessMessage"/> <!-- Search updated virtual product(from above step) in the grid --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedVirtualProduct"/> - <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPageToSearchUpdatedVirtualProduct"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{updateVirtualProductWithTierPriceInStock.name}}" stepKey="fillVirtualProductNameInNameFilter"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceOutOfStockVisibleInCategoryAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceOutOfStockVisibleInCategoryAndSearchTest.xml index f7f5385381590..52d8c08294e86 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceOutOfStockVisibleInCategoryAndSearchTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceOutOfStockVisibleInCategoryAndSearchTest.xml @@ -37,8 +37,7 @@ </after> <!-- Search default virtual product in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage1"/> - <waitForPageLoad stepKey="waitForProductCatalogPage1"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage1"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAllFilter" /> <fillField selector="{{AdminProductGridFilterSection.keywordSearch}}" userInput="$$initialVirtualProduct.name$$" stepKey="fillVirtualProductNameInKeywordSearch"/> <click selector="{{AdminProductGridFilterSection.keywordSearchButton}}" stepKey="clickKeywordSearchButton"/> @@ -81,8 +80,7 @@ <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSaveSuccessMessage"/> <!-- Search updated virtual product(from above step) in the grid page --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedVirtualProduct"/> - <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPageToSearchUpdatedVirtualProduct"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{updateVirtualTierPriceOutOfStock.name}}" stepKey="fillVirtualProductNameInNameFilter"/> 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 437532b9baebf..26ff1bc45be9d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityDateTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityDateTest.xml @@ -39,7 +39,7 @@ <generateDate date="now" format="m/j/Y" stepKey="generateDefaultDate"/> <!--Navigate to Stores > Attributes > Product.--> - <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="goToProductAttributes"/> + <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="goToProductAttributes"/> <!--Create new Product Attribute as TextField, with code and default value.--> <actionGroup ref="CreateProductAttributeWithDateFieldActionGroup" stepKey="createAttribute"> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityDropdownTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityDropdownTest.xml index 580a5bd4939bb..61787dcff0b91 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityDropdownTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityDropdownTest.xml @@ -33,7 +33,7 @@ </after> <!--Navigate to Stores > Attributes > Product.--> - <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="goToProductAttributes"/> + <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="goToProductAttributes"/> <!--Create new Product Attribute as TextField, with code and default value.--> <actionGroup ref="CreateProductAttributeActionGroup" stepKey="createAttribute"> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityDropdownWithSingleQuoteTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityDropdownWithSingleQuoteTest.xml index e24bf0d7b1115..73c8bafba0625 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityDropdownWithSingleQuoteTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityDropdownWithSingleQuoteTest.xml @@ -33,7 +33,7 @@ </after> <!--Navigate to Stores > Attributes > Product.--> - <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="goToProductAttributes"/> + <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="goToProductAttributes"/> <!--Create new Product Attribute as TextField, with code and default value.--> <actionGroup ref="CreateProductAttributeActionGroup" stepKey="createAttribute"> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityMultiSelectTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityMultiSelectTest.xml index 0a84d9af3c918..9952f6a4a85fe 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityMultiSelectTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityMultiSelectTest.xml @@ -32,7 +32,7 @@ </after> <!--Navigate to Stores > Attributes > Product.--> - <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="goToProductAttributes"/> + <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="goToProductAttributes"/> <!--Create new Product Attribute as TextField, with code and default value.--> <actionGroup ref="CreateProductAttributeActionGroup" stepKey="createAttribute"> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityPriceTest.xml index 97eff20b2d560..760cd5e0e488a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityPriceTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityPriceTest.xml @@ -33,7 +33,7 @@ </after> <!--Navigate to Stores > Attributes > Product.--> - <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="goToProductAttributes"/> + <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="goToProductAttributes"/> <!--Create new Product Attribute with Price--> <actionGroup ref="CreateProductAttributeActionGroup" stepKey="createAttribute"> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityTextFieldTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityTextFieldTest.xml index c0cff7b0b2bc9..ea94fc58400a6 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityTextFieldTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateProductAttributeEntityTextFieldTest.xml @@ -33,7 +33,7 @@ </after> <!--Navigate to Stores > Attributes > Product.--> - <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="goToProductAttributes"/> + <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="goToProductAttributes"/> <!--Create new Product Attribute as TextField, with code and default value.--> <actionGroup ref="CreateProductAttributeWithTextFieldActionGroup" stepKey="createAttribute"> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/SimpleProductTwoCustomOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/SimpleProductTwoCustomOptionsTest.xml index 7b2e004495fea..ea4d32b780f6e 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/SimpleProductTwoCustomOptionsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/SimpleProductTwoCustomOptionsTest.xml @@ -37,7 +37,7 @@ <actionGroup ref="DeleteProductUsingProductGridActionGroup" stepKey="deleteProduct"> <argument name="product" value="SimpleProduct3"/> </actionGroup> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> </after> <!-- opens the custom option panel and clicks add options --> diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/OptionManagementTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/OptionManagementTest.php index edbbaebd0576b..05bd3ec2a3d33 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/OptionManagementTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/OptionManagementTest.php @@ -10,10 +10,14 @@ use Magento\Catalog\Api\Data\ProductAttributeInterface; use Magento\Catalog\Model\Product\Attribute\OptionManagement; use Magento\Eav\Api\AttributeOptionManagementInterface; +use Magento\Eav\Api\AttributeOptionUpdateInterface; use Magento\Eav\Api\Data\AttributeOptionInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +/** + * Class to test management of attribute options + */ class OptionManagementTest extends TestCase { /** @@ -22,18 +26,28 @@ class OptionManagementTest extends TestCase protected $model; /** - * @var MockObject + * @var AttributeOptionManagementInterface|MockObject */ protected $eavOptionManagementMock; + /** + * @var AttributeOptionUpdateInterface|MockObject + */ + private $eavOptionUpdateMock; + protected function setUp(): void { $this->eavOptionManagementMock = $this->getMockForAbstractClass(AttributeOptionManagementInterface::class); + $this->eavOptionUpdateMock = $this->getMockForAbstractClass(AttributeOptionUpdateInterface::class); $this->model = new OptionManagement( - $this->eavOptionManagementMock + $this->eavOptionManagementMock, + $this->eavOptionUpdateMock ); } + /** + * Test to Retrieve list of attribute options + */ public function testGetItems() { $attributeCode = 10; @@ -44,6 +58,9 @@ public function testGetItems() $this->assertEquals([], $this->model->getItems($attributeCode)); } + /** + * Test to Add option to attribute + */ public function testAdd() { $attributeCode = 42; @@ -56,6 +73,9 @@ public function testAdd() $this->assertTrue($this->model->add($attributeCode, $optionMock)); } + /** + * Test to delete attribute option + */ public function testDelete() { $attributeCode = 'atrCde'; @@ -68,6 +88,9 @@ public function testDelete() $this->assertTrue($this->model->delete($attributeCode, $optionId)); } + /** + * Test to delete attribute option with invalid option id + */ public function testDeleteWithInvalidOption() { $this->expectException('Magento\Framework\Exception\InputException'); @@ -77,4 +100,24 @@ public function testDeleteWithInvalidOption() $this->eavOptionManagementMock->expects($this->never())->method('delete'); $this->model->delete($attributeCode, $optionId); } + + /** + * Test to update attribute option + */ + public function testUpdate() + { + $attributeCode = 'atrCde'; + $optionId = 10; + $optionMock = $this->getMockForAbstractClass(AttributeOptionInterface::class); + + $this->eavOptionUpdateMock->expects($this->once()) + ->method('update') + ->with( + ProductAttributeInterface::ENTITY_TYPE_CODE, + $attributeCode, + $optionId, + $optionMock + )->willReturn(true); + $this->assertTrue($this->model->update($attributeCode, $optionId, $optionMock)); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Type/FileTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Type/FileTest.php index 539489f18f404..0e6fb8ececf03 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Type/FileTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Type/FileTest.php @@ -24,6 +24,8 @@ use PHPUnit\Framework\TestCase; /** + * Test file option type + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class FileTest extends TestCase @@ -142,6 +144,14 @@ protected function getFileObject() ); } + public function testGetFormattedOptionValueWithUnserializedValue() + { + $fileObject = $this->getFileObject(); + + $value = 'some unserialized value, 1, 2.test'; + $this->assertEquals($value, $fileObject->getFormattedOptionValue($value)); + } + public function testGetCustomizedView() { $fileObject = $this->getFileObject(); diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml index 5a7a3135b4bfe..97a787c87bfa8 100644 --- a/app/code/Magento/Catalog/etc/di.xml +++ b/app/code/Magento/Catalog/etc/di.xml @@ -35,6 +35,7 @@ <preference for="Magento\Catalog\Api\Data\ProductAttributeTypeInterface" type="Magento\Catalog\Model\Product\Attribute\Type" /> <preference for="Magento\Catalog\Api\ProductAttributeGroupRepositoryInterface" type="Magento\Catalog\Model\ProductAttributeGroupRepository" /> <preference for="Magento\Catalog\Api\ProductAttributeOptionManagementInterface" type="Magento\Catalog\Model\Product\Attribute\OptionManagement" /> + <preference for="Magento\Catalog\Api\ProductAttributeOptionUpdateInterface" type="Magento\Catalog\Model\Product\Attribute\OptionManagement" /> <preference for="Magento\Catalog\Api\ProductLinkRepositoryInterface" type="Magento\Catalog\Model\ProductLink\Repository" /> <preference for="Magento\Catalog\Api\Data\ProductAttributeSearchResultsInterface" type="Magento\Catalog\Model\ProductAttributeSearchResults" /> <preference for="Magento\Catalog\Api\Data\CategoryAttributeSearchResultsInterface" type="Magento\Catalog\Model\CategoryAttributeSearchResults" /> diff --git a/app/code/Magento/Catalog/etc/webapi.xml b/app/code/Magento/Catalog/etc/webapi.xml index 3f82175ab02eb..5e799cd9f426d 100644 --- a/app/code/Magento/Catalog/etc/webapi.xml +++ b/app/code/Magento/Catalog/etc/webapi.xml @@ -183,6 +183,12 @@ <resource ref="Magento_Catalog::attributes_attributes" /> </resources> </route> + <route url="/V1/products/attributes/:attributeCode/options/:optionId" method="PUT"> + <service class="Magento\Catalog\Api\ProductAttributeOptionUpdateInterface" method="update" /> + <resources> + <resource ref="Magento_Catalog::attributes_attributes" /> + </resources> + </route> <route url="/V1/products/attributes/:attributeCode/options/:optionId" method="DELETE"> <service class="Magento\Catalog\Api\ProductAttributeOptionManagementInterface" method="delete" /> <resources> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml index d1f9ebd4c99a4..3aacc75d0b4a4 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml @@ -42,7 +42,7 @@ <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> </actionGroup> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> </after> <!-- 1. Begin creating a new catalog price rule --> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleByPercentTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleByPercentTest.xml index fcae0065f1b53..61c5ab1795553 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleByPercentTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleByPercentTest.xml @@ -42,7 +42,7 @@ <argument name="name" value="{{_defaultCatalogRule.name}}"/> <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> </actionGroup> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> <deleteData createDataKey="createProduct" stepKey="deleteSimpleProduct"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> </after> @@ -61,8 +61,7 @@ <see stepKey="seeNewPrice2" selector="{{StorefrontProductInfoMainSection.updatedPrice}}" userInput="$110.70"/> <!-- Add the product to cart and check that the price is correct there --> - <click stepKey="addToCart" selector="{{StorefrontProductActionSection.addToCart}}"/> - <waitForPageLoad stepKey="waitForAddedToCart"/> + <actionGroup ref="StorefrontClickAddToCartOnProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage"/> <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCheckout"/> <see stepKey="seeNewPriceInCart" selector="{{CheckoutCartSummarySection.subtotal}}" userInput="$110.70"/> </test> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleWithInvalidDataTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleWithInvalidDataTest.xml index 90a0835508b06..77228dde8797f 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleWithInvalidDataTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleWithInvalidDataTest.xml @@ -20,7 +20,7 @@ <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> </before> <after> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> </after> <actionGroup ref="NewCatalogPriceRuleWithInvalidDataActionGroup" stepKey="createNewPriceRule"> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest/AdminDeleteCatalogPriceRuleEntityFromConfigurableProductTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest/AdminDeleteCatalogPriceRuleEntityFromConfigurableProductTest.xml index 6b34fd1e67e9b..6666e80f3ee04 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest/AdminDeleteCatalogPriceRuleEntityFromConfigurableProductTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest/AdminDeleteCatalogPriceRuleEntityFromConfigurableProductTest.xml @@ -131,9 +131,7 @@ <!-- Assert that the rule isn't present in the Shopping Cart --> <selectOption selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" userInput="option1" stepKey="selectOption1"/> - <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addToCart1"/> - <waitForPageLoad time="30" stepKey="waitForPageLoad4"/> - <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> + <actionGroup ref="StorefrontClickAddToCartOnProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage"/> <see selector="{{StorefrontMessagesSection.success}}" userInput="You added $$createConfigProduct1.name$ to your shopping cart." stepKey="seeAddToCartSuccessMessage"/> <actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="openMiniShoppingCart1"/> <see selector="{{StorefrontMinicartSection.productPriceByName($$createConfigProduct1.name$$)}}" userInput="$$createConfigProduct1.price$$" stepKey="seeCorrectProductPrice1"/> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogPriceRuleByProductAttributeTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogPriceRuleByProductAttributeTest.xml index 1919f7d5cc544..56144b5908868 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogPriceRuleByProductAttributeTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogPriceRuleByProductAttributeTest.xml @@ -232,9 +232,8 @@ <see userInput="You saved the rule." selector="{{ContentManagementSection.StoreConfigurationPageSuccessMessage}}" stepKey="seeMessage"/> <see userInput="Updated rules applied." selector="{{ContentManagementSection.StoreConfigurationPageSuccessMessage}}" stepKey="seeSuccessMessage"/> - <!-- Run cron twice --> - <magentoCLI command="cron:run" stepKey="runCron1"/> - <magentoCLI command="cron:run" stepKey="runCron2"/> + <!-- Run cron --> + <magentoCron stepKey="runAllCronJobs"/> <magentoCLI command="cache:flush" stepKey="flushCache"/> <!-- Go to Frontend and open the simple product --> diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.xml b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.xml index 329f5e8cae3f6..ad426c4bc6c4c 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.xml +++ b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.xml @@ -56,8 +56,7 @@ <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProduct"/> <!--Select product and go toUpdate Attribute page--> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="GoToCatalogPageChangingView"/> - <waitForPageLoad stepKey="WaitForPageToLoadFullyChangingView"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="goToCatalogPageChangingView"/> <actionGroup ref="FilterProductGridByNameActionGroup" stepKey="filterBundleProductOptionsDownToName"> <argument name="product" value="ApiSimpleProduct"/> </actionGroup> diff --git a/app/code/Magento/Checkout/Model/Cart.php b/app/code/Magento/Checkout/Model/Cart.php index cec99909dc999..9f1ff991c93e3 100644 --- a/app/code/Magento/Checkout/Model/Cart.php +++ b/app/code/Magento/Checkout/Model/Cart.php @@ -18,6 +18,7 @@ * @api * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @deprecated 100.1.0 Use \Magento\Quote\Model\Quote instead * @see \Magento\Quote\Api\Data\CartInterface */ @@ -272,6 +273,10 @@ public function addOrderItem($orderItem, $qtyFlag = null) * with the same id may have different sets of order attributes. */ $product = $this->productRepository->getById($orderItem->getProductId(), false, $storeId, true); + if ($orderItem->getOrderId() !== null) { + //reorder existing order + $product->setSkipCheckRequiredOption(true); + } } catch (NoSuchEntityException $e) { return $this; } @@ -282,7 +287,14 @@ public function addOrderItem($orderItem, $qtyFlag = null) } else { $info->setQty(1); } - + $productOptions = $orderItem->getProductOptions(); + if ($productOptions !== null && !empty($productOptions['options'])) { + $formattedOptions = []; + foreach ($productOptions['options'] as $option) { + $formattedOptions[$option['option_id']] = $option['option_value']; + } + $info->setData('options', $formattedOptions); + } $this->addProduct($product, $info); } return $this; @@ -291,8 +303,8 @@ public function addOrderItem($orderItem, $qtyFlag = null) /** * Get product object based on requested product information * - * @param Product|int|string $productInfo - * @return Product + * @param Product|int|string $productInfo + * @return Product * @throws \Magento\Framework\Exception\LocalizedException */ protected function _getProduct($productInfo) @@ -332,8 +344,8 @@ protected function _getProduct($productInfo) /** * Get request for product add to cart procedure * - * @param \Magento\Framework\DataObject|int|array $requestInfo - * @return \Magento\Framework\DataObject + * @param \Magento\Framework\DataObject|int|array $requestInfo + * @return \Magento\Framework\DataObject * @throws \Magento\Framework\Exception\LocalizedException */ protected function _getProductRequest($requestInfo) diff --git a/app/code/Magento/Cms/Test/Unit/Model/PageRepository/ValidationCompositeTest.php b/app/code/Magento/Cms/Test/Unit/Model/PageRepository/ValidationCompositeTest.php index 9b02050156cc7..f942e62588f0e 100644 --- a/app/code/Magento/Cms/Test/Unit/Model/PageRepository/ValidationCompositeTest.php +++ b/app/code/Magento/Cms/Test/Unit/Model/PageRepository/ValidationCompositeTest.php @@ -43,7 +43,7 @@ public function testConstructorValidation($validators) new ValidationComposite($this->subject, $validators); } - public function testSaveInvokesValidatorsWithSucess() + public function testSaveInvokesValidatorsWithSuccess() { $validator1 = $this->getMockForAbstractClass(ValidatorInterface::class); $validator2 = $this->getMockForAbstractClass(ValidatorInterface::class); diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminAddDefaultImageConfigurableTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminAddDefaultImageConfigurableTest.xml index 0d83cc6610194..9628121348476 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminAddDefaultImageConfigurableTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminAddDefaultImageConfigurableTest.xml @@ -81,7 +81,7 @@ </createData> </before> <after> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> <deleteData createDataKey="simple1Handle" stepKey="deleteSimple1"/> <deleteData createDataKey="simple2Handle" stepKey="deleteSimple2"/> <deleteData createDataKey="childProductHandle1" stepKey="deleteChild1"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckConfigurableProductAttributeValueUniquenessTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckConfigurableProductAttributeValueUniquenessTest.xml index 8962efbb8dd26..43083ee13b0fc 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckConfigurableProductAttributeValueUniquenessTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckConfigurableProductAttributeValueUniquenessTest.xml @@ -46,8 +46,7 @@ </createData> <!--Go to created product page--> <comment userInput="Go to created product page" stepKey="goToProdPage"/> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="goToProductGrid"/> - <waitForPageLoad stepKey="waitForProductPage1"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="goToProductGrid"/> <actionGroup ref="FilterProductGridByName2ActionGroup" stepKey="filterByName"> <argument name="name" value="$$createConfigProduct.name$$"/> </actionGroup> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest/AdminCreateConfigurableProductAfterGettingIncorrectSKUMessageTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest/AdminCreateConfigurableProductAfterGettingIncorrectSKUMessageTest.xml index 274a75aedbc5f..d53cc5f34b967 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest/AdminCreateConfigurableProductAfterGettingIncorrectSKUMessageTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest/AdminCreateConfigurableProductAfterGettingIncorrectSKUMessageTest.xml @@ -60,8 +60,7 @@ <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveProduct2"/> <conditionalClick selector="{{AdminChooseAffectedAttributeSetPopup.confirm}}" dependentSelector="{{AdminChooseAffectedAttributeSetPopup.confirm}}" visible="true" stepKey="clickOnConfirmInPopup"/> <see userInput="You saved the product." stepKey="seeSaveConfirmation"/> - <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="goToProductAttributes"/> - <waitForPageLoad stepKey="waitForProductAttributes"/> + <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="goToProductAttributes"/> <click selector="{{AdminProductAttributeGridSection.ResetFilter}}" stepKey="resetFiltersOnGrid1"/> <fillField selector="{{AdminProductAttributeGridSection.FilterByAttributeCode}}" userInput="color" stepKey="fillFilter"/> <click selector="{{AdminProductAttributeGridSection.Search}}" stepKey="clickSearch"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductBasedOnParentSkuTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductBasedOnParentSkuTest.xml index e5456429373e1..abe4ce5e46661 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductBasedOnParentSkuTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductBasedOnParentSkuTest.xml @@ -68,8 +68,7 @@ <actionGroup ref="SaveConfigurableProductAddToCurrentAttributeSetActionGroup" stepKey="saveProduct"/> <!-- Assert child products generated sku in grid --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="openProductCatalogPage"/> - <waitForPageLoad stepKey="waitForProductCatalogPageLoad"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage"/> <actionGroup ref="FilterProductGridByName2ActionGroup" stepKey="filterFirstProductByNameInGrid"> <argument name="name" value="{{colorConfigurableProductAttribute1.name}}"/> </actionGroup> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminRelatedProductsTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminRelatedProductsTest.xml index b6b3d21c8a626..3ca6b21e7dbb9 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminRelatedProductsTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminRelatedProductsTest.xml @@ -81,7 +81,7 @@ <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin1"/> </before> <after> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> <deleteData createDataKey="simple1Handle" stepKey="deleteSimple1"/> <deleteData createDataKey="simple2Handle" stepKey="deleteSimple2"/> <deleteData createDataKey="childProductHandle1" stepKey="deleteChild1"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminRemoveDefaultImageConfigurableTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminRemoveDefaultImageConfigurableTest.xml index 86d4070a9a2c8..4a28e76851b25 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminRemoveDefaultImageConfigurableTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminRemoveDefaultImageConfigurableTest.xml @@ -81,7 +81,7 @@ </createData> </before> <after> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> <deleteData createDataKey="simple1Handle" stepKey="deleteSimple1"/> <deleteData createDataKey="simple2Handle" stepKey="deleteSimple2"/> <deleteData createDataKey="childProductHandle1" stepKey="deleteChild1"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductChildSearchTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductChildSearchTest.xml index fbf23597a3927..77579c4d90ad5 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductChildSearchTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductChildSearchTest.xml @@ -117,8 +117,7 @@ <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> <!-- Go to the product page for the first product --> - <amOnPage stepKey="goToProductGrid" url="{{ProductCatalogPage.url}}"/> - <waitForPageLoad stepKey="waitForProductGridLoad"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="goToProductGrid"/> <actionGroup stepKey="searchForSimpleProduct" ref="FilterProductGridBySku2ActionGroup"> <argument name="sku" value="$$createConfigChildProduct1.sku$$"/> </actionGroup> diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/Product/Initialization/CleanConfigurationTmpImagesTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/Product/Initialization/CleanConfigurationTmpImagesTest.php index 0a014b9aeef99..bb79c13bba82a 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/Product/Initialization/CleanConfigurationTmpImagesTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/Product/Initialization/CleanConfigurationTmpImagesTest.php @@ -64,7 +64,7 @@ class CleanConfigurationTmpImagesTest extends TestCase /** * @var Json|MockObject */ - private $seralizer; + private $serializer; /** * @var ProductInitializationHelper|MockObject @@ -87,7 +87,7 @@ protected function setUp(): void $this->writeFolder = $this->getMockBuilder(Write::class) ->disableOriginalConstructor() ->getMock(); - $this->seralizer = $this->getMockBuilder(Json::class) + $this->serializer = $this->getMockBuilder(Json::class) ->disableOriginalConstructor() ->getMock(); $this->subjectMock = $this->getMockBuilder(ProductInitializationHelper::class) @@ -106,7 +106,7 @@ protected function setUp(): void 'fileStorageDb' => $this->fileStorageDb, 'mediaConfig' => $this->mediaConfig, 'filesystem' => $this->filesystem, - 'seralizer' => $this->seralizer + 'serializer' => $this->serializer ] ); } diff --git a/app/code/Magento/Cookie/Test/Unit/Helper/CookieTest.php b/app/code/Magento/Cookie/Test/Unit/Helper/CookieTest.php index 9e370a186d272..6522c3ad1dcaa 100644 --- a/app/code/Magento/Cookie/Test/Unit/Helper/CookieTest.php +++ b/app/code/Magento/Cookie/Test/Unit/Helper/CookieTest.php @@ -162,6 +162,6 @@ public function getConfigMethodStub($hashName) return $defaultConfig[$hashName]; } - throw new \InvalidArgumentException('Unknow id = ' . $hashName); + throw new \InvalidArgumentException('Unknown id = ' . $hashName); } } diff --git a/app/code/Magento/Customer/Model/ResourceModel/Address/Grid/Collection.php b/app/code/Magento/Customer/Model/ResourceModel/Address/Grid/Collection.php index 0e2eb3e1d8e65..c7b44288bc85f 100644 --- a/app/code/Magento/Customer/Model/ResourceModel/Address/Grid/Collection.php +++ b/app/code/Magento/Customer/Model/ResourceModel/Address/Grid/Collection.php @@ -185,7 +185,7 @@ public function addFieldToFilter($field, $condition = null) { if ($field === 'region') { $conditionSql = $this->_getConditionSql( - $this->getRegionNameExpresion(), + $this->getRegionNameExpression(), $condition ); $this->getSelect()->where($conditionSql); @@ -211,7 +211,7 @@ public function addFullTextFilter(string $value) $whereCondition = ''; foreach ($fields as $key => $field) { $field = $field === 'region' - ? $this->getRegionNameExpresion() + ? $this->getRegionNameExpression() : 'main_table.' . $field; $condition = $this->_getConditionSql( $this->getConnection()->quoteIdentifier($field), @@ -246,18 +246,18 @@ private function joinRegionNameTable() )->joinLeft( ['rnt' => $this->getTable('directory_country_region_name')], "rnt.region_id={$regionIdField} AND {$localeCondition}", - ['region' => $this->getRegionNameExpresion()] + ['region' => $this->getRegionNameExpression()] ); return $this; } /** - * Get SQL Expresion to define Region Name field by locale + * Get SQL Expression to define Region Name field by locale * * @return \Zend_Db_Expr */ - private function getRegionNameExpresion(): \Zend_Db_Expr + private function getRegionNameExpression(): \Zend_Db_Expr { $connection = $this->getConnection(); $defaultNameExpr = $connection->getIfNullSql( diff --git a/app/code/Magento/Customer/Model/ResourceModel/Grid/Collection.php b/app/code/Magento/Customer/Model/ResourceModel/Grid/Collection.php index bf8ef767063bd..0fab27161ce25 100644 --- a/app/code/Magento/Customer/Model/ResourceModel/Grid/Collection.php +++ b/app/code/Magento/Customer/Model/ResourceModel/Grid/Collection.php @@ -74,7 +74,7 @@ public function addFieldToFilter($field, $condition = null) { if ($field === 'billing_region') { $conditionSql = $this->_getConditionSql( - $this->getRegionNameExpresion(), + $this->getRegionNameExpression(), $condition ); $this->getSelect()->where($conditionSql); @@ -100,7 +100,7 @@ public function addFullTextFilter(string $value) $whereCondition = ''; foreach ($fields as $key => $field) { $field = $field === 'billing_region' - ? $this->getRegionNameExpresion() + ? $this->getRegionNameExpression() : 'main_table.' . $field; $condition = $this->_getConditionSql( $this->getConnection()->quoteIdentifier($field), @@ -152,18 +152,18 @@ private function joinRegionNameTable() )->joinLeft( ['rnt' => $this->getTable('directory_country_region_name')], "rnt.region_id={$regionIdField} AND {$localeCondition}", - ['billing_region' => $this->getRegionNameExpresion()] + ['billing_region' => $this->getRegionNameExpression()] ); return $this; } /** - * Get SQL Expresion to define Region Name field by locale + * Get SQL Expression to define Region Name field by locale * * @return \Zend_Db_Expr */ - private function getRegionNameExpresion(): \Zend_Db_Expr + private function getRegionNameExpression(): \Zend_Db_Expr { $connection = $this->getConnection(); $defaultNameExpr = $connection->getIfNullSql( diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCheckTaxAddingValidVATIdTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCheckTaxAddingValidVATIdTest.xml index 80cdeadb391da..85f4976c88572 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCheckTaxAddingValidVATIdTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCheckTaxAddingValidVATIdTest.xml @@ -83,9 +83,7 @@ <see userInput="$$createProduct.name$$" selector="{{StorefrontProductInfoMainSection.productName}}" stepKey="assertFirstProductNameTitle"/> <!--Add a product to the cart--> - <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart"/> - <waitForPageLoad stepKey="waitForAddProductToCart"/> - <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> + <actionGroup ref="StorefrontClickAddToCartOnProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage"/> <!--Proceed to checkout--> <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="GoToCheckoutFromMinicartActionGroup"/> <!-- Click next button to open payment section --> diff --git a/app/code/Magento/Downloadable/Controller/Download/Sample.php b/app/code/Magento/Downloadable/Controller/Download/Sample.php index e2561092a7592..839083b320878 100644 --- a/app/code/Magento/Downloadable/Controller/Download/Sample.php +++ b/app/code/Magento/Downloadable/Controller/Download/Sample.php @@ -3,14 +3,21 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + declare(strict_types=1); namespace Magento\Downloadable\Controller\Download; +use Magento\Downloadable\Controller\Download; use Magento\Downloadable\Helper\Download as DownloadHelper; +use Magento\Downloadable\Helper\File; use Magento\Downloadable\Model\RelatedProductRetriever; use Magento\Downloadable\Model\Sample as SampleModel; +use Magento\Downloadable\Model\SampleFactory; use Magento\Framework\App\Action\Context; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\CatalogInventory\Api\StockConfigurationInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\App\ResponseInterface; /** @@ -18,24 +25,49 @@ * * @SuppressWarnings(PHPMD.AllPurposeAction) */ -class Sample extends \Magento\Downloadable\Controller\Download +class Sample extends Download { /** * @var RelatedProductRetriever */ private $relatedProductRetriever; + /** + * @var File + */ + private $file; + + /** + * @var SampleFactory + */ + private $sampleFactory; + + /** + * @var StockConfigurationInterface + */ + private $stockConfiguration; + /** * @param Context $context * @param RelatedProductRetriever $relatedProductRetriever + * @param File|null $file + * @param SampleFactory|null $sampleFactory + * @param StockConfigurationInterface|null $stockConfiguration */ public function __construct( Context $context, - RelatedProductRetriever $relatedProductRetriever + RelatedProductRetriever $relatedProductRetriever, + ?File $file = null, + ?SampleFactory $sampleFactory = null, + ?StockConfigurationInterface $stockConfiguration = null ) { parent::__construct($context); $this->relatedProductRetriever = $relatedProductRetriever; + $this->file = $file ?: ObjectManager::getInstance()->get(File::class); + $this->sampleFactory = $sampleFactory ?: ObjectManager::getInstance()->get(SampleFactory::class); + $this->stockConfiguration = $stockConfiguration + ?: ObjectManager::getInstance()->get(StockConfigurationInterface::class); } /** @@ -47,43 +79,60 @@ public function execute() { $sampleId = $this->getRequest()->getParam('sample_id', 0); /** @var SampleModel $sample */ - $sample = $this->_objectManager->create(SampleModel::class); + $sample = $this->sampleFactory->create(); $sample->load($sampleId); - if ($sample->getId() && $this->isProductSalable($sample)) { - $resource = ''; - $resourceType = ''; - if ($sample->getSampleType() == DownloadHelper::LINK_TYPE_URL) { - $resource = $sample->getSampleUrl(); - $resourceType = DownloadHelper::LINK_TYPE_URL; - } elseif ($sample->getSampleType() == DownloadHelper::LINK_TYPE_FILE) { - /** @var \Magento\Downloadable\Helper\File $helper */ - $helper = $this->_objectManager->get(\Magento\Downloadable\Helper\File::class); - $resource = $helper->getFilePath($sample->getBasePath(), $sample->getSampleFile()); - $resourceType = DownloadHelper::LINK_TYPE_FILE; - } - try { - $this->_processDownload($resource, $resourceType); - // phpcs:ignore Magento2.Security.LanguageConstruct.ExitUsage - exit(0); - } catch (\Exception $e) { - $this->messageManager->addError( - __('Sorry, there was an error getting requested content. Please contact the store owner.') - ); - } + if ($this->isCanDownload($sample)) { + $this->download($sample); } return $this->getResponse()->setRedirect($this->_redirect->getRedirectUrl()); } /** - * Check is related product salable. + * Is sample can be downloaded * * @param SampleModel $sample * @return bool */ - private function isProductSalable(SampleModel $sample): bool + private function isCanDownload(SampleModel $sample): bool { $product = $this->relatedProductRetriever->getProduct((int) $sample->getProductId()); - return $product ? $product->isSalable() : false; + if ($product && $sample->getId()) { + $isProductEnabled = (int) $product->getStatus() === Status::STATUS_ENABLED; + + return $product->isSalable() || $this->stockConfiguration->isShowOutOfStock() && $isProductEnabled; + } + + return false; + } + + /** + * Download process + * + * @param SampleModel $sample + * @return void + */ + private function download(SampleModel $sample): void + { + $resource = ''; + $resourceType = ''; + + if ($sample->getSampleType() === DownloadHelper::LINK_TYPE_URL) { + $resource = $sample->getSampleUrl(); + $resourceType = DownloadHelper::LINK_TYPE_URL; + } elseif ($sample->getSampleType() === DownloadHelper::LINK_TYPE_FILE) { + $resource = $this->file->getFilePath($sample->getBasePath(), $sample->getSampleFile()); + $resourceType = DownloadHelper::LINK_TYPE_FILE; + } + + try { + $this->_processDownload($resource, $resourceType); + // phpcs:ignore Magento2.Security.LanguageConstruct.ExitUsage + exit(0); + } catch (\Exception $e) { + $this->messageManager->addErrorMessage( + __('Sorry, there was an error getting requested content. Please contact the store owner.') + ); + } } } diff --git a/app/code/Magento/Downloadable/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Downloadable/Test/Mftf/Data/ProductData.xml index 2986532ef1138..9dca730dfd5c5 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Data/ProductData.xml @@ -105,4 +105,15 @@ <requiredEntity type="downloadable_link">downloadableLink1</requiredEntity> <requiredEntity type="downloadable_link">downloadableLink2</requiredEntity> </entity> + <entity name="DownloadableProductWithoutLinksOutOfStock" type="product"> + <data key="sku" unique="suffix">downloadableproduct</data> + <data key="type_id">downloadable</data> + <data key="attribute_set_id">4</data> + <data key="name" unique="suffix">DownloadableProduct</data> + <data key="price">99.99</data> + <data key="quantity">50</data> + <data key="status">1</data> + <data key="is_in_stock">0</data> + <data key="urlKey" unique="suffix">downloadableproduct</data> + </entity> </entities> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminAddDefaultImageDownloadableProductTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminAddDefaultImageDownloadableProductTest.xml index c634a8426eac0..59aae6f5f28a3 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminAddDefaultImageDownloadableProductTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminAddDefaultImageDownloadableProductTest.xml @@ -27,7 +27,7 @@ <argument name="product" value="DownloadableProduct"/> </actionGroup> <magentoCLI stepKey="removeDownloadableDomain" command="downloadable:domains:remove static.magento.com"/> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> </after> <!-- Create product --> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminRemoveDefaultImageDownloadableProductTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminRemoveDefaultImageDownloadableProductTest.xml index 27d3d3d10a0b7..24917a8b332ed 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminRemoveDefaultImageDownloadableProductTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminRemoveDefaultImageDownloadableProductTest.xml @@ -24,7 +24,7 @@ </before> <after> <magentoCLI stepKey="removeDownloadableDomain" command="downloadable:domains:remove static.magento.com"/> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> </after> <!-- Create product --> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/VerifyOutOfStockDownloadableProductSamplesAreAccessibleTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/VerifyOutOfStockDownloadableProductSamplesAreAccessibleTest.xml new file mode 100644 index 0000000000000..337d4c7dd38b5 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/VerifyOutOfStockDownloadableProductSamplesAreAccessibleTest.xml @@ -0,0 +1,79 @@ +<?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="VerifyOutOfStockDownloadableProductSamplesAreAccessibleTest"> + <annotations> + <features value="Downloadable"/> + <stories value="Downloadable product"/> + <title value="Samples of Downloadable Products are accessible, if product is out of stock"/> + <description value="Samples of Downloadable Products are accessible, if product is out of stock"/> + <severity value="MAJOR"/> + <testCaseId value="MC-35639"/> + <group value="downloadable"/> + <group value="catalog"/> + </annotations> + <before> + <!-- Enable show out of stock product --> + <magentoCLI stepKey="enableShowOutOfStockProduct" command="config:set cataloginventory/options/show_out_of_stock 1"/> + + <!-- Add downloadable domains --> + <magentoCLI stepKey="addDownloadableDomain" command="downloadable:domains:add example.com static.magento.com"/> + + <!-- Create category --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + + <!-- Create downloadable product --> + <createData entity="DownloadableProductWithoutLinksOutOfStock" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!-- Add downloadable link --> + <createData entity="downloadableLink1" stepKey="addDownloadableLink"> + <requiredEntity createDataKey="createProduct"/> + </createData> + + <!-- Add downloadable sample --> + <createData entity="DownloadableSample" stepKey="addDownloadableSample"> + <requiredEntity createDataKey="createProduct"/> + </createData> + </before> + <after> + <!-- Disable show out of stock product --> + <magentoCLI stepKey="enableShowOutOfStockProduct" command="config:set cataloginventory/options/show_out_of_stock 0"/> + + <!-- Remove downloadable domains --> + <magentoCLI stepKey="removeDownloadableDomain" command="downloadable:domains:remove example.com static.magento.com"/> + + <!-- Delete product --> + <deleteData createDataKey="createProduct" stepKey="deleteDownloadableProduct"/> + + <!-- Delete category --> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <!-- Admin logout --> + <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> + </after> + + <!-- Open Downloadable product from precondition on Storefront --> + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openStorefrontProductPage"> + <argument name="productUrl" value="$createProduct.custom_attributes[url_key]$"/> + </actionGroup> + + <!-- Sample url is accessible --> + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="seeDownloadableSample"> + <argument name="selector" value="{{StorefrontDownloadableProductSection.downloadableSampleLabel(DownloadableSample.title)}}"/> + </actionGroup> + <click selector="{{StorefrontDownloadableProductSection.downloadableSampleLabel(DownloadableSample.title)}}" stepKey="clickDownloadableSample"/> + <switchToNextTab stepKey="switchToSampleTab"/> + <wait time="2" stepKey="waitToMakeSureThereWillBeNoRedirectToHomePage"/> + <seeInCurrentUrl url="downloadable/download/sample/sample_id/" stepKey="amOnSampleDownloadPage"/> + <closeTab stepKey="closeSampleTab"/> + </test> +</tests> diff --git a/app/code/Magento/Eav/Api/AttributeOptionManagementInterface.php b/app/code/Magento/Eav/Api/AttributeOptionManagementInterface.php index 84aefa700a52a..5359230c08c2a 100644 --- a/app/code/Magento/Eav/Api/AttributeOptionManagementInterface.php +++ b/app/code/Magento/Eav/Api/AttributeOptionManagementInterface.php @@ -15,8 +15,8 @@ interface AttributeOptionManagementInterface /** * Add option to attribute * - * @param string $attributeCode * @param int $entityType + * @param string $attributeCode * @param \Magento\Eav\Api\Data\AttributeOptionInterface $option * @throws \Magento\Framework\Exception\StateException * @throws \Magento\Framework\Exception\InputException diff --git a/app/code/Magento/Eav/Api/AttributeOptionUpdateInterface.php b/app/code/Magento/Eav/Api/AttributeOptionUpdateInterface.php new file mode 100644 index 0000000000000..fd755a08fdf9a --- /dev/null +++ b/app/code/Magento/Eav/Api/AttributeOptionUpdateInterface.php @@ -0,0 +1,35 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Eav\Api; + +/** + * Interface to update attribute option + * + * @api + */ +interface AttributeOptionUpdateInterface +{ + /** + * Update attribute option + * + * @param string $entityType + * @param string $attributeCode + * @param int $optionId + * @param \Magento\Eav\Api\Data\AttributeOptionInterface $option + * @return bool + * @throws \Magento\Framework\Exception\StateException + * @throws \Magento\Framework\Exception\InputException + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function update( + string $entityType, + string $attributeCode, + int $optionId, + \Magento\Eav\Api\Data\AttributeOptionInterface $option + ): bool; +} diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php b/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php index 0ea4c324fe5c9..e99f4395953ad 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php @@ -7,7 +7,12 @@ namespace Magento\Eav\Model\Entity\Attribute; +use Magento\Eav\Api\AttributeOptionManagementInterface; +use Magento\Eav\Api\AttributeOptionUpdateInterface; use Magento\Eav\Api\Data\AttributeInterface as EavAttributeInterface; +use Magento\Eav\Api\Data\AttributeOptionInterface; +use Magento\Eav\Model\AttributeRepository; +use Magento\Eav\Model\ResourceModel\Entity\Attribute; use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Exception\StateException; @@ -15,26 +20,26 @@ /** * Eav Option Management */ -class OptionManagement implements \Magento\Eav\Api\AttributeOptionManagementInterface +class OptionManagement implements AttributeOptionManagementInterface, AttributeOptionUpdateInterface { /** - * @var \Magento\Eav\Model\AttributeRepository + * @var AttributeRepository */ protected $attributeRepository; /** - * @var \Magento\Eav\Model\ResourceModel\Entity\Attribute + * @var Attribute */ protected $resourceModel; /** - * @param \Magento\Eav\Model\AttributeRepository $attributeRepository - * @param \Magento\Eav\Model\ResourceModel\Entity\Attribute $resourceModel + * @param AttributeRepository $attributeRepository + * @param Attribute $resourceModel * @codeCoverageIgnore */ public function __construct( - \Magento\Eav\Model\AttributeRepository $attributeRepository, - \Magento\Eav\Model\ResourceModel\Entity\Attribute $resourceModel + AttributeRepository $attributeRepository, + Attribute $resourceModel ) { $this->attributeRepository = $attributeRepository; $this->resourceModel = $resourceModel; @@ -45,45 +50,100 @@ public function __construct( * * @param int $entityType * @param string $attributeCode - * @param \Magento\Eav\Api\Data\AttributeOptionInterface $option + * @param AttributeOptionInterface $option * @return string * @throws InputException * @throws NoSuchEntityException * @throws StateException - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function add($entityType, $attributeCode, $option) { - if (empty($attributeCode)) { - throw new InputException(__('The attribute code is empty. Enter the code and try again.')); + $attribute = $this->loadAttribute($entityType, (string)$attributeCode); + + $label = trim($option->getLabel() ?: ''); + if (empty($label)) { + throw new InputException(__('The attribute option label is empty. Enter the value and try again.')); } - $attribute = $this->attributeRepository->get($entityType, $attributeCode); - if (!$attribute->usesSource()) { - throw new StateException(__('The "%1" attribute doesn\'t work with options.', $attributeCode)); + if ($attribute->getSource()->getOptionId($label) !== null) { + throw new InputException( + __( + 'Admin store attribute option label "%1" is already exists.', + $option->getLabel() + ) + ); } - $optionLabel = $option->getLabel(); - $optionId = $this->getOptionId($option); - $options = []; - $options['value'][$optionId][0] = $optionLabel; - $options['order'][$optionId] = $option->getSortOrder(); + $optionId = $this->getNewOptionId($option); + $this->saveOption($attribute, $option, $optionId); - if (is_array($option->getStoreLabels())) { - foreach ($option->getStoreLabels() as $label) { - $options['value'][$optionId][$label->getStoreId()] = $label->getLabel(); - } - } + return $this->retrieveOptionId($attribute, $option); + } - if (!$this->isAttributeOptionLabelExists($attribute, (string) $options['value'][$optionId][0])) { + /** + * @inheritdoc + */ + public function update( + string $entityType, + string $attributeCode, + int $optionId, + AttributeOptionInterface $option + ): bool { + $attribute = $this->loadAttribute($entityType, (string)$attributeCode); + if (empty($optionId)) { + throw new InputException(__('The option id is empty. Enter the value and try again.')); + } + $label = trim($option->getLabel() ?: ''); + if (empty($label)) { + throw new InputException(__('The attribute option label is empty. Enter the value and try again.')); + } + if ($attribute->getSource()->getOptionText($optionId) === false) { throw new InputException( __( - 'Admin store attribute option label "%1" is already exists.', - $options['value'][$optionId][0] + 'The \'%1\' attribute doesn\'t include an option id \'%2\'.', + $attribute->getAttributeCode(), + $optionId + ) + ); + } + $optionIdByLabel = $attribute->getSource()->getOptionId($label); + if (!empty($optionIdByLabel) && (int)$optionIdByLabel !== (int)$optionId) { + throw new InputException( + __( + 'Admin store attribute option label \'%1\' is already exists.', + $option->getLabel() ) ); } + $this->saveOption($attribute, $option, $optionId); + + return true; + } + + /** + * Save attribute option + * + * @param EavAttributeInterface $attribute + * @param AttributeOptionInterface $option + * @param int|string $optionId + * @return AttributeOptionInterface + * @throws StateException + */ + private function saveOption( + EavAttributeInterface $attribute, + AttributeOptionInterface $option, + $optionId + ): AttributeOptionInterface { + $optionLabel = trim($option->getLabel()); + $options = []; + $options['value'][$optionId][0] = $optionLabel; + $options['order'][$optionId] = $option->getSortOrder(); + if (is_array($option->getStoreLabels())) { + foreach ($option->getStoreLabels() as $label) { + $options['value'][$optionId][$label->getStoreId()] = $label->getLabel(); + } + } if ($option->getIsDefault()) { $attribute->setDefault([$optionId]); } @@ -91,29 +151,35 @@ public function add($entityType, $attributeCode, $option) $attribute->setOption($options); try { $this->resourceModel->save($attribute); - if ($optionLabel && $attribute->getAttributeCode()) { - $this->setOptionValue($option, $attribute, $optionLabel); - } } catch (\Exception $e) { - throw new StateException(__('The "%1" attribute can\'t be saved.', $attributeCode)); + throw new StateException(__('The "%1" attribute can\'t be saved.', $attribute->getAttributeCode())); } - return $this->getOptionId($option); + return $option; } /** - * @inheritdoc + * Get option id to create new option + * + * @param AttributeOptionInterface $option + * @return string */ - public function delete($entityType, $attributeCode, $optionId) + private function getNewOptionId(AttributeOptionInterface $option): string { - if (empty($attributeCode)) { - throw new InputException(__('The attribute code is empty. Enter the code and try again.')); + $optionId = trim($option->getValue() ?: ''); + if (empty($optionId)) { + $optionId = 'new_option'; } - $attribute = $this->attributeRepository->get($entityType, $attributeCode); - if (!$attribute->usesSource()) { - throw new StateException(__('The "%1" attribute has no option.', $attributeCode)); - } + return 'id_' . $optionId; + } + + /** + * @inheritdoc + */ + public function delete($entityType, $attributeCode, $optionId) + { + $attribute = $this->loadAttribute($entityType, $attributeCode); $this->validateOption($attribute, $optionId); $removalMarker = [ @@ -173,63 +239,55 @@ protected function validateOption($attribute, $optionId) } /** - * Returns option id + * Load attribute * - * @param \Magento\Eav\Api\Data\AttributeOptionInterface $option - * @return string + * @param string|int $entityType + * @param string $attributeCode + * @return EavAttributeInterface + * @throws InputException + * @throws NoSuchEntityException + * @throws StateException */ - private function getOptionId(\Magento\Eav\Api\Data\AttributeOptionInterface $option) : string + private function loadAttribute($entityType, string $attributeCode): EavAttributeInterface { - return 'id_' . ($option->getValue() ?: 'new_option'); + if (empty($attributeCode)) { + throw new InputException(__('The attribute code is empty. Enter the code and try again.')); + } + + $attribute = $this->attributeRepository->get($entityType, $attributeCode); + if (!$attribute->usesSource()) { + throw new StateException(__('The "%1" attribute doesn\'t work with options.', $attributeCode)); + } + + $attribute->setStoreId(0); + + return $attribute; } /** - * Set option value + * Retrieve option id * - * @param \Magento\Eav\Api\Data\AttributeOptionInterface $option * @param EavAttributeInterface $attribute - * @param string $optionLabel - * @return void + * @param AttributeOptionInterface $option + * @return string */ - private function setOptionValue( - \Magento\Eav\Api\Data\AttributeOptionInterface $option, + private function retrieveOptionId( EavAttributeInterface $attribute, - string $optionLabel - ) { - $optionId = $attribute->getSource()->getOptionId($optionLabel); + AttributeOptionInterface $option + ) : string { + $label = trim($option->getLabel()); + $optionId = $attribute->getSource()->getOptionId($label); if ($optionId) { - $option->setValue($attribute->getSource()->getOptionId($optionId)); + $option->setValue($optionId); } elseif (is_array($option->getStoreLabels())) { foreach ($option->getStoreLabels() as $label) { - if ($optionId = $attribute->getSource()->getOptionId($label->getLabel())) { - $option->setValue($attribute->getSource()->getOptionId($optionId)); + $optionId = $attribute->getSource()->getOptionId($label->getLabel()); + if ($optionId) { break; } } } - } - - /** - * Checks if the incoming attribute option label for admin store is already exists. - * - * @param EavAttributeInterface $attribute - * @param string $adminStoreLabel - * @param int $storeId - * @return bool - */ - private function isAttributeOptionLabelExists( - EavAttributeInterface $attribute, - string $adminStoreLabel, - int $storeId = 0 - ) :bool { - $attribute->setStoreId($storeId); - - foreach ($attribute->getSource()->toOptionArray() as $existingAttributeOption) { - if ($existingAttributeOption['label'] === $adminStoreLabel) { - return false; - } - } - return true; + return (string) $optionId; } } diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/OptionManagementTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/OptionManagementTest.php index 2084db08a1afb..b96b1e26696cd 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/OptionManagementTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/OptionManagementTest.php @@ -15,10 +15,17 @@ use Magento\Eav\Model\Entity\Attribute\Source\SourceInterface; use Magento\Eav\Model\Entity\Attribute\Source\Table as EavAttributeSource; use Magento\Eav\Model\ResourceModel\Entity\Attribute; -use Magento\Framework\Model\AbstractModel; -use PHPUnit\Framework\MockObject\MockObject; +use Magento\Framework\Exception\InputException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Exception\StateException; +use PHPUnit\Framework\MockObject\MockObject as MockObject; use PHPUnit\Framework\TestCase; +/** + * Tests for Eav Option Management functionality + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class OptionManagementTest extends TestCase { /** @@ -27,15 +34,18 @@ class OptionManagementTest extends TestCase protected $model; /** - * @var \PHPUnit\Framework\MockObject\MockObject + * @var MockObject|AttributeRepository */ protected $attributeRepositoryMock; /** - * @var \PHPUnit\Framework\MockObject\MockObject + * @var MockObject|Attribute */ protected $resourceModelMock; + /** + * @inheritdoc + */ protected function setUp(): void { $this->attributeRepositoryMock = $this->createMock(AttributeRepository::class); @@ -47,124 +57,189 @@ protected function setUp(): void ); } + /** + * Test to add attribute option + */ public function testAdd() { $entityType = 42; + $storeId = 4; $attributeCode = 'atrCde'; - $attributeMock = $this->getAttribute(); - $optionMock = $this->getAttributeOption(); - $labelMock = $this->getAttributeOptionLabel(); - $option = - ['value' => [ + $label = 'optionLabel'; + $storeLabel = 'labelLabel'; + $sortOder = 'optionSortOrder'; + $option = [ + 'value' => [ 'id_new_option' => [ - 0 => 'optionLabel', - 42 => 'labelLabel', + 0 => $label, + $storeId => $storeLabel, ], ], - 'order' => [ - 'id_new_option' => 'optionSortOrder', - ], - ]; + 'order' => [ + 'id_new_option' => $sortOder, + ] + ]; + $newOptionId = 10; - $this->attributeRepositoryMock->expects($this->once())->method('get')->with($entityType, $attributeCode) - ->willReturn($attributeMock); - $attributeMock->expects($this->once())->method('usesSource')->willReturn(true); - $optionMock->expects($this->once())->method('getLabel')->willReturn('optionLabel'); - $optionMock->expects($this->once())->method('getSortOrder')->willReturn('optionSortOrder'); - $optionMock->expects($this->exactly(2))->method('getStoreLabels')->willReturn([$labelMock]); - $labelMock->expects($this->once())->method('getStoreId')->willReturn(42); - $labelMock->expects($this->once())->method('getLabel')->willReturn('labelLabel'); - $optionMock->expects($this->once())->method('getIsDefault')->willReturn(true); + $optionMock = $this->getAttributeOption(); + $labelMock = $this->getAttributeOptionLabel(); + /** @var SourceInterface|MockObject $sourceMock */ + $sourceMock = $this->createMock(EavAttributeSource::class); + $sourceMock->method('getOptionId') + ->willReturnMap( + [ + [$label, null], + [$storeLabel, $newOptionId], + [$newOptionId, $newOptionId], + ] + ); + + /** @var EavAbstractAttribute|MockObject $attributeMock */ + $attributeMock = $this->getMockBuilder(EavAbstractAttribute::class) + ->disableOriginalConstructor() + ->addMethods(['setDefault', 'setOption']) + ->onlyMethods(['usesSource', 'getSource']) + ->getMock(); + $attributeMock->method('usesSource')->willReturn(true); $attributeMock->expects($this->once())->method('setDefault')->with(['id_new_option']); $attributeMock->expects($this->once())->method('setOption')->with($option); + $attributeMock->method('getSource')->willReturn($sourceMock); + $this->attributeRepositoryMock->expects($this->once()) + ->method('get') + ->with($entityType, $attributeCode) + ->willReturn($attributeMock); + $optionMock->method('getLabel')->willReturn($label); + $optionMock->method('getSortOrder')->willReturn($sortOder); + $optionMock->method('getIsDefault')->willReturn(true); + $optionMock->method('getStoreLabels')->willReturn([$labelMock]); + $labelMock->method('getStoreId')->willReturn($storeId); + $labelMock->method('getLabel')->willReturn($storeLabel); $this->resourceModelMock->expects($this->once())->method('save')->with($attributeMock); - $this->assertEquals('id_new_option', $this->model->add($entityType, $attributeCode, $optionMock)); + $this->assertEquals( + $newOptionId, + $this->model->add($entityType, $attributeCode, $optionMock) + ); } + /** + * Test to add attribute option with empty attribute code + */ public function testAddWithEmptyAttributeCode() { - $this->expectException('Magento\Framework\Exception\InputException'); - $this->expectExceptionMessage('The attribute code is empty. Enter the code and try again.'); + $this->expectExceptionMessage("The attribute code is empty. Enter the code and try again."); + $this->expectException(InputException::class); $entityType = 42; $attributeCode = ''; $optionMock = $this->getAttributeOption(); $this->resourceModelMock->expects($this->never())->method('save'); $this->model->add($entityType, $attributeCode, $optionMock); } - + /** + * Test to add attribute option without use source + */ public function testAddWithWrongOptions() { - $this->expectException('Magento\Framework\Exception\StateException'); $this->expectExceptionMessage('The "testAttribute" attribute doesn\'t work with options.'); + $this->expectException(StateException::class); $entityType = 42; $attributeCode = 'testAttribute'; - $attributeMock = $this->getAttribute(); + /** @var EavAbstractAttribute|MockObject $attributeMock */ + $attributeMock = $this->getMockBuilder(EavAbstractAttribute::class) + ->disableOriginalConstructor() + ->addMethods(['setDefault', 'setOption', 'setStoreId']) + ->onlyMethods(['usesSource', 'getSource']) + ->getMock(); $optionMock = $this->getAttributeOption(); - $this->attributeRepositoryMock->expects($this->once())->method('get')->with($entityType, $attributeCode) + $this->attributeRepositoryMock->expects($this->once()) + ->method('get') + ->with($entityType, $attributeCode) ->willReturn($attributeMock); $attributeMock->expects($this->once())->method('usesSource')->willReturn(false); $this->resourceModelMock->expects($this->never())->method('save'); $this->model->add($entityType, $attributeCode, $optionMock); } + /** + * Test to add attribute option wit save exception + */ public function testAddWithCannotSaveException() { - $this->expectException('Magento\Framework\Exception\StateException'); + $this->expectException(StateException::class); $this->expectExceptionMessage('The "atrCde" attribute can\'t be saved.'); + $entityType = 42; + $storeId = 4; $attributeCode = 'atrCde'; - $optionMock = $this->getAttributeOption(); - $attributeMock = $this->getAttribute(); - $labelMock = $this->getAttributeOptionLabel(); - $option = - ['value' => [ + $label = 'optionLabel'; + $storeLabel = 'labelLabel'; + $sortOder = 'optionSortOrder'; + $option = [ + 'value' => [ 'id_new_option' => [ - 0 => 'optionLabel', - 42 => 'labelLabel', + 0 => $label, + $storeId => $storeLabel, ], ], - 'order' => [ - 'id_new_option' => 'optionSortOrder', - ], - ]; + 'order' => [ + 'id_new_option' => $sortOder, + ] + ]; - $this->attributeRepositoryMock->expects($this->once())->method('get')->with($entityType, $attributeCode) - ->willReturn($attributeMock); - $attributeMock->expects($this->once())->method('usesSource')->willReturn(true); - $optionMock->expects($this->once())->method('getLabel')->willReturn('optionLabel'); - $optionMock->expects($this->once())->method('getSortOrder')->willReturn('optionSortOrder'); - $optionMock->expects($this->exactly(2))->method('getStoreLabels')->willReturn([$labelMock]); - $labelMock->expects($this->once())->method('getStoreId')->willReturn(42); - $labelMock->expects($this->once())->method('getLabel')->willReturn('labelLabel'); - $optionMock->expects($this->once())->method('getIsDefault')->willReturn(true); + $optionMock = $this->getAttributeOption(); + $labelMock = $this->getAttributeOptionLabel(); + /** @var SourceInterface|MockObject $sourceMock */ + $sourceMock = $this->createMock(EavAttributeSource::class); + /** @var EavAbstractAttribute|MockObject $attributeMock */ + $attributeMock = $this->getMockBuilder(EavAbstractAttribute::class) + ->disableOriginalConstructor() + ->addMethods(['setDefault', 'setOption', 'setStoreId']) + ->onlyMethods(['usesSource', 'getSource', 'getAttributeCode']) + ->getMock(); + $attributeMock->method('usesSource')->willReturn(true); $attributeMock->expects($this->once())->method('setDefault')->with(['id_new_option']); $attributeMock->expects($this->once())->method('setOption')->with($option); + $attributeMock->method('getSource')->willReturn($sourceMock); + $attributeMock->method('getAttributeCode')->willReturn($attributeCode); + $this->attributeRepositoryMock->expects($this->once()) + ->method('get') + ->with($entityType, $attributeCode) + ->willReturn($attributeMock); + $optionMock->method('getLabel')->willReturn($label); + $optionMock->method('getSortOrder')->willReturn($sortOder); + $optionMock->method('getIsDefault')->willReturn(true); + $optionMock->method('getStoreLabels')->willReturn([$labelMock]); + $labelMock->method('getStoreId')->willReturn($storeId); + $labelMock->method('getLabel')->willReturn($storeLabel); + $this->resourceModelMock->expects($this->once())->method('save')->with($attributeMock) ->willThrowException(new \Exception()); $this->model->add($entityType, $attributeCode, $optionMock); } + /** + * Test to delete attribute option + */ public function testDelete() { $entityType = 42; $attributeCode = 'atrCode'; $optionId = 'option'; - $attributeMock = $this->getMockForAbstractClass( - AbstractModel::class, - [], - '', - false, - false, - true, - ['usesSource', 'getSource', 'getId', 'getOptionText', 'addData'] - ); + + /** @var EavAbstractAttribute|MockObject $attributeMock */ + $attributeMock = $this->getMockBuilder(EavAbstractAttribute::class) + ->disableOriginalConstructor() + ->addMethods(['getOptionText']) + ->onlyMethods(['usesSource', 'getSource', 'getId', 'addData']) + ->getMock(); $removalMarker = [ 'option' => [ 'value' => [$optionId => []], 'delete' => [$optionId => '1'], ], ]; - $this->attributeRepositoryMock->expects($this->once())->method('get')->with($entityType, $attributeCode) + $this->attributeRepositoryMock->expects($this->once()) + ->method('get') + ->with($entityType, $attributeCode) ->willReturn($attributeMock); $attributeMock->expects($this->once())->method('usesSource')->willReturn(true); $attributeMock->expects($this->once())->method('getSource')->willReturnSelf(); @@ -175,22 +250,23 @@ public function testDelete() $this->assertTrue($this->model->delete($entityType, $attributeCode, $optionId)); } + /** + * Test to delete attribute option with save exception + */ public function testDeleteWithCannotSaveException() { - $this->expectException('Magento\Framework\Exception\StateException'); $this->expectExceptionMessage('The "atrCode" attribute can\'t be saved.'); + $this->expectException(StateException::class); + $entityType = 42; $attributeCode = 'atrCode'; $optionId = 'option'; - $attributeMock = $this->getMockForAbstractClass( - AbstractModel::class, - [], - '', - false, - false, - true, - ['usesSource', 'getSource', 'getId', 'getOptionText', 'addData'] - ); + /** @var EavAbstractAttribute|MockObject $attributeMock */ + $attributeMock = $this->getMockBuilder(EavAbstractAttribute::class) + ->disableOriginalConstructor() + ->addMethods(['getOptionText']) + ->onlyMethods(['usesSource', 'getSource', 'getId', 'addData']) + ->getMock(); $removalMarker = [ 'option' => [ 'value' => [$optionId => []], @@ -204,28 +280,29 @@ public function testDeleteWithCannotSaveException() $attributeMock->expects($this->once())->method('getOptionText')->willReturn('optionText'); $attributeMock->expects($this->never())->method('getId'); $attributeMock->expects($this->once())->method('addData')->with($removalMarker); - $this->resourceModelMock->expects($this->once())->method('save')->with($attributeMock) + $this->resourceModelMock->expects($this->once()) + ->method('save') + ->with($attributeMock) ->willThrowException(new \Exception()); $this->model->delete($entityType, $attributeCode, $optionId); } + /** + * Test to delete with wrong option + */ public function testDeleteWithWrongOption() { - $this->expectException('Magento\Framework\Exception\NoSuchEntityException'); $this->expectExceptionMessage('The "atrCode" attribute doesn\'t include an option with "option" ID.'); + $this->expectException(NoSuchEntityException::class); + $entityType = 42; $attributeCode = 'atrCode'; $optionId = 'option'; - $attributeMock = $this->getMockForAbstractClass( - AbstractModel::class, - [], - '', - false, - false, - true, - ['usesSource', 'getSource', 'getAttributeCode'] - ); - $this->attributeRepositoryMock->expects($this->once())->method('get')->with($entityType, $attributeCode) + /** @var EavAbstractAttribute|MockObject $attributeMock */ + $attributeMock = $this->createMock(EavAbstractAttribute::class); + $this->attributeRepositoryMock->expects($this->once()) + ->method('get') + ->with($entityType, $attributeCode) ->willReturn($attributeMock); $sourceMock = $this->getMockForAbstractClass(SourceInterface::class); $sourceMock->expects($this->once())->method('getOptionText')->willReturn(false); @@ -236,33 +313,40 @@ public function testDeleteWithWrongOption() $this->model->delete($entityType, $attributeCode, $optionId); } + /** + * Test to delete with absent option + */ public function testDeleteWithAbsentOption() { - $this->expectException('Magento\Framework\Exception\StateException'); - $this->expectExceptionMessage('The "atrCode" attribute has no option.'); + $this->expectExceptionMessage('The "atrCode" attribute doesn\'t work with options.'); + $this->expectException(StateException::class); + $entityType = 42; $attributeCode = 'atrCode'; $optionId = 'option'; - $attributeMock = $this->getMockForAbstractClass( - AbstractModel::class, - [], - '', - false, - false, - true, - ['usesSource', 'getSource', 'getId', 'getOptionText', 'addData'] - ); - $this->attributeRepositoryMock->expects($this->once())->method('get')->with($entityType, $attributeCode) + /** @var EavAbstractAttribute|MockObject $attributeMock */ + $attributeMock = $this->getMockBuilder(EavAbstractAttribute::class) + ->disableOriginalConstructor() + ->addMethods(['getOptionText']) + ->onlyMethods(['usesSource', 'getSource', 'getId', 'addData']) + ->getMock(); + $this->attributeRepositoryMock->expects($this->once()) + ->method('get') + ->with($entityType, $attributeCode) ->willReturn($attributeMock); $attributeMock->expects($this->once())->method('usesSource')->willReturn(false); $this->resourceModelMock->expects($this->never())->method('save'); $this->model->delete($entityType, $attributeCode, $optionId); } + /** + * Test to delete with empty attribute code + */ public function testDeleteWithEmptyAttributeCode() { - $this->expectException('Magento\Framework\Exception\InputException'); - $this->expectExceptionMessage('The attribute code is empty. Enter the code and try again.'); + $this->expectExceptionMessage("The attribute code is empty. Enter the code and try again."); + $this->expectException(InputException::class); + $entityType = 42; $attributeCode = ''; $optionId = 'option'; @@ -270,86 +354,56 @@ public function testDeleteWithEmptyAttributeCode() $this->model->delete($entityType, $attributeCode, $optionId); } + /** + * Test to get items + */ public function testGetItems() { $entityType = 42; $attributeCode = 'atrCode'; - $attributeMock = $this->getMockForAbstractClass( - AbstractModel::class, - [], - '', - false, - false, - true, - ['getOptions'] - ); - $optionsMock = [$this->getMockForAbstractClass(EavAttributeOptionInterface::class)]; - $this->attributeRepositoryMock->expects($this->once())->method('get')->with($entityType, $attributeCode) + $attributeMock = $this->createMock(EavAbstractAttribute::class); + $optionsMock = [$this->createMock(EavAttributeOptionInterface::class)]; + $this->attributeRepositoryMock->expects($this->once()) + ->method('get') + ->with($entityType, $attributeCode) ->willReturn($attributeMock); $attributeMock->expects($this->once())->method('getOptions')->willReturn($optionsMock); $this->assertEquals($optionsMock, $this->model->getItems($entityType, $attributeCode)); } + /** + * Test to get items with load exception + */ public function testGetItemsWithCannotLoadException() { - $this->expectException('Magento\Framework\Exception\StateException'); $this->expectExceptionMessage('The options for "atrCode" attribute can\'t be loaded.'); + $this->expectException(StateException::class); $entityType = 42; $attributeCode = 'atrCode'; - $attributeMock = $this->getMockForAbstractClass( - AbstractModel::class, - [], - '', - false, - false, - true, - ['getOptions'] - ); - $this->attributeRepositoryMock->expects($this->once())->method('get')->with($entityType, $attributeCode) + $attributeMock = $this->createMock(EavAbstractAttribute::class); + $this->attributeRepositoryMock->expects($this->once()) + ->method('get') + ->with($entityType, $attributeCode) ->willReturn($attributeMock); - $attributeMock->expects($this->once())->method('getOptions')->willThrowException(new \Exception()); + $attributeMock->expects($this->once()) + ->method('getOptions') + ->willThrowException(new \Exception()); $this->model->getItems($entityType, $attributeCode); } + /** + * Test to get items with empty attribute code + */ public function testGetItemsWithEmptyAttributeCode() { - $this->expectException('Magento\Framework\Exception\InputException'); - $this->expectExceptionMessage('The attribute code is empty. Enter the code and try again.'); + $this->expectExceptionMessage("The attribute code is empty. Enter the code and try again."); + $this->expectException(InputException::class); + $entityType = 42; $attributeCode = ''; $this->model->getItems($entityType, $attributeCode); } - /** - * Returns attribute entity mock. - * - * @param array $attributeOptions attribute options for return - * @return MockObject|EavAbstractAttribute - */ - private function getAttribute(array $attributeOptions = []) - { - $attribute = $this->getMockBuilder(EavAbstractAttribute::class) - ->disableOriginalConstructor() - ->setMethods( - [ - 'usesSource', - 'setDefault', - 'setOption', - 'setStoreId', - 'getSource', - ] - ) - ->getMock(); - $source = $this->getMockBuilder(EavAttributeSource::class) - ->disableOriginalConstructor() - ->getMock(); - - $attribute->method('getSource')->willReturn($source); - $source->method('toOptionArray')->willReturn($attributeOptions); - - return $attribute; - } - /** * Return attribute option entity mock. * diff --git a/app/code/Magento/Eav/etc/di.xml b/app/code/Magento/Eav/etc/di.xml index 21f248f1b1094..4f5d7d7112961 100644 --- a/app/code/Magento/Eav/etc/di.xml +++ b/app/code/Magento/Eav/etc/di.xml @@ -20,6 +20,7 @@ <preference for="Magento\Eav\Api\Data\AttributeFrontendLabelInterface" type="Magento\Eav\Model\Entity\Attribute\FrontendLabel" /> <preference for="Magento\Eav\Api\Data\AttributeOptionInterface" type="Magento\Eav\Model\Entity\Attribute\Option" /> <preference for="Magento\Eav\Api\AttributeOptionManagementInterface" type="Magento\Eav\Model\Entity\Attribute\OptionManagement" /> + <preference for="Magento\Eav\Api\AttributeOptionUpdateInterface" type="Magento\Eav\Model\Entity\Attribute\OptionManagement" /> <preference for="Magento\Eav\Api\Data\AttributeOptionLabelInterface" type="Magento\Eav\Model\Entity\Attribute\OptionLabel" /> <preference for="Magento\Eav\Api\Data\AttributeValidationRuleInterface" type="Magento\Eav\Model\Entity\Attribute\ValidationRule" /> <preference for="Magento\Eav\Api\Data\AttributeSearchResultsInterface" type="Magento\Eav\Model\AttributeSearchResults" /> diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Client/Elasticsearch.php b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Client/Elasticsearch.php index 8d8787a5eff72..1ea2b6958734c 100644 --- a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Client/Elasticsearch.php +++ b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Client/Elasticsearch.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Elasticsearch\Elasticsearch5\Model\Client; use Magento\Framework\Exception\LocalizedException; @@ -11,7 +12,7 @@ /** * Elasticsearch client * - * @deprecated the Elasticsearch 5 doesn't supported due to EOL + * @deprecated 100.3.5 the Elasticsearch 5 doesn't supported due to EOL */ class Elasticsearch implements ClientInterface { @@ -48,8 +49,10 @@ public function __construct( $options = [], $elasticsearchClient = null ) { - if (empty($options['hostname']) || ((!empty($options['enableAuth']) && - ($options['enableAuth'] == 1)) && (empty($options['username']) || empty($options['password'])))) { + if (empty($options['hostname']) + || ((!empty($options['enableAuth']) && ($options['enableAuth'] == 1)) + && (empty($options['username']) || empty($options['password']))) + ) { throw new LocalizedException( __('The search failed because of a search engine misconfiguration.') ); @@ -302,7 +305,15 @@ public function addFieldsMapping(array $fields, $index, $entityType) ] ), ], - ] + ], + [ + 'integer_mapping' => [ + 'match_mapping_type' => 'long', + 'mapping' => [ + 'type' => 'integer', + ], + ], + ], ], ], ], @@ -323,7 +334,6 @@ public function addFieldsMapping(array $fields, $index, $entityType) */ private function prepareFieldInfo($fieldInfo) { - if (strcmp($this->getServerVersion(), '5') < 0) { if ($fieldInfo['type'] == 'keyword') { $fieldInfo['type'] = 'string'; diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php index 245e4d494afe1..9fa001097df87 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Elasticsearch\Model\Adapter\BatchDataMapper; use Magento\CatalogSearch\Model\Indexer\Fulltext\Action\DataProvider; @@ -74,7 +75,7 @@ class ProductDataMapper implements BatchDataMapperInterface private $attributesExcludedFromMerge = [ 'status', 'visibility', - 'tax_class_id' + 'tax_class_id', ]; /** @@ -85,8 +86,11 @@ class ProductDataMapper implements BatchDataMapperInterface ]; /** - * Construction for DocumentDataMapper - * + * @var string[] + */ + private $filterableAttributeTypes; + + /** * @param Builder $builder * @param FieldMapperInterface $fieldMapper * @param DateFieldType $dateFieldType @@ -94,6 +98,7 @@ class ProductDataMapper implements BatchDataMapperInterface * @param DataProvider $dataProvider * @param array $excludedAttributes * @param array $sortableAttributesValuesToImplode + * @param array $filterableAttributeTypes */ public function __construct( Builder $builder, @@ -102,7 +107,8 @@ public function __construct( AdditionalFieldsProviderInterface $additionalFieldsProvider, DataProvider $dataProvider, array $excludedAttributes = [], - array $sortableAttributesValuesToImplode = [] + array $sortableAttributesValuesToImplode = [], + array $filterableAttributeTypes = [] ) { $this->builder = $builder; $this->fieldMapper = $fieldMapper; @@ -115,6 +121,7 @@ public function __construct( $this->additionalFieldsProvider = $additionalFieldsProvider; $this->dataProvider = $dataProvider; $this->attributeOptionsCache = []; + $this->filterableAttributeTypes = $filterableAttributeTypes; } /** @@ -212,7 +219,7 @@ private function convertAttribute(Attribute $attribute, array $attributeValues, if ($retrievedValue !== null) { $productAttributes[$attribute->getAttributeCode()] = $retrievedValue; - if ($attribute->getIsSearchable()) { + if ($this->isAttributeLabelsShouldBeMapped($attribute)) { $attributeLabels = $this->getValuesLabels($attribute, $attributeValues, $storeId); $retrievedLabel = $this->retrieveFieldValue($attributeLabels); if ($retrievedLabel) { @@ -224,6 +231,26 @@ private function convertAttribute(Attribute $attribute, array $attributeValues, return $productAttributes; } + /** + * Check if an attribute has one of the next storefront properties enabled for mapping labels: + * - "Use in Search" (is_searchable) + * - "Visible in Advanced Search" (is_visible_in_advanced_search) + * - "Use in Layered Navigation" (is_filterable) + * - "Use in Search Results Layered Navigation" (is_filterable_in_search) + * + * @param Attribute $attribute + * @return bool + */ + private function isAttributeLabelsShouldBeMapped(Attribute $attribute): bool + { + return ( + $attribute->getIsSearchable() + || $attribute->getIsVisibleInAdvancedSearch() + || $attribute->getIsFilterable() + || $attribute->getIsFilterableInSearch() + ); + } + /** * Prepare attribute values. * @@ -249,6 +276,15 @@ private function prepareAttributeValues( $attributeValues = $this->prepareMultiselectValues($attributeValues); } + if (in_array($attribute->getFrontendInput(), $this->filterableAttributeTypes)) { + $attributeValues = array_map( + function (string $valueId) { + return (int)$valueId; + }, + $attributeValues + ); + } + if ($this->isAttributeDate($attribute)) { foreach ($attributeValues as $key => $attributeValue) { $attributeValues[$key] = $this->dateFieldType->formatDate($storeId, $attributeValue); diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php index 575a64dc43abd..fc146e801124a 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php @@ -340,7 +340,7 @@ public function testAddFieldsMapping() 'match_mapping_type' => 'string', 'mapping' => [ 'type' => 'integer', - 'index' => true + 'index' => true, ], ], ], @@ -354,6 +354,14 @@ public function testAddFieldsMapping() ], ], ], + [ + 'integer_mapping' => [ + 'match_mapping_type' => 'long', + 'mapping' => [ + 'type' => 'integer', + ], + ], + ], ], ], ], @@ -424,7 +432,15 @@ public function testAddFieldsMappingFailure() 'index' => true, ], ], - ] + ], + [ + 'integer_mapping' => [ + 'match_mapping_type' => 'long', + 'mapping' => [ + 'type' => 'integer', + ], + ], + ], ], ], ], diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/BatchDataMapper/ProductDataMapperTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/BatchDataMapper/ProductDataMapperTest.php index 2c87549da6075..9f1b59b1bfc81 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/BatchDataMapper/ProductDataMapperTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/BatchDataMapper/ProductDataMapperTest.php @@ -21,6 +21,8 @@ use PHPUnit\Framework\TestCase; /** + * Unit tests for \Magento\Elasticsearch\Model\Adapter\BatchDataMapper\ProductDataMapper class. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class ProductDataMapperTest extends TestCase @@ -56,12 +58,12 @@ class ProductDataMapperTest extends TestCase private $additionalFieldsProvider; /** - * @var MockObject + * @var DataProvider|MockObject */ private $dataProvider; /** - * Set up test environment. + * @inheritdoc */ protected function setUp(): void { @@ -71,6 +73,11 @@ protected function setUp(): void $this->attribute = $this->createMock(Attribute::class); $this->additionalFieldsProvider = $this->getMockForAbstractClass(AdditionalFieldsProviderInterface::class); $this->dateFieldTypeMock = $this->createMock(Date::class); + $filterableAttributeTypes = [ + 'boolean' => 'boolean', + 'multiselect' => 'multiselect', + 'select' => 'select', + ]; $objectManager = new ObjectManagerHelper($this); $this->model = $objectManager->getObject( @@ -81,6 +88,7 @@ protected function setUp(): void 'dateFieldType' => $this->dateFieldTypeMock, 'dataProvider' => $this->dataProvider, 'additionalFieldsProvider' => $this->additionalFieldsProvider, + 'filterableAttributeTypes' => $filterableAttributeTypes, ] ); } @@ -159,8 +167,8 @@ public function testGetMap(int $productId, array $attributeData, $attributeValue $productId => [$attributeId => $attributeValue], ]; $documents = $this->model->map($documentData, $storeId, $context); - $returnAttributeData['store_id'] = $storeId; - $this->assertEquals($returnAttributeData, $documents[$productId]); + $returnAttributeData = ['store_id' => $storeId] + $returnAttributeData; + $this->assertSame($returnAttributeData, $documents[$productId]); } /** @@ -305,8 +313,8 @@ public static function mapProvider(): array ['value' => '2', 'label' => 'Disabled'], ], ], - [10 => '1', 11 => '2'], - ['status' => '1'], + [10 => '1', 11 => '2'], + ['status' => 1], ], 'select without options' => [ 10, @@ -318,7 +326,7 @@ public static function mapProvider(): array 'options' => [], ], '44', - ['color' => '44'], + ['color' => 44], ], 'unsearchable select with options' => [ 10, @@ -333,7 +341,7 @@ public static function mapProvider(): array ], ], '44', - ['color' => '44'], + ['color' => 44], ], 'searchable select with options' => [ 10, @@ -348,7 +356,7 @@ public static function mapProvider(): array ], ], '44', - ['color' => '44', 'color_value' => 'red'], + ['color' => 44, 'color_value' => 'red'], ], 'composite select with options' => [ 10, @@ -363,7 +371,7 @@ public static function mapProvider(): array ], ], [10 => '44', 11 => '45'], - ['color' => ['44', '45'], 'color_value' => ['red', 'black']], + ['color' => [44, 45], 'color_value' => ['red', 'black']], ], 'multiselect without options' => [ 10, @@ -430,10 +438,10 @@ public static function mapProvider(): array 'backend_type' => 'int', 'frontend_input' => 'int', 'is_searchable' => false, - 'options' => [] + 'options' => [], ], 15, - [] + [], ], ]; } diff --git a/app/code/Magento/Elasticsearch/etc/di.xml b/app/code/Magento/Elasticsearch/etc/di.xml index 633e67dfe698e..6c1a771958081 100644 --- a/app/code/Magento/Elasticsearch/etc/di.xml +++ b/app/code/Magento/Elasticsearch/etc/di.xml @@ -153,6 +153,11 @@ <type name="Magento\Elasticsearch\Model\Adapter\BatchDataMapper\ProductDataMapper"> <arguments> <argument name="additionalFieldsProvider" xsi:type="object">additionalFieldsProviderForElasticsearch</argument> + <argument name="filterableAttributeTypes" xsi:type="array"> + <item name="boolean" xsi:type="string">boolean</item> + <item name="multiselect" xsi:type="string">multiselect</item> + <item name="select" xsi:type="string">select</item> + </argument> </arguments> </type> <preference for="Magento\Elasticsearch\Model\Adapter\BatchDataMapperInterface" type="Magento\Elasticsearch\Model\Adapter\BatchDataMapper\DataMapperResolver" /> diff --git a/app/code/Magento/Elasticsearch6/Model/Client/Elasticsearch.php b/app/code/Magento/Elasticsearch6/Model/Client/Elasticsearch.php index 0571b075aff28..e552d0067df84 100644 --- a/app/code/Magento/Elasticsearch6/Model/Client/Elasticsearch.php +++ b/app/code/Magento/Elasticsearch6/Model/Client/Elasticsearch.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Elasticsearch6\Model\Client; use Magento\AdvancedSearch\Model\Client\ClientInterface; @@ -48,8 +49,10 @@ public function __construct( $elasticsearchClient = null, $fieldsMappingPreprocessors = [] ) { - if (empty($options['hostname']) || ((!empty($options['enableAuth']) && - ($options['enableAuth'] == 1)) && (empty($options['username']) || empty($options['password'])))) { + if (empty($options['hostname']) + || ((!empty($options['enableAuth']) && ($options['enableAuth'] == 1)) + && (empty($options['username']) || empty($options['password']))) + ) { throw new LocalizedException( __('The search failed because of a search engine misconfiguration.') ); @@ -303,7 +306,15 @@ public function addFieldsMapping(array $fields, $index, $entityType) 'mapping' => [ 'type' => 'text', 'index' => true, - 'copy_to' => '_search' + 'copy_to' => '_search', + ], + ], + ], + [ + 'integer_mapping' => [ + 'match_mapping_type' => 'long', + 'mapping' => [ + 'type' => 'integer', ], ], ], diff --git a/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontElasticsearchSearchInvalidValueTest.xml b/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontElasticsearchSearchInvalidValueTest.xml index e173090bfa318..15aa599b93956 100644 --- a/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontElasticsearchSearchInvalidValueTest.xml +++ b/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontElasticsearchSearchInvalidValueTest.xml @@ -41,11 +41,9 @@ <argument name="productAttributeCode" value="{{textProductAttribute.attribute_code}}"/> </actionGroup> <actionGroup ref="AssertProductAttributeRemovedSuccessfullyActionGroup" stepKey="deleteProductAttributeSuccess"/> - <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="navigateToProductAttributeGrid"/> - <waitForPageLoad stepKey="waitForAttributePageLoad"/> + <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="navigateToProductAttributeGrid"/> <click selector="{{AdminProductAttributeGridSection.ResetFilter}}" stepKey="resetFiltersOnGrid"/> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="goToProductCatalog"/> - <waitForPageLoad stepKey="waitForProductIndexPage"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="goToProductCatalog"/> <actionGroup ref="DeleteProductsIfTheyExistActionGroup" stepKey="deleteProduct"/> <actionGroup ref="ResetProductGridToDefaultViewActionGroup" stepKey="resetFiltersIfExist"/> <magentoCLI command="indexer:reindex catalogsearch_fulltext" stepKey="reindex"/> @@ -53,7 +51,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> <!--Create new searchable product attribute--> - <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="goToProductAttributes"/> + <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="goToProductAttributes"/> <actionGroup ref="AdminCreateSearchableProductAttributeActionGroup" stepKey="createAttribute"> <argument name="attribute" value="textProductAttribute"/> </actionGroup> diff --git a/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php b/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php index 2a7fa2ce8114a..427e630ac2099 100644 --- a/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php +++ b/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php @@ -461,10 +461,18 @@ public function testAddFieldsMapping() 'mapping' => [ 'type' => 'text', 'index' => true, - 'copy_to' => '_search' + 'copy_to' => '_search', ], ], - ] + ], + [ + 'integer_mapping' => [ + 'match_mapping_type' => 'long', + 'mapping' => [ + 'type' => 'integer', + ], + ], + ], ], ], ], @@ -531,10 +539,18 @@ public function testAddFieldsMappingFailure() 'mapping' => [ 'type' => 'text', 'index' => true, - 'copy_to' => '_search' + 'copy_to' => '_search', ], ], - ] + ], + [ + 'integer_mapping' => [ + 'match_mapping_type' => 'long', + 'mapping' => [ + 'type' => 'integer', + ], + ], + ], ], ], ], diff --git a/app/code/Magento/Elasticsearch7/Model/Client/Elasticsearch.php b/app/code/Magento/Elasticsearch7/Model/Client/Elasticsearch.php index 4b318f987abfe..a16a70b1cd702 100644 --- a/app/code/Magento/Elasticsearch7/Model/Client/Elasticsearch.php +++ b/app/code/Magento/Elasticsearch7/Model/Client/Elasticsearch.php @@ -51,8 +51,10 @@ public function __construct( $elasticsearchClient = null, $fieldsMappingPreprocessors = [] ) { - if (empty($options['hostname']) || ((!empty($options['enableAuth']) && - ($options['enableAuth'] == 1)) && (empty($options['username']) || empty($options['password'])))) { + if (empty($options['hostname']) + || ((!empty($options['enableAuth']) && ($options['enableAuth'] == 1)) + && (empty($options['username']) || empty($options['password']))) + ) { throw new LocalizedException( __('The search failed because of a search engine misconfiguration.') ); @@ -71,7 +73,7 @@ public function __construct( * @param array $query * @return array */ - public function suggest(array $query) : array + public function suggest(array $query): array { return $this->getElasticsearchClient()->suggest($query); } @@ -96,7 +98,7 @@ private function getElasticsearchClient(): \Elasticsearch\Client * * @return bool */ - public function ping() : bool + public function ping(): bool { if ($this->pingResult === null) { $this->pingResult = $this->getElasticsearchClient() @@ -111,7 +113,7 @@ public function ping() : bool * * @return bool */ - public function testConnection() : bool + public function testConnection(): bool { return $this->ping(); } @@ -122,7 +124,7 @@ public function testConnection() : bool * @param array $options * @return array */ - private function buildESConfig(array $options = []) : array + private function buildESConfig(array $options = []): array { $hostname = preg_replace('/http[s]?:\/\//i', '', $options['hostname']); // @codingStandardsIgnoreStart @@ -194,12 +196,13 @@ public function deleteIndex(string $index) * @param string $index * @return bool */ - public function isEmptyIndex(string $index) : bool + public function isEmptyIndex(string $index): bool { $stats = $this->getElasticsearchClient()->indices()->stats(['index' => $index, 'metric' => 'docs']); - if ($stats['indices'][$index]['primaries']['docs']['count'] === 0) { + if ($stats['indices'][$index]['primaries']['docs']['count'] === 0) { return true; } + return false; } @@ -234,7 +237,7 @@ public function updateAlias(string $alias, string $newIndex, string $oldIndex = * @param string $index * @return bool */ - public function indexExists(string $index) : bool + public function indexExists(string $index): bool { return $this->getElasticsearchClient()->indices()->exists(['index' => $index]); } @@ -246,12 +249,13 @@ public function indexExists(string $index) : bool * @param string $index * @return bool */ - public function existsAlias(string $alias, string $index = '') : bool + public function existsAlias(string $alias, string $index = ''): bool { $params = ['name' => $alias]; if ($index) { $params['index'] = $index; } + return $this->getElasticsearchClient()->indices()->existsAlias($params); } @@ -261,7 +265,7 @@ public function existsAlias(string $alias, string $index = '') : bool * @param string $alias * @return array */ - public function getAlias(string $alias) : array + public function getAlias(string $alias): array { return $this->getElasticsearchClient()->indices()->getAlias(['name' => $alias]); } @@ -311,7 +315,15 @@ public function addFieldsMapping(array $fields, string $index, string $entityTyp 'mapping' => [ 'type' => 'text', 'index' => true, - 'copy_to' => '_search' + 'copy_to' => '_search', + ], + ], + ], + [ + 'integer_mapping' => [ + 'match_mapping_type' => 'long', + 'mapping' => [ + 'type' => 'integer', ], ], ], @@ -333,7 +345,7 @@ public function addFieldsMapping(array $fields, string $index, string $entityTyp * @param array $query * @return array */ - public function query(array $query) : array + public function query(array $query): array { return $this->getElasticsearchClient()->search($query); } diff --git a/app/code/Magento/Elasticsearch7/Test/Unit/Model/Client/ElasticsearchTest.php b/app/code/Magento/Elasticsearch7/Test/Unit/Model/Client/ElasticsearchTest.php index 091387f844d55..a31a1971a5acc 100644 --- a/app/code/Magento/Elasticsearch7/Test/Unit/Model/Client/ElasticsearchTest.php +++ b/app/code/Magento/Elasticsearch7/Test/Unit/Model/Client/ElasticsearchTest.php @@ -460,10 +460,18 @@ public function testAddFieldsMapping() 'mapping' => [ 'type' => 'text', 'index' => true, - 'copy_to' => '_search' + 'copy_to' => '_search', ], ], - ] + ], + [ + 'integer_mapping' => [ + 'match_mapping_type' => 'long', + 'mapping' => [ + 'type' => 'integer', + ], + ], + ], ], ], ], @@ -531,10 +539,18 @@ public function testAddFieldsMappingFailure() 'mapping' => [ 'type' => 'text', 'index' => true, - 'copy_to' => '_search' + 'copy_to' => '_search', ], ], - ] + ], + [ + 'integer_mapping' => [ + 'match_mapping_type' => 'long', + 'mapping' => [ + 'type' => 'integer', + ], + ], + ], ], ], ], diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminAddDefaultImageGroupedProductTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminAddDefaultImageGroupedProductTest.xml index 04b704b9193ca..0371cdcd843f2 100644 --- a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminAddDefaultImageGroupedProductTest.xml +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminAddDefaultImageGroupedProductTest.xml @@ -32,7 +32,7 @@ <actionGroup ref="DeleteProductBySkuActionGroup" stepKey="deleteGroupedProduct"> <argument name="sku" value="{{GroupedProduct.sku}}"/> </actionGroup> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> <deleteData createDataKey="createProductOne" stepKey="deleteProductOne"/> <deleteData createDataKey="createProductTwo" stepKey="deleteProductTwo"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminRemoveDefaultImageGroupedProductTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminRemoveDefaultImageGroupedProductTest.xml index 053949fa20fb2..83f43c3a1cd8a 100644 --- a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminRemoveDefaultImageGroupedProductTest.xml +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminRemoveDefaultImageGroupedProductTest.xml @@ -29,7 +29,7 @@ </createData> </before> <after> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> <deleteData createDataKey="createProductOne" stepKey="deleteProductOne"/> <deleteData createDataKey="createProductTwo" stepKey="deleteProductTwo"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> diff --git a/app/code/Magento/Indexer/Test/Mftf/ActionGroup/IndexerActionGroup/UpdateIndexerOnSaveActionGroup.xml b/app/code/Magento/Indexer/Test/Mftf/ActionGroup/IndexerActionGroup/UpdateIndexerOnSaveActionGroup.xml deleted file mode 100644 index efa6291d5de63..0000000000000 --- a/app/code/Magento/Indexer/Test/Mftf/ActionGroup/IndexerActionGroup/UpdateIndexerOnSaveActionGroup.xml +++ /dev/null @@ -1,11 +0,0 @@ -<?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="updateIndexerOnSave" extends="AdminIndexerSetUpdateOnSaveActionGroup" deprecated="Use AdminIndexerSetUpdateOnSaveActionGroup"/> -</actionGroups> diff --git a/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/StorefrontCreateNewSubscriberActionGroup.xml b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/StorefrontCreateNewSubscriberActionGroup.xml index 0aee2cb9b2e3c..482ecec583552 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/StorefrontCreateNewSubscriberActionGroup.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/StorefrontCreateNewSubscriberActionGroup.xml @@ -8,7 +8,8 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="StorefrontCreateNewSubscriberActionGroup"> + <actionGroup name="StorefrontCreateNewSubscriberActionGroup" deprecated="Use StorefrontCreateNewsletterSubscriberActionGroup"> + <!-- Deprecated Due to inconsistency with the best practices --> <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnStorefrontPage"/> <submitForm selector="{{BasicFrontendNewsletterFormSection.subscribeForm}}" parameterArray="['email' => '{{_defaultNewsletter.senderEmail}}']" diff --git a/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/StorefrontCreateNewsletterSubscriberActionGroup.xml b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/StorefrontCreateNewsletterSubscriberActionGroup.xml new file mode 100644 index 0000000000000..44104f3adf0d9 --- /dev/null +++ b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/StorefrontCreateNewsletterSubscriberActionGroup.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="StorefrontCreateNewsletterSubscriberActionGroup"> + <arguments> + <argument name="email" type="string"/> + </arguments> + <fillField stepKey="fillEmailField" selector="{{BasicFrontendNewsletterFormSection.newsletterEmail}}" userInput="{{email}}"/> + <click selector="{{BasicFrontendNewsletterFormSection.subscribeButton}}" stepKey="submitForm"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Section/NewsletterTemplateSection/BasicFrontendNewsletterFormSection.xml b/app/code/Magento/Newsletter/Test/Mftf/Section/NewsletterTemplateSection/BasicFrontendNewsletterFormSection.xml index 8475fb4d55b9e..f4c685e730be3 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/Section/NewsletterTemplateSection/BasicFrontendNewsletterFormSection.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/Section/NewsletterTemplateSection/BasicFrontendNewsletterFormSection.xml @@ -8,8 +8,8 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="BasicFrontendNewsletterFormSection"> - <element name="newsletterEmail" type="input" selector="#newsletter"/> - <element name="subscribeButton" type="button" selector=".subscribe" timeout="30"/> + <element name="newsletterEmail" type="input" selector=".control #newsletter"/> + <element name="subscribeButton" type="button" selector=".actions .action.subscribe.primary" timeout="30"/> <element name="subscribeForm" type="input" selector="#newsletter-validate-detail" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminMarketingDeleteNewsletterSubscriberTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminMarketingDeleteNewsletterSubscriberTest.xml index eea77a6be0784..c472d262a34c8 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminMarketingDeleteNewsletterSubscriberTest.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminMarketingDeleteNewsletterSubscriberTest.xml @@ -14,6 +14,7 @@ <stories value="Subscribers Deleting"/> <title value="Admin deletes newsletter subscribers"/> <description value="Admin should be able delete newsletter subscribers"/> + <severity value="CRITICAL"/> <group value="newsletter"/> </annotations> <before> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/StorefrontNewsletterGuestSubscriptionWithDisallowedOptionTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/StorefrontNewsletterGuestSubscriptionWithDisallowedOptionTest.xml new file mode 100644 index 0000000000000..6c62434f1620e --- /dev/null +++ b/app/code/Magento/Newsletter/Test/Mftf/Test/StorefrontNewsletterGuestSubscriptionWithDisallowedOptionTest.xml @@ -0,0 +1,40 @@ +<?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="StorefrontNewsletterGuestSubscriptionWithDisallowedOptionTest"> + <annotations> + <features value="Newsletter"/> + <stories value="Disabled Guest Newsletter Subscription"/> + <title value="Newsletter Subscription for guest is disabled and cannot be performed"/> + <description value="Guest cannot subscribe to Newsletter if it is disallowed in configurations"/> + <severity value="AVERAGE"/> + <group value="newsletter"/> + <group value="configuration"/> + <testCaseId value="MC-35728"/> + </annotations> + <before> + <magentoCLI stepKey="disableGuestSubscription" command="config:set newsletter/subscription/allow_guest_subscribe 0"/> + <magentoCLI command="cache:clean config" stepKey="cleanCache"/> + </before> + <after> + <magentoCLI stepKey="allowGuestSubscription" command="config:set newsletter/subscription/allow_guest_subscribe 1"/> + <magentoCLI command="cache:clean config" stepKey="cacheClean"/> + </after> + + <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="openStorefrontPage"/> + <actionGroup ref="StorefrontCreateNewsletterSubscriberActionGroup" stepKey="createSubscription"> + <argument name="email" value="{{_defaultNewsletter.senderEmail}}"/> + </actionGroup> + <actionGroup ref="StorefrontAssertErrorMessageActionGroup" stepKey="assertMessage"> + <argument name="message" value="Sorry, but the administrator denied subscription for guests. Please register."/> + <argument name="messageType" value="error"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/VerifyRegistredLinkDisplayedForGuestSubscriptionNoTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/VerifyRegistredLinkDisplayedForGuestSubscriptionNoTest.xml index cffce8da1d710..dbce742aa0eef 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/Test/VerifyRegistredLinkDisplayedForGuestSubscriptionNoTest.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/Test/VerifyRegistredLinkDisplayedForGuestSubscriptionNoTest.xml @@ -8,7 +8,8 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="VerifyRegistredLinkDisplayedForGuestSubscriptionNoTest"> + <test name="VerifyRegistredLinkDisplayedForGuestSubscriptionNoTest" deprecated="Use StorefrontNewsletterGuestSubscriptionWithDisallowedOptionTest"> + <!-- Deprecated Due to inconsistency with the best practices --> <annotations> <features value="Newsletter"/> <stories value="Configure guest newsletter subscription to 'No'"/> @@ -22,6 +23,11 @@ <magentoCLI command="config:set newsletter/subscription/allow_guest_subscribe 0" stepKey="setConfigGuestSubscriptionDisable"/> </before> - <actionGroup ref="StorefrontCreateNewSubscriberActionGroup" stepKey="createSubscriber"/> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnStorefrontPage"/> + <submitForm selector="{{BasicFrontendNewsletterFormSection.subscribeForm}}" + parameterArray="['email' => '{{_defaultNewsletter.senderEmail}}']" + button="{{BasicFrontendNewsletterFormSection.subscribeButton}}" stepKey="submitForm"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <waitForElementVisible stepKey="waitForErrorAppears" selector="{{StorefrontMessagesSection.error}}"/> </test> </tests> diff --git a/app/code/Magento/PageCache/Test/Mftf/Test/AdminFrontendAreaSessionMustNotAffectAdminAreaTest.xml b/app/code/Magento/PageCache/Test/Mftf/Test/AdminFrontendAreaSessionMustNotAffectAdminAreaTest.xml index d2c738398aae1..fdf8ceef0d647 100644 --- a/app/code/Magento/PageCache/Test/Mftf/Test/AdminFrontendAreaSessionMustNotAffectAdminAreaTest.xml +++ b/app/code/Magento/PageCache/Test/Mftf/Test/AdminFrontendAreaSessionMustNotAffectAdminAreaTest.xml @@ -61,8 +61,7 @@ <actionGroup ref="AdminLoginActionGroup" stepKey="LoginAsAdmin"/> <!-- 2. Navigate Go to "Catalog"->"Products" --> - <amOnPage url="{{ProductCatalogPage.url}}" stepKey="onCatalogProductPage"/> - <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="onCatalogProductPage"/> <!-- 3. Open separate tab with Storefront --> <openNewTab stepKey="openNewTab"/> diff --git a/app/code/Magento/Paypal/Model/Api/Nvp.php b/app/code/Magento/Paypal/Model/Api/Nvp.php index 9e4f7693f4bfb..33fc7fcccf0db 100644 --- a/app/code/Magento/Paypal/Model/Api/Nvp.php +++ b/app/code/Magento/Paypal/Model/Api/Nvp.php @@ -1423,7 +1423,7 @@ protected function _validateResponse($method, $response) */ protected function _deformatNVP($nvpstr) { - $intial = 0; + $initial = 0; $nvpArray = []; $nvpstr = strpos($nvpstr, "\r\n\r\n") !== false ? substr($nvpstr, strpos($nvpstr, "\r\n\r\n") + 4) : $nvpstr; @@ -1435,7 +1435,7 @@ protected function _deformatNVP($nvpstr) $valuepos = strpos($nvpstr, '&') ? strpos($nvpstr, '&') : strlen($nvpstr); /*getting the Key and Value values and storing in a Associative Array*/ - $keyval = substr($nvpstr, $intial, $keypos); + $keyval = substr($nvpstr, $initial, $keypos); $valval = substr($nvpstr, $keypos + 1, $valuepos - $keypos - 1); //decoding the response $nvpArray[urldecode($keyval)] = urldecode($valval); diff --git a/app/code/Magento/ProductAlert/Model/Email.php b/app/code/Magento/ProductAlert/Model/Email.php index 3351166aa6a12..379ae29ef4649 100644 --- a/app/code/Magento/ProductAlert/Model/Email.php +++ b/app/code/Magento/ProductAlert/Model/Email.php @@ -1,9 +1,10 @@ <?php - /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\ProductAlert\Model; use Magento\Catalog\Model\Product; @@ -40,7 +41,7 @@ * @api * @since 100.0.2 * @method int getStoreId() - * @method $this setStoreId() + * @method $this setStoreId(int $storeId) */ class Email extends AbstractModel { @@ -206,7 +207,7 @@ public function getType() * * @return $this */ - public function setWebsite(\Magento\Store\Model\Website $website) + public function setWebsite(Website $website) { $this->_website = $website; return $this; @@ -275,7 +276,7 @@ public function clean() * * @return $this */ - public function addPriceProduct(\Magento\Catalog\Model\Product $product) + public function addPriceProduct(Product $product) { $this->_priceProducts[$product->getId()] = $product; return $this; @@ -288,7 +289,7 @@ public function addPriceProduct(\Magento\Catalog\Model\Product $product) * * @return $this */ - public function addStockProduct(\Magento\Catalog\Model\Product $product) + public function addStockProduct(Product $product) { $this->_stockProducts[$product->getId()] = $product; return $this; @@ -342,7 +343,7 @@ public function send() return false; } - $storeId = $this->getStoreId() ?: (int) $this->_customer->getStoreId(); + $storeId = (int) $this->getStoreId() ?: (int) $this->_customer->getStoreId(); $store = $this->getStore($storeId); $this->_appEmulation->startEnvironmentEmulation($storeId); @@ -378,12 +379,13 @@ public function send() 'customerName' => $customerName, 'alertGrid' => $alertGrid, ] - )->setFrom( + )->setFromByScope( $this->_scopeConfig->getValue( self::XML_PATH_EMAIL_IDENTITY, ScopeInterface::SCOPE_STORE, $storeId - ) + ), + $storeId )->addTo( $this->_customer->getEmail(), $customerName diff --git a/app/code/Magento/Sales/Block/Order/Email/Items/DefaultItems.php b/app/code/Magento/Sales/Block/Order/Email/Items/DefaultItems.php index 064405daf89a8..cbb79f188f231 100644 --- a/app/code/Magento/Sales/Block/Order/Email/Items/DefaultItems.php +++ b/app/code/Magento/Sales/Block/Order/Email/Items/DefaultItems.php @@ -3,8 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Sales\Block\Order\Email\Items; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\View\Element\Template; use Magento\Sales\Model\Order\Creditmemo\Item as CreditmemoItem; use Magento\Sales\Model\Order\Invoice\Item as InvoiceItem; use Magento\Sales\Model\Order\Item as OrderItem; @@ -16,7 +20,7 @@ * @author Magento Core Team <core@magentocommerce.com> * @since 100.0.2 */ -class DefaultItems extends \Magento\Framework\View\Element\Template +class DefaultItems extends Template { /** * Retrieve current order model instance @@ -92,6 +96,7 @@ public function getSku($item) * Return product additional information block * * @return \Magento\Framework\View\Element\AbstractBlock + * @throws LocalizedException */ public function getProductAdditionalInformationBlock() { @@ -103,10 +108,13 @@ public function getProductAdditionalInformationBlock() * * @param OrderItem|InvoiceItem|CreditmemoItem $item * @return string + * @throws LocalizedException */ public function getItemPrice($item) { $block = $this->getLayout()->getBlock('item_price'); + $item->setRowTotal((float) $item->getPrice() * (float) $this->getItem()->getQty()); + $item->setBaseRowTotal((float) $item->getBasePrice() * (float) $this->getItem()->getQty()); $block->setItem($item); return $block->toHtml(); } diff --git a/app/code/Magento/Sales/Model/AdminOrder/Create.php b/app/code/Magento/Sales/Model/AdminOrder/Create.php index 67a533ea88550..e0b61e9bb150e 100644 --- a/app/code/Magento/Sales/Model/AdminOrder/Create.php +++ b/app/code/Magento/Sales/Model/AdminOrder/Create.php @@ -663,6 +663,14 @@ public function initFromOrderItem(\Magento\Sales\Model\Order\Item $orderItem, $q if (is_numeric($qty)) { $buyRequest->setQty($qty); } + $productOptions = $orderItem->getProductOptions(); + if ($productOptions !== null && !empty($productOptions['options'])) { + $formattedOptions = []; + foreach ($productOptions['options'] as $option) { + $formattedOptions[$option['option_id']] = $option['option_value']; + } + $buyRequest->setData('options', $formattedOptions); + } $item = $this->getQuote()->addProduct($product, $buyRequest); if (is_string($item)) { return $item; @@ -1369,7 +1377,6 @@ protected function _setQuoteAddress(\Magento\Quote\Model\Quote\Address $address, $data = isset($data['region']) && is_array($data['region']) ? array_merge($data, $data['region']) : $data; $addressForm = $this->_metadataFormFactory->create( - AddressMetadataInterface::ENTITY_TYPE_ADDRESS, 'adminhtml_customer_address', $data, diff --git a/app/code/Magento/Sales/Model/Order/Email/Sender/InvoiceSender.php b/app/code/Magento/Sales/Model/Order/Email/Sender/InvoiceSender.php index 05164d1b7b5f3..d0247294e75a1 100644 --- a/app/code/Magento/Sales/Model/Order/Email/Sender/InvoiceSender.php +++ b/app/code/Magento/Sales/Model/Order/Email/Sender/InvoiceSender.php @@ -3,18 +3,20 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Sales\Model\Order\Email\Sender; +use Magento\Framework\DataObject; +use Magento\Framework\Event\ManagerInterface; use Magento\Payment\Helper\Data as PaymentHelper; use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Address\Renderer; use Magento\Sales\Model\Order\Email\Container\InvoiceIdentity; use Magento\Sales\Model\Order\Email\Container\Template; use Magento\Sales\Model\Order\Email\Sender; use Magento\Sales\Model\Order\Invoice; use Magento\Sales\Model\ResourceModel\Order\Invoice as InvoiceResource; -use Magento\Sales\Model\Order\Address\Renderer; -use Magento\Framework\Event\ManagerInterface; -use Magento\Framework\DataObject; /** * Sends order invoice email to the customer. @@ -106,6 +108,12 @@ public function send(Invoice $invoice, $forceSyncMode = false) $order = $invoice->getOrder(); $this->identityContainer->setStore($order->getStore()); + if ($this->checkIfPartialInvoice($order, $invoice)) { + $order->setBaseSubtotal((float) $invoice->getBaseSubtotal()); + $order->setBaseTaxAmount((float) $invoice->getBaseTaxAmount()); + $order->setBaseShippingAmount((float) $invoice->getBaseShippingAmount()); + } + $transport = [ 'order' => $order, 'order_id' => $order->getId(), @@ -165,4 +173,18 @@ protected function getPaymentHtml(Order $order) $this->identityContainer->getStore()->getStoreId() ); } + + /** + * Check if the order contains partial invoice + * + * @param Order $order + * @param Invoice $invoice + * @return bool + */ + private function checkIfPartialInvoice(Order $order, Invoice $invoice): bool + { + $totalQtyOrdered = (float) $order->getTotalQtyOrdered(); + $totalQtyInvoiced = (float) $invoice->getTotalQty(); + return $totalQtyOrdered !== $totalQtyInvoiced; + } } diff --git a/app/code/Magento/Sales/Model/Reorder/Reorder.php b/app/code/Magento/Sales/Model/Reorder/Reorder.php index a1a8d6e8c9928..a5d40df07bd69 100644 --- a/app/code/Magento/Sales/Model/Reorder/Reorder.php +++ b/app/code/Magento/Sales/Model/Reorder/Reorder.php @@ -225,7 +225,8 @@ private function getOrderProducts(string $storeId, array $orderItemProductIds): ->addStoreFilter() ->addAttributeToSelect('*') ->joinAttribute('status', 'catalog_product/status', 'entity_id', null, 'inner') - ->joinAttribute('visibility', 'catalog_product/visibility', 'entity_id', null, 'inner'); + ->joinAttribute('visibility', 'catalog_product/visibility', 'entity_id', null, 'inner') + ->addOptionsToResult(); return $collection->getItems(); } diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithMinimumAmountEnabledTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithMinimumAmountEnabledTest.xml index ade1f783c1309..1f6d7c40be99b 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithMinimumAmountEnabledTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithMinimumAmountEnabledTest.xml @@ -32,7 +32,7 @@ <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> <createData entity="DisabledMinimumOrderAmount" stepKey="disableMinimumOrderAmount"/> <actionGroup ref="ClearCacheActionGroup" stepKey="clearCacheAfter"/> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> </after> <!--Admin creates order--> diff --git a/app/code/Magento/Sales/Test/Unit/Block/Order/Email/Items/DefaultItemsTest.php b/app/code/Magento/Sales/Test/Unit/Block/Order/Email/Items/DefaultItemsTest.php index 4d8b8033f60da..7123a81306ef1 100644 --- a/app/code/Magento/Sales/Test/Unit/Block/Order/Email/Items/DefaultItemsTest.php +++ b/app/code/Magento/Sales/Test/Unit/Block/Order/Email/Items/DefaultItemsTest.php @@ -11,7 +11,7 @@ use Magento\Backend\Block\Template\Context; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Framework\View\Layout; -use Magento\Quote\Model\Quote\Item; +use Magento\Quote\Model\Quote\Item as QuoteItem; use Magento\Sales\Block\Order\Email\Items\DefaultItems; use Magento\Sales\Model\Order\Item as OrderItem; use PHPUnit\Framework\MockObject\MockObject; @@ -20,7 +20,7 @@ class DefaultItemsTest extends TestCase { /** - * @var MockObject|\Magento\Sales\Block\Order\Email\Items\DefaultItem + * @var MockObject|DefaultItems */ protected $block; @@ -39,9 +39,16 @@ class DefaultItemsTest extends TestCase */ protected $objectManager; - /** @var MockObject|Item */ + /** + * @var MockObject|OrderItem + */ protected $itemMock; + /** + * @var MockObject|QuoteItem + */ + protected $quoteItemMock; + /** * Initialize required data */ @@ -54,16 +61,6 @@ protected function setUp(): void ->setMethods(['getBlock']) ->getMock(); - $this->block = $this->objectManager->getObject( - DefaultItems::class, - [ - 'context' => $this->objectManager->getObject( - Context::class, - ['layout' => $this->layoutMock] - ) - ] - ); - $this->priceRenderBlock = $this->getMockBuilder(Template::class) ->disableOriginalConstructor() ->setMethods(['setItem', 'toHtml']) @@ -72,16 +69,47 @@ protected function setUp(): void $this->itemMock = $this->getMockBuilder(OrderItem::class) ->disableOriginalConstructor() ->getMock(); + + $this->quoteItemMock = $this->getMockBuilder(QuoteItem::class) + ->disableOriginalConstructor() + ->setMethods(['getQty']) + ->getMock(); + + $this->block = $this->objectManager->getObject( + DefaultItems::class, + [ + 'context' => $this->objectManager->getObject( + Context::class, + ['layout' => $this->layoutMock] + ), + 'data' => [ + 'item' => $this->quoteItemMock + ] + ] + ); } - public function testGetItemPrice() + /** + * @param float $price + * @param string $html + * @param float $quantity + * @dataProvider getItemPriceDataProvider + * */ + public function testGetItemPrice($price, $html, $quantity) { - $html = '$34.28'; - $this->layoutMock->expects($this->once()) ->method('getBlock') ->with('item_price') ->willReturn($this->priceRenderBlock); + $this->quoteItemMock->expects($this->any()) + ->method('getQty') + ->willReturn($quantity); + $this->itemMock->expects($this->any()) + ->method('setRowTotal') + ->willReturn($price * $quantity); + $this->itemMock->expects($this->any()) + ->method('setBaseRowTotal') + ->willReturn($price * $quantity); $this->priceRenderBlock->expects($this->once()) ->method('setItem') @@ -93,4 +121,15 @@ public function testGetItemPrice() $this->assertEquals($html, $this->block->getItemPrice($this->itemMock)); } + + /** + * @return array + */ + public function getItemPriceDataProvider() + { + return [ + 'get default item price' => [34.28,'$34.28',1.0], + 'get item price with quantity 2.0' => [12.00,'$24.00',2.0] + ]; + } } diff --git a/app/code/Magento/SalesRule/Model/Coupon/Quote/UpdateCouponUsages.php b/app/code/Magento/SalesRule/Model/Coupon/Quote/UpdateCouponUsages.php new file mode 100644 index 0000000000000..0ee2ee09cad57 --- /dev/null +++ b/app/code/Magento/SalesRule/Model/Coupon/Quote/UpdateCouponUsages.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\SalesRule\Model\Coupon\Quote; + +use Magento\Quote\Api\Data\CartInterface; +use Magento\SalesRule\Model\Coupon\Usage\Processor as CouponUsageProcessor; +use Magento\SalesRule\Model\Coupon\Usage\UpdateInfo; +use Magento\SalesRule\Model\Coupon\Usage\UpdateInfoFactory; + +/** + * Updates the coupon usages from quote + */ +class UpdateCouponUsages +{ + /** + * @var CouponUsageProcessor + */ + private $couponUsageProcessor; + + /** + * @var UpdateInfoFactory + */ + private $updateInfoFactory; + + /** + * @param CouponUsageProcessor $couponUsageProcessor + * @param UpdateInfoFactory $updateInfoFactory + */ + public function __construct( + CouponUsageProcessor $couponUsageProcessor, + UpdateInfoFactory $updateInfoFactory + ) { + $this->couponUsageProcessor = $couponUsageProcessor; + $this->updateInfoFactory = $updateInfoFactory; + } + + /** + * Executes the current command + * + * @param CartInterface $quote + * @param bool $increment + * @return void + */ + public function execute(CartInterface $quote, bool $increment): void + { + if (!$quote->getAppliedRuleIds()) { + return; + } + + /** @var UpdateInfo $updateInfo */ + $updateInfo = $this->updateInfoFactory->create(); + $updateInfo->setAppliedRuleIds(explode(',', $quote->getAppliedRuleIds())); + $updateInfo->setCouponCode((string)$quote->getCouponCode()); + $updateInfo->setCustomerId((int)$quote->getCustomerId()); + $updateInfo->setIsIncrement($increment); + + $this->couponUsageProcessor->process($updateInfo); + } +} diff --git a/app/code/Magento/SalesRule/Model/Coupon/UpdateCouponUsages.php b/app/code/Magento/SalesRule/Model/Coupon/UpdateCouponUsages.php index 3236c80e1b7ed..1645f205d1e55 100644 --- a/app/code/Magento/SalesRule/Model/Coupon/UpdateCouponUsages.php +++ b/app/code/Magento/SalesRule/Model/Coupon/UpdateCouponUsages.php @@ -8,56 +8,39 @@ namespace Magento\SalesRule\Model\Coupon; use Magento\Sales\Api\Data\OrderInterface; -use Magento\SalesRule\Model\Coupon; -use Magento\SalesRule\Model\ResourceModel\Coupon\Usage; -use Magento\SalesRule\Model\Rule\CustomerFactory; -use Magento\SalesRule\Model\RuleFactory; +use Magento\SalesRule\Model\Coupon\Usage\Processor as CouponUsageProcessor; +use Magento\SalesRule\Model\Coupon\Usage\UpdateInfo; +use Magento\SalesRule\Model\Coupon\Usage\UpdateInfoFactory; /** - * Updates the coupon usages. + * Updates the coupon usages */ class UpdateCouponUsages { /** - * @var RuleFactory + * @var CouponUsageProcessor */ - private $ruleFactory; + private $couponUsageProcessor; /** - * @var RuleFactory + * @var UpdateInfoFactory */ - private $ruleCustomerFactory; + private $updateInfoFactory; /** - * @var Coupon - */ - private $coupon; - - /** - * @var Usage - */ - private $couponUsage; - - /** - * @param RuleFactory $ruleFactory - * @param CustomerFactory $ruleCustomerFactory - * @param Coupon $coupon - * @param Usage $couponUsage + * @param CouponUsageProcessor $couponUsageProcessor + * @param UpdateInfoFactory $updateInfoFactory */ public function __construct( - RuleFactory $ruleFactory, - CustomerFactory $ruleCustomerFactory, - Coupon $coupon, - Usage $couponUsage + CouponUsageProcessor $couponUsageProcessor, + UpdateInfoFactory $updateInfoFactory ) { - $this->ruleFactory = $ruleFactory; - $this->ruleCustomerFactory = $ruleCustomerFactory; - $this->coupon = $coupon; - $this->couponUsage = $couponUsage; + $this->couponUsageProcessor = $couponUsageProcessor; + $this->updateInfoFactory = $updateInfoFactory; } /** - * Executes the current command. + * Executes the current command * * @param OrderInterface $subject * @param bool $increment @@ -68,86 +51,16 @@ public function execute(OrderInterface $subject, bool $increment): OrderInterfac if (!$subject || !$subject->getAppliedRuleIds()) { return $subject; } - // lookup rule ids - $ruleIds = explode(',', $subject->getAppliedRuleIds()); - $ruleIds = array_unique($ruleIds); - $customerId = (int)$subject->getCustomerId(); - // use each rule (and apply to customer, if applicable) - foreach ($ruleIds as $ruleId) { - if (!$ruleId) { - continue; - } - $this->updateRuleUsages($increment, (int)$ruleId, $customerId); - } - $this->updateCouponUsages($subject, $increment, $customerId); - - return $subject; - } - /** - * Update the number of rule usages. - * - * @param bool $increment - * @param int $ruleId - * @param int $customerId - */ - private function updateRuleUsages(bool $increment, int $ruleId, int $customerId) - { - /** @var \Magento\SalesRule\Model\Rule $rule */ - $rule = $this->ruleFactory->create(); - $rule->load($ruleId); - if ($rule->getId()) { - $rule->loadCouponCode(); - if ($increment || $rule->getTimesUsed() > 0) { - $rule->setTimesUsed($rule->getTimesUsed() + ($increment ? 1 : -1)); - $rule->save(); - } - if ($customerId) { - $this->updateCustomerRuleUsages($increment, $ruleId, $customerId); - } - } - } + /** @var UpdateInfo $updateInfo */ + $updateInfo = $this->updateInfoFactory->create(); + $updateInfo->setAppliedRuleIds(explode(',', $subject->getAppliedRuleIds())); + $updateInfo->setCouponCode((string)$subject->getCouponCode()); + $updateInfo->setCustomerId((int)$subject->getCustomerId()); + $updateInfo->setIsIncrement($increment); - /** - * Update the number of rule usages per customer. - * - * @param bool $increment - * @param int $ruleId - * @param int $customerId - */ - private function updateCustomerRuleUsages(bool $increment, int $ruleId, int $customerId): void - { - /** @var \Magento\SalesRule\Model\Rule\Customer $ruleCustomer */ - $ruleCustomer = $this->ruleCustomerFactory->create(); - $ruleCustomer->loadByCustomerRule($customerId, $ruleId); - if ($ruleCustomer->getId()) { - if ($increment || $ruleCustomer->getTimesUsed() > 0) { - $ruleCustomer->setTimesUsed($ruleCustomer->getTimesUsed() + ($increment ? 1 : -1)); - } - } elseif ($increment) { - $ruleCustomer->setCustomerId($customerId)->setRuleId($ruleId)->setTimesUsed(1); - } - $ruleCustomer->save(); - } + $this->couponUsageProcessor->process($updateInfo); - /** - * Update the number of coupon usages. - * - * @param OrderInterface $subject - * @param bool $increment - * @param int $customerId - */ - private function updateCouponUsages(OrderInterface $subject, bool $increment, int $customerId): void - { - $this->coupon->load($subject->getCouponCode(), 'code'); - if ($this->coupon->getId()) { - if ($increment || $this->coupon->getTimesUsed() > 0) { - $this->coupon->setTimesUsed($this->coupon->getTimesUsed() + ($increment ? 1 : -1)); - $this->coupon->save(); - } - if ($customerId) { - $this->couponUsage->updateCustomerCouponTimesUsed($customerId, $this->coupon->getId(), $increment); - } - } + return $subject; } } diff --git a/app/code/Magento/SalesRule/Model/Coupon/Usage/Processor.php b/app/code/Magento/SalesRule/Model/Coupon/Usage/Processor.php new file mode 100644 index 0000000000000..90a456d5ff833 --- /dev/null +++ b/app/code/Magento/SalesRule/Model/Coupon/Usage/Processor.php @@ -0,0 +1,149 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\SalesRule\Model\Coupon\Usage; + +use Magento\SalesRule\Model\Coupon; +use Magento\SalesRule\Model\ResourceModel\Coupon\Usage; +use Magento\SalesRule\Model\Rule\CustomerFactory; +use Magento\SalesRule\Model\RuleFactory; + +/** + * Processor to update coupon usage + */ +class Processor +{ + /** + * @var RuleFactory + */ + private $ruleFactory; + + /** + * @var RuleFactory + */ + private $ruleCustomerFactory; + + /** + * @var Coupon + */ + private $coupon; + + /** + * @var Usage + */ + private $couponUsage; + + /** + * @param RuleFactory $ruleFactory + * @param CustomerFactory $ruleCustomerFactory + * @param Coupon $coupon + * @param Usage $couponUsage + */ + public function __construct( + RuleFactory $ruleFactory, + CustomerFactory $ruleCustomerFactory, + Coupon $coupon, + Usage $couponUsage + ) { + $this->ruleFactory = $ruleFactory; + $this->ruleCustomerFactory = $ruleCustomerFactory; + $this->coupon = $coupon; + $this->couponUsage = $couponUsage; + } + + /** + * Update coupon usage + * + * @param UpdateInfo $updateInfo + */ + public function process(UpdateInfo $updateInfo): void + { + if (empty($updateInfo->getAppliedRuleIds())) { + return; + } + + if (!empty($updateInfo->getCouponCode())) { + $this->updateCouponUsages($updateInfo); + } + $isIncrement = $updateInfo->isIncrement(); + $customerId = $updateInfo->getCustomerId(); + // use each rule (and apply to customer, if applicable) + foreach (array_unique($updateInfo->getAppliedRuleIds()) as $ruleId) { + if (!(int)$ruleId) { + continue; + } + $this->updateRuleUsages($isIncrement, (int)$ruleId); + if ($customerId) { + $this->updateCustomerRuleUsages($isIncrement, (int)$ruleId, $customerId); + } + } + } + + /** + * Update the number of coupon usages + * + * @param UpdateInfo $updateInfo + */ + private function updateCouponUsages(UpdateInfo $updateInfo): void + { + $isIncrement = $updateInfo->isIncrement(); + $this->coupon->load($updateInfo->getCouponCode(), 'code'); + if ($this->coupon->getId()) { + if ($updateInfo->isIncrement() || $this->coupon->getTimesUsed() > 0) { + $this->coupon->setTimesUsed($this->coupon->getTimesUsed() + ($isIncrement ? 1 : -1)); + $this->coupon->save(); + } + if ($updateInfo->getCustomerId()) { + $this->couponUsage->updateCustomerCouponTimesUsed( + $updateInfo->getCustomerId(), + $this->coupon->getId(), + $isIncrement + ); + } + } + } + + /** + * Update the number of rule usages + * + * @param bool $isIncrement + * @param int $ruleId + */ + private function updateRuleUsages(bool $isIncrement, int $ruleId): void + { + $rule = $this->ruleFactory->create(); + $rule->load($ruleId); + if ($rule->getId()) { + $rule->loadCouponCode(); + if ($isIncrement || $rule->getTimesUsed() > 0) { + $rule->setTimesUsed($rule->getTimesUsed() + ($isIncrement ? 1 : -1)); + $rule->save(); + } + } + } + + /** + * Update the number of rule usages per customer + * + * @param bool $isIncrement + * @param int $ruleId + * @param int $customerId + */ + private function updateCustomerRuleUsages(bool $isIncrement, int $ruleId, int $customerId): void + { + $ruleCustomer = $this->ruleCustomerFactory->create(); + $ruleCustomer->loadByCustomerRule($customerId, $ruleId); + if ($ruleCustomer->getId()) { + if ($isIncrement || $ruleCustomer->getTimesUsed() > 0) { + $ruleCustomer->setTimesUsed($ruleCustomer->getTimesUsed() + ($isIncrement ? 1 : -1)); + } + } elseif ($isIncrement) { + $ruleCustomer->setCustomerId($customerId)->setRuleId($ruleId)->setTimesUsed(1); + } + $ruleCustomer->save(); + } +} diff --git a/app/code/Magento/SalesRule/Model/Coupon/Usage/UpdateInfo.php b/app/code/Magento/SalesRule/Model/Coupon/Usage/UpdateInfo.php new file mode 100644 index 0000000000000..328093ca1af0e --- /dev/null +++ b/app/code/Magento/SalesRule/Model/Coupon/Usage/UpdateInfo.php @@ -0,0 +1,107 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\SalesRule\Model\Coupon\Usage; + +use Magento\Framework\DataObject; + +/** + * Coupon usages info to update + */ +class UpdateInfo extends DataObject +{ + private const APPLIED_RULE_IDS_KEY = 'applied_rule_ids'; + private const COUPON_CODE_KEY = 'coupon_code'; + private const CUSTOMER_ID_KEY = 'customer_id'; + private const IS_INCREMENT_KEY = 'is_increment'; + + /** + * Get applied rule ids + * + * @return array + */ + public function getAppliedRuleIds(): array + { + return (array)$this->getData(self::APPLIED_RULE_IDS_KEY); + } + + /** + * Set applied rule ids + * + * @param array $value + * @return void + */ + public function setAppliedRuleIds(array $value): void + { + $this->setData(self::APPLIED_RULE_IDS_KEY, $value); + } + + /** + * Get coupon code + * + * @return string + */ + public function getCouponCode(): string + { + return (string)$this->getData(self::COUPON_CODE_KEY); + } + + /** + * Set coupon code + * + * @param string $value + * @return void + */ + public function setCouponCode(string $value): void + { + $this->setData(self::COUPON_CODE_KEY, $value); + } + + /** + * Get customer id + * + * @return int|null + */ + public function getCustomerId(): ?int + { + return $this->getData(self::CUSTOMER_ID_KEY) !== null + ? (int) $this->getData(self::CUSTOMER_ID_KEY) + : null; + } + + /** + * Set customer id + * + * @param int|null $value + * @return void + */ + public function setCustomerId(?int $value): void + { + $this->setData(self::CUSTOMER_ID_KEY, $value); + } + + /** + * Get update mode: increment - true, decrement - false + * + * @return bool + */ + public function isIncrement(): bool + { + return (bool)$this->getData(self::IS_INCREMENT_KEY); + } + + /** + * Set update mode: increment - true, decrement - false + * + * @param bool $value + * @return void + */ + public function setIsIncrement(bool $value): void + { + $this->setData(self::IS_INCREMENT_KEY, $value); + } +} diff --git a/app/code/Magento/SalesRule/Observer/AssignCouponDataAfterOrderCustomerAssignObserver.php b/app/code/Magento/SalesRule/Observer/AssignCouponDataAfterOrderCustomerAssignObserver.php index 2d771e4560fcf..1d416fbcf4f52 100644 --- a/app/code/Magento/SalesRule/Observer/AssignCouponDataAfterOrderCustomerAssignObserver.php +++ b/app/code/Magento/SalesRule/Observer/AssignCouponDataAfterOrderCustomerAssignObserver.php @@ -45,9 +45,10 @@ public function execute(Observer $observer) $event = $observer->getEvent(); /** @var OrderInterface $order */ $order = $event->getData(self::EVENT_KEY_ORDER); - - if ($order->getCustomerId()) { - $this->updateCouponUsages->execute($order, true); + if (!$order->getCustomerId()) { + return; } + + $this->updateCouponUsages->execute($order, true); } } diff --git a/app/code/Magento/SalesRule/Observer/CouponUsagesDecrement.php b/app/code/Magento/SalesRule/Observer/CouponUsagesDecrement.php new file mode 100644 index 0000000000000..d0c7199405879 --- /dev/null +++ b/app/code/Magento/SalesRule/Observer/CouponUsagesDecrement.php @@ -0,0 +1,42 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\SalesRule\Observer; + +use Magento\Framework\Event\Observer as EventObserver; +use Magento\Framework\Event\ObserverInterface; +use Magento\Quote\Api\Data\CartInterface; +use Magento\SalesRule\Model\Coupon\Quote\UpdateCouponUsages; + +/** + * Decrement number of coupon usages after error of placing order + */ +class CouponUsagesDecrement implements ObserverInterface +{ + /** + * @var UpdateCouponUsages + */ + private $updateCouponUsages; + + /** + * @param UpdateCouponUsages $updateCouponUsages + */ + public function __construct(UpdateCouponUsages $updateCouponUsages) + { + $this->updateCouponUsages = $updateCouponUsages; + } + + /** + * @inheritdoc + */ + public function execute(EventObserver $observer) + { + /** @var CartInterface $quote */ + $quote = $observer->getQuote(); + $this->updateCouponUsages->execute($quote, false); + } +} diff --git a/app/code/Magento/SalesRule/Plugin/CouponUsagesDecrement.php b/app/code/Magento/SalesRule/Plugin/CouponUsagesDecrement.php index 87a7c2ed1bd38..3be801a288479 100644 --- a/app/code/Magento/SalesRule/Plugin/CouponUsagesDecrement.php +++ b/app/code/Magento/SalesRule/Plugin/CouponUsagesDecrement.php @@ -49,11 +49,13 @@ public function __construct( */ public function afterCancel(OrderService $subject, bool $result, $orderId): bool { - $order = $this->orderRepository->get($orderId); - if ($result) { - $this->updateCouponUsages->execute($order, false); + if (!$result) { + return $result; } + $order = $this->orderRepository->get($orderId); + $this->updateCouponUsages->execute($order, false); + return $result; } } diff --git a/app/code/Magento/SalesRule/Plugin/CouponUsagesIncrement.php b/app/code/Magento/SalesRule/Plugin/CouponUsagesIncrement.php index 14bbb5fce02a5..66a32f37eee2f 100644 --- a/app/code/Magento/SalesRule/Plugin/CouponUsagesIncrement.php +++ b/app/code/Magento/SalesRule/Plugin/CouponUsagesIncrement.php @@ -7,12 +7,13 @@ namespace Magento\SalesRule\Plugin; -use Magento\Sales\Api\Data\OrderInterface; -use Magento\Sales\Model\Service\OrderService; -use Magento\SalesRule\Model\Coupon\UpdateCouponUsages; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\QuoteManagement; +use Magento\SalesRule\Model\Coupon\Quote\UpdateCouponUsages; /** - * Increments number of coupon usages after placing order. + * Increments number of coupon usages before placing order */ class CouponUsagesIncrement { @@ -24,24 +25,28 @@ class CouponUsagesIncrement /** * @param UpdateCouponUsages $updateCouponUsages */ - public function __construct( - UpdateCouponUsages $updateCouponUsages - ) { + public function __construct(UpdateCouponUsages $updateCouponUsages) + { $this->updateCouponUsages = $updateCouponUsages; } /** - * Increments number of coupon usages after placing order. + * Increments number of coupon usages before placing order * - * @param OrderService $subject - * @param OrderInterface $result - * @return OrderInterface + * @param QuoteManagement $subject + * @param Quote $quote + * @param array $orderData + * @return void * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @throws NoSuchEntityException */ - public function afterPlace(OrderService $subject, OrderInterface $result): OrderInterface + public function beforeSubmit(QuoteManagement $subject, Quote $quote, $orderData = []) { - $this->updateCouponUsages->execute($result, true); + /* if coupon code has been canceled then need to notify the customer */ + if (!$quote->getCouponCode() && $quote->dataHasChangedFor('coupon_code')) { + throw new NoSuchEntityException(__("The coupon code isn't valid. Verify the code and try again.")); + } - return $result; + $this->updateCouponUsages->execute($quote, true); } } diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleEmptyFromDateTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleEmptyFromDateTest.xml index 221f80b887fe5..f32442ca5bc98 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleEmptyFromDateTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleEmptyFromDateTest.xml @@ -83,9 +83,7 @@ <!-- Spot check the storefront --> <amOnPage url="$$product.custom_attributes[url_key]$$.html" stepKey="goToProductPage"/> <waitForPageLoad stepKey="waitForProductPageLoad"/> - <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart"/> - <waitForPageLoad stepKey="waitForAddToCart"/> - <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> + <actionGroup ref="StorefrontClickAddToCartOnProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage"/> <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCartPage"/> <actionGroup ref="StorefrontApplyCouponActionGroup" stepKey="applyCoupon"> <argument name="coupon" value="_defaultCoupon"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForCouponCodeTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForCouponCodeTest.xml index e2a65685bd97e..557a585858868 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForCouponCodeTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForCouponCodeTest.xml @@ -75,9 +75,7 @@ <!-- Spot check the storefront --> <amOnPage url="{{_defaultProduct.urlKey}}.html" stepKey="goToProductPage"/> <waitForPageLoad stepKey="waitForProductPageLoad"/> - <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart"/> - <waitForPageLoad stepKey="waitForAddToCart"/> - <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> + <actionGroup ref="StorefrontClickAddToCartOnProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage"/> <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCartPage"/> <actionGroup ref="StorefrontApplyCouponActionGroup" stepKey="applyCoupon"> <argument name="coupon" value="_defaultCoupon"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForGeneratedCouponTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForGeneratedCouponTest.xml index 9f4168575595a..e18a9eaadcd23 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForGeneratedCouponTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForGeneratedCouponTest.xml @@ -79,9 +79,7 @@ <!-- Spot check the storefront --> <amOnPage url="{{_defaultProduct.urlKey}}.html" stepKey="goToProductPage"/> <waitForPageLoad stepKey="waitForProductPageLoad"/> - <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart"/> - <waitForPageLoad stepKey="waitForAddToCart"/> - <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> + <actionGroup ref="StorefrontClickAddToCartOnProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage"/> <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCartPage"/> <conditionalClick selector="{{StorefrontSalesRuleCartCouponSection.couponHeader}}" dependentSelector="{{StorefrontSalesRuleCartCouponSection.discountBlockActive}}" visible="false" stepKey="clickCouponHeader"/> <waitForElementVisible selector="{{StorefrontSalesRuleCartCouponSection.couponField}}" stepKey="waitForCouponField" /> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/CartPriceRuleForConfigurableProductTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/CartPriceRuleForConfigurableProductTest.xml index bc608c0e06086..ad1ff69a60901 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/CartPriceRuleForConfigurableProductTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/CartPriceRuleForConfigurableProductTest.xml @@ -126,15 +126,11 @@ <!-- Add the first product to the cart --> <amOnPage url="$$createConfigChildProduct1.sku$$.html" stepKey="goToProductPage1"/> <waitForPageLoad stepKey="waitForProductPageLoad1"/> - <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart1"/> - <waitForPageLoad stepKey="waitForAddToCart1"/> - <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> + <actionGroup ref="StorefrontClickAddToCartOnProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage"/> <!-- Add the second product to the cart --> <amOnPage url="$$createConfigChildProduct2.sku$$.html" stepKey="goToProductPage2"/> <waitForPageLoad stepKey="waitForProductPageLoad2"/> - <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart2"/> - <waitForPageLoad stepKey="waitForAddToCart2"/> - <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage2"/> + <actionGroup ref="StorefrontClickAddToCartOnProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage2"/> <!--View and edit cart--> <actionGroup ref="ClickViewAndEditCartFromMiniCartActionGroup" stepKey="clickViewAndEditCartFromMiniCart"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleCountryTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleCountryTest.xml index 51e25d3a7e255..eef5dadfbe5d8 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleCountryTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleCountryTest.xml @@ -66,9 +66,7 @@ <amOnPage url="$$createPreReqProduct.name$$.html" stepKey="goToProductPage"/> <waitForPageLoad stepKey="waitForProductPageLoad"/> <fillField selector="{{StorefrontProductActionSection.quantity}}" userInput="1" stepKey="fillQuantity"/> - <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart"/> - <waitForPageLoad stepKey="waitForAddToCart"/> - <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> + <actionGroup ref="StorefrontClickAddToCartOnProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage"/> <!-- Should not see the discount yet because we have not set country --> <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCartPage"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRulePostcodeTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRulePostcodeTest.xml index 420bc37d5c1b2..69097e3269fcb 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRulePostcodeTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRulePostcodeTest.xml @@ -70,9 +70,7 @@ <amOnPage url="$$createPreReqProduct.name$$.html" stepKey="goToProductPage"/> <waitForPageLoad stepKey="waitForProductPageLoad"/> <fillField selector="{{StorefrontProductActionSection.quantity}}" userInput="1" stepKey="fillQuantity"/> - <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart"/> - <waitForPageLoad stepKey="waitForAddToCart"/> - <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> + <actionGroup ref="StorefrontClickAddToCartOnProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage"/> <!-- Should not see the discount yet because we have not filled in postcode --> <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCartPage"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleQuantityTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleQuantityTest.xml index 279747f87d66d..18057965c28e1 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleQuantityTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleQuantityTest.xml @@ -68,9 +68,7 @@ <amOnPage url="$$createPreReqProduct.name$$.html" stepKey="goToProductPage"/> <waitForPageLoad stepKey="waitForProductPageLoad"/> <fillField selector="{{StorefrontProductActionSection.quantity}}" userInput="1" stepKey="fillQuantity"/> - <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart"/> - <waitForPageLoad stepKey="waitForAddToCart"/> - <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> + <actionGroup ref="StorefrontClickAddToCartOnProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage"/> <!-- Should not see the discount yet because we have only 1 item in our cart --> <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCartPage"/> @@ -81,9 +79,7 @@ <amOnPage url="$$createPreReqProduct.name$$.html" stepKey="goToProductPage2"/> <waitForPageLoad stepKey="waitForProductPageLoad2"/> <fillField selector="{{StorefrontProductActionSection.quantity}}" userInput="1" stepKey="fillQuantity2"/> - <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart2"/> - <waitForPageLoad stepKey="waitForAddToCart2"/> - <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage2"/> + <actionGroup ref="StorefrontClickAddToCartOnProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage2"/> <!-- Now we should see the discount because we have more than 1 item --> <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCartPage2"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleStateTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleStateTest.xml index a3f32c0781a52..c13b74b6990d0 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleStateTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleStateTest.xml @@ -66,9 +66,7 @@ <amOnPage url="$$createPreReqProduct.name$$.html" stepKey="goToProductPage"/> <waitForPageLoad stepKey="waitForProductPageLoad"/> <fillField selector="{{StorefrontProductActionSection.quantity}}" userInput="1" stepKey="fillQuantity"/> - <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart"/> - <waitForPageLoad stepKey="waitForAddToCart"/> - <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> + <actionGroup ref="StorefrontClickAddToCartOnProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage"/> <!-- Should not see the discount yet because we have not filled in postcode --> <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCartPage"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotalTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotalTest.xml index 39ac14315110e..97b75ae772f08 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotalTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotalTest.xml @@ -66,9 +66,7 @@ <amOnPage url="$$createPreReqProduct.name$$.html" stepKey="goToProductPage"/> <waitForPageLoad stepKey="waitForProductPageLoad"/> <fillField selector="{{StorefrontProductActionSection.quantity}}" userInput="1" stepKey="fillQuantity"/> - <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart"/> - <waitForPageLoad stepKey="waitForAddToCart"/> - <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> + <actionGroup ref="StorefrontClickAddToCartOnProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage"/> <!-- Should not see the discount yet because we have not exceeded $200 --> <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCartPage"/> @@ -79,9 +77,7 @@ <amOnPage url="$$createPreReqProduct.name$$.html" stepKey="goToProductPage2"/> <waitForPageLoad stepKey="waitForProductPageLoad2"/> <fillField selector="{{StorefrontProductActionSection.quantity}}" userInput="1" stepKey="fillQuantity2"/> - <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart2"/> - <waitForPageLoad stepKey="waitForAddToCart2"/> - <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage2"/> + <actionGroup ref="StorefrontClickAddToCartOnProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage2"/> <!-- Now we should see the discount because we exceeded $200 --> <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCartPage2"/> diff --git a/app/code/Magento/SalesRule/etc/di.xml b/app/code/Magento/SalesRule/etc/di.xml index c4bc9c3a6decb..05bd801c3b99f 100644 --- a/app/code/Magento/SalesRule/etc/di.xml +++ b/app/code/Magento/SalesRule/etc/di.xml @@ -191,6 +191,8 @@ </type> <type name="Magento\Sales\Model\Service\OrderService"> <plugin name="coupon_uses_decrement_plugin" type="Magento\SalesRule\Plugin\CouponUsagesDecrement" /> + </type> + <type name="\Magento\Quote\Model\QuoteManagement"> <plugin name="coupon_uses_increment_plugin" type="Magento\SalesRule\Plugin\CouponUsagesIncrement" sortOrder="20"/> </type> <preference diff --git a/app/code/Magento/SalesRule/etc/events.xml b/app/code/Magento/SalesRule/etc/events.xml index c55c37de71aac..0c8335b0a6716 100644 --- a/app/code/Magento/SalesRule/etc/events.xml +++ b/app/code/Magento/SalesRule/etc/events.xml @@ -39,4 +39,7 @@ <event name="sales_quote_collect_totals_before"> <observer name="salesrule_sales_quote_collect_totals_before" instance="\Magento\SalesRule\Observer\QuoteResetAppliedRulesObserver" /> </event> + <event name="sales_model_service_quote_submit_failure"> + <observer name="sales_rule_decrement_coupon_usage_quote_submit_failure" instance="\Magento\SalesRule\Observer\CouponUsagesDecrement" /> + </event> </config> diff --git a/app/code/Magento/SalesRule/view/frontend/requirejs-config.js b/app/code/Magento/SalesRule/view/frontend/requirejs-config.js index 21f49fb3080fc..484020a573f07 100644 --- a/app/code/Magento/SalesRule/view/frontend/requirejs-config.js +++ b/app/code/Magento/SalesRule/view/frontend/requirejs-config.js @@ -11,6 +11,9 @@ var config = { }, 'Magento_Checkout/js/model/shipping-save-processor': { 'Magento_SalesRule/js/model/shipping-save-processor-mixin': true + }, + 'Magento_Checkout/js/action/place-order': { + 'Magento_SalesRule/js/model/place-order-mixin': true } } } diff --git a/app/code/Magento/SalesRule/view/frontend/web/js/model/place-order-mixin.js b/app/code/Magento/SalesRule/view/frontend/web/js/model/place-order-mixin.js new file mode 100644 index 0000000000000..da4de3fa19c5e --- /dev/null +++ b/app/code/Magento/SalesRule/view/frontend/web/js/model/place-order-mixin.js @@ -0,0 +1,42 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +define([ + 'jquery', + 'mage/utils/wrapper', + 'Magento_Checkout/js/model/quote', + 'Magento_SalesRule/js/model/coupon', + 'Magento_Checkout/js/action/get-totals' +], function ($, wrapper, quote, coupon, getTotalsAction) { + 'use strict'; + + return function (placeOrderAction) { + return wrapper.wrap(placeOrderAction, function (originalAction, paymentData, messageContainer) { + var result; + + $.when( + result = originalAction(paymentData, messageContainer) + ).fail( + function () { + var deferred = $.Deferred(), + + /** + * Update coupon form + */ + updateCouponCallback = function () { + if (quote.totals() && !quote.totals()['coupon_code']) { + coupon.setCouponCode(''); + coupon.setIsApplied(false); + } + }; + + getTotalsAction([], deferred); + $.when(deferred).done(updateCouponCallback); + } + ); + + return result; + }); + }; +}); diff --git a/app/code/Magento/Swatches/Plugin/Eav/Model/Entity/Attribute/OptionManagement.php b/app/code/Magento/Swatches/Plugin/Eav/Model/Entity/Attribute/OptionManagement.php index 795c48f12ebcc..43a44534aa942 100644 --- a/app/code/Magento/Swatches/Plugin/Eav/Model/Entity/Attribute/OptionManagement.php +++ b/app/code/Magento/Swatches/Plugin/Eav/Model/Entity/Attribute/OptionManagement.php @@ -8,6 +8,9 @@ namespace Magento\Swatches\Plugin\Eav\Model\Entity\Attribute; use Magento\Catalog\Api\Data\ProductAttributeInterface; +use Magento\Catalog\Model\Product\Attribute\OptionManagement as CatalogOptionManagement; +use Magento\Eav\Api\Data\AttributeInterface; +use Magento\Eav\Api\Data\AttributeOptionInterface; use Magento\Eav\Model\AttributeRepository; use Magento\Store\Model\Store; use Magento\Swatches\Helper\Data; @@ -41,28 +44,61 @@ public function __construct( /** * Add swatch value to the attribute option * - * @param \Magento\Catalog\Model\Product\Attribute\OptionManagement $subject + * @param CatalogOptionManagement $subject * @param string $attributeCode - * @param \Magento\Eav\Api\Data\AttributeOptionInterface $option + * @param AttributeOptionInterface $option * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function beforeAdd( - \Magento\Catalog\Model\Product\Attribute\OptionManagement $subject, + CatalogOptionManagement $subject, ?string $attributeCode, - \Magento\Eav\Api\Data\AttributeOptionInterface $option + AttributeOptionInterface $option ) { - if (empty($attributeCode)) { + $attribute = $this->initAttribute($attributeCode); + if (!$attribute) { return; } - $attribute = $this->attributeRepository->get( - ProductAttributeInterface::ENTITY_TYPE_CODE, - $attributeCode - ); - if (!$attribute || !$this->swatchHelper->isSwatchAttribute($attribute)) { + + $optionId = $this->getNewOptionId($option); + $this->setSwatchAttributeOption($attribute, $option, $optionId); + } + + /** + * Update swatch value of attribute option + * + * @param CatalogOptionManagement $subject + * @param string $attributeCode + * @param int $optionId + * @param AttributeOptionInterface $option + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeUpdate( + CatalogOptionManagement $subject, + $attributeCode, + $optionId, + AttributeOptionInterface $option + ) { + $attribute = $this->initAttribute($attributeCode); + if (!$attribute) { return; } - $optionId = $this->getOptionId($option); - $optionsValue = $option->getValue(); + + $this->setSwatchAttributeOption($attribute, $option, (string)$optionId); + } + + /** + * Set attribute swatch option + * + * @param AttributeInterface $attribute + * @param AttributeOptionInterface $option + * @param string $optionId + */ + private function setSwatchAttributeOption( + AttributeInterface $attribute, + AttributeOptionInterface $option, + string $optionId + ): void { + $optionsValue = trim($option->getValue() ?: ''); if ($this->swatchHelper->isVisualSwatch($attribute)) { $attribute->setData('swatchvisual', ['value' => [$optionId => $optionsValue]]); } else { @@ -80,13 +116,40 @@ public function beforeAdd( } /** - * Returns option id + * Init swatch attribute * - * @param \Magento\Eav\Api\Data\AttributeOptionInterface $option + * @param string $attributeCode + * @return bool|AttributeInterface + */ + private function initAttribute($attributeCode) + { + if (empty($attributeCode)) { + return false; + } + $attribute = $this->attributeRepository->get( + ProductAttributeInterface::ENTITY_TYPE_CODE, + $attributeCode + ); + if (!$attribute || !$this->swatchHelper->isSwatchAttribute($attribute)) { + return false; + } + + return $attribute; + } + + /** + * Get option id to create new option + * + * @param AttributeOptionInterface $option * @return string */ - private function getOptionId(\Magento\Eav\Api\Data\AttributeOptionInterface $option) : string + private function getNewOptionId(AttributeOptionInterface $option): string { - return 'id_' . ($option->getValue() ?: 'new_option'); + $optionId = trim($option->getValue() ?: ''); + if (empty($optionId)) { + $optionId = 'new_option'; + } + + return 'id_' . $optionId; } } diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateVisualSwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateVisualSwatchTest.xml index 1a6c0341c0704..62a3c668fcf07 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateVisualSwatchTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateVisualSwatchTest.xml @@ -24,8 +24,7 @@ </before> <after> <!-- Clean up our modifications to the existing color attribute --> - <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="goToProductAttributes"/> - <waitForPageLoad stepKey="waitForProductAttributes"/> + <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="goToProductAttributes"/> <fillField selector="{{AdminProductAttributeGridSection.FilterByAttributeCode}}" userInput="color" stepKey="fillFilter"/> <click selector="{{AdminProductAttributeGridSection.Search}}" stepKey="clickSearch"/> <click selector="{{AdminProductAttributeGridSection.AttributeCode('color')}}" stepKey="clickRowToEdit"/> @@ -39,8 +38,7 @@ </after> <!-- Go to the edit page for the "color" attribute --> - <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="goToProductAttributes"/> - <waitForPageLoad stepKey="waitForProductAttributes"/> + <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="goToProductAttributes"/> <fillField selector="{{AdminProductAttributeGridSection.FilterByAttributeCode}}" userInput="color" stepKey="fillFilter"/> <click selector="{{AdminProductAttributeGridSection.Search}}" stepKey="clickSearch"/> <click selector="{{AdminProductAttributeGridSection.AttributeCode('color')}}" stepKey="clickRowToEdit"/> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/AdminDisablingSwatchTooltipsTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/AdminDisablingSwatchTooltipsTest.xml index 8fd21acbd51d9..4075d89e0894f 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/AdminDisablingSwatchTooltipsTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/AdminDisablingSwatchTooltipsTest.xml @@ -26,8 +26,7 @@ </before> <after> <!-- Clean up our modifications to the existing color attribute --> - <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="goToProductAttributes"/> - <waitForPageLoad stepKey="waitForProductAttributes"/> + <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="goToProductAttributes"/> <fillField selector="{{AdminProductAttributeGridSection.FilterByAttributeCode}}" userInput="color" stepKey="fillFilter"/> <click selector="{{AdminProductAttributeGridSection.Search}}" stepKey="clickSearch"/> @@ -48,8 +47,7 @@ </after> <!-- Go to the edit page for the "color" attribute --> - <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="goToProductAttributes"/> - <waitForPageLoad stepKey="waitForProductAttributes"/> + <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="goToProductAttributes"/> <fillField selector="{{AdminProductAttributeGridSection.FilterByAttributeCode}}" userInput="color" stepKey="fillFilter"/> <click selector="{{AdminProductAttributeGridSection.Search}}" stepKey="clickSearch"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartGuestSimpleTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartGuestSimpleTest.xml index b1e91886960c5..9359f7348e8a2 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartGuestSimpleTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartGuestSimpleTest.xml @@ -70,7 +70,7 @@ <!-- Ensure tax won't be shown in the cart --> <actionGroup ref="ChangeToDefaultTaxConfigurationUIActionGroup" stepKey="changeToDefaultTaxConfiguration"/> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> </after> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartGuestVirtualTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartGuestVirtualTest.xml index 8a04156f3d857..4dea418c8321e 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartGuestVirtualTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartGuestVirtualTest.xml @@ -70,7 +70,7 @@ <!-- Ensure tax won't be shown in the cart --> <actionGroup ref="ChangeToDefaultTaxConfigurationUIActionGroup" stepKey="changeToDefaultTaxConfiguration"/> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> <deleteData createDataKey="virtualProduct1" stepKey="deleteVirtualProduct1"/> </after> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartLoggedInSimpleTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartLoggedInSimpleTest.xml index b76f015679ae2..018594c39ba67 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartLoggedInSimpleTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartLoggedInSimpleTest.xml @@ -84,7 +84,7 @@ <!-- Ensure tax won't be shown in the cart --> <actionGroup ref="ChangeToDefaultTaxConfigurationUIActionGroup" stepKey="changeToDefaultTaxConfiguration"/> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> </after> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartLoggedInVirtualTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartLoggedInVirtualTest.xml index 5f98093ec874f..09ffca716e318 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartLoggedInVirtualTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartLoggedInVirtualTest.xml @@ -83,7 +83,7 @@ <!-- Ensure tax won't be shown in the cart --> <actionGroup ref="ChangeToDefaultTaxConfigurationUIActionGroup" stepKey="changeToDefaultTaxConfiguration"/> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> <deleteData createDataKey="virtualProduct1" stepKey="deleteVirtualProduct1"/> </after> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutGuestSimpleTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutGuestSimpleTest.xml index d005f4b657448..3c72b5177e692 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutGuestSimpleTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutGuestSimpleTest.xml @@ -70,7 +70,7 @@ <!-- Ensure tax won't be shown in the cart --> <actionGroup ref="ChangeToDefaultTaxConfigurationUIActionGroup" stepKey="changeToDefaultTaxConfiguration"/> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> </after> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutGuestVirtualTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutGuestVirtualTest.xml index d1fc0654fc496..4cd508b03d156 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutGuestVirtualTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutGuestVirtualTest.xml @@ -70,7 +70,7 @@ <!-- Ensure tax won't be shown in the cart --> <actionGroup ref="ChangeToDefaultTaxConfigurationUIActionGroup" stepKey="changeToDefaultTaxConfiguration"/> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> <deleteData createDataKey="virtualProduct1" stepKey="deleteVirtualProduct1"/> </after> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutLoggedInSimpleTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutLoggedInSimpleTest.xml index 18a1a11d35fd2..43d12bf848f13 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutLoggedInSimpleTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutLoggedInSimpleTest.xml @@ -70,7 +70,7 @@ <!-- Ensure tax won't be shown in the cart --> <actionGroup ref="ChangeToDefaultTaxConfigurationUIActionGroup" stepKey="changeToDefaultTaxConfiguration"/> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> </after> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutLoggedInVirtualTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutLoggedInVirtualTest.xml index 35a483da7f690..949d9a89b656f 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutLoggedInVirtualTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutLoggedInVirtualTest.xml @@ -70,7 +70,7 @@ <!-- Ensure tax won't be shown in the cart --> <actionGroup ref="ChangeToDefaultTaxConfigurationUIActionGroup" stepKey="changeToDefaultTaxConfiguration"/> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> <deleteData createDataKey="virtualProduct1" stepKey="deleteVirtualProduct1"/> </after> diff --git a/app/code/Magento/Ui/Test/Mftf/ActionGroup/StorefrontAssertErrorMessageActionGroup.xml b/app/code/Magento/Ui/Test/Mftf/ActionGroup/StorefrontAssertErrorMessageActionGroup.xml new file mode 100644 index 0000000000000..fa9b7c377e32b --- /dev/null +++ b/app/code/Magento/Ui/Test/Mftf/ActionGroup/StorefrontAssertErrorMessageActionGroup.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="StorefrontAssertErrorMessageActionGroup"> + <arguments> + <argument name="message" type="string"/> + <argument name="messageType" type="string" defaultValue="success"/> + </arguments> + + <see userInput="{{message}}" selector="{{StorefrontMessagesSection.messageByType(messageType)}}" stepKey="verifyMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Ui/Test/Mftf/Section/StorefrontMessagesSection.xml b/app/code/Magento/Ui/Test/Mftf/Section/StorefrontMessagesSection.xml index c58479a7b73e5..f46e25a832134 100644 --- a/app/code/Magento/Ui/Test/Mftf/Section/StorefrontMessagesSection.xml +++ b/app/code/Magento/Ui/Test/Mftf/Section/StorefrontMessagesSection.xml @@ -12,5 +12,6 @@ <element name="success" type="text" selector="div.message-success.success.message"/> <element name="error" type="text" selector="div.message-error.error.message"/> <element name="noticeMessage" type="text" selector="div.message.notice div"/> + <element name="messageByType" type="text" selector=".messages .message-{{messageType}}" parameterized="true" /> </section> </sections> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductAfterImportTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductAfterImportTest.xml index a70065dc1d307..2dd7df9cbb548 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductAfterImportTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductAfterImportTest.xml @@ -43,7 +43,7 @@ <deleteData createDataKey="simpleSubCategory1" stepKey="deleteSimpleSubCategory1"/> <comment userInput="Disable config to generate category/product URL Rewrites " stepKey="commentDisableConfig" /> <magentoCLI command="config:set catalog/seo/generate_category_product_rewrites 1" stepKey="disableGenerateUrlRewrite"/> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="amOnLogoutPage"/> </after> <comment userInput="1. Log in to Admin " stepKey="commentAdminLogin" /> diff --git a/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection.php b/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection.php index 61d444f786ca8..b10b62ec21167 100644 --- a/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection.php +++ b/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection.php @@ -575,10 +575,15 @@ protected function _joinProductNameTable() $storeId = $this->_storeManager->getStore(\Magento\Store\Model\Store::ADMIN_CODE)->getId(); $entityMetadata = $this->getMetadataPool()->getMetadata(ProductInterface::class); + $linkField = $entityMetadata->getLinkField(); $this->getSelect()->join( + ['product_entity' => $this->getTable('catalog_product_entity')], + 'product_entity.entity_id = main_table.product_id', + [] + )->join( ['product_name_table' => $attribute->getBackendTable()], - 'product_name_table.' . $entityMetadata->getLinkField() . ' = main_table.product_id' . + 'product_name_table.' . $linkField . ' = product_entity.' . $linkField . ' AND product_name_table.store_id = ' . $storeId . ' AND product_name_table.attribute_id = ' . @@ -588,6 +593,7 @@ protected function _joinProductNameTable() $this->_isProductNameJoined = true; } + return $this; } diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/AdminDeleteCustomerWishListItemTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/AdminDeleteCustomerWishListItemTest.xml index af229b3507077..32bec763b2fd6 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Test/AdminDeleteCustomerWishListItemTest.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/AdminDeleteCustomerWishListItemTest.xml @@ -14,6 +14,7 @@ <stories value="Wishlist items deleting"/> <title value="Admin deletes an item from customer wishlist"/> <description value="Admin Should be able delete items from customer wishlist"/> + <severity value="AVERAGE"/> <testCaseId value="MC-35170"/> <group value="wishlist"/> </annotations> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontDisabledCustomerWishlistFunctionalityTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontDisabledCustomerWishlistFunctionalityTest.xml index 17d3ff1009b9b..65cce1dcfc1c3 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontDisabledCustomerWishlistFunctionalityTest.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontDisabledCustomerWishlistFunctionalityTest.xml @@ -15,6 +15,7 @@ <title value="Wishlist Functionality is disabled in system configurations and not visible on FE"/> <description value="Customer should not see wishlist functionality if it's disabled"/> <testCaseId value="MC-35200"/> + <severity value="AVERAGE"/> <group value="wishlist"/> <group value="configuration"/> </annotations> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontShareWishlistWithMoreThanMaximumAllowedEmailsQtyTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontShareWishlistWithMoreThanMaximumAllowedEmailsQtyTest.xml index 7ec06e3f3cf4d..281272293e6a9 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontShareWishlistWithMoreThanMaximumAllowedEmailsQtyTest.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontShareWishlistWithMoreThanMaximumAllowedEmailsQtyTest.xml @@ -15,6 +15,7 @@ <title value="Sharing wishlist with more than Maximum Allowed Emails qty"/> <description value="Customer should not have a possibility share wishlist with more than maximum allowed emails qty"/> <testCaseId value="MC-35167"/> + <severity value="AVERAGE"/> <group value="wishlist"/> <group value="configuration"/> </annotations> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontShareWishlistWithMoreThanMaximumAllowedTextLengthLimitTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontShareWishlistWithMoreThanMaximumAllowedTextLengthLimitTest.xml new file mode 100644 index 0000000000000..65e67f75eb7e8 --- /dev/null +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontShareWishlistWithMoreThanMaximumAllowedTextLengthLimitTest.xml @@ -0,0 +1,59 @@ +<?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="StorefrontShareWishlistWithMoreThanMaximumAllowedTextLengthLimitTest"> + <annotations> + <features value="Wishlist"/> + <stories value="Sharing wishlist with more than Maximum Allowed Text Length Limit"/> + <title value="Sharing wishlist with more than Maximum Allowed Text Length Limit"/> + <description value="Customer should not have a possibility share wishlist with more than maximum allowed Email Text Length Limit"/> + <testCaseId value="MC-35647"/> + <severity value="AVERAGE"/> + <group value="wishlist"/> + <group value="configuration"/> + </annotations> + <before> + <magentoCLI command="config:set wishlist/email/text_limit 10" stepKey="changeTextLengthLimit"/> + <magentoCLI command="cache:clean config" stepKey="cleanCache"/> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <magentoCLI command="cron:run --group=index" stepKey="runCronIndexer"/> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + </before> + <after> + <magentoCLI command="config:set wishlist/email/text_limit 255" stepKey="returnDefaultValue"/> + <magentoCLI command="cache:clean config" stepKey="cacheClean"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="logoutCustomer"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + </after> + + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginToStorefrontAccount"> + <argument name="Customer" value="$createCustomer$"/> + </actionGroup> + <actionGroup ref="OpenStoreFrontProductPageActionGroup" stepKey="openProductPage"> + <argument name="productUrlKey" value="$$createProduct.custom_attributes[url_key]$$"/> + </actionGroup> + <actionGroup ref="StorefrontCustomerAddProductToWishlistActionGroup" stepKey="addToWishlistProduct"> + <argument name="productVar" value="$createProduct$"/> + </actionGroup> + <actionGroup ref="StorefrontShareCustomerWishlistActionGroup" stepKey="shareWishList"> + <argument name="email" value="{{Wishlist.shareInfo_emails}}"/> + <argument name="message" value="{{Wishlist.shareInfo_message}}"/> + </actionGroup> + <actionGroup ref="AssertMessageCustomerChangeAccountInfoActionGroup" stepKey="assertMessage"> + <argument name="message" value="Message length must not exceed 10 symbols"/> + <argument name="messageType" value="error"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Wishlist/Test/Unit/Model/ResourceModel/Item/CollectionTest.php b/app/code/Magento/Wishlist/Test/Unit/Model/ResourceModel/Item/CollectionTest.php index 72705acb8cd06..28705d54e6e20 100644 --- a/app/code/Magento/Wishlist/Test/Unit/Model/ResourceModel/Item/CollectionTest.php +++ b/app/code/Magento/Wishlist/Test/Unit/Model/ResourceModel/Item/CollectionTest.php @@ -54,7 +54,8 @@ class CollectionTest extends TestCase /** @var string */ protected $sql = "SELECT `main_table`.* FROM `testMainTableName` AS `main_table` - INNER JOIN `testBackendTableName` AS `product_name_table` ON product_name_table.entity_id = main_table.product_id + INNER JOIN `testEntityTableName` AS `product_entity` ON product_entity.entity_id = main_table.product_id + INNER JOIN `testBackendTableName` AS `product_name_table` ON product_name_table.entity_id = product_entity.entity_id AND product_name_table.store_id = 1 AND product_name_table.attribute_id = 12 WHERE (INSTR(product_name_table.value, 'TestProductName'))"; @@ -90,18 +91,13 @@ protected function setUp(): void ->expects($this->any()) ->method('getConnection') ->willReturn($connection); - $resource - ->expects($this->any()) - ->method('getMainTable') - ->willReturn('testMainTableName'); - $resource - ->expects($this->any()) - ->method('getTableName') - ->willReturn('testMainTableName'); $resource ->expects($this->any()) ->method('getTable') - ->willReturn('testMainTableName'); + ->willReturnOnConsecutiveCalls( + 'testMainTableName', + 'testEntityTableName' + ); $catalogConfFactory = $this->createPartialMock( ConfigFactory::class, diff --git a/composer.json b/composer.json index 1b260cc122865..765cad085df47 100644 --- a/composer.json +++ b/composer.json @@ -88,7 +88,7 @@ "friendsofphp/php-cs-fixer": "~2.16.0", "lusitanian/oauth": "~0.8.10", "magento/magento-coding-standard": "*", - "magento/magento2-functional-testing-framework": "3.0.0-RC5", + "magento/magento2-functional-testing-framework": "^3.0", "pdepend/pdepend": "~2.7.1", "phpcompatibility/php-compatibility": "^9.3", "phpmd/phpmd": "^2.8.0", diff --git a/composer.lock b/composer.lock index e5614cfd0ac99..fdb3a8a3708b7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f3674961f96b48fdd025a6c94610c8eb", + "content-hash": "68246e4c5ac6555ff2d3d013c81c3537", "packages": [ { "name": "colinmollenhour/cache-backend-file", @@ -206,16 +206,6 @@ "ssl", "tls" ], - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], "time": "2020-04-08T08:27:21+00:00" }, { @@ -462,12 +452,6 @@ "Xdebug", "performance" ], - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - } - ], "time": "2020-03-01T12:26:26+00:00" }, { @@ -3924,20 +3908,6 @@ "x.509", "x509" ], - "funding": [ - { - "url": "https://github.com/terrafrost", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpseclib", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", - "type": "tidelift" - } - ], "time": "2020-04-04T23:17:33+00:00" }, { @@ -4474,20 +4444,6 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], "time": "2020-05-20T17:43:50+00:00" }, { @@ -4666,20 +4622,6 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], "time": "2020-05-30T20:35:19+00:00" }, { @@ -4729,20 +4671,6 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], "time": "2020-05-20T17:43:50+00:00" }, { @@ -5939,12 +5867,6 @@ "functional testing", "unit testing" ], - "funding": [ - { - "url": "https://opencollective.com/codeception", - "type": "open_collective" - } - ], "time": "2020-05-24T13:58:47+00:00" }, { @@ -6517,20 +6439,6 @@ "redis", "xcache" ], - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", - "type": "tidelift" - } - ], "time": "2020-05-27T16:24:54+00:00" }, { @@ -6654,20 +6562,6 @@ "constructor", "instantiate" ], - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", - "type": "tidelift" - } - ], "time": "2020-05-29T17:27:14+00:00" }, { @@ -6730,20 +6624,6 @@ "parser", "php" ], - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", - "type": "tidelift" - } - ], "time": "2020-05-25T17:44:05+00:00" }, { @@ -7203,16 +7083,16 @@ }, { "name": "magento/magento2-functional-testing-framework", - "version": "3.0.0-RC5", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/magento/magento2-functional-testing-framework.git", - "reference": "e5126f4eb476e227e3b668b622159c917f123175" + "reference": "8d98efa7434a30ab9e82ef128c430ef8e3a50699" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/magento/magento2-functional-testing-framework/zipball/e5126f4eb476e227e3b668b622159c917f123175", - "reference": "e5126f4eb476e227e3b668b622159c917f123175", + "url": "https://api.github.com/repos/magento/magento2-functional-testing-framework/zipball/8d98efa7434a30ab9e82ef128c430ef8e3a50699", + "reference": "8d98efa7434a30ab9e82ef128c430ef8e3a50699", "shasum": "" }, "require": { @@ -7237,6 +7117,7 @@ "spomky-labs/otphp": "^10.0", "symfony/console": "^4.4", "symfony/finder": "^5.0", + "symfony/http-foundation": "^5.0", "symfony/mime": "^5.0", "symfony/process": "^4.4", "vlucas/phpdotenv": "^2.4", @@ -7288,7 +7169,7 @@ "magento", "testing" ], - "time": "2020-06-15T19:51:46+00:00" + "time": "2020-07-09T21:26:19+00:00" }, { "name": "mikey179/vfsstream", @@ -9855,20 +9736,6 @@ ], "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], "time": "2020-05-27T08:34:37+00:00" }, { @@ -9930,20 +9797,6 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], "time": "2020-05-24T12:18:07+00:00" }, { @@ -10007,20 +9860,6 @@ "mime", "mime-type" ], - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], "time": "2020-05-25T12:33:44+00:00" }, { @@ -10196,20 +10035,6 @@ "portable", "shim" ], - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], "time": "2020-05-12T16:47:27+00:00" }, { @@ -10323,20 +10148,6 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], "time": "2020-05-20T17:43:50+00:00" }, { @@ -10703,8 +10514,7 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": { - "magento/composer": 20, - "magento/magento2-functional-testing-framework": 5 + "magento/composer": 20 }, "prefer-stable": true, "prefer-lowest": false, @@ -10727,6 +10537,5 @@ "ext-zip": "*", "lib-libxml": "*" }, - "platform-dev": [], - "plugin-api-version": "1.1.0" + "platform-dev": [] } diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeOptionManagementInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeOptionManagementInterfaceTest.php index 64f51b93cde50..2b628c05ae736 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeOptionManagementInterfaceTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeOptionManagementInterfaceTest.php @@ -7,14 +7,21 @@ use Magento\Eav\Api\Data\AttributeOptionInterface; use Magento\Eav\Api\Data\AttributeOptionLabelInterface; +use Magento\Framework\Webapi\Rest\Request; use Magento\TestFramework\TestCase\WebapiAbstract; +/** + * Class to test Eav Option Management functionality + */ class ProductAttributeOptionManagementInterfaceTest extends WebapiAbstract { const SERVICE_NAME = 'catalogProductAttributeOptionManagementV1'; const SERVICE_VERSION = 'V1'; const RESOURCE_PATH = '/V1/products/attributes'; + /** + * Test to get attribute options + */ public function testGetItems() { $testAttributeCode = 'quantity_and_stock_status'; @@ -29,64 +36,56 @@ public function testGetItems() ], ]; - $serviceInfo = [ - 'rest' => [ - 'resourcePath' => self::RESOURCE_PATH . '/' . $testAttributeCode . '/options', - 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET, - ], - 'soap' => [ - 'service' => self::SERVICE_NAME, - 'serviceVersion' => self::SERVICE_VERSION, - 'operation' => self::SERVICE_NAME . 'getItems', - ], - ]; - - $response = $this->_webApiCall($serviceInfo, ['attributeCode' => $testAttributeCode]); + $response = $this->webApiCallAttributeOptions( + $testAttributeCode, + Request::HTTP_METHOD_GET, + 'getItems', + ['attributeCode' => $testAttributeCode] + ); $this->assertIsArray($response); $this->assertEquals($expectedOptions, $response); } /** + * Test to add attribute option + * + * @param array $optionData * @magentoApiDataFixture Magento/Catalog/Model/Product/Attribute/_files/select_attribute.php * @dataProvider addDataProvider */ - public function testAdd($optionData) + public function testAdd(array $optionData) { $testAttributeCode = 'select_attribute'; - $serviceInfo = [ - 'rest' => [ - 'resourcePath' => self::RESOURCE_PATH . '/' . $testAttributeCode . '/options', - 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, - ], - 'soap' => [ - 'service' => self::SERVICE_NAME, - 'serviceVersion' => self::SERVICE_VERSION, - 'operation' => self::SERVICE_NAME . 'add', - ], - ]; - - $response = $this->_webApiCall( - $serviceInfo, + $response = $this->webApiCallAttributeOptions( + $testAttributeCode, + Request::HTTP_METHOD_POST, + 'add', [ 'attributeCode' => $testAttributeCode, 'option' => $optionData, ] ); - $this->assertNotNull($response); - $updatedData = $this->getAttributeOptions($testAttributeCode); - $lastOption = array_pop($updatedData); - $this->assertEquals( - $optionData[AttributeOptionInterface::STORE_LABELS][0][AttributeOptionLabelInterface::LABEL], - $lastOption['label'] - ); + $this->assertTrue(is_numeric($response)); + /* Check new option labels by stores */ + $expectedStoreLabels = [ + 'all' => $optionData[AttributeOptionLabelInterface::LABEL], + 'default' => $optionData[AttributeOptionInterface::STORE_LABELS][0][AttributeOptionLabelInterface::LABEL], + ]; + foreach ($expectedStoreLabels as $store => $label) { + $option = $this->getAttributeOption($testAttributeCode, $label, $store); + $this->assertNotNull($option); + $this->assertEquals($response, $option['value']); + } } /** + * Data provider for adding attribute option + * * @return array */ - public function addDataProvider() + public function addDataProvider(): array { $optionPayload = [ AttributeOptionInterface::LABEL => 'new color', @@ -114,62 +113,111 @@ public function addDataProvider() 'option_with_value_node_that_is_a_number' => [ array_merge($optionPayload, [AttributeOptionInterface::VALUE => '123']) ], - ]; } /** + * Test to delete attribute option + * * @magentoApiDataFixture Magento/Catalog/Model/Product/Attribute/_files/select_attribute.php */ public function testDelete() { $attributeCode = 'select_attribute'; - //get option Id $optionList = $this->getAttributeOptions($attributeCode); $this->assertGreaterThan(0, count($optionList)); $lastOption = array_pop($optionList); $this->assertNotEmpty($lastOption['value']); $optionId = $lastOption['value']; - $serviceInfo = [ - 'rest' => [ - 'resourcePath' => self::RESOURCE_PATH . '/' . $attributeCode . '/options/' . $optionId, - 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_DELETE, - ], - 'soap' => [ - 'service' => self::SERVICE_NAME, - 'serviceVersion' => self::SERVICE_VERSION, - 'operation' => self::SERVICE_NAME . 'delete', - ], - ]; - $this->assertTrue($this->_webApiCall( - $serviceInfo, + $response = $this->webApiCallAttributeOptions( + $attributeCode, + Request::HTTP_METHOD_DELETE, + 'delete', [ 'attributeCode' => $attributeCode, 'optionId' => $optionId, - ] - )); + ], + $optionId + ); + $this->assertTrue($response); $updatedOptions = $this->getAttributeOptions($attributeCode); $this->assertEquals($optionList, $updatedOptions); } /** - * @param $testAttributeCode + * Perform Web API call to the system under test + * + * @param string $attributeCode + * @param string $httpMethod + * @param string $soapMethod + * @param array $arguments + * @param null $storeCode + * @param null $optionId * @return array|bool|float|int|string */ - private function getAttributeOptions($testAttributeCode) - { + private function webApiCallAttributeOptions( + string $attributeCode, + string $httpMethod, + string $soapMethod, + array $arguments = [], + $optionId = null, + $storeCode = null + ) { $serviceInfo = [ 'rest' => [ - 'resourcePath' => self::RESOURCE_PATH . '/' . $testAttributeCode . '/options', - 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET, + 'resourcePath' => self::RESOURCE_PATH . '/' . $attributeCode . '/options' + . ($optionId ? '/' .$optionId : ''), + 'httpMethod' => $httpMethod, ], 'soap' => [ 'service' => self::SERVICE_NAME, 'serviceVersion' => self::SERVICE_VERSION, - 'operation' => self::SERVICE_NAME . 'getItems', + 'operation' => self::SERVICE_NAME . $soapMethod, ], ]; - return $this->_webApiCall($serviceInfo, ['attributeCode' => $testAttributeCode]); + + return $this->_webApiCall($serviceInfo, $arguments, null, $storeCode); + } + + /** + * @param string $testAttributeCode + * @param string|null $storeCode + * @return array|bool|float|int|string + */ + private function getAttributeOptions(string $testAttributeCode, ?string $storeCode = null) + { + return $this->webApiCallAttributeOptions( + $testAttributeCode, + Request::HTTP_METHOD_GET, + 'getItems', + ['attributeCode' => $testAttributeCode], + null, + $storeCode + ); + } + + /** + * @param string $attributeCode + * @param string $optionLabel + * @param string|null $storeCode + * @return array|null + */ + private function getAttributeOption( + string $attributeCode, + string $optionLabel, + ?string $storeCode = null + ): ?array { + $attributeOptions = $this->getAttributeOptions($attributeCode, $storeCode); + $option = null; + /** @var array $attributeOption */ + foreach ($attributeOptions as $attributeOption) { + if ($attributeOption['label'] === $optionLabel) { + $option = $attributeOption; + break; + } + } + + return $option; } } diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeOptionUpdateInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeOptionUpdateInterfaceTest.php new file mode 100644 index 0000000000000..dc3648f68b10c --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeOptionUpdateInterfaceTest.php @@ -0,0 +1,234 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Api; + +use Magento\Eav\Api\Data\AttributeOptionInterface; +use Magento\Eav\Api\Data\AttributeOptionLabelInterface; +use Magento\Framework\Webapi\Rest\Request; +use Magento\TestFramework\TestCase\WebapiAbstract; + +/** + * Class to test update Product Attribute Options + */ +class ProductAttributeOptionUpdateInterfaceTest extends WebapiAbstract +{ + private const SERVICE_NAME_UPDATE = 'catalogProductAttributeOptionUpdateV1'; + private const SERVICE_NAME = 'catalogProductAttributeOptionManagementV1'; + private const SERVICE_VERSION = 'V1'; + private const RESOURCE_PATH = '/V1/products/attributes'; + + /** + * Test to update attribute option + * + * @magentoApiDataFixture Magento/Catalog/Model/Product/Attribute/_files/select_attribute.php + */ + public function testUpdate() + { + $testAttributeCode = 'select_attribute'; + $optionData = [ + AttributeOptionInterface::LABEL => 'Fixture Option Changed', + AttributeOptionInterface::VALUE => 'option_value', + AttributeOptionInterface::STORE_LABELS => [ + [ + AttributeOptionLabelInterface::LABEL => 'Store Label Changed', + AttributeOptionLabelInterface::STORE_ID => 1, + ], + ], + ]; + + $existOptionLabel = 'Fixture Option'; + $existAttributeOption = $this->getAttributeOption($testAttributeCode, $existOptionLabel, 'all'); + $optionId = $existAttributeOption['value']; + + $response = $this->webApiCallAttributeOptions( + $testAttributeCode, + Request::HTTP_METHOD_PUT, + 'update', + [ + 'attributeCode' => $testAttributeCode, + 'optionId' => $optionId, + 'option' => $optionData, + ], + $optionId + ); + + $this->assertTrue($response); + + /* Check update option labels by stores */ + $expectedStoreLabels = [ + 'all' => $optionData[AttributeOptionLabelInterface::LABEL], + 'default' => $optionData[AttributeOptionInterface::STORE_LABELS][0][AttributeOptionLabelInterface::LABEL], + ]; + foreach ($expectedStoreLabels as $store => $label) { + $this->assertNotNull($this->getAttributeOption($testAttributeCode, $label, $store)); + } + } + + /** + * Test to update option with already exist exception + * + * Test to except case when the two options has a same label + * + * @magentoApiDataFixture Magento/Catalog/Model/Product/Attribute/_files/select_attribute.php + */ + public function testUpdateWithAlreadyExistsException() + { + $this->expectExceptionMessage("Admin store attribute option label '%1' is already exists."); + $testAttributeCode = 'select_attribute'; + + $newOptionData = [ + AttributeOptionInterface::LABEL => 'New Option', + AttributeOptionInterface::VALUE => 'new_option_value', + ]; + $newOptionId = $this->webApiCallAttributeOptions( + $testAttributeCode, + Request::HTTP_METHOD_POST, + 'add', + [ + 'attributeCode' => $testAttributeCode, + 'option' => $newOptionData, + ] + ); + + $editOptionData = [ + AttributeOptionInterface::LABEL => 'Fixture Option', + AttributeOptionInterface::VALUE => $newOptionId, + ]; + $this->webApiCallAttributeOptions( + $testAttributeCode, + Request::HTTP_METHOD_PUT, + 'update', + [ + 'attributeCode' => $testAttributeCode, + 'optionId' => $newOptionId, + 'option' => $editOptionData, + ], + $newOptionId + ); + } + + /** + * Test to update option with not exist exception + * + * @magentoApiDataFixture Magento/Catalog/Model/Product/Attribute/_files/select_attribute.php + */ + public function testUpdateWithNotExistsException() + { + $this->expectExceptionMessage("The '%1' attribute doesn't include an option id '%2'."); + $testAttributeCode = 'select_attribute'; + + $newOptionData = [ + AttributeOptionInterface::LABEL => 'New Option', + AttributeOptionInterface::VALUE => 'new_option_value' + ]; + $newOptionId = (int)$this->webApiCallAttributeOptions( + $testAttributeCode, + Request::HTTP_METHOD_POST, + 'add', + [ + 'attributeCode' => $testAttributeCode, + 'option' => $newOptionData, + ] + ); + + $newOptionId++; + $editOptionData = [ + AttributeOptionInterface::LABEL => 'New Option Changed', + AttributeOptionInterface::VALUE => $newOptionId + ]; + $this->webApiCallAttributeOptions( + $testAttributeCode, + Request::HTTP_METHOD_PUT, + 'update', + [ + 'attributeCode' => $testAttributeCode, + 'optionId' => $newOptionId, + 'option' => $editOptionData, + ], + $newOptionId + ); + } + + /** + * Perform Web API call to the system under test + * + * @param string $attributeCode + * @param string $httpMethod + * @param string $soapMethod + * @param array $arguments + * @param null $storeCode + * @param null $optionId + * @return array|bool|float|int|string + */ + private function webApiCallAttributeOptions( + string $attributeCode, + string $httpMethod, + string $soapMethod, + array $arguments = [], + $optionId = null, + $storeCode = null + ) { + $resourcePath = self::RESOURCE_PATH . "/{$attributeCode}/options"; + if ($optionId) { + $resourcePath .= '/' . $optionId; + } + $serviceName = $soapMethod === 'update' ? self::SERVICE_NAME_UPDATE : self::SERVICE_NAME; + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => $resourcePath, + 'httpMethod' => $httpMethod, + ], + 'soap' => [ + 'service' => $serviceName, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => $serviceName . $soapMethod, + ], + ]; + + return $this->_webApiCall($serviceInfo, $arguments, null, $storeCode); + } + + /** + * @param string $attributeCode + * @param string $optionLabel + * @param string|null $storeCode + * @return array|null + */ + private function getAttributeOption( + string $attributeCode, + string $optionLabel, + ?string $storeCode = null + ): ?array { + $attributeOptions = $this->getAttributeOptions($attributeCode, $storeCode); + $option = null; + /** @var array $attributeOption */ + foreach ($attributeOptions as $attributeOption) { + if ($attributeOption['label'] === $optionLabel) { + $option = $attributeOption; + break; + } + } + + return $option; + } + + /** + * @param string $testAttributeCode + * @param string|null $storeCode + * @return array|bool|float|int|string + */ + private function getAttributeOptions(string $testAttributeCode, ?string $storeCode = null) + { + return $this->webApiCallAttributeOptions( + $testAttributeCode, + Request::HTTP_METHOD_GET, + 'getItems', + ['attributeCode' => $testAttributeCode], + null, + $storeCode + ); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/SpecialPriceStorageTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/SpecialPriceStorageTest.php index ef374dc1873cf..90fe075f91e30 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/SpecialPriceStorageTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/SpecialPriceStorageTest.php @@ -68,6 +68,35 @@ public function testGet() $this->assertEquals($product->getSpecialPrice(), $response[0]['price']); } + /** + * Test get method when special price is 0. + * + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testGetZeroValue() + { + $specialPrice = 0; + $productRepository = $this->objectManager->create(ProductRepositoryInterface::class); + $product = $productRepository->get(self::SIMPLE_PRODUCT_SKU, true); + $product->setData('special_price', $specialPrice); + $productRepository->save($product); + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => '/V1/products/special-price-information', + 'httpMethod' => Request::HTTP_METHOD_POST + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'Get', + ], + ]; + $response = $this->_webApiCall($serviceInfo, ['skus' => [self::SIMPLE_PRODUCT_SKU]]); + $this->assertNotEmpty($response); + $this->assertEquals($specialPrice, $response[0]['price']); + } + /** * Test update method. * diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/ReorderTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/ReorderTest.php index 7bece410a06f8..0baee2797bf5d 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/ReorderTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/ReorderTest.php @@ -108,7 +108,7 @@ public function testSimpleProductOutOfStock() /** @var \Magento\Catalog\Api\ProductRepositoryInterface $repository */ $productRepository = Bootstrap::getObjectManager() ->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); - $productSku = 'simple'; + $productSku = 'simple-2'; /** @var \Magento\Catalog\Api\Data\ProductInterface $product */ $product = $productRepository->get($productSku); diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/WsdlGenerationFromDataObjectTest.php b/dev/tests/api-functional/testsuite/Magento/Webapi/WsdlGenerationFromDataObjectTest.php index dadc2caef7a13..c43cb81683aac 100644 --- a/dev/tests/api-functional/testsuite/Magento/Webapi/WsdlGenerationFromDataObjectTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Webapi/WsdlGenerationFromDataObjectTest.php @@ -116,7 +116,7 @@ protected function _getWsdlContent($wsdlUrl) $responseDom->loadXML($responseContent), "Valid XML is always expected as a response for WSDL request." ); - return $responseContent; + return $responseDom->saveXML(); } /** @@ -207,7 +207,7 @@ protected function _checkComplexTypesDeclaration($wsdlContent) <xsd:sequence> <xsd:element name="id" minOccurs="1" maxOccurs="1" type="xsd:int"> <xsd:annotation> - <xsd:documentation></xsd:documentation> + <xsd:documentation/> <xsd:appinfo xmlns:inf="{$this->_soapUrl}"> <inf:min/> <inf:max/> @@ -231,7 +231,7 @@ protected function _checkComplexTypesDeclaration($wsdlContent) <xsd:sequence> <xsd:element name="entityId" minOccurs="1" maxOccurs="1" type="xsd:int"> <xsd:annotation> - <xsd:documentation></xsd:documentation> + <xsd:documentation/> <xsd:appinfo xmlns:inf="{$this->_soapUrl}"> <inf:min/> <inf:max/> @@ -266,7 +266,7 @@ protected function _checkComplexTypesDeclaration($wsdlContent) <xsd:sequence> <xsd:element name="result" minOccurs="1" maxOccurs="1" type="tns:TestModule5V2EntityAllSoapAndRest"> <xsd:annotation> - <xsd:documentation></xsd:documentation> + <xsd:documentation/> <xsd:appinfo xmlns:inf="{$this->_soapUrl}"> <inf:callInfo> <inf:callName>testModule5AllSoapAndRestV2Item</inf:callName> @@ -290,7 +290,7 @@ protected function _checkComplexTypesDeclaration($wsdlContent) <xsd:sequence> <xsd:element name="result" minOccurs="1" maxOccurs="1" type="tns:TestModule5V1EntityAllSoapAndRest"> <xsd:annotation> - <xsd:documentation></xsd:documentation> + <xsd:documentation/> <xsd:appinfo xmlns:inf="{$this->_soapUrl}"> <inf:callInfo> <inf:callName>testModule5AllSoapAndRestV1Item</inf:callName> @@ -331,7 +331,7 @@ protected function _checkReferencedTypeDeclaration($wsdlContent) <xsd:sequence> <xsd:element name="price" minOccurs="1" maxOccurs="1" type="xsd:int"> <xsd:annotation> - <xsd:documentation></xsd:documentation> + <xsd:documentation/> <xsd:appinfo xmlns:inf="{$this->_soapUrl}"> <inf:min/> <inf:max/> @@ -835,7 +835,7 @@ protected function _checkFaultsComplexTypeSection($wsdlContent) <xsd:sequence> <xsd:element name="key" minOccurs="1" maxOccurs="1" type="xsd:string"> <xsd:annotation> - <xsd:documentation></xsd:documentation> + <xsd:documentation/> <xsd:appinfo xmlns:inf="{$this->_soapUrl}"> <inf:maxLength/> </xsd:appinfo> @@ -843,7 +843,7 @@ protected function _checkFaultsComplexTypeSection($wsdlContent) </xsd:element> <xsd:element name="value" minOccurs="1" maxOccurs="1" type="xsd:string"> <xsd:annotation> - <xsd:documentation></xsd:documentation> + <xsd:documentation/> <xsd:appinfo xmlns:inf="{$this->_soapUrl}"> <inf:maxLength/> </xsd:appinfo> @@ -865,7 +865,7 @@ protected function _checkFaultsComplexTypeSection($wsdlContent) <xsd:sequence> <xsd:element name="message" minOccurs="1" maxOccurs="1" type="xsd:string"> <xsd:annotation> - <xsd:documentation></xsd:documentation> + <xsd:documentation/> <xsd:appinfo xmlns:inf="{$this->_baseUrl}/soap/{$this->_storeCode}?services=testModule5AllSoapAndRestV2"> <inf:maxLength/> </xsd:appinfo> @@ -888,7 +888,7 @@ protected function _checkFaultsComplexTypeSection($wsdlContent) <xsd:sequence> <xsd:element name="message" minOccurs="1" maxOccurs="1" type="xsd:string"> <xsd:annotation> - <xsd:documentation></xsd:documentation> + <xsd:documentation/> <xsd:appinfo xmlns:inf="{$this->_baseUrl}/soap/{$this->_storeCode}?services=testModule5AllSoapAndRestV1%2CtestModule5AllSoapAndRestV2"> <inf:maxLength/> </xsd:appinfo> diff --git a/dev/tests/integration/testsuite/Magento/Captcha/Observer/ResetAttemptForFrontendObserverTest.php b/dev/tests/integration/testsuite/Magento/Captcha/Observer/ResetAttemptForFrontendObserverTest.php index c0acf3344f60f..33c42d794bd78 100644 --- a/dev/tests/integration/testsuite/Magento/Captcha/Observer/ResetAttemptForFrontendObserverTest.php +++ b/dev/tests/integration/testsuite/Magento/Captcha/Observer/ResetAttemptForFrontendObserverTest.php @@ -34,7 +34,7 @@ protected function setUp(): void /** * @magentoDataFixture Magento/Captcha/_files/failed_logins_frontend.php */ - public function testSuccesfulLoginRemovesFailedAttempts() + public function testSuccessfulLoginRemovesFailedAttempts() { $customerEmail = 'mageuser@dummy.com'; $customerFactory = $this->objectManager->get(CustomerFactory::class); diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapperTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapperTest.php index a5c88fc7571a2..2bff6f5ce82f6 100644 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapperTest.php +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapperTest.php @@ -54,7 +54,7 @@ protected function setUp(): void $this->model = $this->objectManager->create( ProductDataMapper::class, [ - 'additionalFieldsProvider' => $additionalFieldsProvider + 'additionalFieldsProvider' => $additionalFieldsProvider, ] ); $this->eavConfig = $this->objectManager->get(Config::class); @@ -83,24 +83,24 @@ public function testMapSelectAttributeWithDifferentStoreLabels(): void $defaultStoreMap = [ $productId => [ 'store_id' => $defaultStore->getId(), - 'select_attribute' => $attributeValue, + 'select_attribute' => (int)$attributeValue, 'select_attribute_value' => 'Table_default', - ] + ], ]; $secondStoreMap = [ $productId => [ 'store_id' => $secondStore->getId(), - 'select_attribute' => $attributeValue, + 'select_attribute' => (int)$attributeValue, 'select_attribute_value' => 'Table_fixture_second_store', - ] + ], ]; $data = [ $productId => [ - $attributeId => $attributeValue - ] + $attributeId => $attributeValue, + ], ]; - $this->assertEquals($defaultStoreMap, $this->model->map($data, $defaultStore->getId(), [])); - $this->assertEquals($secondStoreMap, $this->model->map($data, $secondStore->getId(), [])); + $this->assertSame($defaultStoreMap, $this->model->map($data, $defaultStore->getId(), [])); + $this->assertSame($secondStoreMap, $this->model->map($data, $secondStore->getId(), [])); } /** diff --git a/dev/tests/integration/testsuite/Magento/ProductAlert/Model/EmailTest.php b/dev/tests/integration/testsuite/Magento/ProductAlert/Model/EmailTest.php index 5f2cee2368c98..94fe0e85a8ddf 100644 --- a/dev/tests/integration/testsuite/Magento/ProductAlert/Model/EmailTest.php +++ b/dev/tests/integration/testsuite/Magento/ProductAlert/Model/EmailTest.php @@ -3,24 +3,32 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\ProductAlert\Model; use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product; use Magento\Customer\Api\AccountManagementInterface; use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Helper\View; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\MailException; use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Store\Model\StoreManagerInterface; use Magento\Store\Model\Website; +use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\Mail\Template\TransportBuilderMock; +use Magento\TestFramework\ObjectManager; +use PHPUnit\Framework\TestCase; /** * Test for Magento\ProductAlert\Model\Email class. * * @magentoAppIsolation enabled + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class EmailTest extends \PHPUnit\Framework\TestCase +class EmailTest extends TestCase { /** * @var Email @@ -28,7 +36,7 @@ class EmailTest extends \PHPUnit\Framework\TestCase protected $_emailModel; /** - * @var \Magento\TestFramework\ObjectManager + * @var ObjectManager */ protected $_objectManager; @@ -38,7 +46,7 @@ class EmailTest extends \PHPUnit\Framework\TestCase protected $customerAccountManagement; /** - * @var \Magento\Customer\Helper\View + * @var View */ protected $_customerViewHelper; @@ -62,11 +70,11 @@ class EmailTest extends \PHPUnit\Framework\TestCase */ protected function setUp(): void { - $this->_objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->_objectManager = Bootstrap::getObjectManager(); $this->customerAccountManagement = $this->_objectManager->create( AccountManagementInterface::class ); - $this->_customerViewHelper = $this->_objectManager->create(\Magento\Customer\Helper\View::class); + $this->_customerViewHelper = $this->_objectManager->create(View::class); $this->transportBuilder = $this->_objectManager->get(TransportBuilderMock::class); $this->customerRepository = $this->_objectManager->create(CustomerRepositoryInterface::class); $this->productRepository = $this->_objectManager->create(ProductRepositoryInterface::class); @@ -100,7 +108,7 @@ public function testSend($isCustomerIdUsed) $this->_emailModel->setCustomerData($customer); } - /** @var \Magento\Catalog\Model\Product $product */ + /** @var Product $product */ $product = $this->productRepository->getById(1); $this->_emailModel->addPriceProduct($product); @@ -165,4 +173,36 @@ public function testEmailForDifferentCustomers(): void ); } } + + /** + * @magentoAppArea frontend + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + * @magentoDataFixture Magento/Store/_files/second_store_with_second_identity.php + */ + public function testScopedMessageIdentity() + { + /** @var Website $website */ + $website = $this->_objectManager->create(Website::class); + $website->load(1); + $this->_emailModel->setWebsite($website); + + /** @var StoreManagerInterface $storeManager */ + $storeManager = $this->_objectManager->create(StoreManagerInterface::class); + $store = $storeManager->getStore('fixture_second_store'); + $this->_emailModel->setStoreId($store->getId()); + + $customer = $this->customerRepository->getById(1); + $this->_emailModel->setCustomerData($customer); + + /** @var Product $product */ + $product = $this->productRepository->getById(1); + + $this->_emailModel->addPriceProduct($product); + $this->_emailModel->send(); + + $from = $this->transportBuilder->getSentMessage()->getFrom()[0]; + $this->assertEquals('Fixture Store Owner', $from->getName()); + $this->assertEquals('fixture.store.owner@example.com', $from->getEmail()); + } } diff --git a/dev/tests/integration/testsuite/Magento/Sales/CustomerData/LastOrderedItemsTest.php b/dev/tests/integration/testsuite/Magento/Sales/CustomerData/LastOrderedItemsTest.php index 6605d43c84264..3919f91a3241e 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/CustomerData/LastOrderedItemsTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/CustomerData/LastOrderedItemsTest.php @@ -42,7 +42,7 @@ public function testDefaultFormatterIsAppliedWhenBasicIntegration() $this->assertEquals( LastOrderedItems::SIDEBAR_ORDER_LIMIT, count($data['items']), - 'Section items count should not be greater then ' . LastOrderedItems::SIDEBAR_ORDER_LIMIT + 'Section items count should not be greater than ' . LastOrderedItems::SIDEBAR_ORDER_LIMIT ); } } diff --git a/dev/tests/integration/testsuite/Magento/Sales/Model/Order/Email/Sender/InvoiceSenderTest.php b/dev/tests/integration/testsuite/Magento/Sales/Model/Order/Email/Sender/InvoiceSenderTest.php index 60021c7086267..55af8e9d2ee62 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Model/Order/Email/Sender/InvoiceSenderTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Model/Order/Email/Sender/InvoiceSenderTest.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Sales\Model\Order\Email\Sender; use Magento\Customer\Api\CustomerRepositoryInterface; @@ -11,6 +13,7 @@ use Magento\Sales\Model\Order\Email\Container\InvoiceIdentity; use Magento\Sales\Model\Order\Invoice; use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\App\Area; use PHPUnit\Framework\TestCase; class InvoiceSenderTest extends TestCase @@ -39,27 +42,33 @@ protected function setUp(): void */ public function testSend() { - \Magento\TestFramework\Helper\Bootstrap::getInstance() - ->loadArea(\Magento\Framework\App\Area::AREA_FRONTEND); - $order = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create(\Magento\Sales\Model\Order::class); + Bootstrap::getInstance() + ->loadArea(Area::AREA_FRONTEND); + $order = Bootstrap::getObjectManager() + ->create(Order::class); $order->loadByIncrementId('100000001'); $order->setCustomerEmail('customer@example.com'); - $invoice = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Sales\Model\Order\Invoice::class + $invoice = Bootstrap::getObjectManager()->create( + Invoice::class ); $invoice->setOrder($order); - + $invoice->setTotalQty(1); + $invoice->setBaseSubtotal(50); + $invoice->setBaseTaxAmount(10); + $invoice->setBaseShippingAmount(5); /** @var InvoiceSender $invoiceSender */ - $invoiceSender = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create(\Magento\Sales\Model\Order\Email\Sender\InvoiceSender::class); + $invoiceSender = Bootstrap::getObjectManager() + ->create(InvoiceSender::class); $this->assertEmpty($invoice->getEmailSent()); $result = $invoiceSender->send($invoice, true); $this->assertTrue($result); $this->assertNotEmpty($invoice->getEmailSent()); + $this->assertEquals($invoice->getBaseSubtotal(), $order->getBaseSubtotal()); + $this->assertEquals($invoice->getBaseTaxAmount(), $order->getBaseTaxAmount()); + $this->assertEquals($invoice->getBaseShippingAmount(), $order->getBaseShippingAmount()); } /** diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_product_out_of_stock.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_product_out_of_stock.php index 9bd4c9b303cb9..e93e4143311b5 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_product_out_of_stock.php +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_product_out_of_stock.php @@ -4,25 +4,82 @@ * See COPYING.txt for license details. */ -use Magento\Sales\Api\Data\OrderInterfaceFactory; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Model\Order; -use Magento\TestFramework\Helper\Bootstrap; +use Magento\Sales\Model\Order\Address as OrderAddress; +use Magento\Sales\Model\Order\Item as OrderItem; +use Magento\Sales\Model\Order\Payment; +use Magento\Store\Model\StoreManagerInterface; use Magento\TestFramework\Workaround\Override\Fixture\Resolver; +use Magento\Catalog\Model\Product; +use Magento\TestFramework\Helper\Bootstrap; -Resolver::getInstance()->requireDataFixture( - 'Magento/Sales/_files/customer_order_item_with_product_and_custom_options.php' -); +Resolver::getInstance()->requireDataFixture('Magento/Sales/_files/default_rollback.php'); +Resolver::getInstance()->requireDataFixture('Magento/Customer/_files/customer.php'); +Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/product_simple_without_custom_options.php'); $objectManager = Bootstrap::getObjectManager(); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(ProductRepositoryInterface::class); +$product = $productRepository->get('simple-2'); +$billingAddress = $objectManager->create( + OrderAddress::class, + [ + 'data' => [ + 'region' => 'CA', + 'region_id' => '12', + 'postcode' => '11111', + 'lastname' => 'lastname', + 'firstname' => 'firstname', + 'street' => 'street', + 'city' => 'Los Angeles', + 'email' => 'admin@example.com', + 'telephone' => '11111111', + 'country_id' => 'US', + ], + ], +); +$billingAddress->setAddressType('billing'); +$shippingAddress = clone $billingAddress; +$shippingAddress->setId(null)->setAddressType('shipping'); + +/** @var Payment $payment */ +$payment = $objectManager->create(\Magento\Sales\Model\Order\Payment::class); +$payment->setMethod('checkmo'); + +/** @var OrderItem $orderItem */ +$orderItem = $objectManager->create(OrderItem::class); +$orderItem->setProductId($product->getId()) + ->setQtyOrdered(2) + ->setBasePrice($product->getPrice()) + ->setPrice($product->getPrice()) + ->setRowTotal($product->getPrice()) + ->setProductType('simple-2') + ->setName($product->getName()) + ->setSku($product->getSku()) + ->setProductOptions(['info_buyRequest' => ['qty' => 1]]); + /** @var Order $order */ -$order = $objectManager->get(OrderInterfaceFactory::class)->create()->loadByIncrementId('100000001'); -$order->setCustomerId(1)->setCustomerIsGuest(false)->save(); +$order = $objectManager->create(Order::class); +$order->setIncrementId('100000001') + ->setCustomerIsGuest(false) + ->setCustomerId(1) + ->setCustomerEmail('customer@null.com') + ->setBillingAddress($billingAddress) + ->setShippingAddress($shippingAddress) + ->setStoreId($objectManager->get(StoreManagerInterface::class)->getStore()->getId()) + ->addItem($orderItem) + ->setPayment($payment); +/** @var OrderRepositoryInterface $orderRepository */ +$orderRepository = $objectManager->create(OrderRepositoryInterface::class); +$orderRepository->save($order); // load product and set it out of stock -/** @var \Magento\Catalog\Api\ProductRepositoryInterface $repository */ -$productRepository = Bootstrap::getObjectManager()->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); -$productSku = 'simple'; -/** @var \Magento\Catalog\Model\Product $product */ +/** @var ProductRepositoryInterface $repository */ +$productRepository = Bootstrap::getObjectManager()->create(ProductRepositoryInterface::class); +$productSku = 'simple-2'; +/** @var Product $product */ $product = $productRepository->get($productSku); // set product as out of stock $product->setStockData( diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_product_out_of_stock_rollback.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_product_out_of_stock_rollback.php index c4b3a1a18b03a..e6ff6de159a17 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_product_out_of_stock_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_product_out_of_stock_rollback.php @@ -5,6 +5,8 @@ */ use Magento\TestFramework\Workaround\Override\Fixture\Resolver; +Resolver::getInstance()->requireDataFixture('Magento/Customer/_files/customer_rollback.php'); Resolver::getInstance()->requireDataFixture( - 'Magento/Sales/_files/customer_order_item_with_product_and_custom_options_rollback.php' + 'Magento/Catalog/_files/product_simple_without_custom_options_rollback.php' ); +Resolver::getInstance()->requireDataFixture('Magento/Sales/_files/default_rollback.php'); diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Plugin/CouponUsagesTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Plugin/CouponUsagesTest.php index f9a8b96ab1f2f..4ed096fa4418a 100644 --- a/dev/tests/integration/testsuite/Magento/SalesRule/Plugin/CouponUsagesTest.php +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Plugin/CouponUsagesTest.php @@ -3,35 +3,34 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\SalesRule\Plugin; use Magento\Framework\DataObject; use Magento\Framework\ObjectManagerInterface; -use Magento\Sales\Model\Order; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\QuoteManagement; +use Magento\Sales\Api\OrderManagementInterface; use Magento\Sales\Model\Service\OrderService; use Magento\SalesRule\Model\Coupon; use Magento\SalesRule\Model\ResourceModel\Coupon\Usage; use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; /** - * Test increasing coupon usages after after order placing and decreasing after order cancellation. + * Test increasing coupon usages after order placing and decreasing after order cancellation. * + * @magentoAppArea frontend * @magentoDbIsolation enabled * @magentoAppIsolation enabled */ -class CouponUsagesTest extends \PHPUnit\Framework\TestCase +class CouponUsagesTest extends TestCase { /** * @var ObjectManagerInterface */ private $objectManager; - /** - * @var Coupon - */ - private $coupon; - /** * @var Usage */ @@ -43,9 +42,9 @@ class CouponUsagesTest extends \PHPUnit\Framework\TestCase private $couponUsage; /** - * @var Order + * @var QuoteManagement */ - private $order; + private $quoteManagement; /** * @var OrderService @@ -58,36 +57,38 @@ class CouponUsagesTest extends \PHPUnit\Framework\TestCase protected function setUp(): void { $this->objectManager = Bootstrap::getObjectManager(); - $this->coupon = $this->objectManager->get(Coupon::class); $this->usage = $this->objectManager->get(Usage::class); $this->couponUsage = $this->objectManager->get(DataObject::class); - $this->order = $this->objectManager->get(Order::class); + $this->quoteManagement = $this->objectManager->get(QuoteManagement::class); $this->orderService = $this->objectManager->get(OrderService::class); } /** * Test increasing coupon usages after after order placing and decreasing after order cancellation. * - * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/SalesRule/_files/coupons_limited_order.php */ - public function testOrderCancellation() + public function testSubmitQuoteAndCancelOrder() { $customerId = 1; $couponCode = 'one_usage'; - $orderId = '100000001'; + $reservedOrderId = 'test01'; - $this->coupon->loadByCode($couponCode); - $this->order->loadByIncrementId($orderId); + /** @var Coupon $coupon */ + $coupon = $this->objectManager->get(Coupon::class); + $coupon->loadByCode($couponCode); + /** @var Quote $quote */ + $quote = $this->objectManager->get(Quote::class); + $quote->load($reservedOrderId, 'reserved_order_id'); // Make sure coupon usages value is incremented then order is placed. - $this->orderService->place($this->order); - $this->usage->loadByCustomerCoupon($this->couponUsage, $customerId, $this->coupon->getId()); - $this->coupon->loadByCode($couponCode); + $order = $this->quoteManagement->submit($quote); + $this->usage->loadByCustomerCoupon($this->couponUsage, $customerId, $coupon->getId()); + $coupon->loadByCode($couponCode); self::assertEquals( 1, - $this->coupon->getTimesUsed() + $coupon->getTimesUsed() ); self::assertEquals( 1, @@ -95,17 +96,66 @@ public function testOrderCancellation() ); // Make sure order coupon usages value is decremented then order is cancelled. - $this->orderService->cancel($this->order->getId()); - $this->usage->loadByCustomerCoupon($this->couponUsage, $customerId, $this->coupon->getId()); - $this->coupon->loadByCode($couponCode); + $this->orderService->cancel($order->getId()); + $this->usage->loadByCustomerCoupon($this->couponUsage, $customerId, $coupon->getId()); + $coupon->loadByCode($couponCode); self::assertEquals( 0, - $this->coupon->getTimesUsed() + $coupon->getTimesUsed() ); self::assertEquals( 0, $this->couponUsage->getTimesUsed() ); } + + /** + * Test to decrement coupon usages after exception on order placing + * + * @magentoDataFixture Magento/SalesRule/_files/coupons_limited_order.php + */ + public function testSubmitQuoteWithError() + { + $customerId = 1; + $couponCode = 'one_usage'; + $reservedOrderId = 'test01'; + $exceptionMessage = 'Some test exception'; + + /** @var Coupon $coupon */ + $coupon = $this->objectManager->get(Coupon::class); + $coupon->loadByCode($couponCode); + /** @var Quote $quote */ + $quote = $this->objectManager->get(Quote::class); + $quote->load($reservedOrderId, 'reserved_order_id'); + + /** @var OrderManagementInterface|MockObject $orderManagement */ + $orderManagement = $this->createMock(OrderManagementInterface::class); + $orderManagement->expects($this->once()) + ->method('place') + ->willThrowException(new \Exception($exceptionMessage)); + + /** @var QuoteManagement $quoteManagement */ + $quoteManagement = $this->objectManager->create( + QuoteManagement::class, + ['orderManagement' => $orderManagement] + ); + + try { + $quoteManagement->submit($quote); + } catch (\Exception $exception) { + $this->assertEquals($exceptionMessage, $exception->getMessage()); + + $this->usage->loadByCustomerCoupon($this->couponUsage, $customerId, $coupon->getId()); + $coupon->loadByCode($couponCode); + self::assertEquals( + 0, + $coupon->getTimesUsed() + ); + self::assertEquals( + 0, + $this->couponUsage->getTimesUsed() + ); + } + } } diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupons_limited.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupons_limited.php index ee477318f52b8..164f8c0d5b7c2 100644 --- a/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupons_limited.php +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupons_limited.php @@ -3,26 +3,34 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - use Magento\SalesRule\Model\Coupon; +use Magento\SalesRule\Model\ResourceModel\Rule\Collection; +use Magento\SalesRule\Model\Rule; +use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\Workaround\Override\Fixture\Resolver; Resolver::getInstance()->requireDataFixture('Magento/SalesRule/_files/rules.php'); -$collection = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\SalesRule\Model\ResourceModel\Rule\Collection::class +$collection = Bootstrap::getObjectManager()->create( + Collection::class ); $items = array_values($collection->getItems()); +/** @var Rule $rule */ +foreach ($items as $rule) { + $rule->setSimpleAction('by_percent') + ->setDiscountAmount(10) + ->save(); +} /** @var Coupon $coupon */ -$coupon = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(Coupon::class); +$coupon = Bootstrap::getObjectManager()->create(Coupon::class); $coupon->setRuleId($items[0]->getId()) ->setCode('one_usage') ->setType(0) ->setUsageLimit(1) ->save(); -$coupon = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(Coupon::class); +$coupon = Bootstrap::getObjectManager()->create(Coupon::class); $coupon->setRuleId($items[1]->getId()) ->setCode('one_usage_per_customer') ->setType(0) diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupons_limited_order.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupons_limited_order.php index 79ee6ffb91f14..83f17ef0d9d46 100644 --- a/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupons_limited_order.php +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupons_limited_order.php @@ -3,25 +3,28 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -use Magento\Sales\Model\Order; +use Magento\Quote\Model\Quote; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\Workaround\Override\Fixture\Resolver; Resolver::getInstance()->requireDataFixture('Magento/SalesRule/_files/coupons_limited.php'); -Resolver::getInstance()->requireDataFixture('Magento/Sales/_files/order.php'); +Resolver::getInstance()->requireDataFixture('Magento/Sales/_files/quote_with_customer.php'); $collection = Bootstrap::getObjectManager()->create( \Magento\SalesRule\Model\ResourceModel\Rule\Collection::class ); $items = array_values($collection->getItems()); -/** @var Order $order */ -$order = Bootstrap::getObjectManager()->create(Order::class); +/** @var Quote $quote */ +$quote = Bootstrap::getObjectManager()->create(Quote::class); +$quote->load('test01', 'reserved_order_id'); +$quote->getShippingAddress() + ->setShippingMethod('flatrate_flatrate') + ->setShippingDescription('Flat Rate - Fixed') + ->setCollectShippingRates(true) + ->collectShippingRates() + ->save(); -$order->loadByIncrementId('100000001') - ->setCouponCode('one_usage') +$quote->setCouponCode('one_usage') ->setAppliedRuleIds("{$items[0]->getId()}") - ->setCreatedAt('2014-10-25 10:10:10') - ->setCustomerId(1) ->save(); diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupons_limited_order_rollback.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupons_limited_order_rollback.php index f44b6d3a75c97..15e58a3e53da5 100644 --- a/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupons_limited_order_rollback.php +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupons_limited_order_rollback.php @@ -5,5 +5,5 @@ */ use Magento\TestFramework\Workaround\Override\Fixture\Resolver; +Resolver::getInstance()->requireDataFixture('Magento/Sales/_files/quote_with_customer_rollback.php'); Resolver::getInstance()->requireDataFixture('Magento/SalesRule/_files/coupons_limited_rollback.php'); -Resolver::getInstance()->requireDataFixture('Magento/Sales/_files/order_rollback.php'); diff --git a/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/root/lib/internal/Magento/Framework/Test/Unit/View/Element/UiComponentFactoryTest.php b/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/root/lib/internal/Magento/Framework/Test/Unit/View/Element/UiComponentFactoryTest.php index 5643ef10782e8..3152b4a6e0efb 100644 --- a/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/root/lib/internal/Magento/Framework/Test/Unit/View/Element/UiComponentFactoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/root/lib/internal/Magento/Framework/Test/Unit/View/Element/UiComponentFactoryTest.php @@ -115,7 +115,7 @@ public function testNonRootComponent() $name = "fieldset"; $context = $this->createMock(\Magento\Framework\View\Element\UiComponent\ContextInterface::class); $arguments = ['context' => $context]; - $defintionArguments = [ + $definitionArguments = [ 'componentType' => 'select', 'attributes' => [ 'class' => '\Some\Class', @@ -132,7 +132,7 @@ public function testNonRootComponent() $this->dataMock->expects($this->once()) ->method('get') ->with($name) - ->willReturn($defintionArguments); + ->willReturn($definitionArguments); $this->objectManagerMock->expects($this->once()) ->method('create') ->with('\Some\Class', $expectedArguments); diff --git a/dev/tests/integration/testsuite/Magento/Store/_files/second_store_with_second_identity.php b/dev/tests/integration/testsuite/Magento/Store/_files/second_store_with_second_identity.php new file mode 100644 index 0000000000000..495992a38c1fe --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Store/_files/second_store_with_second_identity.php @@ -0,0 +1,38 @@ +<?php +/** + * Create fixture store with second identity + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Config\Model\ResourceModel\Config; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\Store; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +Resolver::getInstance()->requireDataFixture('Magento/Store/_files/second_store.php'); + +$objectManager = Bootstrap::getObjectManager(); +$store = $objectManager->create(Store::class); +if ($storeId = $store->load('fixture_second_store', 'code')->getId()) { + /** @var Config $configResource */ + $configResource = $objectManager->get(Config::class); + $configResource->saveConfig( + 'trans_email/ident_general/name', + 'Fixture Store Owner', + ScopeInterface::SCOPE_STORES, + $storeId + ); + $configResource->saveConfig( + 'trans_email/ident_general/email', + 'fixture.store.owner@example.com', + ScopeInterface::SCOPE_STORES, + $storeId + ); + $scopeConfig = $objectManager->get(ScopeConfigInterface::class); + $scopeConfig->clean(); +} diff --git a/dev/tests/integration/testsuite/Magento/Store/_files/second_store_with_second_identity_rollback.php b/dev/tests/integration/testsuite/Magento/Store/_files/second_store_with_second_identity_rollback.php new file mode 100644 index 0000000000000..6769a640a13d1 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Store/_files/second_store_with_second_identity_rollback.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Config\Model\ResourceModel\Config; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\Store; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +$objectManager = Bootstrap::getObjectManager(); +$store = $objectManager->create(Store::class); +$storeId = $store->load('fixture_second_store', 'code')->getId(); + +if ($storeId) { + $configResource = $objectManager->get(Config::class); + $configResource->deleteConfig( + 'trans_email/ident_general/name', + ScopeInterface::SCOPE_STORES, + $storeId + ); + $configResource->deleteConfig( + 'trans_email/ident_general/email', + ScopeInterface::SCOPE_STORES, + $storeId + ); +} + +Resolver::getInstance()->requireDataFixture('Magento/Store/_files/second_store_rollback.php'); diff --git a/lib/internal/Magento/Framework/App/Utility/Files.php b/lib/internal/Magento/Framework/App/Utility/Files.php index 901bbbde3dc9f..5f5814c5c6568 100644 --- a/lib/internal/Magento/Framework/App/Utility/Files.php +++ b/lib/internal/Magento/Framework/App/Utility/Files.php @@ -1116,7 +1116,7 @@ public function getJsFilesForArea($area) } else { $frontendPaths = [BP . "/lib/web/mage"]; /* current structure of /lib/web/mage directory contains frontend javascript in the root, - backend javascript in subdirectories. That's why script shouldn't go recursive throught subdirectories + backend javascript in subdirectories. That's why script shouldn't go recursive through subdirectories to get js files for frontend */ $files = array_merge($files, self::getFiles($frontendPaths, '*.js', false)); } diff --git a/lib/internal/Magento/Framework/Locale/Test/Unit/CurrencyTest.php b/lib/internal/Magento/Framework/Locale/Test/Unit/CurrencyTest.php index f04300eeeb5ab..664540d33f7ad 100644 --- a/lib/internal/Magento/Framework/Locale/Test/Unit/CurrencyTest.php +++ b/lib/internal/Magento/Framework/Locale/Test/Unit/CurrencyTest.php @@ -41,8 +41,8 @@ class CurrencyTest extends TestCase const TEST_NONCACHED_CURRENCY_LOCALE = 'en_US'; const TEST_CACHED_CURRENCY = 'CAD'; const TEST_CACHED_CURRENCY_LOCALE = 'en_CA'; - const TEST_NONEXISTANT_CURRENCY = 'QQQ'; - const TEST_NONEXISTANT_CURRENCY_LOCALE = 'fr_FR'; + const TEST_NONEXISTENT_CURRENCY = 'QQQ'; + const TEST_NONEXISTENT_CURRENCY_LOCALE = 'fr_FR'; const TEST_EXCEPTION_CURRENCY = 'ZZZ'; const TEST_EXCEPTION_CURRENCY_LOCALE = 'es_ES'; @@ -146,9 +146,9 @@ public function testGetCurrencyCached() $this->assertEquals([self::TEST_CACHED_CURRENCY], $retrievedCurrencyObject->getCurrencyList()); } - public function testGetNonExistantCurrency() + public function testGetNonExistentCurrency() { - $options = new \Zend_Currency(null, self::TEST_NONEXISTANT_CURRENCY_LOCALE); + $options = new \Zend_Currency(null, self::TEST_NONEXISTENT_CURRENCY_LOCALE); $this->mockCurrencyFactory ->expects($this->once()) @@ -164,10 +164,10 @@ public function testGetNonExistantCurrency() ->method('dispatch'); $retrievedCurrencyObject = $this->testCurrencyObject - ->getCurrency(self::TEST_NONEXISTANT_CURRENCY); + ->getCurrency(self::TEST_NONEXISTENT_CURRENCY); $this->assertInstanceOf('Zend_Currency', $retrievedCurrencyObject); - $this->assertEquals(self::TEST_NONEXISTANT_CURRENCY_LOCALE, $retrievedCurrencyObject->getLocale()); + $this->assertEquals(self::TEST_NONEXISTENT_CURRENCY_LOCALE, $retrievedCurrencyObject->getLocale()); $this->assertEquals('euro', $retrievedCurrencyObject->getName()); $this->assertEquals(['EUR'], $retrievedCurrencyObject->getCurrencyList()); }