Skip to content

Commit 49e63ea

Browse files
committed
Merge remote-tracking branch 'origin/2.3-develop' into MC-20071
2 parents 68531d9 + 1ebd7cc commit 49e63ea

File tree

770 files changed

+27494
-4434
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

770 files changed

+27494
-4434
lines changed

app/code/Magento/AdminAnalytics/view/adminhtml/ui_component/admin_usage_notification.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@
8585
<item name="text" xsi:type="string" translate="true"><![CDATA[
8686
<p>Help us improve Magento Admin by allowing us to collect usage data.</p>
8787
<p>All usage data that we collect for this purpose cannot be used to individually identify you and is used only to improve the Magento Admin and related products and services.</p>
88-
<p>You can learn more and opt out at any time by following the instructions in <a href="https://docs.magento.com/m2/ce/user_guide/stores/admin.html" target="_blank">merchant documentation</a>.</p>
88+
<p>You can learn more and opt out at any time by following the instructions in <a href="https://docs.magento.com/m2/ce/user_guide/stores/admin.html" target="_blank" tabindex="0">merchant documentation</a>.</p>
8989
]]></item>
9090
</item>
9191
</argument>

app/code/Magento/AdminAnalytics/view/adminhtml/web/js/modal/component.js

Lines changed: 84 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,7 @@ define([
2020
enableLogAction: '${ $.provider }:data.enableLogAction',
2121
disableLogAction: '${ $.provider }:data.disableLogAction'
2222
},
23-
options: {
24-
keyEventHandlers: {
25-
/**
26-
* Prevents escape key from exiting out of modal
27-
*/
28-
escapeKey: function () {
29-
return;
30-
}
31-
}
32-
},
23+
options: {},
3324
notificationWindow: null
3425
},
3526

@@ -41,11 +32,32 @@ define([
4132
this._super();
4233
},
4334

35+
/**
36+
* Configure ESC and TAB so user can't leave modal
37+
* without selecting an option
38+
*
39+
* @returns {Object} Chainable.
40+
*/
41+
initModalEvents: function () {
42+
this._super();
43+
//Don't allow ESC key to close modal
44+
this.options.keyEventHandlers.escapeKey = this.handleEscKey.bind(this);
45+
//Restrict tab action to the modal
46+
this.options.keyEventHandlers.tabKey = this.handleTabKey.bind(this);
47+
48+
return this;
49+
},
50+
4451
/**
4552
* Once the modal is opened it hides the X
4653
*/
4754
onOpened: function () {
48-
$('.modal-header button.action-close').hide();
55+
$('.modal-header button.action-close').attr('disabled', true).hide();
56+
57+
this.focusableElements = $(this.rootSelector).find('a[href], button:enabled');
58+
this.firstFocusableElement = this.focusableElements[0];
59+
this.lastFocusableElement = this.focusableElements[this.focusableElements.length - 1];
60+
this.firstFocusableElement.focus();
4961
},
5062

5163
/**
@@ -104,11 +116,70 @@ define([
104116
* Allows admin usage popup to be shown first and then new release notification
105117
*/
106118
openReleasePopup: function () {
107-
var notifiModal = registry.get('release_notification.release_notification.notification_modal_1');
119+
var notificationModalSelector = 'release_notification.release_notification.notification_modal_1';
108120

109121
if (analyticsPopupConfig.releaseVisible) {
110-
notifiModal.initializeContentAfterAnalytics();
122+
registry.get(notificationModalSelector).initializeContentAfterAnalytics();
111123
}
124+
},
125+
126+
/**
127+
* Handle Tab and Shift+Tab key event
128+
*
129+
* Keep the tab actions restricted to the popup modal
130+
* so the user must select an option to dismiss the modal
131+
*/
132+
handleTabKey: function (event) {
133+
var modal = this,
134+
KEY_TAB = 9;
135+
136+
/**
137+
* Handle Shift+Tab to tab backwards
138+
*/
139+
function handleBackwardTab() {
140+
if (document.activeElement === modal.firstFocusableElement ||
141+
document.activeElement === $(modal.rootSelector)[0]
142+
) {
143+
event.preventDefault();
144+
modal.lastFocusableElement.focus();
145+
}
146+
}
147+
148+
/**
149+
* Handle Tab forward
150+
*/
151+
function handleForwardTab() {
152+
if (document.activeElement === modal.lastFocusableElement) {
153+
event.preventDefault();
154+
modal.firstFocusableElement.focus();
155+
}
156+
}
157+
158+
switch (event.keyCode) {
159+
case KEY_TAB:
160+
if (modal.focusableElements.length === 1) {
161+
event.preventDefault();
162+
break;
163+
}
164+
165+
if (event.shiftKey) {
166+
handleBackwardTab();
167+
break;
168+
}
169+
handleForwardTab();
170+
break;
171+
default:
172+
break;
173+
}
174+
},
175+
176+
/**
177+
* Handle Esc key
178+
*
179+
* Esc key should not close modal
180+
*/
181+
handleEscKey: function (event) {
182+
event.preventDefault();
112183
}
113184
}
114185
);
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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="AdminSystemMessagesWarningActionGroup">
12+
<annotations>
13+
<description>Check warning system message exists.</description>
14+
</annotations>
15+
<arguments>
16+
<argument name="message" type="string"/>
17+
</arguments>
18+
19+
<waitForElementVisible selector="{{AdminSystemMessagesSection.systemMessagesDropdown}}" stepKey="waitMessagesDropdownAppears"/>
20+
<conditionalClick selector="{{AdminSystemMessagesSection.systemMessagesDropdown}}" dependentSelector="{{AdminSystemMessagesSection.messagesBlock}}" visible="false" stepKey="openMessagesBlockIfCollapsed"/>
21+
<see userInput="{{message}}" selector="{{AdminSystemMessagesSection.warning}}" stepKey="seeWarningMessage"/>
22+
</actionGroup>
23+
</actionGroups>

app/code/Magento/AdminNotification/Test/Mftf/Section/AdminSystemMessagesSection.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,9 @@
1111
<section name="AdminSystemMessagesSection">
1212
<element name="systemMessagesDropdown" type="button" selector="#system_messages .message-system-action-dropdown"/>
1313
<element name="actionMessageLog" type="button" selector="//*[contains(@class, 'message-system-summary')]/a[contains(text(), '{{textMessage}}')]" parameterized="true"/>
14+
<element name="messagesBlock" type="block" selector="#system_messages div.message-system-collapsible"/>
15+
<element name="success" type="text" selector="#system_messages div.message-success"/>
16+
<element name="warning" type="text" selector="#system_messages div.message-warning"/>
17+
<element name="notice" type="text" selector="#system_messages div.message-notice"/>
1418
</section>
1519
</sections>
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\AdvancedSearch\Test\Unit\Model\Recommendations;
9+
10+
use Magento\AdvancedSearch\Model\Recommendations\DataProvider;
11+
use Magento\Framework\App\Config\ScopeConfigInterface;
12+
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
13+
use Magento\Catalog\Model\Layer\Resolver;
14+
use Magento\AdvancedSearch\Model\ResourceModel\Recommendations;
15+
use Magento\AdvancedSearch\Model\ResourceModel\RecommendationsFactory;
16+
use Magento\Search\Model\QueryResult;
17+
use Magento\Search\Model\QueryResultFactory;
18+
use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection;
19+
use Magento\Catalog\Model\Layer as SearchLayer;
20+
use Magento\Store\Model\ScopeInterface;
21+
use Magento\Search\Model\QueryInterface;
22+
23+
/**
24+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
25+
*
26+
* Class \Magento\AdvancedSearch\Test\Unit\Model\Recommendations\DataProviderTest
27+
*/
28+
class DataProviderTest extends \PHPUnit\Framework\TestCase
29+
{
30+
/**
31+
* @var DataProvider;
32+
*/
33+
private $model;
34+
35+
/**
36+
* @var ObjectManagerHelper
37+
*/
38+
private $objectManagerHelper;
39+
40+
/**
41+
* @var \PHPUnit_Framework_MockObject_MockObject|ScopeConfigInterface
42+
*/
43+
private $scopeConfigMock;
44+
45+
/**
46+
* @var \PHPUnit_Framework_MockObject_MockObject|Resolver
47+
*/
48+
private $layerResolverMock;
49+
50+
/**
51+
* @var \PHPUnit_Framework_MockObject_MockObject|SearchLayer
52+
*/
53+
private $searchLayerMock;
54+
55+
/**
56+
* @var \PHPUnit_Framework_MockObject_MockObject|RecommendationsFactory
57+
*/
58+
private $recommendationsFactoryMock;
59+
60+
/**
61+
* @var \PHPUnit_Framework_MockObject_MockObject|Recommendations
62+
*/
63+
private $recommendationsMock;
64+
65+
/**
66+
* @var \PHPUnit_Framework_MockObject_MockObject|Resolver
67+
*/
68+
private $queryResultFactory;
69+
70+
/**
71+
* Set up test environment.
72+
*
73+
* @return void
74+
*/
75+
protected function setUp()
76+
{
77+
$this->scopeConfigMock = $this->createMock(ScopeConfigInterface::class);
78+
$this->layerResolverMock = $this->getMockBuilder(Resolver::class)
79+
->disableOriginalConstructor()
80+
->setMethods(['get'])
81+
->getMock();
82+
83+
$this->searchLayerMock = $this->createMock(SearchLayer::class);
84+
85+
$this->layerResolverMock->expects($this->any())
86+
->method('get')
87+
->will($this->returnValue($this->searchLayerMock));
88+
89+
$this->recommendationsFactoryMock = $this->getMockBuilder(RecommendationsFactory::class)
90+
->disableOriginalConstructor()
91+
->setMethods(['create'])
92+
->getMock();
93+
94+
$this->recommendationsMock = $this->createMock(Recommendations::class);
95+
96+
$this->queryResultFactory = $this->getMockBuilder(QueryResultFactory::class)
97+
->disableOriginalConstructor()
98+
->setMethods(['create'])
99+
->getMock();
100+
101+
$this->objectManagerHelper = new ObjectManagerHelper($this);
102+
$this->model = $this->objectManagerHelper->getObject(
103+
DataProvider::class,
104+
[
105+
'scopeConfig' => $this->scopeConfigMock,
106+
'layerResolver' => $this->layerResolverMock,
107+
'recommendationsFactory' => $this->recommendationsFactoryMock,
108+
'queryResultFactory' => $this->queryResultFactory
109+
]
110+
);
111+
}
112+
113+
/**
114+
* Test testGetItems() when Search Recommendations disabled.
115+
*
116+
* @return void
117+
*/
118+
public function testGetItemsWhenDisabledSearchRecommendations()
119+
{
120+
$isEnabledSearchRecommendations = false;
121+
122+
/** @var $queryInterfaceMock QueryInterface */
123+
$queryInterfaceMock = $this->createMock(QueryInterface::class);
124+
125+
$this->scopeConfigMock->expects($this->any())
126+
->method('isSetFlag')
127+
->with('catalog/search/search_recommendations_enabled', ScopeInterface::SCOPE_STORE)
128+
->willReturn($isEnabledSearchRecommendations);
129+
130+
$result = $this->model->getItems($queryInterfaceMock);
131+
$this->assertEquals([], $result);
132+
}
133+
134+
/**
135+
* Test testGetItems() when Search Recommendations enabled.
136+
*
137+
* @return void
138+
*/
139+
public function testGetItemsWhenEnabledSearchRecommendations()
140+
{
141+
$storeId = 1;
142+
$searchRecommendationsCountConfig = 2;
143+
$isEnabledSearchRecommendations = true;
144+
$queryText = 'test';
145+
146+
/** @var $queryInterfaceMock QueryInterface */
147+
$queryInterfaceMock = $this->createMock(QueryInterface::class);
148+
$queryInterfaceMock->expects($this->any())->method('getQueryText')->willReturn($queryText);
149+
150+
$this->scopeConfigMock->expects($this->any())
151+
->method('isSetFlag')
152+
->with('catalog/search/search_recommendations_enabled', ScopeInterface::SCOPE_STORE)
153+
->willReturn($isEnabledSearchRecommendations);
154+
155+
$this->scopeConfigMock->expects($this->any())
156+
->method('getValue')
157+
->with('catalog/search/search_recommendations_count', ScopeInterface::SCOPE_STORE)
158+
->willReturn($searchRecommendationsCountConfig);
159+
160+
$productCollectionMock = $this->createMock(ProductCollection::class);
161+
$productCollectionMock->expects($this->any())->method('getStoreId')->willReturn($storeId);
162+
163+
$this->searchLayerMock->expects($this->any())->method('getProductCollection')
164+
->willReturn($productCollectionMock);
165+
166+
$this->recommendationsFactoryMock->expects($this->any())->method('create')
167+
->willReturn($this->recommendationsMock);
168+
169+
$this->recommendationsMock->expects($this->any())->method('getRecommendationsByQuery')
170+
->with($queryText, ['store_id' => $storeId], $searchRecommendationsCountConfig)
171+
->willReturn(
172+
[
173+
[
174+
'query_text' => 'a',
175+
'num_results' => 3
176+
],
177+
[
178+
'query_text' => 'b',
179+
'num_results' => 2
180+
]
181+
]
182+
);
183+
$queryResultMock = $this->createMock(QueryResult::class);
184+
$this->queryResultFactory->expects($this->any())->method('create')->willReturn($queryResultMock);
185+
186+
$result = $this->model->getItems($queryInterfaceMock);
187+
$this->assertEquals(2, count($result));
188+
}
189+
}

app/code/Magento/Analytics/Test/Mftf/Data/UserRoleData.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
1010
xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
1111
<entity name="adminNoReportRole" type="user_role">
12+
<data key="all">0</data>
1213
<data key="rolename" unique="suffix">noreport</data>
1314
<data key="current_password">123123q</data>
1415
<array key="resource">

0 commit comments

Comments
 (0)