diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingNavigateMenuTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingNavigateMenuTest.xml index 67d6715285697..d400bcf5f22fc 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingNavigateMenuTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingNavigateMenuTest.xml @@ -30,6 +30,7 @@ + diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginAfterJSMinificationTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginAfterJSMinificationTest.xml index 38749dfd792ca..5550e3b317b0d 100644 --- a/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginAfterJSMinificationTest.xml +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginAfterJSMinificationTest.xml @@ -18,19 +18,18 @@ - - - + - + + diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/AddAttributeToTemplate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/AddAttributeToTemplate.php index a05602403e08f..21312e4614dc3 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/AddAttributeToTemplate.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/AddAttributeToTemplate.php @@ -17,9 +17,11 @@ use Magento\Eav\Api\Data\AttributeGroupInterfaceFactory; use Magento\Eav\Api\Data\AttributeInterface; use Magento\Eav\Api\Data\AttributeSetInterface; +use Magento\Eav\Model\Cache\Type as CacheType; use Magento\Framework\Api\ExtensionAttributesFactory; use Magento\Framework\Api\SearchCriteriaBuilder; use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\App\CacheInterface; use Magento\Framework\App\ObjectManager; use Magento\Framework\Controller\Result\Json; use Magento\Framework\Controller\Result\JsonFactory; @@ -29,7 +31,7 @@ use Psr\Log\LoggerInterface; /** - * Class AddAttributeToTemplate + * Assign attribute to attribute set. * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -80,6 +82,11 @@ class AddAttributeToTemplate extends Product implements HttpPostActionInterface */ protected $extensionAttributesFactory; + /** + * @var CacheInterface + */ + private $cache; + /** * Constructor * @@ -94,8 +101,8 @@ class AddAttributeToTemplate extends Product implements HttpPostActionInterface * @param AttributeManagementInterface $attributeManagement * @param LoggerInterface $logger * @param ExtensionAttributesFactory $extensionAttributesFactory + * @param CacheInterface|null $cache * @SuppressWarnings(PHPMD.ExcessiveParameterList) - * @SuppressWarnings(PHPMD.LongVariable) * @SuppressWarnings(PHPMD.NPathComplexity) */ public function __construct( @@ -109,7 +116,8 @@ public function __construct( SearchCriteriaBuilder $searchCriteriaBuilder = null, AttributeManagementInterface $attributeManagement = null, LoggerInterface $logger = null, - ExtensionAttributesFactory $extensionAttributesFactory = null + ExtensionAttributesFactory $extensionAttributesFactory = null, + CacheInterface $cache = null ) { parent::__construct($context, $productBuilder); $this->resultJsonFactory = $resultJsonFactory; @@ -129,6 +137,7 @@ public function __construct( ->get(LoggerInterface::class); $this->extensionAttributesFactory = $extensionAttributesFactory ?: ObjectManager::getInstance() ->get(ExtensionAttributesFactory::class); + $this->cache = $cache ?? ObjectManager::getInstance()->get(CacheInterface::class); } /** @@ -203,6 +212,7 @@ function (AttributeInterface $attribute) use ($attributeSet, $attributeGroup) { ); } ); + $this->cache->clean([CacheType::CACHE_TAG]); } catch (LocalizedException $e) { $response->setError(true); $response->setMessage($e->getMessage()); @@ -223,7 +233,7 @@ function (AttributeInterface $attribute) use ($attributeSet, $attributeGroup) { */ private function getBasicAttributeSearchCriteriaBuilder() { - $attributeIds = (array) $this->getRequest()->getParam('attributeIds', []); + $attributeIds = (array)$this->getRequest()->getParam('attributeIds', []); if (empty($attributeIds['selected'])) { throw new LocalizedException(__('Attributes were missing and must be specified.')); diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Flat/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Category/Flat/Action/Full.php index a62e3d8f83b85..57fb3d64dea4a 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Flat/Action/Full.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Flat/Action/Full.php @@ -79,6 +79,7 @@ protected function populateFlatTables(array $stores) } $category['store_id'] = $store->getId(); $data[] = $this->prepareValuesToInsert( + // phpcs:ignore Magento2.Performance.ForeachArrayMerge array_merge($category, $attributesData[$category[$linkField]]) ); } @@ -183,7 +184,7 @@ private function getActualStoreTablesForCategoryFlat(): array foreach ($this->storeManager->getStores() as $store) { $actualStoreTables[] = sprintf( '%s_store_%s', - $this->connection->getTableName('catalog_category_flat'), + $this->connection->getTableName($this->getTableName('catalog_category_flat')), $store->getId() ); } @@ -199,7 +200,7 @@ private function getActualStoreTablesForCategoryFlat(): array private function deleteAbandonedStoreCategoryFlatTables(): void { $existentTables = $this->connection->getTables( - $this->connection->getTableName('catalog_category_flat_store_%') + $this->connection->getTableName($this->getTableName('catalog_category_flat_store_%')) ); $actualStoreTables = $this->getActualStoreTablesForCategoryFlat(); diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/AbstractAction.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/AbstractAction.php index a0acacd4dfd2f..565ac1b121980 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/AbstractAction.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/AbstractAction.php @@ -222,7 +222,7 @@ protected function _updateRelationProducts($storeId, $productIds = null) ['t' => $this->_productIndexerHelper->getTable($relation->getTable())], ['entity_table.entity_id', $relation->getChildFieldName(), new \Zend_Db_Expr('1')] )->join( - ['entity_table' => $this->_connection->getTableName('catalog_product_entity')], + ['entity_table' => $this->_productIndexerHelper->getTable('catalog_product_entity')], "entity_table.{$metadata->getLinkField()} = t.{$relation->getParentFieldName()}", [] )->join( diff --git a/app/code/Magento/Catalog/Model/Product/Type/Price.php b/app/code/Magento/Catalog/Model/Product/Type/Price.php index 814865fa75917..e702965270639 100644 --- a/app/code/Magento/Catalog/Model/Product/Type/Price.php +++ b/app/code/Magento/Catalog/Model/Product/Type/Price.php @@ -262,7 +262,7 @@ protected function _applyTierPrice($product, $qty, $finalPrice) $tierPrice = $product->getTierPrice($qty); if (is_numeric($tierPrice)) { - $finalPrice = min($finalPrice, $tierPrice); + $finalPrice = min($finalPrice, (float) $tierPrice); } return $finalPrice; } @@ -645,7 +645,7 @@ public function calculateSpecialPrice( ) { if ($specialPrice !== null && $specialPrice != false) { if ($this->_localeDate->isScopeDateInInterval($store, $specialPriceFrom, $specialPriceTo)) { - $finalPrice = min($finalPrice, $specialPrice); + $finalPrice = min($finalPrice, (float) $specialPrice); } } return $finalPrice; diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminExpandProductAttributesTabActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminExpandProductAttributesTabActionGroup.xml new file mode 100644 index 0000000000000..a423681c0a490 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminExpandProductAttributesTabActionGroup.xml @@ -0,0 +1,19 @@ + + + + + + + Expands the 'Attributes' tab on the Admin Product page. + + + + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontAttributeOptionPresentInLayeredNavigationActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontAttributeOptionPresentInLayeredNavigationActionGroup.xml new file mode 100644 index 0000000000000..46d017b9094e8 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontAttributeOptionPresentInLayeredNavigationActionGroup.xml @@ -0,0 +1,26 @@ + + + + + + + Clicks on the attribute label. Checks for attribute option presence. + + + + + + + + + + + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategorySidebarSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategorySidebarSection.xml index 2ec7997f87b7e..d629441782551 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategorySidebarSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategorySidebarSection.xml @@ -15,8 +15,10 @@ + +
- \ No newline at end of file + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckCustomAttributeValuesAfterProductSaveTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckCustomAttributeValuesAfterProductSaveTest.xml new file mode 100644 index 0000000000000..7c6f6ab66f63d --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckCustomAttributeValuesAfterProductSaveTest.xml @@ -0,0 +1,91 @@ + + + + + + + + + <description value="Checks that saved custom attribute values are reflected on the product's edit page"/> + <severity value="MAJOR"/> + <testCaseId value="MC-29653"/> + <useCaseId value="MC-29023"/> + <group value="catalog"/> + </annotations> + <before> + <!-- Create multi select product attribute --> + <createData entity="productAttributeMultiselectTwoOptions" stepKey="createMultiSelectProductAttribute"/> + <!-- Add options to created product attribute --> + <createData entity="productAttributeOption1" stepKey="addFirstOptionToAttribute"> + <requiredEntity createDataKey="createMultiSelectProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="addSecondOptionToAttribute"> + <requiredEntity createDataKey="createMultiSelectProductAttribute"/> + </createData> + <createData entity="productAttributeOption3" stepKey="addThirdOptionToAttribute"> + <requiredEntity createDataKey="createMultiSelectProductAttribute"/> + </createData> + <!-- Create simple product --> + <createData entity="SimpleProduct2" stepKey="createSimpleProduct"/> + <magentoCLI command="indexer:reindex" arguments="catalogsearch_fulltext" stepKey="reindexCatalogSearch"/> + <!-- Login to Admin page --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <!-- Delete created entities --> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="createMultiSelectProductAttribute" stepKey="deleteMultiSelectProductAttribute"/> + <!-- Logout from Admin page --> + <actionGroup ref="logout" stepKey="logoutFromAdmin"/> + </after> + + <!-- Open created product for edit --> + <amOnPage url="{{AdminProductEditPage.url($createSimpleProduct.id$)}}" stepKey="goToProductEditPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + + <!-- Add created attribute to the product --> + <actionGroup ref="AddProductAttributeInProductModalActionGroup" stepKey="addAttributeToProduct"> + <argument name="attributeCode" value="$createMultiSelectProductAttribute.attribute_code$"/> + </actionGroup> + <waitForPageLoad stepKey="waitForAttributeAdded"/> + + <!-- Expand 'Attributes' tab --> + <actionGroup ref="AdminExpandProductAttributesTabActionGroup" stepKey="expandAttributesTab"/> + <!-- Check created attribute presents in the 'Attributes' tab --> + <seeElement selector="{{AdminProductAttributesSection.attributeDropdownByCode($createMultiSelectProductAttribute.attribute_code$)}}" stepKey="assertAttributeIsPresentInTab"/> + <!-- Select attribute options --> + <selectOption selector="{{AdminProductAttributesSection.attributeDropdownByCode($createMultiSelectProductAttribute.attribute_code$)}}" parameterArray="[$addFirstOptionToAttribute.option[store_labels][0][label]$, $addThirdOptionToAttribute.option[store_labels][0][label]$]" stepKey="selectAttributeOptions"/> + <!-- Save product --> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProductForm"/> + + <!-- Check attribute options are selected --> + <actionGroup ref="AdminExpandProductAttributesTabActionGroup" stepKey="expandAttributesTabAfterProductSave"/> + <seeOptionIsSelected selector="{{AdminProductAttributesSection.attributeDropdownByCode($createMultiSelectProductAttribute.attribute_code$)}}" userInput="$addFirstOptionToAttribute.option[store_labels][0][label]$" stepKey="assertFirstOptionIsSelected"/> + <seeOptionIsSelected selector="{{AdminProductAttributesSection.attributeDropdownByCode($createMultiSelectProductAttribute.attribute_code$)}}" userInput="$addThirdOptionToAttribute.option[store_labels][0][label]$" stepKey="assertThirdOptionIsSelected"/> + + <!-- Search for the product on Storefront --> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToHomePage"/> + <waitForPageLoad stepKey="waitForHomePageLoad"/> + <actionGroup ref="StorefrontCheckQuickSearchActionGroup" stepKey="searchProductOnStorefront"> + <argument name="phrase" value="$createSimpleProduct.name$"/> + </actionGroup> + + <!-- Assert that attribute values present in layered navigation --> + <actionGroup ref="AssertStorefrontAttributeOptionPresentInLayeredNavigationActionGroup" stepKey="assertFirstAttributeOptionPresence"> + <argument name="attributeLabel" value="$createMultiSelectProductAttribute.attribute[frontend_labels][0][label]$"/> + <argument name="attributeOptionLabel" value="$addFirstOptionToAttribute.option[store_labels][0][label]$"/> + <argument name="attributeOptionPosition" value="1"/> + </actionGroup> + <actionGroup ref="AssertStorefrontAttributeOptionPresentInLayeredNavigationActionGroup" stepKey="assertThirdAttributeOptionPresence"> + <argument name="attributeLabel" value="$createMultiSelectProductAttribute.attribute[frontend_labels][0][label]$"/> + <argument name="attributeOptionLabel" value="$addThirdOptionToAttribute.option[store_labels][0][label]$"/> + <argument name="attributeOptionPosition" value="2"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndEditVirtualProductSettingsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndEditVirtualProductSettingsTest.xml index 34451aba78f21..70cbfe7259da0 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndEditVirtualProductSettingsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndEditVirtualProductSettingsTest.xml @@ -51,7 +51,7 @@ <actionGroup ref="logout" stepKey="adminLogout"/> </after> - <actionGroup ref="DisabledWYSIWYGActionGroup" stepKey="disableWYSIWYG"/> + <comment userInput="remove me" stepKey="disableWYSIWYG"/> <!-- Create new virtual product --> <actionGroup ref="GoToSpecifiedCreateProductPageActionGroup" stepKey="createVirtualProduct"> @@ -185,6 +185,6 @@ <actionGroup ref="StorefrontOpenCartFromMinicartActionGroup" stepKey="openCart"/> <dontSeeElement selector="{{StorefrontProductCartGiftOptionSection.giftOptions}}" stepKey="dontSeeGiftOptionBtn"/> - <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> + <comment userInput="remove me" stepKey="enableWYSIWYG"/> </test> </tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDuplicateProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDuplicateProductTest.xml index 4507e1f880a86..b74efe18af708 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDuplicateProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDuplicateProductTest.xml @@ -15,6 +15,9 @@ <testCaseId value="MC-14714"/> <severity value="CRITICAL"/> <group value="mtf_migrated"/> + <skip> + <issueId value="MC-30409"/> + </skip> </annotations> <before> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductImageAssignmentForMultipleStoresTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductImageAssignmentForMultipleStoresTest.xml index 455e77666c3a2..59be8157d2f87 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductImageAssignmentForMultipleStoresTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductImageAssignmentForMultipleStoresTest.xml @@ -18,9 +18,6 @@ <testCaseId value="MAGETWO-58718"/> <group value="product"/> <group value="WYSIWYGDisabled"/> - <skip> - <issueId value="MC-13841"/> - </skip> </annotations> <before> <!-- Login Admin --> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithInactiveIncludeInMenuTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithInactiveIncludeInMenuTest.xml index 3fea9c0eed7ca..0c705c180d073 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithInactiveIncludeInMenuTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithInactiveIncludeInMenuTest.xml @@ -39,6 +39,7 @@ <click selector="{{AdminCategoryBasicFieldSection.includeInMenuLabel}}" stepKey="disableIncludeInMenu"/> <scrollTo selector="{{AdminCategoryContentSection.sectionHeader}}" x="0" y="-80" stepKey="scrollToContent"/> <click selector="{{AdminCategoryContentSection.sectionHeader}}" stepKey="selectContent"/> + <scrollTo selector="{{AdminCategoryContentSection.description}}" x="0" y="-80" stepKey="scrollToDescription"/> <fillField selector="{{AdminCategoryContentSection.description}}" userInput="Updated category Description Fields" stepKey="fillUpdatedDescription"/> <scrollTo selector="{{AdminCategorySEOSection.SectionHeader}}" x="0" y="-80" stepKey="scrollToSearchEngineOptimization"/> <click selector="{{AdminCategorySEOSection.SectionHeader}}" stepKey="selectSearchEngineOptimization"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontProductsDisplayUsingElasticSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontProductsDisplayUsingElasticSearchTest.xml new file mode 100644 index 0000000000000..3c1bcb3b352cd --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontProductsDisplayUsingElasticSearchTest.xml @@ -0,0 +1,222 @@ +<?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="StoreFrontProductsDisplayUsingElasticSearchTest"> + <annotations> + <stories value="Display All Products"/> + <title value="Display All Products on a Page"/> + <description value="Set Up Elastic Search and Display all Products on Page"/> + <testCaseId value="MC-30209"/> + <severity value="CRITICAL"/> + <group value="Catalog"/> + </annotations> + <before> + <!-- Login Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create Category and Simple Products--> + <createData entity="SimpleSubCategory" stepKey="createCategory1"/> + <createData entity="SimpleProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct2"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct3"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct4"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct5"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct6"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct7"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct8"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct9"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct10"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct11"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct12"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct13"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct14"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + + <createData entity="SimpleProduct" stepKey="createSimpleProduct15"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + + <createData entity="SimpleProduct" stepKey="createSimpleProduct16"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + + <createData entity="SimpleProduct" stepKey="createSimpleProduct17"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + + <createData entity="SimpleProduct" stepKey="createSimpleProduct18"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + + <createData entity="SimpleProduct" stepKey="createSimpleProduct19"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + + <createData entity="SimpleProduct" stepKey="createSimpleProduct20"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + + <createData entity="SimpleProduct" stepKey="createSimpleProduct21"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + + <createData entity="SimpleProduct" stepKey="createSimpleProduct22"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + + <createData entity="SimpleProduct" stepKey="createSimpleProduct23"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + + <createData entity="SimpleProduct" stepKey="createSimpleProduct24"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + + <createData entity="SimpleProduct" stepKey="createSimpleProduct25"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + + <createData entity="SimpleProduct" stepKey="createSimpleProduct26"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct27"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct28"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + + <createData entity="SimpleProduct" stepKey="createSimpleProduct29"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct30"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + + <!--Enable ElasticSearch as search engine.--> + <magentoCLI command="config:set catalog/search/engine elasticsearch6" stepKey="enableElasticSearchAsSearchEngine"/> + <magentoCLI command="indexer:reindex" stepKey="performReindexAfterElasticSearchEnable"/> + <magentoCLI command="cache:flush" stepKey="cleanCacheAfterElasticSearchEnable"/> + + </before> + <after> + <!--Delete created products, category --> + <deleteData createDataKey="createCategory1" stepKey="deleteCategory"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="createSimpleProduct2" stepKey="deleteSimpleProduct2"/> + <deleteData createDataKey="createSimpleProduct3" stepKey="deleteSimpleProduct3"/> + <deleteData createDataKey="createSimpleProduct4" stepKey="deleteSimpleProduct4"/> + <deleteData createDataKey="createSimpleProduct5" stepKey="deleteSimpleProduct5"/> + <deleteData createDataKey="createSimpleProduct6" stepKey="deleteSimpleProduct6"/> + <deleteData createDataKey="createSimpleProduct7" stepKey="deleteSimpleProduct7"/> + <deleteData createDataKey="createSimpleProduct8" stepKey="deleteSimpleProduct8"/> + <deleteData createDataKey="createSimpleProduct9" stepKey="deleteSimpleProduct9"/> + <deleteData createDataKey="createSimpleProduct10" stepKey="deleteSimpleProduct10"/> + <deleteData createDataKey="createSimpleProduct11" stepKey="deleteSimpleProduct11"/> + <deleteData createDataKey="createSimpleProduct12" stepKey="deleteSimpleProduct12"/> + <deleteData createDataKey="createSimpleProduct13" stepKey="deleteSimpleProduct13"/> + <deleteData createDataKey="createSimpleProduct14" stepKey="deleteSimpleProduct14"/> + <deleteData createDataKey="createSimpleProduct15" stepKey="deleteSimpleProduct15"/> + <deleteData createDataKey="createSimpleProduct16" stepKey="deleteSimpleProduct16"/> + <deleteData createDataKey="createSimpleProduct17" stepKey="deleteSimpleProduct17"/> + <deleteData createDataKey="createSimpleProduct18" stepKey="deleteSimpleProduct18"/> + <deleteData createDataKey="createSimpleProduct19" stepKey="deleteSimpleProduct19"/> + <deleteData createDataKey="createSimpleProduct20" stepKey="deleteSimpleProduct20"/> + <deleteData createDataKey="createSimpleProduct21" stepKey="deleteSimpleProduct21"/> + <deleteData createDataKey="createSimpleProduct22" stepKey="deleteSimpleProduct22"/> + <deleteData createDataKey="createSimpleProduct23" stepKey="deleteSimpleProduct23"/> + <deleteData createDataKey="createSimpleProduct24" stepKey="deleteSimpleProduct24"/> + <deleteData createDataKey="createSimpleProduct25" stepKey="deleteSimpleProduct25"/> + <deleteData createDataKey="createSimpleProduct26" stepKey="deleteSimpleProduct26"/> + <deleteData createDataKey="createSimpleProduct27" stepKey="deleteSimpleProduct27"/> + <deleteData createDataKey="createSimpleProduct28" stepKey="deleteSimpleProduct28"/> + <deleteData createDataKey="createSimpleProduct29" stepKey="deleteSimpleProduct29"/> + <deleteData createDataKey="createSimpleProduct30" stepKey="deleteSimpleProduct30"/> + + + <!--Revert ElasticSearch as search engine.--> + <actionGroup ref="ResetSearchEngineConfigurationActionGroup" stepKey="resetCatalogSearchConfiguration"/> + <magentoCLI command="indexer:reindex" stepKey="performReindexAfterElasticSearchDisable"/> + <magentoCLI command="cache:flush" stepKey="cleanCacheAfterElasticSearchDisable"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Open Storefront on the myCategory page--> + <amOnPage url="/$$createCategory1.name$$.html" stepKey="GoToStorefrontCategory"/> + <waitForPageLoad stepKey="waitForStorefrontCategoryPageLoad"/> + + <!--Select 12 items per page and verify number of products displayed in each page --> + <conditionalClick selector="{{StorefrontCategoryTopToolbarSection.gridMode}}" visible="true" dependentSelector="{{StorefrontCategoryTopToolbarSection.gridMode}}" stepKey="seeProductGridIsActive"/> + <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" stepKey="scrollToBottomToolbarSection"/> + <selectOption selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" userInput="12" stepKey="selectPerPageOption"/> + <!--Verify number of products displayed in First Page --> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="12" stepKey="seeNumberOfProductsInFirstPage"/> + <!--Verify number of products displayed in Second Page --> + <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="scrollToNextButton"/> + <click selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="clickOnNextPage"/> + <waitForPageLoad stepKey="waitForPageToLoad4"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="12" stepKey="seeNumberOfProductsInSecondPage"/> + <!--Verify number of products displayed in third Page --> + <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="scrollToNextButton1"/> + <click selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="clickOnNextPage1"/> + <waitForPageLoad stepKey="waitForPageToLoad2"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="6" stepKey="seeNumberOfProductsInThirdPage"/> + + <!--Select First Page using page number--> + <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.previousPage}}" stepKey="scrollToPreviousPage4"/> + <click selector="{{StorefrontCategoryBottomToolbarSection.pageNumber('1')}}" stepKey="clickOnFirstPage"/> + <waitForPageLoad stepKey="waitForPageToLoad9"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="12" stepKey="seeNumberOfProductsFirstPage2"/> + <!--Select 24 items per page and verify number of products displayed in each page --> + <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" stepKey="scrollToPerPage"/> + <selectOption selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" userInput="24" stepKey="selectPerPageOption1"/> + <waitForPageLoad stepKey="waitForPageToLoad10"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="24" stepKey="seeNumberOfProductsInFirstPage3"/> + <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="scrollToNextButton2"/> + <click selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="clickOnNextPage2"/> + <waitForPageLoad stepKey="waitForPageToLoad11"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="6" stepKey="seeNumberOfProductsInSecondPage3"/> + <!--Select First Page using page number--> + <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.pageNumber('1')}}" stepKey="scrollToPreviousPage5"/> + <click selector="{{StorefrontCategoryBottomToolbarSection.pageNumber('1')}}" stepKey="clickOnFirstPage2"/> + <waitForPageLoad stepKey="waitForPageToLoad13"/> + <!--Select 36 items per page and verify number of products displayed in each page --> + <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" stepKey="scrollToPerPage4"/> + <selectOption selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" userInput="36" stepKey="selectPerPageOption2"/> + <waitForPageLoad stepKey="waitForPageToLoad12"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="30" stepKey="seeNumberOfProductsInFirstPage4"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/view/frontend/web/js/product/provider.js b/app/code/Magento/Catalog/view/frontend/web/js/product/provider.js index b29ebe7d57d1c..ca9381c45e2ab 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/product/provider.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/product/provider.js @@ -143,7 +143,8 @@ define([ _.each(ids, function (id) { if ( currentTime - id['added_at'] < ~~this.idsStorage.lifetime && - !_.contains(currentProductIds, id['product_id']) + !_.contains(currentProductIds, id['product_id']) && + (!id.hasOwnProperty('website_id') || id['website_id'] === window.checkout.websiteId) ) { _ids[id['product_id']] = id; diff --git a/app/code/Magento/Catalog/view/frontend/web/js/product/storage/ids-storage-compare.js b/app/code/Magento/Catalog/view/frontend/web/js/product/storage/ids-storage-compare.js index 2dc6105ca24d5..a904d8ed3b3da 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/product/storage/ids-storage-compare.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/product/storage/ids-storage-compare.js @@ -72,7 +72,8 @@ define([ _.each(data, function (item) { result[item.id] = { 'added_at': new Date().getTime() / 1000, - 'product_id': item.id + 'product_id': item.id, + 'website_id': window.checkout.websiteId }; }); diff --git a/app/code/Magento/Catalog/view/frontend/web/js/product/view/provider.js b/app/code/Magento/Catalog/view/frontend/web/js/product/view/provider.js index 9d3746973ef58..f4ce882dd668b 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/product/view/provider.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/product/view/provider.js @@ -90,7 +90,8 @@ define([ _.each(this.data.items, function (item, key) { result[key] = { 'added_at': new Date().getTime() / 1000, - 'product_id': key + 'product_id': key, + 'website_id': window.checkout.websiteId }; }, this); diff --git a/app/code/Magento/Catalog/view/frontend/web/template/product/image_with_borders.html b/app/code/Magento/Catalog/view/frontend/web/template/product/image_with_borders.html index d59237c190f71..f8b8ede792566 100644 --- a/app/code/Magento/Catalog/view/frontend/web/template/product/image_with_borders.html +++ b/app/code/Magento/Catalog/view/frontend/web/template/product/image_with_borders.html @@ -6,6 +6,6 @@ --> <span class="product-image-container" data-bind="style: {width: width + 'px'}"> <span class="product-image-wrapper" data-bind="style: {'padding-bottom': height/width*100 + '%'}"> - <img class="product-image-photo" data-bind="attr: {src: src, alt: alt}, style: {width: width + 'px', height: height + 'px'}" /> + <img class="product-image-photo" data-bind="attr: {src: src, alt: alt}, style: {width: 'auto', height: 'auto'}" /> </span> </span> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleAndConfigurableProductTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleAndConfigurableProductTest.xml index 2f12df8b75fc6..514366fa45c52 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleAndConfigurableProductTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleAndConfigurableProductTest.xml @@ -17,9 +17,6 @@ <testCaseId value="MC-14770"/> <group value="CatalogRule"/> <group value="mtf_migrated"/> - <skip> - <issueId value="MC-17140"/> - </skip> </annotations> <before> <!-- Login as Admin --> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductAndFixedMethodTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductAndFixedMethodTest.xml index 956a2e07fc6b4..bb0a890fd0974 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductAndFixedMethodTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductAndFixedMethodTest.xml @@ -17,9 +17,6 @@ <testCaseId value="MC-14771"/> <group value="CatalogRule"/> <group value="mtf_migrated"/> - <skip> - <issueId value="MC-17140"/> - </skip> </annotations> <before> <!-- Login as Admin --> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductForNewCustomerGroupTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductForNewCustomerGroupTest.xml index e5e3cf9ead5e5..4ab2d0d491548 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductForNewCustomerGroupTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductForNewCustomerGroupTest.xml @@ -17,9 +17,6 @@ <testCaseId value="MC-14772"/> <group value="CatalogRule"/> <group value="mtf_migrated"/> - <skip> - <issueId value="MC-17140"/> - </skip> </annotations> <before> <!-- Login as Admin --> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml index a4fc6757ae6cb..3423baf7970eb 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml @@ -17,6 +17,9 @@ <severity value="CRITICAL"/> <testCaseId value="MC-79"/> <group value="CatalogRule"/> + <skip> + <issueId value="MC-30384"/> + </skip> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="login"/> diff --git a/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/CustomAttributeStockStatusFilter.php b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/CustomAttributeStockStatusFilter.php new file mode 100644 index 0000000000000..28aa3df2d56b4 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/CustomAttributeStockStatusFilter.php @@ -0,0 +1,100 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogSearch\Model\Search\FilterMapper; + +use Magento\Catalog\Model\Product; +use Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver; +use Magento\Eav\Model\Config as EavConfig; +use Magento\Framework\DB\Select; +use Magento\Framework\Search\Request\FilterInterface; + +/** + * Add stock status filter for each requested filter + */ +class CustomAttributeStockStatusFilter +{ + /** + * Suffix to append to filter name in order to generate stock status table alias for JOIN clause + */ + private const STOCK_STATUS_TABLE_ALIAS_SUFFIX = '_stock_index'; + /** + * Attribute types to apply + */ + private const TARGET_ATTRIBUTE_TYPES = [ + 'select', + 'multiselect' + ]; + /** + * @var EavConfig + */ + private $eavConfig; + /** + * @var AliasResolver + */ + private $aliasResolver; + /** + * @var StockStatusQueryBuilder|null + */ + private $stockStatusQueryBuilder; + + /** + * @param EavConfig $eavConfig + * @param AliasResolver $aliasResolver + * @param StockStatusQueryBuilder $stockStatusQueryBuilder + */ + public function __construct( + EavConfig $eavConfig, + AliasResolver $aliasResolver, + StockStatusQueryBuilder $stockStatusQueryBuilder + ) { + $this->eavConfig = $eavConfig; + $this->aliasResolver = $aliasResolver; + $this->stockStatusQueryBuilder = $stockStatusQueryBuilder; + } + + /** + * Apply stock status filter to provided filter + * + * @param Select $select + * @param mixed $values + * @param FilterInterface[] $filters + * @return Select + */ + public function apply(Select $select, $values = null, FilterInterface ...$filters): Select + { + $select = clone $select; + foreach ($filters as $filter) { + if ($this->isApplicable($filter)) { + $mainTableAlias = $this->aliasResolver->getAlias($filter); + $stockTableAlias = $mainTableAlias . self::STOCK_STATUS_TABLE_ALIAS_SUFFIX; + $select = $this->stockStatusQueryBuilder->apply( + $select, + $mainTableAlias, + $stockTableAlias, + 'source_id', + $values + ); + } + } + return $select; + } + + /** + * Check if stock status filter is applicable to provided filter + * + * @param FilterInterface $filter + * @return bool + */ + private function isApplicable(FilterInterface $filter): bool + { + $attribute = $this->eavConfig->getAttribute(Product::ENTITY, $filter->getField()); + return $attribute + && $filter->getType() === FilterInterface::TYPE_TERM + && in_array($attribute->getFrontendInput(), self::TARGET_ATTRIBUTE_TYPES, true); + } +} diff --git a/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/FilterMapper.php b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/FilterMapper.php index 7136fad5b19a9..1c4a803c1dd00 100644 --- a/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/FilterMapper.php +++ b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/FilterMapper.php @@ -9,13 +9,14 @@ use Magento\CatalogSearch\Model\Search\SelectContainer\SelectContainer; use Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver; use Magento\CatalogInventory\Model\Stock; +use Magento\Framework\App\ObjectManager; /** - * Class FilterMapper * This class applies filters to Select based on SelectContainer configuration * - * @deprecated + * @deprecated MySQL search engine is not recommended. * @see \Magento\ElasticSearch + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class FilterMapper { @@ -43,6 +44,10 @@ class FilterMapper * @var StockStatusFilter */ private $stockStatusFilter; + /** + * @var CustomAttributeStockStatusFilter + */ + private $customAttributeStockStatusFilter; /** * @param AliasResolver $aliasResolver @@ -50,24 +55,27 @@ class FilterMapper * @param FilterStrategyInterface $filterStrategy * @param VisibilityFilter $visibilityFilter * @param StockStatusFilter $stockStatusFilter + * @param CustomAttributeStockStatusFilter|null $customAttributeStockStatusFilter */ public function __construct( AliasResolver $aliasResolver, CustomAttributeFilter $customAttributeFilter, FilterStrategyInterface $filterStrategy, VisibilityFilter $visibilityFilter, - StockStatusFilter $stockStatusFilter + StockStatusFilter $stockStatusFilter, + ?CustomAttributeStockStatusFilter $customAttributeStockStatusFilter = null ) { $this->aliasResolver = $aliasResolver; $this->customAttributeFilter = $customAttributeFilter; $this->filterStrategy = $filterStrategy; $this->visibilityFilter = $visibilityFilter; $this->stockStatusFilter = $stockStatusFilter; + $this->customAttributeStockStatusFilter = $customAttributeStockStatusFilter + ?? ObjectManager::getInstance()->get(CustomAttributeStockStatusFilter::class); } /** - * Applies filters to Select query in SelectContainer - * based on SelectContainer configuration + * Applies filters to Select query in SelectContainer based on SelectContainer configuration * * @param SelectContainer $selectContainer * @return SelectContainer @@ -79,22 +87,22 @@ public function applyFilters(SelectContainer $selectContainer) { $select = $selectContainer->getSelect(); - if ($selectContainer->hasCustomAttributesFilters()) { - $select = $this->customAttributeFilter->apply($select, ...$selectContainer->getCustomAttributesFilters()); - } - - $filterType = StockStatusFilter::FILTER_JUST_ENTITY; - if ($selectContainer->hasCustomAttributesFilters()) { - $filterType = StockStatusFilter::FILTER_ENTITY_AND_SUB_PRODUCTS; - } - $select = $this->stockStatusFilter->apply( $select, Stock::STOCK_IN_STOCK, - $filterType, + StockStatusFilter::FILTER_JUST_ENTITY, $selectContainer->isShowOutOfStockEnabled() ); + if ($selectContainer->hasCustomAttributesFilters()) { + $select = $this->customAttributeFilter->apply($select, ...$selectContainer->getCustomAttributesFilters()); + $select = $this->customAttributeStockStatusFilter->apply( + $select, + $selectContainer->isShowOutOfStockEnabled() ? null : Stock::STOCK_IN_STOCK, + ...$selectContainer->getCustomAttributesFilters() + ); + } + $appliedFilters = []; if ($selectContainer->hasVisibilityFilter()) { diff --git a/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/StockStatusFilter.php b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/StockStatusFilter.php index 0e3ba0d4e669f..420c69a7325f6 100644 --- a/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/StockStatusFilter.php +++ b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/StockStatusFilter.php @@ -6,6 +6,7 @@ namespace Magento\CatalogSearch\Model\Search\FilterMapper; +use Magento\Framework\App\ObjectManager; use Magento\Framework\App\ResourceConnection; use Magento\Framework\DB\Select; use Magento\Framework\Search\Adapter\Mysql\ConditionManager; @@ -13,10 +14,9 @@ use Magento\CatalogInventory\Api\StockRegistryInterface; /** - * Class StockStatusFilter * Adds filter by stock status to base select * - * @deprecated + * @deprecated MySQL search engine is not recommended. * @see \Magento\ElasticSearch */ class StockStatusFilter @@ -56,23 +56,31 @@ class StockStatusFilter * @var StockRegistryInterface */ private $stockRegistry; + /** + * @var StockStatusQueryBuilder + */ + private $stockStatusQueryBuilder; /** * @param ResourceConnection $resourceConnection * @param ConditionManager $conditionManager * @param StockConfigurationInterface $stockConfiguration * @param StockRegistryInterface $stockRegistry + * @param StockStatusQueryBuilder|null $stockStatusQueryBuilder */ public function __construct( ResourceConnection $resourceConnection, ConditionManager $conditionManager, StockConfigurationInterface $stockConfiguration, - StockRegistryInterface $stockRegistry + StockRegistryInterface $stockRegistry, + ?StockStatusQueryBuilder $stockStatusQueryBuilder = null ) { $this->resourceConnection = $resourceConnection; $this->conditionManager = $conditionManager; $this->stockConfiguration = $stockConfiguration; $this->stockRegistry = $stockRegistry; + $this->stockStatusQueryBuilder = $stockStatusQueryBuilder + ?? ObjectManager::getInstance()->get(StockStatusQueryBuilder::class); } /** @@ -94,99 +102,27 @@ public function apply(Select $select, $stockValues, $type, $showOutOfStockFlag) $select = clone $select; $mainTableAlias = $this->extractTableAliasFromSelect($select); - $this->addMainStockStatusJoin($select, $stockValues, $mainTableAlias, $showOutOfStockFlag); + $select = $this->stockStatusQueryBuilder->apply( + $select, + $mainTableAlias, + 'stock_index', + 'entity_id', + $showOutOfStockFlag ? null : $stockValues + ); if ($type === self::FILTER_ENTITY_AND_SUB_PRODUCTS) { - $this->addSubProductsStockStatusJoin($select, $stockValues, $mainTableAlias, $showOutOfStockFlag); + $select = $this->stockStatusQueryBuilder->apply( + $select, + $mainTableAlias, + 'sub_products_stock_index', + 'source_id', + $showOutOfStockFlag ? null : $stockValues + ); } return $select; } - /** - * Adds filter join for products by stock status - * In case when $showOutOfStockFlag is true - joins are still required to filter only enabled products - * - * @param Select $select - * @param array|int $stockValues - * @param string $mainTableAlias - * @param bool $showOutOfStockFlag - * @return void - */ - private function addMainStockStatusJoin(Select $select, $stockValues, $mainTableAlias, $showOutOfStockFlag) - { - $catalogInventoryTable = $this->resourceConnection->getTableName('cataloginventory_stock_status'); - $select->joinInner( - ['stock_index' => $catalogInventoryTable], - $this->conditionManager->combineQueries( - [ - sprintf('stock_index.product_id = %s.entity_id', $mainTableAlias), - $this->conditionManager->generateCondition( - 'stock_index.website_id', - '=', - $this->stockConfiguration->getDefaultScopeId() - ), - $showOutOfStockFlag - ? '' - : $this->conditionManager->generateCondition( - 'stock_index.stock_status', - is_array($stockValues) ? 'in' : '=', - $stockValues - ), - $this->conditionManager->generateCondition( - 'stock_index.stock_id', - '=', - (int) $this->stockRegistry->getStock()->getStockId() - ), - ], - Select::SQL_AND - ), - [] - ); - } - - /** - * Adds filter join for sub products by stock status - * In case when $showOutOfStockFlag is true - joins are still required to filter only enabled products - * - * @param Select $select - * @param array|int $stockValues - * @param string $mainTableAlias - * @param bool $showOutOfStockFlag - * @return void - */ - private function addSubProductsStockStatusJoin(Select $select, $stockValues, $mainTableAlias, $showOutOfStockFlag) - { - $catalogInventoryTable = $this->resourceConnection->getTableName('cataloginventory_stock_status'); - $select->joinInner( - ['sub_products_stock_index' => $catalogInventoryTable], - $this->conditionManager->combineQueries( - [ - sprintf('sub_products_stock_index.product_id = %s.source_id', $mainTableAlias), - $this->conditionManager->generateCondition( - 'sub_products_stock_index.website_id', - '=', - $this->stockConfiguration->getDefaultScopeId() - ), - $showOutOfStockFlag - ? '' - : $this->conditionManager->generateCondition( - 'sub_products_stock_index.stock_status', - is_array($stockValues) ? 'in' : '=', - $stockValues - ), - $this->conditionManager->generateCondition( - 'sub_products_stock_index.stock_id', - '=', - (int) $this->stockRegistry->getStock()->getStockId() - ), - ], - Select::SQL_AND - ), - [] - ); - } - /** * Extracts alias for table that is used in FROM clause in Select * diff --git a/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/StockStatusQueryBuilder.php b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/StockStatusQueryBuilder.php new file mode 100644 index 0000000000000..2d0d408875661 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/StockStatusQueryBuilder.php @@ -0,0 +1,106 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogSearch\Model\Search\FilterMapper; + +use Magento\CatalogInventory\Model\ResourceModel\Stock\Status as StockStatusResourceModel; +use Magento\Framework\DB\Select; +use Magento\Framework\Search\Adapter\Mysql\ConditionManager; +use Magento\CatalogInventory\Api\StockConfigurationInterface; +use Magento\CatalogInventory\Api\StockRegistryInterface; + +/** + * Add stock status filter to Select + */ +class StockStatusQueryBuilder +{ + /** + * @var StockStatusResourceModel + */ + private $stockStatusResourceModel; + + /** + * @var ConditionManager + */ + private $conditionManager; + + /** + * @var StockConfigurationInterface + */ + private $stockConfiguration; + + /** + * @var StockRegistryInterface + */ + private $stockRegistry; + + /** + * @param StockStatusResourceModel $stockStatusResourceModel + * @param ConditionManager $conditionManager + * @param StockConfigurationInterface $stockConfiguration + * @param StockRegistryInterface $stockRegistry + */ + public function __construct( + StockStatusResourceModel $stockStatusResourceModel, + ConditionManager $conditionManager, + StockConfigurationInterface $stockConfiguration, + StockRegistryInterface $stockRegistry + ) { + $this->stockStatusResourceModel = $stockStatusResourceModel; + $this->conditionManager = $conditionManager; + $this->stockConfiguration = $stockConfiguration; + $this->stockRegistry = $stockRegistry; + } + + /** + * Add stock filter to Select + * + * @param Select $select + * @param string $mainTableAlias + * @param string $stockTableAlias + * @param string $joinField + * @param mixed $values + * @return Select + */ + public function apply( + Select $select, + string $mainTableAlias, + string $stockTableAlias, + string $joinField, + $values = null + ): Select { + $select->joinInner( + [$stockTableAlias => $this->stockStatusResourceModel->getMainTable()], + $this->conditionManager->combineQueries( + [ + sprintf('%s.product_id = %s.%s', $stockTableAlias, $mainTableAlias, $joinField), + $this->conditionManager->generateCondition( + sprintf('%s.website_id', $stockTableAlias), + '=', + $this->stockConfiguration->getDefaultScopeId() + ), + $values === null + ? '' + : $this->conditionManager->generateCondition( + sprintf('%s.stock_status', $stockTableAlias), + is_array($values) ? 'in' : '=', + $values + ), + $this->conditionManager->generateCondition( + sprintf('%s.stock_id', $stockTableAlias), + '=', + (int) $this->stockRegistry->getStock()->getStockId() + ), + ], + Select::SQL_AND + ), + [] + ); + + return $select; + } +} diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/AddressStateFieldShouldNotAcceptJustIntegerValuesTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/AddressStateFieldShouldNotAcceptJustIntegerValuesTest.xml index f3e31d23f715b..dd454d7aca10b 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/AddressStateFieldShouldNotAcceptJustIntegerValuesTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/AddressStateFieldShouldNotAcceptJustIntegerValuesTest.xml @@ -23,6 +23,8 @@ <createData entity="ApiSimpleProduct" stepKey="createProduct"> <requiredEntity createDataKey="createCategory"/> </createData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> </before> <after> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml index 917a4ac58464f..861f379988031 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml @@ -17,6 +17,9 @@ <severity value="CRITICAL"/> <testCaseId value="MC-5922"/> <group value="checkout"/> + <skip> + <issueId value="MC-30111"/> + </skip> </annotations> <before> <createData entity="SimpleSubCategory" stepKey="simplecategory"/> diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/AdminAssertTermInGridActionGroup.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/AdminAssertTermInGridActionGroup.xml new file mode 100644 index 0000000000000..9a855c6f8b5e9 --- /dev/null +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/AdminAssertTermInGridActionGroup.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="AdminAssertTermInGridActionGroup"> + <arguments> + <argument name="termName" type="string"/> + </arguments> + <amOnPage url="{{AdminTermsPage.url}}" stepKey="onTermGridPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <fillField selector="{{AdminTermGridSection.filterByTermName}}" userInput="{{termName}}" stepKey="fillTermNameFilter"/> + <click selector="{{AdminTermGridSection.searchButton}}" stepKey="clickSearchButton"/> + <see selector="{{AdminTermGridSection.firstRowConditionName}}" userInput="{{termName}}" stepKey="assertTermInGrid"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/CreateNewTermActionGroup.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/CreateNewTermActionGroup.xml new file mode 100644 index 0000000000000..d420cc155a77c --- /dev/null +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/CreateNewTermActionGroup.xml @@ -0,0 +1,27 @@ +<?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="CreateNewTermActionGroup"> + <arguments> + <argument name="term"/> + </arguments> + <amOnPage url="{{AdminNewTermPage.url}}" stepKey="amOnNewTermPage"/> + <waitForPageLoad stepKey="waitForAdminNewTermPageLoad"/> + <fillField selector="{{AdminNewTermFormSection.conditionName}}" userInput="{{term.name}}" stepKey="fillFieldConditionName"/> + <selectOption selector="{{AdminNewTermFormSection.isActive}}" userInput="{{term.isActive}}" stepKey="selectOptionIsActive"/> + <selectOption selector="{{AdminNewTermFormSection.isHtml}}" userInput="{{term.isHtml}}" stepKey="selectOptionIsHtml"/> + <selectOption selector="{{AdminNewTermFormSection.mode}}" userInput="{{term.mode}}" stepKey="selectOptionMode"/> + <selectOption selector="{{AdminNewTermFormSection.storeView}}" userInput="{{term.storeView}}" stepKey="selectOptionStoreView" /> + <fillField selector="{{AdminNewTermFormSection.checkboxText}}" userInput="{{term.checkboxText}}" stepKey="fillFieldCheckboxText"/> + <fillField selector="{{AdminNewTermFormSection.content}}" userInput="{{term.content}}" stepKey="fillFieldContent"/> + <click selector="{{AdminNewTermFormSection.save}}" stepKey="saveTerm"/> + <see selector="{{AdminTermFormMessagesSection.successMessage}}" userInput="You saved the condition." stepKey="seeSuccessMessage"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/DeleteTermActionGroup.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/DeleteTermActionGroup.xml new file mode 100644 index 0000000000000..b88101ce88ef6 --- /dev/null +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/DeleteTermActionGroup.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="DeleteTermActionGroup"> + <arguments> + <argument name="term" /> + </arguments> + <amOnPage url="{{AdminTermsPage.url}}" stepKey="onTermGridPage"/> + <waitForPageLoad stepKey="waitForAdminTermsGridPageLoad"/> + <fillField selector="{{AdminTermGridSection.filterByTermName}}" userInput="{{term.name}}" stepKey="fillTermNameFilter"/> + <click selector="{{AdminTermGridSection.searchButton}}" stepKey="clickSearchButton"/> + <click selector="{{AdminTermGridSection.firstRowConditionId}}" stepKey="clickFirstRow"/> + <waitForPageLoad stepKey="waitForEditTermPageLoad"/> + <click selector="{{AdminEditTermFormSection.delete}}" stepKey="clickDeleteButton"/> + <click selector="{{AdminEditTermFormSection.acceptPopupButton}}" stepKey="clickDeleteOkButton"/> + <waitForPageLoad stepKey="waitForAdminTermsGridPageLoad2"/> + <see selector="{{AdminTermFormMessagesSection.successMessage}}" userInput="You deleted the condition." stepKey="seeSuccessMessage"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/StorefrontAssertTermAbsentInCheckoutActionGroup.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/StorefrontAssertTermAbsentInCheckoutActionGroup.xml new file mode 100644 index 0000000000000..7be17d8ca69d0 --- /dev/null +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/StorefrontAssertTermAbsentInCheckoutActionGroup.xml @@ -0,0 +1,31 @@ +<?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="StorefrontAssertTermAbsentInCheckoutActionGroup"> + <arguments> + <argument name="termCheckboxText" type="string"/> + </arguments> + <!--Check if agreement is absent on checkout--> + <dontSee selector="{{StorefrontCheckoutAgreementsSection.checkoutAgreementButton}}" userInput="{{termCheckboxText}}" stepKey="seeTermInCheckout"/> + + <!--Checkout select Check/Money Order payment--> + <waitForLoadingMaskToDisappear stepKey="waitForPaymentPageRendering"/> + <waitForPageLoad stepKey="waitForPaymentRendering"/> + <conditionalClick selector="{{StorefrontCheckoutPaymentMethodSection.checkPaymentMethodByName('Check / Money order')}}" dependentSelector="{{StorefrontCheckoutPaymentMethodSection.checkPaymentMethodByName('Check / Money order')}}" visible="true" stepKey="selectCheckmoPaymentMethod"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskAfterPaymentMethodSelection"/> + + <!--Click Place Order button--> + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/> + + <!--See success messages--> + <see selector="{{CheckoutSuccessMainSection.successTitle}}" userInput="Thank you for your purchase!" stepKey="seeSuccessTitle"/> + <see selector="{{CheckoutSuccessMainSection.orderNumberText}}" userInput="Your order # is: " stepKey="seeOrderNumber"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/StorefrontAssertTermInCheckoutActionGroup.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/StorefrontAssertTermInCheckoutActionGroup.xml new file mode 100644 index 0000000000000..0cf745ce4e04f --- /dev/null +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/StorefrontAssertTermInCheckoutActionGroup.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontAssertTermInCheckoutActionGroup"> + <arguments> + <argument name="termCheckboxText" type="string"/> + </arguments> + <!--Check if agreement is present on checkout and select it--> + <see selector="{{StorefrontCheckoutAgreementsSection.checkoutAgreementButton}}" userInput="{{termCheckboxText}}" stepKey="seeTermInCheckout"/> + <selectOption selector="{{StorefrontCheckoutAgreementsSection.checkoutAgreementCheckbox}}" userInput="{{termCheckboxText}}" stepKey="checkAgreement"/> + + <!--Checkout select Check/Money Order payment--> + <waitForLoadingMaskToDisappear stepKey="waitForPaymentPageRendering"/> + <waitForPageLoad stepKey="waitForPaymentRendering"/> + <conditionalClick selector="{{StorefrontCheckoutPaymentMethodSection.checkPaymentMethodByName('Check / Money order')}}" dependentSelector="{{StorefrontCheckoutPaymentMethodSection.checkPaymentMethodByName('Check / Money order')}}" visible="true" stepKey="selectCheckmoPaymentMethod"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskAfterPaymentMethodSelection"/> + + <!--Click Place Order button--> + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/> + + <!--See success messages--> + <see selector="{{CheckoutSuccessMainSection.successTitle}}" userInput="Thank you for your purchase!" stepKey="seeSuccessTitle"/> + <see selector="{{CheckoutSuccessMainSection.orderNumberText}}" userInput="Your order # is: " stepKey="seeOrderNumber"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/StorefrontAssertTermRequireMessageInMultishippingCheckoutActionGroup.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/StorefrontAssertTermRequireMessageInMultishippingCheckoutActionGroup.xml new file mode 100644 index 0000000000000..35ac4826ccfef --- /dev/null +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/StorefrontAssertTermRequireMessageInMultishippingCheckoutActionGroup.xml @@ -0,0 +1,41 @@ +<?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="StorefrontAssertTermRequireMessageInMultishippingCheckoutActionGroup"> + <arguments> + <argument name="termCheckboxText" type="string"/> + </arguments> + + <!--Go to Checkout Cart and proceed with multiple addresses--> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCheckoutCart"/> + <waitForPageLoad stepKey="waitForCheckoutPageLoad"/> + <click selector="{{MultishippingSection.checkoutWithMultipleAddresses}}" stepKey="proceedMultishipping"/> + + <!--Procees do overview page--> + <click selector="{{StorefrontMultishippingCheckoutAddressesToolbarSection.goToShippingInformation}}" stepKey="clickGoToShippingInformation"/> + <waitForPageLoad stepKey="waitForCheckoutAddressToolbarPageLoad"/> + <click selector="{{StorefrontMultishippingCheckoutShippingToolbarSection.continueToBilling}}" stepKey="clickContinueToBilling"/> + <waitForPageLoad stepKey="waitForCheckoutShippingToolbarPageLoad"/> + <click selector="{{StorefrontMultishippingCheckoutBillingToolbarSection.goToReviewOrder}}" stepKey="clickGoToReviewOrder"/> + <waitForPageLoad stepKey="waitForCheckoutBillingToolbarPageLoad"/> + + <!--Check if agreement is present on checkout and select it--> + <scrollTo selector="{{StorefrontMultishippingCheckoutOverviewReviewSection.placeOrder}}" stepKey="scrollToButtonPlaceOrder"/> + <see selector="{{StorefrontCheckoutAgreementsSection.checkoutAgreementButton}}" userInput="{{termCheckboxText}}" stepKey="seeTermInCheckout"/> + <click selector="{{StorefrontMultishippingCheckoutOverviewReviewSection.placeOrder}}" stepKey="tryToPlaceOrder1"/> + <see selector="{{StorefrontCheckoutAgreementsSection.checkoutAgreementErrorMessage}}" userInput="This is a required field." stepKey="seeErrorMessage"/> + <selectOption selector="{{StorefrontCheckoutAgreementsSection.checkoutAgreementCheckbox}}" userInput="{{termCheckboxText}}" stepKey="checkAgreement"/> + <click selector="{{StorefrontMultishippingCheckoutOverviewReviewSection.placeOrder}}" stepKey="tryToPlaceOrder2"/> + <dontSee selector="{{StorefrontCheckoutAgreementsSection.checkoutAgreementErrorMessage}}" userInput="This is a required field." stepKey="dontSeeErrorMessage"/> + + <!--See success message--> + <see selector="{{CheckoutSuccessMainSection.successTitle}}" userInput="Thank you for your purchase!" stepKey="seeSuccessTitle"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/StorefrontProcessCheckoutToPaymentActionGroup.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/StorefrontProcessCheckoutToPaymentActionGroup.xml new file mode 100644 index 0000000000000..c40f24836c815 --- /dev/null +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/StorefrontProcessCheckoutToPaymentActionGroup.xml @@ -0,0 +1,35 @@ +<?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="StorefrontProcessCheckoutToPaymentActionGroup"> + <!--Go to Checkout--> + <waitForElementNotVisible selector="{{StorefrontMinicartSection.emptyCart}}" stepKey="waitUpdateQuantity"/> + <wait time="5" stepKey="waitMinicartRendering"/> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickCart"/> + <click selector="{{StorefrontMinicartSection.goToCheckout}}" stepKey="goToCheckout"/> + + <!--Process steps--> + <fillField selector="{{CheckoutShippingSection.email}}" userInput="{{CustomerEntityOne.email}}" stepKey="enterEmail"/> + <fillField selector="{{CheckoutShippingSection.firstName}}" userInput="{{CustomerEntityOne.firstname}}" stepKey="enterFirstName"/> + <fillField selector="{{CheckoutShippingSection.lastName}}" userInput="{{CustomerEntityOne.lastname}}" stepKey="enterLastName"/> + <fillField selector="{{CheckoutShippingSection.street}}" userInput="{{CustomerAddressSimple.street[0]}}" stepKey="enterStreet"/> + <fillField selector="{{CheckoutShippingSection.city}}" userInput="{{CustomerAddressSimple.city}}" stepKey="enterCity"/> + <selectOption selector="{{CheckoutShippingSection.region}}" userInput="{{CustomerAddressSimple.state}}" stepKey="selectRegion"/> + <fillField selector="{{CheckoutShippingSection.postcode}}" userInput="{{CustomerAddressSimple.postcode}}" stepKey="enterPostcode"/> + <fillField selector="{{CheckoutShippingSection.telephone}}" userInput="{{CustomerAddressSimple.telephone}}" stepKey="enterTelephone"/> + <waitForLoadingMaskToDisappear stepKey="waitForShippingMethods"/> + <click selector="{{CheckoutShippingMethodsSection.checkShippingMethodByName('')}}" stepKey="selectShippingMethod"/> + <waitForElement selector="{{CheckoutShippingSection.next}}" time="30" stepKey="waitForTheNextButton"/> + <waitForElementNotVisible selector=".loading-mask" time="300" stepKey="waitForProcessShippingMethod"/> + <click selector="{{CheckoutShippingSection.next}}" stepKey="clickNext"/> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" stepKey="waitForPaymentSectionLoaded"/> + <seeInCurrentUrl url="{{CheckoutPage.url}}/#payment" stepKey="assertCheckoutPaymentUrl"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/Data/TermData.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/Data/TermData.xml new file mode 100644 index 0000000000000..f34aa52d1ebe3 --- /dev/null +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/Data/TermData.xml @@ -0,0 +1,38 @@ +<?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="activeTextTerm" type="term"> + <data key="name" unique="suffix">name</data> + <data key="isActive">Enabled</data> + <data key="isHtml">Text</data> + <data key="mode">Manually</data> + <data key="storeView">Default Store View</data> + <data key="checkboxText" unique="suffix">test_checkbox</data> + <data key="content" unique="suffix">TestMessage</data> + </entity> + <entity name="activeHtmlTerm" type="term"> + <data key="name" unique="suffix">name</data> + <data key="isActive">Enabled</data> + <data key="isHtml">HTML</data> + <data key="mode">Manually</data> + <data key="storeView">Default Store View</data> + <data key="checkboxText" unique="suffix">test_checkbox</data> + <data key="content"><html></data> + </entity> + <entity name="disabledTextTerm" type="term"> + <data key="name" unique="suffix">name</data> + <data key="isActive">Disabled</data> + <data key="isHtml">Text</data> + <data key="mode">Manually</data> + <data key="storeView">Default Store View</data> + <data key="checkboxText" unique="suffix">test_checkbox</data> + <data key="content" unique="suffix">TestMessage</data> + </entity> +</entities> diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/Page/AdminEditTermPage.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/Page/AdminEditTermPage.xml new file mode 100644 index 0000000000000..3a13f7d7d5e60 --- /dev/null +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/Page/AdminEditTermPage.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="AdminEditTermPage" url="checkout/agreement/edit/id/{{termId}}/" area="admin" module="Magento_CheckoutAgreements" parameterized="true"> + <section name="AdminEditTermFormSection"/> + </page> +</pages> diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/Page/AdminNewTermPage.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/Page/AdminNewTermPage.xml new file mode 100644 index 0000000000000..51ec18a1d17e5 --- /dev/null +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/Page/AdminNewTermPage.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminNewTermPage" url="checkout/agreement/new" area="admin" module="Magento_CheckoutAgreements"> + <section name="AdminNewTermFormSection"/> + <section name="AdminTermFormMessagesSection"/> + </page> +</pages> diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/Page/AdminTermsPage.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/Page/AdminTermsPage.xml new file mode 100644 index 0000000000000..4fb1a9704d9d0 --- /dev/null +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/Page/AdminTermsPage.xml @@ -0,0 +1,12 @@ +<?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="AdminTermsPage" url="checkout/agreement/" area="admin" module="Magento_CheckoutAgreements"> + <section name="AdminTermGridSection"/> + </page> +</pages> diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/Page/CheckoutPage.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/Page/CheckoutPage.xml new file mode 100644 index 0000000000000..fdb75ab4bc4c2 --- /dev/null +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/Page/CheckoutPage.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="CheckoutPage" url="/checkout" area="storefront" module="Magento_CheckoutAgreements"> + <section name="StorefrontCheckoutAgreementsSection"/> + </page> +</pages> diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/Section/AdminEditTermFormSection.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/Section/AdminEditTermFormSection.xml new file mode 100644 index 0000000000000..734a75b49c03a --- /dev/null +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/Section/AdminEditTermFormSection.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="AdminEditTermFormSection"> + <element name="delete" type="button" selector=".page-main-actions #delete"/> + <element name="acceptPopupButton" type="button" selector="button.action-primary.action-accept"/> + </section> +</sections> diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/Section/AdminNewTermFormSection.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/Section/AdminNewTermFormSection.xml new file mode 100644 index 0000000000000..aeff29ef116fc --- /dev/null +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/Section/AdminNewTermFormSection.xml @@ -0,0 +1,22 @@ +<?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="AdminNewTermFormSection"> + <element name="save" type="button" selector=".page-main-actions #save"/> + <element name="conditionName" type="input" selector="#name"/> + <element name="isActive" type="select" selector="#is_active"/> + <element name="isHtml" type="select" selector="#is_html"/> + <element name="mode" type="select" selector="#mode"/> + <element name="storeView" type="multiselect" selector="#stores"/> + <element name="checkboxText" type="input" selector="#checkbox_text"/> + <element name="content" type="textarea" selector="#content"/> + <element name="contentHeight" type="input" selector="#content_height"/> + </section> +</sections> diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/Section/AdminTermFormMessagesSection.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/Section/AdminTermFormMessagesSection.xml new file mode 100644 index 0000000000000..a3a91855b0640 --- /dev/null +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/Section/AdminTermFormMessagesSection.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="AdminTermFormMessagesSection"> + <element name="successMessage" type="text" selector=".message-success"/> + <element name="errorMessage" type="text" selector=".message.message-error.error"/> + </section> +</sections> diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/Section/AdminTermGridSection.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/Section/AdminTermGridSection.xml new file mode 100644 index 0000000000000..326f9dcce4320 --- /dev/null +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/Section/AdminTermGridSection.xml @@ -0,0 +1,18 @@ +<?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="AdminTermGridSection"> + <element name="searchButton" type="button" selector="//div[contains(@class,'admin__data-grid-header')]//div[contains(@class,'admin__filter-actions')]/button[1]"/> + <element name="resetButton" type="button" selector="//div[contains(@class,'admin__data-grid-header')]//div[contains(@class,'admin__filter-actions')]/button[2]"/> + <element name="filterByTermName" type="input" selector="#agreementGrid_filter_name"/> + <element name="firstRowConditionName" type="text" selector=".data-grid>tbody>tr>td.col-name"/> + <element name="firstRowConditionId" type="text" selector=".data-grid>tbody>tr>td.col-id.col-agreement_id"/> + <element name="successMessage" type="text" selector=".message-success"/> + </section> +</sections> diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/Section/StorefrontCheckoutAgreementsSection.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/Section/StorefrontCheckoutAgreementsSection.xml new file mode 100644 index 0000000000000..cb3e98949c622 --- /dev/null +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/Section/StorefrontCheckoutAgreementsSection.xml @@ -0,0 +1,16 @@ +<?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="StorefrontCheckoutAgreementsSection"> + <element name="checkoutAgreementCheckbox" type="checkbox" selector="div.checkout-agreement.field.choice.required > input"/> + <element name="checkoutAgreementButton" type="button" selector="div.checkout-agreements-block > div > div > div > label > button > span"/> + <element name="checkoutAgreementErrorMessage" type="button" selector="div.checkout-agreement.field.choice.required > div.mage-error"/> + </section> +</sections> diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminCreateActiveHtmlTermEntityTest.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminCreateActiveHtmlTermEntityTest.xml new file mode 100644 index 0000000000000..dc329ae63ba21 --- /dev/null +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminCreateActiveHtmlTermEntityTest.xml @@ -0,0 +1,52 @@ +<?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="AdminCreateActiveHtmlTermEntityTest"> + <annotations> + <features value="CheckoutAgreements"/> + <stories value="Checkout agreements"/> + <title value="Create active HTML checkout agreement"/> + <description value="Admin should be able to create active HTML checkout agreement"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14668"/> + <group value="checkoutAgreements"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <magentoCLI command="config:set checkout/options/enable_agreements 1" stepKey="setEnableTermsOnCheckout"/> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin" /> + <createData entity="SimpleTwo" stepKey="createdProduct"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <magentoCLI command="config:set checkout/options/enable_agreements 0" stepKey="setDisableTermsOnCheckout"/> + <deleteData createDataKey="createdProduct" stepKey="deletedProduct"/> + <actionGroup ref="DeleteTermActionGroup" stepKey="deleteTerm"> + <argument name="term" value="activeHtmlTerm"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <actionGroup ref="CreateNewTermActionGroup" stepKey="createTerm"> + <argument name="term" value="activeHtmlTerm"/> + </actionGroup> + <actionGroup ref="AdminAssertTermInGridActionGroup" stepKey="assertTermInGrid"> + <argument name="termName" value="{{activeHtmlTerm.name}}"/> + </actionGroup> + <actionGroup ref="AddSimpleProductToCartActionGroup" stepKey="addProductToTheCart"> + <argument name="product" value="$$createdProduct$$"/> + </actionGroup> + <actionGroup ref="StorefrontProcessCheckoutToPaymentActionGroup" stepKey="processCheckoutToThePaymentMethodsPage"/> + <actionGroup ref="StorefrontAssertTermInCheckoutActionGroup" stepKey="assertTermInCheckout"> + <argument name="termCheckboxText" value="{{activeHtmlTerm.checkboxText}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminCreateActiveTextTermEntityTest.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminCreateActiveTextTermEntityTest.xml new file mode 100644 index 0000000000000..a7baa061750c5 --- /dev/null +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminCreateActiveTextTermEntityTest.xml @@ -0,0 +1,52 @@ +<?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="AdminCreateActiveTextTermEntityTest"> + <annotations> + <features value="CheckoutAgreements"/> + <stories value="Checkout agreements"/> + <title value="Create active text checkout agreement"/> + <description value="Admin should be able to create active text checkout agreement"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14667"/> + <group value="checkoutAgreements"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <magentoCLI command="config:set checkout/options/enable_agreements 1" stepKey="setEnableTermsOnCheckout"/> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin" /> + <createData entity="SimpleTwo" stepKey="createdProduct"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <magentoCLI command="config:set checkout/options/enable_agreements 0" stepKey="setDisableTermsOnCheckout"/> + <deleteData createDataKey="createdProduct" stepKey="deletedProduct"/> + <actionGroup ref="DeleteTermActionGroup" stepKey="deleteTerm"> + <argument name="term" value="activeTextTerm"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <actionGroup ref="CreateNewTermActionGroup" stepKey="createTerm"> + <argument name="term" value="activeTextTerm"/> + </actionGroup> + <actionGroup ref="AdminAssertTermInGridActionGroup" stepKey="assertTermInGrid"> + <argument name="termName" value="{{activeTextTerm.name}}"/> + </actionGroup> + <actionGroup ref="AddSimpleProductToCartActionGroup" stepKey="addProductToTheCart"> + <argument name="product" value="$$createdProduct$$"/> + </actionGroup> + <actionGroup ref="StorefrontProcessCheckoutToPaymentActionGroup" stepKey="processCheckoutToThePaymentMethodsPage"/> + <actionGroup ref="StorefrontAssertTermInCheckoutActionGroup" stepKey="assertTermInCheckout"> + <argument name="termCheckboxText" value="{{activeTextTerm.checkboxText}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminCreateDisabledTextTermEntityTest.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminCreateDisabledTextTermEntityTest.xml new file mode 100644 index 0000000000000..d5f35fc79fbed --- /dev/null +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminCreateDisabledTextTermEntityTest.xml @@ -0,0 +1,52 @@ +<?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="AdminCreateDisabledTextTermEntityTest"> + <annotations> + <features value="CheckoutAgreements"/> + <stories value="Checkout agreements"/> + <title value="Create disabled text checkout agreement"/> + <description value="Admin should be able to create disabled text checkout agreement"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14669"/> + <group value="checkoutAgreements"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <magentoCLI command="config:set checkout/options/enable_agreements 1" stepKey="setEnableTermsOnCheckout"/> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin" /> + <createData entity="SimpleTwo" stepKey="createdProduct"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <magentoCLI command="config:set checkout/options/enable_agreements 0" stepKey="setDisableTermsOnCheckout"/> + <deleteData createDataKey="createdProduct" stepKey="deletedProduct"/> + <actionGroup ref="DeleteTermActionGroup" stepKey="deleteTerm"> + <argument name="term" value="disabledTextTerm"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <actionGroup ref="CreateNewTermActionGroup" stepKey="createTerm"> + <argument name="term" value="disabledTextTerm"/> + </actionGroup> + <actionGroup ref="AdminAssertTermInGridActionGroup" stepKey="assertTermInGrid"> + <argument name="termName" value="{{disabledTextTerm.name}}"/> + </actionGroup> + <actionGroup ref="AddSimpleProductToCartActionGroup" stepKey="addProductToTheCart"> + <argument name="product" value="$$createdProduct$$"/> + </actionGroup> + <actionGroup ref="StorefrontProcessCheckoutToPaymentActionGroup" stepKey="processCheckoutToThePaymentMethodsPage"/> + <actionGroup ref="StorefrontAssertTermAbsentInCheckoutActionGroup" stepKey="assertTermAbsentInCheckout"> + <argument name="termCheckboxText" value="{{disabledTextTerm.checkboxText}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminCreateEnabledTextTermOnMultishippingEntityTest.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminCreateEnabledTextTermOnMultishippingEntityTest.xml new file mode 100644 index 0000000000000..462b13ae386c4 --- /dev/null +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminCreateEnabledTextTermOnMultishippingEntityTest.xml @@ -0,0 +1,58 @@ +<?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="AdminCreateEnabledTextTermOnMultishippingEntityTest"> + <annotations> + <features value="CheckoutAgreements"/> + <stories value="Checkout agreements"/> + <title value="Create enabled text checkout multishipping agreement"/> + <description value="Admin should be able to create enabled text checkout multishipping agreement"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14670"/> + <group value="checkoutAgreements"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <magentoCLI command="config:set checkout/options/enable_agreements 1" stepKey="setEnableTermsOnCheckout"/> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin" /> + <createData entity="Simple_US_Customer_Multiple_Addresses" stepKey="createdCustomer"/> + <createData entity="SimpleTwo" stepKey="createdProduct1"/> + <createData entity="SimpleTwo" stepKey="createdProduct2"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <magentoCLI command="config:set checkout/options/enable_agreements 0" stepKey="setDisableTermsOnCheckout"/> + <deleteData createDataKey="createdCustomer" stepKey="deletedCustomer"/> + <deleteData createDataKey="createdProduct1" stepKey="deletedProduct1"/> + <deleteData createDataKey="createdProduct2" stepKey="deletedProduct2"/> + <actionGroup ref="DeleteTermActionGroup" stepKey="deleteTerm"> + <argument name="term" value="activeTextTerm"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <actionGroup ref="CreateNewTermActionGroup" stepKey="createTerm"> + <argument name="term" value="activeTextTerm"/> + </actionGroup> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="customerLogin"> + <argument name="Customer" value="$$createdCustomer$$" /> + </actionGroup> + <actionGroup ref="AddSimpleProductToCartActionGroup" stepKey="addProduct1ToTheCart"> + <argument name="product" value="$$createdProduct1$$"/> + </actionGroup> + <actionGroup ref="AddSimpleProductToCartActionGroup" stepKey="addProduct2ToTheCart"> + <argument name="product" value="$$createdProduct2$$"/> + </actionGroup> + <actionGroup ref="StorefrontAssertTermRequireMessageInMultishippingCheckoutActionGroup" stepKey="assertTermInCheckout"> + <argument name="termCheckboxText" value="{{activeTextTerm.checkboxText}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AddStoreViewToCmsPageActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AddStoreViewToCmsPageActionGroup.xml index 505f3bebbf70c..fabf6480b174b 100644 --- a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AddStoreViewToCmsPageActionGroup.xml +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AddStoreViewToCmsPageActionGroup.xml @@ -10,7 +10,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AddStoreViewToCmsPageActionGroup" extends="NavigateToCreatedCMSPageActionGroup"> <annotations> - <description>EXTENDS: navigateToCreatedCMSPage. Adds the provided Store View to a Page.</description> + <description>EXTENDS: NavigateToCreatedCMSPageActionGroup. Adds the provided Store View to a Page.</description> </annotations> <arguments> <argument name="storeViewName" type="string"/> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminDisableCMSPageActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminDisableCMSPageActionGroup.xml new file mode 100644 index 0000000000000..6617c8eea8155 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminDisableCMSPageActionGroup.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="AdminDisableCMSPageActionGroup"> + <seeElement selector="{{CmsNewPagePageBasicFieldsSection.isActive('1')}}" stepKey="seePageIsEnabled" /> + <click selector="{{CmsNewPagePageBasicFieldsSection.isActiveLabel}}" stepKey="setPageDisabled"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminFillCMSPageContentFieldActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminFillCMSPageContentFieldActionGroup.xml new file mode 100644 index 0000000000000..f62ee4a8a4ccd --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminFillCMSPageContentFieldActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminFillCMSPageContentFieldActionGroup"> + <arguments> + <argument name="content" type="string"/> + </arguments> + <fillField selector="{{CmsNewPagePageContentSection.content}}" userInput="{{content}}" stepKey="fillFieldContent"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCreateNewCMSPageActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCreateNewCMSPageActionGroup.xml new file mode 100644 index 0000000000000..79ce1bc9d8e47 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCreateNewCMSPageActionGroup.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="AdminOpenCreateNewCMSPageActionGroup"> + <amOnPage url="{{CmsNewPagePage.url}}" stepKey="navigateToCreateNewPage"/> + <waitForPageLoad stepKey="waitForNewPagePageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminSelectCMSPageFromGridActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminSelectCMSPageFromGridActionGroup.xml new file mode 100644 index 0000000000000..bd721b4d7d40c --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminSelectCMSPageFromGridActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminSelectCMSPageFromGridActionGroup"> + <arguments> + <argument name="identifier" defaultValue=""/> + </arguments> + <click selector="{{CmsPagesPageActionsSection.select(identifier)}}" stepKey="clickSelectCMSPage" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminSelectCMSPageStoreViewActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminSelectCMSPageStoreViewActionGroup.xml new file mode 100644 index 0000000000000..3e4a67e595c04 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminSelectCMSPageStoreViewActionGroup.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="AdminSelectCMSPageStoreViewActionGroup"> + <arguments> + <argument name="storeViewName" type="string"/> + </arguments> + <click selector="{{CmsNewPagePiwSection.header}}" stepKey="clickPageInWebsites"/> + <waitForElementVisible selector="{{CmsNewPagePiwSection.selectStoreView(storeViewName)}}" stepKey="waitForStoreGridReload"/> + <clickWithLeftButton selector="{{CmsNewPagePiwSection.selectStoreView(storeViewName)}}" stepKey="clickStoreView"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertCMSPageStoreIdActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertCMSPageStoreIdActionGroup.xml new file mode 100644 index 0000000000000..4d05d68e5e9b2 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertCMSPageStoreIdActionGroup.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="AssertCMSPageStoreIdActionGroup"> + <arguments> + <argument name="storeId" type="string"/> + </arguments> + <click selector="{{CmsNewPagePiwSection.header}}" stepKey="clickPageInWebsites"/> + <waitForElementVisible selector="{{CmsNewPagePiwSection.storeIdDropdown}}" stepKey="waitForStoresSectionReload"/> + <grabValueFrom selector="{{CmsNewPagePiwSection.storeIdDropdown}}" stepKey="grabValueFromStoreView"/> + <assertEquals stepKey="assertTitle" message="pass"> + <expectedResult type="string">{{storeId}}</expectedResult> + <actualResult type="variable">grabValueFromStoreView</actualResult> + </assertEquals> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/FillOutCMSPageContentActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/FillOutCMSPageContentActionGroup.xml index 5a1ab2c05d24d..0ee195ae13a3c 100644 --- a/app/code/Magento/Cms/Test/Mftf/ActionGroup/FillOutCMSPageContentActionGroup.xml +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/FillOutCMSPageContentActionGroup.xml @@ -15,6 +15,8 @@ <fillField selector="{{CmsNewPagePageBasicFieldsSection.pageTitle}}" userInput="{{_duplicatedCMSPage.title}}" stepKey="fillFieldTitle"/> <click selector="{{CmsNewPagePageContentSection.header}}" stepKey="clickExpandContentTabForPage"/> + <fillField selector="{{CmsNewPagePageContentSection.contentHeading}}" userInput="{{_duplicatedCMSPage.content_heading}}" stepKey="fillFieldContentHeading"/> + <scrollTo selector="{{CmsNewPagePageContentSection.content}}" stepKey="scrollToPageContent"/> <fillField selector="{{CmsNewPagePageContentSection.content}}" userInput="{{_duplicatedCMSPage.content}}" stepKey="fillFieldContent"/> <click selector="{{CmsNewPagePageSeoSection.header}}" stepKey="clickExpandSearchEngineOptimisation"/> <fillField selector="{{CmsNewPagePageSeoSection.urlKey}}" userInput="{{_duplicatedCMSPage.identifier}}" stepKey="fillFieldUrlKey"/> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/StorefrontGoToCMSPageActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/StorefrontGoToCMSPageActionGroup.xml index 90f7a0036e1e6..ba0f728004ad3 100644 --- a/app/code/Magento/Cms/Test/Mftf/ActionGroup/StorefrontGoToCMSPageActionGroup.xml +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/StorefrontGoToCMSPageActionGroup.xml @@ -16,7 +16,7 @@ <argument name="identifier" type="string"/> </arguments> - <amOnPage url="{{StorefrontHomePage.url}}{{identifier}}" stepKey="amOnCmsPageOnStorefront"/> + <amOnPage url="{{StorefrontHomePage.url}}/{{identifier}}" stepKey="amOnCmsPageOnStorefront"/> <waitForPageLoad stepKey="waitForPageLoadOnStorefront"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePageBasicFieldsSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePageBasicFieldsSection.xml index 7288e5d455d52..92170c447fa11 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePageBasicFieldsSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePageBasicFieldsSection.xml @@ -12,6 +12,7 @@ <element name="pageTitle" type="input" selector="input[name=title]"/> <element name="RequiredFieldIndicator" type="text" selector=" return window.getComputedStyle(document.querySelector('._required[data-index=title]>.admin__field-label span'), ':after').getPropertyValue('content');"/> <element name="isActive" type="button" selector="//input[@name='is_active' and @value='{{var1}}']" parameterized="true"/> + <element name="isActiveLabel" type="button" selector="div[data-index=is_active] .admin__actions-switch-label"/> <element name="duplicatedURLKey" type="input" selector="//input[contains(@data-value,'{{var1}}')]" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePiwSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePiwSection.xml index bd487e3b2c03c..7bb6e606d7943 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePiwSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePiwSection.xml @@ -11,5 +11,6 @@ <section name="CmsNewPagePiwSection"> <element name="header" type="button" selector="div[data-index=websites]" timeout="30"/> <element name="selectStoreView" type="select" selector="//option[contains(text(),'{{var1}}')]" parameterized="true"/> + <element name="storeIdDropdown" type="select" selector="//div[@data-bind="scope: 'cms_page_form.cms_page_form'"]//select[@name='store_id']"/> </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGCMSTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGCMSTest.xml index dd45c6768e67f..201a84ecb3c65 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGCMSTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGCMSTest.xml @@ -55,6 +55,7 @@ <seeElement selector="{{StorefrontCMSPageSection.mediaDescription}}" stepKey="assertMediaDescription"/> <seeElementInDOM selector="{{StorefrontCMSPageSection.imageSource(ImageUpload3.fileName)}}" stepKey="assertMediaSource"/> <after> + <actionGroup ref="DisabledWYSIWYGActionGroup" stepKey="disableWYSIWYGFirst"/> <deleteData createDataKey="createCMSPage" stepKey="deletePreReqCMSPage" /> <actionGroup ref="DisabledWYSIWYGActionGroup" stepKey="disableWYSIWYG"/> <actionGroup ref="logout" stepKey="logout"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreateDisabledPageTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreateDisabledPageTest.xml new file mode 100644 index 0000000000000..5b83807eca244 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreateDisabledPageTest.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCMSPageCreateDisabledPageTest"> + <annotations> + <features value="Cms"/> + <title value="Create disabled CMS Page via the Admin"/> + <description value="Admin should be able to create a CMS Page"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14677"/> + <group value="backend"/> + <group value="Cms"/> + <group value="mtf_migrated"/> + <group value="WYSIWYGDisabled"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Go to New CMS Page page--> + <actionGroup ref="AdminOpenCreateNewCMSPageActionGroup" stepKey="navigateToCreateNewPage"/> + <!--Set page disabled--> + <actionGroup ref="AdminDisableCMSPageActionGroup" stepKey="setCMSPageDisabled"/> + <!--Fill the CMS page form--> + <actionGroup ref="FillOutCMSPageContent" stepKey="fillBasicPageDataForDisabledPage"/> + <!--Verify successfully saved--> + <actionGroup ref="SaveCmsPageActionGroup" stepKey="saveDisabledPage"/> + <!--Check that page is disabled on frontend--> + <actionGroup ref="StorefrontGoToCMSPageActionGroup" stepKey="goToCMSPageOnStorefront"> + <argument name="identifier" value="{{_duplicatedCMSPage.identifier}}"/> + </actionGroup> + <actionGroup ref="AssertCMSPageNotFoundOnStorefrontActionGroup" stepKey="seeNotFoundError"/> + <!--Delete page--> + <actionGroup ref="DeletePageByUrlKeyActionGroup" stepKey="deleteDisabledPage"> + <argument name="UrlKey" value="{{_duplicatedCMSPage.identifier}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageForDefaultStoreTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageForDefaultStoreTest.xml new file mode 100644 index 0000000000000..a9f5fcd8b17e0 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageForDefaultStoreTest.xml @@ -0,0 +1,52 @@ +<?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="AdminCMSPageCreatePageForDefaultStoreTest"> + <annotations> + <features value="Cms"/> + <title value="Create CMS Page via the Admin for default store"/> + <description value="Admin should be able to create a CMS Page"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14676"/> + <group value="backend"/> + <group value="Cms"/> + <group value="mtf_migrated"/> + <group value="WYSIWYGDisabled"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Go to New CMS Page page--> + <actionGroup ref="AdminOpenCreateNewCMSPageActionGroup" stepKey="navigateToCreateNewPage"/> + <!--Fill the CMS page form--> + <actionGroup ref="FillOutCMSPageContent" stepKey="fillBasicPageDataForPageWithDefaultStore"/> + <actionGroup ref="AdminSelectCMSPageStoreViewActionGroup" stepKey="selectCMSPageStoreView"> + <argument name="storeViewName" value="Default Store View"/> + </actionGroup> + <!--Verify successfully saved--> + <actionGroup ref="SaveCmsPageActionGroup" stepKey="savePageWithDefaultStore"/> + <!--Navigate to page in Admin--> + <actionGroup ref="NavigateToCreatedCMSPageActionGroup" stepKey="navigateToCMSPageWithDefaultStoreInAdmin"> + <argument name="CMSPage" value="_duplicatedCMSPage"/> + </actionGroup> + <!--Verify Page Data in Admin--> + <actionGroup ref="AssertCMSPageContentActionGroup" stepKey="verifyPageWithDefaultStoreDataInAdmin"/> + <!--Verify Store ID--> + <actionGroup ref="AssertCMSPageStoreIdActionGroup" stepKey="verifyStoreId"> + <argument name="storeId" value="1"/> + </actionGroup> + <!--Delete page--> + <actionGroup ref="DeletePageByUrlKeyActionGroup" stepKey="deletePageWithDefaultStore"> + <argument name="UrlKey" value="{{_duplicatedCMSPage.identifier}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageInSingleStoreModeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageInSingleStoreModeTest.xml new file mode 100644 index 0000000000000..1ec85f90f46ef --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageInSingleStoreModeTest.xml @@ -0,0 +1,56 @@ +<?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="AdminCMSPageCreatePageInSingleStoreModeTest"> + <annotations> + <features value="Cms"/> + <title value="Create CMS Page via the Admin in single store mode"/> + <description value="Admin should be able to create a CMS Page"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14679"/> + <group value="backend"/> + <group value="Cms"/> + <group value="mtf_migrated"/> + <group value="WYSIWYGDisabled"/> + </annotations> + <before> + <magentoCLI command="config:set {{StorefrontSingleStoreModeEnabledConfigData.path}} {{StorefrontSingleStoreModeEnabledConfigData.value}}" stepKey="enableSingleStoreMode" /> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <magentoCLI command="config:set {{StorefrontSingleStoreModeDisabledConfigData.path}} {{StorefrontSingleStoreModeDisabledConfigData.value}}" stepKey="disableSingleStoreMode" /> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Go to New CMS Page page--> + <actionGroup ref="AdminOpenCreateNewCMSPageActionGroup" stepKey="navigateToCreateNewPage"/> + <!--Fill the CMS page form--> + <actionGroup ref="FillOutCMSPageContent" stepKey="fillBasicPageDataForPageWithDefaultStore"/> + <!--Verify successfully saved--> + <actionGroup ref="SaveCmsPageActionGroup" stepKey="savePageInSingleStoreMode"/> + <!--verify page on frontend--> + <actionGroup ref="StorefrontGoToCMSPageActionGroup" stepKey="navigateToPageOnStoreFront"> + <argument name="identifier" value="{{_duplicatedCMSPage.identifier}}"/> + </actionGroup> + <actionGroup ref="AssertStoreFrontCMSPageActionGroup" stepKey="verifyPageDataOnFrontend"> + <argument name="cmsTitle" value="{{_duplicatedCMSPage.title}}"/> + <argument name="cmsContent" value="{{_duplicatedCMSPage.content}}"/> + <argument name="cmsContentHeading" value="{{_duplicatedCMSPage.content_heading}}"/> + </actionGroup> + <!--Navigate to page in Admin--> + <actionGroup ref="NavigateToCreatedCMSPageActionGroup" stepKey="navigateToCMSPageInAdminInSingleStoreMode"> + <argument name="CMSPage" value="_duplicatedCMSPage"/> + </actionGroup> + <!--Verify Page Data in Admin--> + <actionGroup ref="AssertCMSPageContentActionGroup" stepKey="verifyPageDataInAdminInSingleStoreMode"/> + <!--Delete page--> + <actionGroup ref="DeletePageByUrlKeyActionGroup" stepKey="deletePageInSingleStoreMode"> + <argument name="UrlKey" value="{{_duplicatedCMSPage.identifier}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageTest.xml new file mode 100644 index 0000000000000..947fa92f2c8ff --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageTest.xml @@ -0,0 +1,53 @@ +<?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="AdminCMSPageCreatePageTest"> + <annotations> + <features value="Cms"/> + <title value="Create CMS Page via the Admin"/> + <description value="Admin should be able to create a CMS Page"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14675"/> + <group value="backend"/> + <group value="Cms"/> + <group value="mtf_migrated"/> + <group value="WYSIWYGDisabled"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Go to New CMS Page page--> + <actionGroup ref="AdminOpenCreateNewCMSPageActionGroup" stepKey="navigateToCreateNewPage"/> + <actionGroup ref="FillOutCMSPageContent" stepKey="fillBasicPageData"/> + <!--verify successfully saved--> + <actionGroup ref="SaveCmsPageActionGroup" stepKey="saveNewPage"/> + <!--verify page on frontend--> + <actionGroup ref="StorefrontGoToCMSPageActionGroup" stepKey="navigateToPageOnStoreFront"> + <argument name="identifier" value="{{_duplicatedCMSPage.identifier}}"/> + </actionGroup> + <actionGroup ref="AssertStoreFrontCMSPageActionGroup" stepKey="verifyPageDataOnFrontend"> + <argument name="cmsTitle" value="{{_duplicatedCMSPage.title}}"/> + <argument name="cmsContent" value="{{_duplicatedCMSPage.content}}"/> + <argument name="cmsContentHeading" value="{{_duplicatedCMSPage.content_heading}}"/> + </actionGroup> + <!--verify page in grid--> + <actionGroup ref="AdminOpenCMSPagesGridActionGroup" stepKey="openCMSPagesGridActionGroup"/> + <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearGridFilters"/> + <actionGroup ref="SortByIdDescendingActionGroup" stepKey="sortGridByIdDescending"/> + <actionGroup ref="AdminSelectCMSPageFromGridActionGroup" stepKey="verifyPageInGrid"> + <argument name="identifier" value="_duplicatedCMSPage.identifier"/> + </actionGroup> + <actionGroup ref="DeletePageByUrlKeyActionGroup" stepKey="deletePage"> + <argument name="UrlKey" value="{{_duplicatedCMSPage.identifier}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageWithBlockTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageWithBlockTest.xml new file mode 100644 index 0000000000000..a6c67dc61dd97 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageWithBlockTest.xml @@ -0,0 +1,53 @@ +<?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="AdminCMSPageCreatePageWithBlockTest"> + <annotations> + <features value="Cms"/> + <title value="Create CMS Page that contains block content via the Admin"/> + <description value="Admin should be able to create a CMS Page"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14678"/> + <group value="backend"/> + <group value="Cms"/> + <group value="mtf_migrated"/> + <group value="WYSIWYGDisabled"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Go to New CMS Page page--> + <actionGroup ref="AdminOpenCreateNewCMSPageActionGroup" stepKey="navigateToCreateNewPage"/> + <actionGroup ref="FillOutCMSPageContent" stepKey="fillBasicPageData"/> + <actionGroup ref="AdminFillCMSPageContentFieldActionGroup" stepKey="fillContentField"> + <argument name="content" value="{{block class='Magento\Framework\View\Element\Text' text='bla bla bla' cache_key='BACKEND_ACL_RESOURCES' cache_lifetime=999}}"/> + </actionGroup> + <actionGroup ref="AdminSelectCMSPageStoreViewActionGroup" stepKey="selectCMSPageStoreViewForPageWithBlock"> + <argument name="storeViewName" value="Default Store View"/> + </actionGroup> + <!--Verify successfully saved--> + <actionGroup ref="SaveCmsPageActionGroup" stepKey="savePageWithBlock"/> + <!--verify page on frontend--> + <actionGroup ref="StorefrontGoToCMSPageActionGroup" stepKey="navigateToPageOnStoreFront"> + <argument name="identifier" value="{{_duplicatedCMSPage.identifier}}"/> + </actionGroup> + <actionGroup ref="AssertStoreFrontCMSPageActionGroup" stepKey="verifyPageWithBlockDataOnFrontend"> + <argument name="cmsTitle" value="{{_duplicatedCMSPage.title}}"/> + <argument name="cmsContent" value="bla bla bla"/> + <argument name="cmsContentHeading" value="{{_duplicatedCMSPage.content_heading}}"/> + </actionGroup> + <!--Delete page with block--> + <actionGroup ref="DeletePageByUrlKeyActionGroup" stepKey="deletePageWithBlock"> + <argument name="UrlKey" value="{{_duplicatedCMSPage.identifier}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Config/Block/System/Config/Form/Field.php b/app/code/Magento/Config/Block/System/Config/Form/Field.php index e4b582a1f0504..ac4a85b7d3bc6 100644 --- a/app/code/Magento/Config/Block/System/Config/Form/Field.php +++ b/app/code/Magento/Config/Block/System/Config/Form/Field.php @@ -43,6 +43,10 @@ public function render(\Magento\Framework\Data\Form\Element\AbstractElement $ele $element->setDisabled(true); } + if ($element->getIsDisableInheritance()) { + $element->setReadonly(true); + } + $html = '<td class="label"><label for="' . $element->getHtmlId() . '"><span' . $this->_renderScopeLabel($element) . '>' . @@ -94,7 +98,7 @@ protected function _renderInheritCheckbox(\Magento\Framework\Data\Form\Element\A $htmlId = $element->getHtmlId(); $namePrefix = preg_replace('#\[value\](\[\])?$#', '', $element->getName()); $checkedHtml = $element->getInherit() == 1 ? 'checked="checked"' : ''; - $disabled = $element->getIsDisableInheritance() == true ? ' disabled="disabled"' : ''; + $disabled = $element->getIsDisableInheritance() == true ? ' disabled="disabled" readonly="1"' : ''; $html = '<td class="use-default">'; $html .= '<input id="' . diff --git a/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/FieldTest.php b/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/FieldTest.php index 6be1fe04b68dd..d942af9352e6c 100644 --- a/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/FieldTest.php +++ b/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/FieldTest.php @@ -7,8 +7,6 @@ /** * Test how class render field html element in Stores Configuration - * - * @package Magento\Config\Test\Unit\Block\System\Config\Form */ class FieldTest extends \PHPUnit\Framework\TestCase { @@ -72,6 +70,7 @@ protected function setUp() 'getCanUseDefaultValue', 'setDisabled', 'getTooltip', + 'setReadonly' ] ); @@ -179,7 +178,8 @@ public function testRenderInheritCheckbox() $this->_elementMock->expects($this->any())->method('getCanUseWebsiteValue')->will($this->returnValue(true)); $this->_elementMock->expects($this->any())->method('getCanUseDefaultValue')->will($this->returnValue(true)); $this->_elementMock->expects($this->once())->method('setDisabled')->with(true); - $this->_elementMock->expects($this->once())->method('getIsDisableInheritance')->willReturn(true); + $this->_elementMock->method('getIsDisableInheritance')->willReturn(true); + $this->_elementMock->method('setReadonly')->with(true); $expected = '<td class="use-default">'; $expected .= '<input id="' . @@ -187,7 +187,7 @@ public function testRenderInheritCheckbox() '_inherit" name="' . $this->_testData['name'] . '[inherit]" type="checkbox" value="1"' . - ' class="checkbox config-inherit" checked="checked"' . ' disabled="disabled"' . + ' class="checkbox config-inherit" checked="checked"' . ' disabled="disabled"' . ' readonly="1"' . ' onclick="toggleValueElements(this, Element.previous(this.parentNode))" /> '; $expected .= '<label for="' . $this->_testData['htmlId'] . '_inherit" class="inherit">Use Website</label>'; diff --git a/app/code/Magento/Customer/Model/AccountManagement.php b/app/code/Magento/Customer/Model/AccountManagement.php index 985cfe0621bf8..55da6a62f0625 100644 --- a/app/code/Magento/Customer/Model/AccountManagement.php +++ b/app/code/Magento/Customer/Model/AccountManagement.php @@ -68,49 +68,55 @@ class AccountManagement implements AccountManagementInterface /** * Configuration paths for create account email template * - * @deprecated + * @deprecated Get rid of Helpers in Password Security Management + * @see EmailNotification::XML_PATH_REGISTER_EMAIL_TEMPLATE */ const XML_PATH_REGISTER_EMAIL_TEMPLATE = 'customer/create_account/email_template'; /** * Configuration paths for register no password email template * - * @deprecated + * @deprecated Get rid of Helpers in Password Security Management + * @see EmailNotification::XML_PATH_REGISTER_EMAIL_TEMPLATE */ const XML_PATH_REGISTER_NO_PASSWORD_EMAIL_TEMPLATE = 'customer/create_account/email_no_password_template'; /** * Configuration paths for remind email identity * - * @deprecated + * @deprecated Get rid of Helpers in Password Security Management + * @see EmailNotification::XML_PATH_REGISTER_EMAIL_TEMPLATE */ const XML_PATH_REGISTER_EMAIL_IDENTITY = 'customer/create_account/email_identity'; /** * Configuration paths for remind email template * - * @deprecated + * @deprecated Get rid of Helpers in Password Security Management + * @see EmailNotification::XML_PATH_REGISTER_EMAIL_TEMPLATE */ const XML_PATH_REMIND_EMAIL_TEMPLATE = 'customer/password/remind_email_template'; /** * Configuration paths for forgot email email template * - * @deprecated + * @deprecated Get rid of Helpers in Password Security Management + * @see EmailNotification::XML_PATH_REGISTER_EMAIL_TEMPLATE */ const XML_PATH_FORGOT_EMAIL_TEMPLATE = 'customer/password/forgot_email_template'; /** * Configuration paths for forgot email identity * - * @deprecated + * @deprecated Get rid of Helpers in Password Security Management + * @see EmailNotification::XML_PATH_REGISTER_EMAIL_TEMPLATE */ const XML_PATH_FORGOT_EMAIL_IDENTITY = 'customer/password/forgot_email_identity'; /** * Configuration paths for account confirmation required * - * @deprecated + * @deprecated Get rid of Helpers in Password Security Management * @see AccountConfirmation::XML_PATH_IS_CONFIRM */ const XML_PATH_IS_CONFIRM = 'customer/create_account/confirm'; @@ -118,42 +124,48 @@ class AccountManagement implements AccountManagementInterface /** * Configuration paths for account confirmation email template * - * @deprecated + * @deprecated Get rid of Helpers in Password Security Management + * @see EmailNotification::XML_PATH_REGISTER_EMAIL_TEMPLATE */ const XML_PATH_CONFIRM_EMAIL_TEMPLATE = 'customer/create_account/email_confirmation_template'; /** * Configuration paths for confirmation confirmed email template * - * @deprecated + * @deprecated Get rid of Helpers in Password Security Management + * @see EmailNotification::XML_PATH_REGISTER_EMAIL_TEMPLATE */ const XML_PATH_CONFIRMED_EMAIL_TEMPLATE = 'customer/create_account/email_confirmed_template'; /** * Constants for the type of new account email to be sent * - * @deprecated + * @deprecated Get rid of Helpers in Password Security Management + * @see EmailNotificationInterface::NEW_ACCOUNT_EMAIL_REGISTERED */ const NEW_ACCOUNT_EMAIL_REGISTERED = 'registered'; /** * Welcome email, when password setting is required * - * @deprecated + * @deprecated Get rid of Helpers in Password Security Management + * @see EmailNotificationInterface::NEW_ACCOUNT_EMAIL_REGISTERED */ const NEW_ACCOUNT_EMAIL_REGISTERED_NO_PASSWORD = 'registered_no_password'; /** * Welcome email, when confirmation is enabled * - * @deprecated + * @deprecated Get rid of Helpers in Password Security Management + * @see EmailNotificationInterface::NEW_ACCOUNT_EMAIL_REGISTERED */ const NEW_ACCOUNT_EMAIL_CONFIRMATION = 'confirmation'; /** * Confirmation email, when account is confirmed * - * @deprecated + * @deprecated Get rid of Helpers in Password Security Management + * @see EmailNotificationInterface::NEW_ACCOUNT_EMAIL_REGISTERED */ const NEW_ACCOUNT_EMAIL_CONFIRMED = 'confirmed'; @@ -179,14 +191,15 @@ class AccountManagement implements AccountManagementInterface /** * Configuration path to customer reset password email template * - * @deprecated + * @deprecated Get rid of Helpers in Password Security Management + * @see Magento/Customer/Model/EmailNotification::XML_PATH_REGISTER_EMAIL_TEMPLATE */ const XML_PATH_RESET_PASSWORD_TEMPLATE = 'customer/password/reset_password_template'; /** * Minimum password length * - * @deprecated + * @deprecated Get rid of Helpers in Password Security Management */ const MIN_PASSWORD_LENGTH = 6; @@ -526,6 +539,7 @@ public function resendConfirmation($email, $websiteId = null, $redirectUrl = '') // If we are not able to send a new account email, this should be ignored $this->logger->critical($e); } + return true; } /** @@ -671,17 +685,16 @@ public function initiatePasswordReset($email, $template, $websiteId = null) */ private function handleUnknownTemplate($template) { - throw new InputException( - __( - 'Invalid value of "%value" provided for the %fieldName field. Possible values: %template1 or %template2.', - [ - 'value' => $template, - 'fieldName' => 'template', - 'template1' => AccountManagement::EMAIL_REMINDER, - 'template2' => AccountManagement::EMAIL_RESET - ] - ) + $phrase = __( + 'Invalid value of "%value" provided for the %fieldName field. Possible values: %template1 or %template2.', + [ + 'value' => $template, + 'fieldName' => 'template', + 'template1' => AccountManagement::EMAIL_REMINDER, + 'template2' => AccountManagement::EMAIL_RESET + ] ); + throw new InputException($phrase); } /** @@ -713,12 +726,6 @@ public function resetPassword($email, $resetToken, $newPassword) $customerSecure->setRpTokenCreatedAt(null); $customerSecure->setPasswordHash($this->createPasswordHash($newPassword)); $this->destroyCustomerSessions($customer->getId()); - if ($this->sessionManager->isSessionExists()) { - //delete old session and move data to the new session - //use this instead of $this->sessionManager->regenerateId because last one doesn't delete old session - // phpcs:ignore Magento2.Functions.DiscouragedFunction - session_regenerate_id(true); - } $this->customerRepository->save($customer); return true; diff --git a/app/code/Magento/Customer/Model/ResourceModel/Address/Relation.php b/app/code/Magento/Customer/Model/ResourceModel/Address/Relation.php index ae342a1b10dd8..cf837e2924161 100644 --- a/app/code/Magento/Customer/Model/ResourceModel/Address/Relation.php +++ b/app/code/Magento/Customer/Model/ResourceModel/Address/Relation.php @@ -1,14 +1,18 @@ <?php /** - * Customer address entity resource model - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Customer\Model\ResourceModel\Address; +use Magento\Customer\Api\Data\CustomerInterface; use Magento\Customer\Model\Address; use Magento\Customer\Model\Customer; +use Magento\Customer\Model\CustomerFactory; +use Magento\Customer\Model\CustomerRegistry; +use Magento\Framework\Model\AbstractModel; use Magento\Framework\Model\ResourceModel\Db\VersionControl\RelationInterface; /** @@ -17,29 +21,36 @@ class Relation implements RelationInterface { /** - * @var \Magento\Customer\Model\CustomerFactory + * @var CustomerFactory */ protected $customerFactory; /** - * @param \Magento\Customer\Model\CustomerFactory $customerFactory + * @var CustomerRegistry */ - public function __construct(\Magento\Customer\Model\CustomerFactory $customerFactory) - { + private $customerRegistry; + + /** + * @param CustomerFactory $customerFactory + * @param CustomerRegistry $customerRegistry + */ + public function __construct( + CustomerFactory $customerFactory, + CustomerRegistry $customerRegistry + ) { $this->customerFactory = $customerFactory; + $this->customerRegistry = $customerRegistry; } /** * Process object relations * - * @param \Magento\Framework\Model\AbstractModel $object + * @param AbstractModel $object * @return void */ - public function processRelation(\Magento\Framework\Model\AbstractModel $object) + public function processRelation(AbstractModel $object): void { - /** - * @var $object Address - */ + /** @var $object Address */ if (!$object->getIsCustomerSaveTransaction() && $object->getId()) { $customer = $this->customerFactory->create()->load($object->getCustomerId()); @@ -53,6 +64,7 @@ public function processRelation(\Magento\Framework\Model\AbstractModel $object) $changedAddresses, $customer->getResource()->getConnection()->quoteInto('entity_id = ?', $customer->getId()) ); + $this->updateCustomerRegistry($customer, $changedAddresses); } } } @@ -71,12 +83,12 @@ private function getDefaultBillingChangedAddress( array $changedAddresses ): array { if ($object->getIsDefaultBilling()) { - $changedAddresses['default_billing'] = $object->getId(); + $changedAddresses[CustomerInterface::DEFAULT_BILLING] = $object->getId(); } elseif ($customer->getDefaultBillingAddress() && $object->getIsDefaultBilling() === false && (int)$customer->getDefaultBillingAddress()->getId() === (int)$object->getId() ) { - $changedAddresses['default_billing'] = null; + $changedAddresses[CustomerInterface::DEFAULT_BILLING] = null; } return $changedAddresses; @@ -96,27 +108,47 @@ private function getDefaultShippingChangedAddress( array $changedAddresses ): array { if ($object->getIsDefaultShipping()) { - $changedAddresses['default_shipping'] = $object->getId(); + $changedAddresses[CustomerInterface::DEFAULT_SHIPPING] = $object->getId(); } elseif ($customer->getDefaultShippingAddress() && $object->getIsDefaultShipping() === false && (int)$customer->getDefaultShippingAddress()->getId() === (int)$object->getId() ) { - $changedAddresses['default_shipping'] = null; + $changedAddresses[CustomerInterface::DEFAULT_SHIPPING] = null; } return $changedAddresses; } + /** + * Push updated customer entity to the registry. + * + * @param Customer $customer + * @param array $changedAddresses + * @return void + */ + private function updateCustomerRegistry(Customer $customer, array $changedAddresses): void + { + if (array_key_exists(CustomerInterface::DEFAULT_BILLING, $changedAddresses)) { + $customer->setDefaultBilling($changedAddresses[CustomerInterface::DEFAULT_BILLING]); + } + + if (array_key_exists(CustomerInterface::DEFAULT_SHIPPING, $changedAddresses)) { + $customer->setDefaultShipping($changedAddresses[CustomerInterface::DEFAULT_SHIPPING]); + } + + $this->customerRegistry->push($customer); + } + /** * Checks if address has chosen as default and has had an id * * @deprecated Is not used anymore due to changes in logic of save of address. * If address was default and becomes not default than default address id for customer must be * set to null - * @param \Magento\Framework\Model\AbstractModel $object + * @param AbstractModel $object * @return bool */ - protected function isAddressDefault(\Magento\Framework\Model\AbstractModel $object) + protected function isAddressDefault(AbstractModel $object) { return $object->getId() && ($object->getIsDefaultBilling() || $object->getIsDefaultShipping()); } diff --git a/app/code/Magento/Customer/Model/Visitor.php b/app/code/Magento/Customer/Model/Visitor.php index 6935b9dca7f28..5fa1af69e9bdd 100644 --- a/app/code/Magento/Customer/Model/Visitor.php +++ b/app/code/Magento/Customer/Model/Visitor.php @@ -12,7 +12,8 @@ /** * Class Visitor * - * @package Magento\Customer\Model + * Used to track sessions of the logged in customers + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ @@ -173,7 +174,7 @@ public function initByRequest($observer) if ($this->requestSafety->isSafeMethod()) { return $this; } - + if (!$this->getId()) { $this->setSessionId($this->session->getSessionId()); $this->save(); @@ -199,6 +200,9 @@ public function saveByRequest($observer) } try { + if ($this->session->getSessionId() && $this->getSessionId() != $this->session->getSessionId()) { + $this->setSessionId($this->session->getSessionId()); + } $this->save(); $this->_eventManager->dispatch('visitor_activity_save', ['visitor' => $this]); $this->session->setVisitorData($this->getData()); diff --git a/app/code/Magento/DownloadableGraphQl/Model/ResourceModel/GetPurchasedDownloadableProducts.php b/app/code/Magento/DownloadableGraphQl/Model/ResourceModel/GetPurchasedDownloadableProducts.php index 82f448ec450f6..4bbdfdb360415 100644 --- a/app/code/Magento/DownloadableGraphQl/Model/ResourceModel/GetPurchasedDownloadableProducts.php +++ b/app/code/Magento/DownloadableGraphQl/Model/ResourceModel/GetPurchasedDownloadableProducts.php @@ -39,13 +39,13 @@ public function execute(int $customerId): array { $connection = $this->resourceConnection->getConnection(); $allowedItemsStatuses = [Item::LINK_STATUS_PENDING_PAYMENT, Item::LINK_STATUS_PAYMENT_REVIEW]; - $downloadablePurchasedTable = $connection->getTableName('downloadable_link_purchased'); + $downloadablePurchasedTable = $this->resourceConnection->getTableName('downloadable_link_purchased'); /* The fields names are hardcoded since there's no existing name reference in the code */ $selectQuery = $connection->select() ->from($downloadablePurchasedTable) ->joinLeft( - ['item' => $connection->getTableName('downloadable_link_purchased_item')], + ['item' => $this->resourceConnection->getTableName('downloadable_link_purchased_item')], "$downloadablePurchasedTable.purchased_id = item.purchased_id" ) ->where("$downloadablePurchasedTable.customer_id = ?", $customerId) diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php index 348a1c708a78c..7a5d6fcdcc1b1 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php @@ -62,6 +62,11 @@ class StaticField implements FieldProviderInterface */ private $fieldNameResolver; + /** + * @var array + */ + private $excludedAttributes; + /** * @param Config $eavConfig * @param FieldTypeConverterInterface $fieldTypeConverter @@ -70,6 +75,7 @@ class StaticField implements FieldProviderInterface * @param FieldIndexResolver $fieldIndexResolver * @param AttributeProvider $attributeAdapterProvider * @param FieldName\ResolverInterface|null $fieldNameResolver + * @param array $excludedAttributes */ public function __construct( Config $eavConfig, @@ -78,7 +84,8 @@ public function __construct( FieldTypeResolver $fieldTypeResolver, FieldIndexResolver $fieldIndexResolver, AttributeProvider $attributeAdapterProvider, - FieldName\ResolverInterface $fieldNameResolver = null + FieldName\ResolverInterface $fieldNameResolver = null, + array $excludedAttributes = [] ) { $this->eavConfig = $eavConfig; $this->fieldTypeConverter = $fieldTypeConverter; @@ -88,6 +95,7 @@ public function __construct( $this->attributeAdapterProvider = $attributeAdapterProvider; $this->fieldNameResolver = $fieldNameResolver ?: ObjectManager::getInstance() ->get(FieldName\ResolverInterface::class); + $this->excludedAttributes = $excludedAttributes; } /** @@ -103,6 +111,9 @@ public function getFields(array $context = []): array $allAttributes = []; foreach ($attributes as $attribute) { + if (in_array($attribute->getAttributeCode(), $this->excludedAttributes, true)) { + continue; + } $attributeAdapter = $this->attributeAdapterProvider->getByAttributeCode($attribute->getAttributeCode()); $fieldName = $this->fieldNameResolver->getFieldName($attributeAdapter); diff --git a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php index ad52f81bf8eda..2ee34b6b4c5b1 100644 --- a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php +++ b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php @@ -85,6 +85,8 @@ public function apply() private function sliceItems(array $items, int $size, int $currentPage): array { if ($size !== 0) { + $totalPages = (int) ceil(count($items)/$size); + $currentPage = min($currentPage, $totalPages); $offset = ($currentPage - 1) * $size; if ($offset < 0) { $offset = 0; diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/StaticFieldTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/StaticFieldTest.php index c08a4298b42d2..a603d6ab47824 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/StaticFieldTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/StaticFieldTest.php @@ -8,6 +8,7 @@ namespace Magento\Elasticsearch\Test\Unit\Model\Adapter\FieldMapper\Product\FieldProvider; use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\StaticField; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Eav\Model\Config; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider; @@ -22,56 +23,56 @@ as FieldIndexResolver; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface as FieldNameResolver; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; /** - * @SuppressWarnings(PHPMD) + * Unit tests for \Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\StaticField class. */ -class StaticFieldTest extends \PHPUnit\Framework\TestCase +class StaticFieldTest extends TestCase { /** - * @var \Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\StaticField + * @var StaticField */ private $provider; /** - * @var \Magento\Eav\Model\Config|\PHPUnit_Framework_MockObject_MockObject + * @var Config|MockObject */ private $eavConfig; /** - * @var FieldTypeConverterInterface + * @var FieldTypeConverterInterface|MockObject */ private $fieldTypeConverter; /** - * @var IndexTypeConverterInterface + * @var IndexTypeConverterInterface|MockObject */ private $indexTypeConverter; /** - * @var AttributeProvider + * @var AttributeProvider|MockObject */ private $attributeAdapterProvider; /** - * @var FieldIndexResolver + * @var FieldIndexResolver|MockObject */ private $fieldIndexResolver; /** - * @var FieldTypeResolver + * @var FieldTypeResolver|MockObject */ private $fieldTypeResolver; /** - * @var FieldNameResolver + * @var FieldNameResolver|MockObject */ private $fieldNameResolver; /** - * Set up test environment - * - * @return void + * @inheritdoc */ protected function setUp() { @@ -105,7 +106,7 @@ protected function setUp() $objectManager = new ObjectManagerHelper($this); $this->provider = $objectManager->getObject( - \Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\StaticField::class, + StaticField::class, [ 'eavConfig' => $this->eavConfig, 'fieldTypeConverter' => $this->fieldTypeConverter, @@ -114,37 +115,42 @@ protected function setUp() 'fieldIndexResolver' => $this->fieldIndexResolver, 'fieldTypeResolver' => $this->fieldTypeResolver, 'fieldNameResolver' => $this->fieldNameResolver, + 'excludedAttributes' => ['price'], ] ); } /** - * @dataProvider attributeProvider * @param string $attributeCode * @param string $inputType - * @param $indexType - * @param $isComplexType - * @param $complexType - * @param $isSortable - * @param $fieldName - * @param $compositeFieldName - * @param $sortFieldName + * @param string|bool $indexType + * @param bool $isComplexType + * @param string $complexType + * @param bool $isSortable + * @param bool $isTextType + * @param string $fieldName + * @param string $compositeFieldName + * @param string $sortFieldName * @param array $expected * @return void + * @dataProvider attributeProvider + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function testGetAllAttributesTypes( - $attributeCode, - $inputType, + string $attributeCode, + string $inputType, $indexType, - $isComplexType, - $complexType, - $isSortable, - $isTextType, - $fieldName, - $compositeFieldName, - $sortFieldName, - $expected - ) { + bool $isComplexType, + string $complexType, + bool $isSortable, + bool $isTextType, + string $fieldName, + string $compositeFieldName, + string $sortFieldName, + array $expected + ): void { $this->fieldTypeResolver->expects($this->any()) ->method('getFieldType') ->willReturn($inputType); @@ -154,32 +160,28 @@ public function testGetAllAttributesTypes( $this->indexTypeConverter->expects($this->any()) ->method('convert') ->with($this->anything()) - ->will( - $this->returnCallback( - function ($type) { - if ($type === 'no_index') { - return 'no'; - } elseif ($type === 'no_analyze') { - return 'not_analyzed'; - } + ->willReturnCallback( + function ($type) { + if ($type === 'no_index') { + return 'no'; + } elseif ($type === 'no_analyze') { + return 'not_analyzed'; } - ) + } ); $this->fieldNameResolver->expects($this->any()) ->method('getFieldName') ->with($this->anything()) - ->will( - $this->returnCallback( - function ($attributeMock, $context) use ($fieldName, $compositeFieldName, $sortFieldName) { - if (empty($context)) { - return $fieldName; - } elseif ($context['type'] === 'sort') { - return $sortFieldName; - } elseif ($context['type'] === 'text') { - return $compositeFieldName; - } + ->willReturnCallback( + function ($attributeMock, $context) use ($fieldName, $compositeFieldName, $sortFieldName) { + if (empty($context)) { + return $fieldName; + } elseif ($context['type'] === 'sort') { + return $sortFieldName; + } elseif ($context['type'] === 'text') { + return $compositeFieldName; } - ) + } ); $productAttributeMock = $this->getMockBuilder(AbstractAttribute::class) @@ -215,23 +217,21 @@ function ($attributeMock, $context) use ($fieldName, $compositeFieldName, $sortF $this->fieldTypeConverter->expects($this->any()) ->method('convert') ->with($this->anything()) - ->will( - $this->returnCallback( - function ($type) use ($complexType) { - static $callCount = []; - $callCount[$type] = !isset($callCount[$type]) ? 1 : ++$callCount[$type]; + ->willReturnCallback( + function ($type) use ($complexType) { + static $callCount = []; + $callCount[$type] = !isset($callCount[$type]) ? 1 : ++$callCount[$type]; - if ($type === 'string') { - return 'string'; - } elseif ($type === 'float') { - return 'float'; - } elseif ($type === 'keyword') { - return 'string'; - } else { - return $complexType; - } + if ($type === 'string') { + return 'string'; + } elseif ($type === 'float') { + return 'float'; + } elseif ($type === 'keyword') { + return 'string'; + } else { + return $complexType; } - ) + } ); $this->assertEquals( @@ -242,8 +242,9 @@ function ($type) use ($complexType) { /** * @return array + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function attributeProvider() + public function attributeProvider(): array { return [ [ @@ -264,25 +265,25 @@ public function attributeProvider() 'fields' => [ 'keyword' => [ 'type' => 'string', - 'index' => 'not_analyzed' - ] - ] + 'index' => 'not_analyzed', + ], + ], ], 'category_ids_value' => [ - 'type' => 'string' + 'type' => 'string', ], 'store_id' => [ 'type' => 'string', - 'index' => 'no' - ] - ] + 'index' => 'no', + ], + ], ], [ 'attr_code', 'text', 'no', false, - null, + 'text', false, true, 'attr_code', @@ -295,22 +296,22 @@ public function attributeProvider() 'fields' => [ 'keyword' => [ 'type' => 'string', - 'index' => 'not_analyzed' - ] - ] + 'index' => 'not_analyzed', + ], + ], ], 'store_id' => [ 'type' => 'string', - 'index' => 'no' - ] + 'index' => 'no', + ], ], ], [ 'attr_code', 'text', - null, false, - null, + false, + 'text', false, false, 'attr_code', @@ -318,20 +319,21 @@ public function attributeProvider() '', [ 'attr_code' => [ - 'type' => 'text' + 'type' => 'text', + 'index' => false, ], 'store_id' => [ 'type' => 'string', - 'index' => 'no' - ] - ] + 'index' => 'no', + ], + ], ], [ 'attr_code', 'text', - null, false, - null, + false, + 'text', true, false, 'attr_code', @@ -340,19 +342,38 @@ public function attributeProvider() [ 'attr_code' => [ 'type' => 'text', + 'index' => false, 'fields' => [ 'sort_attr_code' => [ 'type' => 'string', - 'index' => 'not_analyzed' - ] - ] + 'index' => 'not_analyzed', + ], + ], ], 'store_id' => [ 'type' => 'string', - 'index' => 'no' - ] - ] - ] + 'index' => 'no', + ], + ], + ], + [ + 'price', + 'text', + false, + false, + 'text', + false, + false, + 'price', + '', + '', + [ + 'store_id' => [ + 'type' => 'string', + 'index' => 'no', + ], + ], + ], ]; } } diff --git a/app/code/Magento/Elasticsearch/etc/di.xml b/app/code/Magento/Elasticsearch/etc/di.xml index 2b29e53635e3a..bb16bba127b56 100644 --- a/app/code/Magento/Elasticsearch/etc/di.xml +++ b/app/code/Magento/Elasticsearch/etc/di.xml @@ -558,4 +558,11 @@ </argument> </arguments> </type> + <type name="Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\StaticField"> + <arguments> + <argument name="excludedAttributes" xsi:type="array"> + <item name="price" xsi:type="string">price</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/ImportExport/Ui/Component/Columns/ExportGridActions.php b/app/code/Magento/ImportExport/Ui/Component/Columns/ExportGridActions.php index b5e36ccd9fbab..2cdc39866a521 100644 --- a/app/code/Magento/ImportExport/Ui/Component/Columns/ExportGridActions.php +++ b/app/code/Magento/ImportExport/Ui/Component/Columns/ExportGridActions.php @@ -56,11 +56,17 @@ public function prepareDataSource(array $dataSource) $name = $this->getData('name'); if (isset($item['file_name'])) { $item[$name]['view'] = [ - 'href' => $this->urlBuilder->getUrl(Download::URL, ['filename' => $item['file_name']]), + 'href' => $this->urlBuilder->getUrl( + Download::URL, + ['_query' => ['filename' => $item['file_name']]] + ), 'label' => __('Download') ]; $item[$name]['delete'] = [ - 'href' => $this->urlBuilder->getUrl(Delete::URL, ['filename' => $item['file_name']]), + 'href' => $this->urlBuilder->getUrl( + Delete::URL, + ['_query' => ['filename' => $item['file_name']]] + ), 'label' => __('Delete'), 'confirm' => [ 'title' => __('Delete'), diff --git a/app/code/Magento/ImportExport/Ui/DataProvider/ExportFileDataProvider.php b/app/code/Magento/ImportExport/Ui/DataProvider/ExportFileDataProvider.php index 83203f1ad8aff..57d1982d3500f 100644 --- a/app/code/Magento/ImportExport/Ui/DataProvider/ExportFileDataProvider.php +++ b/app/code/Magento/ImportExport/Ui/DataProvider/ExportFileDataProvider.php @@ -100,7 +100,7 @@ public function getData() } $result = []; foreach ($files as $file) { - $result['items'][]['file_name'] = $this->fileIO->getPathInfo($file)['basename']; + $result['items'][]['file_name'] = $this->getPathToExportFile($this->fileIO->getPathInfo($file)); } $pageSize = (int) $this->request->getParam('paging')['pageSize']; @@ -112,6 +112,31 @@ public function getData() return $result; } + /** + * Return relative export file path after "var/export" + * + * @param mixed $file + * @return string + */ + private function getPathToExportFile($file): string + { + $directory = $this->fileSystem->getDirectoryRead(DirectoryList::VAR_DIR); + $delimiter = '/'; + $cutPath = explode( + $delimiter, + $directory->getAbsolutePath() . 'export' + ); + $filePath = explode( + $delimiter, + $file['dirname'] + ); + + return ltrim( + implode($delimiter, array_diff($filePath, $cutPath)) . $delimiter . $file['basename'], + $delimiter + ); + } + /** * Get files from directory path, sort them by date modified and return sorted array of full path to files * @@ -127,8 +152,10 @@ private function getExportFiles(string $directoryPath): array return []; } foreach ($files as $filePath) { - //phpcs:ignore Magento2.Functions.DiscouragedFunction - $sortedFiles[filemtime($filePath)] = $filePath; + if ($this->file->isFile($filePath)) { + //phpcs:ignore Magento2.Functions.DiscouragedFunction + $sortedFiles[filemtime($filePath)] = $filePath; + } } //sort array elements using key value krsort($sortedFiles); diff --git a/app/code/Magento/Indexer/Setup/Recurring.php b/app/code/Magento/Indexer/Setup/Recurring.php index 2f1946fab1ab7..30e4ad438e73d 100644 --- a/app/code/Magento/Indexer/Setup/Recurring.php +++ b/app/code/Magento/Indexer/Setup/Recurring.php @@ -13,12 +13,15 @@ use Magento\Framework\Setup\InstallSchemaInterface; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\SchemaSetupInterface; +use Magento\Framework\Indexer\IndexerInterfaceFactory; use Magento\Framework\Indexer\ConfigInterface; use Magento\Indexer\Model\Indexer\State; use Magento\Indexer\Model\Indexer\StateFactory; use Magento\Indexer\Model\ResourceModel\Indexer\State\CollectionFactory; /** + * Indexer recurring setup + * * @codeCoverageIgnore * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -51,6 +54,11 @@ class Recurring implements InstallSchemaInterface */ private $stateFactory; + /** + * @var IndexerInterfaceFactory + */ + private $indexerFactory; + /** * Init * @@ -59,23 +67,26 @@ class Recurring implements InstallSchemaInterface * @param ConfigInterface $config * @param EncryptorInterface $encryptor * @param EncoderInterface $encoder + * @param IndexerInterfaceFactory $indexerFactory */ public function __construct( CollectionFactory $statesFactory, StateFactory $stateFactory, ConfigInterface $config, EncryptorInterface $encryptor, - EncoderInterface $encoder + EncoderInterface $encoder, + IndexerInterfaceFactory $indexerFactory ) { $this->statesFactory = $statesFactory; $this->stateFactory = $stateFactory; $this->config = $config; $this->encryptor = $encryptor; $this->encoder = $encoder; + $this->indexerFactory = $indexerFactory; } /** - * {@inheritdoc} + * @inheritdoc */ public function install(SchemaSetupInterface $setup, ModuleContextInterface $context) { @@ -107,6 +118,11 @@ public function install(SchemaSetupInterface $setup, ModuleContextInterface $con $state->setStatus(StateInterface::STATUS_INVALID); $state->save(); } + + $indexer = $this->indexerFactory->create()->load($indexerId); + if ($indexer->isScheduled()) { + $indexer->getView()->unsubscribe()->subscribe(); + } } } } diff --git a/app/code/Magento/Indexer/Setup/RecurringData.php b/app/code/Magento/Indexer/Setup/RecurringData.php deleted file mode 100644 index 1f6ea09ba853f..0000000000000 --- a/app/code/Magento/Indexer/Setup/RecurringData.php +++ /dev/null @@ -1,56 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Indexer\Setup; - -use Magento\Framework\Indexer\IndexerInterfaceFactory; -use Magento\Framework\Setup\InstallDataInterface; -use Magento\Framework\Setup\ModuleContextInterface; -use Magento\Framework\Setup\ModuleDataSetupInterface; -use Magento\Framework\Indexer\ConfigInterface; - -/** - * Recurring data upgrade for indexer module - */ -class RecurringData implements InstallDataInterface -{ - /** - * @var IndexerInterfaceFactory - */ - private $indexerFactory; - - /** - * @var ConfigInterface - */ - private $configInterface; - - /** - * RecurringData constructor. - * - * @param IndexerInterfaceFactory $indexerFactory - * @param ConfigInterface $configInterface - */ - public function __construct( - IndexerInterfaceFactory $indexerFactory, - ConfigInterface $configInterface - ) { - $this->indexerFactory = $indexerFactory; - $this->configInterface = $configInterface; - } - - /** - * {@inheritdoc} - */ - public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context) - { - foreach (array_keys($this->configInterface->getIndexers()) as $indexerId) { - $indexer = $this->indexerFactory->create()->load($indexerId); - if ($indexer->isScheduled()) { - $indexer->getView()->unsubscribe()->subscribe(); - } - } - } -} diff --git a/app/code/Magento/Integration/Test/Mftf/Test/AdminCreateIntegrationEntityWithDuplicatedNameTest.xml b/app/code/Magento/Integration/Test/Mftf/Test/AdminCreateIntegrationEntityWithDuplicatedNameTest.xml new file mode 100644 index 0000000000000..b23436d474ed8 --- /dev/null +++ b/app/code/Magento/Integration/Test/Mftf/Test/AdminCreateIntegrationEntityWithDuplicatedNameTest.xml @@ -0,0 +1,50 @@ +<?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="AdminCreateIntegrationEntityWithDuplicatedNameTest"> + <annotations> + <features value="Integration"/> + <stories value="Creating System Integration With Duplicated Name"/> + <title value="Admin System Integration With Duplicated Name"/> + <description value="Admin Creates New Integration With Duplicated Name"/> + <severity value="MAJOR"/> + <testCaseId value="MC-19889"/> + <group value="integration"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToIntegrationsPage"> + <argument name="menuUiId" value="{{AdminMenuSystem.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuSystemExtensionsIntegrations.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminNavigateToCreateIntegrationPageActionGroup" stepKey="clickAddNewIntegrationButton"/> + <actionGroup ref="AdminCreatesNewIntegrationActionGroup" stepKey="createNewIntegration"> + <argument name="name" value="Integration1"/> + <argument name="password" value="{{_ENV.MAGENTO_ADMIN_PASSWORD}}"/> + </actionGroup> + <actionGroup ref="AdminSubmitNewIntegrationFormActionGroup" stepKey="submitTheForm"/> + <actionGroup ref="AdminNavigateToCreateIntegrationPageActionGroup" stepKey="clickAddNewIntegrationButtonSecondTime"/> + <actionGroup ref="AdminCreatesNewIntegrationActionGroup" stepKey="createNewIntegrationWithDuplicatedName"> + <argument name="name" value="Integration1"/> + <argument name="password" value="{{_ENV.MAGENTO_ADMIN_PASSWORD}}"/> + </actionGroup> + <actionGroup ref="AdminSubmitNewIntegrationFormActionGroup" stepKey="submitTheFormWithDuplicatedName"/> + <actionGroup ref="AssertAdminMessageCreateIntegrationEntityActionGroup" stepKey="seeErrorMessage"> + <argument name="message" value="The integration with name "Integration1" exists."/> + <argument value="error" name="messageType"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Multishipping/Test/Mftf/Page/MultishippingCheckoutAddressesPage.xml b/app/code/Magento/Multishipping/Test/Mftf/Page/MultishippingCheckoutAddressesPage.xml new file mode 100644 index 0000000000000..08ff948b200a7 --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Mftf/Page/MultishippingCheckoutAddressesPage.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="MultishippingCheckoutAddressesPage" url="/multishipping/checkout/addresses" module="Magento_Multishipping" area="storefront"> + <section name="StorefrontMultishippingCheckoutAddressesToolbarSection"/> + </page> +</pages> diff --git a/app/code/Magento/Multishipping/Test/Mftf/Page/MultishippingCheckoutBillingPage.xml b/app/code/Magento/Multishipping/Test/Mftf/Page/MultishippingCheckoutBillingPage.xml new file mode 100644 index 0000000000000..6e1fbaed2d29a --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Mftf/Page/MultishippingCheckoutBillingPage.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="MultishippingCheckoutBillingPage" url="/multishipping/checkout/billing" module="Magento_Multishipping" area="storefront"> + <section name="StorefrontMultishippingCheckoutBillingToolbarSection"/> + </page> +</pages> diff --git a/app/code/Magento/Multishipping/Test/Mftf/Page/MultishippingCheckoutOverviewPage.xml b/app/code/Magento/Multishipping/Test/Mftf/Page/MultishippingCheckoutOverviewPage.xml new file mode 100644 index 0000000000000..2f56f70f96e02 --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Mftf/Page/MultishippingCheckoutOverviewPage.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="MultishippingCheckoutOverviewPage" url="/multishipping/checkout/overview" module="Magento_Multishipping" area="storefront"> + <section name="StorefrontMultishippingCheckoutOverviewReviewSection"/> + </page> +</pages> diff --git a/app/code/Magento/Multishipping/Test/Mftf/Page/MultishippingCheckoutShippingPage.xml b/app/code/Magento/Multishipping/Test/Mftf/Page/MultishippingCheckoutShippingPage.xml new file mode 100644 index 0000000000000..a3d54a9ed90a7 --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Mftf/Page/MultishippingCheckoutShippingPage.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="MultishippingCheckoutShippingPage" url="/multishipping/checkout/shipping" module="Magento_Multishipping" area="storefront"> + <section name="StorefrontMultishippingCheckoutShippingToolbarSection"/> + </page> +</pages> diff --git a/app/code/Magento/Multishipping/Test/Mftf/Section/StorefrontMultishippingCheckoutAddressesToolbarSection.xml b/app/code/Magento/Multishipping/Test/Mftf/Section/StorefrontMultishippingCheckoutAddressesToolbarSection.xml new file mode 100644 index 0000000000000..f238afe672156 --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Mftf/Section/StorefrontMultishippingCheckoutAddressesToolbarSection.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="StorefrontMultishippingCheckoutAddressesToolbarSection"> + <element name="goToShippingInformation" type="button" selector="button.action.primary.continue"/> + </section> +</sections> diff --git a/app/code/Magento/Multishipping/Test/Mftf/Section/StorefrontMultishippingCheckoutBillingToolbarSection.xml b/app/code/Magento/Multishipping/Test/Mftf/Section/StorefrontMultishippingCheckoutBillingToolbarSection.xml new file mode 100644 index 0000000000000..6cfc09c1653fd --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Mftf/Section/StorefrontMultishippingCheckoutBillingToolbarSection.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="StorefrontMultishippingCheckoutBillingToolbarSection"> + <element name="goToReviewOrder" type="button" selector="button.action.primary.continue"/> + </section> +</sections> diff --git a/app/code/Magento/Multishipping/Test/Mftf/Section/StorefrontMultishippingCheckoutOverviewReviewSection.xml b/app/code/Magento/Multishipping/Test/Mftf/Section/StorefrontMultishippingCheckoutOverviewReviewSection.xml new file mode 100644 index 0000000000000..3c4de2990f9a5 --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Mftf/Section/StorefrontMultishippingCheckoutOverviewReviewSection.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="StorefrontMultishippingCheckoutOverviewReviewSection"> + <element name="placeOrder" type="button" selector="button.action.primary.submit"/> + </section> +</sections> diff --git a/app/code/Magento/Multishipping/Test/Mftf/Section/StorefrontMultishippingCheckoutShippingToolbarSection.xml b/app/code/Magento/Multishipping/Test/Mftf/Section/StorefrontMultishippingCheckoutShippingToolbarSection.xml new file mode 100644 index 0000000000000..deea09acf2f2a --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Mftf/Section/StorefrontMultishippingCheckoutShippingToolbarSection.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="StorefrontMultishippingCheckoutShippingToolbarSection"> + <element name="continueToBilling" type="button" selector="button.action.primary.continue"/> + </section> +</sections> diff --git a/app/code/Magento/Newsletter/Controller/Subscriber/NewAction.php b/app/code/Magento/Newsletter/Controller/Subscriber/NewAction.php index ea52ae8aaa864..0944ea4626586 100644 --- a/app/code/Magento/Newsletter/Controller/Subscriber/NewAction.php +++ b/app/code/Magento/Newsletter/Controller/Subscriber/NewAction.php @@ -172,7 +172,9 @@ public function execute() $message = $this->getSuccessMessage((int)$subscriber->getSubscriberStatus()); $this->messageManager->addSuccessMessage($message); } catch (LocalizedException $e) { - $this->messageManager->addErrorMessage($e->getMessage()); + $this->messageManager->addComplexErrorMessage( + 'localizedSubscriptionErrorMessage', ['message' => $e->getMessage()] + ); } catch (\Exception $e) { $this->messageManager->addExceptionMessage($e, __('Something went wrong with the subscription.')); } diff --git a/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/AssertAdminNewsletterConfigFieldGuestNotAllowedActionGroup.xml b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/AssertAdminNewsletterConfigFieldGuestNotAllowedActionGroup.xml new file mode 100644 index 0000000000000..73fb9de740ba7 --- /dev/null +++ b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/AssertAdminNewsletterConfigFieldGuestNotAllowedActionGroup.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="AssertAdminNewsletterConfigFieldGuestNotAllowedActionGroup"> + <amOnPage url="{{AdminNewsletterConfigPage.url}}" stepKey="amOnNewsletterConfigPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <click selector="{{AdminNewsletterConfigPageSubscriptionOptionsSection.allowGuestSubscription}}" stepKey="allowEdit"/> + <selectOption selector="{{AdminNewsletterConfigPageSubscriptionOptionsSection.guestSubscription}}" userInput="No" stepKey="noGuestSubscription"/> + <click selector="{{AdminMainActionsSection.save}}" stepKey="saveNewsletterConfig"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Page/AdminNewsletterConfigPage.xml b/app/code/Magento/Newsletter/Test/Mftf/Page/AdminNewsletterConfigPage.xml new file mode 100644 index 0000000000000..00cbaa1656326 --- /dev/null +++ b/app/code/Magento/Newsletter/Test/Mftf/Page/AdminNewsletterConfigPage.xml @@ -0,0 +1,13 @@ +<?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="AdminNewsletterConfigPage" url="admin/system_config/edit/section/newsletter/" area="admin" module="Magento_Newsletter"> + <section name="AdminNewsletterConfigPageSubscriptionOptionsSection"/> + </page> +</pages> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Section/AdminNewsletterConfigPageSubscriptionOptionsSection.xml b/app/code/Magento/Newsletter/Test/Mftf/Section/AdminNewsletterConfigPageSubscriptionOptionsSection.xml new file mode 100644 index 0000000000000..26fdcf10ede02 --- /dev/null +++ b/app/code/Magento/Newsletter/Test/Mftf/Section/AdminNewsletterConfigPageSubscriptionOptionsSection.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="AdminNewsletterConfigPageSubscriptionOptionsSection"> + <element name="allowGuestSubscription" type="input" selector="#newsletter_subscription_allow_guest_subscribe_inherit"/> + <element name="guestSubscription" type="select" selector="#newsletter_subscription_allow_guest_subscribe" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Section/NewsletterTemplateSection.xml b/app/code/Magento/Newsletter/Test/Mftf/Section/NewsletterTemplateSection.xml index 4c8f641f78ae3..ebc1577fd5ece 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/Section/NewsletterTemplateSection.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/Section/NewsletterTemplateSection.xml @@ -7,6 +7,10 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="BasicFrontendNewsletterFormSection"> + <element name="newsletterEmail" type="input" selector="#newsletter"/> + <element name="subscribeButton" type="button" selector=".subscribe"/> + </section> <section name="BasicFieldNewsletterSection"> <element name="templateName" type="input" selector="#code"/> <element name="templateSubject" type="input" selector="#subject"/> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/VerifyRegistredLinkDisplayedForGuestSubscriptionNoTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/VerifyRegistredLinkDisplayedForGuestSubscriptionNoTest.xml new file mode 100644 index 0000000000000..17a2462622ec1 --- /dev/null +++ b/app/code/Magento/Newsletter/Test/Mftf/Test/VerifyRegistredLinkDisplayedForGuestSubscriptionNoTest.xml @@ -0,0 +1,25 @@ +<?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="VerifyRegistredLinkDisplayedForGuestSubscriptionNoTest"> + <before> + <!--Log in to Magento as admin.--> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Allow Guest Subscription NO--> + <actionGroup ref="AssertAdminNewsletterConfigFieldGuestNotAllowedActionGroup" stepKey="amOnNewsletterConfigField"/> + <!--Log out from Magento admin.--> + <actionGroup ref="logout" stepKey="logoutFromAdmin"/> + </before> + + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnStorefrontPage"/> + <fillField selector="{{BasicFrontendNewsletterFormSection.newsletterEmail}}" userInput="{{_defaultNewsletter.senderEmail}}" stepKey="fillTemplateEmail" /> + <click selector="{{BasicFrontendNewsletterFormSection.subscribeButton}}" stepKey="subscribe"/> + </test> +</tests> diff --git a/app/code/Magento/Newsletter/etc/frontend/di.xml b/app/code/Magento/Newsletter/etc/frontend/di.xml index eef123a3304e3..99e90faff4034 100644 --- a/app/code/Magento/Newsletter/etc/frontend/di.xml +++ b/app/code/Magento/Newsletter/etc/frontend/di.xml @@ -13,4 +13,16 @@ </argument> </arguments> </type> + <type name="Magento\Framework\View\Element\Message\MessageConfigurationsPool"> + <arguments> + <argument name="configurationsMap" xsi:type="array"> + <item name="localizedSubscriptionErrorMessage" xsi:type="array"> + <item name="renderer" xsi:type="const">\Magento\Framework\View\Element\Message\Renderer\BlockRenderer::CODE</item> + <item name="data" xsi:type="array"> + <item name="template" xsi:type="string">Magento_Newsletter::messages/localizedSubscriptionErrorMessage.phtml</item> + </item> + </item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Newsletter/view/frontend/templates/messages/localizedSubscriptionErrorMessage.phtml b/app/code/Magento/Newsletter/view/frontend/templates/messages/localizedSubscriptionErrorMessage.phtml new file mode 100644 index 0000000000000..46c35002e3995 --- /dev/null +++ b/app/code/Magento/Newsletter/view/frontend/templates/messages/localizedSubscriptionErrorMessage.phtml @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** @var \Magento\Framework\View\Element\Template $block */ +?> +<?= $block->escapeHtml(__($block->getData('message')), ['a']); ?> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderWithCatalogPriceTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderWithCatalogPriceTest.xml index 4c7c8b163a38c..dc74e024b2629 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderWithCatalogPriceTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderWithCatalogPriceTest.xml @@ -36,6 +36,8 @@ <updateData createDataKey="createGuestCart" entity="GuestOrderPaymentMethod" stepKey="sendGuestPaymentInformation"> <requiredEntity createDataKey="createGuestCart"/> </updateData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> <!--END Create order via API--> </before> <after> diff --git a/app/code/Magento/Store/Test/Mftf/Data/StoreConfigData.xml b/app/code/Magento/Store/Test/Mftf/Data/StoreConfigData.xml index 9f4de2ffb5723..6b95faafa5868 100644 --- a/app/code/Magento/Store/Test/Mftf/Data/StoreConfigData.xml +++ b/app/code/Magento/Store/Test/Mftf/Data/StoreConfigData.xml @@ -21,6 +21,18 @@ <data key="label">Yes</data> <data key="value">1</data> </entity> + <entity name="StorefrontSingleStoreModeEnabledConfigData"> + <data key="path">general/single_store_mode/enabled</data> + <data key="scope_id">0</data> + <data key="label">Yes</data> + <data key="value">1</data> + </entity> + <entity name="StorefrontSingleStoreModeDisabledConfigData"> + <data key="path">general/single_store_mode/enabled</data> + <data key="scope_id">0</data> + <data key="label">No</data> + <data key="value">0</data> + </entity> <entity name="MinifyJavaScriptFilesDisableConfigData"> <!-- Default value --> <data key="path">dev/js/minify_files</data> diff --git a/app/code/Magento/Theme/Controller/Result/JsFooterPlugin.php b/app/code/Magento/Theme/Controller/Result/JsFooterPlugin.php index 317ab39d307cd..6a80dec460660 100644 --- a/app/code/Magento/Theme/Controller/Result/JsFooterPlugin.php +++ b/app/code/Magento/Theme/Controller/Result/JsFooterPlugin.php @@ -41,7 +41,7 @@ public function beforeSendResponse(Http $subject) { $content = $subject->getContent(); $script = []; - if (strpos($content, '</body') !== false) { + if (is_string($content) && strpos($content, '</body') !== false) { if ($this->scopeConfig->isSetFlag( self::XML_PATH_DEV_MOVE_JS_TO_BOTTOM, ScopeInterface::SCOPE_STORE diff --git a/app/code/Magento/Ups/Model/Carrier.php b/app/code/Magento/Ups/Model/Carrier.php index 103ba9d3fb4b7..64da25a2a170b 100644 --- a/app/code/Magento/Ups/Model/Carrier.php +++ b/app/code/Magento/Ups/Model/Carrier.php @@ -47,7 +47,8 @@ use Zend_Http_Client; /** - * UPS shipping implementation + * UPS shipping implementation. + * * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -155,7 +156,9 @@ class Carrier extends AbstractCarrierOnline implements CarrierInterface * @inheritdoc */ protected $_debugReplacePrivateDataKeys = [ - 'UserId', 'Password', 'AccessLicenseNumber' + 'UserId', + 'Password', + 'AccessLicenseNumber', ]; /** @@ -372,9 +375,10 @@ public function setRequest(RateRequest $request) $destCountry = self::USA_COUNTRY_ID; } - //for UPS, puero rico state for US will assume as puerto rico country - if ($destCountry == self::USA_COUNTRY_ID && ($request->getDestPostcode() == '00912' || - $request->getDestRegionCode() == self::PUERTORICO_COUNTRY_ID) + //for UPS, puerto rico state for US will assume as puerto rico country + if ($destCountry == self::USA_COUNTRY_ID + && ($request->getDestPostcode() == '00912' + || $request->getDestRegionCode() == self::PUERTORICO_COUNTRY_ID) ) { $destCountry = self::PUERTORICO_COUNTRY_ID; } @@ -1322,20 +1326,28 @@ public function getResponse() } /** - * Get allowed shipping methods + * Get allowed shipping methods. * * @return array */ public function getAllowedMethods() { - $allowed = explode(',', $this->getConfigData('allowed_methods')); - $arr = []; - $isByCode = $this->getConfigData('type') == 'UPS_XML'; - foreach ($allowed as $code) { - $arr[$code] = $isByCode ? $this->getShipmentByCode($code) : $this->configHelper->getCode('method', $code); + $allowedMethods = explode(',', (string)$this->getConfigData('allowed_methods')); + $isUpsXml = $this->getConfigData('type') === 'UPS_XML'; + $origin = $this->getConfigData('origin_shipment'); + + $availableByTypeMethods = $isUpsXml + ? $this->configHelper->getCode('originShipment', $origin) + : $this->configHelper->getCode('method'); + + $methods = []; + foreach ($availableByTypeMethods as $methodCode => $methodData) { + if (in_array($methodCode, $allowedMethods)) { + $methods[$methodCode] = $methodData->getText(); + } } - return $arr; + return $methods; } /** @@ -1896,20 +1908,18 @@ public function getContainerTypes(DataObject $params = null) ]; } $containerTypes = $containerTypes + [ - '03' => __('UPS Tube'), - '04' => __('PAK'), - '2a' => __('Small Express Box'), - '2b' => __('Medium Express Box'), - '2c' => __('Large Express Box'), - ]; + '03' => __('UPS Tube'), + '04' => __('PAK'), + '2a' => __('Small Express Box'), + '2b' => __('Medium Express Box'), + '2c' => __('Large Express Box'), + ]; } return ['00' => __('Customer Packaging')] + $containerTypes; - } elseif ($countryShipper == self::USA_COUNTRY_ID && - $countryRecipient == self::PUERTORICO_COUNTRY_ID && - ($method == '03' || - $method == '02' || - $method == '01') + } elseif ($countryShipper == self::USA_COUNTRY_ID + && $countryRecipient == self::PUERTORICO_COUNTRY_ID + && in_array($method, ['01', '02', '03']) ) { // Container types should be the same as for domestic $params->setCountryRecipient(self::USA_COUNTRY_ID); diff --git a/app/code/Magento/Ups/Test/Unit/Model/CarrierTest.php b/app/code/Magento/Ups/Test/Unit/Model/CarrierTest.php index 0d33087a0f3bc..34dd4f0fe142b 100644 --- a/app/code/Magento/Ups/Test/Unit/Model/CarrierTest.php +++ b/app/code/Magento/Ups/Test/Unit/Model/CarrierTest.php @@ -3,15 +3,16 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Ups\Test\Unit\Model; use Magento\Directory\Model\Country; use Magento\Directory\Model\CountryFactory; use Magento\Framework\App\Config\ScopeConfigInterface; -use Magento\Framework\DataObject; use Magento\Framework\HTTP\ClientFactory; use Magento\Framework\HTTP\ClientInterface; use Magento\Framework\Model\AbstractModel; +use Magento\Framework\Phrase; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Quote\Model\Quote\Address\RateRequest; use Magento\Quote\Model\Quote\Address\RateResult\Error; @@ -20,11 +21,15 @@ use Magento\Shipping\Model\Rate\ResultFactory; use Magento\Shipping\Model\Simplexml\Element; use Magento\Shipping\Model\Simplexml\ElementFactory; +use Magento\Store\Model\ScopeInterface; +use Magento\Ups\Helper\Config; use Magento\Ups\Model\Carrier; use PHPUnit_Framework_MockObject_MockObject as MockObject; use Psr\Log\LoggerInterface; /** + * Unit tests for \Magento\Ups\Model\Carrier class. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class CarrierTest extends \PHPUnit\Framework\TestCase @@ -92,17 +97,23 @@ class CarrierTest extends \PHPUnit\Framework\TestCase */ private $logger; + /** + * @var Config|MockObject + */ + private $configHelper; + + /** + * @inheritdoc + */ protected function setUp() { $this->helper = new ObjectManager($this); $this->scope = $this->getMockBuilder(ScopeConfigInterface::class) ->disableOriginalConstructor() + ->setMethods(['getValue', 'isSetFlag']) ->getMock(); - $this->scope->method('getValue') - ->willReturnCallback([$this, 'scopeConfigGetValue']); - $this->error = $this->getMockBuilder(Error::class) ->setMethods(['setCarrier', 'setCarrierTitle', 'setErrorMessage']) ->getMock(); @@ -143,6 +154,11 @@ protected function setUp() $this->logger = $this->getMockForAbstractClass(LoggerInterface::class); + $this->configHelper = $this->getMockBuilder(Config::class) + ->disableOriginalConstructor() + ->setMethods(['getCode']) + ->getMock(); + $this->model = $this->helper->getObject( Carrier::class, [ @@ -153,6 +169,7 @@ protected function setUp() 'xmlElFactory' => $xmlFactory, 'logger' => $this->logger, 'httpClientFactory' => $httpClientFactory, + 'configHelper' => $this->configHelper, ] ); } @@ -189,14 +206,17 @@ public function scopeConfigGetValue(string $path) * @param bool $freeShippingEnabled * @param int $requestSubtotal * @param int $expectedPrice + * @return void */ public function testGetMethodPrice( - $cost, - $shippingMethod, - $freeShippingEnabled, - $requestSubtotal, - $expectedPrice - ) { + int $cost, + string $shippingMethod, + bool $freeShippingEnabled, + int $requestSubtotal, + int $expectedPrice + ): void { + $this->scope->method('getValue') + ->willReturnCallback([$this, 'scopeConfigGetValue']); $path = 'carriers/' . $this->model->getCarrierCode() . '/'; $this->scope->method('isSetFlag') ->with($path . 'free_shipping_enable') @@ -244,8 +264,13 @@ public function getMethodPriceProvider() ]; } - public function testCollectRatesErrorMessage() + /** + * @return void + */ + public function testCollectRatesErrorMessage(): void { + $this->scope->method('getValue') + ->willReturnCallback([$this, 'scopeConfigGetValue']); $this->scope->method('isSetFlag') ->willReturn(false); @@ -373,6 +398,87 @@ public function countryDataProvider() ]; } + /** + * @dataProvider allowedMethodsDataProvider + * @param string $carrierType + * @param string $methodType + * @param string $methodCode + * @param string $methodTitle + * @param string $allowedMethods + * @param array $expectedMethods + * @return void + */ + public function testGetAllowedMethods( + string $carrierType, + string $methodType, + string $methodCode, + string $methodTitle, + string $allowedMethods, + array $expectedMethods + ): void { + $this->scope->method('getValue') + ->willReturnMap( + [ + [ + 'carriers/ups/allowed_methods', + ScopeInterface::SCOPE_STORE, + null, + $allowedMethods, + ], + [ + 'carriers/ups/type', + ScopeInterface::SCOPE_STORE, + null, + $carrierType, + ], + [ + 'carriers/ups/origin_shipment', + ScopeInterface::SCOPE_STORE, + null, + 'Shipments Originating in United States', + ], + ] + ); + $this->configHelper->method('getCode') + ->with($methodType) + ->willReturn([$methodCode => new Phrase($methodTitle)]); + $actualMethods = $this->model->getAllowedMethods(); + $this->assertEquals($expectedMethods, $actualMethods); + } + + /** + * @return array + */ + public function allowedMethodsDataProvider(): array + { + return [ + [ + 'UPS', + 'method', + '1DM', + 'Next Day Air Early AM', + '', + [], + ], + [ + 'UPS', + 'method', + '1DM', + 'Next Day Air Early AM', + '1DM,1DML,1DA', + ['1DM' => 'Next Day Air Early AM'], + ], + [ + 'UPS_XML', + 'originShipment', + '01', + 'UPS Next Day Air', + '01,02,03', + ['01' => 'UPS Next Day Air'], + ], + ]; + } + /** * Creates mock for XML factory. * diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteProductURLRewriteEntityTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteProductURLRewriteEntityTest.xml new file mode 100644 index 0000000000000..2becb177ee72b --- /dev/null +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteProductURLRewriteEntityTest.xml @@ -0,0 +1,60 @@ +<?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="AdminDeleteProductURLRewriteEntityTest"> + <annotations> + <stories value="Delete Product UrlRewrite"/> + <title value="Delete created product URL rewrite"/> + <description value="Login as admin, create product with category and UrlRewrite, delete created URL rewrite"/> + <testCaseId value="MC-5347"/> + <severity value="MAJOR"/> + <group value="urlRewrite"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteProduct"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Filter and Select the created Product --> + <actionGroup ref="AdminSearchUrlRewriteProductBySkuActionGroup" stepKey="searchProduct"> + <argument name="productSku" value="$$createSimpleProduct.sku$$"/> + </actionGroup> + + <!-- Update the Store, RequestPath, RedirectType and Description --> + <actionGroup ref="AdminAddUrlRewriteForProductActionGroup" stepKey="addUrlRewrite"> + <argument name="storeValue" value="Default Store View"/> + <argument name="requestPath" value="{{FirstLevelSubCat.name_lwr}}/{{_defaultProduct.urlKey}}.html"/> + <argument name="redirectTypeValue" value="Temporary (302)"/> + <argument name="description" value="End To End Test"/> + </actionGroup> + + <!--Delete Created Rewrite, Assert Success Message --> + <actionGroup ref="AdminDeleteUrlRewriteActionGroup" stepKey="deleteCreatedRewrite"> + <argument name="requestPath" value="{{FirstLevelSubCat.name_lwr}}/{{_defaultProduct.urlKey}}.html"/> + </actionGroup> + <!-- Assert Deleted Rewrite is Not in Grid --> + <actionGroup ref="AdminSearchDeletedUrlRewriteActionGroup" stepKey="searchDeletedURLRewriteInGrid"> + <argument name="requestPath" value="{{FirstLevelSubCat.name_lwr}}/{{_defaultProduct.urlKey}}.html"/> + </actionGroup> + <!-- Assert Page By Url Rewrite is Not Found --> + <actionGroup ref="AssertPageByUrlRewriteIsNotFoundActionGroup" stepKey="amOnPage"> + <argument name="requestPath" value="{{FirstLevelSubCat.name_lwr}}/{{_defaultProduct.urlKey}}.html"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductAfterImportTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductAfterImportTest.xml index a3d3b897ef75d..88c28230b8347 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductAfterImportTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductAfterImportTest.xml @@ -16,6 +16,9 @@ <severity value="MAJOR"/> <testCaseId value="MC-20229"/> <group value="urlRewrite"/> + <skip> + <issueId value="MC-30409"/> + </skip> </annotations> <before> <comment userInput="Set the configuration for Generate category/product URL Rewrites" stepKey="commentSetURLRewriteConfiguration" /> diff --git a/app/code/Magento/User/Model/ResourceModel/User.php b/app/code/Magento/User/Model/ResourceModel/User.php index 4eaf6116056dd..d9bc555b8e391 100644 --- a/app/code/Magento/User/Model/ResourceModel/User.php +++ b/app/code/Magento/User/Model/ResourceModel/User.php @@ -13,6 +13,7 @@ use Magento\Authorization\Model\UserContextInterface; use Magento\Framework\Acl\Data\CacheInterface; use Magento\Framework\App\ObjectManager; +use Magento\Framework\Exception\LocalizedException; use Magento\User\Model\Backend\Config\ObserverConfig; use Magento\User\Model\User as ModelUser; @@ -249,14 +250,17 @@ protected function _afterLoad(\Magento\Framework\Model\AbstractModel $user) * * @param \Magento\Framework\Model\AbstractModel $user * @return bool - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ public function delete(\Magento\Framework\Model\AbstractModel $user) { + $uid = $user->getId(); + if (!$uid) { + return false; + } + $this->_beforeDelete($user); $connection = $this->getConnection(); - - $uid = $user->getId(); $connection->beginTransaction(); try { $connection->delete($this->getMainTable(), ['user_id = ?' => $uid]); @@ -264,12 +268,15 @@ public function delete(\Magento\Framework\Model\AbstractModel $user) $this->getTable('authorization_role'), ['user_id = ?' => $uid, 'user_type = ?' => UserContextInterface::USER_TYPE_ADMIN] ); - } catch (\Magento\Framework\Exception\LocalizedException $e) { + } catch (LocalizedException $e) { $connection->rollBack(); + return false; } + $connection->commit(); $this->_afterDelete($user); + return true; } diff --git a/app/code/Magento/Usps/etc/config.xml b/app/code/Magento/Usps/etc/config.xml index 68c1b31fcd515..7bfe3dc86a06e 100644 --- a/app/code/Magento/Usps/etc/config.xml +++ b/app/code/Magento/Usps/etc/config.xml @@ -11,7 +11,7 @@ <usps> <active>0</active> <sallowspecific>0</sallowspecific> - <allowed_methods>0_FCLE,0_FCL,0_FCP,1,2,3,4,6,7,13,16,17,22,23,25,27,28,33,34,35,36,37,42,43,53,55,56,57,61,INT_1,INT_2,INT_4,INT_6,INT_7,INT_8,INT_9,INT_10,INT_11,INT_12,INT_13,INT_14,INT_15,INT_16,INT_20,INT_26</allowed_methods> + <allowed_methods>0_FCLE,0_FCL,0_FCP,1,2,3,4,6,7,13,16,17,22,23,25,27,28,33,34,35,36,37,42,43,53,57,61,INT_1,INT_2,INT_4,INT_6,INT_7,INT_8,INT_9,INT_10,INT_11,INT_12,INT_13,INT_14,INT_15,INT_16,INT_20</allowed_methods> <container>VARIABLE</container> <cutoff_cost /> <free_method /> diff --git a/app/code/Magento/Vault/view/frontend/web/js/view/payment/method-renderer/vault.js b/app/code/Magento/Vault/view/frontend/web/js/view/payment/method-renderer/vault.js index 732bd4961f4fc..9a2e75f6c07d1 100644 --- a/app/code/Magento/Vault/view/frontend/web/js/view/payment/method-renderer/vault.js +++ b/app/code/Magento/Vault/view/frontend/web/js/view/payment/method-renderer/vault.js @@ -104,6 +104,24 @@ define( : false; }, + /** + * Return state of place order button. + * + * @return {Boolean} + */ + isButtonActive: function () { + return this.isActive() && this.isPlaceOrderActionAllowed(); + }, + + /** + * Check if payment is active. + * + * @return {Boolean} + */ + isActive: function () { + return this.isChecked() === this.getId(); + }, + /** * @returns {*} */ diff --git a/app/code/Magento/Vault/view/frontend/web/template/payment/form.html b/app/code/Magento/Vault/view/frontend/web/template/payment/form.html index 5f32281686a65..98c0fbfb00177 100644 --- a/app/code/Magento/Vault/view/frontend/web/template/payment/form.html +++ b/app/code/Magento/Vault/view/frontend/web/template/payment/form.html @@ -49,7 +49,10 @@ type="submit" data-bind=" click: placeOrder, - attr: {title: $t('Place Order')}"> + attr: {title: $t('Place Order')}, + enable: isButtonActive() + " + disabled> <span translate="'Place Order'"></span> </button> </div> diff --git a/dev/tests/functional/tests/app/Magento/Cms/Test/TestCase/CreateCmsPageEntityTest.xml b/dev/tests/functional/tests/app/Magento/Cms/Test/TestCase/CreateCmsPageEntityTest.xml index 88434bbc009ce..bf477e1215486 100644 --- a/dev/tests/functional/tests/app/Magento/Cms/Test/TestCase/CreateCmsPageEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Cms/Test/TestCase/CreateCmsPageEntityTest.xml @@ -8,7 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Cms\Test\TestCase\CreateCmsPageEntityTest" summary="Create Cms Page" ticketId="MAGETWO-25580"> <variation name="CreateCmsPageEntityTestVariation1" summary="Create CMS Content Page" ticketId="MAGETWO-12399"> - <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test, severity:S1</data> + <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test, severity:S1, mftf_migrated:yes</data> <data name="fixtureType" xsi:type="string">cmsPage</data> <data name="data/is_active" xsi:type="string">Yes</data> <data name="data/title" xsi:type="string">NewCmsPage%isolation%</data> @@ -20,7 +20,7 @@ <constraint name="Magento\Cms\Test\Constraint\AssertCmsPagePreview" /> </variation> <variation name="CreateCmsPageEntityTestVariation2" summary="Create page for default store view"> - <data name="tag" xsi:type="string">severity:S1</data> + <data name="tag" xsi:type="string">severity:S1, mftf_migrated:yes</data> <data name="fixtureType" xsi:type="string">cmsPage</data> <data name="data/is_active" xsi:type="string">Yes</data> <data name="data/title" xsi:type="string">NewCmsPage%isolation%</data> @@ -31,7 +31,7 @@ <constraint name="Magento\Cms\Test\Constraint\AssertCmsPageForm" /> </variation> <variation name="CreateCmsPageEntityTestVariation4" summary="Create disabled page"> - <data name="tag" xsi:type="string">severity:S3</data> + <data name="tag" xsi:type="string">severity:S3, mftf_migrated:yes</data> <data name="fixtureType" xsi:type="string">cmsPage</data> <data name="data/title" xsi:type="string">NewCmsPage%isolation%</data> <data name="data/identifier" xsi:type="string">identifier-%isolation%</data> @@ -42,7 +42,7 @@ <constraint name="Magento\Cms\Test\Constraint\AssertCmsPageDisabledOnFrontend" /> </variation> <variation name="CreateCmsPageEntityTestVariation5" summary="Block Cache Exploit" ticketId="MAGETWO-48017"> - <data name="tag" xsi:type="string">severity:S2</data> + <data name="tag" xsi:type="string">severity:S2, mftf_migrated:yes</data> <data name="fixtureType" xsi:type="string">cmsPage</data> <data name="data/title" xsi:type="string">NewCmsPage%isolation%</data> <data name="data/identifier" xsi:type="string">identifier-%isolation%</data> @@ -55,7 +55,7 @@ <constraint name="Magento\Cms\Test\Constraint\AssertCmsPageOnFrontend" /> </variation> <variation name="CreateCmsPageEntityTestVariation6" summary="Create CMS page with single store mode" ticketId="MAGETWO-59654"> - <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test</data> + <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test, mftf_migrated:yes</data> <data name="configData" xsi:type="string">enable_single_store_mode</data> <data name="fixtureType" xsi:type="string">cmsPage</data> <data name="data/is_active" xsi:type="string">Yes</data> diff --git a/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/CreateIntegrationWithDuplicatedNameTest.xml b/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/CreateIntegrationWithDuplicatedNameTest.xml index 0a3fff5b3e510..2cd14d9de05a1 100644 --- a/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/CreateIntegrationWithDuplicatedNameTest.xml +++ b/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/CreateIntegrationWithDuplicatedNameTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Integration\Test\TestCase\CreateIntegrationWithDuplicatedNameTest" summary="Create Integration with existing name" ticketId="MAGETWO-16756"> <variation name="CreateIntegrationWithDuplicatedNameTestVariation1"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="integration/dataset" xsi:type="string">default</data> <constraint name="Magento\Integration\Test\Constraint\AssertIntegrationNameDuplicationErrorMessage" /> </variation> diff --git a/dev/tests/functional/tests/app/Magento/UrlRewrite/Test/TestCase/DeleteProductUrlRewriteEntityTest.xml b/dev/tests/functional/tests/app/Magento/UrlRewrite/Test/TestCase/DeleteProductUrlRewriteEntityTest.xml index 7a98f463072cb..7e8f8729716b0 100644 --- a/dev/tests/functional/tests/app/Magento/UrlRewrite/Test/TestCase/DeleteProductUrlRewriteEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/UrlRewrite/Test/TestCase/DeleteProductUrlRewriteEntityTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\UrlRewrite\Test\TestCase\DeleteProductUrlRewriteEntityTest" summary="Delete Product URL Rewrites" ticketId="MAGETWO-23287"> <variation name="DeleteProductUrlRewriteEntityTestVariation1"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="productRedirect/dataset" xsi:type="string">default_without_target</data> <data name="productRedirect/data/target_path/entity" xsi:type="string">product/%catalogProductSimple::product_100_dollar%</data> <constraint name="Magento\UrlRewrite\Test\Constraint\AssertUrlRewriteDeletedMessage" /> diff --git a/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/DateGroupDataProvider.php b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/DateGroupDataProvider.php new file mode 100644 index 0000000000000..7f9d5362c4f83 --- /dev/null +++ b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/DateGroupDataProvider.php @@ -0,0 +1,210 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\Catalog\Block\Product\View\Options; + +use Magento\Catalog\Api\Data\ProductCustomOptionInterface; +use Magento\Catalog\Model\Product\Option; + +/** + * Data provider with product custom options from date group(date, date & time, time). + */ +class DateGroupDataProvider +{ + /** + * Return options data. + * + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * + * @return array + */ + public function getData(): array + { + return [ + 'type_date_required' => [ + [ + Option::KEY_TITLE => 'Test option date title 1', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_DATE, + Option::KEY_IS_REQUIRE => 1, + Option::KEY_PRICE => 10, + Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_SKU => 'test-option-date-title-1', + ], + [ + 'block_with_required_class' => '<div class="field date required"', + 'title' => '<span>Test option date title 1</span>', + 'price' => 'data-price-amount="10"', + ], + ], + 'type_date_not_required' => [ + [ + Option::KEY_TITLE => 'Test option date title 2', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_DATE, + Option::KEY_IS_REQUIRE => 0, + Option::KEY_PRICE => 10, + Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_SKU => 'test-option-date-title-2', + ], + [ + 'block_with_required_class' => '<div class="field date"', + 'title' => '<span>Test option date title 2</span>', + 'price' => 'data-price-amount="10"', + ], + ], + 'type_date_fixed_price' => [ + [ + Option::KEY_TITLE => 'Test option date title 3', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_DATE, + Option::KEY_IS_REQUIRE => 0, + Option::KEY_PRICE => 50, + Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_SKU => 'test-option-date-title-3', + ], + [ + 'block_with_required_class' => '<div class="field date"', + 'title' => '<span>Test option date title 3</span>', + 'price' => 'data-price-amount="50"', + ], + ], + 'type_date_percent_price' => [ + [ + Option::KEY_TITLE => 'Test option date title 4', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_DATE, + Option::KEY_IS_REQUIRE => 0, + Option::KEY_PRICE => 50, + Option::KEY_PRICE_TYPE => 'percent', + Option::KEY_SKU => 'test-option-date-title-4', + ], + [ + 'block_with_required_class' => '<div class="field date"', + 'title' => '<span>Test option date title 4</span>', + 'price' => 'data-price-amount="5"', + ], + ], + 'type_date_and_time_required' => [ + [ + Option::KEY_TITLE => 'Test option date and time title 1', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_DATE_TIME, + Option::KEY_IS_REQUIRE => 1, + Option::KEY_PRICE => 10, + Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_SKU => 'test-option-date-and-time-title-1', + ], + [ + 'block_with_required_class' => '<div class="field date required"', + 'title' => '<span>Test option date and time title 1</span>', + 'price' => 'data-price-amount="10"', + ], + ], + 'type_date_and_time_not_required' => [ + [ + Option::KEY_TITLE => 'Test option date and time title 2', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_DATE_TIME, + Option::KEY_IS_REQUIRE => 0, + Option::KEY_PRICE => 10, + Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_SKU => 'test-option-date-and-time-title-2', + ], + [ + 'block_with_required_class' => '<div class="field date"', + 'title' => '<span>Test option date and time title 2</span>', + 'price' => 'data-price-amount="10"', + ], + ], + 'type_date_and_time_fixed_price' => [ + [ + Option::KEY_TITLE => 'Test option date and time title 3', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_DATE_TIME, + Option::KEY_IS_REQUIRE => 0, + Option::KEY_PRICE => 50, + Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_SKU => 'test-option-date-and-time-title-3', + ], + [ + 'block_with_required_class' => '<div class="field date"', + 'title' => '<span>Test option date and time title 3</span>', + 'price' => 'data-price-amount="50"', + ], + ], + 'type_date_and_time_percent_price' => [ + [ + Option::KEY_TITLE => 'Test option date and time title 4', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_DATE_TIME, + Option::KEY_IS_REQUIRE => 0, + Option::KEY_PRICE => 50, + Option::KEY_PRICE_TYPE => 'percent', + Option::KEY_SKU => 'test-option-date-and-time-title-4', + ], + [ + 'block_with_required_class' => '<div class="field date"', + 'title' => '<span>Test option date and time title 4</span>', + 'price' => 'data-price-amount="5"', + ], + ], + 'type_time_required' => [ + [ + Option::KEY_TITLE => 'Test option time title 1', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_TIME, + Option::KEY_IS_REQUIRE => 1, + Option::KEY_PRICE => 10, + Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_SKU => 'test-option-time-title-1', + ], + [ + 'block_with_required_class' => '<div class="field date required"', + 'title' => '<span>Test option time title 1</span>', + 'price' => 'data-price-amount="10"', + ], + ], + 'type_time_not_required' => [ + [ + Option::KEY_TITLE => 'Test option time title 2', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_TIME, + Option::KEY_IS_REQUIRE => 0, + Option::KEY_PRICE => 10, + Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_SKU => 'test-option-time-title-2', + ], + [ + 'block_with_required_class' => '<div class="field date"', + 'title' => '<span>Test option time title 2</span>', + 'price' => 'data-price-amount="10"', + ], + ], + 'type_time_fixed_price' => [ + [ + Option::KEY_TITLE => 'Test option time title 3', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_TIME, + Option::KEY_IS_REQUIRE => 0, + Option::KEY_PRICE => 50, + Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_SKU => 'test-option-time-title-3', + ], + [ + 'block_with_required_class' => '<div class="field date"', + 'title' => '<span>Test option time title 3</span>', + 'price' => 'data-price-amount="50"', + ], + ], + 'type_time_percent_price' => [ + [ + Option::KEY_TITLE => 'Test option time title 4', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_TIME, + Option::KEY_IS_REQUIRE => 0, + Option::KEY_PRICE => 50, + Option::KEY_PRICE_TYPE => 'percent', + Option::KEY_SKU => 'test-option-time-title-4', + ], + [ + 'block_with_required_class' => '<div class="field date"', + 'title' => '<span>Test option time title 4</span>', + 'price' => 'data-price-amount="5"', + ], + ], + ]; + } +} diff --git a/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/FileGroupDataProvider.php b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/FileGroupDataProvider.php new file mode 100644 index 0000000000000..c28cb770a806e --- /dev/null +++ b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/FileGroupDataProvider.php @@ -0,0 +1,134 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\Catalog\Block\Product\View\Options; + +use Magento\Catalog\Api\Data\ProductCustomOptionInterface; +use Magento\Catalog\Model\Product\Option; + +/** + * Data provider with product custom options from file group(file). + */ +class FileGroupDataProvider +{ + /** + * Return options data. + * + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * + * @return array + */ + public function getData(): array + { + return [ + 'type_file_required' => [ + [ + Option::KEY_TITLE => 'Test option file title 1', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_FILE, + Option::KEY_IS_REQUIRE => 1, + Option::KEY_PRICE => 10, + Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_SKU => 'test-option-file-title-1', + Option::KEY_SORT_ORDER => 1, + Option::KEY_FILE_EXTENSION => 'png, jpg', + ], + [ + 'block_with_required_class' => '<div class="field file required">', + 'label_for_created_option' => '<label class="label" for="options_%s_file"', + 'title' => '<span>Test option file title 1</span>', + 'price' => 'data-price-amount="10"', + 'required_element' => '/<input type="file"/', + 'file_extension' => '<strong>png, jpg</strong>', + ], + ], + 'type_file_not_required' => [ + [ + Option::KEY_TITLE => 'Test option file title 2', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_FILE, + Option::KEY_IS_REQUIRE => 0, + Option::KEY_PRICE => 10, + Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_SKU => 'test-option-file-title-2', + Option::KEY_SORT_ORDER => 1, + Option::KEY_FILE_EXTENSION => 'png, jpg', + ], + [ + 'block_with_required_class' => '<div class="field file">', + 'label_for_created_option' => '<label class="label" for="options_%s_file"', + 'title' => '<span>Test option file title 2</span>', + 'price' => 'data-price-amount="10"', + 'required_element' => '/<input type="file"/', + 'file_extension' => '<strong>png, jpg</strong>', + ], + ], + 'type_file_fixed_price' => [ + [ + Option::KEY_TITLE => 'Test option file title 3', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_FILE, + Option::KEY_IS_REQUIRE => 0, + Option::KEY_PRICE => 50, + Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_SKU => 'test-option-file-title-3', + Option::KEY_SORT_ORDER => 1, + Option::KEY_FILE_EXTENSION => 'png, jpg', + ], + [ + 'block_with_required_class' => '<div class="field file">', + 'label_for_created_option' => '<label class="label" for="options_%s_file"', + 'title' => '<span>Test option file title 3</span>', + 'price' => 'data-price-amount="50"', + 'required_element' => '/<input type="file"/', + 'file_extension' => '<strong>png, jpg</strong>', + ], + ], + 'type_file_percent_price' => [ + [ + Option::KEY_TITLE => 'Test option file title 4', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_FILE, + Option::KEY_IS_REQUIRE => 0, + Option::KEY_PRICE => 50, + Option::KEY_PRICE_TYPE => 'percent', + Option::KEY_SKU => 'test-option-file-title-4', + Option::KEY_SORT_ORDER => 1, + Option::KEY_FILE_EXTENSION => 'png, jpg', + ], + [ + 'block_with_required_class' => '<div class="field file">', + 'label_for_created_option' => '<label class="label" for="options_%s_file"', + 'title' => '<span>Test option file title 4</span>', + 'price' => 'data-price-amount="5"', + 'required_element' => '/<input type="file"/', + 'file_extension' => '<strong>png, jpg</strong>', + ], + ], + 'type_file_with_width_and_height' => [ + [ + Option::KEY_TITLE => 'Test option file title 5', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_FILE, + Option::KEY_IS_REQUIRE => 0, + Option::KEY_PRICE => 50, + Option::KEY_PRICE_TYPE => 'percent', + Option::KEY_SKU => 'test-option-file-title-5', + Option::KEY_SORT_ORDER => 1, + Option::KEY_FILE_EXTENSION => 'png, jpg', + Option::KEY_IMAGE_SIZE_X => 10, + Option::KEY_IMAGE_SIZE_Y => 81, + ], + [ + 'block_with_required_class' => '<div class="field file">', + 'label_for_created_option' => '<label class="label" for="options_%s_file"', + 'title' => '<span>Test option file title 5</span>', + 'price' => 'data-price-amount="5"', + 'required_element' => '/<input type="file"/', + 'file_extension' => '<strong>png, jpg</strong>', + 'file_width' => '/%s:.*<strong>10 px.<\/strong>/', + 'file_height' => '/%s:.*<strong>81 px.<\/strong>/', + ], + ], + ]; + } +} diff --git a/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/SelectGroupDataProvider.php b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/SelectGroupDataProvider.php new file mode 100644 index 0000000000000..2a13c1cd45466 --- /dev/null +++ b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/SelectGroupDataProvider.php @@ -0,0 +1,363 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\Catalog\Block\Product\View\Options; + +use Magento\Catalog\Api\Data\ProductCustomOptionInterface; +use Magento\Catalog\Model\Product\Option; +use Magento\Catalog\Model\Product\Option\Value; + +/** + * Data provider with product custom options from select group(drop-down, radio buttons, checkbox, multiple select). + */ +class SelectGroupDataProvider +{ + /** + * Return options data. + * + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * + * @return array + */ + public function getData(): array + { + return [ + 'type_drop_down_required' => [ + [ + Option::KEY_TITLE => 'Test option drop-down title 1', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_DROP_DOWN, + Option::KEY_IS_REQUIRE => 1, + ], + [ + Value::KEY_TITLE => 'Test option drop-down title 1 value 1', + Value::KEY_PRICE => 10, + Value::KEY_PRICE_TYPE => 'fixed', + Value::KEY_SKU => 'test-option-drop-down-title-1-value-1', + ], + [ + 'block_with_required_class' => '<div class="field required">', + 'label_for_created_option' => '<label class="label" for="select_%s">', + 'title' => '<span>Test option drop-down title 1</span>', + 'required_element' => '/<select/', + 'option_value_item' => '/<option value="%s" price="10" >%s \+\s{11}\$10.00.*/', + 'not_contain_arr' => [ + '/<select.*multiple="multiple"/', + ], + ], + ], + 'type_drop_down_not_required' => [ + [ + Option::KEY_TITLE => 'Test option drop-down title 2', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_DROP_DOWN, + Option::KEY_IS_REQUIRE => 0, + ], + [ + Value::KEY_TITLE => 'Test option drop-down title 2 value 1', + Value::KEY_PRICE => 10, + Value::KEY_PRICE_TYPE => 'fixed', + Value::KEY_SKU => 'test-option-drop-down-title-2-value-1', + ], + [ + 'block_with_required_class' => '<div class="field">', + 'label_for_created_option' => '<label class="label" for="select_%s">', + 'title' => '<span>Test option drop-down title 2</span>', + 'required_element' => '/<select/', + 'option_value_item' => '/<option value="%s" price="10" >%s \+\s{11}\$10.00.*/', + 'not_contain_arr' => [ + '/<select.*multiple="multiple"/', + ], + ], + ], + 'type_drop_down_value_fixed_price' => [ + [ + Option::KEY_TITLE => 'Test option drop-down title 3', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_DROP_DOWN, + Option::KEY_IS_REQUIRE => 0, + ], + [ + Value::KEY_TITLE => 'Test option drop-down title 3 value 1', + Value::KEY_PRICE => 50, + Value::KEY_PRICE_TYPE => 'fixed', + Value::KEY_SKU => 'test-option-drop-down-title-3-value-1', + ], + [ + 'block_with_required_class' => '<div class="field">', + 'label_for_created_option' => '<label class="label" for="select_%s">', + 'title' => '<span>Test option drop-down title 3</span>', + 'required_element' => '/<select/', + 'option_value_item' => '/<option value="%s" price="50" >%s \+\s{11}\$50.00.*/', + 'not_contain_arr' => [ + '/<select.*multiple="multiple"/', + ], + ], + ], + 'type_drop_down_value_percent_price' => [ + [ + Option::KEY_TITLE => 'Test option drop-down title 4', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_DROP_DOWN, + Option::KEY_IS_REQUIRE => 0, + ], + [ + Value::KEY_TITLE => 'Test option drop-down title 4 value 1', + Value::KEY_PRICE => 50, + Value::KEY_PRICE_TYPE => 'percent', + Value::KEY_SKU => 'test-option-drop-down-title-4-value-1', + ], + [ + 'block_with_required_class' => '<div class="field">', + 'label_for_created_option' => '<label class="label" for="select_%s">', + 'title' => '<span>Test option drop-down title 4</span>', + 'required_element' => '/<select/', + 'option_value_item' => '/<option value="%s" price="5" >%s \+\s{11}\$5.00.*/', + 'not_contain_arr' => [ + '/<select.*multiple="multiple"/', + ], + ], + ], + 'type_radio_button_required' => [ + [ + Option::KEY_TITLE => 'Test option radio-button title 1', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_RADIO, + Option::KEY_IS_REQUIRE => 1, + ], + [ + Value::KEY_TITLE => 'Test option radio-button title 1 value 1', + Value::KEY_PRICE => 10, + Value::KEY_PRICE_TYPE => 'fixed', + Value::KEY_SKU => 'test-option-radio-button-title-1-value-1', + ], + [ + 'block_with_required_class' => '<div class="field required">', + 'label_for_created_option' => '<label class="label" for="select_%s">', + 'title' => '<span>Test option radio-button title 1</span>', + 'required_element' => '/<input type="radio"/', + 'price' => 'data-price-amount="10"', + ], + ], + 'type_radio_button_not_required' => [ + [ + Option::KEY_TITLE => 'Test option radio-button title 2', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_RADIO, + Option::KEY_IS_REQUIRE => 0, + ], + [ + Value::KEY_TITLE => 'Test option radio-button title 2 value 1', + Value::KEY_PRICE => 10, + Value::KEY_PRICE_TYPE => 'fixed', + Value::KEY_SKU => 'test-option-radio-button-title-2-value-1', + ], + [ + 'block_with_required_class' => '<div class="field">', + 'label_for_created_option' => '<label class="label" for="select_%s">', + 'title' => '<span>Test option radio-button title 2</span>', + 'required_element' => '/<input type="radio"/', + 'price' => 'data-price-amount="10"', + ], + ], + 'type_radio_button_value_fixed_price' => [ + [ + Option::KEY_TITLE => 'Test option radio-button title 3', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_RADIO, + Option::KEY_IS_REQUIRE => 0, + ], + [ + Value::KEY_TITLE => 'Test option radio-button title 3 value 1', + Value::KEY_PRICE => 50, + Value::KEY_PRICE_TYPE => 'fixed', + Value::KEY_SKU => 'test-option-radio-button-title-3-value-1', + ], + [ + 'block_with_required_class' => '<div class="field">', + 'label_for_created_option' => '<label class="label" for="select_%s">', + 'title' => '<span>Test option radio-button title 3</span>', + 'required_element' => '/<input type="radio"/', + 'price' => 'data-price-amount="50"', + ], + ], + 'type_radio_button_value_percent_price' => [ + [ + Option::KEY_TITLE => 'Test option radio-button title 4', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_RADIO, + Option::KEY_IS_REQUIRE => 0, + ], + [ + Value::KEY_TITLE => 'Test option radio-button title 4 value 1', + Value::KEY_PRICE => 50, + Value::KEY_PRICE_TYPE => 'percent', + Value::KEY_SKU => 'test-option-radio-button-title-4-value-1', + ], + [ + 'block_with_required_class' => '<div class="field">', + 'label_for_created_option' => '<label class="label" for="select_%s">', + 'title' => '<span>Test option radio-button title 4</span>', + 'required_element' => '/<input type="radio"/', + 'price' => 'data-price-amount="5"', + ], + ], + 'type_checkbox_required' => [ + [ + Option::KEY_TITLE => 'Test option checkbox title 1', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_CHECKBOX, + Option::KEY_IS_REQUIRE => 1, + ], + [ + Value::KEY_TITLE => 'Test option checkbox title 1 value 1', + Value::KEY_PRICE => 10, + Value::KEY_PRICE_TYPE => 'fixed', + Value::KEY_SKU => 'test-option-checkbox-title-1-value-1', + ], + [ + 'block_with_required_class' => '<div class="field required">', + 'label_for_created_option' => '<label class="label" for="select_%s">', + 'title' => '<span>Test option checkbox title 1</span>', + 'required_element' => '/<input type="checkbox"/', + 'price' => 'data-price-amount="10"', + ], + ], + 'type_checkbox_not_required' => [ + [ + Option::KEY_TITLE => 'Test option checkbox title 2', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_CHECKBOX, + Option::KEY_IS_REQUIRE => 0, + ], + [ + Value::KEY_TITLE => 'Test option checkbox title 2 value 1', + Value::KEY_PRICE => 10, + Value::KEY_PRICE_TYPE => 'fixed', + Value::KEY_SKU => 'test-option-checkbox-title-2-value-1', + ], + [ + 'block_with_required_class' => '<div class="field">', + 'label_for_created_option' => '<label class="label" for="select_%s">', + 'title' => '<span>Test option checkbox title 2</span>', + 'required_element' => '/<input type="checkbox"/', + 'price' => 'data-price-amount="10"', + ], + ], + 'type_checkbox_value_fixed_price' => [ + [ + Option::KEY_TITLE => 'Test option checkbox title 3', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_CHECKBOX, + Option::KEY_IS_REQUIRE => 0, + ], + [ + Value::KEY_TITLE => 'Test option checkbox title 3 value 1', + Value::KEY_PRICE => 50, + Value::KEY_PRICE_TYPE => 'fixed', + Value::KEY_SKU => 'test-option-checkbox-title-3-value-1', + ], + [ + 'block_with_required_class' => '<div class="field">', + 'label_for_created_option' => '<label class="label" for="select_%s">', + 'title' => '<span>Test option checkbox title 3</span>', + 'required_element' => '/<input type="checkbox"/', + 'price' => 'data-price-amount="50"', + ], + ], + 'type_checkbox_value_percent_price' => [ + [ + Option::KEY_TITLE => 'Test option checkbox title 4', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_CHECKBOX, + Option::KEY_IS_REQUIRE => 0, + ], + [ + Value::KEY_TITLE => 'Test option checkbox title 4 value 1', + Value::KEY_PRICE => 50, + Value::KEY_PRICE_TYPE => 'percent', + Value::KEY_SKU => 'test-option-checkbox-title-4-value-1', + ], + [ + 'block_with_required_class' => '<div class="field">', + 'label_for_created_option' => '<label class="label" for="select_%s">', + 'title' => '<span>Test option checkbox title 4</span>', + 'required_element' => '/<input type="checkbox"/', + 'price' => 'data-price-amount="5"', + ], + ], + 'type_multiselect_required' => [ + [ + Option::KEY_TITLE => 'Test option multiselect title 1', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_MULTIPLE, + Option::KEY_IS_REQUIRE => 1, + ], + [ + Value::KEY_TITLE => 'Test option multiselect title 1 value 1', + Value::KEY_PRICE => 10, + Value::KEY_PRICE_TYPE => 'fixed', + Value::KEY_SKU => 'test-option-multiselect-title-1-value-1', + ], + [ + 'block_with_required_class' => '<div class="field required">', + 'label_for_created_option' => '<label class="label" for="select_%s">', + 'title' => '<span>Test option multiselect title 1</span>', + 'required_element' => '/<select.*multiple="multiple"/', + 'option_value_item' => '/<option value="%s" price="10" >%s \+\s{11}\$10.00.*/', + ], + ], + 'type_multiselect_not_required' => [ + [ + Option::KEY_TITLE => 'Test option multiselect title 2', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_MULTIPLE, + Option::KEY_IS_REQUIRE => 0, + ], + [ + Value::KEY_TITLE => 'Test option multiselect title 2 value 1', + Value::KEY_PRICE => 10, + Value::KEY_PRICE_TYPE => 'fixed', + Value::KEY_SKU => 'test-option-multiselect-title-2-value-1', + ], + [ + 'block_with_required_class' => '<div class="field">', + 'label_for_created_option' => '<label class="label" for="select_%s">', + 'title' => '<span>Test option multiselect title 2</span>', + 'required_element' => '/<select.*multiple="multiple"/', + 'option_value_item' => '/<option value="%s" price="10" >%s \+\s{11}\$10.00.*/', + ], + ], + 'type_multiselect_value_fixed_price' => [ + [ + Option::KEY_TITLE => 'Test option multiselect title 3', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_MULTIPLE, + Option::KEY_IS_REQUIRE => 0, + ], + [ + Value::KEY_TITLE => 'Test option multiselect title 3 value 1', + Value::KEY_PRICE => 50, + Value::KEY_PRICE_TYPE => 'fixed', + Value::KEY_SKU => 'test-option-multiselect-title-3-value-1', + ], + [ + 'block_with_required_class' => '<div class="field">', + 'label_for_created_option' => '<label class="label" for="select_%s">', + 'title' => '<span>Test option multiselect title 3</span>', + 'required_element' => '/<select.*multiple="multiple"/', + 'option_value_item' => '/<option value="%s" price="50" >%s \+\s{11}\$50.00.*/', + ], + ], + 'type_multiselect_value_percent_price' => [ + [ + Option::KEY_TITLE => 'Test option multiselect title 4', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_MULTIPLE, + Option::KEY_IS_REQUIRE => 0, + ], + [ + Value::KEY_TITLE => 'Test option multiselect title 4 value 1', + Value::KEY_PRICE => 50, + Value::KEY_PRICE_TYPE => 'percent', + Value::KEY_SKU => 'test-option-multiselect-title-4-value-1', + ], + [ + 'block_with_required_class' => '<div class="field">', + 'label_for_created_option' => '<label class="label" for="select_%s">', + 'title' => '<span>Test option multiselect title 4</span>', + 'required_element' => '/<select.*multiple="multiple"/', + 'option_value_item' => '/<option value="%s" price="5" >%s \+\s{11}\$5.00.*/', + ], + ], + ]; + } +} diff --git a/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/TextGroupDataProvider.php b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/TextGroupDataProvider.php new file mode 100644 index 0000000000000..75a6da0593d73 --- /dev/null +++ b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/TextGroupDataProvider.php @@ -0,0 +1,212 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\Catalog\Block\Product\View\Options; + +use Magento\Catalog\Api\Data\ProductCustomOptionInterface; +use Magento\Catalog\Model\Product\Option; + +/** + * Data provider with product custom options from text group(field, area). + */ +class TextGroupDataProvider +{ + /** + * Return options data. + * + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * + * @return array + */ + public function getData(): array + { + return [ + 'type_field_required' => [ + [ + Option::KEY_TITLE => 'Test option field title 1', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_FIELD, + Option::KEY_IS_REQUIRE => 1, + Option::KEY_PRICE => 10, + Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_SKU => 'test-option-field-title-1', + Option::KEY_MAX_CHARACTERS => 0, + ], + [ + 'block_with_required_class' => '<div class="field required">', + 'label_for_created_option' => '<label class="label" for="options_%s_text">', + 'title' => '<span>Test option field title 1</span>', + 'price' => 'data-price-amount="10"', + 'required_element' => '/<input type="text"/', + ], + ], + 'type_field_not_required' => [ + [ + Option::KEY_TITLE => 'Test option field title 2', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_FIELD, + Option::KEY_IS_REQUIRE => 0, + Option::KEY_PRICE => 10, + Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_SKU => 'test-option-field-title-2', + Option::KEY_MAX_CHARACTERS => 0, + ], + [ + 'block_with_required_class' => '<div class="field">', + 'label_for_created_option' => '<label class="label" for="options_%s_text">', + 'title' => '<span>Test option field title 2</span>', + 'price' => 'data-price-amount="10"', + 'required_element' => '/<input type="text"/', + ], + ], + 'type_field_fixed_price' => [ + [ + Option::KEY_TITLE => 'Test option field title 3', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_FIELD, + Option::KEY_IS_REQUIRE => 0, + Option::KEY_PRICE => 50, + Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_SKU => 'test-option-field-title-3', + Option::KEY_MAX_CHARACTERS => 0, + ], + [ + 'block_with_required_class' => '<div class="field">', + 'label_for_created_option' => '<label class="label" for="options_%s_text">', + 'title' => '<span>Test option field title 3</span>', + 'price' => 'data-price-amount="50"', + 'required_element' => '/<input type="text"/', + ], + ], + 'type_field_percent_price' => [ + [ + Option::KEY_TITLE => 'Test option field title 4', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_FIELD, + Option::KEY_IS_REQUIRE => 0, + Option::KEY_PRICE => 50, + Option::KEY_PRICE_TYPE => 'percent', + Option::KEY_SKU => 'test-option-field-title-4', + Option::KEY_MAX_CHARACTERS => 0, + ], + [ + 'block_with_required_class' => '<div class="field">', + 'label_for_created_option' => '<label class="label" for="options_%s_text">', + 'title' => '<span>Test option field title 4</span>', + 'price' => 'data-price-amount="5"', + 'required_element' => '/<input type="text"/', + ], + ], + 'type_field_max_characters' => [ + [ + Option::KEY_TITLE => 'Test option field title 5', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_FIELD, + Option::KEY_IS_REQUIRE => 0, + Option::KEY_PRICE => 10, + Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_SKU => 'test-option-field-title-5', + Option::KEY_MAX_CHARACTERS => 99, + ], + [ + 'block_with_required_class' => '<div class="field">', + 'label_for_created_option' => '<label class="label" for="options_%s_text">', + 'title' => '<span>Test option field title 5</span>', + 'price' => 'data-price-amount="10"', + 'required_element' => '/<input type="text"/', + 'max_characters' => 'Maximum 99 characters', + ], + ], + 'type_area_required' => [ + [ + Option::KEY_TITLE => 'Test option area title 1', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_AREA, + Option::KEY_IS_REQUIRE => 1, + Option::KEY_PRICE => 10, + Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_SKU => 'test-option-area-title-1', + Option::KEY_MAX_CHARACTERS => 0, + ], + [ + 'block_with_required_class' => '<div class="field textarea required">', + 'label_for_created_option' => '<label class="label" for="options_%s_text">', + 'title' => '<span>Test option area title 1</span>', + 'price' => 'data-price-amount="10"', + 'required_element' => '/<textarea/', + ], + ], + 'type_area_not_required' => [ + [ + Option::KEY_TITLE => 'Test option area title 2', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_AREA, + Option::KEY_IS_REQUIRE => 0, + Option::KEY_PRICE => 10, + Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_SKU => 'test-option-area-title-2', + Option::KEY_MAX_CHARACTERS => 0, + ], + [ + 'block_with_required_class' => '<div class="field textarea">', + 'label_for_created_option' => '<label class="label" for="options_%s_text">', + 'title' => '<span>Test option area title 2</span>', + 'price' => 'data-price-amount="10"', + 'required_element' => '/<textarea/', + ], + ], + 'type_area_fixed_price' => [ + [ + Option::KEY_TITLE => 'Test option area title 3', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_AREA, + Option::KEY_IS_REQUIRE => 0, + Option::KEY_PRICE => 50, + Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_SKU => 'test-option-area-title-3', + Option::KEY_MAX_CHARACTERS => 0, + ], + [ + 'block_with_required_class' => '<div class="field textarea">', + 'label_for_created_option' => '<label class="label" for="options_%s_text">', + 'title' => '<span>Test option area title 3</span>', + 'price' => 'data-price-amount="50"', + 'required_element' => '/<textarea/', + ], + ], + 'type_area_percent_price' => [ + [ + Option::KEY_TITLE => 'Test option area title 4', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_AREA, + Option::KEY_IS_REQUIRE => 0, + Option::KEY_PRICE => 50, + Option::KEY_PRICE_TYPE => 'percent', + Option::KEY_SKU => 'test-option-area-title-4', + Option::KEY_MAX_CHARACTERS => 0, + ], + [ + 'block_with_required_class' => '<div class="field textarea">', + 'label_for_created_option' => '<label class="label" for="options_%s_text">', + 'title' => '<span>Test option area title 4</span>', + 'price' => 'data-price-amount="5"', + 'required_element' => '/<textarea/', + ], + ], + 'type_area_max_characters' => [ + [ + Option::KEY_TITLE => 'Test option area title 5', + Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_AREA, + Option::KEY_IS_REQUIRE => 0, + Option::KEY_PRICE => 10, + Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_SKU => 'test-option-area-title-5', + Option::KEY_MAX_CHARACTERS => 99, + ], + [ + 'block_with_required_class' => '<div class="field textarea">', + 'label_for_created_option' => '<label class="label" for="options_%s_text">', + 'title' => '<span>Test option area title 5</span>', + 'price' => 'data-price-amount="10"', + 'required_element' => '/<textarea/', + 'max_characters' => 'Maximum 99 characters', + ], + ], + ]; + } +} diff --git a/dev/tests/integration/framework/Magento/TestFramework/Event/Transaction.php b/dev/tests/integration/framework/Magento/TestFramework/Event/Transaction.php index 9ab6d67014129..2add2ed48fb98 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Event/Transaction.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Event/Transaction.php @@ -4,11 +4,11 @@ * See COPYING.txt for license details. */ +namespace Magento\TestFramework\Event; + /** * Database transaction events manager */ -namespace Magento\TestFramework\Event; - class Transaction { /** @@ -86,6 +86,7 @@ protected function _processTransactionRequests($eventName, \PHPUnit\Framework\Te * Start transaction and fire 'startTransaction' event * * @param \PHPUnit\Framework\TestCase $test + * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ protected function _startTransaction(\PHPUnit\Framework\TestCase $test) { @@ -93,7 +94,21 @@ protected function _startTransaction(\PHPUnit\Framework\TestCase $test) $this->_getConnection()->beginTransparentTransaction(); $this->_isTransactionActive = true; try { + /** + * Add any warning during transaction execution as a failure. + */ + set_error_handler( + function ($errNo, $errStr, $errFile, $errLine) use ($test) { + $errMsg = sprintf("%s: %s in %s:%s.", "Warning", $errStr, $errFile, $errLine); + $test->getTestResultObject()->addError($test, new \PHPUnit\Framework\Warning($errMsg), 0); + + // Allow error to be handled by next error handler + return false; + }, + E_WARNING + ); $this->_eventManager->fireEvent('startTransaction', [$test]); + restore_error_handler(); } catch (\Exception $e) { $test->getTestResultObject()->addFailure( $test, diff --git a/dev/tests/integration/phpunit.xml.dist b/dev/tests/integration/phpunit.xml.dist index 858de7a9873e7..56812163ed5f2 100644 --- a/dev/tests/integration/phpunit.xml.dist +++ b/dev/tests/integration/phpunit.xml.dist @@ -78,8 +78,6 @@ </php> <!-- Test listeners --> <listeners> - <listener class="Magento\TestFramework\Event\PhpUnit"/> - <listener class="Magento\TestFramework\ErrorLog\Listener"/> <listener class="Yandex\Allure\Adapter\AllureAdapter"> <arguments> <string>var/allure-results</string> <!-- XML files output directory --> @@ -127,5 +125,8 @@ </array> </arguments> </listener> + <!-- Run after AllureAdapter to allow it to initialize properly --> + <listener class="Magento\TestFramework\Event\PhpUnit"/> + <listener class="Magento\TestFramework\ErrorLog\Listener"/> </listeners> </phpunit> diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/ListProduct/ProductInCategoriesViewTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/ListProduct/ProductInCategoriesViewTest.php index b9adb051981c0..48f6e455a5b9f 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/ListProduct/ProductInCategoriesViewTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/ListProduct/ProductInCategoriesViewTest.php @@ -100,6 +100,19 @@ public function productDataProvider(): array ]; } + /** + * @magentoConfigFixture default_store cataloginventory/options/show_out_of_stock 1 + * @magentoDataFixture Magento/Catalog/_files/out_of_stock_product_with_category.php + * @return void + */ + public function testCategoryOutOfStockProductView(): void + { + $collection = $this->getCategoryProductCollection(333); + + $this->assertEquals(1, $collection->getSize()); + $this->assertEquals('out-of-stock-product', $collection->getFirstItem()->getSku()); + } + /** * @magentoDataFixture Magento/Catalog/_files/category_product.php * @dataProvider productVisibilityProvider diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/Attribute/AbstractAttributeTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/Attribute/AbstractAttributeTest.php index 399abea6a0760..80e2ac52cecd6 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/Attribute/AbstractAttributeTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/Attribute/AbstractAttributeTest.php @@ -16,6 +16,7 @@ use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Registry; use Magento\Framework\View\LayoutInterface; +use Magento\Store\Model\StoreManagerInterface; use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; @@ -48,6 +49,9 @@ abstract class AbstractAttributeTest extends TestCase /** @var Output */ private $outputHelper; + /** @var StoreManagerInterface */ + private $storeManager; + /** * @inheritdoc */ @@ -62,6 +66,7 @@ protected function setUp() $this->registry = $this->objectManager->get(Registry::class); $this->block = $this->layout->createBlock(Attributes::class); $this->outputHelper = $this->objectManager->create(Output::class); + $this->storeManager = $this->objectManager->get(StoreManagerInterface::class); } /** @@ -145,6 +150,35 @@ protected function processAttributeHtmlOutput( $this->assertEquals($expectedAttributeValue, $output); } + /** + * Process attribute view per store views + * + * @param string $sku + * @param int $attributeScopeValue + * @param string $attributeValue + * @param string $expectedAttributeValue + * @param string $storeCode + * @return void + */ + protected function processMultiStoreView( + string $sku, + int $attributeScopeValue, + string $attributeValue, + string $storeCode + ): void { + $currentStore = $this->storeManager->getStore(); + $this->updateAttribute(['is_global' => $attributeScopeValue, 'is_visible_on_front' => true]); + $this->storeManager->setCurrentStore($storeCode); + + try { + $product = $this->updateProduct($sku, $attributeValue); + $this->registerProduct($product); + $this->assertEquals($this->prepareExpectedData($attributeValue), $this->block->getAdditionalData()); + } finally { + $this->storeManager->setCurrentStore($currentStore); + } + } + /** * Get attribute * @@ -185,8 +219,11 @@ private function prepareExpectedData(string $expectedValue): array */ private function updateProduct(string $productSku, string $attributeValue): ProductInterface { + $value = $this->getAttribute()->usesSource() + ? $this->attribute->getSource()->getOptionId($attributeValue) + : $attributeValue; $product = $this->productRepository->get($productSku); - $product->addData([$this->getAttributeCode() => $attributeValue]); + $product->addData([$this->getAttributeCode() => $value]); return $this->productRepository->save($product); } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/Attribute/DropdownAttributeTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/Attribute/DropdownAttributeTest.php index 98799822dcfb0..f6c7e81b13a23 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/Attribute/DropdownAttributeTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/Attribute/DropdownAttributeTest.php @@ -7,6 +7,8 @@ namespace Magento\Catalog\Block\Product\View\Attribute; +use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface; + /** * Class checks dropdown attribute displaying on frontend * @@ -77,6 +79,43 @@ public function attributeWithTagsProvider(): array ]; } + /** + * @magentoDbIsolation disabled + * + * @magentoDataFixture Magento/Catalog/_files/dropdown_attribute.php + * @magentoDataFixture Magento/Store/_files/core_fixturestore.php + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * + * @return void + */ + public function testAttributePerStoreView(): void + { + $this->processMultiStoreView( + 'simple2', + ScopedAttributeInterface::SCOPE_STORE, + 'Option 3', + 'fixturestore' + ); + } + + /** + * @magentoDbIsolation disabled + * + * @magentoDataFixture Magento/Catalog/_files/dropdown_attribute.php + * @magentoDataFixture Magento/Catalog/_files/product_two_websites.php + * + * @return void + */ + public function testAttributePerWebsites(): void + { + $this->processMultiStoreView( + 'simple-on-two-websites', + ScopedAttributeInterface::SCOPE_WEBSITE, + 'Option 3', + 'fixture_second_store' + ); + } + /** * @inheritdoc */ diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/Attribute/TextAttributeTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/Attribute/TextAttributeTest.php index b61c5fd22d5b0..dae5fad160128 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/Attribute/TextAttributeTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/Attribute/TextAttributeTest.php @@ -7,6 +7,8 @@ namespace Magento\Catalog\Block\Product\View\Attribute; +use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface; + /** * Class checks text attribute displaying on frontend * @@ -77,6 +79,43 @@ public function attributeWithTagsProvider(): array ]; } + /** + * @magentoDbIsolation disabled + * + * @magentoDataFixture Magento/Catalog/_files/product_varchar_attribute.php + * @magentoDataFixture Magento/Store/_files/core_fixturestore.php + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * + * @return void + */ + public function testAttributePerStoreView(): void + { + $this->processMultiStoreView( + 'simple2', + ScopedAttributeInterface::SCOPE_STORE, + 'second store view value', + 'fixturestore' + ); + } + + /** + * @magentoDbIsolation disabled + * + * @magentoDataFixture Magento/Catalog/_files/product_two_websites.php + * @magentoDataFixture Magento/Catalog/_files/product_varchar_attribute.php + * + * @return void + */ + public function testAttributePerWebsites(): void + { + $this->processMultiStoreView( + 'simple-on-two-websites', + ScopedAttributeInterface::SCOPE_WEBSITE, + 'second website value', + 'fixture_second_store' + ); + } + /** * @inheritdoc */ diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/Options/RenderOptionsTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/Options/RenderOptionsTest.php new file mode 100644 index 0000000000000..e83563a6ad474 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/Options/RenderOptionsTest.php @@ -0,0 +1,295 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Block\Product\View\Options; + +use Magento\Catalog\Api\Data\ProductCustomOptionInterface; +use Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory; +use Magento\Catalog\Api\Data\ProductCustomOptionValuesInterfaceFactory; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Block\Product\View\Options; +use Magento\Catalog\Model\Product\Option; +use Magento\Catalog\Model\Product\Option\Value; +use Magento\Framework\View\Element\Template; +use Magento\Framework\View\Result\Page; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Helper\CacheCleaner; +use Magento\TestFramework\ObjectManager; +use PHPUnit\Framework\TestCase; + +/** + * Assert that product custom options render as expected. + * + * @magentoDbIsolation disabled + * @magentoAppArea frontend + */ +class RenderOptionsTest extends TestCase +{ + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var ProductCustomOptionInterfaceFactory + */ + private $productCustomOptionFactory; + + /** + * @var ProductCustomOptionValuesInterfaceFactory + */ + private $productCustomOptionValuesFactory; + + /** + * @var Page + */ + private $page; + + /** + * @inheritdoc + */ + protected function setUp() + { + CacheCleaner::cleanAll(); + $this->objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $this->objectManager->create(ProductRepositoryInterface::class); + $this->productCustomOptionFactory = $this->objectManager->get(ProductCustomOptionInterfaceFactory::class); + $this->productCustomOptionValuesFactory = $this->objectManager->get( + ProductCustomOptionValuesInterfaceFactory::class + ); + $this->page = $this->objectManager->create(Page::class); + parent::setUp(); + } + + /** + * Check that options from text group(field, area) render as expected. + * + * @magentoDataFixture Magento/Catalog/_files/product_without_options_with_stock_data.php + * @dataProvider \Magento\TestFramework\Catalog\Block\Product\View\Options\TextGroupDataProvider::getData() + * + * @param array $optionData + * @param array $checkArray + * @return void + */ + public function testRenderCustomOptionsFromTextGroup(array $optionData, array $checkArray): void + { + $option = $this->addOptionToProduct($optionData); + $optionHtml = $this->getOptionHtml(); + $this->baseOptionAsserts($option, $optionHtml, $checkArray); + + if ($optionData[Option::KEY_MAX_CHARACTERS] > 0) { + $this->assertContains($checkArray['max_characters'], $optionHtml); + } else { + $this->assertNotContains('class="character-counter', $optionHtml); + } + } + + /** + * Check that options from file group(file) render as expected. + * + * @magentoDataFixture Magento/Catalog/_files/product_without_options_with_stock_data.php + * @dataProvider \Magento\TestFramework\Catalog\Block\Product\View\Options\FileGroupDataProvider::getData() + * + * @param array $optionData + * @param array $checkArray + * @return void + */ + public function testRenderCustomOptionsFromFileGroup(array $optionData, array $checkArray): void + { + $option = $this->addOptionToProduct($optionData); + $optionHtml = $this->getOptionHtml(); + $this->baseOptionAsserts($option, $optionHtml, $checkArray); + $this->assertContains($checkArray['file_extension'], $optionHtml); + + if (isset($checkArray['file_width'])) { + $checkArray['file_width'] = sprintf($checkArray['file_width'], __('Maximum image width')); + $this->assertRegExp($checkArray['file_width'], $optionHtml); + } + + if (isset($checkArray['file_height'])) { + $checkArray['file_height'] = sprintf($checkArray['file_height'], __('Maximum image height')); + $this->assertRegExp($checkArray['file_height'], $optionHtml); + } + } + + /** + * Check that options from select group(drop-down, radio buttons, checkbox, multiple select) render as expected. + * + * @magentoDataFixture Magento/Catalog/_files/product_without_options_with_stock_data.php + * @dataProvider \Magento\TestFramework\Catalog\Block\Product\View\Options\SelectGroupDataProvider::getData() + * + * @param array $optionData + * @param array $optionValueData + * @param array $checkArray + * @return void + */ + public function testRenderCustomOptionsFromSelectGroup( + array $optionData, + array $optionValueData, + array $checkArray + ): void { + $option = $this->addOptionToProduct($optionData, $optionValueData); + $optionValues = $option->getValues(); + $optionValue = reset($optionValues); + $optionHtml = $this->getOptionHtml(); + $this->baseOptionAsserts($option, $optionHtml, $checkArray); + + if (isset($checkArray['not_contain_arr'])) { + foreach ($checkArray['not_contain_arr'] as $notContainPattern) { + $this->assertNotRegExp($notContainPattern, $optionHtml); + } + } + + if (isset($checkArray['option_value_item'])) { + $checkArray['option_value_item'] = sprintf( + $checkArray['option_value_item'], + $optionValue->getOptionTypeId(), + $optionValueData[Value::KEY_TITLE] + ); + $this->assertRegExp($checkArray['option_value_item'], $optionHtml); + } + } + + /** + * Check that options from date group(date, date & time, time) render as expected. + * + * @magentoDataFixture Magento/Catalog/_files/product_without_options_with_stock_data.php + * @dataProvider \Magento\TestFramework\Catalog\Block\Product\View\Options\DateGroupDataProvider::getData() + * + * @param array $optionData + * @param array $checkArray + * @return void + */ + public function testRenderCustomOptionsFromDateGroup(array $optionData, array $checkArray): void + { + $option = $this->addOptionToProduct($optionData); + $optionHtml = $this->getOptionHtml(); + $this->baseOptionAsserts($option, $optionHtml, $checkArray); + + switch ($optionData[Option::KEY_TYPE]) { + case ProductCustomOptionInterface::OPTION_TYPE_DATE: + $this->assertContains("<select name=\"options[{$option->getOptionId()}][month]\"", $optionHtml); + $this->assertContains("<select name=\"options[{$option->getOptionId()}][day]\"", $optionHtml); + $this->assertContains("<select name=\"options[{$option->getOptionId()}][year]\"", $optionHtml); + $this->assertNotContains("<select name=\"options[{$option->getOptionId()}][hour]\"", $optionHtml); + $this->assertNotContains("<select name=\"options[{$option->getOptionId()}][minute]\"", $optionHtml); + $this->assertNotContains("<select name=\"options[{$option->getOptionId()}][day_part]\"", $optionHtml); + break; + case ProductCustomOptionInterface::OPTION_TYPE_DATE_TIME: + $this->assertContains("<select name=\"options[{$option->getOptionId()}][month]\"", $optionHtml); + $this->assertContains("<select name=\"options[{$option->getOptionId()}][day]\"", $optionHtml); + $this->assertContains("<select name=\"options[{$option->getOptionId()}][year]\"", $optionHtml); + $this->assertContains("<select name=\"options[{$option->getOptionId()}][hour]\"", $optionHtml); + $this->assertContains("<select name=\"options[{$option->getOptionId()}][minute]\"", $optionHtml); + $this->assertContains("<select name=\"options[{$option->getOptionId()}][day_part]\"", $optionHtml); + break; + case ProductCustomOptionInterface::OPTION_TYPE_TIME: + $this->assertNotContains("<select name=\"options[{$option->getOptionId()}][month]\"", $optionHtml); + $this->assertNotContains("<select name=\"options[{$option->getOptionId()}][day]\"", $optionHtml); + $this->assertNotContains("<select name=\"options[{$option->getOptionId()}][year]\"", $optionHtml); + $this->assertContains("<select name=\"options[{$option->getOptionId()}][hour]\"", $optionHtml); + $this->assertContains("<select name=\"options[{$option->getOptionId()}][minute]\"", $optionHtml); + $this->assertContains("<select name=\"options[{$option->getOptionId()}][day_part]\"", $optionHtml); + break; + } + } + + /** + * Base asserts for rendered options. + * + * @param ProductCustomOptionInterface $option + * @param string $optionHtml + * @param array $checkArray + * @return void + */ + private function baseOptionAsserts( + ProductCustomOptionInterface $option, + string $optionHtml, + array $checkArray + ): void { + $this->assertContains($checkArray['block_with_required_class'], $optionHtml); + $this->assertContains($checkArray['title'], $optionHtml); + + if (isset($checkArray['label_for_created_option'])) { + $checkArray['label_for_created_option'] = sprintf( + $checkArray['label_for_created_option'], + $option->getOptionId() + ); + $this->assertContains($checkArray['label_for_created_option'], $optionHtml); + } + + if (isset($checkArray['price'])) { + $this->assertContains($checkArray['price'], $optionHtml); + } + + if (isset($checkArray['required_element'])) { + $this->assertRegExp($checkArray['required_element'], $optionHtml); + } + } + + /** + * Add custom option to product with data. + * + * @param array $optionData + * @param array $optionValueData + * @return ProductCustomOptionInterface + */ + private function addOptionToProduct(array $optionData, array $optionValueData = []): ProductCustomOptionInterface + { + $product = $this->productRepository->get('simple'); + $optionData[Option::KEY_PRODUCT_SKU] = $product->getSku(); + + if (!empty($optionValueData)) { + $optionValueData = $this->productCustomOptionValuesFactory->create(['data' => $optionValueData]); + $optionData['values'] = [$optionValueData]; + } + + $option = $this->productCustomOptionFactory->create(['data' => $optionData]); + $product->setOptions([$option]); + $createdOptions = $this->productRepository->save($product)->getOptions(); + + return reset($createdOptions); + } + + /** + * Render custom options block. + * + * @return string + */ + private function getOptionHtml(): string + { + $product = $this->productRepository->get('simple'); + $optionsBlock = $this->getOptionsBlock(); + $optionsBlock->setProduct($product); + + return $optionsBlock->toHtml(); + } + + /** + * Get options block. + * + * @return Options + */ + private function getOptionsBlock(): Options + { + $this->page->addHandle([ + 'default', + 'catalog_product_view', + ]); + $this->page->getLayout()->generateXml(); + /** @var Template $productInfoFormOptionsBlock */ + $productInfoFormOptionsBlock = $this->page->getLayout()->getBlock('product.info.form.options'); + $optionsWrapperBlock = $productInfoFormOptionsBlock->getChildBlock('product_options_wrapper'); + + return $optionsWrapperBlock->getChildBlock('product_options'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Delete/AbstractDeleteAttributeControllerTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Delete/AbstractDeleteAttributeControllerTest.php new file mode 100644 index 0000000000000..6f1ff8567349f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Delete/AbstractDeleteAttributeControllerTest.php @@ -0,0 +1,83 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute\Delete; + +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; +use Magento\Framework\App\Request\Http; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Message\MessageInterface; +use Magento\TestFramework\TestCase\AbstractBackendController; + +/** + * Abstract delete attribute test using catalog/product_attribute/delete controller action. + */ +abstract class AbstractDeleteAttributeControllerTest extends AbstractBackendController +{ + /** + * @var string + */ + protected $uri = 'backend/catalog/product_attribute/delete/attribute_id/%s'; + + /** + * @var ProductAttributeRepositoryInterface + */ + private $productAttributeRepository; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->productAttributeRepository = $this->_objectManager->get(ProductAttributeRepositoryInterface::class); + } + + /** + * Delete attribute via controller action. + * + * @param string $attributeCode + * @return void + */ + protected function dispatchDeleteAttribute(string $attributeCode): void + { + $attribute = $this->productAttributeRepository->get($attributeCode); + $this->getRequest()->setMethod(Http::METHOD_POST); + $this->dispatch(sprintf($this->uri, $attribute->getAttributeId())); + $this->assertSessionMessages( + $this->equalTo([(string)__('You deleted the product attribute.')]), + MessageInterface::TYPE_SUCCESS + ); + } + + /** + * Assert that attribute is deleted from DB. + * + * @param string $attributeCode + * @return void + */ + protected function assertAttributeIsDeleted(string $attributeCode): void + { + $this->expectExceptionObject( + new NoSuchEntityException( + __( + 'The attribute with a "%1" attributeCode doesn\'t exist. Verify the attribute and try again.', + $attributeCode + ) + ) + ); + $this->productAttributeRepository->get($attributeCode); + } + + /** + * @inheritdoc + */ + public function testAclHasAccess() + { + $this->markTestIncomplete('AclHasAccess test is not complete'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Delete/CatalogAttributesControllerTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Delete/CatalogAttributesControllerTest.php new file mode 100644 index 0000000000000..e95737209cb7f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Delete/CatalogAttributesControllerTest.php @@ -0,0 +1,145 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute\Delete; + +/** + * Delete catalog product attributes with input types like "media_image", "price", + * "date", "select", "multiselect", "textarea", "texteditor", "text" and "boolean". + * Attributes from Magento_Catalog and Magento_Eav modules. + * + * @magentoAppArea adminhtml + * @magentoDbIsolation enabled + */ +class CatalogAttributesControllerTest extends AbstractDeleteAttributeControllerTest +{ + /** + * Assert that attribute with input type "media_image" will be deleted + * after dispatch delete product attribute action. + * + * @magentoDataFixture Magento/Catalog/_files/product_image_attribute.php + * + * @return void + */ + public function testDeleteMediaImageAttribute(): void + { + $this->dispatchDeleteAttribute('image_attribute'); + $this->assertAttributeIsDeleted('image_attribute'); + } + + /** + * Assert that attribute with input type "price" will be deleted + * after dispatch delete product attribute action. + * + * @magentoDataFixture Magento/Catalog/_files/product_decimal_attribute.php + * + * @return void + */ + public function testDeletePriceAttribute(): void + { + $this->dispatchDeleteAttribute('decimal_attribute'); + $this->assertAttributeIsDeleted('decimal_attribute'); + } + + /** + * Assert that attribute with input type "date" will be deleted + * after dispatch delete product attribute action. + * + * @magentoDataFixture Magento/Catalog/_files/product_date_attribute.php + * + * @return void + */ + public function testDeleteDateAttribute(): void + { + $this->dispatchDeleteAttribute('date_attribute'); + $this->assertAttributeIsDeleted('date_attribute'); + } + + /** + * Assert that attribute with input type "select" will be deleted + * after dispatch delete product attribute action. + * + * @magentoDataFixture Magento/Catalog/_files/product_dropdown_attribute.php + * + * @return void + */ + public function testDeleteSelectAttribute(): void + { + $this->dispatchDeleteAttribute('dropdown_attribute'); + $this->assertAttributeIsDeleted('dropdown_attribute'); + } + + /** + * Assert that attribute with input type "multiselect" will be deleted + * after dispatch delete product attribute action. + * + * @magentoDataFixture Magento/Catalog/_files/multiselect_attribute.php + * + * @return void + */ + public function testDeleteMultiselectAttribute(): void + { + $this->dispatchDeleteAttribute('multiselect_attribute'); + $this->assertAttributeIsDeleted('multiselect_attribute'); + } + + /** + * Assert that attribute with input type "textarea" will be deleted + * after dispatch delete product attribute action. + * + * @magentoDataFixture Magento/Catalog/_files/product_text_attribute.php + * + * @return void + */ + public function testDeleteTextareaAttribute(): void + { + $this->dispatchDeleteAttribute('text_attribute'); + $this->assertAttributeIsDeleted('text_attribute'); + } + + /** + * Assert that attribute with input type "texteditor" will be deleted + * after dispatch delete product attribute action. + * + * @magentoDataFixture Magento/Eav/_files/product_texteditor_attribute.php + * + * @return void + */ + public function testDeleteTextEditorAttribute(): void + { + $this->dispatchDeleteAttribute('text_editor_attribute'); + $this->assertAttributeIsDeleted('text_editor_attribute'); + } + + /** + * Assert that attribute with input type "text" will be deleted + * after dispatch delete product attribute action. + * + * @magentoDataFixture Magento/Catalog/_files/product_varchar_attribute.php + * + * @return void + */ + public function testDeleteTextAttribute(): void + { + $this->dispatchDeleteAttribute('varchar_attribute'); + $this->assertAttributeIsDeleted('varchar_attribute'); + } + + /** + * Assert that attribute with input type "boolean" will be deleted + * after dispatch delete product attribute action. + * + * @magentoDataFixture Magento/Catalog/_files/product_boolean_attribute.php + * + * @return void + */ + public function testDeleteBooleanAttribute(): void + { + $this->dispatchDeleteAttribute('boolean_attribute'); + $this->assertAttributeIsDeleted('boolean_attribute'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Delete/DeleteAttributeControllerErrorTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Delete/DeleteAttributeControllerErrorTest.php new file mode 100644 index 0000000000000..31aef9c85b9bd --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Delete/DeleteAttributeControllerErrorTest.php @@ -0,0 +1,79 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute\Delete; + +use Magento\Catalog\Model\Category; +use Magento\Eav\Api\AttributeRepositoryInterface; +use Magento\Framework\App\Request\Http; +use Magento\Framework\Escaper; +use Magento\Framework\Message\MessageInterface; + +/** + * Error during delete attribute using catalog/product_attribute/delete controller action. + * + * @magentoAppArea adminhtml + * @magentoDbIsolation enabled + */ +class DeleteAttributeControllerErrorTest extends AbstractDeleteAttributeControllerTest +{ + /** + * @var Escaper + */ + private $escaper; + + /** + * @var AttributeRepositoryInterface + */ + private $attributeRepository; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->escaper = $this->_objectManager->get(Escaper::class); + $this->attributeRepository = $this->_objectManager->get(AttributeRepositoryInterface::class); + } + + /** + * Try to delete attribute via controller action without attribute ID. + * + * @return void + */ + public function testDispatchWithoutAttributeId(): void + { + $this->getRequest()->setMethod(Http::METHOD_POST); + $this->dispatch(sprintf($this->uri, '')); + $this->assertSessionMessages( + $this->equalTo([$this->escaper->escapeHtml((string)__('We can\'t find an attribute to delete.'))]), + MessageInterface::TYPE_ERROR + ); + } + + /** + * Try to delete category attribute via controller action. + * + * @magentoDataFixture Magento/Catalog/_files/category_attribute.php + * + * @return void + */ + public function testDispatchWithNonProductAttribute(): void + { + $categoryAttribute = $this->attributeRepository->get( + Category::ENTITY, + 'test_attribute_code_666' + ); + $this->getRequest()->setMethod(Http::METHOD_POST); + $this->dispatch(sprintf($this->uri, $categoryAttribute->getAttributeId())); + $this->assertSessionMessages( + $this->equalTo([$this->escaper->escapeHtml((string)__('We can\'t delete the attribute.'))]), + MessageInterface::TYPE_ERROR + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Save/AdvancedPricingTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Save/AdvancedPricingTest.php new file mode 100644 index 0000000000000..da4cf6335fb05 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Save/AdvancedPricingTest.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\Controller\Adminhtml\Product\Save; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Customer\Api\Data\GroupInterface; +use Magento\Framework\App\Request\Http; +use Magento\Framework\Message\MessageInterface; +use Magento\TestFramework\TestCase\AbstractBackendController; + +/** + * Test cases for set advanced price to product. + * + * @magentoAppArea adminhtml + * @magentoDbIsolation enabled + */ +class AdvancedPricingTest extends AbstractBackendController +{ + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->productRepository = $this->_objectManager->get(ProductRepositoryInterface::class); + } + + /** + * Assert that special price correctly saved to product. + * + * @magentoDataFixture Magento/Catalog/_files/product_without_options.php + * + * @return void + */ + public function testAddSpecialPriceToProduct(): void + { + $product = $this->productRepository->get('simple'); + $postData = [ + 'product' => [ + 'special_price' => 8, + ], + ]; + $this->assertNull($product->getSpecialPrice()); + $this->dispatchWithData((int)$product->getEntityId(), $postData); + $product = $this->productRepository->get('simple', false, null, true); + $this->assertEquals(8, $product->getSpecialPrice()); + } + + /** + * Assert that tier price correctly saved to product. + * + * @magentoDataFixture Magento/Catalog/_files/product_without_options.php + * + * @return void + */ + public function testAddTierPriceToProduct(): void + { + $product = $this->productRepository->get('simple'); + $postData = [ + 'product' => [ + 'tier_price' => [ + [ + 'website_id' => '0', + 'cust_group' => GroupInterface::CUST_GROUP_ALL, + 'price_qty' => '100', + 'price' => 5, + 'value_type' => 'fixed', + ] + ], + ], + ]; + $this->assertEquals(10, $product->getTierPrice(100)); + $this->dispatchWithData((int)$product->getEntityId(), $postData); + $product = $this->productRepository->get('simple', false, null, true); + $this->assertEquals(5, $product->getTierPrice(100)); + } + + /** + * Dispatch product save with data. + * + * @param int $productId + * @param array $productPostData + * @return void + */ + private function dispatchWithData(int $productId, array $productPostData): void + { + $this->getRequest()->setPostValue($productPostData); + $this->getRequest()->setMethod(Http::METHOD_POST); + $this->dispatch('backend/catalog/product/save/id/' . $productId); + $this->assertSessionMessages( + $this->contains('You saved the product.'), + MessageInterface::TYPE_SUCCESS + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Type/PriceTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Type/PriceTest.php index 8d3a119873afb..fe7207d310345 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Type/PriceTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Type/PriceTest.php @@ -3,105 +3,214 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Catalog\Model\Product\Type; +use Magento\Catalog\Api\Data\ProductCustomOptionInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Option; +use Magento\Customer\Model\Session; +use Magento\Framework\DataObject; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; +use PHPUnit\Framework\TestCase; /** + * Simple product price test. + * * @magentoDataFixture Magento/Catalog/_files/product_simple.php + * @magentoDbIsolation enabled */ -class PriceTest extends \PHPUnit\Framework\TestCase +class PriceTest extends TestCase { /** - * @var \Magento\Catalog\Model\Product\Type\Price + * @var ObjectManager + */ + private $objectManager; + + /** + * @var Price */ - protected $_model; + private $productPrice; - protected function setUp() + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var Session + */ + private $customerSession; + + /** + * @inheritdoc + */ + protected function setUp(): void { - $this->_model = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Catalog\Model\Product\Type\Price::class - ); + $this->objectManager = Bootstrap::getObjectManager(); + $this->productPrice = $this->objectManager->create(Price::class); + $this->productRepository = $this->objectManager->create(ProductRepositoryInterface::class); + $this->customerSession = $this->objectManager->get(Session::class); } - public function testGetPrice() + /** + * Assert that for logged user product price equal to price from catalog rule. + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + * @magentoDataFixture Magento/CatalogRule/_files/catalog_rule_6_off_logged_user.php + * @magentoDataFixture Magento/Customer/_files/customer.php + * + * @magentoDbIsolation disabled + * @magentoAppArea frontend + * @magentoAppIsolation enabled + * + * @return void + */ + public function testPriceByRuleForLoggedUser(): void { - $this->assertEquals('test', $this->_model->getPrice(new \Magento\Framework\DataObject(['price' => 'test']))); + $product = $this->productRepository->get('simple'); + $this->assertEquals(10, $this->productPrice->getFinalPrice(1, $product)); + $this->customerSession->setCustomerId(1); + try { + $this->assertEquals(4, $this->productPrice->getFinalPrice(1, $product)); + } finally { + $this->customerSession->setCustomerId(null); + } } - public function testGetFinalPrice() + /** + * Assert price for different customer groups. + * + * @magentoDataFixture Magento/Catalog/_files/simple_product_with_tier_price_for_logged_user.php + * @magentoDataFixture Magento/Customer/_files/customer.php + * + * @magentoAppIsolation enabled + * + * @return void + */ + public function testTierPriceWithDifferentCustomerGroups(): void { - $repository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Catalog\Model\ProductRepository::class - ); - $product = $repository->get('simple'); - // fixture + $product = $this->productRepository->get('simple'); + $this->assertEquals(8, $this->productPrice->getFinalPrice(2, $product)); + $this->assertEquals(5, $this->productPrice->getFinalPrice(3, $product)); + $this->customerSession->setCustomerId(1); + try { + $this->assertEquals(1, $this->productPrice->getFinalPrice(3, $product)); + } finally { + $this->customerSession->setCustomerId(null); + } + } + + /** + * Get price from custom object. + * + * @return void + */ + public function testGetPrice(): void + { + $objectWithPrice = $this->objectManager->create(DataObject::class, ['data' => ['price' => 'test']]); + $this->assertEquals('test', $this->productPrice->getPrice($objectWithPrice)); + } + + /** + * Get product final price for different product count. + * + * @return void + */ + public function testGetFinalPrice(): void + { + $product = $this->productRepository->get('simple'); // regular & tier prices - $this->assertEquals(10.0, $this->_model->getFinalPrice(1, $product)); - $this->assertEquals(8.0, $this->_model->getFinalPrice(2, $product)); - $this->assertEquals(5.0, $this->_model->getFinalPrice(5, $product)); + $this->assertEquals(10.0, $this->productPrice->getFinalPrice(1, $product)); + $this->assertEquals(8.0, $this->productPrice->getFinalPrice(2, $product)); + $this->assertEquals(5.0, $this->productPrice->getFinalPrice(5, $product)); // with options $buyRequest = $this->prepareBuyRequest($product); $product->getTypeInstance()->prepareForCart($buyRequest, $product); //product price + options price(10+1+2+3+3) - $this->assertEquals(19.0, $this->_model->getFinalPrice(1, $product)); + $this->assertEquals(19.0, $this->productPrice->getFinalPrice(1, $product)); //product tier price + options price(5+1+2+3+3) - $this->assertEquals(14.0, $this->_model->getFinalPrice(5, $product)); + $this->assertEquals(14.0, $this->productPrice->getFinalPrice(5, $product)); } - public function testGetFormatedPrice() + /** + * Assert that formated price is correct. + * + * @return void + */ + public function testGetFormatedPrice(): void { - $repository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Catalog\Model\ProductRepository::class - ); - $product = $repository->get('simple'); - // fixture - $this->assertEquals('<span class="price">$10.00</span>', $this->_model->getFormatedPrice($product)); + $product = $this->productRepository->get('simple'); + $this->assertEquals('<span class="price">$10.00</span>', $this->productPrice->getFormatedPrice($product)); } - public function testCalculatePrice() + /** + * Test calculate price by date. + * + * @return void + */ + public function testCalculatePrice(): void { - $this->assertEquals(10, $this->_model->calculatePrice(10, 8, '1970-12-12 23:59:59', '1971-01-01 01:01:01')); - $this->assertEquals(8, $this->_model->calculatePrice(10, 8, '1970-12-12 23:59:59', '2034-01-01 01:01:01')); + $this->assertEquals( + 10, + $this->productPrice->calculatePrice(10, 8, '1970-12-12 23:59:59', '1971-01-01 01:01:01') + ); + $this->assertEquals( + 8, + $this->productPrice->calculatePrice(10, 8, '1970-12-12 23:59:59', '2034-01-01 01:01:01') + ); } - public function testCalculateSpecialPrice() + /** + * Test calculate price by date. + * + * @return void + */ + public function testCalculateSpecialPrice(): void { $this->assertEquals( 10, - $this->_model->calculateSpecialPrice(10, 8, '1970-12-12 23:59:59', '1971-01-01 01:01:01') + $this->productPrice->calculateSpecialPrice(10, 8, '1970-12-12 23:59:59', '1971-01-01 01:01:01') ); $this->assertEquals( 8, - $this->_model->calculateSpecialPrice(10, 8, '1970-12-12 23:59:59', '2034-01-01 01:01:01') + $this->productPrice->calculateSpecialPrice(10, 8, '1970-12-12 23:59:59', '2034-01-01 01:01:01') ); } - public function testIsTierPriceFixed() + /** + * Assert that product tier price is fixed. + * + * @return void + */ + public function testIsTierPriceFixed(): void { - $this->assertTrue($this->_model->isTierPriceFixed()); + $this->assertTrue($this->productPrice->isTierPriceFixed()); } /** - * Build buy request based on product custom options + * Build buy request based on product custom options. * * @param Product $product - * @return \Magento\Framework\DataObject + * @return DataObject */ - private function prepareBuyRequest(Product $product) + private function prepareBuyRequest(Product $product): DataObject { $options = []; - /** @var $option \Magento\Catalog\Model\Product\Option */ + /** @var Option $option */ foreach ($product->getOptions() as $option) { switch ($option->getGroupByType()) { - case \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_GROUP_DATE: + case ProductCustomOptionInterface::OPTION_GROUP_DATE: $value = ['year' => 2013, 'month' => 8, 'day' => 9, 'hour' => 13, 'minute' => 35]; break; - case \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_GROUP_SELECT: + case ProductCustomOptionInterface::OPTION_GROUP_SELECT: $value = key($option->getValues()); break; default: @@ -111,6 +220,6 @@ private function prepareBuyRequest(Product $product) $options[$option->getId()] = $value; } - return new \Magento\Framework\DataObject(['qty' => 1, 'options' => $options]); + return $this->objectManager->create(DataObject::class, ['data' => ['qty' => 1, 'options' => $options]]); } } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_different_price_products_on_two_websites.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_different_price_products_on_two_websites.php new file mode 100644 index 0000000000000..91a5ece179ba6 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_different_price_products_on_two_websites.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/category_with_different_price_products.php'; +require __DIR__ . '/../../Store/_files/second_website_with_two_stores.php'; + +$objectManager = Bootstrap::getObjectManager(); +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class); +$defaultWebsiteId = $websiteRepository->get('base')->getId(); +$websiteId = $websiteRepository->get('test')->getId(); + +$product = $productRepository->get('simple1000'); +$product->setWebsiteIds([$defaultWebsiteId, $websiteId]); +$productRepository->save($product); + +$product = $productRepository->get('simple1001'); +$product->setWebsiteIds([$defaultWebsiteId, $websiteId]); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_different_price_products_on_two_websites_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_different_price_products_on_two_websites_rollback.php new file mode 100644 index 0000000000000..d4c531c4da4db --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_different_price_products_on_two_websites_rollback.php @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/category_with_different_price_products_rollback.php'; +require __DIR__ . '/../../Store/_files/second_website_with_two_stores_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/out_of_stock_product_with_category.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/out_of_stock_product_with_category.php new file mode 100644 index 0000000000000..43670125e0315 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/out_of_stock_product_with_category.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\CategoryLinkManagementInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Model\ProductFactory; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/category.php'; + +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductFactory $productFactory */ +$productFactory = $objectManager->get(ProductFactory::class); +/** @var CategoryLinkManagementInterface $categoryLinkManagement */ +$categoryLinkManagement = $objectManager->get(CategoryLinkManagementInterface::class); +$product = $productFactory->create(); +$product->isObjectNew(true); +$product->setTypeId(Type::TYPE_SIMPLE) + ->setAttributeSetId($product->getDefaultAttributeSetId()) + ->setWebsiteIds([1]) + ->setName('Simple Product Out Of Stock') + ->setSku('out-of-stock-product') + ->setPrice(10) + ->setWeight(1) + ->setShortDescription("Short description") + ->setTaxClassId(0) + ->setDescription('Description with <b>html tag</b>') + ->setMetaTitle('meta title') + ->setMetaKeyword('meta keyword') + ->setMetaDescription('meta description') + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setCategoryIds([333]) + ->setStockData( + [ + 'use_config_manage_stock' => 1, + 'qty' => 0, + 'is_qty_decimal' => 0, + 'is_in_stock' => 0, + ] + ) + ->setCanSaveCustomOptions(true) + ->setHasOptions(true); +/** @var ProductRepositoryInterface $productRepositoryFactory */ +$productRepository = $objectManager->create(ProductRepositoryInterface::class); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/out_of_stock_product_with_category_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/out_of_stock_product_with_category_rollback.php new file mode 100644 index 0000000000000..ee07b064adc66 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/out_of_stock_product_with_category_rollback.php @@ -0,0 +1,30 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/category_rollback.php'; + +$objectManager = Bootstrap::getObjectManager(); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(ProductRepositoryInterface::class); + +try { + $productRepository->deleteById('out-of-stock-product'); +} catch (NoSuchEntityException $e) { + //already removed +} +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/simple_product_with_tier_price_for_logged_user.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/simple_product_with_tier_price_for_logged_user.php new file mode 100644 index 0000000000000..e7e64566a4371 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/simple_product_with_tier_price_for_logged_user.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/product_simple.php'; + +$product = $productRepository->get('simple', false, null, true); +$tierPrices = $product->getTierPrices() ?? []; +$tierPriceExtensionAttributes = $tpExtensionAttributesFactory->create()->setWebsiteId($adminWebsite->getId()); +$tierPrices[] = $tierPriceFactory->create( + [ + 'data' => [ + 'customer_group_id' => 1, + 'qty' => 3, + 'value' => 1 + ] + ] +)->setExtensionAttributes($tierPriceExtensionAttributes); +$product->setTierPrices($tierPrices); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/simple_product_with_tier_price_for_logged_user_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/simple_product_with_tier_price_for_logged_user_rollback.php new file mode 100644 index 0000000000000..e17fdac4904c9 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/simple_product_with_tier_price_for_logged_user_rollback.php @@ -0,0 +1,8 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/product_simple_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/catalog_rule_6_off_logged_user.php b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/catalog_rule_6_off_logged_user.php new file mode 100644 index 0000000000000..3343a837d17db --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/catalog_rule_6_off_logged_user.php @@ -0,0 +1,38 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\CatalogRule\Api\CatalogRuleRepositoryInterface; +use Magento\CatalogRule\Api\Data\RuleInterface; +use Magento\CatalogRule\Api\Data\RuleInterfaceFactory; +use Magento\CatalogRule\Model\Indexer\IndexBuilder; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var IndexBuilder $indexBuilder */ +$indexBuilder = $objectManager->get(IndexBuilder::class); +/** @var CatalogRuleRepositoryInterface $catalogRuleRepository */ +$catalogRuleRepository = $objectManager->get(CatalogRuleRepositoryInterface::class); +/** @var RuleInterfaceFactory $catalogRuleFactory */ +$catalogRuleFactory = $objectManager->get(RuleInterfaceFactory::class); +$catalogRule = $catalogRuleFactory->create( + [ + 'data' => [ + RuleInterface::IS_ACTIVE => 1, + RuleInterface::NAME => 'Test Catalog Rule for logged user', + 'customer_group_ids' => 1, + RuleInterface::DISCOUNT_AMOUNT => 6, + 'website_ids' => [1], + RuleInterface::SIMPLE_ACTION => 'by_fixed', + RuleInterface::STOP_RULES_PROCESSING => false, + RuleInterface::SORT_ORDER => 0, + 'sub_is_enable' => 0, + 'sub_discount_amount' => 0, + ] + ] +); +$catalogRuleRepository->save($catalogRule); +$indexBuilder->reindexFull(); diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/catalog_rule_6_off_logged_user_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/catalog_rule_6_off_logged_user_rollback.php new file mode 100644 index 0000000000000..37c2c8c1f2173 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/catalog_rule_6_off_logged_user_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\CatalogRule\Api\CatalogRuleRepositoryInterface; +use Magento\CatalogRule\Model\Indexer\IndexBuilder; +use Magento\CatalogRule\Model\ResourceModel\Rule\CollectionFactory; +use Magento\CatalogRule\Model\Rule; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var IndexBuilder $indexBuilder */ +$indexBuilder = $objectManager->get(IndexBuilder::class); +/** @var CatalogRuleRepositoryInterface $ruleRepository */ +$ruleRepository = $objectManager->create(CatalogRuleRepositoryInterface::class); +/** @var CollectionFactory $ruleCollectionFactory */ +$ruleCollectionFactory = $objectManager->get(CollectionFactory::class); +$ruleCollection = $ruleCollectionFactory->create(); +$ruleCollection->addFieldToFilter('name', ['eq' => 'Test Catalog Rule for logged user']); +$ruleCollection->setPageSize(1); +/** @var Rule $rule */ +$rule = $ruleCollection->getFirstItem(); +if ($rule->getId()) { + $ruleRepository->delete($rule); +} +$indexBuilder->reindexFull(); diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/ResourceModel/Fulltext/CollectionTest.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/ResourceModel/Fulltext/CollectionTest.php index 8863834078214..55465e938e71b 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/ResourceModel/Fulltext/CollectionTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/ResourceModel/Fulltext/CollectionTest.php @@ -40,6 +40,7 @@ public function testLoadWithFilterSearch($request, $filters, $expectedCount) /** * @dataProvider filtersDataProviderQuickSearch * @magentoDataFixture Magento/Framework/Search/_files/products.php + * @magentoAppIsolation enabled */ public function testLoadWithFilterQuickSearch($filters, $expectedCount) { @@ -61,6 +62,7 @@ public function testLoadWithFilterQuickSearch($filters, $expectedCount) /** * @dataProvider filtersDataProviderCatalogView * @magentoDataFixture Magento/Framework/Search/_files/products.php + * @magentoAppIsolation enabled */ public function testLoadWithFilterCatalogView($filters, $expectedCount) { @@ -78,6 +80,7 @@ public function testLoadWithFilterCatalogView($filters, $expectedCount) /** * @magentoDataFixture Magento/Framework/Search/_files/products_with_the_same_search_score.php + * @magentoAppIsolation enabled */ public function testSearchResultsAreTheSameForSameRequests() { @@ -142,4 +145,157 @@ public function filtersDataProviderCatalogView() [[], 5], ]; } + + /** + * Test configurable product with multiple options + * + * @magentoDataFixture Magento/CatalogSearch/_files/product_configurable_two_options.php + * @magentoConfigFixture default/catalog/search/engine mysql + * @magentoDataFixture Magento/CatalogSearch/_files/full_reindex.php + * @magentoAppIsolation enabled + * @dataProvider configurableProductWithMultipleOptionsDataProvider + * @param array $filters + * @param bool $found + * @param array $outOfStock + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function testConfigurableProductWithMultipleOptions(array $filters, bool $found, array $outOfStock = []) + { + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + /**@var $stockRegistry \Magento\CatalogInventory\Model\StockRegistry */ + $stockRegistry = $objectManager->get(\Magento\CatalogInventory\Model\StockRegistry::class); + /**@var $stockItemRepository \Magento\CatalogInventory\Api\StockItemRepositoryInterface */ + $stockItemRepository = $objectManager->get(\Magento\CatalogInventory\Api\StockItemRepositoryInterface::class); + $collection = $objectManager->create( + \Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection::class, + ['searchRequestName' => 'filter_by_configurable_product_options'] + ); + foreach ($outOfStock as $sku) { + $stockItem = $stockRegistry->getStockItemBySku($sku); + $stockItem->setQty(0); + $stockItem->setIsInStock(0); + $stockItemRepository->save($stockItem); + } + + $options = ['test_configurable', 'test_configurable_2']; + foreach ($options as $option) { + if (isset($filters[$option])) { + $filters[$option] = $this->getOptionValue($option, $filters[$option]); + } + } + $filters['category_ids'] = 2; + foreach ($filters as $field => $value) { + $collection->addFieldToFilter($field, $value); + } + $collection->load(); + $items = $collection->getItems(); + if ($found) { + $this->assertCount(1, $items); + $item = array_shift($items); + $this->assertEquals('configurable_with_2_opts', $item['sku']); + } + $this->assertCount(0, $items); + } + + /** + * Provide filters to test configurable product with multiple options + * + * @return array + */ + public function configurableProductWithMultipleOptionsDataProvider(): array + { + return [ + [ + [], + true + ], + [ + ['test_configurable' => 'Option 1'], + true + ], + [ + ['test_configurable' => 'Option 2'], + true + ], + [ + ['test_configurable_2' => 'Option 1'], + true + ], + [ + ['test_configurable_2' => 'Option 2'], + true + ], + [ + ['test_configurable' => 'Option 1', 'test_configurable_2' => 'Option 1'], + true + ], + [ + ['test_configurable' => 'Option 1', 'test_configurable_2' => 'Option 2'], + true + ], + [ + ['test_configurable' => 'Option 2', 'test_configurable_2' => 'Option 1'], + true + ], + [ + ['test_configurable' => 'Option 2', 'test_configurable_2' => 'Option 2'], + true + ], + [ + ['test_configurable' => 'Option 2', 'test_configurable_2' => 'Option 2'], + false, + [ + 'configurable2_option_12', + 'configurable2_option_22', + ] + ], + [ + ['test_configurable' => 'Option 2', 'test_configurable_2' => 'Option 2'], + false, + [ + 'configurable2_option_21', + 'configurable2_option_22', + ] + ], + [ + ['test_configurable' => 'Option 2'], + false, + [ + 'configurable2_option_21', + 'configurable2_option_22', + ] + ], + [ + [], + false, + [ + 'configurable2_option_11', + 'configurable2_option_12', + 'configurable2_option_21', + 'configurable2_option_22', + ] + ], + ]; + } + + /** + * Get attribute option value by label + * + * @param string $attributeName + * @param string $optionLabel + * @return string|null + */ + private function getOptionValue(string $attributeName, string $optionLabel): ?string + { + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $eavConfig = $objectManager->get(\Magento\Eav\Model\Config::class); + $attribute = $eavConfig->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $attributeName); + $option = null; + foreach ($attribute->getOptions() as $option) { + if ($option->getLabel() === $optionLabel) { + return $option->getValue(); + } + } + return null; + } } diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/_files/product_configurable_two_options.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/_files/product_configurable_two_options.php new file mode 100644 index 0000000000000..67bd8b831b878 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/_files/product_configurable_two_options.php @@ -0,0 +1,166 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +require __DIR__ . '/../../../Magento/ConfigurableProduct/_files/configurable_attribute.php'; +require __DIR__ . '/../../../Magento/ConfigurableProduct/_files/configurable_attribute_2.php'; + +use Magento\Catalog\Api\CategoryLinkManagementInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Setup\CategorySetup; +use Magento\CatalogInventory\Model\Stock\Item; +use Magento\ConfigurableProduct\Helper\Product\Options\Factory; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\Eav\Api\AttributeRepositoryInterface; +use Magento\Eav\Api\Data\AttributeOptionInterface; +use Magento\Framework\Search\Request\Config; +use Magento\Framework\Search\Request\Config\Converter; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var AttributeRepositoryInterface $attributeRepository */ +$attributeRepository = $objectManager->create(AttributeRepositoryInterface::class); +/** @var \Magento\Eav\Model\Config $eavConfig */ +$eavConfig = $objectManager->get(\Magento\Eav\Model\Config::class); +$attributes = ['test_configurable', 'test_configurable_2']; +foreach ($attributes as $attributeName) { + $attributeModel = $eavConfig->getAttribute(Product::ENTITY, $attributeName); + $attributeModel->addData([ + 'is_searchable' => 1, + 'is_filterable' => 1, + 'is_filterable_in_search' => 1, + 'is_visible_in_advanced_search' => 1, + ]); + $attributeRepository->save($attributeModel); +} + +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(ProductRepositoryInterface::class); + +/** @var $installer CategorySetup */ +$installer = $objectManager->create(CategorySetup::class); + +/** @var AttributeOptionInterface[] $options */ +$options = $attribute->getOptions(); + +$attribute1Values = []; +$attribute2Values = []; +$attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default'); +$associatedProductIds = []; +array_shift($options); +$index1 = 1; +foreach ($options as $option1) { + /** @var AttributeOptionInterface[] $options */ + $options2 = $attribute2->getOptions(); + array_shift($options2); + $index2 = 1; + foreach ($options2 as $option2) { + /** @var $product Product */ + $product = $objectManager->create(Product::class); + $product->setTypeId(Type::TYPE_SIMPLE) + ->setAttributeSetId($attributeSetId) + ->setWebsiteIds([1]) + ->setName('Configurable2 Option' . $index1 . $index2) + ->setSku('configurable2_option_' . $index1 . $index2) + ->setPrice(random_int(10, 100)) + ->setTestConfigurable($option1->getValue()) + ->setTestConfigurable2($option2->getValue()) + ->setVisibility(Visibility::VISIBILITY_NOT_VISIBLE) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); + + $product = $productRepository->save($product); + + /** @var Item $stockItem */ + $stockItem = $objectManager->create(Item::class); + $stockItem->load($product->getId(), 'product_id'); + + if (!$stockItem->getProductId()) { + $stockItem->setProductId($product->getId()); + } + $stockItem->setUseConfigManageStock(1); + $stockItem->setQty(1000); + $stockItem->setIsQtyDecimal(0); + $stockItem->setIsInStock(1); + $stockItem->save(); + + $attribute1Values[] = [ + 'label' => 'test1', + 'attribute_id' => $attribute->getId(), + 'value_index' => $option1->getValue(), + ]; + $attribute2Values[] = [ + 'label' => 'test2', + 'attribute_id' => $attribute2->getId(), + 'value_index' => $option2->getValue(), + ]; + $associatedProductIds[] = $product->getId(); + $index2++; + } + $index1++; +} + +/** @var $product Product */ +$product = $objectManager->create(Product::class); + +/** @var Factory $optionsFactory */ +$optionsFactory = $objectManager->create(Factory::class); + +$configurableAttributesData = [ + [ + 'attribute_id' => $attribute->getId(), + 'code' => $attribute->getAttributeCode(), + 'label' => $attribute->getStoreLabel(), + 'position' => '0', + 'values' => $attribute1Values, + ], + [ + 'attribute_id' => $attribute2->getId(), + 'code' => $attribute2->getAttributeCode(), + 'label' => $attribute2->getStoreLabel(), + 'position' => '1', + 'values' => $attribute2Values, + ], +]; + +$configurableOptions = $optionsFactory->create($configurableAttributesData); + +$extensionConfigurableAttributes = $product->getExtensionAttributes(); +$extensionConfigurableAttributes->setConfigurableProductOptions($configurableOptions); +$extensionConfigurableAttributes->setConfigurableProductLinks($associatedProductIds); + +$product->setExtensionAttributes($extensionConfigurableAttributes); + +$product->setTypeId(Configurable::TYPE_CODE) + ->setAttributeSetId($attributeSetId) + ->setWebsiteIds([1]) + ->setName('configurable with 2 opts') + ->setSku('configurable_with_2_opts') + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 1, 'is_in_stock' => 1]); + +$productRepository->save($product); + +/** @var CategoryLinkManagementInterface $categoryLinkManagement */ +$categoryLinkManagement = $objectManager->create(CategoryLinkManagementInterface::class); + +$categoryLinkManagement->assignProductToCategories( + $product->getSku(), + [2] +); + +/** @var Converter $converter */ +$converter = $objectManager->create(Converter::class); +$document = new DOMDocument(); +$document->load(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'requests.xml'); +$requestConfig = $converter->convert($document); +/** @var Config $config */ +$config = $objectManager->get(Config::class); +$config->merge($requestConfig); diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/_files/product_configurable_two_options_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/_files/product_configurable_two_options_rollback.php new file mode 100644 index 0000000000000..4bb2ea4fa8178 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/_files/product_configurable_two_options_rollback.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\CatalogInventory\Model\Stock\Status; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); + +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +$list = [ + 'configurable2_option_11', + 'configurable2_option_12', + 'configurable2_option_21', + 'configurable2_option_22', + 'configurable_with_2_opts' +]; + +foreach ($list as $sku) { + try { + $product = $productRepository->get($sku, true); + + $stockStatus = $objectManager->create(Status::class); + $stockStatus->load($product->getEntityId(), 'product_id'); + $stockStatus->delete(); + + $productRepository->delete($product); + } catch (NoSuchEntityException $e) { + //Product already removed + } +} + +require __DIR__ . '/../../../Magento/ConfigurableProduct/_files/configurable_attribute_rollback.php'; +require __DIR__ . '/../../../Magento/ConfigurableProduct/_files/configurable_attribute_2_rollback.php'; + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/_files/requests.xml b/dev/tests/integration/testsuite/Magento/CatalogSearch/_files/requests.xml new file mode 100644 index 0000000000000..660aa36cc291d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/_files/requests.xml @@ -0,0 +1,38 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<requests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Search/etc/search_request_merged.xsd"> + <request query="filter_by_configurable_product_options" index="catalogsearch_fulltext"> + <dimensions> + <dimension name="scope" value="default"/> + </dimensions> + <queries> + <query xsi:type="boolQuery" name="filter_by_configurable_product_options" boost="1"> + <queryReference clause="must" ref="category"/> + <queryReference clause="must" ref="test_configurable"/> + <queryReference clause="must" ref="test_configurable_2"/> + </query> + <query xsi:type="filteredQuery" name="category"> + <filterReference clause="must" ref="category_filter"/> + </query> + <query xsi:type="filteredQuery" name="test_configurable"> + <filterReference clause="must" ref="test_configurable_filter"/> + </query> + <query xsi:type="filteredQuery" name="test_configurable_2"> + <filterReference clause="must" ref="test_configurable_2_filter"/> + </query> + </queries> + <filters> + <filter xsi:type="termFilter" name="category_filter" field="category_ids" value="$category_ids$"/> + <filter xsi:type="termFilter" name="test_configurable_filter" field="test_configurable" value="$test_configurable$"/> + <filter xsi:type="termFilter" name="test_configurable_2_filter" field="test_configurable_2" value="$test_configurable_2$"/> + </filters> + <aggregations/> + <from>0</from> + <size>10</size> + </request> +</requests> diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Model/CategoryUrlRewriteGeneratorTest.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Model/CategoryUrlRewriteGeneratorTest.php index b6fa2fac2ca80..67a1e0bb43ecb 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Model/CategoryUrlRewriteGeneratorTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Model/CategoryUrlRewriteGeneratorTest.php @@ -351,7 +351,7 @@ private function getCountOfRewrites($productId, $categoryId): string $model = $this->objectManager->get(Product::class); $connection = $model->getConnection(); $select = $connection->select(); - $select->from(Product::TABLE_NAME, 'COUNT(*)'); + $select->from($model->getTable(Product::TABLE_NAME), 'COUNT(*)'); $select->where('category_id = ?', $categoryId); $select->where('product_id = ?', $productId); return $connection->fetchOne($select); diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/ResourceModel/AddressRepositoryTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/ResourceModel/AddressRepositoryTest.php index 299d09d9400de..fb88a66423e65 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Model/ResourceModel/AddressRepositoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/ResourceModel/AddressRepositoryTest.php @@ -40,6 +40,8 @@ class AddressRepositoryTest extends \PHPUnit\Framework\TestCase /** @var \Magento\Framework\Api\DataObjectHelper */ private $dataObjectHelper; + private $customerRegistry; + /** * Set up. */ @@ -56,6 +58,7 @@ protected function setUp() \Magento\Customer\Api\Data\AddressInterfaceFactory::class ); $this->dataObjectHelper = $this->objectManager->create(\Magento\Framework\Api\DataObjectHelper::class); + $this->customerRegistry = $this->objectManager->get(\Magento\Customer\Model\CustomerRegistry::class); $regionFactory = $this->objectManager->get(RegionInterfaceFactory::class); $region = $regionFactory->create() @@ -96,10 +99,7 @@ protected function setUp() */ protected function tearDown() { - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - /** @var \Magento\Customer\Model\CustomerRegistry $customerRegistry */ - $customerRegistry = $objectManager->get(\Magento\Customer\Model\CustomerRegistry::class); - $customerRegistry->remove(1); + $this->customerRegistry->remove(1); } /** @@ -506,6 +506,60 @@ public function testSaveAddressWithRestrictedCountries() self::assertNotEmpty($saved->getId()); } + /** + * Test for saving address with extra spaces in phone. + * + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDataFixture Magento/Customer/_files/customer_address.php + */ + public function testSaveNewAddressWithExtraSpacesInPhone() + { + $proposedAddress = $this->_createSecondAddress() + ->setCustomerId(1) + ->setTelephone(' 123456 '); + $returnedAddress = $this->repository->save($proposedAddress); + $savedAddress = $this->repository->getById($returnedAddress->getId()); + $this->assertEquals('123456', $savedAddress->getTelephone()); + } + + /** + * Scenario for customer's default shipping and billing address saving and rollback. + * + * @magentoDataFixture Magento/Customer/_files/customer_without_addresses.php + */ + public function testCustomerAddressRelationSynchronisation() + { + /** + * Creating new address which is default shipping and billing for existing customer. + */ + $address = $this->expectedAddresses[0]; + $address->setId(null); + $address->setCustomerId(1); + $address->setIsDefaultShipping(true); + $address->setIsDefaultBilling(true); + $savedAddress = $this->repository->save($address); + + /** + * Customer registry should be updated with default shipping and billing addresses. + */ + $customer = $this->getCustomer('customer@example.com', 1); + $this->assertEquals($savedAddress->getId(), $customer->getDefaultShipping()); + $this->assertEquals($savedAddress->getId(), $customer->getDefaultBilling()); + + /** + * Registry should be clean up for reading data from DB. + */ + $this->repository->deleteById($savedAddress->getId()); + $this->customerRegistry->removeByEmail('customer@example.com'); + + /** + * Customer's default shipping and billing addresses should be updated. + */ + $customer = $this->getCustomer('customer@example.com', 1); + $this->assertNull($customer->getDefaultShipping()); + $this->assertNull($customer->getDefaultBilling()); + } + /** * Helper function that returns an Address Data Object that matches the data from customer_address fixture * @@ -571,20 +625,4 @@ private function getWebsite(string $code): WebsiteInterface $repository = $this->objectManager->get(WebsiteRepositoryInterface::class); return $repository->get($code); } - - /** - * Test for saving address with extra spaces in phone. - * - * @magentoDataFixture Magento/Customer/_files/customer.php - * @magentoDataFixture Magento/Customer/_files/customer_address.php - */ - public function testSaveNewAddressWithExtraSpacesInPhone() - { - $proposedAddress = $this->_createSecondAddress() - ->setCustomerId(1) - ->setTelephone(' 123456 '); - $returnedAddress = $this->repository->save($proposedAddress); - $savedAddress = $this->repository->getById($returnedAddress->getId()); - $this->assertEquals('123456', $savedAddress->getTelephone()); - } } diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_with_uk_address.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_with_uk_address.php new file mode 100644 index 0000000000000..a7ad0bb82719f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_with_uk_address.php @@ -0,0 +1,78 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Customer\Api\AddressMetadataInterface; +use Magento\Customer\Api\AddressRepositoryInterface; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Api\Data\AddressInterface; +use Magento\Customer\Model\Address; +use Magento\Customer\Model\AddressFactory; +use Magento\Customer\Model\CustomerFactory; +use Magento\Customer\Model\CustomerRegistry; +use Magento\Customer\Model\AddressRegistry; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\Store\Model\Website; +use Magento\Store\Model\WebsiteRepository; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var CustomerRepositoryInterface $customerRepository */ +$customerRepository = $objectManager->create(CustomerRepositoryInterface::class); +/** @var CustomerFactory $customerFactory */ +$customerFactory = $objectManager->get(CustomerFactory::class); +$customer = $customerFactory->create(); +/** @var CustomerRegistry $customerRegistry */ +$customerRegistry = $objectManager->get(CustomerRegistry::class); +/** @var WebsiteRepository $websiteRepository */ +$websiteRepository = $objectManager->create(WebsiteRepositoryInterface::class); +/** @var Website $mainWebsite */ +$mainWebsite = $websiteRepository->get('base'); +$customer->setWebsiteId($mainWebsite->getId()) + ->setEmail('customer_uk_address@test.com') + ->setPassword('password') + ->setGroupId(1) + ->setStoreId($mainWebsite->getDefaultStore()->getId()) + ->setIsActive(1) + ->setPrefix('Mr.') + ->setFirstname('John') + ->setMiddlename('A') + ->setLastname('Smith') + ->setSuffix('Esq.') + ->setTaxvat('12') + ->setGender(0); +/** @var AddressFactory $customerAddressFactory */ +$customerAddressFactory = $objectManager->get(AddressFactory::class); +/** @var AddressRepositoryInterface $customerAddressRepository */ +$customerAddressRepository = $objectManager->create(AddressRepositoryInterface::class); +/** @var Address $customerAddress */ +$customerAddress = $customerAddressFactory->create(); +$customerAddress->isObjectNew(true); +$customerAddress->setData( + [ + 'attribute_set_id' => AddressMetadataInterface::ATTRIBUTE_SET_ID_ADDRESS, + AddressInterface::TELEPHONE => 3468676, + AddressInterface::POSTCODE => 'EC1A 1AA', + AddressInterface::COUNTRY_ID => 'GB', + AddressInterface::CITY => 'London', + AddressInterface::COMPANY => 'CompanyName', + AddressInterface::STREET => 'test street address', + AddressInterface::LASTNAME => 'Smith', + AddressInterface::FIRSTNAME => 'John', + AddressInterface::REGION_ID => 1, + ] +); +$customer->addAddress($customerAddress); +$customer->isObjectNew(true); +$customerDataModel = $customerRepository->save($customer->getDataModel()); +$addressId = $customerDataModel->getAddresses()[0]->getId(); +$customerDataModel->setDefaultShipping($addressId); +$customerDataModel->setDefaultBilling($addressId); +$customerRepository->save($customerDataModel); +$customerRegistry->remove($customerDataModel->getId()); +/** @var AddressRegistry $addressRegistry */ +$addressRegistry = $objectManager->get(AddressRegistry::class); +$addressRegistry->remove($customerAddress->getId()); diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_with_uk_address_rollback.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_with_uk_address_rollback.php new file mode 100644 index 0000000000000..e00d80833ea04 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_with_uk_address_rollback.php @@ -0,0 +1,30 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Customer\Api\AddressRepositoryInterface; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); +/** @var CustomerRepositoryInterface $customerRepository */ +$customerRepository = $objectManager->get(CustomerRepositoryInterface::class); + +try { + $customer = $customerRepository->get('customer_uk_address@test.com'); + $customerRepository->delete($customer); +} catch (NoSuchEntityException $exception) { + //Already deleted +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Eav/_files/product_texteditor_attribute.php b/dev/tests/integration/testsuite/Magento/Eav/_files/product_texteditor_attribute.php new file mode 100644 index 0000000000000..1ac4a35e8c6a5 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Eav/_files/product_texteditor_attribute.php @@ -0,0 +1,51 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory; +use Magento\Eav\Setup\EavSetup; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Catalog\Model\Product\Attribute\Frontend\Inputtype\Presentation; + +$objectManager = Bootstrap::getObjectManager(); +/** @var AttributeFactory $attributeFactory */ +$attributeFactory = $objectManager->get(AttributeFactory::class); +/** @var ProductAttributeRepositoryInterface $productAttributeRepository */ +$productAttributeRepository = $objectManager->get(ProductAttributeRepositoryInterface::class); +$attribute = $attributeFactory->create()->loadByCode(Product::ENTITY, 'text_editor_attribute'); +if (!$attribute->getId()) { + /** @var Presentation $presentation */ + $presentation = $objectManager->get(Presentation::class); + /** @var EavSetup $installer */ + $installer = $objectManager->create(EavSetup::class); + $attributeData = [ + 'attribute_code' => 'text_editor_attribute', + 'is_global' => 1, + 'is_user_defined' => 1, + 'frontend_input' => 'texteditor', + 'is_unique' => 0, + 'is_required' => 0, + 'is_searchable' => 0, + 'is_visible_in_advanced_search' => 0, + 'is_comparable' => 0, + 'is_filterable' => 0, + 'is_filterable_in_search' => 0, + 'is_used_for_promo_rules' => 0, + 'is_html_allowed_on_front' => 1, + 'is_visible_on_front' => 0, + 'used_in_product_listing' => 0, + 'used_for_sort_by' => 0, + 'frontend_label' => ['Text editor attribute'], + 'backend_type' => 'text', + ]; + $attribute->setData($presentation->convertPresentationDataToInputType($attributeData)); + $productAttributeRepository->save($attribute); + $attribute = $productAttributeRepository->get('text_editor_attribute'); + /* Assign attribute to attribute set */ + $installer->addAttributeToGroup(Product::ENTITY, 'Default', 'Attributes', $attribute->getId()); +} diff --git a/dev/tests/integration/testsuite/Magento/Eav/_files/product_texteditor_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Eav/_files/product_texteditor_attribute_rollback.php new file mode 100644 index 0000000000000..e3b1fa0202a44 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Eav/_files/product_texteditor_attribute_rollback.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductAttributeRepositoryInterface $productAttributeRepository */ +$productAttributeRepository = $objectManager->get(ProductAttributeRepositoryInterface::class); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); +try { + $attribute = $productAttributeRepository->get('text_editor_attribute'); + $productAttributeRepository->delete($attribute); +} catch (NoSuchEntityException $e) { + //Attribute already deleted. +} +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch6/Controller/QuickSearchTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch6/Controller/QuickSearchTest.php new file mode 100644 index 0000000000000..637d3d1a9d252 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch6/Controller/QuickSearchTest.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch6\Controller; + +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\TestCase\AbstractController; + +/** + * Tests quick search on Storefront. + */ +class QuickSearchTest extends AbstractController +{ + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->storeManager = $this->_objectManager->get(StoreManagerInterface::class); + } + + /** + * Tests quick search with "Price Navigation Step Calculation" sets to "Automatic (equalize product counts)". + * + * @magentoAppArea frontend + * @magentoDbIsolation disabled + * @magentoConfigFixture fixturestore_store catalog/layered_navigation/price_range_calculation improved + * @magentoConfigFixture fixturestore_store catalog/layered_navigation/one_price_interval 1 + * @magentoConfigFixture fixturestore_store catalog/layered_navigation/interval_division_limit 1 + * @magentoConfigFixture default/catalog/search/engine elasticsearch6 + * @magentoConfigFixture default_store catalog/search/elasticsearch6_index_prefix storefront_quick_search + * @magentoConfigFixture fixturestore_store catalog/search/elasticsearch6_index_prefix storefront_quick_search + * @magentoDataFixture Magento/Catalog/_files/products_for_search.php + * @magentoDataFixture Magento/Store/_files/core_fixturestore.php + * @magentoDataFixture Magento/CatalogSearch/_files/full_reindex.php + */ + public function testQuickSearchWithImprovedPriceRangeCalculation() + { + $secondStore = $this->storeManager->getStore('fixturestore'); + $this->storeManager->setCurrentStore($secondStore); + + try { + $this->dispatch('/catalogsearch/result/?q=search+product'); + $responseBody = $this->getResponse()->getBody(); + } finally { + $defaultStore = $this->storeManager->getStore('default'); + $this->storeManager->setCurrentStore($defaultStore); + } + + $this->assertContains('search product 1', $responseBody); + } +} diff --git a/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Export/File/DeleteTest.php b/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Export/File/DeleteTest.php index 91764684da173..6b1463aa1a9df 100644 --- a/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Export/File/DeleteTest.php +++ b/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Export/File/DeleteTest.php @@ -29,6 +29,16 @@ class DeleteTest extends AbstractBackendController */ private $fileName = 'catalog_product.csv'; + /** + * @var Filesystem + */ + private $fileSystem; + + /** + * @var string + */ + private $sourceFilePath; + /** * @inheritdoc */ @@ -36,35 +46,61 @@ protected function setUp() { parent::setUp(); - $filesystem = $this->_objectManager->get(Filesystem::class); - $sourceFilePath = __DIR__ . '/../../Import/_files' . DIRECTORY_SEPARATOR . $this->fileName; - $destinationFilePath = 'export' . DIRECTORY_SEPARATOR . $this->fileName; + $this->fileSystem = $this->_objectManager->get(Filesystem::class); + $this->sourceFilePath = __DIR__ . '/../../Import/_files' . DIRECTORY_SEPARATOR . $this->fileName; //Refers to tests 'var' directory - $this->varDirectory = $filesystem->getDirectoryRead(DirectoryList::VAR_DIR); - //Refers to application root directory - $rootDirectory = $filesystem->getDirectoryWrite(DirectoryList::ROOT); - $rootDirectory->copyFile($sourceFilePath, $this->varDirectory->getAbsolutePath($destinationFilePath)); + $this->varDirectory = $this->fileSystem->getDirectoryRead(DirectoryList::VAR_DIR); } /** * Check that file can be removed under var/export directory. * + * @param string $file + * @dataProvider testExecuteProvider * @return void * @magentoConfigFixture default_store admin/security/use_form_key 1 */ - public function testExecute(): void + public function testExecute($file): void { + $fullPath = 'export/' . $file; + $this->copyFile($fullPath); $request = $this->getRequest(); - $request->setParam('filename', $this->fileName); + $request->setParam('filename', $file); $request->setMethod(Http::METHOD_POST); - if ($this->varDirectory->isExist('export/' . $this->fileName)) { + if ($this->varDirectory->isExist($fullPath)) { $this->dispatch('backend/admin/export_file/delete'); } else { throw new \AssertionError('Export product file supposed to exist'); } - $this->assertFalse($this->varDirectory->isExist('export/' . $this->fileName)); + $this->assertFalse($this->varDirectory->isExist($fullPath)); + } + + /** + * Copy csv file from sourceFilePath to destinationFilePath + * + * @param $destinationFilePath + * @return void + */ + private function copyFile($destinationFilePath): void + { + //Refers to application root directory + $rootDirectory = $this->fileSystem->getDirectoryWrite(DirectoryList::ROOT); + $rootDirectory->copyFile($this->sourceFilePath, $this->varDirectory->getAbsolutePath($destinationFilePath)); + } + + /** + * Csv file path for copying from sourceFilePath and for future deleting + * + * @return array + */ + public static function testExecuteProvider(): array + { + return [ + ['catalog_product.csv'], + ['test/catalog_product.csv'] + ]; } /** diff --git a/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Export/File/DownloadTest.php b/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Export/File/DownloadTest.php index 073ecc6fd06a4..578df4290a99e 100644 --- a/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Export/File/DownloadTest.php +++ b/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Export/File/DownloadTest.php @@ -7,7 +7,6 @@ namespace Magento\ImportExport\Controller\Adminhtml\Export\File; -use Magento\Backend\Model\Auth\Session; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\App\Request\Http; use Magento\Framework\Filesystem; @@ -16,7 +15,6 @@ use Magento\TestFramework\TestCase\AbstractBackendController; use Magento\Backend\Model\UrlInterface as BackendUrl; use Magento\Backend\Model\Auth; -use Magento\TestFramework\Bootstrap as TestBootstrap; /** * Test for \Magento\ImportExport\Controller\Adminhtml\Export\File\Download class. @@ -28,11 +26,6 @@ class DownloadTest extends AbstractBackendController */ private $fileName = 'catalog_product.csv'; - /** - * @var string - */ - private $filesize; - /** * @var Auth */ @@ -43,6 +36,21 @@ class DownloadTest extends AbstractBackendController */ private $backendUrl; + /** + * @var WriteInterface + */ + private $varDirectory; + + /** + * @var Filesystem + */ + private $fileSystem; + + /** + * @var string + */ + private $sourceFilePath; + /** * @inheritdoc */ @@ -50,31 +58,29 @@ protected function setUp() { parent::setUp(); - $filesystem = $this->_objectManager->get(Filesystem::class); + $this->fileSystem = $this->_objectManager->get(Filesystem::class); $auth = $this->_objectManager->get(Auth::class); $auth->getAuthStorage()->setIsFirstPageAfterLogin(false); $this->backendUrl = $this->_objectManager->get(BackendUrl::class); $this->backendUrl->turnOnSecretKey(); - - $sourceFilePath = __DIR__ . '/../../Import/_files' . DIRECTORY_SEPARATOR . $this->fileName; - $destinationFilePath = 'export' . DIRECTORY_SEPARATOR . $this->fileName; + $this->sourceFilePath = __DIR__ . '/../../Import/_files' . DIRECTORY_SEPARATOR . $this->fileName; //Refers to tests 'var' directory - $varDirectory = $filesystem->getDirectoryRead(DirectoryList::VAR_DIR); - //Refers to application root directory - $rootDirectory = $filesystem->getDirectoryWrite(DirectoryList::ROOT); - $rootDirectory->copyFile($sourceFilePath, $varDirectory->getAbsolutePath($destinationFilePath)); - $this->filesize = $varDirectory->stat($destinationFilePath)['size']; + $this->varDirectory = $this->fileSystem->getDirectoryRead(DirectoryList::VAR_DIR); } /** * Check that file can be downloaded. * + * @param string $file + * @dataProvider testExecuteProvider * @return void * @magentoConfigFixture default_store admin/security/use_form_key 1 * @magentoAppArea adminhtml */ - public function testExecute(): void + public function testExecute($file): void { + $this->copyFile('export/' . $file); + $fileSize = $this->varDirectory->stat('export/' . $file)['size']; $request = $this->getRequest(); list($routeName, $controllerName, $actionName) = explode('/', Download::URL); $request->setMethod(Http::METHOD_GET) @@ -104,12 +110,38 @@ public function testExecute(): void 'Incorrect response header "content-disposition"' ); $this->assertEquals( - $this->filesize, + $fileSize, $contentLength->getFieldValue(), 'Incorrect response header "content-length"' ); } + /** + * Copy csv file from sourceFilePath to destinationFilePath + * + * @param $destinationFilePath + * @return void + */ + private function copyFile($destinationFilePath): void + { + //Refers to application root directory + $rootDirectory = $this->fileSystem->getDirectoryWrite(DirectoryList::ROOT); + $rootDirectory->copyFile($this->sourceFilePath, $this->varDirectory->getAbsolutePath($destinationFilePath)); + } + + /** + * Csv file path for copying from sourceFilePath and for future deleting + * + * @return array + */ + public static function testExecuteProvider(): array + { + return [ + ['catalog_product.csv'], + ['test/catalog_product.csv'] + ]; + } + /** * @inheritdoc */ diff --git a/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/AbstractFiltersTest.php b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/AbstractFiltersTest.php index 472eac444b9d4..fed8c76852872 100644 --- a/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/AbstractFiltersTest.php +++ b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/AbstractFiltersTest.php @@ -74,22 +74,9 @@ protected function setUp() $this->objectManager = Bootstrap::getObjectManager(); $this->categoryCollectionFactory = $this->objectManager->create(CollectionFactory::class); $this->layout = $this->objectManager->get(LayoutInterface::class); - $layerResolver = $this->objectManager->create(Resolver::class); - - if ($this->getLayerType() === Resolver::CATALOG_LAYER_SEARCH) { - $layerResolver->create(Resolver::CATALOG_LAYER_SEARCH); - $this->navigationBlock = $this->objectManager->create( - SearchNavigationBlock::class, - [ - 'layerResolver' => $layerResolver, - ] - ); - } else { - $this->navigationBlock = $this->objectManager->create(CategoryNavigationBlock::class); - } - $this->attributeRepository = $this->objectManager->create(ProductAttributeRepositoryInterface::class); $this->productRepository = $this->objectManager->create(ProductRepositoryInterface::class); + $this->createNavigationBlockInstance(); } /** @@ -121,7 +108,7 @@ protected function getCategoryFiltersAndAssert( array $expectation, string $categoryName ): void { - $this->updateAttribute($this->getAttributeCode(), $attributeData); + $this->updateAttribute($attributeData); $this->updateProducts($products, $this->getAttributeCode()); $this->clearInstanceAndReindexSearch(); $category = $this->loadCategory($categoryName, Store::DEFAULT_STORE_ID); @@ -150,7 +137,7 @@ protected function getSearchFiltersAndAssert( array $attributeData, array $expectation ): void { - $this->updateAttribute($this->getAttributeCode(), $attributeData); + $this->updateAttribute($attributeData); $this->updateProducts($products, $this->getAttributeCode()); $this->clearInstanceAndReindexSearch(); $this->navigationBlock->getRequest()->setParams(['q' => 'Simple Product']); @@ -188,15 +175,13 @@ function (AbstractFilter $filter) use ($code) { /** * Updates attribute data. * - * @param string $attributeCode * @param array $data * @return void */ protected function updateAttribute( - string $attributeCode, array $data ): void { - $attribute = $this->attributeRepository->get($attributeCode); + $attribute = $this->attributeRepository->get($this->getAttributeCode()); $attribute->addData($data); $this->attributeRepository->save($attribute); } @@ -226,14 +211,18 @@ protected function prepareFilterItems(AbstractFilter $filter): array * * @param array $products * @param string $attributeCode + * @param int $storeId * @return void */ - protected function updateProducts(array $products, string $attributeCode): void - { + protected function updateProducts( + array $products, + string $attributeCode, + int $storeId = Store::DEFAULT_STORE_ID + ): void { $attribute = $this->attributeRepository->get($attributeCode); foreach ($products as $productSku => $stringValue) { - $product = $this->productRepository->get($productSku, false, Store::DEFAULT_STORE_ID, true); + $product = $this->productRepository->get($productSku, false, $storeId, true); $product->addData( [$attribute->getAttributeCode() => $attribute->getSource()->getOptionId($stringValue)] ); @@ -275,4 +264,26 @@ protected function loadCategory(string $categoryName, int $storeId): CategoryInt return $category; } + + /** + * Creates navigation block instance. + * + * @return void + */ + protected function createNavigationBlockInstance(): void + { + $layerResolver = $this->objectManager->create(Resolver::class); + + if ($this->getLayerType() === Resolver::CATALOG_LAYER_SEARCH) { + $layerResolver->create(Resolver::CATALOG_LAYER_SEARCH); + $this->navigationBlock = $this->objectManager->create( + SearchNavigationBlock::class, + [ + 'layerResolver' => $layerResolver, + ] + ); + } else { + $this->navigationBlock = $this->objectManager->create(CategoryNavigationBlock::class); + } + } } diff --git a/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/DecimalFilterTest.php b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/DecimalFilterTest.php index c8cb6397b12fd..eb4148d77b21e 100644 --- a/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/DecimalFilterTest.php +++ b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/DecimalFilterTest.php @@ -74,12 +74,15 @@ protected function prepareFilterItems(AbstractFilter $filter): array /** * @inheritdoc */ - protected function updateProducts(array $products, string $attributeCode): void - { + protected function updateProducts( + array $products, + string $attributeCode, + int $storeId = Store::DEFAULT_STORE_ID + ): void { $attribute = $this->attributeRepository->get($attributeCode); foreach ($products as $productSku => $value) { - $product = $this->productRepository->get($productSku, false, Store::DEFAULT_STORE_ID, true); + $product = $this->productRepository->get($productSku, false, $storeId, true); $product->addData( [$attribute->getAttributeCode() => $value] ); diff --git a/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/FilterScopeTest.php b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/FilterScopeTest.php new file mode 100644 index 0000000000000..5dad3c0cafa24 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/FilterScopeTest.php @@ -0,0 +1,160 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LayeredNavigation\Block\Navigation\Category; + +use Magento\Catalog\Model\Layer\Filter\AbstractFilter; +use Magento\Catalog\Model\Layer\Resolver; +use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface; +use Magento\LayeredNavigation\Block\Navigation\AbstractFiltersTest; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Provides tests for custom filter with different scopes in navigation block on category page. + * + * @magentoAppArea frontend + * @magentoAppIsolation enabled + * @magentoDbIsolation disabled + */ +class FilterScopeTest extends AbstractFiltersTest +{ + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var int + */ + private $oldStoreId; + + /** + * @var int + */ + private $currentStoreId; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->storeManager = $this->objectManager->get(StoreManagerInterface::class); + $this->oldStoreId = (int)$this->storeManager->getStore()->getId(); + $this->currentStoreId = (int)$this->storeManager->getStore('fixture_second_store')->getId(); + } + + /** + * @magentoDataFixture Magento/Catalog/_files/product_dropdown_attribute.php + * @magentoDataFixture Magento/Catalog/_files/category_with_different_price_products_on_two_websites.php + * @dataProvider filtersWithScopeDataProvider + * @param int $scope + * @param array $products + * @param array $expectation + * @return void + */ + public function testGetFilters(int $scope, array $products, array $expectation): void + { + $this->updateAttribute( + [ + 'is_filterable' => AbstractFilter::ATTRIBUTE_OPTIONS_ONLY_WITH_RESULTS, + 'is_global' => $scope, + ] + ); + $this->updateProductsOnStore($products); + $this->clearInstanceAndReindexSearch(); + try { + $this->storeManager->setCurrentStore($this->currentStoreId); + $this->navigationBlock->getLayer()->setCurrentCategory( + $this->loadCategory('Category 999', $this->currentStoreId) + ); + $this->navigationBlock->setLayout($this->layout); + $filter = $this->getFilterByCode($this->navigationBlock->getFilters(), $this->getAttributeCode()); + $this->assertNotNull($filter); + $this->assertEquals($expectation, $this->prepareFilterItems($filter)); + } finally { + $this->storeManager->setCurrentStore($this->oldStoreId); + } + } + + /** + * @return array + */ + public function filtersWithScopeDataProvider(): array + { + return [ + 'with_scope_store' => [ + 'scope' => ScopedAttributeInterface::SCOPE_STORE, + 'products' => [ + 'default' => ['simple1000' => 'Option 1', 'simple1001' => 'Option 2'], + 'fixture_second_store' => ['simple1000' => 'Option 2', 'simple1001' => 'Option 3'], + ], + 'expectation' => [ + ['label' => 'Option 2', 'count' => 1], + ['label' => 'Option 3', 'count' => 1], + ], + ], + 'with_scope_website' => [ + 'scope' => ScopedAttributeInterface::SCOPE_WEBSITE, + 'products' => [ + 'default' => ['simple1000' => 'Option 3', 'simple1001' => 'Option 2'], + 'fixture_second_store' => ['simple1000' => 'Option 1', 'simple1001' => 'Option 2'], + ], + 'expectation' => [ + ['label' => 'Option 1', 'count' => 1], + ['label' => 'Option 2', 'count' => 1], + ], + ], + 'with_scope_global' => [ + 'scope' => ScopedAttributeInterface::SCOPE_GLOBAL, + 'products' => [ + 'default' => ['simple1000' => 'Option 1'], + 'fixture_second_store' => ['simple1001' => 'Option 2'], + ], + 'expectation' => [ + ['label' => 'Option 1', 'count' => 1], + ['label' => 'Option 2', 'count' => 1], + ], + ], + ]; + } + + /** + * @inheritdoc + */ + protected function getLayerType(): string + { + return Resolver::CATALOG_LAYER_CATEGORY; + } + + /** + * @inheritdoc + */ + protected function getAttributeCode(): string + { + return 'dropdown_attribute'; + } + + /** + * Updates products data for store. + * + * @param array $productsData + * @return void + */ + private function updateProductsOnStore(array $productsData): void + { + try { + foreach ($productsData as $storeCode => $products) { + $storeId = (int)$this->storeManager->getStore($storeCode)->getId(); + $this->storeManager->setCurrentStore($storeId); + $this->updateProducts($products, $this->getAttributeCode(), $storeId); + } + } finally { + $this->storeManager->setCurrentStore($this->oldStoreId); + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/quotes.php b/dev/tests/integration/testsuite/Magento/Sales/_files/quotes.php index 508563df2d5c0..bd0ae81b15862 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/_files/quotes.php +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/quotes.php @@ -5,6 +5,7 @@ */ declare(strict_types=1); +use Magento\Framework\App\Config; use Magento\Store\Model\StoreRepository; use Magento\Quote\Model\QuoteFactory; use Magento\Quote\Model\Quote; @@ -23,6 +24,9 @@ $quoteRepository = $objectManager->get(QuoteRepository::class); /** @var StoreRepository $storeRepository */ $storeRepository = $objectManager->get(StoreRepository::class); +/** @var Config $appConfig */ +$appConfig = $objectManager->get(Config::class); +$appConfig->clean(); /** @var Store $defaultStore */ $defaultStore = $storeRepository->getActiveStoreByCode('default'); diff --git a/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Delete/SwatchesAttributesControllerTest.php b/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Delete/SwatchesAttributesControllerTest.php new file mode 100644 index 0000000000000..1ff53b312e65a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Delete/SwatchesAttributesControllerTest.php @@ -0,0 +1,48 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Swatches\Controller\Adminhtml\Product\Attribute\Delete; + +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Delete\AbstractDeleteAttributeControllerTest; + +/** + * Delete catalog product attributes with input types like "swatch_text" and "swatch_visual". + * Attributes from Magento_Swatches module. + * + * @magentoAppArea adminhtml + * @magentoDbIsolation enabled + */ +class SwatchesAttributesControllerTest extends AbstractDeleteAttributeControllerTest +{ + /** + * Assert that attribute with input type "swatch_text" will be deleted + * after dispatch delete product attribute action. + * + * @magentoDataFixture Magento/Swatches/_files/product_text_swatch_attribute.php + * + * @return void + */ + public function testDeleteSwatchTextAttribute(): void + { + $this->dispatchDeleteAttribute('text_swatch_attribute'); + $this->assertAttributeIsDeleted('text_swatch_attribute'); + } + + /** + * Assert that attribute with input type "swatch_visual" will be deleted + * after dispatch delete product attribute action. + * + * @magentoDataFixture Magento/Swatches/_files/swatch_attribute.php + * + * @return void + */ + public function testDeleteSwatchVisualAttribute(): void + { + $this->dispatchDeleteAttribute('color_swatch'); + $this->assertAttributeIsDeleted('color_swatch'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/User/Model/ResourceModel/UserTest.php b/dev/tests/integration/testsuite/Magento/User/Model/ResourceModel/UserTest.php index 53a0b748c32f6..0eabe3a214ec2 100644 --- a/dev/tests/integration/testsuite/Magento/User/Model/ResourceModel/UserTest.php +++ b/dev/tests/integration/testsuite/Magento/User/Model/ResourceModel/UserTest.php @@ -3,40 +3,60 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\User\Model\ResourceModel; +use Magento\Authorization\Model\ResourceModel\Role\Collection as UserRoleCollection; +use Magento\Authorization\Model\ResourceModel\Role\CollectionFactory as UserRoleCollectionFactory; +use Magento\Authorization\Model\UserContextInterface; use Magento\TestFramework\Helper\Bootstrap; -use Magento\User\Model\User; use Magento\User\Model\ResourceModel\User as UserResourceModel; +use Magento\User\Model\User; +use Magento\User\Model\UserFactory; /** * @magentoAppArea adminhtml */ class UserTest extends \PHPUnit\Framework\TestCase { - /** @var UserResourceModel */ + /** + * @var UserResourceModel + */ private $model; + /** + * @var UserRoleCollectionFactory + */ + private $userRoleCollectionFactory; + + /** + * @var UserFactory + */ + private $userFactory; + + /** + * @inheritdoc + */ protected function setUp() { - $this->model = Bootstrap::getObjectManager()->get( - UserResourceModel::class - ); + $this->model = Bootstrap::getObjectManager()->get(UserResourceModel::class); + $this->userRoleCollectionFactory = Bootstrap::getObjectManager()->get(UserRoleCollectionFactory::class); + $this->userFactory = Bootstrap::getObjectManager()->get(UserFactory::class); } /** * Tests if latest password is stored after user creating * when password lifetime config value is zero (disabled as fact) * + * @return void * @magentoConfigFixture current_store admin/security/password_lifetime 0 * @magentoDataFixture Magento/User/_files/dummy_user.php */ - public function testGetLatestPasswordWhenZeroPasswordLifetime() + public function testGetLatestPasswordWhenZeroPasswordLifetime(): void { /** @var User $user */ - $user = Bootstrap::getObjectManager()->create( - User::class - ); + $user = $this->userFactory->create(); $user->loadByUsername('dummy_username'); $latestPassword = $this->model->getLatestPassword($user->getId()); @@ -46,12 +66,49 @@ public function testGetLatestPasswordWhenZeroPasswordLifetime() ); } - public function testCountAll() + /** + * Test that user role is not deleted after deleting empty user. + * + * @return void + */ + public function testDelete(): void + { + $this->checkRoleCollectionSize(); + /** @var User $user */ + $user = $this->userFactory->create(); + $this->model->delete($user); + $this->checkRoleCollectionSize(); + } + + /** + * Ensure that role collection size is correct. + * + * @return void + */ + private function checkRoleCollectionSize(): void + { + /** @var UserRoleCollection $roleCollection */ + $roleCollection = $this->userRoleCollectionFactory->create(); + $roleCollection->setUserFilter(0, UserContextInterface::USER_TYPE_ADMIN); + $this->assertEquals(1, $roleCollection->getSize()); + } + + /** + * Check total user count. + * + * @return void + */ + public function testCountAll(): void { $this->assertSame(1, $this->model->countAll()); } - public function testGetValidationRulesBeforeSave() + /** + * Check validation rules has correct type. + * + * @return void + */ + public function testGetValidationRulesBeforeSave(): void { $rules = $this->model->getValidationRulesBeforeSave(); $this->assertInstanceOf('Zend_Validate_Interface', $rules); diff --git a/dev/tests/integration/testsuite/Magento/Webapi/_files/webapi_user.php b/dev/tests/integration/testsuite/Magento/Webapi/_files/webapi_user.php index 63479bee9cf6d..50204998c7da3 100644 --- a/dev/tests/integration/testsuite/Magento/Webapi/_files/webapi_user.php +++ b/dev/tests/integration/testsuite/Magento/Webapi/_files/webapi_user.php @@ -15,7 +15,7 @@ ->create(Magento\Framework\App\ResourceConnection::class); $adapter = $connection->getConnection(); $select = $adapter->select() - ->from('authorization_role', ['role_id']) + ->from($connection->getTableName('authorization_role'), ['role_id']) ->where('role_name = ?', 'Administrators') ->where('parent_id = ?', 0) ->limit(1); diff --git a/dev/tests/integration/testsuite/Magento/Weee/Block/Product/View/Attribute/FixedProductTaxAttributeTest.php b/dev/tests/integration/testsuite/Magento/Weee/Block/Product/View/Attribute/FixedProductTaxAttributeTest.php index 6cb7d6dc0346d..a0aeb13f77518 100644 --- a/dev/tests/integration/testsuite/Magento/Weee/Block/Product/View/Attribute/FixedProductTaxAttributeTest.php +++ b/dev/tests/integration/testsuite/Magento/Weee/Block/Product/View/Attribute/FixedProductTaxAttributeTest.php @@ -11,11 +11,14 @@ use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Block\Product\ListProduct; use Magento\Catalog\Pricing\Render as CatalogPricingRender; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Model\Session; use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Pricing\Render; use Magento\Framework\Pricing\Render\RendererPool; use Magento\Framework\Registry; use Magento\Framework\View\LayoutInterface; +use Magento\Store\Model\StoreManagerInterface; use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; @@ -27,20 +30,12 @@ * @magentoAppArea frontend * @magentoDataFixture Magento/Weee/_files/fixed_product_attribute.php * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class FixedProductTaxAttributeTest extends TestCase { - /** @var array */ - private const TEST_TAX_DATA = [ - [ - 'region_id' => '1', - 'country' => 'US', - 'val' => '', - 'value' => '5', - 'website_id' => '1', - 'state' => '', - ] - ]; + /** @var array */ + private $textTaxData; /** @var ObjectManagerInterface */ private $objectManager; @@ -60,6 +55,18 @@ class FixedProductTaxAttributeTest extends TestCase /** @var Registry */ private $registry; + /** @var StoreManagerInterface */ + private $storeManager; + + /** @var CustomerRepositoryInterface */ + private $customerRepository; + + /** @var Session */ + private $customerSession; + + /** @var int */ + private $baseWebsiteId; + /** * @inheritdoc */ @@ -73,6 +80,19 @@ protected function setUp() $this->productListBlock = $this->layout->createBlock(ListProduct::class); $this->attributeCode = 'fixed_product_attribute'; $this->registry = $this->objectManager->get(Registry::class); + $this->storeManager = $this->objectManager->get(StoreManagerInterface::class); + $this->customerRepository = $this->objectManager->create(CustomerRepositoryInterface::class); + $this->customerSession = $this->objectManager->get(Session::class); + $this->baseWebsiteId = (int) $this->storeManager->getWebsite('base')->getId(); + $this->textTaxData = [ + [ + 'country' => 'US', + 'val' => '', + 'value' => '5', + 'website_id' => $this->baseWebsiteId, + 'state' => '', + ] + ]; } /** @@ -81,6 +101,8 @@ protected function setUp() protected function tearDown() { $this->registry->unregister('product'); + $this->registry->unregister('current_product'); + $this->customerSession->logout(); parent::tearDown(); } @@ -88,11 +110,13 @@ protected function tearDown() /** * @magentoConfigFixture default_store tax/weee/enable 1 * @magentoConfigFixture default_store tax/weee/display_list 0 + * + * @return void */ public function testFPTCategoryPageIncludingFPTOnly(): void { $this->prepareLayoutCategoryPage(); - $product = $this->updateProduct('simple2', self::TEST_TAX_DATA); + $product = $this->updateProduct('simple2', $this->textTaxData); $productPrice = $this->productListBlock->getProductPrice($product); $this->assertEquals('$15.00', preg_replace('/\s+/', '', strip_tags($productPrice))); } @@ -100,11 +124,13 @@ public function testFPTCategoryPageIncludingFPTOnly(): void /** * @magentoConfigFixture default_store tax/weee/enable 1 * @magentoConfigFixture default_store tax/weee/display_list 1 + * + * @return void */ public function testFPTCategoryPageIncludingFPTAndDescription(): void { $this->prepareLayoutCategoryPage(); - $product = $this->updateProduct('simple2', self::TEST_TAX_DATA); + $product = $this->updateProduct('simple2', $this->textTaxData); $productPrice = $this->productListBlock->getProductPrice($product); $this->assertContains('data-label="fixed product tax"', $productPrice); $this->assertEquals('$15.00$5.00', preg_replace('/\s+/', '', strip_tags($productPrice))); @@ -113,11 +139,13 @@ public function testFPTCategoryPageIncludingFPTAndDescription(): void /** * @magentoConfigFixture default_store tax/weee/enable 1 * @magentoConfigFixture default_store tax/weee/display_list 2 + * + * @return void */ public function testFPTCategoryPageExcludingFPTIncludingDescriptionAndPrice(): void { $this->prepareLayoutCategoryPage(); - $product = $this->updateProduct('simple2', self::TEST_TAX_DATA); + $product = $this->updateProduct('simple2', $this->textTaxData); $productPrice = $this->productListBlock->getProductPrice($product); $this->assertContains('data-label="fixed product tax"', $productPrice); $this->assertEquals('$10.00$5.00$15.00', preg_replace('/\s+/', '', strip_tags($productPrice))); @@ -126,22 +154,26 @@ public function testFPTCategoryPageExcludingFPTIncludingDescriptionAndPrice(): v /** * @magentoConfigFixture default_store tax/weee/enable 1 * @magentoConfigFixture default_store tax/weee/display_list 3 + * + * @return void */ public function testFPTCategoryPageExcludingFPT(): void { $this->prepareLayoutCategoryPage(); - $product = $this->updateProduct('simple2', self::TEST_TAX_DATA); + $product = $this->updateProduct('simple2', $this->textTaxData); $productPrice = $this->productListBlock->getProductPrice($product); $this->assertEquals('$10.00', preg_replace('/\s+/', '', strip_tags($productPrice))); } /** * @magentoConfigFixture default_store tax/weee/enable 1 - * @magentoConfigFixture default_store tax/weee/display_list 0 + * @magentoConfigFixture default_store tax/weee/display 0 + * + * @return void */ public function testFPTProductPageIncludingFPTOnly(): void { - $product = $this->updateProduct('simple2', self::TEST_TAX_DATA); + $product = $this->updateProduct('simple2', $this->textTaxData); $this->registerProduct($product); $block = $this->prepareLayoutProductPage(); $productPrice = $block->toHtml(); @@ -150,11 +182,13 @@ public function testFPTProductPageIncludingFPTOnly(): void /** * @magentoConfigFixture default_store tax/weee/enable 1 - * @magentoConfigFixture default_store tax/weee/display_list 1 + * @magentoConfigFixture default_store tax/weee/display 1 + * + * @return void */ public function testFPTProductPageIncludingFPTAndDescription(): void { - $product = $this->updateProduct('simple2', self::TEST_TAX_DATA); + $product = $this->updateProduct('simple2', $this->textTaxData); $this->registerProduct($product); $block = $this->prepareLayoutProductPage(); $productPrice = $block->toHtml(); @@ -164,11 +198,13 @@ public function testFPTProductPageIncludingFPTAndDescription(): void /** * @magentoConfigFixture default_store tax/weee/enable 1 - * @magentoConfigFixture default_store tax/weee/display_list 2 + * @magentoConfigFixture default_store tax/weee/display 2 + * + * @return void */ public function testFPTProductPageExcludingFPTIncludingDescriptionAndPrice(): void { - $product = $this->updateProduct('simple2', self::TEST_TAX_DATA); + $product = $this->updateProduct('simple2', $this->textTaxData); $this->registerProduct($product); $block = $this->prepareLayoutProductPage(); $productPrice = $block->toHtml(); @@ -178,11 +214,148 @@ public function testFPTProductPageExcludingFPTIncludingDescriptionAndPrice(): vo /** * @magentoConfigFixture default_store tax/weee/enable 1 - * @magentoConfigFixture default_store tax/weee/display_list 3 + * @magentoConfigFixture default_store tax/weee/display 3 + * + * @return void */ public function testFPTProductPageExcludingFPT(): void { - $product = $this->updateProduct('simple2', self::TEST_TAX_DATA); + $product = $this->updateProduct('simple2', $this->textTaxData); + $this->registerProduct($product); + $block = $this->prepareLayoutProductPage(); + $productPrice = $block->toHtml(); + $this->assertEquals('$10.00', preg_replace('/\s+/', '', strip_tags($productPrice))); + } + + /** + * @magentoDbIsolation disabled + * + * @magentoConfigFixture default/catalog/price/scope 1 + * @magentoConfigFixture fixture_second_store_store tax/weee/enable 1 + * @magentoConfigFixture fixture_second_store_store tax/weee/display 2 + * + * @magentoDataFixture Magento/Weee/_files/fixed_product_attribute.php + * @magentoDataFixture Magento/Catalog/_files/product_two_websites.php + * + * @return void + */ + public function testFPTPerWebsites(): void + { + $currentStore = $this->storeManager->getStore(); + try { + $secondStore = $this->storeManager->getStore('fixture_second_store'); + $taxData = [ + [ + 'region_id' => '1', + 'country' => 'US', + 'val' => '', + 'value' => '5', + 'website_id' => $secondStore->getWebsiteId(), + 'state' => '', + ] + ]; + $this->storeManager->setCurrentStore($secondStore); + $product = $this->updateProduct('simple-on-two-websites', $taxData); + $this->registerProduct($product); + $block = $this->prepareLayoutProductPage(); + $productPrice = $block->toHtml(); + $this->assertEquals('$10.00$5.00$15.00', preg_replace('/\s+/', '', strip_tags($productPrice))); + } finally { + $this->storeManager->setCurrentStore($currentStore); + } + } + + /** + * @magentoConfigFixture default_store tax/weee/enable 1 + * @magentoConfigFixture default_store tax/weee/display 0 + * + * @magentoDataFixture Magento/Weee/_files/fixed_product_attribute.php + * @magentoDataFixture Magento/Customer/_files/customer_one_address.php + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * + * @return void + */ + public function testApplyTwoFPTForCustomer(): void + { + $email = 'customer_one_address@test.com'; + $expectedPrice = '$30.00'; + $taxData = [ + [ + 'country' => 'US', + 'val' => '', + 'value' => '5', + 'website_id' => $this->baseWebsiteId, + 'state' => '', + ], + [ + 'country' => 'US', + 'val' => '', + 'value' => '15', + 'website_id' => $this->baseWebsiteId, + 'state' => 1, + ] + ]; + $this->loginCustomerByEmail($email); + $product = $this->updateProduct('simple2', $taxData); + $this->registerProduct($product); + $block = $this->prepareLayoutProductPage(); + $productPrice = $block->toHtml(); + $this->assertEquals($expectedPrice, preg_replace('/\s+/', '', strip_tags($productPrice))); + } + + /** + * @magentoConfigFixture default_store tax/defaults/country GB + * @magentoConfigFixture default_store tax/weee/enable 1 + * @magentoConfigFixture default_store tax/weee/display 0 + * + * @magentoDataFixture Magento/Weee/_files/fixed_product_attribute.php + * @magentoDataFixture Magento/Customer/_files/customer_no_address.php + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * + * @return void + */ + public function testApplyFPTWithoutAddressCustomer(): void + { + $email = 'customer5@example.com'; + $expectedPrice = '$10.00'; + $taxData = [ + [ + 'country' => 'US', + 'val' => '', + 'value' => '5', + 'website_id' => $this->baseWebsiteId, + 'state' => '', + ], + [ + 'country' => 'US', + 'val' => '', + 'value' => '15', + 'website_id' => $this->baseWebsiteId, + 'state' => 1, + ], + ]; + $this->loginCustomerByEmail($email); + $product = $this->updateProduct('simple2', $taxData); + $this->registerProduct($product); + $block = $this->prepareLayoutProductPage(); + $productPrice = $block->toHtml(); + $this->assertEquals($expectedPrice, preg_replace('/\s+/', '', strip_tags($productPrice))); + } + + /** + * @magentoConfigFixture default_store tax/weee/enable 1 + * @magentoConfigFixture default_store tax/weee/display 0 + * + * @magentoDataFixture Magento/Weee/_files/fixed_product_attribute.php + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * @magentoDataFixture Magento/Customer/_files/customer_with_uk_address.php + * + * @return void + */ + public function testApplyFPTWithForeignCountryAddress(): void + { + $this->loginCustomerByEmail('customer_uk_address@test.com'); + $product = $this->updateProduct('simple2', $this->textTaxData); $this->registerProduct($product); $block = $this->prepareLayoutProductPage(); $productPrice = $block->toHtml(); @@ -248,5 +421,19 @@ private function registerProduct(ProductInterface $product): void { $this->registry->unregister('product'); $this->registry->register('product', $product); + $this->registry->unregister('current_product'); + $this->registry->register('current_product', $product); + } + + /** + * Login customer by email + * + * @param string $email + * @return void + */ + private function loginCustomerByEmail(string $email): void + { + $customer = $this->customerRepository->get($email); + $this->customerSession->loginById($customer->getId()); } } diff --git a/dev/tests/integration/testsuite/Magento/Weee/Controller/Adminhtml/Product/Attribute/Delete/WeeeAttributesControllerTest.php b/dev/tests/integration/testsuite/Magento/Weee/Controller/Adminhtml/Product/Attribute/Delete/WeeeAttributesControllerTest.php new file mode 100644 index 0000000000000..19e4ea2259641 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Weee/Controller/Adminhtml/Product/Attribute/Delete/WeeeAttributesControllerTest.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Weee\Controller\Adminhtml\Product\Attribute\Delete; + +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Delete\AbstractDeleteAttributeControllerTest; + +/** + * Delete catalog product attributes with input types like "weee". + * Attributes from Magento_Weee module. + * + * @magentoAppArea adminhtml + * @magentoDbIsolation enabled + */ +class WeeeAttributesControllerTest extends AbstractDeleteAttributeControllerTest +{ + /** + * Assert that attribute with input type "weee" will be deleted + * after dispatch delete product attribute action. + * + * @magentoDataFixture Magento/Weee/_files/fixed_product_attribute.php + * + * @return void + */ + public function testDeleteSwatchTextAttribute(): void + { + $this->dispatchDeleteAttribute('fixed_product_attribute'); + $this->assertAttributeIsDeleted('fixed_product_attribute'); + } +} diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Vault/view/frontend/web/js/view/payment/method-renderer/vault.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Vault/view/frontend/web/js/view/payment/method-renderer/vault.test.js new file mode 100644 index 0000000000000..3724a3ab0c9f3 --- /dev/null +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Vault/view/frontend/web/js/view/payment/method-renderer/vault.test.js @@ -0,0 +1,123 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'squire', + 'ko' +], function (Squire, ko) { + 'use strict'; + + var injector = new Squire(), + vault, + mocks = { + 'Magento_Checkout/js/model/checkout-data-resolver': { + resolveBillingAddress: jasmine.createSpy().and.returnValue(true) + }, + 'Magento_Checkout/js/checkout-data': { + setSelectedPaymentMethod: jasmine.createSpy().and.returnValue(false) + }, + 'Magento_Checkout/js/model/quote': { + billingAddress: ko.observable(null), + shippingAddress: ko.observable(null), + paymentMethod: ko.observable(null), + totals: ko.observable({}) + } + }, + billingAddress = { + city: 'Culver City', + company: 'Magento', + country_id: 'US',// jscs:ignore requireCamelCaseOrUpperCaseIdentifiers + firstname: 'John', + lastname: 'Doe', + postcode: '90230', + region: '', + region_id: '12',// jscs:ignore requireCamelCaseOrUpperCaseIdentifiers + street: { + 0: '6161 West Centinela Avenue', + 1: '' + }, + telephone: '+15555555555' + }; + + beforeEach(function (done) { + window.checkoutConfig = { + quoteData: { + /* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */ + entity_Id: 1 + }, + formKey: 'formKey' + }; + injector.mock(mocks); + injector.require(['Magento_Vault/js/view/payment/method-renderer/vault'], function (Constr) { + var params = { + index: 'vaultIndex', + item: { + method: 'vault' + } + }; + + vault = new Constr(params); + // eslint-disable-next-line max-nested-callbacks + /** Stub */ + vault.isChecked = function () { + return mocks['Magento_Checkout/js/model/quote'].paymentMethod() ? + mocks['Magento_Checkout/js/model/quote'].paymentMethod().method : null; + }; + done(); + }); + }); + + afterEach(function () { + try { + injector.remove(); + injector.clean(); + } catch (e) { + } + mocks['Magento_Checkout/js/model/quote'].billingAddress(null); + mocks['Magento_Checkout/js/model/quote'].paymentMethod(null); + }); + + describe('Magento_Vault/js/view/payment/method-renderer/vault', function () { + + it('There is no payment method and billing address', function () { + expect(vault.isButtonActive()).toBeFalsy(); + + expect(vault.isActive()).toBeFalsy(); + expect(vault.isPlaceOrderActionAllowed()).toBeFalsy(); + }); + + it('Payment method exists, but place order action is not allowed', function () { + vault.selectPaymentMethod(); + expect(mocks['Magento_Checkout/js/model/quote'].paymentMethod().method).toEqual('vaultIndex'); + + expect(vault.isButtonActive()).toBeFalsy(); + + expect(vault.isActive()).toBeTruthy(); + expect(vault.isPlaceOrderActionAllowed()).toBeFalsy(); + + }); + + it('Billing address exists, but there is no selected payment method', function () { + mocks['Magento_Checkout/js/model/quote'].billingAddress(billingAddress); + + expect(vault.isButtonActive()).toBeFalsy(); + + expect(vault.isActive()).toBeFalsy(); + expect(vault.isPlaceOrderActionAllowed).toBeTruthy(); + }); + + it('Button is active', function () { + vault.selectPaymentMethod(); + expect(mocks['Magento_Checkout/js/model/quote'].paymentMethod().method).toEqual('vaultIndex'); + + mocks['Magento_Checkout/js/model/quote'].billingAddress(billingAddress); + + expect(vault.isButtonActive()).toBeTruthy(); + + expect(vault.isActive()).toBeTruthy(); + expect(vault.isPlaceOrderActionAllowed()).toBeTruthy(); + }); + }); +}); diff --git a/lib/web/mage/adminhtml/form.js b/lib/web/mage/adminhtml/form.js index 487c71484e4c5..4dfbde6afa9d7 100644 --- a/lib/web/mage/adminhtml/form.js +++ b/lib/web/mage/adminhtml/form.js @@ -494,7 +494,8 @@ define([ inputs.each(function (item) { // don't touch hidden inputs (and Use Default inputs too), bc they may have custom logic if ((!item.type || item.type != 'hidden') && !($(item.id + '_inherit') && $(item.id + '_inherit').checked) && //eslint-disable-line - !(currentConfig['can_edit_price'] != undefined && !currentConfig['can_edit_price']) //eslint-disable-line + !(currentConfig['can_edit_price'] != undefined && !currentConfig['can_edit_price']) && //eslint-disable-line + !item.getAttribute('readonly') //eslint-disable-line ) { item.disabled = false; jQuery(item).removeClass('ignore-validate'); diff --git a/setup/src/Magento/Setup/Fixtures/CategoriesFixture.php b/setup/src/Magento/Setup/Fixtures/CategoriesFixture.php index e8a0a469e2ee5..782f688f8a09e 100644 --- a/setup/src/Magento/Setup/Fixtures/CategoriesFixture.php +++ b/setup/src/Magento/Setup/Fixtures/CategoriesFixture.php @@ -9,6 +9,7 @@ use Magento\Catalog\Model\Category; use Magento\Catalog\Model\CategoryFactory; use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory; +use Magento\Store\Model\Store; use Magento\Store\Model\StoreManager; /** @@ -82,7 +83,7 @@ public function __construct( protected $priority = 20; /** - * {@inheritdoc} + * @inheritdoc */ public function execute() { @@ -97,7 +98,7 @@ public function execute() $category = $this->categoryFactory->create(); $category->load($parentCategoryId); // Need for generation url rewrites per all category store view - $category->setStoreId(\Magento\Store\Model\Store::DEFAULT_STORE_ID); + $category->setStoreId(Store::DEFAULT_STORE_ID); $categoryIndex = 1; $this->generateCategories( $category, @@ -130,6 +131,7 @@ private function generateCategories( $category->setId(null) ->setUrlKey(null) ->setUrlPath(null) + ->setStoreId(Store::DEFAULT_STORE_ID) ->setName($this->getCategoryName($parentCategory, $nestingLevel, $i)) ->setParentId($parentCategory->getId()) ->setLevel($parentCategory->getLevel() + 1) @@ -237,7 +239,7 @@ private function getCategoryPrefix() } /** - * {@inheritdoc} + * @inheritdoc */ public function getActionTitle() { @@ -245,7 +247,7 @@ public function getActionTitle() } /** - * {@inheritdoc} + * @inheritdoc */ public function introduceParamLabels() { diff --git a/setup/src/Magento/Setup/Test/Unit/Fixtures/CategoriesFixtureTest.php b/setup/src/Magento/Setup/Test/Unit/Fixtures/CategoriesFixtureTest.php index 9e2a7b46514bd..348afab198ab8 100644 --- a/setup/src/Magento/Setup/Test/Unit/Fixtures/CategoriesFixtureTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Fixtures/CategoriesFixtureTest.php @@ -12,6 +12,7 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Setup\Fixtures\CategoriesFixture; use Magento\Setup\Fixtures\FixtureModel; +use Magento\Store\Model\Store; class CategoriesFixtureTest extends \PHPUnit\Framework\TestCase { @@ -40,7 +41,10 @@ class CategoriesFixtureTest extends \PHPUnit\Framework\TestCase */ private $categoryFactoryMock; - public function setUp() + /** + * @inhertidoc + */ + protected function setUp() { $this->fixtureModelMock = $this->createMock(FixtureModel::class); $this->collectionFactoryMock = $this->createPartialMock(CollectionFactory::class, ['create']); @@ -146,6 +150,10 @@ public function testExecute() $categoryMock->expects($this->once()) ->method('setIsActive') ->willReturnSelf(); + $categoryMock->expects($this->exactly(2)) + ->method('setStoreId') + ->with(Store::DEFAULT_STORE_ID) + ->willReturnSelf(); $this->categoryFactoryMock->expects($this->once())->method('create')->willReturn($categoryMock);