Tested XOSModel methods, getChoices is missing test
diff --git a/xos/core/xoslib/.eslintrc b/xos/core/xoslib/.eslintrc
index 35b0e35..211611c 100644
--- a/xos/core/xoslib/.eslintrc
+++ b/xos/core/xoslib/.eslintrc
@@ -13,7 +13,8 @@
"beforeColon": false,
"afterColon": true
}],
- "indent": [2, 2]
+ "indent": [2, 4],
+ "valid-jsdoc": 2
},
"globals": {
}
diff --git a/xos/core/xoslib/karma.conf.js b/xos/core/xoslib/karma.conf.js
index 4beb3b7..5eba2fe 100644
--- a/xos/core/xoslib/karma.conf.js
+++ b/xos/core/xoslib/karma.conf.js
@@ -1,6 +1,8 @@
// Karma configuration
// Generated on Tue Oct 06 2015 09:27:10 GMT+0000 (UTC)
+/* eslint indent: [2,2], quotes: [2, "single"]*/
+
/*eslint-disable*/
module.exports = function(config) {
/*eslint-enable*/
diff --git a/xos/core/xoslib/spec/xoslib/xos-backbone.test.js b/xos/core/xoslib/spec/xoslib/xos-backbone.test.js
index ec1b939..32f1551 100644
--- a/xos/core/xoslib/spec/xoslib/xos-backbone.test.js
+++ b/xos/core/xoslib/spec/xoslib/xos-backbone.test.js
@@ -23,15 +23,75 @@
});
describe('listMethods method', () => {
- it('should list all methods in instance', () => {
- const instance = {
- m1: () => {},
- m2: () => {}
- };
+
+ const instance = {
+ m1: () => {},
+ m2: () => {}
+ };
+ it('should list all methods in instance', () => {
let res = model.listMethods.apply(instance);
expect(res.length).toBe(2);
expect(res[0]).toEqual('m1');
});
});
+
+ describe('the Save method', () => {
+ const ctxPS = {
+ preSave: () => {}
+ };
+
+ const args = ['attr', 'opts'];
+
+ beforeEach(() => {
+ spyOn(ctxPS, 'preSave');
+ spyOn(Backbone.Model.prototype, 'save');
+ });
+
+ it('should call the preSave method', () => {
+ model.save.apply(ctxPS, args);
+ expect(ctxPS.preSave).toHaveBeenCalled();
+ expect(Backbone.Model.prototype.save).toHaveBeenCalledWith(args[0], args[1]);
+ });
+
+ it('should not call the preSave method', () => {
+ model.save.apply({}, args);
+ expect(ctxPS.preSave).not.toHaveBeenCalled();
+ expect(Backbone.Model.prototype.save).toHaveBeenCalledWith(args[0], args[1]);
+ });
+ });
+
+ describe('the getChoices method', () => {
+
+ const instance = {
+ m2mFields: {'flavors': 'flavors', 'sites': 'sites', 'images': 'images'}
+ };
+
+ xit('should be tested, what is this doing?', () => {
+ model.getChoices.apply(instance);
+ });
+ });
+
+ describe('the xosValidate method', () => {
+
+ const instance = {
+ validators: {'network_ports': ['portspec']}
+ }
+
+ const validAttrs = {network_ports: 'tcp 123'};
+
+ it('should call specified validator on a field and pass', () => {
+ let err = model.xosValidate.apply(instance, [validAttrs]);
+ expect(err).toBeUndefined();
+ });
+
+ // set wrong value and recall xosValidate
+ const invalidAttrs = {network_ports: 'abd 456'};
+
+ it('should call specified validator on a field and not pass', () => {
+ let err = model.xosValidate.apply(instance, [invalidAttrs]);
+ expect(err).not.toBeUndefined();
+ expect(err).toEqual({network_ports: 'must be a valid portspec (example: \'tcp 123, udp 456-789\')'});
+ });
+ });
});
\ No newline at end of file
diff --git a/xos/core/xoslib/static/js/xoslib/xos-backbone.js b/xos/core/xoslib/static/js/xoslib/xos-backbone.js
index 74f62ef..3e43279 100644
--- a/xos/core/xoslib/static/js/xoslib/xos-backbone.js
+++ b/xos/core/xoslib/static/js/xoslib/xos-backbone.js
@@ -53,114 +53,123 @@
/* from backbone-tastypie.js */
url: function() {
- // TODO handle error if no property
- var url = this.attributes.resource_uri;
+ // TODO handle error if no property
+ var url = this.attributes.resource_uri;
- if (!url) {
- if (this.id) {
- url = this.urlRoot + this.id;
- } else {
- // this happens when creating a new model.
- url = this.urlRoot;
+ if (!url) {
+ if (this.id) {
+ url = this.urlRoot + this.id;
+ } else {
+ // this happens when creating a new model.
+ url = this.urlRoot;
+ }
+ }
+
+ if (!url) {
+ // XXX I'm not sure this does anything useful
+ url = ( _.isFunction( this.collection.url ) ? this.collection.url() : this.collection.url );
+ url = url || this.urlRoot;
+ }
+
+ // remove any existing query parameters
+ url && ( url.indexOf("?") > -1 ) && ( url = url.split("?")[0] );
+
+ url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
+
+ url && ( url += "?no_hyperlinks=1" );
+
+ return url;
+ },
+
+ listMethods: function() {
+ var res = [];
+ for(var m in this) {
+ if(typeof this[m] == "function") {
+ res.push(m)
+ }
+ }
+ return res;
+ },
+
+ save: function(attributes, options) {
+ if (this.preSave) {
+ this.preSave();
+ }
+ return Backbone.Model.prototype.save.call(this, attributes, options);
+ },
+
+ getChoices: function(fieldName, excludeChosen) {
+ choices=[];
+ console.log(xos);
+ if (fieldName in this.m2mFields) {
+ for (index in xos[this.m2mFields[fieldName]].models) {
+ candidate = xos[this.m2mFields[fieldName]].models[index];
+ if (excludeChosen && idInArray(candidate.id, this.attributes[fieldName])) {
+ continue;
+ }
+ choices.push(candidate.id);
+ }
+ }
+ return choices;
+ },
+
+ /* If a 'validate' method is supplied, then it will be called
+ automatically on save. Unfortunately, save calls neither the
+ 'error' nor the 'success' callback if the validator fails.
+
+ For now, we're calling our validator 'xosValidate' so this
+ autoamtic validation doesn't occur.
+ */
+
+ /**
+ * This should call custom validators defined in the model
+ *
+ * @param {object} attrs an object containin the model attributes
+ * @param {object} options (unused)
+ * @returns {Array} Errors list
+ */
+ xosValidate: function(attrs, options) {
+ errors = {};
+ foundErrors = false;
+ _.each(this.validators, function(validatorList, fieldName) {
+ _.each(validatorList, function(validator) {
+ if (fieldName in attrs) {
+ // call validateField method in xos-utils.js
+ validatorResult = validateField(validator, attrs[fieldName], this)
+ if (validatorResult != true) {
+ errors[fieldName] = validatorResult;
+ foundErrors = true;
}
}
-
- if (!url) {
- // XXX I'm not sure this does anything useful
- url = ( _.isFunction( this.collection.url ) ? this.collection.url() : this.collection.url );
- url = url || this.urlRoot;
- }
-
- // remove any existing query parameters
- url && ( url.indexOf("?") > -1 ) && ( url = url.split("?")[0] );
-
- url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
-
- url && ( url += "?no_hyperlinks=1" );
-
- return url;
- },
-
- listMethods: function() {
- var res = [];
- for(var m in this) {
- if(typeof this[m] == "function") {
- res.push(m)
- }
- }
- return res;
- },
-
- save: function(attributes, options) {
- if (this.preSave) {
- this.preSave();
- }
- return Backbone.Model.prototype.save.call(this, attributes, options);
- },
-
- getChoices: function(fieldName, excludeChosen) {
- choices=[];
- if (fieldName in this.m2mFields) {
- for (index in xos[this.m2mFields[fieldName]].models) {
- candidate = xos[this.m2mFields[fieldName]].models[index];
- if (excludeChosen && idInArray(candidate.id, this.attributes[fieldName])) {
- continue;
- }
- choices.push(candidate.id);
- }
- }
- return choices;
- },
-
- /* If a 'validate' method is supplied, then it will be called
- automatically on save. Unfortunately, save calls neither the
- 'error' nor the 'success' callback if the validator fails.
-
- For now, we're calling our validator 'xosValidate' so this
- autoamtic validation doesn't occur.
- */
-
- xosValidate: function(attrs, options) {
- errors = {};
- foundErrors = false;
- _.each(this.validators, function(validatorList, fieldName) {
- _.each(validatorList, function(validator) {
- if (fieldName in attrs) {
- validatorResult = validateField(validator, attrs[fieldName], this)
- if (validatorResult != true) {
- errors[fieldName] = validatorResult;
- foundErrors = true;
- }
- }
- });
});
- if (foundErrors) {
- return errors;
- }
- // backbone.js semantics -- on successful validate, return nothing
- },
+ });
+ if (foundErrors) {
+ return errors;
+ }
+ // backbone.js semantics -- on successful validate, return nothing
+ },
- /* uncommenting this would make validate() call xosValidate()
- validate: function(attrs, options) {
- r = this.xosValidate(attrs, options);
- console.log("validate");
- console.log(r);
- return r;
- }, */
+ /* uncommenting this would make validate() call xosValidate()
+ validate: function(attrs, options) {
+ r = this.xosValidate(attrs, options);
+ console.log("validate");
+ console.log(r);
+ return r;
+ }, */
});
XOSCollection = Backbone.Collection.extend({
objects: function() {
- return this.models.map(function(element) { return element.attributes; });
- },
+ return this.models.map(function(element) { return element.attributes; });
+ },
initialize: function(){
- this.isLoaded = false;
- this.failedLoad = false;
- this.startedLoad = false;
- this.sortVar = 'name';
- this.sortOrder = 'asc';
- this.on( "sort", this.sorted );
+ this.isLoaded = false;
+ this.failedLoad = false;
+ this.startedLoad = false;
+ this.sortVar = 'name';
+ this.sortOrder = 'asc';
+ this.on( "sort", this.sorted );
},
relatedCollections: [],
@@ -175,12 +184,12 @@
},
simpleComparator: function( model ){
- parts=this.sortVar.split(".");
- result = model.get(parts[0]);
- for (index=1; index<parts.length; ++index) {
- result=result[parts[index]];
- }
- return result;
+ parts=this.sortVar.split(".");
+ result = model.get(parts[0]);
+ for (index=1; index<parts.length; ++index) {
+ result=result[parts[index]];
+ }
+ return result;
},
comparator: function (left, right) {
@@ -190,7 +199,7 @@
if (l === void 0) return -1;
if (r === void 0) return 1;
- if (this.sortOrder=="desc") {
+ if (this.sortOrder == "desc") {
return l < r ? 1 : l > r ? -1 : 0;
} else {
return l < r ? -1 : l > r ? 1 : 0;
@@ -276,8 +285,8 @@
}
// when the original success function completes mark this collection as fetched
- var self = this,
- successWrapper = function(success){
+ var self = this;
+ var successWrapper = function(success){
return function(){
self._fetched = true;
success && success.apply(this, arguments);
@@ -313,35 +322,35 @@
*/
filterBy: function(fieldName, value) {
- filtered = this.filter(function(obj) {
- return obj.get(fieldName) == value;
- });
- return new this.constructor(filtered);
+ filtered = this.filter(function(obj) {
+ return obj.get(fieldName) == value;
+ });
+ return new this.constructor(filtered);
},
/* from backbone-tastypie.js */
url: function( models ) {
- var url = this.urlRoot || ( models && models.length && models[0].urlRoot );
- url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
+ var url = this.urlRoot || ( models && models.length && models[0].urlRoot );
+ url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
- url && ( url += "?no_hyperlinks=1" );
+ url && ( url += "?no_hyperlinks=1" );
- if (this.currentUserCanSee) {
- url && ( url += "¤t_user_can_see=1" );
- }
+ if (this.currentUserCanSee) {
+ url && ( url += "¤t_user_can_see=1" );
+ }
- return url;
- },
+ return url;
+ },
listMethods: function() {
- var res = [];
- for(var m in this) {
- if(typeof this[m] == "function") {
- res.push(m)
- }
+ var res = [];
+ for(var m in this) {
+ if(typeof this[m] == "function") {
+ res.push(m)
}
- return res;
- },
+ }
+ return res;
+ },
});
function get_defaults(modelName) {
@@ -486,27 +495,27 @@
});
define_model(this, {urlRoot: SLICE_API,
- relatedCollections: {"instances": "slice", "slicePrivileges": "slice", "networks": "owner", "controller_slices": "slice"},
- foreignCollections: ["services", "sites"],
- foreignFields: {"service": "services", "site": "sites"},
- listFields: ["backend_status", "id", "name", "enabled", "description", "slice_url", "site", "max_instances", "service"],
- detailFields: ["backend_status", "backend_register", "name", "site", "enabled", "description", "slice_url", "max_instances"],
- inputType: {"enabled": "checkbox"},
- modelName: "slice",
- xosValidate: function(attrs, options) {
- errors = XOSModel.prototype.xosValidate.call(this, attrs, options);
- // validate that slice.name starts with site.login_base
- site = attrs.site || this.site;
- if ((site!=undefined) && (attrs.name!=undefined)) {
- site = xos.sites.get(site);
- if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
+ relatedCollections: {"instances": "slice", "slicePrivileges": "slice", "networks": "owner", "controller_slices": "slice"},
+ foreignCollections: ["services", "sites"],
+ foreignFields: {"service": "services", "site": "sites"},
+ listFields: ["backend_status", "id", "name", "enabled", "description", "slice_url", "site", "max_instances", "service"],
+ detailFields: ["backend_status", "backend_register", "name", "site", "enabled", "description", "slice_url", "max_instances"],
+ inputType: {"enabled": "checkbox"},
+ modelName: "slice",
+ xosValidate: function(attrs, options) {
+ errors = XOSModel.prototype.xosValidate.call(this, attrs, options);
+ // validate that slice.name starts with site.login_base
+ site = attrs.site || this.site;
+ if ((site!=undefined) && (attrs.name!=undefined)) {
+ site = xos.sites.get(site);
+ if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
errors = errors || {};
errors["name"] = "must start with " + site.attributes.login_base + "_";
- }
- }
- return errors;
- },
- });
+ }
+ }
+ return errors;
+ },
+ });
define_model(this, {urlRoot: SLICEPRIVILEGE_API,
foreignCollections: ["slices", "users", "sliceRoles"],
@@ -699,30 +708,30 @@
// enhanced REST
// XXX this really needs to somehow be combined with Slice, to avoid duplication
define_model(this, {urlRoot: SLICEPLUS_API,
- relatedCollections: {"instances": "slice", "slicePrivileges": "slice", "networks": "owner"},
- foreignCollections: ["services", "sites"],
- foreignFields: {"service": "services", "site": "sites"},
- listFields: ["backend_status", "id", "name", "enabled", "description", "slice_url", "site", "max_instances", "service"],
- detailFields: ["backend_status", "backend_register", "name", "site", "enabled", "description", "slice_url", "max_instances"],
- inputType: {"enabled": "checkbox"},
- modelName: "slicePlus",
- collectionName: "slicesPlus",
- defaults: extend_defaults("slice", {"network_ports": "", "site_allocation": []}),
- validators: {"network_ports": ["portspec"]},
- xosValidate: function(attrs, options) {
- errors = XOSModel.prototype.xosValidate.call(this, attrs, options);
- // validate that slice.name starts with site.login_base
- site = attrs.site || this.site;
- if ((site!=undefined) && (attrs.name!=undefined)) {
- site = xos.sites.get(site);
- if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
+ relatedCollections: {"instances": "slice", "slicePrivileges": "slice", "networks": "owner"},
+ foreignCollections: ["services", "sites"],
+ foreignFields: {"service": "services", "site": "sites"},
+ listFields: ["backend_status", "id", "name", "enabled", "description", "slice_url", "site", "max_instances", "service"],
+ detailFields: ["backend_status", "backend_register", "name", "site", "enabled", "description", "slice_url", "max_instances"],
+ inputType: {"enabled": "checkbox"},
+ modelName: "slicePlus",
+ collectionName: "slicesPlus",
+ defaults: extend_defaults("slice", {"network_ports": "", "site_allocation": []}),
+ validators: {"network_ports": ["portspec"]},
+ xosValidate: function(attrs, options) {
+ errors = XOSModel.prototype.xosValidate.call(this, attrs, options);
+ // validate that slice.name starts with site.login_base
+ site = attrs.site || this.site;
+ if ((site!=undefined) && (attrs.name!=undefined)) {
+ site = xos.sites.get(site);
+ if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
errors = errors || {};
errors["name"] = "must start with " + site.attributes.login_base + "_";
- }
- }
- return errors;
- },
- });
+ }
+ }
+ return errors;
+ },
+ });
define_model(this, {urlRoot: TENANTVIEW_API,
modelName: "tenantview",
@@ -804,13 +813,13 @@
}
(function() {
- var _sync = Backbone.sync;
- Backbone.sync = function(method, model, options){
- options.beforeSend = function(xhr){
- var token = getCookie("csrftoken");
- xhr.setRequestHeader('X-CSRFToken', token);
+ var _sync = Backbone.sync;
+ Backbone.sync = function(method, model, options){
+ options.beforeSend = function(xhr){
+ var token = getCookie("csrftoken");
+ xhr.setRequestHeader('X-CSRFToken', token);
+ };
+ return _sync(method, model, options);
};
- return _sync(method, model, options);
- };
})();
}
diff --git a/xos/core/xoslib/static/js/xoslib/xosHelper.js b/xos/core/xoslib/static/js/xoslib/xosHelper.js
index 1352c69..54ecba5 100644
--- a/xos/core/xoslib/static/js/xoslib/xosHelper.js
+++ b/xos/core/xoslib/static/js/xoslib/xosHelper.js
@@ -55,16 +55,16 @@
});
XOSRouter = Marionette.AppRouter.extend({
- initialize: function() {
- this.routeStack=[];
- },
-
- onRoute: function(x,y,z) {
- this.routeStack.push(Backbone.history.fragment);
- this.routeStack = this.routeStack.slice(-32); // limit the size of routeStack to something reasonable
- },
-
- prevPage: function() {
+ initialize: function() {
+ this.routeStack=[];
+ },
+
+ onRoute: function(x,y,z) {
+ this.routeStack.push(Backbone.history.fragment);
+ this.routeStack = this.routeStack.slice(-32); // limit the size of routeStack to something reasonable
+ },
+
+ prevPage: function() {
return this.routeStack.slice(-1)[0];
},
@@ -83,14 +83,14 @@
}
Marionette.AppRouter.prototype.navigate.call(this, href, options);
},
- });
-
-// XXX - We import backbone multiple times (BAD!) since the import happens
-// inside of the view's html. The second time it's imported (developer
-// view), it wipes out Backbone.Syphon. So, save it as Backbone_Syphon for
-// now.
-Backbone_Syphon = Backbone.Syphon
-Backbone_Syphon.InputReaders.register('select', function(el) {
+ });
+
+// XXX - We import backbone multiple times (BAD!) since the import happens
+// inside of the view's html. The second time it's imported (developer
+// view), it wipes out Backbone.Syphon. So, save it as Backbone_Syphon for
+// now.
+Backbone_Syphon = Backbone.Syphon
+Backbone_Syphon.InputReaders.register('select', function(el) {
// Modify syphon so that if a select has "syphonall" in the class, then
// the value of every option will be returned, regardless of whether of
// not it is selected.
@@ -564,7 +564,7 @@
var that = this;
var isNew = !this.model.id;
- console.log(data);
+ console.log('data', data);
this.$el.find(".help-inline").remove();
@@ -982,77 +982,77 @@
};
//console.log(aoData);
-
- // function used to populate the DataTable with the current
- // content of the collection
- var populateTable = function()
- {
- //console.log("populatetable!");
-
- // clear out old row views
- rows = [];
-
- sSearch = null;
- iDisplayStart = 0;
- iDisplayLength = 1000;
- sortDirs = [];
- sortCols = [];
- _.each(aoData, function(param) {
- if (param.name == "sSortDir_0") {
- sortDirs = [param.value];
- } else if (param.name == "iSortCol_0") {
- sortCols = [view.columnsByIndex[param.value].mData];
- } else if (param.name == "iDisplayStart") {
- iDisplayStart = param.value;
- } else if (param.name == "iDisplayLength") {
- iDisplayLength = param.value;
- } else if (param.name == "sSearch") {
- sSearch = param.value;
- }
- });
-
- aaData = view.collection.toJSON();
-
- // apply backbone filtering on the models
- if (view.filter) {
- aaData = aaData.filter( function(row) { model = {}; model.attributes = row; return view.filter(model); } );
- }
-
- var totalSize = aaData.length;
-
- // turn the ForeignKey fields into human readable things
- for (rowIndex in aaData) {
- row = aaData[rowIndex];
- for (fieldName in row) {
- if (fieldName in view.columnsByFieldName) {
- mSearchText = view.columnsByFieldName[fieldName].mSearchText;
- if (mSearchText) {
- row[fieldName] = mSearchText(row[fieldName]);
- }
- }
- }
- }
-
- // apply datatables search
- if (sSearch) {
- aaData = aaData.filter( function(row) { return searchMatch(row, sSearch); });
- }
-
- var filteredSize = aaData.length;
-
- // apply datatables sort
- aaData.sort(function(a,b) { return compareColumns(sortCols, sortDirs, a, b); });
-
- // slice it for pagination
- if (iDisplayLength >= 0) {
- aaData = aaData.slice(iDisplayStart, iDisplayStart+iDisplayLength);
- }
-
- return fnCallback({iTotalRecords: totalSize,
- iTotalDisplayRecords: filteredSize,
- aaData: aaData});
- };
-
+
+ // function used to populate the DataTable with the current
+ // content of the collection
+ var populateTable = function()
+ {
+ //console.log("populatetable!");
+
+ // clear out old row views
+ rows = [];
+
+ sSearch = null;
+ iDisplayStart = 0;
+ iDisplayLength = 1000;
+ sortDirs = [];
+ sortCols = [];
+ _.each(aoData, function(param) {
+ if (param.name == "sSortDir_0") {
+ sortDirs = [param.value];
+ } else if (param.name == "iSortCol_0") {
+ sortCols = [view.columnsByIndex[param.value].mData];
+ } else if (param.name == "iDisplayStart") {
+ iDisplayStart = param.value;
+ } else if (param.name == "iDisplayLength") {
+ iDisplayLength = param.value;
+ } else if (param.name == "sSearch") {
+ sSearch = param.value;
+ }
+ });
+
+ aaData = view.collection.toJSON();
+
+ // apply backbone filtering on the models
+ if (view.filter) {
+ aaData = aaData.filter( function(row) { model = {}; model.attributes = row; return view.filter(model); } );
+ }
+
+ var totalSize = aaData.length;
+
+ // turn the ForeignKey fields into human readable things
+ for (rowIndex in aaData) {
+ row = aaData[rowIndex];
+ for (fieldName in row) {
+ if (fieldName in view.columnsByFieldName) {
+ mSearchText = view.columnsByFieldName[fieldName].mSearchText;
+ if (mSearchText) {
+ row[fieldName] = mSearchText(row[fieldName]);
+ }
+ }
+ }
+ }
+
+ // apply datatables search
+ if (sSearch) {
+ aaData = aaData.filter( function(row) { return searchMatch(row, sSearch); });
+ }
+
+ var filteredSize = aaData.length;
+
+ // apply datatables sort
+ aaData.sort(function(a,b) { return compareColumns(sortCols, sortDirs, a, b); });
+
+ // slice it for pagination
+ if (iDisplayLength >= 0) {
+ aaData = aaData.slice(iDisplayStart, iDisplayStart+iDisplayLength);
+ }
+
+ return fnCallback({iTotalRecords: totalSize,
+ iTotalDisplayRecords: filteredSize,
+ aaData: aaData});
+ };
+
aoData.shift(); // ignore sEcho
populateTable();