compago-ajax
Advanced tools
Comparing version 1.0.0 to 1.0.1
{ | ||
"name": "compago-ajax", | ||
"version": "1.0.0", | ||
"description": "", | ||
"version": "1.0.1", | ||
"description": "An AJAX storage engine for the Compago framework.", | ||
"main": "dist/node/index.js", | ||
"keywords": [ | ||
"ajax" | ||
"ajax", | ||
"storage", | ||
"compago" | ||
], | ||
@@ -18,3 +20,3 @@ "repository": { | ||
"scripts": { | ||
"build": "npm run clean && npm run build-node", | ||
"lint": "eslint src/index.js tests/unit.test.js", | ||
"report-coverage": "cat ./coverage/lcov.info | codecov", | ||
@@ -33,3 +35,3 @@ "test": "babel-node ./node_modules/istanbul/lib/cli cover node_modules/mocha/bin/_mocha -- -R spec tests/unit.test.js" | ||
"dependencies": { | ||
"compago-listener": "npm:compago-listener@^1.0.2", | ||
"compago-listener": "npm:compago-listener@^1.0.4", | ||
"compago-model": "npm:compago-model@^1.0.1" | ||
@@ -43,2 +45,9 @@ } | ||
}, | ||
"eslintConfig": { | ||
"extends": "airbnb", | ||
"rules": { | ||
"no-nested-ternary": 1, | ||
"eqeqeq": [2, "smart"] | ||
} | ||
}, | ||
"devDependencies": { | ||
@@ -49,2 +58,5 @@ "babel-cli": "^6.3.17", | ||
"compago-view": "^1.0.0", | ||
"eslint": "^1.10.3", | ||
"eslint-config-airbnb": "^2.1.1", | ||
"eslint-plugin-react": "^3.13.1", | ||
"expect": "^1.13.4", | ||
@@ -56,5 +68,5 @@ "istanbul": "^1.0.0-alpha.2", | ||
"dependencies": { | ||
"compago-listener": "^1.0.2", | ||
"compago-listener": "^1.0.4", | ||
"compago-model": "^1.0.1" | ||
} | ||
} |
import { Listener } from 'compago-listener'; | ||
import Model from "compago-model"; | ||
import Model from 'compago-model'; | ||
export default class Ajax { | ||
/** | ||
* Facilitates interaction with a REST server through the XMLHttpRequest API. | ||
* | ||
* @mixes Listener | ||
*/ | ||
class Ajax { | ||
/** | ||
* @param {Object} [options] | ||
* @param {string} [options.url] The URL for requests, by default uses the window's origin. | ||
* @example | ||
* model.storage = new Ajax('http://example.com/post/'); | ||
* @param {string} [options.url] the URL for requests, by default uses the window's origin | ||
*/ | ||
@@ -20,4 +23,4 @@ constructor(options = {}) { | ||
* | ||
* @param {Model} model The model to be checked. | ||
* @return {boolean} | ||
* @param {Model} model the model to be checked | ||
* @return {boolean} True if the model is already stored on the server | ||
*/ | ||
@@ -31,12 +34,12 @@ static isStored(model) { | ||
* | ||
* @param {string} method A method name to execute. | ||
* Internal method names are mapped to ajax methods in `this.methods`. | ||
* @param {(Model|Collection)} model A model or a collection to by synchronized. | ||
* @param {string} method a method name to execute. | ||
* Internal method names are mapped to ajax methods in `Ajax.methods`. | ||
* @param {(Model|Collection)} model a model or a collection to by synchronized | ||
* @param {Object} options | ||
* @param {Function} options.success The function called if the request succeeds. | ||
* @param {Function} options.error The function called if the server returns an error. | ||
* @param {string} [options.url] A specific url for the request, in case it's different from the default url of the storage | ||
* @param {boolean} [options.silent] to avoid firing any events. | ||
* @param {Boolean} [options.patch] to send only changed attributes (if present) using `PATCH` method | ||
* @return {Object} Returns an XMLHttpRequest object. | ||
* @param {Function} options.success the function called if the request succeeds | ||
* @param {Function} options.error the function called if the server returns an error | ||
* @param {string} [options.url] a specific url for the request, in case it's different from the default url of the storage | ||
* @param {boolean} [options.silent] whether to avoid firing any events | ||
* @param {Boolean} [options.patch] whether to send only changed attributes (if present) using `PATCH` method | ||
* @return {XMLHttpRequest} an request object | ||
*/ | ||
@@ -48,6 +51,6 @@ sync(method, model, options) { | ||
let {url=this.url, patch, silent, success, error} = options; | ||
const { patch, silent, success, error } = options; | ||
let { url = this.url } = options; | ||
const xhr = new XMLHttpRequest(); | ||
const isStored = this.constructor.isStored(model); | ||
let status, data; | ||
const self = this; | ||
@@ -76,2 +79,3 @@ const changes = (patch && model.changes) ? model.changes : false; | ||
let data; | ||
if (method === 'write') { | ||
@@ -82,7 +86,8 @@ xhr.setRequestHeader('Content-Type', 'application/json'); | ||
xhr.onreadystatechange = function() { | ||
xhr.onreadystatechange = () => { | ||
const status = xhr.status; | ||
if (xhr.readyState === 4) { | ||
if (!silent) self.emit('response', options); | ||
const response = Ajax.isJSONString(xhr.responseText) ? JSON.parse(xhr.responseText) : xhr.responseText; | ||
if (((status = xhr.status) >= 200) && (status < 300 || status === 304)) { | ||
const response = Ajax._isJSONString(xhr.responseText) ? JSON.parse(xhr.responseText) : xhr.responseText; | ||
if ((status >= 200) && (status < 300 || status === 304)) { | ||
success(response); | ||
@@ -105,4 +110,4 @@ } else { | ||
* @param {Object} [options] | ||
* @param {boolean} [options.silent] Avoids firing `dispose` event. | ||
* @return {Ajax} | ||
* @param {boolean} [options.silent] whether to avoid emitting the `dispose` event. | ||
* @return {this} | ||
*/ | ||
@@ -118,6 +123,6 @@ dispose(options = {}) { | ||
* | ||
* @param {*} val The value to be checked. | ||
* @return {Boolean} | ||
* @param {*} val the value to be checked | ||
* @return {Boolean} True if the value is a valid JSON string | ||
*/ | ||
static isJSONString(val) { | ||
static _isJSONString(val) { | ||
try { | ||
@@ -133,3 +138,3 @@ JSON.parse(val); | ||
/** | ||
* The map translating internal method names to the HTTP methods. | ||
* The map translating internal method names to their respective HTTP methods. | ||
*/ | ||
@@ -141,3 +146,5 @@ Ajax.methods = { | ||
'update': 'PUT', | ||
'patch': 'PATCH' | ||
}; | ||
'patch': 'PATCH', | ||
}; | ||
export default Ajax; |
@@ -0,1 +1,3 @@ | ||
/* eslint-env node, mocha */ | ||
import Ajax from '../src/index'; | ||
@@ -7,41 +9,40 @@ import Model from 'compago-model'; | ||
describe('Ajax', ()=> { | ||
describe('Ajax', () => { | ||
let storage; | ||
beforeEach(()=> { | ||
storage = new Ajax({url: 'http://example.com/posts'}); | ||
beforeEach(() => { | ||
storage = new Ajax({ url: 'http://example.com/posts' }); | ||
}); | ||
describe('constructor', ()=> { | ||
it('creates an ajax storage controller', ()=> { | ||
describe('constructor', () => { | ||
it('creates an ajax storage controller', () => { | ||
expect(storage.url).toBe('http://example.com/posts'); | ||
}); | ||
}); | ||
describe('isStored', ()=> { | ||
it('checks whether a model has been already persisted on the server', ()=> { | ||
const model = {id: 100}; | ||
describe('isStored', () => { | ||
it('checks whether a model has been already persisted on the server', () => { | ||
const model = { id: 100 }; | ||
expect(Ajax.isStored(model)).toBe(true); | ||
expect(Ajax.isStored({})).toBe(false); | ||
}); | ||
}); | ||
describe('sync', ()=> { | ||
let xhr, model, options, requests; | ||
describe('sync', () => { | ||
let xhr; | ||
let model; | ||
let options; | ||
let requests; | ||
beforeEach(()=> { | ||
beforeEach(() => { | ||
xhr = sinon.useFakeXMLHttpRequest(); | ||
global.XMLHttpRequest = xhr; | ||
requests = []; | ||
/* eslint no-shadow: 1*/ | ||
xhr.onCreate = (xhr) => { | ||
requests.push(xhr) | ||
requests.push(xhr); | ||
}; | ||
model = {id: 42}; | ||
model.toJSON = ()=> { | ||
return model | ||
model = { id: 42 }; | ||
model.toJSON = () => { | ||
return model; | ||
}; | ||
@@ -53,7 +54,7 @@ options = {}; | ||
afterEach(()=> { | ||
afterEach(() => { | ||
xhr.restore(); | ||
}); | ||
it('returns `false` if no valid operation type is provided', ()=> { | ||
it('returns `false` if no valid operation type is provided', () => { | ||
expect(storage.sync()).toBe(false); | ||
@@ -63,10 +64,10 @@ expect(requests.length).toBe(0); | ||
it('parses JSON responses', ()=> { | ||
it('parses JSON responses', () => { | ||
storage.sync('read', model, options); | ||
requests[0].respond(200, {'Content-Type': 'application/json'}, '{"name":"Arthur"}'); | ||
expect(options.success.calls[0].arguments).toEqual([{name: 'Arthur'}]); | ||
requests[0].respond(200, { 'Content-Type': 'application/json' }, '{"name":"Arthur"}'); | ||
expect(options.success.calls[0].arguments).toEqual([{ name: 'Arthur' }]); | ||
expect(options.error).toNotHaveBeenCalled(); | ||
}); | ||
it('retrieves a model', ()=> { | ||
it('retrieves a model', () => { | ||
storage.sync('read', model, options); | ||
@@ -76,3 +77,3 @@ expect(requests.length).toBe(1); | ||
expect(options.success).toNotHaveBeenCalled(); | ||
requests[0].respond(200, {'Content-Type': 'text/plain'}, "Ok"); | ||
requests[0].respond(200, { 'Content-Type': 'text/plain' }, 'Ok'); | ||
expect(options.success).toHaveBeenCalledWith('Ok'); | ||
@@ -82,4 +83,4 @@ expect(options.error).toNotHaveBeenCalled(); | ||
it('invokes `option.error` when attempts to retrieve an unsaved model', ()=> { | ||
let m = new Model(); | ||
it('invokes `option.error` when attempts to retrieve an unsaved model', () => { | ||
const m = new Model(); | ||
storage.sync('read', m, options); | ||
@@ -90,4 +91,4 @@ expect(options.error).toHaveBeenCalled(); | ||
it('retrieves all models in a collection', ()=> { | ||
let collection = {}; | ||
it('retrieves all models in a collection', () => { | ||
const collection = {}; | ||
storage.sync('read', collection, options); | ||
@@ -97,3 +98,3 @@ expect(requests[0].url).toBe('http://example.com/posts'); | ||
expect(options.success).toNotHaveBeenCalled(); | ||
requests[0].respond(200, {'Content-Type': 'text/plain'}, "Ok"); | ||
requests[0].respond(200, { 'Content-Type': 'text/plain' }, 'Ok'); | ||
expect(options.success).toHaveBeenCalledWith('Ok'); | ||
@@ -103,3 +104,3 @@ expect(options.error).toNotHaveBeenCalled(); | ||
it('saves a model', ()=> { | ||
it('saves a model', () => { | ||
delete model.id; | ||
@@ -113,3 +114,3 @@ model.name = 'Arthur'; | ||
it('updates a model', ()=> { | ||
it('updates a model', () => { | ||
storage.sync('write', model, options); | ||
@@ -121,4 +122,4 @@ expect(requests[0].url).toBe('http://example.com/posts/42'); | ||
it('sends only changes if `patch:true`', ()=> { | ||
model.changes = {name: 'Arthur'}; | ||
it('sends only changes if `patch:true`', () => { | ||
model.changes = { name: 'Arthur' }; | ||
options.patch = true; | ||
@@ -131,4 +132,3 @@ storage.sync('write', model, options); | ||
it('sends all attributes if no changes found despite `patch:true`', ()=> { | ||
it('sends all attributes if no changes found despite `patch:true`', () => { | ||
options.patch = true; | ||
@@ -142,3 +142,3 @@ storage.sync('write', model, options); | ||
it('deletes a single model', ()=> { | ||
it('deletes a single model', () => { | ||
storage.sync('erase', model, options); | ||
@@ -149,3 +149,3 @@ expect(requests[0].url).toBe('http://example.com/posts/42'); | ||
it('invokes `option.success` when attempts to delete an unsaved model', ()=> { | ||
it('invokes `option.success` when attempts to delete an unsaved model', () => { | ||
delete model.id; | ||
@@ -157,3 +157,3 @@ storage.sync('erase', model, options); | ||
it('invokes `options.error` if a error is returned by the server', ()=> { | ||
it('invokes `options.error` if a error is returned by the server', () => { | ||
storage.sync('read', model, options); | ||
@@ -163,3 +163,3 @@ expect(requests[0].url).toBe('http://example.com/posts/42'); | ||
expect(options.success).toNotHaveBeenCalled(); | ||
requests[0].respond(404, {'Content-Type': 'text/plain'}, 'Not Ok'); | ||
requests[0].respond(404, { 'Content-Type': 'text/plain' }, 'Not Ok'); | ||
expect(options.success).toNotHaveBeenCalled(); | ||
@@ -169,3 +169,3 @@ expect(options.error).toHaveBeenCalledWith('Not Ok'); | ||
it('fires `before:request`, `request`, and `response` events unless `silent:true`', ()=> { | ||
it('fires `before:request`, `request`, and `response` events unless `silent:true`', () => { | ||
storage.someMethod = expect.createSpy(); | ||
@@ -178,3 +178,3 @@ storage.otherMethod = expect.createSpy(); | ||
storage.sync('read', model, options); | ||
requests[0].respond(200, {'Content-Type': 'text/plain'}, 'Not Ok'); | ||
requests[0].respond(200, { 'Content-Type': 'text/plain' }, 'Not Ok'); | ||
expect(storage.someMethod).toHaveBeenCalled(); | ||
@@ -185,3 +185,3 @@ expect(storage.otherMethod).toHaveBeenCalled(); | ||
it('does not fire events if `silent:true`', ()=> { | ||
it('does not fire events if `silent:true`', () => { | ||
storage.someMethod = expect.createSpy(); | ||
@@ -195,22 +195,19 @@ storage.otherMethod = expect.createSpy(); | ||
storage.sync('read', model, options); | ||
requests[0].respond(200, {'Content-Type': 'text/plain'}, 'Not Ok'); | ||
requests[0].respond(200, { 'Content-Type': 'text/plain' }, 'Not Ok'); | ||
expect(storage.someMethod).toNotHaveBeenCalled(); | ||
expect(storage.otherMethod).toNotHaveBeenCalled(); | ||
expect(storage.anotherMethod).toNotHaveBeenCalled(); | ||
}) | ||
}); | ||
}); | ||
describe('isJSONString', ()=> { | ||
it('checks whether the passed argument is a JSON string or not', ()=> { | ||
expect(Ajax.isJSONString('{"name":"Arthur"}')).toBe(true); | ||
expect(Ajax.isJSONString('Arthur')).toBe(false); | ||
describe('_isJSONString', () => { | ||
it('checks whether the passed argument is a JSON string or not', () => { | ||
expect(Ajax._isJSONString('{"name":"Arthur"}')).toBe(true); | ||
expect(Ajax._isJSONString('Arthur')).toBe(false); | ||
}); | ||
}); | ||
describe('dispose', ()=> { | ||
it("prepares the storage controller to be disposed", ()=> { | ||
storage.on(storage, 'dispose', ()=> { | ||
describe('dispose', () => { | ||
it('prepares the storage controller to be disposed', () => { | ||
storage.on(storage, 'dispose', () => { | ||
}); | ||
@@ -223,4 +220,3 @@ expect(storage._events).toExist(); | ||
}); | ||
it('fires `dispose` event unless `silent:true`', ()=> { | ||
it('fires `dispose` event unless `silent:true`', () => { | ||
storage.someMethod = expect.createSpy(); | ||
@@ -230,9 +226,8 @@ storage.on(storage, 'dispose', storage.someMethod); | ||
expect(storage.someMethod).toHaveBeenCalled(); | ||
let otherMethod = expect.createSpy(); | ||
const otherMethod = expect.createSpy(); | ||
storage.on(storage, 'dispose', storage.otherMethod); | ||
storage.dispose({silent: true}); | ||
storage.dispose({ silent: true }); | ||
expect(otherMethod).toNotHaveBeenCalled(); | ||
}); | ||
}); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
15778
8
313
11
Updatedcompago-listener@^1.0.4