Browse Source

Bump v0.3.10

pull/36/head
Artur Arseniev 10 years ago
parent
commit
9c29762ffc
  1. 2
      bower.json
  2. 32
      dist/grapes.min.js
  3. 2
      package.json
  4. 24
      src/selector_manager/config/config.js
  5. 164
      src/selector_manager/main.js
  6. 32
      src/selector_manager/model/Selector.js
  7. 9
      src/selector_manager/model/Selectors.js
  8. 5
      src/selector_manager/template/classTag.html
  9. 27
      src/selector_manager/template/classTags.html
  10. 129
      src/selector_manager/view/ClassTagView.js
  11. 280
      src/selector_manager/view/ClassTagsView.js
  12. 127
      test/specs/selector_manager/e2e/ClassManager.js
  13. 94
      test/specs/selector_manager/main.js
  14. 53
      test/specs/selector_manager/model/SelectorModels.js
  15. 132
      test/specs/selector_manager/view/ClassTagView.js
  16. 195
      test/specs/selector_manager/view/ClassTagsView.js

2
bower.json

@ -1,7 +1,7 @@
{
"name": "grapesjs",
"description": "Open source Web Template Editor",
"version": "0.3.9",
"version": "0.3.10",
"author": "Artur Arseniev",
"homepage": "http://grapesjs.com",
"main": [

32
dist/grapes.min.js

File diff suppressed because one or more lines are too long

2
package.json

@ -1,7 +1,7 @@
{
"name": "grapesjs",
"description": "Open source Web Template Editor",
"version": "0.3.9",
"version": "0.3.10",
"author": "Artur Arseniev",
"license": "BSD-3-Clause",
"homepage": "http://grapesjs.com",

24
src/selector_manager/config/config.js

@ -0,0 +1,24 @@
define(function () {
return {
// Style prefix
stylePrefix : 'clm-',
// Default classes
selectors : [],
// Label for classes
label: 'Classes',
// Label for states
statesLabel: '- State -',
// States
states: [
{ name: 'hover', label: 'Hover' },
{ name: 'active', label: 'Click' },
{ name: 'nth-of-type(2n)', label: 'Even/Odd' }
],
};
});

164
src/selector_manager/main.js

@ -0,0 +1,164 @@
/**
* * [init](#init)
* * [add](#add)
* * [get](#get)
* * [getAll](#getall)
* * [render](#render)
*
* Selectors in GrapesJS are used in CSS Composer inside Rules and in Components as classes. To get better this concept let's take
* a look at this code:
*
* ```css
* span > #send-btn.btn{
* ...
* }
* ```
* ```html
* <span>
* <button id="send-btn" class="btn"></button>
* </span>
* ```
*
* In this scenario we get:
* span -> selector of type `tag`
* send-btn -> selector of type `id`
* btn -> selector of type `class`
*
* So, for example, being `btn` the same class entity it'll be easier to refactor and track things.
*
* Before using methods you should get first the module from the editor instance, in this way:
*
* ```js
* var selectorManager = editor.SelectorManager;
* ```
*
* @module SelectorManager
*/
define(function(require) {
return function(config) {
var c = config || {},
defaults = require('./config/config');
Selectors = require('./model/Selectors');
ClassTagsView = require('./view/ClassTagsView');
var selectors, selectorTags;
return {
/**
* Name of the module
* @type {String}
* @private
*/
name: 'SelectorManager',
/**
* Indicates if module is public
* @type {Boolean}
* @private
*/
public: true,
/**
* Initialize module. Automatically called with a new instance of the editor
* @param {Object} config Configurations
* @param {Array<Object>} [config.selectors=[]] Default selectors
* @param {Array<Object>} [config.states=[]] Default states
* @param {String} [config.label='Classes'] Classes label
* @param {String} [config.statesLabel='- State -'] The empty state label
* @return {this}
* @example
* ...
* {
* selectors: [
* {name:'myselector1'},
* ...
* ],
* states: [{
* name: 'hover', label: 'Hover'
* },{
* name: 'active', label: 'Click'
* }],
* statesLabel: '- Selecte State -',
* }
*/
init: function(conf) {
c = conf || {};
for (var name in defaults) {
if (!(name in c))
c[name] = defaults[name];
}
var ppfx = c.pStylePrefix;
if(ppfx)
c.stylePrefix = ppfx + c.stylePrefix;
selectors = new Selectors(c.selectors);
selectorTags = new ClassTagsView({
collection: selectors,
config: c,
});
return this;
},
/**
* Add new selector to collection if it's not already exists. Class type is a default one
* @param {String} name Selector name
* @param {Object} opts Selector options
* @param {String} [opts.label=''] Label for the selector, if it's not provided the label will be the same as the name
* @param {String} [opts.type='class'] Type of the selector. At the moment, only 'class' is available
* @return {Model}
* @example
* var selector = selectorManager.add('selectorName');
* // Same as
* var selector = selectorManager.add('selectorName', {
* type: 'class',
* label: 'selectorName'
* });
* */
add: function(name, opts){
var obj = opts || {};
obj.name = name;
return selectors.add(obj);
},
/**
* Get selector by its name
* @param {String} name Selector name
* @return {Model|null}
* @example
* var selector = selectorManager.get('selectorName');
* */
get: function(name) {
return selectors.where({name: name})[0];
},
/**
* Get all selectors
* @return {Collection}
* */
getAll: function() {
return selectors;
},
/**
* Render class selectors. If an array of selectors is provided a new instance of the collection will be rendered
* @param {Array<Object>} selectors
* @return {HTMLElement}
*/
render: function(selectors) {
if(selectors){
var view = new ClassTagsView({
collection: new Selectors(selectors),
config: c,
});
return view.render().el;
}else
return selectorTags.render().el;
},
};
};
});

32
src/selector_manager/model/Selector.js

@ -0,0 +1,32 @@
define(['backbone'],
function (Backbone) {
return Backbone.Model.extend({
idAttribute: 'name',
defaults: {
name: '',
label: '',
type: 'class',
active: true,
},
initialize: function() {
this.set('name', this.escapeName(this.get('name')));
var label = this.get('label').trim();
if(!label)
this.set('label', this.get('name'));
},
/**
* Escape string
* @param {string} name
*
* @return {string}
*/
escapeName: function(name) {
return name.toLowerCase().replace(/([^a-z0-9\w]+)/gi, '-');
},
});
});

9
src/selector_manager/model/Selectors.js

@ -0,0 +1,9 @@
define(['backbone','./Selector'],
function (Backbone, Selector) {
return Backbone.Collection.extend({
model: Selector,
});
});

5
src/selector_manager/template/classTag.html

@ -0,0 +1,5 @@
<span id="<%= pfx %>checkbox" class="fa"></span>
<span id="<%= pfx %>tag-label">
<input class="<%= ppfx %>no-app" value="<%= label %>" <%= inputProp %>/>
</span>
<span id="<%= pfx %>close">&Cross;</span>

27
src/selector_manager/template/classTags.html

@ -0,0 +1,27 @@
<div id="<%= pfx %>up">
<div id="<%= pfx %>label"><%= label %></div>
<div id="<%= pfx %>status-c">
<span id="<%= pfx %>input-c">
<div class="<%= pfx %>field <%= pfx %>select">
<span id="<%= pfx %>input-holder">
<select id="<%= pfx %>states">
<option value=""><%= statesLabel %></option>
</select>
</span>
<div class="<%= pfx %>sel-arrow">
<div class="<%= pfx %>d-s-arrow"></div>
</div>
</div>
</span>
</div>
</div>
<div id="<%= pfx %>tags-field">
<div id="<%= pfx %>tags-c"></div>
<input id="<%= pfx %>new" />
<span id="<%= pfx %>add-tag" class="fa fa-plus"></span>
</div>
<div id="<%= pfx %>sel-help">
<div id="<%= pfx %>label">Selected</div>
<div id="<%= pfx %>sel"></div>
<div style="clear:both"></div>
</div>

129
src/selector_manager/view/ClassTagView.js

@ -0,0 +1,129 @@
define(['backbone', 'text!./../template/classTag.html'],
function (Backbone, tagTemplate) {
/**
* @class ClassTagView
* */
return Backbone.View.extend({
template: _.template(tagTemplate),
events: {},
initialize: function(o) {
this.config = o.config || {};
this.coll = o.coll || null;
this.pfx = this.config.stylePrefix || '';
this.ppfx = this.config.pStylePrefix || '';
this.inputProp = 'readonly';
this.target = this.config.em;
this.className = this.pfx + 'tag';
this.closeId = this.pfx + 'close';
this.chkId = this.pfx + 'checkbox';
this.labelId = this.pfx + 'tag-label';
this.events['click #' + this.closeId ] = 'removeTag';
this.events['click #' + this.chkId ] = 'changeStatus';
this.events['dblclick #' + this.labelId ] = 'startEditTag';
this.events['keypress #' + this.labelId + ' input'] = 'updateInputLabel';
this.events['blur #' + this.labelId + ' input'] = 'endEditTag';
this.listenTo( this.model, 'change:active', this.updateStatus);
this.delegateEvents();
},
/**
* Start editing tag
*/
startEditTag: function(){
this.$labelInput.prop(this.inputProp, false);
},
/**
* End editing tag. If the class typed already exists the
* old one will be restored otherwise will be changed
*/
endEditTag: function(){
var value = this.$labelInput.val();
var next = this.model.escapeName(value);
if(this.target){
var clsm = this.target.get('SelectorManager');
if(clsm){
if(clsm.get(next))
this.$labelInput.val(this.model.get('label'));
else
this.model.set({ name: next, label: value});
}
}
this.$labelInput.prop(this.inputProp, true);
},
/**
* Update status of the tag
*/
changeStatus: function(){
this.model.set('active', !this.model.get('active'));
this.target.trigger('targetClassUpdated');
},
/**
* Remove tag from the selected component
* @param {Object} e
*/
removeTag: function(e){
var comp = this.target.get('selectedComponent');
if(comp)
comp.get('classes').remove(this.model);
if(this.coll){
this.coll.remove(this.model);
this.target.trigger('targetClassRemoved');
}
this.remove();
},
/**
* Update status of the checkbox
*/
updateStatus: function(){
if(!this.$chk)
this.$chk = this.$el.find('#' + this.pfx + 'checkbox');
if(this.model.get('active')){
this.$chk.removeClass('fa-circle-o').addClass('fa-dot-circle-o');
this.$el.removeClass('opac50');
}else{
this.$chk.removeClass('fa-dot-circle-o').addClass('fa-circle-o');
this.$el.addClass('opac50');
}
},
/**
* Update label's input
*/
updateInputLabel: function(){
if(!this.$labelInput)
this.$labelInput = this.$el.find('input');
var size = this.$labelInput.val().length - 2;
size = size < 1 ? 1 : size;
this.$labelInput.attr('size', size);
},
/** @inheritdoc */
render : function(){
this.$el.html( this.template({
label: this.model.get('label'),
pfx: this.pfx,
ppfx: this.ppfx,
inputProp: this.inputProp,
}));
this.updateStatus();
this.$el.attr('class', this.className);
this.updateInputLabel();
return this;
},
});
});

280
src/selector_manager/view/ClassTagsView.js

@ -0,0 +1,280 @@
define(['backbone', 'text!./../template/classTags.html', './ClassTagView'],
function (Backbone, tagsTemplate, ClassTagView) {
/**
* @class ClassTagsView
* */
return Backbone.View.extend({
template: _.template(tagsTemplate),
events: {},
initialize: function(o) {
this.config = o.config || {};
this.pfx = this.config.stylePrefix || '';
this.className = this.pfx + 'tags';
this.addBtnId = this.pfx + 'add-tag';
this.newInputId = this.pfx + 'new';
this.stateInputId = this.pfx + 'states';
this.stateInputC = this.pfx + 'input-c';
this.states = this.config.states || [];
this.events['click #' + this.addBtnId] = 'startNewTag';
this.events['blur #' + this.newInputId] = 'endNewTag';
this.events['keyup #' + this.newInputId] = 'onInputKeyUp';
this.events['change #' + this.stateInputId] = 'stateChanged';
this.target = this.config.em;
this.listenTo(this.target ,'change:selectedComponent',this.componentChanged);
this.listenTo(this.target, 'targetClassUpdated', this.updateSelector);
this.listenTo(this.collection, 'add', this.addNew);
this.listenTo(this.collection, 'reset', this.renderClasses);
this.listenTo(this.collection, 'remove', this.tagRemoved);
this.delegateEvents();
},
/**
* Triggered when a tag is removed from collection
* @param {Object} model Removed model
*/
tagRemoved: function(model){
this.updateStateVis();
},
/**
* Create select input with states
* @return {string} String of options
*/
getStateOptions: function(){
var strInput = '';
for(var i = 0; i < this.states.length; i++){
strInput += '<option value="' + this.states[i].name + '">' + this.states[i].label + '</option>';
}
return strInput;
},
/**
* Add new model
* @param {Object} model
*/
addNew: function(model){
this.addToClasses(model);
},
/**
* Start tag creation
* @param {Object} e
*
*/
startNewTag: function(e) {
this.$addBtn.hide();
this.$input.show().focus();
},
/**
* End tag creation
* @param {Object} e
*
*/
endNewTag: function(e) {
this.$addBtn.show();
this.$input.hide().val('');
},
/**
* Checks what to do on keyup event
* @param {Object} e
*/
onInputKeyUp: function(e) {
if (e.keyCode === 13)
this.addNewTag(this.$input.val());
else if(e.keyCode === 27)
this.endNewTag();
},
/**
* Triggered when component is changed
* @param {Object} e
*/
componentChanged: function(e){
this.compTarget = this.target.get('selectedComponent');
if(this.compTarget)
this.getStates().val(this.compTarget.get('state'));
var models = this.compTarget ? this.compTarget.get('classes').models : [];
this.collection.reset(models);
this.updateStateVis();
},
/**
* Update states visibility. Hides states in case there is no tags
* inside collection
*/
updateStateVis: function(){
if(this.collection.length)
this.getStatesC().css('display','block');
else
this.getStatesC().css('display','none');
this.updateSelector();
},
/**
* Udpate selector helper
* @return {this}
* @private
*/
updateSelector: function(){
this.compTarget = this.target.get('selectedComponent');
if(!this.compTarget || !this.compTarget.get)
return;
var result = '';
var models = this.compTarget.get('classes');
models.each(function(model){
if(model.get('active'))
result += '.' + model.get('name');
});
var state = this.compTarget.get('state');
result = state ? result + ':' + state : result;
var el = this.el.querySelector('#' + this.pfx + 'sel');
if(el)
el.innerHTML = result;
},
/**
* Triggered when the select with states is changed
* @param {Object} e
*/
stateChanged: function(e){
if(this.compTarget){
this.compTarget.set('state', this.$states.val());
if(this.target)
this.target.trigger('targetStateUpdated');
this.updateSelector();
}
},
/**
* Add new tag to collection, if possible, and to the component
* @param {Object} e
*/
addNewTag: function(name){
if(!name)
return;
if(this.target){
var cm = this.target.get('SelectorManager');
var model = cm.add(name);
if(this.compTarget){
var targetCls = this.compTarget.get('classes');
var lenB = targetCls.length;
targetCls.add(model);
var lenA = targetCls.length;
this.collection.add(model);
if(lenA > lenB)
this.target.trigger('targetClassAdded');
this.updateStateVis();
}
}
this.endNewTag();
},
/**
* Add new object to collection
* @param {Object} model Model
* @param {Object} fragmentEl Fragment collection
*
* @return {Object} Object created
* */
addToClasses: function(model, fragmentEl) {
var fragment = fragmentEl || null;
var view = new ClassTagView({
model: model,
config: this.config,
coll: this.collection,
});
var rendered = view.render().el;
if(fragment)
fragment.appendChild(rendered);
else
this.getClasses().append(rendered);
return rendered;
},
/**
* Render the collection of classes
* @return {this}
*/
renderClasses: function() {
var fragment = document.createDocumentFragment();
this.collection.each(function(model){
this.addToClasses(model, fragment);
},this);
if(this.getClasses())
this.getClasses().empty().append(fragment);
return this;
},
/**
* Return classes element
* @return {HTMLElement}
* @private
*/
getClasses: function() {
if(!this.$classes)
this.$classes = this.$el.find('#' + this.pfx + 'tags-c');
return this.$classes;
},
/**
* Return states element
* @return {HTMLElement}
* @private
*/
getStates: function() {
if(!this.$states)
this.$states = this.$el.find('#' + this.stateInputId);
return this.$states;
},
/**
* Return states container element
* @return {HTMLElement}
* @private
*/
getStatesC: function() {
if(!this.$statesC)
this.$statesC = this.$el.find('#' + this.stateInputC);
return this.$statesC;
},
/** @inheritdoc */
render : function(){
this.$el.html( this.template({
label: this.config.label,
statesLabel: this.config.statesLabel,
pfx: this.pfx,
}));
this.$input = this.$el.find('input#' + this.newInputId);
this.$addBtn = this.$el.find('#' + this.addBtnId);
this.$classes = this.$el.find('#' + this.pfx + 'tags-c');
this.$states = this.$el.find('#' + this.stateInputId);
this.$statesC = this.$el.find('#' + this.stateInputC);
this.$states.append(this.getStateOptions());
this.renderClasses();
this.$el.attr('class', this.className);
return this;
},
});
});

127
test/specs/selector_manager/e2e/ClassManager.js

@ -0,0 +1,127 @@
define(['GrapesJS', 'SelectorManager/model/Selectors', 'SelectorManager/view/ClassTagsView'],
function(GrapesJS, Selectors, ClassTagsView) {
return {
run : function(){
describe('E2E tests', function() {
/**
* Create tags viewer
* @param {Object} ctx
*/
var instClassTagViewer = function(ctx){
var $clm;
var clm = ctx.gjs.editor.get('SelectorManager');
if(clm){
$clm = new ClassTagsView({
collection: new Selectors([]),
config: {
em: ctx.gjs.editor
},
}).render();
ctx.$fixture.append($clm.el);
}
return $clm;
};
before(function () {
this.$fixtures = $("#fixtures");
this.$fixture = $('<div id="SelectorManager-fixture"></div>');
});
beforeEach(function () {
this.gjs = GrapesJS.init({
stylePrefix: '',
storage: { autoload: 0, type:'none' },
assetManager: {
storageType: 'none',
},
container: '#SelectorManager-fixture',
});
this.$fixture.empty().appendTo(this.$fixtures);
this.gjs.render();
});
afterEach(function () {
delete this.gjs;
});
after(function () {
this.$fixture.remove();
});
describe('Interaction with Components', function() {
beforeEach(function () {
this.wrapper = this.gjs.editor.get('Components').getWrapper().get('components');
this.$clm = instClassTagViewer(this);
});
afterEach(function () {
delete this.wrapper;
delete this.$clm;
});
it('Assign correctly new class to component', function() {
var model = this.wrapper.add({});
model.get('classes').length.should.equal(0);
this.gjs.editor.set('selectedComponent', model);
this.$clm.addNewTag('test');
model.get('classes').length.should.equal(1);
model.get('classes').at(0).get('name').should.equal('test');
});
it('Classes from components are correctly imported inside main container', function() {
var model = this.wrapper.add([
{ classes: ['test11', 'test12', 'test13'] },
{ classes: ['test11', 'test22', 'test22'] },
]);
this.gjs.editor.get('SelectorManager').getAll().length.should.equal(4);
});
it('Class imported into component is the same model from main container', function() {
var model = this.wrapper.add({ classes: ['test1'] });
var clModel = model.get('classes').at(0);
var clModel2 = this.gjs.editor.get('SelectorManager').getAll().at(0);
clModel.should.deep.equal(clModel2);
});
it('Can assign only one time the same class on selected component and the class viewer', function() {
var model = this.wrapper.add({});
this.gjs.editor.set('selectedComponent', model);
this.$clm.addNewTag('test');
this.$clm.addNewTag('test');
model.get('classes').length.should.equal(1);
model.get('classes').at(0).get('name').should.equal('test');
this.$clm.collection.length.should.equal(1);
this.$clm.collection.at(0).get('name').should.equal('test');
});
it('Removing from container removes also from selected component', function() {
var model = this.wrapper.add({});
this.gjs.editor.set('selectedComponent', model);
this.$clm.addNewTag('test');
this.$clm.getClasses().find('.tag #close').trigger('click')
model.get('classes').length.should.equal(0);
});
it("Trigger correctly event on target with new class add", function() {
var spy = sinon.spy();
var model = this.wrapper.add({});
this.gjs.editor.set('selectedComponent', model);
this.$clm.addNewTag('test');
this.gjs.editor.on("targetClassAdded", spy);
this.$clm.addNewTag('test');
spy.called.should.equal(false);
this.$clm.addNewTag('test2');
spy.called.should.equal(true);
});
});
});
}
};
});

94
test/specs/selector_manager/main.js

@ -0,0 +1,94 @@
var modulePath = './../../../test/specs/selector_manager';
define([
'SelectorManager',
modulePath + '/model/SelectorModels',
modulePath + '/view/ClassTagView',
modulePath + '/view/ClassTagsView',
modulePath + '/e2e/ClassManager'
],
function(
SelectorManager,
Models,
ClassTagView,
ClassTagsView,
e2e
) {
describe('SelectorManager', function() {
describe('Main', function() {
var obj;
beforeEach(function () {
obj = new SelectorManager().init();
});
afterEach(function () {
delete obj;
});
it('Object exists', function() {
obj.should.be.exist;
});
it('No selectors inside', function() {
obj.getAll().length.should.equal(0);
});
it('Able to add default selectors', function() {
var cm = new SelectorManager().init({
selectors: ['test1', 'test2', 'test3'],
});
cm.getAll().length.should.equal(3);
});
it('Add new selector', function() {
obj.add('test');
obj.getAll().length.should.equal(1);
});
it('Default new selector is a class type', function() {
obj.add('test');
obj.get('test').get('type').should.equal('class');
});
it('Check name property', function() {
var name = 'test';
var sel = obj.add(name);
sel.get('name').should.equal(name);
sel.get('label').should.equal(name);
});
it('Add 2 selectors', function() {
obj.add('test');
obj.add('test2');
obj.getAll().length.should.equal(2);
});
it('Adding 2 selectors with the same name is not possible', function() {
obj.add('test');
obj.add('test');
obj.getAll().length.should.equal(1);
});
it('Get selector', function() {
var name = 'test';
var sel = obj.add(name);
obj.get(name).should.deep.equal(sel);
});
it('Get empty class', function() {
(obj.get('test') === undefined).should.equal(true);
});
});
Models.run();
ClassTagView.run();
ClassTagsView.run();
e2e.run();
});
});

53
test/specs/selector_manager/model/SelectorModels.js

@ -0,0 +1,53 @@
var path = 'SelectorManager/model/';
define([path + 'Selector',
path + 'Selectors'],
function(Selector, Selectors) {
return {
run : function(){
describe('Selector', function() {
beforeEach(function () {
this.obj = new Selector();
});
afterEach(function () {
delete this.obj;
});
it('Has name property', function() {
this.obj.has('name').should.equal(true);
});
it('Has label property', function() {
this.obj.has('label').should.equal(true);
});
it('Has active property', function() {
this.obj.has('active').should.equal(true);
});
it('escapeName test', function() {
this.obj.escapeName('@Te sT*').should.equal('-te-st-');
});
it('Name is corrected at instantiation', function() {
this.obj = new Selector({ name: '@Te sT*'});
this.obj.get('name').should.equal('-te-st-');
});
});
describe('Selectors', function() {
it('Creates collection item correctly', function() {
var c = new Selectors();
var m = c.add({});
m.should.be.an.instanceOf(Selector);
});
});
}
};
});

132
test/specs/selector_manager/view/ClassTagView.js

@ -0,0 +1,132 @@
var path = 'SelectorManager/view/';
define([path + 'ClassTagView', 'SelectorManager/model/Selectors'],
function(ClassTagView, Selectors) {
return {
run : function(){
describe('ClassTagView', function() {
var obj;
var fixture;
var fixtures;
var testLabel;
var coll;
before(function () {
fixtures = $("#fixtures");
fixture = $('<div class="classtag-fixture"></div>');
});
beforeEach(function () {
coll = new Selectors();
testLabel = 'TestLabel';
var model = coll.add({
name: 'test',
label: testLabel,
});
obj = new ClassTagView({
config : {},
model: model,
coll: coll
});
obj.target = { get:function(){} };
_.extend(obj.target, Backbone.Events);
fixture.empty().appendTo(fixtures);
fixture.html(obj.render().el);
});
afterEach(function () {
delete obj.model;
});
after(function () {
fixture.remove();
});
it('Object exists', function() {
ClassTagView.should.be.exist;
});
it('Not empty', function() {
var $el = obj.$el;
$el.html().should.not.be.empty;
});
it('Not empty', function() {
var $el = obj.$el;
$el.html().should.contain(testLabel);
});
describe('Should be rendered correctly', function() {
it('Has close button', function() {
var $el = obj.$el;
$el.find('#close').should.have.property(0);
});
it('Has checkbox', function() {
var $el = obj.$el;
$el.find('#checkbox').should.have.property(0);
});
it('Has label', function() {
var $el = obj.$el;
$el.find('#tag-label').should.have.property(0);
});
});
it('Could be removed', function() {
var spy = sinon.spy();
obj.config.target = { get:function(){} };
sinon.stub(obj.config.target, 'get').returns(0);
obj.$el.find('#close').trigger('click');
fixture.html().should.be.empty;
});
it('On remove triggers event', function() {
var spy = sinon.spy();
sinon.stub(obj.target, 'get').returns(0);
obj.target.on("targetClassRemoved", spy);
obj.$el.find('#close').trigger('click');
spy.called.should.equal(true);
});
it('Checkbox toggles status', function() {
var spy = sinon.spy();
obj.model.on("change:active", spy);
obj.model.set('active', true);
obj.$el.find('#checkbox').trigger('click');
obj.model.get('active').should.equal(false);
spy.called.should.equal(true);
});
it('On toggle triggers event', function() {
var spy = sinon.spy();
sinon.stub(obj.target, 'get').returns(0);
obj.target.on("targetClassUpdated", spy);
obj.$el.find('#checkbox').trigger('click');
spy.called.should.equal(true);
});
it('Label input is disabled', function() {
var inputProp = obj.inputProp;
obj.$labelInput.prop(inputProp).should.equal(true);
});
it('On double click label input is enable', function() {
var inputProp = obj.inputProp;
obj.$el.find('#tag-label').trigger('dblclick');
obj.$labelInput.prop(inputProp).should.equal(false);
});
it('On blur label input turns back disabled', function() {
var inputProp = obj.inputProp;
obj.$el.find('#tag-label').trigger('dblclick');
obj.endEditTag();
obj.$labelInput.prop(inputProp).should.equal(true);
});
});
}
};
});

195
test/specs/selector_manager/view/ClassTagsView.js

@ -0,0 +1,195 @@
var path = 'SelectorManager/view/';
define([path + 'ClassTagsView', 'SelectorManager/model/Selectors'],
function(ClassTagsView, Selectors) {
return {
run : function(){
describe('ClassTagsView', function() {
var view;
var fixture;
var fixtures;
var testLabel;
var coll;
var target;
before(function () {
fixtures = $("#fixtures");
fixture = $('<div class="classtag-fixture"></div>');
});
beforeEach(function () {
target = { get: function(){} };
coll = new Selectors();
_.extend(target, Backbone.Events);
view = new ClassTagsView({
config : { em: target },
collection: coll
});
this.targetStub = {
add: function(v){ return {name: v}; }
};
this.compTargetStub = {
get: function(){ return { add: function(){} }}
};
fixture.empty().appendTo(fixtures);
fixture.html(view.render().el);
this.btnAdd = view.$el.find('#' + view.addBtnId);
this.input = view.$el.find('input#' + view.newInputId);
this.$tags = fixture.find('#tags-c');
this.$states = fixture.find('#states');
this.$statesC = fixture.find('#input-c');
});
afterEach(function () {
delete view.collection;
});
after(function () {
fixture.remove();
});
it('Object exists', function() {
ClassTagsView.should.be.exist;
});
it('Not tags inside', function() {
this.$tags.html().should.equal('');
});
it('Add new tag triggers correct method', function() {
sinon.stub(view, "addToClasses");
coll.add({ name: 'test' });
view.addToClasses.calledOnce.should.equal(true);
});
it('Start new tag creation', function() {
this.btnAdd.click();
(this.btnAdd.css('display') == 'none').should.equal(true);
(this.input.css('display') !== 'none').should.equal(true);
});
it('Stop tag creation', function() {
this.btnAdd.click();
this.input.val('test')
this.input.blur();
(this.btnAdd.css('display') !== 'none').should.equal(true);
(this.input.css('display') == 'none').should.equal(true);
this.input.val().should.equal('');
});
it('Check keyup of ESC on input', function() {
this.btnAdd.click();
sinon.stub(view, "addNewTag");
this.input.trigger({
type: 'keyup',
keyCode: 13
});
view.addNewTag.calledOnce.should.equal(true);
});
it('Check keyup on ENTER on input', function() {
this.btnAdd.click();
sinon.stub(view, "endNewTag");
this.input.trigger({
type: 'keyup',
keyCode: 27
});
view.endNewTag.calledOnce.should.equal(true);
});
it('Collection changes on update of target', function() {
coll.add({ name: 'test' });
target.trigger('change:selectedComponent');
coll.length.should.equal(0);
});
it('Collection reacts on reset', function() {
coll.add([{ name: 'test1' }, { name: 'test2' }]);
sinon.stub(view, "addToClasses");
coll.trigger('reset');
view.addToClasses.calledTwice.should.equal(true);
});
it("Don't accept empty tags", function() {
view.addNewTag('');
this.$tags.html().should.equal('');
});
it("Accept new tags", function() {
sinon.stub(target, "get").returns(this.targetStub);
view.compTarget = this.compTargetStub;
view.addNewTag('test');
view.compTarget = this.compTargetStub;
view.addNewTag('test2');
this.$tags.children().length.should.equal(2);
});
it("New tag correctly added", function() {
coll.add({ label: 'test' });
this.$tags.children().first().find('#tag-label input').val().should.equal('test');
});
it("States are hidden in case no tags", function() {
view.updateStateVis();
this.$statesC.css('display').should.equal('none');
});
it("States are visible in case of more tags inside", function() {
coll.add({ label: 'test' });
view.updateStateVis();
this.$statesC.css('display').should.equal('block');
});
it("Update state visibility on new tag", function() {
sinon.stub(view, "updateStateVis");
sinon.stub(target, "get").returns(this.targetStub);
view.compTarget = this.compTargetStub;
view.addNewTag('test');
view.updateStateVis.called.should.equal(true);
});
it("Update state visibility on removing of the tag", function() {
sinon.stub(target, "get").returns(this.targetStub);
view.compTarget = this.compTargetStub;
view.addNewTag('test');
sinon.stub(view, "updateStateVis");
coll.remove(coll.at(0));
view.updateStateVis.calledOnce.should.equal(true);
});
it("Output correctly state options", function() {
var view = new ClassTagsView({
config : {
em: target,
states: [ { name: 'testName', label: 'testLabel' } ],
},
collection: coll
});
view.getStateOptions().should.equal('<option value="testName">testLabel</option>');
});
describe('Should be rendered correctly', function() {
it('Has label', function() {
view.$el.find('#label').should.have.property(0);
});
it('Has tags container', function() {
view.$el.find('#tags-c').should.have.property(0);
});
it('Has add button', function() {
view.$el.find('#add-tag').should.have.property(0);
});
it('Has states input', function() {
view.$el.find('#states').should.have.property(0);
});
});
});
}
};
});
Loading…
Cancel
Save