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 @@ + + + + + + + diff --git a/dev/tests/functional/tests/app/Magento/Swatches/Test/Repository/ConfigurableProduct.xml b/dev/tests/functional/tests/app/Magento/Swatches/Test/Repository/ConfigurableProduct.xml index 175209ab36c4a..a55f96962caa2 100644 --- a/dev/tests/functional/tests/app/Magento/Swatches/Test/Repository/ConfigurableProduct.xml +++ b/dev/tests/functional/tests/app/Magento/Swatches/Test/Repository/ConfigurableProduct.xml @@ -40,5 +40,73 @@ custom_attribute_set + + Test configurable product with two swatch attributes %isolation% + sku_test_configurable_product_%isolation% + This item has weight + 30 + Yes + Catalog, Search + + taxable_goods + + configurable-product-%isolation% + + two_text_swatch + + + In Stock + + + default_subcategory + + + Main Website + + + custom_attribute_set + + + 40 + price_40 + + + two_text_swatches + + + + Test configurable product with color and size %isolation% + sku_test_configurable_product_%isolation% + This item has weight + 30 + Yes + Catalog, Search + + taxable_goods + + configurable-product-%isolation% + + text_swatch_with_dropdown + + + In Stock + + + default_subcategory + + + Main Website + + + custom_attribute_set + + + 40 + price_40 + + + swatches_with_dropdown + + diff --git a/dev/tests/functional/tests/app/Magento/Swatches/Test/Repository/ConfigurableProduct/CheckoutData.xml b/dev/tests/functional/tests/app/Magento/Swatches/Test/Repository/ConfigurableProduct/CheckoutData.xml new file mode 100644 index 0000000000000..7e571be1129f8 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Swatches/Test/Repository/ConfigurableProduct/CheckoutData.xml @@ -0,0 +1,47 @@ + + + + + + + + + attribute_key_0 + option_key_1 + + + attribute_key_1 + option_key_2 + + + + 1 + + 42 + 1 + 47 + + + + + + + attribute_key_0 + option_key_1 + + + + 1 + + 42 + 1 + 47 + + + + diff --git a/dev/tests/functional/tests/app/Magento/Swatches/Test/Repository/ConfigurableProduct/ConfigurableAttributesData.xml b/dev/tests/functional/tests/app/Magento/Swatches/Test/Repository/ConfigurableProduct/ConfigurableAttributesData.xml index f702347a0d660..b7dba8848e778 100644 --- a/dev/tests/functional/tests/app/Magento/Swatches/Test/Repository/ConfigurableProduct/ConfigurableAttributesData.xml +++ b/dev/tests/functional/tests/app/Magento/Swatches/Test/Repository/ConfigurableProduct/ConfigurableAttributesData.xml @@ -44,5 +44,145 @@ + + + + + + 12.00 + Yes + + + 20.00 + Yes + + + 18.00 + Yes + + + + + + + 42.00 + Yes + + + 40.00 + Yes + + + 48.00 + Yes + + + + + + swatchesProductAttribute::attribute_type_text_swatch + swatchesProductAttribute::attribute_type_text_swatch + + + + 10 + 1 + + + 10 + 1 + + + 10 + 1 + + + 10 + 1 + + + 10 + 1 + + + 10 + 1 + + + 10 + 1 + + + 10 + 1 + + + 10 + 1 + + + + + + + + + 12.00 + Yes + + + 20.00 + Yes + + + 18.00 + Yes + + + + + + + 42.00 + Yes + + + 40.00 + Yes + + + + + + swatchesProductAttribute::attribute_type_text_swatch + catalogProductAttribute::size + + + + 10 + 1 + + + 10 + 1 + + + 10 + 1 + + + 10 + 1 + + + 10 + 1 + + + 10 + 1 + + + diff --git a/dev/tests/functional/tests/app/Magento/Swatches/Test/TestCase/AddConfigurableProductWithSwatchToShoppingCartTest.php b/dev/tests/functional/tests/app/Magento/Swatches/Test/TestCase/AddConfigurableProductWithSwatchToShoppingCartTest.php new file mode 100644 index 0000000000000..d766641c13da7 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Swatches/Test/TestCase/AddConfigurableProductWithSwatchToShoppingCartTest.php @@ -0,0 +1,37 @@ +executeScenario(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Swatches/Test/TestCase/AddConfigurableProductWithSwatchToShoppingCartTest.xml b/dev/tests/functional/tests/app/Magento/Swatches/Test/TestCase/AddConfigurableProductWithSwatchToShoppingCartTest.xml new file mode 100644 index 0000000000000..825b47021b647 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Swatches/Test/TestCase/AddConfigurableProductWithSwatchToShoppingCartTest.xml @@ -0,0 +1,16 @@ + + + + + + addOptions + configurableProductSwatch::product_with_two_text_swatch + + + + diff --git a/dev/tests/functional/tests/app/Magento/Swatches/Test/TestCase/TryToAddConfigurableProductWithSwatchToShoppingCartTest.php b/dev/tests/functional/tests/app/Magento/Swatches/Test/TestCase/TryToAddConfigurableProductWithSwatchToShoppingCartTest.php new file mode 100644 index 0000000000000..84db977bc5f0d --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Swatches/Test/TestCase/TryToAddConfigurableProductWithSwatchToShoppingCartTest.php @@ -0,0 +1,37 @@ +executeScenario(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Swatches/Test/TestCase/TryToAddConfigurableProductWithSwatchToShoppingCartTest.xml b/dev/tests/functional/tests/app/Magento/Swatches/Test/TestCase/TryToAddConfigurableProductWithSwatchToShoppingCartTest.xml new file mode 100644 index 0000000000000..056e3996fc34a --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Swatches/Test/TestCase/TryToAddConfigurableProductWithSwatchToShoppingCartTest.xml @@ -0,0 +1,16 @@ + + + + + + addOptions + configurableProductSwatch::product_with_text_swatch_and_size + + + + diff --git a/dev/tests/functional/tests/app/Magento/Swatches/Test/TestStep/AddProductToCartFromCatalogCategoryPageStep.php b/dev/tests/functional/tests/app/Magento/Swatches/Test/TestStep/AddProductToCartFromCatalogCategoryPageStep.php new file mode 100644 index 0000000000000..a0f18966cdb2f --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Swatches/Test/TestStep/AddProductToCartFromCatalogCategoryPageStep.php @@ -0,0 +1,102 @@ +fixtureFactory = $fixtureFactory; + $this->cmsIndex = $cmsIndex; + $this->categoryView = $categoryView; + $this->product = $product; + } + + /** + * Update configurable product. + * + * @return array + */ + public function run() + { + /** @var string $categoryName */ + $categoryName = $this->product->getCategoryIds()[0]; + + $this->cmsIndex->open(); + $this->cmsIndex->getTopmenu()->selectCategoryByName($categoryName); + + /** @var ListProduct $productsList */ + $productsList = $this->categoryView->getListSwatchesProductBlock(); + + /** @var ProductItem $productItemBlock */ + $productItemBlock = $productsList->getProductItem($this->product); + $productItemBlock->fillData($this->product); + $productItemBlock->clickAddToCart(); + $cart = [ + 'data' => [ + 'items' => [ + 'products' => [$this->product], + ], + ], + ]; + + return [ + 'cart' => $this->fixtureFactory->createByCode('cart', $cart), + ]; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Swatches/Test/etc/curl/di.xml b/dev/tests/functional/tests/app/Magento/Swatches/Test/etc/curl/di.xml index b0d43eb4cce2f..2b9c857144aae 100644 --- a/dev/tests/functional/tests/app/Magento/Swatches/Test/etc/curl/di.xml +++ b/dev/tests/functional/tests/app/Magento/Swatches/Test/etc/curl/di.xml @@ -6,5 +6,6 @@ */ --> - + diff --git a/dev/tests/functional/tests/app/Magento/Swatches/Test/etc/testcase.xml b/dev/tests/functional/tests/app/Magento/Swatches/Test/etc/testcase.xml new file mode 100644 index 0000000000000..6d7d057da2c41 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Swatches/Test/etc/testcase.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + +