From 33a63734da6b091323f08ebf507f9c60e5eb7ea4 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Tue, 17 May 2016 12:52:55 +0200 Subject: [PATCH] Add PropertyStackView tests --- src/demo.js | 3 +- src/style_manager/view/LayerView.js | 2 +- .../view/PropertyCompositeView.js | 60 ++-- src/style_manager/view/PropertyIntegerView.js | 6 +- src/style_manager/view/PropertyStackView.js | 68 +++-- src/style_manager/view/PropertyView.js | 38 ++- src/utils/Sorter.js | 5 +- test/specs/style_manager/main.js | 3 + .../style_manager/view/PropertyStackView.js | 282 ++++++++++++++++++ test/specs/style_manager/view/PropertyView.js | 14 + 10 files changed, 417 insertions(+), 64 deletions(-) create mode 100644 test/specs/style_manager/view/PropertyStackView.js diff --git a/src/demo.js b/src/demo.js index ce7f1a261..abe7cfd45 100644 --- a/src/demo.js +++ b/src/demo.js @@ -500,7 +500,7 @@ require(['config/require-config'], function() { min : 0, },{ name : 'Left', - property : 'padding-Left', + property : 'padding-left', type : 'integer', units : ['px','%'], defaults : 0, @@ -619,6 +619,7 @@ require(['config/require-config'], function() { name : 'Text shadow', property : 'text-shadow', type : 'stack', + detached : true, preview : true, properties : [{ name: 'X position', diff --git a/src/style_manager/view/LayerView.js b/src/style_manager/view/LayerView.js index 79c8de7a1..a082cd1dd 100644 --- a/src/style_manager/view/LayerView.js +++ b/src/style_manager/view/LayerView.js @@ -127,7 +127,7 @@ define(['backbone', 'text!./../templates/layer.html'], this.model.collection.remove(this.model); if(this.stackModel && this.stackModel.set){ - this.stackModel.trigger('refreshValue'); + this.stackModel.trigger('updateValue'); this.stackModel.set({stackIndex: null},{silent: true}); } }, diff --git a/src/style_manager/view/PropertyCompositeView.js b/src/style_manager/view/PropertyCompositeView.js index 083b737c1..048b30d18 100644 --- a/src/style_manager/view/PropertyCompositeView.js +++ b/src/style_manager/view/PropertyCompositeView.js @@ -48,34 +48,45 @@ define(['backbone','./PropertyView', 'text!./../templates/propertyComposite.html }, this); var PropertiesView = require('./PropertiesView'); - var that = this; - - var propsViewOpts = { - config: this.config, - collection: this.props, - target: this.target, - propTarget: this.propTarget, - // Each child property will receive a full composite string, eg. '0px 0px 10px 0px' - // I need to extract from that string the corresponding one to that property. - customValue: function(property, mIndex){ - return that.valueOnIndex(mIndex, property.model); - }, - }; - - // On any change made to children I need to update composite value - if(!detached) - propsViewOpts.onChange = function(el, model){ - var result = that.build(el, model); - that.model.set('value', result); - }; - - var propsView = new PropertiesView(propsViewOpts); + var propsView = new PropertiesView(this.getPropsConfig()); this.$props = propsView.render().$el; this.$el.find('#'+ this.pfx +'input-holder').html(this.$props); } } }, + /** + * Returns configurations that should be past to properties + * @param {Object} opts + * @return {Object} + */ + getPropsConfig: function(opts){ + var that = this; + + result = { + config: this.config, + collection: this.props, + target: this.target, + propTarget: this.propTarget, + // On any change made to children I need to update composite value + onChange: function(el, view, opts){ + var result = that.build(); + that.model.set('value', result); + }, + // Each child property will receive a full composite string, eg. '0px 0px 10px 0px' + // I need to extract from that string the corresponding one to that property. + customValue: function(property, mIndex){ + return that.valueOnIndex(mIndex, property.model); + }, + }; + + // If detached let follow its standard flow + if(this.model.get('detached')) + delete result.onChange; + + return result; + }, + /** * Get default value of the property * @return {string} @@ -111,10 +122,11 @@ define(['backbone','./PropertyView', 'text!./../templates/propertyComposite.html /** * Build composite value * @param {Object} selectedEl Selected element - * @param {Object} propertyModel Property model + * @param {Object} propertyView Property view + * @param {Object} opts Options * @return {string} * */ - build: function(selectedEl, propertyModel){ + build: function(selectedEl, propertyView, opts){ var result = ''; this.model.get('properties').each(function(prop){ var v = prop.getValue(); diff --git a/src/style_manager/view/PropertyIntegerView.js b/src/style_manager/view/PropertyIntegerView.js index 3d325def9..c0f904bba 100644 --- a/src/style_manager/view/PropertyIntegerView.js +++ b/src/style_manager/view/PropertyIntegerView.js @@ -63,7 +63,8 @@ define(['backbone','./PropertyView', 'text!./../templates/propertyInteger.html'] upArrowClick: function(e){ var value = this.model.get('value'); value = isNaN(value) ? 1 : parseInt(value,10) + 1; - value = value > this.max ? this.max : value; + if(this.max !== null) + value = value > this.max ? this.max : value; this.model.set('value',value); }, @@ -76,7 +77,8 @@ define(['backbone','./PropertyView', 'text!./../templates/propertyInteger.html'] downArrowClick: function(e){ var value = this.model.get('value'); value = isNaN(value) ? 0 : parseInt(value,10) - 1; - value = value < this.min ? this.min : value; + if(this.min !== null) + value = value < this.min ? this.min : value; this.model.set('value',value); }, diff --git a/src/style_manager/view/PropertyStackView.js b/src/style_manager/view/PropertyStackView.js index f80764737..c439d402a 100644 --- a/src/style_manager/view/PropertyStackView.js +++ b/src/style_manager/view/PropertyStackView.js @@ -11,7 +11,7 @@ define(['backbone','./PropertyCompositeView', 'text!./../templates/propertyStack PropertyCompositeView.prototype.initialize.apply(this, arguments); this.model.set('stackIndex', null); this.listenTo( this.model ,'change:stackIndex', this.indexChanged); - this.listenTo( this.model ,'refreshValue', this.refreshValue); + this.listenTo( this.model ,'updateValue', this.valueUpdated); this.className = this.pfx + 'property '+ this.pfx +'stack'; this.events['click #'+this.pfx+'add'] = 'addLayer'; @@ -27,7 +27,9 @@ define(['backbone','./PropertyCompositeView', 'text!./../templates/propertyStack }, /** - * Triggered when another layer has been selected + * Triggered when another layer has been selected. + * This allow to move all rendered properties to a new + * selected layer * @param {Event} * * @return {Object} @@ -35,26 +37,43 @@ define(['backbone','./PropertyCompositeView', 'text!./../templates/propertyStack indexChanged: function(e){ var layer = this.getLayers().at(this.model.get('stackIndex')); layer.set('props', this.$props); - this.target.trigger('change:selectedComponent');//TODO replace with getTarget }, /** * Get array of values from layers - * TODO replace with pluck * @return Array * */ getStackValues: function(){ - var a = []; - this.getLayers().each(function(layer){ - a.push( layer.get('value') ); - }); - return a; + return this.getLayers().pluck('value'); + }, + + /** @inheritDoc */ + getPropsConfig: function(opts){ + var that = this; + var result = PropertyCompositeView.prototype.getPropsConfig.apply(this, arguments); + result.onChange = function(el, view, opt){ + var model = view.model; + // This will update the layer value + var result = that.build(el, model); + + if(that.model.get('detached')){ + var propVal = ''; + var index = model.collection.indexOf(model); + that.getLayers().each(function(layer){ + var vals = layer.get('value').split(' '); + if(vals.length && vals[index]) + propVal += (propVal ? ',' : '') + vals[index]; + }); + view.updateTargetStyle(propVal, null, opt); + }else + that.model.set('value', result); + }; + return result; }, /** * Extract string from composite value * @param integer Index - * TODO missing valueOnIndex * @return string * */ valueOnIndex: function(index){ @@ -72,11 +91,12 @@ define(['backbone','./PropertyCompositeView', 'text!./../templates/propertyStack }, /** @inheritdoc */ - build: function(selectedEl, propertyModel){ - if(this.model.get('stackIndex') === null) + build: function(){ + var stackIndex = this.model.get('stackIndex'); + if(stackIndex === null) return; var result = PropertyCompositeView.prototype.build.apply(this, arguments); - var model = this.getLayers().at(this.model.get('stackIndex')); + var model = this.getLayers().at(stackIndex); if(!model) return; model.set('value',result); @@ -90,24 +110,28 @@ define(['backbone','./PropertyCompositeView', 'text!./../templates/propertyStack * @return Object * */ addLayer: function(e){ - if(this.selectedComponent){ + if(this.getTarget()){ var layers = this.getLayers(); var layer = layers.add({ name : 'test' }); var index = layers.indexOf(layer); layer.set('value', this.getDefaultValue()); - this.refreshValue(); + this.valueUpdated(); this.model.set('stackIndex', index); return layer; } }, /** - * Refresh value - * - * @return void - * */ - refreshValue: function(){ - this.model.set('value', this.createValue()); + * Fired when the input value is updated + */ + valueUpdated: function(){ + if(!this.model.get('detached')) + this.model.set('value', this.createValue()); + else{ + this.model.get('properties').each(function(prop){ + prop.trigger('change:value'); + }); + } }, /** @@ -166,7 +190,7 @@ define(['backbone','./PropertyCompositeView', 'text!./../templates/propertyStack var layers = this.getLayers(); layers.reset(); layers.add(n); - this.refreshValue(); + this.valueUpdated(); this.model.set({stackIndex: null}, {silent: true}); }, diff --git a/src/style_manager/view/PropertyView.js b/src/style_manager/view/PropertyView.js index d00f166c3..3435ff358 100644 --- a/src/style_manager/view/PropertyView.js +++ b/src/style_manager/view/PropertyView.js @@ -141,8 +141,7 @@ define(['backbone', 'text!./../templates/propertyLabel.html', 'text!./../templat * @param {Object} opt Options * */ valueChanged: function(e, val, opt){ - var mVal = this.getValueForTarget(), - avSt = opt ? opt.avoidStore : 0; + var mVal = this.getValueForTarget(); if(this.$input) this.setValue(mVal); @@ -164,20 +163,33 @@ define(['backbone', 'text!./../templates/propertyLabel.html', 'text!./../templat var onChange = this.onChange; if(onChange && typeof onChange === "function"){ - onChange(target, this.model); - }else{ - var componentCss = _.clone( target.get('style') ); + onChange(target, this, opt); + }else + this.updateTargetStyle(value, null, opt); + }, - if(value) - componentCss[this.property] = value; - else - delete componentCss[this.property]; + /** + * Update target style + * @param {string} propertyValue + * @param {string} propertyName + * @param {Object} opts + */ + updateTargetStyle: function(propertyValue, propertyName, opts){ + var propName = propertyName || this.property; + var value = propertyValue || ''; + var avSt = opts ? opts.avoidStore : 0; + var target = this.getTarget(); + var targetStyle = _.clone(target.get('style')); - target.set('style', componentCss, { avoidStore : avSt}); + if(value) + targetStyle[propName] = value; + else + delete targetStyle[propName]; - if(this.helperComponent) - this.helperComponent.set('style', componentCss, { avoidStore : avSt}); - } + target.set('style', targetStyle, { avoidStore : avSt}); + + if(this.helperComponent) + this.helperComponent.set('style', targetStyle, { avoidStore : avSt}); }, /** diff --git a/src/utils/Sorter.js b/src/utils/Sorter.js index 099b3e3d2..c4e51ffa5 100644 --- a/src/utils/Sorter.js +++ b/src/utils/Sorter.js @@ -9,7 +9,7 @@ define(['backbone'], this.elT = 0; this.elL = 0; this.borderOffset = o.borderOffset || 10; - this.freezeClass = o.freezeClass || 'freezed'; + this.freezeClass = o.freezeClass || 'opac50'; var el = o.container; this.el = typeof el === 'string' ? document.querySelector(o.container) : el; @@ -98,6 +98,7 @@ define(['backbone'], this.el.appendChild(this.plh); } + this.eV.className += ' ' + this.freezeClass; this.$el.on('mousemove',this.onMove); $(document).on('mouseup',this.endMove); $(document).on('keypress',this.rollback); @@ -350,6 +351,8 @@ define(['backbone'], $(document).off('mouseup', this.endMove); $(document).off('keypress', this.rollback); this.plh.style.display = 'none'; + var clsReg = new RegExp('(?:^|\\s)'+this.freezeClass+'(?!\\S)', 'gi'); + this.eV.className = this.eV.className.replace(clsReg, ''); if(this.moved) this.move(this.target, this.eV, this.lastPos); }, diff --git a/test/specs/style_manager/main.js b/test/specs/style_manager/main.js index 223b86122..7f0ce2545 100644 --- a/test/specs/style_manager/main.js +++ b/test/specs/style_manager/main.js @@ -11,6 +11,7 @@ define([ modulePath + '/view/PropertyIntegerView', modulePath + '/view/PropertyColorView', modulePath + '/view/PropertyCompositeView', + modulePath + '/view/PropertyStackView', modulePath + '/view/LayerView', ], function( @@ -24,6 +25,7 @@ define([ PropertyIntegerView, PropertyColorView, PropertyCompositeView, + PropertyStackView, LayerView ) { @@ -199,6 +201,7 @@ define([ PropertyIntegerView.run(); PropertyColorView.run(); PropertyCompositeView.run(); + PropertyStackView.run(); LayerView.run(); }); diff --git a/test/specs/style_manager/view/PropertyStackView.js b/test/specs/style_manager/view/PropertyStackView.js new file mode 100644 index 000000000..9d9573f56 --- /dev/null +++ b/test/specs/style_manager/view/PropertyStackView.js @@ -0,0 +1,282 @@ +var path = 'StyleManager/view/'; +define([path + 'PropertyStackView', 'StyleManager/model/Property', 'DomComponents/model/Component'], + function(PropertyStackView, Property, Component) { + + return { + run : function(){ + + describe('PropertyStackView', function() { + + var component; + var $fixtures; + var $fixture; + var target; + var model; + var view; + var propName = 'testprop'; + var propValue = 'test1value'; + var defValue = 'test2value'; + var layers = [ + {value: 'lval1'}, + {value: 'lval2 lval22'}, + {value: 'lval3 lval32 lval33'} + ]; + var properties = [ + {property: 'subprop1'}, + { + type: 'integer', + property: 'subprop2', + defaults: 0, + units: ['%', 'px'] + }, + { + type: 'select', + property: 'subprop3', + defaults: 'val2', + list: [ + {value:'val1'}, + {value:'val2'}, + {value:'val3'}, + ] + }, + ]; + + before(function () { + $fixtures = $("#fixtures"); + $fixture = $('
'); + }); + + beforeEach(function () { + target = new Component(); + component = new Component(); + target.model = component; + model = new Property({ + type: 'stack', + property: propName, + properties: properties + }); + view = new PropertyStackView({ + model: model + }); + $fixture.empty().appendTo($fixtures); + $fixture.html(view.render().el); + }); + + afterEach(function () { + //view.remove(); // strange errors ??? + }); + + after(function () { + $fixture.remove(); + delete component; + delete view; + delete model; + }); + + it('Rendered correctly', function() { + var prop = view.el; + $fixture.get(0).querySelector('.property').should.be.ok; + prop.querySelector('.label').should.be.ok; + prop.querySelector('.field').should.be.ok; + prop.querySelector('#add').should.be.ok; + }); + + it('Layers rendered', function() { + view.el.querySelector('.layers').should.be.ok; + }); + + it('Layers should exist', function() { + view.$props.should.be.ok; + }); + + it('Layers rendered correctly', function() { + var children = view.$props.get(0).children; + children.length.should.equal(properties.length + 1); + children[0].id.should.equal(properties[0].property); + children[1].id.should.equal(properties[1].property); + children[2].id.should.equal(properties[2].property); + }); + + it('Input value is empty', function() { + view.model.get('value').should.be.empty; + }); + + it('Layers container is empty', function() { + var layers = view.el.querySelector('.layers'); + layers.innerHTML.should.be.empty; + }); + + describe('With layers', function() { + + beforeEach(function () { + model = new Property({ + type: 'stack', + property: propName, + properties: properties, + }); + view = new PropertyStackView({ + model: model, + propTarget: target + }); + $fixture.empty().appendTo($fixtures); + $fixture.html(view.render().el); + model.get('layers').add(layers); + }); + + it('Layers inserted', function() { + view.getLayers().length.should.equal(layers.length); + }); + + it('Get value on index', function() { + view.model.set('stackIndex', 1); + view.valueOnIndex(1).should.equal('lval22'); + }); + + it('createValue merges layers', function() { + view.createValue().should.equal('lval1, lval2 lval22, lval3 lval32 lval33'); + }); + + it('Add layer', function() { + view.addLayer(); + view.getLayers().length.should.equal(layers.length+1); + }); + + }); + + describe('With target setted', function() { + + var prop2Val; + var prop3Val; + var prop2Unit; + var finalResult; + var $prop1; + var $prop2; + var $prop3; + + beforeEach(function () { + model = new Property({ + type: 'stack', + property: propName, + properties: properties + }); + view = new PropertyStackView({ + model: model, + propTarget: target + }); + $fixture.empty().appendTo($fixtures); + $fixture.html(view.render().el); + prop3Val = properties[2].list[2].value; + prop2Val = properties[1].defaults; + prop2Unit = properties[1].units[0]; + finalResult = propValue + ' ' + prop2Val + prop2Unit +' ' + prop3Val; + $prop1 = view.$props.find('#' + properties[0].property + ' input'); + $prop2 = view.$props.find('#' + properties[1].property + ' input'); + $prop3 = view.$props.find('#' + properties[2].property + ' select'); + }); + + it('Update model on input change', function() { + $prop1.val(propValue).trigger('change'); + $prop3.val(prop3Val).trigger('change'); + view.model.get('value').should.equal(finalResult); + }); + + it('Update value on models change', function() { + view.model.get('properties').at(0).set('value', propValue); + view.model.get('properties').at(2).set('value', prop3Val); + view.model.get('value').should.equal(finalResult); + }); + + it('Update target on value change', function() { + $prop1.val(propValue).trigger('change'); + var compStyle = view.getTarget().get('style'); + var assertStyle = {}; + assertStyle[propName] = propValue + ' 0% val2'; + compStyle.should.deep.equal(assertStyle); + }); + + it('Update target on detached value change', function() { + model = new Property({ + type: 'stack', + property: propName, + properties: properties, + detached: true, + }); + view = new PropertyStackView({ + model: model, + propTarget: target + }); + $fixture.html(view.render().el); + $prop1 = view.$props.find('#' + properties[0].property + ' input'); + $prop1.val(propValue).trigger('change'); + var compStyle = view.getTarget().get('style'); + var assertStyle = {}; + assertStyle[properties[0].property] = $prop1.val(); + compStyle.should.deep.equal(assertStyle); + }); + + it('Update value and input on target swap', function() { + var style = {}; + style[propName] = finalResult; + component.set('style', style); + view.propTarget.trigger('update'); + $prop1.val().should.equal(propValue); + $prop3.val().should.equal(prop3Val); + }); + + it('Update value after multiple swaps', function() { + var style = {}; + style[propName] = finalResult; + component.set('style', style); + view.propTarget.trigger('update'); + style[propName] = propValue + '2 ' + prop2Val + '2' + prop2Unit + ' ' + 'val1'; + component.set('style', style); + view.propTarget.trigger('update'); + $prop1.val().should.equal(propValue + '2'); + $prop2.val().should.equal('2'); + $prop3.val().should.equal('val1'); + }); + + it('The value is correctly extracted from the composite string', function() { + var style = {}; + style[propName] = 'value1 value2 value3 value4'; + component.set('style', style); + view.valueOnIndex(2).should.equal('value3'); + view.valueOnIndex(0).should.equal('value1'); + (view.valueOnIndex(4) === null).should.equal(true); + }); + + it('Build value from properties', function() { + view.model.get('properties').at(0).set('value', propValue); + view.model.get('properties').at(2).set('value', prop3Val); + view.build().should.equal(finalResult); + }); + + }) + + describe('Init property', function() { + + beforeEach(function () { + model = new Property({ + type: 'stack', + property: propName, + properties: properties, + defaults: defValue, + }); + view = new PropertyStackView({ + model: model + }); + $fixture.empty().appendTo($fixtures); + $fixture.html(view.render().el); + }); + + it('Value as default', function() { + view.model.get('value').should.equal(defValue); + }); + + }); + + }); + } + }; + +}); \ No newline at end of file diff --git a/test/specs/style_manager/view/PropertyView.js b/test/specs/style_manager/view/PropertyView.js index 4448a68af..43c86f494 100644 --- a/test/specs/style_manager/view/PropertyView.js +++ b/test/specs/style_manager/view/PropertyView.js @@ -163,6 +163,20 @@ define([path + 'PropertyView', 'StyleManager/model/Property', 'DomComponents/mod $fixture.html(view.render().el); }); + it('updateTargetStyle', function() { + view.updateTargetStyle(propValue); + var style = {}; + style[propName] = propValue; + component.get('style').should.deep.equal(style); + }); + + it('updateTargetStyle with custom property', function() { + view.updateTargetStyle(propValue, propName + '2'); + var style = {}; + style[propName + '2'] = propValue; + component.get('style').should.deep.equal(style); + }); + it('Update value and input on target swap', function() { var style = {}; style[propName] = propValue;