├── .nvmrc ├── .bowerrc ├── .gitignore ├── source ├── app │ ├── logger.coffee │ ├── main.coffee │ ├── custom_sinon.coffee │ ├── request_status.coffee │ ├── mock.coffee │ └── plasticine.coffee └── test │ ├── index.html │ ├── spec │ ├── plasticine.coffee │ ├── modify_request.coffee │ └── fake_request.coffee │ └── config.coffee ├── .editorconfig ├── bower.json ├── LICENSE-MIT ├── package.json ├── CHANGELOG.md ├── grunt_tasks └── spec.coffee ├── README.md ├── Gruntfile.coffee └── dist └── plasticine.js /.nvmrc: -------------------------------------------------------------------------------- 1 | 0.10.24 2 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "components" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .tmp/ 3 | components/ 4 | -------------------------------------------------------------------------------- /source/app/logger.coffee: -------------------------------------------------------------------------------- 1 | Signal = require 'signals' 2 | 3 | module.exports = 4 | debug : new Signal() 5 | warn : new Signal() 6 | error : new Signal() 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /source/app/main.coffee: -------------------------------------------------------------------------------- 1 | require.config 2 | name: 'plasticine' 3 | paths: 4 | 'crossroads' : '../components/crossroads.js/dist/crossroads' 5 | 'signals' : '../components/crossroads.js/dev/lib/signals' 6 | packages: [ 7 | { 8 | name: 'lodash' 9 | location: '../components/lodash-amd/modern' 10 | } 11 | ] 12 | -------------------------------------------------------------------------------- /source/app/custom_sinon.coffee: -------------------------------------------------------------------------------- 1 | root = this 2 | previous_sinon = root.sinon 3 | sinon = 4 | noConflict: -> 5 | root.sinon = previous_sinon 6 | return this 7 | extend: require('lodash/objects/assign') 8 | 9 | `/* @include ../components/sinon/lib/sinon/util/event.js */` 10 | `/* @include ../components/sinon/lib/sinon/util/fake_xml_http_request.js */` 11 | 12 | module.exports = sinon.noConflict() 13 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plasticine", 3 | "version": "0.1.3", 4 | "main": "dist/plasticine.js", 5 | "dependencies": { 6 | "almond": "0.2.9", 7 | "crossroads.js": "0.12.0", 8 | "lodash-amd":"2.4.1" 9 | }, 10 | "devDependencies": { 11 | "requirejs": "2.1.14", 12 | "mocha": "1.20.1", 13 | "sinon-chai": "2.5.0", 14 | "sinon": "1.10.2", 15 | "chai":"1.9.1", 16 | "jquery":"2.1.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /source/app/request_status.coffee: -------------------------------------------------------------------------------- 1 | sinon = require './custom_sinon' 2 | 3 | module.exports = 4 | isUnset: (request) -> 5 | request.readyState is sinon.FakeXMLHttpRequest.UNSET 6 | isOpened: (request) -> 7 | request.readyState is sinon.FakeXMLHttpRequest.OPENED 8 | isHeadersReceived: (request) -> 9 | request.readyState is sinon.FakeXMLHttpRequest.HEADERS_RECEIVED 10 | isLoading: (request) -> 11 | request.readyState is sinon.FakeXMLHttpRequest.LOADING 12 | isDone: (request) -> 13 | request.readyState is sinon.FakeXMLHttpRequest.DONE 14 | -------------------------------------------------------------------------------- /source/test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Mocha Spec Runner 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 David Fournier 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plasticine", 3 | "description": "A client side server mock", 4 | "version": "0.1.3", 5 | "homepage": "https://github.com/dfournier/plasticine", 6 | "engines": { 7 | "node": "0.10.24" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/dfournier/plasticine.git" 12 | }, 13 | "author": { 14 | "name": "David Fournier", 15 | "email": "fr.david.fournier@gmail.com" 16 | }, 17 | "licenses": [ 18 | { 19 | "type": "MIT", 20 | "url": "https://github.com/dfournier/plasticine/blob/master/LICENSE-MIT" 21 | } 22 | ], 23 | "devDependencies": { 24 | "grunt" : "0.4.5", 25 | "grunt-mocha" : "0.4.11", 26 | "grunt-contrib-watch" : "0.6.1", 27 | "grunt-contrib-clean" : "0.5.0", 28 | "grunt-contrib-coffee" : "0.10.1", 29 | "grunt-contrib-requirejs" : "0.4.4", 30 | "grunt-contrib-connect" : "0.7.1", 31 | "grunt-contrib-copy" : "0.5.0", 32 | "grunt-renaming-wrap" : "0.3.0", 33 | "grunt-file-process" : "0.2.2", 34 | "grunt-preprocess" : "4.0.0", 35 | "grunt-amd-wrap" : "1.0.1", 36 | "sinon" : "1.10.2", 37 | "grunt-banner" : "0.2.3" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v0.1.3: Sun Mar 01 2015 20:53:29 2 | 3 | Major fixes: 4 | 5 | * Request urls send to the server were rewrited 6 | 7 | 8 | ## v0.1.2: Mon Jun 23 2014 23:29:58 9 | 10 | Major fixes: 11 | 12 | * Universal Module Definition was setting the wrong module name 13 | * Universal Module Definition wasn't giving the good context 14 | * Request body are now JSON parsed when it's needed 15 | 16 | Improvements: 17 | 18 | * Mocha gets the spec file names via a route instead of setting it with a file process. 19 | 20 | 21 | ## v0.1.1: Tue Jun 10 2014 11:53:22 22 | 23 | Major fixes: 24 | 25 | * Allow `afterDelete`, `afterPost`, `afterPut` and `afterPatch` keys as parameters in the addMock method. 26 | 27 | 28 | ## v0.1.0: Mon Jun 09 2014 23:22:11 29 | 30 | New features: 31 | 32 | * Support of PATCH method. 33 | * Real server request can be modified with `afterGet`, `afterDelete`, `afterPost`, `afterPut` and `afterPatch` callbacks. 34 | 35 | Improvements: 36 | 37 | * Update all dependendices to the latest version. 38 | * Clean up the build process. 39 | 40 | 41 | ## v0.0.1: Thu Mar 06 2014 00:00:32 42 | 43 | Minor fixes: 44 | 45 | * Fix code exemple rendering in Readme file. 46 | * Default fake request header is empty. 47 | 48 | 49 | ## v0.0.0: Sun Mar 02 2014 23:58:20 50 | 51 | Initial version. 52 | -------------------------------------------------------------------------------- /grunt_tasks/spec.coffee: -------------------------------------------------------------------------------- 1 | module.exports = (grunt) -> 2 | 3 | fs = require('fs') 4 | serve_application = (req, res, next) -> 5 | if req.url is '/test/spec/list.json' 6 | getFiles = (dir) -> 7 | node = 8 | name: dir.split('/').pop() 9 | files: [] 10 | directories: [] 11 | files = fs.readdirSync(dir) 12 | for file in files 13 | name = dir + '/' + file 14 | if fs.statSync(name).isDirectory() 15 | node.directories.push getFiles(name) 16 | else 17 | if (/\.coffee$/).test(file) 18 | node.files.push file.replace(/\.coffee$/, '.js') 19 | return node 20 | 21 | main_node = getFiles("#{grunt.config('dir.source')}test/spec") 22 | res.end JSON.stringify main_node 23 | else 24 | next() 25 | 26 | grunt.config.merge 27 | mocha: 28 | all: 29 | options: 30 | urls : ['http://localhost:8000/test'] 31 | reporter : 'Progress' 32 | run : false 33 | log : true 34 | logErrors: true 35 | 36 | connect: 37 | development: 38 | options: 39 | open: 'http://0.0.0.0:8000/test' 40 | base: ["./", "<%= dir.tmp %>"] 41 | middleware: (connect, options, middlewares) -> 42 | middlewares.push serve_application 43 | return middlewares 44 | -------------------------------------------------------------------------------- /source/test/spec/plasticine.coffee: -------------------------------------------------------------------------------- 1 | module.exports = -> describe 'Default behavior', -> 2 | it 'should define global variable', -> 3 | @plasticine.should.to.exist 4 | @plasticine.should.to.have.keys [ 5 | 'fakeRequests' 6 | 'realRequests' 7 | 'pendingRequests' 8 | 'addMock' 9 | 'restore' 10 | 'logger' 11 | ] 12 | 13 | 14 | it 'should not intercept request', (done) -> 15 | params = 16 | headers: 17 | version: 'v0' 18 | processData: false 19 | $.ajax("/index.html", params).done (data) => 20 | data.should.equal "" 21 | @requests[0].requestHeaders.should.contain.keys 'version' 22 | @requests[0].requestHeaders.version.should.equal 'v0' 23 | @requests[0].status.should.equal 200 24 | done() 25 | @requests.should.have.length 1 26 | @requests[0].url.should.equal "#{window.location.origin}/index.html" 27 | @requests[0].respond null, 200, "" 28 | 29 | 30 | it 'should not intercept request to an other domain', (done) -> 31 | params = 32 | headers: 33 | version: 'v0' 34 | processData: false 35 | $.ajax("http://my.other.domain.com/index.html", params).done (data) => 36 | data.should.equal "" 37 | @requests[0].requestHeaders.should.contain.keys 'version' 38 | @requests[0].requestHeaders.version.should.equal 'v0' 39 | @requests[0].status.should.equal 200 40 | done() 41 | @requests.should.have.length 1 42 | @requests[0].url.should.equal "http://my.other.domain.com/index.html" 43 | @requests[0].respond null, 200, "" 44 | -------------------------------------------------------------------------------- /source/app/mock.coffee: -------------------------------------------------------------------------------- 1 | Router = require "crossroads" 2 | Logger = require "./logger" 3 | requestStatus = require "./request_status" 4 | 5 | clone = require "lodash/objects/clone" 6 | isEmpty = require "lodash/objects/isEmpty" 7 | # capitalize should be in lodah soon 8 | # capitalize = require "lodash/strings/capitalize" 9 | capitalize = (s) -> s[0].toUpperCase() + s[1..] 10 | 11 | defineRouterCallback = (method, callback) -> 12 | route = "/#{method.toUpperCase()}#{@route}" 13 | @createdRoutes.push Router.addRoute route, callback 14 | 15 | fakeRequest = (method) -> 16 | defineRouterCallback.call this, method, (xhr, route_params) => 17 | if xhr.fakeResponse? 18 | Logger.warn.dispatch "ALREADY_FAKED", this, xhr 19 | return false 20 | 21 | xhr.fakeResponse = 22 | mock : this 23 | isResponseSet : false 24 | response : null 25 | 26 | ready_state_change = => 27 | return unless requestStatus.isOpened(xhr.request) and xhr.request.sendFlag 28 | return if xhr.fakeResponse.isResponseSet 29 | xhr.fakeResponse.isResponseSet = true 30 | 31 | new_arguments = [route_params] 32 | if method in ['post', 'put', 'patch'] 33 | new_arguments.push JSON.parse(xhr.request.requestBody) 34 | 35 | xhr.fakeResponse.response = @[method].apply(@, new_arguments) 36 | xhr.fakeResponse.response.headers = MockBase.header 37 | xhr.isBodyProcessed = true 38 | xhr.response = clone xhr.fakeResponse.response, true 39 | xhr.responseReady.dispatch() 40 | 41 | xhr.request.addEventListener "readystatechange", ready_state_change, false 42 | 43 | modifyRequest = (method) -> 44 | defineRouterCallback.call this, method, (xhr, route_params) => 45 | xhr.responseReady.add => 46 | xhr.processBody() 47 | new_response = xhr.response 48 | modifier = 49 | source : clone(xhr.response, true) 50 | output : new_response 51 | @["after#{capitalize(method)}"](new_response) 52 | 53 | xhr.responseModifiers = modifier 54 | xhr.response = clone modifier.output, true 55 | 56 | 57 | module.exports = class MockBase 58 | @header: {} 59 | 60 | constructor: -> 61 | @createdRoutes = [] 62 | @disposed = false 63 | 64 | 65 | setUp: -> 66 | supported_method = ['get', 'put', 'post', 'delete', 'patch'] 67 | for method in supported_method when @[method]? 68 | fakeRequest.call(this, method) 69 | 70 | for method in supported_method when @["after#{capitalize(method)}"]? 71 | modifyRequest.call(this, method) 72 | 73 | 74 | dispose: -> 75 | return if @disposed 76 | @disposed = true 77 | while @createdRoutes.length isnt 0 78 | Router.removeRoute @createdRoutes.pop() 79 | -------------------------------------------------------------------------------- /source/test/config.coffee: -------------------------------------------------------------------------------- 1 | require.config 2 | baseUrl: '/app' 3 | packages: [ 4 | { 5 | name: 'lodash' 6 | location: '/components/lodash-amd/modern' 7 | } 8 | { 9 | name: 'sinon' 10 | location: '/components/sinon/lib/sinon' 11 | main: '../sinon' 12 | } 13 | ] 14 | paths: 15 | 'chai' : '/components/chai/chai' 16 | 'sinon-chai' : '/components/sinon-chai/lib/sinon-chai' 17 | 'jquery' : '/components/jquery/dist/jquery' 18 | 'plasticine' : '/app/plasticine' 19 | 'crossroads' : '/components/crossroads.js/dist/crossroads' 20 | 'signals' : '/components/crossroads.js/dev/lib/signals' 21 | shim: 22 | 'sinon': 23 | deps: [ 24 | '/components/sinon/lib/sinon.js' 25 | '/components/sinon/lib/sinon/util/event.js' 26 | '/components/sinon/lib/sinon/util/fake_xml_http_request.js' 27 | ] 28 | exports: 'sinon' 29 | 30 | files = [ 31 | 'chai' 32 | 'sinon-chai' 33 | 'sinon' 34 | ] 35 | 36 | 37 | require ['jquery'], ($) -> 38 | $.ajax({url: 'spec/list.json', method: 'GET'}).done (data) -> 39 | main_node = JSON.parse(data) 40 | get_files = (path, node) -> 41 | out = [] 42 | new_path = path + node.name + '/' 43 | out.push new_path + file for file in node.files 44 | for directory in node.directories 45 | out = out.concat get_files(new_path, directory) 46 | return out 47 | files = files.concat get_files '/test/', main_node 48 | 49 | capitalize = (s) -> 50 | s.charAt(0).toUpperCase() + s[1...] 51 | 52 | get_requires = (path, node, is_base = false) -> 53 | new_path = path + node.name + '/' 54 | name = if is_base then "" else capitalize node.name 55 | describe name, -> 56 | for file in node.files 57 | require(new_path + file)() 58 | for directory in node.directories 59 | get_requires(new_path, directory) 60 | 61 | 62 | require files, -> 63 | chai = require("chai") 64 | sinonChai = require("sinon-chai") 65 | 66 | should = chai.should() 67 | 68 | chai.use(sinonChai); 69 | mocha.setup 70 | globals: ['should', 'sinon'] 71 | 72 | describe '', -> 73 | before (done) -> 74 | require ['sinon/util/event', 'sinon/util/fake_xml_http_request'], => 75 | xhr = sinon.useFakeXMLHttpRequest() 76 | require ['plasticine'], (plasticine) => 77 | xhr.onCreate = (request) => 78 | request.respondHelper = (status, data) -> 79 | request.respond( 80 | status 81 | {"Content-Type": "application/json"} 82 | JSON.stringify data) 83 | @requests.push(request) 84 | @plasticine = plasticine 85 | done() 86 | 87 | beforeEach -> 88 | @requests = [] 89 | 90 | get_requires('/test/', main_node, true) 91 | 92 | mocha.run() 93 | -------------------------------------------------------------------------------- /source/app/plasticine.coffee: -------------------------------------------------------------------------------- 1 | Router = require 'crossroads' 2 | Signal = require 'signals' 3 | Mock = require './mock' 4 | Logger = require './logger' 5 | requestStatus = require './request_status' 6 | 7 | sinon = require './custom_sinon' 8 | defer = require "lodash/functions/defer" 9 | 10 | # setup Router 11 | Router.normalizeFn = Router.NORM_AS_OBJECT; 12 | Router.ignoreState = true 13 | Router.greedy = true 14 | 15 | initialize = -> 16 | protect_from_faking = false 17 | protectFromFaking = -> 18 | protect_from_faking = true 19 | 20 | getProtectFromFakingValue = -> 21 | out = protect_from_faking 22 | protect_from_faking = false 23 | return out 24 | 25 | # instantiate and configure fake request 26 | Request = sinon.useFakeXMLHttpRequest() 27 | 28 | Request.useFilters = true 29 | 30 | Request.onCreate = (request) -> 31 | Plasticine.pendingRequests.push 32 | request : request 33 | protectFromFaking : getProtectFromFakingValue() 34 | responseReady : new Signal() 35 | fakeResponse : null 36 | responseModifiers : [] 37 | processBody: -> 38 | unless @isBodyProcessed 39 | @isBodyProcessed = true 40 | @response.body = JSON.parse @response.body 41 | isBodyProcessed : false 42 | 43 | Request.addFilter (method, url) -> 44 | xhr = Plasticine.pendingRequests.pop() 45 | if xhr.protectFromFaking 46 | Plasticine.realRequests.push xhr 47 | return true 48 | else 49 | urlHelper = document.createElement('a') 50 | urlHelper.href = url 51 | url = urlHelper.pathname 52 | 53 | Router.parse "/#{method}#{url.split('?')[0]}", [xhr] 54 | Plasticine.fakeRequests.push xhr 55 | 56 | xhr.responseReady.add -> 57 | xhr.response.body = JSON.stringify(xhr.response.body) if xhr.isBodyProcessed 58 | xhr.request.respond( 59 | xhr.response.status 60 | xhr.response.headers 61 | xhr.response.body) 62 | 63 | unless xhr.fakeResponse? 64 | protectFromFaking() 65 | real_request = new Request 66 | xhr.realResponse = 67 | request : real_request 68 | 69 | real_request.open method, urlHelper.origin + url, true 70 | xhr.request.onSend = -> 71 | for key, value of xhr.request.requestHeaders 72 | real_request.setRequestHeader key, value 73 | real_request.send xhr.request.requestBody || '' 74 | 75 | ready_state_change = -> 76 | if requestStatus.isDone(real_request) 77 | xhr.response = 78 | status : real_request.status 79 | headers : real_request.requestHeaders 80 | body : real_request.responseText 81 | xhr.responseReady.dispatch() 82 | real_request.addEventListener "readystatechange", ready_state_change, false 83 | ready_state_change() 84 | 85 | return false 86 | 87 | initialize() 88 | 89 | # public API 90 | 91 | module.exports = Plasticine = 92 | fakeRequests : [] 93 | realRequests : [] 94 | pendingRequests : [] 95 | logger : Logger 96 | 97 | restore: -> 98 | Request.restore() 99 | Request.filters = [] 100 | initialize() 101 | 102 | addMock: (params = {}) -> 103 | model = new Mock() 104 | available_params = [ 105 | 'route' 106 | 'get' 107 | 'put' 108 | 'post' 109 | 'patch' 110 | 'delete' 111 | 'afterGet' 112 | 'afterPut' 113 | 'afterPost' 114 | 'afterPatch' 115 | 'afterDelete' 116 | ] 117 | model[key] = params[key] for key in available_params 118 | model.setUp() 119 | 120 | return model 121 | -------------------------------------------------------------------------------- /source/test/spec/modify_request.coffee: -------------------------------------------------------------------------------- 1 | clone = require 'lodash/objects/clone' 2 | module.exports = -> describe 'Modify request', -> 3 | 4 | done_callback = null 5 | fail_callback = null 6 | ajax_call = null 7 | content = null 8 | 9 | before -> 10 | ajax_call = -> 11 | $.ajax 12 | type : 'GET' 13 | url : '/info.json' 14 | .done(done_callback) 15 | .fail(fail_callback) 16 | 17 | beforeEach -> 18 | mock_callback = sinon.spy() 19 | done_callback = sinon.spy() 20 | fail_callback = sinon.spy() 21 | 22 | content = 23 | message : 'Hello' 24 | receiver : 'world' 25 | 26 | describe 'basic functionalities', -> 27 | mock = null 28 | mock_callback = null 29 | 30 | before -> 31 | mock = @plasticine.addMock 32 | route: '/info.json' 33 | afterGet: (request) -> 34 | copy = clone request, true 35 | mock_callback(copy) 36 | request.body.emitter = 'Main server' 37 | 38 | after -> 39 | mock.dispose() 40 | 41 | beforeEach -> 42 | mock_callback = sinon.spy() 43 | 44 | it 'should not intercept request and modify the response', (done) -> 45 | ajax_call().always => 46 | fail_callback.should.not.have.been.called 47 | done_callback.should.have.been.calledOnce 48 | mock_callback.should.have.been.calledOnce 49 | mock_callback.firstCall.should.have.been.calledWith 50 | status : 200 51 | headers : {} 52 | body : 53 | message : 'Hello' 54 | receiver : 'world' 55 | JSON.parse(done_callback.firstCall.args[0]).should.deep.equal 56 | message : 'Hello' 57 | receiver : 'world' 58 | emitter : 'Main server' 59 | done() 60 | @requests.should.have.length 1 61 | @requests[0].respondHelper 200, content 62 | 63 | describe 'with concurrent mocks', -> 64 | mock1 = null 65 | mock2 = null 66 | mock_callback1 = null 67 | mock_callback2 = null 68 | 69 | before -> 70 | mock1 = @plasticine.addMock 71 | route: '/info.json' 72 | afterGet: (request) -> 73 | mock_callback1(clone request, true) 74 | request.body.emitter = 'Main server' 75 | 76 | mock2 = @plasticine.addMock 77 | route: '/info.json' 78 | afterGet: (request) -> 79 | mock_callback2(clone request, true) 80 | request.body.message = 'Ciao' 81 | 82 | after -> 83 | mock1.dispose() 84 | mock2.dispose() 85 | 86 | beforeEach -> 87 | mock_callback1 = sinon.spy() 88 | mock_callback2 = sinon.spy() 89 | 90 | it 'should apply mock consecutively', (done) -> 91 | ajax_call().always => 92 | fail_callback.should.not.have.been.called 93 | done_callback.should.have.been.calledOnce 94 | mock_callback1.should.have.been.calledOnce 95 | mock_callback2.should.have.been.calledOnce 96 | mock_callback1.should.have.been.calledBefore mock_callback2 97 | mock_callback1.should.have.been.calledWith 98 | status : 200 99 | headers : {} 100 | body : 101 | message : 'Hello' 102 | receiver : 'world' 103 | mock_callback2.should.have.been.calledWith 104 | status : 200 105 | headers : {} 106 | body : 107 | message : 'Hello' 108 | receiver : 'world' 109 | emitter : 'Main server' 110 | JSON.parse(done_callback.firstCall.args[0]).should.deep.equal 111 | message : 'Ciao' 112 | receiver : 'world' 113 | emitter : 'Main server' 114 | done() 115 | @requests.should.have.length 1 116 | @requests[0].respondHelper 200, content 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Plasticine 2 | 3 | Play as much as you want with me and get a nice server. 4 | 5 | ## About 6 | 7 | Plasticine is designed by a font-end developper for front-end developpers. It supplies a simple API to fake some requests. It can be used when dvelopping a new feature without a ready back-end. Just define the API the server will use, create your mock and start to develop! 8 | 9 | ## Usage 10 | 11 | The library is define as UMD module (see [amdWeb](https://github.com/umdjs/umd/blob/master/amdWeb.js)). It can be injected as an AMD module or added in the JavascriptCode and define the global variable `Plasticine`. 12 | 13 | Here an example to play with Plasticine: 14 | ```JavaScript 15 | Plasticine.addMock({ 16 | route: '/info.json', 17 | get: function() { 18 | return {status: 200, body: {message: 'Hello world!'}}; 19 | } 20 | }); 21 | ``` 22 | 23 | With this sample, any request GET on route info.json is faked (no request is send). The response of the request is defined by the `get` callback. 24 | 25 | ### `Plasticine.addMock(params):Mock` 26 | 27 | `params` is an object with those keys: 28 | 29 | * `route`: define the request route to catch. It can have parameters, syntax define by [crossroads.js](http://millermedeiros.github.io/crossroads.js/#crossroads-add_route). This parameter is mandatory. 30 | * `get`: callback to determine a GET request fake response. Callback get route variables as parameters and return an object with keys `status` and `body`: 31 | ```JavaScript 32 | Plasticine.addMock({ 33 | route: '/messages/{id}.json', 34 | get: function(route_params) { 35 | if (route_params.id === '1') 36 | return {status: 200, body: {message: 'Hello world!'}}; 37 | else 38 | return {status: 404, body: {error: 'unknown message'}}; 39 | } 40 | }); 41 | ``` 42 | * `delete`: same as `get` but on a DELETE request. 43 | * `post`: same as `get` but on a POST request and callback second parameter has request payload: 44 | ```JavaScript 45 | Plasticine.addMock({ 46 | route: '/messages/{id}.json', 47 | post: function(route_params, data) { 48 | data.id = Math.round(Math.random()*1000000000) 49 | return {status: 200, body: data}; 50 | } 51 | }); 52 | ``` 53 | * `put`: same as `post` but on a PUT request. 54 | * `patch`: same as `post` but on a PATCH request. 55 | * `afterGet`: callback to modify a request before notifying it's loaded. The callback get an object whith keys `status`, `headers` and `body` which it can change to affect the request response: 56 | ```JavaScript 57 | Plasticine.addMock({ 58 | route: '/messages/{id}.json', 59 | afterGet: function (request) { 60 | // add key starred to introduce a new feature which is under development by backend 61 | request.body.starred = Math.random() < 0.5 ? true : false; 62 | }); 63 | ``` 64 | * `afterDelete`: same as `afterGet` but on a DELETE request. 65 | * `afterPost`: same as `afterGet` but on a POST request. 66 | * `afterPut`: same as `afterGet` but on a PUT request. 67 | * `afterPatch`: same as `afterGet` but on a PATCH request. 68 | 69 | 70 | ### `Mock.dispose()` 71 | 72 | Calling this method makes the `Mock` return by `Plasticine.addMock()` not to intercept requests anymore. 73 | 74 | ## Dependencies 75 | 76 | All dependencies are include in the library: 77 | 78 | * Library dependencies 79 | * [Almond](https://github.com/jrburke/almond): to load library modules 80 | * [Crossroads.js](http://millermedeiros.github.io/crossroads.js): to define route and parse request url 81 | * [Lodash](http://lodash.com/): AMD utilities 82 | * [Sinon.js](http://sinonjs.org/): to intercept requests and to test the library 83 | * Development dependencies 84 | * [Chai](http://chaijs.com/): assertion library 85 | * [jQuery](http://jquery.com/): to send requests 86 | * [Mocha](http://mochajs.org/): test framework 87 | * [Sinon.js](http://sinonjs.org/): to create fake server 88 | 89 | ## Roadmap 90 | 91 | * Configure custom delay 92 | * Chrome extention to mixed up faked and not faked requests 93 | -------------------------------------------------------------------------------- /source/test/spec/fake_request.coffee: -------------------------------------------------------------------------------- 1 | module.exports = -> describe 'Fake request', -> 2 | 3 | mock_callback = null 4 | done_callback = null 5 | fail_callback = null 6 | ajax_call = null 7 | content = null 8 | 9 | before -> 10 | ajax_call = -> 11 | $.ajax 12 | type : 'GET' 13 | url : '/info.json' 14 | .done(done_callback) 15 | .fail(fail_callback) 16 | 17 | beforeEach -> 18 | mock_callback = sinon.spy() 19 | done_callback = sinon.spy() 20 | fail_callback = sinon.spy() 21 | 22 | content = 23 | message : 'Hello' 24 | receiver : 'world' 25 | 26 | describe 'basic functionalities', -> 27 | mock = null 28 | 29 | before -> 30 | mock = @plasticine.addMock 31 | route: '/info.json' 32 | get: -> 33 | mock_callback() 34 | status: 200 35 | body: 36 | message : 'Someone here?' 37 | receiver : 'world' 38 | 39 | it 'should intercept with a static route on a GET', (done) -> 40 | ajax_call().always => 41 | @requests.should.have.length 0 42 | fail_callback.should.not.have.been.called 43 | done_callback.should.have.been.calledOnce 44 | response_text = done_callback.firstCall.args[0] 45 | (JSON.parse response_text).should.be.deep.equal 46 | message : 'Someone here?' 47 | receiver : 'world' 48 | mock_callback.should.have.been.calledOnce 49 | done() 50 | 51 | 52 | it 'should not intercept when the mock is disposed', (done) -> 53 | mock.dispose() 54 | ajax_call().always -> 55 | fail_callback.should.not.have.been.called 56 | done_callback.should.have.been.calledOnce 57 | response_text = done_callback.firstCall.args[0] 58 | (JSON.parse response_text).should.be.deep.equal content 59 | mock_callback.should.not.have.been.called 60 | done() 61 | @requests.should.have.length 1 62 | @requests[0].respondHelper 200, content 63 | 64 | describe 'arguments passed to callbacks', -> 65 | mock = null 66 | get_callback_stub = sinon.stub() 67 | delete_callback_stub = sinon.stub() 68 | post_callback_stub = sinon.stub() 69 | put_callback_stub = sinon.stub() 70 | patch_callback_stub = sinon.stub() 71 | 72 | before -> 73 | get_callback_stub.returns status: 200, body: {} 74 | delete_callback_stub.returns status: 200, body: {} 75 | post_callback_stub.returns status: 200, body: {} 76 | put_callback_stub.returns status: 200, body: {} 77 | patch_callback_stub.returns status: 200, body: {} 78 | 79 | mock = @plasticine.addMock 80 | route : '/info.json' 81 | get : get_callback_stub 82 | delete : delete_callback_stub 83 | post : post_callback_stub 84 | put : put_callback_stub 85 | patch : patch_callback_stub 86 | 87 | after -> 88 | mock.dispose() 89 | 90 | it 'should not give argument on a GET', (done) -> 91 | $.ajax 92 | type : 'GET' 93 | url : '/info.json' 94 | .always -> 95 | get_callback_stub.getCall(0).args.should.have.length 1 96 | done() 97 | 98 | it 'should not give argument on a DELETE', (done) -> 99 | $.ajax 100 | type : 'DELETE' 101 | url : '/info.json' 102 | .always -> 103 | delete_callback_stub.getCall(0).args.should.have.length 1 104 | done() 105 | 106 | it 'should give request data as argument on a POST', (done) -> 107 | data = {message: 'Hello', author: 'world'} 108 | $.ajax 109 | type : 'POST' 110 | url : '/info.json' 111 | data : JSON.stringify(data) 112 | .always -> 113 | post_callback_stub.getCall(0).args.should.have.length 2 114 | post_callback_stub.getCall(0).args[1].should.eql data 115 | done() 116 | 117 | it 'should give request data as argument on a PUT', (done) -> 118 | data = {message: 'Hello', author: 'world'} 119 | $.ajax 120 | type : 'PUT' 121 | url : '/info.json' 122 | data : JSON.stringify(data) 123 | .always -> 124 | put_callback_stub.getCall(0).args.should.have.length 2 125 | put_callback_stub.getCall(0).args[1].should.be.eql data 126 | done() 127 | 128 | it 'should give request data as argument on a PATCH', (done) -> 129 | data = {message: 'Hello', author: 'world'} 130 | $.ajax 131 | type : 'PATCH' 132 | url : '/info.json' 133 | data : JSON.stringify(data) 134 | .always -> 135 | patch_callback_stub.getCall(0).args.should.have.length 2 136 | patch_callback_stub.getCall(0).args[1].should.be.eql data 137 | done() 138 | 139 | describe 'with concurrent mocks', -> 140 | mock1 = null 141 | mock2 = null 142 | mock1_spy = sinon.spy() 143 | mock2_spy = sinon.spy() 144 | logger_spy = sinon.spy() 145 | 146 | before -> 147 | mock1 = @plasticine.addMock 148 | route : '/info.json' 149 | get : -> 150 | mock1_spy() 151 | status : 200 152 | body : {} 153 | 154 | mock2 = @plasticine.addMock 155 | route : '/info.json' 156 | get : -> 157 | mock2_spy() 158 | status : 200 159 | body : {} 160 | 161 | after -> 162 | mock1.dispose() 163 | mock2.dispose() 164 | 165 | it 'should warn if 2 mocks try to fake the same route', (done) -> 166 | @plasticine.logger.warn.add logger_spy 167 | $.ajax 168 | type : 'GET' 169 | url : '/info.json' 170 | .always (data, event, xhr) -> 171 | mock1_spy.should.have.been.calledOnce 172 | mock2_spy.should.have.not.been.called 173 | logger_spy.should.have.been.calledOnce 174 | logger_spy.firstCall.args[0].should.equal 'ALREADY_FAKED' 175 | logger_spy.firstCall.args[1].should.equal mock2 176 | done() 177 | -------------------------------------------------------------------------------- /Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | "use strict" 2 | module.exports = (grunt) -> 3 | 4 | # Project configuration. 5 | grunt.initConfig 6 | 7 | # Metadata. 8 | pkg: grunt.file.readJSON("package.json") 9 | 10 | dir: 11 | source : 'source/' 12 | bower : 'components/' 13 | tmp : '.tmp/' 14 | 15 | # Task configuration. 16 | clean: 17 | tmp : ["<%= dir.tmp %>"] 18 | dist : ["dist"] 19 | 20 | copy: 21 | source: 22 | files: [ 23 | expand: true 24 | cwd: "<%= dir.source %>" 25 | src: ['**', '!**/*.coffee'] 26 | dest: "<%= dir.tmp %>" 27 | ] 28 | components: 29 | files: [ 30 | expand: true 31 | cwd: "<%= dir.bower %>" 32 | src: ['**'] 33 | dest: "<%= dir.tmp %><%= dir.bower %>" 34 | ] 35 | 36 | coffee: 37 | compile: 38 | options: 39 | bare : true 40 | sourceMap : false 41 | expand : true 42 | cwd : "<%= dir.source %>" 43 | src : ['**/*.coffee'] 44 | dest : "<%= dir.tmp %>" 45 | ext : '.js' 46 | 47 | amdwrap: 48 | compile: 49 | expand : true 50 | cwd : "<%= dir.tmp %>" 51 | src : ['app/**/*.js', 'test/spec/**/*.js'] 52 | dest : "<%= dir.tmp %>" 53 | 54 | wrap: 55 | dist: 56 | expand: true 57 | cwd: "dist/" 58 | src: ["**"] 59 | dest: "dist/" 60 | options: 61 | wrapper: [ 62 | """ 63 | (function (root, factory) { 64 | if (typeof define === 'function' && define.amd) { 65 | // AMD. Register as an anonymous module. 66 | define(['plasticine'], factory); 67 | } else { 68 | // Browser globals 69 | root.Plasticine = factory(root); 70 | } 71 | }(this, function (global) { 72 | """ 73 | """ 74 | return require('plasticine'); 75 | })); 76 | """] 77 | 78 | preprocess: 79 | javascript: 80 | options: 81 | inline: true 82 | src: ["<%= dir.tmp %>app/**/*.js"] 83 | 84 | watch: 85 | options: 86 | livereload: true 87 | spawn: false 88 | cwd : "<%= dir.source %>" 89 | coffeeFileModified: 90 | files: "**/*.coffee" 91 | tasks: ["coffee", "amdwrap:compile", "mocha"] 92 | options: 93 | event: ['changed'] 94 | coffeeFileAdded: 95 | files: "**/*.coffee" 96 | tasks: ["coffee", "amdwrap:compile", "mocha"] 97 | options: 98 | event: ['added'] 99 | coffeeFileDeleted: 100 | files: "**/*.coffee" 101 | tasks: ["clean:tmp", "coffee", "mocha"] 102 | options: 103 | event: ['deleted'] 104 | 105 | requirejs: 106 | compile: 107 | options: 108 | mainConfigFile: "<%= dir.tmp %>app/main.js" 109 | out: "dist/plasticine.js" 110 | optimize: 'none' 111 | cjsTranslate: true 112 | baseUrl: '<%= dir.tmp %>app' 113 | paths: 114 | requireLib: '../components/almond/almond' 115 | include: ['requireLib'] 116 | 117 | usebanner: 118 | dist: 119 | options: 120 | position: 'top' 121 | linebreak: true 122 | banner: 123 | """ 124 | /*! 125 | * plasticine JavaScript Library <%= pkg.version %> 126 | * https://github.com/dfournier/plasticine 127 | * 128 | * Copyright 2014 David Fournier 129 | * Released under the MIT license 130 | * https://github.com/dfournier/plasticine/blob/master/LICENSE-MIT 131 | * 132 | * Date: <%= grunt.template.today() %> 133 | */ 134 | """ 135 | files: 136 | src: "dist/plasticine.js" 137 | 138 | 139 | grunt.task.loadTasks 'grunt_tasks' 140 | grunt.loadNpmTasks "grunt-contrib-clean" 141 | grunt.loadNpmTasks "grunt-mocha" 142 | grunt.loadNpmTasks "grunt-contrib-watch" 143 | grunt.loadNpmTasks "grunt-contrib-connect" 144 | grunt.loadNpmTasks "grunt-contrib-copy" 145 | grunt.loadNpmTasks "grunt-contrib-coffee" 146 | grunt.loadNpmTasks "grunt-amd-wrap" 147 | grunt.loadNpmTasks "grunt-renaming-wrap" 148 | grunt.loadNpmTasks 'grunt-preprocess' 149 | grunt.loadNpmTasks "grunt-contrib-requirejs" 150 | grunt.loadNpmTasks "grunt-banner" 151 | 152 | grunt.event.on 'watch', (action, filepath, target) -> 153 | coffee_files = [] 154 | compile_config = -> 155 | coffee_files.push 'test/config.coffee' 156 | 157 | coffee_task = 'coffee.compile' 158 | root_path = grunt.config.get("#{coffee_task}.cwd") 159 | relative_path = filepath.replace(new RegExp("^#{root_path}"), '') 160 | ext = grunt.config.get("#{coffee_task}.ext") 161 | relative_compiled_path = relative_path.replace(/.coffee$/, ext) 162 | compiled_file = grunt.config.get('dir.tmp') + relative_compiled_path 163 | 164 | if target in ['coffeeFileModified', 'coffeeFileAdded'] 165 | coffee_files.push relative_path 166 | grunt.config("amdwrap.compile.src", relative_compiled_path) 167 | 168 | # recompile test/config.coffee if a file is added or deleted in test/spec folder 169 | if action in ['deleted', 'added'] and (/^test\/spec\//).test relative_path 170 | compile_config() 171 | 172 | if target is 'coffeeFileDeleted' 173 | grunt.config('clean.tmp', compiled_file) 174 | compile_config() if (/^test\/spec\//).test relative_path 175 | 176 | grunt.config("#{coffee_task}.src", coffee_files) 177 | 178 | 179 | grunt.registerTask "compileTest", ["amdwrap:compile"] 180 | 181 | grunt.registerTask "default", ["test"] 182 | grunt.registerTask "compile", ["clean:tmp", "coffee", "copy", "preprocess"] 183 | grunt.registerTask "build", ["clean:dist", "compile", "requirejs", "wrap:dist", "usebanner"] 184 | 185 | grunt.registerTask "start", ["compile", "compileTest", "connect:development", "watch"] 186 | 187 | grunt.registerTask "test", ["compile", "compileTest", "mocha"] 188 | -------------------------------------------------------------------------------- /dist/plasticine.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * plasticine JavaScript Library 0.1.3 3 | * https://github.com/dfournier/plasticine 4 | * 5 | * Copyright 2014 David Fournier 6 | * Released under the MIT license 7 | * https://github.com/dfournier/plasticine/blob/master/LICENSE-MIT 8 | * 9 | * Date: Sun Mar 01 2015 20:53:29 10 | */ 11 | (function (root, factory) { 12 | if (typeof define === 'function' && define.amd) { 13 | // AMD. Register as an anonymous module. 14 | define(['plasticine'], factory); 15 | } else { 16 | // Browser globals 17 | root.Plasticine = factory(root); 18 | } 19 | }(this, function (global) { 20 | /** 21 | * @license almond 0.2.9 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved. 22 | * Available via the MIT or new BSD license. 23 | * see: http://github.com/jrburke/almond for details 24 | */ 25 | //Going sloppy to avoid 'use strict' string cost, but strict practices should 26 | //be followed. 27 | /*jslint sloppy: true */ 28 | /*global setTimeout: false */ 29 | 30 | var requirejs, require, define; 31 | (function (undef) { 32 | var main, req, makeMap, handlers, 33 | defined = {}, 34 | waiting = {}, 35 | config = {}, 36 | defining = {}, 37 | hasOwn = Object.prototype.hasOwnProperty, 38 | aps = [].slice, 39 | jsSuffixRegExp = /\.js$/; 40 | 41 | function hasProp(obj, prop) { 42 | return hasOwn.call(obj, prop); 43 | } 44 | 45 | /** 46 | * Given a relative module name, like ./something, normalize it to 47 | * a real name that can be mapped to a path. 48 | * @param {String} name the relative name 49 | * @param {String} baseName a real name that the name arg is relative 50 | * to. 51 | * @returns {String} normalized name 52 | */ 53 | function normalize(name, baseName) { 54 | var nameParts, nameSegment, mapValue, foundMap, lastIndex, 55 | foundI, foundStarMap, starI, i, j, part, 56 | baseParts = baseName && baseName.split("/"), 57 | map = config.map, 58 | starMap = (map && map['*']) || {}; 59 | 60 | //Adjust any relative paths. 61 | if (name && name.charAt(0) === ".") { 62 | //If have a base name, try to normalize against it, 63 | //otherwise, assume it is a top-level require that will 64 | //be relative to baseUrl in the end. 65 | if (baseName) { 66 | //Convert baseName to array, and lop off the last part, 67 | //so that . matches that "directory" and not name of the baseName's 68 | //module. For instance, baseName of "one/two/three", maps to 69 | //"one/two/three.js", but we want the directory, "one/two" for 70 | //this normalization. 71 | baseParts = baseParts.slice(0, baseParts.length - 1); 72 | name = name.split('/'); 73 | lastIndex = name.length - 1; 74 | 75 | // Node .js allowance: 76 | if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) { 77 | name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, ''); 78 | } 79 | 80 | name = baseParts.concat(name); 81 | 82 | //start trimDots 83 | for (i = 0; i < name.length; i += 1) { 84 | part = name[i]; 85 | if (part === ".") { 86 | name.splice(i, 1); 87 | i -= 1; 88 | } else if (part === "..") { 89 | if (i === 1 && (name[2] === '..' || name[0] === '..')) { 90 | //End of the line. Keep at least one non-dot 91 | //path segment at the front so it can be mapped 92 | //correctly to disk. Otherwise, there is likely 93 | //no path mapping for a path starting with '..'. 94 | //This can still fail, but catches the most reasonable 95 | //uses of .. 96 | break; 97 | } else if (i > 0) { 98 | name.splice(i - 1, 2); 99 | i -= 2; 100 | } 101 | } 102 | } 103 | //end trimDots 104 | 105 | name = name.join("/"); 106 | } else if (name.indexOf('./') === 0) { 107 | // No baseName, so this is ID is resolved relative 108 | // to baseUrl, pull off the leading dot. 109 | name = name.substring(2); 110 | } 111 | } 112 | 113 | //Apply map config if available. 114 | if ((baseParts || starMap) && map) { 115 | nameParts = name.split('/'); 116 | 117 | for (i = nameParts.length; i > 0; i -= 1) { 118 | nameSegment = nameParts.slice(0, i).join("/"); 119 | 120 | if (baseParts) { 121 | //Find the longest baseName segment match in the config. 122 | //So, do joins on the biggest to smallest lengths of baseParts. 123 | for (j = baseParts.length; j > 0; j -= 1) { 124 | mapValue = map[baseParts.slice(0, j).join('/')]; 125 | 126 | //baseName segment has config, find if it has one for 127 | //this name. 128 | if (mapValue) { 129 | mapValue = mapValue[nameSegment]; 130 | if (mapValue) { 131 | //Match, update name to the new value. 132 | foundMap = mapValue; 133 | foundI = i; 134 | break; 135 | } 136 | } 137 | } 138 | } 139 | 140 | if (foundMap) { 141 | break; 142 | } 143 | 144 | //Check for a star map match, but just hold on to it, 145 | //if there is a shorter segment match later in a matching 146 | //config, then favor over this star map. 147 | if (!foundStarMap && starMap && starMap[nameSegment]) { 148 | foundStarMap = starMap[nameSegment]; 149 | starI = i; 150 | } 151 | } 152 | 153 | if (!foundMap && foundStarMap) { 154 | foundMap = foundStarMap; 155 | foundI = starI; 156 | } 157 | 158 | if (foundMap) { 159 | nameParts.splice(0, foundI, foundMap); 160 | name = nameParts.join('/'); 161 | } 162 | } 163 | 164 | return name; 165 | } 166 | 167 | function makeRequire(relName, forceSync) { 168 | return function () { 169 | //A version of a require function that passes a moduleName 170 | //value for items that may need to 171 | //look up paths relative to the moduleName 172 | return req.apply(undef, aps.call(arguments, 0).concat([relName, forceSync])); 173 | }; 174 | } 175 | 176 | function makeNormalize(relName) { 177 | return function (name) { 178 | return normalize(name, relName); 179 | }; 180 | } 181 | 182 | function makeLoad(depName) { 183 | return function (value) { 184 | defined[depName] = value; 185 | }; 186 | } 187 | 188 | function callDep(name) { 189 | if (hasProp(waiting, name)) { 190 | var args = waiting[name]; 191 | delete waiting[name]; 192 | defining[name] = true; 193 | main.apply(undef, args); 194 | } 195 | 196 | if (!hasProp(defined, name) && !hasProp(defining, name)) { 197 | throw new Error('No ' + name); 198 | } 199 | return defined[name]; 200 | } 201 | 202 | //Turns a plugin!resource to [plugin, resource] 203 | //with the plugin being undefined if the name 204 | //did not have a plugin prefix. 205 | function splitPrefix(name) { 206 | var prefix, 207 | index = name ? name.indexOf('!') : -1; 208 | if (index > -1) { 209 | prefix = name.substring(0, index); 210 | name = name.substring(index + 1, name.length); 211 | } 212 | return [prefix, name]; 213 | } 214 | 215 | /** 216 | * Makes a name map, normalizing the name, and using a plugin 217 | * for normalization if necessary. Grabs a ref to plugin 218 | * too, as an optimization. 219 | */ 220 | makeMap = function (name, relName) { 221 | var plugin, 222 | parts = splitPrefix(name), 223 | prefix = parts[0]; 224 | 225 | name = parts[1]; 226 | 227 | if (prefix) { 228 | prefix = normalize(prefix, relName); 229 | plugin = callDep(prefix); 230 | } 231 | 232 | //Normalize according 233 | if (prefix) { 234 | if (plugin && plugin.normalize) { 235 | name = plugin.normalize(name, makeNormalize(relName)); 236 | } else { 237 | name = normalize(name, relName); 238 | } 239 | } else { 240 | name = normalize(name, relName); 241 | parts = splitPrefix(name); 242 | prefix = parts[0]; 243 | name = parts[1]; 244 | if (prefix) { 245 | plugin = callDep(prefix); 246 | } 247 | } 248 | 249 | //Using ridiculous property names for space reasons 250 | return { 251 | f: prefix ? prefix + '!' + name : name, //fullName 252 | n: name, 253 | pr: prefix, 254 | p: plugin 255 | }; 256 | }; 257 | 258 | function makeConfig(name) { 259 | return function () { 260 | return (config && config.config && config.config[name]) || {}; 261 | }; 262 | } 263 | 264 | handlers = { 265 | require: function (name) { 266 | return makeRequire(name); 267 | }, 268 | exports: function (name) { 269 | var e = defined[name]; 270 | if (typeof e !== 'undefined') { 271 | return e; 272 | } else { 273 | return (defined[name] = {}); 274 | } 275 | }, 276 | module: function (name) { 277 | return { 278 | id: name, 279 | uri: '', 280 | exports: defined[name], 281 | config: makeConfig(name) 282 | }; 283 | } 284 | }; 285 | 286 | main = function (name, deps, callback, relName) { 287 | var cjsModule, depName, ret, map, i, 288 | args = [], 289 | callbackType = typeof callback, 290 | usingExports; 291 | 292 | //Use name if no relName 293 | relName = relName || name; 294 | 295 | //Call the callback to define the module, if necessary. 296 | if (callbackType === 'undefined' || callbackType === 'function') { 297 | //Pull out the defined dependencies and pass the ordered 298 | //values to the callback. 299 | //Default to [require, exports, module] if no deps 300 | deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps; 301 | for (i = 0; i < deps.length; i += 1) { 302 | map = makeMap(deps[i], relName); 303 | depName = map.f; 304 | 305 | //Fast path CommonJS standard dependencies. 306 | if (depName === "require") { 307 | args[i] = handlers.require(name); 308 | } else if (depName === "exports") { 309 | //CommonJS module spec 1.1 310 | args[i] = handlers.exports(name); 311 | usingExports = true; 312 | } else if (depName === "module") { 313 | //CommonJS module spec 1.1 314 | cjsModule = args[i] = handlers.module(name); 315 | } else if (hasProp(defined, depName) || 316 | hasProp(waiting, depName) || 317 | hasProp(defining, depName)) { 318 | args[i] = callDep(depName); 319 | } else if (map.p) { 320 | map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {}); 321 | args[i] = defined[depName]; 322 | } else { 323 | throw new Error(name + ' missing ' + depName); 324 | } 325 | } 326 | 327 | ret = callback ? callback.apply(defined[name], args) : undefined; 328 | 329 | if (name) { 330 | //If setting exports via "module" is in play, 331 | //favor that over return value and exports. After that, 332 | //favor a non-undefined return value over exports use. 333 | if (cjsModule && cjsModule.exports !== undef && 334 | cjsModule.exports !== defined[name]) { 335 | defined[name] = cjsModule.exports; 336 | } else if (ret !== undef || !usingExports) { 337 | //Use the return value from the function. 338 | defined[name] = ret; 339 | } 340 | } 341 | } else if (name) { 342 | //May just be an object definition for the module. Only 343 | //worry about defining if have a module name. 344 | defined[name] = callback; 345 | } 346 | }; 347 | 348 | requirejs = require = req = function (deps, callback, relName, forceSync, alt) { 349 | if (typeof deps === "string") { 350 | if (handlers[deps]) { 351 | //callback in this case is really relName 352 | return handlers[deps](callback); 353 | } 354 | //Just return the module wanted. In this scenario, the 355 | //deps arg is the module name, and second arg (if passed) 356 | //is just the relName. 357 | //Normalize module name, if it contains . or .. 358 | return callDep(makeMap(deps, callback).f); 359 | } else if (!deps.splice) { 360 | //deps is a config object, not an array. 361 | config = deps; 362 | if (config.deps) { 363 | req(config.deps, config.callback); 364 | } 365 | if (!callback) { 366 | return; 367 | } 368 | 369 | if (callback.splice) { 370 | //callback is an array, which means it is a dependency list. 371 | //Adjust args if there are dependencies 372 | deps = callback; 373 | callback = relName; 374 | relName = null; 375 | } else { 376 | deps = undef; 377 | } 378 | } 379 | 380 | //Support require(['a']) 381 | callback = callback || function () {}; 382 | 383 | //If relName is a function, it is an errback handler, 384 | //so remove it. 385 | if (typeof relName === 'function') { 386 | relName = forceSync; 387 | forceSync = alt; 388 | } 389 | 390 | //Simulate async callback; 391 | if (forceSync) { 392 | main(undef, deps, callback, relName); 393 | } else { 394 | //Using a non-zero value because of concern for what old browsers 395 | //do, and latest browsers "upgrade" to 4 if lower value is used: 396 | //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout: 397 | //If want a value immediately, use require('id') instead -- something 398 | //that works in almond on the global level, but not guaranteed and 399 | //unlikely to work in other AMD implementations. 400 | setTimeout(function () { 401 | main(undef, deps, callback, relName); 402 | }, 4); 403 | } 404 | 405 | return req; 406 | }; 407 | 408 | /** 409 | * Just drops the config on the floor, but returns req in case 410 | * the config return value is used. 411 | */ 412 | req.config = function (cfg) { 413 | return req(cfg); 414 | }; 415 | 416 | /** 417 | * Expose module registry for debugging and tooling 418 | */ 419 | requirejs._defined = defined; 420 | 421 | define = function (name, deps, callback) { 422 | 423 | //This module may not have dependencies 424 | if (!deps.splice) { 425 | //deps is not an array, so probably means 426 | //an object literal or factory function for 427 | //the value. Adjust args. 428 | callback = deps; 429 | deps = []; 430 | } 431 | 432 | if (!hasProp(defined, name) && !hasProp(waiting, name)) { 433 | waiting[name] = [name, deps, callback]; 434 | } 435 | }; 436 | 437 | define.amd = { 438 | jQuery: true 439 | }; 440 | }()); 441 | 442 | define("requireLib", function(){}); 443 | 444 | /*jslint indent:4, white:true, nomen:true, plusplus:true */ 445 | /*global define:false, require:false, exports:false, module:false, signals:false */ 446 | 447 | /** @license 448 | * JS Signals 449 | * Released under the MIT license 450 | * Author: Miller Medeiros 451 | * Version: 0.8.1 - Build: 266 (2012/07/31 03:33 PM) 452 | */ 453 | 454 | (function(global){ 455 | 456 | // SignalBinding ------------------------------------------------- 457 | //================================================================ 458 | 459 | /** 460 | * Object that represents a binding between a Signal and a listener function. 461 | *
- This is an internal constructor and shouldn't be called by regular users. 462 | *
- inspired by Joa Ebert AS3 SignalBinding and Robert Penner's Slot classes. 463 | * @author Miller Medeiros 464 | * @constructor 465 | * @internal 466 | * @name SignalBinding 467 | * @param {Signal} signal Reference to Signal object that listener is currently bound to. 468 | * @param {Function} listener Handler function bound to the signal. 469 | * @param {boolean} isOnce If binding should be executed just once. 470 | * @param {Object} [listenerContext] Context on which listener will be executed (object that should represent the `this` variable inside listener function). 471 | * @param {Number} [priority] The priority level of the event listener. (default = 0). 472 | */ 473 | function SignalBinding(signal, listener, isOnce, listenerContext, priority) { 474 | 475 | /** 476 | * Handler function bound to the signal. 477 | * @type Function 478 | * @private 479 | */ 480 | this._listener = listener; 481 | 482 | /** 483 | * If binding should be executed just once. 484 | * @type boolean 485 | * @private 486 | */ 487 | this._isOnce = isOnce; 488 | 489 | /** 490 | * Context on which listener will be executed (object that should represent the `this` variable inside listener function). 491 | * @memberOf SignalBinding.prototype 492 | * @name context 493 | * @type Object|undefined|null 494 | */ 495 | this.context = listenerContext; 496 | 497 | /** 498 | * Reference to Signal object that listener is currently bound to. 499 | * @type Signal 500 | * @private 501 | */ 502 | this._signal = signal; 503 | 504 | /** 505 | * Listener priority 506 | * @type Number 507 | * @private 508 | */ 509 | this._priority = priority || 0; 510 | } 511 | 512 | SignalBinding.prototype = { 513 | 514 | /** 515 | * If binding is active and should be executed. 516 | * @type boolean 517 | */ 518 | active : true, 519 | 520 | /** 521 | * Default parameters passed to listener during `Signal.dispatch` and `SignalBinding.execute`. (curried parameters) 522 | * @type Array|null 523 | */ 524 | params : null, 525 | 526 | /** 527 | * Call listener passing arbitrary parameters. 528 | *

If binding was added using `Signal.addOnce()` it will be automatically removed from signal dispatch queue, this method is used internally for the signal dispatch.

529 | * @param {Array} [paramsArr] Array of parameters that should be passed to the listener 530 | * @return {*} Value returned by the listener. 531 | */ 532 | execute : function (paramsArr) { 533 | var handlerReturn, params; 534 | if (this.active && !!this._listener) { 535 | params = this.params? this.params.concat(paramsArr) : paramsArr; 536 | handlerReturn = this._listener.apply(this.context, params); 537 | if (this._isOnce) { 538 | this.detach(); 539 | } 540 | } 541 | return handlerReturn; 542 | }, 543 | 544 | /** 545 | * Detach binding from signal. 546 | * - alias to: mySignal.remove(myBinding.getListener()); 547 | * @return {Function|null} Handler function bound to the signal or `null` if binding was previously detached. 548 | */ 549 | detach : function () { 550 | return this.isBound()? this._signal.remove(this._listener, this.context) : null; 551 | }, 552 | 553 | /** 554 | * @return {Boolean} `true` if binding is still bound to the signal and have a listener. 555 | */ 556 | isBound : function () { 557 | return (!!this._signal && !!this._listener); 558 | }, 559 | 560 | /** 561 | * @return {Function} Handler function bound to the signal. 562 | */ 563 | getListener : function () { 564 | return this._listener; 565 | }, 566 | 567 | /** 568 | * Delete instance properties 569 | * @private 570 | */ 571 | _destroy : function () { 572 | delete this._signal; 573 | delete this._listener; 574 | delete this.context; 575 | }, 576 | 577 | /** 578 | * @return {boolean} If SignalBinding will only be executed once. 579 | */ 580 | isOnce : function () { 581 | return this._isOnce; 582 | }, 583 | 584 | /** 585 | * @return {string} String representation of the object. 586 | */ 587 | toString : function () { 588 | return '[SignalBinding isOnce:' + this._isOnce +', isBound:'+ this.isBound() +', active:' + this.active + ']'; 589 | } 590 | 591 | }; 592 | 593 | 594 | /*global SignalBinding:false*/ 595 | 596 | // Signal -------------------------------------------------------- 597 | //================================================================ 598 | 599 | function validateListener(listener, fnName) { 600 | if (typeof listener !== 'function') { 601 | throw new Error( 'listener is a required param of {fn}() and should be a Function.'.replace('{fn}', fnName) ); 602 | } 603 | } 604 | 605 | /** 606 | * Custom event broadcaster 607 | *
- inspired by Robert Penner's AS3 Signals. 608 | * @name Signal 609 | * @author Miller Medeiros 610 | * @constructor 611 | */ 612 | function Signal() { 613 | /** 614 | * @type Array. 615 | * @private 616 | */ 617 | this._bindings = []; 618 | this._prevParams = null; 619 | } 620 | 621 | Signal.prototype = { 622 | 623 | /** 624 | * Signals Version Number 625 | * @type String 626 | * @const 627 | */ 628 | VERSION : '0.8.1', 629 | 630 | /** 631 | * If Signal should keep record of previously dispatched parameters and 632 | * automatically execute listener during `add()`/`addOnce()` if Signal was 633 | * already dispatched before. 634 | * @type boolean 635 | */ 636 | memorize : false, 637 | 638 | /** 639 | * @type boolean 640 | * @private 641 | */ 642 | _shouldPropagate : true, 643 | 644 | /** 645 | * If Signal is active and should broadcast events. 646 | *

IMPORTANT: Setting this property during a dispatch will only affect the next dispatch, if you want to stop the propagation of a signal use `halt()` instead.

647 | * @type boolean 648 | */ 649 | active : true, 650 | 651 | /** 652 | * @param {Function} listener 653 | * @param {boolean} isOnce 654 | * @param {Object} [listenerContext] 655 | * @param {Number} [priority] 656 | * @return {SignalBinding} 657 | * @private 658 | */ 659 | _registerListener : function (listener, isOnce, listenerContext, priority) { 660 | 661 | var prevIndex = this._indexOfListener(listener, listenerContext), 662 | binding; 663 | 664 | if (prevIndex !== -1) { 665 | binding = this._bindings[prevIndex]; 666 | if (binding.isOnce() !== isOnce) { 667 | throw new Error('You cannot add'+ (isOnce? '' : 'Once') +'() then add'+ (!isOnce? '' : 'Once') +'() the same listener without removing the relationship first.'); 668 | } 669 | } else { 670 | binding = new SignalBinding(this, listener, isOnce, listenerContext, priority); 671 | this._addBinding(binding); 672 | } 673 | 674 | if(this.memorize && this._prevParams){ 675 | binding.execute(this._prevParams); 676 | } 677 | 678 | return binding; 679 | }, 680 | 681 | /** 682 | * @param {SignalBinding} binding 683 | * @private 684 | */ 685 | _addBinding : function (binding) { 686 | //simplified insertion sort 687 | var n = this._bindings.length; 688 | do { --n; } while (this._bindings[n] && binding._priority <= this._bindings[n]._priority); 689 | this._bindings.splice(n + 1, 0, binding); 690 | }, 691 | 692 | /** 693 | * @param {Function} listener 694 | * @return {number} 695 | * @private 696 | */ 697 | _indexOfListener : function (listener, context) { 698 | var n = this._bindings.length, 699 | cur; 700 | while (n--) { 701 | cur = this._bindings[n]; 702 | if (cur._listener === listener && cur.context === context) { 703 | return n; 704 | } 705 | } 706 | return -1; 707 | }, 708 | 709 | /** 710 | * Check if listener was attached to Signal. 711 | * @param {Function} listener 712 | * @param {Object} [context] 713 | * @return {boolean} if Signal has the specified listener. 714 | */ 715 | has : function (listener, context) { 716 | return this._indexOfListener(listener, context) !== -1; 717 | }, 718 | 719 | /** 720 | * Add a listener to the signal. 721 | * @param {Function} listener Signal handler function. 722 | * @param {Object} [listenerContext] Context on which listener will be executed (object that should represent the `this` variable inside listener function). 723 | * @param {Number} [priority] The priority level of the event listener. Listeners with higher priority will be executed before listeners with lower priority. Listeners with same priority level will be executed at the same order as they were added. (default = 0) 724 | * @return {SignalBinding} An Object representing the binding between the Signal and listener. 725 | */ 726 | add : function (listener, listenerContext, priority) { 727 | validateListener(listener, 'add'); 728 | return this._registerListener(listener, false, listenerContext, priority); 729 | }, 730 | 731 | /** 732 | * Add listener to the signal that should be removed after first execution (will be executed only once). 733 | * @param {Function} listener Signal handler function. 734 | * @param {Object} [listenerContext] Context on which listener will be executed (object that should represent the `this` variable inside listener function). 735 | * @param {Number} [priority] The priority level of the event listener. Listeners with higher priority will be executed before listeners with lower priority. Listeners with same priority level will be executed at the same order as they were added. (default = 0) 736 | * @return {SignalBinding} An Object representing the binding between the Signal and listener. 737 | */ 738 | addOnce : function (listener, listenerContext, priority) { 739 | validateListener(listener, 'addOnce'); 740 | return this._registerListener(listener, true, listenerContext, priority); 741 | }, 742 | 743 | /** 744 | * Remove a single listener from the dispatch queue. 745 | * @param {Function} listener Handler function that should be removed. 746 | * @param {Object} [context] Execution context (since you can add the same handler multiple times if executing in a different context). 747 | * @return {Function} Listener handler function. 748 | */ 749 | remove : function (listener, context) { 750 | validateListener(listener, 'remove'); 751 | 752 | var i = this._indexOfListener(listener, context); 753 | if (i !== -1) { 754 | this._bindings[i]._destroy(); //no reason to a SignalBinding exist if it isn't attached to a signal 755 | this._bindings.splice(i, 1); 756 | } 757 | return listener; 758 | }, 759 | 760 | /** 761 | * Remove all listeners from the Signal. 762 | */ 763 | removeAll : function () { 764 | var n = this._bindings.length; 765 | while (n--) { 766 | this._bindings[n]._destroy(); 767 | } 768 | this._bindings.length = 0; 769 | }, 770 | 771 | /** 772 | * @return {number} Number of listeners attached to the Signal. 773 | */ 774 | getNumListeners : function () { 775 | return this._bindings.length; 776 | }, 777 | 778 | /** 779 | * Stop propagation of the event, blocking the dispatch to next listeners on the queue. 780 | *

IMPORTANT: should be called only during signal dispatch, calling it before/after dispatch won't affect signal broadcast.

781 | * @see Signal.prototype.disable 782 | */ 783 | halt : function () { 784 | this._shouldPropagate = false; 785 | }, 786 | 787 | /** 788 | * Dispatch/Broadcast Signal to all listeners added to the queue. 789 | * @param {...*} [params] Parameters that should be passed to each handler. 790 | */ 791 | dispatch : function (params) { 792 | if (! this.active) { 793 | return; 794 | } 795 | 796 | var paramsArr = Array.prototype.slice.call(arguments), 797 | n = this._bindings.length, 798 | bindings; 799 | 800 | if (this.memorize) { 801 | this._prevParams = paramsArr; 802 | } 803 | 804 | if (! n) { 805 | //should come after memorize 806 | return; 807 | } 808 | 809 | bindings = this._bindings.slice(); //clone array in case add/remove items during dispatch 810 | this._shouldPropagate = true; //in case `halt` was called before dispatch or during the previous dispatch. 811 | 812 | //execute all callbacks until end of the list or until a callback returns `false` or stops propagation 813 | //reverse loop since listeners with higher priority will be added at the end of the list 814 | do { n--; } while (bindings[n] && this._shouldPropagate && bindings[n].execute(paramsArr) !== false); 815 | }, 816 | 817 | /** 818 | * Forget memorized arguments. 819 | * @see Signal.memorize 820 | */ 821 | forget : function(){ 822 | this._prevParams = null; 823 | }, 824 | 825 | /** 826 | * Remove all bindings from signal and destroy any reference to external objects (destroy Signal object). 827 | *

IMPORTANT: calling any method on the signal instance after calling dispose will throw errors.

828 | */ 829 | dispose : function () { 830 | this.removeAll(); 831 | delete this._bindings; 832 | delete this._prevParams; 833 | }, 834 | 835 | /** 836 | * @return {string} String representation of the object. 837 | */ 838 | toString : function () { 839 | return '[Signal active:'+ this.active +' numListeners:'+ this.getNumListeners() +']'; 840 | } 841 | 842 | }; 843 | 844 | 845 | // Namespace ----------------------------------------------------- 846 | //================================================================ 847 | 848 | /** 849 | * Signals namespace 850 | * @namespace 851 | * @name signals 852 | */ 853 | var signals = Signal; 854 | 855 | /** 856 | * Custom event broadcaster 857 | * @see Signal 858 | */ 859 | // alias for backwards compatibility (see #gh-44) 860 | signals.Signal = Signal; 861 | 862 | 863 | 864 | //exports to multiple environments 865 | if(typeof define === 'function' && define.amd){ //AMD 866 | define('signals',[],function () { return signals; }); 867 | } else if (typeof module !== 'undefined' && module.exports){ //node 868 | module.exports = signals; 869 | } else { //browser 870 | //use string because of Google closure compiler ADVANCED_MODE 871 | /*jslint sub:true */ 872 | global['signals'] = signals; 873 | } 874 | 875 | }(this)); 876 | 877 | /** @license 878 | * crossroads 879 | * Author: Miller Medeiros | MIT License 880 | * v0.12.0 (2013/01/21 13:47) 881 | */ 882 | 883 | (function () { 884 | var factory = function (signals) { 885 | 886 | var crossroads, 887 | _hasOptionalGroupBug, 888 | UNDEF; 889 | 890 | // Helpers ----------- 891 | //==================== 892 | 893 | // IE 7-8 capture optional groups as empty strings while other browsers 894 | // capture as `undefined` 895 | _hasOptionalGroupBug = (/t(.+)?/).exec('t')[1] === ''; 896 | 897 | function arrayIndexOf(arr, val) { 898 | if (arr.indexOf) { 899 | return arr.indexOf(val); 900 | } else { 901 | //Array.indexOf doesn't work on IE 6-7 902 | var n = arr.length; 903 | while (n--) { 904 | if (arr[n] === val) { 905 | return n; 906 | } 907 | } 908 | return -1; 909 | } 910 | } 911 | 912 | function arrayRemove(arr, item) { 913 | var i = arrayIndexOf(arr, item); 914 | if (i !== -1) { 915 | arr.splice(i, 1); 916 | } 917 | } 918 | 919 | function isKind(val, kind) { 920 | return '[object '+ kind +']' === Object.prototype.toString.call(val); 921 | } 922 | 923 | function isRegExp(val) { 924 | return isKind(val, 'RegExp'); 925 | } 926 | 927 | function isArray(val) { 928 | return isKind(val, 'Array'); 929 | } 930 | 931 | function isFunction(val) { 932 | return typeof val === 'function'; 933 | } 934 | 935 | //borrowed from AMD-utils 936 | function typecastValue(val) { 937 | var r; 938 | if (val === null || val === 'null') { 939 | r = null; 940 | } else if (val === 'true') { 941 | r = true; 942 | } else if (val === 'false') { 943 | r = false; 944 | } else if (val === UNDEF || val === 'undefined') { 945 | r = UNDEF; 946 | } else if (val === '' || isNaN(val)) { 947 | //isNaN('') returns false 948 | r = val; 949 | } else { 950 | //parseFloat(null || '') returns NaN 951 | r = parseFloat(val); 952 | } 953 | return r; 954 | } 955 | 956 | function typecastArrayValues(values) { 957 | var n = values.length, 958 | result = []; 959 | while (n--) { 960 | result[n] = typecastValue(values[n]); 961 | } 962 | return result; 963 | } 964 | 965 | //borrowed from AMD-Utils 966 | function decodeQueryString(str, shouldTypecast) { 967 | var queryArr = (str || '').replace('?', '').split('&'), 968 | n = queryArr.length, 969 | obj = {}, 970 | item, val; 971 | while (n--) { 972 | item = queryArr[n].split('='); 973 | val = shouldTypecast ? typecastValue(item[1]) : item[1]; 974 | obj[item[0]] = (typeof val === 'string')? decodeURIComponent(val) : val; 975 | } 976 | return obj; 977 | } 978 | 979 | 980 | // Crossroads -------- 981 | //==================== 982 | 983 | /** 984 | * @constructor 985 | */ 986 | function Crossroads() { 987 | this.bypassed = new signals.Signal(); 988 | this.routed = new signals.Signal(); 989 | this._routes = []; 990 | this._prevRoutes = []; 991 | this._piped = []; 992 | this.resetState(); 993 | } 994 | 995 | Crossroads.prototype = { 996 | 997 | greedy : false, 998 | 999 | greedyEnabled : true, 1000 | 1001 | ignoreCase : true, 1002 | 1003 | ignoreState : false, 1004 | 1005 | shouldTypecast : false, 1006 | 1007 | normalizeFn : null, 1008 | 1009 | resetState : function(){ 1010 | this._prevRoutes.length = 0; 1011 | this._prevMatchedRequest = null; 1012 | this._prevBypassedRequest = null; 1013 | }, 1014 | 1015 | create : function () { 1016 | return new Crossroads(); 1017 | }, 1018 | 1019 | addRoute : function (pattern, callback, priority) { 1020 | var route = new Route(pattern, callback, priority, this); 1021 | this._sortedInsert(route); 1022 | return route; 1023 | }, 1024 | 1025 | removeRoute : function (route) { 1026 | arrayRemove(this._routes, route); 1027 | route._destroy(); 1028 | }, 1029 | 1030 | removeAllRoutes : function () { 1031 | var n = this.getNumRoutes(); 1032 | while (n--) { 1033 | this._routes[n]._destroy(); 1034 | } 1035 | this._routes.length = 0; 1036 | }, 1037 | 1038 | parse : function (request, defaultArgs) { 1039 | request = request || ''; 1040 | defaultArgs = defaultArgs || []; 1041 | 1042 | // should only care about different requests if ignoreState isn't true 1043 | if ( !this.ignoreState && 1044 | (request === this._prevMatchedRequest || 1045 | request === this._prevBypassedRequest) ) { 1046 | return; 1047 | } 1048 | 1049 | var routes = this._getMatchedRoutes(request), 1050 | i = 0, 1051 | n = routes.length, 1052 | cur; 1053 | 1054 | if (n) { 1055 | this._prevMatchedRequest = request; 1056 | 1057 | this._notifyPrevRoutes(routes, request); 1058 | this._prevRoutes = routes; 1059 | //should be incremental loop, execute routes in order 1060 | while (i < n) { 1061 | cur = routes[i]; 1062 | cur.route.matched.dispatch.apply(cur.route.matched, defaultArgs.concat(cur.params)); 1063 | cur.isFirst = !i; 1064 | this.routed.dispatch.apply(this.routed, defaultArgs.concat([request, cur])); 1065 | i += 1; 1066 | } 1067 | } else { 1068 | this._prevBypassedRequest = request; 1069 | this.bypassed.dispatch.apply(this.bypassed, defaultArgs.concat([request])); 1070 | } 1071 | 1072 | this._pipeParse(request, defaultArgs); 1073 | }, 1074 | 1075 | _notifyPrevRoutes : function(matchedRoutes, request) { 1076 | var i = 0, prev; 1077 | while (prev = this._prevRoutes[i++]) { 1078 | //check if switched exist since route may be disposed 1079 | if(prev.route.switched && this._didSwitch(prev.route, matchedRoutes)) { 1080 | prev.route.switched.dispatch(request); 1081 | } 1082 | } 1083 | }, 1084 | 1085 | _didSwitch : function (route, matchedRoutes){ 1086 | var matched, 1087 | i = 0; 1088 | while (matched = matchedRoutes[i++]) { 1089 | // only dispatch switched if it is going to a different route 1090 | if (matched.route === route) { 1091 | return false; 1092 | } 1093 | } 1094 | return true; 1095 | }, 1096 | 1097 | _pipeParse : function(request, defaultArgs) { 1098 | var i = 0, route; 1099 | while (route = this._piped[i++]) { 1100 | route.parse(request, defaultArgs); 1101 | } 1102 | }, 1103 | 1104 | getNumRoutes : function () { 1105 | return this._routes.length; 1106 | }, 1107 | 1108 | _sortedInsert : function (route) { 1109 | //simplified insertion sort 1110 | var routes = this._routes, 1111 | n = routes.length; 1112 | do { --n; } while (routes[n] && route._priority <= routes[n]._priority); 1113 | routes.splice(n+1, 0, route); 1114 | }, 1115 | 1116 | _getMatchedRoutes : function (request) { 1117 | var res = [], 1118 | routes = this._routes, 1119 | n = routes.length, 1120 | route; 1121 | //should be decrement loop since higher priorities are added at the end of array 1122 | while (route = routes[--n]) { 1123 | if ((!res.length || this.greedy || route.greedy) && route.match(request)) { 1124 | res.push({ 1125 | route : route, 1126 | params : route._getParamsArray(request) 1127 | }); 1128 | } 1129 | if (!this.greedyEnabled && res.length) { 1130 | break; 1131 | } 1132 | } 1133 | return res; 1134 | }, 1135 | 1136 | pipe : function (otherRouter) { 1137 | this._piped.push(otherRouter); 1138 | }, 1139 | 1140 | unpipe : function (otherRouter) { 1141 | arrayRemove(this._piped, otherRouter); 1142 | }, 1143 | 1144 | toString : function () { 1145 | return '[crossroads numRoutes:'+ this.getNumRoutes() +']'; 1146 | } 1147 | }; 1148 | 1149 | //"static" instance 1150 | crossroads = new Crossroads(); 1151 | crossroads.VERSION = '0.12.0'; 1152 | 1153 | crossroads.NORM_AS_ARRAY = function (req, vals) { 1154 | return [vals.vals_]; 1155 | }; 1156 | 1157 | crossroads.NORM_AS_OBJECT = function (req, vals) { 1158 | return [vals]; 1159 | }; 1160 | 1161 | 1162 | // Route -------------- 1163 | //===================== 1164 | 1165 | /** 1166 | * @constructor 1167 | */ 1168 | function Route(pattern, callback, priority, router) { 1169 | var isRegexPattern = isRegExp(pattern), 1170 | patternLexer = router.patternLexer; 1171 | this._router = router; 1172 | this._pattern = pattern; 1173 | this._paramsIds = isRegexPattern? null : patternLexer.getParamIds(pattern); 1174 | this._optionalParamsIds = isRegexPattern? null : patternLexer.getOptionalParamsIds(pattern); 1175 | this._matchRegexp = isRegexPattern? pattern : patternLexer.compilePattern(pattern, router.ignoreCase); 1176 | this.matched = new signals.Signal(); 1177 | this.switched = new signals.Signal(); 1178 | if (callback) { 1179 | this.matched.add(callback); 1180 | } 1181 | this._priority = priority || 0; 1182 | } 1183 | 1184 | Route.prototype = { 1185 | 1186 | greedy : false, 1187 | 1188 | rules : void(0), 1189 | 1190 | match : function (request) { 1191 | request = request || ''; 1192 | return this._matchRegexp.test(request) && this._validateParams(request); //validate params even if regexp because of `request_` rule. 1193 | }, 1194 | 1195 | _validateParams : function (request) { 1196 | var rules = this.rules, 1197 | values = this._getParamsObject(request), 1198 | key; 1199 | for (key in rules) { 1200 | // normalize_ isn't a validation rule... (#39) 1201 | if(key !== 'normalize_' && rules.hasOwnProperty(key) && ! this._isValidParam(request, key, values)){ 1202 | return false; 1203 | } 1204 | } 1205 | return true; 1206 | }, 1207 | 1208 | _isValidParam : function (request, prop, values) { 1209 | var validationRule = this.rules[prop], 1210 | val = values[prop], 1211 | isValid = false, 1212 | isQuery = (prop.indexOf('?') === 0); 1213 | 1214 | if (val == null && this._optionalParamsIds && arrayIndexOf(this._optionalParamsIds, prop) !== -1) { 1215 | isValid = true; 1216 | } 1217 | else if (isRegExp(validationRule)) { 1218 | if (isQuery) { 1219 | val = values[prop +'_']; //use raw string 1220 | } 1221 | isValid = validationRule.test(val); 1222 | } 1223 | else if (isArray(validationRule)) { 1224 | if (isQuery) { 1225 | val = values[prop +'_']; //use raw string 1226 | } 1227 | isValid = this._isValidArrayRule(validationRule, val); 1228 | } 1229 | else if (isFunction(validationRule)) { 1230 | isValid = validationRule(val, request, values); 1231 | } 1232 | 1233 | return isValid; //fail silently if validationRule is from an unsupported type 1234 | }, 1235 | 1236 | _isValidArrayRule : function (arr, val) { 1237 | if (! this._router.ignoreCase) { 1238 | return arrayIndexOf(arr, val) !== -1; 1239 | } 1240 | 1241 | if (typeof val === 'string') { 1242 | val = val.toLowerCase(); 1243 | } 1244 | 1245 | var n = arr.length, 1246 | item, 1247 | compareVal; 1248 | 1249 | while (n--) { 1250 | item = arr[n]; 1251 | compareVal = (typeof item === 'string')? item.toLowerCase() : item; 1252 | if (compareVal === val) { 1253 | return true; 1254 | } 1255 | } 1256 | return false; 1257 | }, 1258 | 1259 | _getParamsObject : function (request) { 1260 | var shouldTypecast = this._router.shouldTypecast, 1261 | values = this._router.patternLexer.getParamValues(request, this._matchRegexp, shouldTypecast), 1262 | o = {}, 1263 | n = values.length, 1264 | param, val; 1265 | while (n--) { 1266 | val = values[n]; 1267 | if (this._paramsIds) { 1268 | param = this._paramsIds[n]; 1269 | if (param.indexOf('?') === 0 && val) { 1270 | //make a copy of the original string so array and 1271 | //RegExp validation can be applied properly 1272 | o[param +'_'] = val; 1273 | //update vals_ array as well since it will be used 1274 | //during dispatch 1275 | val = decodeQueryString(val, shouldTypecast); 1276 | values[n] = val; 1277 | } 1278 | // IE will capture optional groups as empty strings while other 1279 | // browsers will capture `undefined` so normalize behavior. 1280 | // see: #gh-58, #gh-59, #gh-60 1281 | if ( _hasOptionalGroupBug && val === '' && arrayIndexOf(this._optionalParamsIds, param) !== -1 ) { 1282 | val = void(0); 1283 | values[n] = val; 1284 | } 1285 | o[param] = val; 1286 | } 1287 | //alias to paths and for RegExp pattern 1288 | o[n] = val; 1289 | } 1290 | o.request_ = shouldTypecast? typecastValue(request) : request; 1291 | o.vals_ = values; 1292 | return o; 1293 | }, 1294 | 1295 | _getParamsArray : function (request) { 1296 | var norm = this.rules? this.rules.normalize_ : null, 1297 | params; 1298 | norm = norm || this._router.normalizeFn; // default normalize 1299 | if (norm && isFunction(norm)) { 1300 | params = norm(request, this._getParamsObject(request)); 1301 | } else { 1302 | params = this._getParamsObject(request).vals_; 1303 | } 1304 | return params; 1305 | }, 1306 | 1307 | interpolate : function(replacements) { 1308 | var str = this._router.patternLexer.interpolate(this._pattern, replacements); 1309 | if (! this._validateParams(str) ) { 1310 | throw new Error('Generated string doesn\'t validate against `Route.rules`.'); 1311 | } 1312 | return str; 1313 | }, 1314 | 1315 | dispose : function () { 1316 | this._router.removeRoute(this); 1317 | }, 1318 | 1319 | _destroy : function () { 1320 | this.matched.dispose(); 1321 | this.switched.dispose(); 1322 | this.matched = this.switched = this._pattern = this._matchRegexp = null; 1323 | }, 1324 | 1325 | toString : function () { 1326 | return '[Route pattern:"'+ this._pattern +'", numListeners:'+ this.matched.getNumListeners() +']'; 1327 | } 1328 | 1329 | }; 1330 | 1331 | 1332 | 1333 | // Pattern Lexer ------ 1334 | //===================== 1335 | 1336 | Crossroads.prototype.patternLexer = (function () { 1337 | 1338 | var 1339 | //match chars that should be escaped on string regexp 1340 | ESCAPE_CHARS_REGEXP = /[\\.+*?\^$\[\](){}\/'#]/g, 1341 | 1342 | //trailing slashes (begin/end of string) 1343 | LOOSE_SLASHES_REGEXP = /^\/|\/$/g, 1344 | LEGACY_SLASHES_REGEXP = /\/$/g, 1345 | 1346 | //params - everything between `{ }` or `: :` 1347 | PARAMS_REGEXP = /(?:\{|:)([^}:]+)(?:\}|:)/g, 1348 | 1349 | //used to save params during compile (avoid escaping things that 1350 | //shouldn't be escaped). 1351 | TOKENS = { 1352 | 'OS' : { 1353 | //optional slashes 1354 | //slash between `::` or `}:` or `\w:` or `:{?` or `}{?` or `\w{?` 1355 | rgx : /([:}]|\w(?=\/))\/?(:|(?:\{\?))/g, 1356 | save : '$1{{id}}$2', 1357 | res : '\\/?' 1358 | }, 1359 | 'RS' : { 1360 | //required slashes 1361 | //used to insert slash between `:{` and `}{` 1362 | rgx : /([:}])\/?(\{)/g, 1363 | save : '$1{{id}}$2', 1364 | res : '\\/' 1365 | }, 1366 | 'RQ' : { 1367 | //required query string - everything in between `{? }` 1368 | rgx : /\{\?([^}]+)\}/g, 1369 | //everything from `?` till `#` or end of string 1370 | res : '\\?([^#]+)' 1371 | }, 1372 | 'OQ' : { 1373 | //optional query string - everything in between `:? :` 1374 | rgx : /:\?([^:]+):/g, 1375 | //everything from `?` till `#` or end of string 1376 | res : '(?:\\?([^#]*))?' 1377 | }, 1378 | 'OR' : { 1379 | //optional rest - everything in between `: *:` 1380 | rgx : /:([^:]+)\*:/g, 1381 | res : '(.*)?' // optional group to avoid passing empty string as captured 1382 | }, 1383 | 'RR' : { 1384 | //rest param - everything in between `{ *}` 1385 | rgx : /\{([^}]+)\*\}/g, 1386 | res : '(.+)' 1387 | }, 1388 | // required/optional params should come after rest segments 1389 | 'RP' : { 1390 | //required params - everything between `{ }` 1391 | rgx : /\{([^}]+)\}/g, 1392 | res : '([^\\/?]+)' 1393 | }, 1394 | 'OP' : { 1395 | //optional params - everything between `: :` 1396 | rgx : /:([^:]+):/g, 1397 | res : '([^\\/?]+)?\/?' 1398 | } 1399 | }, 1400 | 1401 | LOOSE_SLASH = 1, 1402 | STRICT_SLASH = 2, 1403 | LEGACY_SLASH = 3, 1404 | 1405 | _slashMode = LOOSE_SLASH; 1406 | 1407 | 1408 | function precompileTokens(){ 1409 | var key, cur; 1410 | for (key in TOKENS) { 1411 | if (TOKENS.hasOwnProperty(key)) { 1412 | cur = TOKENS[key]; 1413 | cur.id = '__CR_'+ key +'__'; 1414 | cur.save = ('save' in cur)? cur.save.replace('{{id}}', cur.id) : cur.id; 1415 | cur.rRestore = new RegExp(cur.id, 'g'); 1416 | } 1417 | } 1418 | } 1419 | precompileTokens(); 1420 | 1421 | 1422 | function captureVals(regex, pattern) { 1423 | var vals = [], match; 1424 | // very important to reset lastIndex since RegExp can have "g" flag 1425 | // and multiple runs might affect the result, specially if matching 1426 | // same string multiple times on IE 7-8 1427 | regex.lastIndex = 0; 1428 | while (match = regex.exec(pattern)) { 1429 | vals.push(match[1]); 1430 | } 1431 | return vals; 1432 | } 1433 | 1434 | function getParamIds(pattern) { 1435 | return captureVals(PARAMS_REGEXP, pattern); 1436 | } 1437 | 1438 | function getOptionalParamsIds(pattern) { 1439 | return captureVals(TOKENS.OP.rgx, pattern); 1440 | } 1441 | 1442 | function compilePattern(pattern, ignoreCase) { 1443 | pattern = pattern || ''; 1444 | 1445 | if(pattern){ 1446 | if (_slashMode === LOOSE_SLASH) { 1447 | pattern = pattern.replace(LOOSE_SLASHES_REGEXP, ''); 1448 | } 1449 | else if (_slashMode === LEGACY_SLASH) { 1450 | pattern = pattern.replace(LEGACY_SLASHES_REGEXP, ''); 1451 | } 1452 | 1453 | //save tokens 1454 | pattern = replaceTokens(pattern, 'rgx', 'save'); 1455 | //regexp escape 1456 | pattern = pattern.replace(ESCAPE_CHARS_REGEXP, '\\$&'); 1457 | //restore tokens 1458 | pattern = replaceTokens(pattern, 'rRestore', 'res'); 1459 | 1460 | if (_slashMode === LOOSE_SLASH) { 1461 | pattern = '\\/?'+ pattern; 1462 | } 1463 | } 1464 | 1465 | if (_slashMode !== STRICT_SLASH) { 1466 | //single slash is treated as empty and end slash is optional 1467 | pattern += '\\/?'; 1468 | } 1469 | return new RegExp('^'+ pattern + '$', ignoreCase? 'i' : ''); 1470 | } 1471 | 1472 | function replaceTokens(pattern, regexpName, replaceName) { 1473 | var cur, key; 1474 | for (key in TOKENS) { 1475 | if (TOKENS.hasOwnProperty(key)) { 1476 | cur = TOKENS[key]; 1477 | pattern = pattern.replace(cur[regexpName], cur[replaceName]); 1478 | } 1479 | } 1480 | return pattern; 1481 | } 1482 | 1483 | function getParamValues(request, regexp, shouldTypecast) { 1484 | var vals = regexp.exec(request); 1485 | if (vals) { 1486 | vals.shift(); 1487 | if (shouldTypecast) { 1488 | vals = typecastArrayValues(vals); 1489 | } 1490 | } 1491 | return vals; 1492 | } 1493 | 1494 | function interpolate(pattern, replacements) { 1495 | if (typeof pattern !== 'string') { 1496 | throw new Error('Route pattern should be a string.'); 1497 | } 1498 | 1499 | var replaceFn = function(match, prop){ 1500 | var val; 1501 | prop = (prop.substr(0, 1) === '?')? prop.substr(1) : prop; 1502 | if (replacements[prop] != null) { 1503 | if (typeof replacements[prop] === 'object') { 1504 | var queryParts = []; 1505 | for(var key in replacements[prop]) { 1506 | queryParts.push(encodeURI(key + '=' + replacements[prop][key])); 1507 | } 1508 | val = '?' + queryParts.join('&'); 1509 | } else { 1510 | // make sure value is a string see #gh-54 1511 | val = String(replacements[prop]); 1512 | } 1513 | 1514 | if (match.indexOf('*') === -1 && val.indexOf('/') !== -1) { 1515 | throw new Error('Invalid value "'+ val +'" for segment "'+ match +'".'); 1516 | } 1517 | } 1518 | else if (match.indexOf('{') !== -1) { 1519 | throw new Error('The segment '+ match +' is required.'); 1520 | } 1521 | else { 1522 | val = ''; 1523 | } 1524 | return val; 1525 | }; 1526 | 1527 | if (! TOKENS.OS.trail) { 1528 | TOKENS.OS.trail = new RegExp('(?:'+ TOKENS.OS.id +')+$'); 1529 | } 1530 | 1531 | return pattern 1532 | .replace(TOKENS.OS.rgx, TOKENS.OS.save) 1533 | .replace(PARAMS_REGEXP, replaceFn) 1534 | .replace(TOKENS.OS.trail, '') // remove trailing 1535 | .replace(TOKENS.OS.rRestore, '/'); // add slash between segments 1536 | } 1537 | 1538 | //API 1539 | return { 1540 | strict : function(){ 1541 | _slashMode = STRICT_SLASH; 1542 | }, 1543 | loose : function(){ 1544 | _slashMode = LOOSE_SLASH; 1545 | }, 1546 | legacy : function(){ 1547 | _slashMode = LEGACY_SLASH; 1548 | }, 1549 | getParamIds : getParamIds, 1550 | getOptionalParamsIds : getOptionalParamsIds, 1551 | getParamValues : getParamValues, 1552 | compilePattern : compilePattern, 1553 | interpolate : interpolate 1554 | }; 1555 | 1556 | }()); 1557 | 1558 | 1559 | return crossroads; 1560 | }; 1561 | 1562 | if (typeof define === 'function' && define.amd) { 1563 | define('crossroads',['signals'], factory); 1564 | } else if (typeof module !== 'undefined' && module.exports) { //Node 1565 | module.exports = factory(require('signals')); 1566 | } else { 1567 | /*jshint sub:true */ 1568 | window['crossroads'] = factory(window['signals']); 1569 | } 1570 | 1571 | }()); 1572 | 1573 | 1574 | define('logger',['require','exports','module','signals'],function (require, exports, module) {var Signal; 1575 | 1576 | Signal = require('signals'); 1577 | 1578 | module.exports = { 1579 | debug: new Signal(), 1580 | warn: new Signal(), 1581 | error: new Signal() 1582 | }; 1583 | 1584 | }); 1585 | 1586 | /** 1587 | * Lo-Dash 2.4.1 (Custom Build) 1588 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 1589 | * Copyright 2012-2013 The Dojo Foundation 1590 | * Based on Underscore.js 1.5.2 1591 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 1592 | * Available under MIT license 1593 | */ 1594 | define('lodash/internals/isNative',[], function() { 1595 | 1596 | /** Used for native method references */ 1597 | var objectProto = Object.prototype; 1598 | 1599 | /** Used to resolve the internal [[Class]] of values */ 1600 | var toString = objectProto.toString; 1601 | 1602 | /** Used to detect if a method is native */ 1603 | var reNative = RegExp('^' + 1604 | String(toString) 1605 | .replace(/[.*+?^${}()|[\]\\]/g, '\\$&') 1606 | .replace(/toString| for [^\]]+/g, '.*?') + '$' 1607 | ); 1608 | 1609 | /** 1610 | * Checks if `value` is a native function. 1611 | * 1612 | * @private 1613 | * @param {*} value The value to check. 1614 | * @returns {boolean} Returns `true` if the `value` is a native function, else `false`. 1615 | */ 1616 | function isNative(value) { 1617 | return typeof value == 'function' && reNative.test(value); 1618 | } 1619 | 1620 | return isNative; 1621 | }); 1622 | 1623 | /** 1624 | * Lo-Dash 2.4.1 (Custom Build) 1625 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 1626 | * Copyright 2012-2013 The Dojo Foundation 1627 | * Based on Underscore.js 1.5.2 1628 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 1629 | * Available under MIT license 1630 | */ 1631 | define('lodash/internals/objectTypes',[], function() { 1632 | 1633 | /** Used to determine if values are of the language type Object */ 1634 | var objectTypes = { 1635 | 'boolean': false, 1636 | 'function': true, 1637 | 'object': true, 1638 | 'number': false, 1639 | 'string': false, 1640 | 'undefined': false 1641 | }; 1642 | 1643 | return objectTypes; 1644 | }); 1645 | 1646 | /** 1647 | * Lo-Dash 2.4.1 (Custom Build) 1648 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 1649 | * Copyright 2012-2013 The Dojo Foundation 1650 | * Based on Underscore.js 1.5.2 1651 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 1652 | * Available under MIT license 1653 | */ 1654 | define('lodash/objects/isObject',['../internals/objectTypes'], function(objectTypes) { 1655 | 1656 | /** 1657 | * Checks if `value` is the language type of Object. 1658 | * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) 1659 | * 1660 | * @static 1661 | * @memberOf _ 1662 | * @category Objects 1663 | * @param {*} value The value to check. 1664 | * @returns {boolean} Returns `true` if the `value` is an object, else `false`. 1665 | * @example 1666 | * 1667 | * _.isObject({}); 1668 | * // => true 1669 | * 1670 | * _.isObject([1, 2, 3]); 1671 | * // => true 1672 | * 1673 | * _.isObject(1); 1674 | * // => false 1675 | */ 1676 | function isObject(value) { 1677 | // check if the value is the ECMAScript language type of Object 1678 | // http://es5.github.io/#x8 1679 | // and avoid a V8 bug 1680 | // http://code.google.com/p/v8/issues/detail?id=2291 1681 | return !!(value && objectTypes[typeof value]); 1682 | } 1683 | 1684 | return isObject; 1685 | }); 1686 | 1687 | /** 1688 | * Lo-Dash 2.4.1 (Custom Build) 1689 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 1690 | * Copyright 2012-2013 The Dojo Foundation 1691 | * Based on Underscore.js 1.5.2 1692 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 1693 | * Available under MIT license 1694 | */ 1695 | define('lodash/utilities/noop',[], function() { 1696 | 1697 | /** 1698 | * A no-operation function. 1699 | * 1700 | * @static 1701 | * @memberOf _ 1702 | * @category Utilities 1703 | * @example 1704 | * 1705 | * var object = { 'name': 'fred' }; 1706 | * _.noop(object) === undefined; 1707 | * // => true 1708 | */ 1709 | function noop() { 1710 | // no operation performed 1711 | } 1712 | 1713 | return noop; 1714 | }); 1715 | 1716 | /** 1717 | * Lo-Dash 2.4.1 (Custom Build) 1718 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 1719 | * Copyright 2012-2013 The Dojo Foundation 1720 | * Based on Underscore.js 1.5.2 1721 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 1722 | * Available under MIT license 1723 | */ 1724 | define('lodash/internals/baseCreate',['./isNative', '../objects/isObject', '../utilities/noop'], function(isNative, isObject, noop) { 1725 | 1726 | /* Native method shortcuts for methods with the same name as other `lodash` methods */ 1727 | var nativeCreate = isNative(nativeCreate = Object.create) && nativeCreate; 1728 | 1729 | /** 1730 | * The base implementation of `_.create` without support for assigning 1731 | * properties to the created object. 1732 | * 1733 | * @private 1734 | * @param {Object} prototype The object to inherit from. 1735 | * @returns {Object} Returns the new object. 1736 | */ 1737 | function baseCreate(prototype, properties) { 1738 | return isObject(prototype) ? nativeCreate(prototype) : {}; 1739 | } 1740 | // fallback for browsers without `Object.create` 1741 | if (!nativeCreate) { 1742 | baseCreate = (function() { 1743 | function Object() {} 1744 | return function(prototype) { 1745 | if (isObject(prototype)) { 1746 | Object.prototype = prototype; 1747 | var result = new Object; 1748 | Object.prototype = null; 1749 | } 1750 | return result || window.Object(); 1751 | }; 1752 | }()); 1753 | } 1754 | 1755 | return baseCreate; 1756 | }); 1757 | 1758 | /** 1759 | * Lo-Dash 2.4.1 (Custom Build) 1760 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 1761 | * Copyright 2012-2013 The Dojo Foundation 1762 | * Based on Underscore.js 1.5.2 1763 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 1764 | * Available under MIT license 1765 | */ 1766 | define('lodash/internals/setBindData',['./isNative', '../utilities/noop'], function(isNative, noop) { 1767 | 1768 | /** Used as the property descriptor for `__bindData__` */ 1769 | var descriptor = { 1770 | 'configurable': false, 1771 | 'enumerable': false, 1772 | 'value': null, 1773 | 'writable': false 1774 | }; 1775 | 1776 | /** Used to set meta data on functions */ 1777 | var defineProperty = (function() { 1778 | // IE 8 only accepts DOM elements 1779 | try { 1780 | var o = {}, 1781 | func = isNative(func = Object.defineProperty) && func, 1782 | result = func(o, o, o) && func; 1783 | } catch(e) { } 1784 | return result; 1785 | }()); 1786 | 1787 | /** 1788 | * Sets `this` binding data on a given function. 1789 | * 1790 | * @private 1791 | * @param {Function} func The function to set data on. 1792 | * @param {Array} value The data array to set. 1793 | */ 1794 | var setBindData = !defineProperty ? noop : function(func, value) { 1795 | descriptor.value = value; 1796 | defineProperty(func, '__bindData__', descriptor); 1797 | }; 1798 | 1799 | return setBindData; 1800 | }); 1801 | 1802 | /** 1803 | * Lo-Dash 2.4.1 (Custom Build) 1804 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 1805 | * Copyright 2012-2013 The Dojo Foundation 1806 | * Based on Underscore.js 1.5.2 1807 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 1808 | * Available under MIT license 1809 | */ 1810 | define('lodash/internals/slice',[], function() { 1811 | 1812 | /** 1813 | * Slices the `collection` from the `start` index up to, but not including, 1814 | * the `end` index. 1815 | * 1816 | * Note: This function is used instead of `Array#slice` to support node lists 1817 | * in IE < 9 and to ensure dense arrays are returned. 1818 | * 1819 | * @private 1820 | * @param {Array|Object|string} collection The collection to slice. 1821 | * @param {number} start The start index. 1822 | * @param {number} end The end index. 1823 | * @returns {Array} Returns the new array. 1824 | */ 1825 | function slice(array, start, end) { 1826 | start || (start = 0); 1827 | if (typeof end == 'undefined') { 1828 | end = array ? array.length : 0; 1829 | } 1830 | var index = -1, 1831 | length = end - start || 0, 1832 | result = Array(length < 0 ? 0 : length); 1833 | 1834 | while (++index < length) { 1835 | result[index] = array[start + index]; 1836 | } 1837 | return result; 1838 | } 1839 | 1840 | return slice; 1841 | }); 1842 | 1843 | /** 1844 | * Lo-Dash 2.4.1 (Custom Build) 1845 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 1846 | * Copyright 2012-2013 The Dojo Foundation 1847 | * Based on Underscore.js 1.5.2 1848 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 1849 | * Available under MIT license 1850 | */ 1851 | define('lodash/internals/baseBind',['./baseCreate', '../objects/isObject', './setBindData', './slice'], function(baseCreate, isObject, setBindData, slice) { 1852 | 1853 | /** 1854 | * Used for `Array` method references. 1855 | * 1856 | * Normally `Array.prototype` would suffice, however, using an array literal 1857 | * avoids issues in Narwhal. 1858 | */ 1859 | var arrayRef = []; 1860 | 1861 | /** Native method shortcuts */ 1862 | var push = arrayRef.push; 1863 | 1864 | /** 1865 | * The base implementation of `_.bind` that creates the bound function and 1866 | * sets its meta data. 1867 | * 1868 | * @private 1869 | * @param {Array} bindData The bind data array. 1870 | * @returns {Function} Returns the new bound function. 1871 | */ 1872 | function baseBind(bindData) { 1873 | var func = bindData[0], 1874 | partialArgs = bindData[2], 1875 | thisArg = bindData[4]; 1876 | 1877 | function bound() { 1878 | // `Function#bind` spec 1879 | // http://es5.github.io/#x15.3.4.5 1880 | if (partialArgs) { 1881 | // avoid `arguments` object deoptimizations by using `slice` instead 1882 | // of `Array.prototype.slice.call` and not assigning `arguments` to a 1883 | // variable as a ternary expression 1884 | var args = slice(partialArgs); 1885 | push.apply(args, arguments); 1886 | } 1887 | // mimic the constructor's `return` behavior 1888 | // http://es5.github.io/#x13.2.2 1889 | if (this instanceof bound) { 1890 | // ensure `new bound` is an instance of `func` 1891 | var thisBinding = baseCreate(func.prototype), 1892 | result = func.apply(thisBinding, args || arguments); 1893 | return isObject(result) ? result : thisBinding; 1894 | } 1895 | return func.apply(thisArg, args || arguments); 1896 | } 1897 | setBindData(bound, bindData); 1898 | return bound; 1899 | } 1900 | 1901 | return baseBind; 1902 | }); 1903 | 1904 | /** 1905 | * Lo-Dash 2.4.1 (Custom Build) 1906 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 1907 | * Copyright 2012-2013 The Dojo Foundation 1908 | * Based on Underscore.js 1.5.2 1909 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 1910 | * Available under MIT license 1911 | */ 1912 | define('lodash/internals/baseCreateWrapper',['./baseCreate', '../objects/isObject', './setBindData', './slice'], function(baseCreate, isObject, setBindData, slice) { 1913 | 1914 | /** 1915 | * Used for `Array` method references. 1916 | * 1917 | * Normally `Array.prototype` would suffice, however, using an array literal 1918 | * avoids issues in Narwhal. 1919 | */ 1920 | var arrayRef = []; 1921 | 1922 | /** Native method shortcuts */ 1923 | var push = arrayRef.push; 1924 | 1925 | /** 1926 | * The base implementation of `createWrapper` that creates the wrapper and 1927 | * sets its meta data. 1928 | * 1929 | * @private 1930 | * @param {Array} bindData The bind data array. 1931 | * @returns {Function} Returns the new function. 1932 | */ 1933 | function baseCreateWrapper(bindData) { 1934 | var func = bindData[0], 1935 | bitmask = bindData[1], 1936 | partialArgs = bindData[2], 1937 | partialRightArgs = bindData[3], 1938 | thisArg = bindData[4], 1939 | arity = bindData[5]; 1940 | 1941 | var isBind = bitmask & 1, 1942 | isBindKey = bitmask & 2, 1943 | isCurry = bitmask & 4, 1944 | isCurryBound = bitmask & 8, 1945 | key = func; 1946 | 1947 | function bound() { 1948 | var thisBinding = isBind ? thisArg : this; 1949 | if (partialArgs) { 1950 | var args = slice(partialArgs); 1951 | push.apply(args, arguments); 1952 | } 1953 | if (partialRightArgs || isCurry) { 1954 | args || (args = slice(arguments)); 1955 | if (partialRightArgs) { 1956 | push.apply(args, partialRightArgs); 1957 | } 1958 | if (isCurry && args.length < arity) { 1959 | bitmask |= 16 & ~32; 1960 | return baseCreateWrapper([func, (isCurryBound ? bitmask : bitmask & ~3), args, null, thisArg, arity]); 1961 | } 1962 | } 1963 | args || (args = arguments); 1964 | if (isBindKey) { 1965 | func = thisBinding[key]; 1966 | } 1967 | if (this instanceof bound) { 1968 | thisBinding = baseCreate(func.prototype); 1969 | var result = func.apply(thisBinding, args); 1970 | return isObject(result) ? result : thisBinding; 1971 | } 1972 | return func.apply(thisBinding, args); 1973 | } 1974 | setBindData(bound, bindData); 1975 | return bound; 1976 | } 1977 | 1978 | return baseCreateWrapper; 1979 | }); 1980 | 1981 | /** 1982 | * Lo-Dash 2.4.1 (Custom Build) 1983 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 1984 | * Copyright 2012-2013 The Dojo Foundation 1985 | * Based on Underscore.js 1.5.2 1986 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 1987 | * Available under MIT license 1988 | */ 1989 | define('lodash/objects/isFunction',[], function() { 1990 | 1991 | /** 1992 | * Checks if `value` is a function. 1993 | * 1994 | * @static 1995 | * @memberOf _ 1996 | * @category Objects 1997 | * @param {*} value The value to check. 1998 | * @returns {boolean} Returns `true` if the `value` is a function, else `false`. 1999 | * @example 2000 | * 2001 | * _.isFunction(_); 2002 | * // => true 2003 | */ 2004 | function isFunction(value) { 2005 | return typeof value == 'function'; 2006 | } 2007 | 2008 | return isFunction; 2009 | }); 2010 | 2011 | /** 2012 | * Lo-Dash 2.4.1 (Custom Build) 2013 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 2014 | * Copyright 2012-2013 The Dojo Foundation 2015 | * Based on Underscore.js 1.5.2 2016 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 2017 | * Available under MIT license 2018 | */ 2019 | define('lodash/internals/createWrapper',['./baseBind', './baseCreateWrapper', '../objects/isFunction', './slice'], function(baseBind, baseCreateWrapper, isFunction, slice) { 2020 | 2021 | /** 2022 | * Used for `Array` method references. 2023 | * 2024 | * Normally `Array.prototype` would suffice, however, using an array literal 2025 | * avoids issues in Narwhal. 2026 | */ 2027 | var arrayRef = []; 2028 | 2029 | /** Native method shortcuts */ 2030 | var push = arrayRef.push, 2031 | unshift = arrayRef.unshift; 2032 | 2033 | /** 2034 | * Creates a function that, when called, either curries or invokes `func` 2035 | * with an optional `this` binding and partially applied arguments. 2036 | * 2037 | * @private 2038 | * @param {Function|string} func The function or method name to reference. 2039 | * @param {number} bitmask The bitmask of method flags to compose. 2040 | * The bitmask may be composed of the following flags: 2041 | * 1 - `_.bind` 2042 | * 2 - `_.bindKey` 2043 | * 4 - `_.curry` 2044 | * 8 - `_.curry` (bound) 2045 | * 16 - `_.partial` 2046 | * 32 - `_.partialRight` 2047 | * @param {Array} [partialArgs] An array of arguments to prepend to those 2048 | * provided to the new function. 2049 | * @param {Array} [partialRightArgs] An array of arguments to append to those 2050 | * provided to the new function. 2051 | * @param {*} [thisArg] The `this` binding of `func`. 2052 | * @param {number} [arity] The arity of `func`. 2053 | * @returns {Function} Returns the new function. 2054 | */ 2055 | function createWrapper(func, bitmask, partialArgs, partialRightArgs, thisArg, arity) { 2056 | var isBind = bitmask & 1, 2057 | isBindKey = bitmask & 2, 2058 | isCurry = bitmask & 4, 2059 | isCurryBound = bitmask & 8, 2060 | isPartial = bitmask & 16, 2061 | isPartialRight = bitmask & 32; 2062 | 2063 | if (!isBindKey && !isFunction(func)) { 2064 | throw new TypeError; 2065 | } 2066 | if (isPartial && !partialArgs.length) { 2067 | bitmask &= ~16; 2068 | isPartial = partialArgs = false; 2069 | } 2070 | if (isPartialRight && !partialRightArgs.length) { 2071 | bitmask &= ~32; 2072 | isPartialRight = partialRightArgs = false; 2073 | } 2074 | var bindData = func && func.__bindData__; 2075 | if (bindData && bindData !== true) { 2076 | // clone `bindData` 2077 | bindData = slice(bindData); 2078 | if (bindData[2]) { 2079 | bindData[2] = slice(bindData[2]); 2080 | } 2081 | if (bindData[3]) { 2082 | bindData[3] = slice(bindData[3]); 2083 | } 2084 | // set `thisBinding` is not previously bound 2085 | if (isBind && !(bindData[1] & 1)) { 2086 | bindData[4] = thisArg; 2087 | } 2088 | // set if previously bound but not currently (subsequent curried functions) 2089 | if (!isBind && bindData[1] & 1) { 2090 | bitmask |= 8; 2091 | } 2092 | // set curried arity if not yet set 2093 | if (isCurry && !(bindData[1] & 4)) { 2094 | bindData[5] = arity; 2095 | } 2096 | // append partial left arguments 2097 | if (isPartial) { 2098 | push.apply(bindData[2] || (bindData[2] = []), partialArgs); 2099 | } 2100 | // append partial right arguments 2101 | if (isPartialRight) { 2102 | unshift.apply(bindData[3] || (bindData[3] = []), partialRightArgs); 2103 | } 2104 | // merge flags 2105 | bindData[1] |= bitmask; 2106 | return createWrapper.apply(null, bindData); 2107 | } 2108 | // fast path for `_.bind` 2109 | var creater = (bitmask == 1 || bitmask === 17) ? baseBind : baseCreateWrapper; 2110 | return creater([func, bitmask, partialArgs, partialRightArgs, thisArg, arity]); 2111 | } 2112 | 2113 | return createWrapper; 2114 | }); 2115 | 2116 | /** 2117 | * Lo-Dash 2.4.1 (Custom Build) 2118 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 2119 | * Copyright 2012-2013 The Dojo Foundation 2120 | * Based on Underscore.js 1.5.2 2121 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 2122 | * Available under MIT license 2123 | */ 2124 | define('lodash/functions/bind',['../internals/createWrapper', '../internals/slice'], function(createWrapper, slice) { 2125 | 2126 | /** 2127 | * Creates a function that, when called, invokes `func` with the `this` 2128 | * binding of `thisArg` and prepends any additional `bind` arguments to those 2129 | * provided to the bound function. 2130 | * 2131 | * @static 2132 | * @memberOf _ 2133 | * @category Functions 2134 | * @param {Function} func The function to bind. 2135 | * @param {*} [thisArg] The `this` binding of `func`. 2136 | * @param {...*} [arg] Arguments to be partially applied. 2137 | * @returns {Function} Returns the new bound function. 2138 | * @example 2139 | * 2140 | * var func = function(greeting) { 2141 | * return greeting + ' ' + this.name; 2142 | * }; 2143 | * 2144 | * func = _.bind(func, { 'name': 'fred' }, 'hi'); 2145 | * func(); 2146 | * // => 'hi fred' 2147 | */ 2148 | function bind(func, thisArg) { 2149 | return arguments.length > 2 2150 | ? createWrapper(func, 17, slice(arguments, 2), null, thisArg) 2151 | : createWrapper(func, 1, null, null, thisArg); 2152 | } 2153 | 2154 | return bind; 2155 | }); 2156 | 2157 | /** 2158 | * Lo-Dash 2.4.1 (Custom Build) 2159 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 2160 | * Copyright 2012-2013 The Dojo Foundation 2161 | * Based on Underscore.js 1.5.2 2162 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 2163 | * Available under MIT license 2164 | */ 2165 | define('lodash/utilities/identity',[], function() { 2166 | 2167 | /** 2168 | * This method returns the first argument provided to it. 2169 | * 2170 | * @static 2171 | * @memberOf _ 2172 | * @category Utilities 2173 | * @param {*} value Any value. 2174 | * @returns {*} Returns `value`. 2175 | * @example 2176 | * 2177 | * var object = { 'name': 'fred' }; 2178 | * _.identity(object) === object; 2179 | * // => true 2180 | */ 2181 | function identity(value) { 2182 | return value; 2183 | } 2184 | 2185 | return identity; 2186 | }); 2187 | 2188 | /** 2189 | * Lo-Dash 2.4.1 (Custom Build) 2190 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 2191 | * Copyright 2012-2013 The Dojo Foundation 2192 | * Based on Underscore.js 1.5.2 2193 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 2194 | * Available under MIT license 2195 | */ 2196 | define('lodash/support',['./internals/isNative'], function(isNative) { 2197 | 2198 | /** Used to detect functions containing a `this` reference */ 2199 | var reThis = /\bthis\b/; 2200 | 2201 | /** 2202 | * An object used to flag environments features. 2203 | * 2204 | * @static 2205 | * @memberOf _ 2206 | * @type Object 2207 | */ 2208 | var support = {}; 2209 | 2210 | /** 2211 | * Detect if functions can be decompiled by `Function#toString` 2212 | * (all but PS3 and older Opera mobile browsers & avoided in Windows 8 apps). 2213 | * 2214 | * @memberOf _.support 2215 | * @type boolean 2216 | */ 2217 | support.funcDecomp = !isNative(window.WinRTError) && reThis.test(function() { return this; }); 2218 | 2219 | /** 2220 | * Detect if `Function#name` is supported (all but IE). 2221 | * 2222 | * @memberOf _.support 2223 | * @type boolean 2224 | */ 2225 | support.funcNames = typeof Function.name == 'string'; 2226 | 2227 | return support; 2228 | }); 2229 | 2230 | /** 2231 | * Lo-Dash 2.4.1 (Custom Build) 2232 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 2233 | * Copyright 2012-2013 The Dojo Foundation 2234 | * Based on Underscore.js 1.5.2 2235 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 2236 | * Available under MIT license 2237 | */ 2238 | define('lodash/internals/baseCreateCallback',['../functions/bind', '../utilities/identity', './setBindData', '../support'], function(bind, identity, setBindData, support) { 2239 | 2240 | /** Used to detected named functions */ 2241 | var reFuncName = /^\s*function[ \n\r\t]+\w/; 2242 | 2243 | /** Used to detect functions containing a `this` reference */ 2244 | var reThis = /\bthis\b/; 2245 | 2246 | /** Native method shortcuts */ 2247 | var fnToString = Function.prototype.toString; 2248 | 2249 | /** 2250 | * The base implementation of `_.createCallback` without support for creating 2251 | * "_.pluck" or "_.where" style callbacks. 2252 | * 2253 | * @private 2254 | * @param {*} [func=identity] The value to convert to a callback. 2255 | * @param {*} [thisArg] The `this` binding of the created callback. 2256 | * @param {number} [argCount] The number of arguments the callback accepts. 2257 | * @returns {Function} Returns a callback function. 2258 | */ 2259 | function baseCreateCallback(func, thisArg, argCount) { 2260 | if (typeof func != 'function') { 2261 | return identity; 2262 | } 2263 | // exit early for no `thisArg` or already bound by `Function#bind` 2264 | if (typeof thisArg == 'undefined' || !('prototype' in func)) { 2265 | return func; 2266 | } 2267 | var bindData = func.__bindData__; 2268 | if (typeof bindData == 'undefined') { 2269 | if (support.funcNames) { 2270 | bindData = !func.name; 2271 | } 2272 | bindData = bindData || !support.funcDecomp; 2273 | if (!bindData) { 2274 | var source = fnToString.call(func); 2275 | if (!support.funcNames) { 2276 | bindData = !reFuncName.test(source); 2277 | } 2278 | if (!bindData) { 2279 | // checks if `func` references the `this` keyword and stores the result 2280 | bindData = reThis.test(source); 2281 | setBindData(func, bindData); 2282 | } 2283 | } 2284 | } 2285 | // exit early if there are no `this` references or `func` is bound 2286 | if (bindData === false || (bindData !== true && bindData[1] & 1)) { 2287 | return func; 2288 | } 2289 | switch (argCount) { 2290 | case 1: return function(value) { 2291 | return func.call(thisArg, value); 2292 | }; 2293 | case 2: return function(a, b) { 2294 | return func.call(thisArg, a, b); 2295 | }; 2296 | case 3: return function(value, index, collection) { 2297 | return func.call(thisArg, value, index, collection); 2298 | }; 2299 | case 4: return function(accumulator, value, index, collection) { 2300 | return func.call(thisArg, accumulator, value, index, collection); 2301 | }; 2302 | } 2303 | return bind(func, thisArg); 2304 | } 2305 | 2306 | return baseCreateCallback; 2307 | }); 2308 | 2309 | /** 2310 | * Lo-Dash 2.4.1 (Custom Build) 2311 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 2312 | * Copyright 2012-2013 The Dojo Foundation 2313 | * Based on Underscore.js 1.5.2 2314 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 2315 | * Available under MIT license 2316 | */ 2317 | define('lodash/internals/shimKeys',['./objectTypes'], function(objectTypes) { 2318 | 2319 | /** Used for native method references */ 2320 | var objectProto = Object.prototype; 2321 | 2322 | /** Native method shortcuts */ 2323 | var hasOwnProperty = objectProto.hasOwnProperty; 2324 | 2325 | /** 2326 | * A fallback implementation of `Object.keys` which produces an array of the 2327 | * given object's own enumerable property names. 2328 | * 2329 | * @private 2330 | * @type Function 2331 | * @param {Object} object The object to inspect. 2332 | * @returns {Array} Returns an array of property names. 2333 | */ 2334 | var shimKeys = function(object) { 2335 | var index, iterable = object, result = []; 2336 | if (!iterable) return result; 2337 | if (!(objectTypes[typeof object])) return result; 2338 | for (index in iterable) { 2339 | if (hasOwnProperty.call(iterable, index)) { 2340 | result.push(index); 2341 | } 2342 | } 2343 | return result 2344 | }; 2345 | 2346 | return shimKeys; 2347 | }); 2348 | 2349 | /** 2350 | * Lo-Dash 2.4.1 (Custom Build) 2351 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 2352 | * Copyright 2012-2013 The Dojo Foundation 2353 | * Based on Underscore.js 1.5.2 2354 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 2355 | * Available under MIT license 2356 | */ 2357 | define('lodash/objects/keys',['../internals/isNative', './isObject', '../internals/shimKeys'], function(isNative, isObject, shimKeys) { 2358 | 2359 | /* Native method shortcuts for methods with the same name as other `lodash` methods */ 2360 | var nativeKeys = isNative(nativeKeys = Object.keys) && nativeKeys; 2361 | 2362 | /** 2363 | * Creates an array composed of the own enumerable property names of an object. 2364 | * 2365 | * @static 2366 | * @memberOf _ 2367 | * @category Objects 2368 | * @param {Object} object The object to inspect. 2369 | * @returns {Array} Returns an array of property names. 2370 | * @example 2371 | * 2372 | * _.keys({ 'one': 1, 'two': 2, 'three': 3 }); 2373 | * // => ['one', 'two', 'three'] (property order is not guaranteed across environments) 2374 | */ 2375 | var keys = !nativeKeys ? shimKeys : function(object) { 2376 | if (!isObject(object)) { 2377 | return []; 2378 | } 2379 | return nativeKeys(object); 2380 | }; 2381 | 2382 | return keys; 2383 | }); 2384 | 2385 | /** 2386 | * Lo-Dash 2.4.1 (Custom Build) 2387 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 2388 | * Copyright 2012-2013 The Dojo Foundation 2389 | * Based on Underscore.js 1.5.2 2390 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 2391 | * Available under MIT license 2392 | */ 2393 | define('lodash/objects/assign',['../internals/baseCreateCallback', './keys', '../internals/objectTypes'], function(baseCreateCallback, keys, objectTypes) { 2394 | 2395 | /** 2396 | * Assigns own enumerable properties of source object(s) to the destination 2397 | * object. Subsequent sources will overwrite property assignments of previous 2398 | * sources. If a callback is provided it will be executed to produce the 2399 | * assigned values. The callback is bound to `thisArg` and invoked with two 2400 | * arguments; (objectValue, sourceValue). 2401 | * 2402 | * @static 2403 | * @memberOf _ 2404 | * @type Function 2405 | * @alias extend 2406 | * @category Objects 2407 | * @param {Object} object The destination object. 2408 | * @param {...Object} [source] The source objects. 2409 | * @param {Function} [callback] The function to customize assigning values. 2410 | * @param {*} [thisArg] The `this` binding of `callback`. 2411 | * @returns {Object} Returns the destination object. 2412 | * @example 2413 | * 2414 | * _.assign({ 'name': 'fred' }, { 'employer': 'slate' }); 2415 | * // => { 'name': 'fred', 'employer': 'slate' } 2416 | * 2417 | * var defaults = _.partialRight(_.assign, function(a, b) { 2418 | * return typeof a == 'undefined' ? b : a; 2419 | * }); 2420 | * 2421 | * var object = { 'name': 'barney' }; 2422 | * defaults(object, { 'name': 'fred', 'employer': 'slate' }); 2423 | * // => { 'name': 'barney', 'employer': 'slate' } 2424 | */ 2425 | var assign = function(object, source, guard) { 2426 | var index, iterable = object, result = iterable; 2427 | if (!iterable) return result; 2428 | var args = arguments, 2429 | argsIndex = 0, 2430 | argsLength = typeof guard == 'number' ? 2 : args.length; 2431 | if (argsLength > 3 && typeof args[argsLength - 2] == 'function') { 2432 | var callback = baseCreateCallback(args[--argsLength - 1], args[argsLength--], 2); 2433 | } else if (argsLength > 2 && typeof args[argsLength - 1] == 'function') { 2434 | callback = args[--argsLength]; 2435 | } 2436 | while (++argsIndex < argsLength) { 2437 | iterable = args[argsIndex]; 2438 | if (iterable && objectTypes[typeof iterable]) { 2439 | var ownIndex = -1, 2440 | ownProps = objectTypes[typeof iterable] && keys(iterable), 2441 | length = ownProps ? ownProps.length : 0; 2442 | 2443 | while (++ownIndex < length) { 2444 | index = ownProps[ownIndex]; 2445 | result[index] = callback ? callback(result[index], iterable[index]) : iterable[index]; 2446 | } 2447 | } 2448 | } 2449 | return result 2450 | }; 2451 | 2452 | return assign; 2453 | }); 2454 | 2455 | define('custom_sinon',['require','exports','module','lodash/objects/assign'],function (require, exports, module) {var previous_sinon, root, sinon; 2456 | 2457 | root = this; 2458 | 2459 | previous_sinon = root.sinon; 2460 | 2461 | sinon = { 2462 | noConflict: function() { 2463 | root.sinon = previous_sinon; 2464 | return this; 2465 | }, 2466 | extend: require('lodash/objects/assign') 2467 | }; 2468 | 2469 | /*jslint eqeqeq: false, onevar: false*/ 2470 | /*global sinon, module, require, ActiveXObject, XMLHttpRequest, DOMParser*/ 2471 | /** 2472 | * Minimal Event interface implementation 2473 | * 2474 | * Original implementation by Sven Fuchs: https://gist.github.com/995028 2475 | * Modifications and tests by Christian Johansen. 2476 | * 2477 | * @author Sven Fuchs (svenfuchs@artweb-design.de) 2478 | * @author Christian Johansen (christian@cjohansen.no) 2479 | * @license BSD 2480 | * 2481 | * Copyright (c) 2011 Sven Fuchs, Christian Johansen 2482 | */ 2483 | 2484 | 2485 | if (typeof sinon == "undefined") { 2486 | this.sinon = {}; 2487 | } 2488 | 2489 | (function () { 2490 | var push = [].push; 2491 | 2492 | sinon.Event = function Event(type, bubbles, cancelable, target) { 2493 | this.initEvent(type, bubbles, cancelable, target); 2494 | }; 2495 | 2496 | sinon.Event.prototype = { 2497 | initEvent: function(type, bubbles, cancelable, target) { 2498 | this.type = type; 2499 | this.bubbles = bubbles; 2500 | this.cancelable = cancelable; 2501 | this.target = target; 2502 | }, 2503 | 2504 | stopPropagation: function () {}, 2505 | 2506 | preventDefault: function () { 2507 | this.defaultPrevented = true; 2508 | } 2509 | }; 2510 | 2511 | sinon.ProgressEvent = function ProgressEvent(type, progressEventRaw, target) { 2512 | this.initEvent(type, false, false, target); 2513 | this.loaded = progressEventRaw.loaded || null; 2514 | this.total = progressEventRaw.total || null; 2515 | }; 2516 | 2517 | sinon.ProgressEvent.prototype = new sinon.Event(); 2518 | 2519 | sinon.ProgressEvent.prototype.constructor = sinon.ProgressEvent; 2520 | 2521 | sinon.CustomEvent = function CustomEvent(type, customData, target) { 2522 | this.initEvent(type, false, false, target); 2523 | this.detail = customData.detail || null; 2524 | }; 2525 | 2526 | sinon.CustomEvent.prototype = new sinon.Event(); 2527 | 2528 | sinon.CustomEvent.prototype.constructor = sinon.CustomEvent; 2529 | 2530 | sinon.EventTarget = { 2531 | addEventListener: function addEventListener(event, listener) { 2532 | this.eventListeners = this.eventListeners || {}; 2533 | this.eventListeners[event] = this.eventListeners[event] || []; 2534 | push.call(this.eventListeners[event], listener); 2535 | }, 2536 | 2537 | removeEventListener: function removeEventListener(event, listener) { 2538 | var listeners = this.eventListeners && this.eventListeners[event] || []; 2539 | 2540 | for (var i = 0, l = listeners.length; i < l; ++i) { 2541 | if (listeners[i] == listener) { 2542 | return listeners.splice(i, 1); 2543 | } 2544 | } 2545 | }, 2546 | 2547 | dispatchEvent: function dispatchEvent(event) { 2548 | var type = event.type; 2549 | var listeners = this.eventListeners && this.eventListeners[type] || []; 2550 | 2551 | for (var i = 0; i < listeners.length; i++) { 2552 | if (typeof listeners[i] == "function") { 2553 | listeners[i].call(this, event); 2554 | } else { 2555 | listeners[i].handleEvent(event); 2556 | } 2557 | } 2558 | 2559 | return !!event.defaultPrevented; 2560 | } 2561 | }; 2562 | }()); 2563 | ; 2564 | 2565 | /** 2566 | * @depend ../../sinon.js 2567 | * @depend event.js 2568 | */ 2569 | /*jslint eqeqeq: false, onevar: false*/ 2570 | /*global sinon, module, require, ActiveXObject, XMLHttpRequest, DOMParser*/ 2571 | /** 2572 | * Fake XMLHttpRequest object 2573 | * 2574 | * @author Christian Johansen (christian@cjohansen.no) 2575 | * @license BSD 2576 | * 2577 | * Copyright (c) 2010-2013 Christian Johansen 2578 | */ 2579 | 2580 | 2581 | // wrapper for global 2582 | (function(global) { 2583 | if (typeof sinon === "undefined") { 2584 | global.sinon = {}; 2585 | } 2586 | 2587 | var supportsProgress = typeof ProgressEvent !== "undefined"; 2588 | var supportsCustomEvent = typeof CustomEvent !== "undefined"; 2589 | sinon.xhr = { XMLHttpRequest: global.XMLHttpRequest }; 2590 | var xhr = sinon.xhr; 2591 | xhr.GlobalXMLHttpRequest = global.XMLHttpRequest; 2592 | xhr.GlobalActiveXObject = global.ActiveXObject; 2593 | xhr.supportsActiveX = typeof xhr.GlobalActiveXObject != "undefined"; 2594 | xhr.supportsXHR = typeof xhr.GlobalXMLHttpRequest != "undefined"; 2595 | xhr.workingXHR = xhr.supportsXHR ? xhr.GlobalXMLHttpRequest : xhr.supportsActiveX 2596 | ? function() { return new xhr.GlobalActiveXObject("MSXML2.XMLHTTP.3.0") } : false; 2597 | xhr.supportsCORS = 'withCredentials' in (new sinon.xhr.GlobalXMLHttpRequest()); 2598 | 2599 | /*jsl:ignore*/ 2600 | var unsafeHeaders = { 2601 | "Accept-Charset": true, 2602 | "Accept-Encoding": true, 2603 | "Connection": true, 2604 | "Content-Length": true, 2605 | "Cookie": true, 2606 | "Cookie2": true, 2607 | "Content-Transfer-Encoding": true, 2608 | "Date": true, 2609 | "Expect": true, 2610 | "Host": true, 2611 | "Keep-Alive": true, 2612 | "Referer": true, 2613 | "TE": true, 2614 | "Trailer": true, 2615 | "Transfer-Encoding": true, 2616 | "Upgrade": true, 2617 | "User-Agent": true, 2618 | "Via": true 2619 | }; 2620 | /*jsl:end*/ 2621 | 2622 | function FakeXMLHttpRequest() { 2623 | this.readyState = FakeXMLHttpRequest.UNSENT; 2624 | this.requestHeaders = {}; 2625 | this.requestBody = null; 2626 | this.status = 0; 2627 | this.statusText = ""; 2628 | this.upload = new UploadProgress(); 2629 | if (sinon.xhr.supportsCORS) { 2630 | this.withCredentials = false; 2631 | } 2632 | 2633 | 2634 | var xhr = this; 2635 | var events = ["loadstart", "load", "abort", "loadend"]; 2636 | 2637 | function addEventListener(eventName) { 2638 | xhr.addEventListener(eventName, function (event) { 2639 | var listener = xhr["on" + eventName]; 2640 | 2641 | if (listener && typeof listener == "function") { 2642 | listener.call(this, event); 2643 | } 2644 | }); 2645 | } 2646 | 2647 | for (var i = events.length - 1; i >= 0; i--) { 2648 | addEventListener(events[i]); 2649 | } 2650 | 2651 | if (typeof FakeXMLHttpRequest.onCreate == "function") { 2652 | FakeXMLHttpRequest.onCreate(this); 2653 | } 2654 | } 2655 | 2656 | // An upload object is created for each 2657 | // FakeXMLHttpRequest and allows upload 2658 | // events to be simulated using uploadProgress 2659 | // and uploadError. 2660 | function UploadProgress() { 2661 | this.eventListeners = { 2662 | "progress": [], 2663 | "load": [], 2664 | "abort": [], 2665 | "error": [] 2666 | } 2667 | } 2668 | 2669 | UploadProgress.prototype.addEventListener = function(event, listener) { 2670 | this.eventListeners[event].push(listener); 2671 | }; 2672 | 2673 | UploadProgress.prototype.removeEventListener = function(event, listener) { 2674 | var listeners = this.eventListeners[event] || []; 2675 | 2676 | for (var i = 0, l = listeners.length; i < l; ++i) { 2677 | if (listeners[i] == listener) { 2678 | return listeners.splice(i, 1); 2679 | } 2680 | } 2681 | }; 2682 | 2683 | UploadProgress.prototype.dispatchEvent = function(event) { 2684 | var listeners = this.eventListeners[event.type] || []; 2685 | 2686 | for (var i = 0, listener; (listener = listeners[i]) != null; i++) { 2687 | listener(event); 2688 | } 2689 | }; 2690 | 2691 | function verifyState(xhr) { 2692 | if (xhr.readyState !== FakeXMLHttpRequest.OPENED) { 2693 | throw new Error("INVALID_STATE_ERR"); 2694 | } 2695 | 2696 | if (xhr.sendFlag) { 2697 | throw new Error("INVALID_STATE_ERR"); 2698 | } 2699 | } 2700 | 2701 | // filtering to enable a white-list version of Sinon FakeXhr, 2702 | // where whitelisted requests are passed through to real XHR 2703 | function each(collection, callback) { 2704 | if (!collection) return; 2705 | for (var i = 0, l = collection.length; i < l; i += 1) { 2706 | callback(collection[i]); 2707 | } 2708 | } 2709 | function some(collection, callback) { 2710 | for (var index = 0; index < collection.length; index++) { 2711 | if(callback(collection[index]) === true) return true; 2712 | } 2713 | return false; 2714 | } 2715 | // largest arity in XHR is 5 - XHR#open 2716 | var apply = function(obj,method,args) { 2717 | switch(args.length) { 2718 | case 0: return obj[method](); 2719 | case 1: return obj[method](args[0]); 2720 | case 2: return obj[method](args[0],args[1]); 2721 | case 3: return obj[method](args[0],args[1],args[2]); 2722 | case 4: return obj[method](args[0],args[1],args[2],args[3]); 2723 | case 5: return obj[method](args[0],args[1],args[2],args[3],args[4]); 2724 | } 2725 | }; 2726 | 2727 | FakeXMLHttpRequest.filters = []; 2728 | FakeXMLHttpRequest.addFilter = function(fn) { 2729 | this.filters.push(fn) 2730 | }; 2731 | var IE6Re = /MSIE 6/; 2732 | FakeXMLHttpRequest.defake = function(fakeXhr,xhrArgs) { 2733 | var xhr = new sinon.xhr.workingXHR(); 2734 | each(["open","setRequestHeader","send","abort","getResponseHeader", 2735 | "getAllResponseHeaders","addEventListener","overrideMimeType","removeEventListener"], 2736 | function(method) { 2737 | fakeXhr[method] = function() { 2738 | return apply(xhr,method,arguments); 2739 | }; 2740 | }); 2741 | 2742 | var copyAttrs = function(args) { 2743 | each(args, function(attr) { 2744 | try { 2745 | fakeXhr[attr] = xhr[attr] 2746 | } catch(e) { 2747 | if(!IE6Re.test(navigator.userAgent)) throw e; 2748 | } 2749 | }); 2750 | }; 2751 | 2752 | var stateChange = function() { 2753 | fakeXhr.readyState = xhr.readyState; 2754 | if(xhr.readyState >= FakeXMLHttpRequest.HEADERS_RECEIVED) { 2755 | copyAttrs(["status","statusText"]); 2756 | } 2757 | if(xhr.readyState >= FakeXMLHttpRequest.LOADING) { 2758 | copyAttrs(["responseText"]); 2759 | } 2760 | if(xhr.readyState === FakeXMLHttpRequest.DONE) { 2761 | copyAttrs(["responseXML"]); 2762 | } 2763 | if(fakeXhr.onreadystatechange) fakeXhr.onreadystatechange.call(fakeXhr, { target: fakeXhr }); 2764 | }; 2765 | if(xhr.addEventListener) { 2766 | for(var event in fakeXhr.eventListeners) { 2767 | if(fakeXhr.eventListeners.hasOwnProperty(event)) { 2768 | each(fakeXhr.eventListeners[event],function(handler) { 2769 | xhr.addEventListener(event, handler); 2770 | }); 2771 | } 2772 | } 2773 | xhr.addEventListener("readystatechange",stateChange); 2774 | } else { 2775 | xhr.onreadystatechange = stateChange; 2776 | } 2777 | apply(xhr,"open",xhrArgs); 2778 | }; 2779 | FakeXMLHttpRequest.useFilters = false; 2780 | 2781 | function verifyRequestOpened(xhr) { 2782 | if (xhr.readyState != FakeXMLHttpRequest.OPENED) { 2783 | throw new Error("INVALID_STATE_ERR - " + xhr.readyState); 2784 | } 2785 | } 2786 | 2787 | function verifyRequestSent(xhr) { 2788 | if (xhr.readyState == FakeXMLHttpRequest.DONE) { 2789 | throw new Error("Request done"); 2790 | } 2791 | } 2792 | 2793 | function verifyHeadersReceived(xhr) { 2794 | if (xhr.async && xhr.readyState != FakeXMLHttpRequest.HEADERS_RECEIVED) { 2795 | throw new Error("No headers received"); 2796 | } 2797 | } 2798 | 2799 | function verifyResponseBodyType(body) { 2800 | if (typeof body != "string") { 2801 | var error = new Error("Attempted to respond to fake XMLHttpRequest with " + 2802 | body + ", which is not a string."); 2803 | error.name = "InvalidBodyException"; 2804 | throw error; 2805 | } 2806 | } 2807 | 2808 | sinon.extend(FakeXMLHttpRequest.prototype, sinon.EventTarget, { 2809 | async: true, 2810 | 2811 | open: function open(method, url, async, username, password) { 2812 | this.method = method; 2813 | this.url = url; 2814 | this.async = typeof async == "boolean" ? async : true; 2815 | this.username = username; 2816 | this.password = password; 2817 | this.responseText = null; 2818 | this.responseXML = null; 2819 | this.requestHeaders = {}; 2820 | this.sendFlag = false; 2821 | if(sinon.FakeXMLHttpRequest.useFilters === true) { 2822 | var xhrArgs = arguments; 2823 | var defake = some(FakeXMLHttpRequest.filters,function(filter) { 2824 | return filter.apply(this,xhrArgs) 2825 | }); 2826 | if (defake) { 2827 | return sinon.FakeXMLHttpRequest.defake(this,arguments); 2828 | } 2829 | } 2830 | this.readyStateChange(FakeXMLHttpRequest.OPENED); 2831 | }, 2832 | 2833 | readyStateChange: function readyStateChange(state) { 2834 | this.readyState = state; 2835 | 2836 | if (typeof this.onreadystatechange == "function") { 2837 | try { 2838 | this.onreadystatechange(); 2839 | } catch (e) { 2840 | sinon.logError("Fake XHR onreadystatechange handler", e); 2841 | } 2842 | } 2843 | 2844 | this.dispatchEvent(new sinon.Event("readystatechange")); 2845 | 2846 | switch (this.readyState) { 2847 | case FakeXMLHttpRequest.DONE: 2848 | this.dispatchEvent(new sinon.Event("load", false, false, this)); 2849 | this.dispatchEvent(new sinon.Event("loadend", false, false, this)); 2850 | this.upload.dispatchEvent(new sinon.Event("load", false, false, this)); 2851 | if (supportsProgress) { 2852 | this.upload.dispatchEvent(new sinon.ProgressEvent('progress', {loaded: 100, total: 100})); 2853 | } 2854 | break; 2855 | } 2856 | }, 2857 | 2858 | setRequestHeader: function setRequestHeader(header, value) { 2859 | verifyState(this); 2860 | 2861 | if (unsafeHeaders[header] || /^(Sec-|Proxy-)/.test(header)) { 2862 | throw new Error("Refused to set unsafe header \"" + header + "\""); 2863 | } 2864 | 2865 | if (this.requestHeaders[header]) { 2866 | this.requestHeaders[header] += "," + value; 2867 | } else { 2868 | this.requestHeaders[header] = value; 2869 | } 2870 | }, 2871 | 2872 | // Helps testing 2873 | setResponseHeaders: function setResponseHeaders(headers) { 2874 | verifyRequestOpened(this); 2875 | this.responseHeaders = {}; 2876 | 2877 | for (var header in headers) { 2878 | if (headers.hasOwnProperty(header)) { 2879 | this.responseHeaders[header] = headers[header]; 2880 | } 2881 | } 2882 | 2883 | if (this.async) { 2884 | this.readyStateChange(FakeXMLHttpRequest.HEADERS_RECEIVED); 2885 | } else { 2886 | this.readyState = FakeXMLHttpRequest.HEADERS_RECEIVED; 2887 | } 2888 | }, 2889 | 2890 | // Currently treats ALL data as a DOMString (i.e. no Document) 2891 | send: function send(data) { 2892 | verifyState(this); 2893 | 2894 | if (!/^(get|head)$/i.test(this.method)) { 2895 | if (this.requestHeaders["Content-Type"]) { 2896 | var value = this.requestHeaders["Content-Type"].split(";"); 2897 | this.requestHeaders["Content-Type"] = value[0] + ";charset=utf-8"; 2898 | } else { 2899 | this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8"; 2900 | } 2901 | 2902 | this.requestBody = data; 2903 | } 2904 | 2905 | this.errorFlag = false; 2906 | this.sendFlag = this.async; 2907 | this.readyStateChange(FakeXMLHttpRequest.OPENED); 2908 | 2909 | if (typeof this.onSend == "function") { 2910 | this.onSend(this); 2911 | } 2912 | 2913 | this.dispatchEvent(new sinon.Event("loadstart", false, false, this)); 2914 | }, 2915 | 2916 | abort: function abort() { 2917 | this.aborted = true; 2918 | this.responseText = null; 2919 | this.errorFlag = true; 2920 | this.requestHeaders = {}; 2921 | 2922 | if (this.readyState > sinon.FakeXMLHttpRequest.UNSENT && this.sendFlag) { 2923 | this.readyStateChange(sinon.FakeXMLHttpRequest.DONE); 2924 | this.sendFlag = false; 2925 | } 2926 | 2927 | this.readyState = sinon.FakeXMLHttpRequest.UNSENT; 2928 | 2929 | this.dispatchEvent(new sinon.Event("abort", false, false, this)); 2930 | 2931 | this.upload.dispatchEvent(new sinon.Event("abort", false, false, this)); 2932 | 2933 | if (typeof this.onerror === "function") { 2934 | this.onerror(); 2935 | } 2936 | }, 2937 | 2938 | getResponseHeader: function getResponseHeader(header) { 2939 | if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { 2940 | return null; 2941 | } 2942 | 2943 | if (/^Set-Cookie2?$/i.test(header)) { 2944 | return null; 2945 | } 2946 | 2947 | header = header.toLowerCase(); 2948 | 2949 | for (var h in this.responseHeaders) { 2950 | if (h.toLowerCase() == header) { 2951 | return this.responseHeaders[h]; 2952 | } 2953 | } 2954 | 2955 | return null; 2956 | }, 2957 | 2958 | getAllResponseHeaders: function getAllResponseHeaders() { 2959 | if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { 2960 | return ""; 2961 | } 2962 | 2963 | var headers = ""; 2964 | 2965 | for (var header in this.responseHeaders) { 2966 | if (this.responseHeaders.hasOwnProperty(header) && 2967 | !/^Set-Cookie2?$/i.test(header)) { 2968 | headers += header + ": " + this.responseHeaders[header] + "\r\n"; 2969 | } 2970 | } 2971 | 2972 | return headers; 2973 | }, 2974 | 2975 | setResponseBody: function setResponseBody(body) { 2976 | verifyRequestSent(this); 2977 | verifyHeadersReceived(this); 2978 | verifyResponseBodyType(body); 2979 | 2980 | var chunkSize = this.chunkSize || 10; 2981 | var index = 0; 2982 | this.responseText = ""; 2983 | 2984 | do { 2985 | if (this.async) { 2986 | this.readyStateChange(FakeXMLHttpRequest.LOADING); 2987 | } 2988 | 2989 | this.responseText += body.substring(index, index + chunkSize); 2990 | index += chunkSize; 2991 | } while (index < body.length); 2992 | 2993 | var type = this.getResponseHeader("Content-Type"); 2994 | 2995 | if (this.responseText && 2996 | (!type || /(text\/xml)|(application\/xml)|(\+xml)/.test(type))) { 2997 | try { 2998 | this.responseXML = FakeXMLHttpRequest.parseXML(this.responseText); 2999 | } catch (e) { 3000 | // Unable to parse XML - no biggie 3001 | } 3002 | } 3003 | 3004 | if (this.async) { 3005 | this.readyStateChange(FakeXMLHttpRequest.DONE); 3006 | } else { 3007 | this.readyState = FakeXMLHttpRequest.DONE; 3008 | } 3009 | }, 3010 | 3011 | respond: function respond(status, headers, body) { 3012 | this.status = typeof status == "number" ? status : 200; 3013 | this.statusText = FakeXMLHttpRequest.statusCodes[this.status]; 3014 | this.setResponseHeaders(headers || {}); 3015 | this.setResponseBody(body || ""); 3016 | }, 3017 | 3018 | uploadProgress: function uploadProgress(progressEventRaw) { 3019 | if (supportsProgress) { 3020 | this.upload.dispatchEvent(new sinon.ProgressEvent("progress", progressEventRaw)); 3021 | } 3022 | }, 3023 | 3024 | uploadError: function uploadError(error) { 3025 | if (supportsCustomEvent) { 3026 | this.upload.dispatchEvent(new sinon.CustomEvent("error", {"detail": error})); 3027 | } 3028 | } 3029 | }); 3030 | 3031 | sinon.extend(FakeXMLHttpRequest, { 3032 | UNSENT: 0, 3033 | OPENED: 1, 3034 | HEADERS_RECEIVED: 2, 3035 | LOADING: 3, 3036 | DONE: 4 3037 | }); 3038 | 3039 | // Borrowed from JSpec 3040 | FakeXMLHttpRequest.parseXML = function parseXML(text) { 3041 | var xmlDoc; 3042 | 3043 | if (typeof DOMParser != "undefined") { 3044 | var parser = new DOMParser(); 3045 | xmlDoc = parser.parseFromString(text, "text/xml"); 3046 | } else { 3047 | xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); 3048 | xmlDoc.async = "false"; 3049 | xmlDoc.loadXML(text); 3050 | } 3051 | 3052 | return xmlDoc; 3053 | }; 3054 | 3055 | FakeXMLHttpRequest.statusCodes = { 3056 | 100: "Continue", 3057 | 101: "Switching Protocols", 3058 | 200: "OK", 3059 | 201: "Created", 3060 | 202: "Accepted", 3061 | 203: "Non-Authoritative Information", 3062 | 204: "No Content", 3063 | 205: "Reset Content", 3064 | 206: "Partial Content", 3065 | 300: "Multiple Choice", 3066 | 301: "Moved Permanently", 3067 | 302: "Found", 3068 | 303: "See Other", 3069 | 304: "Not Modified", 3070 | 305: "Use Proxy", 3071 | 307: "Temporary Redirect", 3072 | 400: "Bad Request", 3073 | 401: "Unauthorized", 3074 | 402: "Payment Required", 3075 | 403: "Forbidden", 3076 | 404: "Not Found", 3077 | 405: "Method Not Allowed", 3078 | 406: "Not Acceptable", 3079 | 407: "Proxy Authentication Required", 3080 | 408: "Request Timeout", 3081 | 409: "Conflict", 3082 | 410: "Gone", 3083 | 411: "Length Required", 3084 | 412: "Precondition Failed", 3085 | 413: "Request Entity Too Large", 3086 | 414: "Request-URI Too Long", 3087 | 415: "Unsupported Media Type", 3088 | 416: "Requested Range Not Satisfiable", 3089 | 417: "Expectation Failed", 3090 | 422: "Unprocessable Entity", 3091 | 500: "Internal Server Error", 3092 | 501: "Not Implemented", 3093 | 502: "Bad Gateway", 3094 | 503: "Service Unavailable", 3095 | 504: "Gateway Timeout", 3096 | 505: "HTTP Version Not Supported" 3097 | }; 3098 | 3099 | sinon.useFakeXMLHttpRequest = function () { 3100 | sinon.FakeXMLHttpRequest.restore = function restore(keepOnCreate) { 3101 | if (xhr.supportsXHR) { 3102 | global.XMLHttpRequest = xhr.GlobalXMLHttpRequest; 3103 | } 3104 | 3105 | if (xhr.supportsActiveX) { 3106 | global.ActiveXObject = xhr.GlobalActiveXObject; 3107 | } 3108 | 3109 | delete sinon.FakeXMLHttpRequest.restore; 3110 | 3111 | if (keepOnCreate !== true) { 3112 | delete sinon.FakeXMLHttpRequest.onCreate; 3113 | } 3114 | }; 3115 | if (xhr.supportsXHR) { 3116 | global.XMLHttpRequest = sinon.FakeXMLHttpRequest; 3117 | } 3118 | 3119 | if (xhr.supportsActiveX) { 3120 | global.ActiveXObject = function ActiveXObject(objId) { 3121 | if (objId == "Microsoft.XMLHTTP" || /^Msxml2\.XMLHTTP/i.test(objId)) { 3122 | 3123 | return new sinon.FakeXMLHttpRequest(); 3124 | } 3125 | 3126 | return new xhr.GlobalActiveXObject(objId); 3127 | }; 3128 | } 3129 | 3130 | return sinon.FakeXMLHttpRequest; 3131 | }; 3132 | 3133 | sinon.FakeXMLHttpRequest = FakeXMLHttpRequest; 3134 | 3135 | })(typeof global === "object" ? global : this); 3136 | 3137 | if (typeof module !== 'undefined' && module.exports) { 3138 | module.exports = sinon; 3139 | } 3140 | ; 3141 | 3142 | module.exports = sinon.noConflict(); 3143 | 3144 | }); 3145 | 3146 | define('request_status',['require','exports','module','./custom_sinon'],function (require, exports, module) {var sinon; 3147 | 3148 | sinon = require('./custom_sinon'); 3149 | 3150 | module.exports = { 3151 | isUnset: function(request) { 3152 | return request.readyState === sinon.FakeXMLHttpRequest.UNSET; 3153 | }, 3154 | isOpened: function(request) { 3155 | return request.readyState === sinon.FakeXMLHttpRequest.OPENED; 3156 | }, 3157 | isHeadersReceived: function(request) { 3158 | return request.readyState === sinon.FakeXMLHttpRequest.HEADERS_RECEIVED; 3159 | }, 3160 | isLoading: function(request) { 3161 | return request.readyState === sinon.FakeXMLHttpRequest.LOADING; 3162 | }, 3163 | isDone: function(request) { 3164 | return request.readyState === sinon.FakeXMLHttpRequest.DONE; 3165 | } 3166 | }; 3167 | 3168 | }); 3169 | 3170 | /** 3171 | * Lo-Dash 2.4.1 (Custom Build) 3172 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 3173 | * Copyright 2012-2013 The Dojo Foundation 3174 | * Based on Underscore.js 1.5.2 3175 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 3176 | * Available under MIT license 3177 | */ 3178 | define('lodash/objects/forOwn',['../internals/baseCreateCallback', './keys', '../internals/objectTypes'], function(baseCreateCallback, keys, objectTypes) { 3179 | 3180 | /** 3181 | * Iterates over own enumerable properties of an object, executing the callback 3182 | * for each property. The callback is bound to `thisArg` and invoked with three 3183 | * arguments; (value, key, object). Callbacks may exit iteration early by 3184 | * explicitly returning `false`. 3185 | * 3186 | * @static 3187 | * @memberOf _ 3188 | * @type Function 3189 | * @category Objects 3190 | * @param {Object} object The object to iterate over. 3191 | * @param {Function} [callback=identity] The function called per iteration. 3192 | * @param {*} [thisArg] The `this` binding of `callback`. 3193 | * @returns {Object} Returns `object`. 3194 | * @example 3195 | * 3196 | * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) { 3197 | * console.log(key); 3198 | * }); 3199 | * // => logs '0', '1', and 'length' (property order is not guaranteed across environments) 3200 | */ 3201 | var forOwn = function(collection, callback, thisArg) { 3202 | var index, iterable = collection, result = iterable; 3203 | if (!iterable) return result; 3204 | if (!objectTypes[typeof iterable]) return result; 3205 | callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3); 3206 | var ownIndex = -1, 3207 | ownProps = objectTypes[typeof iterable] && keys(iterable), 3208 | length = ownProps ? ownProps.length : 0; 3209 | 3210 | while (++ownIndex < length) { 3211 | index = ownProps[ownIndex]; 3212 | if (callback(iterable[index], index, collection) === false) return result; 3213 | } 3214 | return result 3215 | }; 3216 | 3217 | return forOwn; 3218 | }); 3219 | 3220 | /** 3221 | * Lo-Dash 2.4.1 (Custom Build) 3222 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 3223 | * Copyright 2012-2013 The Dojo Foundation 3224 | * Based on Underscore.js 1.5.2 3225 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 3226 | * Available under MIT license 3227 | */ 3228 | define('lodash/collections/forEach',['../internals/baseCreateCallback', '../objects/forOwn'], function(baseCreateCallback, forOwn) { 3229 | 3230 | /** 3231 | * Iterates over elements of a collection, executing the callback for each 3232 | * element. The callback is bound to `thisArg` and invoked with three arguments; 3233 | * (value, index|key, collection). Callbacks may exit iteration early by 3234 | * explicitly returning `false`. 3235 | * 3236 | * Note: As with other "Collections" methods, objects with a `length` property 3237 | * are iterated like arrays. To avoid this behavior `_.forIn` or `_.forOwn` 3238 | * may be used for object iteration. 3239 | * 3240 | * @static 3241 | * @memberOf _ 3242 | * @alias each 3243 | * @category Collections 3244 | * @param {Array|Object|string} collection The collection to iterate over. 3245 | * @param {Function} [callback=identity] The function called per iteration. 3246 | * @param {*} [thisArg] The `this` binding of `callback`. 3247 | * @returns {Array|Object|string} Returns `collection`. 3248 | * @example 3249 | * 3250 | * _([1, 2, 3]).forEach(function(num) { console.log(num); }).join(','); 3251 | * // => logs each number and returns '1,2,3' 3252 | * 3253 | * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { console.log(num); }); 3254 | * // => logs each number and returns the object (property order is not guaranteed across environments) 3255 | */ 3256 | function forEach(collection, callback, thisArg) { 3257 | var index = -1, 3258 | length = collection ? collection.length : 0; 3259 | 3260 | callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3); 3261 | if (typeof length == 'number') { 3262 | while (++index < length) { 3263 | if (callback(collection[index], index, collection) === false) { 3264 | break; 3265 | } 3266 | } 3267 | } else { 3268 | forOwn(collection, callback); 3269 | } 3270 | return collection; 3271 | } 3272 | 3273 | return forEach; 3274 | }); 3275 | 3276 | /** 3277 | * Lo-Dash 2.4.1 (Custom Build) 3278 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 3279 | * Copyright 2012-2013 The Dojo Foundation 3280 | * Based on Underscore.js 1.5.2 3281 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 3282 | * Available under MIT license 3283 | */ 3284 | define('lodash/internals/arrayPool',[], function() { 3285 | 3286 | /** Used to pool arrays and objects used internally */ 3287 | var arrayPool = []; 3288 | 3289 | return arrayPool; 3290 | }); 3291 | 3292 | /** 3293 | * Lo-Dash 2.4.1 (Custom Build) 3294 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 3295 | * Copyright 2012-2013 The Dojo Foundation 3296 | * Based on Underscore.js 1.5.2 3297 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 3298 | * Available under MIT license 3299 | */ 3300 | define('lodash/internals/getArray',['./arrayPool'], function(arrayPool) { 3301 | 3302 | /** 3303 | * Gets an array from the array pool or creates a new one if the pool is empty. 3304 | * 3305 | * @private 3306 | * @returns {Array} The array from the pool. 3307 | */ 3308 | function getArray() { 3309 | return arrayPool.pop() || []; 3310 | } 3311 | 3312 | return getArray; 3313 | }); 3314 | 3315 | /** 3316 | * Lo-Dash 2.4.1 (Custom Build) 3317 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 3318 | * Copyright 2012-2013 The Dojo Foundation 3319 | * Based on Underscore.js 1.5.2 3320 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 3321 | * Available under MIT license 3322 | */ 3323 | define('lodash/objects/isArray',['../internals/isNative'], function(isNative) { 3324 | 3325 | /** `Object#toString` result shortcuts */ 3326 | var arrayClass = '[object Array]'; 3327 | 3328 | /** Used for native method references */ 3329 | var objectProto = Object.prototype; 3330 | 3331 | /** Used to resolve the internal [[Class]] of values */ 3332 | var toString = objectProto.toString; 3333 | 3334 | /* Native method shortcuts for methods with the same name as other `lodash` methods */ 3335 | var nativeIsArray = isNative(nativeIsArray = Array.isArray) && nativeIsArray; 3336 | 3337 | /** 3338 | * Checks if `value` is an array. 3339 | * 3340 | * @static 3341 | * @memberOf _ 3342 | * @type Function 3343 | * @category Objects 3344 | * @param {*} value The value to check. 3345 | * @returns {boolean} Returns `true` if the `value` is an array, else `false`. 3346 | * @example 3347 | * 3348 | * (function() { return _.isArray(arguments); })(); 3349 | * // => false 3350 | * 3351 | * _.isArray([1, 2, 3]); 3352 | * // => true 3353 | */ 3354 | var isArray = nativeIsArray || function(value) { 3355 | return value && typeof value == 'object' && typeof value.length == 'number' && 3356 | toString.call(value) == arrayClass || false; 3357 | }; 3358 | 3359 | return isArray; 3360 | }); 3361 | 3362 | /** 3363 | * Lo-Dash 2.4.1 (Custom Build) 3364 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 3365 | * Copyright 2012-2013 The Dojo Foundation 3366 | * Based on Underscore.js 1.5.2 3367 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 3368 | * Available under MIT license 3369 | */ 3370 | define('lodash/internals/maxPoolSize',[], function() { 3371 | 3372 | /** Used as the max size of the `arrayPool` and `objectPool` */ 3373 | var maxPoolSize = 40; 3374 | 3375 | return maxPoolSize; 3376 | }); 3377 | 3378 | /** 3379 | * Lo-Dash 2.4.1 (Custom Build) 3380 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 3381 | * Copyright 2012-2013 The Dojo Foundation 3382 | * Based on Underscore.js 1.5.2 3383 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 3384 | * Available under MIT license 3385 | */ 3386 | define('lodash/internals/releaseArray',['./arrayPool', './maxPoolSize'], function(arrayPool, maxPoolSize) { 3387 | 3388 | /** 3389 | * Releases the given array back to the array pool. 3390 | * 3391 | * @private 3392 | * @param {Array} [array] The array to release. 3393 | */ 3394 | function releaseArray(array) { 3395 | array.length = 0; 3396 | if (arrayPool.length < maxPoolSize) { 3397 | arrayPool.push(array); 3398 | } 3399 | } 3400 | 3401 | return releaseArray; 3402 | }); 3403 | 3404 | /** 3405 | * Lo-Dash 2.4.1 (Custom Build) 3406 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 3407 | * Copyright 2012-2013 The Dojo Foundation 3408 | * Based on Underscore.js 1.5.2 3409 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 3410 | * Available under MIT license 3411 | */ 3412 | define('lodash/internals/baseClone',['../objects/assign', '../collections/forEach', '../objects/forOwn', './getArray', '../objects/isArray', '../objects/isObject', './releaseArray', './slice'], function(assign, forEach, forOwn, getArray, isArray, isObject, releaseArray, slice) { 3413 | 3414 | /** Used to match regexp flags from their coerced string values */ 3415 | var reFlags = /\w*$/; 3416 | 3417 | /** `Object#toString` result shortcuts */ 3418 | var argsClass = '[object Arguments]', 3419 | arrayClass = '[object Array]', 3420 | boolClass = '[object Boolean]', 3421 | dateClass = '[object Date]', 3422 | funcClass = '[object Function]', 3423 | numberClass = '[object Number]', 3424 | objectClass = '[object Object]', 3425 | regexpClass = '[object RegExp]', 3426 | stringClass = '[object String]'; 3427 | 3428 | /** Used to identify object classifications that `_.clone` supports */ 3429 | var cloneableClasses = {}; 3430 | cloneableClasses[funcClass] = false; 3431 | cloneableClasses[argsClass] = cloneableClasses[arrayClass] = 3432 | cloneableClasses[boolClass] = cloneableClasses[dateClass] = 3433 | cloneableClasses[numberClass] = cloneableClasses[objectClass] = 3434 | cloneableClasses[regexpClass] = cloneableClasses[stringClass] = true; 3435 | 3436 | /** Used for native method references */ 3437 | var objectProto = Object.prototype; 3438 | 3439 | /** Used to resolve the internal [[Class]] of values */ 3440 | var toString = objectProto.toString; 3441 | 3442 | /** Native method shortcuts */ 3443 | var hasOwnProperty = objectProto.hasOwnProperty; 3444 | 3445 | /** Used to lookup a built-in constructor by [[Class]] */ 3446 | var ctorByClass = {}; 3447 | ctorByClass[arrayClass] = Array; 3448 | ctorByClass[boolClass] = Boolean; 3449 | ctorByClass[dateClass] = Date; 3450 | ctorByClass[funcClass] = Function; 3451 | ctorByClass[objectClass] = Object; 3452 | ctorByClass[numberClass] = Number; 3453 | ctorByClass[regexpClass] = RegExp; 3454 | ctorByClass[stringClass] = String; 3455 | 3456 | /** 3457 | * The base implementation of `_.clone` without argument juggling or support 3458 | * for `thisArg` binding. 3459 | * 3460 | * @private 3461 | * @param {*} value The value to clone. 3462 | * @param {boolean} [isDeep=false] Specify a deep clone. 3463 | * @param {Function} [callback] The function to customize cloning values. 3464 | * @param {Array} [stackA=[]] Tracks traversed source objects. 3465 | * @param {Array} [stackB=[]] Associates clones with source counterparts. 3466 | * @returns {*} Returns the cloned value. 3467 | */ 3468 | function baseClone(value, isDeep, callback, stackA, stackB) { 3469 | if (callback) { 3470 | var result = callback(value); 3471 | if (typeof result != 'undefined') { 3472 | return result; 3473 | } 3474 | } 3475 | // inspect [[Class]] 3476 | var isObj = isObject(value); 3477 | if (isObj) { 3478 | var className = toString.call(value); 3479 | if (!cloneableClasses[className]) { 3480 | return value; 3481 | } 3482 | var ctor = ctorByClass[className]; 3483 | switch (className) { 3484 | case boolClass: 3485 | case dateClass: 3486 | return new ctor(+value); 3487 | 3488 | case numberClass: 3489 | case stringClass: 3490 | return new ctor(value); 3491 | 3492 | case regexpClass: 3493 | result = ctor(value.source, reFlags.exec(value)); 3494 | result.lastIndex = value.lastIndex; 3495 | return result; 3496 | } 3497 | } else { 3498 | return value; 3499 | } 3500 | var isArr = isArray(value); 3501 | if (isDeep) { 3502 | // check for circular references and return corresponding clone 3503 | var initedStack = !stackA; 3504 | stackA || (stackA = getArray()); 3505 | stackB || (stackB = getArray()); 3506 | 3507 | var length = stackA.length; 3508 | while (length--) { 3509 | if (stackA[length] == value) { 3510 | return stackB[length]; 3511 | } 3512 | } 3513 | result = isArr ? ctor(value.length) : {}; 3514 | } 3515 | else { 3516 | result = isArr ? slice(value) : assign({}, value); 3517 | } 3518 | // add array properties assigned by `RegExp#exec` 3519 | if (isArr) { 3520 | if (hasOwnProperty.call(value, 'index')) { 3521 | result.index = value.index; 3522 | } 3523 | if (hasOwnProperty.call(value, 'input')) { 3524 | result.input = value.input; 3525 | } 3526 | } 3527 | // exit for shallow clone 3528 | if (!isDeep) { 3529 | return result; 3530 | } 3531 | // add the source value to the stack of traversed objects 3532 | // and associate it with its clone 3533 | stackA.push(value); 3534 | stackB.push(result); 3535 | 3536 | // recursively populate clone (susceptible to call stack limits) 3537 | (isArr ? forEach : forOwn)(value, function(objValue, key) { 3538 | result[key] = baseClone(objValue, isDeep, callback, stackA, stackB); 3539 | }); 3540 | 3541 | if (initedStack) { 3542 | releaseArray(stackA); 3543 | releaseArray(stackB); 3544 | } 3545 | return result; 3546 | } 3547 | 3548 | return baseClone; 3549 | }); 3550 | 3551 | /** 3552 | * Lo-Dash 2.4.1 (Custom Build) 3553 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 3554 | * Copyright 2012-2013 The Dojo Foundation 3555 | * Based on Underscore.js 1.5.2 3556 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 3557 | * Available under MIT license 3558 | */ 3559 | define('lodash/objects/clone',['../internals/baseClone', '../internals/baseCreateCallback'], function(baseClone, baseCreateCallback) { 3560 | 3561 | /** 3562 | * Creates a clone of `value`. If `isDeep` is `true` nested objects will also 3563 | * be cloned, otherwise they will be assigned by reference. If a callback 3564 | * is provided it will be executed to produce the cloned values. If the 3565 | * callback returns `undefined` cloning will be handled by the method instead. 3566 | * The callback is bound to `thisArg` and invoked with one argument; (value). 3567 | * 3568 | * @static 3569 | * @memberOf _ 3570 | * @category Objects 3571 | * @param {*} value The value to clone. 3572 | * @param {boolean} [isDeep=false] Specify a deep clone. 3573 | * @param {Function} [callback] The function to customize cloning values. 3574 | * @param {*} [thisArg] The `this` binding of `callback`. 3575 | * @returns {*} Returns the cloned value. 3576 | * @example 3577 | * 3578 | * var characters = [ 3579 | * { 'name': 'barney', 'age': 36 }, 3580 | * { 'name': 'fred', 'age': 40 } 3581 | * ]; 3582 | * 3583 | * var shallow = _.clone(characters); 3584 | * shallow[0] === characters[0]; 3585 | * // => true 3586 | * 3587 | * var deep = _.clone(characters, true); 3588 | * deep[0] === characters[0]; 3589 | * // => false 3590 | * 3591 | * _.mixin({ 3592 | * 'clone': _.partialRight(_.clone, function(value) { 3593 | * return _.isElement(value) ? value.cloneNode(false) : undefined; 3594 | * }) 3595 | * }); 3596 | * 3597 | * var clone = _.clone(document.body); 3598 | * clone.childNodes.length; 3599 | * // => 0 3600 | */ 3601 | function clone(value, isDeep, callback, thisArg) { 3602 | // allows working with "Collections" methods without using their `index` 3603 | // and `collection` arguments for `isDeep` and `callback` 3604 | if (typeof isDeep != 'boolean' && isDeep != null) { 3605 | thisArg = callback; 3606 | callback = isDeep; 3607 | isDeep = false; 3608 | } 3609 | return baseClone(value, isDeep, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 1)); 3610 | } 3611 | 3612 | return clone; 3613 | }); 3614 | 3615 | /** 3616 | * Lo-Dash 2.4.1 (Custom Build) 3617 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 3618 | * Copyright 2012-2013 The Dojo Foundation 3619 | * Based on Underscore.js 1.5.2 3620 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 3621 | * Available under MIT license 3622 | */ 3623 | define('lodash/objects/isEmpty',['./forOwn', './isFunction'], function(forOwn, isFunction) { 3624 | 3625 | /** `Object#toString` result shortcuts */ 3626 | var argsClass = '[object Arguments]', 3627 | arrayClass = '[object Array]', 3628 | objectClass = '[object Object]', 3629 | stringClass = '[object String]'; 3630 | 3631 | /** Used for native method references */ 3632 | var objectProto = Object.prototype; 3633 | 3634 | /** Used to resolve the internal [[Class]] of values */ 3635 | var toString = objectProto.toString; 3636 | 3637 | /** 3638 | * Checks if `value` is empty. Arrays, strings, or `arguments` objects with a 3639 | * length of `0` and objects with no own enumerable properties are considered 3640 | * "empty". 3641 | * 3642 | * @static 3643 | * @memberOf _ 3644 | * @category Objects 3645 | * @param {Array|Object|string} value The value to inspect. 3646 | * @returns {boolean} Returns `true` if the `value` is empty, else `false`. 3647 | * @example 3648 | * 3649 | * _.isEmpty([1, 2, 3]); 3650 | * // => false 3651 | * 3652 | * _.isEmpty({}); 3653 | * // => true 3654 | * 3655 | * _.isEmpty(''); 3656 | * // => true 3657 | */ 3658 | function isEmpty(value) { 3659 | var result = true; 3660 | if (!value) { 3661 | return result; 3662 | } 3663 | var className = toString.call(value), 3664 | length = value.length; 3665 | 3666 | if ((className == arrayClass || className == stringClass || className == argsClass ) || 3667 | (className == objectClass && typeof length == 'number' && isFunction(value.splice))) { 3668 | return !length; 3669 | } 3670 | forOwn(value, function() { 3671 | return (result = false); 3672 | }); 3673 | return result; 3674 | } 3675 | 3676 | return isEmpty; 3677 | }); 3678 | 3679 | define('mock',['require','exports','module','crossroads','./logger','./request_status','lodash/objects/clone','lodash/objects/isEmpty'],function (require, exports, module) {var Logger, MockBase, Router, capitalize, clone, defineRouterCallback, fakeRequest, isEmpty, modifyRequest, requestStatus; 3680 | 3681 | Router = require("crossroads"); 3682 | 3683 | Logger = require("./logger"); 3684 | 3685 | requestStatus = require("./request_status"); 3686 | 3687 | clone = require("lodash/objects/clone"); 3688 | 3689 | isEmpty = require("lodash/objects/isEmpty"); 3690 | 3691 | capitalize = function(s) { 3692 | return s[0].toUpperCase() + s.slice(1); 3693 | }; 3694 | 3695 | defineRouterCallback = function(method, callback) { 3696 | var route; 3697 | route = "/" + (method.toUpperCase()) + this.route; 3698 | return this.createdRoutes.push(Router.addRoute(route, callback)); 3699 | }; 3700 | 3701 | fakeRequest = function(method) { 3702 | return defineRouterCallback.call(this, method, (function(_this) { 3703 | return function(xhr, route_params) { 3704 | var ready_state_change; 3705 | if (xhr.fakeResponse != null) { 3706 | Logger.warn.dispatch("ALREADY_FAKED", _this, xhr); 3707 | return false; 3708 | } 3709 | xhr.fakeResponse = { 3710 | mock: _this, 3711 | isResponseSet: false, 3712 | response: null 3713 | }; 3714 | ready_state_change = function() { 3715 | var new_arguments; 3716 | if (!(requestStatus.isOpened(xhr.request) && xhr.request.sendFlag)) { 3717 | return; 3718 | } 3719 | if (xhr.fakeResponse.isResponseSet) { 3720 | return; 3721 | } 3722 | xhr.fakeResponse.isResponseSet = true; 3723 | new_arguments = [route_params]; 3724 | if (method === 'post' || method === 'put' || method === 'patch') { 3725 | new_arguments.push(JSON.parse(xhr.request.requestBody)); 3726 | } 3727 | xhr.fakeResponse.response = _this[method].apply(_this, new_arguments); 3728 | xhr.fakeResponse.response.headers = MockBase.header; 3729 | xhr.isBodyProcessed = true; 3730 | xhr.response = clone(xhr.fakeResponse.response, true); 3731 | return xhr.responseReady.dispatch(); 3732 | }; 3733 | return xhr.request.addEventListener("readystatechange", ready_state_change, false); 3734 | }; 3735 | })(this)); 3736 | }; 3737 | 3738 | modifyRequest = function(method) { 3739 | return defineRouterCallback.call(this, method, (function(_this) { 3740 | return function(xhr, route_params) { 3741 | return xhr.responseReady.add(function() { 3742 | var modifier, new_response; 3743 | xhr.processBody(); 3744 | new_response = xhr.response; 3745 | modifier = { 3746 | source: clone(xhr.response, true), 3747 | output: new_response 3748 | }; 3749 | _this["after" + (capitalize(method))](new_response); 3750 | xhr.responseModifiers = modifier; 3751 | return xhr.response = clone(modifier.output, true); 3752 | }); 3753 | }; 3754 | })(this)); 3755 | }; 3756 | 3757 | module.exports = MockBase = (function() { 3758 | MockBase.header = {}; 3759 | 3760 | function MockBase() { 3761 | this.createdRoutes = []; 3762 | this.disposed = false; 3763 | } 3764 | 3765 | MockBase.prototype.setUp = function() { 3766 | var method, supported_method, _i, _j, _len, _len1, _results; 3767 | supported_method = ['get', 'put', 'post', 'delete', 'patch']; 3768 | for (_i = 0, _len = supported_method.length; _i < _len; _i++) { 3769 | method = supported_method[_i]; 3770 | if (this[method] != null) { 3771 | fakeRequest.call(this, method); 3772 | } 3773 | } 3774 | _results = []; 3775 | for (_j = 0, _len1 = supported_method.length; _j < _len1; _j++) { 3776 | method = supported_method[_j]; 3777 | if (this["after" + (capitalize(method))] != null) { 3778 | _results.push(modifyRequest.call(this, method)); 3779 | } 3780 | } 3781 | return _results; 3782 | }; 3783 | 3784 | MockBase.prototype.dispose = function() { 3785 | var _results; 3786 | if (this.disposed) { 3787 | return; 3788 | } 3789 | this.disposed = true; 3790 | _results = []; 3791 | while (this.createdRoutes.length !== 0) { 3792 | _results.push(Router.removeRoute(this.createdRoutes.pop())); 3793 | } 3794 | return _results; 3795 | }; 3796 | 3797 | return MockBase; 3798 | 3799 | })(); 3800 | 3801 | }); 3802 | 3803 | /** 3804 | * Lo-Dash 2.4.1 (Custom Build) 3805 | * Build: `lodash modularize modern exports="amd" -o ./modern/` 3806 | * Copyright 2012-2013 The Dojo Foundation 3807 | * Based on Underscore.js 1.5.2 3808 | * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 3809 | * Available under MIT license 3810 | */ 3811 | define('lodash/functions/defer',['../objects/isFunction', '../internals/slice'], function(isFunction, slice) { 3812 | 3813 | /** Used as a safe reference for `undefined` in pre ES5 environments */ 3814 | var undefined; 3815 | 3816 | /** 3817 | * Defers executing the `func` function until the current call stack has cleared. 3818 | * Additional arguments will be provided to `func` when it is invoked. 3819 | * 3820 | * @static 3821 | * @memberOf _ 3822 | * @category Functions 3823 | * @param {Function} func The function to defer. 3824 | * @param {...*} [arg] Arguments to invoke the function with. 3825 | * @returns {number} Returns the timer id. 3826 | * @example 3827 | * 3828 | * _.defer(function(text) { console.log(text); }, 'deferred'); 3829 | * // logs 'deferred' after one or more milliseconds 3830 | */ 3831 | function defer(func) { 3832 | if (!isFunction(func)) { 3833 | throw new TypeError; 3834 | } 3835 | var args = slice(arguments, 1); 3836 | return setTimeout(function() { func.apply(undefined, args); }, 1); 3837 | } 3838 | 3839 | return defer; 3840 | }); 3841 | 3842 | define('plasticine',['require','exports','module','crossroads','signals','./mock','./logger','./request_status','./custom_sinon','lodash/functions/defer'],function (require, exports, module) {var Logger, Mock, Plasticine, Router, Signal, defer, initialize, requestStatus, sinon; 3843 | 3844 | Router = require('crossroads'); 3845 | 3846 | Signal = require('signals'); 3847 | 3848 | Mock = require('./mock'); 3849 | 3850 | Logger = require('./logger'); 3851 | 3852 | requestStatus = require('./request_status'); 3853 | 3854 | sinon = require('./custom_sinon'); 3855 | 3856 | defer = require("lodash/functions/defer"); 3857 | 3858 | Router.normalizeFn = Router.NORM_AS_OBJECT; 3859 | 3860 | Router.ignoreState = true; 3861 | 3862 | Router.greedy = true; 3863 | 3864 | initialize = function() { 3865 | var Request, getProtectFromFakingValue, protectFromFaking, protect_from_faking; 3866 | protect_from_faking = false; 3867 | protectFromFaking = function() { 3868 | return protect_from_faking = true; 3869 | }; 3870 | getProtectFromFakingValue = function() { 3871 | var out; 3872 | out = protect_from_faking; 3873 | protect_from_faking = false; 3874 | return out; 3875 | }; 3876 | Request = sinon.useFakeXMLHttpRequest(); 3877 | Request.useFilters = true; 3878 | Request.onCreate = function(request) { 3879 | return Plasticine.pendingRequests.push({ 3880 | request: request, 3881 | protectFromFaking: getProtectFromFakingValue(), 3882 | responseReady: new Signal(), 3883 | fakeResponse: null, 3884 | responseModifiers: [], 3885 | processBody: function() { 3886 | if (!this.isBodyProcessed) { 3887 | this.isBodyProcessed = true; 3888 | return this.response.body = JSON.parse(this.response.body); 3889 | } 3890 | }, 3891 | isBodyProcessed: false 3892 | }); 3893 | }; 3894 | return Request.addFilter(function(method, url) { 3895 | var ready_state_change, real_request, xhr; 3896 | xhr = Plasticine.pendingRequests.pop(); 3897 | if (xhr.protectFromFaking) { 3898 | Plasticine.realRequests.push(xhr); 3899 | return true; 3900 | } else { 3901 | if (url.charAt(0) !== '/') { 3902 | url = '/' + url; 3903 | } 3904 | Router.parse("/" + method + (url.split('?')[0]), [xhr]); 3905 | Plasticine.fakeRequests.push(xhr); 3906 | xhr.responseReady.add(function() { 3907 | if (xhr.isBodyProcessed) { 3908 | xhr.response.body = JSON.stringify(xhr.response.body); 3909 | } 3910 | return xhr.request.respond(xhr.response.status, xhr.response.headers, xhr.response.body); 3911 | }); 3912 | if (xhr.fakeResponse == null) { 3913 | protectFromFaking(); 3914 | real_request = new Request; 3915 | xhr.realResponse = { 3916 | request: real_request 3917 | }; 3918 | real_request.open(method, url, true); 3919 | xhr.request.onSend = function() { 3920 | var key, value, _ref; 3921 | _ref = xhr.request.requestHeaders; 3922 | for (key in _ref) { 3923 | value = _ref[key]; 3924 | real_request.setRequestHeader(key, value); 3925 | } 3926 | return real_request.send(xhr.request.requestBody || ''); 3927 | }; 3928 | ready_state_change = function() { 3929 | if (requestStatus.isDone(real_request)) { 3930 | xhr.response = { 3931 | status: real_request.status, 3932 | headers: real_request.requestHeaders, 3933 | body: real_request.responseText 3934 | }; 3935 | return xhr.responseReady.dispatch(); 3936 | } 3937 | }; 3938 | real_request.addEventListener("readystatechange", ready_state_change, false); 3939 | ready_state_change(); 3940 | } 3941 | return false; 3942 | } 3943 | }); 3944 | }; 3945 | 3946 | initialize(); 3947 | 3948 | module.exports = Plasticine = { 3949 | fakeRequests: [], 3950 | realRequests: [], 3951 | pendingRequests: [], 3952 | logger: Logger, 3953 | restore: function() { 3954 | Request.restore(); 3955 | Request.filters = []; 3956 | return initialize(); 3957 | }, 3958 | addMock: function(params) { 3959 | var available_params, key, model, _i, _len; 3960 | if (params == null) { 3961 | params = {}; 3962 | } 3963 | model = new Mock(); 3964 | available_params = ['route', 'get', 'put', 'post', 'patch', 'delete', 'afterGet', 'afterPut', 'afterPost', 'afterPatch', 'afterDelete']; 3965 | for (_i = 0, _len = available_params.length; _i < _len; _i++) { 3966 | key = available_params[_i]; 3967 | model[key] = params[key]; 3968 | } 3969 | model.setUp(); 3970 | return model; 3971 | } 3972 | }; 3973 | 3974 | }); 3975 | 3976 | 3977 | return require('plasticine'); 3978 | })); --------------------------------------------------------------------------------