Skip to content

Commit 4192ac1

Browse files
authored
Merge pull request magento#4048 from magento-tsg/2.2-develop-pr90
[TSG] Backporting for 2.2 (pr90) (2.2-develop)
2 parents 8cf665b + 8fd2fc6 commit 4192ac1

File tree

19 files changed

+417
-18
lines changed

19 files changed

+417
-18
lines changed

app/code/Magento/Backend/Test/Mftf/Section/AdminPopupModalSection.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@
1010
<section name="AdminPopupModalSection">
1111
<element name="message" type="text" selector="aside.modal-popup .modal-content .popup-window-content"/>
1212
<element name="ok" type="button" selector="//span[contains(text(),'Ok')]/ancestor::button"/>
13+
<element name="confirm" type="button" selector="//aside[contains(@class, 'modal-popup')]//footer/button[normalize-space(.)='Confirm']" timeout="30"/>
1314
</section>
1415
</sections>

app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,12 @@ public function __construct(
7979
}
8080

8181
/**
82-
* Save product action
82+
* Save product action.
8383
*
8484
* @return \Magento\Backend\Model\View\Result\Redirect
8585
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
8686
* @SuppressWarnings(PHPMD.NPathComplexity)
87+
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
8788
*/
8889
public function execute()
8990
{
@@ -143,6 +144,7 @@ public function execute()
143144
if ($redirectBack === 'duplicate') {
144145
$product->unsetData('quantity_and_stock_status');
145146
$newProduct = $this->productCopier->copy($product);
147+
$this->checkUniqueAttributes($product);
146148
$this->messageManager->addSuccessMessage(__('You duplicated the product.'));
147149
}
148150
} catch (\Magento\Framework\Exception\LocalizedException $e) {
@@ -321,4 +323,25 @@ private function persistMediaData(ProductInterface $product, array $data)
321323

322324
return $data;
323325
}
326+
327+
/**
328+
* Check unique attributes and add error to message manager.
329+
*
330+
* @param \Magento\Catalog\Model\Product $product
331+
*/
332+
private function checkUniqueAttributes(\Magento\Catalog\Model\Product $product)
333+
{
334+
$uniqueLabels = [];
335+
foreach ($product->getAttributes() as $attribute) {
336+
if ($attribute->getIsUnique() && $attribute->getIsUserDefined()
337+
&& !empty($product->getData($attribute->getAttributeCode()))
338+
) {
339+
$uniqueLabels[] = $attribute->getDefaultFrontendLabel();
340+
}
341+
}
342+
if ($uniqueLabels) {
343+
$uniqueLabels = implode('", "', $uniqueLabels);
344+
$this->messageManager->addErrorMessage(__('The value of attribute(s) "%1" must be unique', $uniqueLabels));
345+
}
346+
}
324347
}

app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/SaveTest.php

Lines changed: 127 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@
55
*/
66
namespace Magento\Catalog\Test\Unit\Controller\Adminhtml\Product;
77

8+
use Magento\Catalog\Api\CategoryLinkManagementInterface;
9+
use Magento\Catalog\Api\Data\ProductAttributeInterface;
810
use Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper;
11+
use Magento\Catalog\Model\Product\Copier;
912
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
1013

1114
/**
15+
* Unit tests for \Magento\Catalog\Controller\Adminhtml\Product\Save class.
16+
*
1217
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
1318
*/
1419
class SaveTest extends \Magento\Catalog\Test\Unit\Controller\Adminhtml\ProductTest
@@ -28,6 +33,21 @@ class SaveTest extends \Magento\Catalog\Test\Unit\Controller\Adminhtml\ProductTe
2833
/** @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject */
2934
private $product;
3035

36+
/**
37+
* @var Copier|\PHPUnit_Framework_MockObject_MockObject
38+
*/
39+
private $productCopierMock;
40+
41+
/**
42+
* @var ProductAttributeInterface|\PHPUnit_Framework_MockObject_MockObject
43+
*/
44+
private $productAttributeMock;
45+
46+
/**
47+
* @var CategoryLinkManagementInterface|\PHPUnit_Framework_MockObject_MockObject
48+
*/
49+
private $categoryLinkManagementMock;
50+
3151
/** @var \Magento\Backend\Model\View\Result\RedirectFactory|\PHPUnit_Framework_MockObject_MockObject */
3252
private $resultRedirectFactory;
3353

@@ -42,18 +62,41 @@ class SaveTest extends \Magento\Catalog\Test\Unit\Controller\Adminhtml\ProductTe
4262

4363
/**
4464
* @return void
65+
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
4566
*/
4667
protected function setUp()
4768
{
4869
$this->productBuilder = $this->createPartialMock(
4970
\Magento\Catalog\Controller\Adminhtml\Product\Builder::class,
5071
['build']
5172
);
52-
$this->product = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)->disableOriginalConstructor()
53-
->setMethods(['addData', 'getSku', 'getTypeId', 'getStoreId', '__sleep', '__wakeup'])->getMock();
73+
$this->product = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)
74+
->disableOriginalConstructor()
75+
->setMethods([
76+
'addData',
77+
'unsetData',
78+
'getData',
79+
'getSku',
80+
'getCategoryIds',
81+
'getAttributes',
82+
'getTypeId',
83+
'getStoreId',
84+
'save',
85+
'__sleep',
86+
'__wakeup',
87+
])
88+
->getMock();
5489
$this->product->expects($this->any())->method('getTypeId')->will($this->returnValue('simple'));
5590
$this->product->expects($this->any())->method('getStoreId')->will($this->returnValue('1'));
5691
$this->productBuilder->expects($this->any())->method('build')->will($this->returnValue($this->product));
92+
$this->productCopierMock = $this->getMockBuilder(Copier::class)
93+
->disableOriginalConstructor()
94+
->getMock();
95+
$this->productAttributeMock = $this->getMockBuilder(ProductAttributeInterface::class)
96+
->setMethods(['getIsUnique', 'getIsUserDefined', 'getAttributeCode', 'getDefaultFrontendLabel'])
97+
->getMockForAbstractClass();
98+
$this->categoryLinkManagementMock = $this->getMockBuilder(CategoryLinkManagementInterface::class)
99+
->getMockForAbstractClass();
57100

58101
$this->messageManagerMock = $this->getMockForAbstractClass(
59102
\Magento\Framework\Message\ManagerInterface::class
@@ -155,4 +198,86 @@ public function exceptionTypeDataProvider()
155198
['Exception', 'addErrorMessage']
156199
];
157200
}
201+
202+
/**
203+
* @return void
204+
*/
205+
public function testExecuteCheckUniqueAttributesOnDuplicate()
206+
{
207+
$productSku = 'test_sku';
208+
$attributeCode = 'test_attribute_code';
209+
210+
$productData = [
211+
'product' => [
212+
'name' => 'test-name',
213+
'sku' => $productSku,
214+
$attributeCode => 'test_attribute',
215+
]
216+
];
217+
218+
$this->request->expects($this->at(1))
219+
->method('getParam')
220+
->with('back', false)
221+
->willReturn('duplicate');
222+
223+
$this->request->expects($this->any())->method('getPostValue')->willReturn($productData);
224+
$this->initializationHelper->expects($this->any())->method('initialize')
225+
->willReturn($this->product);
226+
227+
$this->product->expects($this->once())
228+
->method('save')
229+
->willReturnSelf();
230+
$this->product->expects($this->any())
231+
->method('getSku')
232+
->willReturn($productSku);
233+
$this->product->expects($this->any())
234+
->method('getCategoryIds')
235+
->willReturn([]);
236+
237+
$this->categoryLinkManagementMock->expects($this->any())
238+
->method('assignProductToCategories')
239+
->with($productSku, [])
240+
->willReturn(true);
241+
242+
$this->product->expects($this->once())
243+
->method('unsetData')
244+
->with('quantity_and_stock_status')
245+
->willReturnSelf();
246+
247+
$this->productCopierMock->expects($this->any())
248+
->method('copy')
249+
->with($this->product)
250+
->willReturn($this->product);
251+
252+
$this->product->expects($this->once())
253+
->method('getAttributes')
254+
->willReturn([$this->productAttributeMock]);
255+
256+
$this->productAttributeMock->expects($this->atLeastOnce())
257+
->method('getIsUnique')
258+
->willReturn('1');
259+
$this->productAttributeMock->expects($this->atLeastOnce())
260+
->method('getIsUserDefined')
261+
->willReturn('1');
262+
$this->productAttributeMock->expects($this->atLeastOnce())
263+
->method('getAttributeCode')
264+
->willReturn($attributeCode);
265+
266+
$this->product->expects($this->any())
267+
->method('getData')
268+
->willReturnMap([
269+
[$attributeCode, null, $productData['product'][$attributeCode]]
270+
]);
271+
272+
$this->productAttributeMock->expects($this->atLeastOnce())
273+
->method('getDefaultFrontendLabel')
274+
->willReturn('Test Attribute Label');
275+
276+
$this->messageManagerMock->expects($this->once())
277+
->method('addErrorMessage');
278+
$this->messageManagerMock->expects($this->atLeastOnce())
279+
->method('addSuccessMessage');
280+
281+
$this->action->execute();
282+
}
158283
}

app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductNameMinicartOnCheckoutPageDifferentStoreViewsTest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
<actionGroup ref="SwitchToTheNewStoreView" stepKey="switchToCustomStoreView">
5353
<argument name="storeViewName" value="customStore"/>
5454
</actionGroup>
55+
<scrollToTopOfPage stepKey="scrolToShowNameField"/>
5556
<click selector="{{AdminProductFormSection.productNameUseDefault}}" stepKey="uncheckUseDefault"/>
5657
<fillField selector="{{AdminProductFormSection.productName}}" userInput="$$createProduct.name$$-new" stepKey="fillProductName"/>
5758
<click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveProduct"/>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
9+
<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
11+
<actionGroup name="AdminSaveCustomerForm">
12+
<scrollToTopOfPage stepKey="scrollToPageTop"/>
13+
<click selector="{{AdminMainActionsSection.save}}" stepKey="clickSaveButton"/>
14+
<waitForElementVisible selector="{{AdminMessagesSection.success}}" stepKey="waitSuccessMessage"/>
15+
<see selector="{{AdminMessagesSection.success}}" userInput="You saved the customer." stepKey="seeSaveMessage"/>
16+
</actionGroup>
17+
</actionGroups>

app/code/Magento/Msrp/Test/Mftf/ActionGroup/AdminProductActionGroup.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
<argument name="msrp" type="string" defaultValue="100"/>
1414
</arguments>
1515
<amOnPage url="{{AdminProductEditPage.url(product.id)}}" stepKey="goToProductEditPage"/>
16-
<waitForPageLoad stepKey="waitForProductEditPageLoad"/>
16+
<scrollToTopOfPage stepKey="scrollToTopToSeeAdvancedPricingButton"/>
1717
<click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickAdvancedPricingLink"/>
18-
<waitForElement selector="{{AdminProductFormAdvancedPricingSection.msrp}}" stepKey="waitForMsrpElement"/>
18+
<waitForElementVisible selector="{{AdminProductFormAdvancedPricingSection.msrp}}" stepKey="waitForMsrpElement"/>
1919
<fillField selector="{{AdminProductFormAdvancedPricingSection.msrp}}" userInput="{{msrp}}" stepKey="fillMsrpField"/>
2020
<click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDoneButton"/>
2121
</actionGroup>

app/code/Magento/Wishlist/Test/Mftf/ActionGroup/StorefrontCustomerWishlistActionGroup.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,16 @@
8787
<click selector="{{StorefrontCustomerWishlistProductSection.productUpdateWishList}}" stepKey="submitUpdateWishlist"/>
8888
<see selector="{{StorefrontMessagesSection.success}}" userInput="{{product.name}} has been updated in your Wish List." stepKey="successMessage"/>
8989
</actionGroup>
90+
91+
<actionGroup name="StorefrontValidateQtyAfterEditProductInWishlist" extends="StorefrontCustomerEditProductInWishlist">
92+
<arguments>
93+
<argument name="maxQtyAllowed" type="string" default="10000"/>
94+
</arguments>
95+
<remove keyForRemoval="successMessage"/>
96+
<waitForAjaxLoad after="submitUpdateWishlist" stepKey="waitForAjaxLoad"/>
97+
<scrollToTopOfPage after="waitForAjaxLoad" stepKey="scrollToTop"/>
98+
<moveMouseOver selector="{{StorefrontCustomerWishlistProductSection.productInfoByName(product.name)}}" after="scrollToTop" stepKey="moveMouseOverProduct"/>
99+
<waitForElementVisible selector="{{StorefrontCustomerWishlistProductSection.productQtyError(product.name)}}" after="moveMouseOverProduct" stepKey="waitForErrorMessage"/>
100+
<see selector="{{StorefrontCustomerWishlistProductSection.productQtyError(product.name)}}" userInput="The maximum you may purchase is {{maxQtyAllowed}}." after="waitForErrorMessage" stepKey="seeErrorMessage"/>
101+
</actionGroup>
90102
</actionGroups>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
9+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
10+
<entity name="DefaultMaxQtyAllowedConfig" type="max_qty_allowed_config">
11+
<requiredEntity type="max_qty_allowed_config_default">DefaultMaxQtyAllowed</requiredEntity>
12+
</entity>
13+
<entity name="DefaultMaxQtyAllowed" type="max_qty_allowed_config_default">
14+
<data key="value">0</data>
15+
</entity>
16+
17+
<entity name="SetMaxQtyAllowedConfigZero" type="max_qty_allowed_config">
18+
<data key="value">0</data>
19+
</entity>
20+
</entities>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
9+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd">
10+
<operation name="MaxQtyAllowedConfig" dataType="max_qty_allowed_config" type="create" auth="adminFormKey" url="/admin/system_config/save/section/cataloginventory/" method="POST" successRegex="/messages-message-success/">
11+
<object key="groups" dataType="max_qty_allowed_config">
12+
<object key="item_options" dataType="max_qty_allowed_config">
13+
<object key="fields" dataType="max_qty_allowed_config">
14+
<object key="max_sale_qty" dataType="max_qty_allowed_config">
15+
<field key="value">string</field>
16+
<object key="inherit" dataType="max_qty_allowed_config_default">
17+
<field key="value">integer</field>
18+
</object>
19+
</object>
20+
</object>
21+
</object>
22+
</object>
23+
</operation>
24+
</operations>

app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontCustomerWishlistProductSection.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@
1919
<element name="productQuantity" type="input" selector="//a[contains(text(), '{{productName}}')]/ancestor::div[@class='product-item-info']//input[@class='input-text qty']" parameterized="true"/>
2020
<element name="productUpdateWishList" type="button" selector=".column.main .actions-toolbar .action.update" timeout="30"/>
2121
<element name="productAddAllToCart" type="button" selector=".column.main .actions-toolbar .action.tocart" timeout="30"/>
22+
<element name="productQtyError" type="text" selector="//li[.//a[contains(text(), '{{productName}}')]]//div[@class='mage-error']" parameterized="true" timeout="30"/>
2223
</section>
2324
</sections>

0 commit comments

Comments
 (0)