diff --git a/README.md b/README.md index 9397d40..340464e 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ In order to test newly added code you must rebuild the distribution. broccoli build dist ``` +### defineFixture Adding fixtures with `defineFixture` tells ic-ajax to resolve the promise with the fixture matching a url instead of making a request. This allows you to test your app without creating fake servers with sinon, etc. @@ -97,6 +98,44 @@ ic.ajax.request('api/v1/courses').then(function(result) { To test failure paths, set the `textStatus` to anything but `success`. +To set a fixture that will match every url with a matching path, regardless of the query string, add an options object as a parameter to `defineFixture` with a property of `fallback` set to true. A fixture will be located for the specific url with a query string, and if no fixture is found, the fallback that matches the path (not considering the query string) will be used. + +Example: + +```js +ic.ajax.defineFixture('api/v1/courses', { + response: [{name: 'basket weaving'}], + jqXHR: {}, + textStatus: 'success' +}, { + fallback: true +}); + +ic.ajax.request('api/v1/courses?this=that').then(function(result) { + deepEqual(result, ic.ajax.lookupFixture('api/v1/courses').response); +}); +``` + +### lookupFixture +Lookup a fixture. If successful, the fixture will be returned, otherwise `undefined` will be returned. + +```js +var coursesFixture = ic.ajax.lookupFixture('api/v1/courses'); +``` + +### removeFixture +Remove a specific fixture. Pass in the url, the fixture that matches that url, if any, will be removed. + +```js +ic.ajax.removeFixture('api/v1/courses'); +``` + +### removeAllFixtures + +```js +ic.ajax.removeAllFixtures(); +``` + Contributing ------------ diff --git a/dist/amd/main.js b/dist/amd/main.js index 57c5d01..97cf1f6 100644 --- a/dist/amd/main.js +++ b/dist/amd/main.js @@ -36,7 +36,8 @@ define( } __exports__.raw = raw;var __fixtures__ = {}; - __exports__.__fixtures__ = __fixtures__; + __exports__.__fixtures__ = __fixtures__;var __fallbackFixtures__ = {}; + __exports__.__fallbackFixtures__ = __fallbackFixtures__; /* * Defines a fixture that will be used instead of an actual ajax * request to a given url. This is useful for testing, allowing you to @@ -49,17 +50,25 @@ define( * response: { firstName: 'Ryan', lastName: 'Florence' }, * textStatus: 'success' * jqXHR: {} + * }, { + * fallback: true * }); * * @param {String} url * @param {Object} fixture + * @param {Object} [options] - options for the fixture + * @param {boolean} [options.fallback=false] - whether or not the fixture should be used for all routes with a matching path that do not have a fixture matching their query string */ - function defineFixture(url, fixture) { + function defineFixture(url, fixture, options) { if (fixture.response) { fixture.response = JSON.parse(JSON.stringify(fixture.response)); } __fixtures__[url] = fixture; + + if (options && options.fallback) { + __fallbackFixtures__[url] = fixture; + } } __exports__.defineFixture = defineFixture;/* @@ -69,13 +78,48 @@ define( */ function lookupFixture (url) { - return __fixtures__ && __fixtures__[url]; + var fixture = __fixtures__ && __fixtures__[url]; + + if (!fixture && typeof url === "string" && url.match(/\?/)) { + fixture = __fallbackFixtures__ && __fallbackFixtures__[url.split("?")[0]]; + } + + return fixture; + } + + __exports__.lookupFixture = lookupFixture;/* + * Removes a fixture by url. + * + * @param {String} url + */ + function removeFixture (url) { + delete __fixtures__[url]; + delete __fallbackFixtures__[url]; } - __exports__.lookupFixture = lookupFixture;function makePromise(settings) { + __exports__.removeFixture = removeFixture;/* + * Removes all fixtures. + */ + function removeAllFixtures () { + emptyObject(__fixtures__); + emptyObject(__fallbackFixtures__); + } + + __exports__.removeAllFixtures = removeAllFixtures;function emptyObject(obj) { + for (var i in obj) { + if (obj.hasOwnProperty(i)) { + delete obj[i]; + } + } + } + + function makePromise(settings) { return new Ember.RSVP.Promise(function(resolve, reject) { var fixture = lookupFixture(settings.url); if (fixture) { + if (fixture.onSend && typeof fixture.onSend === "function") { + fixture.onSend(settings); + } if (fixture.textStatus === 'success' || fixture.textStatus == null) { return Ember.run.later(null, resolve, fixture); } else { diff --git a/dist/cjs/main.js b/dist/cjs/main.js index 4b6b7ef..6906f80 100644 --- a/dist/cjs/main.js +++ b/dist/cjs/main.js @@ -33,7 +33,8 @@ function raw() { } exports.raw = raw;var __fixtures__ = {}; -exports.__fixtures__ = __fixtures__; +exports.__fixtures__ = __fixtures__;var __fallbackFixtures__ = {}; +exports.__fallbackFixtures__ = __fallbackFixtures__; /* * Defines a fixture that will be used instead of an actual ajax * request to a given url. This is useful for testing, allowing you to @@ -46,17 +47,25 @@ exports.__fixtures__ = __fixtures__; * response: { firstName: 'Ryan', lastName: 'Florence' }, * textStatus: 'success' * jqXHR: {} + * }, { + * fallback: true * }); * * @param {String} url * @param {Object} fixture + * @param {Object} [options] - options for the fixture + * @param {boolean} [options.fallback=false] - whether or not the fixture should be used for all routes with a matching path that do not have a fixture matching their query string */ -function defineFixture(url, fixture) { +function defineFixture(url, fixture, options) { if (fixture.response) { fixture.response = JSON.parse(JSON.stringify(fixture.response)); } __fixtures__[url] = fixture; + + if (options && options.fallback) { + __fallbackFixtures__[url] = fixture; + } } exports.defineFixture = defineFixture;/* @@ -66,13 +75,48 @@ exports.defineFixture = defineFixture;/* */ function lookupFixture (url) { - return __fixtures__ && __fixtures__[url]; + var fixture = __fixtures__ && __fixtures__[url]; + + if (!fixture && typeof url === "string" && url.match(/\?/)) { + fixture = __fallbackFixtures__ && __fallbackFixtures__[url.split("?")[0]]; + } + + return fixture; +} + +exports.lookupFixture = lookupFixture;/* + * Removes a fixture by url. + * + * @param {String} url + */ +function removeFixture (url) { + delete __fixtures__[url]; + delete __fallbackFixtures__[url]; } -exports.lookupFixture = lookupFixture;function makePromise(settings) { +exports.removeFixture = removeFixture;/* + * Removes all fixtures. + */ +function removeAllFixtures () { + emptyObject(__fixtures__); + emptyObject(__fallbackFixtures__); +} + +exports.removeAllFixtures = removeAllFixtures;function emptyObject(obj) { + for (var i in obj) { + if (obj.hasOwnProperty(i)) { + delete obj[i]; + } + } +} + +function makePromise(settings) { return new Ember.RSVP.Promise(function(resolve, reject) { var fixture = lookupFixture(settings.url); if (fixture) { + if (fixture.onSend && typeof fixture.onSend === "function") { + fixture.onSend(settings); + } if (fixture.textStatus === 'success' || fixture.textStatus == null) { return Ember.run.later(null, resolve, fixture); } else { diff --git a/dist/globals/main.js b/dist/globals/main.js index 0489c46..e0c4fc4 100644 --- a/dist/globals/main.js +++ b/dist/globals/main.js @@ -34,7 +34,8 @@ function raw() { } exports.raw = raw;var __fixtures__ = {}; -exports.__fixtures__ = __fixtures__; +exports.__fixtures__ = __fixtures__;var __fallbackFixtures__ = {}; +exports.__fallbackFixtures__ = __fallbackFixtures__; /* * Defines a fixture that will be used instead of an actual ajax * request to a given url. This is useful for testing, allowing you to @@ -47,17 +48,25 @@ exports.__fixtures__ = __fixtures__; * response: { firstName: 'Ryan', lastName: 'Florence' }, * textStatus: 'success' * jqXHR: {} + * }, { + * fallback: true * }); * * @param {String} url * @param {Object} fixture + * @param {Object} [options] - options for the fixture + * @param {boolean} [options.fallback=false] - whether or not the fixture should be used for all routes with a matching path that do not have a fixture matching their query string */ -function defineFixture(url, fixture) { +function defineFixture(url, fixture, options) { if (fixture.response) { fixture.response = JSON.parse(JSON.stringify(fixture.response)); } __fixtures__[url] = fixture; + + if (options && options.fallback) { + __fallbackFixtures__[url] = fixture; + } } exports.defineFixture = defineFixture;/* @@ -67,13 +76,48 @@ exports.defineFixture = defineFixture;/* */ function lookupFixture (url) { - return __fixtures__ && __fixtures__[url]; + var fixture = __fixtures__ && __fixtures__[url]; + + if (!fixture && typeof url === "string" && url.match(/\?/)) { + fixture = __fallbackFixtures__ && __fallbackFixtures__[url.split("?")[0]]; + } + + return fixture; +} + +exports.lookupFixture = lookupFixture;/* + * Removes a fixture by url. + * + * @param {String} url + */ +function removeFixture (url) { + delete __fixtures__[url]; + delete __fallbackFixtures__[url]; } -exports.lookupFixture = lookupFixture;function makePromise(settings) { +exports.removeFixture = removeFixture;/* + * Removes all fixtures. + */ +function removeAllFixtures () { + emptyObject(__fixtures__); + emptyObject(__fallbackFixtures__); +} + +exports.removeAllFixtures = removeAllFixtures;function emptyObject(obj) { + for (var i in obj) { + if (obj.hasOwnProperty(i)) { + delete obj[i]; + } + } +} + +function makePromise(settings) { return new Ember.RSVP.Promise(function(resolve, reject) { var fixture = lookupFixture(settings.url); if (fixture) { + if (fixture.onSend && typeof fixture.onSend === "function") { + fixture.onSend(settings); + } if (fixture.textStatus === 'success' || fixture.textStatus == null) { return Ember.run.later(null, resolve, fixture); } else { diff --git a/dist/named-amd/main.js b/dist/named-amd/main.js index c928c76..5d69c22 100644 --- a/dist/named-amd/main.js +++ b/dist/named-amd/main.js @@ -36,7 +36,8 @@ define("ic-ajax", } __exports__.raw = raw;var __fixtures__ = {}; - __exports__.__fixtures__ = __fixtures__; + __exports__.__fixtures__ = __fixtures__;var __fallbackFixtures__ = {}; + __exports__.__fallbackFixtures__ = __fallbackFixtures__; /* * Defines a fixture that will be used instead of an actual ajax * request to a given url. This is useful for testing, allowing you to @@ -49,17 +50,25 @@ define("ic-ajax", * response: { firstName: 'Ryan', lastName: 'Florence' }, * textStatus: 'success' * jqXHR: {} + * }, { + * fallback: true * }); * * @param {String} url * @param {Object} fixture + * @param {Object} [options] - options for the fixture + * @param {boolean} [options.fallback=false] - whether or not the fixture should be used for all routes with a matching path that do not have a fixture matching their query string */ - function defineFixture(url, fixture) { + function defineFixture(url, fixture, options) { if (fixture.response) { fixture.response = JSON.parse(JSON.stringify(fixture.response)); } __fixtures__[url] = fixture; + + if (options && options.fallback) { + __fallbackFixtures__[url] = fixture; + } } __exports__.defineFixture = defineFixture;/* @@ -69,13 +78,48 @@ define("ic-ajax", */ function lookupFixture (url) { - return __fixtures__ && __fixtures__[url]; + var fixture = __fixtures__ && __fixtures__[url]; + + if (!fixture && typeof url === "string" && url.match(/\?/)) { + fixture = __fallbackFixtures__ && __fallbackFixtures__[url.split("?")[0]]; + } + + return fixture; + } + + __exports__.lookupFixture = lookupFixture;/* + * Removes a fixture by url. + * + * @param {String} url + */ + function removeFixture (url) { + delete __fixtures__[url]; + delete __fallbackFixtures__[url]; } - __exports__.lookupFixture = lookupFixture;function makePromise(settings) { + __exports__.removeFixture = removeFixture;/* + * Removes all fixtures. + */ + function removeAllFixtures () { + emptyObject(__fixtures__); + emptyObject(__fallbackFixtures__); + } + + __exports__.removeAllFixtures = removeAllFixtures;function emptyObject(obj) { + for (var i in obj) { + if (obj.hasOwnProperty(i)) { + delete obj[i]; + } + } + } + + function makePromise(settings) { return new Ember.RSVP.Promise(function(resolve, reject) { var fixture = lookupFixture(settings.url); if (fixture) { + if (fixture.onSend && typeof fixture.onSend === "function") { + fixture.onSend(settings); + } if (fixture.textStatus === 'success' || fixture.textStatus == null) { return Ember.run.later(null, resolve, fixture); } else { diff --git a/karma.conf.js b/karma.conf.js index 9452e31..8d915b0 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -4,7 +4,7 @@ module.exports = function(config) { frameworks: ['qunit'], files: [ - 'bower_components/jquery/jquery.js', + 'bower_components/jquery/dist/jquery.js', 'bower_components/handlebars/handlebars.js', 'bower_components/ember/ember.js', 'bower_components/sinon/index.js', diff --git a/lib/main.js b/lib/main.js index 538d746..35ac0a0 100644 --- a/lib/main.js +++ b/lib/main.js @@ -32,6 +32,7 @@ export function raw() { } export var __fixtures__ = {}; +export var __fallbackFixtures__ = {}; /* * Defines a fixture that will be used instead of an actual ajax @@ -45,17 +46,25 @@ export var __fixtures__ = {}; * response: { firstName: 'Ryan', lastName: 'Florence' }, * textStatus: 'success' * jqXHR: {} + * }, { + * fallback: true * }); * * @param {String} url * @param {Object} fixture + * @param {Object} [options] - options for the fixture + * @param {boolean} [options.fallback=false] - whether or not the fixture should be used for all routes with a matching path that do not have a fixture matching their query string */ -export function defineFixture(url, fixture) { +export function defineFixture(url, fixture, options) { if (fixture.response) { fixture.response = JSON.parse(JSON.stringify(fixture.response)); } __fixtures__[url] = fixture; + + if (options && options.fallback) { + __fallbackFixtures__[url] = fixture; + } } /* @@ -65,13 +74,48 @@ export function defineFixture(url, fixture) { */ export function lookupFixture (url) { - return __fixtures__ && __fixtures__[url]; + var fixture = __fixtures__ && __fixtures__[url]; + + if (!fixture && typeof url === "string" && url.match(/\?/)) { + fixture = __fallbackFixtures__ && __fallbackFixtures__[url.split("?")[0]]; + } + + return fixture; +} + +/* + * Removes a fixture by url. + * + * @param {String} url + */ +export function removeFixture (url) { + delete __fixtures__[url]; + delete __fallbackFixtures__[url]; +} + +/* + * Removes all fixtures. + */ +export function removeAllFixtures () { + emptyObject(__fixtures__); + emptyObject(__fallbackFixtures__); +} + +function emptyObject(obj) { + for (var i in obj) { + if (obj.hasOwnProperty(i)) { + delete obj[i]; + } + } } function makePromise(settings) { return new Ember.RSVP.Promise(function(resolve, reject) { var fixture = lookupFixture(settings.url); if (fixture) { + if (fixture.onSend && typeof fixture.onSend === "function") { + fixture.onSend(settings); + } if (fixture.textStatus === 'success' || fixture.textStatus == null) { return Ember.run.later(null, resolve, fixture); } else { diff --git a/package.json b/package.json index c988c00..2489737 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "karma-html2js-preprocessor": "^0.1.0", "karma-qunit": "^0.1.1", "karma-script-launcher": "^0.1.0", - "qunitjs": "~1.12.0", + "qunitjs": "~1.14.0", "bower": "^1.3.5", "rf-release": "^0.1.0", "broccoli-cli": "0.0.1", @@ -36,4 +36,4 @@ }, "homepage": "https://github.com/instructure/ic-ajax", "dependencies": {} -} \ No newline at end of file +} diff --git a/test/main.spec.js b/test/main.spec.js index c32c9fe..ad77ba8 100644 --- a/test/main.spec.js +++ b/test/main.spec.js @@ -1,9 +1,60 @@ -module('ic-ajax'); +module('ic-ajax', { + teardown: function() { + ic.ajax.removeAllFixtures(); + } +}); test('presence', function() { ok(ic.ajax, 'ic.ajax is defined'); }); +test('finds fixtures', function() { + ic.ajax.defineFixture('/get', { + response: { foo: 'bar' }, + textStatus: 'success', + jqXHR: {} + }); + + ok(ic.ajax.lookupFixture('/get')); +}); + +test('removes fixtures', function() { + ic.ajax.defineFixture('/get', { + response: { foo: 'bar' }, + textStatus: 'success', + jqXHR: {} + }); + ic.ajax.defineFixture('/post', { + errorThrown: 'Unprocessable Entity', + textStatus: 'error', + jqXHR: {} + }); + + ok(ic.ajax.lookupFixture('/get')); + ic.ajax.removeFixture('/get'); + ok(!ic.ajax.lookupFixture('/get')); + ok(ic.ajax.lookupFixture('/post')); +}); + +test('removes all fixtures', function() { + ic.ajax.defineFixture('/get', { + response: { foo: 'bar' }, + textStatus: 'success', + jqXHR: {} + }); + ic.ajax.defineFixture('/post', { + errorThrown: 'Unprocessable Entity', + textStatus: 'error', + jqXHR: {} + }); + + ok(ic.ajax.lookupFixture('/get')); + ok(ic.ajax.lookupFixture('/post')); + ic.ajax.removeAllFixtures(); + ok(!ic.ajax.lookupFixture('/get')); + ok(!ic.ajax.lookupFixture('/post')); +}); + asyncTest('pulls from fixtures', function() { ic.ajax.defineFixture('/get', { response: { foo: 'bar' }, @@ -17,6 +68,57 @@ asyncTest('pulls from fixtures', function() { }); }); +asyncTest('uses a designated fallback fixture if no fixture is found for a request with a query string', function() { + ic.ajax.defineFixture('/get', { + response: { foo: 'bar' }, + textStatus: 'success', + jqXHR: {} + }, { + fallback: true + }); + + ok(ic.ajax.lookupFixture('/get?this=that')); + ic.ajax.raw('/get?this=that').then(function(result) { + start(); + deepEqual(result, ic.ajax.lookupFixture('/get')); + }); +}); + +asyncTest('does not use a designated fallback fixture if a fixture is found for a request matching its query string', function() { + var queryStringResponse = { foo: 'baz'}; + + ic.ajax.defineFixture('/get', { + response: { foo: 'bar' }, + textStatus: 'success', + jqXHR: {} + }, { + fallback: true + }); + + ic.ajax.defineFixture('/get?this=that', { + response: queryStringResponse, + textStatus: 'success', + jqXHR: {} + }); + + ic.ajax.raw('/get?this=that').then(function(result) { + start(); + deepEqual(result.response, queryStringResponse); + }); +}); + +test('does not set a fixture as a fallback fixture if `fallback` is not set to true in the options', function() { + var queryStringResponse = { foo: 'baz'}; + + ic.ajax.defineFixture('/get', { + response: { foo: 'bar' }, + textStatus: 'success', + jqXHR: {} + }); + + ok(!ic.ajax.lookupFixture('/get?this=that')); +}); + asyncTest('rejects the promise when the textStatus of the fixture is not success', function() { ic.ajax.defineFixture('/post', { errorThrown: 'Unprocessable Entity', @@ -106,6 +208,38 @@ asyncTest('the fixture jqXHR survives the response copy', function() { ) }); +asyncTest('adds onSend callback option to inspect xhr settings', function() { + + var xhrRequests = []; + + ic.ajax.defineFixture('/post', { + response: {}, + textStatus: 'success', + jqXHR: {}, + onSend: function(settings) { + xhrRequests.push(settings); + } + }); + + var req = { + type: "POST", + contentType: "application/json", + dataType: "json", + url: "/post", + data: JSON.stringify({ + foo: "bar" + }) + }; + + Ember.RSVP.all([ic.ajax.request(req), ic.ajax.request(req)]) + .then(function() { + start(); + equal(xhrRequests.length, 2); + var payload = JSON.parse(xhrRequests[0].data); + equal(payload.foo, "bar"); + }); +}); + test('throws if success or error callbacks are used', function() { var k = function() {}; throws(function() {