Skip to content

Commit

Permalink
Merge pull request #45 from naugtur/xhr2-upgrade
Browse files Browse the repository at this point in the history
Fixes #32, update to xhr2 with integration tests
  • Loading branch information
naugtur committed Jul 11, 2015
2 parents c342b40 + 18ec49e commit 5820e98
Show file tree
Hide file tree
Showing 5 changed files with 321 additions and 263 deletions.
4 changes: 2 additions & 2 deletions ampersand-sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ module.exports = function (method, model, options) {
// Make the request. The callback executes functions that are compatible
// With jQuery.ajax's syntax.
var request = options.xhr = options.xhrImplementation(ajaxSettings, function (err, resp, body) {
if (err) {
if (options.error) return options.error(resp, 'error', err.message);
if (err || resp.statusCode >= 400) {
if (options.error) return options.error(resp, 'error', err.message || body);
} else {
// Parse body as JSON if a string.
if (body && typeof body === 'string') {
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"lodash.includes": "^3.1.0",
"lodash.result": "^3.0.0",
"qs": "^2.2.4",
"xhr": "^1.10.0"
"xhr": "^2.0.1"
},
"devDependencies": {
"ampersand-model": "^4.0.1",
Expand Down Expand Up @@ -50,7 +50,7 @@
},
"scripts": {
"start": "run-browser test/*",
"test": "browserify test/* | tape-run | tap-spec"
"test": "browserify test/index.js | tape-run | tap-spec"
},
"testling": {
"files": "test/*.js",
Expand Down
261 changes: 2 additions & 259 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -1,259 +1,2 @@
var test = require('tape');
var sync = require('../ampersand-sync');
var Model = require('ampersand-model');


function getStub(data) {
return {
url: '/library',
trigger: function () {
// capture args for comparison
this.args = arguments;
},
toJSON: function () {
return data || {};
}
};
}

test('should allow models to overwrite ajax configs at the model level', function (t) {
t.plan(3);
var Me = Model.extend({
url: '/hi',
ajaxConfig: {
useXDR: true,
xhrFields: {
withCredentials: true
}
}
});
var m = new Me();
m.on('request', function (model, xhr, options, ajaxSettings) {
t.equal(ajaxSettings.type, 'GET');
t.equal(ajaxSettings.xhrFields.withCredentials, true);
t.equal(ajaxSettings.useXDR, true);
t.end();
});
var xhr = sync('read', m);
});

test('read', function (t) {
var xhr = sync('read', getStub());
t.equal(xhr.ajaxSettings.url, '/library');
t.equal(xhr.ajaxSettings.type, 'GET');
t.ok(!xhr.ajaxSettings.json);
t.ok(!xhr.ajaxSettings.data);
var xhr2 = sync('read', getStub(), {url: '/library/books'});
t.equal(xhr2.ajaxSettings.url, '/library/books', 'passed url should overwrite model url');
t.end();
});

test('passing data', function (t) {
// on reads it should be part of the URL
var xhr = sync('read', getStub(), {data: {a: 'a', one: 1}});
t.equal(xhr.ajaxSettings.url, '/library?a=a&one=1', 'data passed to reads should be made into a query string');

var modelStub = getStub();
modelStub.url = '/library?something=hi';
var xhr2 = sync('read', modelStub, {data: {a: 'a', one: 1}});
t.equal(xhr2.ajaxSettings.url, '/library?something=hi&a=a&one=1', 'data passed to reads should be appended to an existing query string in the url');

var xhr3 = sync('read', getStub(), {url: '/library/books', data: {a: 'a', one: 1}});
t.equal(xhr3.ajaxSettings.url, '/library/books?a=a&one=1', 'data passed to reads should be added as a query string to overwritten url');
t.end();
});

test('create', function (t) {
var xhr = sync('create', getStub({
title: 'The Tempest',
author: 'Bill Shakespeare',
length: 123
}));
t.equal(xhr.ajaxSettings.url, '/library');
t.equal(xhr.ajaxSettings.type, 'POST');
t.equal(xhr.ajaxSettings.headers['Content-Type'], 'application/json');
var data = xhr.ajaxSettings.json;
t.equal(data.title, 'The Tempest');
t.equal(data.author, 'Bill Shakespeare');
t.equal(data.length, 123);
t.end();
});

test('update', function (t) {
var xhr = sync('update', getStub({
id: '1-the-tempest',
author: 'William Shakespeare'
}));
t.equal(xhr.ajaxSettings.url, '/library');
t.equal(xhr.ajaxSettings.type, 'PUT');
t.equal(xhr.ajaxSettings.headers['Content-Type'], 'application/json');
var data = xhr.ajaxSettings.json;
t.equal(data.id, '1-the-tempest');
t.equal(data.author, 'William Shakespeare');
t.end();
});

test('update with emulateHTTP and emulateJSON', function (t) {
var xhr = sync('update', getStub({
id: '2-the-tempest',
author: 'Tim Shakespeare',
length: 123
}),
{
emulateHTTP: true,
emulateJSON: true
}
);
t.equal(xhr.ajaxSettings.url, '/library');
t.equal(xhr.ajaxSettings.type, 'POST');
t.equal(xhr.ajaxSettings.body, 'model%5Bid%5D=2-the-tempest&model%5Bauthor%5D=Tim%20Shakespeare&model%5Blength%5D=123&_method=PUT');
t.equal(xhr.ajaxSettings.headers['Content-Type'], 'application/x-www-form-urlencoded');
t.end();
});

test('update with just emulateHTTP', function (t) {
var xhr = sync('update', getStub({
id: '2-the-tempest',
author: 'Tim Shakespeare',
length: 123
}),
{
emulateHTTP: true
}
);
t.equal(xhr.ajaxSettings.url, '/library');
t.equal(xhr.ajaxSettings.type, 'POST');
t.equal(xhr.ajaxSettings.headers['Content-Type'], 'application/json');
var data = xhr.ajaxSettings.json;
t.equal(data.id, '2-the-tempest');
t.equal(data.author, 'Tim Shakespeare');
t.equal(data.length, 123);
t.end();
});


test('update with just emulateJSON', function (t) {
var xhr = sync('update', getStub({
id: '2-the-tempest',
author: 'Tim Shakespeare',
length: 123
}),
{
emulateJSON: true
}
);
t.equal(xhr.ajaxSettings.url, '/library');
t.equal(xhr.ajaxSettings.type, 'PUT');
t.equal(xhr.ajaxSettings.headers['Content-Type'], 'application/x-www-form-urlencoded');
t.equal(xhr.ajaxSettings.body, 'model%5Bid%5D=2-the-tempest&model%5Bauthor%5D=Tim%20Shakespeare&model%5Blength%5D=123');
t.end();
});

test('delete', function (t) {
var xhr = sync('delete', getStub({
author: 'Tim Shakespeare',
length: 123
}));
t.equal(xhr.ajaxSettings.url, '/library');
t.equal(xhr.ajaxSettings.type, 'DELETE');
t.notOk(xhr.ajaxSettings.data);
t.end();
});


test('destroy with emulateHTTP', function (t) {
var xhr = sync('delete', getStub({
author: 'Tim Shakespeare',
length: 123
}),
{
emulateHTTP: true,
emulateJSON: true
}
);
t.equal(xhr.ajaxSettings.url, '/library');
t.equal(xhr.ajaxSettings.type, 'POST');
t.equal(xhr.ajaxSettings.body, '_method=DELETE');
t.end();
});

test('urlError', function (t) {
t.throws(function () {
var xhr = sync('read', {});
}, Error);
t.end();
});

test('Call provided error callback on error.', function (t) {
t.plan(1);
var xhr = sync('read', getStub(), {
error: function () {
t.pass();
t.end();
}
});
xhr.ajaxSettings.error();
});

test('Call provided error callback is bad JSON error.', function (t) {
t.plan(3);

var xhr = sync('read', getStub(), {
error: function (resp, type, error) {
t.deepEqual(resp, {}, 'should be passed through response');
t.equal(type, 'error', 'is string \'error\' as per jquery');
t.ok(error=='Unable to parse JSON string' || error=='Unexpected end of input', 'should be json parse message');
t.end();
},
xhrImplementation: function (ajaxSettings, callback) {
callback(null, {}, '{"bad": "json');
return {};
}
});
});

test('Don\'t call success when error occurs and there\'s no error callback', function (t) {
t.plan(1);

var xhr = sync('read', getStub(), {
success: function (resp, type, error) {
t.fail('doh');
},
xhrImplementation: function (ajaxSettings, callback) {
callback(new Error(), {}, '{"good": "json"}');
t.pass();
t.end();
return {};
}
});
});


test('Call user provided beforeSend function.', function (t) {
t.plan(1);
var xhr = sync('delete', getStub(), {
beforeSend: function (_xhr) {
t.pass();
},
emulateHTTP: true
});
t.end();
});

test('Call user provided beforeSend function from model\'s ajaxConfig when no custom xhrFields are passed', function (t) {
t.plan(1);

var Me = Model.extend({
url: '/hi',
ajaxConfig: {
beforeSend: function (xhr) {
t.pass();
}
}
});

var m = new Me();
var xhr = sync('create', m);

t.end();
});
require('./unit');
require('./integration');
53 changes: 53 additions & 0 deletions test/integration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
var test = require('tape');
var sync = require('../ampersand-sync');
var Model = require('ampersand-model');


test('should get a response for read', function (t) {
t.plan(2);
var Me = Model.extend({
url: 'http://www.mocky.io/v2/54f1d2b932d8370a036e5b21',
ajaxConfig: {
useXDR: true
},
props: {
foo: 'string'
},
sync: sync //override with this sync, so fetch doesn't use the stable one from dependencies
});
var m = new Me();
m.on('change', function () {
//TODO: assert more arguments
t.equal(m.foo, "bar");
});
m.fetch({
success: function (data) {
//TODO: assert more arguments
t.equal(data.foo, "bar");
t.end();
},
error: function () {
t.fail('error while fetching (are you offline?)');
t.end();
}
});
});

test('should call error when read results in 404', function (t) {
t.plan(1);
var Me = Model.extend({
url: '/nothing',
sync: sync
});
var m = new Me();
m.fetch({
success: function () {
t.fail('unexpected success call');
t.end();
},
error: function () {
t.pass('received an expected error');
t.end();
}
});
});
Loading

0 comments on commit 5820e98

Please sign in to comment.