From 2743f81d9540c4cd28dc60b6ad194a87664f2753 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Wed, 27 Apr 2016 14:26:02 +0200 Subject: [PATCH] Add ComponentView tests --- src/dom_components/main.js | 165 ++++++++++++++---- src/dom_components/model/Component.js | 8 +- src/dom_components/model/ComponentImage.js | 12 +- src/dom_components/model/ComponentText.js | 12 +- src/dom_components/model/Components.js | 3 - src/dom_components/view/ComponentImageView.js | 5 +- src/dom_components/view/ComponentTextView.js | 7 +- src/dom_components/view/ComponentView.js | 15 +- src/dom_components/view/ComponentsView.js | 34 ++-- test/specs/dom_components/main.js | 38 +++- .../dom_components/view/componentView.js | 76 ++++++++ 11 files changed, 285 insertions(+), 90 deletions(-) diff --git a/src/dom_components/main.js b/src/dom_components/main.js index 02aee9d2f..c73170b5e 100644 --- a/src/dom_components/main.js +++ b/src/dom_components/main.js @@ -1,19 +1,57 @@ +/** + * + * - [getWrapper](#getwrapper) + * - [getComponents](#getcomponents) + * - [addComponent](#addcomponent) + * - [render](#render) + * + * With this module is possible to manage all the HTML structure inside the canvas + * You can init the editor with initial components via configuration + * + * ```js + * var editor = new GrapesJS({ + * ... + * components: {...} // Check below for the possible properties + * ... + * }); + * ``` + * + * + * Before using methods you should get first the module from the editor instance, in this way: + * + * ```js + * var ComponentsService = editor.get('Components'); + * ``` + * + * @module Components + * @param {Object} config Configurations + * @param {Array} [config.defaults=[]] Array of possible components + * @example + * ... + * components: { + * defaults: [ + * { + * style: { background: 'red'} + * components:[ + * {style: { background: 'blue'}}, + * {style: { background: 'green'}} + * ] + * } + * ], + * } + * ... + */ define(function(require) { - /** - * @class Components - * @param {Object} Configurations - * - * @return {Object} - * */ - function Components(config) - { - var c = config || {}, - defaults = require('./config/config'), - Component = require('./model/Component'), - ComponentText = require('./model/ComponentText'), - ComponentImage = require('./model/ComponentImage'), - ComponentView = require('./view/ComponentView'), - ComponentImageView = require('./view/ComponentImageView'), + + var Components = function (config){ + + var c = config || {}, + defaults = require('./config/config'), + Component = require('./model/Component'), + ComponentText = require('./model/ComponentText'), + ComponentImage = require('./model/ComponentImage'), + ComponentView = require('./view/ComponentView'), + ComponentImageView = require('./view/ComponentImageView'), ComponentTextView = require('./view/ComponentTextView'); // Set default options @@ -35,54 +73,115 @@ define(function(require) { c.wrapper.style = {}; c.wrapper.style.position = 'relative'; - this.component = new Component(c.wrapper, { sm: c.em }); + var component = new Component(c.wrapper, { sm: c.em }); var obj = { - model: this.component, + model: component, config: c, }; this.c = c; - this.ComponentView = new ComponentView(obj); - } + var componentView = new ComponentView(obj); - Components.prototype = { + return { /** - * Returns main wrapper which will contain all new components - * + * Returns privately the main wrapper * @return {Object} + * @private */ getComponent : function(){ - return this.component; + return component; }, /** - * Returns main wrapper which will contain all new components - * - * @return {Object} + * Returns root component inside the canvas. Something like inside HTML page + * The wrapper doesn't differ from the original Component Model + * @return {Component} Root Component + * @example + * // Change background of the wrapper and set some attribute + * var wrapper = ComponentsService.getWrapper(); + * wrapper.set('style', {'background-color': 'red'}); + * wrapper.set('attributes', {'title': 'Hello!'}); */ getWrapper: function(){ return this.getComponent(); }, /** - * Returns children from the wrapper - * - * @return {Object} + * Returns wrapper's children collection. Once you have the collection you can + * add other Components(Models) inside. Each component can have several nested + * components inside and you can nest them as more as you wish. + * @return {Components} Collection of components + * @example + * // Let's add some component + * var wrapperChildren = ComponentsService.getComponents(); + * var comp1 = wrapperChildren.add({ + * style: { 'background-color': 'red'} + * }); + * var comp2 = wrapperChildren.add({ + * tagName: 'span', + * attributes: { title: 'Hello!'} + * }); + * // Now let's add an other one inside first component + * // First we have to get the collection inside. Each + * // component has 'components' property + * var comp1Children = comp1.get('components'); + * // Procede as before. You could also add multiple objects + * comp1Children.add([ + * { style: { 'background-color': 'blue'}}, + * { style: { height: '100px', width: '100px'}} + * ]); + * // Remove comp2 + * wrapperChildren.remove(comp2); */ getComponents: function(){ return this.getWrapper().get('components'); }, /** - * Render and returns wrapper - * - * @return {Object} + * Add new components to the wrapper's children. It's the same + * as 'ComponentsService.getComponents().add(...)' + * @param {Object|Component|Array} component Component/s to add + * @param {string} [component.tagName='div'] Tag name + * @param {string} [component.type=''] Type of the component. Available: ''(default), 'text', 'image' + * @param {boolean} [component.removable=true] If component is removable + * @param {boolean} [component.movable=true] If is possible to move the component around the structure + * @param {boolean} [component.droppable=true] If is possible to drop inside other components + * @param {boolean} [component.badgable=true] If the badge is visible when the component is selected + * @param {boolean} [component.stylable=true] If is possible to style component + * @param {boolean} [component.copyable=true] If is possible to copy&paste the component + * @param {string} [component.content=''] String inside component + * @param {Object} [component.style={}] Style object + * @param {Object} [component.attributes={}] Attribute object + * @return {Component|Array} Component/s added + * @example + * // Example of a new component with some extra property + * var comp1 = ComponentsService.addComponent({ + * tagName: 'div', + * removable: true, // Can't remove it + * movable: true, // Can't move it + * copyable: true, // Disable copy/past + * content: 'Content text', // Text inside component + * style: { color: 'red'}, + * attributes: { title: 'here' } + * }); + */ + addComponent: function(component){ + return this.getComponents().add(component); + }, + + /** + * Render and returns wrapper element with all components inside. + * Once the wrapper is rendered, and it's what happens when you init the editor, + * the all new components will be added automatically and property changes are all + * updated immediately + * @return {HTMLElement} */ render : function(){ - return this.ComponentView.render().el; + return componentView.render().el; }, + }; }; return Components; diff --git a/src/dom_components/model/Component.js b/src/dom_components/model/Component.js index bc7de044a..2c0671c1a 100644 --- a/src/dom_components/model/Component.js +++ b/src/dom_components/model/Component.js @@ -1,8 +1,6 @@ define(['backbone','./Components', 'ClassManager/model/ClassTags'], function (Backbone, Components, ClassTags) { - /** - * @class Component - * */ + return Backbone.Model.extend({ defaults: { @@ -36,8 +34,8 @@ define(['backbone','./Components', 'ClassManager/model/ClassTags'], /** * Normalize input classes from array to array of objects * @param {Array} arr - * * @return {Array} + * @private */ normalizeClasses: function(arr){ var res = []; @@ -65,6 +63,7 @@ define(['backbone','./Components', 'ClassManager/model/ClassTags'], /** * Override original clone method + * @private */ clone: function() { @@ -91,6 +90,7 @@ define(['backbone','./Components', 'ClassManager/model/ClassTags'], * Get name of the component * * @return {String} + * @private * */ getName: function(){ if(!this.name){ diff --git a/src/dom_components/model/ComponentImage.js b/src/dom_components/model/ComponentImage.js index 550a193f3..35e72a654 100644 --- a/src/dom_components/model/ComponentImage.js +++ b/src/dom_components/model/ComponentImage.js @@ -1,14 +1,12 @@ -define(['./Component'], +define(['./Component'], function (Component) { - /** - * @class ComponentImage - * */ - return Component.extend({ - + + return Component.extend({ + defaults: _.extend({}, Component.prototype.defaults, { src : '', droppable : false, }), - + }); }); diff --git a/src/dom_components/model/ComponentText.js b/src/dom_components/model/ComponentText.js index 5c28a927c..4fc08a037 100644 --- a/src/dom_components/model/ComponentText.js +++ b/src/dom_components/model/ComponentText.js @@ -1,14 +1,12 @@ -define(['./Component'], +define(['./Component'], function (Component) { - /** - * @class ComponentText - * */ - return Component.extend({ - + + return Component.extend({ + defaults: _.extend({}, Component.prototype.defaults, { content : '', droppable : false, }), - + }); }); diff --git a/src/dom_components/model/Components.js b/src/dom_components/model/Components.js index a57aa2f14..743adac71 100644 --- a/src/dom_components/model/Components.js +++ b/src/dom_components/model/Components.js @@ -1,8 +1,5 @@ define([ 'backbone', 'require'], function (Backbone, require) { - /** - * @class Components - * */ return Backbone.Collection.extend({ diff --git a/src/dom_components/view/ComponentImageView.js b/src/dom_components/view/ComponentImageView.js index a84668f7b..d59091c2c 100644 --- a/src/dom_components/view/ComponentImageView.js +++ b/src/dom_components/view/ComponentImageView.js @@ -1,8 +1,5 @@ define(['backbone', './ComponentView'], function (Backbone, ComponentView) { - /** - * @class ComponentImageView - * */ return ComponentView.extend({ @@ -32,6 +29,7 @@ define(['backbone', './ComponentView'], * Update src attribute * * @return void + * @private * */ updateSrc: function(){ this.$el.attr('src',this.model.get("src")); @@ -42,6 +40,7 @@ define(['backbone', './ComponentView'], * @param {Object} e Event * * @return void + * @private * */ openModal: function(e){ var that = this; diff --git a/src/dom_components/view/ComponentTextView.js b/src/dom_components/view/ComponentTextView.js index cc0cbae5b..787984f52 100644 --- a/src/dom_components/view/ComponentTextView.js +++ b/src/dom_components/view/ComponentTextView.js @@ -1,8 +1,5 @@ define(['backbone', './ComponentView'], function (Backbone, ComponentView) { - /** - * @class ComponentTextView - * */ return ComponentView.extend({ @@ -23,6 +20,7 @@ define(['backbone', './ComponentView'], * Enable this component to be editable, * load also the mini toolbar for quick editing * @param Event + * @private * */ enableEditing: function(e){ if(this.rte){ @@ -38,6 +36,7 @@ define(['backbone', './ComponentView'], /** * Disable this component to be editable * @param Event + * @private * */ disableEditing: function(e){ if(this.rte){ @@ -50,6 +49,7 @@ define(['backbone', './ComponentView'], /** Isolate disable propagation method * @param Event + * @private * */ disablePropagation: function(e){ e.stopPropagation(); @@ -59,6 +59,7 @@ define(['backbone', './ComponentView'], * Update contents of the element * * @return void + * @private **/ updateContents : function(){ this.model.set('content', this.$el.html()); diff --git a/src/dom_components/view/ComponentView.js b/src/dom_components/view/ComponentView.js index a1d621702..78847dca9 100644 --- a/src/dom_components/view/ComponentView.js +++ b/src/dom_components/view/ComponentView.js @@ -1,8 +1,6 @@ define(['backbone', './ComponentsView'], function (Backbone, ComponentsView) { - /** - * @class ComponentView - * */ + return Backbone.View.extend({ className : function(){ //load classes from model @@ -14,7 +12,7 @@ define(['backbone', './ComponentsView'], }, initialize: function(opt){ - this.config = opt.config; + this.config = opt.config || {}; this.pfx = this.config.stylePrefix; this.components = this.model.get('components'); this.attr = this.model.get("attributes"); @@ -34,6 +32,7 @@ define(['backbone', './ComponentsView'], /** * Import, if possible, classes inside main container + * @private * */ importClasses: function(){ var clm = this.config.em.get('ClassManager'); @@ -48,6 +47,7 @@ define(['backbone', './ComponentsView'], /** * Fires on state update. If the state is not empty will add a helper class * @param {Event} e + * @private * */ updateState: function(e){ var cl = 'hc-state'; @@ -63,6 +63,7 @@ define(['backbone', './ComponentsView'], /** * Update item on status change * @param {Event} e + * @private * */ updateStatus: function(e){ var s = this.model.get('status'), @@ -83,6 +84,7 @@ define(['backbone', './ComponentsView'], * This method is called before initialize * * @return {Array}|null + * @private * */ getClasses: function(){ var attr = this.model.get("attributes"), @@ -97,6 +99,7 @@ define(['backbone', './ComponentsView'], * Update attributes * * @return void + * @private * */ updateAttributes: function(){ var attributes = {}, @@ -118,6 +121,7 @@ define(['backbone', './ComponentsView'], * Update style attribute * * @return void + * @private * */ updateStyle: function(){ this.$el.attr('style', this.getStyleString()); @@ -127,6 +131,7 @@ define(['backbone', './ComponentsView'], * Return style string * * @return {String} + * @private * */ getStyleString: function(){ var style = ''; @@ -141,6 +146,7 @@ define(['backbone', './ComponentsView'], /** * Update classe attribute + * @private * */ updateClasses: function(){ var str = ''; @@ -158,6 +164,7 @@ define(['backbone', './ComponentsView'], /** * Reply to event call * @param object Event that generated the request + * @private * */ eventCall: function(event){ event.viewResponse = this; diff --git a/src/dom_components/view/ComponentsView.js b/src/dom_components/view/ComponentsView.js index f82489927..4fb5ebe4a 100644 --- a/src/dom_components/view/ComponentsView.js +++ b/src/dom_components/view/ComponentsView.js @@ -1,41 +1,41 @@ define(['backbone','require'], function(Backbone, require) { - /** - * @class ComponentsView - * */ + return Backbone.View.extend({ - + initialize: function(o) { this.config = o.config; this.listenTo( this.collection, 'add', this.addTo ); this.listenTo( this.collection, 'reset', this.render ); }, - + /** * Add to collection * @param {Object} Model - * + * * @return void + * @private * */ addTo: function(model){ var i = this.collection.indexOf(model); this.addToCollection(model, null, i); }, - + /** * Add new object to collection * @param {Object} Model * @param {Object} Fragment collection * @param {Integer} Index of append - * + * * @return {Object} Object rendered + * @private * */ addToCollection: function(model, fragmentEl, index){ if(!this.compView) this.compView = require('./ComponentView'); var fragment = fragmentEl || null, viewObject = this.compView; - + switch(model.get('type')){ case 'text': if(!this.compViewText) @@ -48,13 +48,13 @@ function(Backbone, require) { viewObject = this.compViewImage; break; } - - var view = new viewObject({ - model : model, + + var view = new viewObject({ + model : model, config : this.config, }); var rendered = view.render().el; - + if(fragment){ fragment.appendChild(rendered); }else{ @@ -76,10 +76,10 @@ function(Backbone, require) { p.append(rendered); } } - + return rendered; }, - + render: function($p) { var fragment = document.createDocumentFragment(); this.$parent = $p || this.$el; @@ -88,9 +88,9 @@ function(Backbone, require) { this.addToCollection(model, fragment); },this); this.$el.append(fragment); - + return this; } - + }); }); \ No newline at end of file diff --git a/test/specs/dom_components/main.js b/test/specs/dom_components/main.js index eab7363ab..97b1369f6 100644 --- a/test/specs/dom_components/main.js +++ b/test/specs/dom_components/main.js @@ -2,22 +2,26 @@ var modulePath = './../../../test/specs/dom_components'; define([ 'DomComponents', - modulePath + '/model/Component' + modulePath + '/model/Component', + modulePath + '/view/ComponentView' ], function(DomComponents, - ComponentModels + ComponentModels, + ComponentView ) { describe('DOM Components', function() { describe('Main', function() { + var obj; + beforeEach(function () { - this.obj = new DomComponents(); + obj = new DomComponents(); }); afterEach(function () { - delete this.obj; + delete obj; }); it('Object exists', function() { @@ -25,22 +29,38 @@ define([ }); it('Wrapper exists', function() { - this.obj.getWrapper().should.not.be.empty; + obj.getWrapper().should.not.be.empty; }); it('No components inside', function() { - this.obj.getComponents().length.should.equal(0); + obj.getComponents().length.should.equal(0); + }); + + it('Add new component', function() { + var comp = obj.addComponent({}); + obj.getComponents().length.should.equal(1); + }); + + it('Add more components at once', function() { + var comp = obj.addComponent([{},{}]); + obj.getComponents().length.should.equal(2); }); it('Render wrapper', function() { - sinon.stub(this.obj.ComponentView, "render").returns({ el: '' }); - this.obj.render(); - this.obj.ComponentView.render.calledOnce.should.equal(true); + obj.render().should.be.ok; + }); + + it('Add components at init', function() { + obj = new DomComponents({ + defaults : [{}, {}, {}] + }); + obj.getComponents().length.should.equal(3); }); }); ComponentModels.run(); + ComponentView.run(); }); }); \ No newline at end of file diff --git a/test/specs/dom_components/view/componentView.js b/test/specs/dom_components/view/componentView.js index e69de29bb..439bd2cbf 100644 --- a/test/specs/dom_components/view/componentView.js +++ b/test/specs/dom_components/view/componentView.js @@ -0,0 +1,76 @@ +var path = 'DomComponents/view/'; +define([path + 'ComponentView', 'DomComponents/model/Component'], + function(ComponentView, Component) { + + return { + run : function(){ + + describe('ComponentView', function() { + + var $fixtures; + var $fixture; + var model; + var view; + var btnClass = 'btn'; + + before(function () { + $fixtures = $("#fixtures"); + $fixture = $('
'); + }); + + beforeEach(function () { + model = new Component(); + view = new ComponentView({ + model: model + }); + $fixture.empty().appendTo($fixtures); + $fixture.html(view.render().el); + }); + + afterEach(function () { + view.remove(); + }); + + after(function () { + $fixture.remove(); + }); + + it('Component empty', function() { + $fixture.html().should.be.equal('
'); + }); +/* + it('Update class', function() { + model.set('className','test'); + view.el.getAttribute('class').should.be.equal(btnClass + ' test'); + }); + + it('Update attributes', function() { + model.set('attributes',{ + 'data-test': 'test-value' + }); + view.el.getAttribute('data-test').should.be.equal('test-value'); + }); + + it('Check enable active', function() { + model.set('active', true, {silent: true}); + view.checkActive(); + view.el.getAttribute('class').should.be.equal(btnClass + ' active'); + }); + + it('Check disable active', function() { + model.set('active', true, {silent: true}); + view.checkActive(); + model.set('active', false, {silent: true}); + view.checkActive(); + view.el.getAttribute('class').should.be.equal(btnClass); + }); + + it('Renders correctly', function() { + view.render().should.be.ok; + }); +*/ + }); + } + }; + +}); \ No newline at end of file