Browse Source

Add ComponentView tests

pull/36/head
Artur Arseniev 11 years ago
parent
commit
2743f81d95
  1. 165
      src/dom_components/main.js
  2. 8
      src/dom_components/model/Component.js
  3. 12
      src/dom_components/model/ComponentImage.js
  4. 12
      src/dom_components/model/ComponentText.js
  5. 3
      src/dom_components/model/Components.js
  6. 5
      src/dom_components/view/ComponentImageView.js
  7. 7
      src/dom_components/view/ComponentTextView.js
  8. 15
      src/dom_components/view/ComponentView.js
  9. 34
      src/dom_components/view/ComponentsView.js
  10. 38
      test/specs/dom_components/main.js
  11. 76
      test/specs/dom_components/view/componentView.js

165
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<Object>} [config.defaults=[]] Array of possible components
* @example
* ...
* components: {
* defaults: [
* {
* style: { background: 'red'}
* components:[
* {style: { background: 'blue'}},
* {style: { background: 'green'}}
* ]
* }
* ],
* }
* ...
*/
define(function(require) { define(function(require) {
/**
* @class Components var Components = function (config){
* @param {Object} Configurations
* var c = config || {},
* @return {Object} defaults = require('./config/config'),
* */ Component = require('./model/Component'),
function Components(config) ComponentText = require('./model/ComponentText'),
{ ComponentImage = require('./model/ComponentImage'),
var c = config || {}, ComponentView = require('./view/ComponentView'),
defaults = require('./config/config'), ComponentImageView = require('./view/ComponentImageView'),
Component = require('./model/Component'),
ComponentText = require('./model/ComponentText'),
ComponentImage = require('./model/ComponentImage'),
ComponentView = require('./view/ComponentView'),
ComponentImageView = require('./view/ComponentImageView'),
ComponentTextView = require('./view/ComponentTextView'); ComponentTextView = require('./view/ComponentTextView');
// Set default options // Set default options
@ -35,54 +73,115 @@ define(function(require) {
c.wrapper.style = {}; c.wrapper.style = {};
c.wrapper.style.position = 'relative'; 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 = { var obj = {
model: this.component, model: component,
config: c, config: c,
}; };
this.c = 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} * @return {Object}
* @private
*/ */
getComponent : function(){ getComponent : function(){
return this.component; return component;
}, },
/** /**
* Returns main wrapper which will contain all new components * Returns root component inside the canvas. Something like <body> inside HTML page
* * The wrapper doesn't differ from the original Component Model
* @return {Object} * @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(){ getWrapper: function(){
return this.getComponent(); return this.getComponent();
}, },
/** /**
* Returns children from the wrapper * Returns wrapper's children collection. Once you have the collection you can
* * add other Components(Models) inside. Each component can have several nested
* @return {Object} * 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(){ getComponents: function(){
return this.getWrapper().get('components'); return this.getWrapper().get('components');
}, },
/** /**
* Render and returns wrapper * Add new components to the wrapper's children. It's the same
* * as 'ComponentsService.getComponents().add(...)'
* @return {Object} * @param {Object|Component|Array<Object>} 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>} 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(){ render : function(){
return this.ComponentView.render().el; return componentView.render().el;
}, },
};
}; };
return Components; return Components;

8
src/dom_components/model/Component.js

@ -1,8 +1,6 @@
define(['backbone','./Components', 'ClassManager/model/ClassTags'], define(['backbone','./Components', 'ClassManager/model/ClassTags'],
function (Backbone, Components, ClassTags) { function (Backbone, Components, ClassTags) {
/**
* @class Component
* */
return Backbone.Model.extend({ return Backbone.Model.extend({
defaults: { defaults: {
@ -36,8 +34,8 @@ define(['backbone','./Components', 'ClassManager/model/ClassTags'],
/** /**
* Normalize input classes from array to array of objects * Normalize input classes from array to array of objects
* @param {Array} arr * @param {Array} arr
*
* @return {Array} * @return {Array}
* @private
*/ */
normalizeClasses: function(arr){ normalizeClasses: function(arr){
var res = []; var res = [];
@ -65,6 +63,7 @@ define(['backbone','./Components', 'ClassManager/model/ClassTags'],
/** /**
* Override original clone method * Override original clone method
* @private
*/ */
clone: function() clone: function()
{ {
@ -91,6 +90,7 @@ define(['backbone','./Components', 'ClassManager/model/ClassTags'],
* Get name of the component * Get name of the component
* *
* @return {String} * @return {String}
* @private
* */ * */
getName: function(){ getName: function(){
if(!this.name){ if(!this.name){

12
src/dom_components/model/ComponentImage.js

@ -1,14 +1,12 @@
define(['./Component'], define(['./Component'],
function (Component) { function (Component) {
/**
* @class ComponentImage return Component.extend({
* */
return Component.extend({
defaults: _.extend({}, Component.prototype.defaults, { defaults: _.extend({}, Component.prototype.defaults, {
src : '', src : '',
droppable : false, droppable : false,
}), }),
}); });
}); });

12
src/dom_components/model/ComponentText.js

@ -1,14 +1,12 @@
define(['./Component'], define(['./Component'],
function (Component) { function (Component) {
/**
* @class ComponentText return Component.extend({
* */
return Component.extend({
defaults: _.extend({}, Component.prototype.defaults, { defaults: _.extend({}, Component.prototype.defaults, {
content : '', content : '',
droppable : false, droppable : false,
}), }),
}); });
}); });

3
src/dom_components/model/Components.js

@ -1,8 +1,5 @@
define([ 'backbone', 'require'], define([ 'backbone', 'require'],
function (Backbone, require) { function (Backbone, require) {
/**
* @class Components
* */
return Backbone.Collection.extend({ return Backbone.Collection.extend({

5
src/dom_components/view/ComponentImageView.js

@ -1,8 +1,5 @@
define(['backbone', './ComponentView'], define(['backbone', './ComponentView'],
function (Backbone, ComponentView) { function (Backbone, ComponentView) {
/**
* @class ComponentImageView
* */
return ComponentView.extend({ return ComponentView.extend({
@ -32,6 +29,7 @@ define(['backbone', './ComponentView'],
* Update src attribute * Update src attribute
* *
* @return void * @return void
* @private
* */ * */
updateSrc: function(){ updateSrc: function(){
this.$el.attr('src',this.model.get("src")); this.$el.attr('src',this.model.get("src"));
@ -42,6 +40,7 @@ define(['backbone', './ComponentView'],
* @param {Object} e Event * @param {Object} e Event
* *
* @return void * @return void
* @private
* */ * */
openModal: function(e){ openModal: function(e){
var that = this; var that = this;

7
src/dom_components/view/ComponentTextView.js

@ -1,8 +1,5 @@
define(['backbone', './ComponentView'], define(['backbone', './ComponentView'],
function (Backbone, ComponentView) { function (Backbone, ComponentView) {
/**
* @class ComponentTextView
* */
return ComponentView.extend({ return ComponentView.extend({
@ -23,6 +20,7 @@ define(['backbone', './ComponentView'],
* Enable this component to be editable, * Enable this component to be editable,
* load also the mini toolbar for quick editing * load also the mini toolbar for quick editing
* @param Event * @param Event
* @private
* */ * */
enableEditing: function(e){ enableEditing: function(e){
if(this.rte){ if(this.rte){
@ -38,6 +36,7 @@ define(['backbone', './ComponentView'],
/** /**
* Disable this component to be editable * Disable this component to be editable
* @param Event * @param Event
* @private
* */ * */
disableEditing: function(e){ disableEditing: function(e){
if(this.rte){ if(this.rte){
@ -50,6 +49,7 @@ define(['backbone', './ComponentView'],
/** Isolate disable propagation method /** Isolate disable propagation method
* @param Event * @param Event
* @private
* */ * */
disablePropagation: function(e){ disablePropagation: function(e){
e.stopPropagation(); e.stopPropagation();
@ -59,6 +59,7 @@ define(['backbone', './ComponentView'],
* Update contents of the element * Update contents of the element
* *
* @return void * @return void
* @private
**/ **/
updateContents : function(){ updateContents : function(){
this.model.set('content', this.$el.html()); this.model.set('content', this.$el.html());

15
src/dom_components/view/ComponentView.js

@ -1,8 +1,6 @@
define(['backbone', './ComponentsView'], define(['backbone', './ComponentsView'],
function (Backbone, ComponentsView) { function (Backbone, ComponentsView) {
/**
* @class ComponentView
* */
return Backbone.View.extend({ return Backbone.View.extend({
className : function(){ //load classes from model className : function(){ //load classes from model
@ -14,7 +12,7 @@ define(['backbone', './ComponentsView'],
}, },
initialize: function(opt){ initialize: function(opt){
this.config = opt.config; this.config = opt.config || {};
this.pfx = this.config.stylePrefix; this.pfx = this.config.stylePrefix;
this.components = this.model.get('components'); this.components = this.model.get('components');
this.attr = this.model.get("attributes"); this.attr = this.model.get("attributes");
@ -34,6 +32,7 @@ define(['backbone', './ComponentsView'],
/** /**
* Import, if possible, classes inside main container * Import, if possible, classes inside main container
* @private
* */ * */
importClasses: function(){ importClasses: function(){
var clm = this.config.em.get('ClassManager'); 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 * Fires on state update. If the state is not empty will add a helper class
* @param {Event} e * @param {Event} e
* @private
* */ * */
updateState: function(e){ updateState: function(e){
var cl = 'hc-state'; var cl = 'hc-state';
@ -63,6 +63,7 @@ define(['backbone', './ComponentsView'],
/** /**
* Update item on status change * Update item on status change
* @param {Event} e * @param {Event} e
* @private
* */ * */
updateStatus: function(e){ updateStatus: function(e){
var s = this.model.get('status'), var s = this.model.get('status'),
@ -83,6 +84,7 @@ define(['backbone', './ComponentsView'],
* This method is called before initialize * This method is called before initialize
* *
* @return {Array}|null * @return {Array}|null
* @private
* */ * */
getClasses: function(){ getClasses: function(){
var attr = this.model.get("attributes"), var attr = this.model.get("attributes"),
@ -97,6 +99,7 @@ define(['backbone', './ComponentsView'],
* Update attributes * Update attributes
* *
* @return void * @return void
* @private
* */ * */
updateAttributes: function(){ updateAttributes: function(){
var attributes = {}, var attributes = {},
@ -118,6 +121,7 @@ define(['backbone', './ComponentsView'],
* Update style attribute * Update style attribute
* *
* @return void * @return void
* @private
* */ * */
updateStyle: function(){ updateStyle: function(){
this.$el.attr('style', this.getStyleString()); this.$el.attr('style', this.getStyleString());
@ -127,6 +131,7 @@ define(['backbone', './ComponentsView'],
* Return style string * Return style string
* *
* @return {String} * @return {String}
* @private
* */ * */
getStyleString: function(){ getStyleString: function(){
var style = ''; var style = '';
@ -141,6 +146,7 @@ define(['backbone', './ComponentsView'],
/** /**
* Update classe attribute * Update classe attribute
* @private
* */ * */
updateClasses: function(){ updateClasses: function(){
var str = ''; var str = '';
@ -158,6 +164,7 @@ define(['backbone', './ComponentsView'],
/** /**
* Reply to event call * Reply to event call
* @param object Event that generated the request * @param object Event that generated the request
* @private
* */ * */
eventCall: function(event){ eventCall: function(event){
event.viewResponse = this; event.viewResponse = this;

34
src/dom_components/view/ComponentsView.js

@ -1,41 +1,41 @@
define(['backbone','require'], define(['backbone','require'],
function(Backbone, require) { function(Backbone, require) {
/**
* @class ComponentsView
* */
return Backbone.View.extend({ return Backbone.View.extend({
initialize: function(o) { initialize: function(o) {
this.config = o.config; this.config = o.config;
this.listenTo( this.collection, 'add', this.addTo ); this.listenTo( this.collection, 'add', this.addTo );
this.listenTo( this.collection, 'reset', this.render ); this.listenTo( this.collection, 'reset', this.render );
}, },
/** /**
* Add to collection * Add to collection
* @param {Object} Model * @param {Object} Model
* *
* @return void * @return void
* @private
* */ * */
addTo: function(model){ addTo: function(model){
var i = this.collection.indexOf(model); var i = this.collection.indexOf(model);
this.addToCollection(model, null, i); this.addToCollection(model, null, i);
}, },
/** /**
* Add new object to collection * Add new object to collection
* @param {Object} Model * @param {Object} Model
* @param {Object} Fragment collection * @param {Object} Fragment collection
* @param {Integer} Index of append * @param {Integer} Index of append
* *
* @return {Object} Object rendered * @return {Object} Object rendered
* @private
* */ * */
addToCollection: function(model, fragmentEl, index){ addToCollection: function(model, fragmentEl, index){
if(!this.compView) if(!this.compView)
this.compView = require('./ComponentView'); this.compView = require('./ComponentView');
var fragment = fragmentEl || null, var fragment = fragmentEl || null,
viewObject = this.compView; viewObject = this.compView;
switch(model.get('type')){ switch(model.get('type')){
case 'text': case 'text':
if(!this.compViewText) if(!this.compViewText)
@ -48,13 +48,13 @@ function(Backbone, require) {
viewObject = this.compViewImage; viewObject = this.compViewImage;
break; break;
} }
var view = new viewObject({ var view = new viewObject({
model : model, model : model,
config : this.config, config : this.config,
}); });
var rendered = view.render().el; var rendered = view.render().el;
if(fragment){ if(fragment){
fragment.appendChild(rendered); fragment.appendChild(rendered);
}else{ }else{
@ -76,10 +76,10 @@ function(Backbone, require) {
p.append(rendered); p.append(rendered);
} }
} }
return rendered; return rendered;
}, },
render: function($p) { render: function($p) {
var fragment = document.createDocumentFragment(); var fragment = document.createDocumentFragment();
this.$parent = $p || this.$el; this.$parent = $p || this.$el;
@ -88,9 +88,9 @@ function(Backbone, require) {
this.addToCollection(model, fragment); this.addToCollection(model, fragment);
},this); },this);
this.$el.append(fragment); this.$el.append(fragment);
return this; return this;
} }
}); });
}); });

38
test/specs/dom_components/main.js

@ -2,22 +2,26 @@ var modulePath = './../../../test/specs/dom_components';
define([ define([
'DomComponents', 'DomComponents',
modulePath + '/model/Component' modulePath + '/model/Component',
modulePath + '/view/ComponentView'
], ],
function(DomComponents, function(DomComponents,
ComponentModels ComponentModels,
ComponentView
) { ) {
describe('DOM Components', function() { describe('DOM Components', function() {
describe('Main', function() { describe('Main', function() {
var obj;
beforeEach(function () { beforeEach(function () {
this.obj = new DomComponents(); obj = new DomComponents();
}); });
afterEach(function () { afterEach(function () {
delete this.obj; delete obj;
}); });
it('Object exists', function() { it('Object exists', function() {
@ -25,22 +29,38 @@ define([
}); });
it('Wrapper exists', function() { it('Wrapper exists', function() {
this.obj.getWrapper().should.not.be.empty; obj.getWrapper().should.not.be.empty;
}); });
it('No components inside', function() { 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() { it('Render wrapper', function() {
sinon.stub(this.obj.ComponentView, "render").returns({ el: '' }); obj.render().should.be.ok;
this.obj.render(); });
this.obj.ComponentView.render.calledOnce.should.equal(true);
it('Add components at init', function() {
obj = new DomComponents({
defaults : [{}, {}, {}]
});
obj.getComponents().length.should.equal(3);
}); });
}); });
ComponentModels.run(); ComponentModels.run();
ComponentView.run();
}); });
}); });

76
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 = $('<div class="components-fixture"></div>');
});
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('<div style="" class=""></div>');
});
/*
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;
});
*/
});
}
};
});
Loading…
Cancel
Save