├── test ├── test_proxy.json ├── test_script.js ├── test.json ├── test_jsonp.js ├── test_proxy.xml ├── browserify │ ├── index.html │ ├── main.js │ └── test.js ├── test-setup.js ├── requirejs │ ├── test_module.js │ ├── run_tests.js │ └── index.html ├── test-timeout.js ├── test-url-match.js ├── nodejs │ └── test.js ├── index.html ├── dist-min.html ├── jquery.js ├── test-headers.js ├── test-namespace.js ├── test-header-match.js ├── test-logging.js ├── test-connection.js ├── test-data-types.js ├── test-mock-clearing.js ├── test-data-match.js ├── test-retaining-ajax-calls.js ├── test-bugs.js └── test-core.js ├── .codeclimate.yml ├── .gitignore ├── .travis.yml ├── .editorconfig ├── bower.json ├── lib ├── jquery.xmldom.js ├── semver.js ├── qunit.css └── json2.js ├── package.json ├── .jshintrc ├── Gruntfile.js ├── CHANGELOG.md ├── CONTRIBUTING.md ├── dist └── jquery.mockjax.min.js └── README.md /test/test_proxy.json: -------------------------------------------------------------------------------- 1 | { "proxy" : true } -------------------------------------------------------------------------------- /test/test_script.js: -------------------------------------------------------------------------------- 1 | TEST_SCRIPT_VAR = 1; -------------------------------------------------------------------------------- /test/test.json: -------------------------------------------------------------------------------- 1 | { "say" : "I'm a json file!" } 2 | -------------------------------------------------------------------------------- /test/test_jsonp.js: -------------------------------------------------------------------------------- 1 | abcdef123456({ "data" : "JSONP is cool" }) -------------------------------------------------------------------------------- /test/test_proxy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | bar 4 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | languages: 2 | JavaScript: true 3 | exclude_paths: 4 | - dist/* 5 | - lib/* 6 | - test/* -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | *.esproj 3 | *.swp 4 | .idea/ 5 | *.iml 6 | node_modules/ 7 | npm-debug.log 8 | 9 | test/browserify/bundle.js 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "4.2" 5 | before_install: npm install -g grunt-cli 6 | branches: 7 | only: 8 | - master 9 | - v1.x 10 | notifications: 11 | email: false 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; EditorConfig is awesome: http://EditorConfig.org 2 | 3 | ; top-most EditorConfig file 4 | root = true 5 | 6 | ; Tab indentation (no size specified) 7 | [*.js] 8 | indent_style = tab 9 | indent_size = 4 10 | end_of_line = lf 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-mockjax", 3 | "main": "dist/jquery.mockjax.js", 4 | "dependencies": { 5 | "jquery": ">=1.5.0" 6 | }, 7 | "ignore": [ 8 | ".editorconfig", 9 | ".gitignore", 10 | ".jshintrc", 11 | "*.md", 12 | "*.json", 13 | "lib", 14 | "test" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /test/browserify/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MockJax Browserify Test 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/browserify/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var jquery = require('../../lib/jquery-3.1.0.js'); 4 | var mockjax = require('../../src/jquery.mockjax')(jquery, window); 5 | 6 | mockjax({ 7 | url: '/resource', 8 | responseText: 'content' 9 | }); 10 | 11 | /* jshint unused:false */ 12 | function getResource(cb) { 13 | jquery.ajax({ 14 | url: '/resource', 15 | success: cb, 16 | error: cb 17 | }); 18 | } 19 | /* jshint unused:true */ 20 | 21 | 22 | // These are just here so that my tests can hit the *same* jQuery instance 23 | // that Mockjax is on as well as the `getResource()` function above. 24 | // You would NOT need this in your own code. 25 | window.jQuery = jquery; 26 | window.getResource = getResource; 27 | -------------------------------------------------------------------------------- /test/test-setup.js: -------------------------------------------------------------------------------- 1 | (function(qunit, $) { 2 | 'use strict'; 3 | 4 | qunit.begin(function() { 5 | 6 | qunit.noErrorCallbackExpected = function noErrorCallbackExpected(xhr) { 7 | qunit.assert.ok(false, 'Error callback executed: ' + xhr.status, xhr.responseText); 8 | }; 9 | 10 | // Speed up our tests 11 | $.mockjaxSettings.responseTime = 0; 12 | $.mockjaxSettings.logging = false; 13 | 14 | // Don't show log messages, but allow logging to work 15 | var noop = function() {}; 16 | $.mockjaxSettings.logger = { 17 | debug: noop, 18 | log: noop, 19 | info: noop, 20 | warn: noop, 21 | error: noop 22 | }; 23 | 24 | qunit.defaultMockjaxSettings = $.extend({}, $.mockjaxSettings); 25 | }); 26 | 27 | qunit.testDone(function() { 28 | $.mockjax.clear(); 29 | $.mockjaxSettings = $.extend({}, qunit.defaultMockjaxSettings); 30 | }); 31 | 32 | })(window.QUnit, window.jQuery); 33 | -------------------------------------------------------------------------------- /test/requirejs/test_module.js: -------------------------------------------------------------------------------- 1 | /* globals define,QUnit */ 2 | 3 | define(['jquery', 'jquery.mockjax'], function ($, mockjax) { 4 | 'use strict'; 5 | 6 | QUnit.module('jquery.mockjax used as AMD module'); 7 | 8 | QUnit.test('returns the mockjax object', function(assert) { 9 | assert.ok(mockjax, 'mockjax object is returned'); 10 | }); 11 | 12 | QUnit.test('sets the mockjax object to the jQuery object', function(assert) { 13 | assert.ok($.mockjax, '$.mockjax object is set'); 14 | }); 15 | 16 | QUnit.test('returns the same object as it sets to $.mockjax', function(assert) { 17 | assert.strictEqual(mockjax, $.mockjax, 'returned mockjax object is the same as $.mockjax object'); 18 | }); 19 | 20 | QUnit.test('mocks a simple request', function (assert) { 21 | var done = assert.async(); 22 | 23 | $.mockjax({ 24 | url: '/resource', 25 | responseText: 'content' 26 | }); 27 | 28 | $.ajax({ 29 | url: '/resource', 30 | success: function(response) { 31 | assert.equal(response, 'content'); 32 | }, 33 | error: function () { 34 | assert.ok(false, 'error callback executed'); 35 | }, 36 | complete: done 37 | }); 38 | }); 39 | }); 40 | 41 | -------------------------------------------------------------------------------- /lib/jquery.xmldom.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery xmlDOM Plugin v1.0 3 | * http://outwestmedia.com/jquery-plugins/xmldom/ 4 | * 5 | * Released: 2009-04-06 6 | * Version: 1.0 7 | * 8 | * Copyright (c) 2009 Jonathan Sharp, Out West Media LLC. 9 | * Dual licensed under the MIT and GPL licenses. 10 | * http://docs.jquery.com/License 11 | */ 12 | (function($) { 13 | // IE DOMParser wrapper 14 | if ( window['DOMParser'] == undefined && window.ActiveXObject ) { 15 | DOMParser = function() { }; 16 | DOMParser.prototype.parseFromString = function( xmlString ) { 17 | var doc = new ActiveXObject('Microsoft.XMLDOM'); 18 | doc.async = 'false'; 19 | doc.loadXML( xmlString ); 20 | return doc; 21 | }; 22 | } 23 | 24 | $.xmlDOM = function(xml, onErrorFn) { 25 | try { 26 | var xmlDoc = ( new DOMParser() ).parseFromString( xml, 'text/xml' ); 27 | if ( $.isXMLDoc( xmlDoc ) ) { 28 | var err = $('parsererror', xmlDoc); 29 | if ( err.length == 1 ) { 30 | throw('Error: ' + $(xmlDoc).text() ); 31 | } 32 | } else { 33 | throw('Unable to parse XML'); 34 | } 35 | } catch( e ) { 36 | var msg = ( e.name == undefined ? e : e.name + ': ' + e.message ); 37 | if ( $.isFunction( onErrorFn ) ) { 38 | onErrorFn( msg ); 39 | } else { 40 | $(document).trigger('xmlParseError', [ msg ]); 41 | } 42 | return $([]); 43 | } 44 | return $( xmlDoc ); 45 | }; 46 | })(jQuery); -------------------------------------------------------------------------------- /test/test-timeout.js: -------------------------------------------------------------------------------- 1 | (function(qunit, $) { 2 | 'use strict'; 3 | 4 | var t = qunit.test; 5 | 6 | /* --------------------- */ 7 | qunit.module( 'Timeouts' ); 8 | /* --------------------- */ 9 | 10 | t('Forcing timeout', function(assert) { 11 | var done = assert.async(); 12 | 13 | $.mockjax({ 14 | url: '/response-callback', 15 | responseText: 'done', 16 | isTimeout: true 17 | }); 18 | 19 | $.ajax({ 20 | url: '/response-callback', 21 | error: function(xhr, textStatus, errorThrown ) { 22 | assert.equal( textStatus, 'timeout', 'Text status is equal to timeout' ); 23 | assert.ok( errorThrown !== 'OK', 'errorThrown is undefined or timeout, not OK' ); 24 | assert.ok(true, 'error callback was called'); 25 | }, 26 | success: function() { 27 | assert.ok(false, 'should not be be successful'); 28 | }, 29 | complete: done 30 | }); 31 | }); 32 | 33 | t('Forcing timeout with Promises', function(assert) { 34 | var done = assert.async(); 35 | 36 | $.mockjax({ 37 | url: '/response-callback', 38 | isTimeout: true 39 | }); 40 | 41 | var request = $.ajax({ 42 | url: '/response-callback' 43 | }); 44 | 45 | request.done(function() { 46 | assert.ok(false, 'Should not be successful'); 47 | }); 48 | 49 | request.fail(function() { 50 | assert.ok(true, 'error callback was called'); 51 | }); 52 | 53 | // always for jquery 1.8+ 54 | (request.always || request.complete)(done); 55 | }); 56 | 57 | })(window.QUnit, window.jQuery); -------------------------------------------------------------------------------- /lib/semver.js: -------------------------------------------------------------------------------- 1 | 2 | (function(qunit) { 3 | qunit.compareSemver = function compareSemver(v1, v2, op) { 4 | var result = false, 5 | p1 = normalizeSemVer(v1), 6 | p2 = normalizeSemVer(v2); 7 | 8 | if (/^===?$/.test(op)) { 9 | result = semverEqual(p1, p2, 3); 10 | } else if (/^/.test(op)) { 16 | result = p1[0] > p2[0] || (semverEqual(p1, p2, 1) && p1[1] > p2[1]) || (semverEqual(p1, p2, 2) && p1[2] > p2[2]); 17 | } 18 | if (!result && /^[<>]=$/.test(op)) { 19 | result = semverEqual(p1, p2, 3); 20 | } 21 | 22 | function semverEqual(p1, p2, cnt) { 23 | var i, equal = true; 24 | for (i=0; i|git'; the newest version in the local lib 6 | // directory is the default 7 | function getJQueryPath() { 8 | var parts = document.location.search.slice(1).split('&'), 9 | length = parts.length, 10 | version = '1.5.2', 11 | i, current; 12 | 13 | for (i = 0; i < length; i++) { 14 | current = parts[i].split('='); 15 | if (current[0] === 'jquery') { 16 | version = current[1]; 17 | break; 18 | } 19 | } 20 | 21 | return version === 'git' ? 'http://code.jquery.com/jquery-git' : 22 | '../lib/jquery-' + version; 23 | } 24 | 25 | require.config({ 26 | // Test the jquery.mockjax registers itself with the right module 27 | // name when loaded just by the file name 28 | baseUrl: '../../src', 29 | paths: { 30 | // jQuery uses fixed name for their AMD module; point it to 31 | // the right path according to the URL parameters 32 | 'jquery': getJQueryPath(), 33 | // Make referring to the test modules easier by a short prefix 34 | 'test': '../test/requirejs' 35 | } 36 | }); 37 | 38 | // Require all modules with tests; it will execute them right away 39 | require(['test/test_module'], function () { 40 | // Initialize the QUnit UI first after the test were run 41 | QUnit.start(); 42 | }); 43 | }(window.QUnit)); 44 | -------------------------------------------------------------------------------- /test/requirejs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MockJax AMD Module Test 5 | 6 | 8 | 9 | 12 | 13 | 18 | 19 | 20 | 21 | 22 | 23 |

24 | MockJax 25 | jQuery 1.7.2 26 | jQuery 1.8.3 27 | jQuery 1.9.1 28 | jQuery 1.10.2 29 | jQuery 1.11.2 30 | jQuery 2.0.3 31 | jQuery 2.1.4 32 | jQuery 2.2.4 33 | jQuery 3.0.0 34 | jQuery 3.1.0 35 | jQuery Latest (git) 36 |

37 | 38 |

39 |

40 |
    41 | 42 | 43 | -------------------------------------------------------------------------------- /test/test-url-match.js: -------------------------------------------------------------------------------- 1 | (function(qunit, $) { 2 | 'use strict'; 3 | 4 | var t = qunit.test; 5 | 6 | /* ------------------------- */ 7 | qunit.module( 'URL Matching' ); 8 | /* ------------------------- */ 9 | 10 | t('Exact string', function(assert) { 11 | var done = assert.async(); 12 | 13 | $.mockjax({ 14 | url: '/exact/string', 15 | responseText: 'exact string' 16 | }); 17 | $.mockjax({ 18 | url: '*', 19 | responseText: 'catch all' 20 | }); 21 | 22 | $.ajax({ 23 | url: '/exact/string', 24 | error: qunit.noErrorCallbackExpected, 25 | complete: function(xhr) { 26 | assert.equal(xhr.responseText, 'exact string', 'Exact string url match'); 27 | done(); 28 | } 29 | }); 30 | }); 31 | 32 | t('Wildcard match', function(assert) { 33 | function mock(mockUrl, url, response) { 34 | $.mockjax({ 35 | url: mockUrl, 36 | responseText: response 37 | }); 38 | $.ajax({ 39 | async: false, 40 | url: url, 41 | error: qunit.noErrorCallbackExpected, 42 | complete: function(xhr) { 43 | assert.equal(xhr.responseText, response); 44 | } 45 | }); 46 | } 47 | mock('/wildcard*w', '/wildcard/123456/w', 'w'); 48 | mock('/wildcard*x', '/wildcard/123456/x', 'x'); 49 | mock('*y', '/wildcard/123456/y', 'y'); 50 | mock('z*', 'z/wildcard/123456', 'z'); 51 | mock('/wildcard*aa/second/*/nice', '/wildcard/123456/aa/second/9991231/nice', 'aa'); 52 | }); 53 | 54 | t('RegEx match', function(assert) { 55 | var done = assert.async(); 56 | 57 | $.mockjax({ 58 | url: /^\/regex-([0-9]+)/i, 59 | responseText: 'regex match' 60 | }); 61 | $.mockjax({ 62 | url: '*', 63 | responseText: 'catch all' 64 | }); 65 | 66 | $.ajax({ 67 | url: '/regex-123456', 68 | error: qunit.noErrorCallbackExpected, 69 | complete: function(xhr) { 70 | assert.equal(xhr.responseText, 'regex match', 'RegEx match'); 71 | done(); 72 | } 73 | }); 74 | }); 75 | 76 | })(window.QUnit, window.jQuery); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-mockjax", 3 | "title": "jQuery Mockjax", 4 | "version": "2.2.1", 5 | "main": "./src/jquery.mockjax.js", 6 | "description": "The jQuery Mockjax Plugin provides a simple and extremely flexible interface for mocking or simulating ajax requests and responses.", 7 | "url": "https://github.com/jakerella/jquery-mockjax", 8 | "scripts": { 9 | "test": "grunt test:all" 10 | }, 11 | "keywords": [ 12 | "ajax", 13 | "mock", 14 | "unit", 15 | "testing", 16 | "jquery-plugin", 17 | "ecosystem:jquery" 18 | ], 19 | "author": { 20 | "name": "Jonathan Sharp", 21 | "url": "http://jdsharp.com" 22 | }, 23 | "contributors": [ 24 | { 25 | "name": "Doug Neiner" 26 | }, 27 | { 28 | "name": "Jonathan Creamer" 29 | }, 30 | { 31 | "name": "Jordan Kasper" 32 | } 33 | ], 34 | "homepage": "https://github.com/jakerella/jquery-mockjax", 35 | "repository": { 36 | "type": "git", 37 | "url": "https://github.com/jakerella/jquery-mockjax.git" 38 | }, 39 | "bugs": { 40 | "web": "http://github.com/jakerella/jquery-mockjax/issues" 41 | }, 42 | "licenses": [ 43 | { 44 | "type": "MIT", 45 | "url": "http://opensource.org/licenses/MIT" 46 | }, 47 | { 48 | "type": "GPLv2", 49 | "url": "http://www.gnu.org/licenses/gpl-2.0.html" 50 | } 51 | ], 52 | "dependencies": { 53 | "jquery": ">=1.5.2" 54 | }, 55 | "devDependencies": { 56 | "browserify": "^13.1.0", 57 | "grunt": "^0.4.5", 58 | "grunt-browserify": "^5.0.0", 59 | "grunt-contrib-concat": "^0.5.0", 60 | "grunt-contrib-jshint": "^0.12.0", 61 | "grunt-contrib-qunit": "^0.5.2", 62 | "grunt-contrib-uglify": "^0.6.0", 63 | "grunt-contrib-watch": "^0.6.1", 64 | "grunt-mocha-test": "^0.12.7", 65 | "jsdom": "~4.2.0", 66 | "load-grunt-tasks": "^0.6.0", 67 | "mocha": "^2.2.4", 68 | "sinon": "^1.17.4", 69 | "xmlhttprequest": "^1.7.0" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /test/nodejs/test.js: -------------------------------------------------------------------------------- 1 | /* globals describe,beforeEach,afterEach,it */ 2 | 3 | var jsDomEnv = require('jsdom').env, 4 | assert = require('assert'); 5 | 6 | describe('Node module setup', function() { 7 | 'use strict'; 8 | 9 | var $, xhr, win; 10 | 11 | beforeEach(function(done) { 12 | jsDomEnv('', function (error, window) { 13 | if (error) { 14 | assert(false); 15 | } else { 16 | win = window; 17 | $ = require('jquery')(window); 18 | xhr = require('xmlhttprequest').XMLHttpRequest; 19 | $.support.cors = true; 20 | $.ajaxSettings.xhr = function () { 21 | /*jshint newcap:false*/ 22 | return new xhr(); 23 | /*jshint newcap:true*/ 24 | }; 25 | } 26 | done(); 27 | }); 28 | }); 29 | 30 | describe('Mockjax Node Module Tests', function() { 31 | 32 | afterEach(function() { 33 | if ($ && $.mockjax) { 34 | $.mockjax.clear(); 35 | } 36 | }); 37 | 38 | 39 | it('should be loaded when required', function() { 40 | var mockjax = require('../../src/jquery.mockjax')($, win); 41 | assert.equal(typeof mockjax, 'function'); 42 | assert.equal(typeof $.mockjax, 'function'); 43 | }); 44 | 45 | it('should mock a simple request using returned module', function(done) { 46 | var mockjax = require('../../src/jquery.mockjax')($, win); 47 | 48 | mockjax({ 49 | url: '/resource', 50 | responseText: 'content' 51 | }); 52 | 53 | $.ajax({ 54 | url: '/resource', 55 | success: function(response) { 56 | assert.equal(response, 'content'); 57 | }, 58 | error: function () { 59 | assert(false); 60 | }, 61 | complete: function () { 62 | done(); 63 | } 64 | }); 65 | }); 66 | 67 | it('should mock a simple request using $.mockjax', function(done) { 68 | require('../../src/jquery.mockjax')($, win); 69 | 70 | $.mockjax({ 71 | url: '/foo', 72 | responseText: 'bar' 73 | }); 74 | 75 | $.ajax({ 76 | url: '/foo', 77 | success: function(response) { 78 | assert.equal(response, 'bar'); 79 | }, 80 | error: function () { 81 | assert(false); 82 | }, 83 | complete: function () { 84 | done(); 85 | } 86 | }); 87 | }); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | MockJax Tests - 28 | 29 | 30 |

    31 | Mockjax 32 | jQuery 1.5.2 33 | jQuery 1.6.4 34 | jQuery 1.7.2 35 | jQuery 1.8.3 36 | jQuery 1.9.1 37 | jQuery 1.10.2 38 | jQuery 1.11.3 39 | jQuery 1.12.4 40 | jQuery 2.0.3 41 | jQuery 2.1.4 42 | jQuery 2.2.4 43 | jQuery 3.0.0 44 | jQuery 3.1.0 45 | jQuery Latest (git) 46 | 47 |

    48 |

    49 |

    50 |
      51 | 52 | 53 | -------------------------------------------------------------------------------- /test/dist-min.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | MockJax Tests - 28 | 29 | 30 |

      31 | Mockjax 32 | jQuery 1.5.2 33 | jQuery 1.6.4 34 | jQuery 1.7.2 35 | jQuery 1.8.3 36 | jQuery 1.9.1 37 | jQuery 1.10.2 38 | jQuery 1.11.3 39 | jQuery 1.12.4 40 | jQuery 2.0.3 41 | jQuery 2.1.4 42 | jQuery 2.2.4 43 | jQuery 3.0.0 44 | jQuery 3.1.0 45 | jQuery Latest (git) 46 | 47 |

      48 |

      49 |

      50 |
        51 | 52 | 53 | -------------------------------------------------------------------------------- /test/jquery.js: -------------------------------------------------------------------------------- 1 | 2 | (function(QUnit, basePath) { 3 | 'use strict'; 4 | 5 | var parts = document.location.search.slice( 1 ).split( '&' ), 6 | length = parts.length, 7 | i = 0, 8 | current, 9 | QUnitDone = false, 10 | QUnitErrors = false, 11 | currIndex = document.location.search.match(/v=([0-9]+)/), 12 | nextIndex = (currIndex && Number(currIndex[1]) || 0) + 1, // +1 because QUnit makes the h1 text a link 13 | version = '1.5.2', 14 | file = 'http://code.jquery.com/jquery-git.js'; 15 | 16 | for ( ; i < length; i++ ) { 17 | current = parts[ i ].split( '=' ); 18 | if ( current[ 0 ] === 'jquery' ) { 19 | version = current[ 1 ]; 20 | break; 21 | } 22 | } 23 | 24 | if (version !== 'git') { 25 | file = basePath + 'lib/jquery-' + version + '.js'; 26 | } 27 | 28 | 29 | document.write( '' ); 30 | 31 | 32 | // Track when QUnit finishes so we can redirect if necessary 33 | QUnit.done(function(details) { 34 | QUnitDone = true; 35 | QUnitErrors = !!details.failed; 36 | }); 37 | 38 | 39 | // Set up the 'run all' button once jQuery is loaded 40 | document.getElementById('jquery').onload = function() { 41 | $(document).ready(function() { 42 | // Sigh... QUnit 'rebuilds' the header, so we have to wait for that before 43 | // attaching our click event, otherwise we lose the handler in the rebuild 44 | setTimeout(function() { 45 | var btn = $('.runall'); 46 | 47 | if (currIndex) { 48 | // We're already in a run... 49 | btn.attr('disabled', 'disabled'); 50 | setupNextRedirect(); 51 | 52 | } else { 53 | // Set up a new run... 54 | btn.removeAttr('disabled').click(function(e) { 55 | e.preventDefault(); 56 | setupNextRedirect(); 57 | }); 58 | } 59 | }, 1000); 60 | }); 61 | }; 62 | 63 | function getNextLink() { 64 | var nextLink = null, 65 | nextLinkNode = $('#qunit-header a:eq(' + nextIndex + ')'); 66 | 67 | if (nextLinkNode && nextLinkNode.length) { 68 | nextLink = nextLinkNode.attr('href') + '&v=' + nextIndex; 69 | } 70 | return nextLink; 71 | } 72 | 73 | function setupNextRedirect() { 74 | var nextLink = getNextLink(); 75 | 76 | if (nextLink) { 77 | if (QUnitDone && !QUnitErrors) { 78 | // QUnit already finished, so we'll redirect 79 | document.location.replace(nextLink); 80 | 81 | } else if (!QUnitDone) { 82 | QUnit.done(function(details) { 83 | if (!details.failed) { 84 | // we only redirect if the last test run succeeded 85 | setTimeout(function() { 86 | document.location.replace(nextLink); 87 | }, 1000); 88 | } 89 | }); 90 | } 91 | } 92 | } 93 | 94 | })(window.QUnit, window.testJQPath || '../'); 95 | -------------------------------------------------------------------------------- /test/test-headers.js: -------------------------------------------------------------------------------- 1 | (function(qunit, $) { 2 | 'use strict'; 3 | 4 | var t = qunit.test; 5 | 6 | /* -------------------- */ 7 | qunit.module( 'Headers' ); 8 | /* -------------------- */ 9 | 10 | t('headers can be inspected via setRequestHeader()', function(assert) { 11 | var done = assert.async(); 12 | 13 | assert.expect(1); 14 | 15 | $(document).ajaxSend(function(event, xhr) { 16 | xhr.setRequestHeader('X-CSRFToken', ''); 17 | }); 18 | 19 | $.mockjax( function ( requestSettings ) { 20 | var key; 21 | 22 | if ('/inspect-headers' === requestSettings.url) { 23 | return { 24 | response: function() { 25 | if (typeof requestSettings.headers['X-Csrftoken'] !== 'undefined') { 26 | key = 'X-Csrftoken'; // bugs in jquery 1.5 27 | } else { 28 | key = 'X-CSRFToken'; 29 | } 30 | assert.equal(requestSettings.headers[key], ''); 31 | this.responseText = {}; 32 | } 33 | }; 34 | } 35 | }); 36 | 37 | $.ajax({ 38 | url: '/inspect-headers', 39 | complete: done 40 | }); 41 | }); 42 | 43 | t('Response status callback', function(assert) { 44 | var done = assert.async(); 45 | 46 | $.mockjax({ 47 | url: '/response-callback', 48 | status: 403 49 | }); 50 | 51 | $.ajax({ 52 | url: '/response-callback', 53 | success: function() { 54 | assert.ok(false, 'Success handler should not have been called'); 55 | }, 56 | complete: function(xhr) { 57 | assert.equal(xhr.status, 403, 'response status matches'); 58 | done(); 59 | } 60 | }); 61 | }); 62 | 63 | t('Setting the content-type', function(assert) { 64 | var done = assert.async(); 65 | 66 | $.mockjax({ 67 | url: '/response-callback', 68 | contentType: 'text/json', 69 | responseText: { 70 | foo: 'bar' 71 | } 72 | }); 73 | 74 | $.ajax({ 75 | url: '/response-callback', 76 | dataType: 'json', 77 | error: qunit.noErrorCallbackExpected, 78 | success: function(json) { 79 | assert.deepEqual(json, { 'foo' : 'bar' }, 'JSON Object matches'); 80 | }, 81 | complete: function(xhr) { 82 | assert.equal(xhr.getResponseHeader('Content-Type'), 'text/json', 'Content type of json'); 83 | done(); 84 | } 85 | }); 86 | }); 87 | 88 | t('Setting additional HTTP response headers', function(assert) { 89 | var done = assert.async(); 90 | 91 | $.mockjax({ 92 | url: '/response-callback', 93 | headers: { 94 | 'X-Must-Exist': 'yes' 95 | }, 96 | responseText: 'done' 97 | }); 98 | 99 | $.ajax({ 100 | url: '/response-callback', 101 | error: qunit.noErrorCallbackExpected, 102 | success: function(response) { 103 | assert.equal( response, 'done', 'Response text matches' ); 104 | }, 105 | complete: function(xhr) { 106 | assert.equal(xhr.getResponseHeader( 'X-Must-Exist' ), 'yes', 'Header matches'); 107 | done(); 108 | } 109 | }); 110 | }); 111 | 112 | t('Testing that request headers do not overwrite response headers', function(assert) { 113 | var done = assert.async(); 114 | 115 | $.mockjax({ 116 | url: '/restful/fortune', 117 | headers : { 118 | prop: 'response' 119 | } 120 | }); 121 | 122 | var returnedXhr = $.ajax({ 123 | type: 'GET', 124 | url: '/restful/fortune', 125 | headers : { 126 | prop : 'request' 127 | }, 128 | success: function(res, status, xhr) { 129 | if (xhr) { 130 | assert.equal(xhr && xhr.getResponseHeader('prop'), 'response', 'response header should be correct'); 131 | } else { 132 | assert.equal(returnedXhr.getResponseHeader('prop'), 'response', 'response header should be correct'); 133 | } 134 | }, 135 | error: qunit.noErrorCallbackExpected, 136 | complete: done 137 | }); 138 | }); 139 | 140 | })(window.QUnit, window.jQuery); -------------------------------------------------------------------------------- /test/test-namespace.js: -------------------------------------------------------------------------------- 1 | (function(qunit, $) { 2 | 'use strict'; 3 | 4 | var t = qunit.test; 5 | 6 | /* -------------------- */ 7 | qunit.module('namespace'); 8 | /* -------------------- */ 9 | 10 | t('url should be namespaced via global mockjax settings', function(assert) { 11 | var done = assert.async(); 12 | 13 | $.mockjaxSettings.namespace = '/api/v1'; 14 | 15 | $.mockjax({ 16 | url: 'myservice' 17 | }); 18 | 19 | $.ajax({ 20 | url: '/api/v1/myservice', 21 | error: qunit.noErrorCallbackExpected, 22 | complete: function(xhr) { 23 | assert.equal(xhr.status, 200, 'Response was successful'); 24 | done(); 25 | } 26 | }); 27 | }); 28 | 29 | t('should be able to override global namespace per-mock', function(assert) { 30 | var done = assert.async(); 31 | 32 | $.mockjaxSettings.namespace = '/api/v1'; 33 | 34 | $.mockjax({ 35 | url: 'myservice', 36 | namespace: '/api/v2' 37 | }); 38 | 39 | $.ajax({ 40 | url: '/api/v2/myservice', 41 | error: qunit.noErrorCallbackExpected, 42 | complete: function(xhr) { 43 | assert.equal(xhr.status, 200, 'Response was successful'); 44 | $.ajax({ 45 | url: '/api/v1/myservice', 46 | error: function() { 47 | assert.ok(true, 'error callback was called'); 48 | done(); 49 | } 50 | }); 51 | } 52 | }); 53 | }); 54 | 55 | t('should not mock a non-matching url within a namespace', function(assert) { 56 | var done = assert.async(); 57 | 58 | $.mockjaxSettings.namespace = '/api/v1'; 59 | 60 | $.mockjax({ 61 | url: 'myservice' 62 | }); 63 | 64 | $.ajax({ 65 | url: '/api/v1/yourservice', 66 | success: function() { 67 | assert.ok(false, 'call should not be successful'); 68 | }, 69 | error: function() { 70 | assert.ok(true, 'error callback was called'); 71 | done(); 72 | } 73 | }); 74 | }); 75 | 76 | t('should handle multiple mocks in a row within a namespace', function(assert) { 77 | var done = assert.async(); 78 | 79 | $.mockjaxSettings.namespace = '/api/v1'; 80 | 81 | $.mockjax({ 82 | url: 'one' 83 | }); 84 | 85 | $.mockjax({ 86 | url: 'two' 87 | }); 88 | 89 | $.ajax({ 90 | url: '/api/v1/one', 91 | error: qunit.noErrorCallbackExpected, 92 | complete: function(xhr) { 93 | assert.equal(xhr.status, 200, 'Response was successful'); 94 | $.ajax({ 95 | url: '/api/v1/two', 96 | complete: function(xhr) { 97 | assert.equal(xhr.status, 200, 'Response was successful'); 98 | done(); 99 | } 100 | }); 101 | } 102 | }); 103 | }); 104 | 105 | t('should pass the correct url to the response settings', function(assert) { 106 | var done = assert.async(); 107 | 108 | $.mockjaxSettings.namespace = '/api/v1'; 109 | 110 | $.mockjax({ 111 | url: 'myservice', 112 | response: function(settings) { 113 | assert.equal(settings.url, '/api/v1/myservice'); 114 | } 115 | }); 116 | 117 | $.ajax({ 118 | url: '/api/v1/myservice', 119 | error: qunit.noErrorCallbackExpected, 120 | complete: function(xhr) { 121 | assert.equal(xhr.status, 200, 'Response was successful'); 122 | done(); 123 | } 124 | }); 125 | }); 126 | 127 | t('should handle extra slashes', function(assert) { 128 | var done = assert.async(); 129 | 130 | $.mockjaxSettings.namespace = '/api/v1/'; 131 | 132 | $.mockjax({ 133 | url: '/myservice' 134 | }); 135 | 136 | $.ajax({ 137 | url: '/api/v1/myservice', 138 | error: qunit.noErrorCallbackExpected, 139 | complete: function(xhr) { 140 | assert.equal(xhr.status, 200, 'Response was successful'); 141 | done(); 142 | } 143 | }); 144 | }); 145 | 146 | t('should handle missing slashes', function(assert) { 147 | var done = assert.async(); 148 | 149 | $.mockjaxSettings.namespace = '/api/v1'; 150 | 151 | $.mockjax({ 152 | url: 'myservice' 153 | }); 154 | 155 | $.ajax({ 156 | url: '/api/v1/myservice', 157 | error: qunit.noErrorCallbackExpected, 158 | complete: function(xhr) { 159 | assert.equal(xhr.status, 200, 'Response was successful'); 160 | done(); 161 | } 162 | }); 163 | }); 164 | 165 | })(window.QUnit, window.jQuery); -------------------------------------------------------------------------------- /test/test-header-match.js: -------------------------------------------------------------------------------- 1 | (function(qunit, $) { 2 | 'use strict'; 3 | 4 | var t = qunit.test; 5 | 6 | /* ----------------------------- */ 7 | qunit.module( 'Headers Matching' ); 8 | /* ----------------------------- */ 9 | 10 | t('Not equal headers', function(assert) { 11 | var done = assert.async(); 12 | 13 | $.mockjax({ 14 | url: '/exact/string', 15 | requestHeaders: { 16 | Authorization: '12345' 17 | }, 18 | responseText: 'Exact headers' 19 | }); 20 | 21 | $.ajax({ 22 | url: '/exact/string', 23 | error: function() { assert.ok(true, 'Error called on bad request headers matching'); }, 24 | success: function() { assert.ok(false, 'Success should not be called'); }, 25 | complete: function() { 26 | var mockedAjaxCalls = $.mockjax.mockedAjaxCalls(); 27 | assert.equal(mockedAjaxCalls.length, 0, 'No mocked Ajax calls should have been returned'); 28 | done(); 29 | } 30 | }); 31 | }); 32 | 33 | t('Not equal headers values', function(assert) { 34 | var done = assert.async(); 35 | 36 | $.mockjax({ 37 | url: '/exact/string', 38 | requestHeaders: { 39 | Authorization: '12345' 40 | }, 41 | responseText: 'Exact headers' 42 | }); 43 | 44 | $.ajax({ 45 | url: '/exact/string', 46 | headers: { 47 | Authorization: '6789' 48 | }, 49 | error: function() { assert.ok(true, 'Error called on bad request headers matching'); }, 50 | success: function() { assert.ok(false, 'Success should not be called'); }, 51 | complete: function() { 52 | var mockedAjaxCalls = $.mockjax.mockedAjaxCalls(); 53 | assert.equal(mockedAjaxCalls.length, 0, 'No mocked Ajax calls should have been returned'); 54 | done(); 55 | } 56 | }); 57 | }); 58 | 59 | t('Not equal multiple headers', function(assert) { 60 | var done = assert.async(); 61 | 62 | $.mockjax({ 63 | url: '/exact/string', 64 | requestHeaders: { 65 | Authorization: '12345', 66 | MyHeader: 'hello' 67 | }, 68 | responseText: 'Exact headers' 69 | }); 70 | 71 | $.ajax({ 72 | url: '/exact/string', 73 | headers: { 74 | Authorization: '12345' 75 | }, 76 | error: function() { assert.ok(true, 'Error called on bad request headers matching'); }, 77 | success: function() { assert.ok(false, 'Success should not be called'); }, 78 | complete: function() { 79 | var mockedAjaxCalls = $.mockjax.mockedAjaxCalls(); 80 | assert.equal(mockedAjaxCalls.length, 0, 'No mocked Ajax calls should have been returned'); 81 | done(); 82 | } 83 | }); 84 | }); 85 | 86 | t('Exact headers keys and values', function(assert) { 87 | var done = assert.async(); 88 | 89 | $.mockjax({ 90 | url: '/exact/string', 91 | requestHeaders: { 92 | Authorization: '12345' 93 | }, 94 | responseText: 'Exact headers' 95 | }); 96 | 97 | $.ajax({ 98 | url: '/exact/string', 99 | error: qunit.noErrorCallbackExpected, 100 | headers: { 101 | Authorization: '12345' 102 | }, 103 | complete: function(xhr) { 104 | var mockedAjaxCalls = $.mockjax.mockedAjaxCalls(); 105 | assert.equal(mockedAjaxCalls.length, 1, 'A mocked Ajax calls should have been returned'); 106 | assert.equal(xhr.responseText, 'Exact headers', 'Exact headers keys and values'); 107 | done(); 108 | } 109 | }); 110 | }); 111 | 112 | t('Exact multiple headers keys and values', function(assert) { 113 | var done = assert.async(); 114 | 115 | $.mockjax({ 116 | url: '/exact/string', 117 | requestHeaders: { 118 | Authorization: '12345', 119 | MyHeader: 'hello' 120 | }, 121 | responseText: 'Exact multiple headers' 122 | }); 123 | 124 | $.ajax({ 125 | url: '/exact/string', 126 | error: qunit.noErrorCallbackExpected, 127 | headers: { 128 | Authorization: '12345', 129 | MyHeader: 'hello' 130 | }, 131 | complete: function(xhr) { 132 | var mockedAjaxCalls = $.mockjax.mockedAjaxCalls(); 133 | assert.equal(mockedAjaxCalls.length, 1, 'A mocked Ajax calls should have been returned'); 134 | assert.equal(xhr.responseText, 'Exact multiple headers', 'Exact headers keys and values'); 135 | done(); 136 | } 137 | }); 138 | }); 139 | 140 | })(window.QUnit, window.jQuery); -------------------------------------------------------------------------------- /test/test-logging.js: -------------------------------------------------------------------------------- 1 | (function(qunit, $, sinon) { 2 | 'use strict'; 3 | 4 | var t = qunit.test; 5 | var winLogger; 6 | 7 | // Note: currently sinon cannot stub object methods in this manner in IE 8 | // See GH issue: https://github.com/sinonjs/sinon/issues/1009 9 | // As such, we'll be skipping the logger tests for IE currently 10 | if (/MSIE/.test(navigator.userAgent)) { 11 | qunit.module('Logging'); 12 | 13 | t('UNABLE TO TEST LOGGER IN IE', function(assert) { 14 | assert.ok(true, 'Cannot stub console functions with Sinon, see https://github.com/sinonjs/sinon/issues/1009'); 15 | }); 16 | return; 17 | } 18 | 19 | /* -------------------- */ 20 | qunit.module( 'Logging', { 21 | /* -------------------- */ 22 | 23 | beforeEach: function() { 24 | winLogger = { 25 | debug: sinon.stub(console, 'debug'), 26 | log: sinon.stub(console, 'log'), 27 | info: sinon.stub(console, 'info'), 28 | warn: sinon.stub(console, 'warn'), 29 | error: sinon.stub(console, 'error') 30 | }; 31 | $.mockjaxSettings.logger = winLogger; 32 | $.mockjaxSettings.logging = 2; 33 | }, 34 | afterEach: function() { 35 | winLogger.debug.restore(); 36 | winLogger.log.restore(); 37 | winLogger.info.restore(); 38 | winLogger.warn.restore(); 39 | winLogger.error.restore(); 40 | } 41 | }); 42 | 43 | t('Default log handler (window.console)', function(assert) { 44 | var done = assert.async(); 45 | 46 | $.mockjax({ 47 | url: '*' 48 | }); 49 | $.ajax({ 50 | url: '/console', 51 | type: 'GET', 52 | complete: function() { 53 | assert.ok(winLogger.info.calledWith('MOCK GET: /console'), 'Default log handler was not called'); 54 | done(); 55 | } 56 | }); 57 | }); 58 | 59 | t('Logging with high level', function(assert) { 60 | $.mockjaxSettings.logging = 4; 61 | $.mockjax._logger.debug({}, 'foobar'); 62 | $.mockjax._logger.info({}, 'foobar'); 63 | $.mockjax._logger.error({}, 'foobar'); 64 | assert.ok(winLogger.debug.calledWith('foobar'), 'Log handler 4 was not called for debug'); 65 | assert.ok(winLogger.info.calledWith('foobar'), 'Log handler 4 was not called for info'); 66 | assert.ok(winLogger.error.calledWith('foobar'), 'Log handler 4 was not called for error'); 67 | }); 68 | 69 | t('Logging with low level', function(assert) { 70 | $.mockjaxSettings.logging = 0; 71 | $.mockjax._logger.debug({}, 'foobar'); 72 | $.mockjax._logger.debug({ logging: 4 }, 'foobar'); 73 | $.mockjax._logger.info({}, 'foobar'); 74 | $.mockjax._logger.error({}, 'foobar'); 75 | assert.strictEqual(winLogger.debug.callCount, 1, 'Log handler 0 was called too much for debug'); 76 | assert.strictEqual(winLogger.info.callCount, 0, 'Log handler 0 was called for info'); 77 | assert.ok(winLogger.error.calledWith('foobar'), 'Log handler 4 was not called for error'); 78 | }); 79 | 80 | t('Custom (deprecated) log handler', function(assert) { 81 | var done = assert.async(); 82 | 83 | var msg = null; 84 | $.mockjaxSettings.log = function customLogger( mockHandler, requestSettings) { 85 | msg = mockHandler.url + ' - ' + requestSettings.type.toUpperCase() + ': ' + requestSettings.url; 86 | }; 87 | $.mockjax({ 88 | url: '*' 89 | }); 90 | $.ajax({ 91 | url: '/console', 92 | type: 'GET', 93 | complete: function() { 94 | assert.equal(msg, '* - GET: /console', 'Custom log handler was not called'); 95 | done(); 96 | } 97 | }); 98 | }); 99 | 100 | t('Disable logging via `logging: false`', function(assert) { 101 | var done = assert.async(); 102 | 103 | $.mockjaxSettings.logging = false; 104 | 105 | $.mockjax({ 106 | url: '*' 107 | }); 108 | $.ajax({ 109 | url: '/console', 110 | complete: function() { 111 | assert.strictEqual(winLogger.info.callCount, 0, 'Log called when disabled'); 112 | 113 | $.mockjax._logger.debug({}, 'foo'); 114 | assert.strictEqual(winLogger.debug.callCount, 0, 'Log called when disabled'); 115 | 116 | done(); 117 | } 118 | }); 119 | }); 120 | 121 | t('Disable logging per mock via `logging: false`', function(assert) { 122 | var done = assert.async(); 123 | 124 | $.mockjax({ 125 | url: '*', 126 | logging: false 127 | }); 128 | 129 | $.ajax({ 130 | url: '/console', 131 | complete: function() { 132 | assert.strictEqual(winLogger.info.callCount, 0, 'Log called when disabled'); 133 | 134 | $.mockjax._logger.warn({}, 'foo'); 135 | assert.strictEqual(winLogger.warn.callCount, 1, 'General log not called when disabled per mock'); 136 | 137 | done(); 138 | } 139 | }); 140 | }); 141 | 142 | 143 | })(window.QUnit, window.jQuery, window.sinon); 144 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // http://www.jshint.com/docs/ 3 | // Based on node-jshint@2.x.x 4 | 5 | "bitwise": true, //prohibits the use of bitwise operators such as ^ (XOR), | (OR) and others 6 | "camelcase": false, //force all variable names to use either camelCase style or UPPER_CASE with underscores 7 | "curly": true, //requires you to always put curly braces around blocks in loops and conditionals 8 | "eqeqeq": true, //prohibits the use of == and != in favor of === and !== 9 | "es3": false, //tells JSHint that your code needs to adhere to ECMAScript 3 specification 10 | "forin": false, //requires all `for in` loops to filter object's items with `hasOwnProperty()` 11 | "immed": true, //prohibits the use of immediate function invocations without wrapping them in parentheses 12 | "indent": 4, //enforces specific tab width 13 | "latedef": false, //prohibits the use of a variable before it was defined 14 | "newcap": true, //requires you to capitalize names of constructor functions 15 | "noarg": true, //prohibits the use of `arguments.caller` and `arguments.callee` 16 | "noempty": true, //warns when you have an empty block in your code 17 | "nonew": true, //prohibits the use of constructor functions for side-effects 18 | "plusplus": false, //prohibits the use of unary increment and decrement operators 19 | "quotmark": true, //enforces the consistency of quotation marks used throughout your code 20 | "undef": true, //prohibits the use of explicitly undeclared variables 21 | "unused": true, //warns when you define and never use your variables 22 | "strict": true, //requires all functions to run in ECMAScript 5's strict mode 23 | "trailing": true, //makes it an error to leave a trailing whitespace in your code 24 | "maxparams": 10, //set the max number of formal parameters allowed per function 25 | "maxdepth": 3, //control how nested do you want your blocks to be 26 | //"maxstatements": 0, //set the max number of statements allowed per function 27 | //"maxcomplexity": 0, //control cyclomatic complexity throughout your code 28 | "maxlen": 140, //set the maximum length of a line 29 | 30 | "asi": false, //suppresses warnings about missing semicolons 31 | "boss": false, //suppresses warnings about the use of assignments in cases where comparisons are expected 32 | "debug": false, //suppresses warnings about the debugger statements in your code 33 | "eqnull": false, //suppresses warnings about == null comparisons 34 | "esnext": false, //your code uses ES.next specific features such as const 35 | "evil": false, //suppresses warnings about the use of eval 36 | "expr": false, //suppresses warnings about the use of expressions where normally you would expect to see assignments or function calls 37 | "funcscope": false, //suppresses warnings about declaring variables inside of control structures while accessing them later from the outside 38 | "globalstrict": false, //suppresses warnings about the use of global strict mode 39 | "iterator": false, //suppresses warnings about the `__iterator__` property 40 | "lastsemic": false, //suppresses warnings about missing semicolons, but only when the semicolon is omitted for the last statement in a one-line block 41 | "laxbreak": false, //suppresses most of the warnings about possibly unsafe line breakings in your code 42 | "laxcomma": false, //suppresses warnings about comma-first coding style 43 | "loopfunc": false, //suppresses warnings about functions inside of loops 44 | "moz": false, //tells JSHint that your code uses Mozilla JavaScript extensions 45 | "multistr": false, //suppresses warnings about multi-line strings 46 | "proto": false, //suppresses warnings about the `__proto__` property 47 | "scripturl": true, //suppresses warnings about the use of script-targeted URLs—such as `javascript:...` 48 | "smarttabs": true, //suppresses warnings about mixed tabs and spaces when the latter are used for alignmnent only 49 | "shadow": false, //suppresses warnings about variable shadowing 50 | "sub": false, //suppresses warnings about using `[]` notation when it can be expressed in dot notation 51 | "supernew": false, //suppresses warnings about "weird" constructions like `new function () { ... }` and `new Object;` 52 | "validthis": true, //suppresses warnings about possible strict violations when the code is running in strict mode and you use `this` in a non-constructor function 53 | 54 | // ENVIRONMENTS / GLOBALS 55 | 56 | "browser": true, 57 | "jquery": true, 58 | "node": true, 59 | "globals": { 60 | "define": true, 61 | "ActiveXObject": true 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 'use strict'; 3 | 4 | // Project configuration 5 | grunt.initConfig({ 6 | // Metadata 7 | pkg: grunt.file.readJSON('package.json'), 8 | 9 | banner: [ 10 | '/*! <%= pkg.title || pkg.name %>', 11 | ' * A Plugin providing simple and flexible mocking of ajax requests and responses', 12 | ' * ', 13 | ' * Version: <%= pkg.version %>', 14 | ' * Home: <%= pkg.homepage %>', 15 | ' * Copyright (c) <%= grunt.template.today("yyyy") %> Jordan Kasper, formerly appendTo;', 16 | ' * NOTE: This repository was taken over by Jordan Kasper (@jakerella) October, 2014', 17 | ' * ', 18 | ' * Dual licensed under the MIT or GPL licenses.', 19 | ' * http://opensource.org/licenses/MIT OR http://www.gnu.org/licenses/gpl-2.0.html', 20 | ' */\n' 21 | ].join('\n'), 22 | 23 | // Task configuration 24 | concat: { 25 | options: { 26 | banner: '<%= banner %>', 27 | stripBanners: true 28 | }, 29 | dist: { 30 | src: ['./src/jquery.mockjax.js'], 31 | dest: './dist/jquery.mockjax.js' 32 | } 33 | }, 34 | uglify: { 35 | options: { 36 | preserveComments: 'some', 37 | }, 38 | dist: { 39 | src: './dist/jquery.mockjax.js', 40 | dest: './dist/jquery.mockjax.min.js' 41 | } 42 | }, 43 | jshint: { 44 | options: { 45 | jshintrc: true 46 | }, 47 | all: [ 48 | 'src/**/*.js', 49 | 'Gruntfile.js', 50 | 'test/test.js', 51 | 'test/requirejs/*.js', 52 | 'test/nodejs/*.js', 53 | 'test/browserify/main.js', 54 | 'test/browserify/test.js' 55 | ] 56 | }, 57 | qunit: { all: [] }, // NOTE: these tests are all run by the `test` task below to run against each jQuery version supported 58 | test: { 59 | all: { 60 | jQueryVersions: [ 61 | '1.5.2', 62 | '1.6.4', 63 | '1.7.2', 64 | '1.8.3', 65 | '1.9.1', 66 | '1.10.2', 67 | '1.11.3', 68 | '1.12.4', 69 | '2.0.3', 70 | '2.1.4', 71 | '2.2.4', 72 | '3.0.0', 73 | '3.1.0' 74 | ] 75 | }, 76 | requirejs: { 77 | jQueryVersions: [ 78 | '1.7.2', 79 | '1.8.3', 80 | '1.9.1', 81 | '1.10.2', 82 | '1.11.3', 83 | '1.12.4', 84 | '2.0.3', 85 | '2.1.4', 86 | '2.2.4', 87 | '3.0.0', 88 | '3.1.0' 89 | ] 90 | }, 91 | latestInBranch: { 92 | jQueryVersions: [ 93 | '1.12.4', 94 | '2.2.4', 95 | '3.1.0' 96 | ] 97 | }, 98 | oldestAndLatest: { 99 | jQueryVersions: [ 100 | '1.5.2', 101 | '1.12.4', 102 | '2.1.4', 103 | '3.1.0' 104 | ] 105 | }, 106 | edge: { 107 | jQueryVersions: ['git'] 108 | }, 109 | dist: { 110 | file: 'dist-min.html', 111 | jQueryVersions: [ 112 | '1.5.2', 113 | '1.6.4', 114 | '1.7.2', 115 | '1.8.3', 116 | '1.9.1', 117 | '1.10.2', 118 | '1.11.3', 119 | '1.12.4', 120 | '2.0.3', 121 | '2.1.4', 122 | '2.2.4', 123 | '3.0.0', 124 | '3.1.0' 125 | ] 126 | }, 127 | browserify: { 128 | file: 'browserify/index.html', 129 | jQueryVersions: ['not-applicable'] 130 | } 131 | }, 132 | mochaTest: { 133 | nodejs: { 134 | src: ['./test/nodejs/*.js'] 135 | } 136 | }, 137 | browserify: { 138 | test: { 139 | src: 'test/browserify/main.js', 140 | dest: 'test/browserify/bundle.js' 141 | } 142 | }, 143 | watch: { 144 | gruntfile: { 145 | files: './Gruntfile.js' 146 | }, 147 | source: { 148 | files: './src/*.js', 149 | tasks: ['jshint', 'test:latestInBranch'] 150 | } 151 | } 152 | }); 153 | 154 | require('load-grunt-tasks')(grunt); 155 | 156 | grunt.registerTask('dev', ['jshint', 'test:all', 'test:requirejs', 'browserify', 'test:browserify', 'mochaTest']); 157 | grunt.registerTask('build', ['dev', 'concat', 'uglify', 'test:dist']); 158 | grunt.registerTask('default', ['dev']); 159 | 160 | grunt.registerTask('test', 'Executes QUnit tests with all supported jQuery versions', function() { 161 | var i, l, 162 | versionUrls = [], 163 | source = arguments[0] || 'all', 164 | versions = grunt.config.get('test' + ('.' + source) + '.jQueryVersions') || [], 165 | file = grunt.config.get('test' + ('.' + source) + '.file') || 'index.html'; 166 | 167 | for (i=0, l=versions.length; i= 140, 'Correct delay simulation (' + delay + ')' ); 91 | assert.strictEqual( executed, 1, 'Callback execution order correct'); 92 | done(); 93 | } 94 | }); 95 | setTimeout(function() { 96 | assert.strictEqual( executed, 0, 'No premature callback execution'); 97 | executed++; 98 | }, 30); 99 | }); 100 | 101 | t('Response time with jsonp', function(assert) { 102 | var done = assert.async(); 103 | 104 | var executed = false, ts = new Date(); 105 | 106 | window.abcdef123456 = function() {}; 107 | 108 | $.ajax({ 109 | url: 'http://foobar.com/jsonp-delay?callback=?', 110 | dataType: 'jsonp', 111 | complete: function() { 112 | var delay = ((new Date()) - ts); 113 | // check against 140ms to allow for browser variance 114 | assert.ok( delay >= 140, 'Correct delay simulation (' + delay + ')' ); 115 | assert.ok( executed, 'Callback execution order correct'); 116 | window.abcdef123456 = null; 117 | done(); 118 | } 119 | }); 120 | 121 | setTimeout(function() { 122 | assert.ok( executed === false, 'No premature callback execution'); 123 | executed = true; 124 | }, 30); 125 | }); 126 | 127 | t('Response time with jsonp deferred response', function(assert) { 128 | var done = assert.async(); 129 | var executed = false, ts = new Date(); 130 | 131 | window.abcdef123456 = function() {}; 132 | 133 | $.ajax({ 134 | url: 'http://foobar.com/jsonp-delay?callback=?', 135 | dataType: 'jsonp' 136 | }).done(function() { 137 | var delay = ((new Date()) - ts); 138 | // check against 140ms to allow for browser variance 139 | assert.ok( delay >= 140, 'Correct delay simulation (' + delay + ')' ); 140 | assert.ok( executed, 'Callback execution order correct'); 141 | window.abcdef123456 = null; 142 | done(); 143 | }); 144 | 145 | setTimeout(function() { 146 | assert.ok( executed === false, 'No premature callback execution'); 147 | executed = true; 148 | }, 30); 149 | }); 150 | 151 | t('Response time with min and max values', function (assert) { 152 | var done = assert.async(); 153 | 154 | var executed = 0, 155 | that = this, 156 | ts = new Date(); 157 | $.ajax({ 158 | url: '/variable-delay', 159 | complete: function () { 160 | var delay = ((new Date()) - ts); 161 | assert.ok(delay >= that.variableDelayMin, 'Variable delay greater than min; delay was ' + delay); 162 | assert.ok(delay <= (that.variableDelayMax + that.processingDuration), 'Variable delay less than max; delay was ' + delay); 163 | assert.equal(executed, 1, 'Callback execution order correct'); 164 | done(); 165 | } 166 | }); 167 | setTimeout(function () { 168 | assert.strictEqual(executed, 0, 'No premature callback execution'); 169 | executed++; 170 | }, 30); 171 | }); 172 | 173 | t('Proxy asynchronous response time', function (assert) { 174 | var done = assert.async(); 175 | var executed = false, ts = new Date(); 176 | 177 | $.ajax({ 178 | url: '/proxy', 179 | type: 'json', 180 | success: function () { 181 | var delay = ((new Date()) - ts); 182 | assert.ok( delay >= 50, 'Correct delay simulation (' + delay + ')' ); 183 | assert.strictEqual(executed, false, 'No premature callback execution'); 184 | executed = true; 185 | done(); 186 | }, 187 | error: qunit.noErrorCallbackExpected 188 | }); 189 | setTimeout(function () { 190 | assert.strictEqual(executed, false, 'No premature callback execution'); 191 | }, 30); 192 | 193 | }); 194 | 195 | })(window.QUnit, window.jQuery); 196 | -------------------------------------------------------------------------------- /test/test-data-types.js: -------------------------------------------------------------------------------- 1 | (function(qunit, $) { 2 | 'use strict'; 3 | 4 | var t = qunit.test; 5 | 6 | /* ----------------------- */ 7 | qunit.module( 'Data Types' ); 8 | /* ----------------------- */ 9 | 10 | t('Response returns text', function(assert) { 11 | var done = assert.async(); 12 | 13 | $.mockjax({ 14 | url: '/text', 15 | contentType: 'text/plain', 16 | responseText: 'just text' 17 | }); 18 | $.ajax({ 19 | url: '/text', 20 | dataType: 'text', 21 | error: qunit.noErrorCallbackExpected, 22 | complete: function(xhr) { 23 | assert.equal(xhr.getResponseHeader('Content-Type'), 'text/plain', 'Content type of text/plain'); 24 | 25 | done(); 26 | } 27 | }); 28 | }); 29 | 30 | t('Response returns html', function(assert) { 31 | var done = assert.async(); 32 | 33 | $.mockjax({ 34 | url: '/html', 35 | contentType: 'text/html', 36 | responseText: '
        String
        ' 37 | }); 38 | $.ajax({ 39 | url: '/html', 40 | dataType: 'html', 41 | success: function(data) { 42 | assert.equal(data, '
        String
        ', 'HTML String matches'); 43 | }, 44 | error: qunit.noErrorCallbackExpected, 45 | complete: function(xhr) { 46 | assert.equal(xhr.getResponseHeader('Content-Type'), 'text/html', 'Content type of text/html'); 47 | done(); 48 | } 49 | }); 50 | }); 51 | 52 | t('Response returns json', function(assert) { 53 | var done = assert.async(); 54 | 55 | $.mockjax({ 56 | url: '/json', 57 | contentType: 'text/json', 58 | responseText: { 'foo' : 'bar', 'baz' : { 'car' : 'far' } } 59 | }); 60 | $.ajax({ 61 | url: '/json', 62 | dataType: 'json', 63 | success: function(json) { 64 | assert.deepEqual(json, { 'foo' : 'bar', 'baz' : { 'car' : 'far' } }, 'JSON Object matches'); 65 | }, 66 | error: qunit.noErrorCallbackExpected, 67 | complete: function(xhr) { 68 | assert.equal(xhr.getResponseHeader('Content-Type'), 'text/json', 'Content type of text/json'); 69 | done(); 70 | } 71 | }); 72 | }); 73 | 74 | t('Response returns jsonp', function(assert) { 75 | var done = assert.async(); 76 | 77 | $.mockjax({ 78 | url: '/jsonp*', 79 | contentType: 'text/json', 80 | proxy: 'test_jsonp.js' 81 | }); 82 | window.abcdef123456 = function(json) { 83 | assert.ok( true, 'JSONP Callback executed'); 84 | assert.deepEqual(json, { 'data' : 'JSONP is cool' }); 85 | }; 86 | 87 | $.ajax({ 88 | url: '/jsonp?callback=?', 89 | jsonpCallback: 'abcdef123456', 90 | dataType: 'jsonp', 91 | error: qunit.noErrorCallbackExpected, 92 | complete: function(xhr) { 93 | assert.equal(xhr.getResponseHeader('Content-Type'), 'text/json', 'Content type of text/json'); 94 | window.abcdef123456 = null; 95 | done(); 96 | } 97 | }); 98 | }); 99 | 100 | t('Response returns jsonp and return value from ajax is a promise if supported', function(assert) { 101 | var done = assert.async(); 102 | 103 | window.rquery = /\?/; 104 | 105 | $.mockjax({ 106 | url:'http://api*', 107 | responseText:{ 108 | success:true, 109 | ids:[21327211] 110 | }, 111 | dataType:'jsonp', 112 | contentType: 'text/json' 113 | }); 114 | 115 | var promiseObject = $.ajax({ 116 | url:'http://api.twitter.com/1/followers/ids.json?screen_name=test_twitter_user', 117 | dataType:'jsonp' 118 | }); 119 | 120 | assert.ok(promiseObject.done && promiseObject.fail, 'Got Promise methods'); 121 | promiseObject.then(function() { 122 | assert.ok(true, 'promise object then is executed'); 123 | done(); 124 | }); 125 | }); 126 | 127 | t('Response executes script', function(assert) { 128 | var done = assert.async(); 129 | 130 | $.mockjax({ 131 | url: '/script', 132 | contentType: 'text/plain', 133 | proxy: 'test_script.js' 134 | }); 135 | 136 | window.TEST_SCRIPT_VAR = 0; 137 | $.ajax({ 138 | url: '/script', 139 | dataType: 'script', 140 | error: qunit.noErrorCallbackExpected, 141 | complete: function(xhr) { 142 | assert.equal(window.TEST_SCRIPT_VAR, 1, 'Script executed'); 143 | assert.equal(xhr.getResponseHeader('Content-Type'), 'text/plain', 'Content type of text/plain'); 144 | 145 | done(); 146 | } 147 | }); 148 | }); 149 | 150 | t('Grouping deferred responses, if supported', function(assert) { 151 | var done = assert.async(); 152 | 153 | window.rquery = /\?/; 154 | 155 | $.mockjax({ 156 | url:'http://api*', 157 | responseText:{ 158 | success:true, 159 | ids:[21327211] 160 | }, 161 | dataType:'jsonp', 162 | contentType: 'text/json' 163 | }); 164 | 165 | var req1 = $.ajax({ 166 | url:'http://api.twitter.com/1/followers/ids.json?screen_name=test_twitter_user', 167 | dataType:'jsonp' 168 | }); 169 | var req2 = $.ajax({ 170 | url:'http://api.twitter.com/1/followers/ids.json?screen_name=test_twitter_user', 171 | dataType:'jsonp' 172 | }); 173 | var req3 = $.ajax({ 174 | url:'http://api.twitter.com/1/followers/ids.json?screen_name=test_twitter_user', 175 | dataType:'jsonp' 176 | }); 177 | 178 | $.when(req1, req2, req3).done(function() { 179 | assert.ok(true, 'Successfully grouped deferred responses'); 180 | done(); 181 | }); 182 | }); 183 | 184 | t('Response returns parsed XML', function(assert) { 185 | var done = assert.async(); 186 | 187 | $.mockjax({ 188 | url: '/xml', 189 | contentType: 'text/xml', 190 | responseXML: 'String' 191 | }); 192 | $.ajax({ 193 | url: '/xml', 194 | dataType: 'xml', 195 | success: function(xmlDom) { 196 | assert.ok( $.isXMLDoc( xmlDom ), 'Data returned is an XML DOM'); 197 | }, 198 | error: qunit.noErrorCallbackExpected, 199 | complete: function(xhr, error) { 200 | assert.ok(true, 'Error: ' + error); 201 | assert.equal(xhr.getResponseHeader('Content-Type'), 'text/xml', 'Content type of text/xml'); 202 | done(); 203 | } 204 | }); 205 | }); 206 | 207 | })(window.QUnit, window.jQuery); 208 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Mockjax # 2 | 3 | First of all, thank you for helping make Mockjax the best plugin it can be! We truly 4 | appreciate the support. Before you submit that Pull Request, please be sure to 5 | follow these guidelines. 6 | 7 | ## Key Points 8 | 9 | * Write small, atomic commits with good messages 10 | * Writes tests (for both passing and failing conditions) 11 | * **Run** the tests (`grunt test`, but also in various browsers) 12 | * Generate a distribution build (`grunt build`) 13 | * Write a good PR! 14 | 15 | 16 | ## Accurately describe your code submission ## 17 | 18 | Be sure to identify everything that is within your pull request in the description. 19 | If you have code that fixes a bug and also cleans up some documentation, please 20 | specify both! Additionally, if your PR fixes or resolves a specific Github issue 21 | please reference it using the `#[id]` format so that the two can be linked! 22 | 23 | ### Commit messages ### 24 | 25 | Just as with the PR description, your commit messages should clearly identify what 26 | was included in that commit. Keep them short and sweet so we can just scan the 27 | titles of the commit and dig deeper if we need to. 28 | 29 | ### Smaller commits ### 30 | 31 | Along the same line, we would prefer to see different aspects of your PR in 32 | separate commits versus one big commit. So if you are submitting a PR that fixes a 33 | bug, updates the documentation, and cleans up some whitespace, please place all 34 | three of those things in **separate commits**! This allows us to roll back specific 35 | work if need be without destroying the entire contribution. 36 | 37 | ## Try to keep the style consistent ## 38 | 39 | As much as possible we need to try to keep the coding style consistent within the 40 | plugin. That means using the same indentation style, quotes, spacing, etc. Please 41 | try to keep your work in line with what is already in the library already, but 42 | feel free to ping someone in the Github issues if you have any questions about 43 | coding style generally. 44 | 45 | ## Add tests! ## 46 | 47 | We really need to see tests for any commit other than documentation. If you are 48 | fixing a bug add a breaking test first, then the code that fixes that test. If you 49 | are developing a new feature, add complete tests for the feature. That includes 50 | tests for success cases as well as failure cases! 51 | 52 | We use [QUnit](http://qunitjs.com/) as our testing tool of choice, so please write 53 | them using that API. For now you can simply add them to the `/test/test.js` file. 54 | There are `module`s in there, so try to add the tests in a logical location. 55 | 56 | ### RUN THE TESTS ### 57 | 58 | Due to the need to load some of the proxy files asynchronously, you'll need to view 59 | the test files over HTTP. You can do some initial testing with PhantomJS using the 60 | Grunt task, but you should also test in (multiple) browsers! 61 | 62 | #### To run from Grunt... 63 | 64 | Simply run: 65 | 66 | ```shell 67 | ~$ grunt test 68 | ``` 69 | 70 | _Note that this will run all tests for all supported versions of jQuery!_ 71 | 72 | #### To run in a browser... 73 | 74 | You should be able to run a small local server: 75 | 76 | Node: 77 | ```shell 78 | ~$ npm install -g http-server 79 | ~$ cd /path/to/mockjax 80 | mockjax/$ http-server -p 8080 81 | ``` 82 | 83 | Python: 84 | ```shell 85 | ~$ cd /path/to/mockjax 86 | mockjax/$ python -m SimpleHTTPServer 8080 87 | ``` 88 | 89 | PHP (5.4+): 90 | ```shell 91 | ~$ cd /path/to/mockjax 92 | mockjax/$ php -S localhost:8080 93 | ``` 94 | 95 | Then just visit http://localhost:8080/test/index.html in the browser! Once there, 96 | be sure to **click through each of the jQuery versions in the header** to run the tests 97 | against each version. (If you have trouble running in different versions, make sure 98 | you are viewing `/test/index.html` not just `/test/` .) 99 | 100 | ### Run your tests everywhere ### 101 | 102 | Lastly, we'd like you to run your tests on as many browsers as possible. Check the 103 | main [README](README.md#browsers-tested) file for the browsers we support. If you 104 | don't have access to one of those browsers, try running the tests using a virtual 105 | machine or via a service like [BrowserStack](http://www.browserstack.com), 106 | [Sauce Labs](https://saucelabs.com), or [Modern.IE](https://www.modern.ie). 107 | 108 | ## Be sure to generate a build! 109 | 110 | Running the default `grunt` task will only lint and test the files, it does not 111 | produce a distribution as that isn't necessary most of the time. Instead, you 112 | should generate a new set of "dist" files before submitting your PR. To do this, 113 | just run `grunt build` 114 | 115 | ## Submit Your PR 116 | 117 | This is the last step! First, be sure you're merging with the correct branch! Version 118 | 2.0 of Mockjax will be the `master` branch very soon (hopefully we remember to update 119 | this message), but if you're submitting a bug fix, it should be submitted to the `v1.x` 120 | branch as well as `master` (if the bug exists in both). 121 | 122 | You should also write a good PR message with information on why this feature or fix is 123 | necesary or a good idea. For features, be sure to include information on _how to use_ 124 | the feature; and for bugs, information on how to reproduce the bug is helpful! 125 | 126 | ## Publishing a Release 127 | 128 | Although individual contributors cannot publish a release, it's good to have 129 | documentation on what goes into that in case anyone needs to take over the process. 130 | Currently, @jakerella is the only one doing so. 131 | 132 | 1. Create a branch for the release (usually with the proposed version number in the name) 133 | 1. Ensure that all tests are passing in all supported browsers that you can (see below). 134 | 1. Update the `CHANGELOG.md`, `package.json` version, and any other necessary files. 135 | (Note that these can be in a commit, but put them in that new branch.) 136 | 1. Make sure to generate fresh dist files if necessary and commit those. 137 | 1. Submit a PR for the branch, this will initiate the Travis CI checks. 138 | 1. Ask others for input on the PR (mostly testing in their own browsers). 139 | 1. *If all is well*, merge the branch into `master` 140 | 1. Create a release on Github with a tag matching the version number and proper info. 141 | 1. Run `npm publish` on `master` to push the new version up to npm. 142 | -------------------------------------------------------------------------------- /lib/qunit.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * QUnit 1.18.0 3 | * http://qunitjs.com/ 4 | * 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under the MIT license 7 | * http://jquery.org/license 8 | * 9 | * Date: 2015-04-03T10:23Z 10 | */ 11 | 12 | /** Font Family and Sizes */ 13 | 14 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 15 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 16 | } 17 | 18 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 19 | #qunit-tests { font-size: smaller; } 20 | 21 | 22 | /** Resets */ 23 | 24 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { 25 | margin: 0; 26 | padding: 0; 27 | } 28 | 29 | 30 | /** Header */ 31 | 32 | #qunit-header { 33 | padding: 0.5em 0 0.5em 1em; 34 | 35 | color: #8699A4; 36 | background-color: #0D3349; 37 | 38 | font-size: 1.5em; 39 | line-height: 1em; 40 | font-weight: 400; 41 | 42 | border-radius: 5px 5px 0 0; 43 | } 44 | 45 | #qunit-header a { 46 | text-decoration: none; 47 | color: #C2CCD1; 48 | } 49 | 50 | #qunit-header a:hover, 51 | #qunit-header a:focus { 52 | color: #FFF; 53 | } 54 | 55 | #qunit-testrunner-toolbar label { 56 | display: inline-block; 57 | padding: 0 0.5em 0 0.1em; 58 | } 59 | 60 | #qunit-banner { 61 | height: 5px; 62 | } 63 | 64 | #qunit-testrunner-toolbar { 65 | padding: 0.5em 1em 0.5em 1em; 66 | color: #5E740B; 67 | background-color: #EEE; 68 | overflow: hidden; 69 | } 70 | 71 | #qunit-userAgent { 72 | padding: 0.5em 1em 0.5em 1em; 73 | background-color: #2B81AF; 74 | color: #FFF; 75 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 76 | } 77 | 78 | #qunit-modulefilter-container { 79 | float: right; 80 | padding: 0.2em; 81 | } 82 | 83 | .qunit-url-config { 84 | display: inline-block; 85 | padding: 0.1em; 86 | } 87 | 88 | .qunit-filter { 89 | display: block; 90 | float: right; 91 | margin-left: 1em; 92 | } 93 | 94 | /** Tests: Pass/Fail */ 95 | 96 | #qunit-tests { 97 | list-style-position: inside; 98 | } 99 | 100 | #qunit-tests li { 101 | padding: 0.4em 1em 0.4em 1em; 102 | border-bottom: 1px solid #FFF; 103 | list-style-position: inside; 104 | } 105 | 106 | #qunit-tests > li { 107 | display: none; 108 | } 109 | 110 | #qunit-tests li.running, 111 | #qunit-tests li.pass, 112 | #qunit-tests li.fail, 113 | #qunit-tests li.skipped { 114 | display: list-item; 115 | } 116 | 117 | #qunit-tests.hidepass li.running, 118 | #qunit-tests.hidepass li.pass { 119 | visibility: hidden; 120 | position: absolute; 121 | width: 0px; 122 | height: 0px; 123 | padding: 0; 124 | border: 0; 125 | margin: 0; 126 | } 127 | 128 | #qunit-tests li strong { 129 | cursor: pointer; 130 | } 131 | 132 | #qunit-tests li.skipped strong { 133 | cursor: default; 134 | } 135 | 136 | #qunit-tests li a { 137 | padding: 0.5em; 138 | color: #C2CCD1; 139 | text-decoration: none; 140 | } 141 | 142 | #qunit-tests li p a { 143 | padding: 0.25em; 144 | color: #6B6464; 145 | } 146 | #qunit-tests li a:hover, 147 | #qunit-tests li a:focus { 148 | color: #000; 149 | } 150 | 151 | #qunit-tests li .runtime { 152 | float: right; 153 | font-size: smaller; 154 | } 155 | 156 | .qunit-assert-list { 157 | margin-top: 0.5em; 158 | padding: 0.5em; 159 | 160 | background-color: #FFF; 161 | 162 | border-radius: 5px; 163 | } 164 | 165 | .qunit-collapsed { 166 | display: none; 167 | } 168 | 169 | #qunit-tests table { 170 | border-collapse: collapse; 171 | margin-top: 0.2em; 172 | } 173 | 174 | #qunit-tests th { 175 | text-align: right; 176 | vertical-align: top; 177 | padding: 0 0.5em 0 0; 178 | } 179 | 180 | #qunit-tests td { 181 | vertical-align: top; 182 | } 183 | 184 | #qunit-tests pre { 185 | margin: 0; 186 | white-space: pre-wrap; 187 | word-wrap: break-word; 188 | } 189 | 190 | #qunit-tests del { 191 | background-color: #E0F2BE; 192 | color: #374E0C; 193 | text-decoration: none; 194 | } 195 | 196 | #qunit-tests ins { 197 | background-color: #FFCACA; 198 | color: #500; 199 | text-decoration: none; 200 | } 201 | 202 | /*** Test Counts */ 203 | 204 | #qunit-tests b.counts { color: #000; } 205 | #qunit-tests b.passed { color: #5E740B; } 206 | #qunit-tests b.failed { color: #710909; } 207 | 208 | #qunit-tests li li { 209 | padding: 5px; 210 | background-color: #FFF; 211 | border-bottom: none; 212 | list-style-position: inside; 213 | } 214 | 215 | /*** Passing Styles */ 216 | 217 | #qunit-tests li li.pass { 218 | color: #3C510C; 219 | background-color: #FFF; 220 | border-left: 10px solid #C6E746; 221 | } 222 | 223 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 224 | #qunit-tests .pass .test-name { color: #366097; } 225 | 226 | #qunit-tests .pass .test-actual, 227 | #qunit-tests .pass .test-expected { color: #999; } 228 | 229 | #qunit-banner.qunit-pass { background-color: #C6E746; } 230 | 231 | /*** Failing Styles */ 232 | 233 | #qunit-tests li li.fail { 234 | color: #710909; 235 | background-color: #FFF; 236 | border-left: 10px solid #EE5757; 237 | white-space: pre; 238 | } 239 | 240 | #qunit-tests > li:last-child { 241 | border-radius: 0 0 5px 5px; 242 | } 243 | 244 | #qunit-tests .fail { color: #000; background-color: #EE5757; } 245 | #qunit-tests .fail .test-name, 246 | #qunit-tests .fail .module-name { color: #000; } 247 | 248 | #qunit-tests .fail .test-actual { color: #EE5757; } 249 | #qunit-tests .fail .test-expected { color: #008000; } 250 | 251 | #qunit-banner.qunit-fail { background-color: #EE5757; } 252 | 253 | /*** Skipped tests */ 254 | 255 | #qunit-tests .skipped { 256 | background-color: #EBECE9; 257 | } 258 | 259 | #qunit-tests .qunit-skipped-label { 260 | background-color: #F4FF77; 261 | display: inline-block; 262 | font-style: normal; 263 | color: #366097; 264 | line-height: 1.8em; 265 | padding: 0 0.5em; 266 | margin: -0.4em 0.4em -0.4em 0; 267 | } 268 | 269 | /** Result */ 270 | 271 | #qunit-testresult { 272 | padding: 0.5em 1em 0.5em 1em; 273 | 274 | color: #2B81AF; 275 | background-color: #D2E0E6; 276 | 277 | border-bottom: 1px solid #FFF; 278 | } 279 | #qunit-testresult .module-name { 280 | font-weight: 700; 281 | } 282 | 283 | /** Fixture */ 284 | 285 | #qunit-fixture { 286 | position: absolute; 287 | top: -10000px; 288 | left: -10000px; 289 | width: 1000px; 290 | height: 1000px; 291 | } -------------------------------------------------------------------------------- /test/test-mock-clearing.js: -------------------------------------------------------------------------------- 1 | (function(qunit, $) { 2 | 'use strict'; 3 | 4 | var t = qunit.test; 5 | 6 | /* ---------------------------------- */ 7 | qunit.module( 'Mock Handler Clearing' ); 8 | /* ---------------------------------- */ 9 | 10 | t('Remove mockjax definition by url', function(assert) { 11 | var done = assert.async(); 12 | 13 | $.mockjax({ 14 | url: '/test', 15 | contentType: 'text/plain', 16 | responseText: 'test' 17 | }); 18 | 19 | $.mockjax({ 20 | url: '*', 21 | contentType: 'text/plain', 22 | responseText: 'default' 23 | }); 24 | 25 | $.ajax({ 26 | url: '/test', 27 | success: function(text) { 28 | assert.equal(text, 'test', 'Test handler responded'); 29 | }, 30 | error: qunit.noErrorCallbackExpected, 31 | complete: function() { 32 | $.mockjax.clear('/test'); 33 | 34 | // Reissue the request expecting the default handler 35 | $.ajax({ 36 | url: '/test', 37 | success: function(text) { 38 | assert.equal(text, 'default', 'Default handler responded'); 39 | }, 40 | error: qunit.noErrorCallbackExpected, 41 | complete: function(xhr) { 42 | assert.equal(xhr.responseText, 'default', 'Default handler responded'); 43 | done(); 44 | } 45 | }); 46 | } 47 | }); 48 | }); 49 | 50 | t('Remove mockjax definition by url (no default handler)', function(assert) { 51 | var done = assert.async(); 52 | 53 | $.mockjax({ 54 | url: '/foobar', 55 | contentType: 'text/plain', 56 | responseText: 'test' 57 | }); 58 | 59 | $.ajax({ 60 | url: '/foobar', 61 | success: function(text) { 62 | assert.equal(text, 'test', 'Test handler responded'); 63 | }, 64 | error: qunit.noErrorCallbackExpected, 65 | complete: function() { 66 | $.mockjax.clear('/foobar'); 67 | 68 | // Reissue the request expecting the error 69 | $.ajax({ 70 | url: '/foobar', 71 | success: function() { 72 | assert.ok(false, 'The mock was not cleared by url'); 73 | }, 74 | error: function(xhr) { 75 | // Test against 0, might want to look at this more in depth 76 | assert.ok(404 === xhr.status || 0 === xhr.status, 'The mock was cleared by url'); 77 | done(); 78 | } 79 | }); 80 | } 81 | }); 82 | }); 83 | 84 | t('Attempt to clear a non-existent but similar url', function(assert) { 85 | var done = assert.async(); 86 | 87 | $.mockjax({ 88 | url: '/test', 89 | contentType: 'text/plain', 90 | responseText: 'test' 91 | }); 92 | 93 | $.mockjax({ 94 | url: '*', 95 | contentType: 'text/plain', 96 | responseText: 'default' 97 | }); 98 | 99 | $.ajax({ 100 | url: '/test', 101 | success: function(text) { 102 | assert.equal(text, 'test', 'Test handler responded'); 103 | }, 104 | error: qunit.noErrorCallbackExpected, 105 | complete: function() { 106 | $.mockjax.clear('/tes'); 107 | 108 | $.ajax({ 109 | url: '/test', 110 | success: function(text) { 111 | assert.equal(text, 'test', 'Test handler responded'); 112 | }, 113 | error: qunit.noErrorCallbackExpected, 114 | complete: done 115 | }); 116 | } 117 | }); 118 | }); 119 | 120 | t('Remove mockjax definition, but not a subpath', function(assert) { 121 | var done = assert.async(); 122 | 123 | $.mockjax({ 124 | url: '/test', 125 | contentType: 'text/plain', 126 | responseText: 'test' 127 | }); 128 | 129 | $.mockjax({ 130 | url: '/test/foo', 131 | contentType: 'text/plain', 132 | responseText: 'foo' 133 | }); 134 | 135 | $.ajax({ 136 | url: '/test', 137 | success: function(text) { 138 | assert.equal(text, 'test', 'Test handler responded'); 139 | }, 140 | error: qunit.noErrorCallbackExpected, 141 | complete: function() { 142 | $.mockjax.clear('/test'); 143 | 144 | $.ajax({ 145 | url: '/test/foo', 146 | success: function(text) { 147 | assert.equal(text, 'foo', 'Test handler responded'); 148 | }, 149 | error: qunit.noErrorCallbackExpected, 150 | complete: done 151 | }); 152 | } 153 | }); 154 | }); 155 | 156 | t('Remove mockjax definition by RegExp', function(assert) { 157 | var done = assert.async(); 158 | 159 | $.mockjax({ 160 | url: '/test', 161 | contentType: 'text/plain', 162 | responseText: 'test' 163 | }); 164 | 165 | $.mockjax({ 166 | url: '*', 167 | contentType: 'text/plain', 168 | responseText: 'default' 169 | }); 170 | 171 | $.ajax({ 172 | url: '/test', 173 | success: function(text) { 174 | assert.equal(text, 'test', 'Test handler responded'); 175 | }, 176 | error: qunit.noErrorCallbackExpected, 177 | complete: function() { 178 | $.mockjax.clear(/test/); 179 | 180 | // Reissue the request expecting the default handler 181 | $.ajax({ 182 | url: '/test', 183 | success: function(text) { 184 | assert.equal(text, 'default', 'Default handler responded'); 185 | }, 186 | error: qunit.noErrorCallbackExpected, 187 | complete: function(xhr) { 188 | assert.equal(xhr.responseText, 'default', 'Default handler responded'); 189 | done(); 190 | } 191 | }); 192 | } 193 | }); 194 | }); 195 | 196 | t('Remove several mockjax definition by RegExp', function(assert) { 197 | var done = assert.async(); 198 | 199 | $.mockjax({ 200 | url: '/test', 201 | contentType: 'text/plain', 202 | responseText: 'test' 203 | }); 204 | 205 | $.mockjax({ 206 | url: '/test1', 207 | contentType: 'text/plain', 208 | responseText: 'test' 209 | }); 210 | 211 | $.mockjax({ 212 | url: '/test/foo', 213 | contentType: 'text/plain', 214 | responseText: 'test' 215 | }); 216 | 217 | $.mockjax({ 218 | url: '*', 219 | contentType: 'text/plain', 220 | responseText: 'default' 221 | }); 222 | 223 | $.ajax({ 224 | url: '/test', 225 | success: function(text) { 226 | assert.equal(text, 'test', 'Test handler responded'); 227 | }, 228 | error: qunit.noErrorCallbackExpected, 229 | complete: function() { 230 | $.mockjax.clear(/test/); 231 | 232 | // Reissue the request expecting the default handler 233 | $.ajax({ 234 | url: '/test', 235 | success: function(text) { 236 | assert.equal(text, 'default', 'Default handler responded'); 237 | }, 238 | error: qunit.noErrorCallbackExpected, 239 | complete: function(xhr) { 240 | assert.equal(xhr.responseText, 'default', 'Default handler responded'); 241 | done(); 242 | } 243 | }); 244 | } 245 | }); 246 | }); 247 | 248 | t('Remove mockjax definition by id', function(assert) { 249 | var done = assert.async(); 250 | 251 | var id = $.mockjax({ 252 | url: '/test', 253 | contentType: 'text/plain', 254 | responseText: 'test' 255 | }); 256 | 257 | $.mockjax({ 258 | url: '*', 259 | contentType: 'text/plain', 260 | responseText: 'default' 261 | }); 262 | 263 | $.ajax({ 264 | url: '/test', 265 | success: function(text) { 266 | assert.equal(text, 'test', 'Test handler responded'); 267 | }, 268 | error: qunit.noErrorCallbackExpected, 269 | complete: function() { 270 | $.mockjax.clear(id); 271 | 272 | // Reissue the request expecting the default handler 273 | $.ajax({ 274 | url: '/test', 275 | success: function(text) { 276 | assert.equal(text, 'default', 'Default handler responded'); 277 | }, 278 | error: qunit.noErrorCallbackExpected, 279 | complete: function(xhr) { 280 | assert.equal(xhr.responseText, 'default', 'Default handler responded'); 281 | done(); 282 | } 283 | }); 284 | } 285 | }); 286 | }); 287 | 288 | t('Clearing mockjax removes all handlers', function(assert) { 289 | var done = assert.async(); 290 | 291 | $.mockjax({ 292 | url: '/api/example/1', 293 | responseText: 'test1' 294 | }); 295 | $.mockjax({ 296 | url: '/api/example/2', 297 | responseText: 'test2' 298 | }); 299 | 300 | $.ajax({ 301 | async: true, 302 | type: 'GET', 303 | url: '/api/example/1', 304 | success: function(text) { 305 | assert.equal('test1', text, 'First call is mocked'); 306 | }, 307 | error: qunit.noErrorCallbackExpected, 308 | complete: function() { 309 | $.mockjax.clear(); 310 | 311 | $.ajax({ 312 | async: true, 313 | type: 'GET', 314 | url: '/api/example/1', 315 | success: function() { 316 | assert.ok( false, 'Call to first endpoint was mocked, but should not have been'); 317 | }, 318 | error: function(xhr) { 319 | // Test against 0, might want to look at this more in depth 320 | assert.ok(404 === xhr.status || 0 === xhr.status, 'First mock cleared after clear()'); 321 | 322 | $.ajax({ 323 | async: true, 324 | type: 'GET', 325 | url: '/api/example/2', 326 | success: function() { 327 | assert.ok( false, 'Call to second endpoint was mocked, but should not have been'); 328 | }, 329 | error: function(xhr) { 330 | // Test against 0, might want to look at this more in depth 331 | assert.ok(404 === xhr.status || 0 === xhr.status, 'Second mock cleared after clear()'); 332 | done(); 333 | } 334 | }); 335 | } 336 | }); 337 | } 338 | }); 339 | }); 340 | 341 | })(window.QUnit, window.jQuery); -------------------------------------------------------------------------------- /test/test-data-match.js: -------------------------------------------------------------------------------- 1 | (function(qunit, $) { 2 | 'use strict'; 3 | 4 | var t = qunit.test; 5 | 6 | /* ---------------------------------- */ 7 | qunit.module( 'Request Data Matching' ); 8 | /* ---------------------------------- */ 9 | 10 | t('Incorrect data matching on request', function(assert) { 11 | var done = assert.async(); 12 | 13 | $.mockjax({ 14 | url: '/response-callback', 15 | data: { 16 | foo: 'bar' 17 | } 18 | }); 19 | 20 | $.ajax({ 21 | url: '/response-callback', 22 | error: function() { assert.ok(true, 'Error called on bad mock/data matching'); }, 23 | data: { 24 | bar: 'baz' 25 | }, 26 | success: function() { 27 | assert.ok( false, 'Success should not be called' ); 28 | }, 29 | complete: done 30 | }); 31 | }); 32 | 33 | t('Correct data matching on request', function(assert) { 34 | var done = assert.async(); 35 | 36 | $.mockjax({ 37 | url: '/response-callback', 38 | contentType: 'text/json', 39 | data: { 40 | foo: 'bar' 41 | }, 42 | responseText: {} 43 | }); 44 | 45 | $.ajax({ 46 | url: '/response-callback', 47 | error: qunit.noErrorCallbackExpected, 48 | data: { 49 | foo: 'bar' 50 | }, 51 | success: function() { 52 | assert.ok( true, 'Successfully matched data' ); 53 | }, 54 | complete: done 55 | }); 56 | }); 57 | 58 | t('Correct data matching on request - request can have additional properties', function(assert) { 59 | var done = assert.async(); 60 | 61 | $.mockjax({ 62 | url: '/response-callback', 63 | data: { 64 | foo: 'bar' 65 | } 66 | }); 67 | 68 | $.ajax({ 69 | url: '/response-callback', 70 | error: qunit.noErrorCallbackExpected, 71 | data: { 72 | foo: 'bar', 73 | bar: 'baz' 74 | }, 75 | success: function() { 76 | assert.ok(true, 'Success should not be called'); 77 | }, 78 | complete: done 79 | }); 80 | }); 81 | 82 | t('Bug #80: Correct data matching on request with empty object literals', function(assert) { 83 | var done = assert.async(); 84 | 85 | $.mockjax({ 86 | url: '/response-callback', 87 | contentType: 'text/json', 88 | data: {}, 89 | responseText: {} 90 | }); 91 | 92 | $.ajax({ 93 | url: '/response-callback', 94 | error: qunit.noErrorCallbackExpected, 95 | data: {}, 96 | success: function() { 97 | assert.ok( true, 'Successfully matched data' ); 98 | }, 99 | complete: done 100 | }); 101 | }); 102 | 103 | t('Correct matching on request without data and mocks with and without data but same url', function(assert) { 104 | var done = assert.async(); 105 | 106 | $.mockjax({ 107 | url: '/response-callback', 108 | data: { 109 | foo: 'bar' 110 | }, 111 | responseText: 'false match' 112 | }); 113 | $.mockjax({ 114 | url: '/response-callback', 115 | responseText: 'correct match' 116 | }); 117 | $.mockjax({ 118 | url: '/response-callback', 119 | data: { 120 | bar: 'foo' 121 | }, 122 | responseText: 'another false match' 123 | }); 124 | 125 | $.ajax({ 126 | url: '/response-callback', 127 | error: qunit.noErrorCallbackExpected, 128 | complete: function(xhr) { 129 | assert.equal(xhr.responseText, 'correct match', 'Matched with correct mock'); 130 | done(); 131 | } 132 | }); 133 | }); 134 | 135 | // Related issue #68 136 | t('Bug #68: Incorrect data matching on request with arrays', function(assert) { 137 | var done = assert.async(); 138 | 139 | $.mockjax({ 140 | url: '/response-callback', 141 | contentType: 'text/json', 142 | data: { 143 | values: [] 144 | } 145 | }); 146 | 147 | $.ajax({ 148 | url: '/response-callback', 149 | error: function() { 150 | assert.ok( true, 'Error callback fired' ); 151 | }, 152 | data: { 153 | values: [1,2,3] 154 | }, 155 | success: function() { 156 | assert.ok( false, 'Success callback fired' ); 157 | }, 158 | complete: done 159 | }); 160 | }); 161 | 162 | t('Correct data matching on request with arrays', function(assert) { 163 | var done = assert.async(); 164 | 165 | $.mockjax({ 166 | url: '/response-callback', 167 | contentType: 'text/json', 168 | data: { 169 | values: [1,2,3] 170 | }, 171 | responseText: {} 172 | }); 173 | 174 | $.ajax({ 175 | url: '/response-callback', 176 | error: qunit.noErrorCallbackExpected, 177 | data: { 178 | values: [1,2,3] 179 | }, 180 | success: function() { 181 | assert.ok(true, 'Success callback fired'); 182 | }, 183 | complete: done 184 | }); 185 | }); 186 | 187 | 188 | t('Multiple data matching requests', function(assert) { 189 | var done = assert.async(); 190 | 191 | $.mockjax({ 192 | url: '/response-callback', 193 | contentType: 'text/json', 194 | data: { 195 | remote: { 196 | test: function(data) { 197 | return data !== 'hello'; 198 | } 199 | } 200 | }, 201 | responseText: { 'yes?': 'no' } 202 | }); 203 | $.mockjax({ 204 | url: '/response-callback', 205 | contentType: 'text/json', 206 | data: { 207 | remote: { 208 | test: function(data) { 209 | return data === 'hello'; 210 | } 211 | } 212 | }, 213 | responseText: { 'yes?': 'yes' } 214 | }); 215 | 216 | var callCount = 2; 217 | $.ajax({ 218 | url: '/response-callback', 219 | error: qunit.noErrorCallbackExpected, 220 | dataType: 'json', 221 | data: { 222 | remote: 'h' 223 | }, 224 | success: function(resp) { 225 | assert.deepEqual( resp, {'yes?': 'no'}, 'correct mock hander' ); 226 | }, 227 | complete: function() { 228 | callCount--; 229 | 230 | if (callCount <= 0) { 231 | done(); 232 | } 233 | } 234 | }); 235 | $.ajax({ 236 | url: '/response-callback', 237 | error: qunit.noErrorCallbackExpected, 238 | data: { 239 | remote: 'hello' 240 | }, 241 | dataType: 'json', 242 | success: function(resp) { 243 | assert.deepEqual( resp, {'yes?': 'yes'}, 'correct mock hander' ); 244 | }, 245 | complete: function() { 246 | callCount--; 247 | 248 | if (callCount <= 0) { 249 | done(); 250 | } 251 | } 252 | }); 253 | }); 254 | 255 | t('Multiple data matching requests with data function', function(assert) { 256 | var done = assert.async(); 257 | 258 | $.mockjax({ 259 | url: '/response-callback', 260 | data: function(data) { 261 | return (data.answer === 'yes'); 262 | }, 263 | responseText: {'yes?': 'yes'} 264 | }); 265 | $.mockjax({ 266 | url: '/response-callback', 267 | data: function(data) { 268 | return (data.answer === 'yes'); 269 | }, 270 | responseText: {'yes?': 'yes'} 271 | }); 272 | 273 | var callCount = 2; 274 | $.ajax({ 275 | url: '/response-callback', 276 | data: { 277 | answer: 'yes' 278 | }, 279 | success: function(resp) { 280 | assert.deepEqual( resp, {'yes?': 'yes'}, 'correct mock hander' ); 281 | }, 282 | complete: function() { 283 | callCount--; 284 | 285 | if (callCount <= 0) { 286 | done(); 287 | } 288 | } 289 | }); 290 | var notMatched = false; 291 | $.ajax({ 292 | url: '/response-callback', 293 | data: { 294 | answer: 'no' 295 | }, 296 | error: function() { 297 | notMatched = true; 298 | }, 299 | complete: function() { 300 | callCount--; 301 | assert.ok( notMatched , 'correct data function' ); 302 | 303 | if (callCount <= 0) { 304 | done(); 305 | } 306 | } 307 | }); 308 | }); 309 | 310 | t('Bug #106: Null matching on request', function(assert) { 311 | var done = assert.async(); 312 | 313 | $.mockjax({ 314 | url: '/response-callback', 315 | contentType: 'text/json', 316 | data: { 317 | foo: 'bar', 318 | bar: null 319 | }, 320 | responseText: {} 321 | }); 322 | 323 | $.ajax({ 324 | url: '/response-callback', 325 | error: qunit.noErrorCallbackExpected, 326 | data: { 327 | foo: 'bar', 328 | bar: null 329 | }, 330 | success: function() { 331 | assert.ok( true, 'Successfully matched data that contained null values' ); 332 | }, 333 | complete: done 334 | }); 335 | }); 336 | 337 | t('Bug #123: match data in query format', function(assert) { 338 | var done = assert.async(); 339 | 340 | $.mockjax({ 341 | url: '/api/query', 342 | data: { 343 | foo: 'bar' 344 | }, 345 | responseText: { foo: 'bar' } 346 | }); 347 | 348 | $.ajax({ 349 | url: '/api/query', 350 | data: 'foo=bar', 351 | success: function() { 352 | assert.ok(true, 'Successfully matched data'); 353 | }, 354 | error: qunit.noErrorCallbackExpected, 355 | complete: done 356 | }); 357 | }); 358 | 359 | t('Bug #123: match data in query format (two params)', function(assert) { 360 | var done = assert.async(); 361 | 362 | $.mockjax({ 363 | url: '/api/query', 364 | data: { 365 | foo: 'bar', 366 | bat: 'baz' 367 | }, 368 | responseText: { foo: 'bar' } 369 | }); 370 | 371 | $.ajax({ 372 | url: '/api/query', 373 | data: 'foo=bar&bat=baz', 374 | success: function() { 375 | assert.ok(true, 'Successfully matched data'); 376 | }, 377 | error: qunit.noErrorCallbackExpected, 378 | complete: done 379 | }); 380 | }); 381 | 382 | t('Bug #123: don\'t match data in query format when not matching', function(assert) { 383 | var done = assert.async(); 384 | 385 | $.mockjax({ 386 | url: '/api/query', 387 | data: { 388 | foo: 'bar', 389 | bat: 'baz' 390 | }, 391 | responseText: { foo: 'bar' } 392 | }); 393 | 394 | $.ajax({ 395 | url: '/api/query', 396 | data: 'foo=bar&bat=boo', 397 | success: function() { 398 | assert.ok(false, 'Should not have mocked request'); 399 | }, 400 | error: function() { 401 | assert.ok(true, 'Correctly failed to match mock'); 402 | }, 403 | complete: done 404 | }); 405 | }); 406 | 407 | t('Bug #123: match data in query format (array of params)', function(assert) { 408 | var done = assert.async(); 409 | 410 | $.mockjax({ 411 | url: '/api/query', 412 | data: { 413 | foo: ['bar', 'bat', 'baz'] 414 | }, 415 | responseText: { foo: 'bar' } 416 | }); 417 | 418 | $.ajax({ 419 | url: '/api/query', 420 | data: 'foo=bar&foo=bat&foo=baz', 421 | success: function() { 422 | assert.ok(true, 'Successfully matched data'); 423 | }, 424 | error: qunit.noErrorCallbackExpected, 425 | complete: done 426 | }); 427 | }); 428 | 429 | })(window.QUnit, window.jQuery); -------------------------------------------------------------------------------- /dist/jquery.mockjax.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery Mockjax 2 | * A Plugin providing simple and flexible mocking of ajax requests and responses 3 | * 4 | * Version: 2.2.1 5 | * Home: https://github.com/jakerella/jquery-mockjax 6 | * Copyright (c) 2016 Jordan Kasper, formerly appendTo; 7 | * NOTE: This repository was taken over by Jordan Kasper (@jakerella) October, 2014 8 | * 9 | * Dual licensed under the MIT or GPL licenses. 10 | * http://opensource.org/licenses/MIT OR http://www.gnu.org/licenses/gpl-2.0.html 11 | */ 12 | !function(a,b){"use strict";if("function"==typeof define&&define.amd&&define.amd.jQuery)define(["jquery"],function(c){return b(c,a)});else{if("object"!=typeof exports)return b(a.jQuery||a.$,a);module.exports=b}}(this,function(a,b){"use strict";function c(c){void 0===b.DOMParser&&b.ActiveXObject&&(b.DOMParser=function(){},DOMParser.prototype.parseFromString=function(a){var b=new ActiveXObject("Microsoft.XMLDOM");return b.async="false",b.loadXML(a),b});try{var d=(new DOMParser).parseFromString(c,"text/xml");if(!a.isXMLDoc(d))throw new Error("Unable to parse XML");var e=a("parsererror",d);if(1===e.length)throw new Error("Error: "+a(d).text());return d}catch(b){var f=void 0===b.name?b:b.name+": "+b.message;return void a(document).trigger("xmlParseError",[f])}}function d(b,c){C.debug(b,["Checking mock data against request data",b,c]);var f=!0;if(a.isFunction(b))return!!b(c);if("string"==typeof c){if(a.isFunction(b.test))return b.test(c);if("object"!=typeof b)return b===c;c=e(c)}return a.each(b,function(e){return void 0===c[e]?f=!1:void("object"==typeof c[e]&&null!==c[e]?(f&&a.isArray(c[e])&&(f=a.isArray(b[e])&&c[e].length===b[e].length),f=f&&d(b[e],c[e])):f=b[e]&&a.isFunction(b[e].test)?f&&b[e].test(c[e]):f&&b[e]===c[e])}),f}function e(a){var b,c,d,e,f={},g=String(a).split(/&/);for(b=0,c=g.length;b=0}function i(b){if(a.isArray(b)&&2===b.length){var c=b[0],d=b[1];if(h(c)&&h(d))return Math.floor(Math.random()*(d-c))+c}else if(h(b))return b;return B}function j(b,d,e){C.debug(b,["Sending fake XHR request",b,d,e]);var g=function(f){return function(){return function(){this.status=b.status,this.statusText=b.statusText,this.readyState=1;var g=function(){this.readyState=4;var e;"json"===d.dataType&&"object"==typeof b.responseText?this.responseText=JSON.stringify(b.responseText):"xml"===d.dataType?"string"==typeof b.responseXML?(this.responseXML=c(b.responseXML),this.responseText=b.responseXML):this.responseXML=b.responseXML:"object"==typeof b.responseText&&null!==b.responseText?(b.contentType="application/json",this.responseText=JSON.stringify(b.responseText)):this.responseText=b.responseText,"number"!=typeof b.status&&"string"!=typeof b.status||(this.status=b.status),"string"==typeof b.statusText&&(this.statusText=b.statusText),e=this.onload||this.onreadystatechange,a.isFunction(e)?(b.isTimeout&&(this.status=-1),e.call(this,b.isTimeout?"timeout":void 0)):b.isTimeout&&(this.status=-1)};if(a.isFunction(b.response)){if(2===b.response.length)return void b.response(e,function(){g.call(f)});b.response(e)}g.call(f)}.apply(f)}}(this);b.proxy?(C.info(b,["Retrieving proxy file: "+b.proxy,b]),v({global:!1,url:b.proxy,type:b.proxyType,data:b.data,async:d.async,dataType:"script"===d.dataType?"text/plain":d.dataType,complete:function(a){b.responseXML=b.responseText=a.responseText,f(b,"status")&&(b.status=a.status),f(b,"statusText")&&(b.statusText=a.statusText),d.async===!1?g():this.responseTimer=setTimeout(g,i(b.responseTime))}})):d.async===!1?g():this.responseTimer=setTimeout(g,i(b.responseTime))}function k(b,c,d,e){return C.debug(b,["Creating new mock XHR object",b,c,d,e]),b=a.extend(!0,{},a.mockjaxSettings,b),"undefined"==typeof b.headers&&(b.headers={}),"undefined"==typeof c.headers&&(c.headers={}),b.contentType&&(b.headers["content-type"]=b.contentType),{status:b.status,statusText:b.statusText,readyState:1,open:function(){},send:function(){e.fired=!0,j.call(this,b,c,d)},abort:function(){clearTimeout(this.responseTimer)},setRequestHeader:function(a,b){c.headers[a]=b},getResponseHeader:function(a){return b.headers&&b.headers[a]?b.headers[a]:"last-modified"===a.toLowerCase()?b.lastModified||(new Date).toString():"etag"===a.toLowerCase()?b.etag||"":"content-type"===a.toLowerCase()?b.contentType||"text/plain":void 0},getAllResponseHeaders:function(){var c="";return b.contentType&&(b.headers["Content-Type"]=b.contentType),a.each(b.headers,function(a,b){c+=a+": "+b+"\n"}),c}}}function l(a,b,c){if(m(a),a.dataType="json",a.data&&z.test(a.data)||z.test(a.url)){p(a,b,c);var d=/^(\w+:)?\/\/([^\/?#]+)/,e=d.exec(a.url),f=e&&(e[1]&&e[1]!==location.protocol||e[2]!==location.host);if(a.dataType="script","GET"===a.type.toUpperCase()&&f){var g=n(a,b,c);return!g||g}}return null}function m(a){"GET"===a.type.toUpperCase()?z.test(a.url)||(a.url+=(/\?/.test(a.url)?"&":"?")+(a.jsonp||"callback")+"=?"):a.data&&z.test(a.data)||(a.data=(a.data?a.data+"&":"")+(a.jsonp||"callback")+"=?")}function n(b,c,d){C.debug(c,["Performing JSONP request",c,b,d]);var e=d&&d.context||b,f=a.Deferred?new a.Deferred:null;if(c.response&&a.isFunction(c.response))c.response(d);else if("object"==typeof c.responseText)a.globalEval("("+JSON.stringify(c.responseText)+")");else{if(c.proxy)return C.info(c,["Performing JSONP proxy request: "+c.proxy,c]),v({global:!1,url:c.proxy,type:c.proxyType,data:c.data,dataType:"script"===b.dataType?"text/plain":b.dataType,complete:function(d){a.globalEval("("+d.responseText+")"),o(b,c,e,f)}}),f;a.globalEval("("+("string"==typeof c.responseText?'"'+c.responseText+'"':c.responseText)+")")}return o(b,c,e,f),f}function o(b,c,d,e){var f;setTimeout(function(){if(q(b,d,c),r(b,d),e){try{f=a.parseJSON(c.responseText)}catch(a){}e.resolveWith(d,[f||c.responseText]),C.log(c,["JSONP mock call complete",c,e])}},i(c.responseTime))}function p(a,c,d){var e=d&&d.context||a,f="string"==typeof a.jsonpCallback&&a.jsonpCallback||"jsonp"+A++;a.data&&(a.data=(a.data+"").replace(z,"="+f+"$1")),a.url=a.url.replace(z,"="+f+"$1"),b[f]=b[f]||function(){q(a,e,c),r(a,e),b[f]=void 0;try{delete b[f]}catch(a){}},a.jsonpCallback=f}function q(b,c,d){b.success&&b.success.call(c,d.responseText||"","success",{}),b.global&&(b.context?a(b.context):a.event).trigger("ajaxSuccess",[{},b])}function r(b,c){b.complete&&b.complete.call(c,{statusText:"success",status:200},"success"),b.global&&(b.context?a(b.context):a.event).trigger("ajaxComplete",[{},b]),b.global&&!--a.active&&a.event.trigger("ajaxStop")}function s(b,c){var d,e,f,h;C.debug(null,["Ajax call intercepted",b,c]),"object"==typeof b?(c=b,b=void 0):(c=c||{},c.url=b||c.url),e=a.ajaxSetup({_origSettings:c},c),e.type=e.method=e.method||e.type,h=function(b,d){var e=c[b.toLowerCase()];return function(){a.isFunction(e)&&e.apply(this,[].slice.call(arguments)),d["onAfter"+b].apply(this,arguments)}};for(var i=0;i1?c.timeout=f.responseTime-1:(f.responseTime=2,c.timeout=1)),a.isFunction(f.onAfterSuccess)&&(c.success=h("Success",f)),a.isFunction(f.onAfterError)&&(c.error=h("Error",f)),a.isFunction(f.onAfterComplete)&&(c.complete=h("Complete",f)),t(f,c),function(b,c,e,f){d=v.call(a,a.extend(!0,{},e,{xhr:function(){return k(b,c,e,f)}}))}(f,e,c,w[i]),d);C.debug(w[i],["Mock does not match request",b,e])}if(C.log(null,["No mock matched to request",b,c]),a.mockjaxSettings.retainAjaxCalls&&y.push(c),a.mockjaxSettings.throwUnmocked===!0)throw new Error("AJAX not mocked: "+c.url);return v.apply(a,[c])}function t(a,b){if(a.url instanceof RegExp&&a.hasOwnProperty("urlParams")){var c=a.url.exec(b.url);if(1!==c.length){c.shift();var d=0,e=c.length,f=a.urlParams.length,g=Math.min(e,f),h={};for(d;d=')) { 192 | // The $.ajax() API changed in version 1.4 to include the third argument: xhr 193 | t('Success callback should have access to xhr object', function(assert) { 194 | var done = assert.async(); 195 | 196 | $.mockjax({ 197 | url: '/response' 198 | }); 199 | 200 | $.ajax({ 201 | type: 'GET', 202 | url: '/response', 203 | success: function() { 204 | assert.ok(arguments[2], 'there is a third argument to the success callback'); 205 | assert.ok(arguments[2] && arguments[2].status === 200, 'third argument has proper status code'); 206 | done(); 207 | }, 208 | error: function() { 209 | assert.ok(false, 'should not result in error'); 210 | done(); 211 | } 212 | }); 213 | }); 214 | } 215 | 216 | t('Dynamic response status callback', function(assert) { 217 | var done = assert.async(); 218 | 219 | $.mockjax({ 220 | url: '/response-callback', 221 | response: function() { 222 | this.status = 500; 223 | this.statusText = 'Internal Server Error'; 224 | } 225 | }); 226 | 227 | $.ajax({ 228 | url: '/response-callback', 229 | dataType: 'text', 230 | data: { 231 | response: 'Hello world' 232 | }, 233 | error: function() { 234 | assert.ok(true, 'error callback was called'); 235 | }, 236 | complete: function(xhr) { 237 | assert.equal(xhr.status, 500, 'Dynamically set response status matches'); 238 | 239 | if( $.fn.jquery !== '1.5.2') { 240 | // This assertion fails in 1.5.2 due to this bug: http://bugs.jquery.com/ticket/9854 241 | // The statusText is being modified internally by jQuery in 1.5.2 242 | assert.equal(xhr.statusText, 'Internal Server Error', 'Dynamically set response statusText matches'); 243 | } 244 | 245 | done(); 246 | } 247 | }); 248 | }); 249 | 250 | t('Default Response Settings', function(assert) { 251 | var done = assert.async(); 252 | 253 | $.mockjax({ 254 | url: '/response-callback' 255 | }); 256 | 257 | $.ajax({ 258 | url: '/response-callback', 259 | dataType: 'text', 260 | data: { 261 | response: '' 262 | }, 263 | complete: function(xhr) { 264 | assert.equal(xhr.status, 200, 'Response status matches default'); 265 | 266 | if( $.fn.jquery !== '1.5.2') { 267 | // This assertion fails in 1.5.2 due to this bug: http://bugs.jquery.com/ticket/9854 268 | // The statusText is being modified internally by jQuery in 1.5.2 269 | assert.equal(xhr.statusText, 'OK', 'Response statusText matches default'); 270 | } 271 | 272 | assert.equal(xhr.responseText.length, 0, 'responseText length should be 0'); 273 | assert.equal(xhr.responseXml === undefined, true, 'responseXml should be undefined'); 274 | done(); 275 | } 276 | }); 277 | }); 278 | 279 | t('Throw new error when throwUnmocked is set to true and unmocked ajax calls are fired', function(assert) { 280 | var done = assert.async(); 281 | 282 | $.mockjaxSettings.throwUnmocked = true; 283 | 284 | try { 285 | $.ajax({ 286 | async: true, 287 | type: 'GET', 288 | url: '/api/example/1', 289 | complete: function() { 290 | assert.ok(false, 'Unmocked ajax request completed successfully and should have thrown an error.'); 291 | done(); 292 | } 293 | }); 294 | } 295 | catch (e) { 296 | assert.ok(e instanceof Error, 'Error was not thrown with "throwUnmocked" set to true and existing unmocked ajax request'); 297 | done(); 298 | } 299 | }); 300 | 301 | t('Get unfired handlers', function(assert) { 302 | var done = assert.async(); 303 | 304 | $.mockjax({ 305 | url: '/api/example/1' 306 | }); 307 | $.mockjax({ 308 | url: '/api/example/2' 309 | }); 310 | 311 | $.ajax({ 312 | async: false, 313 | type: 'GET', 314 | url: '/api/example/1', 315 | complete: function() { 316 | var handlersNotFired = $.mockjax.unfiredHandlers(); 317 | assert.equal(handlersNotFired.length, 1, 'all mocks were fired'); 318 | assert.equal(handlersNotFired[0].url, '/api/example/2', 'mockjax call has unexpected url'); 319 | done(); 320 | } 321 | }); 322 | }); 323 | 324 | t('Get unfired handlers after calling mockjax.clear', function(assert) { 325 | var done = assert.async(); 326 | 327 | $.mockjax({ 328 | url: '/api/example/1' 329 | }); 330 | $.mockjax({ 331 | url: '/api/example/2' 332 | }); 333 | $.mockjax({ 334 | url: '/api/example/3' 335 | }); 336 | 337 | $.ajax({ 338 | async: false, 339 | type: 'GET', 340 | url: '/api/example/1', 341 | complete: function() { 342 | $.mockjax.clear(2); 343 | var handlersNotFired = $.mockjax.unfiredHandlers(); 344 | assert.equal(handlersNotFired.length, 1, 'all mocks were fired'); 345 | assert.equal(handlersNotFired[0].url, '/api/example/2', 'mockjax call has unexpected url'); 346 | done(); 347 | } 348 | }); 349 | }); 350 | 351 | t('Response settings correct using PUT method', function(assert) { 352 | var done = assert.async(); 353 | 354 | $.mockjax({ 355 | url: '/put-request', 356 | type: 'PUT', 357 | responseText: 'this was a PUT' 358 | }); 359 | 360 | $.ajax({ 361 | url: '/put-request', 362 | type: 'PUT', 363 | dataType: 'text', 364 | complete: function(xhr) { 365 | assert.equal(xhr.status, 200, 'Response status matches default'); 366 | 367 | assert.equal(xhr.responseText, 'this was a PUT', 'responseText is correct'); 368 | done(); 369 | } 370 | }); 371 | }); 372 | 373 | t('Preserve context when set in jsonp ajax requet', function(assert) { 374 | var done = assert.async(); 375 | 376 | $.mockjax({ 377 | url: '/jsonp*', 378 | contentType: 'text/json', 379 | proxy: 'test_jsonp.js' 380 | }); 381 | 382 | window.abcdef123456 = function() {}; 383 | var cxt = {context: 'context'}; 384 | 385 | $.ajax({ 386 | url: '/jsonp?callback=?', 387 | jsonpCallback: 'abcdef123456', 388 | dataType: 'jsonp', 389 | error: qunit.noErrorCallbackExpected, 390 | context: cxt}) 391 | .done(function() { 392 | assert.deepEqual(this, cxt, 'this is equal to context object'); 393 | window.abcdef123456 = null; 394 | done(); 395 | }); 396 | }); 397 | 398 | t('Validate this is the $.ajax object if context is not set', function(assert) { 399 | var done = assert.async(); 400 | 401 | $.mockjax({ 402 | url: '/jsonp*', 403 | contentType: 'text/json', 404 | proxy: 'test_jsonp.js' 405 | }); 406 | 407 | window.abcdef123456 = function() {}; 408 | 409 | $.ajax({ 410 | url: '/jsonp?callback=?', 411 | jsonpCallback: 'abcdef123456', 412 | dataType: 'jsonp', 413 | error: qunit.noErrorCallbackExpected 414 | }) 415 | .done(function() { 416 | assert.ok(this.jsonp, '\'this\' is the $.ajax object for this request.'); 417 | window.abcdef123456 = null; 418 | done(); 419 | }); 420 | }); 421 | 422 | t('Dynamic mock definition', function(assert) { 423 | var done = assert.async(); 424 | 425 | $.mockjax( function( settings ) { 426 | var service = settings.url.match(/\/users\/(.*)$/); 427 | if (service) { 428 | return { 429 | proxy: 'test_proxy.json' 430 | }; 431 | } 432 | }); 433 | 434 | $.ajax({ 435 | url: '/users/test', 436 | dataType: 'json', 437 | error: qunit.noErrorCallbackExpected, 438 | success: function(json) { 439 | assert.ok(json && json.proxy, 'Proxy request succeeded'); 440 | }, 441 | complete: done 442 | }); 443 | }); 444 | 445 | t('Dynamic mock response generation', function(assert) { 446 | var done = assert.async(); 447 | 448 | $.mockjax({ 449 | url: '/response-callback', 450 | response: function() { 451 | this.responseText = { currentTime: 'now: ' + new Date() }; 452 | } 453 | }); 454 | 455 | $.ajax({ 456 | url: '/response-callback', 457 | dataType: 'json', 458 | error: qunit.noErrorCallbackExpected, 459 | success: function(json) { 460 | assert.equal(typeof json.currentTime, 'string', 'Dynamic response succeeded'); 461 | }, 462 | complete: done 463 | }); 464 | }); 465 | 466 | t('Case-insensitive matching for request types', function(assert) { 467 | var done = assert.async(); 468 | 469 | $.mockjax({ 470 | url: '/case_insensitive_match', 471 | type: 'GET', 472 | responseText: 'uppercase type response' 473 | }); 474 | 475 | $.ajax({ 476 | url: '/case_insensitive_match', 477 | type: 'get', 478 | error: qunit.noErrorCallbackExpected, 479 | complete: function(xhr) { 480 | assert.equal(xhr.responseText, 'uppercase type response', 'Request matched regardless of case'); 481 | done(); 482 | } 483 | }); 484 | }); 485 | 486 | t('Inspecting $.mockjax.handler(id) after request has fired', function(assert) { 487 | var ID = $.mockjax({ 488 | url: '/mockjax_properties', 489 | responseText: 'Hello Word' 490 | }); 491 | 492 | $.ajax({ 493 | url: '/mockjax_properties', 494 | complete: function() {} 495 | }); 496 | 497 | assert.ok($.mockjax.handler(ID).fired, 'Sets the mock\'s fired property to true'); 498 | }); 499 | 500 | t('Inspecting $.mockjax() with multiple mocks argument', function(assert) { 501 | var done = assert.async(); 502 | var handlers = $.mockjax([ 503 | { url: '/response-callback', responseText: 'First' }, 504 | { url: '/response-callback', responseText: 'Second' } 505 | ]); 506 | 507 | assert.equal(handlers.length, 2, 'Not enough mocks') 508 | 509 | var callCount = 2; 510 | $.ajax({ 511 | url: '/response-callback', 512 | complete: function() { 513 | callCount--; 514 | if (callCount === 0) { 515 | done(); 516 | } 517 | } 518 | }); 519 | $.ajax({ 520 | url: '/response-callback', 521 | complete: function() { 522 | callCount--; 523 | if (callCount === 0) { 524 | done(); 525 | } 526 | } 527 | }); 528 | }); 529 | 530 | t('Inspecting $.mockjax() with empty multiple mocks argument', function(assert) { 531 | var done = assert.async(); 532 | var handlers = $.mockjax([]); 533 | 534 | assert.equal(handlers.length, 0) 535 | 536 | $.ajax({ 537 | url: '/response-callback', 538 | error: function() { 539 | done(); 540 | } 541 | }); 542 | }); 543 | 544 | t('Inspecting $.mockjax() with null in multiple mocks argument', function(assert) { 545 | var done = assert.async(); 546 | var handlers = $.mockjax([ null ]); 547 | 548 | assert.equal(handlers.length, 1) 549 | 550 | $.ajax({ 551 | url: '/response-callback', 552 | error: function() { 553 | done(); 554 | } 555 | }); 556 | }); 557 | 558 | t('Inspecting $.mockjax() with multiple mocks argument and reset handler', function(assert) { 559 | var done = assert.async(); 560 | var handlers = $.mockjax([ 561 | { url: '/rest', responseText: 'will be reset' } 562 | ]); 563 | 564 | assert.equal(handlers.length, 1); 565 | $.mockjax.clear(handlers[0]); 566 | 567 | $.ajax({ 568 | url: '/response-callback', 569 | error: function() { 570 | done(); 571 | } 572 | }); 573 | }); 574 | 575 | })(window.QUnit, window.jQuery); 576 | -------------------------------------------------------------------------------- /lib/json2.js: -------------------------------------------------------------------------------- 1 | /* 2 | http://www.JSON.org/json2.js 3 | 2010-03-20 4 | 5 | Public Domain. 6 | 7 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 8 | 9 | See http://www.JSON.org/js.html 10 | 11 | 12 | This code should be minified before deployment. 13 | See http://javascript.crockford.com/jsmin.html 14 | 15 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 16 | NOT CONTROL. 17 | 18 | 19 | This file creates a global JSON object containing two methods: stringify 20 | and parse. 21 | 22 | JSON.stringify(value, replacer, space) 23 | value any JavaScript value, usually an object or array. 24 | 25 | replacer an optional parameter that determines how object 26 | values are stringified for objects. It can be a 27 | function or an array of strings. 28 | 29 | space an optional parameter that specifies the indentation 30 | of nested structures. If it is omitted, the text will 31 | be packed without extra whitespace. If it is a number, 32 | it will specify the number of spaces to indent at each 33 | level. If it is a string (such as '\t' or ' '), 34 | it contains the characters used to indent at each level. 35 | 36 | This method produces a JSON text from a JavaScript value. 37 | 38 | When an object value is found, if the object contains a toJSON 39 | method, its toJSON method will be called and the result will be 40 | stringified. A toJSON method does not serialize: it returns the 41 | value represented by the name/value pair that should be serialized, 42 | or undefined if nothing should be serialized. The toJSON method 43 | will be passed the key associated with the value, and this will be 44 | bound to the value 45 | 46 | For example, this would serialize Dates as ISO strings. 47 | 48 | Date.prototype.toJSON = function (key) { 49 | function f(n) { 50 | // Format integers to have at least two digits. 51 | return n < 10 ? '0' + n : n; 52 | } 53 | 54 | return this.getUTCFullYear() + '-' + 55 | f(this.getUTCMonth() + 1) + '-' + 56 | f(this.getUTCDate()) + 'T' + 57 | f(this.getUTCHours()) + ':' + 58 | f(this.getUTCMinutes()) + ':' + 59 | f(this.getUTCSeconds()) + 'Z'; 60 | }; 61 | 62 | You can provide an optional replacer method. It will be passed the 63 | key and value of each member, with this bound to the containing 64 | object. The value that is returned from your method will be 65 | serialized. If your method returns undefined, then the member will 66 | be excluded from the serialization. 67 | 68 | If the replacer parameter is an array of strings, then it will be 69 | used to select the members to be serialized. It filters the results 70 | such that only members with keys listed in the replacer array are 71 | stringified. 72 | 73 | Values that do not have JSON representations, such as undefined or 74 | functions, will not be serialized. Such values in objects will be 75 | dropped; in arrays they will be replaced with null. You can use 76 | a replacer function to replace those with JSON values. 77 | JSON.stringify(undefined) returns undefined. 78 | 79 | The optional space parameter produces a stringification of the 80 | value that is filled with line breaks and indentation to make it 81 | easier to read. 82 | 83 | If the space parameter is a non-empty string, then that string will 84 | be used for indentation. If the space parameter is a number, then 85 | the indentation will be that many spaces. 86 | 87 | Example: 88 | 89 | text = JSON.stringify(['e', {pluribus: 'unum'}]); 90 | // text is '["e",{"pluribus":"unum"}]' 91 | 92 | 93 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 94 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 95 | 96 | text = JSON.stringify([new Date()], function (key, value) { 97 | return this[key] instanceof Date ? 98 | 'Date(' + this[key] + ')' : value; 99 | }); 100 | // text is '["Date(---current time---)"]' 101 | 102 | 103 | JSON.parse(text, reviver) 104 | This method parses a JSON text to produce an object or array. 105 | It can throw a SyntaxError exception. 106 | 107 | The optional reviver parameter is a function that can filter and 108 | transform the results. It receives each of the keys and values, 109 | and its return value is used instead of the original value. 110 | If it returns what it received, then the structure is not modified. 111 | If it returns undefined then the member is deleted. 112 | 113 | Example: 114 | 115 | // Parse the text. Values that look like ISO date strings will 116 | // be converted to Date objects. 117 | 118 | myData = JSON.parse(text, function (key, value) { 119 | var a; 120 | if (typeof value === 'string') { 121 | a = 122 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 123 | if (a) { 124 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 125 | +a[5], +a[6])); 126 | } 127 | } 128 | return value; 129 | }); 130 | 131 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 132 | var d; 133 | if (typeof value === 'string' && 134 | value.slice(0, 5) === 'Date(' && 135 | value.slice(-1) === ')') { 136 | d = new Date(value.slice(5, -1)); 137 | if (d) { 138 | return d; 139 | } 140 | } 141 | return value; 142 | }); 143 | 144 | 145 | This is a reference implementation. You are free to copy, modify, or 146 | redistribute. 147 | */ 148 | 149 | /*jslint evil: true, strict: false */ 150 | 151 | /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 152 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 153 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 154 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 155 | test, toJSON, toString, valueOf 156 | */ 157 | 158 | 159 | // Create a JSON object only if one does not already exist. We create the 160 | // methods in a closure to avoid creating global variables. 161 | 162 | if (!this.JSON) { 163 | this.JSON = {}; 164 | } 165 | 166 | (function () { 167 | 168 | function f(n) { 169 | // Format integers to have at least two digits. 170 | return n < 10 ? '0' + n : n; 171 | } 172 | 173 | if (typeof Date.prototype.toJSON !== 'function') { 174 | 175 | Date.prototype.toJSON = function (key) { 176 | 177 | return isFinite(this.valueOf()) ? 178 | this.getUTCFullYear() + '-' + 179 | f(this.getUTCMonth() + 1) + '-' + 180 | f(this.getUTCDate()) + 'T' + 181 | f(this.getUTCHours()) + ':' + 182 | f(this.getUTCMinutes()) + ':' + 183 | f(this.getUTCSeconds()) + 'Z' : null; 184 | }; 185 | 186 | String.prototype.toJSON = 187 | Number.prototype.toJSON = 188 | Boolean.prototype.toJSON = function (key) { 189 | return this.valueOf(); 190 | }; 191 | } 192 | 193 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 194 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 195 | gap, 196 | indent, 197 | meta = { // table of character substitutions 198 | '\b': '\\b', 199 | '\t': '\\t', 200 | '\n': '\\n', 201 | '\f': '\\f', 202 | '\r': '\\r', 203 | '"' : '\\"', 204 | '\\': '\\\\' 205 | }, 206 | rep; 207 | 208 | 209 | function quote(string) { 210 | 211 | // If the string contains no control characters, no quote characters, and no 212 | // backslash characters, then we can safely slap some quotes around it. 213 | // Otherwise we must also replace the offending characters with safe escape 214 | // sequences. 215 | 216 | escapable.lastIndex = 0; 217 | return escapable.test(string) ? 218 | '"' + string.replace(escapable, function (a) { 219 | var c = meta[a]; 220 | return typeof c === 'string' ? c : 221 | '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 222 | }) + '"' : 223 | '"' + string + '"'; 224 | } 225 | 226 | 227 | function str(key, holder) { 228 | 229 | // Produce a string from holder[key]. 230 | 231 | var i, // The loop counter. 232 | k, // The member key. 233 | v, // The member value. 234 | length, 235 | mind = gap, 236 | partial, 237 | value = holder[key]; 238 | 239 | // If the value has a toJSON method, call it to obtain a replacement value. 240 | 241 | if (value && typeof value === 'object' && 242 | typeof value.toJSON === 'function') { 243 | value = value.toJSON(key); 244 | } 245 | 246 | // If we were called with a replacer function, then call the replacer to 247 | // obtain a replacement value. 248 | 249 | if (typeof rep === 'function') { 250 | value = rep.call(holder, key, value); 251 | } 252 | 253 | // What happens next depends on the value's type. 254 | 255 | switch (typeof value) { 256 | case 'string': 257 | return quote(value); 258 | 259 | case 'number': 260 | 261 | // JSON numbers must be finite. Encode non-finite numbers as null. 262 | 263 | return isFinite(value) ? String(value) : 'null'; 264 | 265 | case 'boolean': 266 | case 'null': 267 | 268 | // If the value is a boolean or null, convert it to a string. Note: 269 | // typeof null does not produce 'null'. The case is included here in 270 | // the remote chance that this gets fixed someday. 271 | 272 | return String(value); 273 | 274 | // If the type is 'object', we might be dealing with an object or an array or 275 | // null. 276 | 277 | case 'object': 278 | 279 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 280 | // so watch out for that case. 281 | 282 | if (!value) { 283 | return 'null'; 284 | } 285 | 286 | // Make an array to hold the partial results of stringifying this object value. 287 | 288 | gap += indent; 289 | partial = []; 290 | 291 | // Is the value an array? 292 | 293 | if (Object.prototype.toString.apply(value) === '[object Array]') { 294 | 295 | // The value is an array. Stringify every element. Use null as a placeholder 296 | // for non-JSON values. 297 | 298 | length = value.length; 299 | for (i = 0; i < length; i += 1) { 300 | partial[i] = str(i, value) || 'null'; 301 | } 302 | 303 | // Join all of the elements together, separated with commas, and wrap them in 304 | // brackets. 305 | 306 | v = partial.length === 0 ? '[]' : 307 | gap ? '[\n' + gap + 308 | partial.join(',\n' + gap) + '\n' + 309 | mind + ']' : 310 | '[' + partial.join(',') + ']'; 311 | gap = mind; 312 | return v; 313 | } 314 | 315 | // If the replacer is an array, use it to select the members to be stringified. 316 | 317 | if (rep && typeof rep === 'object') { 318 | length = rep.length; 319 | for (i = 0; i < length; i += 1) { 320 | k = rep[i]; 321 | if (typeof k === 'string') { 322 | v = str(k, value); 323 | if (v) { 324 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 325 | } 326 | } 327 | } 328 | } else { 329 | 330 | // Otherwise, iterate through all of the keys in the object. 331 | 332 | for (k in value) { 333 | if (Object.hasOwnProperty.call(value, k)) { 334 | v = str(k, value); 335 | if (v) { 336 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 337 | } 338 | } 339 | } 340 | } 341 | 342 | // Join all of the member texts together, separated with commas, 343 | // and wrap them in braces. 344 | 345 | v = partial.length === 0 ? '{}' : 346 | gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + 347 | mind + '}' : '{' + partial.join(',') + '}'; 348 | gap = mind; 349 | return v; 350 | } 351 | } 352 | 353 | // If the JSON object does not yet have a stringify method, give it one. 354 | 355 | if (typeof JSON.stringify !== 'function') { 356 | JSON.stringify = function (value, replacer, space) { 357 | 358 | // The stringify method takes a value and an optional replacer, and an optional 359 | // space parameter, and returns a JSON text. The replacer can be a function 360 | // that can replace values, or an array of strings that will select the keys. 361 | // A default replacer method can be provided. Use of the space parameter can 362 | // produce text that is more easily readable. 363 | 364 | var i; 365 | gap = ''; 366 | indent = ''; 367 | 368 | // If the space parameter is a number, make an indent string containing that 369 | // many spaces. 370 | 371 | if (typeof space === 'number') { 372 | for (i = 0; i < space; i += 1) { 373 | indent += ' '; 374 | } 375 | 376 | // If the space parameter is a string, it will be used as the indent string. 377 | 378 | } else if (typeof space === 'string') { 379 | indent = space; 380 | } 381 | 382 | // If there is a replacer, it must be a function or an array. 383 | // Otherwise, throw an error. 384 | 385 | rep = replacer; 386 | if (replacer && typeof replacer !== 'function' && 387 | (typeof replacer !== 'object' || 388 | typeof replacer.length !== 'number')) { 389 | throw new Error('JSON.stringify'); 390 | } 391 | 392 | // Make a fake root object containing our value under the key of ''. 393 | // Return the result of stringifying the value. 394 | 395 | return str('', {'': value}); 396 | }; 397 | } 398 | 399 | 400 | // If the JSON object does not yet have a parse method, give it one. 401 | 402 | if (typeof JSON.parse !== 'function') { 403 | JSON.parse = function (text, reviver) { 404 | 405 | // The parse method takes a text and an optional reviver function, and returns 406 | // a JavaScript value if the text is a valid JSON text. 407 | 408 | var j; 409 | 410 | function walk(holder, key) { 411 | 412 | // The walk method is used to recursively walk the resulting structure so 413 | // that modifications can be made. 414 | 415 | var k, v, value = holder[key]; 416 | if (value && typeof value === 'object') { 417 | for (k in value) { 418 | if (Object.hasOwnProperty.call(value, k)) { 419 | v = walk(value, k); 420 | if (v !== undefined) { 421 | value[k] = v; 422 | } else { 423 | delete value[k]; 424 | } 425 | } 426 | } 427 | } 428 | return reviver.call(holder, key, value); 429 | } 430 | 431 | 432 | // Parsing happens in four stages. In the first stage, we replace certain 433 | // Unicode characters with escape sequences. JavaScript handles many characters 434 | // incorrectly, either silently deleting them, or treating them as line endings. 435 | 436 | text = String(text); 437 | cx.lastIndex = 0; 438 | if (cx.test(text)) { 439 | text = text.replace(cx, function (a) { 440 | return '\\u' + 441 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 442 | }); 443 | } 444 | 445 | // In the second stage, we run the text against regular expressions that look 446 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 447 | // because they can cause invocation, and '=' because it can cause mutation. 448 | // But just to be safe, we want to reject all unexpected forms. 449 | 450 | // We split the second stage into 4 regexp operations in order to work around 451 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 452 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 453 | // replace all simple value tokens with ']' characters. Third, we delete all 454 | // open brackets that follow a colon or comma or that begin the text. Finally, 455 | // we look to see that the remaining characters are only whitespace or ']' or 456 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 457 | 458 | if (/^[\],:{}\s]*$/. 459 | test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). 460 | replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). 461 | replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 462 | 463 | // In the third stage we use the eval function to compile the text into a 464 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 465 | // in JavaScript: it can begin a block or an object literal. We wrap the text 466 | // in parens to eliminate the ambiguity. 467 | 468 | j = eval('(' + text + ')'); 469 | 470 | // In the optional fourth stage, we recursively walk the new structure, passing 471 | // each name/value pair to a reviver function for possible transformation. 472 | 473 | return typeof reviver === 'function' ? 474 | walk({'': j}, '') : j; 475 | } 476 | 477 | // If the text is not JSON parseable, then a SyntaxError is thrown. 478 | 479 | throw new SyntaxError('JSON.parse'); 480 | }; 481 | } 482 | }()); 483 | 484 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Patch motivation 2 | [Diff for All changes with last official version](https://github.com/vvscode/jquery-mockjax/compare/5c971d1f0db3b5e385bafbd3bd205ae6bccf81f0...HEAD) 3 | 4 | It pass at least one year till last activity at original repo. There are two PR's which add needed functionality, but thus they are outdated and were not updated long time - I make fork and patch myself. 5 | 6 | [How we use it on our projects to mock test data dymanicly](https://github.com/vvscode/code-notes/blob/master/socket-mocks/ajax-mock.js) 7 | 8 | Changes: 9 | - mockjaxHandler can get access to original settings via `requestSettings._origSettings` - that's allow to override callbacks if we need 10 | - `onAfter`-hooks now receive all params from handling callbacks ( see source code and PR's at original repo ) 11 | 12 | I'll add all feature changes to the list above. I don't update version, so install package from branch/commit 13 | 14 | 15 | # jQuery Mockjax: Ajax request mocking # 16 | [http://github.com/jakerella/jquery-mockjax/](http://github.com/jakerella/jquery-mockjax/) 17 | 18 | [![Codacy Badge](https://www.codacy.com/project/badge/72d5f8c1c29ee60f6282d7d3fa9cb52c)](https://www.codacy.com/app/mikehostetler_1249/jquery-mockjax/dashboard) 19 | [![Travis CI Badge](https://travis-ci.org/jakerella/jquery-mockjax.svg?branch=master)](https://travis-ci.org/jakerella/jquery-mockjax) 20 | 21 | **Note that we recently switched the `master` branch to version 2!** There are some 22 | minor breaking changes in v2, so if you need an older version, please check the 23 | [v1.x](https://github.com/jakerella/jquery-mockjax/tree/v1.x) branch or the list of 24 | [releases](https://github.com/jakerella/jquery-mockjax/tags) in Github. 25 | 26 | jQuery Mockjax provides request/response mocking for ajax requests using the 27 | jQuery API and provides all standard behaviors in the request/response flow. 28 | 29 | You may report any issues you may find [in the github issue tracking](https://github.com/jakerella/jquery-mockjax/issues). 30 | 31 | **Table of Contents** 32 | 33 | * [About Mockjax and Its History](#about-mockjax-and-its-history) 34 | * [Basic Documentation](#basic-documentation) 35 | * [API Methods](#api-methods) 36 | * [Overview: Your First Mock](#overview-your-first-mock) 37 | * [Mockjax in Depth](#mockjax-in-depth) 38 | * [Detailed Request and Response Definition](#detailed-request-and-response-definition) 39 | * [Defining a Request to Match](#defining-a-request-to-match) 40 | * [Defining Multiple Requests](#defining-multiple-requests) 41 | * [Defining a Response](#defining-a-response) 42 | * [Advanced Mocking Techniques](#advanced-mocking-techniques) 43 | * [Simulating Response Time and Latency](#simulating-response-time-and-latency) 44 | * [Simulating HTTP Response Statuses](#simulating-http-response-statuses) 45 | * [Setting the Content-Type](#setting-the-content-type) 46 | * [Setting Additional HTTP Response Headers](#setting-additional-http-response-headers) 47 | * [Dynamically Generating Mock Definitions](#dynamically-generating-mock-definitions) 48 | * [Accessing Request Headers](#accessing-request-headers) 49 | * [Forced Simulation of Server Timeouts](#forced-simulation-of-server-timeouts) 50 | * [Dynamically Generating Mock Responses](#dynamically-generating-mock-responses) 51 | * [Data Types](#data-types) 52 | * [Performing Actions After Request Completion](#performing-actions-after-request-completion) 53 | * [Globally Defining Mockjax Settings](#globally-defining-mockjax-settings) 54 | * [Setting a Global URL Namespace](#setting-global-url-namespace) 55 | * [Removing Mockjax Handlers](#removing-mockjax-handlers) 56 | * [Miscellaneous Information](#miscellaneous-information) 57 | * [jQuery Version Support](#jquery-version-support) 58 | * [Browsers Tested](#browsers-tested) 59 | * [Using Mockjax in Other Ways (Node, require, browserify, etc)](#using-mockjax-in-other-ways) 60 | * [Logging](#logging) 61 | * [Release History](#release-history) 62 | * [License](#license) 63 | 64 | 65 | ## About Mockjax and Its History ## 66 | 67 | Most backend developers are familiar with the concepts of [mocking 68 | objects](http://en.wikipedia.org/wiki/Mock_object) or stubbing in 69 | methods for unit testing. For those not familiar with mocking, it's the 70 | simulation of an interface or API for testing or integration development 71 | purposes. Mocking with front-end development though is still quite new. Mockjax 72 | gives front end developers the ability to define ajax requests that should be 73 | mocked out, as well as how those requests should be responded to. These mocks 74 | can be extremely simple or quite complex, representing the entire request-response 75 | workflow. 76 | 77 | At appendTo we developed a lot of applications which use 78 | [RESTFUL](http://en.wikipedia.org/wiki/Representational_State_Transfer) 79 | web services, but much of the time those services are not yet created. 80 | We spec out the service contract and data format at the beginning of a project 81 | and develop the front-end interface against mock data while the back end team 82 | builds the production services. 83 | 84 | This plugin was originally developed by appendTo in March 2010 and the 85 | [team](http://twitter.com/appendto/team) has been using it in many projects since. 86 | 87 | 88 | ## Basic Documentation ## 89 | 90 | ### API Methods ### 91 | 92 | Mockjax consists of just a few methods, each listed below. You'll find plenty of 93 | examples in the sections below, but if you're looking for a specific option, 94 | checkout this list: 95 | 96 | * `Number $.mockjax(/* Object */ options)` 97 | * Sets up a mockjax handler for a matching request 98 | * Returns that handler's index, can be used to clear individual handlers 99 | * `options`: [Object] Defines the settings to use for the mocked request 100 | * `url`: [String | RegExp] Specifies the url of the request that the data should be mocked for. If it is a string and contains any asterisks ( `*` ), they will be treated as a wildcard by translating to a regular expression. Any `*` will be replaced with `.+`. If you run into trouble with this shortcut, switch to using a full regular expression instead of a string and asterisk combination 101 | * `data`: [Object | Function] In addition to the URL, match parameters 102 | * `type`: [String] Specify what HTTP method to match, usually GET or POST. Case-insensitive, so `get` and `post` also work 103 | * `headers`: [Object] Keys will be simulated as additional headers returned from the server for the request (**NOTE: This is NOT used to match request headers!**) 104 | * `status`: [Number] An integer that specifies a valid server response code. This simulates a server response code 105 | * `statusText`: [String] Specifies a valid server response code description. This simulates a server response code description 106 | * `responseTime`: [Number] An integer that specifies a simulated network 107 | and server latency (in milliseconds). Default is `500`. Setting this 108 | to `0` will minimize the simulated latency 109 | * `isTimeout`: [Boolean] Determines whether or not the mock will force a timeout on the request 110 | * `contentType`: [String] Specifies the content type for the response 111 | * `response`: [Function] A function that accepts the request settings and allows for the dynamic setting of response settings (including the body of the response) upon each request (see examples below) 112 | * `responseText`: [String] Specifies the mocked text, or a mocked object literal, for the request 113 | * `responseXML`: [String] Specifies the mocked XML for the request 114 | * `proxy`: [String] Specifies a path to a file, from which the contents will be returned for the request 115 | * `lastModified`: [String] A date string specifying the mocked last-modified time for the request. This is used by `$.ajax` to determine if the requested data is new since the last request 116 | * `etag`: [String] Specifies a unique identifier referencing a specific version of the requested data. This is used by `$.ajax` to determine if the requested data is new since the last request. (see [HTTP_ETag](http://en.wikipedia.org/wiki/HTTP_ETag)) 117 | * `onAfterSuccess`: [Function] A callback that will be called after the success method has been called, this is useful to check a condition after the call has been completed 118 | * `onAfterError`: [Function] A callback that will be called after the error method has been called, this is useful to check a condition after the call has been completed 119 | * `onAfterComplete`: [Function] Similar to onAfterSuccess, but will be executed after the complete method has been called 120 | * `Object $.mockjax.handler(/* Number */ id)` 121 | * Returns the mock request settings for the handler with the provided `id` 122 | * `void $.mockjax.clear([/* Number */ id])` 123 | * If the `id` is provided, the handler with that ID is cleared (that is, requests matching it will no longer do so, the handler is completely removed) 124 | * If no `id` is provided, all handlers are cleared, resetting Mockjax to its initial state 125 | * `Array $.mockjax.mockedAjaxCalls()` 126 | * Returns an array of all mocked ajax calls with each entry being the request settings object as passed into the `$.mockjax()` function 127 | * If `$.mockjaxSettings.retainAjaxCalls is set to false, this will always be empty 128 | * `Array $.mockjax.unfiredHandlers()` 129 | * Returns an array of all mock handler settings that have not been used. In other words, if a handler has been used for a `$.ajax()` call then it will _not_ appear in this array 130 | * `Array $.mockjax.unmockedAjaxCalls()` 131 | * Returns an array of all unmocked Ajax calls that were made. The array contains the settings object passed into `$.ajax({...})` 132 | * If `$.mockjaxSettings.retainAjaxCalls is set to false, this will always be empty 133 | * `void $.mockjax.clearRetainedAjaxCalls()` 134 | * Empties the arrays returned by `$.mockjax.mockedAjaxCalls` and `$.mockjax.unmockedAjaxCalls` 135 | 136 | ### Overview: Your First Mock ### 137 | 138 | Our first example will be for a simple REST service for a fortune app 139 | with the REST endpoint being `/restful/fortune` which returns the 140 | following JSON message: 141 | 142 | ```json 143 | { 144 | "status": "success", 145 | "fortune" : "Are you a turtle?" 146 | } 147 | ``` 148 | 149 | To pull the fortune into our page, we'd use the following HTML and jQuery 150 | code: 151 | 152 | ```html 153 | 154 | 155 | 156 | Fortune App 157 | 158 | 159 | 160 |
        161 | 162 | 163 | ``` 164 | ```javascript 165 | $.getJSON("/restful/fortune", function(response) { 166 | if ( response.status == "success") { 167 | $("#fortune").html( "Your fortune is: " + response.fortune ); 168 | } else { 169 | $("#fortune").html( "Things do not look good, no fortune was told" ); 170 | } 171 | }); 172 | ``` 173 | 174 | At this point if we were to run this code it would fail since the REST 175 | service has yet to be implemented. This is where the benefit of the 176 | Mockjax plugin starts to pay off. The first step in using Mockjax is to 177 | include the plugin by just adding a regular script tag: 178 | 179 | ```html 180 | 181 | ... 182 | 183 | 184 | ``` 185 | 186 | Once you have that included, you can start intercepting Ajax requests 187 | and mocking the responses. So let's mock out the service by including 188 | the following code: 189 | 190 | ```javascript 191 | $.mockjax({ 192 | url: "/restful/fortune", 193 | responseText: { 194 | status: "success", 195 | fortune: "Are you a mock turtle?" 196 | } 197 | }); 198 | ``` 199 | 200 | **Defining a JSON string inline requires a `JSON.stringify()` method to be 201 | available. For some browsers you may need to include 202 | [json2.js](https://raw.github.com/douglascrockford/JSON-js/master/json2.js), 203 | which is included in the `lib` folder.** However, you could also simply 204 | provide an already stringified version of your JSON in the `responseText` 205 | property. 206 | 207 | _If you plan on mocking xml responses, you may also have to include 208 | `jquery.xmldom.js`, which can also be found in the `lib` folder._ 209 | 210 | ### Mockjax in Depth ### 211 | 212 | What Mockjax does at this point is replace the `$.ajax()` method with a 213 | wrapper that transparently checks the URL being requested. If the URL 214 | matches one defined by `$.mockjax()`, it intercepts the request 215 | and sets up a mock `XMLHttpRequest` object before executing the 216 | `jQuery.ajax()` handler. Otherwise, the request is handed back to the 217 | native `$.ajax()` method for normal execution. One benefit in this 218 | implementation detail is that by simulating the `XMLHttpRequest` object, 219 | the plugin continues to make use of jQuery's native ajax handling, so 220 | there are no concerns with implementing a custom Ajax workflow. 221 | 222 | As you write code to mock responses, there's great value in the fact that 223 | there are no modifications required to production code. The mocks can be 224 | transparently inserted. This provides easy integration into most 225 | frameworks by including the plugin and mock definitions through your 226 | build framework. It's also possible to include it at run time by 227 | listening for a query string flag and injecting the plugin and definitions. 228 | 229 | Now let's look at the various approaches to defining mocks as offered by 230 | the plugin. The sections below feature an extensive overview of the 231 | flexibility in Mockjax and creating responses. 232 | 233 | #### Data Types Available for Mocking #### 234 | 235 | jQuery is able to handle and parse `Text`, `HTML`, `JSON`, `JSONP`, 236 | `Script` and `XML` data formats and Mockjax is able to mock any of those 237 | formats. Two things to note: depending upon how you mock out `JSON` and 238 | `JSONP` you may need to include [json2.js](https://raw.github.com/douglascrockford/JSON-js/master/json2.js) 239 | for the `JSON.stringify()` method (older browsers only, typically). Additionally 240 | if you mock XML inline, you'll need to include the [`xmlDOM`](http://github.com/jakerella/jquery-xmldom) 241 | plugin that transforms a string of XML into a DOM object. However, if you use 242 | the proxy approach outlined below then there should be no need to include either 243 | the JSON or XMLDOM plugins in any case. 244 | 245 | 246 | ## Detailed Request and Response Definition ## 247 | 248 | ### Defining a Request to Match ### 249 | 250 | The first thing you need to do when mocking a request is define the URL 251 | end-point to intercept and mock. As with our example above this can be a 252 | simple string: 253 | 254 | ```javascript 255 | $.mockjax({ 256 | url: "/url/to/rest-service" 257 | }); 258 | ``` 259 | 260 | or contain a `*` as a wildcard: 261 | 262 | ```javascript 263 | $.mockjax({ 264 | // Matches /data/quote, /data/tweet etc. 265 | url: "/data/*" 266 | }); 267 | ``` 268 | 269 | or a full regular expression: 270 | 271 | ```javascript 272 | $.mockjax({ 273 | // Matches /data/quote, /data/tweet but not /data/quotes 274 | url: /^\/data\/(quote|tweet)$/i 275 | }); 276 | ``` 277 | 278 | You can also match against the data option in addition to url: 279 | 280 | ```javascript 281 | $.mockjax({ 282 | url: "/rest", 283 | data: { action: "foo" } 284 | }); 285 | ``` 286 | 287 | The data option may be a custom matching function returning `true` of `false` 288 | whether the data is expected or not: 289 | 290 | ```javascript 291 | $.mockjax([ 292 | url: "/rest", 293 | data: function( data ) { 294 | return deepEqual( data, expected ); 295 | } 296 | ]); 297 | ``` 298 | 299 | The data function is a recommended place for assertions. Return `true` and let 300 | a testing framework of choice do the rest: 301 | 302 | ```javascript 303 | $.mockjax([ 304 | url: "/rest", 305 | data: function ( json ) { 306 | assert.deepEqual( JSON.parse(json), expected ); // QUnit example. 307 | return true; 308 | } 309 | ]); 310 | ``` 311 | 312 | To capture URL parameters, use a capturing regular expression for the 313 | URL and a `urlParams` array to indicate, ordinally, the names of the 314 | paramters that will be captured: 315 | 316 | ```javascript 317 | $.mockjax({ 318 | // matches /author/{any number here}/isbn/{any number with dashes here} 319 | // for example: "/author/1234/isbn/1234-5678-9012-0" 320 | url: /^\/author\/([\d]+)\/isbn\/([\d\-]+)$/, 321 | // names of matching params 322 | urlParams: ["authorID", "isbnNumber"], 323 | response: function (settings) { 324 | var authorID = settings.urlParams.authorID; 325 | var isbnNumber = settings.urlParams.isbnNumber; 326 | // etc... 327 | } 328 | }); 329 | ``` 330 | 331 | ### Defining Multiple Requests ### 332 | 333 | Since version 2.2 it is allowed to define several requests at once. 334 | `$.mockjax([...])` returns a array of handlers' indexes. It is possible to 335 | reset handler by index. Read more in [Removing Mockjax Handlers](#removing-mockjax-handlers). 336 | 337 | ```javascript 338 | var handlers = $.mockjax([ 339 | {url: '/rest', responseText: 'one'}, 340 | {url: '/rest', responseText: 'two'} 341 | ]); 342 | 343 | $.mockjax.clear(handlers[0]); 344 | ``` 345 | 346 | ### Defining a Response ### 347 | 348 | The second step is to define the type and content of the response. The two main 349 | properties you will be dealing with are either `responseText` or 350 | `responseXML`. These properties mirror the native `XMLHttpRequest` 351 | object properties that are set during a live response. There are three 352 | different patterns for specifying the responses: Inline, Proxy, and 353 | Callback. 354 | 355 | #### Inline Responses #### 356 | 357 | A simple text response would be: 358 | 359 | ```javascript 360 | $.mockjax({ 361 | url: "/restful/api", 362 | responseText: "A text response from the server" 363 | }); 364 | ``` 365 | 366 | A simple JSON response would be: 367 | 368 | ```javascript 369 | $.mockjax({ 370 | url: "/restful/api", 371 | // You may need to include the [json2.js](https://raw.github.com/douglascrockford/JSON-js/master/json2.js) library for older browsers 372 | responseText: { "foo": "bar" } 373 | }); 374 | ``` 375 | 376 | Also note that a JSON response is really just a text response that jQuery will 377 | parse as JSON for you (and return a JSON object to the `success` and `complete` 378 | callbacks). 379 | 380 | A simple XML response would be: 381 | 382 | ```javascript 383 | $.mockjax({ 384 | url: "/restful/api", 385 | // Need to include the xmlDOM plugin to have this translated into a DOM object 386 | responseXML: "Hello world!" 387 | }); 388 | ``` 389 | 390 | As you can see, if you have a significant amount of data being 391 | mocked this becomes unwieldy. So that brings us to the next pattern: 392 | the proxy. 393 | 394 | #### Proxy #### 395 | 396 | In this example below, the Mockjax plugin will intercept requests for 397 | `/restful/api` and redirect them to `/mocks/data.json`: 398 | 399 | ```javascript 400 | $.mockjax({ 401 | url: "/restful/api", 402 | proxy: "/mocks/data.json" 403 | }); 404 | ``` 405 | 406 | The `/mocks/data.json` file can have any valid JSON content you want, and allows 407 | you to maintain that mock data in its own file for maintainability. 408 | 409 | > Note: If you're testing your code with a poxy, it is best to run an actual web 410 | server for the tests. Simply loading `test/index.html` from the file system may 411 | result in the proxy file not being loaded correctly. We recommend using something 412 | like the [`http-server` npm module](https://www.npmjs.com/package/http-server). 413 | 414 | #### Callback #### 415 | 416 | In the final response pattern, we can define a callback function on the 417 | `response` property and have it set `responseText` or `responseXML` as 418 | needed: 419 | 420 | ```javascript 421 | $.mockjax({ 422 | url: "/restful/api", 423 | response: function(settings) { 424 | // Investigate the `settings` to determine the response... 425 | 426 | this.responseText = "Hello world!"; 427 | } 428 | }); 429 | ``` 430 | 431 | The default version of this callback is synchronous. If you provide both parameters 432 | to the callback function, you can use asynchronous code to set the dynamic response. 433 | 434 | ```javascript 435 | $.mockjax({ 436 | url: '/restful/api', 437 | response: function(settings, done) { 438 | var self = this; 439 | someAsyncMethod(function(data){ 440 | self.responseText = data; 441 | done(); 442 | }); 443 | } 444 | }); 445 | ``` 446 | 447 | Note that the callback is given the settings provided to the `$.mockjax({...})` 448 | method merged with any Ajax settings defined by jQuery or your application. This 449 | allows you to thoroughly investigate the request before setting the response 450 | body (or headers). 451 | 452 | 453 | ## Advanced Mocking Techniques ## 454 | 455 | At this point we've looked at a series of basic mocking techniques with 456 | Mockjax and will now unpack some of the additional functionality 457 | contained in the plugin. 458 | 459 | ### Simulating Response Time and Latency ### 460 | 461 | Simulating network and server latency for a mock is as simple as adding 462 | a `responseTime` property to your mock definition: 463 | 464 | ```javascript 465 | $.mockjax({ 466 | url: "/restful/api", 467 | // Simulate a network latency of 750ms 468 | responseTime: 750, 469 | responseText: "A text response from the server" 470 | }); 471 | ``` 472 | 473 | You can also use an interval for `responseTime` to randomize latency: 474 | 475 | ```javascript 476 | $.mockjax({ 477 | url: "/restful/api", 478 | // Use a random value between 250ms and 750ms 479 | responseTime: [250, 750], 480 | responseText: "A text response from the server" 481 | }); 482 | ``` 483 | 484 | ### Simulating HTTP Response Statuses ### 485 | 486 | It's also possible to simulate response statuses other than 200 (default 487 | for Mockjax) by simply adding a `status` property. 488 | 489 | ```javascript 490 | $.mockjax({ 491 | url: "/restful/api", 492 | // Server 500 error occurred 493 | status: 500, 494 | responseText: "A text response from the server" 495 | }); 496 | ``` 497 | 498 | These forced error status codes will be handled just as if the server had 499 | returned the error: the `error` callback will get executed with the proper 500 | arguments. 501 | 502 | ### Setting the Content-Type ### 503 | 504 | You can set the content type to associate with the mock response, in the 505 | example below, we're setting a JSON content type. 506 | 507 | ```javascript 508 | $.mockjax({ 509 | url: "/restful/api", 510 | contentType: "application/json", 511 | responseText: { 512 | hello: "World!" 513 | } 514 | }); 515 | ``` 516 | 517 | ### Setting Additional HTTP Response Headers ### 518 | 519 | Additional HTTP Response Headers may be provided by setting a key in the 520 | headers object literal: 521 | 522 | ```javascript 523 | $.mockjax({ 524 | url: "/restful/api", 525 | contentType: "application/json", 526 | responseText: { 527 | hello: "World!" 528 | }, 529 | headers: { 530 | etag: "xyz123" 531 | } 532 | }); 533 | ``` 534 | 535 | ### Dynamically Generating Mock Definitions ### 536 | 537 | In some situations, all of your REST calls are based upon a URL schema. 538 | Mockjax has the ability for you to specify a callback function that is 539 | handed the `$.ajax` request settings. The callback function may then 540 | either return false to allow the request to be handled natively, or 541 | return an object literal with relevant Mockjax parameters set. Below is 542 | an example that rewrites all Ajax requests to proxy to static mocks: 543 | 544 | ```javascript 545 | $.mockjax(function(settings) { 546 | 547 | // settings.url might be: "/restful/" such as "/restful/user" 548 | 549 | var service = settings.url.match(/\/restful\/(.*)$/); 550 | if ( service ) { 551 | return { 552 | proxy: "/mocks/" + service[1] + ".json" 553 | }; 554 | } 555 | // If you get here, there was no url match 556 | return; 557 | }); 558 | ``` 559 | 560 | ### Accessing Request Headers ### 561 | 562 | In some situations, you may need access to the request headers to determine 563 | matching or response bodies. To do this, you will need to specify a 564 | callback function that is handed the `$.ajax` request settings: 565 | 566 | ```javascript 567 | $.mockjax(function( requestSettings ) { 568 | // Here is our manual URL matching... 569 | if ( requestSettings.url === "/restful/user" ) { 570 | // We have a match, so we return a response callback... 571 | return { 572 | response: function( origSettings ) { 573 | 574 | // now we check the request headers, which may be set directly 575 | // on the xhr object through an ajaxSetup() call or otherwise: 576 | 577 | if ( requestSettings.headers["Authentication"] === "some-token" ) { 578 | this.responseText = { user: { id: 13 } }; 579 | } else { 580 | this.status = 403; 581 | this.responseText = "You are not authorized"; 582 | } 583 | } 584 | }; 585 | } 586 | // If you get here, there was no url match 587 | return; 588 | }); 589 | ``` 590 | 591 | ### Forced Simulation of Server Timeouts ### 592 | 593 | Because of the way Mockjax was implemented, it takes advantage of 594 | jQuery's internal timeout handling for requests. But if you'd like to 595 | force a timeout for a request you can do so by setting the `isTimeout` 596 | property to true: 597 | 598 | ```javascript 599 | $.mockjax({ 600 | url: '/restful/api', 601 | responseTime: 1000, 602 | isTimeout: true 603 | }); 604 | ``` 605 | 606 | ### Dynamically Generating Mock Responses ### 607 | 608 | It's also possible to dynamically generate the response text upon each 609 | request by implementing a callback function on the `response` parameter: 610 | 611 | ```javascript 612 | $.mockjax({ 613 | url: "/restful/webservice", 614 | dataType: "json", 615 | response: function(settings) { 616 | this.responseText = { 617 | randomText: "random " + Math.random() 618 | }; 619 | } 620 | }); 621 | ``` 622 | 623 | ### Data Types ### 624 | 625 | Many of the examples above mock a `json` response. You can also mock `xml`: 626 | 627 | ```javascript 628 | $.mockjax({ 629 | url: "/some/xml", 630 | dataType: "xml", 631 | responseXML: "Hello world XML" 632 | }); 633 | ``` 634 | 635 | (Don't forget that it's likely you'll need the [`xmlDOM`](http://github.com/jakerella/jquery-xmldom) library as well!) 636 | 637 | And `html`: 638 | 639 | ```javascript 640 | $.mockjax({ 641 | url: "/some/webservice", 642 | dataType: "html", 643 | responseText: "
        Hello there
        " 644 | }); 645 | ``` 646 | 647 | ### Performing Actions After Request Completion ### 648 | 649 | If you need to perform some actions after a call has completed you can 650 | use one of the `onAfter{Xxxxx}` options. For example, to fire a method when 651 | a request completes (either successfully or not): 652 | 653 | ```javascript 654 | $.mockjax({ 655 | url: "/api/end/point", 656 | onAfterComplete: function() { 657 | // do any required cleanup 658 | } 659 | }); 660 | ``` 661 | 662 | ### Globally Defining Mockjax Settings ### 663 | 664 | It is also possible to define the global defaults for all Mockjax 665 | requests by overwriting the `$.mockjaxSettings` object. By default the 666 | settings are as follows: 667 | 668 | ```javascript 669 | { 670 | log: null, // DEPRECATED, use $.mockjaxSettings.logger instead 671 | logger: window.console, 672 | logging: 2, 673 | logLevelMethods: ['error', 'warn', 'info', 'log', 'debug'], 674 | namespace: null, 675 | status: 200, 676 | statusText: "OK", 677 | responseTime: 500, 678 | isTimeout: false, 679 | throwUnmocked: false, 680 | retainAjaxCalls: true, 681 | contentType: "text/plain", 682 | response: "", 683 | responseText: "", 684 | responseXML: "", 685 | proxy: "", 686 | proxyType: "GET", 687 | lastModified: null, 688 | etag: "", 689 | headers: { 690 | etag: "IJF@H#@923uf8023hFO@I#H#", 691 | "content-type" : "text/plain" 692 | } 693 | } 694 | ``` 695 | 696 | To overwrite a particular settings such as the default `content-type`, you 697 | would do the following: 698 | 699 | ```javascript 700 | $.mockjaxSettings.contentType = "application/json"; 701 | ``` 702 | 703 | ### Setting a Global URL Namespace ### 704 | 705 | The namespace option in `$.mockjaxSettings` allows you to apply a prefix to 706 | all of your mocked urls, such as `/api/v1`. 707 | 708 | ```javascript 709 | $.mockjaxSettings.namespace = "/api/v1"; 710 | ``` 711 | 712 | Then the following mock will match `/api/v1/rest`: 713 | 714 | ```javascript 715 | $.mockjax({ 716 | url: "/rest" 717 | }) 718 | ``` 719 | 720 | The global namespace option can also be overwritten on a particular mock. 721 | 722 | ```javascript 723 | $.mockjax({ 724 | url: "/rest-2", 725 | namespace: null 726 | }) 727 | ``` 728 | 729 | Note that the namespace prefix does not apply to proxies. 730 | 731 | ### Removing Mockjax Handlers ### 732 | 733 | If you need to reset the Mockjax handlers you've added, just call 734 | `$.mockjax.clear()`. _This will NOT reset the `$.mockjaxSettings`!_ 735 | 736 | ```javascript 737 | $.mockjax.clear(); 738 | ``` 739 | 740 | You can also clear individual mock handlers using their ID: 741 | 742 | ```javascript 743 | var id = $.mockjax({ 744 | ... 745 | }); 746 | 747 | $.mockjax.clear(id); 748 | ``` 749 | 750 | 751 | ## Miscellaneous Information ## 752 | 753 | ### jQuery Version Support ### 754 | 755 | We strive to ensure that Mockjax is tested on the furthest patch version of all 756 | minor (and major) versions of jQuery beginning with 1.5.2 going all the way 757 | through 2.x. In other words, we don't test 1.6.1, but rather 1.6.4 (the furthest 758 | patch version on the 1.6.x line). The QUnit tests in the `/test` directory include 759 | links to each version of jQuery tested in the header. 760 | 761 | ### Browsers Tested ### 762 | 763 | We use virtual machines to test current versions of the browsers below. In addition, 764 | we test the specific versions of IE specified. 765 | 766 | * Internet Explorer 8-11 767 | * Firefox 768 | * Safari 769 | * Chrome 770 | * Opera 771 | 772 | _Please note that while we strive to keep `master` as bug free as possible, we do 773 | not necessarily run tests in all of the above browsers for every single commit. We 774 | do, however, ensure all tests are passing before tagging a release._ 775 | 776 | 777 | ### Using Mockjax in Other Ways ### 778 | 779 | You can use Mockjax as a Node module, with require.js, or with Browserify... and 780 | presumably in other ways as well. We have tests for each of the methods above. 781 | 782 | When using Mockjax as a Node module (including with Browserify), **you must 783 | provide the module with the jQuery library and a `window`**. Here is an example 784 | using a module intended for use as a "browserified" module: 785 | 786 | ```js 787 | var jquery = require('jquery'); 788 | var mockjax = require('jquery.mockjax')(jquery, window); 789 | // Note that we expect `window` to be defined once this file is browserified and 790 | // used in a browser. If it isn't Mockjax will have a problem! 791 | 792 | mockjax({ 793 | url: '/resource', 794 | responseText: 'content' 795 | }); 796 | 797 | function getResource(cb) { 798 | jquery.ajax({ 799 | url: '/resource', 800 | success: cb, 801 | error: cb 802 | }); 803 | } 804 | ``` 805 | 806 | 807 | ### Logging ### 808 | 809 | Mockjax logs various pieces of information to the `console` (on `window`) in 810 | browsers, or to stdout in Node). You can customize various aspects of the 811 | logging to suit your needs. By default, only 'error', 'warn' or 'info' messages 812 | will be shown, but detailed information may be available in debug logs. Below 813 | are some common things you might need to do to get better logging information. 814 | 815 | #### Show different levels of log messages 816 | 817 | ```js 818 | $.mockjaxSettings.logging = 4; // very verbose debug messages 819 | $.mockjaxSettings.logging = 3; // verbose log messages 820 | $.mockjaxSettings.logging = 2; // informational messages 821 | $.mockjaxSettings.logging = 1; // warning messages 822 | $.mockjaxSettings.logging = 0; // only critical error messages 823 | ``` 824 | 825 | (Note that each level enables that level plus any lower number... thus setting 826 | logging to `2` also enables warnings and errors.) 827 | 828 | #### Implement a custom logger 829 | 830 | If you don't want to use the `console` object, you can pass in your own logging 831 | implementation with the `logger` setting. Note that your logger must either 832 | implement the `debug`, `log`, `info`, `warn`, and `error` methods, or you must 833 | also provide what methods map to the 5 levels (0 through 4). 834 | 835 | ```js 836 | $.mockjaxSettings.logger = { 837 | debug: function() { ... }, 838 | log: function() { ... }, 839 | // ... 840 | }; 841 | ``` 842 | 843 | Your logger methods may receive any number of arguments to log out, either as 844 | strings or objects, similar to how the `window.console` object methods work. 845 | 846 | If you have a logger that uses different methods names, specify them in this array: 847 | 848 | ```js 849 | $.mockjaxSettings.logLevelMethods = ['critical', 'bad', 'stuff', 'log', 'verbose']; 850 | ``` 851 | 852 | Note that the first entry in this array (index `0`) will be errors while the last 853 | entry will be verbose output. Anything beyond index `4` will be ignored. 854 | 855 | #### What about the old `log` setting? 856 | 857 | This was an undocumented feature whereby you could provide a `log` method using 858 | `$.mockjaxSettings`, however, it is no longer used internally. This undocumented 859 | option is now **deprecated**, and while it will work, log messages of ALL levels 860 | will be sent to it. 861 | 862 | If you have no idea what we're talking about... good! Don't worry about it. The 863 | proper way to implement your own logger is via `$.mockjaxSettings.logger`. 864 | 865 | ### Release History ### 866 | 867 | Please read the [CHANGELOG](https://github.com/jakerella/jquery-mockjax/blob/master/CHANGELOG.md) 868 | for a list of changes per release. 869 | 870 | Note that all releases are tagged in Github for easy reference, the `master` branch 871 | should *not* be considered a stable release! 872 | 873 | ### License ### 874 | 875 | Copyright (c) 2014 Jordan Kasper, formerly appendTo 876 | 877 | NOTE: This repository was taken over by Jordan Kasper (@jakerella) October, 2014 878 | 879 | Dual licensed under the MIT or GPL licenses: 880 | [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT) 881 | [http://www.gnu.org/licenses/gpl-2.0.html](http://www.gnu.org/licenses/gpl-2.0.html) 882 | 883 | ### Troubleshooting ### 884 | 885 | If mockjax appears to be behaving unexpectedly, be sure to check the console 886 | logs for warnings. 887 | 888 | ### Contributing ### 889 | 890 | We welcome any contributions by the community, whether in the form of a Pull 891 | Request, issue submission and comments, or just sharing on social media! 892 | 893 | If you want to contribute code to the project, please read our 894 | [Contribution guidelines](CONTRIBUTING.md) to see what you need to do to get your 895 | Pull Request ready for merging. 896 | 897 | #### Admins #### 898 | 899 | All pull requests are reviewed by the wonderful collaborators on this project: 900 | * [Doug Neiner](https://github.com/dcneiner) 901 | * [Jonathan Creamer](https://github.com/jcreamer898) 902 | * [Jordan Kasper](https://github.com/jakerella) 903 | --------------------------------------------------------------------------------