diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CategoriesTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CategoriesTest.php index f913779fc526e9b8e45e592a4e1a8834d0ff7f03..2070a64884b1172f5e00e6e950981d10c5ddadb3 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CategoriesTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CategoriesTest.php @@ -8,6 +8,7 @@ namespace Magento\Catalog\Test\Unit\Ui\DataProvider\Product\Form\Modifier; use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Categories; use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory as CategoryCollectionFactory; use Magento\Catalog\Model\ResourceModel\Category\Collection as CategoryCollection; +use Magento\Framework\App\CacheInterface; use Magento\Framework\DB\Helper as DbHelper; use Magento\Framework\UrlInterface; use Magento\Store\Model\Store; @@ -112,4 +113,38 @@ class CategoriesTest extends AbstractModifierTest $this->assertArrayHasKey($groupCode, $this->getModel()->modifyMeta($meta)); } + + public function testModifyMetaWithCaching() + { + $this->arrayManagerMock->expects($this->exactly(2)) + ->method('findPath') + ->willReturn(true); + $cacheManager = $this->getMockBuilder(CacheInterface::class) + ->getMockForAbstractClass(); + $cacheManager->expects($this->once()) + ->method('load') + ->with(Categories::CATEGORY_TREE_ID . '_'); + $cacheManager->expects($this->once()) + ->method('save'); + + $modifier = $this->createModel(); + $cacheContextProperty = new \ReflectionProperty( + Categories::class, + 'cacheManager' + ); + $cacheContextProperty->setAccessible(true); + $cacheContextProperty->setValue($modifier, $cacheManager); + + $groupCode = 'test_group_code'; + $meta = [ + $groupCode => [ + 'children' => [ + 'category_ids' => [ + 'sortOrder' => 10, + ], + ], + ], + ]; + $modifier->modifyMeta($meta); + } } diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php index b0e663532e8690ab75c8a0580155d3629009f86e..0e46b5899851f477abaae8a0591c4e95e872ef3e 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php @@ -7,6 +7,8 @@ namespace Magento\Catalog\Ui\DataProvider\Product\Form\Modifier; use Magento\Catalog\Model\Locator\LocatorInterface; use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory as CategoryCollectionFactory; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\App\CacheInterface; use Magento\Framework\DB\Helper as DbHelper; use Magento\Catalog\Model\Category as CategoryModel; use Magento\Framework\UrlInterface; @@ -14,9 +16,16 @@ use Magento\Framework\Stdlib\ArrayManager; /** * Data provider for categories field of product page + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Categories extends AbstractModifier { + /**#@+ + * Category tree cache id + */ + const CATEGORY_TREE_ID = 'CATALOG_PRODUCT_CATEGORY_TREE'; + /**#@-*/ + /** * @var CategoryCollectionFactory */ @@ -29,6 +38,7 @@ class Categories extends AbstractModifier /** * @var array + * @deprecated */ protected $categoriesTrees = []; @@ -47,6 +57,11 @@ class Categories extends AbstractModifier */ protected $arrayManager; + /** + * @var CacheInterface + */ + private $cacheManager; + /** * @param LocatorInterface $locator * @param CategoryCollectionFactory $categoryCollectionFactory @@ -68,6 +83,21 @@ class Categories extends AbstractModifier $this->arrayManager = $arrayManager; } + /** + * Retrieve cache interface + * + * @return CacheInterface + * @deprecated + */ + private function getCacheManager() + { + if (!$this->cacheManager) { + $this->cacheManager = ObjectManager::getInstance() + ->get(CacheInterface::class); + } + return $this->cacheManager; + } + /** * {@inheritdoc} */ @@ -254,8 +284,9 @@ class Categories extends AbstractModifier */ protected function getCategoriesTree($filter = null) { - if (isset($this->categoriesTrees[$filter])) { - return $this->categoriesTrees[$filter]; + $categoryTree = $this->getCacheManager()->load(self::CATEGORY_TREE_ID . '_' . $filter); + if ($categoryTree) { + return unserialize($categoryTree); } $storeId = $this->locator->getStore()->getId(); @@ -307,9 +338,16 @@ class Categories extends AbstractModifier $categoryById[$category->getId()]['label'] = $category->getName(); $categoryById[$category->getParentId()]['optgroup'][] = &$categoryById[$category->getId()]; } + + $this->getCacheManager()->save( + serialize($categoryById[CategoryModel::TREE_ROOT_ID]['optgroup']), + self::CATEGORY_TREE_ID . '_' . $filter, + [ + \Magento\Catalog\Model\Category::CACHE_TAG, + \Magento\Framework\App\Cache\Type\Block::CACHE_TAG + ] + ); - $this->categoriesTrees[$filter] = $categoryById[CategoryModel::TREE_ROOT_ID]['optgroup']; - - return $this->categoriesTrees[$filter]; + return $categoryById[CategoryModel::TREE_ROOT_ID]['optgroup']; } } diff --git a/app/code/Magento/Ui/view/base/web/js/form/components/fieldset.js b/app/code/Magento/Ui/view/base/web/js/form/components/fieldset.js index b7031f1df85c24edc1c68761bb370682eb5bfcd0..d0799aad750d9f776b495717c832c6b1cb89d754 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/components/fieldset.js +++ b/app/code/Magento/Ui/view/base/web/js/form/components/fieldset.js @@ -19,6 +19,9 @@ define([ level: 0, visible: true, disabled: false, + listens: { + 'opened': 'onVisibilityChange' + }, additionalClasses: {} }, @@ -30,7 +33,19 @@ define([ _.bindAll(this, 'onChildrenUpdate', 'onChildrenError', 'onContentLoading'); return this._super() - ._setClasses(); + ._setClasses(); + }, + + /** + * Initializes components' configuration. + * + * @returns {Fieldset} Chainable. + */ + initConfig: function () { + this._super(); + this._wasOpened = this.opened || !this.collapsible; + + return this; }, /** @@ -117,6 +132,17 @@ define([ return this; }, + /** + * Handler of the "opened" property changes. + * + * @param {Boolean} isOpened + */ + onVisibilityChange: function (isOpened) { + if (!this._wasOpened) { + this._wasOpened = isOpened; + } + }, + /** * Is being invoked on children validation error. * Sets error property to one incoming. diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/ui-select.js b/app/code/Magento/Ui/view/base/web/js/form/element/ui-select.js index 393b0f04e512cb9df9dd509044b26c599fd170c0..be312c71f1fb20a1a2140765d598d8567bfaf648 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/ui-select.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/ui-select.js @@ -9,7 +9,8 @@ define([ 'Magento_Ui/js/lib/key-codes', 'mage/translate', 'ko', - 'jquery' + 'jquery', + 'Magento_Ui/js/lib/view/utils/async' ], function (_, Abstract, keyCodes, $t, ko, $) { 'use strict'; @@ -148,9 +149,14 @@ define([ showPath: true, labelsDecoration: false, disableLabel: false, + filterRateLimit: 500, closeBtnLabel: $t('Done'), optgroupTmpl: 'ui/grid/filters/elements/ui-select-optgroup', quantityPlaceholder: $t('options'), + hoverClass: '_hover', + rootListSelector: 'ul.admin__action-multiselect-menu-inner._root', + visibleOptionSelector: 'li.admin__action-multiselect-menu-inner-item:visible', + actionTargetSelector: '.action-menu-item', selectedPlaceholders: { defaultPlaceholder: $t('Select...'), lotPlaceholders: $t('Selected') @@ -179,6 +185,23 @@ define([ } }, + /** + * Initializes UISelect component. + * + * @returns {UISelect} Chainable. + */ + initialize: function () { + this._super(); + + $.async( + this.rootListSelector, + this, + this.onRootListRender.bind(this) + ); + + return this; + }, + /** * Parses options and merges the result with instance * Set defaults according to mode and levels configuration @@ -281,6 +304,8 @@ define([ 'filterOptionsFocus' ]); + this.filterInputValue.extend({rateLimit: this.filterRateLimit}); + return this; }, @@ -307,17 +332,22 @@ define([ * @returns {Boolean} level visibility. */ showLevels: function (data) { - var curLevel = ++data.level; + var curLevel = ++data.level, + isVisible; - if (!data.visible) { - data.visible = ko.observable(!!data.hasOwnProperty(this.separator) && + if (data.visible) { + isVisible = data.visible(); + } else { + isVisible = !!data.hasOwnProperty(this.separator) && _.isBoolean(this.levelsVisibility) && this.levelsVisibility || - data.hasOwnProperty(this.separator) && parseInt(this.levelsVisibility, 10) >= curLevel); + data.hasOwnProperty(this.separator) && parseInt(this.levelsVisibility, 10) >= curLevel; + data.visible = ko.observable(isVisible); + data.isVisited = isVisible; } - return data.visible(); + return isVisible; }, /** @@ -398,7 +428,13 @@ define([ var value = this.filterInputValue().trim().toLowerCase(), array = []; - if (value === '') { + if (value && value.length < 2) { + return false; + } + + this.cleanHoveredElement(); + + if (!value) { this.renderPath = false; this.options(this.cacheOptions.tree); this._setItemsQuantity(false); @@ -421,7 +457,6 @@ define([ this.options(array); this._setItemsQuantity(array.length); } - this.cleanHoveredElement(); return false; } @@ -509,8 +544,12 @@ define([ * @returns {Object} Chainable */ cleanHoveredElement: function () { - if (!_.isNull(this.hoverElIndex())) { - this.hoverElIndex(null); + if (this.hoveredElement) { + $(this.hoveredElement) + .children(this.actionTargetSelector) + .removeClass(this.hoverClass); + + this.hoveredElement = null; } return this; @@ -543,14 +582,16 @@ define([ * @return {Boolean} */ isHovered: function (data) { - var index = this.getOptionIndex(data), - status = this.hoverElIndex() === index; + var element = this.hoveredElement, + elementData; - if (this.selectType === 'optgroup' && data.hasOwnProperty(this.separator)) { + if (!element) { return false; } - return status; + elementData = ko.dataFor(this.hoveredElement); + + return data.value === elementData.value; }, /** @@ -612,10 +653,10 @@ define([ * Change visibility to child level * * @param {Object} data - element data - * @param {Object} elem - element */ - openChildLevel: function (data, elem) { - var contextElement; + openChildLevel: function (data) { + var contextElement = data, + isVisible; if ( this.openLevelsAction && @@ -623,8 +664,13 @@ define([ this.openLevelsAction && data.hasOwnProperty(this.separator) && parseInt(this.levelsVisibility, 10) <= data.level ) { - contextElement = ko.contextFor($(elem).parents('li').children('ul')[0]).$data.current; - contextElement.visible(!contextElement.visible()); + isVisible = !contextElement.visible(); + + if (isVisible && !contextElement.isVisited) { + contextElement.isVisited = true; + } + + contextElement.visible(isVisible); } }, @@ -642,25 +688,23 @@ define([ }, /** - * Add hover to some list element and clears element ID to variable + * @deprecated + */ + onMousemove: function () {}, + + /** + * Handles hover on list items. * - * @param {Object} data - object with data about this element - * @param {Number} index - element index * @param {Object} event - mousemove event */ - onMousemove: function (data, index, event) { - var id, - context = ko.contextFor(event.target); - - if (this.isCursorPositionChange(event)) { - return false; - } + onDelegatedMouseMouve: function (event) { + var target = $(event.currentTarget).closest(this.visibleOptionSelector)[0]; - if (typeof context.$data === 'object') { - id = this.getOptionIndex(context.$data); + if (this.isCursorPositionChange(event) || this.hoveredElement === target) { + return; } - id !== this.hoverElIndex() ? this.hoverElIndex(id) : false; + this._hoverTo(target); this.setCursorPosition(event); }, @@ -736,8 +780,8 @@ define([ } if (this.listVisible()) { - if (!_.isNull(this.hoverElIndex())) { - this.toggleOptionSelected(this.cacheOptions.plain[this.hoverElIndex()]); + if (this.hoveredElement) { + this.toggleOptionSelected(ko.dataFor(this.hoveredElement)); } } else { this.setListVisible(true); @@ -756,35 +800,7 @@ define([ * selected first option in list */ pageDownKeyHandler: function () { - var el, - nextEl, - nextData, - nextIndex; - - if (!this.listVisible()) { - return false; - } - - if (this.filterInputValue()) { - el = !_.isNull(this.hoverElIndex()) ? - this._getElemByData(this.cacheOptions.plain[this.hoverElIndex()]) : false; - nextEl = el ? el.next() : $(this.cacheUiSelect).find('li:visible').eq(0); - nextIndex = nextEl.length ? nextEl.index() : 0; - nextData = this.options()[nextIndex]; - this.hoverElIndex(this.getOptionIndex(nextData)); - - return false; - } - - if (!_.isNull(this.hoverElIndex()) && this.hoverElIndex() !== this.cacheOptions.plain.length - 1) { - this._setHoverToElement(1); - this._scrollTo(this.hoverElIndex()); - - return false; - } - - this._setHoverToElement(1, -1); - this._scrollTo(this.hoverElIndex()); + this._setHoverToElement(1); }, /** @@ -813,28 +829,19 @@ define([ * Set hover to visible element * * @param {Number} direction - iterator - * @param {Number} index - current hovered element - * @param {Array} list - collection items */ - _setHoverToElement: function (direction, index, list) { - var modifiedIndex, - curData, - canBeHovered = true; - - list = list || $(this.cacheUiSelect).find('li'); - index = index || _.isNumber(index) ? index : this.hoverElIndex(); - modifiedIndex = index + direction; - modifiedIndex < 0 ? modifiedIndex = this.cacheOptions.plain.length - 1 : false; - curData = this.cacheOptions.plain[modifiedIndex]; + _setHoverToElement: function (direction) { + var element; - if (this.selectType === 'optgroup' && !_.findWhere(this.cacheOptions.lastOptions, {value: curData.value})) { - canBeHovered = false; + if (direction === 1) { + element = this._getNextElement(); + } else if (direction === -1) { + element = this._getPreviousElement(); } - if (list.eq(modifiedIndex).is(':visible') && canBeHovered) { - this.hoverElIndex(modifiedIndex); - } else { - this._setHoverToElement(direction, modifiedIndex, list); + if (element) { + this._hoverTo(element); + this._scrollTo(element); } }, @@ -844,18 +851,15 @@ define([ * * @param {Number} index - element index */ - _scrollTo: function (index) { - var curEl, - parents, - wrapper, + _scrollTo: function (element) { + var curEl = $(element).children(this.actionTargetSelector), + wrapper = $(this.rootList), curElPos = {}, wrapperPos = {}; - curEl = $(this.cacheUiSelect).find('li').eq(index); - parents = curEl.parents('ul'); - wrapper = parents.eq(parents.length - 1); curElPos.start = curEl.offset().top; - curElPos.end = curElPos.start + curEl.height(); + curElPos.end = curElPos.start + curEl.outerHeight(); + wrapperPos.start = wrapper.offset().top; wrapperPos.end = wrapperPos.start + wrapper.height(); @@ -871,46 +875,7 @@ define([ * selected last option in list */ pageUpKeyHandler: function () { - var el, - nextEl, - nextIndex, - nextData; - - if (!this.listVisible()) { - return false; - } - - if (this.filterInputValue()) { - el = !_.isNull(this.hoverElIndex()) ? - this._getElemByData(this.cacheOptions.plain[this.hoverElIndex()]) : false; - nextEl = el ? el.prev() : $(this.cacheUiSelect).find('li:visible').eq(this.options().length-1); - nextIndex = nextEl.length ? nextEl.index() : this.options().length-1; - nextData = this.options()[nextIndex]; - this.hoverElIndex(this.getOptionIndex(nextData)); - - return false; - } - - - if (this.filterInputValue()) { - el = !_.isNull(this.hoverElIndex()) ? - this._getElemByData(this.cacheOptions.plain[this.hoverElIndex()]) : false; - nextEl = el ? el.next() : $(this.cacheUiSelect).find('li:visible').eq(0); - nextIndex = nextEl.length ? nextEl.index() : 0; - nextData = this.options()[nextIndex]; - this.hoverElIndex(this.getOptionIndex(nextData)); - - return false; - } - - if (this.hoverElIndex()) { - this._setHoverToElement(-1); - this._scrollTo(this.hoverElIndex()); - - return false; - } - this._setHoverToElement(-1, this.cacheOptions.plain.length); - this._scrollTo(this.hoverElIndex()); + this._setHoverToElement(-1); }, /** @@ -990,6 +955,129 @@ define([ return selected.map(function (option) { return option.label; }).join(', '); + }, + + /** + * Defines previous option element to + * the one that is currently hovered. + * + * @returns {Element} + */ + _getPreviousElement: function () { + var currentElement = this.hoveredElement, + lastElement = this._getLastIn(this.rootList), + previousElement; + + if (!currentElement) { + return lastElement; + } + + previousElement = $(currentElement).prev()[0]; + + return ( + this._getLastIn(previousElement) || + previousElement || + this._getFirstParentOf(currentElement) || + lastElement + ); + }, + + /** + * Defines next option element to + * the one that is currently hovered. + * + * @returns {Element} + */ + _getNextElement: function () { + var currentElement = this.hoveredElement, + firstElement = this._getFirstIn(this.rootList); + + if (!currentElement) { + return firstElement; + } + + return ( + this._getFirstIn(currentElement) || + $(currentElement).next()[0] || + this._getParentsOf(currentElement).next()[0] || + firstElement + ); + }, + + /** + * Returns first option element in provided scope. + * + * @param {Element} scope + * @returns {Element} + */ + _getFirstIn: function (scope) { + return $(scope).find(this.visibleOptionSelector)[0]; + }, + + /** + * Returns last descendant option element in provided scope. + * + * @param {Element} scope + * @returns {Element} + */ + _getLastIn: function (scope) { + return $(scope).find(this.visibleOptionSelector).last()[0]; + }, + + /** + * Returns a collection of parent option elements. + * + * @param {Element} scope + * @returns {jQueryCollection} + */ + _getParentsOf: function (scope) { + return $(scope).parents(this.visibleOptionSelector); + }, + + /** + * Returns first parent option element. + * + * @param {Element} scope + * @returns {Element} + */ + _getFirstParentOf: function (scope) { + return this._getParentsOf(scope)[0]; + }, + + /** + * Sets hover class to provided option element. + * + * @param {Element} element + */ + _hoverTo: function(element) { + if (this.hoveredElement) { + $(this.hoveredElement) + .children(this.actionTargetSelector) + .removeClass(this.hoverClass); + } + + $(element) + .children(this.actionTargetSelector) + .addClass(this.hoverClass); + + this.hoveredElement = element; + }, + + /** + * Callback which fires when root list element is rendered. + * + * @param {Element} element + */ + onRootListRender: function (element) { + var targetSelector = 'li > ' + this.actionTargetSelector; + + this.rootList = element; + + $(this.rootList).on( + 'mousemove', + targetSelector, + this.onDelegatedMouseMouve.bind(this) + ); } }); }); diff --git a/app/code/Magento/Ui/view/base/web/templates/form/fieldset.html b/app/code/Magento/Ui/view/base/web/templates/form/fieldset.html index b45e5385d037ac9b4cecca6888a7318e6ec8e384..6cb7fb62b3a911309797333c12f33435850243cb 100644 --- a/app/code/Magento/Ui/view/base/web/templates/form/fieldset.html +++ b/app/code/Magento/Ui/view/base/web/templates/form/fieldset.html @@ -9,7 +9,8 @@ attr="'data-level': $data.level, 'data-index': index" data-bind="visible: $data.visible === undefined ? true: $data.visible"> <div class="fieldset-wrapper-title" - attr="tabindex: !collapsible ? -1 : 0" + attr="tabindex: !collapsible ? -1 : 0, + 'data-state-collapsible': collapsible ? opened() ? 'open' : 'closed' : null" click="toggleOpened" keyboard="13: toggleOpened" if="label"> @@ -30,7 +31,7 @@ <span class="admin__page-nav-item-message _error"> <span class="admin__page-nav-item-message-icon"></span> <span class="admin__page-nav-item-message-tooltip" - data-bind="i18n: 'This tab contains invalid data. Please resolve this before saving.'"> + data-bind="i18n: 'This tab contains invalid data. Please resolve this before saving.'"> </span> </span> <span class="admin__page-nav-item-message-loader"> @@ -43,7 +44,10 @@ </div> <div class="admin__fieldset-wrapper-content" - css="'admin__collapsible-content': collapsible, '_show': opened, '_hide': !opened"> - <fieldset class="admin__fieldset" each="data: elems, as: 'element'" render=""/> + css="'admin__collapsible-content': collapsible, '_show': opened, '_hide': !opened()"> + <fieldset + if="opened() || _wasOpened" + class="admin__fieldset" + each="data: elems, as: 'element'" render=""/> </div> </div> diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select-optgroup.html b/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select-optgroup.html index 506aa4381c9b816bb4608802732bb7ffed3c3a46..5f938b6e343128e4d3700d605234eec16a216f47 100644 --- a/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select-optgroup.html +++ b/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select-optgroup.html @@ -10,6 +10,7 @@ attr: { 'data-level': $data.current.level++ }"> + <!-- ko if: $data.current.visible() || $data.current.isVisited --> <!-- ko foreach: { data: $data.current.optgroup, as: 'option'} --> <li class="admin__action-multiselect-menu-inner-item" data-bind="css: { _parent: $data.optgroup }"> @@ -56,4 +57,5 @@ <!-- /ko--> </li> <!-- /ko --> + <!-- /ko --> </ul> \ No newline at end of file diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select.html b/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select.html index a913d27c8aa5aeba6968ab6283dc4717e65e3a9d..fae89160e138e8d56ba6a99a34f5957d36f6a35b 100644 --- a/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select.html +++ b/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select.html @@ -178,4 +178,4 @@ </div> <!-- /ko --> </div> -</div> +</div> \ No newline at end of file diff --git a/app/code/Magento/Ui/view/base/web/templates/modal/modal-component.html b/app/code/Magento/Ui/view/base/web/templates/modal/modal-component.html index c856f9fd68d077b3df7b9d3692a5406915c1e41e..8b45955b5a480363ac05fb5fee3cd4e50632a446 100644 --- a/app/code/Magento/Ui/view/base/web/templates/modal/modal-component.html +++ b/app/code/Magento/Ui/view/base/web/templates/modal/modal-component.html @@ -4,9 +4,6 @@ * See COPYING.txt for license details. */ --> - -<div data-bind="css: modalClass, hasFocus: focused"> - <!-- ko foreach: { data: elems, as: 'element' } --> - <!-- ko template: element.getTemplate() --><!-- /ko --> - <!-- /ko --> +<div css="modalClass" hasFocus="focused"> + <each if="state() || $data.modal" args="data: elems, as: 'element'" render=""/> </div> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Category/Edit/CategoryForm.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Category/Edit/CategoryForm.xml index 42f490d76191a524b12c7837ab5958907199fece..63535106514f2c893b0f822daeb7350fd7d892a3 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Category/Edit/CategoryForm.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Category/Edit/CategoryForm.xml @@ -8,7 +8,7 @@ <sections> <general_information> <class>\Magento\Ui\Test\Block\Adminhtml\Section</class> - <selector>div[class=fieldset-wrapper]</selector> + <selector>[data-index="general"]</selector> <strategy>css selector</strategy> <fields> <is_active> @@ -31,8 +31,8 @@ </general_information> <content> <class>\Magento\Ui\Test\Block\Adminhtml\Section</class> - <selector>//div[contains(@class,'admin__collapsible-block-wrapper')][descendant::select[@name='landing_page']]</selector> - <strategy>xpath</strategy> + <selector>[data-index="content"]</selector> + <strategy>css selector</strategy> <fields> <description> <input>textarea</input> @@ -45,8 +45,8 @@ </content> <display_setting> <class>\Magento\Ui\Test\Block\Adminhtml\Section</class> - <selector>//div[contains(@class,'admin__collapsible-block-wrapper')][descendant::input[@name='is_anchor']]</selector> - <strategy>xpath</strategy> + <selector>[data-index="display_settings"]</selector> + <strategy>css selector</strategy> <fields> <display_mode> <input>select</input> @@ -83,8 +83,8 @@ </display_setting> <seo> <class>\Magento\Ui\Test\Block\Adminhtml\Section</class> - <selector>//div[contains(@class,'admin__collapsible-block-wrapper')][descendant::input[@name='meta_title']]</selector> - <strategy>xpath</strategy> + <selector>[data-index="search_engine_optimization"]</selector> + <strategy>css selector</strategy> <fields> <url_key> <input>input</input> @@ -98,13 +98,13 @@ </seo> <category_products> <class>\Magento\Catalog\Test\Block\Adminhtml\Category\Edit\Section\Products</class> - <selector>//div[contains(@class,'admin__collapsible-block-wrapper')][descendant::div[@id='catalog_category_products']]</selector> - <strategy>xpath</strategy> + <selector>[data-index="assign_products"]</selector> + <strategy>css selector</strategy> </category_products> <design> <class>\Magento\Ui\Test\Block\Adminhtml\Section</class> - <selector>//div[contains(@class,'admin__collapsible-block-wrapper')][descendant::select[@name='page_layout']]</selector> - <strategy>xpath</strategy> + <selector>[data-index="design"]</selector> + <strategy>css selector</strategy> <fields> <use_parent_category_settings> <input>checkbox</input> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertAddedProductAttributeOnProductForm.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertAddedProductAttributeOnProductForm.php index 78e28abebe44bfd8f009fa8aee49f0d77e6c974a..54bf06f70b5f1c0f192b79bda3ade75a85cd37f7 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertAddedProductAttributeOnProductForm.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertAddedProductAttributeOnProductForm.php @@ -92,9 +92,8 @@ class AssertAddedProductAttributeOnProductForm extends AbstractConstraint $catalogProductAttribute = ($productAttributeOriginal !== null) ? array_merge($productAttributeOriginal->getData(), $attribute->getData()) : $attribute->getData(); - if ($catalogProductEdit->getProductForm()->isSectionVisible(self::ATTRIBUTES)) { - $catalogProductEdit->getProductForm()->openSection(self::ATTRIBUTES); - } + $catalogProductEdit->getProductForm()->openSection(self::ATTRIBUTES); + \PHPUnit_Framework_Assert::assertTrue( $catalogProductEdit->getProductForm()->checkAttributeLabel($catalogProductAttribute), "Product Attribute is absent on Product form." diff --git a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Block/Adminhtml/Promo/Catalog/Edit/PromoForm.xml b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Block/Adminhtml/Promo/Catalog/Edit/PromoForm.xml index 25e8617faf11292f03e40461bb146cbd6a1779c1..4474370eebbc75d27383be14700bdbb6f47d90c4 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Block/Adminhtml/Promo/Catalog/Edit/PromoForm.xml +++ b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Block/Adminhtml/Promo/Catalog/Edit/PromoForm.xml @@ -8,8 +8,8 @@ <sections> <rule_information> <class>\Magento\CatalogRule\Test\Block\Adminhtml\Promo\Catalog\Edit\Section\RuleInformation</class> - <selector>//div[contains(@class,'admin__collapsible-block-wrapper')][descendant::input[@name='name']]</selector> - <strategy>xpath</strategy> + <selector>[data-index="rule_information"]</selector> + <strategy>css selector</strategy> <fields> <is_active> <input>select</input> @@ -26,19 +26,20 @@ </rule_information> <conditions> <class>\Magento\CatalogRule\Test\Block\Adminhtml\Promo\Catalog\Edit\Section\Conditions</class> - <selector>//div[contains(@class,'admin__collapsible-block-wrapper')][descendant::div[@class='rule-tree']]</selector> - <strategy>xpath</strategy> + <selector>[data-index="block_promo_catalog_edit_tab_conditions"]</selector> + <strategy>css selector</strategy> <fields> <conditions> <selector>[id^="catalog_rule_formrule_conditions_fieldset_"]</selector> + <strategy>css selector</strategy> <input>conditions</input> </conditions> </fields> </conditions> <actions> <class>\Magento\Ui\Test\Block\Adminhtml\Section</class> - <selector>//div[contains(@class,'admin__collapsible-block-wrapper')][descendant::select[@name='simple_action']]</selector> - <strategy>xpath</strategy> + <selector>[data-index="actions"]</selector> + <strategy>css selector</strategy> <fields> <simple_action> <input>select</input> diff --git a/dev/tests/functional/tests/app/Magento/SalesRule/Test/Block/Adminhtml/Promo/Quote/Edit/PromoQuoteForm.xml b/dev/tests/functional/tests/app/Magento/SalesRule/Test/Block/Adminhtml/Promo/Quote/Edit/PromoQuoteForm.xml index cc48c0fc1cfb54ee51bc4e8ddffed12df210348e..6dcb041efa0703e86dcb2de2fbf38e1456600775 100644 --- a/dev/tests/functional/tests/app/Magento/SalesRule/Test/Block/Adminhtml/Promo/Quote/Edit/PromoQuoteForm.xml +++ b/dev/tests/functional/tests/app/Magento/SalesRule/Test/Block/Adminhtml/Promo/Quote/Edit/PromoQuoteForm.xml @@ -40,8 +40,8 @@ </rule_information> <conditions> <class>\Magento\Ui\Test\Block\Adminhtml\Section</class> - <selector>//div[contains(@class,'admin__collapsible-block-wrapper')][descendant::div[@class='rule-tree']]</selector> - <strategy>xpath</strategy> + <selector>[data-index="conditions"]</selector> + <strategy>css selector</strategy> <fields> <conditions_serialized> <selector>.fieldset[id^="sales_rule_formrule_conditions_fieldset_"]</selector> @@ -51,8 +51,8 @@ </conditions> <actions> <class>\Magento\Ui\Test\Block\Adminhtml\Section</class> - <selector>//div[contains(@class,'admin__collapsible-block-wrapper')][descendant::select[@name='simple_action']]</selector> - <strategy>xpath</strategy> + <selector>[data-index="actions"]</selector> + <strategy>css selector</strategy> <fields> <simple_action> <input>select</input> @@ -77,7 +77,7 @@ </actions> <labels> <class>\Magento\SalesRule\Test\Block\Adminhtml\Promo\Quote\Edit\Section\Labels</class> - <selector>//div[contains(@class,'admin__collapsible-block-wrapper')][descendant::input[@name='store_labels[0]']]</selector> - <strategy>xpath</strategy> + <selector>[data-index="labels"]</selector> + <strategy>css selector</strategy> </labels> </sections> diff --git a/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/FormSections.php b/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/FormSections.php index 6a5919b8cb1cfcfc2c8a666009e977ab0baafe0b..b5edbc6c791939cbf8f7d2e3b54ded1b22add3d7 100644 --- a/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/FormSections.php +++ b/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/FormSections.php @@ -6,50 +6,26 @@ namespace Magento\Ui\Test\Block\Adminhtml; -use Magento\Mtf\Client\Locator; -use Magento\Mtf\Client\ElementInterface; use Magento\Mtf\Fixture\InjectableFixture; /** - * Is used to represent a new unified form with collapsible sections on the page. + * Is used to represent a new unified form with collapsible sections inside. */ class FormSections extends AbstractFormContainers { /** - * CSS locator of the section collapsible title + * CSS locator of collapsed section. * * @var string */ - protected $sectionTitle = '.fieldset-wrapper-title'; + protected $collapsedSection = '[data-state-collapsible="closed"]'; /** - * CSS locator of the section content + * CSS locator of expanded section. * * @var string */ - protected $content = '.admin__fieldset-wrapper-content'; - - /** - * XPath locator of the collapsible fieldset - * - * @var string - */ - protected $collapsible = - 'div[contains(@class,"fieldset-wrapper")][contains(@class,"admin__collapsible-block-wrapper")]'; - - /** - * Locator for opened collapsible tab. - * - * @var string - */ - protected $opened = '._show'; - - /** - * Locator for error messages. - * - * @var string - */ - protected $errorMessages = '[data-ui-id="messages-message-error"]'; + protected $expandedSection = '[data-state-collapsible="open"]'; /** * Get Section class. @@ -72,58 +48,39 @@ class FormSections extends AbstractFormContainers } /** - * Get the section title element - * - * @param string $sectionName - * @return ElementInterface - */ - protected function getSectionTitleElement($sectionName) - { - $container = $this->getContainerElement($sectionName); - return $container->find($this->sectionTitle); - } - - /** - * Opens the section. + * Expand section by its name. * * @param string $sectionName * @return $this */ public function openSection($sectionName) { - if ($this->isCollapsible($sectionName) && !$this->isCollapsed($sectionName)) { - $this->getSectionTitleElement($sectionName)->click(); - } else { - //Scroll to the top of the page so that the page actions header does not overlap any controls - $this->browser->find($this->header)->hover(); + $section = $this->getContainerElement($sectionName)->find($this->collapsedSection); + if ($section->isVisible()) { + $section->click(); } + return $this; } /** - * Checks if the section is collapsible on the form. + * Check if section is collapsible. * + * @deprecated * @param string $sectionName * @return bool */ public function isCollapsible($sectionName) { - $title = $this->getSectionTitleElement($sectionName); - if (!$title->isVisible()) { - return false; - }; - return $title->find('parent::' . $this->collapsible, Locator::SELECTOR_XPATH)->isVisible(); - } + $section = $this->getContainerElement($sectionName); - /** - * Check if collapsible section is opened. - * - * @param string $sectionName - * @return bool - */ - private function isCollapsed($sectionName) - { - return $this->getContainerElement($sectionName)->find($this->opened)->isVisible(); + if ($section->find($this->collapsedSection)->isVisible()) { + return true; + } elseif ($section->find($this->expandedSection)->isVisible()) { + return true; + } else { + return false; + } } /** @@ -151,11 +108,12 @@ class FormSections extends AbstractFormContainers /** * Check if section is visible. * + * @deprecated * @param string $sectionName * @return bool */ public function isSectionVisible($sectionName) { - return ($this->isCollapsible($sectionName) && !$this->isCollapsed($sectionName)); + return !$this->getContainerElement($sectionName)->find($this->collapsedSection)->isVisible(); } }