diff --git a/app/code/Magento/Catalog/Block/Product/AbstractProduct.php b/app/code/Magento/Catalog/Block/Product/AbstractProduct.php index ec6244519f7a5..c10bc32f18be0 100644 --- a/app/code/Magento/Catalog/Block/Product/AbstractProduct.php +++ b/app/code/Magento/Catalog/Block/Product/AbstractProduct.php @@ -124,7 +124,7 @@ public function __construct(\Magento\Catalog\Block\Product\Context $context, arr */ public function getAddToCartUrl($product, $additional = []) { - if ($product->getTypeInstance()->hasRequiredOptions($product)) { + if (!$product->getTypeInstance()->isPossibleBuyFromList($product)) { if (!isset($additional['_escape'])) { $additional['_escape'] = true; } diff --git a/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php b/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php index 4cb64f2318a9b..2650add33f8e1 100644 --- a/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php +++ b/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php @@ -1092,4 +1092,16 @@ public function getAssociatedProducts($product) { return []; } + + /** + * Check if product can be potentially buyed from the category page or some + * other list + * + * @param \Magento\Catalog\Model\Product $product + * @return bool + */ + public function isPossibleBuyFromList($product) + { + return !$this->hasRequiredOptions($product); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Product/ListProductTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Product/ListProductTest.php index 3a2c72db94243..f617052878fe0 100644 --- a/app/code/Magento/Catalog/Test/Unit/Block/Product/ListProductTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Block/Product/ListProductTest.php @@ -195,9 +195,9 @@ public function testGetAddToCartPostParams() ]; $this->typeInstanceMock->expects($this->once()) - ->method('hasRequiredOptions') + ->method('isPossibleBuyFromList') ->with($this->equalTo($this->productMock)) - ->will($this->returnValue(false)); + ->will($this->returnValue(true)); $this->cartHelperMock->expects($this->any()) ->method('getAddUrl') ->with($this->equalTo($this->productMock), $this->equalTo([])) diff --git a/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js b/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js index d160edb37fb4f..7dc3e43b64ced 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js @@ -86,6 +86,17 @@ define([ } if (res.backUrl) { + var eventData = { + 'form': form, + 'redirectParameters': [] + } + // trigger global event, so other modules will be able add parameters to redirect url + $('body').trigger('catalogCategoryAddToCartRedirect', eventData); + if (eventData.redirectParameters.length > 0) { + var parameters = res.backUrl.split('#'); + parameters.push(eventData.redirectParameters.join('&')); + res.backUrl = parameters.join('#'); + } window.location = res.backUrl; return; } diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php index 34efc90c95619..956a201dcdf06 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php @@ -1361,4 +1361,25 @@ public function getSalableUsedProducts(Product $product, $requiredAttributeIds = return $usedSalableProducts; } + + /** + * @inheritdoc + */ + public function isPossibleBuyFromList($product) + { + /** @var bool $isAllCustomOptionsDisplayed */ + $isAllCustomOptionsDisplayed = true; + + foreach ($this->getConfigurableAttributes($product) as $attribute) { + /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $eavAttribute */ + $eavAttribute = $attribute->getProductAttribute(); + + $isAllCustomOptionsDisplayed = ( + $isAllCustomOptionsDisplayed + && $eavAttribute->getData('used_in_product_listing') + ); + } + + return $isAllCustomOptionsDisplayed; + } } diff --git a/app/code/Magento/Swatches/view/frontend/templates/product/listing/renderer.phtml b/app/code/Magento/Swatches/view/frontend/templates/product/listing/renderer.phtml index 386f5e7b78f39..8262dd5f5172c 100644 --- a/app/code/Magento/Swatches/view/frontend/templates/product/listing/renderer.phtml +++ b/app/code/Magento/Swatches/view/frontend/templates/product/listing/renderer.phtml @@ -7,15 +7,17 @@
diff --git a/app/code/Magento/Swatches/view/frontend/web/js/catalog-add-to-cart.js b/app/code/Magento/Swatches/view/frontend/web/js/catalog-add-to-cart.js new file mode 100644 index 0000000000000..0e0c94b4ad62c --- /dev/null +++ b/app/code/Magento/Swatches/view/frontend/web/js/catalog-add-to-cart.js @@ -0,0 +1,17 @@ +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +require([ + 'jquery' +], function ($) { + 'use strict'; + + $('body').on('catalogCategoryAddToCartRedirect', function (event, data) { + $(data.form).find('[name*="super"]').each(function (index, item) { + var $item = $(item); + + data.redirectParameters.push($item.attr('data-attr-name') + '=' + $item.val()); + }); + }); +}); diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Handler/ConfigurableProduct/Curl.php b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Handler/ConfigurableProduct/Curl.php index 415c8b704676d..b975cf44abf9a 100644 --- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Handler/ConfigurableProduct/Curl.php +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Handler/ConfigurableProduct/Curl.php @@ -152,7 +152,7 @@ protected function prepareConfigurableMatrix(FixtureInterface $product) $keyIds[] = $attribute['options'][$optionKey]['id']; $configurableAttribute[] = sprintf( '"%s":"%s"', - $attribute['attribute_code'], + isset($attribute['attribute_code']) ? $attribute['attribute_code'] : $attribute['frontend_label'], $attribute['options'][$optionKey]['id'] ); } diff --git a/dev/tests/functional/tests/app/Magento/Swatches/Test/Block/Product/ListProduct.php b/dev/tests/functional/tests/app/Magento/Swatches/Test/Block/Product/ListProduct.php index 711072ae9e7cf..240563eb886d9 100644 --- a/dev/tests/functional/tests/app/Magento/Swatches/Test/Block/Product/ListProduct.php +++ b/dev/tests/functional/tests/app/Magento/Swatches/Test/Block/Product/ListProduct.php @@ -5,9 +5,10 @@ */ namespace Magento\Swatches\Test\Block\Product; +use Magento\Catalog\Test\Block\Product\ListProduct as CatalogListProduct; use Magento\Mtf\Client\Locator; use Magento\Mtf\Fixture\FixtureInterface; -use Magento\Catalog\Test\Block\Product\ListProduct as CatalogListProduct; +use Magento\Swatches\Test\Block\Product\ProductList\ProductItem; /** * @inheritdoc @@ -22,7 +23,7 @@ public function getProductItem(FixtureInterface $product) $locator = sprintf($this->productItem, $product->getName()); return $this->blockFactory->create( - \Magento\Swatches\Test\Block\Product\ProductList\ProductItem::class, + ProductItem::class, ['element' => $this->_rootElement->find($locator, Locator::SELECTOR_XPATH)] ); } 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 a1bc18a1a6d2d..fc9874021d8ce 100755 --- 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 @@ -6,8 +6,10 @@ namespace Magento\Swatches\Test\Block\Product\ProductList; -use Magento\Mtf\Client\Locator; use Magento\Catalog\Test\Block\Product\ProductList\ProductItem as CatalogProductItem; +use Magento\ConfigurableProduct\Test\Fixture\ConfigurableProduct; +use Magento\Mtf\Client\Locator; +use Magento\Swatches\Test\Fixture\SwatchesProductAttribute; /** * Product item block on frontend category view. @@ -15,12 +17,19 @@ class ProductItem extends CatalogProductItem { /** - * Selector for the swatches of the product. + * Selector for the swatches blocks of the product. * * @var string */ protected $swatchBlockSelector = '.swatch-attribute-options'; + /** + * Selector for the swatches items of the product. + * + * @var string + */ + private $swatchItemSelector = 'div[option-id="%s"]'; + /** * Check swatches visibility. * @@ -30,4 +39,72 @@ public function isSwatchesBlockVisible() { return $this->_rootElement->find($this->swatchBlockSelector)->isVisible(); } + + /** + * Fill product options on category page. + * + * @param ConfigurableProduct $product + * @return void + */ + public function fillData(ConfigurableProduct $product) + { + /** @var array $checkoutData */ + $checkoutData = $product->getCheckoutData(); + + /** @var array $options */ + $options = $checkoutData['options']['configurable_options']; + + /** @var array $confAttrData */ + $confAttrData = $product->getDataFieldConfig('configurable_attributes_data'); + + /** @var ConfigurableProduct\ConfigurableAttributesData $confAttrSource */ + $confAttrSource = $confAttrData['source']; + + /** @var SwatchesProductAttribute[] $attributes */ + $attributes = $confAttrSource->getAttributes(); + + foreach ($options as $option) { + if (!isset($attributes[$option['title']])) { + continue; + } + + /** @var array $availableOptions */ + $availableOptions = $attributes[$option['title']]->getOptions(); + + /** @var string $optionKey */ + $optionKey = str_replace('option_key_', '', $option['value']); + + if (!isset($availableOptions[$optionKey])) { + continue; + } + + /** @var array $optionForSelect */ + $optionForSelect = $availableOptions[$optionKey]; + + $this->clickOnSwatch($optionForSelect['id']); + } + } + + /** + * Click on swatch. + * + * @param $optionId + */ + private function clickOnSwatch($optionId) + { + /** @var string $selector */ + $selector = sprintf($this->swatchItemSelector, $optionId); + + $this->_rootElement->find($selector, Locator::SELECTOR_CSS)->click(); + } + + /** + * @inheritdoc + */ + public function clickAddToCart() + { + $this->_rootElement->hover(); + + parent::clickAddToCart(); + } } 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 new file mode 100644 index 0000000000000..8d8fd9ed35639 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Swatches/Test/Block/Product/ViewWithSwatches.php @@ -0,0 +1,62 @@ +getCheckoutData(); + + /** @var array $availableAttributes */ + $availableAttributes = $product->getConfigurableAttributesData(); + + /** @var array $attributesData */ + $attributesData = $availableAttributes['attributes_data']; + + /** @var array $formData */ + $formData = []; + + /** @var array $item */ + foreach ($checkoutData['options']['configurable_options'] as $item) { + /** @var string $selector */ + $selector = sprintf( + $this->swatchAttributeSelector, + $attributesData[$item['title']]['attribute_code'] + ); + + $this->waitForElementVisible($selector); + + /** @var string $selected */ + $selected = $this->_rootElement->find($selector)->getText(); + + $formData[$item['title']] = $selected; + } + + return $formData; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Swatches/Test/Constraint/AssertSwatchConfigurableProductPage.php b/dev/tests/functional/tests/app/Magento/Swatches/Test/Constraint/AssertSwatchConfigurableProductPage.php new file mode 100644 index 0000000000000..675ffe2e6d2ef --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Swatches/Test/Constraint/AssertSwatchConfigurableProductPage.php @@ -0,0 +1,123 @@ +product = $product; + $this->productView = $catalogProductView->getProductViewWithSwatchesBlock(); + $this->objectManager->create( + AddProductToCartFromCatalogCategoryPageStep::class, + [ + 'product' => $product, + ] + )->run(); + + /*we need this line to wait until page will be fully loaded*/ + $this->productView->getSelectedSwatchOptions($this->product); + + /** @var array $errors */ + $errors = $this->verify(); + + \PHPUnit_Framework_Assert::assertEmpty( + $errors, + "\nFound the following errors:\n" . implode(" \n", $errors) + ); + } + + /** + * Verify product on product view page. + * + * @return array + */ + protected function verify() + { + /** @var array $errors */ + $errors = parent::verify(); + $errors[] = $this->verifySwatches(); + + return array_filter($errors); + } + + /** + * Verify selected swatches on product view page. + * + * @return array|string + */ + protected function verifySwatches() + { + $actualData = $this->productView->getSelectedSwatchOptions($this->product); + $expectedData = $this->convertCheckoutData($this->product); + + return $this->verifyData($expectedData, $actualData); + } + + /** + * Get swatch attributes formatter to attributes comparison. + * + * @param FixtureInterface $product + * @return array + */ + public function convertCheckoutData(FixtureInterface $product) + { + /** @var array $out */ + $out = []; + + /** @var array $checkoutData */ + $checkoutData = $product->getCheckoutData(); + + /** @var array $availableAttributes */ + $availableAttributes = $product->getConfigurableAttributesData(); + + /** @var array $attributesData */ + $attributesData = $availableAttributes['attributes_data']; + + /** @var array $item */ + foreach ($checkoutData['options']['configurable_options'] as $item) { + $out[$item['title']] = + $attributesData[$item['title']]['options'][$item['value']]['label']; + } + + return $out; + } + + /** + * Return string representation of the object. + * + * @return string + */ + public function toString() + { + return 'Swatch attributes displayed as expected on product page'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Swatches/Test/Fixture/Cart/Item.php b/dev/tests/functional/tests/app/Magento/Swatches/Test/Fixture/Cart/Item.php new file mode 100644 index 0000000000000..13564b5c56f2c --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Swatches/Test/Fixture/Cart/Item.php @@ -0,0 +1,17 @@ + + +