diff --git a/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php b/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php index 6e36339b8bff1..0e197fc811adc 100644 --- a/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php +++ b/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php @@ -53,7 +53,7 @@ class MassSchedule private $logger; /** - * @var OperationRepository + * @var OperationRepositoryInterface */ private $operationRepository; @@ -75,7 +75,7 @@ class MassSchedule * @param AsyncResponseInterfaceFactory $asyncResponseFactory * @param BulkManagementInterface $bulkManagement * @param LoggerInterface $logger - * @param OperationRepository $operationRepository + * @param OperationRepositoryInterface $operationRepository * @param UserContextInterface $userContext * @param Encryptor $encryptor */ @@ -85,7 +85,7 @@ public function __construct( AsyncResponseInterfaceFactory $asyncResponseFactory, BulkManagementInterface $bulkManagement, LoggerInterface $logger, - OperationRepository $operationRepository, + OperationRepositoryInterface $operationRepository, UserContextInterface $userContext, Encryptor $encryptor ) { @@ -137,7 +137,7 @@ public function publishMass($topicName, array $entitiesArray, $groupId = null, $ $requestItem = $this->itemStatusInterfaceFactory->create(); try { - $operation = $this->operationRepository->createByTopic($topicName, $entityParams, $groupId); + $operation = $this->operationRepository->create($topicName, $entityParams, $groupId, $key); $operations[] = $operation; $requestItem->setId($key); $requestItem->setStatus(ItemStatusInterface::STATUS_ACCEPTED); diff --git a/app/code/Magento/AsynchronousOperations/Model/OperationRepositoryInterface.php b/app/code/Magento/AsynchronousOperations/Model/OperationRepositoryInterface.php new file mode 100644 index 0000000000000..601ab44af5023 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/OperationRepositoryInterface.php @@ -0,0 +1,31 @@ +' => '', + * '' => '', + * ) + * @param string $groupId + * @param int $operationId + * @return OperationInterface + */ + public function create($topicName, $entityParams, $groupId, $operationId): OperationInterface; +} diff --git a/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/OperationRepository.php b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/OperationRepository.php index 54e65cc3470dd..5e42d0a2310b9 100644 --- a/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/OperationRepository.php +++ b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/OperationRepository.php @@ -10,6 +10,7 @@ use Magento\AsynchronousOperations\Api\Data\OperationInterface; use Magento\AsynchronousOperations\Api\Data\OperationInterfaceFactory; +use Magento\AsynchronousOperations\Model\OperationRepositoryInterface; use Magento\Framework\MessageQueue\MessageValidator; use Magento\Framework\MessageQueue\MessageEncoder; use Magento\Framework\Serialize\Serializer\Json; @@ -18,10 +19,10 @@ /** * Create operation for list of bulk operations. */ -class OperationRepository +class OperationRepository implements OperationRepositoryInterface { /** - * @var \Magento\AsynchronousOperations\Api\Data\OperationInterfaceFactory + * @var OperationInterfaceFactory */ private $operationFactory; @@ -67,10 +68,14 @@ public function __construct( } /** - * @param $topicName - * @param $entityParams - * @param $groupId - * @return mixed + * Create operation by topic, parameters and group ID + * + * @param string $topicName + * @param array $entityParams + * @param string $groupId + * @return OperationInterface + * @deprecated No longer used. + * @see create() */ public function createByTopic($topicName, $entityParams, $groupId) { @@ -91,8 +96,16 @@ public function createByTopic($topicName, $entityParams, $groupId) ], ]; - /** @var \Magento\AsynchronousOperations\Api\Data\OperationInterface $operation */ + /** @var OperationInterface $operation */ $operation = $this->operationFactory->create($data); return $this->entityManager->save($operation); } + + /** + * @inheritDoc + */ + public function create($topicName, $entityParams, $groupId, $operationId): OperationInterface + { + return $this->createByTopic($topicName, $entityParams, $groupId); + } } diff --git a/app/code/Magento/AsynchronousOperations/etc/di.xml b/app/code/Magento/AsynchronousOperations/etc/di.xml index 171a01cedf289..0d8126358abf4 100644 --- a/app/code/Magento/AsynchronousOperations/etc/di.xml +++ b/app/code/Magento/AsynchronousOperations/etc/di.xml @@ -18,6 +18,7 @@ + diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminNavigateToPersistentShoppingCartSettingsActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminNavigateToPersistentShoppingCartSettingsActionGroup.xml new file mode 100644 index 0000000000000..811187b9fe95e --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminNavigateToPersistentShoppingCartSettingsActionGroup.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminPersistentShoppingCartOptionsAvailableActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminPersistentShoppingCartOptionsAvailableActionGroup.xml new file mode 100644 index 0000000000000..3b75602e6d89c --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminPersistentShoppingCartOptionsAvailableActionGroup.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + diff --git a/app/code/Magento/Backend/Test/Mftf/Page/AdminConfigurationPersistentShoppingCartPage.xml b/app/code/Magento/Backend/Test/Mftf/Page/AdminConfigurationPersistentShoppingCartPage.xml new file mode 100644 index 0000000000000..f7e579c57e21c --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Page/AdminConfigurationPersistentShoppingCartPage.xml @@ -0,0 +1,14 @@ + + + + + +
+ + diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginFailedTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginFailedTest.xml new file mode 100644 index 0000000000000..1239fc471f59e --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginFailedTest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + <description value="Admin should not be able to log into the backend with invalid credentials"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-71572"/> + <group value="example"/> + <group value="login"/> + </annotations> + + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"> + <argument name="password" value="INVALID!{{_ENV.MAGENTO_ADMIN_PASSWORD}}"/> + </actionGroup> + <actionGroup ref="AssertMessageOnAdminLoginActionGroup" stepKey="assertErrorMessage"/> + </test> +</tests> diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginSuccessfulTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginSuccessfulTest.xml new file mode 100644 index 0000000000000..a8de04f4342de --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginSuccessfulTest.xml @@ -0,0 +1,27 @@ +<?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="AdminLoginSuccessfulTest"> + <annotations> + <features value="Backend"/> + <stories value="Login on the Admin Login page"/> + <title value="Admin should be able to log into the Magento Admin backend successfully"/> + <description value="Admin should be able to log into the Magento Admin backend successfully"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-71572"/> + <group value="example"/> + <group value="login"/> + </annotations> + + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="assertLoggedIn"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> + </test> +</tests> diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml index 566328e075600..09893f5f51e5e 100644 --- a/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminLoginTest"> + <test name="AdminLoginTest" deprecated="Replaced with AdminLoginSuccessfulTest"> <annotations> <features value="Backend"/> <stories value="Login on the Admin Login page"/> @@ -20,7 +20,7 @@ <group value="login"/> </annotations> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> <seeInCurrentUrl url="{{AdminLoginPage.url}}" stepKey="seeAdminLoginUrl"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </test> diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminPersistentShoppingCartSettingsTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminPersistentShoppingCartSettingsTest.xml new file mode 100644 index 0000000000000..cf1ed2d63034f --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminPersistentShoppingCartSettingsTest.xml @@ -0,0 +1,34 @@ +<?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="AdminPersistentShoppingCartSettingsTest"> + <annotations> + <features value="Backend"/> + <stories value="Enable Persistent Shopping cart"/> + <title value="Admin should be able to manage persistent shopping cart settings"/> + <description value="Admin should be able to enable persistent shopping cart in Magento Admin backend and see additional options"/> + <group value="backend"/> + </annotations> + + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <magentoCLI stepKey="enablePersistentShoppingCart" command="config:set persistent/options/enabled 1"/> + <magentoCLI stepKey="cacheClean" command="cache:clean config"/> + </before> + <after> + <magentoCLI stepKey="disablePersistentShoppingCart" command="config:set persistent/options/enabled 0"/> + <magentoCLI stepKey="cacheClean" command="cache:clean config"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + </after> + + <actionGroup ref="AdminNavigateToPersistentShoppingCartSettingsActionGroup" stepKey="navigateToPersistenceSettings"/> + <actionGroup ref="AssertAdminPersistentShoppingCartOptionsAvailableActionGroup" stepKey="assertOptions"/> + </test> +</tests> diff --git a/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml b/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest.xml similarity index 98% rename from app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml rename to app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest.xml index c45a8aece5ffc..77dc83eec1176 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="CreateAnAdminOrderUsingBraintreePaymentTest1"> + <test name="CreateAnAdminOrderUsingBraintreePaymentTest1Test"> <annotations> <features value="Backend"/> <stories value="Creation an admin order using Braintree payment"/> diff --git a/app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscount.xml b/app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscountTest.xml similarity index 99% rename from app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscount.xml rename to app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscountTest.xml index d2b0479f2bba6..5efa5fd0db6b6 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscount.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscountTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="CreateAdminOrderPayedWithOnlinePaymentIncludingTaxAndDiscount"> + <test name="CreateAdminOrderPayedWithOnlinePaymentIncludingTaxAndDiscountTest"> <annotations> <features value="Braintree"/> <stories value="Get access to a New Credit Memo Page from Invoice for Order payed with online payment via Admin"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminCreateAndEditBundleProductSettingsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminCreateAndEditBundleProductSettingsTest.xml index b143bd63280ea..1d2d566ffd447 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminCreateAndEditBundleProductSettingsTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminCreateAndEditBundleProductSettingsTest.xml @@ -126,7 +126,7 @@ <!-- Verify Url Key after changing --> <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductPage"> - <argument name="productUrl" value="{{ApiBundleProduct.name}}"/> + <argument name="productUrl" value="{{ApiBundleProduct.urlKey}}"/> </actionGroup> <!-- Assert product design settings "Layout empty" --> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteABundleProduct.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteABundleProductTest.xml similarity index 99% rename from app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteABundleProduct.xml rename to app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteABundleProductTest.xml index f272f3f98a8c9..a98d544aad3b6 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteABundleProduct.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteABundleProductTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminDeleteABundleProduct"> + <test name="AdminDeleteABundleProductTest"> <annotations> <features value="Bundle"/> <stories value="Admin list bundle products"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminFilterProductListByBundleProduct.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminFilterProductListByBundleProductTest.xml similarity index 98% rename from app/code/Magento/Bundle/Test/Mftf/Test/AdminFilterProductListByBundleProduct.xml rename to app/code/Magento/Bundle/Test/Mftf/Test/AdminFilterProductListByBundleProductTest.xml index 5aa72fb651985..dea39fcb45908 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminFilterProductListByBundleProduct.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminFilterProductListByBundleProductTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminFilterProductListByBundleProduct"> + <test name="AdminFilterProductListByBundleProductTest"> <annotations> <features value="Bundle"/> <stories value="Admin list bundle products"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGrid.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGridTest.xml similarity index 99% rename from app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGrid.xml rename to app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGridTest.xml index 364d4fa68e590..62a66b7d092ef 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGrid.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGridTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontBundleProductShownInCategoryListAndGrid"> + <test name="StorefrontBundleProductShownInCategoryListAndGridTest"> <annotations> <features value="Bundle"/> <stories value="Bundle products list on Storefront"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPrices.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPricesTest.xml similarity index 99% rename from app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPrices.xml rename to app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPricesTest.xml index 4bb54436e8729..0c9be915a7a8b 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPrices.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPricesTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontCheckBundleProductOptionTierPrices"> + <test name="StorefrontCheckBundleProductOptionTierPricesTest"> <annotations> <features value="Bundle"/> <stories value="View bundle products"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontGoToDetailsPageWhenAddingToCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontGoToDetailsPageWhenAddingToCartTest.xml index 7ced26bab2c96..f0e16bbdd1b99 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontGoToDetailsPageWhenAddingToCartTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontGoToDetailsPageWhenAddingToCartTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontGoToDetailsPageWhenAddingToCart"> + <test name="StorefrontGoToDetailsPageWhenAddingToCartTest"> <annotations> <features value="Bundle"/> <stories value="Bundle products list on Storefront"/> diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml index 977ee78c0d201..b1c3ed52f163b 100644 --- a/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml +++ b/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml @@ -65,7 +65,7 @@ <scrollToTopOfPage stepKey="ScrollToTop"/> <click selector="{{CaptchaFormsDisplayingSection.captcha}}" stepKey="ClickToCloseCaptcha"/> </test> - <test name="CaptchaWithDisabledGuestCheckout"> + <test name="CaptchaWithDisabledGuestCheckoutTest"> <annotations> <features value="Captcha"/> <stories value="MC-5602 - CAPTCHA doesn't appear in login popup after refreshing page."/> diff --git a/app/code/Magento/Catalog/Block/Product/Image.php b/app/code/Magento/Catalog/Block/Product/Image.php index 7a7f9c0affc7d..ccc37029bedf7 100644 --- a/app/code/Magento/Catalog/Block/Product/Image.php +++ b/app/code/Magento/Catalog/Block/Product/Image.php @@ -14,7 +14,7 @@ * @method string getHeight() * @method string getLabel() * @method float getRatio() - * @method string getCustomAttributes() + * @method array getCustomAttributes() * @method string getClass() * @since 100.0.2 */ diff --git a/app/code/Magento/Catalog/Block/Product/ImageFactory.php b/app/code/Magento/Catalog/Block/Product/ImageFactory.php index 0c69a40b246bb..d43a77695e363 100644 --- a/app/code/Magento/Catalog/Block/Product/ImageFactory.php +++ b/app/code/Magento/Catalog/Block/Product/ImageFactory.php @@ -68,20 +68,17 @@ public function __construct( } /** - * Retrieve image custom attributes for HTML element + * Remove class from custom attributes * * @param array $attributes - * @return string + * @return array */ - private function getStringCustomAttributes(array $attributes): string + private function filterCustomAttributes(array $attributes): array { - $result = []; - foreach ($attributes as $name => $value) { - if ($name != 'class') { - $result[] = $name . '="' . $value . '"'; - } + if (isset($attributes['class'])) { + unset($attributes['class']); } - return !empty($result) ? implode(' ', $result) : ''; + return $attributes; } /** @@ -170,7 +167,7 @@ public function create(Product $product, string $imageId, array $attributes = nu 'height' => $imageMiscParams['image_height'], 'label' => $this->getLabel($product, $imageMiscParams['image_type']), 'ratio' => $this->getRatio($imageMiscParams['image_width'] ?? 0, $imageMiscParams['image_height'] ?? 0), - 'custom_attributes' => $this->getStringCustomAttributes($attributes), + 'custom_attributes' => $this->filterCustomAttributes($attributes), 'class' => $this->getClass($attributes), 'product_id' => $product->getId() ], diff --git a/app/code/Magento/Catalog/Block/Product/View.php b/app/code/Magento/Catalog/Block/Product/View.php index ed6278c2b585d..437171bcb4bc6 100644 --- a/app/code/Magento/Catalog/Block/Product/View.php +++ b/app/code/Magento/Catalog/Block/Product/View.php @@ -323,9 +323,9 @@ public function getQuantityValidators() */ public function getIdentities() { - $identities = $this->getProduct()->getIdentities(); + $product = $this->getProduct(); - return $identities; + return $product ? $product->getIdentities() : []; } /** diff --git a/app/code/Magento/Catalog/Model/Category/DataProvider.php b/app/code/Magento/Catalog/Model/Category/DataProvider.php index fe7258398d191..d8c79c485e3e5 100644 --- a/app/code/Magento/Catalog/Model/Category/DataProvider.php +++ b/app/code/Magento/Catalog/Model/Category/DataProvider.php @@ -41,6 +41,7 @@ * @api * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.TooManyFields) + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @since 101.0.0 */ class DataProvider extends ModifierPoolDataProvider @@ -176,6 +177,10 @@ class DataProvider extends ModifierPoolDataProvider * @var AuthorizationInterface */ private $auth; + /** + * @var Image + */ + private $categoryImage; /** * @param string $name @@ -196,6 +201,7 @@ class DataProvider extends ModifierPoolDataProvider * @param ScopeOverriddenValue|null $scopeOverriddenValue * @param ArrayManager|null $arrayManager * @param FileInfo|null $fileInfo + * @param Image|null $categoryImage * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -216,7 +222,8 @@ public function __construct( ?ArrayUtils $arrayUtils = null, ScopeOverriddenValue $scopeOverriddenValue = null, ArrayManager $arrayManager = null, - FileInfo $fileInfo = null + FileInfo $fileInfo = null, + ?Image $categoryImage = null ) { $this->eavValidationRules = $eavValidationRules; $this->collection = $categoryCollectionFactory->create(); @@ -232,6 +239,7 @@ public function __construct( ObjectManager::getInstance()->get(ScopeOverriddenValue::class); $this->arrayManager = $arrayManager ?: ObjectManager::getInstance()->get(ArrayManager::class); $this->fileInfo = $fileInfo ?: ObjectManager::getInstance()->get(FileInfo::class); + $this->categoryImage = $categoryImage ?? ObjectManager::getInstance()->get(Image::class); parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data, $pool); } @@ -601,11 +609,7 @@ private function convertValues($category, $categoryData): array // phpcs:ignore Magento2.Functions.DiscouragedFunction $categoryData[$attributeCode][0]['name'] = basename($fileName); - if ($this->fileInfo->isBeginsWithMediaDirectoryPath($fileName)) { - $categoryData[$attributeCode][0]['url'] = $fileName; - } else { - $categoryData[$attributeCode][0]['url'] = $category->getImageUrl($attributeCode); - } + $categoryData[$attributeCode][0]['url'] = $this->categoryImage->getUrl($category, $attributeCode); $categoryData[$attributeCode][0]['size'] = isset($stat) ? $stat['size'] : 0; $categoryData[$attributeCode][0]['type'] = $mime; diff --git a/app/code/Magento/Catalog/Model/Category/FileInfo.php b/app/code/Magento/Catalog/Model/Category/FileInfo.php index 76b6a2e75d0ea..7d679f2645be1 100644 --- a/app/code/Magento/Catalog/Model/Category/FileInfo.php +++ b/app/code/Magento/Catalog/Model/Category/FileInfo.php @@ -245,4 +245,15 @@ private function getMediaDirectoryPathRelativeToBaseDirectoryPath(string $filePa return $mediaDirectoryRelativeSubpath; } + + /** + * Get file relative path to media directory + * + * @param string $filename + * @return string + */ + public function getRelativePathToMediaDirectory(string $filename): string + { + return $this->getFilePath($filename); + } } diff --git a/app/code/Magento/Catalog/Model/Category/Image.php b/app/code/Magento/Catalog/Model/Category/Image.php new file mode 100644 index 0000000000000..ea5700cb386d0 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Category/Image.php @@ -0,0 +1,75 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\Category; + +use Magento\Catalog\Model\Category; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\UrlInterface; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Category Image Service + */ +class Image +{ + private const ATTRIBUTE_NAME = 'image'; + /** + * @var FileInfo + */ + private $fileInfo; + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * Initialize dependencies. + * + * @param FileInfo $fileInfo + * @param StoreManagerInterface $storeManager + */ + public function __construct( + FileInfo $fileInfo, + StoreManagerInterface $storeManager + ) { + $this->fileInfo = $fileInfo; + $this->storeManager = $storeManager; + } + /** + * Resolve category image URL + * + * @param Category $category + * @param string $attributeCode + * @return string + * @throws LocalizedException + */ + public function getUrl(Category $category, string $attributeCode = self::ATTRIBUTE_NAME): string + { + $url = ''; + $image = $category->getData($attributeCode); + if ($image) { + if (is_string($image)) { + $store = $this->storeManager->getStore(); + $mediaBaseUrl = $store->getBaseUrl(UrlInterface::URL_TYPE_MEDIA); + if ($this->fileInfo->isBeginsWithMediaDirectoryPath($image)) { + $relativePath = $this->fileInfo->getRelativePathToMediaDirectory($image); + $url = rtrim($mediaBaseUrl, '/') . '/' . ltrim($relativePath, '/'); + } elseif (substr($image, 0, 1) !== '/') { + $url = rtrim($mediaBaseUrl, '/') . '/' . ltrim(FileInfo::ENTITY_MEDIA_PATH, '/') . '/' . $image; + } else { + $url = $image; + } + } else { + throw new LocalizedException( + __('Something went wrong while getting the image url.') + ); + } + } + return $url; + } +} diff --git a/app/code/Magento/Catalog/Model/Config/LayerCategoryConfig.php b/app/code/Magento/Catalog/Model/Config/LayerCategoryConfig.php new file mode 100644 index 0000000000000..0e8565e3b25d1 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Config/LayerCategoryConfig.php @@ -0,0 +1,80 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\Config; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Config for category in the layered navigation + */ +class LayerCategoryConfig +{ + private const XML_PATH_CATALOG_LAYERED_NAVIGATION_DISPLAY_CATEGORY = 'catalog/layered_navigation/display_category'; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * LayerCategoryConfig constructor + * + * @param ScopeConfigInterface $scopeConfig + * @param StoreManagerInterface $storeManager + */ + public function __construct( + ScopeConfigInterface $scopeConfig, + StoreManagerInterface $storeManager + ) { + $this->scopeConfig = $scopeConfig; + $this->storeManager = $storeManager; + } + + /** + * Check if category filter item should be added in the layered navigation + * + * @param string $scopeType + * @param null|int|string $scopeCode + * + * @return bool + */ + public function isCategoryFilterVisibleInLayerNavigation( + $scopeType = ScopeInterface::SCOPE_STORES, + $scopeCode = null + ): bool { + if (!$scopeCode) { + $scopeCode = $this->getStoreId(); + } + + return $this->scopeConfig->isSetFlag( + static::XML_PATH_CATALOG_LAYERED_NAVIGATION_DISPLAY_CATEGORY, + $scopeType, + $scopeCode + ); + } + + /** + * Get the current store ID + * + * @return int + * + * @throws NoSuchEntityException + */ + private function getStoreId(): int + { + return (int) $this->storeManager->getStore()->getId(); + } +} diff --git a/app/code/Magento/Catalog/Model/ImageUploader.php b/app/code/Magento/Catalog/Model/ImageUploader.php index d333ea589b997..1a9b6820d0f90 100644 --- a/app/code/Magento/Catalog/Model/ImageUploader.php +++ b/app/code/Magento/Catalog/Model/ImageUploader.php @@ -236,8 +236,10 @@ public function moveFileFromTmp($imageName, $returnRelativePath = false) $storage->put($baseImagePath, $content); } catch (\Exception $e) { + $this->logger->critical($e); throw new \Magento\Framework\Exception\LocalizedException( - __('Something went wrong while saving the file(s).') + __('Something went wrong while saving the file(s).'), + $e ); } @@ -291,7 +293,8 @@ public function saveFileToTmpDir($fileId) } catch (\Exception $e) { $this->logger->critical($e); throw new \Magento\Framework\Exception\LocalizedException( - __('Something went wrong while saving the file(s).') + __('Something went wrong while saving the file(s).'), + $e ); } } diff --git a/app/code/Magento/Catalog/Model/Layer/FilterList.php b/app/code/Magento/Catalog/Model/Layer/FilterList.php index b8e9b8ad4aaa5..a7eba474c58d8 100644 --- a/app/code/Magento/Catalog/Model/Layer/FilterList.php +++ b/app/code/Magento/Catalog/Model/Layer/FilterList.php @@ -7,6 +7,9 @@ namespace Magento\Catalog\Model\Layer; +use Magento\Catalog\Model\Config\LayerCategoryConfig; +use Magento\Framework\App\ObjectManager; + /** * Layer navigation filters */ @@ -44,18 +47,26 @@ class FilterList */ protected $filters = []; + /** + * @var LayerCategoryConfig + */ + private $layerCategoryConfig; + /** * @param \Magento\Framework\ObjectManagerInterface $objectManager * @param FilterableAttributeListInterface $filterableAttributes + * @param LayerCategoryConfig $layerCategoryConfig * @param array $filters */ public function __construct( \Magento\Framework\ObjectManagerInterface $objectManager, FilterableAttributeListInterface $filterableAttributes, + LayerCategoryConfig $layerCategoryConfig, array $filters = [] ) { $this->objectManager = $objectManager; $this->filterableAttributes = $filterableAttributes; + $this->layerCategoryConfig = $layerCategoryConfig; /** Override default filter type models */ $this->filterTypes = array_merge($this->filterTypes, $filters); @@ -70,9 +81,11 @@ public function __construct( public function getFilters(\Magento\Catalog\Model\Layer $layer) { if (!count($this->filters)) { - $this->filters = [ - $this->objectManager->create($this->filterTypes[self::CATEGORY_FILTER], ['layer' => $layer]), - ]; + if ($this->layerCategoryConfig->isCategoryFilterVisibleInLayerNavigation()) { + $this->filters = [ + $this->objectManager->create($this->filterTypes[self::CATEGORY_FILTER], ['layer' => $layer]), + ]; + } foreach ($this->filterableAttributes->getList() as $attribute) { $this->filters[] = $this->createAttributeFilter($attribute, $layer); } diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php b/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php index a9afb7cec45e2..6a078a915119c 100644 --- a/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php +++ b/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php @@ -61,6 +61,10 @@ public function create($sku, ProductAttributeMediaGalleryEntryInterface $entry) $existingMediaGalleryEntries = $product->getMediaGalleryEntries(); $existingEntryIds = []; if ($existingMediaGalleryEntries == null) { + // set all media types if not specified + if ($entry->getTypes() == null) { + $entry->setTypes(array_keys($product->getMediaAttributes())); + } $existingMediaGalleryEntries = [$entry]; } else { foreach ($existingMediaGalleryEntries as $existingEntries) { diff --git a/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php b/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php index 2aa92b8f0316e..ecb7322ac10d2 100644 --- a/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php +++ b/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php @@ -224,7 +224,15 @@ private function processEntries(ProductInterface $product, array $newEntries, ar $this->processNewMediaGalleryEntry($product, $newEntry); $finalGallery = $product->getData('media_gallery'); - $newEntryId = key(array_diff_key($product->getData('media_gallery')['images'], $entriesById)); + + $entryIds = array_keys( + array_diff_key( + $product->getData('media_gallery')['images'], + $entriesById + ) + ); + $newEntryId = array_pop($entryIds); + $newEntry = array_replace_recursive($newEntry, $finalGallery['images'][$newEntryId]); $entriesById[$newEntryId] = $newEntry; $finalGallery['images'][$newEntryId] = $newEntry; diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Website/Link.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Website/Link.php index de4ffc5d862f9..d9016d8a852e8 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Website/Link.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Website/Link.php @@ -7,8 +7,10 @@ use Magento\Catalog\Api\Data\ProductInterface; use Magento\Framework\App\ResourceConnection; -use Magento\Framework\EntityManager\MetadataPool; +/** + * Class Link used for assign website to the product + */ class Link { /** @@ -28,6 +30,7 @@ public function __construct( /** * Retrieve associated with product websites ids + * * @param int $productId * @return array */ @@ -48,29 +51,53 @@ public function getWebsiteIdsByProductId($productId) /** * Return true - if websites was changed, and false - if not + * * @param ProductInterface $product * @param array $websiteIds * @return bool */ public function saveWebsiteIds(ProductInterface $product, array $websiteIds) + { + $productId = (int) $product->getId(); + return $this->updateProductWebsite($productId, $websiteIds); + } + + /** + * Get Product website table + * + * @return string + */ + private function getProductWebsiteTable() + { + return $this->resourceConnection->getTableName('catalog_product_website'); + } + + /** + * Update product website table + * + * @param int $productId + * @param array $websiteIds + * @return bool + */ + public function updateProductWebsite(int $productId, array $websiteIds): bool { $connection = $this->resourceConnection->getConnection(); - $oldWebsiteIds = $this->getWebsiteIdsByProductId($product->getId()); + $oldWebsiteIds = $this->getWebsiteIdsByProductId($productId); $insert = array_diff($websiteIds, $oldWebsiteIds); $delete = array_diff($oldWebsiteIds, $websiteIds); if (!empty($insert)) { $data = []; foreach ($insert as $websiteId) { - $data[] = ['product_id' => (int) $product->getId(), 'website_id' => (int) $websiteId]; + $data[] = ['product_id' => $productId, 'website_id' => (int)$websiteId]; } $connection->insertMultiple($this->getProductWebsiteTable(), $data); } if (!empty($delete)) { foreach ($delete as $websiteId) { - $condition = ['product_id = ?' => (int) $product->getId(), 'website_id = ?' => (int) $websiteId]; + $condition = ['product_id = ?' => $productId, 'website_id = ?' => (int)$websiteId]; $connection->delete($this->getProductWebsiteTable(), $condition); } } @@ -81,12 +108,4 @@ public function saveWebsiteIds(ProductInterface $product, array $websiteIds) return false; } - - /** - * @return string - */ - private function getProductWebsiteTable() - { - return $this->resourceConnection->getTableName('catalog_product_website'); - } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/ProductWebsiteAssignmentHandler.php b/app/code/Magento/Catalog/Model/ResourceModel/ProductWebsiteAssignmentHandler.php new file mode 100644 index 0000000000000..10e0a09593208 --- /dev/null +++ b/app/code/Magento/Catalog/Model/ResourceModel/ProductWebsiteAssignmentHandler.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); +namespace Magento\Catalog\Model\ResourceModel; + +use Magento\Catalog\Model\ResourceModel\Product\Website\Link; +use Magento\Framework\EntityManager\Operation\AttributeInterface; + +/** + * Class purpose is to handle product websites assignment + */ +class ProductWebsiteAssignmentHandler implements AttributeInterface +{ + /** + * @var Link + */ + private $productLink; + + /** + * ProductWebsiteAssignmentHandler constructor + * + * @param Link $productLink + */ + public function __construct( + Link $productLink + ) { + $this->productLink = $productLink; + } + + /** + * Assign product website entity to the product repository + * + * @param string $entityType + * @param array $entityData + * @param array $arguments + * @return array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @throws \Exception + */ + public function execute($entityType, $entityData, $arguments = []): array + { + $websiteIds = array_key_exists('website_ids', $entityData) ? + array_filter($entityData['website_ids'], function ($websiteId) { + return $websiteId !== null; + }) : []; + $productId = array_key_exists('entity_id', $entityData) ? (int) $entityData['entity_id'] : null; + + if (!empty($productId) && !empty($websiteIds)) { + $this->productLink->updateProductWebsite($productId, $websiteIds); + } + return $entityData; + } +} diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddSimpleProductToCartActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddSimpleProductToCartActionGroup.xml index 81e3b8c99d9d2..68a051c232338 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddSimpleProductToCartActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddSimpleProductToCartActionGroup.xml @@ -8,7 +8,7 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="AddSimpleProductToCartActionGroup"> + <actionGroup name="AddSimpleProductToCartActionGroup" deprecated="Avoid using super-ActionGroups. Use StorefrontOpenProductEntityPageActionGroup, StorefrontAddSimpleProductToCartActionGroup and StorefrontAssertProductAddedToCartResultMessageActionGroup"> <annotations> <description>Navigates to the Storefront Product page. Then adds the Product to the Cart. Validates that the Success Message is present and correct.</description> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageBaseRoleActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageBaseRoleActionGroup.xml new file mode 100644 index 0000000000000..aa5311d389d7a --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageBaseRoleActionGroup.xml @@ -0,0 +1,25 @@ +<?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="AdminAssignImageBaseRoleActionGroup"> + <annotations> + <description>Requires the navigation to the Product Creation page. Checks the Base Role area for image.</description> + </annotations> + <arguments> + <argument name="image"/> + </arguments> + <conditionalClick selector="{{AdminProductImagesSection.productImagesToggle}}" dependentSelector="{{AdminProductImagesSection.imageFile(image.fileName)}}" visible="false" stepKey="expandImages"/> + <waitForElementVisible selector="{{AdminProductImagesSection.imageFile(image.fileName)}}" stepKey="seeProductImageName"/> + <click selector="{{AdminProductImagesSection.imageFile(image.fileName)}}" stepKey="clickProductImage"/> + <waitForElementVisible selector="{{AdminProductImagesSection.altText}}" stepKey="seeAltTextSection"/> + <checkOption selector="{{AdminProductImagesSection.roleBase}}" stepKey="checkRoles"/> + <click selector="{{AdminSlideOutDialogSection.closeButton}}" stepKey="clickCloseButton"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup.xml new file mode 100644 index 0000000000000..fc010cec4cb65 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup.xml @@ -0,0 +1,21 @@ +<?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="AdminChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup" extends="ChangeSeoUrlKeyForSubCategoryActionGroup"> + <annotations> + <description>Requires navigation to subcategory creation/edit. Updates the Search Engine Optimization with uncheck Redirect Checkbox .</description> + </annotations> + <arguments> + <argument name="value" type="string"/> + </arguments> + + <uncheckOption selector="{{AdminCategorySEOSection.UrlKeyRedirectCheckbox}}" stepKey="uncheckRedirectCheckbox" after="enterURLKey"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenProductImagesSectionActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenProductImagesSectionActionGroup.xml new file mode 100644 index 0000000000000..4d49b13a8bf5a --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenProductImagesSectionActionGroup.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="AdminOpenProductImagesSectionActionGroup"> + <annotations> + <description>Requires the navigation to the Product page. Opens 'Image and Videos' section.</description> + </annotations> + <conditionalClick selector="{{AdminProductImagesSection.productImagesToggle}}" dependentSelector="{{AdminProductImagesSection.imageUploadButton}}" visible="false" stepKey="openProductImagesSection"/> + <waitForElementVisible selector="{{AdminProductImagesSection.imageUploadButton}}" stepKey="waitForImageUploadButton"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminProductGridCellActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminProductGridCellActionGroup.xml new file mode 100644 index 0000000000000..724c852e7b0be --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminProductGridCellActionGroup.xml @@ -0,0 +1,23 @@ +<?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="AssertAdminProductGridCellActionGroup"> + <annotations> + <description>Checks value for Admin Product Grid cell by provided row and column.</description> + </annotations> + <arguments> + <argument name="row" type="string" defaultValue="1"/> + <argument name="column" type="string" defaultValue="Name"/> + <argument name="value" type="string" defaultValue="1"/> + </arguments> + + <see selector="{{AdminProductGridSection.productGridCell(row,column)}}" userInput="{{value}}" stepKey="seeProductGridCellWithProvidedValue"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminProductImageRolesSelectedActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminProductImageRolesSelectedActionGroup.xml new file mode 100644 index 0000000000000..3bb6210d6b824 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminProductImageRolesSelectedActionGroup.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertAdminProductImageRolesSelectedActionGroup"> + <annotations> + <description>Requires the navigation to the Product page and opened 'Image and Videos' section. + Checks the Base, Small, Thumbnail and Swatch Roles are selected for provided image.</description> + </annotations> + <arguments> + <argument name="imageFileName" type="string" defaultValue="test_image"/> + </arguments> + <waitForElementVisible selector="{{AdminProductImagesSection.imageFile(imageFileName)}}" stepKey="seeProductImageName"/> + <click selector="{{AdminProductImagesSection.imageFile(imageFileName)}}" stepKey="clickProductImage"/> + <waitForElementVisible selector="{{AdminProductImagesSection.isBaseSelected}}" stepKey="checkRoleBaseSelected"/> + <waitForElementVisible selector="{{AdminProductImagesSection.isSmallSelected}}" stepKey="checkRoleSmallSelected"/> + <waitForElementVisible selector="{{AdminProductImagesSection.isThumbnailSelected}}" stepKey="checkRoleThumbnailSelected"/> + <waitForElementVisible selector="{{AdminProductImagesSection.isSwatchSelected}}" stepKey="checkRoleSwatchSelected"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup.xml new file mode 100644 index 0000000000000..8986db7af9246 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup.xml @@ -0,0 +1,22 @@ +<?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="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup"> + <annotations> + <description>Goes to the home Page Recently VIewed Product and Grab the Prdouct name and Position from it.</description> + </annotations> + <arguments> + <argument name="productName" type="string"/> + <argument name="productPosition" type="string"/> + </arguments> + <grabTextFrom selector="{{StoreFrontRecentlyViewedProductSection.ProductName(productPosition)}}" stepKey="grabRelatedProductPosition"/> + <assertContains expected="{{productName}}" actual="$grabRelatedProductPosition" stepKey="assertRelatedProductName"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontLayeredNavigationCategoryFilterNotVisibleActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontLayeredNavigationCategoryFilterNotVisibleActionGroup.xml new file mode 100644 index 0000000000000..c892594201f17 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontLayeredNavigationCategoryFilterNotVisibleActionGroup.xml @@ -0,0 +1,14 @@ +<?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"> + <!-- On a category page with layered navigation, verify if the category filter item is NOT present --> + <actionGroup name="AssertStorefrontLayeredNavigationCategoryFilterNotVisibleActionGroup"> + <!-- Verify category filter item is NOT present --> + <dontSee selector="{{StorefrontCategorySidebarSection.layeredFilterBlock}}" userInput="Category" stepKey="seeCategoryFilterInLayeredNav"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontLayeredNavigationCategoryFilterVisibleActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontLayeredNavigationCategoryFilterVisibleActionGroup.xml new file mode 100644 index 0000000000000..8eebafd3cd8ab --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontLayeredNavigationCategoryFilterVisibleActionGroup.xml @@ -0,0 +1,14 @@ +<?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"> + <!-- On a category page with layered navigation, verify if the category filter item is present --> + <actionGroup name="AssertStorefrontLayeredNavigationCategoryFilterVisibleActionGroup"> + <!-- Verify category filter item is present --> + <see selector="{{StorefrontCategorySidebarSection.layeredFilterBlock}}" userInput="Category" stepKey="seeCategoryFilterInLayeredNav"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckAdminProductGridColumnOptionActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckAdminProductGridColumnOptionActionGroup.xml new file mode 100644 index 0000000000000..dc10933150617 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckAdminProductGridColumnOptionActionGroup.xml @@ -0,0 +1,21 @@ +<?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="CheckAdminProductGridColumnOptionActionGroup"> + <annotations> + <description>Checks Admin Product Grid 'Columns' option.</description> + </annotations> + <arguments> + <argument name="optionName" type="string" defaultValue="Name"/> + </arguments> + + <checkOption selector="{{AdminProductGridFilterSection.viewColumnOption(optionName)}}" stepKey="checkColumn"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ClearFiltersAdminProductGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ClearFiltersAdminProductGridActionGroup.xml new file mode 100644 index 0000000000000..fb75b65120287 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ClearFiltersAdminProductGridActionGroup.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="ClearFiltersAdminProductGridActionGroup"> + <annotations> + <description>Clicks on 'Clear Filters'.</description> + </annotations> + + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters"/> + <waitForPageLoad stepKey="waitForGridLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ResetAdminProductGridColumnsActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ResetAdminProductGridColumnsActionGroup.xml new file mode 100644 index 0000000000000..8e10da66a83af --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ResetAdminProductGridColumnsActionGroup.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="ResetAdminProductGridColumnsActionGroup"> + <annotations> + <description>Clicks 'reset' for Admin Product Grid 'Columns' dropdown.</description> + </annotations> + + <click selector="{{AdminProductGridFilterSection.resetGridColumns}}" stepKey="resetProductGridColumns"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenProductEntityPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenProductEntityPageActionGroup.xml new file mode 100644 index 0000000000000..88dcaff646799 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenProductEntityPageActionGroup.xml @@ -0,0 +1,21 @@ +<?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="StorefrontOpenProductEntityPageActionGroup"> + <annotations> + <description>Opens Storefront Product page for the provided Product Entity</description> + </annotations> + <arguments> + <argument name="product" type="entity"/> + </arguments> + + <amOnPage url="{{StorefrontProductPage.url(product.custom_attributes[url_key])}}" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoaded"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ToggleAdminProductGridColumnsDropdownActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ToggleAdminProductGridColumnsDropdownActionGroup.xml new file mode 100644 index 0000000000000..783c05797c6e1 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ToggleAdminProductGridColumnsDropdownActionGroup.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="ToggleAdminProductGridColumnsDropdownActionGroup"> + <annotations> + <description>Toggles Admin Product Grid 'Columns' dropdown.</description> + </annotations> + + <click selector="{{AdminProductGridFilterSection.columnsDropdown}}" stepKey="toggleColumnsDropdown"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml index f54ce9af83e88..e252931a0363f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml @@ -110,6 +110,22 @@ <data key="is_active">false</data> <data key="include_in_menu">false</data> </entity> + <entity name="_defaultCategoryDifferentUrlStore" type="category"> + <data key="name" unique="suffix">SimpleCategory</data> + <data key="name_lwr" unique="suffix">simplecategory</data> + <data key="is_active">true</data> + <data key="url_key_default_store" unique="suffix">default-simplecategory</data> + <data key="url_key_custom_store" unique="suffix">custom-simplecategory</data> + </entity> + <entity name="SimpleSubCategoryDifferentUrlStore" type="category"> + <data key="name" unique="suffix">SimpleSubCategory</data> + <data key="name_lwr" unique="suffix">simplesubcategory</data> + <data key="is_active">true</data> + <data key="url_key_default_store" unique="suffix">default-simplesubcategory</data> + <data key="url_key_custom_store" unique="suffix">custom-simplesubcategory</data> + <data key="include_in_menu">true</data> + <var key="parent_id" entityType="category" entityKey="id" /> + </entity> <!-- Category from file "prepared-for-sample-data.csv"--> <entity name="Gear" type="category"> <data key="name">Gear</data> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ConfigData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ConfigData.xml new file mode 100644 index 0000000000000..35c5c8ac3c866 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ConfigData.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="EnableCategoryFilterOnCategoryPageConfigData"> + <data key="path">catalog/layered_navigation/display_category</data> + <data key="value">1</data> + </entity> + <entity name="DisableCategoryFilterOnCategoryPageConfigData"> + <data key="path">catalog/layered_navigation/display_category</data> + <data key="value">0</data> + </entity> +</entities> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml index 1effb4ed0664e..9b35ceba0494c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml @@ -31,6 +31,14 @@ <data key="attribute_code">short_description</data> <data key="value" unique="suffix">API Product Short Description</data> </entity> + <entity name="ApiProductSpecialPrice" type="custom_attribute"> + <data key="attribute_code">special_price</data> + <data key="value">51.51</data> + </entity> + <entity name="ApiProductCost" type="custom_attribute"> + <data key="attribute_code">cost</data> + <data key="value">50.05</data> + </entity> <entity name="ApiProductNewsFromDate" type="custom_attribute"> <data key="attribute_code">news_from_date</data> <data key="value">2018-05-17 00:00:00</data> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml index 75b4ef773a934..7016a1c1d0358 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml @@ -35,4 +35,11 @@ <data key="label">Magento Logo</data> <requiredEntity type="ImageContent">MagentoLogoImageContentExportImport</requiredEntity> </entity> + <entity name="ApiProductAttributeMediaGalleryEntryWithoutTypesTestImage" type="ProductAttributeMediaGalleryEntry"> + <data key="media_type">image</data> + <data key="label" unique="suffix">Test Image</data> + <data key="position">0</data> + <data key="disabled">false</data> + <requiredEntity type="ImageContent">TestImageContent</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml index a44db8010a822..d5962619984ef 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml @@ -175,6 +175,10 @@ <data key="status">1</data> <data key="quantity">0</data> </entity> + <entity name="SimpleOutOfStockProductWithSpecialPriceAndCost" type="product" extends="SimpleOutOfStockProduct"> + <requiredEntity type="custom_attribute_array">ApiProductSpecialPrice</requiredEntity> + <requiredEntity type="custom_attribute_array">ApiProductCost</requiredEntity> + </entity> <entity name="SimpleProductInStockQuantityZero" type="product"> <data key="name" unique="suffix">SimpleProductInStockQuantityZero</data> <data key="sku" unique="suffix">testSku</data> @@ -876,6 +880,14 @@ <data key="weight">5</data> <requiredEntity type="product_extension_attribute">EavStock100</requiredEntity> </entity> + <entity name="Magento2" type="image"> + <data key="title" unique="suffix">Magento2</data> + <data key="file_type">Upload File</data> + <data key="shareable">Yes</data> + <data key="file">magento2.jpg</data> + <data key="filename">magento2</data> + <data key="file_extension">jpg</data> + </entity> <entity name="Magento3" type="image"> <data key="title" unique="suffix">Magento3</data> <data key="price">1.00</data> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/RecentlyViewedProductStoreData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/RecentlyViewedProductStoreData.xml new file mode 100644 index 0000000000000..15eec8495234b --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Data/RecentlyViewedProductStoreData.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="RecentlyViewedProductScopeStore"> + <data key="path">catalog/recently_products/scope</data> + <data key="value">store</data> + </entity> + <entity name="RecentlyViewedProductScopeWebsite"> + <data key="path">catalog/recently_products/scope</data> + <data key="value">website</data> + </entity> + <entity name="RecentlyViewedProductScopeStoreGroup"> + <data key="path">catalog/recently_products/scope</data> + <data key="value">group</data> + </entity> +</entities> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml index 07dd26381fe08..1aff1a5031413 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml @@ -21,7 +21,7 @@ <element name="firstProductRowEditButton" type="button" selector="table.data-grid tr.data-row td .action-menu-item:first-of-type"/> <element name="productThumbnail" type="text" selector="table.data-grid tr:nth-child({{row}}) td.data-grid-thumbnail-cell > img" parameterized="true"/> <element name="productThumbnailBySrc" type="text" selector="img.admin__control-thumbnail[src*='{{pattern}}']" parameterized="true"/> - <element name="productGridCell" type="text" selector="//tr[{{row}}]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., '{{column}}')]/preceding-sibling::th) +1 ]" parameterized="true"/> + <element name="productGridCell" type="text" selector="//tr[{{row}}]//td[count(//div[@data-role='grid-wrapper']//tr//th[normalize-space(.)='{{column}}']/preceding-sibling::th) +1 ]" parameterized="true"/> <element name="productGridHeaderCell" type="text" selector="//div[@data-role='grid-wrapper']//tr//th[contains(., '{{column}}')]" parameterized="true"/> <element name="multicheckDropdown" type="button" selector="div[data-role='grid-wrapper'] th.data-grid-multicheck-cell button.action-multicheck-toggle"/> <element name="multicheckOption" type="button" selector="//div[@data-role='grid-wrapper']//th[contains(@class, data-grid-multicheck-cell)]//li//span[text() = '{{label}}']" parameterized="true"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StoreFrontRecentProductSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StoreFrontRecentProductSection.xml new file mode 100644 index 0000000000000..387e252ae93d4 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StoreFrontRecentProductSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> + <!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + --> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + + <section name="StoreFrontRecentlyViewedProductSection"> + <element name="ProductName" type="text" selector="//div[@class='products-grid']/ol/li[position()={{position}}]/div/div[@class='product-item-details']/strong/a" parameterized="true"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSetTest.xml similarity index 95% rename from app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml rename to app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSetTest.xml index cdb9a0a8b75d0..2e55d9fbfa4bc 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSetTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminChangeProductAttributeSet"> + <test name="AdminChangeProductAttributeSetTest"> <annotations> <features value="Checkout"/> <stories value="The required product attribute is not displayed when change attribute set"/> @@ -49,7 +49,7 @@ <actionGroup ref="ClearProductsFilterActionGroup" stepKey="clearProductsFilter"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForSimpleProduct"> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckMediaRolesForFirstAddedImageViaApiTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckMediaRolesForFirstAddedImageViaApiTest.xml new file mode 100644 index 0000000000000..c31054e3dc192 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckMediaRolesForFirstAddedImageViaApiTest.xml @@ -0,0 +1,41 @@ +<?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="AdminCheckMediaRolesForFirstAddedImageViaApiTest"> + <annotations> + <stories value="Add Simple Product with image via API"/> + <title value="Check that added image for created product has selected image roles."/> + <description value="Login as admin, create simple product, add image to created product (via API).Go to + Admin Product Edit page for created product to check that added image has selected image roles."/> + <group value="catalog"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginToAdminPanel"/> + <createData entity="SimpleOutOfStockProduct" stepKey="createSimpleProduct"/> + <createData entity="ApiProductAttributeMediaGalleryEntryWithoutTypesTestImage" stepKey="createSimpleProductImage"> + <requiredEntity createDataKey="createSimpleProduct"/> + </createData> + </before> + <after> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <actionGroup ref="logout" stepKey="logout"/> + <!-- Reindex invalidated indices after product attribute has been created/deleted --> + <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + </after> + + <actionGroup ref="GoToProductPageViaIDActionGroup" stepKey="goToSimpleProduct"> + <argument name="productId" value="$$createSimpleProduct.id$$"/> + </actionGroup> + <actionGroup ref="AdminOpenProductImagesSectionActionGroup" stepKey="openProductImagesSection"/> + <actionGroup ref="AssertAdminProductImageRolesSelectedActionGroup" stepKey="checkImageRolesSelected"> + <argument name="imageFileName" value="$createSimpleProductImage.entry[content][name]$"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckProductListPriceAttributesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckProductListPriceAttributesTest.xml new file mode 100644 index 0000000000000..f2d6d9442fb18 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckProductListPriceAttributesTest.xml @@ -0,0 +1,64 @@ +<?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="AdminCheckProductListPriceAttributesTest"> + <annotations> + <stories value="Check price attributes values on Admin Product List"/> + <title value="Check price attributes values on Admin Product List."/> + <description value="Login as admin, create simple product, add cost, special price. Go to Admin + Product List page filter grid by created product, add mentioned columns to grid, check values."/> + <group value="catalog"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginToAdminPanel"/> + <createData entity="SimpleOutOfStockProductWithSpecialPriceAndCost" stepKey="createSimpleProduct"/> + + <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="adminOpenProductIndexPage"/> + <actionGroup ref="FilterProductGridBySkuActionGroup" stepKey="filterProductGridByCreatedSimpleProductSku"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + </before> + <after> + <actionGroup ref="ClearFiltersAdminProductGridActionGroup" stepKey="clearFiltersAdminProductGrid"/> + <actionGroup ref="ToggleAdminProductGridColumnsDropdownActionGroup" stepKey="openToResetColumnsDropdown"/> + <actionGroup ref="ResetAdminProductGridColumnsActionGroup" stepKey="resetAdminProductGridColumns"/> + + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <actionGroup ref="logout" stepKey="logout"/> + <!-- Reindex invalidated indices after product attribute has been created/deleted --> + <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + </after> + + <actionGroup ref="ToggleAdminProductGridColumnsDropdownActionGroup" stepKey="openColumnsDropdown"/> + <actionGroup ref="CheckAdminProductGridColumnOptionActionGroup" stepKey="checkSpecialPriceOption"> + <argument name="optionName" value="Special Price"/> + </actionGroup> + <actionGroup ref="CheckAdminProductGridColumnOptionActionGroup" stepKey="checkCostOption"> + <argument name="optionName" value="Cost"/> + </actionGroup> + <actionGroup ref="ToggleAdminProductGridColumnsDropdownActionGroup" stepKey="closeColumnsDropdown"/> + + <actionGroup ref="AssertAdminProductGridCellActionGroup" stepKey="seePrice"> + <argument name="row" value="1"/> + <argument name="column" value="Price"/> + <argument name="value" value="${{SimpleOutOfStockProduct.price}}"/> + </actionGroup> + <actionGroup ref="AssertAdminProductGridCellActionGroup" stepKey="seeCorrectSpecialPrice"> + <argument name="row" value="1"/> + <argument name="column" value="Special Price"/> + <argument name="value" value="${{ApiProductSpecialPrice.value}}"/> + </actionGroup> + <actionGroup ref="AssertAdminProductGridCellActionGroup" stepKey="seeCorrectCost"> + <argument name="row" value="1"/> + <argument name="column" value="Cost"/> + <argument name="value" value="${{ApiProductCost.value}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithProductsGridFilter.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithProductsGridFilterTest.xml similarity index 99% rename from app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithProductsGridFilter.xml rename to app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithProductsGridFilterTest.xml index 2a4718223ef0c..9a580df52259b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithProductsGridFilter.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithProductsGridFilterTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminCreateCategoryWithProductsGridFilter"> + <test name="AdminCreateCategoryWithProductsGridFilterTest"> <annotations> <stories value="Create categories"/> <title value="Apply category products grid filter"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductCustomAttributeSet.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductCustomAttributeSetTest.xml similarity index 98% rename from app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductCustomAttributeSet.xml rename to app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductCustomAttributeSetTest.xml index 7f6feaff3ed5d..2fbd1ac2cf321 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductCustomAttributeSet.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductCustomAttributeSetTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminCreateProductCustomAttributeSet"> + <test name="AdminCreateProductCustomAttributeSetTest"> <annotations> <features value="Catalog"/> <stories value="Add/Update attribute set"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryAndCheckUrlRewritesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryAndCheckUrlRewritesTest.xml index 061bc795b2bff..77ae5dbf64840 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryAndCheckUrlRewritesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryAndCheckUrlRewritesTest.xml @@ -22,6 +22,9 @@ <createData entity="FirstLevelSubCat" stepKey="createDefaultCategory"> <field key="is_active">true</field> </createData> + <!-- Perform reindex and flush cache --> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> </before> <after> <deleteData createDataKey="createDefaultCategory" stepKey="deleteCategory"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKey.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKeyTest.xml similarity index 99% rename from app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKey.xml rename to app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKeyTest.xml index 400cc891b3c91..9babd94ef2641 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKey.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKeyTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKey"> + <test name="AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKeyTest"> <annotations> <stories value="Product"/> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminVerifyProductOrderTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminVerifyProductOrderTest.xml index 09ddcd040bea4..bd1a5aaf9ed42 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminVerifyProductOrderTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminVerifyProductOrderTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminVerifyProductOrder"> + <test name="AdminVerifyProductOrderTest"> <annotations> <features value="Catalog"/> <stories value="Verify Product Order"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreLevelTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreLevelTest.xml new file mode 100644 index 0000000000000..8243f21cb6510 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreLevelTest.xml @@ -0,0 +1,166 @@ +<?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="StoreFrontRecentlyViewedAtStoreLevelTest"> + <annotations> + <stories value="Recently Viewed Product"/> + <title value="Recently Viewed Product at store level"/> + <description value="Recently Viewed Product should not be displayed on second store , if configured as, Per Store "/> + <testCaseId value="MC-32018"/> + <severity value="CRITICAL"/> + <group value="catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create Simple Product and Category --> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createSimpleProduct1"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct2"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct3"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct4"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <!-- Create store1 for default website --> + <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="createFirstStore"> + <argument name="website" value="{{_defaultWebsite.name}}"/> + <argument name="storeGroupName" value="{{customStore.name}}"/> + <argument name="storeGroupCode" value="{{customStore.code}}"/> + </actionGroup> + <!-- Create Storeview1 for Store1--> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView"> + <argument name="StoreGroup" value="customStore"/> + <argument name="customStore" value="storeViewData"/> + </actionGroup> + <!--Create storeView 2--> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreViewTwo"> + <argument name="StoreGroup" value="customStore"/> + <argument name="customStore" value="customStoreEN"/> + </actionGroup> + <!-- Set Stores > Configurations > Catalog > Recently Viewed/Compared Products > Show for Current = store --> + <magentoCLI command="config:set {{RecentlyViewedProductScopeStoreGroup.path}} {{RecentlyViewedProductScopeStoreGroup.value}}" stepKey="RecentlyViewedProductScopeStoreGroup"/> + </before> + <after> + <!-- Delete Product and Category --> + <deleteData createDataKey="createSimpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="createSimpleProduct2" stepKey="deleteSimpleProduct2"/> + <deleteData createDataKey="createSimpleProduct3" stepKey="deleteSimpleProduct3"/> + <deleteData createDataKey="createSimpleProduct4" stepKey="deleteSimpleProduct4"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <!-- Delete store1 for default website --> + <actionGroup ref="DeleteCustomStoreActionGroup" stepKey="deleteFirstStore"> + <argument name="storeGroupName" value="customStore.name"/> + </actionGroup> + + <!--Reset Stores > Configurations > Catalog > Recently Viewed/Compared Products > Show for Current = Website--> + <magentoCLI command="config:set {{RecentlyViewedProductScopeWebsite.path}} {{RecentlyViewedProductScopeWebsite.value}}" stepKey="RecentlyViewedProductScopeWebsite"/> + + <!-- Clear Widget--> + <actionGroup ref="AdminEditCMSPageContentActionGroup" stepKey="clearRecentlyViewedWidgetsFromCMSContent"> + <argument name="content" value="{{CmsHomePageContent.content}}"/> + <argument name="pageId" value="{{CmsHomePageContent.page_id}}"/> + </actionGroup> + <!-- Logout Admin --> + <actionGroup ref="logout" stepKey="logout"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCacheAfterDeletion"/> + </after> + <!--Create widget for recently viewed products--> + <actionGroup ref="AdminEditCMSPageContentActionGroup" stepKey="clearRecentlyViewedWidgetsFromCMSContentBefore"> + <argument name="content" value="{{CmsHomePageContent.content}}"/> + <argument name="pageId" value="{{CmsHomePageContent.page_id}}"/> + </actionGroup> + + <amOnPage url="{{AdminCmsPageEditPage.url(CmsHomePageContent.page_id)}}" stepKey="navigateToEditCmsHomePage"/> + <waitForPageLoad time="50" stepKey="waitForCmsContentPageToLoad"/> + + <actionGroup ref="AdminInsertRecentlyViewedWidgetActionGroup" stepKey="insertRecentlyViewedWidget"> + <argument name="attributeSelector1" value="show_attributes"/> + <argument name="attributeSelector2" value="show_buttons"/> + <argument name="productAttributeSection1" value="1"/> + <argument name="productAttributeSection2" value="4"/> + <argument name="buttonToShowSection1" value="1"/> + <argument name="buttonToShowSection2" value="3"/> + </actionGroup> + <!-- Warm up cache --> + <magentoCLI command="cache:flush" stepKey="flushCacheAfterWidgetCreated"/> + <!-- Navigate to product 3 on store front --> + <amOnPage url="{{StorefrontProductPage.url($createSimpleProduct2.name$)}}" stepKey="goToStoreOneProductPageTwo"/> + <amOnPage url="{{StorefrontProductPage.url($createSimpleProduct3.name$)}}" stepKey="goToStoreOneProductPageThree"/> + <!-- Go to Home Page --> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnStoreFrontHomePage"/> + <waitForPageLoad time="30" stepKey="waitForHomeContentPageToLoad"/> + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertStoreOneRecentlyViewedProduct2"> + <argument name="productName" value="$$createSimpleProduct2.name$$"/> + <argument name="productPosition" value="2"/> + </actionGroup> + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertStoreOneRecentlyViewedProduct3"> + <argument name="productName" value="$$createSimpleProduct3.name$$"/> + <argument name="productPosition" value="1"/> + </actionGroup> + <!-- Switch to second store and add second product (visible on second store) to wishlist --> + <click selector="{{StorefrontFooterSection.switchStoreButton}}" stepKey="clickSwitchStoreButtonOnDefaultStore"/> + <click selector="{{StorefrontFooterSection.storeLink(customStore.name)}}" stepKey="selectCustomStore"/> + <amOnPage url="{{StorefrontProductPage.url($createSimpleProduct1.name$)}}" stepKey="goToStore2ProductPage1"/> + <amOnPage url="{{StorefrontProductPage.url($createSimpleProduct2.name$)}}" stepKey="goToStore2ProductPage2"/> + <!-- Go to Home Page --> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnHomePage2"/> + <waitForPageLoad time="30" stepKey="waitForStoreHomeContentPageToLoad"/> + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertNextStore1RecentlyViewedProduct1"> + <argument name="productName" value="$$createSimpleProduct1.name$$"/> + <argument name="productPosition" value="2"/> + </actionGroup> + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertNextStore1RecentlyViewedProduct2"> + <argument name="productName" value="$$createSimpleProduct2.name$$"/> + <argument name="productPosition" value="1"/> + </actionGroup> + + <grabTextFrom selector="{{StoreFrontRecentlyViewedProductSection.ProductName('2')}}" stepKey="grabDontSeeHomeProduct3"/> + <assertNotContains expected="$$createSimpleProduct3.name$$" actual="$grabDontSeeHomeProduct3" stepKey="assertNotSeeProduct3"/> + + <!-- Switch Storeview--> + <actionGroup ref="StorefrontSwitchStoreViewActionGroup" stepKey="switchStoreViewActionGroup"> + <argument name="storeView" value="customStoreEN"/> + </actionGroup> + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertNextStoreView2RecentlyViewedProduct1"> + <argument name="productName" value="$$createSimpleProduct1.name$$"/> + <argument name="productPosition" value="2"/> + </actionGroup> + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertNextStoreView2RecentlyViewedProduct2"> + <argument name="productName" value="$$createSimpleProduct2.name$$"/> + <argument name="productPosition" value="1"/> + </actionGroup> + + <grabTextFrom selector="{{StoreFrontRecentlyViewedProductSection.ProductName('2')}}" stepKey="grabStoreView2DontSeeHomeProduct3"/> + <assertNotContains expected="$$createSimpleProduct3.name$$" actual="$grabDontSeeHomeProduct3" stepKey="assertStoreView2NotSeeProduct3"/> + + <!-- Switch to default store--> + + <click selector="{{StorefrontFooterSection.switchStoreButton}}" stepKey="clickSwitchStoreButtonOnHomeDefaultStore"/> + <click selector="{{StorefrontFooterSection.storeLink('Main Website Store')}}" stepKey="selectDefaultStoreToSwitchOn"/> + + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertSwitchStore1RecentlyViewedProduct2"> + <argument name="productName" value="$$createSimpleProduct2.name$$"/> + <argument name="productPosition" value="2"/> + </actionGroup> + + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertSwitchStore1RecentlyViewedProduct3"> + <argument name="productName" value="$$createSimpleProduct3.name$$"/> + <argument name="productPosition" value="1"/> + </actionGroup> + <grabTextFrom selector="{{StoreFrontRecentlyViewedProductSection.ProductName('2')}}" stepKey="grabDontSeeHomeProduct1"/> + <assertNotContains expected="$$createSimpleProduct1.name$$" actual="$grabDontSeeHomeProduct1" stepKey="assertNotSeeProduct1"/> + + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreViewLevelTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreViewLevelTest.xml new file mode 100644 index 0000000000000..f8ee9e562a6a9 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreViewLevelTest.xml @@ -0,0 +1,149 @@ +<?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="StoreFrontRecentlyViewedAtStoreViewLevelTest"> + <annotations> + <stories value="Recently Viewed Product"/> + <title value="Recently Viewed Product at store view level"/> + <description value="Recently Viewed Product should not be displayed on second store view, if configured as, Per Store View "/> + <testCaseId value="MC-31877"/> + <severity value="CRITICAL"/> + <group value="catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create Simple Product and Category --> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createSimpleProduct1"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct2"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct3"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct4"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <!--Create storeView 1--> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreViewOne"> + <argument name="customStore" value="customStoreEN"/> + </actionGroup> + <!-- Set Stores > Configurations > Catalog > Recently Viewed/Compared Products > Show for Current = store view--> + <magentoCLI command="config:set {{RecentlyViewedProductScopeStore.path}} {{RecentlyViewedProductScopeStore.value}}" stepKey="RecentlyViewedProductScopeStore"/> + </before> + + <after> + <!-- Delete Product and Category --> + <deleteData createDataKey="createSimpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="createSimpleProduct2" stepKey="deleteSimpleProduct2"/> + <deleteData createDataKey="createSimpleProduct3" stepKey="deleteSimpleProduct3"/> + <deleteData createDataKey="createSimpleProduct4" stepKey="deleteSimpleProduct4"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <waitForPageLoad time="30" stepKey="waitForPageLoadWebSite"/> + <magentoCLI command="config:set {{RecentlyViewedProductScopeWebsite.path}} {{RecentlyViewedProductScopeWebsite.value}}" stepKey="RecentlyViewedProductScopeWebsite"/> + <!--Delete store views--> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteFirstStoreView"> + <argument name="customStore" value="customStoreEN"/> + </actionGroup> + + <!-- Clear Widget--> + <actionGroup ref="AdminEditCMSPageContentActionGroup" stepKey="clearRecentlyViewedWidgetsFromCMSContent"> + <argument name="content" value="{{CmsHomePageContent.content}}"/> + <argument name="pageId" value="{{CmsHomePageContent.page_id}}"/> + </actionGroup> + + <!-- Logout Admin --> + <actionGroup ref="logout" stepKey="logout"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCacheAfterDeletion"/> + + </after> + + <!--Create widget for recently viewed products--> + <actionGroup ref="AdminEditCMSPageContentActionGroup" stepKey="clearRecentlyViewedWidgetsFromCMSContentBefore"> + <argument name="content" value="{{CmsHomePageContent.content}}"/> + <argument name="pageId" value="{{CmsHomePageContent.page_id}}"/> + </actionGroup> + + <amOnPage url="{{AdminCmsPageEditPage.url(CmsHomePageContent.page_id)}}" stepKey="navigateToEditHomePagePage"/> + <waitForPageLoad time="50" stepKey="waitForContentPageToLoad"/> + + <actionGroup ref="AdminInsertRecentlyViewedWidgetActionGroup" stepKey="insertRecentlyViewedWidget"> + <argument name="attributeSelector1" value="show_attributes"/> + <argument name="attributeSelector2" value="show_buttons"/> + <argument name="productAttributeSection1" value="1"/> + <argument name="productAttributeSection2" value="4"/> + <argument name="buttonToShowSection1" value="1"/> + <argument name="buttonToShowSection2" value="3"/> + </actionGroup> + + <!-- Warm up cache --> + <magentoCLI command="cache:flush" stepKey="flushCacheAfterWidgetCreated"/> + + <!-- Navigate to product 3 on store front --> + <amOnPage url="{{StorefrontProductPage.url($createSimpleProduct2.name$)}}" stepKey="goToStore1ProductPage2"/> + <amOnPage url="{{StorefrontProductPage.url($createSimpleProduct3.name$)}}" stepKey="goToStore1ProductPage3"/> + <!-- Go to Home Page --> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnHomePage"/> + <waitForPageLoad time="30" stepKey="homeWaitForPageLoad"/> + + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertStore1RecentlyViewedProduct2"> + <argument name="productName" value="$$createSimpleProduct2.name$$"/> + <argument name="productPosition" value="2"/> + </actionGroup> + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertStore1RecentlyViewedProduct3"> + <argument name="productName" value="$$createSimpleProduct3.name$$"/> + <argument name="productPosition" value="1"/> + </actionGroup> + + <!-- Switch store view --> + <waitForPageLoad time="40" stepKey="waitForStorefrontPageLoad"/> + <actionGroup ref="StorefrontSwitchStoreViewActionGroup" stepKey="switchStoreViewActionGroup"> + <argument name="storeView" value="customStoreEN"/> + </actionGroup> + + <amOnPage url="{{StorefrontProductPage.url($createSimpleProduct1.name$)}}" stepKey="goToStore2ProductPage1"/> + <amOnPage url="{{StorefrontProductPage.url($createSimpleProduct2.name$)}}" stepKey="goToStore2ProductPage2"/> + + <!-- Go to Home Page --> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnStoreViewHomePage"/> + <waitForPageLoad time="30" stepKey="homePageWaitForStoreView"/> + + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertNextStore1RecentlyViewedProduct1"> + <argument name="productName" value="$$createSimpleProduct1.name$$"/> + <argument name="productPosition" value="2"/> + </actionGroup> + + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertNextStore1RecentlyViewedProduct2"> + <argument name="productName" value="$$createSimpleProduct2.name$$"/> + <argument name="productPosition" value="1"/> + </actionGroup> + + <grabTextFrom selector="{{StoreFrontRecentlyViewedProductSection.ProductName('2')}}" stepKey="grabDontSeeHomeProduct3"/> + <assertNotContains expected="$$createSimpleProduct3.name$$" actual="$grabDontSeeHomeProduct3" stepKey="assertNotSeeProduct3"/> + + <actionGroup ref="StorefrontSwitchDefaultStoreViewActionGroup" stepKey="switchToDefualtStoreView"/> + + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertSwitchStore1RecentlyViewedProduct2"> + <argument name="productName" value="$$createSimpleProduct2.name$$"/> + <argument name="productPosition" value="2"/> + </actionGroup> + + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertSwitchStore1RecentlyViewedProduct3"> + <argument name="productName" value="$$createSimpleProduct3.name$$"/> + <argument name="productPosition" value="1"/> + </actionGroup> + + <grabTextFrom selector="{{StoreFrontRecentlyViewedProductSection.ProductName('2')}}" stepKey="grabDontSeeHomeProduct1"/> + <assertNotContains expected="$$createSimpleProduct1.name$$" actual="$grabDontSeeHomeProduct1" stepKey="assertNotSeeProduct1"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithCategoryFilterTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithCategoryFilterTest.xml new file mode 100644 index 0000000000000..8955f43e1b335 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithCategoryFilterTest.xml @@ -0,0 +1,63 @@ +<?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="StorefrontCategoryPageWithCategoryFilterTest"> + <annotations> + <title value="Category with Layered Navigation - verify presence of category filter"/> + <stories value="Category page: Layered Navigation with category filter"/> + <description value="Verify that the category filter is present in layered navigation on category page"/> + <features value="Catalog"/> + <severity value="MINOR"/> + <group value="Catalog"/> + </annotations> + + <before> + <!-- Create one category --> + <createData entity="_defaultCategory" stepKey="defaultCategory"> + <field key="name">TopCategory</field> + </createData> + <!-- Create second category, having as parent the 1st one --> + <createData entity="SubCategoryWithParent" stepKey="subCategory"> + <field key="name">SubCategory</field> + <field key="parent_id">$$defaultCategory.id$$</field> + <requiredEntity createDataKey="defaultCategory"/> + </createData> + + <!-- Create a product assigned to the 1st category --> + <createData entity="_defaultProduct" stepKey="createSimpleProduct1"> + <requiredEntity createDataKey="defaultCategory"/> + </createData> + + <!-- Create 2nd product assigned to the 2nd category --> + <!-- The "Category filter" item is not shown in layered navigation --> + <!-- if there are not subcategories with products to show --> + <createData entity="_defaultProduct" stepKey="createSimpleProduct2"> + <requiredEntity createDataKey="subCategory"/> + </createData> + + <!-- Set the category filter to be present on the category page layered navigation --> + <magentoCLI command="config:set {{EnableCategoryFilterOnCategoryPageConfigData.path}} {{EnableCategoryFilterOnCategoryPageConfigData.value}}" stepKey="setCategoryFilterVisibleOnStorefront"/> + + <magentoCLI command="cache:flush" stepKey="clearCache1"/> + </before> + + <after> + <deleteData createDataKey="createSimpleProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="createSimpleProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="subCategory" stepKey="deleteSubCategory"/> + <deleteData createDataKey="defaultCategory" stepKey="deleteCategoryMainCategory"/> + </after> + + <actionGroup ref="StorefrontNavigateCategoryPageActionGroup" stepKey="navigateToCategoryPage"> + <argument name="category" value="$$defaultCategory$$"/> + </actionGroup> + + <actionGroup ref="AssertStorefrontLayeredNavigationCategoryFilterVisibleActionGroup" stepKey="checkCategoryFilterIsPresent" /> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithoutCategoryFilterTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithoutCategoryFilterTest.xml new file mode 100644 index 0000000000000..7900a712e0664 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithoutCategoryFilterTest.xml @@ -0,0 +1,63 @@ +<?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="StorefrontCategoryPageWithoutCategoryFilterTest"> + <annotations> + <title value="Category with Layered Navigation - verify absence of category filter"/> + <stories value="Category page: Layered Navigation without category filter"/> + <description value="Verify that the category filter is NOT present in layered navigation on category page"/> + <features value="Catalog"/> + <severity value="MINOR"/> + <group value="Catalog"/> + </annotations> + + <before> + <!-- Create one category --> + <createData entity="_defaultCategory" stepKey="defaultCategory"> + <field key="name">TopCategory</field> + </createData> + <!-- Create second category, having as parent the 1st one --> + <createData entity="SubCategoryWithParent" stepKey="subCategory"> + <field key="name">SubCategory</field> + <field key="parent_id">$$defaultCategory.id$$</field> + <requiredEntity createDataKey="defaultCategory"/> + </createData> + + <!-- Create a product assigned to the 1st category --> + <createData entity="_defaultProduct" stepKey="createSimpleProduct1"> + <requiredEntity createDataKey="defaultCategory"/> + </createData> + + <!-- Create 2nd product assigned to the 2nd category --> + <!-- The "Category filter" item is not shown in layered navigation --> + <!-- if there are not subcategories with products to show --> + <createData entity="_defaultProduct" stepKey="createSimpleProduct2"> + <requiredEntity createDataKey="subCategory"/> + </createData> + + <!-- Set the category filter to NOT be present on the category page layered navigation --> + <magentoCLI command="config:set {{DisableCategoryFilterOnCategoryPageConfigData.path}} {{DisableCategoryFilterOnCategoryPageConfigData.value}}" stepKey="hideCategoryFilterOnStorefront"/> + + <magentoCLI command="cache:flush" stepKey="clearCache"/> + </before> + + <after> + <deleteData createDataKey="createSimpleProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="createSimpleProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="subCategory" stepKey="deleteSubCategory"/> + <deleteData createDataKey="defaultCategory" stepKey="deleteCategoryMainCategory"/> + </after> + + <actionGroup ref="StorefrontNavigateCategoryPageActionGroup" stepKey="navigateToCategoryPage"> + <argument name="category" value="$$defaultCategory$$"/> + </actionGroup> + + <actionGroup ref="AssertStorefrontLayeredNavigationCategoryFilterNotVisibleActionGroup" stepKey="checkCategoryFilterIsNotPresent" /> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontConfigurableOptionsThumbImagesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontConfigurableOptionsThumbImagesTest.xml new file mode 100644 index 0000000000000..134fd89c2c813 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontConfigurableOptionsThumbImagesTest.xml @@ -0,0 +1,228 @@ +<?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="StorefrontConfigurableOptionsThumbImagesTest"> + <annotations> + <stories value="Configurable Product"/> + <title value="Check thumbnail images and active image for Configurable Product"/> + <description value="Login as admin, create attribute with two options, configurable product with two + associated simple products. Add few images for products, check the fotorama thumbnail images + (visible and active) for each selected option for the configurable product"/> + <group value="catalog"/> + </annotations> + <before> + <!-- Login as Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + + <!-- Create Default Category --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + + <!-- Create an attribute with two options to be used in the first child product --> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + + <!-- Add the attribute just created to default attribute set --> + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + + <!-- Get the first option of the attribute created --> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <!-- Get the second option of the attribute created --> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getConfigAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <!-- Create Configurable product --> + <createData entity="BaseConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!-- Create a simple product and give it the attribute with the first option --> + <createData entity="ApiSimpleOne" stepKey="createConfigChildProduct1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + </createData> + + <!--Create a simple product and give it the attribute with the second option --> + <createData entity="ApiSimpleTwo" stepKey="createConfigChildProduct2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + + <!-- Create the configurable product --> + <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + + <!-- Add the first simple product to the configurable product --> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild1"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct1"/> + </createData> + + <!-- Add the second simple product to the configurable product --> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild2"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct2"/> + </createData> + + <!-- ConfigProduct --> + <!-- Go to Product Page (ConfigProduct) --> + <actionGroup ref="GoToProductPageViaIDActionGroup" stepKey="goToConfigProduct"> + <argument name="productId" value="$$createConfigProduct.id$$"/> + </actionGroup> + + <!--Switch to 'Default Store View' scope and open product page--> + <actionGroup ref="SwitchToTheNewStoreViewActionGroup" stepKey="SwitchDefaultStoreViewForConfigProduct"> + <argument name="storeViewName" value="'Default Store View'"/> + </actionGroup> + + <!-- Add images for ConfigProduct --> + <actionGroup ref="AddProductImageActionGroup" stepKey="addConfigProductMagento3"> + <argument name="image" value="Magento3"/> + </actionGroup> + + <actionGroup ref="AddProductImageActionGroup" stepKey="addConfigProductTestImageAdobe"> + <argument name="image" value="TestImageAdobe"/> + </actionGroup> + <actionGroup ref="AdminAssignImageBaseRoleActionGroup" stepKey="assignTestImageAdobeBaseRole"> + <argument name="image" value="TestImageAdobe"/> + </actionGroup> + + <!-- Save changes fot ConfigProduct --> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveConfigProductProduct"/> + + <!-- ChildProduct1 --> + <!-- Go to Product Page (ChildProduct1) --> + <actionGroup ref="GoToProductPageViaIDActionGroup" stepKey="goToChildProduct1"> + <argument name="productId" value="$$createConfigChildProduct1.id$$"/> + </actionGroup> + + <!--Switch to 'Default Store View' scope and open product page--> + <actionGroup ref="SwitchToTheNewStoreViewActionGroup" stepKey="SwitchDefaultStoreViewForChildProduct1"> + <argument name="storeViewName" value="'Default Store View'"/> + </actionGroup> + + <!-- Add images for ChildProduct1 --> + <actionGroup ref="AddProductImageActionGroup" stepKey="addChildProduct1ProductImage"> + <argument name="image" value="ProductImage"/> + </actionGroup> + <actionGroup ref="AddProductImageActionGroup" stepKey="addChildProduct1Magento2"> + <argument name="image" value="Magento2"/> + </actionGroup> + <actionGroup ref="AdminAssignImageRolesIfUnassignedActionGroup" stepKey="assignMagento2Role"> + <argument name="image" value="Magento2"/> + </actionGroup> + + <!-- Save changes fot ChildProduct1 --> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveChildProduct1Product"/> + + <!-- ChildProduct2 --> + <!-- Go to Product Page (ChildProduct2) --> + <actionGroup ref="GoToProductPageViaIDActionGroup" stepKey="goToChildProduct2"> + <argument name="productId" value="$$createConfigChildProduct2.id$$"/> + </actionGroup> + + <!--Switch to 'Default Store View' scope and open product page--> + <actionGroup ref="SwitchToTheNewStoreViewActionGroup" stepKey="SwitchDefaultStoreViewForChildProduct2"> + <argument name="storeViewName" value="'Default Store View'"/> + </actionGroup> + + <!-- Add image for ChildProduct2 --> + <actionGroup ref="AddProductImageActionGroup" stepKey="addChildProduct2TestImageNew"> + <argument name="image" value="TestImageNew"/> + </actionGroup> + + <!-- Save changes fot ChildProduct2 --> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveChildProduct2Product"/> + </before> + <after> + <!-- Delete Created Data --> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createConfigChildProduct1" stepKey="deleteConfigChildProduct1"/> + <deleteData createDataKey="createConfigChildProduct2" stepKey="deleteConfigChildProduct2"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteAttribute"/> + <actionGroup ref="logout" stepKey="logout"/> + + <!-- Reindex invalidated indices after product attribute has been created/deleted --> + <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + </after> + + <!-- Open ConfigProduct in Store Front Page --> + <amOnPage url="$$createConfigProduct.sku$$.html" stepKey="openProductInStoreFront"/> + <waitForPageLoad stepKey="waitForProductToLoad"/> + + <!-- Check fotorama thumbnail images (no selected options) --> + <actionGroup ref="StorefrontAssertFotoramaImageAvailabilityActionGroup" stepKey="seeMagento3ForNoOption"> + <argument name="fileName" value="{{Magento3.filename}}"/> + </actionGroup> + + <actionGroup ref="StorefrontAssertFotoramaImageAvailabilityActionGroup" stepKey="seeActiveTestImageAdobeForNoOption"> + <argument name="fileName" value="{{TestImageAdobe.filename}}"/> + </actionGroup> + + <!-- Select first option --> + <actionGroup ref="StorefrontProductPageSelectDropDownOptionValueActionGroup" stepKey="selectFirstOptionValue"> + <argument name="attributeLabel" value="$$createConfigProductAttribute.default_frontend_label$$"/> + <argument name="optionLabel" value="$$getConfigAttributeOption1.label$$"/> + </actionGroup> + + <!-- Check fotorama thumbnail images (first option selected) --> + <actionGroup ref="StorefrontAssertFotoramaImageAvailabilityActionGroup" stepKey="seeMagento3ForFirstOption"> + <argument name="fileName" value="{{Magento3.filename}}"/> + </actionGroup> + + <actionGroup ref="StorefrontAssertFotoramaImageAvailabilityActionGroup" stepKey="seeTestImageAdobeForFirstOption"> + <argument name="fileName" value="{{TestImageAdobe.filename}}"/> + </actionGroup> + + <actionGroup ref="StorefrontAssertFotoramaImageAvailabilityActionGroup" stepKey="seeProductImageForFirstOption"> + <argument name="fileName" value="{{ProductImage.filename}}"/> + </actionGroup> + + <!-- Check active fotorama thumbnail image (first option selected) --> + <actionGroup ref="StorefrontAssertActiveProductImageActionGroup" stepKey="seeActiveMagento2ForFirstOption"> + <argument name="fileName" value="{{Magento2.filename}}"/> + </actionGroup> + + <!-- Select second option --> + <actionGroup ref="StorefrontProductPageSelectDropDownOptionValueActionGroup" stepKey="selectSecondOptionValue"> + <argument name="attributeLabel" value="$$createConfigProductAttribute.default_frontend_label$$"/> + <argument name="optionLabel" value="$$getConfigAttributeOption2.label$$"/> + </actionGroup> + + <!-- Check fotorama thumbnail images (second option selected) --> + <actionGroup ref="StorefrontAssertFotoramaImageAvailabilityActionGroup" stepKey="seeMagento3ForSecondOption"> + <argument name="fileName" value="{{Magento3.filename}}"/> + </actionGroup> + + <actionGroup ref="StorefrontAssertFotoramaImageAvailabilityActionGroup" stepKey="seeTestImageAdobeForSecondOption"> + <argument name="fileName" value="{{TestImageAdobe.filename}}"/> + </actionGroup> + + <!-- Check active fotorama thumbnail image (second option selected) --> + <actionGroup ref="StorefrontAssertActiveProductImageActionGroup" stepKey="seeActiveTestImageNewForSecondOption"> + <argument name="fileName" value="{{TestImageNew.filename}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuote.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuoteTest.xml similarity index 98% rename from app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuote.xml rename to app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuoteTest.xml index 07c2e8a972596..1615f75395fed 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuote.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuoteTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontProductNameWithDoubleQuote"> + <test name="StorefrontProductNameWithDoubleQuoteTest"> <annotations> <features value="Catalog"/> <stories value="Create products"/> @@ -66,7 +66,7 @@ </actionGroup> </test> - <test name="StorefrontProductNameWithHTMLEntities"> + <test name="StorefrontProductNameWithHTMLEntitiesTest"> <annotations> <features value="Catalog"/> <stories value="Create product"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitleTest.xml similarity index 99% rename from app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml rename to app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitleTest.xml index 36a803b03199b..b8aed7f0ac2ad 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitleTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle"> + <test name="StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitleTest"> <annotations> <features value="Catalog"/> <stories value="Custom options"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSelectedByQueryParamsConfigurableOptionsThumbImagesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSelectedByQueryParamsConfigurableOptionsThumbImagesTest.xml new file mode 100644 index 0000000000000..941ae6fb84c56 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSelectedByQueryParamsConfigurableOptionsThumbImagesTest.xml @@ -0,0 +1,38 @@ +<?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="StorefrontSelectedByQueryParamsConfigurableOptionsThumbImagesTest" + extends="StorefrontConfigurableOptionsThumbImagesTest"> + <annotations> + <stories value="Configurable Product"/> + <title value="Check thumbnail images and active image for Configurable Product with predefined + by query params options"/> + <description value="Login as admin, create attribute with two options, configurable product with two + associated simple products. Add few images for products, check the fotorama thumbnail images + (visible and active) for each option for the configurable product using product URL with params + to selected needed option."/> + <group value="catalog"/> + </annotations> + + <!-- Select first option using product query params URL --> + <amOnPage + url="$$createConfigProduct.sku$$.html#$$createConfigProductAttribute.attribute_id$$=$$getConfigAttributeOption1.value$$" + stepKey="selectFirstOptionValue"/> + <reloadPage stepKey="selectFirstOptionValueRefreshPage" after="selectFirstOptionValue"/> + <waitForPageLoad stepKey="waitForProductWithSelectedFirstOptionToLoad" after="selectFirstOptionValueRefreshPage"/> + + <!-- Select second option using product query params URL --> + <amOnPage + url="$$createConfigProduct.sku$$.html#$$createConfigProductAttribute.attribute_id$$=$$getConfigAttributeOption2.value$$" + stepKey="selectSecondOptionValue"/> + <reloadPage stepKey="selectSecondOptionValueRefreshPage" after="selectSecondOptionValue"/> + <waitForPageLoad stepKey="waitForProductWithSelectedSecondOptionToLoad" after="selectSecondOptionValueRefreshPage"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyDefaultWYSIWYGToolbarOnProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyDefaultWYSIWYGToolbarOnProductTest.xml index 8c10f22b7b09e..ae74a000d76e8 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyDefaultWYSIWYGToolbarOnProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyDefaultWYSIWYGToolbarOnProductTest.xml @@ -47,7 +47,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> </test> - <test name="Verifydefaultcontrolsonproductshortdescription"> + <test name="VerifydefaultcontrolsonproductshortdescriptionTest"> <annotations> <features value="Catalog"/> <stories value="Default toolbar configuration in Magento-MAGETWO-70412"/> diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Product/ImageFactoryTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Product/ImageFactoryTest.php index 9a15a5c6c7243..07395f1c81dd3 100644 --- a/app/code/Magento/Catalog/Test/Unit/Block/Product/ImageFactoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Block/Product/ImageFactoryTest.php @@ -145,7 +145,7 @@ private function getTestDataWithoutAttributes(): array 'height' => 100, 'label' => 'test_image_label', 'ratio' => 1, - 'custom_attributes' => '', + 'custom_attributes' => [], 'product_id' => null, 'class' => 'product-image-photo' ], @@ -203,7 +203,10 @@ private function getTestDataWithAttributes(): array 'height' => 50, 'label' => 'test_product_name', 'ratio' => 0.5, // <== - 'custom_attributes' => 'name_1="value_1" name_2="value_2"', + 'custom_attributes' => [ + 'name_1' => 'value_1', + 'name_2' => 'value_2', + ], 'product_id' => null, 'class' => 'my-class' ], diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Category/DataProviderTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Category/DataProviderTest.php index 4ce50537f27bd..ce131a1953bfd 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Category/DataProviderTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Category/DataProviderTest.php @@ -10,6 +10,7 @@ use Magento\Catalog\Model\Category\Attribute\Backend\Image; use Magento\Catalog\Model\Category\DataProvider; use Magento\Catalog\Model\Category\FileInfo; +use Magento\Catalog\Model\Category\Image as CategoryImage; use Magento\Catalog\Model\CategoryFactory; use Magento\Catalog\Model\ResourceModel\Category\Collection; use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory; @@ -98,6 +99,11 @@ class DataProviderTest extends TestCase */ private $auth; + /** + * @var CategoryImage|MockObject + */ + private $categoryImage; + /** * @inheritDoc */ @@ -155,6 +161,11 @@ protected function setUp() $this->arrayUtils = $this->getMockBuilder(ArrayUtils::class) ->setMethods(['flatten']) ->disableOriginalConstructor()->getMock(); + + $this->categoryImage = $this->createPartialMock( + CategoryImage::class, + ['getUrl'] + ); } /** @@ -185,7 +196,8 @@ private function getModel() 'categoryFactory' => $this->categoryFactory, 'pool' => $this->modifierPool, 'auth' => $this->auth, - 'arrayUtils' => $this->arrayUtils + 'arrayUtils' => $this->arrayUtils, + 'categoryImage' => $this->categoryImage, ] ); @@ -324,8 +336,8 @@ public function testGetData() $categoryMock->expects($this->once()) ->method('getAttributes') ->willReturn(['image' => $attributeMock]); - $categoryMock->expects($this->once()) - ->method('getImageUrl') + $this->categoryImage->expects($this->once()) + ->method('getUrl') ->willReturn($categoryUrl); $this->registry->expects($this->once()) diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Category/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Category/ImageTest.php new file mode 100644 index 0000000000000..7cf63a56283f2 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/Category/ImageTest.php @@ -0,0 +1,146 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Test\Unit\Model\Category; + +use Magento\Catalog\Model\Category; +use Magento\Catalog\Model\Category\FileInfo; +use Magento\Catalog\Model\Category\Image; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\UrlInterface; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManager; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Test category image resolver + */ +class ImageTest extends TestCase +{ + /** + * @var Store|MockObject + */ + private $store; + /** + * @var Category + */ + private $category; + /** + * @var Image + */ + private $model; + + /** + * @inheritDoc + */ + protected function setUp() + { + $storeManager = $this->createPartialMock(StoreManager::class, ['getStore']); + $this->store = $this->createPartialMock(Store::class, ['getBaseUrl']); + $storeManager->method('getStore')->willReturn($this->store); + $objectManager = new ObjectManager($this); + $this->category = $objectManager->getObject(Category::class); + $this->model = $objectManager->getObject( + Image::class, + [ + 'storeManager' => $storeManager, + 'fileInfo' => $this->getFileInfo() + ] + ); + } + + /** + * Test that image URL resolver works correctly with different base URL format + * + * @param string $baseUrl + * @param string $imagePath + * @param string $url + * @dataProvider getUrlDataProvider + */ + public function testGetUrl(string $imagePath, string $baseUrl, string $url) + { + $this->store->method('getBaseUrl') + ->with(UrlInterface::URL_TYPE_MEDIA) + ->willReturn($baseUrl); + $this->category->setData('image_attr_code', $imagePath); + $this->assertEquals($url, $this->model->getUrl($this->category, 'image_attr_code')); + } + + /** + * @return array + */ + public function getUrlDataProvider() + { + return [ + [ + 'testimage', + 'http://www.example.com/', + 'http://www.example.com/catalog/category/testimage' + ], + [ + 'testimage', + 'http://www.example.com/pub/media/', + 'http://www.example.com/pub/media/catalog/category/testimage' + ], + [ + 'testimage', + 'http://www.example.com/base/path/pub/media/', + 'http://www.example.com/base/path/pub/media/catalog/category/testimage' + ], + [ + '/pub/media/catalog/category/testimage', + 'http://www.example.com/pub/media/', + 'http://www.example.com/pub/media/catalog/category/testimage' + ], + [ + '/pub/media/catalog/category/testimage', + 'http://www.example.com/base/path/pub/media/', + 'http://www.example.com/base/path/pub/media/catalog/category/testimage' + ], + [ + '/pub/media/posters/testimage', + 'http://www.example.com/pub/media/', + 'http://www.example.com/pub/media/posters/testimage' + ], + [ + '/pub/media/posters/testimage', + 'http://www.example.com/base/path/pub/media/', + 'http://www.example.com/base/path/pub/media/posters/testimage' + ], + [ + '', + 'http://www.example.com/', + '' + ] + ]; + } + + /** + * Get FileInfo mock + * + * @return MockObject + */ + private function getFileInfo(): MockObject + { + $mediaDir = 'pub/media'; + $fileInfo = $this->createMock(FileInfo::class); + $fileInfo->method('isBeginsWithMediaDirectoryPath') + ->willReturnCallback( + function ($path) use ($mediaDir) { + return strpos(ltrim($path, '/'), $mediaDir) === 0; + } + ); + $fileInfo->method('getRelativePathToMediaDirectory') + ->willReturnCallback( + function ($path) use ($mediaDir) { + return str_replace($mediaDir, '', $path); + } + ); + return $fileInfo; + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php index 731c5efd99746..84c433379b156 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php @@ -7,8 +7,15 @@ namespace Magento\Catalog\Test\Unit\Model\Layer; +use Magento\Catalog\Model\Config\LayerCategoryConfig; use \Magento\Catalog\Model\Layer\FilterList; +use PHPUnit\Framework\MockObject\MockObject; +/** + * Filter List Test + * + * Check whenever the given filters list matches the expected result + */ class FilterListTest extends \PHPUnit\Framework\TestCase { /** @@ -36,6 +43,14 @@ class FilterListTest extends \PHPUnit\Framework\TestCase */ protected $model; + /** + * @var LayerCategoryConfig|MockObject + */ + private $layerCategoryConfigMock; + + /** + * Set Up + */ protected function setUp() { $this->objectManagerMock = $this->createMock(\Magento\Framework\ObjectManagerInterface::class); @@ -51,8 +66,14 @@ protected function setUp() ]; $this->layerMock = $this->createMock(\Magento\Catalog\Model\Layer::class); + $this->layerCategoryConfigMock = $this->createMock(LayerCategoryConfig::class); - $this->model = new FilterList($this->objectManagerMock, $this->attributeListMock, $filters); + $this->model = new FilterList( + $this->objectManagerMock, + $this->attributeListMock, + $this->layerCategoryConfigMock, + $filters + ); } /** @@ -90,9 +111,57 @@ public function testGetFilters($method, $value, $expectedClass) ->method('getList') ->will($this->returnValue([$this->attributeMock])); + $this->layerCategoryConfigMock->expects($this->once()) + ->method('isCategoryFilterVisibleInLayerNavigation') + ->willReturn(true); + $this->assertEquals(['filter', 'filter'], $this->model->getFilters($this->layerMock)); } + /** + * Test filters list result when category should not be included + * + * @param string $method + * @param string $value + * @param string $expectedClass + * @param array $expectedResult + * + * @dataProvider getFiltersWithoutCategoryDataProvider + * + * @return void + */ + public function testGetFiltersWithoutCategoryFilter( + string $method, + string $value, + string $expectedClass, + array $expectedResult + ): void { + $this->objectManagerMock->expects($this->at(0)) + ->method('create') + ->with( + $expectedClass, + [ + 'data' => ['attribute_model' => $this->attributeMock], + 'layer' => $this->layerMock + ] + ) + ->will($this->returnValue('filter')); + + $this->attributeMock->expects($this->once()) + ->method($method) + ->will($this->returnValue($value)); + + $this->attributeListMock->expects($this->once()) + ->method('getList') + ->will($this->returnValue([$this->attributeMock])); + + $this->layerCategoryConfigMock->expects($this->once()) + ->method('isCategoryFilterVisibleInLayerNavigation') + ->willReturn(false); + + $this->assertEquals($expectedResult, $this->model->getFilters($this->layerMock)); + } + /** * @return array */ @@ -116,4 +185,23 @@ public function getFiltersDataProvider() ] ]; } + + /** + * Provides attribute filters without category item + * + * @return array + */ + public function getFiltersWithoutCategoryDataProvider(): array + { + return [ + 'Filters contains only price attribute' => [ + 'method' => 'getFrontendInput', + 'value' => 'price', + 'expectedClass' => 'PriceFilterClass', + 'expectedResult' => [ + 'filter' + ] + ] + ]; + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Gallery/GalleryManagementTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Gallery/GalleryManagementTest.php index 6d4e98b60ad18..30994eda87273 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Gallery/GalleryManagementTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Gallery/GalleryManagementTest.php @@ -59,6 +59,7 @@ protected function setUp() 'getCustomAttribute', 'getMediaGalleryEntries', 'setMediaGalleryEntries', + 'getMediaAttributes', ] ); $this->mediaGalleryEntryMock = @@ -99,6 +100,9 @@ public function testCreateWithCannotSaveException() $entryContentMock = $this->getMockBuilder(\Magento\Framework\Api\Data\ImageContentInterface::class) ->disableOriginalConstructor() ->getMock(); + $attributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class) + ->disableOriginalConstructor() + ->getMock(); $this->mediaGalleryEntryMock->expects($this->any())->method('getContent')->willReturn($entryContentMock); $this->productRepositoryMock->expects($this->once()) ->method('get') @@ -108,6 +112,10 @@ public function testCreateWithCannotSaveException() $this->contentValidatorMock->expects($this->once())->method('isValid')->with($entryContentMock) ->willReturn(true); + $this->productMock->expects($this->any()) + ->method('getMediaAttributes') + ->willReturn(['small_image' => $attributeMock]); + $this->productRepositoryMock->expects($this->once())->method('save')->with($this->productMock) ->willThrowException(new \Exception()); $this->model->create($productSku, $this->mediaGalleryEntryMock); @@ -133,6 +141,8 @@ public function testCreate() $this->contentValidatorMock->expects($this->once())->method('isValid')->with($entryContentMock) ->willReturn(true); + $this->mediaGalleryEntryMock->expects($this->any())->method('getTypes')->willReturn(['small_image']); + $newEntryMock = $this->createMock(\Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface::class); $newEntryMock->expects($this->exactly(2))->method('getId')->willReturn(42); $this->productMock->expects($this->at(2))->method('getMediaGalleryEntries') diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/ProductWebsiteAssignmentHandlerTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/ProductWebsiteAssignmentHandlerTest.php new file mode 100644 index 0000000000000..664aa311008f2 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/ProductWebsiteAssignmentHandlerTest.php @@ -0,0 +1,101 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); +namespace Magento\Catalog\Test\Unit\Model\ResourceModel; + +use Magento\Catalog\Model\ResourceModel\Product\Website\Link; +use Magento\Catalog\Model\ResourceModel\ProductWebsiteAssignmentHandler; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit\Framework\TestCase; + +class ProductWebsiteAssignmentHandlerTest extends TestCase +{ + /** + * @var ProductWebsiteAssignmentHandler + */ + protected $handler; + + /** + * @var Link|\PHPUnit_Framework_MockObject_MockObject + */ + protected $productLinkMock; + + protected function setUp() + { + $objectManager = new ObjectManager($this); + + $this->productLinkMock = $this->createPartialMock( + Link::class, + ['updateProductWebsite'] + ); + $this->handler = $objectManager->getObject( + ProductWebsiteAssignmentHandler::class, + [ + 'productLink' => $this->productLinkMock + ] + ); + } + + /** + * @param $actualData + * @param $expectedResult + * @dataProvider productWebsitesDataProvider + * @throws \Exception + */ + public function testUpdateProductWebsiteReturnValidResult($actualData, $expectedResult) + { + $this->productLinkMock->expects($this->any())->method('updateProductWebsite')->willReturn($expectedResult); + $this->assertEquals( + $actualData['entityData'], + $this->handler->execute($actualData['entityType'], $actualData['entityData']) + ); + } + + /** + * @return array + */ + public function productWebsitesDataProvider(): array + { + return [ + [ + [ + 'entityType' => 'product', + 'entityData' => [ + 'entity_id' => '12345', + 'website_ids' => ['1', '2', '3'], + 'name' => 'test-1', + 'sku' => 'test-1' + ] + ], + true + ], + [ + [ + 'entityType' => 'product', + 'entityData' => [ + 'entity_id' => null, + 'website_ids' => ['1', '2', '3'], + 'name' => 'test-1', + 'sku' => 'test-1' + ] + ], + false + ], + [ + [ + 'entityType' => 'product', + 'entityData' => [ + 'entity_id' => '12345', + 'website_ids' => [null], + 'name' => 'test-1', + 'sku' => 'test-1' + ] + ], + false + ] + ]; + } +} diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Modifier/PriceAttributes.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Modifier/PriceAttributes.php new file mode 100644 index 0000000000000..7f333441dab34 --- /dev/null +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Modifier/PriceAttributes.php @@ -0,0 +1,105 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Ui\DataProvider\Product\Modifier; + +use Magento\Framework\Currency; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Locale\CurrencyInterface; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Ui\DataProvider\Modifier\ModifierInterface; + +/** + * Modify product listing price attributes + */ +class PriceAttributes implements ModifierInterface +{ + /** + * @var array + */ + private $priceAttributeList; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var CurrencyInterface + */ + private $localeCurrency; + + /** + * PriceAttributes constructor. + * + * @param StoreManagerInterface $storeManager + * @param CurrencyInterface $localeCurrency + * @param array $priceAttributeList + */ + public function __construct( + StoreManagerInterface $storeManager, + CurrencyInterface $localeCurrency, + array $priceAttributeList = [] + ) { + $this->storeManager = $storeManager; + $this->localeCurrency = $localeCurrency; + $this->priceAttributeList = $priceAttributeList; + } + + /** + * @inheritdoc + */ + public function modifyData(array $data): array + { + if (empty($data) || empty($this->priceAttributeList)) { + return $data; + } + + foreach ($data['items'] as &$item) { + foreach ($this->priceAttributeList as $priceAttribute) { + if (isset($item[$priceAttribute])) { + $item[$priceAttribute] = $this->getCurrency()->toCurrency(sprintf("%f", $item[$priceAttribute])); + } + } + } + + return $data; + } + + /** + * @inheritdoc + */ + public function modifyMeta(array $meta): array + { + return $meta; + } + + /** + * Retrieve store + * + * @return StoreInterface + * @throws NoSuchEntityException + */ + private function getStore(): StoreInterface + { + return $this->storeManager->getStore(); + } + + /** + * Retrieve currency + * + * @return Currency + * @throws NoSuchEntityException + */ + private function getCurrency(): Currency + { + $baseCurrencyCode = $this->getStore()->getBaseCurrencyCode(); + + return $this->localeCurrency->getCurrency($baseCurrencyCode); + } +} diff --git a/app/code/Magento/Catalog/ViewModel/Category/Image.php b/app/code/Magento/Catalog/ViewModel/Category/Image.php new file mode 100644 index 0000000000000..2982779bd2eb3 --- /dev/null +++ b/app/code/Magento/Catalog/ViewModel/Category/Image.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\ViewModel\Category; + +use Magento\Catalog\Model\Category; +use Magento\Catalog\Model\Category\Image as CategoryImage; +use Magento\Framework\View\Element\Block\ArgumentInterface; + +/** + * Category image view model + */ +class Image implements ArgumentInterface +{ + private const ATTRIBUTE_NAME = 'image'; + /** + * @var CategoryImage + */ + private $image; + + /** + * Initialize dependencies. + * + * @param CategoryImage $image + */ + public function __construct(CategoryImage $image) + { + $this->image = $image; + } + + /** + * Resolve category image URL + * + * @param Category $category + * @param string $attributeCode + * @return string + */ + public function getUrl(Category $category, string $attributeCode = self::ATTRIBUTE_NAME): string + { + return $this->image->getUrl($category, $attributeCode); + } +} diff --git a/app/code/Magento/Catalog/ViewModel/Category/Output.php b/app/code/Magento/Catalog/ViewModel/Category/Output.php new file mode 100644 index 0000000000000..367d59daea48e --- /dev/null +++ b/app/code/Magento/Catalog/ViewModel/Category/Output.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\ViewModel\Category; + +use Magento\Catalog\Helper\Output as OutputHelper; +use Magento\Catalog\Model\Category; +use Magento\Framework\View\Element\Block\ArgumentInterface; + +/** + * Category attribute output view model + */ +class Output implements ArgumentInterface +{ + /** + * @var OutputHelper + */ + private $outputHelper; + + /** + * Initialize dependencies. + * + * @param OutputHelper $outputHelper + */ + public function __construct(OutputHelper $outputHelper) + { + $this->outputHelper = $outputHelper; + } + + /** + * Prepare category attribute html output + * + * @param Category $category + * @param string $attributeHtml + * @param string $attributeName + * @return string + */ + public function categoryAttribute(Category $category, string $attributeHtml, string $attributeName): string + { + return $this->outputHelper->categoryAttribute($category, $attributeHtml, $attributeName); + } +} diff --git a/app/code/Magento/Catalog/etc/adminhtml/di.xml b/app/code/Magento/Catalog/etc/adminhtml/di.xml index 4f905cd85f3d1..7d74ab38a8560 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/di.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/di.xml @@ -177,6 +177,10 @@ <item name="class" xsi:type="string">Magento\Catalog\Ui\DataProvider\Product\Modifier\Attributes</item> <item name="sortOrder" xsi:type="number">10</item> </item> + <item name="priceAttributes" xsi:type="array"> + <item name="class" xsi:type="string">Magento\Catalog\Ui\DataProvider\Product\Modifier\PriceAttributes</item> + <item name="sortOrder" xsi:type="number">10</item> + </item> </argument> </arguments> </virtualType> @@ -188,6 +192,14 @@ </argument> </arguments> </type> + <type name="Magento\Catalog\Ui\DataProvider\Product\Modifier\PriceAttributes"> + <arguments> + <argument name="priceAttributeList" xsi:type="array"> + <item name="cost" xsi:type="string">cost</item> + <item name="special_price" xsi:type="string">special_price</item> + </argument> + </arguments> + </type> <type name="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\CustomOptions"> <arguments> <argument name="scopeName" xsi:type="string">product_form.product_form</argument> diff --git a/app/code/Magento/Catalog/etc/adminhtml/system.xml b/app/code/Magento/Catalog/etc/adminhtml/system.xml index f59990cdcea96..4e10453f542bb 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/system.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/system.xml @@ -138,6 +138,12 @@ <hide_in_single_store_mode>1</hide_in_single_store_mode> </field> </group> + <group id="layered_navigation"> + <field id="display_category" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <label>Display Category Filter</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + </field> + </group> <group id="navigation" translate="label" type="text" sortOrder="500" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Category Top Navigation</label> <field id="max_depth" translate="label" type="text" sortOrder="1" showInDefault="1" canRestore="1"> diff --git a/app/code/Magento/Catalog/etc/config.xml b/app/code/Magento/Catalog/etc/config.xml index 68289904db0cf..e7beb3d083226 100644 --- a/app/code/Magento/Catalog/etc/config.xml +++ b/app/code/Magento/Catalog/etc/config.xml @@ -54,6 +54,9 @@ <time_format>12h</time_format> <forbidden_extensions>php,exe</forbidden_extensions> </custom_options> + <layered_navigation> + <display_category>1</display_category> + </layered_navigation> </catalog> <indexer> <catalog_product_price> diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml index 223d690d28327..ff67989d337bb 100644 --- a/app/code/Magento/Catalog/etc/di.xml +++ b/app/code/Magento/Catalog/etc/di.xml @@ -891,6 +891,11 @@ <item name="update" xsi:type="string">Magento\Catalog\Model\ResourceModel\UpdateHandler</item> </item> </item> + <item name="websites" xsi:type="array"> + <item name="Magento\Catalog\Api\Data\ProductInterface" xsi:type="array"> + <item name="create" xsi:type="string">Magento\Catalog\Model\ResourceModel\ProductWebsiteAssignmentHandler</item> + </item> + </item> </argument> </arguments> </type> diff --git a/app/code/Magento/Catalog/view/frontend/layout/catalog_category_view.xml b/app/code/Magento/Catalog/view/frontend/layout/catalog_category_view.xml index 5fee1d8447e5a..c4adcaf785012 100644 --- a/app/code/Magento/Catalog/view/frontend/layout/catalog_category_view.xml +++ b/app/code/Magento/Catalog/view/frontend/layout/catalog_category_view.xml @@ -9,7 +9,12 @@ <body> <referenceContainer name="columns.top"> <container name="category.view.container" htmlTag="div" htmlClass="category-view" after="-"> - <block class="Magento\Catalog\Block\Category\View" name="category.image" template="Magento_Catalog::category/image.phtml"/> + <block class="Magento\Catalog\Block\Category\View" name="category.image" template="Magento_Catalog::category/image.phtml"> + <arguments> + <argument name="image" xsi:type="object">Magento\Catalog\ViewModel\Category\Image</argument> + <argument name="output" xsi:type="object">Magento\Catalog\ViewModel\Category\Output</argument> + </arguments> + </block> <block class="Magento\Catalog\Block\Category\View" name="category.description" template="Magento_Catalog::category/description.phtml"/> <block class="Magento\Catalog\Block\Category\View" name="category.cms" template="Magento_Catalog::category/cms.phtml"/> </container> diff --git a/app/code/Magento/Catalog/view/frontend/templates/category/image.phtml b/app/code/Magento/Catalog/view/frontend/templates/category/image.phtml index 02593d3b541a1..8f72e4713d22b 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/category/image.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/category/image.phtml @@ -16,10 +16,9 @@ // phpcs:disable Magento2.Security.LanguageConstruct.DirectOutput ?> <?php - $_helper = $this->helper(Magento\Catalog\Helper\Output::class); $_category = $block->getCurrentCategory(); $_imgHtml = ''; - if ($_imgUrl = $_category->getImageUrl()) { + if ($_imgUrl = $block->getImage()->getUrl($_category)) { $_imgHtml = '<div class="category-image"><img src="' . $block->escapeUrl($_imgUrl) . '" alt="' @@ -27,7 +26,7 @@ . '" title="' . $block->escapeHtmlAttr($_category->getName()) . '" class="image" /></div>'; - $_imgHtml = $_helper->categoryAttribute($_category, $_imgHtml, 'image'); + $_imgHtml = $block->getOutput()->categoryAttribute($_category, $_imgHtml, 'image'); /* @noEscape */ echo $_imgHtml; } ?> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml index 24cae93ca61c0..9f8fdfb518408 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml @@ -10,7 +10,9 @@ ?> <img class="photo image <?= $escaper->escapeHtmlAttr($block->getClass()) ?>" - <?= $escaper->escapeHtml($block->getCustomAttributes()) ?> + <?php foreach ($block->getCustomAttributes() as $name => $value): ?> + <?= $escaper->escapeHtmlAttr($name) ?>="<?= $escaper->escapeHtmlAttr($value) ?>" + <?php endforeach; ?> src="<?= $escaper->escapeUrl($block->getImageUrl()) ?>" loading="lazy" width="<?= $escaper->escapeHtmlAttr($block->getWidth()) ?>" diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml index e8ddabce504ea..2f352438297d4 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml @@ -14,7 +14,9 @@ <span class="product-image-wrapper" style="padding-bottom: <?= ($block->getRatio() * 100) ?>%;"> <img class="<?= $escaper->escapeHtmlAttr($block->getClass()) ?>" - <?= $escaper->escapeHtmlAttr($block->getCustomAttributes()) ?> + <?php foreach ($block->getCustomAttributes() as $name => $value): ?> + <?= $escaper->escapeHtmlAttr($name) ?>="<?= $escaper->escapeHtmlAttr($value) ?>" + <?php endforeach; ?> src="<?= $escaper->escapeUrl($block->getImageUrl()) ?>" loading="lazy" width="<?= $escaper->escapeHtmlAttr($block->getWidth()) ?>" diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest.xml index dc715a5d95f2f..890df17f113d5 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="QuickSearchProductBySku"> + <test name="QuickSearchProductBySkuTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="User should be able to use Quick Search to find products"/> @@ -41,7 +41,7 @@ <argument name="productUrlKey" value="$createSimpleProduct.custom_attributes[url_key]$"/> </actionGroup> </test> - <test name="QuickSearchProductByName" extends="QuickSearchProductBySku"> + <test name="QuickSearchProductByNameTest" extends="QuickSearchProductBySkuTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="User should be able to use Quick Search to find products via Name"/> @@ -56,7 +56,7 @@ <argument name="phrase" value="$createSimpleProduct.name$"/> </actionGroup> </test> - <test name="QuickSearchProductByNameWithSpecialChars" extends="QuickSearchProductBySku"> + <test name="QuickSearchProductByNameWithSpecialCharsTest" extends="QuickSearchProductBySkuTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="Quick Search can find products with names that contain special characters"/> @@ -76,7 +76,7 @@ <argument name="phrase" value="$createSimpleProduct.name$"/> </actionGroup> </test> - <test name="QuickSearchEmptyResults"> + <test name="QuickSearchEmptyResultsTest"> <annotations> <features value="CatalogSearch"/> <stories value="Search Product on Storefront"/> @@ -109,7 +109,7 @@ <actionGroup ref="StorefrontCheckSearchIsEmptyActionGroup" stepKey="checkEmpty"/> </test> - <test name="QuickSearchWithTwoCharsEmptyResults" extends="QuickSearchEmptyResults"> + <test name="QuickSearchWithTwoCharsEmptyResultsTest" extends="QuickSearchEmptyResultsTest"> <annotations> <features value="CatalogSearch"/> <stories value="Search Product on Storefront"/> @@ -143,7 +143,7 @@ </actionGroup> </test> - <test name="QuickSearchProductByNameWithThreeLetters" extends="QuickSearchProductBySku"> + <test name="QuickSearchProductByNameWithThreeLettersTest" extends="QuickSearchProductBySkuTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="User should be able to use Quick Search to find products by their first three letters"/> @@ -159,7 +159,7 @@ <argument name="phrase" value="{$getFirstThreeLetters}"/> </actionGroup> </test> - <test name="QuickSearchProductBy128CharQuery" extends="QuickSearchProductBySku"> + <test name="QuickSearchProductBy128CharQueryTest" extends="QuickSearchProductBySkuTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="User should be able to use Quick Search product with long names, using first 128 letters"/> @@ -180,7 +180,7 @@ </actionGroup> </test> - <test name="QuickSearchTwoProductsWithSameWeight"> + <test name="QuickSearchTwoProductsWithSameWeightTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="Quick Search should sort products with the same weight appropriately"/> @@ -263,7 +263,7 @@ <argument name="index" value="1"/> </actionGroup> </test> - <test name="QuickSearchTwoProductsWithDifferentWeight" extends="QuickSearchTwoProductsWithSameWeight"> + <test name="QuickSearchTwoProductsWithDifferentWeightTest" extends="QuickSearchTwoProductsWithSameWeightTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="Quick Search should sort products with the different weight appropriately"/> @@ -293,7 +293,7 @@ </actionGroup> </test> - <test name="QuickSearchAndAddToCart"> + <test name="QuickSearchAndAddToCartTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="User should be able to use Quick Search to find a simple product and add it to cart"/> @@ -325,7 +325,7 @@ <argument name="productName" value="$createSimpleProduct.name$"/> </actionGroup> </test> - <test name="QuickSearchAndAddToCartVirtual"> + <test name="QuickSearchAndAddToCartVirtualTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="User should be able to use Quick Search to find a virtual product and add it to cart"/> @@ -357,7 +357,7 @@ <argument name="productName" value="$createVirtualProduct.name$"/> </actionGroup> </test> - <test name="QuickSearchAndAddToCartConfigurable"> + <test name="QuickSearchAndAddToCartConfigurableTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="User should be able to use Quick Search to find a configurable product and add it to cart"/> @@ -401,7 +401,7 @@ <argument name="optionName" value="{{colorProductAttribute1.name}}"/> </actionGroup> </test> - <test name="QuickSearchAndAddToCartDownloadable"> + <test name="QuickSearchAndAddToCartDownloadableTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="User should be able to use Quick Search to find a downloadable product and add it to cart"/> @@ -438,7 +438,7 @@ <argument name="productName" value="$createProduct.name$"/> </actionGroup> </test> - <test name="QuickSearchAndAddToCartGrouped"> + <test name="QuickSearchAndAddToCartGroupedTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="User should be able to use Quick Search to find a grouped product and add it to cart"/> @@ -475,7 +475,7 @@ <argument name="productName" value="$createProduct.name$"/> </actionGroup> </test> - <test name="QuickSearchAndAddToCartBundleDynamic"> + <test name="QuickSearchAndAddToCartBundleDynamicTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="User should be able to use Quick Search to find a Bundle Dynamic product and add it to cart"/> @@ -531,7 +531,7 @@ <argument name="productName" value="$createBundleProduct.name$"/> </actionGroup> </test> - <test name="QuickSearchAndAddToCartBundleFixed"> + <test name="QuickSearchAndAddToCartBundleFixedTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="User should be able to use Quick Search to find a Bundle Fixed product and add it to cart"/> diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGenerator.php index 5d08ea33ff8a1..a6589c6062846 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGenerator.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGenerator.php @@ -67,7 +67,7 @@ public function generate($storeId, Product $product, ObjectRegistry $productCate $anchorCategoryIds = $category->getAnchorsAbove(); if ($anchorCategoryIds) { foreach ($anchorCategoryIds as $anchorCategoryId) { - $anchorCategory = $this->categoryRepository->get($anchorCategoryId); + $anchorCategory = $this->categoryRepository->get($anchorCategoryId, $storeId); if ((int)$anchorCategory->getParentId() === Category::TREE_ROOT_ID) { continue; } diff --git a/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlPathGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlPathGenerator.php index ac3a5092bb3bf..da2dd8a505869 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlPathGenerator.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlPathGenerator.php @@ -3,20 +3,19 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\CatalogUrlRewrite\Model; -use Magento\Store\Model\Store; use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\Category; use Magento\Catalog\Model\Product; -use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\StoreManagerInterface; /** - * Class ProductUrlPathGenerator + * Model product url path generator */ class ProductUrlPathGenerator { @@ -150,7 +149,7 @@ protected function prepareProductUrlKey(Product $product) $urlKey = (string)$product->getUrlKey(); $urlKey = trim(strtolower($urlKey)); - return $urlKey ?: $product->formatUrlKey($product->getName()); + return $product->formatUrlKey($urlKey ?: $product->getName()); } /** diff --git a/app/code/Magento/CatalogUrlRewrite/Setup/Patch/Data/UpdateUrlKeyForProducts.php b/app/code/Magento/CatalogUrlRewrite/Setup/Patch/Data/UpdateUrlKeyForProducts.php new file mode 100644 index 0000000000000..5e7039912999b --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Setup/Patch/Data/UpdateUrlKeyForProducts.php @@ -0,0 +1,102 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogUrlRewrite\Setup\Patch\Data; + +use Magento\Catalog\Model\Product\Url; +use Magento\Eav\Setup\EavSetup; +use Magento\Eav\Setup\EavSetupFactory; +use Magento\Framework\Setup\ModuleDataSetupInterface; +use Magento\Framework\Setup\Patch\DataPatchInterface; +use Magento\Framework\Setup\Patch\PatchVersionInterface; + +/** + * Update url_key all products. + */ +class UpdateUrlKeyForProducts implements DataPatchInterface, PatchVersionInterface +{ + /** + * @var ModuleDataSetupInterface + */ + private $moduleDataSetup; + + /** + * @var EavSetup + */ + private $eavSetup; + + /** + * @var Url + */ + private $urlProduct; + + /** + * @param ModuleDataSetupInterface $moduleDataSetup + * @param EavSetupFactory $eavSetupFactory + * @param Url $urlProduct + */ + public function __construct( + ModuleDataSetupInterface $moduleDataSetup, + EavSetupFactory $eavSetupFactory, + Url $urlProduct + ) { + $this->moduleDataSetup = $moduleDataSetup; + $this->eavSetup = $eavSetupFactory->create(['setup' => $moduleDataSetup]); + $this->urlProduct = $urlProduct; + } + + /** + * @inheritdoc + */ + public function apply() + { + $productTypeId = $this->eavSetup->getEntityTypeId(\Magento\Catalog\Model\Product::ENTITY); + $table = $this->moduleDataSetup->getTable('catalog_product_entity_varchar'); + $select = $this->moduleDataSetup->getConnection()->select()->from( + $table, + ['value_id', 'value'] + )->where( + 'attribute_id = ?', + $this->eavSetup->getAttributeId($productTypeId, 'url_key') + ); + + $result = $this->moduleDataSetup->getConnection()->fetchAll($select); + foreach ($result as $key => $item) { + $result[$key]['value'] = $this->urlProduct->formatUrlKey($item['value']); + } + + foreach (array_chunk($result, 500, true) as $pathResult) { + $this->moduleDataSetup->getConnection()->insertOnDuplicate($table, $pathResult, ['value']); + } + + return $this; + } + + /** + * @inheritDoc + */ + public static function getVersion() + { + return "2.4.0"; + } + + /** + * @inheritdoc + */ + public static function getDependencies() + { + return []; + } + + /** + * @inheritdoc + */ + public function getAliases() + { + return []; + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/ActionGroup/AssertStorefrontProductRewriteUrlSubCategoryActionGroup.xml b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/ActionGroup/AssertStorefrontProductRewriteUrlSubCategoryActionGroup.xml new file mode 100644 index 0000000000000..4675d3b2669a4 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/ActionGroup/AssertStorefrontProductRewriteUrlSubCategoryActionGroup.xml @@ -0,0 +1,21 @@ +<?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="AssertStorefrontProductRewriteUrlSubCategoryActionGroup"> + <annotations> + <description>Validates that the provided Product Title is present on the Rewrite URL with a subcategory page.</description> + </annotations> + <arguments> + <argument name="category" type="string" defaultValue="simplecategory"/> + <argument name="product" defaultValue="SimpleProduct" /> + </arguments> + + <amOnPage url="{{category}}/{{product.urlKey}}2.html" stepKey="goToProductPage"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{product.name}}" stepKey="seeProductNameInStoreFront"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Data/GenerateCategoryProductUrlRewriteConfigData.xml b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Data/GenerateCategoryProductUrlRewriteConfigData.xml index 10d2213b64717..9ce6d397a551b 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Data/GenerateCategoryProductUrlRewriteConfigData.xml +++ b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Data/GenerateCategoryProductUrlRewriteConfigData.xml @@ -19,4 +19,12 @@ <data key="label">No</data> <data key="value">0</data> </entity> + <entity name="EnableCategoriesPathProductUrls"> + <data key="path">catalog/seo/product_use_categories</data> + <data key="value">1</data> + </entity> + <entity name="DisableCategoriesPathProductUrls"> + <data key="path">catalog/seo/product_use_categories</data> + <data key="value">0</data> + </entity> </entities> diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminRewriteProductWithTwoStoreTest.xml b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminRewriteProductWithTwoStoreTest.xml new file mode 100644 index 0000000000000..db4811273a5cc --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminRewriteProductWithTwoStoreTest.xml @@ -0,0 +1,86 @@ +<?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="AdminRewriteProductWithTwoStoreTest"> + <annotations> + <title value="Rewriting URL of product"/> + <description value="Rewriting URL of product. Verify the full URL address"/> + <group value="CatalogUrlRewrite"/> + </annotations> + + <before> + <magentoCLI command="config:set {{EnableCategoriesPathProductUrls.path}} {{EnableCategoriesPathProductUrls.value}}" stepKey="enableUseCategoriesPath"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView" /> + <createData entity="_defaultCategoryDifferentUrlStore" stepKey="defaultCategory"/> + <createData entity="SimpleSubCategoryDifferentUrlStore" stepKey="subCategory"> + <requiredEntity createDataKey="defaultCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="simpleProduct"> + <requiredEntity createDataKey="subCategory"/> + </createData> + </before> + + <after> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView"/> + <actionGroup ref="logout" stepKey="logout"/> + <deleteData createDataKey="simpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="defaultCategory" stepKey="deleteNewRootCategory"/> + <magentoCLI command="config:set {{DisableCategoriesPathProductUrls.path}} {{DisableCategoriesPathProductUrls.value}}" stepKey="disableUseCategoriesPath"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </after> + + <actionGroup ref="NavigateToCreatedCategoryActionGroup" stepKey="navigateToCreatedDefaultCategory"> + <argument name="Category" value="$$defaultCategory$$"/> + </actionGroup> + <actionGroup ref="AdminSwitchStoreViewActionGroup" stepKey="AdminSwitchDefaultStoreViewForDefaultCategory"> + <argument name="storeView" value="_defaultStore.name"/> + </actionGroup> + <actionGroup ref="AdminChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup" stepKey="changeSeoUrlKeyForDefaultCategoryDefaultStore"> + <argument name="value" value="{{_defaultCategoryDifferentUrlStore.url_key_default_store}}"/> + </actionGroup> + <actionGroup ref="AdminSwitchStoreViewActionGroup" stepKey="AdminSwitchCustomStoreViewForDefaultCategory"> + <argument name="storeView" value="customStore.name"/> + </actionGroup> + <actionGroup ref="AdminChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup" stepKey="changeSeoUrlKeyForDefaultCategoryCustomStore"> + <argument name="value" value="{{_defaultCategoryDifferentUrlStore.url_key_custom_store}}"/> + </actionGroup> + + <actionGroup ref="NavigateToCreatedCategoryActionGroup" stepKey="navigateToCreatedSubCategory"> + <argument name="Category" value="$$subCategory$$"/> + </actionGroup> + <actionGroup ref="AdminSwitchStoreViewActionGroup" stepKey="AdminSwitchDefaultStoreViewForSubCategory"> + <argument name="storeView" value="_defaultStore.name"/> + </actionGroup> + <actionGroup ref="AdminChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup" stepKey="changeSeoUrlKeyForSubCategoryDefaultStore"> + <argument name="value" value="{{SimpleSubCategoryDifferentUrlStore.url_key_default_store}}"/> + </actionGroup> + <actionGroup ref="AdminSwitchStoreViewActionGroup" stepKey="AdminSwitchCustomStoreViewForSubCategory"> + <argument name="storeView" value="customStore.name"/> + </actionGroup> + + <actionGroup ref="AdminChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup" stepKey="changeSeoUrlKeyForSubCategoryCustomStore"> + <argument name="value" value="{{SimpleSubCategoryDifferentUrlStore.url_key_custom_store}}"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductRewriteUrlSubCategoryActionGroup" stepKey="validatesRewriteUrlDefaultStore"> + <argument name="category" value="{{_defaultCategoryDifferentUrlStore.url_key_default_store}}"/> + <argument name="product" value="SimpleProduct" /> + </actionGroup> + + <actionGroup ref="StorefrontSwitchStoreViewActionGroup" stepKey="switchStore"> + <argument name="storeView" value="customStore" /> + </actionGroup> + <actionGroup ref="AssertStorefrontProductRewriteUrlSubCategoryActionGroup" stepKey="validatesRewriteUrlCustomStore"> + <argument name="category" value="{{_defaultCategoryDifferentUrlStore.url_key_custom_store}}"/> + <argument name="product" value="SimpleProduct" /> + </actionGroup> + + </test> +</tests> diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/AnchorUrlRewriteGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/AnchorUrlRewriteGeneratorTest.php index 662e156b8f100..d8fec2de0e46e 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/AnchorUrlRewriteGeneratorTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/AnchorUrlRewriteGeneratorTest.php @@ -3,53 +3,67 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Product; +use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Catalog\Model\Product; +use Magento\CatalogUrlRewrite\Model\ObjectRegistry; +use Magento\CatalogUrlRewrite\Model\Product\AnchorUrlRewriteGenerator; +use Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator; use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; +use Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory; +use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject as MockObject; -class AnchorUrlRewriteGeneratorTest extends \PHPUnit\Framework\TestCase +class AnchorUrlRewriteGeneratorTest extends TestCase { - /** @var \Magento\CatalogUrlRewrite\Model\Product\AnchorUrlRewriteGenerator */ + /** @var AnchorUrlRewriteGenerator */ protected $anchorUrlRewriteGenerator; - /** @var \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator|\PHPUnit_Framework_MockObject_MockObject */ + /** @var ProductUrlPathGenerator|MockObject */ protected $productUrlPathGenerator; - /** @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject */ + /** @var Product|MockObject */ protected $product; - /** @var \Magento\Catalog\Api\CategoryRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var CategoryRepositoryInterface|MockObject */ private $categoryRepositoryInterface; - /** @var \Magento\CatalogUrlRewrite\Model\ObjectRegistry|\PHPUnit_Framework_MockObject_MockObject */ + /** @var ObjectRegistry|MockObject */ protected $categoryRegistry; - /** @var \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory|\PHPUnit_Framework_MockObject_MockObject */ + /** @var UrlRewriteFactory|MockObject */ protected $urlRewriteFactory; - /** @var \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|\PHPUnit_Framework_MockObject_MockObject */ + /** @var UrlRewrite|MockObject */ protected $urlRewrite; + /** + * @inheritDoc + */ protected function setUp() { - $this->urlRewriteFactory = $this->getMockBuilder(\Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory::class) + $this->urlRewriteFactory = $this->getMockBuilder(UrlRewriteFactory::class) ->setMethods(['create']) ->disableOriginalConstructor()->getMock(); - $this->urlRewrite = $this->getMockBuilder(\Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class) + $this->urlRewrite = $this->getMockBuilder(UrlRewrite::class) ->disableOriginalConstructor()->getMock(); - $this->product = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + $this->product = $this->getMockBuilder(Product::class) ->disableOriginalConstructor()->getMock(); $this->categoryRepositoryInterface = $this->getMockBuilder( - \Magento\Catalog\Api\CategoryRepositoryInterface::class + CategoryRepositoryInterface::class )->disableOriginalConstructor()->getMock(); - $this->categoryRegistry = $this->getMockBuilder(\Magento\CatalogUrlRewrite\Model\ObjectRegistry::class) + $this->categoryRegistry = $this->getMockBuilder(ObjectRegistry::class) ->disableOriginalConstructor()->getMock(); $this->productUrlPathGenerator = $this->getMockBuilder( - \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator::class + ProductUrlPathGenerator::class )->disableOriginalConstructor()->getMock(); $this->anchorUrlRewriteGenerator = (new ObjectManager($this))->getObject( - \Magento\CatalogUrlRewrite\Model\Product\AnchorUrlRewriteGenerator::class, + AnchorUrlRewriteGenerator::class, [ 'productUrlPathGenerator' => $this->productUrlPathGenerator, 'urlRewriteFactory' => $this->urlRewriteFactory, @@ -58,7 +72,12 @@ protected function setUp() ); } - public function testGenerateEmpty() + /** + * Verify generate if category registry list is empty. + * + * @return void + */ + public function testGenerateEmpty(): void { $this->categoryRegistry->expects($this->any())->method('getList')->will($this->returnValue([])); @@ -68,7 +87,12 @@ public function testGenerateEmpty() ); } - public function testGenerateCategories() + /** + * Verify generate product rewrites for anchor categories. + * + * @return void + */ + public function testGenerateCategories(): void { $urlPathWithCategory = 'category1/category2/category3/simple-product.html'; $storeId = 10; @@ -100,9 +124,9 @@ public function testGenerateCategories() ->expects($this->any()) ->method('get') ->withConsecutive( - [ 'category_id' => $categoryIds[0]], - [ 'category_id' => $categoryIds[1]], - [ 'category_id' => $categoryIds[2]] + [$categoryIds[0], $storeId], + [$categoryIds[1], $storeId], + [$categoryIds[2], $storeId] ) ->will($this->returnValue($category)); $this->categoryRegistry->expects($this->any())->method('getList') diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlPathGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlPathGeneratorTest.php index 5076577447af3..95ef16c5ace4c 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlPathGeneratorTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlPathGeneratorTest.php @@ -7,34 +7,42 @@ namespace Magento\CatalogUrlRewrite\Test\Unit\Model; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Category; +use Magento\Catalog\Model\Product; +use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator; use Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\StoreManagerInterface; +use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject as MockObject; /** - * Class ProductUrlPathGeneratorTest + * Verify ProductUrlPathGenerator class */ -class ProductUrlPathGeneratorTest extends \PHPUnit\Framework\TestCase +class ProductUrlPathGeneratorTest extends TestCase { - /** @var \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator */ + /** @var ProductUrlPathGenerator */ protected $productUrlPathGenerator; - /** @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var StoreManagerInterface|MockObject */ protected $storeManager; - /** @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var ScopeConfigInterface|MockObject */ protected $scopeConfig; - /** @var \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator|\PHPUnit_Framework_MockObject_MockObject */ + /** @var CategoryUrlPathGenerator|MockObject */ protected $categoryUrlPathGenerator; - /** @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject */ + /** @var Product|MockObject */ protected $product; - /** @var \Magento\Catalog\Api\ProductRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var ProductRepositoryInterface|MockObject */ protected $productRepository; - /** @var \Magento\Catalog\Model\Category|\PHPUnit_Framework_MockObject_MockObject */ + /** @var Category|MockObject */ protected $category; /** @@ -42,7 +50,7 @@ class ProductUrlPathGeneratorTest extends \PHPUnit\Framework\TestCase */ protected function setUp(): void { - $this->category = $this->createMock(\Magento\Catalog\Model\Category::class); + $this->category = $this->createMock(Category::class); $productMethods = [ '__wakeup', 'getData', @@ -54,17 +62,17 @@ protected function setUp(): void 'setStoreId', ]; - $this->product = $this->createPartialMock(\Magento\Catalog\Model\Product::class, $productMethods); - $this->storeManager = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); - $this->scopeConfig = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class); + $this->product = $this->createPartialMock(Product::class, $productMethods); + $this->storeManager = $this->createMock(StoreManagerInterface::class); + $this->scopeConfig = $this->createMock(ScopeConfigInterface::class); $this->categoryUrlPathGenerator = $this->createMock( - \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator::class + CategoryUrlPathGenerator::class ); - $this->productRepository = $this->createMock(\Magento\Catalog\Api\ProductRepositoryInterface::class); + $this->productRepository = $this->createMock(ProductRepositoryInterface::class); $this->productRepository->expects($this->any())->method('getById')->willReturn($this->product); $this->productUrlPathGenerator = (new ObjectManager($this))->getObject( - \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator::class, + ProductUrlPathGenerator::class, [ 'storeManager' => $this->storeManager, 'scopeConfig' => $this->scopeConfig, @@ -75,13 +83,15 @@ protected function setUp(): void } /** + * Data provider for testGetUrlPath. + * * @return array */ public function getUrlPathDataProvider(): array { return [ - 'path based on url key uppercase' => ['Url-Key', null, 0, 'url-key'], - 'path based on url key' => ['url-key', null, 0, 'url-key'], + 'path based on url key uppercase' => ['Url-Key', null, 1, 'url-key'], + 'path based on url key' => ['url-key', null, 1, 'url-key'], 'path based on product name 1' => ['', 'product-name', 1, 'product-name'], 'path based on product name 2' => [null, 'product-name', 1, 'product-name'], 'path based on product name 3' => [false, 'product-name', 1, 'product-name'] @@ -89,6 +99,8 @@ public function getUrlPathDataProvider(): array } /** + * Verify get url path. + * * @dataProvider getUrlPathDataProvider * @param string|null|bool $urlKey * @param string|null|bool $productName @@ -109,6 +121,8 @@ public function testGetUrlPath($urlKey, $productName, $formatterCalled, $result) } /** + * Verify get url key. + * * @param string|bool $productUrlKey * @param string|bool $expectedUrlKey * @return void @@ -122,6 +136,8 @@ public function testGetUrlKey($productUrlKey, $expectedUrlKey): void } /** + * Data provider for testGetUrlKey. + * * @return array */ public function getUrlKeyDataProvider(): array @@ -133,6 +149,8 @@ public function getUrlKeyDataProvider(): array } /** + * Verify get url path with default utl key. + * * @param string|null|bool $storedUrlKey * @param string|null|bool $productName * @param string $expectedUrlKey @@ -150,6 +168,8 @@ public function testGetUrlPathDefaultUrlKey($storedUrlKey, $productName, $expect } /** + * Data provider for testGetUrlPathDefaultUrlKey. + * * @return array */ public function getUrlPathDefaultUrlKeyDataProvider(): array @@ -161,6 +181,8 @@ public function getUrlPathDefaultUrlKeyDataProvider(): array } /** + * Verify get url path with category. + * * @return void */ public function testGetUrlPathWithCategory(): void @@ -177,6 +199,8 @@ public function testGetUrlPathWithCategory(): void } /** + * Verify get url path with suffix. + * * @return void */ public function testGetUrlPathWithSuffix(): void @@ -198,6 +222,8 @@ public function testGetUrlPathWithSuffix(): void } /** + * Verify get url path with suffix and category and store. + * * @return void */ public function testGetUrlPathWithSuffixAndCategoryAndStore(): void @@ -219,6 +245,8 @@ public function testGetUrlPathWithSuffixAndCategoryAndStore(): void } /** + * Verify get canonical url path. + * * @return void */ public function testGetCanonicalUrlPath(): void @@ -232,6 +260,8 @@ public function testGetCanonicalUrlPath(): void } /** + * Verify get canonical path with category. + * * @return void */ public function testGetCanonicalUrlPathWithCategory(): void diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertProductAddToCartErrorMessageActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertProductAddToCartErrorMessageActionGroup.xml new file mode 100644 index 0000000000000..2147f837d0abc --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertProductAddToCartErrorMessageActionGroup.xml @@ -0,0 +1,17 @@ +<?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="StorefrontAssertProductAddToCartErrorMessageActionGroup"> + <arguments> + <argument name="message" type="string" defaultValue=""/> + </arguments> + <waitForElementVisible selector="{{StorefrontMessagesSection.error}}" time="10" stepKey="waitForProductAddedMessage"/> + <see selector="{{StorefrontMessagesSection.error}}" userInput="{{message}}" stepKey="seeAddToCartErrorMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertProductAddToCartSuccessMessageActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertProductAddToCartSuccessMessageActionGroup.xml new file mode 100644 index 0000000000000..60c9461c53f7b --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertProductAddToCartSuccessMessageActionGroup.xml @@ -0,0 +1,17 @@ +<?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="StorefrontAssertProductAddToCartSuccessMessageActionGroup"> + <arguments> + <argument name="message" type="string" defaultValue=""/> + </arguments> + <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" time="30" stepKey="waitForProductAddedMessage"/> + <see selector="{{StorefrontMessagesSection.success}}" userInput="message" stepKey="seeAddToCartSuccessMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontProductPageAddSimpleProductToCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontProductPageAddSimpleProductToCartActionGroup.xml new file mode 100644 index 0000000000000..202f8989dce0f --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontProductPageAddSimpleProductToCartActionGroup.xml @@ -0,0 +1,15 @@ +<?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="StorefrontProductPageAddSimpleProductToCartActionGroup"> + <click selector="{{StorefrontProductPageSection.addToCartBtn}}" stepKey="addToCart"/> + <waitForElementVisible selector="{{StorefrontProductActionSection.addToCartButtonTitleIsAddToCart}}" stepKey="waitForElementVisibleAddToCartButtonTitleIsAddToCart"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/CheckCheckoutSuccessPageTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/CheckCheckoutSuccessPageTest.xml index b939209751fcd..eb49f53921ea4 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/CheckCheckoutSuccessPageTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/CheckCheckoutSuccessPageTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="CheckCheckoutSuccessPageAsRegisterCustomer"> + <test name="CheckCheckoutSuccessPageAsRegisterCustomerTest"> <annotations> <features value="Checkout"/> <stories value="Success page elements are presented for placed order as Customer"/> @@ -131,7 +131,7 @@ <seeElement selector="{{StorefrontCustomerOrderViewSection.orderTitle}}" stepKey="seeOrderTitleOnPrint"/> <switchToWindow stepKey="switchToWindow2"/> </test> - <test name="CheckCheckoutSuccessPageAsGuest"> + <test name="CheckCheckoutSuccessPageAsGuestTest"> <annotations> <features value="Checkout"/> <stories value="Success page elements are presented for placed order as Guest"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartAndCheckoutItemsCountTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartAndCheckoutItemsCountTest.xml index c3f173961f0c5..8db1f8801ae1d 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartAndCheckoutItemsCountTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartAndCheckoutItemsCountTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontCartItemsCountDisplayItemsQuantities"> + <test name="StorefrontCartItemsCountDisplayItemsQuantitiesTest"> <annotations> <stories value="Checkout order summary has wrong item count"/> <title value="Checkout order summary has wrong item count - display items quantities"/> @@ -57,7 +57,7 @@ <argument name="itemsText" value="3 Items in Cart"/> </actionGroup> </test> - <test name="StorefrontCartItemsCountDisplayUniqueItems" extends="StorefrontCartItemsCountDisplayItemsQuantities"> + <test name="StorefrontCartItemsCountDisplayUniqueItemsTest" extends="StorefrontCartItemsCountDisplayItemsQuantitiesTest"> <annotations> <stories value="Checkout order summary has wrong item count"/> <title value="Checkout order summary has wrong item count - display unique items"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml index 580b4e32b0b28..3a686c8efdd5f 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml @@ -82,7 +82,7 @@ <waitForElementVisible selector="{{AdminEditCustomerOrdersSection.orderGrid}}" stepKey="waitForOrdersGridVisible"/> <see selector="{{AdminEditCustomerOrdersSection.orderGrid}}" userInput="$$createCustomer.firstname$$ $$createCustomer.lastname$$" stepKey="verifyOrder"/> </test> - <test name="StorefrontCustomerCheckoutTestWithMultipleAddressesAndTaxRates"> + <test name="StorefrontCustomerCheckoutTestWithMultipleAddressesAndTaxRatesTest"> <annotations> <features value="Checkout"/> <stories value="Customer checkout"/> @@ -198,7 +198,7 @@ <waitForPageLoad stepKey="waitForOrderSuccessPage2"/> <see selector="{{CheckoutSuccessMainSection.success}}" userInput="Your order number is:" stepKey="seeSuccessMessage2"/> </test> - <test name="StorefrontCustomerCheckoutTestWithRestrictedCountriesForPayment"> + <test name="StorefrontCustomerCheckoutTestWithRestrictedCountriesForPaymentTest"> <annotations> <features value="Checkout"/> <stories value="Checkout flow if payment solutions are not available"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml index cce4d9f0345d7..53d7904ffdc38 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml @@ -93,7 +93,7 @@ <remove keyForRemoval="guestGoToCheckoutFromMinicart" /> <actionGroup ref="GoToCheckoutFromCartActionGroup" stepKey="guestGoToCheckoutFromCart" after="seeCartQuantity" /> </test> - <test name="StorefrontGuestCheckoutTestWithRestrictedCountriesForPayment"> + <test name="StorefrontGuestCheckoutTestWithRestrictedCountriesForPaymentTest"> <annotations> <features value="Checkout"/> <stories value="Checkout flow if payment solutions are not available"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCheckoutTest.xml index cbf0072d44aed..778967c187f65 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCheckoutTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontVerifySecureURLRedirectCheckout"> + <test name="StorefrontVerifySecureURLRedirectCheckoutTest"> <annotations> <features value="Checkout"/> <stories value="Storefront Secure URLs"/> diff --git a/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js b/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js index a8e70b65019ce..f9def2fd58b23 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js @@ -122,6 +122,9 @@ define([ submitForm: function () { this.element .off('submit', this.onSubmit) + .on('submit', function () { + $(document.body).trigger('processStart'); + }) .submit(); } }); diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminEditCMSPageContentActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminEditCMSPageContentActionGroup.xml new file mode 100644 index 0000000000000..b745e9705ed30 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminEditCMSPageContentActionGroup.xml @@ -0,0 +1,24 @@ +<?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="AdminEditCMSPageContentActionGroup"> + <arguments> + <argument name="content" type="string" /> + <argument name="pageId" type="string" /> + </arguments> + <amOnPage url="{{AdminCmsPageEditPage.url(pageId)}}" stepKey="navigateToEditCMSPage"/> + <waitForPageLoad stepKey="waitForCmsPageEditPage"/> + <conditionalClick selector="{{CmsNewPagePageActionsSection.contentSectionName}}" dependentSelector="{{CatalogWidgetSection.insertWidgetButton}}" visible="false" stepKey="clickShowHideEditorIfVisible"/> + <waitForElementVisible selector="{{CmsNewPagePageContentSection.content}}" stepKey="waitForContentField"/> + <fillField selector="{{CmsNewPagePageContentSection.content}}" userInput="{{content}}" stepKey="resetCMSPageToDefaultContent"/> + <click selector="{{CmsNewPagePageActionsSection.saveAndContinueEdit}}" stepKey="clickSave"/> + <waitForPageLoad stepKey="waitForSettingsApply"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminInsertRecentlyViewedWidgetActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminInsertRecentlyViewedWidgetActionGroup.xml new file mode 100644 index 0000000000000..e8c66c68348fc --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminInsertRecentlyViewedWidgetActionGroup.xml @@ -0,0 +1,40 @@ +<?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="AdminInsertRecentlyViewedWidgetActionGroup"> + <arguments> + <argument name="attributeSelector1" type="string" defaultValue="show_attributes"/> + <argument name="attributeSelector2" type="string" defaultValue="show_buttons"/> + <argument name="productAttributeSection1" type="string" defaultValue="1"/> + <argument name="productAttributeSection2" type="string" defaultValue="4" /> + <argument name="buttonToShowSection1" type="string" defaultValue="1"/> + <argument name="buttonToShowSection2" type="string" defaultValue="3" /> + </arguments> + <click selector="{{CmsNewPagePageActionsSection.contentSectionName}}" stepKey="expandContent"/> + <waitForPageLoad time="50" stepKey="waitForPageLoadContentSection"/> + <conditionalClick selector="{{CmsNewPagePageActionsSection.showHideEditor}}" dependentSelector="{{CmsNewPagePageActionsSection.showHideEditor}}" visible="true" stepKey="clickNextShowHideEditorIfVisible"/> + <waitForElementVisible selector="{{CatalogWidgetSection.insertWidgetButton}}" stepKey="waitForInsertWidgetElement"/> + <click selector="{{CatalogWidgetSection.insertWidgetButton}}" stepKey="clickInsertWidget"/> + <waitForElementVisible selector="{{InsertWidgetSection.widgetTypeDropDown}}" time="30" stepKey="waitForWidgetTypeDropDownVisible"/> + <!--Select "Widget Type"--> + <selectOption selector="{{WidgetSection.WidgetType}}" userInput="Recently Viewed Products" stepKey="selectRecentlyViewedProducts"/> + <waitForPageLoad time="30" stepKey="waitForPageLoadWidgetType"/> + <!--Select all product attributes--> + <dragAndDrop selector1="{{AdminRecentlyViewedWidgetSection.attributeSelector(attributeSelector1,productAttributeSection1)}}" selector2="{{AdminRecentlyViewedWidgetSection.attributeSelector(attributeSelector1,productAttributeSection2)}}" stepKey="selectProductSpecifiedOptions"/> + <!--Select all buttons to show--> + <dragAndDrop selector1="{{AdminRecentlyViewedWidgetSection.attributeSelector(attributeSelector2,buttonToShowSection2)}}" selector2="{{AdminRecentlyViewedWidgetSection.attributeSelector(attributeSelector2,buttonToShowSection2)}}" stepKey="selectButtonSpecifiedOptions"/> + <click selector="{{WidgetSection.InsertWidget}}" stepKey="clickInsertWidgetToSave"/> + <waitForPageLoad time="30" stepKey="waitForWidgetInsertPageLoad"/> + <!-- Check that widget is inserted --> + <waitForElementVisible selector="#cms_page_form_content" stepKey="checkCMSContent" time="30"/> + <click selector="{{CmsNewPagePageActionsSection.saveAndContinueEdit}}" stepKey="clickNextSave"/> + <waitForPageLoad stepKey="waitForPageActionSave" time="30"/> + <waitForElementVisible selector="*[data-ui-id='messages-message-success']" time="60" stepKey="waitForSaveSuccess"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Cms/Test/Mftf/Data/CmsHomepageData.xml b/app/code/Magento/Cms/Test/Mftf/Data/CmsHomepageData.xml new file mode 100644 index 0000000000000..ce2ce747716a2 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Data/CmsHomepageData.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="CmsHomePageContent"> + <data key="content">CMS homepage content goes here</data> + <data key="page_id">2</data> + </entity> + +</entities> \ No newline at end of file diff --git a/app/code/Magento/Cms/Test/Mftf/Section/AdminRecentlyViewedWidgetSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/AdminRecentlyViewedWidgetSection.xml new file mode 100644 index 0000000000000..37d1491945491 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Section/AdminRecentlyViewedWidgetSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminRecentlyViewedWidgetSection"> + <element name="attributeSelector" type="multiselect" selector="select[name='parameters[{{attributeName}}][]'] option:nth-of-type({{attributePosition}})" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/StoreFrontMobileViewValidation.xml b/app/code/Magento/Cms/Test/Mftf/Test/StoreFrontMobileViewValidationTest.xml similarity index 98% rename from app/code/Magento/Cms/Test/Mftf/Test/StoreFrontMobileViewValidation.xml rename to app/code/Magento/Cms/Test/Mftf/Test/StoreFrontMobileViewValidationTest.xml index 6165def067ef4..38005682287e8 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/StoreFrontMobileViewValidation.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/StoreFrontMobileViewValidationTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StoreFrontMobileViewValidation"> + <test name="StoreFrontMobileViewValidationTest"> <annotations> <features value="Cms"/> <stories value="Mobile view page footer should stick to the bottom of page on Store front"/> diff --git a/app/code/Magento/Config/Test/Mftf/Section/AdminPersistentShoppingCartSection.xml b/app/code/Magento/Config/Test/Mftf/Section/AdminPersistentShoppingCartSection.xml new file mode 100644 index 0000000000000..7dc0d16e39556 --- /dev/null +++ b/app/code/Magento/Config/Test/Mftf/Section/AdminPersistentShoppingCartSection.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminPersistentShoppingCartSection"> + <element name="DefaultLayoutsTab" type="button" selector=".entry-edit-head-link"/> + <element name="CheckIfTabExpand" type="button" selector=".entry-edit-head-link:not(.open)"/> + <element name="persistenceLifeTime" type="input" selector="#persistent_options_lifetime"/> + <element name="rememberMeEnable" type="input" selector="#persistent_options_remember_enabled"/> + <element name="rememberMeDefault" type="input" selector="#persistent_options_remember_default"/> + <element name="clearPersistenceOnLogout" type="input" selector="#persistent_options_logout_clear"/> + <element name="persistShoppingCart" type="input" selector="#persistent_options_shopping_cart"/> + </section> +</sections> diff --git a/app/code/Magento/Config/Test/Mftf/Test/ConfigurationTest.xml b/app/code/Magento/Config/Test/Mftf/Test/ConfigurationTest.xml index 916a5a09a09c0..9700d8024ce8f 100644 --- a/app/code/Magento/Config/Test/Mftf/Test/ConfigurationTest.xml +++ b/app/code/Magento/Config/Test/Mftf/Test/ConfigurationTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="VerifyAllowDynamicMediaURLsSettingIsRemoved"> + <test name="VerifyAllowDynamicMediaURLsSettingIsRemovedTest"> <annotations> <features value="Backend"/> <stories value="Dynamic Media URL"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductOutOfStockTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductOutOfStockTest.xml index 7e77f070bf6a5..a6fd3ba05c1fc 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductOutOfStockTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductOutOfStockTest.xml @@ -134,7 +134,7 @@ <see stepKey="checkForOutOfStock3" selector="{{StorefrontProductInfoMainSection.stockIndication}}" userInput="OUT OF STOCK"/> </test> - <test name="AdminConfigurableProductOutOfStockTestDeleteChildren"> + <test name="AdminConfigurableProductOutOfStockTestDeleteChildrenTest"> <annotations> <features value="ConfigurableProduct"/> <stories value="Product visibility when in stock/out of stock"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndEditConfigurableProductSettingsTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndEditConfigurableProductSettingsTest.xml index 2503af780d5d3..6f5e8ddd54759 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndEditConfigurableProductSettingsTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndEditConfigurableProductSettingsTest.xml @@ -91,7 +91,7 @@ <!-- Verify Url Key after changing --> <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductPage"> - <argument name="productUrl" value="{{ApiConfigurableProduct.name}}"/> + <argument name="productUrl" value="{{ApiConfigurableProduct.urlKey}}"/> </actionGroup> <!-- Assert product design settings "Layout empty" --> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml index 24c60006a3504..a2cfd47eb3402 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml @@ -8,13 +8,13 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="ProductsQtyReturnAfterOrderCancel"> + <test name="ProductsQtyReturnAfterOrderCancelTest"> <annotations> <features value="ConfigurableProduct"/> <stories value="Cancel order"/> - <title value="Product qunatity return after order cancel"/> - <description value="Check Product qunatity return after order cancel"/> + <title value="Product quantity return after order cancel"/> + <description value="Check Product quantity return after order cancel"/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-97228"/> <useCaseId value="MAGETWO-82221"/> @@ -75,7 +75,7 @@ <click selector="{{AdminOrderDetailsMainActionsSection.invoice}}" stepKey="clickInvoiceButton"/> <waitForPageLoad stepKey="waitForNewInvoicePageLoad"/> <fillField selector="{{AdminInvoiceItemsSection.qtyToInvoiceColumn}}" userInput="1" stepKey="ChangeQtyToInvoice"/> - <click selector="{{AdminInvoiceItemsSection.updateQty}}" stepKey="updateQunatity"/> + <click selector="{{AdminInvoiceItemsSection.updateQty}}" stepKey="updateQuantity"/> <waitForPageLoad stepKey="waitPageToBeLoaded"/> <click selector="{{AdminInvoiceMainActionsSection.submitInvoice}}" stepKey="clickSubmitInvoice"/> <click selector="{{AdminOrderDetailsMainActionsSection.ship}}" stepKey="clickShipAction"/> diff --git a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js index 6f3af43bf5c7a..8fd1d761040d7 100644 --- a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js +++ b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js @@ -13,7 +13,8 @@ define([ 'priceUtils', 'priceBox', 'jquery-ui-modules/widget', - 'jquery/jquery.parsequery' + 'jquery/jquery.parsequery', + 'fotoramaVideoEvents' ], function ($, _, mageTemplate, $t, priceUtils) { 'use strict'; @@ -307,9 +308,13 @@ define([ _changeProductImage: function () { var images, initialImages = this.options.mediaGalleryInitial, - galleryObject = $(this.options.mediaGallerySelector).data('gallery'); + gallery = $(this.options.mediaGallerySelector).data('gallery'); + + if (_.isUndefined(gallery)) { + $(this.options.mediaGallerySelector).on('gallery:loaded', function () { + this._changeProductImage(); + }.bind(this)); - if (!galleryObject) { return; } @@ -325,17 +330,35 @@ define([ images = $.extend(true, [], images); images = this._setImageIndex(images); - galleryObject.updateData(images); - - $(this.options.mediaGallerySelector).AddFotoramaVideoEvents({ - selectedOption: this.simpleProduct, - dataMergeStrategy: this.options.gallerySwitchStrategy - }); + gallery.updateData(images); + this._addFotoramaVideoEvents(false); } else { - galleryObject.updateData(initialImages); + gallery.updateData(initialImages); + this._addFotoramaVideoEvents(true); + } + }, + + /** + * Add video events + * + * @param {Boolean} isInitial + * @private + */ + _addFotoramaVideoEvents: function (isInitial) { + if (_.isUndefined($.mage.AddFotoramaVideoEvents)) { + return; + } + + if (isInitial) { $(this.options.mediaGallerySelector).AddFotoramaVideoEvents(); + + return; } + $(this.options.mediaGallerySelector).AddFotoramaVideoEvents({ + selectedOption: this.simpleProduct, + dataMergeStrategy: this.options.gallerySwitchStrategy + }); }, /** diff --git a/app/code/Magento/Contact/Test/Mftf/Test/StorefrontVerifySecureURLRedirectContactTest.xml b/app/code/Magento/Contact/Test/Mftf/Test/StorefrontVerifySecureURLRedirectContactTest.xml index 3ef941fa2e0ce..0c46ed4729d66 100644 --- a/app/code/Magento/Contact/Test/Mftf/Test/StorefrontVerifySecureURLRedirectContactTest.xml +++ b/app/code/Magento/Contact/Test/Mftf/Test/StorefrontVerifySecureURLRedirectContactTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontVerifySecureURLRedirectContact"> + <test name="StorefrontVerifySecureURLRedirectContactTest"> <annotations> <features value="Contact"/> <stories value="Storefront Secure URLs"/> diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Cart.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Cart.php index ec4bd93ee4ff0..656a78d1165e3 100644 --- a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Cart.php +++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Cart.php @@ -5,37 +5,51 @@ */ namespace Magento\Customer\Block\Adminhtml\Edit\Tab; -use Magento\Catalog\Model\Product; +use Magento\Backend\Block\Template\Context; +use Magento\Backend\Block\Widget\Form; +use Magento\Backend\Block\Widget\Grid\Extended; +use Magento\Backend\Helper\Data; +use Magento\Customer\Block\Adminhtml\Edit\Tab\View\Grid\Renderer\Item; +use Magento\Customer\Block\Adminhtml\Grid\Renderer\Multiaction; use Magento\Customer\Controller\RegistryConstants; -use Magento\Directory\Model\Currency; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Data\CollectionFactory; +use Magento\Framework\Data\FormFactory; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Registry; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\QuoteFactory; +use Magento\Store\Model\System\Store as SystemStore; /** * Adminhtml customer orders grid block * * @api * @since 100.0.2 + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Cart extends \Magento\Backend\Block\Widget\Grid\Extended +class Cart extends Extended { /** * Core registry * - * @var \Magento\Framework\Registry + * @var Registry */ protected $_coreRegistry = null; /** - * @var \Magento\Framework\Data\CollectionFactory + * @var CollectionFactory */ protected $_dataCollectionFactory; /** - * @var \Magento\Quote\Api\CartRepositoryInterface + * @var CartRepositoryInterface */ protected $quoteRepository; /** - * @var \Magento\Quote\Model\Quote + * @var Quote */ protected $quote = null; @@ -45,32 +59,46 @@ class Cart extends \Magento\Backend\Block\Widget\Grid\Extended protected $_parentTemplate; /** - * @var \Magento\Quote\Model\QuoteFactory + * @var QuoteFactory */ protected $quoteFactory; + /** + * @var SystemStore + */ + private $systemStore; + /** + * @var FormFactory + */ + private $formFactory; /** - * @param \Magento\Backend\Block\Template\Context $context - * @param \Magento\Backend\Helper\Data $backendHelper - * @param \Magento\Quote\Api\CartRepositoryInterface $quoteRepository - * @param \Magento\Framework\Data\CollectionFactory $dataCollectionFactory - * @param \Magento\Framework\Registry $coreRegistry - * @param \Magento\Quote\Model\QuoteFactory $quoteFactory + * @param Context $context + * @param Data $backendHelper + * @param CartRepositoryInterface $quoteRepository + * @param CollectionFactory $dataCollectionFactory + * @param Registry $coreRegistry + * @param QuoteFactory $quoteFactory * @param array $data + * @param SystemStore|null $systemStore + * @param FormFactory|null $formFactory */ public function __construct( - \Magento\Backend\Block\Template\Context $context, - \Magento\Backend\Helper\Data $backendHelper, - \Magento\Quote\Api\CartRepositoryInterface $quoteRepository, - \Magento\Framework\Data\CollectionFactory $dataCollectionFactory, - \Magento\Framework\Registry $coreRegistry, - \Magento\Quote\Model\QuoteFactory $quoteFactory, - array $data = [] + Context $context, + Data $backendHelper, + CartRepositoryInterface $quoteRepository, + CollectionFactory $dataCollectionFactory, + Registry $coreRegistry, + QuoteFactory $quoteFactory, + array $data = [], + ?SystemStore $systemStore = null, + ?FormFactory $formFactory = null ) { $this->_dataCollectionFactory = $dataCollectionFactory; $this->_coreRegistry = $coreRegistry; $this->quoteRepository = $quoteRepository; $this->quoteFactory = $quoteFactory; + $this->systemStore = $systemStore ?? ObjectManager::getInstance()->get(SystemStore::class); + $this->formFactory = $formFactory ?? ObjectManager::getInstance()->get(FormFactory::class); parent::__construct($context, $backendHelper, $data); } @@ -92,8 +120,11 @@ protected function _construct() */ protected function _prepareGrid() { - $this->setId('customer_cart_grid' . $this->getWebsiteId()); + $this->setId('customer_cart_grid'); parent::_prepareGrid(); + if (!$this->_storeManager->isSingleStoreMode()) { + $this->prepareWebsiteFilter(); + } } /** @@ -129,7 +160,7 @@ protected function _prepareColumns() [ 'header' => __('Product'), 'index' => 'name', - 'renderer' => \Magento\Customer\Block\Adminhtml\Edit\Tab\View\Grid\Renderer\Item::class + 'renderer' => Item::class ] ); @@ -167,7 +198,7 @@ protected function _prepareColumns() [ 'header' => __('Action'), 'index' => 'quote_item_id', - 'renderer' => \Magento\Customer\Block\Adminhtml\Grid\Renderer\Multiaction::class, + 'renderer' => Multiaction::class, 'filter' => false, 'sortable' => false, 'actions' => [ @@ -245,10 +276,59 @@ protected function getQuote() try { $this->quote = $this->quoteRepository->getForCustomer($customerId, $storeIds); - } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + } catch (NoSuchEntityException $e) { $this->quote = $this->quoteFactory->create()->setSharedStoreIds($storeIds); } } return $this->quote; } + + /** + * Add website filter block to the layout + * + * @return void + */ + private function prepareWebsiteFilter(): void + { + $form = $this->formFactory->create(); + $form->addField( + 'website_filter', + 'select', + [ + 'name' => 'website_id', + 'values' => $this->systemStore->getWebsiteOptionHash(), + 'value' => $this->getWebsiteId() ?? $this->_storeManager->getWebsite()->getId(), + 'no_span' => true, + 'onchange' => "{$this->getJsObjectName()}.loadByElement(this);", + ] + ); + /** + * @var Form $formWidget + */ + $formWidget = $this->getLayout()->createBlock(Form::class); + $formWidget->setForm($form); + $formWidget->setTemplate('Magento_Customer::tab/cart_website_filter_form.phtml'); + $this->setChild( + 'website_filter_block', + $formWidget + ); + } + + /** + * @inheritDoc + */ + public function getMainButtonsHtml() + { + return $this->getWebsiteFilterHtml() . parent::getMainButtonsHtml(); + } + + /** + * Generate website filter + * + * @return string + */ + private function getWebsiteFilterHtml(): string + { + return $this->getChildHtml('website_filter_block'); + } } diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/Cart.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/Cart.php index 1e4c1fb001ea3..6528ac4c1f211 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/Cart.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/Cart.php @@ -5,84 +5,116 @@ */ namespace Magento\Customer\Controller\Adminhtml\Index; +use Magento\Backend\App\Action\Context; +use Magento\Backend\Model\View\Result\ForwardFactory; use Magento\Customer\Api\AccountManagementInterface; use Magento\Customer\Api\AddressRepositoryInterface; use Magento\Customer\Api\CustomerRepositoryInterface; use Magento\Customer\Api\Data\AddressInterfaceFactory; use Magento\Customer\Api\Data\CustomerInterfaceFactory; +use Magento\Customer\Controller\Adminhtml\Index as BaseAction; +use Magento\Customer\Helper\View; use Magento\Customer\Model\Address\Mapper; -use Magento\Framework\DataObjectFactory as ObjectFactory; +use Magento\Customer\Model\AddressFactory; +use Magento\Customer\Model\CustomerFactory; +use Magento\Customer\Model\Metadata\FormFactory; use Magento\Framework\Api\DataObjectHelper; +use Magento\Framework\Api\ExtensibleDataObjectConverter; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; +use Magento\Framework\App\Response\Http\FileFactory; +use Magento\Framework\Controller\Result\JsonFactory; +use Magento\Framework\DataObjectFactory as ObjectFactory; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Math\Random; +use Magento\Framework\Reflection\DataObjectProcessor; +use Magento\Framework\Registry; +use Magento\Framework\View\Result\Layout; +use Magento\Framework\View\Result\LayoutFactory; +use Magento\Framework\View\Result\PageFactory; +use Magento\Newsletter\Model\SubscriberFactory; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\QuoteFactory; +use Magento\Store\Model\StoreManagerInterface; /** + * Admin customer shopping cart controller + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @deprecated 100.2.0 */ -class Cart extends \Magento\Customer\Controller\Adminhtml\Index +class Cart extends BaseAction implements HttpGetActionInterface, HttpPostActionInterface { /** - * @var \Magento\Quote\Model\QuoteFactory + * @var QuoteFactory */ private $quoteFactory; + /** + * @var StoreManagerInterface + */ + private $storeManager; /** * Constructor * - * @param \Magento\Backend\App\Action\Context $context - * @param \Magento\Framework\Registry $coreRegistry - * @param \Magento\Framework\App\Response\Http\FileFactory $fileFactory - * @param \Magento\Customer\Model\CustomerFactory $customerFactory - * @param \Magento\Customer\Model\AddressFactory $addressFactory - * @param \Magento\Customer\Model\Metadata\FormFactory $formFactory - * @param \Magento\Newsletter\Model\SubscriberFactory $subscriberFactory - * @param \Magento\Customer\Helper\View $viewHelper - * @param \Magento\Framework\Math\Random $random + * @param Context $context + * @param Registry $coreRegistry + * @param FileFactory $fileFactory + * @param CustomerFactory $customerFactory + * @param AddressFactory $addressFactory + * @param FormFactory $formFactory + * @param SubscriberFactory $subscriberFactory + * @param View $viewHelper + * @param Random $random * @param CustomerRepositoryInterface $customerRepository - * @param \Magento\Framework\Api\ExtensibleDataObjectConverter $extensibleDataObjectConverter + * @param ExtensibleDataObjectConverter $extensibleDataObjectConverter * @param Mapper $addressMapper * @param AccountManagementInterface $customerAccountManagement * @param AddressRepositoryInterface $addressRepository * @param CustomerInterfaceFactory $customerDataFactory * @param AddressInterfaceFactory $addressDataFactory * @param \Magento\Customer\Model\Customer\Mapper $customerMapper - * @param \Magento\Framework\Reflection\DataObjectProcessor $dataObjectProcessor + * @param DataObjectProcessor $dataObjectProcessor * @param DataObjectHelper $dataObjectHelper * @param ObjectFactory $objectFactory * @param \Magento\Framework\View\LayoutFactory $layoutFactory - * @param \Magento\Framework\View\Result\LayoutFactory $resultLayoutFactory - * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory - * @param \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory - * @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory - * @param \Magento\Quote\Model\QuoteFactory|null $quoteFactory + * @param LayoutFactory $resultLayoutFactory + * @param PageFactory $resultPageFactory + * @param ForwardFactory $resultForwardFactory + * @param JsonFactory $resultJsonFactory + * @param QuoteFactory|null $quoteFactory + * @param StoreManagerInterface|null $storeManager * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( - \Magento\Backend\App\Action\Context $context, - \Magento\Framework\Registry $coreRegistry, - \Magento\Framework\App\Response\Http\FileFactory $fileFactory, - \Magento\Customer\Model\CustomerFactory $customerFactory, - \Magento\Customer\Model\AddressFactory $addressFactory, - \Magento\Customer\Model\Metadata\FormFactory $formFactory, - \Magento\Newsletter\Model\SubscriberFactory $subscriberFactory, - \Magento\Customer\Helper\View $viewHelper, - \Magento\Framework\Math\Random $random, + Context $context, + Registry $coreRegistry, + FileFactory $fileFactory, + CustomerFactory $customerFactory, + AddressFactory $addressFactory, + FormFactory $formFactory, + SubscriberFactory $subscriberFactory, + View $viewHelper, + Random $random, CustomerRepositoryInterface $customerRepository, - \Magento\Framework\Api\ExtensibleDataObjectConverter $extensibleDataObjectConverter, + ExtensibleDataObjectConverter $extensibleDataObjectConverter, Mapper $addressMapper, AccountManagementInterface $customerAccountManagement, AddressRepositoryInterface $addressRepository, CustomerInterfaceFactory $customerDataFactory, AddressInterfaceFactory $addressDataFactory, \Magento\Customer\Model\Customer\Mapper $customerMapper, - \Magento\Framework\Reflection\DataObjectProcessor $dataObjectProcessor, + DataObjectProcessor $dataObjectProcessor, DataObjectHelper $dataObjectHelper, ObjectFactory $objectFactory, \Magento\Framework\View\LayoutFactory $layoutFactory, - \Magento\Framework\View\Result\LayoutFactory $resultLayoutFactory, - \Magento\Framework\View\Result\PageFactory $resultPageFactory, - \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory, - \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory, - \Magento\Quote\Model\QuoteFactory $quoteFactory = null + LayoutFactory $resultLayoutFactory, + PageFactory $resultPageFactory, + ForwardFactory $resultForwardFactory, + JsonFactory $resultJsonFactory, + QuoteFactory $quoteFactory = null, + ?StoreManagerInterface $storeManager = null ) { parent::__construct( $context, @@ -111,13 +143,14 @@ public function __construct( $resultForwardFactory, $resultJsonFactory ); - $this->quoteFactory = $quoteFactory ?: $this->_objectManager->get(\Magento\Quote\Model\QuoteFactory::class); + $this->quoteFactory = $quoteFactory ?: $this->_objectManager->get(QuoteFactory::class); + $this->storeManager = $storeManager ?? $this->_objectManager->get(StoreManagerInterface::class); } /** * Handle and then get cart grid contents * - * @return \Magento\Framework\View\Result\Layout + * @return Layout */ public function execute() { @@ -127,16 +160,17 @@ public function execute() // delete an item from cart $deleteItemId = $this->getRequest()->getPost('delete'); if ($deleteItemId) { - /** @var \Magento\Quote\Api\CartRepositoryInterface $quoteRepository */ - $quoteRepository = $this->_objectManager->create(\Magento\Quote\Api\CartRepositoryInterface::class); - /** @var \Magento\Quote\Model\Quote $quote */ + /** @var CartRepositoryInterface $quoteRepository */ + $quoteRepository = $this->_objectManager->create(CartRepositoryInterface::class); + /** @var Quote $quote */ try { - $quote = $quoteRepository->getForCustomer($customerId); - } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + $storeIds = $this->storeManager->getWebsite($websiteId)->getStoreIds(); + $quote = $quoteRepository->getForCustomer($customerId, $storeIds); + } catch (NoSuchEntityException $e) { $quote = $this->quoteFactory->create(); } $quote->setWebsite( - $this->_objectManager->get(\Magento\Store\Model\StoreManagerInterface::class)->getWebsite($websiteId) + $this->storeManager->getWebsite($websiteId) ); $item = $quote->getItemById($deleteItemId); if ($item && $item->getId()) { diff --git a/app/code/Magento/Customer/Model/AttributeMetadataResolver.php b/app/code/Magento/Customer/Model/AttributeMetadataResolver.php index c936de1bd0230..27c5f77674577 100644 --- a/app/code/Magento/Customer/Model/AttributeMetadataResolver.php +++ b/app/code/Magento/Customer/Model/AttributeMetadataResolver.php @@ -6,17 +6,17 @@ */ namespace Magento\Customer\Model; +use Magento\Customer\Api\Data\AddressInterface; +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Customer\Model\Config\Share as ShareConfig; use Magento\Customer\Model\ResourceModel\Address\Attribute\Source\CountryWithWebsites; +use Magento\Eav\Api\Data\AttributeInterface; use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; -use Magento\Customer\Api\Data\AddressInterface; -use Magento\Ui\DataProvider\EavValidationRules; -use Magento\Ui\Component\Form\Field; use Magento\Eav\Model\Entity\Type; -use Magento\Eav\Api\Data\AttributeInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\View\Element\UiComponent\ContextInterface; -use Magento\Customer\Api\Data\CustomerInterface; -use Magento\Customer\Model\Config\Share as ShareConfig; -use Magento\Customer\Model\FileUploaderDataResolver; +use Magento\Ui\Component\Form\Field; +use Magento\Ui\DataProvider\EavValidationRules; /** * Class to build meta data of the customer or customer address attribute @@ -75,25 +75,33 @@ class AttributeMetadataResolver */ private $shareConfig; + /** + * @var GroupManagement + */ + private $groupManagement; + /** * @param CountryWithWebsites $countryWithWebsiteSource * @param EavValidationRules $eavValidationRules * @param FileUploaderDataResolver $fileUploaderDataResolver * @param ContextInterface $context * @param ShareConfig $shareConfig + * @param GroupManagement|null $groupManagement */ public function __construct( CountryWithWebsites $countryWithWebsiteSource, EavValidationRules $eavValidationRules, FileUploaderDataResolver $fileUploaderDataResolver, ContextInterface $context, - ShareConfig $shareConfig + ShareConfig $shareConfig, + ?GroupManagement $groupManagement = null ) { $this->countryWithWebsiteSource = $countryWithWebsiteSource; $this->eavValidationRules = $eavValidationRules; $this->fileUploaderDataResolver = $fileUploaderDataResolver; $this->context = $context; $this->shareConfig = $shareConfig; + $this->groupManagement = $groupManagement ?? ObjectManager::getInstance()->get(GroupManagement::class); } /** @@ -111,6 +119,7 @@ public function getAttributesMeta( bool $allowToShowHiddenAttributes ): array { $meta = $this->modifyBooleanAttributeMeta($attribute); + $this->modifyGroupAttributeMeta($attribute); // use getDataUsingMethod, since some getters are defined and apply additional processing of returning value foreach (self::$metaProperties as $metaName => $origName) { $value = $attribute->getDataUsingMethod($origName); @@ -196,6 +205,21 @@ private function modifyBooleanAttributeMeta(AttributeInterface $attribute): arra return $meta; } + /** + * Modify group attribute meta data + * + * @param AttributeInterface $attribute + * @return void + */ + private function modifyGroupAttributeMeta(AttributeInterface $attribute): void + { + if ($attribute->getAttributeCode() === 'group_id') { + $defaultGroup = $this->groupManagement->getDefaultGroup(); + $defaultGroupId = !empty($defaultGroup) ? $defaultGroup->getId() : null; + $attribute->setDataUsingMethod(self::$metaProperties['default'], $defaultGroupId); + } + } + /** * Add global scope parameter and filter options to website meta * diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml index 56afa8854ce0d..59601a58e64c7 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml @@ -8,7 +8,7 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="SignUpNewUserFromStorefrontActionGroup"> + <actionGroup name="SignUpNewUserFromStorefrontActionGroup" deprecated="Avoid using super-ActionGroups. See StorefrontCreateCustomerTest for replacement."> <annotations> <description>Goes to the Storefront. Clicks on 'Create Account'. Fills in the provided Customer details, excluding Newsletter Sign-Up. Clicks on 'Create Account' button. Validate that the Customer details are present and correct.</description> </annotations> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFakeBrokenSessionActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFakeBrokenSessionActionGroup.xml new file mode 100644 index 0000000000000..3d989a7833b8a --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFakeBrokenSessionActionGroup.xml @@ -0,0 +1,14 @@ +<?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="StorefrontFakeBrokenSessionActionGroup"> + <resetCookie userInput="PHPSESSID" stepKey="resetCookieForCart"/> + <resetCookie userInput="form_key" stepKey="resetCookieForCart2"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml index 49fa60436ed5a..e9a21ab9fcfa7 100644 --- a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml @@ -178,6 +178,18 @@ <data key="website_id">0</data> <requiredEntity type="address">US_Address_CA</requiredEntity> </entity> + <entity name="Simple_US_Customer_CA_Without_Email" type="customer"> + <data key="group_id">0</data> + <data key="default_billing">true</data> + <data key="default_shipping">true</data> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="fullname">John Doe</data> + <data key="password">pwdTest123!</data> + <data key="store_id">0</data> + <data key="website_id">0</data> + <requiredEntity type="address">US_Address_CA</requiredEntity> + </entity> <entity name="Simple_US_Customer_For_Update" type="customer"> <var key="id" entityKey="id" entityType="customer"/> <data key="firstname">Jane</data> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminProductBackRedirectNavigateFromCustomerViewCartProduct.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminProductBackRedirectNavigateFromCustomerViewCartProductTest.xml similarity index 99% rename from app/code/Magento/Customer/Test/Mftf/Test/AdminProductBackRedirectNavigateFromCustomerViewCartProduct.xml rename to app/code/Magento/Customer/Test/Mftf/Test/AdminProductBackRedirectNavigateFromCustomerViewCartProductTest.xml index 18106836ce137..3e65c688e3474 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminProductBackRedirectNavigateFromCustomerViewCartProduct.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminProductBackRedirectNavigateFromCustomerViewCartProductTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminProductBackRedirectNavigateFromCustomerViewCartProduct"> + <test name="AdminProductBackRedirectNavigateFromCustomerViewCartProductTest"> <annotations> <features value="Customer"/> <stories value="Product Back Button"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/ChangeCustomerGroupTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/ChangeCustomerGroupTest.xml index eb46a9d2b1ace..0eca9811f17f1 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/ChangeCustomerGroupTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/ChangeCustomerGroupTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="ChangingSingleCustomerGroupViaGrid"> + <test name="ChangingSingleCustomerGroupViaGridTest"> <annotations> <title value="DEPRECATED Change a single customer group via grid"/> <description value="From the selection of All Customers select a single customer to change their group"/> @@ -61,7 +61,7 @@ </actionGroup> </test> - <test name="ChangingAllCustomerGroupViaGrid" extends="ChangingSingleCustomerGroupViaGrid"> + <test name="ChangingAllCustomerGroupViaGridTest" extends="ChangingSingleCustomerGroupViaGridTest"> <annotations> <title value="DEPRECATED Change all customers' group via grid"/> <description value="Select All customers to change their group"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AddingProductWithExpiredSessionTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontAddProductToCartWithExpiredSessionTest.xml similarity index 64% rename from app/code/Magento/Customer/Test/Mftf/Test/AddingProductWithExpiredSessionTest.xml rename to app/code/Magento/Customer/Test/Mftf/Test/StorefrontAddProductToCartWithExpiredSessionTest.xml index 01f35439f23b8..73d050cfa09b9 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AddingProductWithExpiredSessionTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontAddProductToCartWithExpiredSessionTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AddingProductWithExpiredSessionTest"> + <test name="StorefrontAddProductToCartWithExpiredSessionTest"> <annotations> <title value="Adding a product to cart from category page with an expired session"/> <description value="Adding a product to cart from category page with an expired session"/> @@ -23,24 +23,20 @@ <createData entity="_defaultProduct" stepKey="createSimpleProduct"> <requiredEntity createDataKey="createCategory"/> </createData> + <magentoCron stepKey="runCronReindex" groups="index"/> </before> - - <!--Navigate to a category page --> - <amOnPage url="$$createSimpleProduct.name$$.html" stepKey="goToProductPage"/> - <waitForPageLoad stepKey="waitForPageLoad"/> - - <!-- Remove PHPSESSID and form_key to replicate an expired session--> - <resetCookie userInput="PHPSESSID" stepKey="resetCookieForCart"/> - <resetCookie userInput="form_key" stepKey="resetCookieForCart2"/> - - <!-- "Add to Cart" any product--> - <click selector="{{StorefrontProductPageSection.addToCartBtn}}" stepKey="addToCart"/> - <see stepKey="assertErrorMessage" userInput="Your session has expired"/> <after> - <!--Delete created product--> <deleteData createDataKey="createSimpleProduct" stepKey="deleteProduct"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> </after> + <actionGroup ref="StorefrontOpenProductEntityPageActionGroup" stepKey="openProductPage"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="StorefrontFakeBrokenSessionActionGroup" stepKey="fakeBrokenSession"/> + <actionGroup ref="StorefrontProductPageAddSimpleProductToCartActionGroup" stepKey="addProductToCart"/> + <actionGroup ref="StorefrontAssertProductAddToCartErrorMessageActionGroup" stepKey="assertFailure"> + <argument name="message" value="Your session has expired"/> + </actionGroup> </test> </tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateCustomerTest.xml index 7899f4ac53132..0bc46e8717f33 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateCustomerTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateCustomerTest.xml @@ -12,19 +12,22 @@ <annotations> <features value="Customer"/> <stories value="Create a Customer via the Storefront"/> - <title value="Admin should be able to create a customer via the storefront"/> - <description value="Admin should be able to create a customer via the storefront"/> + <title value="As a Customer I should be able to register an account on Storefront"/> + <description value="As a Customer I should be able to register an account on Storefront"/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-23546"/> <group value="customer"/> <group value="create"/> </annotations> - <after> - <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> - </after> - <actionGroup ref="SignUpNewUserFromStorefrontActionGroup" stepKey="SignUpNewUser"> - <argument name="Customer" value="CustomerEntityOne"/> + <actionGroup ref="StorefrontOpenCustomerAccountCreatePageActionGroup" stepKey="openCreateAccountPage"/> + <actionGroup ref="StorefrontFillCustomerAccountCreationFormActionGroup" stepKey="fillCreateAccountForm"> + <argument name="customer" value="Simple_US_Customer"/> + </actionGroup> + <actionGroup ref="StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup" stepKey="submitCreateAccountForm"/> + <actionGroup ref="AssertMessageCustomerCreateAccountActionGroup" stepKey="seeSuccessMessage"> + <argument name="messageType" value="success"/> + <argument name="message" value="Thank you for registering with Main Website Store."/> </actionGroup> </test> </tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateExistingCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateExistingCustomerTest.xml index 952ac235d92a4..07ac295e5cce0 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateExistingCustomerTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateExistingCustomerTest.xml @@ -8,29 +8,28 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontCreateExistingCustomerTest"> + <test name="StorefrontCreateExistingCustomerTest" extends="StorefrontCreateCustomerTest"> <annotations> <features value="Customer"/> <stories value="Customer Registration"/> - <title value="Attempt to register customer on storefront with existing email"/> - <description value="Attempt to register customer on storefront with existing email"/> + <title value="As a Customer I should not be able to register an account using already registered e-mail"/> + <description value="As a Customer I should not be able to register an account using already registered e-mail"/> <testCaseId value="MC-10907"/> <severity value="MAJOR"/> <group value="customers"/> <group value="mtf_migrated"/> </annotations> <before> - <createData entity="Simple_US_Customer" stepKey="customer"/> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> </before> <after> - <deleteData createDataKey="customer" stepKey="deleteCustomer"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> </after> - <actionGroup ref="StorefrontOpenCustomerAccountCreatePageActionGroup" stepKey="openCreateAccountPage"/> <actionGroup ref="StorefrontFillCustomerAccountCreationFormActionGroup" stepKey="fillCreateAccountForm"> - <argument name="customer" value="$$customer$$"/> + <argument name="customer" value="$$createCustomer$$"/> </actionGroup> - <actionGroup ref="StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup" stepKey="submitCreateAccountForm"/> + <remove keyForRemoval="seeSuccessMessage"/> <actionGroup ref="AssertMessageCustomerCreateAccountActionGroup" stepKey="seeErrorMessage"> <argument name="messageType" value="error"/> <argument name="message" value="There is already an account with this email address."/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCustomerTest.xml index da9dddf0539d3..f504af2334e10 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCustomerTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCustomerTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontVerifySecureURLRedirectCustomer"> + <test name="StorefrontVerifySecureURLRedirectCustomerTest"> <annotations> <features value="Customer"/> <stories value="Storefront Secure URLs"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/_Deprecated_Test.xml b/app/code/Magento/Customer/Test/Mftf/Test/_Deprecated_Test.xml new file mode 100644 index 0000000000000..5f948f5b69cae --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/_Deprecated_Test.xml @@ -0,0 +1,11 @@ +<?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="AddingProductWithExpiredSessionTest" extends="StorefrontAddProductToCartWithExpiredSessionTest" deprecated="Use StorefrontAddProductToCartWithExpiredSessionTest"/> +</tests> diff --git a/app/code/Magento/Customer/Test/Unit/Model/AttributeMetadataResolverTest.php b/app/code/Magento/Customer/Test/Unit/Model/AttributeMetadataResolverTest.php new file mode 100644 index 0000000000000..aef9d8ca40e85 --- /dev/null +++ b/app/code/Magento/Customer/Test/Unit/Model/AttributeMetadataResolverTest.php @@ -0,0 +1,166 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Test\Unit\Model; + +use Magento\Customer\Model\Attribute; +use Magento\Customer\Model\AttributeMetadataResolver; +use Magento\Customer\Model\Config\Share as ShareConfig; +use Magento\Customer\Model\FileUploaderDataResolver; +use Magento\Customer\Model\GroupManagement; +use Magento\Customer\Model\ResourceModel\Address\Attribute\Source\CountryWithWebsites; +use Magento\Eav\Model\Entity\Type; +use Magento\Framework\View\Element\UiComponent\ContextInterface; +use Magento\Ui\DataProvider\EavValidationRules; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Class AttributeMetadataResolverTest + * + * Validate attributeMetadata contains correct values in meta data array + */ +class AttributeMetadataResolverTest extends TestCase +{ + /** + * @var CountryWithWebsites|MockObject + */ + private $countryWithWebsiteSource; + + /** + * @var EavValidationRules|MockObject + */ + private $eavValidationRules; + + /** + * @var FileUploaderDataResolver|MockObject + */ + private $fileUploaderDataResolver; + + /** + * @var ShareConfig|MockObject + */ + private $shareConfig; + + /** + * @var GroupManagement|MockObject + */ + private $groupManagement; + + /** + * @var ContextInterface|MockObject + */ + private $context; + + /** + * @var AttributeMetadataResolver + */ + private $model; + + /** + * @var Attribute|MockObject + */ + private $attribute; + + /** + * @inheritdoc + */ + public function setUp() + { + $this->countryWithWebsiteSource = $this->getMockBuilder(CountryWithWebsites::class) + ->setMethods(['getAllOptions']) + ->disableOriginalConstructor() + ->getMock(); + $this->eavValidationRules = $this->getMockBuilder(EavValidationRules::class) + ->setMethods(['build']) + ->disableOriginalConstructor() + ->getMock(); + $this->fileUploaderDataResolver = $this->getMockBuilder(FileUploaderDataResolver::class) + ->setMethods(['overrideFileUploaderMetadata']) + ->disableOriginalConstructor() + ->getMock(); + $this->context = $this->getMockBuilder(ContextInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->shareConfig = $this->getMockBuilder(ShareConfig::class) + ->disableOriginalConstructor() + ->getMock(); + $this->groupManagement = $this->getMockBuilder(GroupManagement::class) + ->setMethods(['getId', 'getDefaultGroup']) + ->disableOriginalConstructor() + ->getMock(); + $this->attribute = $this->getMockBuilder(Attribute::class) + ->setMethods([ + 'usesSource', + 'getDataUsingMethod', + 'getAttributeCode', + 'getFrontendInput', + 'getSource', + 'setDataUsingMethod' + ]) + ->disableOriginalConstructor() + ->getMock(); + + $this->model = new AttributeMetadataResolver( + $this->countryWithWebsiteSource, + $this->eavValidationRules, + $this->fileUploaderDataResolver, + $this->context, + $this->shareConfig, + $this->groupManagement + ); + } + + /** + * Test to get meta data of the customer or customer address attribute + * + * @return void + */ + public function testGetAttributesMetaHasDefaultAttributeValue(): void + { + $rules = [ + 'required-entry' => true + ]; + $defaultGroupId = '3'; + $allowToShowHiddenAttributes = false; + $usesSource = false; + $entityType = $this->getMockBuilder(Type::class) + ->disableOriginalConstructor() + ->getMock(); + $this->attribute->expects($this->once()) + ->method('usesSource') + ->willReturn($usesSource); + $this->attribute->expects($this->once()) + ->method('getAttributeCode') + ->willReturn('group_id'); + $this->groupManagement->expects($this->once()) + ->method('getDefaultGroup') + ->willReturnSelf(); + $this->groupManagement->expects($this->once()) + ->method('getId') + ->willReturn($defaultGroupId); + $this->attribute->expects($this->at(9)) + ->method('getDataUsingMethod') + ->with('default_value') + ->willReturn($defaultGroupId); + $this->attribute->expects($this->once()) + ->method('setDataUsingMethod') + ->willReturnSelf(); + $this->eavValidationRules->expects($this->once()) + ->method('build') + ->with($this->attribute) + ->willReturn($rules); + $this->fileUploaderDataResolver->expects($this->once()) + ->method('overrideFileUploaderMetadata') + ->with($entityType, $this->attribute) + ->willReturnSelf(); + + $meta = $this->model->getAttributesMeta($this->attribute, $entityType, $allowToShowHiddenAttributes); + $this->assertArrayHasKey('default', $meta['arguments']['data']['config']); + $this->assertEquals($defaultGroupId, $meta['arguments']['data']['config']['default']); + } +} diff --git a/app/code/Magento/Customer/etc/adminhtml/system.xml b/app/code/Magento/Customer/etc/adminhtml/system.xml index 6234c2a84ac83..fca625d847a1d 100644 --- a/app/code/Magento/Customer/etc/adminhtml/system.xml +++ b/app/code/Magento/Customer/etc/adminhtml/system.xml @@ -86,6 +86,9 @@ <comment>To show VAT number on Storefront, set Show VAT Number on Storefront option to Yes.</comment> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> + <field id="email_domain" translate="label" type="text" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <label>Default Email Domain</label> + </field> <field id="email_template" translate="label comment" type="select" sortOrder="70" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Default Welcome Email</label> <comment>Email template chosen based on theme fallback when "Default" option is selected.</comment> diff --git a/app/code/Magento/Customer/etc/config.xml b/app/code/Magento/Customer/etc/config.xml index db04ad7c94963..ab2020580a2eb 100644 --- a/app/code/Magento/Customer/etc/config.xml +++ b/app/code/Magento/Customer/etc/config.xml @@ -23,6 +23,7 @@ <email_confirmed_template>customer_create_account_email_confirmed_template</email_confirmed_template> <viv_disable_auto_group_assign_default>0</viv_disable_auto_group_assign_default> <vat_frontend_visibility>0</vat_frontend_visibility> + <email_domain>example.com</email_domain> <generate_human_friendly_id>0</generate_human_friendly_id> </create_account> <default> diff --git a/app/code/Magento/Customer/view/adminhtml/templates/tab/cart_website_filter_form.phtml b/app/code/Magento/Customer/view/adminhtml/templates/tab/cart_website_filter_form.phtml new file mode 100644 index 0000000000000..ec903fa978fce --- /dev/null +++ b/app/code/Magento/Customer/view/adminhtml/templates/tab/cart_website_filter_form.phtml @@ -0,0 +1,10 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** @var $block \Magento\Backend\Block\Widget\Form */ +?> +<?= $block->getFormHtml() ?> +<?= $block->getChildHtml('form_after') ?> diff --git a/app/code/Magento/Customer/view/frontend/web/js/validation.js b/app/code/Magento/Customer/view/frontend/web/js/validation.js index 67a714212026a..573556f0f33a2 100644 --- a/app/code/Magento/Customer/view/frontend/web/js/validation.js +++ b/app/code/Magento/Customer/view/frontend/web/js/validation.js @@ -2,6 +2,7 @@ define([ 'jquery', 'moment', 'jquery/validate', + 'validation', 'mage/translate' ], function ($, moment) { 'use strict'; diff --git a/app/code/Magento/CustomerImportExport/Model/Import/Address.php b/app/code/Magento/CustomerImportExport/Model/Import/Address.php index 4eee5a39d55e1..09d76ec3fb71f 100644 --- a/app/code/Magento/CustomerImportExport/Model/Import/Address.php +++ b/app/code/Magento/CustomerImportExport/Model/Import/Address.php @@ -13,6 +13,7 @@ use Magento\Store\Model\Store; use Magento\CustomerImportExport\Model\ResourceModel\Import\Address\Storage as AddressStorage; use Magento\ImportExport\Model\Import\AbstractSource; +use Magento\Customer\Model\Indexer\Processor; /** * Customer address import @@ -254,6 +255,11 @@ class Address extends AbstractCustomer */ private $addressStorage; + /** + * @var Processor + */ + private $indexerProcessor; + /** * @param \Magento\Framework\Stdlib\StringUtils $string * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig @@ -274,6 +280,7 @@ class Address extends AbstractCustomer * @param array $data * @param CountryWithWebsitesSource|null $countryWithWebsites * @param AddressStorage|null $addressStorage + * @param Processor $indexerProcessor * * @SuppressWarnings(PHPMD.NPathComplexity) * @SuppressWarnings(PHPMD.ExcessiveParameterList) @@ -296,8 +303,9 @@ public function __construct( \Magento\Framework\Stdlib\DateTime $dateTime, \Magento\Customer\Model\Address\Validator\Postcode $postcodeValidator, array $data = [], - CountryWithWebsitesSource $countryWithWebsites = null, - AddressStorage $addressStorage = null + ?CountryWithWebsitesSource $countryWithWebsites = null, + ?AddressStorage $addressStorage = null, + ?Processor $indexerProcessor = null ) { $this->_customerFactory = $customerFactory; $this->_addressFactory = $addressFactory; @@ -348,6 +356,9 @@ public function __construct( $this->addressStorage = $addressStorage ?: ObjectManager::getInstance()->get(AddressStorage::class); + $this->indexerProcessor = $indexerProcessor + ?: ObjectManager::getInstance()->get(Processor::class); + $this->_initAttributes(); $this->_initCountryRegions(); } @@ -562,6 +573,7 @@ protected function _importData() $this->_deleteAddressEntities($deleteRowIds); } + $this->indexerProcessor->markIndexerAsInvalid(); return true; } diff --git a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php index cf3fa6b0b521f..8f5bb951ce737 100644 --- a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php +++ b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php @@ -11,6 +11,8 @@ use Magento\ImportExport\Model\Import; use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface; use Magento\ImportExport\Model\Import\AbstractSource; +use Magento\Customer\Model\Indexer\Processor; +use Magento\Framework\App\ObjectManager; /** * Customer entity import @@ -168,6 +170,11 @@ class Customer extends AbstractCustomer 'lock_expires', ]; + /** + * @var Processor + */ + private $indexerProcessor; + /** * @param \Magento\Framework\Stdlib\StringUtils $string * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig @@ -182,6 +189,7 @@ class Customer extends AbstractCustomer * @param \Magento\Customer\Model\ResourceModel\Attribute\CollectionFactory $attrCollectionFactory * @param \Magento\Customer\Model\CustomerFactory $customerFactory * @param array $data + * @param Processor $indexerProcessor * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -197,7 +205,8 @@ public function __construct( \Magento\CustomerImportExport\Model\ResourceModel\Import\Customer\StorageFactory $storageFactory, \Magento\Customer\Model\ResourceModel\Attribute\CollectionFactory $attrCollectionFactory, \Magento\Customer\Model\CustomerFactory $customerFactory, - array $data = [] + array $data = [], + ?Processor $indexerProcessor = null ) { $this->_resourceHelper = $resourceHelper; @@ -254,6 +263,7 @@ public function __construct( /** @var $customerResource \Magento\Customer\Model\ResourceModel\Customer */ $customerResource = $this->_customerModel->getResource(); $this->_entityTable = $customerResource->getEntityTable(); + $this->indexerProcessor = $indexerProcessor ?: ObjectManager::getInstance()->get(Processor::class); } /** @@ -554,7 +564,7 @@ protected function _importData() $this->_deleteCustomerEntities($entitiesToDelete); } } - + $this->indexerProcessor->markIndexerAsInvalid(); return true; } diff --git a/app/code/Magento/CustomerImportExport/Test/Unit/Model/Import/AddressTest.php b/app/code/Magento/CustomerImportExport/Test/Unit/Model/Import/AddressTest.php index 126a9e1791779..3df8b1ae5ef72 100644 --- a/app/code/Magento/CustomerImportExport/Test/Unit/Model/Import/AddressTest.php +++ b/app/code/Magento/CustomerImportExport/Test/Unit/Model/Import/AddressTest.php @@ -298,9 +298,6 @@ protected function _createCustomerEntityMock() public function getWebsites($withDefault = false) { $websites = []; - if (!$withDefault) { - unset($websites[0]); - } foreach ($this->_websites as $id => $code) { if (!$withDefault && $id == \Magento\Store\Model\Store::DEFAULT_STORE_ID) { continue; @@ -330,97 +327,6 @@ public function iterate(\Magento\Framework\Data\Collection $collection, $pageSiz } } - /** - * Create mock for custom behavior test - * - * @return Address|\PHPUnit_Framework_MockObject_MockObject - */ - protected function _getModelMockForTestImportDataWithCustomBehaviour() - { - // input data - $customBehaviorRows = [ - [ - AbstractEntity::COLUMN_ACTION => 'update', - Address::COLUMN_ADDRESS_ID => $this->_customBehaviour['update_id'], - ], - [ - AbstractEntity::COLUMN_ACTION => AbstractEntity::COLUMN_ACTION_VALUE_DELETE, - Address::COLUMN_ADDRESS_ID => $this->_customBehaviour['delete_id'] - ], - ]; - $updateResult = [ - 'entity_row_new' => [], - 'entity_row_update' => $this->_customBehaviour['update_id'], - 'attributes' => [], - 'defaults' => [], - ]; - // entity adapter mock - $modelMock = $this->createPartialMock( - \Magento\CustomerImportExport\Model\Import\Address::class, - [ - 'validateRow', - '_prepareDataForUpdate', - '_saveAddressEntities', - '_saveAddressAttributes', - '_saveCustomerDefaults', - '_deleteAddressEntities', - '_mergeEntityAttributes', - 'getErrorAggregator', - 'getCustomerStorage', - 'prepareCustomerData', - ] - ); - //Adding behaviours - $availableBehaviors = new \ReflectionProperty($modelMock, '_availableBehaviors'); - $availableBehaviors->setAccessible(true); - $availableBehaviors->setValue($modelMock, $this->_availableBehaviors); - // mock to imitate data source model - $dataSourceMock = $this->createPartialMock( - \Magento\ImportExport\Model\ResourceModel\Import\Data::class, - ['getNextBunch', '__wakeup', 'getIterator'] - ); - $dataSourceMock->expects($this->at(0))->method('getNextBunch')->will($this->returnValue($customBehaviorRows)); - $dataSourceMock->expects($this->at(1))->method('getNextBunch')->will($this->returnValue(null)); - $dataSourceMock->expects($this->any()) - ->method('getIterator') - ->willReturn($this->getMockForAbstractClass(\Iterator::class)); - - $dataSourceModel = new \ReflectionProperty( - \Magento\CustomerImportExport\Model\Import\Address::class, - '_dataSourceModel' - ); - $dataSourceModel->setAccessible(true); - $dataSourceModel->setValue($modelMock, $dataSourceMock); - // mock expects for entity adapter - $modelMock->expects($this->any())->method('validateRow')->will($this->returnValue(true)); - $modelMock->expects($this->any()) - ->method('getErrorAggregator') - ->will($this->returnValue($this->errorAggregator)); - $modelMock->expects($this->any())->method('_prepareDataForUpdate')->will($this->returnValue($updateResult)); - $modelMock->expects( - $this->any() - )->method( - '_saveAddressEntities' - )->will( - $this->returnCallback([$this, 'validateSaveAddressEntities']) - ); - $modelMock->expects($this->any())->method('_saveAddressAttributes')->will($this->returnValue($modelMock)); - $modelMock->expects($this->any())->method('_saveCustomerDefaults')->will($this->returnValue($modelMock)); - $modelMock->expects( - $this->any() - )->method( - '_deleteAddressEntities' - )->will( - $this->returnCallback([$this, 'validateDeleteAddressEntities']) - ); - $modelMock->expects($this->any())->method('_mergeEntityAttributes')->will($this->returnValue([])); - $modelMock->expects($this->any()) - ->method('getCustomerStorage') - ->willReturn($this->_createCustomerStorageMock()); - - return $modelMock; - } - /** * Create mock for customer address model class * @@ -449,7 +355,9 @@ protected function _getModelMock() new \Magento\Framework\Stdlib\DateTime(), $this->createMock(\Magento\Customer\Model\Address\Validator\Postcode::class), $this->_getModelDependencies(), - $this->countryWithWebsites + $this->countryWithWebsites, + $this->createMock(\Magento\CustomerImportExport\Model\ResourceModel\Import\Address\Storage::class), + $this->createMock(\Magento\Customer\Model\Indexer\Processor::class) ); $property = new \ReflectionProperty($modelMock, '_availableBehaviors'); @@ -606,20 +514,6 @@ public function testGetDefaultAddressAttributeMapping() ); } - /** - * Test if correct methods are invoked according to different custom behaviours - * - * @covers \Magento\CustomerImportExport\Model\Import\Address::_importData - */ - public function testImportDataWithCustomBehaviour() - { - $this->_model = $this->_getModelMockForTestImportDataWithCustomBehaviour(); - $this->_model->setParameters(['behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_CUSTOM]); - - // validation in validateSaveAddressEntities and validateDeleteAddressEntities - $this->_model->importData(); - } - /** * Validation method for _saveAddressEntities (callback for _saveAddressEntities) * diff --git a/app/code/Magento/CustomerImportExport/Test/Unit/Model/Import/CustomerTest.php b/app/code/Magento/CustomerImportExport/Test/Unit/Model/Import/CustomerTest.php deleted file mode 100644 index 9a7183d5b5f72..0000000000000 --- a/app/code/Magento/CustomerImportExport/Test/Unit/Model/Import/CustomerTest.php +++ /dev/null @@ -1,236 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -/** - * Test class for \Magento\CustomerImportExport\Model\Import\Customer - */ -namespace Magento\CustomerImportExport\Test\Unit\Model\Import; - -use Magento\CustomerImportExport\Model\Import\Customer; -use Magento\CustomerImportExport\Model\ResourceModel\Import\Customer\Storage; - -class CustomerTest extends \PHPUnit\Framework\TestCase -{ - /** - * Customer entity import model - * - * @var Customer|\PHPUnit_Framework_MockObject_MockObject - */ - protected $_model; - - /** - * Available behaviours - * - * @var array - */ - protected $_availableBehaviors = [ - \Magento\ImportExport\Model\Import::BEHAVIOR_ADD_UPDATE, - \Magento\ImportExport\Model\Import::BEHAVIOR_DELETE, - \Magento\ImportExport\Model\Import::BEHAVIOR_CUSTOM, - ]; - - /** - * Custom behavior input rows - * - * @var array - */ - protected $_inputRows = [ - 'create' => [ - Customer::COLUMN_ACTION => 'create', - Customer::COLUMN_EMAIL => 'create@email.com', - Customer::COLUMN_WEBSITE => 'website1', - ], - 'update' => [ - Customer::COLUMN_ACTION => 'update', - Customer::COLUMN_EMAIL => 'update@email.com', - Customer::COLUMN_WEBSITE => 'website1', - ], - 'delete' => [ - Customer::COLUMN_ACTION => Customer::COLUMN_ACTION_VALUE_DELETE, - Customer::COLUMN_EMAIL => 'delete@email.com', - Customer::COLUMN_WEBSITE => 'website1', - ], - ]; - - /** - * Customer ids for all custom behavior input rows - * - * @var array - */ - protected $_customerIds = ['create' => 1, 'update' => 2, 'delete' => 3]; - - /** - * Unset entity adapter model - */ - protected function tearDown() - { - unset($this->_model); - - parent::tearDown(); - } - - /** - * Create mock for import with custom behavior test - * - * @return Customer|\PHPUnit_Framework_MockObject_MockObject - */ - protected function _getModelMockForTestImportDataWithCustomBehaviour() - { - // entity adapter mock - $modelMock = $this->getMockBuilder(\Magento\CustomerImportExport\Model\Import\Customer::class) - ->disableOriginalConstructor() - ->setMethods( - [ - 'validateRow', - '_getCustomerId', - '_prepareDataForUpdate', - '_saveCustomerEntities', - '_saveCustomerAttributes', - '_deleteCustomerEntities', - 'getErrorAggregator', - 'getCustomerStorage', - ] - ) - ->getMock(); - - $errorAggregator = $this->createPartialMock( - \Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregator::class, - ['hasToBeTerminated'] - ); - - $availableBehaviors = new \ReflectionProperty($modelMock, '_availableBehaviors'); - $availableBehaviors->setAccessible(true); - $availableBehaviors->setValue($modelMock, $this->_availableBehaviors); - - // mock to imitate data source model - $dataSourceModelMock = $this->getMockBuilder(\Magento\ImportExport\Model\ResourceModel\Import\Data::class) - ->disableOriginalConstructor() - ->setMethods([ - 'getNextBunch', - '__wakeup', - ]) - ->getMock(); - - $dataSourceModelMock->expects($this->at(0)) - ->method('getNextBunch') - ->will($this->returnValue($this->_inputRows)); - $dataSourceModelMock->expects($this->at(1)) - ->method('getNextBunch') - ->will($this->returnValue(null)); - - $property = new \ReflectionProperty( - \Magento\CustomerImportExport\Model\Import\Customer::class, - '_dataSourceModel' - ); - $property->setAccessible(true); - $property->setValue($modelMock, $dataSourceModelMock); - - $modelMock->expects($this->any()) - ->method('validateRow') - ->will($this->returnValue(true)); - - $modelMock->expects($this->any()) - ->method('_getCustomerId') - ->will($this->returnValue($this->_customerIds['delete'])); - - $modelMock->expects($this->any()) - ->method('_prepareDataForUpdate') - ->will($this->returnCallback([$this, 'prepareForUpdateMock'])); - - $modelMock->expects($this->any()) - ->method('_saveCustomerEntities') - ->will($this->returnCallback([$this, 'validateSaveCustomerEntities'])); - - $modelMock->expects($this->any()) - ->method('_saveCustomerAttributes') - ->will($this->returnValue($modelMock)); - - $modelMock->expects($this->any()) - ->method('_deleteCustomerEntities') - ->will($this->returnCallback([$this, 'validateDeleteCustomerEntities'])); - - $modelMock->expects($this->any()) - ->method('getErrorAggregator') - ->will($this->returnValue($errorAggregator)); - /** @var \PHPUnit_Framework_MockObject_MockObject $storageMock */ - $storageMock = $this->createMock(Storage::class); - $storageMock->expects($this->any())->method('prepareCustomers'); - $modelMock->expects($this->any()) - ->method('getCustomerStorage') - ->willReturn($storageMock); - - return $modelMock; - } - - /** - * Test whether correct methods are invoked in case of custom behaviour for each row in action column - */ - public function testImportDataWithCustomBehaviour() - { - $this->_model = $this->_getModelMockForTestImportDataWithCustomBehaviour(); - $this->_model->setParameters(['behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_CUSTOM]); - - // validation in validateSaveCustomerEntities and validateDeleteCustomerEntities - $this->_model->importData(); - } - - /** - * Emulate data preparing depending on value in row action column - * - * @param array $rowData - * @return int - */ - public function prepareForUpdateMock(array $rowData) - { - $preparedResult = [ - Customer::ENTITIES_TO_CREATE_KEY => [], - Customer::ENTITIES_TO_UPDATE_KEY => [], - Customer::ATTRIBUTES_TO_SAVE_KEY => ['table' => []], - ]; - - $actionColumnKey = Customer::COLUMN_ACTION; - if ($rowData[$actionColumnKey] == 'create') { - $preparedResult[Customer::ENTITIES_TO_CREATE_KEY] = [ - ['entity_id' => $this->_customerIds['create']], - ]; - } elseif ($rowData[$actionColumnKey] == 'update') { - $preparedResult[Customer::ENTITIES_TO_UPDATE_KEY] = [ - ['entity_id' => $this->_customerIds['update']], - ]; - } - - return $preparedResult; - } - - /** - * Validation method for _saveCustomerEntities - * - * @param array $entitiesToCreate - * @param array $entitiesToUpdate - * @return Customer|\PHPUnit_Framework_MockObject_MockObject - */ - public function validateSaveCustomerEntities(array $entitiesToCreate, array $entitiesToUpdate) - { - $this->assertCount(1, $entitiesToCreate); - $this->assertEquals($this->_customerIds['create'], $entitiesToCreate[0]['entity_id']); - $this->assertCount(1, $entitiesToUpdate); - $this->assertEquals($this->_customerIds['update'], $entitiesToUpdate[0]['entity_id']); - return $this->_model; - } - - /** - * Validation method for _deleteCustomerEntities - * - * @param array $customerIdsToDelete - * @return Customer|\PHPUnit_Framework_MockObject_MockObject - */ - public function validateDeleteCustomerEntities(array $customerIdsToDelete) - { - $this->assertCount(1, $customerIdsToDelete); - $this->assertEquals($this->_customerIds['delete'], $customerIdsToDelete[0]); - return $this->_model; - } -} diff --git a/app/code/Magento/Developer/etc/adminhtml/system.xml b/app/code/Magento/Developer/etc/adminhtml/system.xml index 10449ab428726..812776ba1da28 100644 --- a/app/code/Magento/Developer/etc/adminhtml/system.xml +++ b/app/code/Magento/Developer/etc/adminhtml/system.xml @@ -11,7 +11,7 @@ <label>Frontend Development Workflow</label> <field id="type" translate="label comment" type="select" sortOrder="1" showInDefault="1" canRestore="1"> <label>Workflow type</label> - <comment>Not available in production mode.</comment> + <comment>Modifying this configuration has no effect in production mode.</comment> <source_model>Magento\Developer\Model\Config\Source\WorkflowType</source_model> <frontend_model>Magento\Developer\Block\Adminhtml\System\Config\WorkflowType</frontend_model> <backend_model>Magento\Developer\Model\Config\Backend\WorkflowType</backend_model> diff --git a/app/code/Magento/Developer/i18n/en_US.csv b/app/code/Magento/Developer/i18n/en_US.csv index 59e67445b2b58..649779cdf853d 100644 --- a/app/code/Magento/Developer/i18n/en_US.csv +++ b/app/code/Magento/Developer/i18n/en_US.csv @@ -9,4 +9,4 @@ "Allowed IPs (comma separated)","Allowed IPs (comma separated)" "Leave empty for access from any location.","Leave empty for access from any location." "Log to File","Log to File" -"Not available in production mode.","Not available in production mode." +"Modifying this configuration has no effect in production mode.","Modifying this configuration has no effect in production mode." diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateAndEditDownloadableProductSettingsTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateAndEditDownloadableProductSettingsTest.xml index a09f205b8c4d0..8a0c6d478ecfc 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateAndEditDownloadableProductSettingsTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateAndEditDownloadableProductSettingsTest.xml @@ -97,7 +97,7 @@ <!-- Verify Url Key after changing --> <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductPage"> - <argument name="productUrl" value="{{ApiDownloadableProduct.name}}"/> + <argument name="productUrl" value="{{ApiDownloadableProduct.urlKey}}"/> </actionGroup> <!-- Assert product design settings "left bar is present at product page with 2 columns" --> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithTierPriceText.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithTierPriceTest.xml similarity index 96% rename from app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithTierPriceText.xml rename to app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithTierPriceTest.xml index ca4e560506ad0..131193e7743e2 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithTierPriceText.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithTierPriceTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminCreateDownloadableProductWithTierPriceText" extends="AdminCreateDownloadableProductWithGroupPriceTest"> + <test name="AdminCreateDownloadableProductWithTierPriceTest" extends="AdminCreateDownloadableProductWithGroupPriceTest"> <annotations> <features value="Catalog"/> <stories value="Create Downloadable Product"/> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontVerifySecureURLRedirectDownloadableTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontVerifySecureURLRedirectDownloadableTest.xml index 6e039ca413a08..d7e0ce3b2ca22 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontVerifySecureURLRedirectDownloadableTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontVerifySecureURLRedirectDownloadableTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontVerifySecureURLRedirectDownloadable"> + <test name="StorefrontVerifySecureURLRedirectDownloadableTest"> <annotations> <features value="Downloadable"/> <stories value="Storefront Secure URLs"/> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminGroupedProductsListTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminGroupedProductsListTest.xml index 151a987ea89cc..7657c9a86a62b 100644 --- a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminGroupedProductsListTest.xml +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminGroupedProductsListTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminGroupedProductsAreListedWhenOutOfStock"> + <test name="AdminGroupedProductsAreListedWhenOutOfStockTest"> <annotations> <features value="GroupedProduct"/> <stories value="MAGETWO-93181: Grouped product doesn't take care about his Linked Products when SalableQuantity < ProductLink.ExtensionAttributes.Qty after Source Deduction"/> diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Delete.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Delete.php index 1e9d194653c9c..1b7fdc2881073 100644 --- a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Delete.php +++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Delete.php @@ -9,9 +9,7 @@ use Magento\Backend\App\Action; use Magento\Framework\App\Action\HttpPostActionInterface; -use Magento\Framework\Controller\ResultFactory; use Magento\Framework\Exception\FileSystemException; -use Magento\Framework\Exception\LocalizedException; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\ImportExport\Controller\Adminhtml\Export as ExportController; use Magento\Framework\Filesystem; @@ -56,29 +54,33 @@ public function __construct( /** * Controller basic method implementation. * - * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface - * @throws LocalizedException + * @return \Magento\Framework\Controller\ResultInterface */ public function execute() { + /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */ + $resultRedirect = $this->resultRedirectFactory->create(); + $resultRedirect->setPath('adminhtml/export/index'); + $fileName = $this->getRequest()->getParam('filename'); + if (empty($fileName) || preg_match('/\.\.(\\\|\/)/', $fileName) !== 0) { + $this->messageManager->addErrorMessage(__('Please provide valid export file name')); + + return $resultRedirect; + } try { - if (empty($fileName = $this->getRequest()->getParam('filename'))) { - throw new LocalizedException(__('Please provide export file name')); - } $directory = $this->filesystem->getDirectoryRead(DirectoryList::VAR_DIR); $path = $directory->getAbsolutePath() . 'export/' . $fileName; - if (!$directory->isFile($path)) { - throw new LocalizedException(__('Sorry, but the data is invalid or the file is not uploaded.')); + if ($directory->isFile($path)) { + $this->file->deleteFile($path); + $this->messageManager->addSuccessMessage(__('File %1 deleted', $fileName)); + } else { + $this->messageManager->addErrorMessage(__('%1 is not a valid file', $fileName)); } - - $this->file->deleteFile($path); - /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ - $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); - $resultRedirect->setPath('adminhtml/export/index'); - return $resultRedirect; } catch (FileSystemException $exception) { - throw new LocalizedException(__('There are no export file with such name %1', $fileName)); + $this->messageManager->addErrorMessage($exception->getMessage()); } + + return $resultRedirect; } } diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Download.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Download.php index 8dbd9a0ae44ba..4107e19860328 100644 --- a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Download.php +++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Download.php @@ -10,7 +10,6 @@ use Magento\Backend\App\Action; use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\App\Response\Http\FileFactory; -use Magento\Framework\Exception\LocalizedException; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\ImportExport\Controller\Adminhtml\Export as ExportController; use Magento\Framework\Filesystem; @@ -55,12 +54,17 @@ public function __construct( * Controller basic method implementation. * * @return \Magento\Framework\App\ResponseInterface - * @throws LocalizedException */ public function execute() { - if (empty($fileName = $this->getRequest()->getParam('filename'))) { - throw new LocalizedException(__('Please provide export file name')); + /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */ + $resultRedirect = $this->resultRedirectFactory->create(); + $resultRedirect->setPath('adminhtml/export/index'); + $fileName = $this->getRequest()->getParam('filename'); + if (empty($fileName) || preg_match('/\.\.(\\\|\/)/', $fileName) !== 0) { + $this->messageManager->addErrorMessage(__('Please provide valid export file name')); + + return $resultRedirect; } try { $path = 'export/' . $fileName; @@ -72,8 +76,11 @@ public function execute() DirectoryList::VAR_DIR ); } - } catch (LocalizedException | \Exception $exception) { - throw new LocalizedException(__('There are no export file with such name %1', $fileName)); + $this->messageManager->addErrorMessage(__('%1 is not a valid file', $fileName)); + } catch (\Exception $exception) { + $this->messageManager->addErrorMessage($exception->getMessage()); } + + return $resultRedirect; } } diff --git a/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DeleteTest.php b/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DeleteTest.php new file mode 100644 index 0000000000000..ef40651f95be9 --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DeleteTest.php @@ -0,0 +1,198 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ImportExport\Test\Unit\Controller\Adminhtml\Export\File; + +use Magento\Backend\App\Action\Context; +use Magento\Backend\Model\View\Result\Redirect; +use Magento\Framework\App\Request\Http; +use Magento\Framework\Controller\Result\Raw; +use Magento\Framework\Controller\Result\RedirectFactory; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\ReadInterface; +use Magento\Framework\Filesystem\DriverInterface; +use Magento\Framework\Message\ManagerInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\ImportExport\Controller\Adminhtml\Export\File\Delete; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class DeleteTest extends TestCase +{ + /** + * @var Context|MockObject + */ + private $contextMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var Http|MockObject + */ + private $requestMock; + + /** + * @var Raw|MockObject + */ + private $redirectMock; + + /** + * @var RedirectFactory|MockObject + */ + private $resultRedirectFactoryMock; + + /** + * @var Filesystem|MockObject + */ + private $fileSystemMock; + + /** + * @var DriverInterface|MockObject + */ + private $fileMock; + + /** + * @var Delete|MockObject + */ + private $deleteControllerMock; + + /** + * @var ManagerInterface|MockObject + */ + private $messageManagerMock; + + /** + * @var ReadInterface|MockObject + */ + private $directoryMock; + + /** + * Set up + */ + protected function setUp() + { + $this->requestMock = $this->getMockBuilder(Http::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->fileSystemMock = $this->getMockBuilder(Filesystem::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->directoryMock = $this->getMockBuilder(ReadInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->fileMock = $this->getMockBuilder(DriverInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->messageManagerMock = $this->getMockBuilder(ManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->contextMock = $this->createPartialMock( + Context::class, + ['getRequest', 'getResultRedirectFactory', 'getMessageManager'] + ); + + $this->redirectMock = $this->createPartialMock(Redirect::class, ['setPath']); + + $this->resultRedirectFactoryMock = $this->createPartialMock( + RedirectFactory::class, + ['create'] + ); + $this->resultRedirectFactoryMock->expects($this->any())->method('create')->willReturn($this->redirectMock); + $this->contextMock->expects($this->any())->method('getRequest')->willReturn($this->requestMock); + $this->contextMock->expects($this->any()) + ->method('getResultRedirectFactory') + ->willReturn($this->resultRedirectFactoryMock); + + $this->contextMock->expects($this->any()) + ->method('getMessageManager') + ->willReturn($this->messageManagerMock); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->deleteControllerMock = $this->objectManagerHelper->getObject( + Delete::class, + [ + 'context' => $this->contextMock, + 'filesystem' => $this->fileSystemMock, + 'file' => $this->fileMock + ] + ); + } + + /** + * Tests download controller with different file names in request. + */ + public function testExecuteSuccess() + { + $this->requestMock->method('getParam') + ->with('filename') + ->willReturn('sampleFile'); + + $this->fileSystemMock->expects($this->once()) + ->method('getDirectoryRead') + ->will($this->returnValue($this->directoryMock)); + $this->directoryMock->expects($this->once())->method('isFile')->willReturn(true); + $this->fileMock->expects($this->once())->method('deleteFile')->willReturn(true); + $this->messageManagerMock->expects($this->once())->method('addSuccessMessage'); + + $this->deleteControllerMock->execute(); + } + + /** + * Tests download controller with different file names in request. + */ + public function testExecuteFileDoesntExists() + { + $this->requestMock->method('getParam') + ->with('filename') + ->willReturn('sampleFile'); + + $this->fileSystemMock->expects($this->once()) + ->method('getDirectoryRead') + ->will($this->returnValue($this->directoryMock)); + $this->directoryMock->expects($this->once())->method('isFile')->willReturn(false); + $this->messageManagerMock->expects($this->once())->method('addErrorMessage'); + + $this->deleteControllerMock->execute(); + } + + /** + * Test execute() with invalid file name + * @param string $requestFilename + * @dataProvider invalidFileDataProvider + */ + public function testExecuteInvalidFileName($requestFilename) + { + $this->requestMock->method('getParam')->with('filename')->willReturn($requestFilename); + $this->messageManagerMock->expects($this->once())->method('addErrorMessage'); + + $this->deleteControllerMock->execute(); + } + + /** + * Data provider to test possible invalid filenames + * @return array + */ + public function invalidFileDataProvider() + { + return [ + 'Relative file name' => ['../.htaccess'], + 'Empty file name' => [''], + 'Null file name' => [null], + ]; + } +} diff --git a/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DownloadTest.php b/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DownloadTest.php new file mode 100644 index 0000000000000..4512aa6365ca5 --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DownloadTest.php @@ -0,0 +1,206 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ImportExport\Test\Unit\Controller\Adminhtml\Export\File; + +use Magento\Backend\App\Action\Context; +use Magento\Backend\Model\View\Result\Redirect; +use Magento\Framework\App\Request\Http; +use Magento\Framework\App\Response\Http\FileFactory; +use Magento\Framework\Controller\Result\Raw; +use Magento\Framework\Controller\Result\RedirectFactory; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\ReadInterface; +use Magento\Framework\Message\ManagerInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\ImportExport\Controller\Adminhtml\Export\File\Download; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class DownloadTest extends TestCase +{ + /** + * @var Context|MockObject + */ + private $contextMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var Http|MockObject + */ + private $requestMock; + + /** + * @var Raw|MockObject + */ + private $redirectMock; + + /** + * @var RedirectFactory|MockObject + */ + private $resultRedirectFactoryMock; + + /** + * @var Filesystem|MockObject + */ + private $fileSystemMock; + + /** + * @var FileFactory|MockObject + */ + private $fileFactoryMock; + + /** + * @var Download|MockObject + */ + private $downloadControllerMock; + + /** + * @var ManagerInterface|MockObject + */ + private $messageManagerMock; + + /** + * @var ReadInterface|MockObject + */ + private $directoryMock; + + /** + * Set up + */ + protected function setUp() + { + $this->requestMock = $this->getMockBuilder(Http::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->fileSystemMock = $this->getMockBuilder(Filesystem::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->directoryMock = $this->getMockBuilder(ReadInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->fileFactoryMock = $this->getMockBuilder(FileFactory::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->messageManagerMock = $this->getMockBuilder(ManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->contextMock = $this->createPartialMock( + Context::class, + ['getRequest', 'getResultRedirectFactory', 'getMessageManager'] + ); + + $this->redirectMock = $this->createPartialMock( + Redirect::class, + ['setPath'] + ); + + $this->resultRedirectFactoryMock = $this->createPartialMock( + RedirectFactory::class, + ['create'] + ); + $this->resultRedirectFactoryMock->expects($this->any()) + ->method('create') + ->willReturn($this->redirectMock); + + $this->contextMock->expects($this->any()) + ->method('getRequest') + ->willReturn($this->requestMock); + + $this->contextMock->expects($this->any()) + ->method('getResultRedirectFactory') + ->willReturn($this->resultRedirectFactoryMock); + + $this->contextMock->expects($this->any()) + ->method('getMessageManager') + ->willReturn($this->messageManagerMock); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->downloadControllerMock = $this->objectManagerHelper->getObject( + Download::class, + [ + 'context' => $this->contextMock, + 'filesystem' => $this->fileSystemMock, + 'fileFactory' => $this->fileFactoryMock + ] + ); + } + + /** + * Tests download controller with successful file downloads + */ + public function testExecuteSuccess() + { + $this->requestMock->method('getParam') + ->with('filename') + ->willReturn('sampleFile.csv'); + + $this->fileSystemMock->expects($this->once()) + ->method('getDirectoryRead') + ->will($this->returnValue($this->directoryMock)); + $this->directoryMock->expects($this->once())->method('isFile')->willReturn(true); + $this->fileFactoryMock->expects($this->once())->method('create'); + + $this->downloadControllerMock->execute(); + } + + /** + * Tests download controller with file that doesn't exist + */ + public function testExecuteFileDoesntExists() + { + $this->requestMock->method('getParam') + ->with('filename') + ->willReturn('sampleFile'); + + $this->fileSystemMock->expects($this->once()) + ->method('getDirectoryRead') + ->will($this->returnValue($this->directoryMock)); + $this->directoryMock->expects($this->once())->method('isFile')->willReturn(false); + $this->messageManagerMock->expects($this->once())->method('addErrorMessage'); + + $this->downloadControllerMock->execute(); + } + + /** + * Test execute() with invalid file name + * @param ?string $requestFilename + * @dataProvider invalidFileDataProvider + */ + public function testExecuteInvalidFileName($requestFilename) + { + $this->requestMock->method('getParam')->with('filename')->willReturn($requestFilename); + $this->messageManagerMock->expects($this->once())->method('addErrorMessage'); + + $this->downloadControllerMock->execute(); + } + + /** + * Data provider to test possible invalid filenames + * @return array + */ + public function invalidFileDataProvider() + { + return [ + 'Relative file name' => ['../.htaccess'], + 'Empty file name' => [''], + 'Null file name' => [null], + ]; + } +} diff --git a/app/code/Magento/ImportExport/i18n/en_US.csv b/app/code/Magento/ImportExport/i18n/en_US.csv index fae93c78baa09..a4943fe72826f 100644 --- a/app/code/Magento/ImportExport/i18n/en_US.csv +++ b/app/code/Magento/ImportExport/i18n/en_US.csv @@ -124,3 +124,6 @@ Summary,Summary "Message is added to queue, wait to get your file soon. Make sure your cron job is running to export the file","Message is added to queue, wait to get your file soon. Make sure your cron job is running to export the file" "Invalid data","Invalid data" "Invalid response","Invalid response" +"File %1 deleted","File %1 deleted" +"Please provide valid export file name","Please provide valid export file name" +"%1 is not a valid file","%1 is not a valid file" diff --git a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php new file mode 100644 index 0000000000000..eba7591c17286 --- /dev/null +++ b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php @@ -0,0 +1,106 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Indexer\Block\Backend\Grid\Column\Renderer; + +use Magento\Backend\Block\Context; +use Magento\Backend\Block\Widget\Grid\Column\Renderer\AbstractRenderer; +use Magento\Framework\DataObject; +use Magento\Framework\Escaper; +use Magento\Framework\Phrase; +use Magento\Indexer\Model\IndexerFactory; + +/** + * Renderer for 'Schedule Status' column in indexer grid + */ +class ScheduleStatus extends AbstractRenderer +{ + /** + * @var Escaper + */ + private $escaper; + + /** + * @var IndexerFactory + */ + private $indexerFactory; + + /** + * @param Context $context + * @param Escaper $escaper + * @param IndexerFactory $indexerFactory + * @param array $data + */ + public function __construct( + Context $context, + Escaper $escaper, + IndexerFactory $indexerFactory, + array $data = [] + ) { + parent::__construct($context, $data); + $this->escaper = $escaper; + $this->indexerFactory = $indexerFactory; + } + + /** + * Render indexer status + * + * @param DataObject $row + * @return string + */ + public function render(DataObject $row) + { + try { + if (!$row->getIsScheduled()) { + return ''; + } + + try { + $indexer = $this->indexerFactory->create(); + $indexer->load($row->getIndexerId()); + $view = $indexer->getView(); + } catch (\InvalidArgumentException $exception) { + // No view for this index. + return ''; + } + + $state = $view->getState()->loadByView($view->getId()); + $changelog = $view->getChangelog()->setViewId($view->getId()); + $currentVersionId = $changelog->getVersion(); + $count = count($changelog->getList($state->getVersionId(), $currentVersionId)); + + if ($count > 1000) { + $class = 'grid-severity-critical'; + } elseif ($count > 100) { + $class = 'grid-severity-major'; + } elseif ($count > 10) { + $class = 'grid-severity-minor'; + } else { + $class = 'grid-severity-notice'; + } + + if ($state->getStatus() !== $state::STATUS_IDLE) { + $class = 'grid-severity-minor'; + } + + $text = new Phrase( + "%status (%count in backlog)", + [ + 'status' => $state->getStatus(), + 'count' => $count, + ] + ); + + return '<span class="' . $class . '"><span>' . $text . '</span></span>'; + } catch (\Exception $exception) { + return '<span class="grid-severity-minor"><span>' . + $this->escaper->escapeHtml( + get_class($exception) . ': ' . $exception->getMessage() + ) . '</span></span>'; + } + } +} diff --git a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Scheduled.php b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Scheduled.php index d5a749270a66f..adfe3dd5b346b 100644 --- a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Scheduled.php +++ b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Scheduled.php @@ -5,6 +5,9 @@ */ namespace Magento\Indexer\Block\Backend\Grid\Column\Renderer; +/** + * Renderer for 'Scheduled' column in indexer grid + */ class Scheduled extends \Magento\Backend\Block\Widget\Grid\Column\Renderer\AbstractRenderer { /** diff --git a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Status.php b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Status.php index d8a017ef5a4b4..e8d6004b2727d 100644 --- a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Status.php +++ b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Status.php @@ -5,6 +5,9 @@ */ namespace Magento\Indexer\Block\Backend\Grid\Column\Renderer; +/** + * Renderer for 'Status' column in indexer grid + */ class Status extends \Magento\Backend\Block\Widget\Grid\Column\Renderer\AbstractRenderer { /** @@ -27,7 +30,7 @@ public function render(\Magento\Framework\DataObject $row) $text = __('Ready'); break; case \Magento\Framework\Indexer\StateInterface::STATUS_WORKING: - $class = 'grid-severity-major'; + $class = 'grid-severity-minor'; $text = __('Processing'); break; } diff --git a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Updated.php b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Updated.php index 92ded9c1f0dc1..6c84ef0e628f7 100644 --- a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Updated.php +++ b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Updated.php @@ -5,6 +5,9 @@ */ namespace Magento\Indexer\Block\Backend\Grid\Column\Renderer; +/** + * Renderer for 'Updated' column in indexer grid + */ class Updated extends \Magento\Backend\Block\Widget\Grid\Column\Renderer\Datetime { /** diff --git a/app/code/Magento/Indexer/Test/Mftf/Data/AdminIndexManagementGridData.xml b/app/code/Magento/Indexer/Test/Mftf/Data/AdminIndexManagementGridData.xml new file mode 100644 index 0000000000000..74494ee6b469c --- /dev/null +++ b/app/code/Magento/Indexer/Test/Mftf/Data/AdminIndexManagementGridData.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminIndexManagementGridData"> + <data key="rowProductPrice">Product Price</data> + </entity> +</entities> diff --git a/app/code/Magento/Indexer/Test/Mftf/Section/AdminIndexManagementSection.xml b/app/code/Magento/Indexer/Test/Mftf/Section/AdminIndexManagementSection.xml index 020cd9654e36b..825358e74f2af 100644 --- a/app/code/Magento/Indexer/Test/Mftf/Section/AdminIndexManagementSection.xml +++ b/app/code/Magento/Indexer/Test/Mftf/Section/AdminIndexManagementSection.xml @@ -17,5 +17,7 @@ <element name="indexerStatus" type="text" selector="//tr[descendant::td[contains(., '{{status}}')]]//*[contains(@class, 'col-indexer_status')]/span" parameterized="true"/> <element name="successMessage" type="text" selector="//*[@data-ui-id='messages-message-success']" timeout="120"/> <element name="selectMassAction" type="select" selector="#gridIndexer_massaction-mass-select"/> + <element name="columnScheduleStatus" type="text" selector="//th[contains(@class, 'col-indexer_schedule_status')]"/> + <element name="indexerScheduleStatus" type="text" selector="//tr[contains(.,'{{var1}}')]//td[contains(@class,'col-indexer_schedule_status')]" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Indexer/Test/Mftf/Test/AdminSystemIndexManagementGridChangesTest.xml b/app/code/Magento/Indexer/Test/Mftf/Test/AdminSystemIndexManagementGridChangesTest.xml new file mode 100644 index 0000000000000..75b040a623451 --- /dev/null +++ b/app/code/Magento/Indexer/Test/Mftf/Test/AdminSystemIndexManagementGridChangesTest.xml @@ -0,0 +1,63 @@ +<?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="AdminSystemIndexManagementGridChangesTest"> + <annotations> + <features value="Indexer"/> + <stories value="Menu Navigation"/> + <title value="Admin system index management grid change test"/> + <description value="Verify changes in 'Schedule column' on system index management"/> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!--Open Index Management Page and Select Index mode "Update by Schedule" --> + <magentoCLI command="indexer:set-mode" arguments="schedule" stepKey="setIndexerModeSchedule"/> + <magentoCLI command="indexer:reindex" stepKey="indexerReindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/></before> + <after> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <magentoCLI command="indexer:set-mode" arguments="realtime" stepKey="setIndexerModeRealTime"/> + <magentoCLI command="indexer:reindex" stepKey="indexerReindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToIndexManagementPageFirst"> + <argument name="menuUiId" value="{{AdminMenuSystem.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuSystemToolsIndexManagement.dataUiId}}"/> + </actionGroup> + <grabTextFrom selector="{{AdminIndexManagementSection.indexerScheduleStatus(AdminIndexManagementGridData.rowProductPrice)}}" stepKey="gradScheduleStatusBeforeChange"/> + + <!-- Verify 'Schedule status' column is present --> + <seeElement selector="{{AdminIndexManagementSection.columnScheduleStatus}}" stepKey="seeScheduleStatusColumn"/> + + <!--Adding Special price to product--> + <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="openAdminProductEditPage"/> + <actionGroup ref="AddSpecialPriceToProductActionGroup" stepKey="addSpecialPrice"/> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProductForm"/> + + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToIndexManagementPageSecond"> + <argument name="menuUiId" value="{{AdminMenuSystem.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuSystemToolsIndexManagement.dataUiId}}"/> + </actionGroup> + <grabTextFrom selector="{{AdminIndexManagementSection.indexerScheduleStatus(AdminIndexManagementGridData.rowProductPrice)}}" stepKey="gradScheduleStatusAfterChange"/> + + <!-- Verify 'Schedule Status' column changes for 'Product Price' --> + <assertNotEquals stepKey="assertChange"> + <expectedResult type="string">$gradScheduleStatusBeforeChange</expectedResult> + <actualResult type="string">$gradScheduleStatusAfterChange</actualResult> + </assertNotEquals> + </test> +</tests> diff --git a/app/code/Magento/Indexer/Test/Unit/Block/Backend/Grid/Column/Renderer/StatusTest.php b/app/code/Magento/Indexer/Test/Unit/Block/Backend/Grid/Column/Renderer/StatusTest.php index 705a9e3998ae8..7249f764a66a1 100644 --- a/app/code/Magento/Indexer/Test/Unit/Block/Backend/Grid/Column/Renderer/StatusTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Block/Backend/Grid/Column/Renderer/StatusTest.php @@ -49,7 +49,7 @@ public function renderDataProvider() ], 'set3' => [ [\Magento\Framework\Indexer\StateInterface::STATUS_WORKING], - ['class' => 'grid-severity-major', 'text' => 'Processing'] + ['class' => 'grid-severity-minor', 'text' => 'Processing'] ] ]; } diff --git a/app/code/Magento/Indexer/view/adminhtml/layout/indexer_indexer_list_grid.xml b/app/code/Magento/Indexer/view/adminhtml/layout/indexer_indexer_list_grid.xml index 501baa47e4938..b5f5a41bf18aa 100644 --- a/app/code/Magento/Indexer/view/adminhtml/layout/indexer_indexer_list_grid.xml +++ b/app/code/Magento/Indexer/view/adminhtml/layout/indexer_indexer_list_grid.xml @@ -81,6 +81,15 @@ <argument name="column_css_class" xsi:type="string">indexer-status</argument> </arguments> </block> + <block class="Magento\Backend\Block\Widget\Grid\Column" name="adminhtml.indexer.grid.columnSet.indexer_schedule_status" as="indexer_schedule_status"> + <arguments> + <argument name="header" xsi:type="string" translate="true">Schedule Status</argument> + <argument name="index" xsi:type="string">schedule_status</argument> + <argument name="renderer" xsi:type="string">Magento\Indexer\Block\Backend\Grid\Column\Renderer\ScheduleStatus</argument> + <argument name="sortable" xsi:type="string">0</argument> + <argument name="column_css_class" xsi:type="string">indexer-schedule-status</argument> + </arguments> + </block> <block class="Magento\Backend\Block\Widget\Grid\Column" name="adminhtml.indexer.grid.columnSet.indexer_updated" as="indexer_updated"> <arguments> <argument name="header" xsi:type="string" translate="true">Updated</argument> diff --git a/app/code/Magento/LayeredNavigation/Test/Mftf/Test/ShopByButtonInMobile.xml b/app/code/Magento/LayeredNavigation/Test/Mftf/Test/ShopByButtonInMobileTest.xml similarity index 99% rename from app/code/Magento/LayeredNavigation/Test/Mftf/Test/ShopByButtonInMobile.xml rename to app/code/Magento/LayeredNavigation/Test/Mftf/Test/ShopByButtonInMobileTest.xml index 76a9cc8f920a1..0e0eb352c8d33 100644 --- a/app/code/Magento/LayeredNavigation/Test/Mftf/Test/ShopByButtonInMobile.xml +++ b/app/code/Magento/LayeredNavigation/Test/Mftf/Test/ShopByButtonInMobileTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="ShopByButtonInMobile"> + <test name="ShopByButtonInMobileTest"> <annotations> <features value="Layered Navigation"/> <stories value="Storefront Shop By collapsible button in mobile themes"/> diff --git a/app/code/Magento/Msrp/Test/Mftf/Data/CustomAttributeData.xml b/app/code/Magento/Msrp/Test/Mftf/Data/CustomAttributeData.xml new file mode 100644 index 0000000000000..6f620c8f9aa7f --- /dev/null +++ b/app/code/Magento/Msrp/Test/Mftf/Data/CustomAttributeData.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="ApiProductMsrp" type="custom_attribute"> + <data key="attribute_code">msrp</data> + <data key="value">111.11</data> + </entity> +</entities> diff --git a/app/code/Magento/Msrp/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Msrp/Test/Mftf/Data/ProductData.xml new file mode 100644 index 0000000000000..aa79b6032df4b --- /dev/null +++ b/app/code/Magento/Msrp/Test/Mftf/Data/ProductData.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="SimpleOutOfStockProductWithSpecialPriceCostAndMsrp" type="product" extends="SimpleOutOfStockProduct"> + <requiredEntity type="custom_attribute_array">ApiProductSpecialPrice</requiredEntity> + <requiredEntity type="custom_attribute_array">ApiProductCost</requiredEntity> + <requiredEntity type="custom_attribute_array">ApiProductMsrp</requiredEntity> + </entity> +</entities> diff --git a/app/code/Magento/Msrp/Test/Mftf/Test/AdminCheckProductListPriceAttributesTest.xml b/app/code/Magento/Msrp/Test/Mftf/Test/AdminCheckProductListPriceAttributesTest.xml new file mode 100644 index 0000000000000..874edf0dff9e3 --- /dev/null +++ b/app/code/Magento/Msrp/Test/Mftf/Test/AdminCheckProductListPriceAttributesTest.xml @@ -0,0 +1,29 @@ +<?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="AdminCheckProductListPriceAttributesTest"> + <annotations> + <group value="msrp"/> + </annotations> + <before> + <createData entity="SimpleOutOfStockProductWithSpecialPriceCostAndMsrp" stepKey="createSimpleProduct"/> + </before> + + <actionGroup ref="CheckAdminProductGridColumnOptionActionGroup" stepKey="checkMsrpOption" after="checkCostOption"> + <argument name="optionName" value="Minimum Advertised Price"/> + </actionGroup> + + <actionGroup ref="AssertAdminProductGridCellActionGroup" stepKey="seeCorrectMsrp" after="seeCorrectCost"> + <argument name="row" value="1"/> + <argument name="column" value="Minimum Advertised Price"/> + <argument name="value" value="${{ApiProductMsrp.value}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Msrp/etc/adminhtml/di.xml b/app/code/Magento/Msrp/etc/adminhtml/di.xml index 6126e9132cd7a..d517bd0aa4e7f 100644 --- a/app/code/Magento/Msrp/etc/adminhtml/di.xml +++ b/app/code/Magento/Msrp/etc/adminhtml/di.xml @@ -19,4 +19,11 @@ </argument> </arguments> </virtualType> + <type name="Magento\Catalog\Ui\DataProvider\Product\Modifier\PriceAttributes"> + <arguments> + <argument name="priceAttributeList" xsi:type="array"> + <item name="msrp" xsi:type="string">msrp</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontVerifySecureURLRedirectMultishippingTest.xml b/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontVerifySecureURLRedirectMultishippingTest.xml index 085a710f2671c..e65747f4d63d0 100644 --- a/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontVerifySecureURLRedirectMultishippingTest.xml +++ b/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontVerifySecureURLRedirectMultishippingTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontVerifySecureURLRedirectMultishipping"> + <test name="StorefrontVerifySecureURLRedirectMultishippingTest"> <!--todo MC-5858: some urls don't redirect to https--> <annotations> <features value="Multishipping"/> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/StorefrontVerifySecureURLRedirectNewsletterTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/StorefrontVerifySecureURLRedirectNewsletterTest.xml index 01b5e706fcefb..c38725f263525 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/Test/StorefrontVerifySecureURLRedirectNewsletterTest.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/Test/StorefrontVerifySecureURLRedirectNewsletterTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontVerifySecureURLRedirectNewsletter"> + <test name="StorefrontVerifySecureURLRedirectNewsletterTest"> <annotations> <features value="Newsletter"/> <stories value="Storefront Secure URLs"/> diff --git a/app/code/Magento/PageCache/etc/varnish4.vcl b/app/code/Magento/PageCache/etc/varnish4.vcl index 6de6b4e917044..f5e25ce36e973 100644 --- a/app/code/Magento/PageCache/etc/varnish4.vcl +++ b/app/code/Magento/PageCache/etc/varnish4.vcl @@ -108,6 +108,11 @@ sub vcl_recv { #unset req.http.Cookie; } + # Authenticated GraphQL requests should not be cached by default + if (req.url ~ "/graphql" && req.http.Authorization ~ "^Bearer") { + return (pass); + } + return (hash); } diff --git a/app/code/Magento/PageCache/etc/varnish5.vcl b/app/code/Magento/PageCache/etc/varnish5.vcl index 4505e74629714..92bb3394486fc 100644 --- a/app/code/Magento/PageCache/etc/varnish5.vcl +++ b/app/code/Magento/PageCache/etc/varnish5.vcl @@ -109,6 +109,11 @@ sub vcl_recv { #unset req.http.Cookie; } + # Authenticated GraphQL requests should not be cached by default + if (req.url ~ "/graphql" && req.http.Authorization ~ "^Bearer") { + return (pass); + } + return (hash); } diff --git a/app/code/Magento/PageCache/etc/varnish6.vcl b/app/code/Magento/PageCache/etc/varnish6.vcl index b43c8a77bca74..eef5e99862538 100644 --- a/app/code/Magento/PageCache/etc/varnish6.vcl +++ b/app/code/Magento/PageCache/etc/varnish6.vcl @@ -109,6 +109,11 @@ sub vcl_recv { #unset req.http.Cookie; } + # Authenticated GraphQL requests should not be cached by default + if (req.url ~ "/graphql" && req.http.Authorization ~ "^Bearer") { + return (pass); + } + return (hash); } diff --git a/app/code/Magento/Payment/Block/Transparent/Redirect.php b/app/code/Magento/Payment/Block/Transparent/Redirect.php new file mode 100644 index 0000000000000..1be6dec4cc1d8 --- /dev/null +++ b/app/code/Magento/Payment/Block/Transparent/Redirect.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Payment\Block\Transparent; + +use Magento\Framework\UrlInterface; +use Magento\Framework\View\Element\Template; +use Magento\Framework\View\Element\Template\Context; + +/** + * Redirect block for register specific params in layout + * + * @api + */ +class Redirect extends Template +{ + /** + * Route path key to make redirect url. + */ + private const ROUTE_PATH = 'route_path'; + + /** + * @var UrlInterface + */ + private $url; + + /** + * @param Context $context + * @param UrlInterface $url + * @param array $data + */ + public function __construct( + Context $context, + UrlInterface $url, + array $data = [] + ) { + $this->url = $url; + parent::__construct($context, $data); + } + + /** + * Returns url for redirect. + * + * @return string + */ + public function getRedirectUrl(): string + { + return $this->url->getUrl($this->getData(self::ROUTE_PATH)); + } + + /** + * Returns params to be redirected. + * + * @return array + */ + public function getPostParams(): array + { + return (array)$this->_request->getPostValue(); + } +} diff --git a/app/code/Magento/Payment/view/adminhtml/templates/transparent/redirect.phtml b/app/code/Magento/Payment/view/adminhtml/templates/transparent/redirect.phtml new file mode 100644 index 0000000000000..17fbdf780a40a --- /dev/null +++ b/app/code/Magento/Payment/view/adminhtml/templates/transparent/redirect.phtml @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** @var \Magento\Payment\Block\Transparent\Redirect $block */ +$params = $block->getPostParams(); +$redirectUrl = $block->getRedirectUrl(); +?> +<html> +<head></head> +<body onload="document.forms['proxy_form'].submit()"> +<form id="proxy_form" action="<?= $block->escapeUrl($redirectUrl) ?>" + method="POST" hidden enctype="application/x-www-form-urlencoded" class="no-display"> + <?php foreach ($params as $name => $value):?> + <input value="<?= $block->escapeHtmlAttr($value) ?>" name="<?= $block->escapeHtmlAttr($name) ?>" type="hidden"/> + <?php endforeach?> +</form> +</body> +</html> diff --git a/app/code/Magento/Payment/view/frontend/templates/transparent/redirect.phtml b/app/code/Magento/Payment/view/frontend/templates/transparent/redirect.phtml new file mode 100644 index 0000000000000..17fbdf780a40a --- /dev/null +++ b/app/code/Magento/Payment/view/frontend/templates/transparent/redirect.phtml @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** @var \Magento\Payment\Block\Transparent\Redirect $block */ +$params = $block->getPostParams(); +$redirectUrl = $block->getRedirectUrl(); +?> +<html> +<head></head> +<body onload="document.forms['proxy_form'].submit()"> +<form id="proxy_form" action="<?= $block->escapeUrl($redirectUrl) ?>" + method="POST" hidden enctype="application/x-www-form-urlencoded" class="no-display"> + <?php foreach ($params as $name => $value):?> + <input value="<?= $block->escapeHtmlAttr($value) ?>" name="<?= $block->escapeHtmlAttr($name) ?>" type="hidden"/> + <?php endforeach?> +</form> +</body> +</html> diff --git a/app/code/Magento/Paypal/Controller/Adminhtml/Transparent/Redirect.php b/app/code/Magento/Paypal/Controller/Adminhtml/Transparent/Redirect.php new file mode 100644 index 0000000000000..8201761cc3a29 --- /dev/null +++ b/app/code/Magento/Paypal/Controller/Adminhtml/Transparent/Redirect.php @@ -0,0 +1,13 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Paypal\Controller\Adminhtml\Transparent; + +/** + * Class for redirecting the Paypal response result to Magento controller. + */ +class Redirect extends \Magento\Paypal\Controller\Transparent\Redirect +{ +} diff --git a/app/code/Magento/Paypal/Controller/Transparent/Redirect.php b/app/code/Magento/Paypal/Controller/Transparent/Redirect.php new file mode 100644 index 0000000000000..1b14a97990275 --- /dev/null +++ b/app/code/Magento/Paypal/Controller/Transparent/Redirect.php @@ -0,0 +1,97 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Paypal\Controller\Transparent; + +use Magento\Framework\App\Action\Action; +use Magento\Framework\App\Action\Context; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\App\CsrfAwareActionInterface; +use Magento\Framework\App\Request\InvalidRequestException; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\Controller\ResultInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\View\Result\LayoutFactory; +use Magento\Payment\Model\Method\Logger; +use Magento\Paypal\Model\Payflow\Transparent; + +/** + * Class for redirecting the Paypal response result to Magento controller. + */ +class Redirect extends Action implements CsrfAwareActionInterface, HttpPostActionInterface +{ + /** + * @var LayoutFactory + */ + private $resultLayoutFactory; + + /** + * @var Transparent + */ + private $transparent; + + /** + * @var Logger + */ + private $logger; + + /** + * @param Context $context + * @param LayoutFactory $resultLayoutFactory + * @param Transparent $transparent + * @param Logger $logger + */ + public function __construct( + Context $context, + LayoutFactory $resultLayoutFactory, + Transparent $transparent, + Logger $logger + ) { + $this->resultLayoutFactory = $resultLayoutFactory; + $this->transparent = $transparent; + $this->logger = $logger; + + parent::__construct($context); + } + + /** + * @inheritdoc + */ + public function createCsrfValidationException( + RequestInterface $request + ): ?InvalidRequestException { + return null; + } + + /** + * @inheritdoc + */ + public function validateForCsrf(RequestInterface $request): ?bool + { + return true; + } + + /** + * Saves the payment in quote + * + * @return ResultInterface + * @throws LocalizedException + */ + public function execute() + { + $gatewayResponse = (array)$this->getRequest()->getPostValue(); + $this->logger->debug( + ['PayPal PayflowPro redirect:' => $gatewayResponse], + $this->transparent->getDebugReplacePrivateDataKeys(), + $this->transparent->getDebugFlag() + ); + + $resultLayout = $this->resultLayoutFactory->create(); + $resultLayout->addDefaultHandle(); + $resultLayout->getLayout()->getUpdate()->load(['transparent_payment_redirect']); + + return $resultLayout; + } +} diff --git a/app/code/Magento/Paypal/Model/Api/Nvp.php b/app/code/Magento/Paypal/Model/Api/Nvp.php index 2ec88df492fb9..9e4f7693f4bfb 100644 --- a/app/code/Magento/Paypal/Model/Api/Nvp.php +++ b/app/code/Magento/Paypal/Model/Api/Nvp.php @@ -1512,17 +1512,18 @@ protected function _applyStreetAndRegionWorkarounds(DataObject $address) } // attempt to fetch region_id from directory if ($address->getCountryId() && $address->getRegion()) { - $regions = $this->_countryFactory->create()->loadByCode( - $address->getCountryId() - )->getRegionCollection()->addRegionCodeOrNameFilter( - $address->getRegion() - )->setPageSize( - 1 - ); - $regionItems = $regions->getItems(); - $region = array_shift($regionItems); - $address->setRegionId($region->getId()); - $address->setExportedKeys(array_merge($address->getExportedKeys(), ['region_id'])); + $regions = $this->_countryFactory->create() + ->loadByCode($address->getCountryId()) + ->getRegionCollection() + ->addRegionCodeOrNameFilter($address->getRegion()) + ->setPageSize(1); + + if ($regions->count()) { + $regionItems = $regions->getItems(); + $region = array_shift($regionItems); + $address->setRegionId($region->getId()); + $address->setExportedKeys(array_merge($address->getExportedKeys(), ['region_id'])); + } } } @@ -1624,7 +1625,7 @@ protected function _filterPeriodUnit($value) case 'year': return 'Year'; default: - break; + return ''; } } @@ -1653,7 +1654,7 @@ protected function _filterBillingAgreementStatus($value) case 'active': return 'Active'; default: - break; + return ''; } } @@ -1694,7 +1695,7 @@ protected function _filterPaymentStatusFromNvpToInfo($value) case 'Voided': return \Magento\Paypal\Model\Info::PAYMENTSTATUS_VOIDED; default: - break; + return null; } } @@ -1712,7 +1713,7 @@ protected function _filterPaymentReviewAction($value) case \Magento\Paypal\Model\Pro::PAYMENT_REVIEW_DENY: return 'Deny'; default: - break; + return null; } } diff --git a/app/code/Magento/Paypal/Model/Payflow/Service/Request/SecureToken.php b/app/code/Magento/Paypal/Model/Payflow/Service/Request/SecureToken.php index 2a4ec764c4172..6e9990f65c450 100644 --- a/app/code/Magento/Paypal/Model/Payflow/Service/Request/SecureToken.php +++ b/app/code/Magento/Paypal/Model/Payflow/Service/Request/SecureToken.php @@ -70,9 +70,9 @@ public function requestToken(Quote $quote, array $urls = []) $request->setCurrency($quote->getBaseCurrencyCode()); $request->setCreatesecuretoken('Y'); $request->setSecuretokenid($this->mathRandom->getUniqueHash()); - $request->setReturnurl($urls['return_url'] ?? $this->url->getUrl('paypal/transparent/response')); - $request->setErrorurl($urls['error_url'] ?? $this->url->getUrl('paypal/transparent/response')); - $request->setCancelurl($urls['cancel_url'] ?? $this->url->getUrl('paypal/transparent/response')); + $request->setReturnurl($urls['return_url'] ?? $this->url->getUrl('paypal/transparent/redirect')); + $request->setErrorurl($urls['error_url'] ?? $this->url->getUrl('paypal/transparent/redirect')); + $request->setCancelurl($urls['cancel_url'] ?? $this->url->getUrl('paypal/transparent/redirect')); $request->setDisablereceipt('TRUE'); $request->setSilenttran('TRUE'); diff --git a/app/code/Magento/Paypal/Model/Payflow/Service/Response/Transaction.php b/app/code/Magento/Paypal/Model/Payflow/Service/Response/Transaction.php index 5db78e6fac520..1e97ac8b8c766 100644 --- a/app/code/Magento/Paypal/Model/Payflow/Service/Response/Transaction.php +++ b/app/code/Magento/Paypal/Model/Payflow/Service/Response/Transaction.php @@ -19,7 +19,8 @@ use Magento\Sales\Api\Data\OrderPaymentInterface; /** - * Class Transaction + * Process PayPal transaction response. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Transaction @@ -90,7 +91,7 @@ public function getResponseObject($gatewayTransactionResponse) $response = $this->transparent->mapGatewayResponse((array) $gatewayTransactionResponse, $response); $this->logger->debug( - (array) $gatewayTransactionResponse, + ['PayPal PayflowPro response:' => (array)$gatewayTransactionResponse], (array) $this->transparent->getDebugReplacePrivateDataKeys(), (bool) $this->transparent->getDebugFlag() ); diff --git a/app/code/Magento/Paypal/Plugin/TransparentSessionChecker.php b/app/code/Magento/Paypal/Plugin/TransparentSessionChecker.php new file mode 100644 index 0000000000000..5157ba3208fb7 --- /dev/null +++ b/app/code/Magento/Paypal/Plugin/TransparentSessionChecker.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Paypal\Plugin; + +use Magento\Framework\App\Request\Http; +use Magento\Framework\Session\SessionStartChecker; + +/** + * Intended to preserve session cookie after submitting POST form from PayPal to Magento controller. + */ +class TransparentSessionChecker +{ + private const TRANSPARENT_REDIRECT_PATH = 'paypal/transparent/redirect'; + + /** + * @var Http + */ + private $request; + + /** + * @param Http $request + */ + public function __construct( + Http $request + ) { + $this->request = $request; + } + + /** + * Prevents session starting while instantiating PayPal transparent redirect controller. + * + * @param SessionStartChecker $subject + * @param bool $result + * @return bool + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterCheck(SessionStartChecker $subject, bool $result): bool + { + if ($result === false) { + return false; + } + + return strpos((string)$this->request->getPathInfo(), self::TRANSPARENT_REDIRECT_PATH) === false; + } +} diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsConflictResolutionForPayPal.xml b/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsConflictResolutionForPayPalTest.xml similarity index 97% rename from app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsConflictResolutionForPayPal.xml rename to app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsConflictResolutionForPayPalTest.xml index 3e1a825861b5e..db5fb3848dcf1 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsConflictResolutionForPayPal.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsConflictResolutionForPayPalTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminConfigPaymentsConflictResolutionForPayPalInUnitedKingdom"> + <test name="AdminConfigPaymentsConflictResolutionForPayPalInUnitedKingdomTest"> <annotations> <features value="PayPal"/> <stories value="Payment methods"/> @@ -73,7 +73,7 @@ <argument name="countryCode" value="gb"/> </actionGroup> </test> - <test name="AdminConfigPaymentsConflictResolutionForPayPalInJapan" extends="AdminConfigPaymentsConflictResolutionForPayPalInUnitedKingdom"> + <test name="AdminConfigPaymentsConflictResolutionForPayPalInJapanTest" extends="AdminConfigPaymentsConflictResolutionForPayPalInUnitedKingdomTest"> <annotations> <features value="PayPal"/> <stories value="Payment methods"/> @@ -113,7 +113,7 @@ <argument name="countryCode" value="jp"/> </actionGroup> </test> - <test name="AdminConfigPaymentsConflictResolutionForPayPalInFrance" extends="AdminConfigPaymentsConflictResolutionForPayPalInUnitedKingdom"> + <test name="AdminConfigPaymentsConflictResolutionForPayPalInFranceTest" extends="AdminConfigPaymentsConflictResolutionForPayPalInUnitedKingdomTest"> <annotations> <features value="PayPal"/> <stories value="Payment methods"/> @@ -153,7 +153,7 @@ <argument name="countryCode" value="fr"/> </actionGroup> </test> - <test name="AdminConfigPaymentsConflictResolutionForPayPalInHongKong" extends="AdminConfigPaymentsConflictResolutionForPayPalInUnitedKingdom"> + <test name="AdminConfigPaymentsConflictResolutionForPayPalInHongKongTest" extends="AdminConfigPaymentsConflictResolutionForPayPalInUnitedKingdomTest"> <annotations> <features value="PayPal"/> <stories value="Payment methods"/> @@ -193,7 +193,7 @@ <argument name="countryCode" value="hk"/> </actionGroup> </test> - <test name="AdminConfigPaymentsConflictResolutionForPayPalInItaly" extends="AdminConfigPaymentsConflictResolutionForPayPalInUnitedKingdom"> + <test name="AdminConfigPaymentsConflictResolutionForPayPalInItalyTest" extends="AdminConfigPaymentsConflictResolutionForPayPalInUnitedKingdomTest"> <annotations> <features value="PayPal"/> <stories value="Payment methods"/> @@ -233,7 +233,7 @@ <argument name="countryCode" value="it"/> </actionGroup> </test> - <test name="AdminConfigPaymentsConflictResolutionForPayPalInSpain" extends="AdminConfigPaymentsConflictResolutionForPayPalInUnitedKingdom"> + <test name="AdminConfigPaymentsConflictResolutionForPayPalInSpainTest" extends="AdminConfigPaymentsConflictResolutionForPayPalInUnitedKingdomTest"> <annotations> <features value="PayPal"/> <stories value="Payment methods"/> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsSectionState.xml b/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsSectionStateTest.xml similarity index 95% rename from app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsSectionState.xml rename to app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsSectionStateTest.xml index ba5e701ceea66..62d77d8aae6f8 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsSectionState.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsSectionStateTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminConfigPaymentsSectionState"> + <test name="AdminConfigPaymentsSectionStateTest"> <annotations> <features value="PayPal"/> <stories value="Payment methods"/> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontVerifySecureURLRedirectPaypalTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontVerifySecureURLRedirectPaypalTest.xml index b2fcfa43181dc..cf0e4b3d0b370 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontVerifySecureURLRedirectPaypalTest.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontVerifySecureURLRedirectPaypalTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontVerifySecureURLRedirectPaypal"> + <test name="StorefrontVerifySecureURLRedirectPaypalTest"> <annotations> <features value="Paypal"/> <stories value="Storefront Secure URLs"/> diff --git a/app/code/Magento/Paypal/etc/di.xml b/app/code/Magento/Paypal/etc/di.xml index c0141bbb3215e..973ed0f91924c 100644 --- a/app/code/Magento/Paypal/etc/di.xml +++ b/app/code/Magento/Paypal/etc/di.xml @@ -252,4 +252,7 @@ </argument> </arguments> </type> + <type name="Magento\Framework\Session\SessionStartChecker"> + <plugin name="transparent_session_checker" type="Magento\Paypal\Plugin\TransparentSessionChecker"/> + </type> </config> diff --git a/app/code/Magento/Paypal/etc/frontend/page_types.xml b/app/code/Magento/Paypal/etc/frontend/page_types.xml index 133ab1ca76162..1da5d54fb385d 100644 --- a/app/code/Magento/Paypal/etc/frontend/page_types.xml +++ b/app/code/Magento/Paypal/etc/frontend/page_types.xml @@ -14,6 +14,7 @@ <type id="paypal_payflow_form" label="Paypal Payflow Form"/> <type id="transparent" label="Paypal Payflow TR Form"/> <type id="transparent_payment_response" label="Paypal Payflow TR Response"/> + <type id="transparent_payment_redirect" label="Paypal Payflow TR Redirect"/> <type id="paypal_payflow_returnurl" label="Paypal Payflow Return URL"/> <type id="paypal_payflowadvanced_cancelpayment" label="Paypal Payflow Advanced Cancel Payment"/> <type id="paypal_payflowadvanced_form" label="Paypal Payflow Advanced Form"/> diff --git a/app/code/Magento/Paypal/view/adminhtml/layout/transparent_payment_redirect.xml b/app/code/Magento/Paypal/view/adminhtml/layout/transparent_payment_redirect.xml new file mode 100644 index 0000000000000..01acf03c0d077 --- /dev/null +++ b/app/code/Magento/Paypal/view/adminhtml/layout/transparent_payment_redirect.xml @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd"> + <container name="root" label="Root"> + <block class="Magento\Payment\Block\Transparent\Redirect" name="transparent_redirect" template="Magento_Payment::transparent/redirect.phtml"> + <arguments> + <argument name="route_path" xsi:type="string">paypal/transparent/response</argument> + </arguments> + </block> + </container> +</layout> diff --git a/app/code/Magento/Paypal/view/frontend/layout/transparent_payment_redirect.xml b/app/code/Magento/Paypal/view/frontend/layout/transparent_payment_redirect.xml new file mode 100644 index 0000000000000..01acf03c0d077 --- /dev/null +++ b/app/code/Magento/Paypal/view/frontend/layout/transparent_payment_redirect.xml @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd"> + <container name="root" label="Root"> + <block class="Magento\Payment\Block\Transparent\Redirect" name="transparent_redirect" template="Magento_Payment::transparent/redirect.phtml"> + <arguments> + <argument name="route_path" xsi:type="string">paypal/transparent/response</argument> + </arguments> + </block> + </container> +</layout> diff --git a/app/code/Magento/Persistent/Model/Checkout/GuestPaymentInformationManagementPlugin.php b/app/code/Magento/Persistent/Model/Checkout/GuestPaymentInformationManagementPlugin.php index 2641102ca4d72..b4df4125a2c60 100644 --- a/app/code/Magento/Persistent/Model/Checkout/GuestPaymentInformationManagementPlugin.php +++ b/app/code/Magento/Persistent/Model/Checkout/GuestPaymentInformationManagementPlugin.php @@ -11,6 +11,8 @@ /** * Plugin to convert shopping cart from persistent cart to guest cart before order save when customer not logged in + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class GuestPaymentInformationManagementPlugin { @@ -93,7 +95,7 @@ public function __construct( * @return void * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function beforeSavePaymentInformationAndPlaceOrder( + public function beforeSavePaymentInformation( GuestPaymentInformationManagement $subject, $cartId, $email, diff --git a/app/code/Magento/Persistent/Observer/PreventExpressCheckoutObserver.php b/app/code/Magento/Persistent/Observer/PreventExpressCheckoutObserver.php deleted file mode 100644 index d7a54f2c5fec3..0000000000000 --- a/app/code/Magento/Persistent/Observer/PreventExpressCheckoutObserver.php +++ /dev/null @@ -1,89 +0,0 @@ -<?php -/** - * - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Persistent\Observer; - -use Magento\Framework\Event\ObserverInterface; - -class PreventExpressCheckoutObserver implements ObserverInterface -{ - /** - * @var \Magento\Framework\Message\ManagerInterface - */ - protected $messageManager; - - /** - * Url model - * - * @var \Magento\Framework\UrlInterface - */ - protected $_url; - - /** - * Persistent session - * - * @var \Magento\Persistent\Helper\Session - */ - protected $_persistentSession = null; - - /** - * @var \Magento\Customer\Model\Session - */ - protected $_customerSession; - - /** - * @var \Magento\Checkout\Helper\ExpressRedirect - */ - protected $_expressRedirectHelper; - - /** - * @param \Magento\Persistent\Helper\Session $persistentSession - * @param \Magento\Customer\Model\Session $customerSession - * @param \Magento\Framework\UrlInterface $url - * @param \Magento\Framework\Message\ManagerInterface $messageManager - * @param \Magento\Checkout\Helper\ExpressRedirect $expressRedirectHelper - */ - public function __construct( - \Magento\Persistent\Helper\Session $persistentSession, - \Magento\Customer\Model\Session $customerSession, - \Magento\Framework\UrlInterface $url, - \Magento\Framework\Message\ManagerInterface $messageManager, - \Magento\Checkout\Helper\ExpressRedirect $expressRedirectHelper - ) { - $this->_persistentSession = $persistentSession; - $this->_customerSession = $customerSession; - $this->_url = $url; - $this->messageManager = $messageManager; - $this->_expressRedirectHelper = $expressRedirectHelper; - } - - /** - * Prevent express checkout - * - * @param \Magento\Framework\Event\Observer $observer - * @return void - */ - public function execute(\Magento\Framework\Event\Observer $observer) - { - if (!($this->_persistentSession->isPersistent() && !$this->_customerSession->isLoggedIn())) { - return; - } - - /** @var $controllerAction \Magento\Checkout\Controller\Express\RedirectLoginInterface*/ - $controllerAction = $observer->getEvent()->getControllerAction(); - if (!$controllerAction || - !$controllerAction instanceof \Magento\Checkout\Controller\Express\RedirectLoginInterface || - $controllerAction->getRedirectActionName() != $controllerAction->getRequest()->getActionName() - ) { - return; - } - - $this->messageManager->addNotice(__('To check out, please sign in using your email address.')); - $customerBeforeAuthUrl = $this->_url->getUrl('persistent/index/expressCheckout'); - - $this->_expressRedirectHelper->redirectLogin($controllerAction, $customerBeforeAuthUrl); - } -} diff --git a/app/code/Magento/Persistent/Test/Unit/Model/Checkout/GuestPaymentInformationManagementPluginTest.php b/app/code/Magento/Persistent/Test/Unit/Model/Checkout/GuestPaymentInformationManagementPluginTest.php index c7f84b476fa7e..01f8c5403ea22 100644 --- a/app/code/Magento/Persistent/Test/Unit/Model/Checkout/GuestPaymentInformationManagementPluginTest.php +++ b/app/code/Magento/Persistent/Test/Unit/Model/Checkout/GuestPaymentInformationManagementPluginTest.php @@ -111,7 +111,7 @@ public function testBeforeSavePaymentInformationAndPlaceOrderCartConvertsToGuest $collectionMock->expects($this->once())->method('walk')->with($walkMethod, $walkArgs); $this->cartRepositoryMock->expects($this->once())->method('save')->with($quoteMock); - $this->plugin->beforeSavePaymentInformationAndPlaceOrder( + $this->plugin->beforeSavePaymentInformation( $this->subjectMock, $cartId, $email, @@ -134,7 +134,7 @@ public function testBeforeSavePaymentInformationAndPlaceOrderShoppingCartNotPers $this->persistentSessionMock->expects($this->once())->method('isPersistent')->willReturn(true); $this->customerSessionMock->expects($this->once())->method('isLoggedIn')->willReturn(false); - $this->plugin->beforeSavePaymentInformationAndPlaceOrder( + $this->plugin->beforeSavePaymentInformation( $this->subjectMock, $cartId, $email, @@ -155,7 +155,7 @@ public function testBeforeSavePaymentInformationAndPlaceOrderPersistentSessionNo $this->persistentSessionMock->expects($this->once())->method('isPersistent')->willReturn(false); - $this->plugin->beforeSavePaymentInformationAndPlaceOrder( + $this->plugin->beforeSavePaymentInformation( $this->subjectMock, $cartId, $email, @@ -177,7 +177,7 @@ public function testBeforeSavePaymentInformationAndPlaceOrderCustomerSessionInLo $this->persistentSessionMock->expects($this->once())->method('isPersistent')->willReturn(true); $this->customerSessionMock->expects($this->once())->method('isLoggedIn')->willReturn(true); - $this->plugin->beforeSavePaymentInformationAndPlaceOrder( + $this->plugin->beforeSavePaymentInformation( $this->subjectMock, $cartId, $email, @@ -201,7 +201,7 @@ public function testBeforeSavePaymentInformationAndPlaceOrderQuoteManagerNotInPe $this->customerSessionMock->expects($this->once())->method('isLoggedIn')->willReturn(false); $this->quoteManagerMock->expects($this->once())->method('isPersistent')->willReturn(false); - $this->plugin->beforeSavePaymentInformationAndPlaceOrder( + $this->plugin->beforeSavePaymentInformation( $this->subjectMock, $cartId, $email, diff --git a/app/code/Magento/Persistent/Test/Unit/Observer/PreventExpressCheckoutObserverTest.php b/app/code/Magento/Persistent/Test/Unit/Observer/PreventExpressCheckoutObserverTest.php deleted file mode 100644 index 7749377bbfcd0..0000000000000 --- a/app/code/Magento/Persistent/Test/Unit/Observer/PreventExpressCheckoutObserverTest.php +++ /dev/null @@ -1,168 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Persistent\Test\Unit\Observer; - -/** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class PreventExpressCheckoutObserverTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Persistent\Observer\PreventExpressCheckoutObserver - */ - protected $_model; - - /** - * @var \Magento\Framework\Event - */ - protected $_event; - - /** - * @var \Magento\Framework\Event\Observer - */ - protected $_observer; - - /** - * Customer session - * - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $_customerSession; - - /** - * Persistent session - * - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $_persistentSession; - - /** - * - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $_messageManager; - - /** - * Url model - * - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $_url; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $_expressRedirectHelper; - - protected function setUp() - { - $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - - $this->_event = new \Magento\Framework\Event(); - $this->_observer = new \Magento\Framework\Event\Observer(); - $this->_observer->setEvent($this->_event); - - $this->_customerSession = $this->getMockBuilder( - \Magento\Customer\Model\Session::class - )->disableOriginalConstructor()->setMethods( - ['isLoggedIn'] - )->getMock(); - - $this->_persistentSession = $this->getMockBuilder( - \Magento\Persistent\Helper\Session::class - )->disableOriginalConstructor()->setMethods( - ['isPersistent'] - )->getMock(); - - $this->_messageManager = $this->getMockBuilder( - \Magento\Framework\Message\ManagerInterface::class - )->disableOriginalConstructor()->setMethods( - [] - )->getMock(); - - $this->_url = $this->getMockBuilder( - \Magento\Framework\UrlInterface::class - )->disableOriginalConstructor()->setMethods( - [] - )->getMock(); - - $this->_expressRedirectHelper = $this->getMockBuilder( - \Magento\Checkout\Helper\ExpressRedirect::class - )->disableOriginalConstructor()->setMethods( - ['redirectLogin'] - )->getMock(); - - $this->_model = $helper->getObject( - \Magento\Persistent\Observer\PreventExpressCheckoutObserver::class, - [ - 'customerSession' => $this->_customerSession, - 'persistentSession' => $this->_persistentSession, - 'messageManager' => $this->_messageManager, - 'url' => $this->_url, - 'expressRedirectHelper' => $this->_expressRedirectHelper - ] - ); - } - - public function testPreventExpressCheckoutOnline() - { - $this->_customerSession->expects($this->once())->method('isLoggedIn')->will($this->returnValue(true)); - $this->_persistentSession->expects($this->once())->method('isPersistent')->will($this->returnValue(true)); - $this->_model->execute($this->_observer); - } - - public function testPreventExpressCheckoutEmpty() - { - $this->_customerSession->expects($this->any())->method('isLoggedIn')->will($this->returnValue(false)); - $this->_persistentSession->expects($this->any())->method('isPersistent')->will($this->returnValue(true)); - - $this->_event->setControllerAction(null); - $this->_model->execute($this->_observer); - - $this->_event->setControllerAction(new \StdClass()); - $this->_model->execute($this->_observer); - - $expectedActionName = 'realAction'; - $unexpectedActionName = 'notAction'; - $request = new \Magento\Framework\DataObject(); - $request->setActionName($unexpectedActionName); - $expressRedirectMock = $this->getMockBuilder( - \Magento\Checkout\Controller\Express\RedirectLoginInterface::class - )->disableOriginalConstructor()->setMethods( - [ - 'getActionFlagList', - 'getResponse', - 'getCustomerBeforeAuthUrl', - 'getLoginUrl', - 'getRedirectActionName', - 'getRequest', - ] - )->getMock(); - $expressRedirectMock->expects($this->any())->method('getRequest')->will($this->returnValue($request)); - $expressRedirectMock->expects( - $this->any() - )->method( - 'getRedirectActionName' - )->will( - $this->returnValue($expectedActionName) - ); - $this->_event->setControllerAction($expressRedirectMock); - $this->_model->execute($this->_observer); - - $expectedAuthUrl = 'expectedAuthUrl'; - $request->setActionName($expectedActionName); - $this->_url->expects($this->once())->method('getUrl')->will($this->returnValue($expectedAuthUrl)); - $this->_expressRedirectHelper->expects( - $this->once() - )->method( - 'redirectLogin' - )->with( - $expressRedirectMock, - $expectedAuthUrl - ); - $this->_model->execute($this->_observer); - } -} diff --git a/app/code/Magento/Persistent/etc/frontend/events.xml b/app/code/Magento/Persistent/etc/frontend/events.xml index 79720695ea6f6..840297b0ff098 100644 --- a/app/code/Magento/Persistent/etc/frontend/events.xml +++ b/app/code/Magento/Persistent/etc/frontend/events.xml @@ -34,7 +34,6 @@ <observer name="persistent_session" instance="Magento\Persistent\Observer\RenewCookieObserver" /> <observer name="persistent_quote" instance="Magento\Persistent\Observer\CheckExpirePersistentQuoteObserver" /> <observer name="persistent_customer" instance="Magento\Persistent\Observer\EmulateCustomerObserver" /> - <observer name="persistent_checkout" instance="Magento\Persistent\Observer\PreventExpressCheckoutObserver" /> </event> <event name="customer_customer_authenticated"> <observer name="persistent" instance="Magento\Persistent\Observer\CustomerAuthenticatedEventObserver" /> diff --git a/app/code/Magento/Quote/i18n/en_US.csv b/app/code/Magento/Quote/i18n/en_US.csv index b24179297493a..c899c432c70d4 100644 --- a/app/code/Magento/Quote/i18n/en_US.csv +++ b/app/code/Magento/Quote/i18n/en_US.csv @@ -31,6 +31,7 @@ Subtotal,Subtotal "Cart %1 does not contain item %2","Cart %1 does not contain item %2" "Could not save quote","Could not save quote" "Cart %1 doesn't contain item %2","Cart %1 doesn't contain item %2" +"The cart doesn't contain the item","The cart doesn't contain the item" "Could not remove item from quote","Could not remove item from quote" "The qty value is required to update quote item.","The qty value is required to update quote item." "Minimum order amount is %1","Minimum order amount is %1" diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php index 91c77a1a3ecc5..0360d9ccf5476 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php @@ -13,7 +13,7 @@ use Magento\Quote\Model\Quote; /** - * Add products to cart + * Adding products to cart using GraphQL */ class AddProductsToCart { @@ -54,16 +54,6 @@ public function execute(Quote $cart, array $cartItems): void $this->addProductToCart->execute($cart, $cartItemData); } - if ($cart->getData('has_error')) { - $e = new GraphQlInputException(__('Shopping cart errors')); - $errors = $cart->getErrors(); - foreach ($errors as $error) { - /** @var MessageInterface $error */ - $e->addError(new GraphQlInputException(__($error->getText()))); - } - throw $e; - } - $this->cartRepository->save($cart); } } diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php b/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php index af70809a1053d..21243a4545fa3 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php @@ -72,9 +72,7 @@ public function execute(string $cartHash, ?int $customerId, int $storeId): Quote } if (false === (bool)$cart->getIsActive()) { - throw new GraphQlNoSuchEntityException( - __('Current user does not have an active cart.') - ); + throw new GraphQlNoSuchEntityException(__('The cart isn\'t active.')); } if ((int)$cart->getStoreId() !== $storeId) { diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartItems.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartItems.php index 2674b3728619a..8017a91b5cfd2 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartItems.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartItems.php @@ -9,6 +9,7 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Quote\Model\Quote\Item as QuoteItem; @@ -29,6 +30,12 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value $cart = $value['model']; $itemsData = []; + if ($cart->getData('has_error')) { + $errors = $cart->getErrors(); + foreach ($errors as $error) { + $itemsData[] = new GraphQlInputException(__($error->getText())); + } + } foreach ($cart->getAllVisibleItems() as $cartItem) { /** * @var QuoteItem $cartItem diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveItemFromCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveItemFromCart.php index bf9ccef8ae44a..c2045d4a0e8d5 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveItemFromCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveItemFromCart.php @@ -65,7 +65,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value try { $this->cartItemRepository->deleteById((int)$cart->getId(), $itemId); } catch (NoSuchEntityException $e) { - throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e); + throw new GraphQlNoSuchEntityException(__('The cart doesn\'t contain the item')); } catch (LocalizedException $e) { throw new GraphQlInputException(__($e->getMessage()), $e); } diff --git a/app/code/Magento/Review/Block/Product/View.php b/app/code/Magento/Review/Block/Product/View.php index c7b813ea8eed9..c66e3e50b919b 100644 --- a/app/code/Magento/Review/Block/Product/View.php +++ b/app/code/Magento/Review/Block/Product/View.php @@ -82,13 +82,20 @@ public function __construct( */ protected function _toHtml() { - $this->getProduct()->setShortDescription(null); + $product = $this->getProduct(); + + if (!$product) { + return ''; + } + + $product->setShortDescription(null); return parent::_toHtml(); } /** * Replace review summary html with more detailed review summary + * * Reviews collection count will be jerked here * * @param \Magento\Catalog\Model\Product $product diff --git a/app/code/Magento/Review/Block/Product/View/ListView.php b/app/code/Magento/Review/Block/Product/View/ListView.php index 5df8a3698e537..2d3d1f6637f1f 100644 --- a/app/code/Magento/Review/Block/Product/View/ListView.php +++ b/app/code/Magento/Review/Block/Product/View/ListView.php @@ -55,7 +55,10 @@ protected function _prepareLayout() */ protected function _beforeToHtml() { - $this->getReviewsCollection()->load()->addRateVotes(); + if ($this->getProductId()) { + $this->getReviewsCollection()->load()->addRateVotes(); + } + return parent::_beforeToHtml(); } diff --git a/app/code/Magento/Review/Test/Mftf/ActionGroup/AdminAddProductReviewActionGroup.xml b/app/code/Magento/Review/Test/Mftf/ActionGroup/AdminAddProductReviewActionGroup.xml new file mode 100644 index 0000000000000..9316194dcc78b --- /dev/null +++ b/app/code/Magento/Review/Test/Mftf/ActionGroup/AdminAddProductReviewActionGroup.xml @@ -0,0 +1,29 @@ +<?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="AdminAddProductReviewActionGroup"> + <arguments> + <argument name="review" type="entity" defaultValue="simpleProductReview"/> + <argument name="sku" type="string"/> + </arguments> + <!--Click on Add New Review --> + <click selector="{{AdminCreateNewReviewSection.addNewReviewButton}}" stepKey="clickOnNewReview"/> + <waitForElementVisible selector="{{AdminCreateNewReviewSection.addNewReviewBySKU(sku)}}" stepKey="waitForVisibleReviewButton"/> + <!--Select Product by SKU and Create Review --> + <click selector="{{AdminCreateNewReviewSection.addNewReviewBySKU(sku)}}" stepKey="addNewReviewBySKU"/> + <waitForElementVisible selector="{{AdminCreateNewReviewSection.select_stores}}" stepKey="waitForVisibleReviewDetails"/> + <selectOption selector="{{AdminCreateNewReviewSection.select_stores}}" userInput="{{review.select_stores[0]}}" stepKey="visibilityField"/> + <fillField selector="{{AdminCreateNewReviewSection.nickname}}" userInput="{{review.nickname}}" stepKey="fillNicknameField"/> + <fillField selector="{{AdminCreateNewReviewSection.title}}" userInput="{{review.title}}" stepKey="fillSummaryField"/> + <fillField selector="{{AdminCreateNewReviewSection.detail}}" userInput="{{review.detail}}" stepKey="fillReviewField"/> + <click selector="{{AdminCreateNewReviewSection.submitReview}}" stepKey="clickSubmitReview"/> + <waitForElementVisible selector="{{AdminCreateNewReviewSection.SuccessMessage}}" stepKey="waitForSuccessMessage"/> + <see selector="{{AdminCreateNewReviewSection.SuccessMessage}}" userInput="You saved the review." stepKey="seeSuccessMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Review/Test/Mftf/ActionGroup/AdminFilterProductReviewActionGroup.xml b/app/code/Magento/Review/Test/Mftf/ActionGroup/AdminFilterProductReviewActionGroup.xml new file mode 100644 index 0000000000000..daab520025477 --- /dev/null +++ b/app/code/Magento/Review/Test/Mftf/ActionGroup/AdminFilterProductReviewActionGroup.xml @@ -0,0 +1,21 @@ +<?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="AdminFilterProductReviewActionGroup"> + <arguments> + <argument name="reviewCount" type="string"/> + </arguments> + <!--Sort Review Column in Grid --> + <waitForPageLoad stepKey="waitForGridToAppear"/> + <fillField userInput="{{reviewCount}}" selector="{{AdminCreateNewReviewSection.gridProducts_filter_review_cnt}}" stepKey="searchReview"/> + <click selector="{{AdminCreateNewReviewSection.searchButton}}" stepKey="startSearch"/> + <waitForPageLoad stepKey="waitForResults"/> + <see userInput="{{reviewCount}}" selector="{{AdminCreateNewReviewSection.gridReviewColumn}}" stepKey="assertReviewColumn"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Review/Test/Mftf/Data/ProductReviewData.xml b/app/code/Magento/Review/Test/Mftf/Data/ProductReviewData.xml index 89758328efd54..f66decd1b7bd0 100644 --- a/app/code/Magento/Review/Test/Mftf/Data/ProductReviewData.xml +++ b/app/code/Magento/Review/Test/Mftf/Data/ProductReviewData.xml @@ -12,5 +12,8 @@ <data key="nickname" unique="suffix">user</data> <data key="title">Review title</data> <data key="detail">Simple product review</data> + <array key="select_stores"> + <item>Default Store View</item> + </array> </entity> </entities> diff --git a/app/code/Magento/Review/Test/Mftf/Page/AdminProductReviewPage.xml b/app/code/Magento/Review/Test/Mftf/Page/AdminProductReviewPage.xml new file mode 100644 index 0000000000000..131fc5e82d944 --- /dev/null +++ b/app/code/Magento/Review/Test/Mftf/Page/AdminProductReviewPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminProductReviewPage" url="review/product/new/{{productId}}/" area="admin" module="Review" parameterized="true"> + <section name="AdminCreateNewReviewSection"/> + </page> +</pages> diff --git a/app/code/Magento/Review/Test/Mftf/Section/AdminCreateNewReviewSection.xml b/app/code/Magento/Review/Test/Mftf/Section/AdminCreateNewReviewSection.xml new file mode 100644 index 0000000000000..3b17b20e9da1b --- /dev/null +++ b/app/code/Magento/Review/Test/Mftf/Section/AdminCreateNewReviewSection.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCreateNewReviewSection"> + <element name="addNewReviewButton" type="button" selector="#add" timeout="30"/> + <element name="addNewReviewBySKU" type="text" selector="//td[contains(@class,'col-sku')][contains(text(),'{{sku}}')]" parameterized="true"/> + <element name="select_stores" type="text" selector="#select_stores"/> + <element name="nickname" type="text" selector="#nickname"/> + <element name="title" type="text" selector="#title"/> + <element name="detail" type="textarea" selector="#detail"/> + <element name="submitReview" type="button" selector="#save_button"/> + <element name="SuccessMessage" type="button" selector="div.message-success"/> + <element name="gridProducts_filter_review_cnt" type="button" selector="#gridProducts_filter_review_cnt"/> + <element name="searchButton" type="button" selector="//*[@id='gridProducts']//button[contains(@title, 'Search')]"/> + <element name="gridReviewColumn" type="text" selector="//tbody//td[@data-column='review_cnt']"/> + <element name="gridCustomer_filter_review_cnt" type="button" selector="#customers_grid_filter_review_cnt"/> + <element name="CustomerSearchButton" type="button" selector="//*[@id='customers_grid']//button[contains(@title, 'Search')]"/> + </section> +</sections> diff --git a/app/code/Magento/Review/Test/Mftf/Test/AdminReviewsByProductsReportTest.xml b/app/code/Magento/Review/Test/Mftf/Test/AdminReviewsByProductsReportTest.xml new file mode 100644 index 0000000000000..050f30d6ca65f --- /dev/null +++ b/app/code/Magento/Review/Test/Mftf/Test/AdminReviewsByProductsReportTest.xml @@ -0,0 +1,64 @@ +<?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="AdminReviewsByProductsReportTest"> + <annotations> + <features value="Review"/> + <stories value="Review By Products"/> + <title value="Admin Reports Review by Products"/> + <description value="Review By Products Grid Filters"/> + <severity value="AVERAGE"/> + <testCaseId value="MC-32083"/> + </annotations> + <before> + <!--Login--> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + <!--Create product and Category--> + <createData stepKey="category" entity="SimpleSubCategory"/> + <createData stepKey="createProduct1" entity="SimpleProduct"> + <requiredEntity createDataKey="category"/> + </createData> + <createData stepKey="createProduct2" entity="SimpleProduct"> + <requiredEntity createDataKey="category"/> + </createData> + </before> + <after> + <!-- Delete reviews --> + <actionGroup ref="AdminOpenReviewsPageActionGroup" stepKey="openAllReviewsPage"/> + <actionGroup ref="AdminDeleteReviewsByUserNicknameActionGroup" stepKey="deleteCustomerReview"/> + <!--delete Category and Products --> + <deleteData createDataKey="createProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="createProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="category" stepKey="deleteCategory"/> + <!--Logout--> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Navigate to Marketing > User Content> All Review --> + <amOnPage url="{{AdminReviewsPage.url}}" stepKey="openReviewsPage"/> + <waitForPageLoad time="30" stepKey="waitForPageLoadCreatedReviewOne"/> + <actionGroup ref="AdminAddProductReviewActionGroup" stepKey="addFirstReview"> + <argument name="sku" value="$$createProduct1.sku$$"/> + </actionGroup> + <waitForPageLoad time="30" stepKey="waitForPageLoadCreatedReviewTwo"/> + <actionGroup ref="AdminAddProductReviewActionGroup" stepKey="addSecondReview"> + <argument name="sku" value="$$createProduct2.sku$$"/> + </actionGroup> + <!-- Navigate to Reports > Reviews >By Products --> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToReportsByProductsPage"> + <argument name="menuUiId" value="{{AdminMenuReports.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuReportsReviewsByProducts.dataUiId}}"/> + </actionGroup> + <!--Sort Review Column --> + <grabTextFrom selector="{{AdminCreateNewReviewSection.gridReviewColumn}}" stepKey="grabReviewQuantity"/> + <actionGroup ref="AdminFilterProductReviewActionGroup" stepKey="navigateToReportsReview"> + <argument name="reviewCount" value="$grabReviewQuantity"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Review/Test/Mftf/Test/StorefrontVerifySecureURLRedirectReviewTest.xml b/app/code/Magento/Review/Test/Mftf/Test/StorefrontVerifySecureURLRedirectReviewTest.xml index b10af7a303cc7..8a2f441e5c4e8 100644 --- a/app/code/Magento/Review/Test/Mftf/Test/StorefrontVerifySecureURLRedirectReviewTest.xml +++ b/app/code/Magento/Review/Test/Mftf/Test/StorefrontVerifySecureURLRedirectReviewTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontVerifySecureURLRedirectReview"> + <test name="StorefrontVerifySecureURLRedirectReviewTest"> <annotations> <features value="Review"/> <stories value="Storefront Secure URLs"/> diff --git a/app/code/Magento/Review/Test/Unit/Block/Product/ListViewTest.php b/app/code/Magento/Review/Test/Unit/Block/Product/ListViewTest.php new file mode 100644 index 0000000000000..a1cc7a05dfef5 --- /dev/null +++ b/app/code/Magento/Review/Test/Unit/Block/Product/ListViewTest.php @@ -0,0 +1,49 @@ +<?php +/** + * Test class for \Magento\Review\Block\Product\View\ListView + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Review\Test\Unit\Block\Product; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Review\Block\Product\View\ListView; +use PHPUnit\Framework\TestCase; + +/** + * Class ViewTest + */ +class ListViewTest extends TestCase +{ + /** + * @var ListView + */ + private $listView; + + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @inheritDoc + */ + protected function setUp() + { + $this->objectManager = new ObjectManager($this); + $this->listView = $this->objectManager->getObject( + ListView::class + ); + } + + /** + * Validate that ListView->toHtml() would not crush if provided product is null + */ + public function testBlockShouldNotFailWithNullProduct() + { + $output = $this->listView->toHtml(); + $this->assertEquals('', $output); + } +} diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php index 03915c0499367..6a62ba7fbf0fd 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php @@ -38,7 +38,7 @@ class Account extends AbstractForm * @var \Magento\Framework\Api\ExtensibleDataObjectConverter */ protected $_extensibleDataObjectConverter; - + private const XML_PATH_EMAIL_REQUIRED_CREATE_ORDER = 'customer/create_account/email_required_create_order'; /** * @param \Magento\Backend\Block\Template\Context $context * @param \Magento\Backend\Model\Session\Quote $sessionQuote @@ -147,7 +147,7 @@ protected function _addAdditionalFormElementData(AbstractElement $element) { switch ($element->getId()) { case 'email': - $element->setRequired(1); + $element->setRequired($this->isEmailRequiredToCreateOrder()); $element->setClass('validate-email admin__control-text'); break; } @@ -164,7 +164,7 @@ public function getFormValues() try { $customer = $this->customerRepository->getById($this->getCustomerId()); } catch (\Exception $e) { - /** If customer does not exist do nothing. */ + $data = []; } $data = isset($customer) ? $this->_extensibleDataObjectConverter->toFlatArray( @@ -204,4 +204,17 @@ private function extractValuesFromAttributes(array $attributes): array return $formValues; } + + /** + * Retrieve email is required field for admin order creation + * + * @return bool + */ + private function isEmailRequiredToCreateOrder() + { + return $this->_scopeConfig->getValue( + self::XML_PATH_EMAIL_REQUIRED_CREATE_ORDER, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + } } diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Reorder.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Reorder.php index 995a6c216d3c2..eeaf4bee1b1c2 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Reorder.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Reorder.php @@ -3,16 +3,29 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Sales\Controller\Adminhtml\Order\Create; use Magento\Backend\App\Action; +use Magento\Backend\Model\View\Result\Forward; use Magento\Backend\Model\View\Result\ForwardFactory; +use Magento\Backend\Model\View\Result\Redirect; +use Magento\Catalog\Helper\Product; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\Escaper; use Magento\Framework\View\Result\PageFactory; -use Magento\Sales\Model\Order\Reorder\UnavailableProductsProvider; use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Controller\Adminhtml\Order\Create; use Magento\Sales\Helper\Reorder as ReorderHelper; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Reorder\UnavailableProductsProvider; +use Psr\Log\LoggerInterface; -class Reorder extends \Magento\Sales\Controller\Adminhtml\Order\Create +/** + * Controller create order. + */ +class Reorder extends Create implements HttpGetActionInterface { /** * @var UnavailableProductsProvider @@ -29,29 +42,37 @@ class Reorder extends \Magento\Sales\Controller\Adminhtml\Order\Create */ private $reorderHelper; + /** + * @var LoggerInterface + */ + private $logger; + /** * @param Action\Context $context - * @param \Magento\Catalog\Helper\Product $productHelper - * @param \Magento\Framework\Escaper $escaper + * @param Product $productHelper + * @param Escaper $escaper * @param PageFactory $resultPageFactory * @param ForwardFactory $resultForwardFactory * @param UnavailableProductsProvider $unavailableProductsProvider * @param OrderRepositoryInterface $orderRepository * @param ReorderHelper $reorderHelper + * @param LoggerInterface $logger */ public function __construct( Action\Context $context, - \Magento\Catalog\Helper\Product $productHelper, - \Magento\Framework\Escaper $escaper, + Product $productHelper, + Escaper $escaper, PageFactory $resultPageFactory, ForwardFactory $resultForwardFactory, UnavailableProductsProvider $unavailableProductsProvider, OrderRepositoryInterface $orderRepository, - ReorderHelper $reorderHelper + ReorderHelper $reorderHelper, + LoggerInterface $logger ) { $this->unavailableProductsProvider = $unavailableProductsProvider; $this->orderRepository = $orderRepository; $this->reorderHelper = $reorderHelper; + $this->logger = $logger; parent::__construct( $context, $productHelper, @@ -62,19 +83,21 @@ public function __construct( } /** - * @return \Magento\Backend\Model\View\Result\Forward|\Magento\Backend\Model\View\Result\Redirect + * Adminhtml controller create order. + * + * @return Forward|Redirect */ public function execute() { $this->_getSession()->clearStorage(); $orderId = $this->getRequest()->getParam('order_id'); - /** @var \Magento\Sales\Model\Order $order */ + /** @var Order $order */ $order = $this->orderRepository->get($orderId); if (!$this->reorderHelper->canReorder($order->getEntityId())) { return $this->resultForwardFactory->create()->forward('noroute'); } - /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ + /** @var Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); if (!$order->getId()) { $resultRedirect->setPath('sales/order/'); @@ -90,10 +113,20 @@ public function execute() } $resultRedirect->setPath('sales/order/view', ['order_id' => $orderId]); } else { - $order->setReordered(true); - $this->_getSession()->setUseOldShippingMethod(true); - $this->_getOrderCreateModel()->initFromOrder($order); - $resultRedirect->setPath('sales/*'); + try { + $order->setReordered(true); + $this->_getSession()->setUseOldShippingMethod(true); + $this->_getOrderCreateModel()->initFromOrder($order); + $resultRedirect->setPath('sales/*'); + } catch (\Magento\Framework\Exception\LocalizedException $e) { + $this->logger->critical($e); + $this->messageManager->addErrorMessage($e->getMessage()); + return $resultRedirect->setPath('sales/*'); + } catch (\Exception $e) { + $this->logger->critical($e); + $this->messageManager->addException($e, __('Error while processing order.')); + return $resultRedirect->setPath('sales/*'); + } } return $resultRedirect; diff --git a/app/code/Magento/Sales/Model/AdminOrder/Create.php b/app/code/Magento/Sales/Model/AdminOrder/Create.php index 81f918e455069..d1b3e67815098 100644 --- a/app/code/Magento/Sales/Model/AdminOrder/Create.php +++ b/app/code/Magento/Sales/Model/AdminOrder/Create.php @@ -28,6 +28,12 @@ */ class Create extends \Magento\Framework\DataObject implements \Magento\Checkout\Model\Cart\CartInterface { + /** + * Xml default email domain path + */ + const XML_PATH_DEFAULT_EMAIL_DOMAIN = 'customer/create_account/email_domain'; + + private const XML_PATH_EMAIL_REQUIRED_CREATE_ORDER = 'customer/create_account/email_required_create_order'; /** * Quote session object * @@ -1363,7 +1369,7 @@ 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, @@ -2031,7 +2037,47 @@ protected function _validate() */ protected function _getNewCustomerEmail() { - return $this->getData('account/email'); + $email = $this->getData('account/email'); + + if ($email || $this->isEmailRequired()) { + return $email; + } + + return $this->generateEmail(); + } + + /** + * Check email is require + * + * @return bool + */ + private function isEmailRequired(): bool + { + return (bool)$this->_scopeConfig->getValue( + self::XML_PATH_EMAIL_REQUIRED_CREATE_ORDER, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + $this->_session->getStore()->getId() + ); + } + + /** + * Generate Email + * + * @return string + */ + private function generateEmail(): string + { + $host = $this->_scopeConfig->getValue( + self::XML_PATH_DEFAULT_EMAIL_DOMAIN, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + $account = time(); + $email = $account . '@' . $host; + $account = $this->getData('account'); + $account['email'] = $email; + $this->setData('account', $account); + + return $email; } /** diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/CheckRequiredFieldsNewOrderFormActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/CheckRequiredFieldsNewOrderFormActionGroup.xml index 25936ad3f6002..d3154a4385be8 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/CheckRequiredFieldsNewOrderFormActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/CheckRequiredFieldsNewOrderFormActionGroup.xml @@ -26,7 +26,6 @@ <clearField selector="{{AdminOrderFormBillingAddressSection.Phone}}" stepKey="clearPhoneField"/> <seeElement selector="{{AdminOrderFormPaymentSection.getShippingMethods}}" stepKey="seeShippingMethodNotSelected"/> <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="trySubmitOrder"/> - <see selector="{{AdminOrderFormBillingAddressSection.emailError}}" userInput="This is a required field." stepKey="seeThatEmailIsRequired"/> <see selector="{{AdminOrderFormBillingAddressSection.firstNameError}}" userInput="This is a required field." stepKey="seeFirstNameRequired"/> <see selector="{{AdminOrderFormBillingAddressSection.lastNameError}}" userInput="This is a required field." stepKey="seeLastNameRequired"/> <see selector="{{AdminOrderFormBillingAddressSection.streetAddressError}}" userInput="This is a required field." stepKey="seeStreetRequired"/> diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/VerifyCreatedOrderInformationWithGeneratedEmailActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/VerifyCreatedOrderInformationWithGeneratedEmailActionGroup.xml new file mode 100644 index 0000000000000..9b1666763b2bc --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/VerifyCreatedOrderInformationWithGeneratedEmailActionGroup.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="VerifyCreatedOrderInformationWithGeneratedEmailActionGroup"> + <annotations> + <description>Validate customer email on order page. Starts on order page.</description> + </annotations> + <arguments> + <argument name="email" type="string" defaultValue="{{CustomerEntityOne.email}}"/> + </arguments> + <see selector="{{AdminOrderDetailsInformationSection.customerEmail}}" userInput="{{email}}" stepKey="seeGeneratedEmail"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Sales/Test/Mftf/Data/ConfigData.xml b/app/code/Magento/Sales/Test/Mftf/Data/ConfigData.xml index 730bebc047f93..25e25006d040a 100644 --- a/app/code/Magento/Sales/Test/Mftf/Data/ConfigData.xml +++ b/app/code/Magento/Sales/Test/Mftf/Data/ConfigData.xml @@ -20,4 +20,12 @@ <data key="label">No</data> <data key="value">0</data> </entity> + <entity name="DisableEmailRequiredForOrder"> + <data key="path">customer/create_account/email_required_create_order</data> + <data key="value">0</data> + </entity> + <entity name="EnableEmailRequiredForOrder"> + <data key="path">customer/create_account/email_required_create_order</data> + <data key="value">1</data> + </entity> </entities> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormAccountSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormAccountSection.xml index 430211e52bfb5..65d86c2940bab 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormAccountSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormAccountSection.xml @@ -12,7 +12,7 @@ <element name="group" type="select" selector="#group_id"/> <element name="email" type="input" selector="#email"/> <element name="requiredGroup" type="text" selector=".admin__field.required[data-ui-id='billing-address-fieldset-element-form-field-group-id']"/> - <element name="requiredEmail" type="text" selector=".admin__field.required[data-ui-id='billing-address-fieldset-element-form-field-email']"/> + <element name="requiredEmail" type="text" selector=".admin__field[data-ui-id='billing-address-fieldset-element-form-field-email']"/> <element name="defaultGeneral" type="text" selector="//*[contains(text(),'General')]" timeout="15"/> <element name="emailErrorMessage" type="text" selector="#email-error"/> </section> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormBillingAddressSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormBillingAddressSection.xml index 4fde9db1d21d8..279cf1dde15b3 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormBillingAddressSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormBillingAddressSection.xml @@ -28,8 +28,6 @@ <element name="VatNumber" type="input" selector="#order-billing_address_vat_id" timeout="30"/> <element name="ValidateVatNumber" type="button" selector="#order-billing_address_vat_id + .actions>button.action-default" timeout="30"/> <element name="SaveAddress" type="checkbox" selector="#order-billing_address_save_in_address_book"/> - - <element name="emailError" type="text" selector="#email-error"/> <element name="firstNameError" type="text" selector="#order-billing_address_firstname-error"/> <element name="lastNameError" type="text" selector="#order-billing_address_lastname-error"/> <element name="streetAddressError" type="text" selector="#order-billing_address_street0-error"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminChangeCustomerGroupInNewOrder.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminChangeCustomerGroupInNewOrderTest.xml similarity index 96% rename from app/code/Magento/Sales/Test/Mftf/Test/AdminChangeCustomerGroupInNewOrder.xml rename to app/code/Magento/Sales/Test/Mftf/Test/AdminChangeCustomerGroupInNewOrderTest.xml index cabb6edec2f52..c8b2b66a758eb 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminChangeCustomerGroupInNewOrder.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminChangeCustomerGroupInNewOrderTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminChangeCustomerGroupInNewOrder"> + <test name="AdminChangeCustomerGroupInNewOrderTest"> <annotations> <title value="Customer account group cannot be selected while creating a new customer in order"/> <stories value="MC-15290: Customer account group cannot be selected while creating a new customer in order"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml index e55cdfeb284b4..d7d5036c45c8f 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml @@ -83,7 +83,7 @@ <waitForPageLoad stepKey="waitForCreatedOrderPageOpened"/> <actionGroup ref="GoToInvoiceIntoOrderActionGroup" stepKey="goToInvoiceIntoOrderPage"/> <fillField selector="{{AdminInvoiceItemsSection.qtyToInvoiceColumn}}" userInput="5" stepKey="ChangeQtyToInvoice"/> - <click selector="{{AdminInvoiceItemsSection.updateQty}}" stepKey="updateQunatity"/> + <click selector="{{AdminInvoiceItemsSection.updateQty}}" stepKey="updateQuantity"/> <waitForPageLoad stepKey="waitPageToBeLoaded"/> <actionGroup ref="SubmitInvoiceActionGroup" stepKey="submitInvoice"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithCustomerWithoutEmailTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithCustomerWithoutEmailTest.xml new file mode 100644 index 0000000000000..bb1f5840ed6bf --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithCustomerWithoutEmailTest.xml @@ -0,0 +1,61 @@ +<?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="AdminCreateOrderWithCustomerWithoutEmailTest"> + <annotations> + <title value="Admin Create Order"/> + <stories value="Admin create order with customer without email."/> + <description value="Verify, admin able to create order with customer without email."/> + <severity value="MINOR"/> + <group value="Sales"/> + </annotations> + <before> + <!--Disable required 'email' field on create order page.--> + <magentoCLI command="config:set {{DisableEmailRequiredForOrder.path}} {{DisableEmailRequiredForOrder.value}}" stepKey="disableRequiredFieldEmailForAdminOrderCreation"/> + <!--Create test data.--> + <createData entity="_defaultCategory" stepKey="category"/> + <createData entity="SimpleProduct" stepKey="simpleProduct"> + <requiredEntity createDataKey="category"/> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <!--Clean up created test data.--> + <deleteData createDataKey="simpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="category" stepKey="deleteCategory"/> + <!--Enable required 'email' field on create order page.--> + <magentoCLI command="config:set {{EnableEmailRequiredForOrder.path}} {{EnableEmailRequiredForOrder.value}}" stepKey="enableRequiredFieldEmailForAdminOrderCreation"/> + <actionGroup ref="logout" stepKey="logout"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </after> + + <!--Create order.--> + <actionGroup ref="NavigateToNewOrderPageNewCustomerActionGroup" stepKey="navigateToNewOrderPageNewCustomerActionGroup" /> + <actionGroup ref="AddSimpleProductToOrderActionGroup" stepKey="addSimpleProductToOrder"> + <argument name="product" value="$$simpleProduct$$"/> + <argument name="productQty" value="{{SimpleProduct.quantity}}"/> + </actionGroup> + <!--Fill customer address without 'email'--> + <actionGroup ref="FillOrderCustomerInformationActionGroup" stepKey="fillCustomerInformation"> + <argument name="customer" value="Simple_US_Customer_CA_Without_Email"/> + <argument name="address" value="US_Address_CA"/> + </actionGroup> + <actionGroup ref="OrderSelectFlatRateShippingActionGroup" stepKey="orderSelectFlatRateShippingMethod"/> + <actionGroup ref="AdminSubmitOrderActionGroup" stepKey="submitOrder"/> + <!--Verify, 'email' is generated.--> + <actionGroup ref="VerifyCreatedOrderInformationWithGeneratedEmailActionGroup" stepKey="verifyCustomerEmail"> + <argument name="email" value="@example.com"/> + </actionGroup> + <grabTextFrom selector="{{AdminOrderDetailsInformationSection.customerEmail}}" stepKey="generatedCustomerEmail"/> + <actionGroup ref="DeleteCustomerByEmailActionGroup" stepKey="deleteCustomer"> + <argument name="email" value="$generatedCustomerEmail"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistory.xml b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistoryTest.xml similarity index 98% rename from app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistory.xml rename to app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistoryTest.xml index ad3a411d92414..ceb8c5f9b1aa2 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistory.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistoryTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontRedirectToOrderHistory"> + <test name="StorefrontRedirectToOrderHistoryTest"> <annotations> <features value="Redirection Rules"/> <stories value="Create Invoice"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontVerifySecureURLRedirectSalesTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontVerifySecureURLRedirectSalesTest.xml index 505493e4e5682..d49ea4cfcbec7 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontVerifySecureURLRedirectSalesTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontVerifySecureURLRedirectSalesTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontVerifySecureURLRedirectSales"> + <test name="StorefrontVerifySecureURLRedirectSalesTest"> <annotations> <features value="Sales"/> <stories value="Storefront Secure URLs"/> diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Create/ReorderTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Create/ReorderTest.php index b11d73de736d4..ce777046b584d 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Create/ReorderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Create/ReorderTest.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Sales\Test\Unit\Controller\Adminhtml\Order\Create; use Magento\Backend\App\Action\Context; @@ -14,19 +16,24 @@ use Magento\Framework\App\RequestInterface; use Magento\Framework\Message\ManagerInterface; use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Controller\Adminhtml\Order\Create\Reorder; use Magento\Sales\Model\AdminOrder\Create; use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Reorder\UnavailableProductsProvider; use Magento\Sales\Helper\Reorder as ReorderHelper; +use Magento\Framework\Exception\NoSuchEntityException; +use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject as MockObject; /** - * Class ReorderTest + * Verify reorder class. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.TooManyFields) */ -class ReorderTest extends \PHPUnit\Framework\TestCase +class ReorderTest extends TestCase { /** * @var Reorder @@ -39,67 +46,67 @@ class ReorderTest extends \PHPUnit\Framework\TestCase private $context; /** - * @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject + * @var RequestInterface|MockObject */ private $requestMock; /** - * @var ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ObjectManagerInterface|MockObject */ private $objectManagerMock; /** - * @var Order|\PHPUnit_Framework_MockObject_MockObject + * @var Order|MockObject */ private $orderMock; /** - * @var ManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ManagerInterface|MockObject */ private $messageManagerMock; /** - * @var ForwardFactory|\PHPUnit_Framework_MockObject_MockObject + * @var ForwardFactory|MockObject */ private $resultForwardFactoryMock; /** - * @var RedirectFactory|\PHPUnit_Framework_MockObject_MockObject + * @var RedirectFactory|MockObject */ private $resultRedirectFactoryMock; /** - * @var Redirect|\PHPUnit_Framework_MockObject_MockObject + * @var Redirect|MockObject */ private $resultRedirectMock; /** - * @var Forward|\PHPUnit_Framework_MockObject_MockObject + * @var Forward|MockObject */ private $resultForwardMock; /** - * @var Quote|\PHPUnit_Framework_MockObject_MockObject + * @var Quote|MockObject */ private $quoteSessionMock; /** - * @var OrderRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + * @var OrderRepositoryInterface|MockObject */ private $orderRepositoryMock; /** - * @var ReorderHelper|\PHPUnit_Framework_MockObject_MockObject + * @var ReorderHelper|MockObject */ private $reorderHelperMock; /** - * @var UnavailableProductsProvider|\PHPUnit_Framework_MockObject_MockObject + * @var UnavailableProductsProvider|MockObject */ private $unavailableProductsProviderMock; /** - * @var Create|\PHPUnit_Framework_MockObject_MockObject + * @var Create|MockObject */ private $orderCreateMock; @@ -109,39 +116,33 @@ class ReorderTest extends \PHPUnit\Framework\TestCase private $orderId; /** - * @return void + * @inheritDoc */ protected function setUp() { $this->orderId = 111; - $this->orderRepositoryMock = $this->getMockBuilder(OrderRepositoryInterface::class)->getMockForAbstractClass(); + $this->orderRepositoryMock = $this->createMock(OrderRepositoryInterface::class); + $this->requestMock = $this->createMock(RequestInterface::class); + $this->objectManagerMock = $this->createMock(ObjectManagerInterface::class); + $this->resultForwardFactoryMock = $this->createMock(ForwardFactory::class); + $this->resultRedirectFactoryMock = $this->createMock(RedirectFactory::class); + $this->resultRedirectMock = $this->createMock(Redirect::class); + $this->resultForwardMock = $this->createMock(Forward::class); + $this->reorderHelperMock = $this->createMock(ReorderHelper::class); + $this->unavailableProductsProviderMock = $this->createMock(UnavailableProductsProvider::class); + $this->orderCreateMock = $this->createMock(Create::class); $this->orderMock = $this->getMockBuilder(Order::class) ->disableOriginalConstructor() ->setMethods(['getEntityId', 'getId', 'setReordered']) ->getMock(); - $this->requestMock = $this->getMockBuilder(RequestInterface::class)->getMockForAbstractClass(); - $this->objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class)->getMockForAbstractClass(); - $this->resultForwardFactoryMock = $this->getMockBuilder(ForwardFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $this->resultRedirectFactoryMock = $this->getMockBuilder(RedirectFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $this->resultRedirectMock = $this->getMockBuilder(Redirect::class)->disableOriginalConstructor()->getMock(); - $this->resultForwardMock = $this->getMockBuilder(Forward::class)->disableOriginalConstructor()->getMock(); $this->quoteSessionMock = $this->getMockBuilder(Quote::class) ->disableOriginalConstructor() ->setMethods(['clearStorage', 'setUseOldShippingMethod']) ->getMock(); - $this->reorderHelperMock = $this->getMockBuilder(ReorderHelper::class) - ->disableOriginalConstructor() - ->getMock(); - $this->messageManagerMock = $this->getMockBuilder(ManagerInterface::class)->getMockForAbstractClass(); - $this->unavailableProductsProviderMock = $this->getMockBuilder(UnavailableProductsProvider::class) - ->disableOriginalConstructor() - ->getMock(); - $this->orderCreateMock = $this->getMockBuilder(Create::class)->disableOriginalConstructor()->getMock(); - $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->messageManagerMock = $this->getMockBuilder(ManagerInterface::class) + ->getMockForAbstractClass(); + + $objectManager = new ObjectManager($this); $this->context = $objectManager->getObject( Context::class, [ @@ -165,9 +166,11 @@ protected function setUp() } /** + * Verify execute with no route. + * * @return void */ - public function testExecuteForward() + public function testExecuteForward(): void { $this->clearStorage(); $this->getOrder(); @@ -178,9 +181,11 @@ public function testExecuteForward() } /** + * Verify execute redirect order grid + * * @return void */ - public function testExecuteRedirectOrderGrid() + public function testExecuteRedirectOrderGrid(): void { $this->clearStorage(); $this->getOrder(); @@ -193,9 +198,11 @@ public function testExecuteRedirectOrderGrid() } /** + * Verify execute redirect back. + * * @return void */ - public function testExecuteRedirectBack() + public function testExecuteRedirectBack(): void { $this->clearStorage(); $this->getOrder(); @@ -210,9 +217,11 @@ public function testExecuteRedirectBack() } /** + * Verify execute redirect new order. + * * @return void */ - public function testExecuteRedirectNewOrder() + public function testExecuteRedirectNewOrder(): void { $this->clearStorage(); $this->getOrder(); @@ -227,9 +236,75 @@ public function testExecuteRedirectNewOrder() } /** + * Verify redirect new order with throws exception. + * + * @return void + */ + public function testExecuteRedirectNewOrderWithThrowsException(): void + { + $exception = new NoSuchEntityException(); + + $this->clearStorage(); + $this->getOrder(); + $this->canReorder(true); + $this->createRedirect(); + $this->getOrderId($this->orderId); + $this->getUnavailableProducts([]); + + $this->orderMock->expects($this->once()) + ->method('setReordered') + ->with(true) + ->willThrowException($exception); + $this->messageManagerMock + ->expects($this->once()) + ->method('addErrorMessage') + ->willReturnSelf(); + $this->resultRedirectMock + ->expects($this->once()) + ->method('setPath') + ->with('sales/*') + ->willReturnSelf(); + $this->assertInstanceOf(Redirect::class, $this->reorder->execute()); + } + + /** + * Verify redirect new order with exception. + * * @return void */ - private function clearStorage() + public function testExecuteRedirectNewOrderWithException(): void + { + $exception = new \Exception(); + + $this->clearStorage(); + $this->getOrder(); + $this->canReorder(true); + $this->createRedirect(); + $this->getOrderId($this->orderId); + $this->getUnavailableProducts([]); + $this->orderMock->expects($this->once()) + ->method('setReordered') + ->with(true) + ->willThrowException(new $exception); + $this->messageManagerMock + ->expects($this->once()) + ->method('addException') + ->with($exception, __('Error while processing order.')) + ->willReturnSelf(); + $this->resultRedirectMock + ->expects($this->once()) + ->method('setPath') + ->with('sales/*') + ->willReturnSelf(); + $this->assertInstanceOf(Redirect::class, $this->reorder->execute()); + } + + /** + * Mock clear storage. + * + * @return void + */ + private function clearStorage(): void { $this->objectManagerMock->expects($this->at(0)) ->method('get') @@ -239,9 +314,11 @@ private function clearStorage() } /** + * Mock get order. + * * @return void */ - private function getOrder() + private function getOrder(): void { $this->requestMock->expects($this->once()) ->method('getParam') @@ -254,9 +331,12 @@ private function getOrder() } /** + * Mock and return 'canReorder' method. + * * @param bool $result + * @return void */ - private function canReorder($result) + private function canReorder(bool $result): void { $entityId = 1; $this->orderMock->expects($this->once())->method('getEntityId')->willReturn($entityId); @@ -267,18 +347,22 @@ private function canReorder($result) } /** + * Mock result forward. + * * @return void */ - private function prepareForward() + private function prepareForward(): void { $this->resultForwardFactoryMock->expects($this->once())->method('create')->willReturn($this->resultForwardMock); $this->resultForwardMock->expects($this->once())->method('forward')->with('noroute')->willReturnSelf(); } /** + * Mock create. + * * @return void */ - private function createRedirect() + private function createRedirect(): void { $this->resultRedirectFactoryMock->expects($this->once()) ->method('create') @@ -286,26 +370,35 @@ private function createRedirect() } /** + * Mock order 'getId' method. + * * @param null|int $orderId + * @return void */ - private function getOrderId($orderId) + private function getOrderId($orderId): void { $this->orderMock->expects($this->once())->method('getId')->willReturn($orderId); } /** + * Mock result redirect 'setPath' method. + * * @param string $path * @param null|array $params + * @return void */ - private function setPath($path, $params = []) + private function setPath(string $path, $params = []): void { $this->resultRedirectMock->expects($this->once())->method('setPath')->with($path, $params); } /** + * Mock unavailable products provider. + * * @param array $unavailableProducts + * @return void */ - private function getUnavailableProducts(array $unavailableProducts) + private function getUnavailableProducts(array $unavailableProducts): void { $this->unavailableProductsProviderMock->expects($this->any()) ->method('getForOrder') @@ -314,9 +407,11 @@ private function getUnavailableProducts(array $unavailableProducts) } /** + * Mock init form order. + * * @return void */ - private function initFromOrder() + private function initFromOrder(): void { $this->orderMock->expects($this->once())->method('setReordered')->with(true)->willReturnSelf(); $this->objectManagerMock->expects($this->at(1)) diff --git a/app/code/Magento/Sales/etc/adminhtml/system.xml b/app/code/Magento/Sales/etc/adminhtml/system.xml index 84caeca093bad..a3834b28e05a6 100644 --- a/app/code/Magento/Sales/etc/adminhtml/system.xml +++ b/app/code/Magento/Sales/etc/adminhtml/system.xml @@ -566,5 +566,15 @@ </field> </group> </section> + <section id="customer"> + <group id="create_account" translate="label" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> + <label>Create New Account Options</label> + <field id="email_required_create_order" translate="label comment" type="select" sortOrder="58" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <label>Email is required field for Admin order creation</label> + <comment>If set YES Email field will be required during Admin order creation for new Customer.</comment> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + </field> + </group> + </section> </system> </config> diff --git a/app/code/Magento/Sales/etc/config.xml b/app/code/Magento/Sales/etc/config.xml index 6918146d86337..5127735a329d6 100644 --- a/app/code/Magento/Sales/etc/config.xml +++ b/app/code/Magento/Sales/etc/config.xml @@ -114,5 +114,10 @@ <async_indexing>0</async_indexing> </grid> </dev> + <customer> + <create_account> + <email_required_create_order>1</email_required_create_order> + </create_account> + </customer> </default> </config> diff --git a/app/code/Magento/Sales/i18n/en_US.csv b/app/code/Magento/Sales/i18n/en_US.csv index 970df2770a524..44626027bac69 100644 --- a/app/code/Magento/Sales/i18n/en_US.csv +++ b/app/code/Magento/Sales/i18n/en_US.csv @@ -797,6 +797,8 @@ Created,Created Refunds,Refunds "Allow Zero GrandTotal for Creditmemo","Allow Zero GrandTotal for Creditmemo" "Allow Zero GrandTotal","Allow Zero GrandTotal" +Email is required field for Admin order creation,Email is required field for Admin order creation +If set YES Email field will be required during Admin order creation for new Customer.,If set YES Email field will be required during Admin order creation for new Customer. "Could not save the shipment tracking","Could not save the shipment tracking" "Please enter a coupon code!","Please enter a coupon code!" "Reorder is not available.","Reorder is not available." diff --git a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtml.php b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtml.php index 56c08864c90c4..af28547456a9d 100644 --- a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtml.php +++ b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtml.php @@ -47,6 +47,7 @@ public function execute() if ($model instanceof AbstractCondition) { $model->setJsFormObject($formName); $model->setFormName($formName); + $this->setJsFormObject($model); $html = $model->asHtmlRecursive(); } else { $html = ''; @@ -54,4 +55,32 @@ public function execute() $this->getResponse() ->setBody($html); } + + /** + * Set jsFormObject for the model object + * + * @return void + * @param AbstractCondition $model + */ + private function setJsFormObject(AbstractCondition $model): void + { + $requestJsFormName = $this->getRequest()->getParam('form'); + $actualJsFormName = $this->getJsFormObjectName($model->getFormName()); + if ($requestJsFormName === $actualJsFormName) { //new + $model->setJsFormObject($actualJsFormName); + } else { //edit + $model->setJsFormObject($requestJsFormName); + } + } + + /** + * Get jsFormObject name + * + * @param string $formName + * @return string + */ + private function getJsFormObjectName(string $formName): string + { + return $formName . 'rule_actions_fieldset_'; + } } diff --git a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewConditionHtml.php b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewConditionHtml.php index 50545fd864866..3646f9592c497 100644 --- a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewConditionHtml.php +++ b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewConditionHtml.php @@ -6,11 +6,13 @@ namespace Magento\SalesRule\Controller\Adminhtml\Promo\Quote; use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Rule\Model\Condition\AbstractCondition; +use Magento\SalesRule\Controller\Adminhtml\Promo\Quote; /** * Controller class NewConditionHtml. Returns condition html */ -class NewConditionHtml extends \Magento\SalesRule\Controller\Adminhtml\Promo\Quote implements HttpPostActionInterface +class NewConditionHtml extends Quote implements HttpPostActionInterface { /** * New condition html action @@ -39,13 +41,40 @@ public function execute() $model->setAttribute($typeArr[1]); } - if ($model instanceof \Magento\Rule\Model\Condition\AbstractCondition) { + if ($model instanceof AbstractCondition) { $model->setJsFormObject($this->getRequest()->getParam('form')); $model->setFormName($formName); + $this->setJsFormObject($model); $html = $model->asHtmlRecursive(); } else { $html = ''; } $this->getResponse()->setBody($html); } + + /** + * Set jsFormObject for the model object + * + * @return void + * @param AbstractCondition $model + */ + private function setJsFormObject(AbstractCondition $model): void + { + $requestJsFormName = $this->getRequest()->getParam('form'); + $actualJsFormName = $this->getJsFormObjectName($model->getFormName()); + if ($requestJsFormName === $actualJsFormName) { //new + $model->setJsFormObject($actualJsFormName); + } + } + + /** + * Get jsFormObject name + * + * @param string $formName + * @return string + */ + private function getJsFormObjectName(string $formName): string + { + return $formName . 'rule_conditions_fieldset_'; + } } diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleCountry.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleCountryTest.xml similarity index 99% rename from app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleCountry.xml rename to app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleCountryTest.xml index 832b9ef8bd4b4..1406d4dfbde67 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleCountry.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleCountryTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontCartPriceRuleCountry"> + <test name="StorefrontCartPriceRuleCountryTest"> <annotations> <features value="SalesRule"/> <stories value="Create cart price rule"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRulePostcode.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRulePostcodeTest.xml similarity index 99% rename from app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRulePostcode.xml rename to app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRulePostcodeTest.xml index 9882b04bdc956..aade41b30284c 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRulePostcode.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRulePostcodeTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontCartPriceRulePostcode"> + <test name="StorefrontCartPriceRulePostcodeTest"> <annotations> <features value="SalesRule"/> <stories value="Create cart price rule"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleQuantity.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleQuantityTest.xml similarity index 95% rename from app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleQuantity.xml rename to app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleQuantityTest.xml index 08a7bd72cd18d..283d22351b1f1 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleQuantity.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleQuantityTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontCartPriceRuleQuantity"> + <test name="StorefrontCartPriceRuleQuantityTest"> <annotations> <features value="SalesRule"/> <stories value="Create cart price rule"/> @@ -24,7 +24,7 @@ <createData entity="_defaultProduct" stepKey="createPreReqProduct"> <requiredEntity createDataKey="createPreReqCategory"/> </createData> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> </before> @@ -62,8 +62,8 @@ <fillField selector="{{AdminCartPriceRulesFormSection.discountAmount}}" userInput="1.00" stepKey="fillDiscountAmount"/> <click selector="{{AdminCartPriceRulesFormSection.save}}" stepKey="clickSaveButton"/> <see selector="{{AdminCartPriceRulesSection.messages}}" userInput="You saved the rule." stepKey="seeSuccessMessage"/> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> - + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> + <!-- Add 1 product to the cart --> <amOnPage url="$$createPreReqProduct.name$$.html" stepKey="goToProductPage"/> <waitForPageLoad stepKey="waitForProductPageLoad"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleState.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleStateTest.xml similarity index 99% rename from app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleState.xml rename to app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleStateTest.xml index 19ffb7c36f992..fafede4120573 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleState.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleStateTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontCartPriceRuleState"> + <test name="StorefrontCartPriceRuleStateTest"> <annotations> <features value="SalesRule"/> <stories value="Create cart price rule"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotal.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotalTest.xml similarity index 97% rename from app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotal.xml rename to app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotalTest.xml index 94e53cfc88047..a32d42e26d15f 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotal.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotalTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontCartPriceRuleSubtotal"> + <test name="StorefrontCartPriceRuleSubtotalTest"> <annotations> <features value="SalesRule"/> <stories value="Create cart price rule"/> @@ -24,7 +24,7 @@ <createData entity="_defaultProduct" stepKey="createPreReqProduct"> <requiredEntity createDataKey="createPreReqCategory"/> </createData> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> </before> diff --git a/app/code/Magento/SendFriendGraphQl/Model/Resolver/SendEmailToFriend.php b/app/code/Magento/SendFriendGraphQl/Model/Resolver/SendEmailToFriend.php index 0a4fe1e3e5616..ebc1981ca965c 100644 --- a/app/code/Magento/SendFriendGraphQl/Model/Resolver/SendEmailToFriend.php +++ b/app/code/Magento/SendFriendGraphQl/Model/Resolver/SendEmailToFriend.php @@ -48,8 +48,14 @@ public function __construct( */ public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) { + $storeId = $context->getExtensionAttributes()->getStore()->getId(); + + if (!$this->sendFriendHelper->isEnabled($storeId)) { + throw new GraphQlInputException(__('"Email to a Friend" is not enabled.')); + } + /** @var ContextInterface $context */ - if (!$this->sendFriendHelper->isAllowForGuest() + if (!$this->sendFriendHelper->isAllowForGuest($storeId) && false === $context->getExtensionAttributes()->getIsCustomer() ) { throw new GraphQlAuthorizationException(__('The current customer isn\'t authorized.')); diff --git a/app/code/Magento/SendFriendGraphQl/Model/Resolver/SendFriendConfiguration.php b/app/code/Magento/SendFriendGraphQl/Model/Resolver/SendFriendConfiguration.php new file mode 100644 index 0000000000000..7149dccdec834 --- /dev/null +++ b/app/code/Magento/SendFriendGraphQl/Model/Resolver/SendFriendConfiguration.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\SendFriendGraphQl\Model\Resolver; + +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\SendFriend\Helper\Data as SendFriendHelper; + +/** + * Resolve Store Config information for SendFriend + */ +class SendFriendConfiguration implements ResolverInterface +{ + /** + * @var SendFriendHelper + */ + private $sendFriendHelper; + + /** + * @param SendFriendHelper $sendFriendHelper + */ + public function __construct(SendFriendHelper $sendFriendHelper) + { + $this->sendFriendHelper = $sendFriendHelper; + } + + /** + * @inheritDoc + */ + public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) + { + $store = $context->getExtensionAttributes()->getStore(); + $storeId = $store->getId(); + + return [ + 'enabled_for_customers' => $this->sendFriendHelper->isEnabled($storeId), + 'enabled_for_guests' => $this->sendFriendHelper->isAllowForGuest($storeId) + ]; + } +} diff --git a/app/code/Magento/SendFriendGraphQl/etc/schema.graphqls b/app/code/Magento/SendFriendGraphQl/etc/schema.graphqls index 1234b65a7b910..f4967779f3822 100644 --- a/app/code/Magento/SendFriendGraphQl/etc/schema.graphqls +++ b/app/code/Magento/SendFriendGraphQl/etc/schema.graphqls @@ -37,3 +37,12 @@ type SendEmailToFriendRecipient { name: String! email: String! } + +type StoreConfig { + send_friend: SendFriendConfiguration @resolver(class: "\\Magento\\SendFriendGraphQl\\Model\\Resolver\\SendFriendConfiguration") @doc(description: "Email to a Friend configuration.") +} + +type SendFriendConfiguration { + enabled_for_customers: Boolean! @doc(description: "Indicates whether the Email to a Friend feature is enabled.") + enabled_for_guests: Boolean! @doc(description: "Indicates whether the Email to a Friend feature is enabled for guests.") +} diff --git a/app/code/Magento/Swatches/Test/Mftf/ActionGroup/AdminUpdateAttributeInputTypeActionGroup.xml b/app/code/Magento/Swatches/Test/Mftf/ActionGroup/AdminUpdateAttributeInputTypeActionGroup.xml new file mode 100644 index 0000000000000..23264601cad94 --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/ActionGroup/AdminUpdateAttributeInputTypeActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminUpdateAttributeInputTypeActionGroup"> + <annotations> + <description>Set value for the "Catalog Input Type for Store Owner" attribute option</description> + </annotations> + <arguments> + <argument name="value" type="string" defaultValue="swatch_visual"/> + </arguments> + <selectOption selector="{{AttributePropertiesSection.InputType}}" userInput="{{value}}" stepKey="setInputType"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Swatches/Test/Mftf/Section/AdminManageSwatchSection.xml b/app/code/Magento/Swatches/Test/Mftf/Section/AdminManageSwatchSection.xml index 629599eba84fe..ab4502f3e9cfb 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Section/AdminManageSwatchSection.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Section/AdminManageSwatchSection.xml @@ -21,6 +21,7 @@ <element name="chooserBlock" type="block" selector="#swatch-visual-options-panel table tbody tr:nth-of-type({{var}}) .swatches-visual-col .swatch_sub-menu_container" parameterized="true"/> <!-- Selector for Admin Description input where the index is zero-based --> <element name="swatchAdminDescriptionByIndex" type="input" selector="input[name='optiontext[value][option_{{index}}][0]']" parameterized="true"/> + <element name="swatchWindow" type="button" selector="#swatch_window_option_option_{{var}}" parameterized="true"/> <element name="nthChooseColor" type="button" selector="#swatch-visual-options-panel table tbody tr:nth-of-type({{var}}) .swatch_row_name.colorpicker_handler" parameterized="true"/> <element name="nthUploadFile" type="button" selector="#swatch-visual-options-panel table tbody tr:nth-of-type({{var}}) .swatch_row_name.btn_choose_file_upload" parameterized="true"/> <element name="nthDelete" type="button" selector="#swatch-visual-options-panel table tbody tr:nth-of-type({{var}}) button.delete-option" parameterized="true"/> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateImageSwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateImageSwatchTest.xml index e7882675866d4..ff1286d8092fc 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateImageSwatchTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateImageSwatchTest.xml @@ -53,6 +53,7 @@ <click selector="{{AdminManageSwatchSection.nthUploadFile('1')}}" stepKey="clickUploadFile1"/> <attachFile selector="input[name='datafile']" userInput="adobe-thumb.jpg" stepKey="attachFile1"/> <fillField selector="{{AdminManageSwatchSection.adminInputByIndex('0')}}" userInput="adobe-thumb" stepKey="fillAdmin1"/> + <click selector="{{AdminManageSwatchSection.swatchWindow('0')}}" stepKey="clicksWatchWindow1"/> <!-- Set swatch image #2 --> <click selector="{{AdminManageSwatchSection.addSwatch}}" stepKey="clickAddSwatch2"/> @@ -62,6 +63,7 @@ <click selector="{{AdminManageSwatchSection.nthUploadFile('2')}}" stepKey="clickUploadFile2"/> <attachFile selector="input[name='datafile']" userInput="adobe-small.jpg" stepKey="attachFile2"/> <fillField selector="{{AdminManageSwatchSection.adminInputByIndex('1')}}" userInput="adobe-small" stepKey="fillAdmin2"/> + <click selector="{{AdminManageSwatchSection.swatchWindow('1')}}" stepKey="clicksWatchWindow2"/> <!-- Set swatch image #3 --> <click selector="{{AdminManageSwatchSection.addSwatch}}" stepKey="clickAddSwatch3"/> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontConfigurableSwatchOptionsThumbImagesTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontConfigurableSwatchOptionsThumbImagesTest.xml new file mode 100644 index 0000000000000..8c90d88f51a10 --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontConfigurableSwatchOptionsThumbImagesTest.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontConfigurableSwatchOptionsThumbImagesTest" + extends="StorefrontConfigurableOptionsThumbImagesTest"> + <annotations> + <features value="Swatches"/> + <stories value="Configurable product with swatch attribute"/> + <title value="Check thumbnail images and active image for Configurable Product with swatch attribute"/> + <description value="Login as admin, create attribute with two options, configurable product with two + associated simple products. Add few images for products, check the fotorama thumbnail images + (visible and active) for each selected option for the configurable product"/> + <group value="swatches"/> + </annotations> + <before> + <!-- Go to created attribute (attribute page) --> + <actionGroup ref="NavigateToEditProductAttributeActionGroup" stepKey="navigateToSkuProductAttribute" after="createConfigProductOption"> + <argument name="ProductAttribute" value="$$createConfigProductAttribute.default_frontend_label$$"/> + </actionGroup> + + <!-- Set 'swatch_visual' value for option "Catalog Input Type for Store Owner" --> + <actionGroup ref="AdminUpdateAttributeInputTypeActionGroup" stepKey="selectSwatchVisualInputType" after="navigateToSkuProductAttribute"/> + + <!-- Set 'yes' value for option "Update Product Preview Image" --> + <actionGroup ref="AdminUpdateProductPreviewImageActionGroup" stepKey="setUpdateProductPreviewImage" after="selectSwatchVisualInputType"/> + + <!-- Save Product Attribute --> + <actionGroup ref="SaveProductAttributeActionGroup" stepKey="saveAttribute" after="setUpdateProductPreviewImage"/> + </before> + + <!-- Select first option --> + <actionGroup ref="StorefrontSelectSwatchOptionOnProductPageActionGroup" stepKey="selectFirstOptionValue"> + <argument name="optionName" value="$$getConfigAttributeOption1.label$$"/> + </actionGroup> + + <!-- Select second option --> + <actionGroup ref="StorefrontSelectSwatchOptionOnProductPageActionGroup" stepKey="selectSecondOptionValue"> + <argument name="optionName" value="$$getConfigAttributeOption2.label$$"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml index 427797bdb09e2..39b3ca51327ba 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml @@ -55,6 +55,7 @@ <click selector="{{AdminManageSwatchSection.nthUploadFile('1')}}" stepKey="clickUploadFile1"/> <attachFile selector="input[name='datafile']" userInput="adobe-thumb.jpg" stepKey="attachFile1"/> <fillField selector="{{AdminManageSwatchSection.adminInputByIndex('0')}}" userInput="adobe-thumb" stepKey="fillAdmin1"/> + <click selector="{{AdminManageSwatchSection.swatchWindow('0')}}" stepKey="clicksWatchWindow1"/> <!-- Set swatch #2 image using the file upload --> <click selector="{{AdminManageSwatchSection.addSwatch}}" stepKey="clickAddSwatch2"/> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSelectedByQueryParamsConfigurableSwatchOptionsThumbImagesTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSelectedByQueryParamsConfigurableSwatchOptionsThumbImagesTest.xml new file mode 100644 index 0000000000000..8e4c6c0217b3a --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSelectedByQueryParamsConfigurableSwatchOptionsThumbImagesTest.xml @@ -0,0 +1,54 @@ +<?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="StorefrontSelectedByQueryParamsConfigurableSwatchOptionsThumbImagesTest" + extends="StorefrontConfigurableOptionsThumbImagesTest"> + <annotations> + <features value="Swatches"/> + <stories value="Configurable product with swatch attribute"/> + <title value="Check thumbnail images and active image for Configurable Product with swatch attribute + with predefined by query params options"/> + <description value="Login as admin, create swatch attribute with two options, configurable product with two + associated simple products. Add few images for products, check the fotorama thumbnail images + (visible and active) for each option for the configurable product using product URL with params + to selected needed option."/> + <group value="swatches"/> + </annotations> + <before> + <!-- Go to created attribute (attribute page) --> + <actionGroup ref="NavigateToEditProductAttributeActionGroup" stepKey="navigateToSkuProductAttribute" after="createConfigProductOption"> + <argument name="ProductAttribute" value="$$createConfigProductAttribute.default_frontend_label$$"/> + </actionGroup> + + <!-- Set 'swatch_visual' value for option "Catalog Input Type for Store Owner" --> + <actionGroup ref="AdminUpdateAttributeInputTypeActionGroup" stepKey="selectSwatchVisualInputType" after="navigateToSkuProductAttribute"/> + + <!-- Set 'yes' value for option "Update Product Preview Image" --> + <actionGroup ref="AdminUpdateProductPreviewImageActionGroup" stepKey="setUpdateProductPreviewImage" after="selectSwatchVisualInputType"/> + + <!-- Save Product Attribute --> + <actionGroup ref="SaveProductAttributeActionGroup" stepKey="saveAttribute" after="setUpdateProductPreviewImage"/> + </before> + + <!-- Select first option using product query params URL --> + <amOnPage + url="$$createConfigProduct.sku$$.html#$$createConfigProductAttribute.attribute_id$$=$$getConfigAttributeOption1.value$$" + stepKey="selectFirstOptionValue"/> + <reloadPage stepKey="selectFirstOptionValueRefreshPage" after="selectFirstOptionValue"/> + <waitForPageLoad stepKey="waitForProductWithSelectedFirstOptionToLoad" after="selectFirstOptionValueRefreshPage"/> + + <!-- Select second option using product query params URL --> + <amOnPage + url="$$createConfigProduct.sku$$.html#$$createConfigProductAttribute.attribute_id$$=$$getConfigAttributeOption2.value$$" + stepKey="selectSecondOptionValue"/> + <reloadPage stepKey="selectSecondOptionValueRefreshPage" after="selectSecondOptionValue"/> + <waitForPageLoad stepKey="waitForProductWithSelectedSecondOptionToLoad" after="selectSecondOptionValueRefreshPage"/> + </test> +</tests> diff --git a/app/code/Magento/Swatches/view/base/web/js/swatch-renderer.js b/app/code/Magento/Swatches/view/base/web/js/swatch-renderer.js index 8b5dfcd80deb4..3e100d2c39168 100644 --- a/app/code/Magento/Swatches/view/base/web/js/swatch-renderer.js +++ b/app/code/Magento/Swatches/view/base/web/js/swatch-renderer.js @@ -1267,6 +1267,14 @@ define([ isInitial; if (isInProductView) { + if (_.isUndefined(gallery)) { + context.find(this.options.mediaGallerySelector).on('gallery:loaded', function () { + this.updateBaseImage(images, context, isInProductView); + }.bind(this)); + + return; + } + imagesToUpdate = images.length ? this._setImageType($.extend(true, [], images)) : []; isInitial = _.isEqual(imagesToUpdate, initialImages); @@ -1276,32 +1284,36 @@ define([ imagesToUpdate = this._setImageIndex(imagesToUpdate); - if (!_.isUndefined(gallery)) { - gallery.updateData(imagesToUpdate); - } else { - context.find(this.options.mediaGallerySelector).on('gallery:loaded', function (loadedGallery) { - loadedGallery = context.find(this.options.mediaGallerySelector).data('gallery'); - loadedGallery.updateData(imagesToUpdate); - }.bind(this)); - } - - if (isInitial) { - $(this.options.mediaGallerySelector).AddFotoramaVideoEvents(); - } else { - $(this.options.mediaGallerySelector).AddFotoramaVideoEvents({ - selectedOption: this.getProduct(), - dataMergeStrategy: this.options.gallerySwitchStrategy - }); - } - - if (gallery) { - gallery.first(); - } + gallery.updateData(imagesToUpdate); + this._addFotoramaVideoEvents(isInitial); } else if (justAnImage && justAnImage.img) { context.find('.product-image-photo').attr('src', justAnImage.img); } }, + /** + * Add video events + * + * @param {Boolean} isInitial + * @private + */ + _addFotoramaVideoEvents: function (isInitial) { + if (_.isUndefined($.mage.AddFotoramaVideoEvents)) { + return; + } + + if (isInitial) { + $(this.options.mediaGallerySelector).AddFotoramaVideoEvents(); + + return; + } + + $(this.options.mediaGallerySelector).AddFotoramaVideoEvents({ + selectedOption: this.getProduct(), + dataMergeStrategy: this.options.gallerySwitchStrategy + }); + }, + /** * Set correct indexes for image set. * diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateInvalidPostcodeTestLength.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateInvalidPostcodeTestLengthTest.xml similarity index 97% rename from app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateInvalidPostcodeTestLength.xml rename to app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateInvalidPostcodeTestLengthTest.xml index a98de31b42f81..3befada509afa 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateInvalidPostcodeTestLength.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateInvalidPostcodeTestLengthTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminCreateTaxRateInvalidPostcodeTestLength"> + <test name="AdminCreateTaxRateInvalidPostcodeTestLengthTest"> <annotations> <stories value="Create tax rate"/> <title value="Create tax rate, invalid post code length"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest.xml index 3d584f988780e..70211a1080951 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontTaxQuoteCartLoggedInSimple"> + <test name="StorefrontTaxQuoteCartLoggedInSimpleTest"> <annotations> <features value="Tax"/> <stories value="Tax Calculation in Shopping Cart"/> @@ -127,7 +127,7 @@ </test> - <test name="StorefrontTaxQuoteCartLoggedInVirtual"> + <test name="StorefrontTaxQuoteCartLoggedInVirtualTest"> <annotations> <features value="Tax"/> <stories value="Tax Calculation in Shopping Cart"/> @@ -243,7 +243,7 @@ <see stepKey="seeTotalExcl3" selector="{{CheckoutPaymentSection.orderSummaryTotalExcluding}}" userInput="$$virtualProduct1.price$$"/> </test> - <test name="StorefrontTaxQuoteCartGuestSimple"> + <test name="StorefrontTaxQuoteCartGuestSimpleTest"> <annotations> <features value="Tax"/> <stories value="Tax Calculation in Shopping Cart"/> @@ -355,7 +355,7 @@ <see stepKey="seeTotalExcl" selector="{{CheckoutPaymentSection.orderSummaryTotalExcluding}}" userInput="$128.00"/> </test> - <test name="StorefrontTaxQuoteCartGuestVirtual"> + <test name="StorefrontTaxQuoteCartGuestVirtualTest"> <annotations> <features value="Tax"/> <stories value="Tax Calculation in Shopping Cart"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest.xml index 050ab3889984b..4c3ab91cf909c 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontTaxQuoteCheckoutGuestVirtual"> + <test name="StorefrontTaxQuoteCheckoutGuestVirtualTest"> <annotations> <features value="Tax"/> <stories value="Tax Calculation in One Page Checkout"/> @@ -114,7 +114,7 @@ <see stepKey="seeTotalExcl" selector="{{CheckoutPaymentSection.orderSummaryTotalExcluding}}" userInput="$$virtualProduct1.price$$"/> </test> - <test name="StorefrontTaxQuoteCheckoutLoggedInSimple"> + <test name="StorefrontTaxQuoteCheckoutLoggedInSimpleTest"> <annotations> <features value="Tax"/> <stories value="Tax Calculation in One Page Checkout"/> @@ -236,7 +236,7 @@ <see stepKey="seeTotalExcl2" selector="{{CheckoutPaymentSection.orderSummaryTotalExcluding}}" userInput="$128.00"/> </test> - <test name="StorefrontTaxQuoteCheckoutGuestSimple"> + <test name="StorefrontTaxQuoteCheckoutGuestSimpleTest"> <annotations> <features value="Tax"/> <stories value="Tax Calculation in One Page Checkout"/> @@ -353,7 +353,7 @@ <see stepKey="seeTotalExcl" selector="{{CheckoutPaymentSection.orderSummaryTotalExcluding}}" userInput="$128.00"/> </test> - <test name="StorefrontTaxQuoteCheckoutLoggedInVirtual"> + <test name="StorefrontTaxQuoteCheckoutLoggedInVirtualTest"> <annotations> <features value="Tax"/> <stories value="Tax Calculation in One Page Checkout"/> diff --git a/app/code/Magento/Theme/view/frontend/requirejs-config.js b/app/code/Magento/Theme/view/frontend/requirejs-config.js index b5ffd358c893b..e14c93d329a07 100644 --- a/app/code/Magento/Theme/view/frontend/requirejs-config.js +++ b/app/code/Magento/Theme/view/frontend/requirejs-config.js @@ -27,9 +27,7 @@ var config = { 'menu': 'mage/menu', 'popupWindow': 'mage/popup-window', 'validation': 'mage/validation/validation', - 'welcome': 'Magento_Theme/js/view/welcome', 'breadcrumbs': 'Magento_Theme/js/view/breadcrumbs', - 'criticalCssLoader': 'Magento_Theme/js/view/critical-css-loader', 'jquery/ui': 'jquery/compat', 'cookieStatus': 'Magento_Theme/js/cookie-status' } diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest.xml index 81cf72a660eda..cdabcc31fd856 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest.xml @@ -127,7 +127,7 @@ <seeElement selector="{{AdminUrlRewriteIndexSection.gridCellByColumnValue('Request Path', 'category-dutch/productformagetwo68980-dutch.html')}}" stepKey="seeUrlInRequestPathColumn5"/> <seeElement selector="{{AdminUrlRewriteIndexSection.gridCellByColumnValue('Target Path', catalog/product/view/id/$grabProductIdFromUrl/category/$$createCategory.id$$)}}" stepKey="seeUrlInTargetPathColumn5"/> </test> - <test name="AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTestWithConfigurationTurnedOff"> + <test name="AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTestWithConfigurationTurnedOffTest"> <annotations> <features value="Url Rewrite"/> <stories value="Url Rewrites for Multiple Storeviews"/> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductInAnchorCategoriesTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductInAnchorCategoriesTest.xml index 497ab653a0594..98c85114631aa 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductInAnchorCategoriesTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductInAnchorCategoriesTest.xml @@ -83,7 +83,7 @@ <seeElement selector="{{AdminUrlRewriteIndexSection.gridCellByColumnValue('Request Path', $simpleSubCategory1.custom_attributes[url_key]$-new/$simpleSubCategory2.custom_attributes[url_key]$/$simpleSubCategory3.custom_attributes[url_key]$/$createSimpleProduct.custom_attributes[url_key]$.html)}}" stepKey="seeInListValue7"/> </test> - <test name="AdminUrlRewritesForProductInAnchorCategoriesTestWithConfigurationTurnedOff"> + <test name="AdminUrlRewritesForProductInAnchorCategoriesTestWithConfigurationTurnedOffTest"> <annotations> <features value="Url Rewrite"/> <stories value="Url-rewrites for product in anchor categories"/> @@ -187,7 +187,7 @@ <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="$$createSimpleProduct.name$$" stepKey="seeProductName7"/> </test> - <test name="AdminUrlRewritesForProductInAnchorCategoriesTestAllStoreView" extends="AdminUrlRewritesForProductInAnchorCategoriesTest"> + <test name="AdminUrlRewritesForProductInAnchorCategoriesTestAllStoreViewTest" extends="AdminUrlRewritesForProductInAnchorCategoriesTest"> <annotations> <features value="Url Rewrite"/> <stories value="Url-rewrites for product in anchor categories for all store views"/> @@ -214,7 +214,7 @@ <remove keyForRemoval="uncheckRedirect2"/> </test> - <test name="AdminUrlRewritesForProductInAnchorCategoriesTestAllStoreViewWithConfigurationTurnedOff" extends="AdminUrlRewritesForProductInAnchorCategoriesTestWithConfigurationTurnedOff"> + <test name="AdminUrlRewritesForProductInAnchorCategoriesTestAllStoreViewWithConfigurationTurnedOffTest" extends="AdminUrlRewritesForProductInAnchorCategoriesTestWithConfigurationTurnedOffTest"> <annotations> <features value="Url Rewrite"/> <stories value="Url-rewrites for product in anchor categories for all store views"/> @@ -241,7 +241,7 @@ <remove keyForRemoval="uncheckRedirect2"/> </test> - <test name="AdminUrlRewritesForProductsWithConfigurationTurnedOff"> + <test name="AdminUrlRewritesForProductsWithConfigurationTurnedOffTest"> <annotations> <features value="Url Rewrite"/> <stories value="No Url-rewrites for product if configuration to generate url rewrite for Generate 'category/product' URL Rewrites is enabled "/> diff --git a/app/code/Magento/Vault/Test/Mftf/Test/StorefrontVerifySecureURLRedirectVaultTest.xml b/app/code/Magento/Vault/Test/Mftf/Test/StorefrontVerifySecureURLRedirectVaultTest.xml index c9d4cb3391cfd..f496e500a4d9b 100644 --- a/app/code/Magento/Vault/Test/Mftf/Test/StorefrontVerifySecureURLRedirectVaultTest.xml +++ b/app/code/Magento/Vault/Test/Mftf/Test/StorefrontVerifySecureURLRedirectVaultTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontVerifySecureURLRedirectVault"> + <test name="StorefrontVerifySecureURLRedirectVaultTest"> <annotations> <features value="Vault"/> <stories value="Storefront Secure URLs"/> diff --git a/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php b/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php index 07d1b4e07fe9d..723e274d1e5fa 100644 --- a/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php +++ b/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php @@ -8,7 +8,6 @@ use Magento\Framework\Webapi\ServiceInputProcessor; use Magento\Framework\Webapi\Rest\Request as RestRequest; -use Magento\Webapi\Controller\Rest\Router; use Magento\Webapi\Controller\Rest\Router\Route; /** @@ -81,7 +80,20 @@ public function resolve() $route = $this->getRoute(); $serviceMethodName = $route->getServiceMethod(); $serviceClassName = $route->getServiceClass(); + $inputData = $this->getInputData(); + return $this->serviceInputProcessor->process($serviceClassName, $serviceMethodName, $inputData); + } + /** + * Get API input data + * + * @return array + */ + public function getInputData() + { + $route = $this->getRoute(); + $serviceMethodName = $route->getServiceMethod(); + $serviceClassName = $route->getServiceClass(); /* * Valid only for updates using PUT when passing id value both in URL and body */ @@ -97,9 +109,7 @@ public function resolve() $inputData = $this->request->getRequestData(); } - $inputData = $this->paramsOverrider->override($inputData, $route->getParameters()); - $inputParams = $this->serviceInputProcessor->process($serviceClassName, $serviceMethodName, $inputData); - return $inputParams; + return $this->paramsOverrider->override($inputData, $route->getParameters()); } /** diff --git a/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php b/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php index 93bddd09faef8..064bd99b9b6bf 100644 --- a/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php +++ b/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php @@ -8,12 +8,12 @@ namespace Magento\WebapiAsync\Controller\Rest\Asynchronous; -use Magento\Framework\Webapi\ServiceInputProcessor; use Magento\Framework\Webapi\Rest\Request as RestRequest; -use Magento\Webapi\Controller\Rest\Router; +use Magento\Framework\Webapi\ServiceInputProcessor; +use Magento\Webapi\Controller\Rest\InputParamsResolver as WebapiInputParamsResolver; use Magento\Webapi\Controller\Rest\ParamsOverrider; use Magento\Webapi\Controller\Rest\RequestValidator; -use Magento\Webapi\Controller\Rest\InputParamsResolver as WebapiInputParamsResolver; +use Magento\Webapi\Controller\Rest\Router; /** * This class is responsible for retrieving resolved input data @@ -96,6 +96,22 @@ public function resolve() } $this->requestValidator->validate(); $webapiResolvedParams = []; + foreach ($this->getInputData() as $key => $singleEntityParams) { + $webapiResolvedParams[$key] = $this->resolveBulkItemParams($singleEntityParams); + } + return $webapiResolvedParams; + } + + /** + * Get API input data + * + * @return array + */ + public function getInputData() + { + if ($this->isBulk === false) { + return [$this->inputParamsResolver->getInputData()]; + } $inputData = $this->request->getRequestData(); $httpMethod = $this->request->getHttpMethod(); @@ -103,12 +119,7 @@ public function resolve() $requestBodyParams = $this->request->getBodyParams(); $inputData = array_merge($requestBodyParams, $inputData); } - - foreach ($inputData as $key => $singleEntityParams) { - $webapiResolvedParams[$key] = $this->resolveBulkItemParams($singleEntityParams); - } - - return $webapiResolvedParams; + return $inputData; } /** diff --git a/app/code/Magento/WebapiAsync/Model/Config.php b/app/code/Magento/WebapiAsync/Model/Config.php index 7980be479dfa5..7329862ca528c 100644 --- a/app/code/Magento/WebapiAsync/Model/Config.php +++ b/app/code/Magento/WebapiAsync/Model/Config.php @@ -3,35 +3,34 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - declare(strict_types=1); namespace Magento\WebapiAsync\Model; +use Magento\AsynchronousOperations\Model\ConfigInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Serialize\SerializerInterface; use Magento\Webapi\Model\Cache\Type\Webapi as WebapiCache; use Magento\Webapi\Model\Config as WebapiConfig; -use Magento\Framework\App\ObjectManager; -use Magento\Framework\Serialize\SerializerInterface; -use Magento\Framework\Exception\LocalizedException; use Magento\Webapi\Model\Config\Converter; /** * Class for accessing to Webapi_Async configuration. */ -class Config implements \Magento\AsynchronousOperations\Model\ConfigInterface +class Config implements ConfigInterface { /** - * @var \Magento\Webapi\Model\Cache\Type\Webapi + * @var WebapiCache */ private $cache; /** - * @var \Magento\Webapi\Model\Config + * @var WebapiConfig */ private $webApiConfig; /** - * @var \Magento\Framework\Serialize\SerializerInterface + * @var SerializerInterface */ private $serializer; @@ -43,18 +42,18 @@ class Config implements \Magento\AsynchronousOperations\Model\ConfigInterface /** * Initialize dependencies. * - * @param \Magento\Webapi\Model\Cache\Type\Webapi $cache - * @param \Magento\Webapi\Model\Config $webApiConfig - * @param \Magento\Framework\Serialize\SerializerInterface|null $serializer + * @param WebapiCache $cache + * @param WebapiConfig $webApiConfig + * @param SerializerInterface $serializer */ public function __construct( WebapiCache $cache, WebapiConfig $webApiConfig, - SerializerInterface $serializer = null + SerializerInterface $serializer ) { $this->cache = $cache; $this->webApiConfig = $webApiConfig; - $this->serializer = $serializer ? : ObjectManager::getInstance()->get(SerializerInterface::class); + $this->serializer = $serializer; } /** diff --git a/app/code/Magento/WebapiAsync/Model/OperationRepository.php b/app/code/Magento/WebapiAsync/Model/OperationRepository.php new file mode 100644 index 0000000000000..695cab2ae4402 --- /dev/null +++ b/app/code/Magento/WebapiAsync/Model/OperationRepository.php @@ -0,0 +1,102 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\WebapiAsync\Model; + +use Magento\AsynchronousOperations\Api\Data\OperationInterface; +use Magento\AsynchronousOperations\Api\Data\OperationInterfaceFactory; +use Magento\AsynchronousOperations\Model\OperationRepositoryInterface; +use Magento\Framework\MessageQueue\MessageValidator; +use Magento\Framework\Serialize\Serializer\Json; +use Magento\Framework\EntityManager\EntityManager; +use Magento\WebapiAsync\Controller\Rest\Asynchronous\InputParamsResolver; + +/** + * Repository class to create operation + */ +class OperationRepository implements OperationRepositoryInterface +{ + /** + * @var OperationInterfaceFactory + */ + private $operationFactory; + + /** + * @var Json + */ + private $jsonSerializer; + + /** + * @var EntityManager + */ + private $entityManager; + + /** + * @var MessageValidator + */ + private $messageValidator; + /** + * @var InputParamsResolver + */ + private $inputParamsResolver; + + /** + * Initialize dependencies. + * + * @param OperationInterfaceFactory $operationFactory + * @param EntityManager $entityManager + * @param MessageValidator $messageValidator + * @param Json $jsonSerializer + * @param InputParamsResolver $inputParamsResolver + */ + public function __construct( + OperationInterfaceFactory $operationFactory, + EntityManager $entityManager, + MessageValidator $messageValidator, + Json $jsonSerializer, + InputParamsResolver $inputParamsResolver + ) { + $this->operationFactory = $operationFactory; + $this->jsonSerializer = $jsonSerializer; + $this->messageValidator = $messageValidator; + $this->entityManager = $entityManager; + $this->inputParamsResolver = $inputParamsResolver; + } + + /** + * @inheritDoc + */ + public function create($topicName, $entityParams, $groupId, $operationId): OperationInterface + { + $this->messageValidator->validate($topicName, $entityParams); + $requestData = $this->inputParamsResolver->getInputData(); + if ($operationId === null || !isset($requestData[$operationId])) { + throw new \InvalidArgumentException( + 'Parameter "$operationId" must not be NULL and must exist in input data' + ); + } + $encodedMessage = $this->jsonSerializer->serialize($requestData[$operationId]); + + $serializedData = [ + 'entity_id' => null, + 'entity_link' => '', + 'meta_information' => $encodedMessage, + ]; + $data = [ + 'data' => [ + OperationInterface::BULK_ID => $groupId, + OperationInterface::TOPIC_NAME => $topicName, + OperationInterface::SERIALIZED_DATA => $this->jsonSerializer->serialize($serializedData), + OperationInterface::STATUS => OperationInterface::STATUS_TYPE_OPEN, + ], + ]; + + /** @var OperationInterface $operation */ + $operation = $this->operationFactory->create($data); + return $this->entityManager->save($operation); + } +} diff --git a/app/code/Magento/WebapiAsync/etc/di.xml b/app/code/Magento/WebapiAsync/etc/di.xml index 7411ec0561d24..cfe1a5dbae53f 100644 --- a/app/code/Magento/WebapiAsync/etc/di.xml +++ b/app/code/Magento/WebapiAsync/etc/di.xml @@ -34,10 +34,31 @@ <argument name="isBulk" xsi:type="boolean">true</argument> </arguments> </virtualType> + <virtualType name="Magento\WebapiAsync\Model\Bulk\OperationRepository" type="Magento\WebapiAsync\Model\OperationRepository"> + <arguments> + <argument name="inputParamsResolver" xsi:type="object">Magento\WebapiAsync\Controller\VirtualType\InputParamsResolver</argument> + </arguments> + </virtualType> + <virtualType name="Magento\WebapiAsync\Model\MassSchedule" type="Magento\AsynchronousOperations\Model\MassSchedule"> + <arguments> + <argument name="operationRepository" xsi:type="object">Magento\WebapiAsync\Model\OperationRepository</argument> + </arguments> + </virtualType> + <virtualType name="Magento\WebapiAsync\Model\Bulk\MassSchedule" type="Magento\AsynchronousOperations\Model\MassSchedule"> + <arguments> + <argument name="operationRepository" xsi:type="object">Magento\WebapiAsync\Model\Bulk\OperationRepository</argument> + </arguments> + </virtualType> + <type name="Magento\WebapiAsync\Controller\Rest\AsynchronousRequestProcessor"> + <arguments> + <argument name="asyncBulkPublisher" xsi:type="object">Magento\WebapiAsync\Model\MassSchedule</argument> + </arguments> + </type> <virtualType name="Magento\WebapiAsync\Controller\Rest\VirtualType\AsynchronousBulkRequestProcessor" type="Magento\WebapiAsync\Controller\Rest\AsynchronousRequestProcessor"> <arguments> <argument name="inputParamsResolver" xsi:type="object">Magento\WebapiAsync\Controller\VirtualType\InputParamsResolver</argument> <argument name="processorPath" xsi:type="const">Magento\WebapiAsync\Controller\Rest\AsynchronousRequestProcessor::BULK_PROCESSOR_PATH</argument> + <argument name="asyncBulkPublisher" xsi:type="object">Magento\WebapiAsync\Model\Bulk\MassSchedule</argument> </arguments> </virtualType> <virtualType name="Magento\WebapiAsync\Controller\Rest\VirtualType\AsynchronousBulkSchemaRequestProcessor" type="Magento\WebapiAsync\Controller\Rest\AsynchronousSchemaRequestProcessor"> diff --git a/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminMassDeleteWidgetActionGroup.xml b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminMassDeleteWidgetActionGroup.xml new file mode 100644 index 0000000000000..e93f99fd475fd --- /dev/null +++ b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminMassDeleteWidgetActionGroup.xml @@ -0,0 +1,49 @@ +<?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="AdminMassDeleteWidgetActionGroup"> + <annotations> + <description>Goes to the Admin Widgets list page. Mass delete widgets.</description> + </annotations> + <arguments> + <argument name="row" type="string"/> + </arguments> + + <amOnPage url="{{AdminWidgetsPage.url}}" stepKey="visitAdminWidetPage"/> + <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" + dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" + visible="true" stepKey="clickClearFilters"/> + + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear1"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <click selector="{{AdminWidgetsSection.massActionSelect}}" stepKey="massActionSelectClick"/> + <click selector="{{AdminWidgetsSection.massActionSelectOptionAll}}" stepKey="massActionSelectOptionAllClick"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear2"/> + <click selector="{{AdminWidgetsSection.massActionSelectAction}}" stepKey="massActionSelectActionClick"/> + <click selector="{{AdminWidgetsSection.massActionSelectActionDelete}}" stepKey="massActionSelectActionDeleteClick"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear3"/> + <click selector="{{AdminWidgetsSection.massActionSelectActionDeleteSubmit}}" stepKey="massActionSelectActionDeleteSubmitClick1"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear4"/> + <seeElement selector="{{AdminWidgetsSection.WidgetViewModalDismiss}}" stepKey="widgetViewModalDismissSeeElement"/> + <waitForPageLoad stepKey="waitForPageLoad3"/> + <seeElement selector="{{AdminWidgetsSection.WidgetViewModalClose}}" stepKey="widgetViewModalCloseSeeElement"/> + <click selector="{{AdminWidgetsSection.WidgetViewModalDismiss}}" stepKey="widgetViewModalDismissClick"/> + <waitForElementNotVisible selector="{{AdminWidgetsSection.WidgetViewModalDismiss}}" stepKey="waitForModalClosed"/> + <seeElement selector="{{AdminWidgetsSection.WidgetViewGridInstanceId(row)}}" stepKey="widgetViewGridRowSeeElement"/> + <waitForPageLoad stepKey="waitForPageLoad4"/> + <waitForElementVisible selector="{{AdminWidgetsSection.WidgetViewGridInstanceId(row)}}" stepKey="widgetViewGridInstanceIdWaitForElementVisible"/> + <click selector="{{AdminWidgetsSection.massActionSelectActionDeleteSubmit}}" stepKey="massActionSelectActionDeleteSubmitClick2"/> + <waitForElementVisible selector="{{AdminWidgetsSection.WidgetViewModalAccept}}" stepKey="waitForModalVisible"/> + <click selector="{{AdminWidgetsSection.WidgetViewModalAccept}}" stepKey="widgetViewModalAcceptClick"/> + <waitForPageLoad stepKey="waitForPageLoad5" /> + <waitForElementVisible selector="{{AdminWidgetsSection.WidgetViewGridInstanceRow(row)}}" stepKey="widgetViewGridInstanceRowWaitForElementVisible"/> + <dontSeeElement selector="{{AdminWidgetsSection.WidgetViewGridInstanceId(row)}}" stepKey="widgetViewGridRowDontSeeElement"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Widget/Test/Mftf/Data/WidgetData.xml b/app/code/Magento/Widget/Test/Mftf/Data/WidgetData.xml index 4c6e98aafd765..9beb5d1da1b1c 100644 --- a/app/code/Magento/Widget/Test/Mftf/Data/WidgetData.xml +++ b/app/code/Magento/Widget/Test/Mftf/Data/WidgetData.xml @@ -11,9 +11,14 @@ <entity name="WidgetWithBlock" type="widget"> <data key="type">CMS Static Block</data> <data key="designTheme">Magento Luma</data> + <data key="design_theme">Magento Luma</data> <data key="name" unique="suffix">testName</data> <data key="store_id">All Store Views</data> + <array key="store_ids"> + <item>All Store Views</item> + </array> <data key="display">All Pages</data> <data key="container">Page Top</data> + <data key="display_on">All Pages</data> </entity> </entities> diff --git a/app/code/Magento/Widget/Test/Mftf/Section/AdminWidgetsSection.xml b/app/code/Magento/Widget/Test/Mftf/Section/AdminWidgetsSection.xml index 162f4e9c41064..f6ca1b25a90f2 100644 --- a/app/code/Magento/Widget/Test/Mftf/Section/AdminWidgetsSection.xml +++ b/app/code/Magento/Widget/Test/Mftf/Section/AdminWidgetsSection.xml @@ -13,5 +13,16 @@ <element name="searchButton" type="button" selector=".action-default.scalable.action-secondary"/> <element name="searchResult" type="text" selector="#widgetInstanceGrid_table>tbody>tr:nth-child(1)"/> <element name="resetFilter" type="button" selector="button[data-action='grid-filter-reset']"/> + <element name="massActionSelect" type="select" selector="#widgetInstanceGrid_massaction-mass-select"/> + <element name="massActionSelectOptionAll" type="select" selector="//*[@id='widgetInstanceGrid_massaction-mass-select']//option[@value='selectAll']"/> + <element name="massActionSelectAction" type="multiselect" selector="//*[@id='widgetInstanceGrid_massaction-select']//option[contains(., 'Action')]" /> + <element name="massActionSelectActionDelete" type="multiselect" selector="//*[@id='widgetInstanceGrid_massaction-select']//option[@value='delete']" /> + <element name="massActionSelectActionDeleteSubmit" type="button" selector="#widgetInstanceGrid_massaction-form button.action-default"/> + <element name="WidgetViewModalAccept" type="button" selector=".modal-popup.confirm._show .action-accept"/> + <element name="WidgetViewModalDismiss" type="button" selector=".modal-popup.confirm._show .action-dismiss"/> + <element name="WidgetViewModalClose" type="button" selector=".modal-popup.confirm._show .action-close"/> + <element name="WidgetViewGridRow" type="text" selector="table.data-grid tbody tr[data-role=row]:nth-of-type({{row}})" parameterized="true"/> + <element name="WidgetViewGridInstanceRow" type="text" selector="table.data-grid tbody tr[data-role=row]:nth-of-type({{row}})" parameterized="true"/> + <element name="WidgetViewGridInstanceId" type="text" selector="table.data-grid tbody tr[data-role=row]:nth-of-type({{row}}) td[data-column=instance_id]" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Widget/Test/Mftf/Test/AdminContentWidgetsMassDeletesTest.xml b/app/code/Magento/Widget/Test/Mftf/Test/AdminContentWidgetsMassDeletesTest.xml new file mode 100644 index 0000000000000..073cdabf37698 --- /dev/null +++ b/app/code/Magento/Widget/Test/Mftf/Test/AdminContentWidgetsMassDeletesTest.xml @@ -0,0 +1,55 @@ +<?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="AdminContentWidgetsMassDeletesTest"> + <annotations> + <features value="Widget"/> + <stories value="Widget mass delete"/> + <title value="Admin mass delete widgets in grid"/> + <description value="Admin select widgets in grid and try to mass delete action, will show pop-up with confirm action"/> + <severity value="MAJOR"/> + <group value="widget"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToContentWidgetsPageFirst"> + <argument name="menuUiId" value="{{AdminMenuContent.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuContentElementsWidgets.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitleFirst"> + <argument name="title" value="{{AdminMenuContentElementsWidgets.pageTitle}}"/> + </actionGroup> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear1"/> + <actionGroup ref="AdminCreateAndSaveWidgetActionGroup" stepKey="addWidgetForTest1"> + <argument name="widget" value="ProductsListWidget"/> + </actionGroup> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear2"/> + <actionGroup ref="AdminCreateAndSaveWidgetActionGroup" stepKey="addWidgetForTest2"> + <argument name="widget" value="ProductsListWidget"/> + </actionGroup> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear4"/> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToContentWidgetsPageSecond"> + <argument name="menuUiId" value="{{AdminMenuContent.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuContentElementsWidgets.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitleSecond"> + <argument name="title" value="{{AdminMenuContentElementsWidgets.pageTitle}}"/> + </actionGroup> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <actionGroup ref="AdminMassDeleteWidgetActionGroup" stepKey="massWidgetDelete"> + <argument name="row" value="1"/> + </actionGroup> + </test> +</tests> + diff --git a/app/code/Magento/Widget/i18n/en_US.csv b/app/code/Magento/Widget/i18n/en_US.csv index 4156b94a1f988..3592cbe9a2042 100644 --- a/app/code/Magento/Widget/i18n/en_US.csv +++ b/app/code/Magento/Widget/i18n/en_US.csv @@ -71,3 +71,4 @@ Product,Product Conditions,Conditions "Widget ID","Widget ID" "Inserting a widget does not create a widget instance.","Inserting a widget does not create a widget instance." +"Are you sure you want to delete the selected widget(s)?","Are you sure you want to delete the selected widget(s)?" diff --git a/app/code/Magento/Widget/view/adminhtml/layout/adminhtml_widget_instance_block.xml b/app/code/Magento/Widget/view/adminhtml/layout/adminhtml_widget_instance_block.xml index c78f9ec225be4..1604ac5ba1122 100644 --- a/app/code/Magento/Widget/view/adminhtml/layout/adminhtml_widget_instance_block.xml +++ b/app/code/Magento/Widget/view/adminhtml/layout/adminhtml_widget_instance_block.xml @@ -25,6 +25,7 @@ <item name="label" xsi:type="string" translate="true">Delete</item> <item name="url" xsi:type="string">*/*/massDelete</item> <item name="selected" xsi:type="string">0</item> + <item name="confirm" xsi:type="string" translate="true">Are you sure you want to delete the selected widget(s)?</item> </item> </argument> </arguments> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontVerifySecureURLRedirectWishlistTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontVerifySecureURLRedirectWishlistTest.xml index 21fa334a43196..72f5bab1e6af5 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontVerifySecureURLRedirectWishlistTest.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontVerifySecureURLRedirectWishlistTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontVerifySecureURLRedirectWishlist"> + <test name="StorefrontVerifySecureURLRedirectWishlistTest"> <annotations> <features value="Wishlist"/> <stories value="Storefront Secure URLs"/> diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php index 3123295166a35..8c3ec20b0ede5 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php @@ -381,6 +381,97 @@ public function testCreate($product) $this->deleteProduct($product[ProductInterface::SKU]); } + /** + * Media gallery entries with external videos + * + * @return array + */ + public function externalVideoDataProvider(): array + { + return [ + [ + [ + [ + 'media_type' => 'external-video', + 'disabled' => false, + 'label' => 'Test Video Created', + 'types' => [], + 'position' => 1, + 'content' => [ + 'type' => 'image/png', + 'name' => 'thumbnail.png', + 'base64_encoded_data' => 'iVBORw0KGgoAAAANSUhEUgAAAP8AAADGCAMAAAAqo6adAAAAA1BMVEUAAP79f' + . '+LBAAAASElEQVR4nO3BMQEAAADCoPVPbQwfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + . 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAA+BsYAAAF7hZJ0AAAAAElFTkSuQmCC', + ], + 'extension_attributes' => [ + 'video_content' => [ + 'media_type' => 'external-video', + 'video_provider' => 'youtube', + 'video_url' => 'https://www.youtube.com/', + 'video_title' => 'Video title', + 'video_description' => 'Video description', + 'video_metadata' => 'Video meta', + ], + ], + ] + ] + ], + [ + [ + [ + 'media_type' => 'external-video', + 'disabled' => false, + 'label' => 'Test Video Updated', + 'types' => [], + 'position' => 1, + 'content' => [ + 'type' => 'image/png', + 'name' => 'thumbnail.png', + 'base64_encoded_data' => 'iVBORw0KGgoAAAANSUhEUgAAAP8AAADGCAMAAAAqo6adAAAAA1BMVEUAAP79f' + . '+LBAAAASElEQVR4nO3BMQEAAADCoPVPbQwfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + . 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAA+BsYAAAF7hZJ0AAAAAElFTkSuQmCC', + ], + 'extension_attributes' => [ + 'video_content' => [ + 'media_type' => 'external-video', + 'video_provider' => 'vimeo', + 'video_url' => 'https://www.vimeo.com/', + 'video_title' => 'Video title', + 'video_description' => 'Video description', + 'video_metadata' => 'Video meta', + ], + ], + ] + ] + ] + ]; + } + + /** + * Test create/ update product with external video media gallery entry + * + * @dataProvider externalVideoDataProvider + * @param array $mediaGalleryData + */ + public function testCreateWithExternalVideo(array $mediaGalleryData) + { + $simpleProductBaseData = $this->getSimpleProductData( + [ + ProductInterface::NAME => 'Product With Ext. Video', + ProductInterface::SKU => 'prod-with-ext-video' + ] + ); + + $simpleProductBaseData['media_gallery_entries'] = $mediaGalleryData; + + $response = $this->saveProduct($simpleProductBaseData); + $this->assertEquals( + $simpleProductBaseData['media_gallery_entries'][0]['extension_attributes'], + $response["media_gallery_entries"][0]["extension_attributes"] + ); + } + /** * @param array $fixtureProduct * diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php index 7ffce2a7f541d..90ee6caec6797 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php @@ -162,7 +162,7 @@ public function testGetNonExistentCart() * @magentoApiDataFixture Magento/GraphQl/Quote/_files/make_cart_inactive.php * * @expectedException Exception - * @expectedExceptionMessage Current user does not have an active cart. + * @expectedExceptionMessage The cart isn't active. */ public function testGetInactiveCart() { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/MergeCartsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/MergeCartsTest.php index 0a8d98eefe9e3..695857f781b23 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/MergeCartsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/MergeCartsTest.php @@ -108,7 +108,7 @@ public function testMergeGuestWithCustomerCart() * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @expectedException \Exception - * @expectedExceptionMessage Current user does not have an active cart. + * @expectedExceptionMessage The cart isn't active. */ public function testGuestCartExpiryAfterMerge() { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php index b1a950bd76ca4..806cd08415ac1 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php @@ -84,7 +84,7 @@ public function testRemoveNonExistentItem() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $notExistentItemId = 999; - $this->expectExceptionMessage("Cart doesn't contain the {$notExistentItemId} item."); + $this->expectExceptionMessage("The cart doesn't contain the item"); $query = $this->getQuery($maskedQuoteId, $notExistentItemId); $this->graphQlMutation($query, [], '', $this->getHeaderMap()); @@ -106,7 +106,7 @@ public function testRemoveItemIfItemIsNotBelongToCart() 'virtual-product' ); - $this->expectExceptionMessage("Cart doesn't contain the {$secondQuoteItemId} item."); + $this->expectExceptionMessage("The cart doesn't contain the item"); $query = $this->getQuery($firstQuoteMaskedId, $secondQuoteItemId); $this->graphQlMutation($query, [], '', $this->getHeaderMap()); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddSimpleProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddSimpleProductToCartTest.php index 01ae565f00bf6..3ee27acfa2418 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddSimpleProductToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddSimpleProductToCartTest.php @@ -8,8 +8,10 @@ namespace Magento\GraphQl\Quote\Guest; use Exception; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQl\ResponseContainsErrorsException; use Magento\TestFramework\TestCase\GraphQlAbstract; /** @@ -79,6 +81,57 @@ public function testAddSimpleProductToCart() self::assertEquals('USD', $rowTotalIncludingTax['currency']); } + /** + * Add disabled product to cart + * + * @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @return void + */ + public function testAddDisabledProductToCart(): void + { + $sku = 'simple3'; + $quantity = 2; + + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $sku, $quantity); + + $this->expectException(ResponseContainsErrorsException::class); + $this->expectExceptionMessage( + 'Could not add the product with SKU ' . $sku . ' to the shopping cart: ' . + 'Product that you are trying to add is not available.' + ); + + $this->graphQlMutation($query); + } + + /** + * Add out of stock product to cart + * + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/set_simple_product_out_of_stock.php + * @return void + * @throws NoSuchEntityException + */ + public function testAddOutOfStockProductToCart(): void + { + $sku = 'simple1'; + $quantity = 1; + + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $sku, $quantity); + + $this->expectException(ResponseContainsErrorsException::class); + $this->expectExceptionMessage( + 'Some of the products are out of stock.' + ); + + $this->graphQlMutation($query); + } + /** * @expectedException Exception * @expectedExceptionMessage Required parameter "cart_id" is missing @@ -191,7 +244,7 @@ public function testAddSimpleProductToCustomerCart() private function getQuery(string $maskedQuoteId, string $sku, float $quantity): string { return <<<QUERY -mutation { +mutation { addSimpleProductsToCart( input: { cart_id: "{$maskedQuoteId}" diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php index ae9b7b32b2dab..1b54e2f57017f 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php @@ -119,7 +119,7 @@ public function testGetNonExistentCart() * @magentoApiDataFixture Magento/GraphQl/Quote/_files/make_cart_inactive.php * * @expectedException Exception - * @expectedExceptionMessage Current user does not have an active cart. + * @expectedExceptionMessage The cart isn't active. */ public function testGetInactiveCart() { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveItemFromCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveItemFromCartTest.php index 931819af781d5..7970e4b15bc32 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveItemFromCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveItemFromCartTest.php @@ -74,7 +74,7 @@ public function testRemoveNonExistentItem() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $notExistentItemId = 999; - $this->expectExceptionMessage("Cart doesn't contain the {$notExistentItemId} item."); + $this->expectExceptionMessage("The cart doesn't contain the item"); $query = $this->getQuery($maskedQuoteId, $notExistentItemId); $this->graphQlMutation($query); @@ -95,7 +95,7 @@ public function testRemoveItemIfItemIsNotBelongToCart() 'virtual-product' ); - $this->expectExceptionMessage("Cart doesn't contain the {$secondQuoteItemId} item."); + $this->expectExceptionMessage("The cart doesn't contain the item"); $query = $this->getQuery($firstQuoteMaskedId, $secondQuoteItemId); $this->graphQlMutation($query); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/SendFriendTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/SendFriendTest.php index e01e074900519..93001dd396cdc 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/SendFriendTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/SendFriendTest.php @@ -45,6 +45,7 @@ protected function setUp() /** * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoConfigFixture default_store sendfriend/email/enabled 1 * @magentoConfigFixture default_store sendfriend/email/allow_guest 1 */ public function testSendFriendGuestEnable() @@ -66,6 +67,7 @@ public function testSendFriendGuestEnable() /** * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoConfigFixture default_store sendfriend/email/enabled 1 * @magentoConfigFixture default_store sendfriend/email/allow_guest 0 * @expectedException \Exception * @expectedExceptionMessage The current customer isn't authorized. @@ -90,9 +92,11 @@ public function testSendFriendGuestDisableAsGuest() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php - * @magentoConfigFixture default_store sendfriend/email/allow_guest 0 + * @magentoConfigFixture default_store sendfriend/email/enabled 0 + * @expectedException \Exception + * @expectedExceptionMessage "Email to a Friend" is not enabled. */ - public function testSendFriendGuestDisableAsCustomer() + public function testSendFriendDisableAsCustomer() { $productId = (int)$this->productRepository->get('simple_product')->getId(); $recipients = '{ @@ -111,6 +115,9 @@ public function testSendFriendGuestDisableAsCustomer() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoConfigFixture default_store sendfriend/email/enabled 1 + * @expectedException \Exception + * @expectedExceptionMessage The product that was requested doesn't exist. Verify the product and try again. */ public function testSendWithoutExistProduct() { @@ -125,15 +132,13 @@ public function testSendWithoutExistProduct() }'; $query = $this->getQuery($productId, $recipients); - $this->expectExceptionMessage( - 'The product that was requested doesn\'t exist. Verify the product and try again.' - ); $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoConfigFixture default_store sendfriend/email/enabled 1 */ public function testMaxSendEmailToFriend() { @@ -176,6 +181,7 @@ public function testMaxSendEmailToFriend() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoConfigFixture default_store sendfriend/email/enabled 1 * @dataProvider sendFriendsErrorsDataProvider * @param string $input * @param string $errorMessage @@ -188,7 +194,7 @@ public function testErrors(string $input, string $errorMessage) sendEmailToFriend( input: { $input - } + } ) { sender { name @@ -210,6 +216,7 @@ public function testErrors(string $input, string $errorMessage) /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoConfigFixture default_store sendfriend/email/enabled 1 * @magentoConfigFixture default_store sendfriend/email/max_per_hour 1 * @magentoApiDataFixture Magento/SendFriend/Fixtures/sendfriend_configuration.php */ @@ -238,6 +245,7 @@ public function testLimitMessagesPerHour() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoConfigFixture default_store sendfriend/email/enabled 1 */ public function testSendProductWithoutSenderEmail() { @@ -256,6 +264,7 @@ public function testSendProductWithoutSenderEmail() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product_without_visibility.php + * @magentoConfigFixture default_store sendfriend/email/enabled 1 */ public function testSendProductWithoutVisibility() { @@ -282,12 +291,12 @@ public function sendFriendsErrorsDataProvider() { return [ [ - 'product_id: 1 + 'product_id: 1 sender: { name: "Name" email: "e@mail.com" message: "Lorem Ipsum" - } + } recipients: [ { name: "" @@ -300,12 +309,12 @@ public function sendFriendsErrorsDataProvider() ]', 'Please provide Name for all of recipients.' ], [ - 'product_id: 1 + 'product_id: 1 sender: { name: "Name" email: "e@mail.com" message: "Lorem Ipsum" - } + } recipients: [ { name: "Recipient Name 1" @@ -318,12 +327,12 @@ public function sendFriendsErrorsDataProvider() ]', 'Please provide Email for all of recipients.' ], [ - 'product_id: 1 + 'product_id: 1 sender: { name: "" email: "e@mail.com" message: "Lorem Ipsum" - } + } recipients: [ { name: "Recipient Name 1" @@ -336,12 +345,12 @@ public function sendFriendsErrorsDataProvider() ]', 'Please provide Name of sender.' ], [ - 'product_id: 1 + 'product_id: 1 sender: { name: "Name" email: "e@mail.com" message: "" - } + } recipients: [ { name: "Recipient Name 1" @@ -403,9 +412,9 @@ private function getQuery(int $productId, string $recipients): string name: "Name" email: "e@mail.com" message: "Lorem Ipsum" - } + } recipients: [{$recipients}] - } + } ) { sender { name diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/StoreConfigTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/StoreConfigTest.php new file mode 100644 index 0000000000000..9d87afb2ddec9 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/StoreConfigTest.php @@ -0,0 +1,124 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\SendFriend; + +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test SendFriend configuration resolves correctly in StoreConfig + */ +class StoreConfigTest extends GraphQlAbstract +{ + public function testSendFriendFieldsAreReturnedWithoutError() + { + $query = $this->getStoreConfigQuery(); + + $response = $this->graphQlQuery($query); + $this->assertArrayNotHasKey('errors', $response); + $this->assertArrayHasKey('send_friend', $response['storeConfig']); + $this->assertArrayHasKey('enabled_for_customers', $response['storeConfig']['send_friend']); + $this->assertArrayHasKey('enabled_for_guests', $response['storeConfig']['send_friend']); + $this->assertNotNull($response['storeConfig']['send_friend']['enabled_for_customers']); + $this->assertNotNull($response['storeConfig']['send_friend']['enabled_for_guests']); + } + + /** + * @magentoConfigFixture default_store sendfriend/email/enabled 0 + */ + public function testSendFriendDisabled() + { + $response = $this->graphQlQuery($this->getStoreConfigQuery()); + + $this->assertResponse( + ['enabled_for_customers' => false, 'enabled_for_guests' => false], + $response + ); + } + + /** + * @magentoConfigFixture default_store sendfriend/email/enabled 1 + * @magentoConfigFixture default_store sendfriend/email/allow_guest 0 + */ + public function testSendFriendEnabledGuestDisabled() + { + $response = $this->graphQlQuery($this->getStoreConfigQuery()); + + $this->assertResponse( + ['enabled_for_customers' => true, 'enabled_for_guests' => false], + $response + ); + } + + /** + * @magentoConfigFixture default_store sendfriend/email/enabled 1 + * @magentoConfigFixture default_store sendfriend/email/allow_guest 1 + */ + public function testSendFriendEnabledGuestEnabled() + { + $response = $this->graphQlQuery($this->getStoreConfigQuery()); + + $this->assertResponse( + ['enabled_for_customers' => true, 'enabled_for_guests' => true], + $response + ); + } + + /** + * Assert response matches expected output + * + * @param array $expectedValues + * @param array $response + */ + private function assertResponse(array $expectedValues, array $response) + { + $this->assertArrayNotHasKey( + 'errors', + $response + ); + $this->assertArrayHasKey( + 'send_friend', + $response['storeConfig'] + ); + $this->assertArrayHasKey( + 'enabled_for_customers', + $response['storeConfig']['send_friend'] + ); + $this->assertArrayHasKey( + 'enabled_for_guests', + $response['storeConfig']['send_friend'] + ); + $this->assertEquals( + $expectedValues['enabled_for_customers'], + $response['storeConfig']['send_friend']['enabled_for_customers'] + ); + $this->assertEquals( + $expectedValues['enabled_for_guests'], + $response['storeConfig']['send_friend']['enabled_for_guests'] + ); + } + + /** + * Return simple storeConfig query to get sendFriend configuration + * + * @return string + */ + private function getStoreConfigQuery() + { + return <<<QUERY +{ + storeConfig{ + id + send_friend { + enabled_for_customers + enabled_for_guests + } + } +} +QUERY; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/WebapiAsync/Model/OrderRepositoryInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/WebapiAsync/Model/OrderRepositoryInterfaceTest.php new file mode 100644 index 0000000000000..bc7940ca35f35 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/WebapiAsync/Model/OrderRepositoryInterfaceTest.php @@ -0,0 +1,174 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\WebapiAsync\Model; + +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Webapi\Rest\Request; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Model\Order; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\MessageQueue\EnvironmentPreconditionException; +use Magento\TestFramework\MessageQueue\PreconditionFailedException; +use Magento\TestFramework\MessageQueue\PublisherConsumerController; +use Magento\TestFramework\TestCase\WebapiAbstract; + +/** + * Test order repository interface via async webapi + */ +class OrderRepositoryInterfaceTest extends WebapiAbstract +{ + private const ASYNC_BULK_SAVE_ORDER = '/async/bulk/V1/orders'; + private const ASYNC_SAVE_ORDER = '/async/V1/orders'; + /** + * @var ObjectManagerInterface + */ + private $objectManager; + /** + * @var PublisherConsumerController + */ + private $publisherConsumerController; + + /** + * @inheritDoc + */ + protected function setUp() + { + parent::setUp(); + $this->objectManager = Bootstrap::getObjectManager(); + + $params = array_merge_recursive( + Bootstrap::getInstance()->getAppInitParams(), + ['MAGE_DIRS' => ['cache' => ['path' => TESTS_TEMP_DIR . '/cache']]] + ); + + /** @var PublisherConsumerController publisherConsumerController */ + $this->publisherConsumerController = $this->objectManager->create( + PublisherConsumerController::class, + [ + 'consumers' => ['async.operations.all'], + 'logFilePath' => TESTS_TEMP_DIR . "/MessageQueueTestLog.txt", + 'appInitParams' => $params, + ] + ); + + try { + $this->publisherConsumerController->initialize(); + } catch (EnvironmentPreconditionException $e) { + $this->markTestSkipped($e->getMessage()); + } catch (PreconditionFailedException $e) { + $this->fail( + $e->getMessage() + ); + } + } + + /** + * @inheritDoc + */ + public function tearDown() + { + $this->publisherConsumerController->stopConsumers(); + parent::tearDown(); + } + + /** + * Check that order is updated successfuly via async webapi + * + * @magentoApiDataFixture Magento/Sales/_files/order.php + * @dataProvider saveDataProvider + * @param array $data + * @param bool $isBulk + * @return void + */ + public function testSave(array $data, bool $isBulk = true): void + { + $this->_markTestAsRestOnly(); + /** @var Order $beforeUpdateOrder */ + $beforeUpdateOrder = $this->objectManager->get(Order::class)->loadByIncrementId('100000001'); + $requestData = [ + 'entity' => array_merge($data, [OrderInterface::ENTITY_ID => $beforeUpdateOrder->getEntityId()]) + ]; + if ($isBulk) { + $requestData = [$requestData]; + } + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => $isBulk ? self::ASYNC_BULK_SAVE_ORDER : self::ASYNC_SAVE_ORDER, + 'httpMethod' => Request::HTTP_METHOD_POST, + ] + ]; + $this->makeAsyncRequest($serviceInfo, $requestData); + try { + $this->publisherConsumerController->waitForAsynchronousResult( + function (Order $beforeUpdateOrder, array $data) { + /** @var Order $afterUpdateOrder */ + $afterUpdateOrder = $this->objectManager->get(Order::class)->load($beforeUpdateOrder->getId()); + foreach ($data as $attribute => $value) { + $getter = 'get' . str_replace(' ', '', ucwords(str_replace('_', ' ', $attribute))); + if ($value !== $afterUpdateOrder->$getter()) { + return false; + } + } + //check that base_grand_total and grand_total are not overwritten + $this->assertEquals( + $beforeUpdateOrder->getBaseGrandTotal(), + $afterUpdateOrder->getBaseGrandTotal() + ); + $this->assertEquals( + $beforeUpdateOrder->getGrandTotal(), + $afterUpdateOrder->getGrandTotal() + ); + return true; + }, + [$beforeUpdateOrder, $data] + ); + } catch (PreconditionFailedException $e) { + $this->fail("Order update via async webapi failed"); + } + } + + /** + * Data provider for tesSave() + * + * @return array + */ + public function saveDataProvider(): array + { + return [ + 'update order in bulk mode' => [ + [ + OrderInterface::CUSTOMER_EMAIL => 'customer.email.modified@magento.test' + ], + true + ], + 'update order in single mode' => [ + [ + OrderInterface::CUSTOMER_EMAIL => 'customer.email.modified@magento.test' + ], + false + ] + ]; + } + + /** + * Make async webapi request + * + * @param array $serviceInfo + * @param array $requestData + * @return void + */ + private function makeAsyncRequest(array $serviceInfo, array $requestData): void + { + $response = $this->_webApiCall($serviceInfo, $requestData); + $this->assertNotEmpty($response['request_items']); + foreach ($response['request_items'] as $requestItem) { + $this->assertEquals('accepted', $requestItem['status']); + } + $this->assertFalse($response['errors']); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGeneratorTest.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGeneratorTest.php new file mode 100644 index 0000000000000..446b423e17187 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGeneratorTest.php @@ -0,0 +1,92 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogUrlRewrite\Model\Product; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\CatalogUrlRewrite\Model\ObjectRegistryFactory; +use Magento\Framework\ObjectManagerInterface; +use Magento\Store\Model\Store; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Verify generate url rewrites for anchor categories. + */ +class AnchorUrlRewriteGeneratorTest extends TestCase +{ + + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var ObjectRegistryFactory + */ + private $objectRegistryFactory; + + /** + * @inheritDoc + */ + public function setUp() + { + parent::setUp(); // TODO: Change the autogenerated stub + + $this->objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $this->objectManager->create(ProductRepositoryInterface::class); + $this->objectRegistryFactory = $this->objectManager->create(ObjectRegistryFactory::class); + } + + /** + * Verify correct generate of the relative "StoreId" + * + * @param string $expect + * @return void + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException + * @magentoDataFixture Magento/CatalogUrlRewrite/_files/product_with_stores.php + * @magentoDbIsolation disabled + * @dataProvider getConfigGenerate + */ + public function testGenerate(string $expect): void + { + $product = $this->productRepository->get('simple'); + $categories = $product->getCategoryCollection(); + $productCategories = $this->objectRegistryFactory->create(['entities' => $categories]); + + /** @var AnchorUrlRewriteGenerator $generator */ + $generator = $this->objectManager->get(AnchorUrlRewriteGenerator::class); + + /** @var $store Store */ + $store = Bootstrap::getObjectManager()->get(Store::class); + $store->load('fixture_second_store', 'code'); + + $urls = $generator->generate($store->getId(), $product, $productCategories); + + $this->assertEquals($expect, $urls[0]->getRequestPath()); + } + + /** + * Data provider for testGenerate + * + * @return array + */ + public function getConfigGenerate(): array + { + return [ + [ + 'expect' => 'category-1-custom/simple-product.html' + ] + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/categories_with_stores.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/categories_with_stores.php new file mode 100644 index 0000000000000..5fc9d75598da6 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/categories_with_stores.php @@ -0,0 +1,80 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Backend\App\Area\FrontNameResolver; +use Magento\Catalog\Model\Category; +use Magento\Store\Model\Store; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/../../../Magento/Store/_files/second_store.php'; +Bootstrap::getInstance()->loadArea(FrontNameResolver::AREA_CODE); + +$store = Bootstrap::getObjectManager()->get(Store::class); +$store->load('fixture_second_store', 'code'); + +/** @var $category Category */ +$category = Bootstrap::getObjectManager()->create(Category::class); +$category->isObjectNew(true); +$category->setId(3) + ->setName('Category 1') + ->setParentId(2) + ->setPath('1/2/3') + ->setLevel(2) + ->setAvailableSortBy('name') + ->setDefaultSortBy('name') + ->setUrlPath('category-1-default') + ->setUrlKey('category-1-default') + ->setIsActive(true) + ->setPosition(1) + ->save(); + +$category = Bootstrap::getObjectManager()->create(Category::class); +$category->isObjectNew(true); +$category->setId(4) + ->setName('Category 1.1') + ->setParentId(3) + ->setPath('1/2/3/4') + ->setLevel(3) + ->setAvailableSortBy('name') + ->setDefaultSortBy('name') + ->setUrlPath('category-1-1-default') + ->setUrlKey('category-1-1-default') + ->setIsActive(true) + ->setPosition(1) + ->save(); + +$category = Bootstrap::getObjectManager()->create(Category::class); +$category->isObjectNew(true); +$category->setId(3) + ->setName('Category 1') + ->setParentId(2) + ->setPath('1/2/3') + ->setLevel(2) + ->setAvailableSortBy('name') + ->setDefaultSortBy('name') + ->setStoreId($store->getId()) + ->setUrlPath('category-1-custom') + ->setUrlKey('category-1-custom') + ->setIsActive(true) + ->setPosition(1) + ->save(); + +$category = Bootstrap::getObjectManager()->create(Category::class); +$category->isObjectNew(true); +$category->setId(4) + ->setName('Category 1.1') + ->setParentId(3) + ->setPath('1/2/3/4') + ->setLevel(3) + ->setAvailableSortBy('name') + ->setDefaultSortBy('name') + ->setStoreId($store->getId()) + ->setUrlPath('category-1-1-custom') + ->setUrlKey('category-1-1-custom') + ->setIsActive(true) + ->setPosition(1) + ->save(); diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/categories_with_stores_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/categories_with_stores_rollback.php new file mode 100644 index 0000000000000..a89c80a61ccbc --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/categories_with_stores_rollback.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +/** @var \Magento\Framework\Registry $registry */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$registry = $objectManager->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $collection */ +$collection = $objectManager->create(\Magento\Catalog\Model\ResourceModel\Category\Collection::class); +$collection + ->addAttributeToFilter('level', 2) + ->load() + ->delete(); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_with_stores.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_with_stores.php new file mode 100644 index 0000000000000..84fa9b3044af9 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_with_stores.php @@ -0,0 +1,45 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +/** @var \Magento\Catalog\Setup\CategorySetup $installer */ +$installer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Catalog\Setup\CategorySetup::class +); + +require __DIR__ . '/categories_with_stores.php'; + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); +$categoryLinkRepository = $objectManager->create( + \Magento\Catalog\Api\CategoryLinkRepositoryInterface::class, + [ + 'productRepository' => $productRepository + ] +); + +/** @var Magento\Catalog\Api\CategoryLinkManagementInterface $linkManagement */ +$categoryLinkManagement = $objectManager->create( + \Magento\Catalog\Api\CategoryLinkManagementInterface::class, + [ + 'productRepository' => $productRepository, + 'categoryLinkRepository' => $categoryLinkRepository + ] +); + +/** @var $product \Magento\Catalog\Model\Product */ +$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); +$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) + ->setAttributeSetId($installer->getAttributeSetId('catalog_product', 'Default')) + ->setName('Simple Product') + ->setSku('simple') + ->setPrice(10) + ->setWeight(18) + ->setStockData(['use_config_manage_stock' => 0]) + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->save(); +$categoryLinkManagement->assignProductToCategories($product->getSku(), [4]); diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_with_stores_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_with_stores_rollback.php new file mode 100644 index 0000000000000..86f0ce34af00c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_with_stores_rollback.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Exception\NoSuchEntityException; + +\Magento\TestFramework\Helper\Bootstrap::getInstance()->getInstance()->reinitialize(); + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ +$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); +try { + $product = $productRepository->get('simple', true); + if ($product->getId()) { + $productRepository->delete($product); + } +} catch (NoSuchEntityException $e) { +} +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/CartsTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/CartsTest.php index fa8577c6c6a40..604f7d8fcb2a1 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/CartsTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/CartsTest.php @@ -55,12 +55,12 @@ public function testGetHtml() ); $html = $this->_block->toHtml(); - $this->assertContains("<div id=\"customer_cart_grid1\"", $html); + $this->assertContains("<div id=\"customer_cart_grid\"", $html); $this->assertRegExp( '/<div class=".*admin__data-grid-toolbar"/', $html ); - $this->assertContains("customer_cart_grid1JsObject = new varienGrid(\"customer_cart_grid1\",", $html); + $this->assertContains("customer_cart_gridJsObject = new varienGrid(\"customer_cart_grid\",", $html); $this->assertContains( 'backend\u002Fcustomer\u002Fcart_product_composite_cart\u002Fconfigure\u002Fwebsite_id\u002F1', $html diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php index 4a7cc7591f7aa..1442449f6aedd 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php @@ -434,7 +434,7 @@ public function testCartAction() $this->getRequest()->setParam('id', 1)->setParam('website_id', 1)->setPostValue('delete', 1); $this->dispatch('backend/customer/index/cart'); $body = $this->getResponse()->getBody(); - $this->assertContains('<div id="customer_cart_grid1"', $body); + $this->assertContains('<div id="customer_cart_grid"', $body); } /** diff --git a/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/AddressTest.php b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/AddressTest.php index 96b7f8ce8ed67..e83b144e395b2 100644 --- a/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/AddressTest.php +++ b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/AddressTest.php @@ -14,6 +14,7 @@ use Magento\ImportExport\Model\Import as ImportModel; use Magento\ImportExport\Model\Import\Adapter as ImportAdapter; use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\Indexer\StateInterface; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -84,6 +85,11 @@ class AddressTest extends \PHPUnit\Framework\TestCase /** @var \Magento\Customer\Model\ResourceModel\Customer */ protected $customerResource; + /** + * @var \Magento\Customer\Model\Indexer\Processor + */ + private $indexerProcessor; + /** * Init new instance of address entity adapter */ @@ -96,6 +102,9 @@ protected function setUp() $this->_entityAdapter = Bootstrap::getObjectManager()->create( $this->_testClassName ); + $this->indexerProcessor = Bootstrap::getObjectManager()->create( + \Magento\Customer\Model\Indexer\Processor::class + ); } /** @@ -353,6 +362,7 @@ public function testImportDataAddUpdate() $requiredAttributes[] = $keyAttribute; foreach (['update', 'remove'] as $action) { foreach ($this->_updateData[$action] as $attributes) { + // phpcs:ignore Magento2.Performance.ForeachArrayMerge $requiredAttributes = array_merge($requiredAttributes, array_keys($attributes)); } } @@ -494,4 +504,29 @@ public function testDifferentOptions(): void $imported = $this->_entityAdapter->importData(); $this->assertTrue($imported, 'Must be successfully imported'); } + + /** + * Test customer indexer gets invalidated after import when Update on Schedule mode is set + * + * @magentoDbIsolation enabled + */ + public function testCustomerIndexer(): void + { + $file = __DIR__ . '/_files/address_import_update.csv'; + $filesystem = Bootstrap::getObjectManager()->create(Filesystem::class); + $directoryWrite = $filesystem->getDirectoryWrite(DirectoryList::ROOT); + $source = new \Magento\ImportExport\Model\Import\Source\Csv($file, $directoryWrite); + $this->_entityAdapter + ->setParameters(['behavior' => ImportModel::BEHAVIOR_ADD_UPDATE]) + ->setSource($source) + ->validateData() + ->hasToBeTerminated(); + $this->indexerProcessor->getIndexer()->reindexAll(); + $statusBeforeImport = $this->indexerProcessor->getIndexer()->getStatus(); + $this->indexerProcessor->getIndexer()->setScheduled(true); + $this->_entityAdapter->importData(); + $statusAfterImport = $this->indexerProcessor->getIndexer()->getStatus(); + $this->assertEquals(StateInterface::STATUS_VALID, $statusBeforeImport); + $this->assertEquals(StateInterface::STATUS_INVALID, $statusAfterImport); + } } diff --git a/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/CustomerTest.php b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/CustomerTest.php index 7b5ddc4b9fa5f..89c84d7c18068 100644 --- a/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/CustomerTest.php +++ b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/CustomerTest.php @@ -11,6 +11,7 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Exception\NoSuchEntityException; use Magento\ImportExport\Model\Import; +use Magento\Framework\Indexer\StateInterface; /** * Test for class \Magento\CustomerImportExport\Model\Import\Customer which covers validation logic @@ -39,6 +40,11 @@ class CustomerTest extends \PHPUnit\Framework\TestCase */ protected $directoryWrite; + /** + * @var \Magento\Customer\Model\Indexer\Processor + */ + private $indexerProcessor; + /** * Create all necessary data for tests */ @@ -49,6 +55,8 @@ protected function setUp() $this->_model = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() ->create(\Magento\CustomerImportExport\Model\Import\Customer::class); $this->_model->setParameters(['behavior' => Import::BEHAVIOR_ADD_UPDATE]); + $this->indexerProcessor = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Customer\Model\Indexer\Processor::class); $propertyAccessor = new \ReflectionProperty($this->_model, 'errorMessageTemplates'); $propertyAccessor->setAccessible(true); @@ -377,6 +385,23 @@ public function testUpdateExistingCustomers(): void $this->assertEquals(1, $customer->getStoreId()); } + /** + * Test customer indexer gets invalidated after import when Update on Schedule mode is set + * + * @magentoDbIsolation enabled + * @return void + */ + public function testCustomerIndexer(): void + { + $this->indexerProcessor->getIndexer()->reindexAll(); + $statusBeforeImport = $this->indexerProcessor->getIndexer()->getStatus(); + $this->indexerProcessor->getIndexer()->setScheduled(true); + $this->doImport(__DIR__ . '/_files/customers_with_gender_to_import.csv', Import::BEHAVIOR_ADD_UPDATE); + $statusAfterImport = $this->indexerProcessor->getIndexer()->getStatus(); + $this->assertEquals(StateInterface::STATUS_VALID, $statusBeforeImport); + $this->assertEquals(StateInterface::STATUS_INVALID, $statusAfterImport); + } + /** * Gets customer entity. * diff --git a/dev/tests/integration/testsuite/Magento/Framework/Filesystem/Directory/ReadTest.php b/dev/tests/integration/testsuite/Magento/Framework/Filesystem/Directory/ReadTest.php index bc77eeb932c9a..e04063eabc36b 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Filesystem/Directory/ReadTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Filesystem/Directory/ReadTest.php @@ -73,17 +73,17 @@ public function testGetRelativePathOutside() $exceptions = 0; $dir = $this->getDirectoryInstance('foo'); try { - $dir->getRelativePath(__DIR__ .'/ReadTest.php'); + $dir->getRelativePath(__DIR__ . '/ReadTest.php'); } catch (ValidatorException $exception) { $exceptions++; } try { - $dir->getRelativePath(__DIR__ .'//./..////Directory/ReadTest.php'); + $dir->getRelativePath(__DIR__ . '//./..////Directory/ReadTest.php'); } catch (ValidatorException $exception) { $exceptions++; } try { - $dir->getRelativePath(__DIR__ .'\..\Directory\ReadTest.php'); + $dir->getRelativePath(__DIR__ . '\..\Directory\ReadTest.php'); } catch (ValidatorException $exception) { $exceptions++; } @@ -222,7 +222,13 @@ public function testIsExist($dirPath, $path, $exists) */ public function existsProvider() { - return [['foo', 'bar', true], ['foo', 'bar/baz/', true], ['foo', 'bar/notexists', false]]; + return [ + ['foo', 'bar', true], + ['foo', 'bar/baz/', true], + ['foo', 'bar/notexists', false], + ['foo', 'foo/../bar/', true], + ['foo', 'foo/../notexists/', false] + ]; } public function testIsExistOutside() diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Controller/Transparent/RedirectTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Controller/Transparent/RedirectTest.php new file mode 100644 index 0000000000000..4a30231012acf --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Paypal/Controller/Transparent/RedirectTest.php @@ -0,0 +1,112 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Paypal\Controller\Transparent; + +use Magento\TestFramework\TestCase\AbstractController; +use Zend\Stdlib\Parameters; + +/** + * Tests PayPal transparent redirect controller. + */ +class RedirectTest extends AbstractController +{ + /** + * Tests transparent redirect for PayPal PayflowPro payment flow. + * + * @SuppressWarnings(PHPMD.Superglobals) + */ + public function testRequestRedirect() + { + $redirectUri = 'paypal/transparent/redirect'; + $postData = [ + 'BILLTOCITY' => 'culver city', + 'AMT' => '0.00', + 'BILLTOEMAIL' => 'user_1@example.com', + 'BILLTOSTREET' => '123 Freedom Blvd. #123 app.111', + 'VISACARDLEVEL' => '12', + 'SHIPTOCITY' => 'culver city' + ]; + + $this->setRequestUri($redirectUri); + $this->getRequest()->setPostValue($postData); + $this->getRequest()->setMethod('POST'); + + $this->dispatch($redirectUri); + + $responseHtml = $this->getResponse()->getBody(); + try { + $responseNvp = $this->convertToNvp($responseHtml); + $this->assertEquals( + $postData, + $responseNvp, + 'POST form should contain all params from POST request' + ); + } catch (\InvalidArgumentException $exception) { + $this->fail($exception->getMessage()); + } + + $this->assertEmpty( + $_SESSION, + 'Session start has to be skipped for current controller' + ); + } + + /** + * Sets REQUEST_URI into request object. + * + * @param string $requestUri + * @return void + */ + private function setRequestUri(string $requestUri) + { + $request = $this->getRequest(); + $reflection = new \ReflectionClass($request); + $property = $reflection->getProperty('requestUri'); + $property->setAccessible(true); + $property->setValue($request, null); + + $request->setServer(new Parameters(['REQUEST_URI' => $requestUri])); + } + + /** + * Converts HTML response to NVP structure + * + * @param string $response + * @return array + */ + private function convertToNvp(string $response): array + { + $document = new \DOMDocument(); + + libxml_use_internal_errors(true); + if (!$document->loadHTML($response)) { + throw new \InvalidArgumentException( + __('The response format was incorrect. Should be valid HTML') + ); + } + libxml_use_internal_errors(false); + + $document->getElementsByTagName('input'); + + $convertedResponse = []; + /** @var \DOMNode $inputNode */ + foreach ($document->getElementsByTagName('input') as $inputNode) { + if (!$inputNode->attributes->getNamedItem('value') + || !$inputNode->attributes->getNamedItem('name') + ) { + continue; + } + $convertedResponse[$inputNode->attributes->getNamedItem('name')->nodeValue] + = $inputNode->attributes->getNamedItem('value')->nodeValue; + } + + unset($convertedResponse['form_key']); + + return $convertedResponse; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php index 8d6e4dbf30ae5..23dc60d347427 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php +++ b/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php @@ -6,6 +6,7 @@ namespace Magento\Paypal\Model\Express; use Magento\Checkout\Model\Type\Onepage; +use Magento\Directory\Model\CountryFactory; use Magento\Framework\ObjectManagerInterface; use Magento\Paypal\Model\Api\Nvp; use Magento\Paypal\Model\Api\Type\Factory; @@ -17,8 +18,6 @@ use Magento\TestFramework\Helper\Bootstrap; /** - * Class CheckoutTest - * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class CheckoutTest extends \PHPUnit\Framework\TestCase @@ -634,10 +633,6 @@ public function testGuestReturnFromPaypal() ->setMethods(['call', 'getExportedShippingAddress', 'getExportedBillingAddress']) ->getMock(); - $api->expects($this->any()) - ->method('call') - ->will($this->returnValue([])); - $apiTypeFactory->expects($this->any()) ->method('create') ->will($this->returnValue($api)); @@ -652,6 +647,14 @@ public function testGuestReturnFromPaypal() ->method('getExportedShippingAddress') ->will($this->returnValue($exportedShippingAddress)); + $this->addCountryFactory($api); + $data = [ + 'COUNTRYCODE' => $quote->getShippingAddress()->getCountryId(), + 'STATE' => 'unknown' + ]; + $api->method('call') + ->willReturn($data); + $paypalInfo->expects($this->once()) ->method('importToPayment') ->with($api, $quote->getPayment()); @@ -710,4 +713,19 @@ private function getFixtureQuote(): Quote return $quoteCollection->getLastItem(); } + + /** + * Adds countryFactory to a mock. + * + * @param \PHPUnit\Framework\MockObject\MockObject $api + * @throws \ReflectionException + * @return void + */ + private function addCountryFactory(\PHPUnit\Framework\MockObject\MockObject $api): void + { + $reflection = new \ReflectionClass($api); + $property = $reflection->getProperty('_countryFactory'); + $property->setAccessible(true); + $property->setValue($api, $this->objectManager->get(CountryFactory::class)); + } } diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtmlTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtmlTest.php index 82f1c53d8f161..b2fc8365c90ea 100644 --- a/dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtmlTest.php +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtmlTest.php @@ -12,6 +12,7 @@ /** * New action html test * + * Verify the request object contains the proper form object for action * @magentoAppArea adminhtml */ class NewActionHtmlTest extends AbstractBackendController @@ -31,6 +32,11 @@ class NewActionHtmlTest extends AbstractBackendController */ private $formName = 'test_form'; + /** + * @var string + */ + private $requestFormName = 'rule_actions_fieldset_'; + /** * Test verifies that execute method has the proper data-form-part value in html response * @@ -73,6 +79,7 @@ private function prepareRequest(): void $this->getRequest()->setParams( [ 'id' => 1, + 'form' => $this->requestFormName, 'form_namespace' => $this->formName, 'type' => 'Magento\SalesRule\Model\Rule\Condition\Product|quote_item_price', ] diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewConditionHtmlTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewConditionHtmlTest.php new file mode 100644 index 0000000000000..f15befedfbca7 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewConditionHtmlTest.php @@ -0,0 +1,88 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\SalesRule\Controller\Adminhtml\Promo\Quote; + +use Magento\TestFramework\TestCase\AbstractBackendController; + +/** + * New condition html test + * + * Verify the request object contains the proper form object for condition + * @magentoAppArea adminhtml + */ +class NewConditionHtmlTest extends AbstractBackendController +{ + /** + * @var string + */ + protected $resource = 'Magento_SalesRule::quote'; + + /** + * @var string + */ + protected $uri = 'backend/sales_rule/promo_quote/newConditionHtml'; + + /** + * @var string + */ + private $formName = 'test_form'; + + /** + * @var string + */ + private $requestFormName = 'rule_conditions_fieldset_'; + + /** + * Test verifies that execute method has the proper data-form-part value in html response + * + * @return void + */ + public function testExecute(): void + { + $this->prepareRequest(); + $this->dispatch($this->uri); + $html = $this->getResponse() + ->getBody(); + $this->assertContains($this->formName, $html); + } + + /** + * @inheritdoc + */ + public function testAclHasAccess() + { + $this->prepareRequest(); + parent::testAclHasAccess(); + } + + /** + * @inheritdoc + */ + public function testAclNoAccess() + { + $this->prepareRequest(); + parent::testAclNoAccess(); + } + + /** + * Prepare request + * + * @return void + */ + private function prepareRequest(): void + { + $this->getRequest()->setParams( + [ + 'id' => 1, + 'form' => $this->requestFormName, + 'form_namespace' => $this->formName, + 'type' => 'Magento\SalesRule\Model\Rule\Condition\Product|category_ids', + ] + )->setMethod('POST'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Swatches/Block/Product/ListProductTest.php b/dev/tests/integration/testsuite/Magento/Swatches/Block/Product/ListProductTest.php index 460e4559a0e84..e6f566ac156db 100644 --- a/dev/tests/integration/testsuite/Magento/Swatches/Block/Product/ListProductTest.php +++ b/dev/tests/integration/testsuite/Magento/Swatches/Block/Product/ListProductTest.php @@ -166,7 +166,7 @@ private function assertProductImage(array $images, string $area, array $expectat $this->updateProductImages($images); $productImage = $this->listingBlock->getImage($this->productRepository->get('configurable'), $area); $this->assertInstanceOf(Image::class, $productImage); - $this->assertEquals($productImage->getCustomAttributes(), ''); + $this->assertEquals($productImage->getCustomAttributes(), []); $this->assertEquals($productImage->getClass(), 'product-image-photo'); $this->assertEquals($productImage->getRatio(), 1.25); $this->assertEquals($productImage->getLabel(), $expectation['label']); diff --git a/dev/tests/static/framework/Magento/Sniffs/Annotation/ClassAnnotationStructureSniff.php b/dev/tests/static/framework/Magento/Sniffs/Annotation/ClassAnnotationStructureSniff.php deleted file mode 100644 index 43df5658bbe0d..0000000000000 --- a/dev/tests/static/framework/Magento/Sniffs/Annotation/ClassAnnotationStructureSniff.php +++ /dev/null @@ -1,125 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); -namespace Magento\Sniffs\Annotation; - -use PHP_CodeSniffer\Sniffs\Sniff; -use PHP_CodeSniffer\Files\File; - -/** - * Sniff to validate structure of class, interface annotations - */ -class ClassAnnotationStructureSniff implements Sniff -{ - /** - * @var AnnotationFormatValidator - */ - private $annotationFormatValidator; - - /** - * @inheritdoc - */ - public function register() - { - return [ - T_CLASS - ]; - } - - /** - * AnnotationStructureSniff constructor. - */ - public function __construct() - { - $this->annotationFormatValidator = new AnnotationFormatValidator(); - } - - /** - * Validates whether annotation block exists for interface, abstract or final classes - * - * @param File $phpcsFile - * @param int $previousCommentClosePtr - * @param int $stackPtr - */ - private function validateInterfaceOrAbstractOrFinalClassAnnotationBlockExists( - File $phpcsFile, - int $previousCommentClosePtr, - int $stackPtr - ) : void { - $tokens = $phpcsFile->getTokens(); - if ($tokens[$stackPtr]['type'] === 'T_CLASS') { - if ($tokens[$stackPtr - 2]['type'] === 'T_ABSTRACT' && - $tokens[$stackPtr - 4]['content'] != $tokens[$previousCommentClosePtr]['content'] - ) { - $error = 'Interface or abstract class is missing annotation block'; - $phpcsFile->addFixableError($error, $stackPtr, 'ClassAnnotation'); - } - if ($tokens[$stackPtr - 2]['type'] === 'T_FINAL' && - $tokens[$stackPtr - 4]['content'] != $tokens[$previousCommentClosePtr]['content'] - ) { - $error = 'Final class is missing annotation block'; - $phpcsFile->addFixableError($error, $stackPtr, 'ClassAnnotation'); - } - } - } - - /** - * Validates whether annotation block exists - * - * @param File $phpcsFile - * @param int $previousCommentClosePtr - * @param int $stackPtr - */ - private function validateAnnotationBlockExists(File $phpcsFile, int $previousCommentClosePtr, int $stackPtr) : void - { - $tokens = $phpcsFile->getTokens(); - $this->validateInterfaceOrAbstractOrFinalClassAnnotationBlockExists( - $phpcsFile, - $previousCommentClosePtr, - $stackPtr - ); - if ($tokens[$stackPtr - 2]['content'] != 'class' && $tokens[$stackPtr - 2]['content'] != 'abstract' - && $tokens[$stackPtr - 2]['content'] != 'final' - && $tokens[$stackPtr - 2]['content'] !== $tokens[$previousCommentClosePtr]['content'] - ) { - $error = 'Class is missing annotation block'; - $phpcsFile->addFixableError($error, $stackPtr, 'ClassAnnotation'); - } - } - - /** - * @inheritdoc - */ - public function process(File $phpcsFile, $stackPtr) - { - $tokens = $phpcsFile->getTokens(); - $previousCommentClosePtr = $phpcsFile->findPrevious(T_DOC_COMMENT_CLOSE_TAG, $stackPtr - 1, 0); - if (!$previousCommentClosePtr) { - $phpcsFile->addError('Comment block is missing', $stackPtr -1, 'MethodArguments'); - return; - } - $this->validateAnnotationBlockExists($phpcsFile, $previousCommentClosePtr, $stackPtr); - $commentStartPtr = $phpcsFile->findPrevious(T_DOC_COMMENT_OPEN_TAG, $stackPtr - 1, 0); - $commentCloserPtr = $tokens[$commentStartPtr]['comment_closer']; - $emptyTypeTokens = [ - T_DOC_COMMENT_WHITESPACE, - T_DOC_COMMENT_STAR - ]; - $shortPtr = $phpcsFile->findNext($emptyTypeTokens, $commentStartPtr +1, $commentCloserPtr, true); - if ($shortPtr === false) { - $error = 'Annotation block is empty'; - $phpcsFile->addError($error, $commentStartPtr, 'MethodAnnotation'); - } else { - $this->annotationFormatValidator->validateDescriptionFormatStructure( - $phpcsFile, - $commentStartPtr, - (int) $shortPtr, - $previousCommentClosePtr, - $emptyTypeTokens - ); - } - } -} diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/ClassAnnotationStructureSniffTest.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/ClassAnnotationStructureSniffTest.php deleted file mode 100644 index afe559fdd6759..0000000000000 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/ClassAnnotationStructureSniffTest.php +++ /dev/null @@ -1,86 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Sniffs\Annotation; - -class ClassAnnotationStructureSniffTest extends \PHPUnit\Framework\TestCase -{ - /** - * @return array - */ - public function processDataProvider() - { - return [ - [ - 'ClassAnnotationFixture.php', - 'class_annotation_errors.txt', - ], - [ - 'AbstractClassAnnotationFixture.php', - 'abstract_class_annotation_errors.txt', - ], - [ - 'ClassAnnotationNoShortDescriptionFixture.php', - 'class_annotation_noshortdescription_errors.txt', - ], - [ - 'ClassAnnotationNoSpacingBetweenLinesFixture.php', - 'class_annotation_nospacingbetweenLines_errors.txt', - ] - ]; - } - - /** - * Copy a file - * - * @param string $source - * @param string $destination - */ - private function copyFile($source, $destination) : void - { - $sourcePath = $source; - $destinationPath = $destination; - $sourceDirectory = opendir($sourcePath); - while ($readFile = readdir($sourceDirectory)) { - if ($readFile != '.' && $readFile != '..') { - if (!file_exists($destinationPath . $readFile)) { - copy($sourcePath . $readFile, $destinationPath . $readFile); - } - } - } - closedir($sourceDirectory); - } - - /** - * @param string $fileUnderTest - * @param string $expectedReportFile - * @dataProvider processDataProvider - */ - public function testProcess($fileUnderTest, $expectedReportFile) - { - $reportFile = __DIR__ . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . 'phpcs_report.txt'; - $this->copyFile( - __DIR__ . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR, - TESTS_TEMP_DIR . DIRECTORY_SEPARATOR - ); - $codeSniffer = new \Magento\TestFramework\CodingStandard\Tool\CodeSniffer( - 'Magento', - $reportFile, - new \Magento\TestFramework\CodingStandard\Tool\CodeSniffer\Wrapper() - ); - $result = $codeSniffer->run( - [TESTS_TEMP_DIR . DIRECTORY_SEPARATOR . $fileUnderTest] - ); - $actual = file_get_contents($reportFile); - $expected = file_get_contents( - TESTS_TEMP_DIR . DIRECTORY_SEPARATOR . $expectedReportFile - ); - unlink($reportFile); - $this->assertEquals(2, $result); - $this->assertEquals($expected, $actual); - } -} diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/AbstractClassAnnotationFixture.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/AbstractClassAnnotationFixture.php deleted file mode 100644 index 43973b3083e56..0000000000000 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/AbstractClassAnnotationFixture.php +++ /dev/null @@ -1,13 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -declare(strict_types=1); - -namespace Magento\Sniffs\Annotation; - -abstract class AbstractClassAnnotationFixture -{ -} diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/ClassAnnotationFixture.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/ClassAnnotationFixture.php deleted file mode 100644 index f9a997dcfeb9d..0000000000000 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/ClassAnnotationFixture.php +++ /dev/null @@ -1,20 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -declare(strict_types=1); - -namespace Magento\Sniffs\Annotation; - -class ClassAnnotationFixture -{ - /** - * - * @inheritdoc - */ - public function getProductListDefaultSortBy1() - { - } -} diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/ClassAnnotationNoShortDescriptionFixture.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/ClassAnnotationNoShortDescriptionFixture.php deleted file mode 100644 index be49cc6c788e9..0000000000000 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/ClassAnnotationNoShortDescriptionFixture.php +++ /dev/null @@ -1,16 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -declare(strict_types=1); - -namespace Magento\Sniffs\Annotation; - -/** - * @SuppressWarnings(PHPMD.NumberOfChildren) - */ -class ClassAnnotationNoShortDescriptionFixture -{ -} diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/ClassAnnotationNoSpacingBetweenLinesFixture.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/ClassAnnotationNoSpacingBetweenLinesFixture.php deleted file mode 100644 index 382185734c281..0000000000000 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/ClassAnnotationNoSpacingBetweenLinesFixture.php +++ /dev/null @@ -1,18 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -declare(strict_types=1); - -namespace Magento\Sniffs\Annotation; - -/** - * Class ClassAnnotationNoSpacingBetweenLinesFixture - * @see Magento\Sniffs\Annotation\_files - */ -class ClassAnnotationNoSpacingBetweenLinesFixture -{ - -} diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/abstract_class_annotation_errors.txt b/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/abstract_class_annotation_errors.txt deleted file mode 100644 index 23698cfb72e27..0000000000000 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/abstract_class_annotation_errors.txt +++ /dev/null @@ -1,10 +0,0 @@ -FILE: ...o/Sniffs/Annotation/_fixtures/AbstractClassAnnotationFixture.php ----------------------------------------------------------------------- -FOUND 1 ERROR AFFECTING 1 LINE ----------------------------------------------------------------------- - 11 | ERROR | [x] Interface or abstract class is missing annotation - | | block ----------------------------------------------------------------------- -PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY\n - - diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/class_annotation_errors.txt b/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/class_annotation_errors.txt deleted file mode 100644 index aca2721131608..0000000000000 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/class_annotation_errors.txt +++ /dev/null @@ -1,11 +0,0 @@ - -FILE: ...Magento/Sniffs/Annotation/_fixtures/class_annotation_fixture.php ----------------------------------------------------------------------- -FOUND 1 ERROR AFFECTING 1 LINE ----------------------------------------------------------------------- - 11 | ERROR | [x] Class is missing annotation block ----------------------------------------------------------------------- -PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY ----------------------------------------------------------------------- - - diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/class_annotation_noshortdescription_errors.txt b/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/class_annotation_noshortdescription_errors.txt deleted file mode 100644 index ecc702c568934..0000000000000 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/class_annotation_noshortdescription_errors.txt +++ /dev/null @@ -1,14 +0,0 @@ -'\n -FILE: ...nnotation/_fixtures/ClassAnnotationNoShortDescriptionFixture.php\n -----------------------------------------------------------------------\n -FOUND 1 ERROR AFFECTING 1 LINE\n -----------------------------------------------------------------------\n - 11 | ERROR | [x] Missing short description\n -----------------------------------------------------------------------\n -PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY\n -----------------------------------------------------------------------\n -\n -\n -' - - diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/class_annotation_nospacingbetweenLines_errors.txt b/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/class_annotation_nospacingbetweenLines_errors.txt deleted file mode 100644 index 0102ca7f79a1f..0000000000000 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/class_annotation_nospacingbetweenLines_errors.txt +++ /dev/null @@ -1,12 +0,0 @@ -'\n -FILE: ...tation/_fixtures/ClassAnnotationNoSpacingBetweenLinesFixture.php\n -----------------------------------------------------------------------\n -FOUND 1 ERROR AFFECTING 1 LINE\n -----------------------------------------------------------------------\n - 13 | ERROR | [x] There must be exactly one blank line between lines short and long descriptions\n -----------------------------------------------------------------------\n -PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY\n -----------------------------------------------------------------------\n -\n -\n -' diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Backend/_files/controller_acl_test_whitelist_ce.txt b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Backend/_files/controller_acl_test_whitelist_ce.txt index 1119824f217bb..8561818022112 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Backend/_files/controller_acl_test_whitelist_ce.txt +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Backend/_files/controller_acl_test_whitelist_ce.txt @@ -21,6 +21,7 @@ Magento\ConfigurableProduct\Controller\Adminhtml\Product\Initialization\Helper\P Magento\Downloadable\Controller\Adminhtml\Product\Initialization\Helper\Plugin\Downloadable Magento\Paypal\Controller\Adminhtml\Transparent\RequestSecureToken Magento\Paypal\Controller\Adminhtml\Transparent\Response +Magento\Paypal\Controller\Adminhtml\Transparent\Redirect Magento\Sales\Controller\Adminhtml\Order\CreditmemoLoader Magento\Search\Controller\Adminhtml\Synonyms\ResultPageBuilder Magento\Shipping\Controller\Adminhtml\Order\ShipmentLoader diff --git a/lib/internal/Magento/Framework/Cache/Backend/Database.php b/lib/internal/Magento/Framework/Cache/Backend/Database.php index 231a8584cc8a5..a33c8f1a0de0e 100644 --- a/lib/internal/Magento/Framework/Cache/Backend/Database.php +++ b/lib/internal/Magento/Framework/Cache/Backend/Database.php @@ -59,6 +59,7 @@ class Database extends \Zend_Cache_Backend implements \Zend_Cache_Backend_Extend * Constructor * * @param array $options associative array of options + * @throws \Zend_Cache_Exception */ public function __construct($options = []) { @@ -82,6 +83,7 @@ public function __construct($options = []) * Get DB adapter * * @return \Magento\Framework\DB\Adapter\AdapterInterface + * @throws \Zend_Cache_Exception */ protected function _getConnection() { @@ -106,6 +108,7 @@ protected function _getConnection() * Get table name where data is stored * * @return string + * @throws \Zend_Cache_Exception */ protected function _getDataTable() { @@ -122,6 +125,7 @@ protected function _getDataTable() * Get table name where tags are stored * * @return string + * @throws \Zend_Cache_Exception */ protected function _getTagsTable() { @@ -139,9 +143,10 @@ protected function _getTagsTable() * * Note : return value is always "string" (unserialization is done by the core not by the backend) * - * @param string $id Cache id - * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @param string $id Cache id + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested * @return string|false cached datas + * @throws \Zend_Cache_Exception */ public function load($id, $doNotTestCacheValidity = false) { @@ -166,8 +171,9 @@ public function load($id, $doNotTestCacheValidity = false) /** * Test if a cache is available or not (for the given id) * - * @param string $id cache id + * @param string $id cache id * @return mixed|false (a cache is not available) or "last modified" timestamp (int) of the available cache record + * @throws \Zend_Cache_Exception */ public function test($id) { @@ -196,11 +202,13 @@ public function test($id) * Note : $data is always "string" (serialization is done by the * core not by the backend) * - * @param string $data Datas to cache - * @param string $id Cache id - * @param string[] $tags Array of strings, the cache record will be tagged by each string entry - * @param int|bool $specificLifetime Integer to set a specific lifetime or null for infinite lifetime + * @param string $data Datas to cache + * @param string $id Cache id + * @param string[] $tags Array of strings, the cache record will be tagged by each string entry + * @param int|bool $specificLifetime Integer to set a specific lifetime or null for infinite lifetime * @return bool true if no problem + * @throws \Zend_Db_Statement_Exception + * @throws \Zend_Cache_Exception */ public function save($data, $id, $tags = [], $specificLifetime = false) { @@ -239,8 +247,9 @@ public function save($data, $id, $tags = [], $specificLifetime = false) /** * Remove a cache record * - * @param string $id Cache id - * @return boolean True if no problem + * @param string $id Cache id + * @return int|boolean Number of affected rows or false on failure + * @throws \Zend_Cache_Exception */ public function remove($id) { @@ -266,9 +275,10 @@ public function remove($id) * \Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags * ($tags can be an array of strings or a single string) * - * @param string $mode Clean mode - * @param string[] $tags Array of tags + * @param string $mode Clean mode + * @param string[] $tags Array of tags * @return boolean true if no problem + * @throws \Zend_Cache_Exception */ public function clean($mode = \Zend_Cache::CLEANING_MODE_ALL, $tags = []) { @@ -301,6 +311,7 @@ public function clean($mode = \Zend_Cache::CLEANING_MODE_ALL, $tags = []) * Return an array of stored cache ids * * @return string[] array of stored cache ids (string) + * @throws \Zend_Cache_Exception */ public function getIds() { @@ -316,6 +327,7 @@ public function getIds() * Return an array of stored tags * * @return string[] array of stored tags (string) + * @throws \Zend_Cache_Exception */ public function getTags() { @@ -330,6 +342,7 @@ public function getTags() * * @param string[] $tags array of tags * @return string[] array of matching cache ids (string) + * @throws \Zend_Cache_Exception */ public function getIdsMatchingTags($tags = []) { @@ -356,6 +369,7 @@ public function getIdsMatchingTags($tags = []) * * @param string[] $tags array of tags * @return string[] array of not matching cache ids (string) + * @throws \Zend_Cache_Exception */ public function getIdsNotMatchingTags($tags = []) { @@ -369,6 +383,7 @@ public function getIdsNotMatchingTags($tags = []) * * @param string[] $tags array of tags * @return string[] array of any matching cache ids (string) + * @throws \Zend_Cache_Exception */ public function getIdsMatchingAnyTags($tags = []) { @@ -404,6 +419,7 @@ public function getFillingPercentage() * * @param string $id cache id * @return array|false array of metadatas (false if the cache id is not found) + * @throws \Zend_Cache_Exception */ public function getMetadatas($id) { @@ -425,6 +441,7 @@ public function getMetadatas($id) * @param string $id cache id * @param int $extraLifetime * @return boolean true if ok + * @throws \Zend_Cache_Exception */ public function touch($id, $extraLifetime) { @@ -471,6 +488,7 @@ public function getCapabilities() * @param string $id * @param string[] $tags * @return bool + * @throws \Zend_Cache_Exception */ protected function _saveTags($id, $tags) { @@ -509,6 +527,8 @@ protected function _saveTags($id, $tags) * @param string $mode * @param string[] $tags * @return bool + * @throws \Zend_Cache_Exception + * @throws \Zend_Db_Statement_Exception * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function _cleanByTags($mode, $tags) @@ -558,6 +578,7 @@ protected function _cleanByTags($mode, $tags) * * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection * @return bool + * @throws \Zend_Cache_Exception */ private function cleanAll(\Magento\Framework\DB\Adapter\AdapterInterface $connection) { @@ -575,6 +596,7 @@ private function cleanAll(\Magento\Framework\DB\Adapter\AdapterInterface $connec * * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection * @return bool + * @throws \Zend_Cache_Exception */ private function cleanOld(\Magento\Framework\DB\Adapter\AdapterInterface $connection) { diff --git a/lib/internal/Magento/Framework/Filesystem/Directory/Read.php b/lib/internal/Magento/Framework/Filesystem/Directory/Read.php index a3a4cec59953f..e23eadd57d866 100644 --- a/lib/internal/Magento/Framework/Filesystem/Directory/Read.php +++ b/lib/internal/Magento/Framework/Filesystem/Directory/Read.php @@ -9,6 +9,7 @@ use Magento\Framework\Exception\ValidatorException; /** + * Filesystem directory instance for read operations * @api */ class Read implements ReadInterface @@ -40,8 +41,6 @@ class Read implements ReadInterface private $pathValidator; /** - * Constructor. Set properties. - * * @param \Magento\Framework\Filesystem\File\ReadFactory $fileFactory * @param \Magento\Framework\Filesystem\DriverInterface $driver * @param string $path @@ -60,6 +59,8 @@ public function __construct( } /** + * Validate the path is correct and within the directory + * * @param null|string $path * @param null|string $scheme * @param bool $absolutePath @@ -96,8 +97,7 @@ protected function setPath($path) } /** - * Retrieves absolute path - * E.g.: /var/www/application/file.txt + * Retrieves absolute path i.e. /var/www/application/file.txt * * @param string $path * @param string $scheme @@ -151,7 +151,7 @@ public function read($path = null) /** * Read recursively * - * @param null $path + * @param string|null $path * @throws ValidatorException * @return string[] */ @@ -207,7 +207,9 @@ public function isExist($path = null) { $this->validatePath($path); - return $this->driver->isExists($this->driver->getAbsolutePath($this->path, $path)); + return $this->driver->isExists( + $this->driver->getRealPathSafety($this->driver->getAbsolutePath($this->path, $path)) + ); } /** diff --git a/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Response.php b/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Response.php index 2adff8162e5a3..dfc68cf975d50 100644 --- a/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Response.php +++ b/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Response.php @@ -19,11 +19,6 @@ class Response extends \Zend\Http\PhpEnvironment\Response implements \Magento\Fr */ protected $isRedirect = false; - /** - * @var bool - */ - private $headersSent; - /** * @inheritdoc */ @@ -192,27 +187,4 @@ public function __sleep() { return ['content', 'isRedirect', 'statusCode']; } - - /** - * Sending provided headers. - * - * Had to be overridden because the original did not work correctly with multi-headers. - */ - public function sendHeaders() - { - if ($this->headersSent()) { - return $this; - } - - $status = $this->renderStatusLine(); - header($status); - - /** @var \Zend\Http\Header\HeaderInterface $header */ - foreach ($this->getHeaders() as $header) { - header($header->toString(), false); - } - - $this->headersSent = true; - return $this; - } } diff --git a/lib/internal/Magento/Framework/Lock/Backend/Cache.php b/lib/internal/Magento/Framework/Lock/Backend/Cache.php index 1bfc7fdeda255..5e1cdd706e326 100644 --- a/lib/internal/Magento/Framework/Lock/Backend/Cache.php +++ b/lib/internal/Magento/Framework/Lock/Backend/Cache.php @@ -49,7 +49,7 @@ public function lock(string $name, int $timeout = -1): bool */ public function unlock(string $name): bool { - return $this->cache->remove($this->getIdentifier($name)); + return (bool)$this->cache->remove($this->getIdentifier($name)); } /** diff --git a/lib/internal/Magento/Framework/Lock/Test/Unit/Backend/CacheTest.php b/lib/internal/Magento/Framework/Lock/Test/Unit/Backend/CacheTest.php new file mode 100644 index 0000000000000..2ec812092d654 --- /dev/null +++ b/lib/internal/Magento/Framework/Lock/Test/Unit/Backend/CacheTest.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\Lock\Test\Unit\Backend; + +use Magento\Framework\Cache\FrontendInterface; +use Magento\Framework\Lock\Backend\Cache; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class CacheTest extends TestCase +{ + const LOCK_PREFIX = 'LOCKED_RECORD_INFO_'; + + /** + * @var FrontendInterface|MockObject + */ + private $frontendCacheMock; + + /** + * @var Cache + */ + private $cache; + + /** + * @inheritDoc + */ + public function setUp() + { + $this->frontendCacheMock = $this->createMock(FrontendInterface::class); + + $objectManager = new ObjectManagerHelper($this); + + $this->cache = $objectManager->getObject( + Cache::class, + [ + 'cache' => $this->frontendCacheMock + ] + ); + } + + /** + * Verify released a lock. + * + * @return void + */ + public function testUnlock(): void + { + $identifier = 'lock_name'; + + $this->frontendCacheMock + ->expects($this->once()) + ->method('remove') + ->with(self::LOCK_PREFIX . $identifier) + ->willReturn(true); + + $this->assertEquals(true, $this->cache->unlock($identifier)); + } +} diff --git a/lib/web/mage/gallery/gallery.js b/lib/web/mage/gallery/gallery.js index 65a14f77de257..02ba72f64127b 100644 --- a/lib/web/mage/gallery/gallery.js +++ b/lib/web/mage/gallery/gallery.js @@ -480,7 +480,7 @@ define([ settings.fotoramaApi.load(data); mainImageIndex = getMainImageIndex(data); - if (mainImageIndex) { + if (settings.fotoramaApi.activeIndex !== mainImageIndex) { settings.fotoramaApi.show({ index: mainImageIndex, time: 0 diff --git a/pub/health_check.php b/pub/health_check.php index 4bdda68851bde..8642877323bbc 100644 --- a/pub/health_check.php +++ b/pub/health_check.php @@ -63,8 +63,10 @@ } $cacheBackendClass = $cacheConfig[ConfigOptionsListConstants::CONFIG_PATH_BACKEND]; try { + /** @var \Magento\Framework\App\Cache\Frontend\Factory $cacheFrontendFactory */ + $cacheFrontendFactory = $objectManager->get(Magento\Framework\App\Cache\Frontend\Factory::class); /** @var \Zend_Cache_Backend_Interface $backend */ - $backend = new $cacheBackendClass($cacheConfig[ConfigOptionsListConstants::CONFIG_PATH_BACKEND_OPTIONS]); + $backend = $cacheFrontendFactory->create($cacheConfig); $backend->test('test_cache_id'); } catch (\Exception $e) { http_response_code(500);