diff --git a/app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscountTest.xml b/app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscountTest.xml index 5efa5fd0db6b6..85d17a69ebae0 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscountTest.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscountTest.xml @@ -74,7 +74,7 @@ - + @@ -93,7 +93,9 @@ - + + + diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddBundleProductToCartFromWishListPageTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddBundleProductToCartFromWishListPageTest.xml index 1498e52850fd5..aa06990f7af78 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddBundleProductToCartFromWishListPageTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddBundleProductToCartFromWishListPageTest.xml @@ -50,14 +50,16 @@ - + + + - + @@ -74,8 +76,8 @@ - - + + diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPricesTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPricesTest.xml index 0c9be915a7a8b..30369d447ab15 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPricesTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPricesTest.xml @@ -27,7 +27,9 @@ - + + + @@ -35,7 +37,9 @@ - + + + diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontVerifyDynamicBundleProductPricesForCombinationOfOptionsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontVerifyDynamicBundleProductPricesForCombinationOfOptionsTest.xml index 896c4f8f25fec..d388549745d51 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontVerifyDynamicBundleProductPricesForCombinationOfOptionsTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontVerifyDynamicBundleProductPricesForCombinationOfOptionsTest.xml @@ -40,7 +40,9 @@ - + + + diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index 860a90c6b09dc..9f33061199d54 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -1741,7 +1741,7 @@ public function addAttributeToSort($attribute, $dir = self::SORT_ORDER_ASC) // optimize if using cat index $filters = $this->_productLimitationFilters; if (isset($filters['category_id']) || isset($filters['visibility'])) { - $this->getSelect()->order('cat_index.position ' . $dir); + $this->getSelect()->order(['cat_index.position ' . $dir, 'e.entity_id ' . $dir]); } else { $this->getSelect()->order('e.entity_id ' . $dir); } diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/GoToProductPageViaIDActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductPageOpenByIdActionGroup.xml similarity index 72% rename from app/code/Magento/Catalog/Test/Mftf/ActionGroup/GoToProductPageViaIDActionGroup.xml rename to app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductPageOpenByIdActionGroup.xml index 104ef83771e9d..61626c0ea5662 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/GoToProductPageViaIDActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductPageOpenByIdActionGroup.xml @@ -8,10 +8,7 @@ - - - Goes to the Product edit page for the provided Product ID. - + diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/_Deprecated_ActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/_Deprecated_ActionGroup.xml new file mode 100644 index 0000000000000..aff1de23af409 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/_Deprecated_ActionGroup.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategorySidebarSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategorySidebarSection.xml index d629441782551..786131c93e5a4 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategorySidebarSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategorySidebarSection.xml @@ -13,7 +13,7 @@ - + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AddToCartCrossSellTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AddToCartCrossSellTest.xml index faba11f2234bc..a7c0575e0cc96 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AddToCartCrossSellTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AddToCartCrossSellTest.xml @@ -7,7 +7,7 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> @@ -42,7 +42,9 @@ - + + + @@ -55,7 +57,9 @@ - + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminBackorderAllowedAddProductToCartTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminBackorderAllowedAddProductToCartTest.xml index 9361637a0a935..2640ab04023a3 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminBackorderAllowedAddProductToCartTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminBackorderAllowedAddProductToCartTest.xml @@ -31,6 +31,7 @@ + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckCustomAttributeValuesAfterProductSaveTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckCustomAttributeValuesAfterProductSaveTest.xml index 5b510a7ecdb0b..8eec2ed40c8cb 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckCustomAttributeValuesAfterProductSaveTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckCustomAttributeValuesAfterProductSaveTest.xml @@ -49,7 +49,9 @@ - + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckMediaRolesForFirstAddedImageViaApiTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckMediaRolesForFirstAddedImageViaApiTest.xml index c31054e3dc192..7d27bfb3a058b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckMediaRolesForFirstAddedImageViaApiTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckMediaRolesForFirstAddedImageViaApiTest.xml @@ -30,7 +30,7 @@ - + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCloneProductWithDuplicateUrlTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCloneProductWithDuplicateUrlTest.xml index a213f2af900cf..fb4013b94333e 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCloneProductWithDuplicateUrlTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCloneProductWithDuplicateUrlTest.xml @@ -34,7 +34,9 @@ - + + + @@ -45,7 +47,9 @@ - + + + @@ -63,7 +67,9 @@ - + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewAttributeFromProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewAttributeFromProductTest.xml index a39bc0bd39e2f..18fbcadf985c3 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewAttributeFromProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewAttributeFromProductTest.xml @@ -54,7 +54,9 @@ - + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductsImageInCaseOfMultipleStoresTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductsImageInCaseOfMultipleStoresTest.xml index 9ed0a8104faa1..b255fd17187da 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductsImageInCaseOfMultipleStoresTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductsImageInCaseOfMultipleStoresTest.xml @@ -40,12 +40,16 @@ - + + + - + + + @@ -80,7 +84,9 @@ - + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilterByNameByStoreViewOnProductGridTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilterByNameByStoreViewOnProductGridTest.xml index 5ad3ee6f83e2d..7f973c99b39a5 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilterByNameByStoreViewOnProductGridTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilterByNameByStoreViewOnProductGridTest.xml @@ -28,7 +28,9 @@ - + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml index cfce9143f6cc6..cc21740cbc59f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml @@ -49,7 +49,9 @@ - + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveProductBetweenCategoriesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveProductBetweenCategoriesTest.xml index 61f169d6f5226..58e3dd5c32488 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveProductBetweenCategoriesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveProductBetweenCategoriesTest.xml @@ -68,7 +68,9 @@ - + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCategoryIndexerInUpdateOnScheduleModeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCategoryIndexerInUpdateOnScheduleModeTest.xml index 592b51dc708e6..4a93b07769fd2 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCategoryIndexerInUpdateOnScheduleModeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCategoryIndexerInUpdateOnScheduleModeTest.xml @@ -74,7 +74,9 @@ - + + + @@ -104,7 +106,9 @@ - + + + @@ -144,7 +148,9 @@ - + + + @@ -200,7 +206,9 @@ - + + + @@ -258,7 +266,9 @@ - + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKeyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKeyTest.xml index 9babd94ef2641..c62c6b4b4c6a9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKeyTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKeyTest.xml @@ -51,7 +51,9 @@ - + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml index 8e8f3ebccafb1..bf5b3db1f1f33 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml @@ -38,7 +38,9 @@ - + + + @@ -76,7 +78,9 @@ - + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveCustomOptionsFromProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveCustomOptionsFromProductTest.xml index 702525ac5bf04..f46a3aea0ef81 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveCustomOptionsFromProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveCustomOptionsFromProductTest.xml @@ -32,7 +32,9 @@ - + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductEditUiTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductEditUiTest.xml index bc5a0319bae7a..c0f121edcf6bd 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductEditUiTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductEditUiTest.xml @@ -34,7 +34,9 @@ - + + + @@ -46,4 +48,4 @@ - \ No newline at end of file + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontSimpleProductWithSpecialAndTierDiscountPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontSimpleProductWithSpecialAndTierDiscountPriceTest.xml index 6817969de65c3..d642f16160c5b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontSimpleProductWithSpecialAndTierDiscountPriceTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontSimpleProductWithSpecialAndTierDiscountPriceTest.xml @@ -34,7 +34,7 @@ - + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontConfigurableOptionsThumbImagesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontConfigurableOptionsThumbImagesTest.xml index 134fd89c2c813..059b90532f8f9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontConfigurableOptionsThumbImagesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontConfigurableOptionsThumbImagesTest.xml @@ -87,7 +87,7 @@ - + @@ -113,7 +113,7 @@ - + @@ -138,7 +138,7 @@ - + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml index f28029d44b9e0..3997198516c39 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml @@ -57,7 +57,9 @@ - + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml index e4446abf6624e..c30c750fec10c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml @@ -104,7 +104,9 @@ - + + + @@ -166,13 +168,17 @@ - + + + - + + + diff --git a/app/code/Magento/CatalogGraphQl/Model/AttributesJoiner.php b/app/code/Magento/CatalogGraphQl/Model/AttributesJoiner.php index b3a9672a47010..69592657241a0 100644 --- a/app/code/Magento/CatalogGraphQl/Model/AttributesJoiner.php +++ b/app/code/Magento/CatalogGraphQl/Model/AttributesJoiner.php @@ -45,7 +45,7 @@ public function __construct(array $fieldToAttributeMap = []) * @param AbstractCollection $collection * @return void */ - public function join(FieldNode $fieldNode, AbstractCollection $collection) : void + public function join(FieldNode $fieldNode, AbstractCollection $collection): void { foreach ($this->getQueryFields($fieldNode) as $field) { $this->addFieldToCollection($collection, $field); @@ -60,19 +60,20 @@ public function join(FieldNode $fieldNode, AbstractCollection $collection) : voi */ public function getQueryFields(FieldNode $fieldNode): array { - if (!isset($this->queryFields[$fieldNode->name->value])) { - $this->queryFields[$fieldNode->name->value] = []; + if (null === $this->getFieldNodeSelections($fieldNode)) { $query = $fieldNode->selectionSet->selections; + $selectedFields = []; /** @var FieldNode $field */ foreach ($query as $field) { if ($field->kind === 'InlineFragment') { continue; } - $this->queryFields[$fieldNode->name->value][] = $field->name->value; + $selectedFields[] = $field->name->value; } + $this->setSelectionsForFieldNode($fieldNode, $selectedFields); } - return $this->queryFields[$fieldNode->name->value]; + return $this->getFieldNodeSelections($fieldNode); } /** @@ -83,7 +84,7 @@ public function getQueryFields(FieldNode $fieldNode): array * @param AbstractCollection $collection * @param string $field */ - private function addFieldToCollection(AbstractCollection $collection, string $field) + private function addFieldToCollection(AbstractCollection $collection, string $field): void { $attribute = isset($this->fieldToAttributeMap[$field]) ? $this->fieldToAttributeMap[$field] : $field; @@ -99,4 +100,28 @@ private function addFieldToCollection(AbstractCollection $collection, string $fi } } } + + /** + * Get the fields selections for a query node + * + * @param FieldNode $fieldNode + * @return array|null + */ + private function getFieldNodeSelections(FieldNode $fieldNode): ?array + { + return $this->queryFields[$fieldNode->name->value][$fieldNode->name->loc->start] ?? null; + } + + /** + * Set the field selections for a query node + * + * Index nodes by name and position so nodes with same name don't collide + * + * @param FieldNode $fieldNode + * @param array $selectedFields + */ + private function setSelectionsForFieldNode(FieldNode $fieldNode, array $selectedFields): void + { + $this->queryFields[$fieldNode->name->value][$fieldNode->name->loc->start] = $selectedFields; + } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/CategoryFilter.php b/app/code/Magento/CatalogGraphQl/Model/Category/CategoryFilter.php index 2c03550404ae0..09694b7cf1a0c 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Category/CategoryFilter.php +++ b/app/code/Magento/CatalogGraphQl/Model/Category/CategoryFilter.php @@ -7,16 +7,20 @@ namespace Magento\CatalogGraphQl\Model\Category; +use Magento\Catalog\Api\CategoryListInterface; use Magento\Catalog\Api\Data\CategoryInterface; -use Magento\Catalog\Model\ResourceModel\Category\Collection; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\Exception\InputException; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Query\Resolver\Argument\SearchCriteria\ArgumentApplier\Filter; +use Magento\Framework\Search\Adapter\Mysql\Query\Builder\Match; +use Magento\Search\Model\Query; use Magento\Store\Api\Data\StoreInterface; use Magento\Store\Model\ScopeInterface; -use Magento\Search\Model\Query; +use Magento\Framework\GraphQl\Query\Resolver\Argument\SearchCriteria\Builder; /** - * Category filter allows to filter collection using 'id, url_key, name' from search criteria. + * Category filter allows filtering category results by attributes. */ class CategoryFilter { @@ -25,78 +29,106 @@ class CategoryFilter */ private $scopeConfig; + /** + * @var CategoryListInterface + */ + private $categoryList; + + /** + * @var Builder + */ + private $searchCriteriaBuilder; + /** * @param ScopeConfigInterface $scopeConfig + * @param CategoryListInterface $categoryList + * @param Builder $searchCriteriaBuilder */ public function __construct( - ScopeConfigInterface $scopeConfig + ScopeConfigInterface $scopeConfig, + CategoryListInterface $categoryList, + Builder $searchCriteriaBuilder ) { $this->scopeConfig = $scopeConfig; + $this->categoryList = $categoryList; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; } /** - * Filter for filtering the requested categories id's based on url_key, ids, name in the result. + * Search for categories * - * @param array $args - * @param Collection $categoryCollection + * @param array $criteria * @param StoreInterface $store + * @return int[] * @throws InputException */ - public function applyFilters(array $args, Collection $categoryCollection, StoreInterface $store) + public function getResult(array $criteria, StoreInterface $store) { - $categoryCollection->addAttributeToFilter(CategoryInterface::KEY_IS_ACTIVE, ['eq' => 1]); - foreach ($args['filters'] as $field => $cond) { - foreach ($cond as $condType => $value) { - if ($field === 'ids') { - $categoryCollection->addIdFilter($value); - } else { - $this->addAttributeFilter($categoryCollection, $field, $condType, $value, $store); - } - } + $categoryIds = []; + $criteria[Filter::ARGUMENT_NAME] = $this->formatMatchFilters($criteria['filters'], $store); + $criteria[Filter::ARGUMENT_NAME][CategoryInterface::KEY_IS_ACTIVE] = ['eq' => 1]; + $searchCriteria = $this->searchCriteriaBuilder->build('categoryList', $criteria); + $pageSize = $criteria['pageSize'] ?? 20; + $currentPage = $criteria['currentPage'] ?? 1; + $searchCriteria->setPageSize($pageSize)->setCurrentPage($currentPage); + + $categories = $this->categoryList->getList($searchCriteria); + foreach ($categories->getItems() as $category) { + $categoryIds[] = (int)$category->getId(); } - } - /** - * Add filter to category collection - * - * @param Collection $categoryCollection - * @param string $field - * @param string $condType - * @param string|array $value - * @param StoreInterface $store - * @throws InputException - */ - private function addAttributeFilter($categoryCollection, $field, $condType, $value, $store) - { - if ($condType === 'match') { - $this->addMatchFilter($categoryCollection, $field, $value, $store); - return; + $totalPages = 0; + if ($categories->getTotalCount() > 0 && $searchCriteria->getPageSize() > 0) { + $totalPages = ceil($categories->getTotalCount() / $searchCriteria->getPageSize()); } - $categoryCollection->addAttributeToFilter($field, [$condType => $value]); + if ($searchCriteria->getCurrentPage() > $totalPages && $categories->getTotalCount() > 0) { + throw new GraphQlInputException( + __( + 'currentPage value %1 specified is greater than the %2 page(s) available.', + [$searchCriteria->getCurrentPage(), $totalPages] + ) + ); + } + + return [ + 'category_ids' => $categoryIds, + 'total_count' => $categories->getTotalCount(), + 'page_info' => [ + 'total_pages' => $totalPages, + 'page_size' => $searchCriteria->getPageSize(), + 'current_page' => $searchCriteria->getCurrentPage(), + ] + ]; } /** - * Add match filter to collection + * Format match filters to behave like fuzzy match * - * @param Collection $categoryCollection - * @param string $field - * @param string $value + * @param array $filters * @param StoreInterface $store + * @return array * @throws InputException */ - private function addMatchFilter($categoryCollection, $field, $value, $store) + private function formatMatchFilters(array $filters, StoreInterface $store): array { $minQueryLength = $this->scopeConfig->getValue( Query::XML_PATH_MIN_QUERY_LENGTH, ScopeInterface::SCOPE_STORE, $store ); - $searchValue = str_replace('%', '', $value); - $matchLength = strlen($searchValue); - if ($matchLength < $minQueryLength) { - throw new InputException(__('Invalid match filter')); - } - $categoryCollection->addAttributeToFilter($field, ['like' => "%{$searchValue}%"]); + foreach ($filters as $filter => $condition) { + $conditionType = current(array_keys($condition)); + if ($conditionType === 'match') { + $searchValue = trim(str_replace(Match::SPECIAL_CHARACTERS, '', $condition[$conditionType])); + $matchLength = strlen($searchValue); + if ($matchLength < $minQueryLength) { + throw new InputException(__('Invalid match filter. Minimum length is %1.', $minQueryLength)); + } + unset($filters[$filter]['match']); + $filters[$filter]['like'] = '%' . $searchValue . '%'; + } + } + return $filters; } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/CategoryFilterAttributesForAst.php b/app/code/Magento/CatalogGraphQl/Model/Category/CategoryFilterAttributesForAst.php new file mode 100644 index 0000000000000..287f98e9f6848 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Category/CategoryFilterAttributesForAst.php @@ -0,0 +1,87 @@ + 'entity_id' + ]; + + /** + * @var array + */ + private $additionalFields = [ + 'is_active' + ]; + + /** + * @var ConfigInterface + */ + private $config; + + /** + * @param ConfigInterface $config + * @param array $additionalFields + * @param array $attributeFieldMapping + */ + public function __construct( + ConfigInterface $config, + array $additionalFields = [], + array $attributeFieldMapping = [] + ) { + $this->config = $config; + $this->additionalFields = array_merge($this->additionalFields, $additionalFields); + $this->fieldMapping = array_merge($this->fieldMapping, $attributeFieldMapping); + } + + /** + * @inheritdoc + * + * Gather attributes for Category filtering + * Example format ['attributeNameInGraphQl' => ['type' => 'String'. 'fieldName' => 'attributeNameInSearchCriteria']] + * + * @return array + */ + public function getEntityAttributes() : array + { + $categoryFilterType = $this->config->getConfigElement('CategoryFilterInput'); + + if (!$categoryFilterType) { + throw new \LogicException(__("CategoryFilterInput type not defined in schema.")); + } + + $fields = []; + foreach ($categoryFilterType->getFields() as $field) { + $fields[$field->getName()] = [ + 'type' => 'String', + 'fieldName' => $this->fieldMapping[$field->getName()] ?? $field->getName(), + ]; + } + + foreach ($this->additionalFields as $additionalField) { + $fields[$additionalField] = [ + 'type' => 'String', + 'fieldName' => $additionalField, + ]; + } + + return $fields; + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoriesQuery.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoriesQuery.php new file mode 100644 index 0000000000000..eb6708dc48f01 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoriesQuery.php @@ -0,0 +1,103 @@ +categoryTree = $categoryTree; + $this->extractDataFromCategoryTree = $extractDataFromCategoryTree; + $this->categoryFilter = $categoryFilter; + } + + /** + * @inheritdoc + */ + public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) + { + $store = $context->getExtensionAttributes()->getStore(); + + if (isset($args['currentPage']) && $args['currentPage'] < 1) { + throw new GraphQlInputException(__('currentPage value must be greater than 0.')); + } + if (isset($args['pageSize']) && $args['pageSize'] < 1) { + throw new GraphQlInputException(__('pageSize value must be greater than 0.')); + } + if (!isset($args['filters'])) { + //When no filters are specified, get the root category + $args['filters']['ids'] = ['eq' => $store->getRootCategoryId()]; + } + + try { + $filterResult = $this->categoryFilter->getResult($args, $store); + } catch (InputException $e) { + throw new GraphQlInputException(__($e->getMessage())); + } + + $rootCategoryIds = $filterResult['category_ids']; + $filterResult['items'] = $this->fetchCategories($rootCategoryIds, $info); + return $filterResult; + } + + /** + * Fetch category tree data + * + * @param array $categoryIds + * @param ResolveInfo $info + * @return array + */ + private function fetchCategories(array $categoryIds, ResolveInfo $info) + { + $fetchedCategories = []; + foreach ($categoryIds as $categoryId) { + $categoryTree = $this->categoryTree->getTree($info, $categoryId); + if (empty($categoryTree)) { + continue; + } + $fetchedCategories[] = current($this->extractDataFromCategoryTree->execute($categoryTree)); + } + + return $fetchedCategories; + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/CategoriesIdentity.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/CategoriesIdentity.php index dd18c463b98de..0a679eb064226 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/CategoriesIdentity.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/CategoriesIdentity.php @@ -7,6 +7,7 @@ namespace Magento\CatalogGraphQl\Model\Resolver\Category; +use Magento\Catalog\Model\Category; use Magento\Framework\GraphQl\Query\Resolver\IdentityInterface; /** @@ -15,7 +16,7 @@ class CategoriesIdentity implements IdentityInterface { /** @var string */ - private $cacheTag = \Magento\Catalog\Model\Category::CACHE_TAG; + private $cacheTag = Category::CACHE_TAG; /** * Get category IDs from resolved data @@ -26,8 +27,9 @@ class CategoriesIdentity implements IdentityInterface public function getIdentities(array $resolvedData): array { $ids = []; - if (!empty($resolvedData)) { - foreach ($resolvedData as $category) { + $resolvedCategories = $resolvedData['items'] ?? $resolvedData; + if (!empty($resolvedCategories)) { + foreach ($resolvedCategories as $category) { $ids[] = sprintf('%s_%s', $this->cacheTag, $category['id']); } if (!empty($ids)) { diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryList.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryList.php index 6b8949d612829..f32c5a1f38425 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryList.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryList.php @@ -10,12 +10,11 @@ use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ExtractDataFromCategoryTree; use Magento\Framework\Exception\InputException; use Magento\Framework\GraphQl\Config\Element\Field; -use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CategoryTree; use Magento\CatalogGraphQl\Model\Category\CategoryFilter; -use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory; /** * Category List resolver, used for GraphQL category data request processing. @@ -27,11 +26,6 @@ class CategoryList implements ResolverInterface */ private $categoryTree; - /** - * @var CollectionFactory - */ - private $collectionFactory; - /** * @var CategoryFilter */ @@ -46,18 +40,15 @@ class CategoryList implements ResolverInterface * @param CategoryTree $categoryTree * @param ExtractDataFromCategoryTree $extractDataFromCategoryTree * @param CategoryFilter $categoryFilter - * @param CollectionFactory $collectionFactory */ public function __construct( CategoryTree $categoryTree, ExtractDataFromCategoryTree $extractDataFromCategoryTree, - CategoryFilter $categoryFilter, - CollectionFactory $collectionFactory + CategoryFilter $categoryFilter ) { $this->categoryTree = $categoryTree; $this->extractDataFromCategoryTree = $extractDataFromCategoryTree; $this->categoryFilter = $categoryFilter; - $this->collectionFactory = $collectionFactory; } /** @@ -70,24 +61,17 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value } $store = $context->getExtensionAttributes()->getStore(); - $rootCategoryIds = []; if (!isset($args['filters'])) { - $rootCategoryIds[] = (int)$store->getRootCategoryId(); - } else { - $categoryCollection = $this->collectionFactory->create(); - try { - $this->categoryFilter->applyFilters($args, $categoryCollection, $store); - } catch (InputException $e) { - return []; - } - - foreach ($categoryCollection as $category) { - $rootCategoryIds[] = (int)$category->getId(); - } + $args['filters']['ids'] = ['eq' => $store->getRootCategoryId()]; + } + try { + $filterResults = $this->categoryFilter->getResult($args, $store); + $rootCategoryIds = $filterResults['category_ids']; + } catch (InputException $e) { + throw new GraphQlInputException(__($e->getMessage())); } - $result = $this->fetchCategories($rootCategoryIds, $info); - return $result; + return $this->fetchCategories($rootCategoryIds, $info); } /** @@ -96,7 +80,6 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value * @param array $categoryIds * @param ResolveInfo $info * @return array - * @throws GraphQlNoSuchEntityException */ private function fetchCategories(array $categoryIds, ResolveInfo $info) { diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch.php index ff845f4796763..4c83afb89cc46 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch.php @@ -7,16 +7,17 @@ namespace Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider; +use Magento\Catalog\Api\Data\ProductSearchResultsInterfaceFactory; +use Magento\Catalog\Model\ResourceModel\Product\Collection; +use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product\CollectionPostProcessor; +use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product\CollectionProcessorInterface; +use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ProductSearch\ProductCollectionSearchCriteriaBuilder; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierFactory; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface; use Magento\Framework\Api\Search\SearchResultInterface; use Magento\Framework\Api\SearchCriteriaInterface; -use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; -use Magento\Catalog\Model\ResourceModel\Product\Collection; -use Magento\Catalog\Api\Data\ProductSearchResultsInterfaceFactory; use Magento\Framework\Api\SearchResultsInterface; -use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product\CollectionProcessorInterface; /** * Product field data provider for product search, used for GraphQL resolver processing. @@ -48,25 +49,33 @@ class ProductSearch */ private $searchResultApplierFactory; + /** + * @var ProductCollectionSearchCriteriaBuilder + */ + private $searchCriteriaBuilder; + /** * @param CollectionFactory $collectionFactory * @param ProductSearchResultsInterfaceFactory $searchResultsFactory * @param CollectionProcessorInterface $collectionPreProcessor * @param CollectionPostProcessor $collectionPostProcessor * @param SearchResultApplierFactory $searchResultsApplierFactory + * @param ProductCollectionSearchCriteriaBuilder $searchCriteriaBuilder */ public function __construct( CollectionFactory $collectionFactory, ProductSearchResultsInterfaceFactory $searchResultsFactory, CollectionProcessorInterface $collectionPreProcessor, CollectionPostProcessor $collectionPostProcessor, - SearchResultApplierFactory $searchResultsApplierFactory + SearchResultApplierFactory $searchResultsApplierFactory, + ProductCollectionSearchCriteriaBuilder $searchCriteriaBuilder ) { $this->collectionFactory = $collectionFactory; $this->searchResultsFactory = $searchResultsFactory; $this->collectionPreProcessor = $collectionPreProcessor; $this->collectionPostProcessor = $collectionPostProcessor; $this->searchResultApplierFactory = $searchResultsApplierFactory; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; } /** @@ -85,15 +94,21 @@ public function getList( /** @var Collection $collection */ $collection = $this->collectionFactory->create(); - //Join search results - $this->getSearchResultsApplier($searchResult, $collection, $this->getSortOrderArray($searchCriteria))->apply(); + //Create a copy of search criteria without filters to preserve the results from search + $searchCriteriaForCollection = $this->searchCriteriaBuilder->build($searchCriteria); + //Apply CatalogSearch results from search and join table + $this->getSearchResultsApplier( + $searchResult, + $collection, + $this->getSortOrderArray($searchCriteriaForCollection) + )->apply(); - $this->collectionPreProcessor->process($collection, $searchCriteria, $attributes); + $this->collectionPreProcessor->process($collection, $searchCriteriaForCollection, $attributes); $collection->load(); $this->collectionPostProcessor->process($collection, $attributes); $searchResults = $this->searchResultsFactory->create(); - $searchResults->setSearchCriteria($searchCriteria); + $searchResults->setSearchCriteria($searchCriteriaForCollection); $searchResults->setItems($collection->getItems()); $searchResults->setTotalCount($searchResult->getTotalCount()); return $searchResults; diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch/ProductCollectionSearchCriteriaBuilder.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch/ProductCollectionSearchCriteriaBuilder.php new file mode 100644 index 0000000000000..4a124d69bd20f --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch/ProductCollectionSearchCriteriaBuilder.php @@ -0,0 +1,77 @@ +searchCriteriaFactory = $searchCriteriaFactory; + $this->filterBuilder = $filterBuilder; + $this->filterGroupBuilder = $filterGroupBuilder; + } + + /** + * Build searchCriteria from search for product collection + * + * @param SearchCriteriaInterface $searchCriteria + * @return SearchCriteriaInterface + */ + public function build(SearchCriteriaInterface $searchCriteria): SearchCriteriaInterface + { + //Create a copy of search criteria without filters to preserve the results from search + $searchCriteriaForCollection = $this->searchCriteriaFactory->create() + ->setSortOrders($searchCriteria->getSortOrders()) + ->setPageSize($searchCriteria->getPageSize()) + ->setCurrentPage($searchCriteria->getCurrentPage()); + + //Add category id to enable sorting by position + foreach ($searchCriteria->getFilterGroups() as $filterGroup) { + foreach ($filterGroup->getFilters() as $filter) { + if ($filter->getField() == CategoryProductLink::KEY_CATEGORY_ID) { + $categoryFilter = $this->filterBuilder + ->setField($filter->getField()) + ->setValue($filter->getValue()) + ->setConditionType($filter->getConditionType()) + ->create(); + + $this->filterGroupBuilder->addFilter($categoryFilter); + $categoryGroup = $this->filterGroupBuilder->create(); + $searchCriteriaForCollection->setFilterGroups([$categoryGroup]); + } + } + } + return $searchCriteriaForCollection; + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php index 8377cd9baa5b4..fbb0e42f2afeb 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php @@ -9,12 +9,11 @@ use Magento\CatalogGraphQl\DataProvider\Product\SearchCriteriaBuilder; use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ProductSearch; -use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; -use Magento\Framework\Api\Search\SearchCriteriaInterface; use Magento\CatalogGraphQl\Model\Resolver\Products\SearchResult; use Magento\CatalogGraphQl\Model\Resolver\Products\SearchResultFactory; +use Magento\Framework\Api\Search\SearchCriteriaInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Search\Api\SearchInterface; -use Magento\Framework\Api\Search\SearchCriteriaInterfaceFactory; use Magento\Search\Model\Search\PageSizeProvider; /** @@ -37,11 +36,6 @@ class Search implements ProductQueryInterface */ private $pageSizeProvider; - /** - * @var SearchCriteriaInterfaceFactory - */ - private $searchCriteriaFactory; - /** * @var FieldSelection */ @@ -61,7 +55,6 @@ class Search implements ProductQueryInterface * @param SearchInterface $search * @param SearchResultFactory $searchResultFactory * @param PageSizeProvider $pageSize - * @param SearchCriteriaInterfaceFactory $searchCriteriaFactory * @param FieldSelection $fieldSelection * @param ProductSearch $productsProvider * @param SearchCriteriaBuilder $searchCriteriaBuilder @@ -70,7 +63,6 @@ public function __construct( SearchInterface $search, SearchResultFactory $searchResultFactory, PageSizeProvider $pageSize, - SearchCriteriaInterfaceFactory $searchCriteriaFactory, FieldSelection $fieldSelection, ProductSearch $productsProvider, SearchCriteriaBuilder $searchCriteriaBuilder @@ -78,7 +70,6 @@ public function __construct( $this->search = $search; $this->searchResultFactory = $searchResultFactory; $this->pageSizeProvider = $pageSize; - $this->searchCriteriaFactory = $searchCriteriaFactory; $this->fieldSelection = $fieldSelection; $this->productsProvider = $productsProvider; $this->searchCriteriaBuilder = $searchCriteriaBuilder; @@ -101,28 +92,18 @@ public function getResult( $realPageSize = $searchCriteria->getPageSize(); $realCurrentPage = $searchCriteria->getCurrentPage(); - // Current page must be set to 0 and page size to max for search to grab all ID's as temporary workaround + //Because of limitations of sort and pagination on search API we will query all IDS $pageSize = $this->pageSizeProvider->getMaxPageSize(); $searchCriteria->setPageSize($pageSize); $searchCriteria->setCurrentPage(0); $itemsResults = $this->search->search($searchCriteria); - //Create copy of search criteria without conditions (conditions will be applied by joining search result) - $searchCriteriaCopy = $this->searchCriteriaFactory->create() - ->setSortOrders($searchCriteria->getSortOrders()) - ->setPageSize($realPageSize) - ->setCurrentPage($realCurrentPage); - - $searchResults = $this->productsProvider->getList($searchCriteriaCopy, $itemsResults, $queryFields); - - //possible division by 0 - if ($realPageSize) { - $maxPages = (int)ceil($searchResults->getTotalCount() / $realPageSize); - } else { - $maxPages = 0; - } + //Address limitations of sort and pagination on search API apply original pagination from GQL query $searchCriteria->setPageSize($realPageSize); $searchCriteria->setCurrentPage($realCurrentPage); + $searchResults = $this->productsProvider->getList($searchCriteria, $itemsResults, $queryFields); + + $totalPages = $realPageSize ? ((int)ceil($searchResults->getTotalCount() / $realPageSize)) : 0; $productArray = []; /** @var \Magento\Catalog\Model\Product $product */ @@ -138,7 +119,7 @@ public function getResult( 'searchAggregation' => $itemsResults->getAggregations(), 'pageSize' => $realPageSize, 'currentPage' => $realCurrentPage, - 'totalPages' => $maxPages, + 'totalPages' => $totalPages, ] ); } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilter.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilter.php index 92888a2775e17..f709f8cd6eb72 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilter.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilter.php @@ -61,6 +61,7 @@ public function apply(Filter $filter, AbstractDb $collection) $category = $this->categoryFactory->create(); $this->categoryResourceModel->load($category, $categoryId); $categoryProducts[$categoryId] = $category->getProductCollection()->getAllIds(); + $collection->addCategoryFilter($category); } $categoryProductIds = array_unique(array_merge(...$categoryProducts)); diff --git a/app/code/Magento/CatalogGraphQl/etc/di.xml b/app/code/Magento/CatalogGraphQl/etc/di.xml index d6f75259e30d7..5fec7bfd4fda7 100644 --- a/app/code/Magento/CatalogGraphQl/etc/di.xml +++ b/app/code/Magento/CatalogGraphQl/etc/di.xml @@ -36,6 +36,7 @@ Magento\CatalogGraphQl\Model\Resolver\Products\FilterArgument\ProductEntityAttributesForAst + Magento\CatalogGraphQl\Model\Category\CategoryFilterAttributesForAst diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index d4b98b311fca4..f77b301d61e28 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -15,8 +15,13 @@ type Query { ): CategoryTree @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\CategoryTree") @doc(description: "The category query searches for categories that match the criteria specified in the search and filter attributes.") @deprecated(reason: "Use 'categoryList' query instead of 'category' query") @cache(cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\CategoryTreeIdentity") categoryList( - filters: CategoryFilterInput @doc(description: "Identifies which Category filter inputs to search for and return.") + filters: CategoryFilterInput @doc(description: "Identifies which Category filter inputs to search for and return.") ): [CategoryTree] @doc(description: "Returns an array of categories based on the specified filters.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\CategoryList") @cache(cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\CategoriesIdentity") + categories ( + filters: CategoryFilterInput @doc(description: "Identifies which Category filter inputs to search for and return.") + pageSize: Int = 20 @doc(description: "Specifies the maximum number of results to return at once. This attribute is optional.") + currentPage: Int = 1 @doc(description: "Specifies which page of results to return. The default value is 1.") + ): CategoryResult @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\CategoriesQuery") @cache(cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\CategoriesIdentity") } type Price @doc(description: "Price is deprecated, replaced by ProductPrice. The Price object defines the price of a product as well as any tax-related adjustments.") { @@ -133,6 +138,12 @@ type CategoryTree implements CategoryInterface @doc(description: "Category Tree children: [CategoryTree] @doc(description: "Child categories tree.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\CategoryTree") } +type CategoryResult @doc(description: "A collection of CategoryTree objects and pagination information.") { + items: [CategoryTree] @doc(description: "A list of categories that match the filter criteria.") + page_info: SearchResultPageInfo @doc(description: "An object that includes the page_info and currentPage values specified in the query.") + total_count: Int @doc(description: "The total number of categories that match the criteria.") +} + type CustomizableDateOption implements CustomizableOptionInterface @doc(description: "CustomizableDateOption contains information about a date picker that is defined as part of a customizable option.") { value: CustomizableDateValue @doc(description: "An object that defines a date field in a customizable option.") product_sku: String @doc(description: "The Stock Keeping Unit of the base product.") @@ -307,8 +318,9 @@ input ProductAttributeFilterInput @doc(description: "ProductAttributeFilterInput input CategoryFilterInput @doc(description: "CategoryFilterInput defines the filters to be used in the search. A filter contains at least one attribute, a comparison operator, and the value that is being searched for.") { ids: FilterEqualTypeInput @doc(description: "Filter by category ID that uniquely identifies the category.") - url_key: FilterEqualTypeInput @doc(description: "Filter by the part of the URL that identifies the category") + url_key: FilterEqualTypeInput @doc(description: "Filter by the part of the URL that identifies the category.") name: FilterMatchTypeInput @doc(description: "Filter by the display name of the category.") + url_path: FilterEqualTypeInput @doc(description: "Filter by the URL path for the category.") } input ProductFilterInput @doc(description: "ProductFilterInput is deprecated, use @ProductAttributeFilterInput instead. ProductFilterInput defines the filters to be used in the search. A filter contains at least one attribute, a comparison operator, and the value that is being searched for.") { diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Test/AdminCreateProductWithZeroMaximumQtyAllowedInShoppingCartTest.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Test/AdminCreateProductWithZeroMaximumQtyAllowedInShoppingCartTest.xml index 43f16c0fa475e..edbf7451c6b6b 100644 --- a/app/code/Magento/CatalogInventory/Test/Mftf/Test/AdminCreateProductWithZeroMaximumQtyAllowedInShoppingCartTest.xml +++ b/app/code/Magento/CatalogInventory/Test/Mftf/Test/AdminCreateProductWithZeroMaximumQtyAllowedInShoppingCartTest.xml @@ -54,7 +54,9 @@ - + + + diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithSpecialPricesTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithSpecialPricesTest.xml index db25ffc9c68b2..f8b477e796c9d 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithSpecialPricesTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithSpecialPricesTest.xml @@ -98,7 +98,7 @@ - + @@ -106,7 +106,7 @@ - + diff --git a/app/code/Magento/CatalogRuleGraphQl/README.md b/app/code/Magento/CatalogRuleGraphQl/README.md new file mode 100644 index 0000000000000..6f9761fedecbb --- /dev/null +++ b/app/code/Magento/CatalogRuleGraphQl/README.md @@ -0,0 +1,3 @@ +# CatalogRuleGraphQl + +The *Magento_CatalogRuleGraphQl* module applies catalog rules to products for GraphQL requests. \ No newline at end of file diff --git a/app/code/Magento/CatalogRuleGraphQl/composer.json b/app/code/Magento/CatalogRuleGraphQl/composer.json new file mode 100644 index 0000000000000..f2aae6c79d16b --- /dev/null +++ b/app/code/Magento/CatalogRuleGraphQl/composer.json @@ -0,0 +1,24 @@ +{ + "name": "magento/module-catalog-rule-graph-ql", + "description": "N/A", + "type": "magento2-module", + "require": { + "php": "~7.1.3||~7.2.0||~7.3.0", + "magento/framework": "*" + }, + "suggest": { + "magento/module-catalog-rule": "*" + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\CatalogRuleGraphQl\\": "" + } + } +} diff --git a/app/code/Magento/CatalogRuleGraphQl/etc/graphql/events.xml b/app/code/Magento/CatalogRuleGraphQl/etc/graphql/events.xml new file mode 100644 index 0000000000000..0d0a1fef5354f --- /dev/null +++ b/app/code/Magento/CatalogRuleGraphQl/etc/graphql/events.xml @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/app/code/Magento/CatalogRuleGraphQl/etc/module.xml b/app/code/Magento/CatalogRuleGraphQl/etc/module.xml new file mode 100644 index 0000000000000..affc22f35a738 --- /dev/null +++ b/app/code/Magento/CatalogRuleGraphQl/etc/module.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/app/code/Magento/CatalogRuleGraphQl/registration.php b/app/code/Magento/CatalogRuleGraphQl/registration.php new file mode 100644 index 0000000000000..df4dcd1cb47b6 --- /dev/null +++ b/app/code/Magento/CatalogRuleGraphQl/registration.php @@ -0,0 +1,9 @@ + - + @@ -224,7 +224,7 @@ - + @@ -506,7 +506,9 @@ - + + + @@ -573,7 +575,9 @@ - + + + diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontQuickSearchConfigurableChildrenTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontQuickSearchConfigurableChildrenTest.xml index 5c3f55c7212ad..954f53814f3c3 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontQuickSearchConfigurableChildrenTest.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontQuickSearchConfigurableChildrenTest.xml @@ -92,7 +92,7 @@ - + diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.xml b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.xml index 453f18003e694..a7b18dcd8a8c9 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.xml +++ b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.xml @@ -37,7 +37,9 @@ - + + + diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductNameMinicartOnCheckoutPageDifferentStoreViewsTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductNameMinicartOnCheckoutPageDifferentStoreViewsTest.xml index 1fff7501f578d..a5f0aaef49175 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductNameMinicartOnCheckoutPageDifferentStoreViewsTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductNameMinicartOnCheckoutPageDifferentStoreViewsTest.xml @@ -41,7 +41,9 @@ - + + + diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdatePriceInShoppingCartAfterProductSaveTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdatePriceInShoppingCartAfterProductSaveTest.xml index 6208b43bcb39b..e3a5546311f3b 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdatePriceInShoppingCartAfterProductSaveTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdatePriceInShoppingCartAfterProductSaveTest.xml @@ -52,7 +52,9 @@ - + + + diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminAddingNewOptionsWithImagesAndPricesToConfigurableProductTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminAddingNewOptionsWithImagesAndPricesToConfigurableProductTest.xml index 543ead3f6732a..07d9719b24d96 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminAddingNewOptionsWithImagesAndPricesToConfigurableProductTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminAddingNewOptionsWithImagesAndPricesToConfigurableProductTest.xml @@ -38,7 +38,9 @@ - + + + diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest.xml index 99cd0cd4242a7..e14e181ae5cce 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest.xml @@ -95,7 +95,9 @@ - + + + diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest.xml index e50a851589239..b56d8823643df 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest.xml @@ -158,7 +158,9 @@ - + + + @@ -254,7 +256,9 @@ - + + + diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml index 361d58c147d38..0bccd2f76982a 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml @@ -50,7 +50,9 @@ - + + + @@ -89,7 +91,9 @@ - + + + @@ -157,7 +161,9 @@ - + + + diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ConfigurableProductPriceAdditionalStoreViewTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ConfigurableProductPriceAdditionalStoreViewTest.xml index a5f62da42575e..23c3de4675718 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ConfigurableProductPriceAdditionalStoreViewTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ConfigurableProductPriceAdditionalStoreViewTest.xml @@ -108,7 +108,9 @@ - + + + @@ -145,7 +147,9 @@ - + + + @@ -166,14 +170,18 @@ - + + + - + + + @@ -185,14 +193,18 @@ - + + + - + + + diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NewProductsListWidgetConfigurableProductTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NewProductsListWidgetConfigurableProductTest.xml index 3dc706645a33b..372aa03e4e152 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NewProductsListWidgetConfigurableProductTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NewProductsListWidgetConfigurableProductTest.xml @@ -78,7 +78,9 @@ - + + + diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NoOptionAvailableToConfigureDisabledProductTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NoOptionAvailableToConfigureDisabledProductTest.xml index dbbeb4e252ef7..e0263c5c011b9 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NoOptionAvailableToConfigureDisabledProductTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NoOptionAvailableToConfigureDisabledProductTest.xml @@ -109,13 +109,17 @@ - + + + - + + + diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontShouldSeeOnlyConfigurableProductChildAssignedToSeparateCategoryTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontShouldSeeOnlyConfigurableProductChildAssignedToSeparateCategoryTest.xml index 9296ebc7cece5..ddb8190c076ee 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontShouldSeeOnlyConfigurableProductChildAssignedToSeparateCategoryTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontShouldSeeOnlyConfigurableProductChildAssignedToSeparateCategoryTest.xml @@ -101,7 +101,7 @@ - + diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontVisibilityOfDuplicateProductTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontVisibilityOfDuplicateProductTest.xml index 6befc15044cc5..aa9b0ac01872a 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontVisibilityOfDuplicateProductTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontVisibilityOfDuplicateProductTest.xml @@ -49,7 +49,9 @@ - + + + diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml index f2b6dc9e8a809..07c84e3f8e6e0 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml @@ -54,7 +54,9 @@ - + + + diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/VerifyDisableDownloadableProductSamplesAreNotAccessibleTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/VerifyDisableDownloadableProductSamplesAreNotAccessibleTest.xml index b641a2541ff98..919fe4b3c645e 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/VerifyDisableDownloadableProductSamplesAreNotAccessibleTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/VerifyDisableDownloadableProductSamplesAreNotAccessibleTest.xml @@ -87,7 +87,7 @@ - + diff --git a/app/code/Magento/Fedex/Test/Mftf/Test/AdminCreatingShippingLabelTest.xml b/app/code/Magento/Fedex/Test/Mftf/Test/AdminCreatingShippingLabelTest.xml index c0b602e772b54..a596f40efa66f 100644 --- a/app/code/Magento/Fedex/Test/Mftf/Test/AdminCreatingShippingLabelTest.xml +++ b/app/code/Magento/Fedex/Test/Mftf/Test/AdminCreatingShippingLabelTest.xml @@ -78,7 +78,9 @@ - + + + diff --git a/app/code/Magento/Indexer/Test/Mftf/Test/AdminSystemIndexManagementGridChangesTest.xml b/app/code/Magento/Indexer/Test/Mftf/Test/AdminSystemIndexManagementGridChangesTest.xml index 75b040a623451..30347ec1a64d0 100644 --- a/app/code/Magento/Indexer/Test/Mftf/Test/AdminSystemIndexManagementGridChangesTest.xml +++ b/app/code/Magento/Indexer/Test/Mftf/Test/AdminSystemIndexManagementGridChangesTest.xml @@ -44,7 +44,9 @@ - + + + diff --git a/app/code/Magento/LayeredNavigation/Test/Mftf/Test/ShopByButtonInMobileTest.xml b/app/code/Magento/LayeredNavigation/Test/Mftf/Test/ShopByButtonInMobileTest.xml index 0e0eb352c8d33..b5fc483e251eb 100644 --- a/app/code/Magento/LayeredNavigation/Test/Mftf/Test/ShopByButtonInMobileTest.xml +++ b/app/code/Magento/LayeredNavigation/Test/Mftf/Test/ShopByButtonInMobileTest.xml @@ -45,7 +45,9 @@ - + + + diff --git a/app/code/Magento/Msrp/Test/Mftf/Test/StorefrontProductWithMapAssignedConfigProductIsCorrectTest.xml b/app/code/Magento/Msrp/Test/Mftf/Test/StorefrontProductWithMapAssignedConfigProductIsCorrectTest.xml index 72443a41d67f4..a9d1d7b11d43e 100644 --- a/app/code/Magento/Msrp/Test/Mftf/Test/StorefrontProductWithMapAssignedConfigProductIsCorrectTest.xml +++ b/app/code/Magento/Msrp/Test/Mftf/Test/StorefrontProductWithMapAssignedConfigProductIsCorrectTest.xml @@ -111,7 +111,9 @@ - + + + @@ -119,7 +121,9 @@ - + + + diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/Address/SaveQuoteAddressToCustomerAddressBook.php b/app/code/Magento/QuoteGraphQl/Model/Cart/Address/SaveQuoteAddressToCustomerAddressBook.php deleted file mode 100644 index c87101156327e..0000000000000 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/Address/SaveQuoteAddressToCustomerAddressBook.php +++ /dev/null @@ -1,104 +0,0 @@ -addressFactory = $addressFactory; - $this->addressRepository = $addressRepository; - $this->regionFactory = $regionFactory; - } - - /** - * Save Address to Customer Address Book. - * - * @param QuoteAddress $quoteAddress - * @param int $customerId - * - * @return void - * @throws GraphQlInputException - */ - public function execute(QuoteAddress $quoteAddress, int $customerId): void - { - try { - /** @var AddressInterface $customerAddress */ - $customerAddress = $this->addressFactory->create(); - $customerAddress->setFirstname($quoteAddress->getFirstname()) - ->setLastname($quoteAddress->getLastname()) - ->setMiddlename($quoteAddress->getMiddlename()) - ->setPrefix($quoteAddress->getPrefix()) - ->setSuffix($quoteAddress->getSuffix()) - ->setVatId($quoteAddress->getVatId()) - ->setCountryId($quoteAddress->getCountryId()) - ->setCompany($quoteAddress->getCompany()) - ->setRegionId($quoteAddress->getRegionId()) - ->setFax($quoteAddress->getFax()) - ->setCity($quoteAddress->getCity()) - ->setPostcode($quoteAddress->getPostcode()) - ->setStreet($quoteAddress->getStreet()) - ->setTelephone($quoteAddress->getTelephone()) - ->setCustomerId($customerId); - - /** @var RegionInterface $region */ - $region = $this->regionFactory->create(); - $region->setRegionCode($quoteAddress->getRegionCode()) - ->setRegion($quoteAddress->getRegion()) - ->setRegionId($quoteAddress->getRegionId()); - $customerAddress->setRegion($region); - - $this->addressRepository->save($customerAddress); - } catch (InputException $inputException) { - $graphQlInputException = new GraphQlInputException(__($inputException->getMessage())); - $errors = $inputException->getErrors(); - foreach ($errors as $error) { - $graphQlInputException->addError(new GraphQlInputException(__($error->getMessage()))); - } - throw $graphQlInputException; - } catch (LocalizedException $exception) { - throw new GraphQlInputException(__($exception->getMessage()), $exception); - } - } -} diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/GetShippingAddress.php b/app/code/Magento/QuoteGraphQl/Model/Cart/GetShippingAddress.php index 2a57c281de183..f0b69885b6e40 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/GetShippingAddress.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/GetShippingAddress.php @@ -12,10 +12,9 @@ use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Quote\Model\Quote\Address; -use Magento\QuoteGraphQl\Model\Cart\Address\SaveQuoteAddressToCustomerAddressBook; /** - * Get shipping address + * Model for getting shipping address */ class GetShippingAddress { @@ -24,21 +23,13 @@ class GetShippingAddress */ private $quoteAddressFactory; - /** - * @var SaveQuoteAddressToCustomerAddressBook - */ - private $saveQuoteAddressToCustomerAddressBook; - /** * @param QuoteAddressFactory $quoteAddressFactory - * @param SaveQuoteAddressToCustomerAddressBook $saveQuoteAddressToCustomerAddressBook */ public function __construct( - QuoteAddressFactory $quoteAddressFactory, - SaveQuoteAddressToCustomerAddressBook $saveQuoteAddressToCustomerAddressBook + QuoteAddressFactory $quoteAddressFactory ) { $this->quoteAddressFactory = $quoteAddressFactory; - $this->saveQuoteAddressToCustomerAddressBook = $saveQuoteAddressToCustomerAddressBook; } /** @@ -96,25 +87,15 @@ private function createShippingAddress( if (null === $customerAddressId) { $shippingAddress = $this->quoteAddressFactory->createBasedOnInputData($addressInput); - - // need to save address only for registered user and if save_in_address_book = true - if (0 !== $customerId - && isset($addressInput['save_in_address_book']) - && (bool)$addressInput['save_in_address_book'] === true - ) { - $this->saveQuoteAddressToCustomerAddressBook->execute($shippingAddress, $customerId); - } } else { if (false === $context->getExtensionAttributes()->getIsCustomer()) { throw new GraphQlAuthorizationException(__('The current customer isn\'t authorized.')); } - $shippingAddress = $this->quoteAddressFactory->createBasedOnCustomerAddress( (int)$customerAddressId, $customerId ); } - return $shippingAddress; } } diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php index 20a3677ef1feb..e600965923c14 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php @@ -13,7 +13,6 @@ use Magento\GraphQl\Model\Query\ContextInterface; use Magento\Quote\Api\Data\CartInterface; use Magento\Quote\Model\Quote\Address; -use Magento\QuoteGraphQl\Model\Cart\Address\SaveQuoteAddressToCustomerAddressBook; /** * Set billing address for a specified shopping cart @@ -30,24 +29,16 @@ class SetBillingAddressOnCart */ private $assignBillingAddressToCart; - /** - * @var SaveQuoteAddressToCustomerAddressBook - */ - private $saveQuoteAddressToCustomerAddressBook; - /** * @param QuoteAddressFactory $quoteAddressFactory * @param AssignBillingAddressToCart $assignBillingAddressToCart - * @param SaveQuoteAddressToCustomerAddressBook $saveQuoteAddressToCustomerAddressBook */ public function __construct( QuoteAddressFactory $quoteAddressFactory, - AssignBillingAddressToCart $assignBillingAddressToCart, - SaveQuoteAddressToCustomerAddressBook $saveQuoteAddressToCustomerAddressBook + AssignBillingAddressToCart $assignBillingAddressToCart ) { $this->quoteAddressFactory = $quoteAddressFactory; $this->assignBillingAddressToCart = $assignBillingAddressToCart; - $this->saveQuoteAddressToCustomerAddressBook = $saveQuoteAddressToCustomerAddressBook; } /** @@ -113,15 +104,6 @@ private function createBillingAddress( ): Address { if (null === $customerAddressId) { $billingAddress = $this->quoteAddressFactory->createBasedOnInputData($addressInput); - - $customerId = $context->getUserId(); - // need to save address only for registered user and if save_in_address_book = true - if (0 !== $customerId - && isset($addressInput['save_in_address_book']) - && (bool)$addressInput['save_in_address_book'] === true - ) { - $this->saveQuoteAddressToCustomerAddressBook->execute($billingAddress, $customerId); - } } else { if (false === $context->getExtensionAttributes()->getIsCustomer()) { throw new GraphQlAuthorizationException(__('The current customer isn\'t authorized.')); @@ -133,7 +115,6 @@ private function createBillingAddress( ); } $errors = $billingAddress->validate(); - if (true !== $errors) { $e = new GraphQlInputException(__('Billing address errors')); foreach ($errors as $error) { @@ -141,7 +122,6 @@ private function createBillingAddress( } throw $e; } - return $billingAddress; } } diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/AvailableShippingMethods.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/AvailableShippingMethods.php index 1eb481bc561d2..74513382df821 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/AvailableShippingMethods.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/AvailableShippingMethods.php @@ -59,7 +59,8 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value if (!isset($value['model'])) { throw new LocalizedException(__('"model" values should be specified')); } - $address = $value['model']; + $address = clone $value['model']; + $address->setLimitCarrier(null); // Allow shipping rates by setting country id for new addresses if (!$address->getCountryId() && $address->getCountryCode()) { diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml index d7d5036c45c8f..ee01abcc989de 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml @@ -52,7 +52,9 @@ - + + + diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithSimpleProductCustomOptionFileTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithSimpleProductCustomOptionFileTest.xml index b694363960def..d1417ba1bc267 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithSimpleProductCustomOptionFileTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithSimpleProductCustomOptionFileTest.xml @@ -33,7 +33,9 @@ - + + + diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminProductInTheShoppingCartCouldBeReachedByAdminDuringOrderCreationWithMultiWebsiteConfigTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminProductInTheShoppingCartCouldBeReachedByAdminDuringOrderCreationWithMultiWebsiteConfigTest.xml index 4f0d777ad0f21..eb9d48cbbd567 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminProductInTheShoppingCartCouldBeReachedByAdminDuringOrderCreationWithMultiWebsiteConfigTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminProductInTheShoppingCartCouldBeReachedByAdminDuringOrderCreationWithMultiWebsiteConfigTest.xml @@ -37,7 +37,7 @@ - + diff --git a/app/code/Magento/Shipping/Test/Mftf/Test/AdminCreateOrderCustomStoreShippingMethodTableRatesTest.xml b/app/code/Magento/Shipping/Test/Mftf/Test/AdminCreateOrderCustomStoreShippingMethodTableRatesTest.xml index 5319992efa585..a4c155e9d3753 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Test/AdminCreateOrderCustomStoreShippingMethodTableRatesTest.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Test/AdminCreateOrderCustomStoreShippingMethodTableRatesTest.xml @@ -76,7 +76,9 @@ - + + + diff --git a/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontProductInfoMainSection.xml index 18ea4152939ac..a4bff2227ffbb 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontProductInfoMainSection.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontProductInfoMainSection.xml @@ -9,13 +9,13 @@
- + - +
diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateTextSwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateTextSwatchTest.xml index b01dff6a8ca06..5437489fd5697 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateTextSwatchTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateTextSwatchTest.xml @@ -6,8 +6,7 @@ */ --> - + @@ -83,19 +82,19 @@ - + Something red. {$grabRedLabel} - + Something green. {$grabGreenLabel} - + Something blue. {$grabBlueLabel} diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontImageColorWhenFilterByColorFilterTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontImageColorWhenFilterByColorFilterTest.xml index 18e9f82e74121..4287d2a91c4ee 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontImageColorWhenFilterByColorFilterTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontImageColorWhenFilterByColorFilterTest.xml @@ -41,7 +41,9 @@ - + + + diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSeeProductImagesMatchingProductSwatchesTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSeeProductImagesMatchingProductSwatchesTest.xml index 8bfdb77cbe177..22ce176da8233 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSeeProductImagesMatchingProductSwatchesTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSeeProductImagesMatchingProductSwatchesTest.xml @@ -63,7 +63,7 @@ - + diff --git a/app/code/Magento/Swatches/view/base/web/js/swatch-renderer.js b/app/code/Magento/Swatches/view/base/web/js/swatch-renderer.js index 3e100d2c39168..a45ee933584ba 100644 --- a/app/code/Magento/Swatches/view/base/web/js/swatch-renderer.js +++ b/app/code/Magento/Swatches/view/base/web/js/swatch-renderer.js @@ -63,12 +63,12 @@ define([ /** * Render tooltips by attributes (only to up). * Required element attributes: - * - option-type (integer, 0-3) - * - option-label (string) - * - option-tooltip-thumb - * - option-tooltip-value - * - thumb-width - * - thumb-height + * - data-option-type (integer, 0-3) + * - data-option-label (string) + * - data-option-tooltip-thumb + * - data-option-tooltip-value + * - data-thumb-width + * - data-thumb-height */ $.widget('mage.SwatchRendererTooltip', { options: { @@ -84,12 +84,12 @@ define([ $this = this.element, $element = $('.' + $widget.options.tooltipClass), timer, - type = parseInt($this.attr('option-type'), 10), - label = $this.attr('option-label'), - thumb = $this.attr('option-tooltip-thumb'), - value = $this.attr('option-tooltip-value'), - width = $this.attr('thumb-width'), - height = $this.attr('thumb-height'), + type = parseInt($this.data('option-type'), 10), + label = $this.data('option-label'), + thumb = $this.data('option-tooltip-thumb'), + value = $this.data('option-tooltip-value'), + width = $this.data('thumb-width'), + height = $this.data('thumb-height'), $image, $title, $corner; @@ -429,8 +429,8 @@ define([ // Create new control container.append( '
' + + 'data-attribute-code="' + item.code + '" ' + + 'data-attribute-id="' + item.id + '">' + label + '
' + - ''; + ''; $.each(config.options, function () { var label = this.label, - attr = ' value="' + this.id + '" option-id="' + this.id + '"'; + attr = ' value="' + this.id + '" data-option-id="' + this.id + '"'; if (!this.hasOwnProperty('products') || this.products.length <= 0) { - attr += ' option-empty="true"'; + attr += ' data-option-empty="true"'; } html += ''; @@ -721,7 +723,7 @@ define([ var $parent = $this.parents('.' + $widget.options.classes.attributeClass), $wrapper = $this.parents('.' + $widget.options.classes.attributeOptionsWrapper), $label = $parent.find('.' + $widget.options.classes.attributeSelectedOptionLabelClass), - attributeId = $parent.attr('attribute-id'), + attributeId = $parent.data('attribute-id'), $input = $parent.find('.' + $widget.options.classes.attributeInput), checkAdditionalData = JSON.parse(this.options.jsonSwatchConfig[attributeId]['additional_data']), $priceBox = $widget.element.parents($widget.options.selectorProduct) @@ -738,14 +740,14 @@ define([ } if ($this.hasClass('selected')) { - $parent.removeAttr('option-selected').find('.selected').removeClass('selected'); + $parent.removeAttr('data-option-selected').find('.selected').removeClass('selected'); $input.val(''); $label.text(''); $this.attr('aria-checked', false); } else { - $parent.attr('option-selected', $this.attr('option-id')).find('.selected').removeClass('selected'); - $label.text($this.attr('option-label')); - $input.val($this.attr('option-id')); + $parent.attr('data-option-selected', $this.data('option-id')).find('.selected').removeClass('selected'); + $label.text($this.data('option-label')); + $input.val($this.data('option-id')); $input.attr('data-attr-name', this._getAttributeCodeById(attributeId)); $this.addClass('selected'); $widget._toggleCheckedAttributes($this, $wrapper); @@ -822,7 +824,7 @@ define([ */ _OnChange: function ($this, $widget) { var $parent = $this.parents('.' + $widget.options.classes.attributeClass), - attributeId = $parent.attr('attribute-id'), + attributeId = $parent.data('attribute-id'), $input = $parent.find('.' + $widget.options.classes.attributeInput); if ($widget.productForm.length > 0) { @@ -832,10 +834,10 @@ define([ } if ($this.val() > 0) { - $parent.attr('option-selected', $this.val()); + $parent.attr('data-option-selected', $this.val()); $input.val($this.val()); } else { - $parent.removeAttr('option-selected'); + $parent.removeAttr('data-option-selected'); $input.val(''); } @@ -862,8 +864,8 @@ define([ * @private */ _Rewind: function (controls) { - controls.find('div[option-id], option[option-id]').removeClass('disabled').removeAttr('disabled'); - controls.find('div[option-empty], option[option-empty]') + controls.find('div[data-option-id], option[data-option-id]').removeClass('disabled').removeAttr('disabled'); + controls.find('div[data-option-empty], option[data-option-empty]') .attr('disabled', true) .addClass('disabled') .attr('tabindex', '-1'); @@ -876,8 +878,8 @@ define([ */ _Rebuild: function () { var $widget = this, - controls = $widget.element.find('.' + $widget.options.classes.attributeClass + '[attribute-id]'), - selected = controls.filter('[option-selected]'); + controls = $widget.element.find('.' + $widget.options.classes.attributeClass + '[data-attribute-id]'), + selected = controls.filter('[data-option-selected]'); // Enable all options $widget._Rewind(controls); @@ -890,16 +892,16 @@ define([ // Disable not available options controls.each(function () { var $this = $(this), - id = $this.attr('attribute-id'), + id = $this.data('attribute-id'), products = $widget._CalcProducts(id); - if (selected.length === 1 && selected.first().attr('attribute-id') === id) { + if (selected.length === 1 && selected.first().data('attribute-id') === id) { return; } - $this.find('[option-id]').each(function () { + $this.find('[data-option-id]').each(function () { var $element = $(this), - option = $element.attr('option-id'); + option = $element.data('option-id'); if (!$widget.optionsMap.hasOwnProperty(id) || !$widget.optionsMap[id].hasOwnProperty(option) || $element.hasClass('selected') || @@ -922,12 +924,13 @@ define([ */ _CalcProducts: function ($skipAttributeId) { var $widget = this, + selectedOptions = '.' + $widget.options.classes.attributeClass + '[data-option-selected]', products = []; // Generate intersection of products - $widget.element.find('.' + $widget.options.classes.attributeClass + '[option-selected]').each(function () { - var id = $(this).attr('attribute-id'), - option = $(this).attr('option-selected'); + $widget.element.find(selectedOptions).each(function () { + var id = $(this).data('attribute-id'), + option = $(this).attr('data-option-selected'); if ($skipAttributeId !== undefined && $skipAttributeId === id) { return; @@ -1353,7 +1356,7 @@ define([ _EmulateSelected: function (selectedAttributes) { $.each(selectedAttributes, $.proxy(function (attributeCode, optionId) { var elem = this.element.find('.' + this.options.classes.attributeClass + - '[attribute-code="' + attributeCode + '"] [option-id="' + optionId + '"]'), + '[data-attribute-code="' + attributeCode + '"] [data-option-id="' + optionId + '"]'), parentInput = elem.parent(); if (elem.hasClass('selected')) { @@ -1377,7 +1380,7 @@ define([ _EmulateSelectedByAttributeId: function (selectedAttributes) { $.each(selectedAttributes, $.proxy(function (attributeId, optionId) { var elem = this.element.find('.' + this.options.classes.attributeClass + - '[attribute-id="' + attributeId + '"] [option-id="' + optionId + '"]'), + '[data-attribute-id="' + attributeId + '"] [data-option-id="' + optionId + '"]'), parentInput = elem.parent(); if (elem.hasClass('selected')) { diff --git a/app/code/Magento/Swatches/view/frontend/templates/product/layered/renderer.phtml b/app/code/Magento/Swatches/view/frontend/templates/product/layered/renderer.phtml index 7cb9b06713ef2..70eb51651f663 100644 --- a/app/code/Magento/Swatches/view/frontend/templates/product/layered/renderer.phtml +++ b/app/code/Magento/Swatches/view/frontend/templates/product/layered/renderer.phtml @@ -11,8 +11,8 @@ ?> getSwatchData(); ?>
+ data-attribute-code="escapeHtmlAttr($swatchData['attribute_code']) ?>" + data-attribute-id="escapeHtmlAttr($swatchData['attribute_id']) ?>">
$label): ?>
@@ -59,27 +59,28 @@ ?>
+ ) ?> no-repeat center; background-size: initial;"> +
escapeHtml($swatchData['swatches'][$option]['value']) ?>
@@ -92,7 +93,7 @@ diff --git a/app/code/Magento/Weee/Test/Mftf/Test/AdminFixedTaxValSavedForSpecificWebsiteTest.xml b/app/code/Magento/Weee/Test/Mftf/Test/AdminFixedTaxValSavedForSpecificWebsiteTest.xml index b929fd3d304c0..b542d6a4d0364 100644 --- a/app/code/Magento/Weee/Test/Mftf/Test/AdminFixedTaxValSavedForSpecificWebsiteTest.xml +++ b/app/code/Magento/Weee/Test/Mftf/Test/AdminFixedTaxValSavedForSpecificWebsiteTest.xml @@ -70,7 +70,9 @@ - + + + @@ -99,7 +101,9 @@ - + + + diff --git a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml index 8caf49ca2c374..c35a6309f1f3d 100644 --- a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml +++ b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml @@ -42,7 +42,7 @@ - + diff --git a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml index 388a254c7028b..d2246f07dd7cb 100644 --- a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml +++ b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml @@ -40,7 +40,7 @@ 10.00 - + diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontDeleteBundleDynamicProductFromWishlistTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontDeleteBundleDynamicProductFromWishlistTest.xml index 3ff3fe0f379ce..6238f0dcbff09 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontDeleteBundleDynamicProductFromWishlistTest.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontDeleteBundleDynamicProductFromWishlistTest.xml @@ -48,7 +48,7 @@ - + diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontMoveDynamicBundleProductFromShoppingCartToWishlistTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontMoveDynamicBundleProductFromShoppingCartToWishlistTest.xml index 6af7c6eae2c2a..e07315e0ee0a4 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontMoveDynamicBundleProductFromShoppingCartToWishlistTest.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontMoveDynamicBundleProductFromShoppingCartToWishlistTest.xml @@ -46,7 +46,7 @@ - + diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/WishListWithDisabledProductTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/WishListWithDisabledProductTest.xml index f9072402dbd73..227212cd05ae8 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Test/WishListWithDisabledProductTest.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/WishListWithDisabledProductTest.xml @@ -40,7 +40,9 @@ - + + + diff --git a/composer.json b/composer.json index 50e8db204f051..f9677d6feca54 100644 --- a/composer.json +++ b/composer.json @@ -133,6 +133,7 @@ "magento/module-catalog-inventory": "*", "magento/module-catalog-inventory-graph-ql": "*", "magento/module-catalog-rule": "*", + "magento/module-catalog-rule-graph-ql": "*", "magento/module-catalog-rule-configurable": "*", "magento/module-catalog-search": "*", "magento/module-catalog-url-rewrite": "*", diff --git a/composer.lock b/composer.lock index f6894e3c31d09..937567f5fe64a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "dc4c877a835fbea7420d8477ade2fe4f", + "content-hash": "5d91717fba26046648157ebf69639b5a", "packages": [ { "name": "braintree/braintree_php", diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoriesQuery/CategoriesFilterTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoriesQuery/CategoriesFilterTest.php new file mode 100644 index 0000000000000..b30eb24da6a2d --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoriesQuery/CategoriesFilterTest.php @@ -0,0 +1,735 @@ +graphQlQuery($query); + $this->assertArrayNotHasKey('errors', $result); + $this->assertCount(1, $result['categories']['items']); + $this->assertResponseFields($result['categories']['items'][0], $expectedResult); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + * @dataProvider filterMultipleCategoriesDataProvider + * @param $field + * @param $condition + * @param $value + * @param $expectedResult + */ + public function testFilterMultipleCategoriesByField($field, $condition, $value, $expectedResult) + { + $query = <<graphQlQuery($query); + $this->assertArrayNotHasKey('errors', $result); + $this->assertCount(count($expectedResult), $result['categories']['items']); + foreach ($expectedResult as $i => $expected) { + $this->assertResponseFields($result['categories']['items'][$i], $expected); + } + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + */ + public function testFilterCategoryByMultipleFields() + { + $query = <<graphQlQuery($query); + $this->assertArrayNotHasKey('errors', $result); + $this->assertCount(3, $result['categories']['items']); + $this->assertEquals(3, $result['categories']['total_count']); + + $expectedCategories = [7 => 'Movable', 9 => 'Movable Position 1', 10 => 'Movable Position 2']; + $actualCategories = array_column($result['categories']['items'], 'name', 'id'); + $this->assertEquals($expectedCategories, $actualCategories); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + */ + public function testFilterWithInactiveCategory() + { + $query = <<graphQlQuery($query); + $this->assertArrayNotHasKey('errors', $result); + $this->assertCount(1, $result['categories']['items']); + $actualCategories = array_column($result['categories']['items'], 'url_key', 'id'); + $this->assertContains('category-2', $actualCategories); + $this->assertNotContains('inactive', $actualCategories); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + */ + public function testQueryChildCategoriesWithProducts() + { + $query = <<graphQlQuery($query); + + $this->assertArrayNotHasKey('errors', $result); + $this->assertCount(1, $result['categories']['items']); + $baseCategory = $result['categories']['items'][0]; + + $this->assertEquals('Category 1', $baseCategory['name']); + $this->assertArrayHasKey('products', $baseCategory); + //Check base category products + $expectedBaseCategoryProducts = [ + ['sku' => 'simple', 'name' => 'Simple Product'], + ['sku' => '12345', 'name' => 'Simple Product Two'], + ['sku' => 'simple-4', 'name' => 'Simple Product Three'] + ]; + $this->assertCategoryProducts($baseCategory, $expectedBaseCategoryProducts); + //Check base category children + $expectedBaseCategoryChildren = [ + ['name' => 'Category 1.1', 'description' => 'Category 1.1 description.'], + ['name' => 'Category 1.2', 'description' => 'Its a description of Test Category 1.2'] + ]; + $this->assertCategoryChildren($baseCategory, $expectedBaseCategoryChildren); + + //Check first child category + $firstChildCategory = $baseCategory['children'][0]; + $this->assertEquals('Category 1.1', $firstChildCategory['name']); + $this->assertEquals('Category 1.1 description.', $firstChildCategory['description']); + $firstChildCategoryExpectedProducts = [ + ['sku' => 'simple', 'name' => 'Simple Product'], + ['sku' => '12345', 'name' => 'Simple Product Two'], + ]; + $this->assertCategoryProducts($firstChildCategory, $firstChildCategoryExpectedProducts); + $firstChildCategoryChildren = [['name' =>'Category 1.1.1']]; + $this->assertCategoryChildren($firstChildCategory, $firstChildCategoryChildren); + //Check second child category + $secondChildCategory = $baseCategory['children'][1]; + $this->assertEquals('Category 1.2', $secondChildCategory['name']); + $this->assertEquals('Its a description of Test Category 1.2', $secondChildCategory['description']); + $firstChildCategoryExpectedProducts = [ + ['sku' => 'simple', 'name' => 'Simple Product'], + ['sku' => 'simple-4', 'name' => 'Simple Product Three'] + ]; + $this->assertCategoryProducts($secondChildCategory, $firstChildCategoryExpectedProducts); + $firstChildCategoryChildren = []; + $this->assertCategoryChildren($secondChildCategory, $firstChildCategoryChildren); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories_disabled.php + */ + public function testQueryCategoryWithDisabledChildren() + { + $query = <<graphQlQuery($query); + + $this->assertArrayNotHasKey('errors', $result); + $this->assertCount(1, $result['categories']['items']); + $baseCategory = $result['categories']['items'][0]; + + $this->assertEquals('Category 1', $baseCategory['name']); + $this->assertArrayHasKey('products', $baseCategory); + //Check base category products + $expectedBaseCategoryProducts = [ + ['sku' => 'simple', 'name' => 'Simple Product'], + ['sku' => '12345', 'name' => 'Simple Product Two'], + ['sku' => 'simple-4', 'name' => 'Simple Product Three'] + ]; + $this->assertCategoryProducts($baseCategory, $expectedBaseCategoryProducts); + //Check base category children + $expectedBaseCategoryChildren = [ + ['name' => 'Category 1.2', 'description' => 'Its a description of Test Category 1.2'] + ]; + $this->assertCategoryChildren($baseCategory, $expectedBaseCategoryChildren); + + //Check first child category + $firstChildCategory = $baseCategory['children'][0]; + $this->assertEquals('Category 1.2', $firstChildCategory['name']); + $this->assertEquals('Its a description of Test Category 1.2', $firstChildCategory['description']); + + $firstChildCategoryExpectedProducts = [ + ['sku' => 'simple', 'name' => 'Simple Product'], + ['sku' => 'simple-4', 'name' => 'Simple Product Three'] + ]; + $this->assertCategoryProducts($firstChildCategory, $firstChildCategoryExpectedProducts); + $firstChildCategoryChildren = []; + $this->assertCategoryChildren($firstChildCategory, $firstChildCategoryChildren); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + */ + public function testNoResultsFound() + { + $query = <<graphQlQuery($query); + $this->assertArrayNotHasKey('errors', $result); + $this->assertArrayHasKey('categories', $result); + $this->assertEquals([], $result['categories']['items']); + } + + /** + * When no filters are supplied, the root category is returned + * + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + */ + public function testEmptyFiltersReturnRootCategory() + { + $query = <<get(StoreManagerInterface::class); + $storeRootCategoryId = $storeManager->getStore()->getRootCategoryId(); + + $result = $this->graphQlQuery($query); + $this->assertArrayNotHasKey('errors', $result); + $this->assertArrayHasKey('categories', $result); + $this->assertEquals('Default Category', $result['categories']['items'][0]['name']); + $this->assertEquals($storeRootCategoryId, $result['categories']['items'][0]['id']); + } + + /** + * Filtering with match value less than minimum query should return empty result + * + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + * @expectedException \Exception + * @expectedExceptionMessage Invalid match filter. Minimum length is 3. + */ + public function testMinimumMatchQueryLength() + { + $query = <<graphQlQuery($query); + } + + /** + * Test category image full name is returned + * + * @magentoApiDataFixture Magento/Catalog/_files/catalog_category_with_long_image_name.php + */ + public function testCategoryImageName() + { + /** @var CategoryCollection $categoryCollection */ + $categoryCollection = Bootstrap::getObjectManager()->get(CategoryCollection::class); + $categoryModel = $categoryCollection + ->addAttributeToSelect('image') + ->addAttributeToFilter('name', ['eq' => 'Parent Image Category']) + ->getFirstItem(); + $categoryId = $categoryModel->getId(); + + $query = <<get(StoreManagerInterface::class); + $storeBaseUrl = $storeManager->getStore()->getBaseUrl('media'); + + $expected = "catalog/category/magento_long_image_name_magento_long_image_name_magento_long_image_name.jpg"; + $expectedImageUrl = rtrim($storeBaseUrl, '/') . '/' . $expected; + + $response = $this->graphQlQuery($query); + $categories = $response['categories']; + $this->assertArrayNotHasKey('errors', $response); + $this->assertNotEmpty($response['categories']['items']); + $expectedImageUrl = str_replace('index.php/', '', $expectedImageUrl); + $categories['items'][0]['image'] = str_replace('index.php/', '', $categories['items'][0]['image']); + $this->assertEquals('Parent Image Category', $categories['items'][0]['name']); + $this->assertEquals($expectedImageUrl, $categories['items'][0]['image']); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + */ + public function testFilterByUrlPathTopLevelCategory() + { + $urlPath = 'category-1'; + $query = <<graphQlQuery($query); + $this->assertArrayNotHasKey('errors', $response); + $categories = $response['categories']; + $this->assertCount(1, $categories); + $this->assertEquals($urlPath, $categories['items'][0]['url_path']); + $this->assertEquals('Category 1', $categories['items'][0]['name']); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + */ + public function testFilterByUrlPathNestedCategory() + { + $urlPath = 'category-1/category-1-1/category-1-1-1'; + $query = <<graphQlQuery($query); + $this->assertArrayNotHasKey('errors', $response); + $categories = $response['categories']; + $this->assertCount(1, $categories); + $this->assertEquals($urlPath, $categories['items'][0]['url_path']); + $this->assertEquals('Category 1.1.1', $categories['items'][0]['name']); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + */ + public function testFilterByUrlPathMultipleCategories() + { + $urlPaths = ['category-1/category-1-1', 'inactive', 'movable-position-2']; + $urlPathsString = '"' . implode('", "', $urlPaths) . '"'; + $query = <<graphQlQuery($query); + $this->assertArrayNotHasKey('errors', $response); + $categories = $response['categories']; + $this->assertCount(2, $categories['items']); + $this->assertEquals($urlPaths[0], $categories['items'][0]['url_path']); + $this->assertEquals('Category 1.1', $categories['items'][0]['name']); + $this->assertEquals($urlPaths[2], $categories['items'][1]['url_path']); + $this->assertEquals('Movable Position 2', $categories['items'][1]['name']); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + */ + public function testFilterByUrlPathNoResults() + { + $query = <<graphQlQuery($query); + $this->assertArrayNotHasKey('errors', $response); + $categories = $response['categories']; + $this->assertCount(0, $categories['items']); + } + + /** + * @return array + */ + public function filterSingleCategoryDataProvider(): array + { + return [ + [ + 'ids', + 'eq', + '4', + [ + 'id' => '4', + 'name' => 'Category 1.1', + 'url_key' => 'category-1-1', + 'url_path' => 'category-1/category-1-1', + 'children_count' => '0', + 'path' => '1/2/3/4', + 'position' => '1' + ] + ], + [ + 'name', + 'match', + 'Movable Position 2', + [ + 'id' => '10', + 'name' => 'Movable Position 2', + 'url_key' => 'movable-position-2', + 'url_path' => 'movable-position-2', + 'children_count' => '0', + 'path' => '1/2/10', + 'position' => '6' + ] + ], + [ + 'url_key', + 'eq', + 'category-1-1-1', + [ + 'id' => '5', + 'name' => 'Category 1.1.1', + 'url_key' => 'category-1-1-1', + 'url_path' => 'category-1/category-1-1/category-1-1-1', + 'children_count' => '0', + 'path' => '1/2/3/4/5', + 'position' => '1' + ] + ], + ]; + } + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @return array + */ + public function filterMultipleCategoriesDataProvider(): array + { + return[ + //Filter by multiple IDs + [ + 'ids', + 'in', + '["4", "9", "10"]', + [ + [ + 'id' => '4', + 'name' => 'Category 1.1', + 'url_key' => 'category-1-1', + 'url_path' => 'category-1/category-1-1', + 'children_count' => '0', + 'path' => '1/2/3/4', + 'position' => '1' + ], + [ + 'id' => '9', + 'name' => 'Movable Position 1', + 'url_key' => 'movable-position-1', + 'url_path' => 'movable-position-1', + 'children_count' => '0', + 'path' => '1/2/9', + 'position' => '5' + ], + [ + 'id' => '10', + 'name' => 'Movable Position 2', + 'url_key' => 'movable-position-2', + 'url_path' => 'movable-position-2', + 'children_count' => '0', + 'path' => '1/2/10', + 'position' => '6' + ] + ] + ], + //Filter by multiple url keys + [ + 'url_key', + 'in', + '["category-1-2", "movable"]', + [ + [ + 'id' => '7', + 'name' => 'Movable', + 'url_key' => 'movable', + 'url_path' => 'movable', + 'children_count' => '0', + 'path' => '1/2/7', + 'position' => '3' + ], + [ + 'id' => '13', + 'name' => 'Category 1.2', + 'url_key' => 'category-1-2', + 'url_path' => 'category-1/category-1-2', + 'children_count' => '0', + 'path' => '1/2/3/13', + 'position' => '2' + ] + ] + ], + //Filter by matching multiple names + [ + 'name', + 'match', + '"Position"', + [ + [ + 'id' => '9', + 'name' => 'Movable Position 1', + 'url_key' => 'movable-position-1', + 'url_path' => 'movable-position-1', + 'children_count' => '0', + 'path' => '1/2/9', + 'position' => '5' + ], + [ + 'id' => '10', + 'name' => 'Movable Position 2', + 'url_key' => 'movable-position-2', + 'url_path' => 'movable-position-2', + 'children_count' => '0', + 'path' => '1/2/10', + 'position' => '6' + ], + [ + 'id' => '11', + 'name' => 'Movable Position 3', + 'url_key' => 'movable-position-3', + 'url_path' => 'movable-position-3', + 'children_count' => '0', + 'path' => '1/2/11', + 'position' => '7' + ] + ] + ] + ]; + } + + /** + * Check category products + * + * @param array $category + * @param array $expectedProducts + */ + private function assertCategoryProducts(array $category, array $expectedProducts) + { + $this->assertEquals(count($expectedProducts), $category['products']['total_count']); + $this->assertCount(count($expectedProducts), $category['products']['items']); + $this->assertResponseFields($category['products']['items'], $expectedProducts); + } + + /** + * Check category child categories + * + * @param array $category + * @param array $expectedChildren + */ + private function assertCategoryChildren(array $category, array $expectedChildren) + { + $this->assertArrayHasKey('children', $category); + $this->assertCount(count($expectedChildren), $category['children']); + foreach ($expectedChildren as $i => $expectedChild) { + $this->assertResponseFields($category['children'][$i], $expectedChild); + } + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoriesQuery/CategoriesPaginationTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoriesQuery/CategoriesPaginationTest.php new file mode 100644 index 0000000000000..3d6982757b932 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoriesQuery/CategoriesPaginationTest.php @@ -0,0 +1,243 @@ +graphQlQuery($query); + $categories = $response['categories']['items']; + $this->assertCount(6, $categories); + $this->assertEquals(count($categories), $response['categories']['total_count']); + $this->assertArrayHasKey('page_info', $response['categories']); + $pageInfo = $response['categories']['page_info']; + $this->assertEquals(1, $pageInfo['current_page']); + $this->assertEquals(20, $pageInfo['page_size']); + $this->assertEquals(1, $pageInfo['total_pages']); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + */ + public function testPageSize() + { + $query = <<graphQlQuery($query); + $categories = $response['categories']['items']; + $this->assertCount(2, $categories); + $this->assertNotEquals(count($categories), $response['categories']['total_count']); + $this->assertEquals(6, $response['categories']['total_count']); + $pageInfo = $response['categories']['page_info']; + $this->assertEquals(1, $pageInfo['current_page']); + $this->assertEquals(2, $pageInfo['page_size']); + $this->assertEquals(3, $pageInfo['total_pages']); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + */ + public function testCurrentPage() + { + $query = <<graphQlQuery($query); + $categories = $response['categories']['items']; + $this->assertCount(1, $categories); + $this->assertEquals(7, $response['categories']['total_count']); + $pageInfo = $response['categories']['page_info']; + $this->assertEquals(3, $pageInfo['current_page']); + $this->assertEquals(3, $pageInfo['page_size']); + $this->assertEquals(3, $pageInfo['total_pages']); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + */ + public function testPaging() + { + $baseQuery = <<graphQlQuery($page1Query); + $this->assertEquals('Default Category', $page1Result['categories']['items'][0]['name']); + $this->assertEquals('Category 1', $page1Result['categories']['items'][1]['name']); + $this->assertEquals(7, $page1Result['categories']['total_count']); + + $page2Query = sprintf($baseQuery, 2); + $page2Result = $this->graphQlQuery($page2Query); + $this->assertEquals('Category 1.1', $page2Result['categories']['items'][0]['name']); + $this->assertEquals('Category 1.1.1', $page2Result['categories']['items'][1]['name']); + + $lastPageQuery = sprintf($baseQuery, $page1Result['categories']['page_info']['total_pages']); + $lastPageResult = $this->graphQlQuery($lastPageQuery); + $this->assertCount(1, $lastPageResult['categories']['items']); + $this->assertEquals('Category 1.2', $lastPageResult['categories']['items'][0]['name']); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage currentPage value must be greater than 0. + */ + public function testCurrentPageZero() + { + $query = <<graphQlQuery($query); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage pageSize value must be greater than 0. + */ + public function testPageSizeZero() + { + $query = <<graphQlQuery($query); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + * @expectedException \Exception + * @expectedExceptionMessage currentPage value 6 specified is greater than the 2 page(s) available. + */ + public function testCurrentPageTooLarge() + { + $query = <<graphQlQuery($query); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoriesQuery/CategoryTreeTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoriesQuery/CategoryTreeTest.php new file mode 100644 index 0000000000000..96b303c689683 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoriesQuery/CategoryTreeTest.php @@ -0,0 +1,741 @@ +objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->categoryRepository = $this->objectManager->get(CategoryRepository::class); + $this->store = $this->objectManager->get(Store::class); + $this->metadataPool = $this->objectManager->get(MetadataPool::class); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testCategoriesTree() + { + $rootCategoryId = 2; + $query = <<graphQlQuery($query); + $this->assertArrayNotHasKey('errors', $response); + $this->assertArrayHasKey('categories', $response); + $baseCategory = $response['categories']['items'][0]; + $this->assertEquals( + 'Its a description of Test Category 1.2', + $baseCategory['children'][0]['children'][1]['description'] + ); + $this->assertEquals('default-category', $baseCategory['url_key']); + $this->assertEquals([], $baseCategory['children'][0]['available_sort_by']); + $this->assertEquals('name', $baseCategory['children'][0]['default_sort_by']); + $this->assertCount(7, $baseCategory['children']); + $this->assertCount(2, $baseCategory['children'][0]['children']); + $this->assertEquals(13, $baseCategory['children'][0]['children'][1]['id']); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testRootCategoryTree() + { + $query = <<graphQlQuery($query); + $this->assertArrayNotHasKey('errors', $response); + $this->assertArrayHasKey('categories', $response); + $baseCategory = $response['categories']['items'][0]; + $this->assertEquals( + 'Its a description of Test Category 1.2', + $baseCategory['children'][0]['children'][1]['description'] + ); + $this->assertEquals('default-category', $baseCategory['url_key']); + $this->assertEquals([], $baseCategory['children'][0]['available_sort_by']); + $this->assertEquals('name', $baseCategory['children'][0]['default_sort_by']); + $this->assertCount(7, $baseCategory['children']); + $this->assertCount(2, $baseCategory['children'][0]['children']); + $this->assertEquals(13, $baseCategory['children'][0]['children'][1]['id']); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + */ + public function testCategoriesTreeWithDisabledCategory() + { + $category = $this->categoryRepository->get(3); + $category->setIsActive(false); + $this->categoryRepository->save($category); + + $rootCategoryId = 2; + $query = <<graphQlQuery($query); + + $this->assertArrayHasKey('categories', $response); + $this->assertArrayHasKey('children', $response['categories']['items'][0]); + $this->assertSame(6, count($response['categories']['items'][0]['children'])); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + */ + public function testGetCategoryById() + { + $categoryId = 13; + $query = <<graphQlQuery($query); + $this->assertEquals('Category 1.2', $response['categories']['items'][0]['name']); + $this->assertEquals(13, $response['categories']['items'][0]['id']); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + */ + public function testGetDisabledCategory() + { + $categoryId = 8; + $query = <<graphQlQuery($query); + $this->assertArrayHasKey('items', $response['categories']); + $this->assertEquals([], $response['categories']['items']); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + */ + public function testGetCategoryIdZero() + { + $query = <<graphQlQuery($query); + $this->assertArrayHasKey('items', $response['categories']); + $this->assertEquals([], $response['categories']['items']); + } + + public function testNonExistentCategoryWithProductCount() + { + $query = <<graphQlQuery($query); + $this->assertArrayHasKey('items', $response['categories']); + $this->assertEquals([], $response['categories']['items']); + $this->assertEquals(0, $response['categories']['total_count']); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testCategoryProducts() + { + $categoryId = 2; + $query = <<graphQlQuery($query); + $this->assertArrayHasKey('products', $response['categories']['items'][0]); + $baseCategory = $response['categories']['items'][0]; + $this->assertArrayHasKey('total_count', $baseCategory['products']); + $this->assertGreaterThanOrEqual(1, $baseCategory['products']['total_count']); + $this->assertEquals(1, $baseCategory['products']['page_info']['current_page']); + $this->assertEquals(20, $baseCategory['products']['page_info']['page_size']); + $this->assertArrayHasKey('sku', $baseCategory['products']['items'][0]); + $firstProduct = $baseCategory['products']['items'][0]; + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); + $firstProductModel = $productRepository->get($firstProduct['sku'], false, null, true); + $this->assertBaseFields($firstProductModel, $firstProduct); + $this->assertAttributes($firstProduct); + $this->assertWebsites($firstProductModel, $firstProduct['websites']); + $this->assertEquals('Category 1', $firstProduct['categories'][0]['name']); + $this->assertEquals('category-1/category-1-1', $firstProduct['categories'][1]['url_path']); + $this->assertCount(3, $firstProduct['categories']); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + */ + public function testAnchorCategory() + { + $query = <<graphQlQuery($query); + $expectedResponse = [ + 'categories' => [ + 'items' => [ + 0 => [ + 'name' => 'Category 1', + 'products' => [ + 'total_count' => 3, + 'items' => [ + ['sku' => '12345'], + ['sku' => 'simple-4'], + ['sku' => 'simple'] + ] + ] + ] + ] + ] + ]; + $this->assertEquals($expectedResponse, $response); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + */ + public function testBreadCrumbs() + { + $query = <<graphQlQuery($query); + $expectedResponse = [ + 'categories' => [ + 'items' => [ + 0 => [ + 'name' => 'Category 1.1.1', + 'breadcrumbs' => [ + [ + 'category_id' => 3, + 'category_name' => "Category 1", + 'category_level' => 2, + 'category_url_key' => "category-1", + 'category_url_path' => "category-1" + ], + [ + 'category_id' => 4, + 'category_name' => "Category 1.1", + 'category_level' => 3, + 'category_url_key' => "category-1-1", + 'category_url_path' => "category-1/category-1-1" + ], + ] + ] + ] + ] + ]; + $this->assertEquals($expectedResponse, $response); + } + + /** + * Test category image is returned as full url (not relative path) + * + * @param string $imagePrefix + * @magentoApiDataFixture Magento/Catalog/_files/catalog_category_with_image.php + * @dataProvider categoryImageDataProvider + */ + public function testCategoryImage(?string $imagePrefix) + { + /** @var CategoryCollection $categoryCollection */ + $categoryCollection = $this->objectManager->get(CategoryCollection::class); + $categoryModel = $categoryCollection + ->addAttributeToSelect('image') + ->addAttributeToFilter('name', ['eq' => 'Parent Image Category']) + ->getFirstItem(); + $categoryId = $categoryModel->getId(); + + if ($imagePrefix !== null) { + // update image to account for different stored image formats + $connection = $categoryCollection->getConnection(); + $productLinkField = $this->metadataPool + ->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class) + ->getLinkField(); + + $defaultStoreId = $this->store->getId(); + + $imageAttributeValue = $imagePrefix . basename($categoryModel->getImage()); + + if (!empty($imageAttributeValue)) { + $query = sprintf( + 'UPDATE %s SET `value` = "%s" ' . + 'WHERE `%s` = %d ' . + 'AND `store_id`= %d ' . + 'AND `attribute_id` = ' . + '(SELECT `ea`.`attribute_id` FROM %s ea WHERE `ea`.`attribute_code` = "image" LIMIT 1)', + $connection->getTableName('catalog_category_entity_varchar'), + $imageAttributeValue, + $productLinkField, + $categoryModel->getData($productLinkField), + $defaultStoreId, + $connection->getTableName('eav_attribute') + ); + $connection->query($query); + } + } + + $query = <<graphQlQuery($query); + $this->assertArrayNotHasKey('errors', $response); + $this->assertNotEmpty($response['categories']); + $categories = $response['categories']['items']; + $storeBaseUrl = $this->objectManager->get(StoreManagerInterface::class)->getStore()->getBaseUrl('media'); + $expectedImageUrl = rtrim($storeBaseUrl, '/') . '/' . ltrim($categoryModel->getImage(), '/'); + $expectedImageUrl = str_replace('index.php/', '', $expectedImageUrl); + + $this->assertEquals($categoryId, $categories[0]['id']); + $this->assertEquals('Parent Image Category', $categories[0]['name']); + $categories[0]['image'] = str_replace('index.php/', '', $categories[0]['image']); + $this->assertEquals($expectedImageUrl, $categories[0]['image']); + + $childCategory = $categories[0]['children'][0]; + $this->assertEquals('Child Image Category', $childCategory['name']); + $childCategory['image'] = str_replace('index.php/', '', $childCategory['image']); + $this->assertEquals($expectedImageUrl, $childCategory['image']); + } + + /** + * @return array + */ + public function categoryImageDataProvider(): array + { + return [ + 'default_filename_strategy' => [ + 'image_prefix' => null + ], + 'just_filename_strategy' => [ + 'image_prefix' => '' + ], + 'with_pub_media_strategy' => [ + 'image_prefix' => '/pub/media/catalog/category/' + ], + 'catalog_category_strategy' => [ + 'image_prefix' => 'catalog/category/' + ], + ]; + } + + /** + * @param ProductInterface $product + * @param array $actualResponse + */ + private function assertBaseFields($product, $actualResponse) + { + $assertionMap = [ + ['response_field' => 'attribute_set_id', 'expected_value' => $product->getAttributeSetId()], + ['response_field' => 'created_at', 'expected_value' => $product->getCreatedAt()], + ['response_field' => 'name', 'expected_value' => $product->getName()], + ['response_field' => 'price', 'expected_value' => [ + 'minimalPrice' => [ + 'amount' => [ + 'value' => $product->getPrice(), + 'currency' => 'USD' + ], + 'adjustments' => [] + ], + 'regularPrice' => [ + 'amount' => [ + 'value' => $product->getPrice(), + 'currency' => 'USD' + ], + 'adjustments' => [] + ], + 'maximalPrice' => [ + 'amount' => [ + 'value' => $product->getPrice(), + 'currency' => 'USD' + ], + 'adjustments' => [] + ], + ] + ], + ['response_field' => 'sku', 'expected_value' => $product->getSku()], + ['response_field' => 'type_id', 'expected_value' => $product->getTypeId()], + ['response_field' => 'updated_at', 'expected_value' => $product->getUpdatedAt()], + ]; + $this->assertResponseFields($actualResponse, $assertionMap); + } + + /** + * @param ProductInterface $product + * @param array $actualResponse + */ + private function assertWebsites($product, $actualResponse) + { + $assertionMap = [ + [ + 'id' => current($product->getExtensionAttributes()->getWebsiteIds()), + 'name' => 'Main Website', + 'code' => 'base', + 'sort_order' => 0, + 'default_group_id' => '1', + 'is_default' => true, + ] + ]; + $this->assertEquals($actualResponse, $assertionMap); + } + + /** + * @param array $actualResponse + */ + private function assertAttributes($actualResponse) + { + $eavAttributes = [ + 'url_key', + 'description', + 'meta_description', + 'meta_keyword', + 'meta_title', + 'short_description', + 'country_of_manufacture', + 'gift_message_available', + 'new_from_date', + 'new_to_date', + 'options_container', + 'special_price', + 'special_from_date', + 'special_to_date', + ]; + foreach ($eavAttributes as $eavAttribute) { + $this->assertArrayHasKey($eavAttribute, $actualResponse); + } + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryListTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryListTest.php index a86c34e57b2bb..4831671dec450 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryListTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryListTest.php @@ -31,9 +31,10 @@ protected function setUp() /** * @magentoApiDataFixture Magento/Catalog/_files/categories.php * @dataProvider filterSingleCategoryDataProvider - * @param $field - * @param $condition - * @param $value + * @param string $field + * @param string $condition + * @param string $value + * @param array $expectedResult */ public function testFilterSingleCategoryByField($field, $condition, $value, $expectedResult) { @@ -359,6 +360,8 @@ public function testEmptyFiltersReturnRootCategory() * Filtering with match value less than minimum query should return empty result * * @magentoApiDataFixture Magento/Catalog/_files/categories.php + * @expectedException \Exception + * @expectedExceptionMessage Invalid match filter. Minimum length is 3. */ public function testMinimumMatchQueryLength() { @@ -375,10 +378,7 @@ public function testMinimumMatchQueryLength() } } QUERY; - $result = $this->graphQlQuery($query); - $this->assertArrayNotHasKey('errors', $result); - $this->assertArrayHasKey('categoryList', $result); - $this->assertEquals([], $result['categoryList']); + $this->graphQlQuery($query); } /** @@ -421,6 +421,114 @@ public function testCategoryImageName() $this->assertEquals($expectedImageUrl, $categoryList[0]['image']); } + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + */ + public function testFilterByUrlPathTopLevelCategory() + { + $urlPath = 'category-1'; + $query = <<graphQlQuery($query); + $this->assertArrayNotHasKey('errors', $response); + $categoryList = $response['categoryList']; + $this->assertCount(1, $categoryList); + $this->assertEquals($urlPath, $categoryList[0]['url_path']); + $this->assertEquals('Category 1', $categoryList[0]['name']); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + */ + public function testFilterByUrlPathNestedCategory() + { + $urlPath = 'category-1/category-1-1/category-1-1-1'; + $query = <<graphQlQuery($query); + $this->assertArrayNotHasKey('errors', $response); + $categoryList = $response['categoryList']; + $this->assertCount(1, $categoryList); + $this->assertEquals($urlPath, $categoryList[0]['url_path']); + $this->assertEquals('Category 1.1.1', $categoryList[0]['name']); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + */ + public function testFilterByUrlPathMultipleCategories() + { + $urlPaths = ['category-1/category-1-1', 'inactive', 'movable-position-2']; + $urlPathsString = '"' . implode('", "', $urlPaths) . '"'; + $query = <<graphQlQuery($query); + $this->assertArrayNotHasKey('errors', $response); + $categoryList = $response['categoryList']; + $this->assertCount(2, $categoryList); + $this->assertEquals($urlPaths[0], $categoryList[0]['url_path']); + $this->assertEquals('Category 1.1', $categoryList[0]['name']); + $this->assertEquals($urlPaths[2], $categoryList[1]['url_path']); + $this->assertEquals('Movable Position 2', $categoryList[1]['name']); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + */ + public function testFilterByUrlPathNoResults() + { + $query = <<graphQlQuery($query); + $this->assertArrayNotHasKey('errors', $response); + $categoryList = $response['categoryList']; + $this->assertCount(0, $categoryList); + } + /** * @return array */ diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index 232a081228648..f367e70fa7397 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -13,6 +13,7 @@ use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\Category; use Magento\Catalog\Model\CategoryLinkManagement; +use Magento\Catalog\Model\ResourceModel\Category\Collection; use Magento\Eav\Model\Config; use Magento\TestFramework\ObjectManager; use Magento\TestFramework\TestCase\GraphQlAbstract; @@ -461,7 +462,7 @@ private function getDefaultAttributeOptionValue(string $attributeCode) : string } /** - * Full text search for Products and then filter the results by custom attribute ( sort is by defaulty by relevance) + * Full text search for Products and then filter the results by custom attribute (default sort is relevance) * * @magentoApiDataFixture Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php * @SuppressWarnings(PHPMD.ExcessiveMethodLength) @@ -1114,6 +1115,81 @@ public function testFilterWithinSpecificPriceRangeSortedByNameDesc() $this->assertEquals(4, $response['products']['page_info']['page_size']); } + /** + * @magentoApiDataFixture Magento/Catalog/_files/category_with_three_products.php + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + */ + public function testSortByPosition() + { + // Get category ID for filtering + /** @var Collection $categoryCollection */ + $categoryCollection = Bootstrap::getObjectManager()->get(Collection::class); + $category = $categoryCollection->addFieldToFilter('name', 'Category 999')->getFirstItem(); + $categoryId = $category->getId(); + + $queryAsc = <<graphQlQuery($queryAsc); + $this->assertArrayNotHasKey('errors', $resultAsc); + $productsAsc = array_column($resultAsc['products']['items'], 'sku'); + $expectedProductsAsc = ['simple1000', 'simple1001', 'simple1002']; + $this->assertEquals($expectedProductsAsc, $productsAsc); + + $queryDesc = <<graphQlQuery($queryDesc); + $this->assertArrayNotHasKey('errors', $resultDesc); + $productsDesc = array_column($resultDesc['products']['items'], 'sku'); + $expectedProductsDesc = array_reverse($expectedProductsAsc); + $this->assertEquals($expectedProductsDesc, $productsDesc); + + //revert position + $productPositions = $category->getProductsPosition(); + $count = 3; + foreach ($productPositions as $productId => $position) { + $productPositions[$productId] = $count; + $count--; + } + + $category->setPostedProducts($productPositions); + $category->save(); + + $queryDesc = <<graphQlQuery($queryDesc); + $this->assertArrayNotHasKey('errors', $resultDesc); + $productsDesc = array_column($resultDesc['products']['items'], 'sku'); + $expectedProductsDesc = $expectedProductsAsc; + $this->assertEquals($expectedProductsDesc, $productsDesc); + } + /** * pageSize = total_count and current page = 2 * expected - error is thrown diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php index 05323a5a7ddf4..1af44012bb515 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php @@ -858,7 +858,7 @@ public function testSetNewBillingAddressWithSaveInAddressBook() $searchCriteria = $this->searchCriteriaBuilder->addFilter('parent_id', $customer->getId())->create(); $addresses = $this->customerAddressRepository->getList($searchCriteria)->getItems(); - self::assertCount(1, $addresses); + self::assertCount(0, $addresses); self::assertArrayHasKey('cart', $response['setBillingAddressOnCart']); $cartResponse = $response['setBillingAddressOnCart']['cart']; @@ -871,6 +871,89 @@ public function testSetNewBillingAddressWithSaveInAddressBook() } } + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testSetBillingAddressAndPlaceOrder() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = <<graphQlMutation($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation( + $this->getSetShippingMethodsQuery($maskedQuoteId, 'flatrate', 'flatrate'), + [], + '', + $this->getHeaderMap() + ); + $this->graphQlMutation( + $this->getSetPaymentMethodQuery( + $maskedQuoteId, + 'checkmo' + ), + [], + '', + $this->getHeaderMap() + ); + $this->graphQlMutation( + $this->getPlaceOrderQuery($maskedQuoteId), + [], + '', + $this->getHeaderMap() + ); + $customer = $this->customerRepository->get('customer@example.com'); + $searchCriteria = $this->searchCriteriaBuilder->addFilter('parent_id', $customer->getId())->create(); + $addresses = $this->customerAddressRepository->getList($searchCriteria)->getItems(); + + self::assertCount(1, $addresses); + self::assertArrayHasKey('cart', $response['setBillingAddressOnCart']); + foreach ($addresses as $address) { + $this->customerAddressRepository->delete($address); + } + } + /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php @@ -1111,4 +1194,88 @@ private function assignQuoteToCustomer( $this->quoteResource->save($quote); return $this->quoteIdToMaskedId->execute((int)$quote->getId()); } + + /** + * @param string $maskedQuoteId + * @param string $shippingMethodCode + * @param string $shippingCarrierCode + * @return string + */ + private function getSetShippingMethodsQuery( + string $maskedQuoteId, + string $shippingMethodCode, + string $shippingCarrierCode + ): string { + return <<searchCriteriaBuilder->addFilter('parent_id', $customer->getId())->create(); $addresses = $this->customerAddressRepository->getList($searchCriteria)->getItems(); + self::assertCount(0, $addresses); + self::assertArrayHasKey('cart', $response['setShippingAddressesOnCart']); + + $cartResponse = $response['setShippingAddressesOnCart']['cart']; + self::assertArrayHasKey('shipping_addresses', $cartResponse); + $shippingAddressResponse = current($cartResponse['shipping_addresses']); + $this->assertNewShippingAddressFields($shippingAddressResponse); + + foreach ($addresses as $address) { + $this->customerAddressRepository->delete($address); + } + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + */ + public function testSetNewShippingAddressAndPlaceOrder() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = <<graphQlMutation($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation( + $this->getSetShippingMethodsQuery($maskedQuoteId, 'flatrate', 'flatrate'), + [], + '', + $this->getHeaderMap() + ); + $this->graphQlMutation( + $this->getSetPaymentMethodQuery( + $maskedQuoteId, + 'checkmo' + ), + [], + '', + $this->getHeaderMap() + ); + $this->graphQlMutation( + $this->getPlaceOrderQuery($maskedQuoteId), + [], + '', + $this->getHeaderMap() + ); + $customer = $this->customerRepository->get('customer@example.com'); + $searchCriteria = $this->searchCriteriaBuilder->addFilter('parent_id', $customer->getId())->create(); + $addresses = $this->customerAddressRepository->getList($searchCriteria)->getItems(); + self::assertCount(1, $addresses); self::assertArrayHasKey('cart', $response['setShippingAddressesOnCart']); @@ -1094,4 +1187,88 @@ private function assignQuoteToCustomer( $this->quoteResource->save($quote); return $this->quoteIdToMaskedId->execute((int)$quote->getId()); } + + /** + * @param string $maskedQuoteId + * @param string $shippingMethodCode + * @param string $shippingCarrierCode + * @return string + */ + private function getSetShippingMethodsQuery( + string $maskedQuoteId, + string $shippingMethodCode, + string $shippingCarrierCode + ): string { + return <<getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlQuery($query); + + $cartItem = $response['cart']['items'][0]; + self::assertEquals(9, $cartItem['prices']['price']['value']); + self::assertEquals(18, $cartItem['prices']['row_total']['value']); + self::assertEquals(18, $cartItem['prices']['row_total_including_tax']['value']); + + self::assertArrayHasKey('prices', $response['cart']); + $pricesResponse = $response['cart']['prices']; + self::assertEquals(18, $pricesResponse['grand_total']['value']); + self::assertEquals(18, $pricesResponse['subtotal_including_tax']['value']); + self::assertEquals(18, $pricesResponse['subtotal_excluding_tax']['value']); + self::assertEquals(18, $pricesResponse['subtotal_with_discount_excluding_tax']['value']); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Tax/_files/tax_rule_for_region_1.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/CatalogRule/_files/catalog_rule_10_off_not_logged.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + */ + public function testGetCartTotalsWithCatalogRuleAndTaxApplied() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlQuery($query); + + $cartItem = $response['cart']['items'][0]; + self::assertEquals(9, $cartItem['prices']['price']['value']); + self::assertEquals(18, $cartItem['prices']['row_total']['value']); + self::assertEquals(19.35, $cartItem['prices']['row_total_including_tax']['value']); + + self::assertArrayHasKey('prices', $response['cart']); + $pricesResponse = $response['cart']['prices']; + self::assertEquals(19.35, $pricesResponse['grand_total']['value']); + self::assertEquals(19.35, $pricesResponse['subtotal_including_tax']['value']); + self::assertEquals(18, $pricesResponse['subtotal_excluding_tax']['value']); + self::assertEquals(18, $pricesResponse['subtotal_with_discount_excluding_tax']['value']); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/cart_rule_discount_no_coupon.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/CatalogRule/_files/catalog_rule_10_off_not_logged.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + */ + public function testGetCartTotalsWithCatalogRuleAndCartRuleApplied() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlQuery($query); + + $cartItem = $response['cart']['items'][0]; + self::assertEquals(9, $cartItem['prices']['price']['value']); + self::assertEquals(18, $cartItem['prices']['row_total']['value']); + self::assertEquals(18, $cartItem['prices']['row_total_including_tax']['value']); + self::assertEquals(9, $cartItem['prices']['total_item_discount']['value']); + + $discount = $cartItem['prices']['discounts'][0]; + self::assertEquals("50% Off for all orders", $discount['label']); + self::assertEquals(9, $discount['amount']['value']); + + self::assertArrayHasKey('prices', $response['cart']); + $pricesResponse = $response['cart']['prices']; + self::assertEquals(9, $pricesResponse['grand_total']['value']); + self::assertEquals(18, $pricesResponse['subtotal_including_tax']['value']); + self::assertEquals(18, $pricesResponse['subtotal_excluding_tax']['value']); + self::assertEquals(9, $pricesResponse['subtotal_with_discount_excluding_tax']['value']); + } + /** * @magentoApiDataFixture Magento/GraphQl/Tax/_files/tax_rule_for_region_1.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php @@ -190,6 +278,15 @@ private function getQuery(string $maskedQuoteId): string value currency } + total_item_discount { + value + } + discounts { + label + amount { + value + } + } } } prices { diff --git a/dev/tests/functional/tests/app/Magento/Swatches/Test/Block/Product/ProductList/ProductItem.php b/dev/tests/functional/tests/app/Magento/Swatches/Test/Block/Product/ProductList/ProductItem.php index 11d290a05cca7..395257a0b74a4 100644 --- a/dev/tests/functional/tests/app/Magento/Swatches/Test/Block/Product/ProductList/ProductItem.php +++ b/dev/tests/functional/tests/app/Magento/Swatches/Test/Block/Product/ProductList/ProductItem.php @@ -19,7 +19,7 @@ class ProductItem extends CatalogProductItem * * @var string */ - protected $swatchSelector = 'div[option-id="%s"]'; + protected $swatchSelector = 'div[data-option-id="%s"]'; /** * Selector for the swatches of the product. diff --git a/dev/tests/functional/tests/app/Magento/Swatches/Test/Block/Product/ViewWithSwatches.php b/dev/tests/functional/tests/app/Magento/Swatches/Test/Block/Product/ViewWithSwatches.php index d30aabd7ff370..b908c95b884bf 100644 --- a/dev/tests/functional/tests/app/Magento/Swatches/Test/Block/Product/ViewWithSwatches.php +++ b/dev/tests/functional/tests/app/Magento/Swatches/Test/Block/Product/ViewWithSwatches.php @@ -55,10 +55,10 @@ public function getSwatchAttributesData() $swatchAttributesData = []; $swatchAttributes = $this->_rootElement->getElements($this->swatchAttributesSelector); foreach ($swatchAttributes as $swatchAttribute) { - $attributeCode = $swatchAttribute->getAttribute('attribute-code'); + $attributeCode = $swatchAttribute->getAttribute('data-attribute-code'); $swatchAttributesData[$attributeCode] = [ 'attribute_code' => $attributeCode, - 'attribute_id' => $swatchAttribute->getAttribute('attribute-id'), + 'attribute_id' => $swatchAttribute->getAttribute('data-attribute-id'), 'label' => $swatchAttribute->find($this->swatchAttributesLabelSelector)->getText(), 'options' => $this->getSwatchAttributeOptionsData($swatchAttribute), ]; @@ -77,7 +77,7 @@ private function getSwatchAttributeOptionsData(ElementInterface $swatchAttribute $optionsData = []; $options = $swatchAttribute->getElements($this->swatchAttributeOptionsSelector); foreach ($options as $option) { - $optionId = $option->getAttribute('option-id'); + $optionId = $option->getAttribute('data-option-id'); $optionsData[$optionId] = [ 'option_id' => $optionId, 'label' => $option->getText(), diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.test.js index 144bfa4a77bce..bdc9410d369af 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.test.js @@ -1,134 +1,134 @@ -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -define([ - 'jquery', - 'Magento_Swatches/js/swatch-renderer' -], function ($, SwatchRenderer) { - 'use strict'; - - describe('Testing "_RenderSwatchOptions" method of SwatchRenderer Widget', function () { - var widget, - html, - optionConfig, - attribute, - optionId = 2, - swathImageHeight = '60', - swathImageWidth = '70', - swathThumbImageHeight = '40', - swathThumbImageWidth = '50'; - - beforeEach(function () { - widget = new SwatchRenderer(); - attribute = { - id: 1, - options: [{ - id: optionId - }] - }; - - widget.options = { - classes: { - optionClass: 'swatch-option' - }, - jsonSwatchConfig: { - 1: { - 2: { - type: 2 - } - } - }, - jsonSwatchImageSizeConfig: { - swatchImage: { - width: swathImageWidth, - height: swathImageHeight - }, - swatchThumb: { - width: swathThumbImageWidth, - height: swathThumbImageHeight - } - } - }; - - optionConfig = widget.options.jsonSwatchConfig[attribute.id]; - html = $(widget._RenderSwatchOptions(attribute, 'option-label-control-id-1'))[0]; - }); - - it('check if swatch config has attribute id', function () { - expect(widget.options.jsonSwatchConfig.hasOwnProperty(attribute.id)).toEqual(true); - }); - - it('check if option config has option id', function () { - expect(optionConfig.hasOwnProperty(optionId)).toEqual(true); - }); - - it('check swatch thumbnail image height attribute', function () { - expect(html.hasAttribute('thumb-height')).toBe(true); - expect(html.getAttribute('thumb-height')).toEqual(swathThumbImageHeight); - }); - - it('check swatch thumbnail image width attribute', function () { - expect(html.hasAttribute('thumb-width')).toBe(true); - expect(html.getAttribute('thumb-width')).toEqual(swathThumbImageWidth); - }); - - it('check swatch image styles', function () { - expect(html.style.height).toEqual(swathImageHeight + 'px'); - expect(html.style.width).toEqual(swathImageWidth + 'px'); - }); - - it('check udate price method', function () { - var productPriceMock = { - find: jasmine.createSpy().and.returnValue({ - hide: jasmine.createSpy(), - priceBox: jasmine.createSpy().and.returnValue(''), - trigger: jasmine.createSpy(), - find: jasmine.createSpy().and.returnValue({ - toggleClass: jasmine.createSpy() - }) - }) - }; - - widget.element = { - parents: jasmine.createSpy().and.returnValue(productPriceMock) - }; - widget._getNewPrices = jasmine.createSpy().and.returnValue(undefined); - widget._UpdatePrice(); - expect(productPriceMock.find().find.calls.count()).toBe(1); - }); - - it('check getSelectedOptionPriceIndex', function () { - var optionMock = '
', - element = $('
' + - optionMock + '
' - ), - optionPricesMock = { - optionPrices: { - p: { - finalPrice: { - amount: 12 - } - } - } - }; - - widget.element = element; - widget.options.classes.attributeClass = 'swatch-attribute'; - widget.options.jsonConfig = optionPricesMock; - widget.optionsMap = { - 2: { - 4: { - products: 'p' - }, - hasOwnProperty: jasmine.createSpy().and.returnValue(true) - }, - hasOwnProperty: jasmine.createSpy().and.returnValue(true) - }; - - expect(widget._getSelectedOptionPriceIndex()).toBe('p'); - }); - }); -}); +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'jquery', + 'Magento_Swatches/js/swatch-renderer' +], function ($, SwatchRenderer) { + 'use strict'; + + describe('Testing "_RenderSwatchOptions" method of SwatchRenderer Widget', function () { + var widget, + html, + optionConfig, + attribute, + optionId = 2, + swathImageHeight = '60', + swathImageWidth = '70', + swathThumbImageHeight = '40', + swathThumbImageWidth = '50'; + + beforeEach(function () { + widget = new SwatchRenderer(); + attribute = { + id: 1, + options: [{ + id: optionId + }] + }; + + widget.options = { + classes: { + optionClass: 'swatch-option' + }, + jsonSwatchConfig: { + 1: { + 2: { + type: 2 + } + } + }, + jsonSwatchImageSizeConfig: { + swatchImage: { + width: swathImageWidth, + height: swathImageHeight + }, + swatchThumb: { + width: swathThumbImageWidth, + height: swathThumbImageHeight + } + } + }; + + optionConfig = widget.options.jsonSwatchConfig[attribute.id]; + html = $(widget._RenderSwatchOptions(attribute, 'option-label-control-id-1'))[0]; + }); + + it('check if swatch config has attribute id', function () { + expect(widget.options.jsonSwatchConfig.hasOwnProperty(attribute.id)).toEqual(true); + }); + + it('check if option config has option id', function () { + expect(optionConfig.hasOwnProperty(optionId)).toEqual(true); + }); + + it('check swatch thumbnail image height attribute', function () { + expect(html.hasAttribute('data-thumb-height')).toBe(true); + expect(html.getAttribute('data-thumb-height')).toEqual(swathThumbImageHeight); + }); + + it('check swatch thumbnail image width attribute', function () { + expect(html.hasAttribute('data-thumb-width')).toBe(true); + expect(html.getAttribute('data-thumb-width')).toEqual(swathThumbImageWidth); + }); + + it('check swatch image styles', function () { + expect(html.style.height).toEqual(swathImageHeight + 'px'); + expect(html.style.width).toEqual(swathImageWidth + 'px'); + }); + + it('check udate price method', function () { + var productPriceMock = { + find: jasmine.createSpy().and.returnValue({ + hide: jasmine.createSpy(), + priceBox: jasmine.createSpy().and.returnValue(''), + trigger: jasmine.createSpy(), + find: jasmine.createSpy().and.returnValue({ + toggleClass: jasmine.createSpy() + }) + }) + }; + + widget.element = { + parents: jasmine.createSpy().and.returnValue(productPriceMock) + }; + widget._getNewPrices = jasmine.createSpy().and.returnValue(undefined); + widget._UpdatePrice(); + expect(productPriceMock.find().find.calls.count()).toBe(1); + }); + + it('check getSelectedOptionPriceIndex', function () { + var optionMock = '
', + element = $('
' + + optionMock + '
' + ), + optionPricesMock = { + optionPrices: { + p: { + finalPrice: { + amount: 12 + } + } + } + }; + + widget.element = element; + widget.options.classes.attributeClass = 'swatch-attribute'; + widget.options.jsonConfig = optionPricesMock; + widget.optionsMap = { + 2: { + 4: { + products: 'p' + }, + hasOwnProperty: jasmine.createSpy().and.returnValue(true) + }, + hasOwnProperty: jasmine.createSpy().and.returnValue(true) + }; + + expect(widget._getSelectedOptionPriceIndex()).toBe('p'); + }); + }); +}); diff --git a/setup/performance-toolkit/benchmark.jmx b/setup/performance-toolkit/benchmark.jmx index 7db2b26e3c621..fef19b05deafd 100644 --- a/setup/performance-toolkit/benchmark.jmx +++ b/setup/performance-toolkit/benchmark.jmx @@ -40947,6 +40947,250 @@ if(response.data.categoryList.length !== 4){ + + 1 + false + 1 + ${graphqlGetCategoryListByCategoryIdPercentage} + mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx + + + +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + + javascript + mpaf/tool/fragments/_system/setup_label.jmx + + + + vars.put("testLabel", "GraphQL Categories Query: Get Multiple Categories By Id"); + + true + + + + + + + Content-Type + application/json + + + Accept + */* + + + mpaf/tool/fragments/ce/api/header_manager_before_token.jmx + + + + mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx + +import java.util.Random; + +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} + +vars.putObject("randomIntGenerator", random); + + + + true + + + + + javascript + + + + random = vars.getObject("randomIntGenerator"); + +var categories = props.get("categories"); + +var numbers = []; + +var sanity = 0; +for(var i = 0; i < 4; i++){ + sanity++; + if(sanity > 100){ + break; + } + var number = random.nextInt(categories.length) + if(numbers.indexOf(number) >= 0){ + i--; + continue; + } + numbers.push(number); +} + +vars.put("category_id_1", categories[numbers[0]].id); +vars.put("category_id_2", categories[numbers[1]].id); +vars.put("category_id_3", categories[numbers[2]].id); +vars.put("category_id_4", categories[numbers[3]].id); + + mpaf/tool/fragments/ce/common/extract_multiple_categories_setup.jmx + + + + + true + + + + false + {"query" : "{\n categories(filters:{ids: {in: [\"${category_id_1}\", \"${category_id_2}\", \"${category_id_3}\", \"${category_id_4}\"]}}) {\n total_count\n page_info {\n total_pages\n current_page\n page_size\n }\n items{\n id\n children {\n id\n name\n url_key\n url_path\n children_count\n path\n image\n productImagePreview: products(pageSize: 1, sort: {name: ASC}) {\n items {\n small_image {\n label\n url\n }\n }\n }\n }\n }\n }\n}"} + = + + + + + ${graphql_port_number} + 60000 + 200000 + ${request_protocol} + + ${base_path}graphql + POST + true + false + true + false + false + + mpaf/tool/fragments/ce/graphql/categories_query_get_multiple_categories_by_id.jmx + + + + javascript + + + + var response = JSON.parse(prev.getResponseDataAsString()); + +if(response.data == undefined || response.data.categories == undefined){ + AssertionResult.setFailureMessage("Categories result is empty."); + AssertionResult.setFailure(true); +} + +if(response.data.categories.items.length !== 4){ + AssertionResult.setFailureMessage("Categories query expected to find 4 categories. " + response.data.categories.items.length + " returned."); + AssertionResult.setFailure(true); +} + + + + + + + + + 1 + false + 1 + ${graphqlGetCategoryListByCategoryIdPercentage} + mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx + + + +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + + javascript + mpaf/tool/fragments/_system/setup_label.jmx + + + + vars.put("testLabel", "GraphQL Categories Query: Get Many Categories with Pagination"); + + true + + + + + + + Content-Type + application/json + + + Accept + */* + + + mpaf/tool/fragments/ce/api/header_manager_before_token.jmx + + + + true + + + + false + {"query" : "{\n categories(filters:{name: {match: \"Category\"}}) {\n total_count\n page_info {\n total_pages\n current_page\n page_size\n }\n items{\n id\n children {\n id\n name\n url_key\n url_path\n children_count\n path\n image\n productImagePreview: products(pageSize: 1, sort: {name: ASC}) {\n items {\n small_image {\n label\n url\n }\n }\n }\n }\n }\n }\n}"} + = + + + + + ${graphql_port_number} + 60000 + 200000 + ${request_protocol} + + ${base_path}graphql + POST + true + false + true + false + false + + mpaf/tool/fragments/ce/graphql/categories_query_get_many_categories_by_name_match.jmx + + + + javascript + + + + var response = JSON.parse(prev.getResponseDataAsString()); + +if(response.data == undefined || response.data.categories == undefined){ + AssertionResult.setFailureMessage("Categories result is empty."); + AssertionResult.setFailure(true); +} + +if(response.data.categories.items.length != 20){ + AssertionResult.setFailureMessage("Categories query expected to find 20 categories. " + response.data.categories.items.length + " returned."); + AssertionResult.setFailure(true); +} + + + + + + + 1 false