diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontCategoryCurrentPageIsNthActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontCategoryCurrentPageIsNthActionGroup.xml new file mode 100644 index 0000000000000..84e14269d24c2 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontCategoryCurrentPageIsNthActionGroup.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + {{expectedPage}} + currentPageText + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontNavigateCategoryNextPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontNavigateCategoryNextPageActionGroup.xml new file mode 100644 index 0000000000000..4776c9d32a34d --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontNavigateCategoryNextPageActionGroup.xml @@ -0,0 +1,17 @@ + + + + + + + Navigates storefront category next page from toolbar + + + + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryBottomToolbarSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryBottomToolbarSection.xml index 09eb4ad954274..c27a6107e5e35 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryBottomToolbarSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryBottomToolbarSection.xml @@ -12,6 +12,6 @@ - + diff --git a/app/code/Magento/Swatches/Block/LayeredNavigation/RenderLayered.php b/app/code/Magento/Swatches/Block/LayeredNavigation/RenderLayered.php index fc13372520945..9ba1083adab74 100644 --- a/app/code/Magento/Swatches/Block/LayeredNavigation/RenderLayered.php +++ b/app/code/Magento/Swatches/Block/LayeredNavigation/RenderLayered.php @@ -5,11 +5,17 @@ */ namespace Magento\Swatches\Block\LayeredNavigation; -use Magento\Eav\Model\Entity\Attribute; +use Magento\Catalog\Model\Layer\Filter\AbstractFilter; +use Magento\Catalog\Model\Layer\Filter\Item as FilterItem; use Magento\Catalog\Model\ResourceModel\Layer\Filter\AttributeFactory; -use Magento\Framework\View\Element\Template; +use Magento\Eav\Model\Entity\Attribute; use Magento\Eav\Model\Entity\Attribute\Option; -use Magento\Catalog\Model\Layer\Filter\Item as FilterItem; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\View\Element\Template; +use Magento\Framework\View\Element\Template\Context; +use Magento\Swatches\Helper\Data; +use Magento\Swatches\Helper\Media; +use Magento\Theme\Block\Html\Pager; /** * Class RenderLayered Render Swatches at Layered Navigation @@ -37,7 +43,7 @@ class RenderLayered extends Template protected $eavAttribute; /** - * @var \Magento\Catalog\Model\Layer\Filter\AbstractFilter + * @var AbstractFilter */ protected $filter; @@ -47,41 +53,52 @@ class RenderLayered extends Template protected $layerAttribute; /** - * @var \Magento\Swatches\Helper\Data + * @var Data */ protected $swatchHelper; /** - * @var \Magento\Swatches\Helper\Media + * @var Media */ protected $mediaHelper; /** - * @param Template\Context $context + * @var Pager + */ + private $htmlPagerBlock; + + /** + * @param Context $context * @param Attribute $eavAttribute * @param AttributeFactory $layerAttribute - * @param \Magento\Swatches\Helper\Data $swatchHelper - * @param \Magento\Swatches\Helper\Media $mediaHelper + * @param Data $swatchHelper + * @param Media $mediaHelper * @param array $data + * @param Pager|null $htmlPagerBlock */ public function __construct( - \Magento\Framework\View\Element\Template\Context $context, + Context $context, Attribute $eavAttribute, AttributeFactory $layerAttribute, - \Magento\Swatches\Helper\Data $swatchHelper, - \Magento\Swatches\Helper\Media $mediaHelper, - array $data = [] + Data $swatchHelper, + Media $mediaHelper, + array $data = [], + ?Pager $htmlPagerBlock = null ) { $this->eavAttribute = $eavAttribute; $this->layerAttribute = $layerAttribute; $this->swatchHelper = $swatchHelper; $this->mediaHelper = $mediaHelper; + $this->htmlPagerBlock = $htmlPagerBlock ?? ObjectManager::getInstance()->get(Pager::class); parent::__construct($context, $data); } /** + * Set filter and attribute objects + * * @param \Magento\Catalog\Model\Layer\Filter\AbstractFilter $filter + * * @return $this * @throws \Magento\Framework\Exception\LocalizedException */ @@ -94,6 +111,8 @@ public function setSwatchFilter(\Magento\Catalog\Model\Layer\Filter\AbstractFilt } /** + * Get attribute swatch data + * * @return array */ public function getSwatchData() @@ -114,30 +133,46 @@ public function getSwatchData() $attributeOptionIds = array_keys($attributeOptions); $swatches = $this->swatchHelper->getSwatchesByOptionsId($attributeOptionIds); - $data = [ + return [ 'attribute_id' => $this->eavAttribute->getId(), 'attribute_code' => $this->eavAttribute->getAttributeCode(), 'attribute_label' => $this->eavAttribute->getStoreLabel(), 'options' => $attributeOptions, 'swatches' => $swatches, ]; - - return $data; } /** + * Build filter option url + * * @param string $attributeCode * @param int $optionId + * * @return string */ public function buildUrl($attributeCode, $optionId) { - $query = [$attributeCode => $optionId]; - return $this->_urlBuilder->getUrl('*/*/*', ['_current' => true, '_use_rewrite' => true, '_query' => $query]); + $query = [ + $attributeCode => $optionId, + // exclude current page from urls + $this->htmlPagerBlock->getPageVarName() => null + ]; + + return $this->_urlBuilder->getUrl( + '*/*/*', + [ + '_current' => true, + '_use_rewrite' => true, + '_query' => $query + ] + ); } /** + * Get view data for option with no results + * * @param Option $swatchOption + * * @return array */ protected function getUnusedOption(Option $swatchOption) @@ -150,8 +185,11 @@ protected function getUnusedOption(Option $swatchOption) } /** + * Get option data if visible + * * @param FilterItem[] $filterItems * @param Option $swatchOption + * * @return array */ protected function getFilterOption(array $filterItems, Option $swatchOption) @@ -166,8 +204,11 @@ protected function getFilterOption(array $filterItems, Option $swatchOption) } /** + * Get view data for option + * * @param FilterItem $filterItem * @param Option $swatchOption + * * @return array */ protected function getOptionViewData(FilterItem $filterItem, Option $swatchOption) @@ -187,15 +228,20 @@ protected function getOptionViewData(FilterItem $filterItem, Option $swatchOptio } /** + * Check if option should be visible + * * @param FilterItem $filterItem + * * @return bool */ protected function isOptionVisible(FilterItem $filterItem) { - return $this->isOptionDisabled($filterItem) && $this->isShowEmptyResults() ? false : true; + return !($this->isOptionDisabled($filterItem) && $this->isShowEmptyResults()); } /** + * Check if attribute values should be visible with no results + * * @return bool */ protected function isShowEmptyResults() @@ -204,7 +250,10 @@ protected function isShowEmptyResults() } /** + * Check if option should be disabled + * * @param FilterItem $filterItem + * * @return bool */ protected function isOptionDisabled(FilterItem $filterItem) @@ -213,8 +262,11 @@ protected function isOptionDisabled(FilterItem $filterItem) } /** + * Retrieve filter item by id + * * @param FilterItem[] $filterItems * @param integer $id + * * @return bool|FilterItem */ protected function getFilterItemById(array $filterItems, $id) @@ -228,14 +280,15 @@ protected function getFilterItemById(array $filterItems, $id) } /** + * Get swatch image path + * * @param string $type * @param string $filename + * * @return string */ public function getSwatchPath($type, $filename) { - $imagePath = $this->mediaHelper->getSwatchAttributeImage($type, $filename); - - return $imagePath; + return $this->mediaHelper->getSwatchAttributeImage($type, $filename); } } diff --git a/app/code/Magento/Swatches/Test/Mftf/ActionGroup/AddTextSwatchToProductActionGroup.xml b/app/code/Magento/Swatches/Test/Mftf/ActionGroup/AddTextSwatchToProductActionGroup.xml index 97a391137d8e3..5f3ec07bd4983 100644 --- a/app/code/Magento/Swatches/Test/Mftf/ActionGroup/AddTextSwatchToProductActionGroup.xml +++ b/app/code/Magento/Swatches/Test/Mftf/ActionGroup/AddTextSwatchToProductActionGroup.xml @@ -19,6 +19,7 @@ + @@ -41,6 +42,7 @@ + diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontRedirectToFirstPageOnFilteringBySwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontRedirectToFirstPageOnFilteringBySwatchTest.xml new file mode 100644 index 0000000000000..c6266e034bffc --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontRedirectToFirstPageOnFilteringBySwatchTest.xml @@ -0,0 +1,103 @@ + + + + + + + + + + <description value="Customers are redirected to first plp page after filtering by swatch"/> + <severity value="MINOR"/> + <group value="Swatches"/> + </annotations> + + <before> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createSimpleProduct1"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct2"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct3"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <magentoCLI command="config:set catalog/frontend/grid_per_page 1" stepKey="setOneProductPerPage"/> + <magentoCLI command="config:set catalog/frontend/grid_per_page_values 1" stepKey="setGridPerPage"/> + + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AddTextSwatchToProductActionGroup" stepKey="addSwatchAttribute"> + <argument name="usedInLayeredNavigation" value="1"/> + </actionGroup> + </before> + + <after> + <actionGroup ref="DeleteProductAttributeActionGroup" stepKey="deleteSwatchAttribute"> + <argument name="ProductAttribute" value="textSwatchAttribute"/> + </actionGroup> + <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> + + <magentoCLI command="config:set catalog/frontend/grid_per_page 12" stepKey="setDefaultProductsPerPage"/> + <magentoCLI command="config:set catalog/frontend/grid_per_page_values 12,24,36" stepKey="setDefaultGridPerPage"/> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches"/> + + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createSimpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="createSimpleProduct2" stepKey="deleteSimpleProduct2"/> + <deleteData createDataKey="createSimpleProduct3" stepKey="deleteSimpleProduct3"/> + </after> + + <amOnPage url="{{AdminProductAttributeSetEditPage.url}}/{{AddToDefaultSet.attributeSetId}}/" stepKey="onAttributeSetEdit"/> + <actionGroup ref="AssignAttributeToGroupActionGroup" stepKey="assignAttributeToGroup"> + <argument name="group" value="Product Details"/> + <argument name="attribute" value="{{textSwatchAttribute.attribute_code}}"/> + </actionGroup> + <actionGroup ref="SaveAttributeSetActionGroup" stepKey="SaveAttributeSet"/> + + <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="goToProductIndexPage"/> + <actionGroup ref="ClearFiltersAdminProductGridActionGroup" stepKey="clearFiltersOnProductIndexPage"/> + + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="goToProduct1EditPage"> + <argument name="product" value="$$createSimpleProduct1$$"/> + </actionGroup> + <selectOption selector="{{AdminProductAttributesSection.attributeDropdownByCode(textSwatchAttribute.attribute_code)}}" userInput="textSwatchOption1" stepKey="selectProduct1AttributeOption"/> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProduct1"/> + + <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="goToProductsGridPage2"/> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="goToProduct2EditPage"> + <argument name="product" value="$$createSimpleProduct2$$"/> + </actionGroup> + <selectOption selector="{{AdminProductAttributesSection.attributeDropdownByCode(textSwatchAttribute.attribute_code)}}" userInput="textSwatchOption1" stepKey="selectProduct2AttributeOption"/> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProduct2"/> + + <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="goToProductsGridPage3"/> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="goToProduct3EditPage"> + <argument name="product" value="$$createSimpleProduct3$$"/> + </actionGroup> + <selectOption selector="{{AdminProductAttributesSection.attributeDropdownByCode(textSwatchAttribute.attribute_code)}}" userInput="textSwatchOption2" stepKey="selectProduct3AttributeOption"/> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProduct3"/> + + <magentoCron groups="index" stepKey="runCronIndexer"/> + + <actionGroup ref="StorefrontNavigateCategoryPageActionGroup" stepKey="navigateToCategoryPage"> + <argument name="category" value="$$createCategory$$"/> + </actionGroup> + <actionGroup ref="StorefrontNavigateCategoryNextPageActionGroup" stepKey="navigateToCategoryNextPage"/> + + <click selector="{{StorefrontCategorySidebarSection.filterOptionTitle(textSwatchAttribute.default_label)}}" stepKey="expandAttribute"/> + <click selector="{{StorefrontCategorySidebarSection.attributeNthOption(textSwatchAttribute.attribute_code, '1')}}" stepKey="filterBySwatch1"/> + <waitForPageLoad stepKey="waitForPageToLoad2"/> + + <actionGroup ref="AssertStorefrontCategoryCurrentPageIsNthActionGroup" stepKey="assertCurrentPageIsFirst"> + <argument name="expectedPage" value="1"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Swatches/Test/Unit/Block/LayeredNavigation/RenderLayeredTest.php b/app/code/Magento/Swatches/Test/Unit/Block/LayeredNavigation/RenderLayeredTest.php index 4056bf27f571e..06960c409b476 100644 --- a/app/code/Magento/Swatches/Test/Unit/Block/LayeredNavigation/RenderLayeredTest.php +++ b/app/code/Magento/Swatches/Test/Unit/Block/LayeredNavigation/RenderLayeredTest.php @@ -18,6 +18,7 @@ use Magento\Swatches\Block\LayeredNavigation\RenderLayered; use Magento\Swatches\Helper\Data; use Magento\Swatches\Helper\Media; +use Magento\Theme\Block\Html\Pager; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -28,35 +29,60 @@ */ class RenderLayeredTest extends TestCase { - /** @var MockObject */ - protected $contextMock; - - /** @var MockObject */ - protected $requestMock; - - /** @var MockObject */ - protected $urlBuilder; - - /** @var MockObject */ - protected $eavAttributeMock; - - /** @var MockObject */ - protected $layerAttributeFactoryMock; - - /** @var MockObject */ - protected $layerAttributeMock; - - /** @var MockObject */ - protected $swatchHelperMock; - - /** @var MockObject */ - protected $mediaHelperMock; - - /** @var MockObject */ - protected $filterMock; - - /** @var MockObject */ - protected $block; + /** + * @var RenderLayered|MockObject + */ + private $block; + + /** + * @var Context|MockObject + */ + private $contextMock; + + /** + * @var RequestInterface|MockObject + */ + private $requestMock; + + /** + * @var Url|MockObject + */ + private $urlBuilder; + + /** + * @var Attribute|MockObject + */ + private $eavAttributeMock; + + /** + * @var AttributeFactory|MockObject + */ + private $layerAttributeFactoryMock; + + /** + * @var \Magento\Catalog\Model\ResourceModel\Layer\Filter\Attribute|MockObject + */ + private $layerAttributeMock; + + /** + * @var Data|MockObject + */ + private $swatchHelperMock; + + /** + * @var Media|MockObject + */ + private $mediaHelperMock; + + /** + * @var AbstractFilter|MockObject + */ + private $filterMock; + + /** + * @var Pager|MockObject + */ + private $htmlBlockPagerMock; protected function setUp(): void { @@ -66,8 +92,8 @@ protected function setUp(): void Url::class, ['getCurrentUrl', 'getRedirectUrl', 'getUrl'] ); - $this->contextMock->expects($this->any())->method('getRequest')->willReturn($this->requestMock); - $this->contextMock->expects($this->any())->method('getUrlBuilder')->willReturn($this->urlBuilder); + $this->contextMock->method('getRequest')->willReturn($this->requestMock); + $this->contextMock->method('getUrlBuilder')->willReturn($this->urlBuilder); $this->eavAttributeMock = $this->createMock(Attribute::class); $this->layerAttributeFactoryMock = $this->createPartialMock( AttributeFactory::class, @@ -80,6 +106,7 @@ protected function setUp(): void $this->swatchHelperMock = $this->createMock(Data::class); $this->mediaHelperMock = $this->createMock(Media::class); $this->filterMock = $this->createMock(AbstractFilter::class); + $this->htmlBlockPagerMock = $this->createMock(Pager::class); $this->block = $this->getMockBuilder(RenderLayered::class) ->setMethods(['filter', 'eavAttribute']) @@ -91,6 +118,7 @@ protected function setUp(): void $this->swatchHelperMock, $this->mediaHelperMock, [], + $this->htmlBlockPagerMock ] ) ->getMock(); @@ -114,7 +142,7 @@ public function testGetSwatchData() $item3 = $this->createMock(Item::class); $item4 = $this->createMock(Item::class); - $item1->expects($this->any())->method('__call')->withConsecutive( + $item1->method('__call')->withConsecutive( ['getValue'], ['getCount'], ['getValue'], @@ -128,9 +156,9 @@ public function testGetSwatchData() 'Yellow' ); - $item2->expects($this->any())->method('__call')->with('getValue')->willReturn('blue'); + $item2->method('__call')->with('getValue')->willReturn('blue'); - $item3->expects($this->any())->method('__call')->withConsecutive( + $item3->method('__call')->withConsecutive( ['getValue'], ['getCount'] )->willReturnOnConsecutiveCalls( @@ -138,7 +166,7 @@ public function testGetSwatchData() 0 ); - $item4->expects($this->any())->method('__call')->withConsecutive( + $item4->method('__call')->withConsecutive( ['getValue'], ['getCount'], ['getValue'], @@ -162,22 +190,22 @@ public function testGetSwatchData() $this->block->method('filter')->willReturn($this->filterMock); $option1 = $this->createMock(Option::class); - $option1->expects($this->any())->method('getValue')->willReturn('yellow'); + $option1->method('getValue')->willReturn('yellow'); $option2 = $this->createMock(Option::class); - $option2->expects($this->any())->method('getValue')->willReturn(null); + $option2->method('getValue')->willReturn(null); $option3 = $this->createMock(Option::class); - $option3->expects($this->any())->method('getValue')->willReturn('red'); + $option3->method('getValue')->willReturn('red'); $option4 = $this->createMock(Option::class); - $option4->expects($this->any())->method('getValue')->willReturn('green'); + $option4->method('getValue')->willReturn('green'); $eavAttribute = $this->createMock(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class); $eavAttribute->expects($this->once()) ->method('getOptions') ->willReturn([$option1, $option2, $option3, $option4]); - $eavAttribute->expects($this->any())->method('getIsFilterable')->willReturn(0); + $eavAttribute->method('getIsFilterable')->willReturn(0); $this->filterMock->expects($this->once())->method('getAttributeModel')->willReturn($eavAttribute); $this->block->method('eavAttribute')->willReturn($eavAttribute); @@ -200,7 +228,7 @@ public function testGetSwatchDataException() { $this->block->method('filter')->willReturn($this->filterMock); $this->block->setSwatchFilter($this->filterMock); - $this->expectException('\RuntimeException'); + $this->expectException(\RuntimeException::class); $this->block->getSwatchData(); }