diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/Backend/ArrayBackend.php b/app/code/Magento/Eav/Model/Entity/Attribute/Backend/ArrayBackend.php index 47a998a95ecb5..c387631e693cd 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/Backend/ArrayBackend.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/Backend/ArrayBackend.php @@ -42,6 +42,8 @@ public function validate($object) $data = $object->getData($attributeCode); if (is_array($data)) { $object->setData($attributeCode, implode(',', array_filter($data))); + } elseif (empty($data)) { + $object->setData($attributeCode, null); } return parent::validate($object); } diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Backend/ArrayTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Backend/ArrayBackendTest.php similarity index 90% rename from app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Backend/ArrayTest.php rename to app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Backend/ArrayBackendTest.php index 24969add7a3ad..e454f1f22f7bb 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Backend/ArrayTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Backend/ArrayBackendTest.php @@ -5,7 +5,7 @@ */ namespace Magento\Eav\Test\Unit\Model\Entity\Attribute\Backend; -class ArrayTest extends \PHPUnit_Framework_TestCase +class ArrayBackendTest extends \PHPUnit_Framework_TestCase { /** * @var \Magento\Eav\Model\Entity\Attribute\Backend\ArrayBackend @@ -37,9 +37,10 @@ protected function setUp() public function testValidate($data) { $this->_attribute->expects($this->atLeastOnce())->method('getAttributeCode')->will($this->returnValue('code')); - $product = new \Magento\Framework\DataObject(['code' => $data]); + $product = new \Magento\Framework\DataObject(['code' => $data, 'empty' => '']); $this->_model->validate($product); $this->assertEquals('1,2,3', $product->getCode()); + $this->assertEquals(null, $product->getEmpty()); } public static function attributeValueDataProvider() diff --git a/app/code/Magento/Ui/view/base/web/js/form/client.js b/app/code/Magento/Ui/view/base/web/js/form/client.js index 6b4c1a3262273..1007c7bbd2b36 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/client.js +++ b/app/code/Magento/Ui/view/base/web/js/form/client.js @@ -22,8 +22,7 @@ define([ function beforeSave(data, url, selectorPrefix, messagesClass) { var save = $.Deferred(); - data = utils.serialize(data); - + data = utils.serialize(utils.filterFormData(data)); data['form_key'] = window.FORM_KEY; if (!url || url === 'undefined') { diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/multiselect.js b/app/code/Magento/Ui/view/base/web/js/form/element/multiselect.js index 37b389dcba027..cea94114990cf 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/multiselect.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/multiselect.js @@ -13,7 +13,10 @@ define([ return Select.extend({ defaults: { size: 5, - elementTmpl: 'ui/form/element/multiselect' + elementTmpl: 'ui/form/element/multiselect', + listens: { + value: 'setDifferedFromDefault setPrepareToSendData' + } }, /** @@ -38,6 +41,21 @@ define([ return _.isString(value) ? value.split(',') : value; }, + /** + * Sets the prepared data to dataSource + * by path, where key is component link to dataSource with + * suffix "-prepared-for-send" + * + * @param {Array} data - current component value + */ + setPrepareToSendData: function (data) { + if (!data.length) { + data = ''; + } + + this.source.set(this.dataScope + '-prepared-for-send', data); + }, + /** * @inheritdoc */ diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/client.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/client.test.js index 8a14feccd6910..4b3bd1d51104d 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/client.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/client.test.js @@ -10,18 +10,22 @@ define([ 'underscore', 'uiRegistry', - 'Magento_Ui/js/form/client' -], function (_, registry, Constr) { + 'Magento_Ui/js/form/client', + 'jquery', + 'mageUtils', + 'jquery/ui' +], function (_, registry, Constr, $, utils) { 'use strict'; describe('Magento_Ui/js/form/client', function () { - var obj = new Constr({ provider: 'provName', name: '', index: '' }); + window.FORM_KEY = 'magentoFormKey'; + registry.set('provName', { on: function () { }, @@ -50,7 +54,98 @@ define([ expect(type).toEqual('object'); }); + it('Check "beforeSave" method. ' + + 'Check calls "filterFormData", "serialize" and "ajax" inside themselves.', function () { + var data = { + key: { + anotherKey: 'value' + }, + anotherKey: [] + }, + params; + + obj.urls.beforeSave = 'requestPath'; + obj.selectorPrefix = 'selectorPrefix'; + obj.messagesClass = 'messagesClass'; + + params = { + url: obj.urls.beforeSave, + data: _.extend(data, { + form_key: 'magentoFormKey' + }), + success: jasmine.any(Function), + complete: jasmine.any(Function) + }; + + utils.filterFormData = jasmine.createSpy().and.returnValue(data); + utils.serialize = jasmine.createSpy().and.returnValue(data); + + $.ajax = jasmine.createSpy(); + obj.save(data); + expect(utils.filterFormData).toHaveBeenCalledWith(data); + expect(utils.serialize).toHaveBeenCalledWith(data); + expect($.ajax).toHaveBeenCalledWith(params); + + }); + it('Check call "beforeSave" method without parameters', function () { + $.ajax = jasmine.createSpy(); + obj.urls.beforeSave = null; + obj.save(); + + expect($.ajax).not.toHaveBeenCalled(); + }); + it('Check call "beforeSave" method. Check "success" ajax callback with success response.' , function () { + var request; + + $.ajax = jasmine.createSpy().and.callFake(function (req) { + request = req.success; + }); + $.fn.notification = jasmine.createSpy(); + obj.urls.beforeSave = 'requestPath'; + obj.save(); + + expect(request({error: false})).toBe(true); + expect($('body').notification).not.toHaveBeenCalledWith('clear'); + }); + + it('Check call "beforeSave" method. Check "success" ajax callback with error response.' , function () { + var request, + notificationArguments; + + $.ajax = jasmine.createSpy().and.callFake(function (req) { + request = req.success; + }); + $.fn.notification = jasmine.createSpy(); + obj.urls.beforeSave = 'requestPath'; + obj.save(); + + notificationArguments = { + error: true, + message: 1, + insertMethod: jasmine.any(Function) + }; + + expect(request({ + error: true, + messages: [1] + })).toBeUndefined(); + expect($('body').notification.calls.allArgs()).toEqual([['clear'], ['add', notificationArguments]]); + }); + it('Check call "beforeSave" method. Check "complete" ajax callback.' , function () { + var request; + + $.ajax = jasmine.createSpy().and.callFake(function (req) { + request = req.complete; + }); + $.fn.trigger = jasmine.createSpy(); + obj.urls.beforeSave = 'requestPath'; + obj.save(); + + expect(request()).toBeUndefined(); + expect($('body').trigger).toHaveBeenCalledWith('processStop'); + }); }); + describe('"initialize" method', function () { it('Check for defined ', function () { expect(obj.hasOwnProperty('initialize')).toBeDefined(); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/multiselect.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/multiselect.test.js new file mode 100644 index 0000000000000..c0f4b345509d0 --- /dev/null +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/multiselect.test.js @@ -0,0 +1,60 @@ +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/* eslint max-nested-callbacks: 0 */ +define([ + 'squire' +], function (Squire) { + 'use strict'; + + var injector = new Squire(), + mocks = { + 'Magento_Ui/js/lib/core/events': { + on: jasmine.createSpy() + }, + 'Magento_Ui/js/lib/registry/registry': { + get: function() { + return { + get: jasmine.createSpy(), + set: jasmine.createSpy() + }; + }, + create: jasmine.createSpy(), + set: jasmine.createSpy(), + async: jasmine.createSpy() + }, + '/mage/utils/wrapper': jasmine.createSpy() + }, + obj, + dataScope = 'dataScope'; + + beforeEach(function (done) { + injector.mock(mocks); + injector.require(['Magento_Ui/js/form/element/multiselect'], function (Constr) { + obj = new Constr({ + provider: 'provName', + name: '', + index: '', + dataScope: dataScope + }); + + done(); + }); + }); + + describe('Magento_Ui/js/form/element/multiselect', function () { + describe('"setPrepareToSendData" method', function () { + it('Check method call with empty array as parameter.', function () { + expect(obj.setPrepareToSendData([])).toBeUndefined(); + expect(obj.source.set).toHaveBeenCalledWith(dataScope + '-prepared-for-send', ''); + }); + + it('Check method call with array with data as parameter.', function () { + expect(obj.setPrepareToSendData(['1', '2', '3'])).toBeUndefined(); + expect(obj.source.set).toHaveBeenCalledWith(dataScope + '-prepared-for-send', ['1', '2', '3']); + }); + }); + }); +}); diff --git a/dev/tests/js/jasmine/tests/lib/mage/misc.test.js b/dev/tests/js/jasmine/tests/lib/mage/misc.test.js index 02e94524aae41..170177b42ccb0 100644 --- a/dev/tests/js/jasmine/tests/lib/mage/misc.test.js +++ b/dev/tests/js/jasmine/tests/lib/mage/misc.test.js @@ -19,6 +19,18 @@ define([ expect(utils.convertToMomentFormat(format)).toBe(momentFormat); }); + it('Check "filterFormData" method', function () { + var suffix = 'prepared-for-send', + separator = '-', + data = { + key: 'value-prepared-before-save' + }; + expect(utils.filterFormData(data, suffix, separator)).toEqual(data); + expect(utils.filterFormData(data, suffix)).toEqual(data); + expect(utils.filterFormData(data)).toEqual(data); + expect(utils.filterFormData()).toEqual({}); + }); + it('Check convertToMomentFormat function for all Magento supported locales', function () { var fixture, diff --git a/lib/web/mage/utils/misc.js b/lib/web/mage/utils/misc.js index 576c2244ad0cd..0f04dab7e440a 100644 --- a/lib/web/mage/utils/misc.js +++ b/lib/web/mage/utils/misc.js @@ -209,6 +209,33 @@ define([ return formData; }, + /** + * Filters data object. Finds properties with suffix + * and sets their values to properties with the same name without suffix. + * + * @param {Object} data - The data object that should be filtered + * @param {String} suffix - The string by which data object should be filtered + * @param {String} separator - The string that is separator between property and suffix + * + * @returns {Object} Filtered data object + */ + filterFormData: function (data, suffix, separator) { + data = data || {}; + suffix = suffix || 'prepared-for-send'; + separator = separator || '-'; + + _.each(data, function (value, key) { + if (_.isObject(value) && !value.length) { + this.filterFormData(value, suffix, separator) + } else if (_.isString(key) && ~key.indexOf(suffix)) { + data[key.split(separator)[0]] = value; + delete data[key]; + } + }, this); + + return data; + }, + /** * Converts PHP IntlFormatter format to moment format. *