Skip to content
Snippets Groups Projects
Commit 231a4c0f authored by totten's avatar totten
Browse files

CRM-12943 - Add CRM.Backbone.sync with qUnit tests

----------------------------------------
* CRM-12943: Make HTML prototype of job UI functional
  http://issues.civicrm.org/jira/browse/CRM-12943
parent 45f0564b
No related branches found
No related tags found
No related merge requests found
......@@ -2,6 +2,161 @@
var CRM = (window.CRM) ? (window.CRM) : (window.CRM = {});
if (!CRM.Backbone) CRM.Backbone = {};
/**
* Backbone.sync provider which uses CRM.api() for I/O.
* To support CRUD operations, model classes must be defined with a "crmEntityName" property.
* To load collections using API queries, set the "crmCriteria" property or override the
* method "toCrmCriteria".
*
* @param method
* @param model
* @param options
*/
CRM.Backbone.sync = function(method, model, options) {
var isCollection = _.isArray(model.models);
if (isCollection) {
var apiOptions = {
success: function(data) {
// unwrap data
options.success(_.toArray(data.values));
},
error: function(data) {
// CRM.api displays errors by default, but Backbone.sync
// protocol requires us to override "error". This restores
// the default behavior.
$().crmError(data.error_message, ts('Error'));
options.error(data);
}
};
switch (method) {
case 'read':
CRM.api(model.crmEntityName, 'get', model.toCrmCriteria(), apiOptions);
break;
default:
apiOptions.error({is_error: 1, error_message: "CRM.Backbone.sync(" + method + ") not implemented for collections"});
break;
}
} else {
// callback options to pass to CRM.api
var apiOptions = {
success: function(data) {
// unwrap data
var values = _.toArray(data['values']);
if (values.length == 1) {
options.success(values[0]);
} else {
data.is_error = 1;
data.error_message = ts("Expected exactly one response");
apiOptions.error(data);
}
},
error: function(data) {
// CRM.api displays errors by default, but Backbone.sync
// protocol requires us to override "error". This restores
// the default behavior.
$().crmError(data.error_message, ts('Error'));
options.error(data);
}
};
switch (method) {
case 'create': // pass-through
case 'update':
CRM.api(model.crmEntityName, 'create', model.toJSON(), apiOptions);
break;
case 'read':
var params = model.toCrmCriteria();
if (!params.id) {
apiOptions.error({is_error: 1, error_message: 'Missing ID for ' + model.crmEntityName});
return;
}
CRM.api(model.crmEntityName, 'get', params, apiOptions);
break;
case 'delete':
default:
apiOptions.error({is_error: 1, error_message: "CRM.Backbone.sync(" + method + ") not implemented for models"});
}
}
};
/**
* Connect a "model" class to CiviCRM's APIv3
*
* @code
* // Setup class
* var ContactModel = Backbone.Model.extend({});
* CRM.Backbone.extendModel(ContactModel, "Contact");
*
* // Use class
* c = new ContactModel({id: 3});
* c.fetch();
* @endcode
*
* @param Class ModelClass
* @param string crmEntityName APIv3 entity name, such as "Contact" or "CustomField"
*/
CRM.Backbone.extendModel = function(ModelClass, crmEntityName) {
// Defaults - if specified in ModelClass, preserve
_.defaults(ModelClass.prototype, {
crmEntityName: crmEntityName,
toCrmCriteria: function() {
return (this.get('id')) ? {id: this.get('id')} : {};
}
});
// Overrides - if specified in ModelClass, replace
_.extend(ModelClass.prototype, {
sync: CRM.Backbone.sync
});
};
/**
* Connect a "collection" class to CiviCRM's APIv3
*
* Note: the collection supports a special property, crmCriteria, which is an array of
* query options to send to the API
*
* @code
* // Setup class
* var ContactModel = Backbone.Model.extend({});
* CRM.Backbone.extendModel(ContactModel, "Contact");
* var ContactCollection = Backbone.Collection.extend({
* model: ContactModel
* });
* CRM.Backbone.extendCollection(ContactCollection);
*
* // Use class
* var c = new ContactCollection([], {
* crmCriteria: {contact_type: 'Organization'}
* });
* c.fetch();
* @endcode
*
* @param Class CollectionClass
*/
CRM.Backbone.extendCollection = function(CollectionClass) {
var origInit = CollectionClass.prototype.initialize;
// Defaults - if specified in CollectionClass, preserve
_.defaults(CollectionClass.prototype, {
crmEntityName: CollectionClass.prototype.model.prototype.crmEntityName,
toCrmCriteria: function() {
return this.crmCriteria || {};
}
});
// Overrides - if specified in CollectionClass, replace
_.extend(CollectionClass.prototype, {
sync: CRM.Backbone.sync,
initialize: function(models, options) {
options || (options = {});
if (options.crmCriteria) {
this.crmCriteria = options.crmCriteria;
}
if (origInit) {
return origInit.apply(this, arguments);
}
}
});
};
CRM.Backbone.Model = Backbone.Model.extend({
/**
* Return JSON version of model -- but only include fields that are
......
/* ------ Fixtures/constants ----- */
var VALID_CONTACT_ID = 3;
var INVALID_CONTACT_ID = 'z';
var ContactModel = Backbone.Model.extend({});
CRM.Backbone.extendModel(ContactModel, 'Contact');
var ContactCollection = Backbone.Collection.extend({
model: ContactModel
});
CRM.Backbone.extendCollection(ContactCollection);
/* ------ Assertions ------ */
function assertApiError(result) {
equal(1, result.is_error, 'Expected error boolean');
ok(result.error_message.length > 0, 'Expected error message')
}
function onUnexpectedError(ignore, result) {
if (result && result.error_message) {
ok(false, "API returned an unexpected error: " + result.error_message);
} else {
ok(false, "API returned an unexpected error: (missing message)");
}
start();
}
function onUnexpectedSuccess(ignore) {
ok(false, "API succeeded - but failure was expected");
start();
}
/* ------ Test cases ------ */
module('model - read');
asyncTest("fetch (ok)", function() {
var c = new ContactModel({id: VALID_CONTACT_ID});
c.fetch({
error: onUnexpectedError,
success: function() {
notEqual(-1, _.indexOf(['Individual', 'Household', 'Organization'], c.get('contact_type')), 'Loaded contact with valid contact_type');
ok(c.get('display_name') != '', 'Loaded contact with valid name');
start();
}
});
});
asyncTest("fetch (error)", function() {
var c = new ContactModel({id: INVALID_CONTACT_ID});
c.fetch({
success: onUnexpectedSuccess,
error: function(model, error) {
assertApiError(error);
start();
}
});
});
module('model - update');
asyncTest("update (ok)", function() {
var NICKNAME = "George" + new Date().getTime();
var c = new ContactModel({id: VALID_CONTACT_ID});
c.save({
nick_name: NICKNAME
}, {
error: onUnexpectedError,
success: function() {
equal(c.get("nick_name"), NICKNAME, "save() should return new nickname");
var c2 = new ContactModel({id: VALID_CONTACT_ID});
c2.fetch({
error: onUnexpectedError,
success: function() {
equal(c2.get("nick_name"), NICKNAME, "fetch() should return new nickname");
start();
}
});
}
});
});
asyncTest("update (error)", function() {
var NICKNAME = "George" + new Date().getTime();
var c = new ContactModel({id: VALID_CONTACT_ID});
c.save({
contact_type: 'Not-a.va+lidConta(ype'
}, {
success: onUnexpectedSuccess,
error: function(model, error) {
assertApiError(error);
start();
}
});
});
module('collection - read');
asyncTest("fetch by contact_type (1+ results)", function() {
var c = new ContactCollection([], {
crmCriteria: {
contact_type: 'Organization'
}
});
c.fetch({
error: onUnexpectedError,
success: function() {
ok(c.models.length > 0, "Expected at least one contact");
c.each(function(model) {
equal(model.get('contact_type'), 'Organization', 'Expected contact with type organization');
ok(model.get('display_name') != '', 'Expected contact with valid name');
});
start();
}
});
});
asyncTest("fetch by crazy name (0 results)", function() {
var c = new ContactCollection([], {
crmCriteria: {
display_name: 'asdf23vmlk2309lk2lkasdk-23ASDF32f'
}
});
c.fetch({
error: onUnexpectedError,
success: function() {
equal(c.models.length, 0, "Expected no contacts");
start();
}
});
});
asyncTest("fetch by malformed ID (error)", function() {
var c = new ContactCollection([], {
crmCriteria: {
id: INVALID_CONTACT_ID
}
});
c.fetch({
success: onUnexpectedSuccess,
error: function(collection, error) {
assertApiError(error);
start();
}
});
});
<?php
CRM_Core_Resources::singleton()
->addScriptFile('civicrm', 'packages/backbone/json2.js', 100, 'html-header', FALSE)
->addScriptFile('civicrm', 'packages/backbone/underscore.js', 110, 'html-header', FALSE)
->addScriptFile('civicrm', 'packages/backbone/backbone.js', 120, 'html-header')
->addScriptFile('civicrm', 'packages/backbone/backbone.modelbinder.js', 125, 'html-header', FALSE)
->addScriptFile('civicrm', 'js/crm.backbone.js', 130, 'html-header', FALSE);
// CRM_Core_Resources::singleton()->addScriptFile(...);
// CRM_Core_Resources::singleton()->addStyleFile(...);
// CRM_Core_Resources::singleton()->addSetting(...);
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment