├── app ├── assets │ ├── js │ │ └── application.js │ └── css │ │ └── application.styl ├── views │ ├── index.jade │ └── layout.jade ├── routes │ └── index.js └── app.js ├── .gitignore ├── test ├── client │ ├── lib │ │ └── jasmine-1.1.0.rc1 │ │ │ ├── jasmine_favicon.png │ │ │ ├── MIT.LICENSE │ │ │ ├── jasmine.css │ │ │ ├── jasmine-html.js │ │ │ └── jasmine.js │ ├── helpers │ │ ├── SpecHelper.js │ │ └── sinon-1.1.1.js │ └── SpecRunner.html ├── features │ ├── foo.feature │ ├── step_definitions │ │ └── sharedSteps.js │ └── support │ │ └── world.js └── app │ ├── index.test.js │ └── testHelper.js ├── log ├── development.log └── test.log ├── package.json ├── Makefile ├── server.js └── README.md /app/assets/js/application.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | TODO 2 | *.log 3 | -------------------------------------------------------------------------------- /app/views/index.jade: -------------------------------------------------------------------------------- 1 | h1= title 2 | p Welcome to #{title} -------------------------------------------------------------------------------- /app/views/layout.jade: -------------------------------------------------------------------------------- 1 | !!! 2 | html 3 | head 4 | title= title 5 | != css('application') 6 | body!= body 7 | -------------------------------------------------------------------------------- /test/client/lib/jasmine-1.1.0.rc1/jasmine_favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olivoil/NodeBDD/HEAD/test/client/lib/jasmine-1.1.0.rc1/jasmine_favicon.png -------------------------------------------------------------------------------- /app/routes/index.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * GET home page. 4 | */ 5 | 6 | exports.index = function(req, res){ 7 | res.render('index', { title: 'Express' }); 8 | }; 9 | -------------------------------------------------------------------------------- /app/assets/css/application.styl: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } -------------------------------------------------------------------------------- /test/features/foo.feature: -------------------------------------------------------------------------------- 1 | Feature: Setup Cucumber.js 2 | As a fervent adept of BDD 3 | I want to start my project with Cucumber features 4 | So that I end up with the right functionalities 5 | 6 | Scenario: Visiting the home page 7 | Given I am on the home page 8 | Then I should see "Express" 9 | -------------------------------------------------------------------------------- /test/client/helpers/SpecHelper.js: -------------------------------------------------------------------------------- 1 | beforeEach(function() { 2 | this.addMatchers({ 3 | // toBePlaying: function(expectedSong) { 4 | // var player = this.actual; 5 | // return player.currentlyPlayingSong === expectedSong && 6 | // player.isPlaying; 7 | // } 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /test/app/index.test.js: -------------------------------------------------------------------------------- 1 | var helper = require('./testHelper'); 2 | 3 | describe("routes", function(){ 4 | describe("#index", function(){ 5 | it("is served", function(done){ 6 | helper.visit('/', function(err, browser, status){ 7 | if(err) done(err); 8 | status.should.equal(200); 9 | done(); 10 | }); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /test/app/testHelper.js: -------------------------------------------------------------------------------- 1 | var server = require('../../server') 2 | , zombie = require('zombie'); 3 | 4 | var page = exports.page = function(path){ 5 | return "http://localhost:" + server.app.address().port + path 6 | }; 7 | 8 | exports.visit = function(path, callback){ 9 | zombie.visit(page(path), function(err, browser, status){ 10 | callback(err, browser, status); 11 | }); 12 | }; 13 | -------------------------------------------------------------------------------- /test/features/step_definitions/sharedSteps.js: -------------------------------------------------------------------------------- 1 | var sharedSteps = module.exports = function(){ 2 | this.World = require('../support/world'); 3 | 4 | this.Given(/^I am on the home page$/, function(next) { 5 | this.visit('/', next); 6 | }); 7 | 8 | this.Then(/^I should see "([^"]*)"$/, function(text, next) { 9 | this.browser.text('body').should.include.string(text); 10 | next(); 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /log/development.log: -------------------------------------------------------------------------------- 1 | {"level":"http","message":"Express server listening on port 3000 in development mode"} 2 | {"level":"http","message":"Express server listening on port 3000 in development mode"} 3 | {"level":"http","message":"Express server listening on port 3000 in development mode"} 4 | {"level":"http","message":"Express server listening on port 3000 in development mode"} 5 | {"level":"http","message":"Express server listening on port 3000 in development mode"} 6 | -------------------------------------------------------------------------------- /test/features/support/world.js: -------------------------------------------------------------------------------- 1 | var zombie = require('zombie') 2 | , HTML5 = require('html5') 3 | , should = require('should') 4 | , server = require('../../../server'); 5 | 6 | var World = module.exports = function(){ 7 | this.browser = new zombie.Browser({runScripts:true, debug:false, htmlParser: HTML5}); 8 | 9 | this.page = function(path){ 10 | return "http://localhost:" + server.app.address().port + path 11 | }; 12 | 13 | this.visit = function(path, callback){ 14 | this.browser.visit( this.page(path), function(err, browser, status){ 15 | callback(err, browser, status); 16 | }); 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "application-name" 3 | , "version": "0.0.1" 4 | , "private": true 5 | , "dependencies": { 6 | "express" : "2.5.1" 7 | , "connect-assets" : "2.1.5" 8 | , "jade" : ">= 0.0.1" 9 | , "stylus" : ">= 0.19.7" 10 | , "nib" : "0.3.1" 11 | , "underscore" : "" 12 | , "winston" : "" 13 | }, 14 | "devDependencies" : { 15 | "mocha" : "*" 16 | , "should" : "*" 17 | , "sinon" : "*" 18 | , "zombie" : "*" 19 | , "cucumber" : "*" 20 | , "html5" : "*" 21 | }, 22 | "scripts": { 23 | "test":"mocha" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MOCHA_REPORTER = spec 2 | UNIT_TESTS = $(shell find test/app -name "*.test.js") 3 | 4 | # vim => :map ,f :w\|!clear && make cucumber 5 | cucumber: 6 | @NODE_ENV=test ./node_modules/.bin/cucumber.js test/features \ 7 | -r test/features/step_definitions 8 | 9 | test-performance: 10 | 11 | # vim => :map ,t :w\|!clear && make spec 12 | spec: 13 | @NODE_ENV=test ./node_modules/.bin/mocha \ 14 | --require should \ 15 | --require sinon \ 16 | --globals prop \ 17 | --reporter $(MOCHA_REPORTER) \ 18 | --slow 50 \ 19 | --growl \ 20 | $(UNIT_TESTS) 21 | 22 | # vim => :map ,c :w\|!clear && make spec-client 23 | spec-client: 24 | open test/client/SpecRunner.html 25 | 26 | .PHONY: cucumber test-performance spec spec-client 27 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var app = require('./app/app') 2 | , winston = require('winston') 3 | , logger; 4 | 5 | // Logger 6 | exports.logger = logger = new (winston.Logger)({ 7 | levels: { 8 | debug : 0 9 | , http : 1 10 | }, 11 | colors : { 12 | debug : 'red' 13 | , http : 'blue' 14 | }, 15 | transports: [ 16 | new winston.transports.Console({ 17 | level : 'debug' 18 | , colorize : true 19 | , timestamp : true 20 | }) 21 | , new (winston.transports.File)({ 22 | filename : "./log/" + (process.env.NODE_ENV || 'development') + ".log" 23 | , level : 'http' 24 | }) 25 | ] 26 | }); 27 | 28 | if (process.env.NODE_ENV == 'production' || process.env.NODE_ENV == 'test'){ 29 | logger.remove(winston.transports.Console); 30 | }; 31 | 32 | // run HTTP Server 33 | exports.app = app.run({ 34 | port: parseInt(process.argv[2] || 3000, 10) 35 | , home: process.cwd() + '/app' 36 | , logger: logger 37 | }); 38 | 39 | // configure socket.io, background jobs, and other concerns here... 40 | -------------------------------------------------------------------------------- /test/client/lib/jasmine-1.1.0.rc1/MIT.LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2011 Pivotal Labs 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | 5 | var express = require('express') 6 | , routes = require('./routes') 7 | , assets = require('connect-assets') 8 | , _ = require('underscore')._ 9 | 10 | var app = module.exports = express.createServer(); 11 | 12 | app.run = function(config){ 13 | var config = _.extend({ 14 | port: 3000 15 | , home: __dirname 16 | , logger: console 17 | }, config); 18 | 19 | // Configuration 20 | app.configure(function(){ 21 | app.set('views', __dirname + '/views'); 22 | app.set('view engine', 'jade'); 23 | app.use(express.bodyParser()); 24 | app.use(express.methodOverride()); 25 | app.use(app.router); 26 | app.use(assets({src: config.home + "/assets"})); 27 | app.use(express.static(__dirname + '/public')); 28 | }); 29 | 30 | app.configure('development', function(){ 31 | app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); 32 | }); 33 | 34 | app.configure('production', function(){ 35 | app.use(express.errorHandler()); 36 | }); 37 | 38 | // Routes 39 | app.get('/', routes.index); 40 | 41 | // Listen 42 | app.listen(config.port, function(){ 43 | config.logger.http("Express server listening on port " + app.address().port + " in " + app.settings.env + " mode"); 44 | }); 45 | 46 | return app; 47 | }; 48 | -------------------------------------------------------------------------------- /test/client/SpecRunner.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Jasmine Spec Runner 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Example app showing how to setup a test environment for an express.js application 2 | 3 | It uses [cucumber.js](https://github.com/cucumber/cucumber-js) with [zombie.js](http://zombie.labnotes.org/) for integration testing, [mocha](http://visionmedia.github.com/mocha/) with [should.js](https://github.com/visionmedia/should.js) for unit testing on the server side, and [jasmine](http://pivotal.github.com/jasmine/) with [sinon](http://sinonjs.org/) for unit testing on the client side. 4 | 5 | ### Install 6 | 7 | ``` shell 8 | $ git clone git://github.com/olivoil/NodeBDD.git 9 | $ cd NodeBDD 10 | $ npm install --dev 11 | ``` 12 | ------ 13 | 14 | The `Makefile` defines commands to run the different kinds of tests: 15 | 16 | ### Run cucumber features 17 | 18 | ``` shell 19 | $ make cucumber 20 | ``` 21 | 22 | If you are using vim, you might find it helpful to map a key to run them: 23 | 24 | ``` vim 25 | :map f :w\|!clear && make cucumber 26 | ``` 27 | 28 | ### Run server-side specs 29 | 30 | ``` shell 31 | $ make spec 32 | ``` 33 | 34 | If you are using vim, you might find it helpful to map a key to run them: 35 | 36 | ``` vim 37 | :map t :w\|!clear && make spec 38 | ``` 39 | 40 | ### Run client-side specs 41 | 42 | ``` shell 43 | $ make spec-client 44 | ``` 45 | 46 | If you are using vim, you might find it helpful to map a key to run them: 47 | 48 | ``` vim 49 | :map c :w\|!make spec-client 50 | ``` 51 | 52 | ------ 53 | 54 | ### TODO 55 | 56 | + Example client-side tests, stubbing the server side with sinon 57 | + Figure out how to make should.js work in the browser, then replace jasmine by mocha 58 | + Setup performance tests 59 | + Setup test coverage reporting 60 | + cli reporting for client-side specs, maybe using Phantomjs? 61 | + Browser Interface for all test reporting 62 | 63 | -------------------------------------------------------------------------------- /log/test.log: -------------------------------------------------------------------------------- 1 | {"level":"http","message":"Express server listening on port 3000 in test mode"} 2 | {"level":"http","message":"Express server listening on port 3000 in test mode"} 3 | {"level":"http","message":"Express server listening on port 3000 in test mode"} 4 | {"level":"http","message":"Express server listening on port 3000 in test mode"} 5 | {"level":"http","message":"Express server listening on port 56392 in test mode"} 6 | {"level":"http","message":"Express server listening on port 56420 in test mode"} 7 | {"level":"http","message":"Express server listening on port 56432 in test mode"} 8 | {"level":"http","message":"Express server listening on port 56453 in test mode"} 9 | {"level":"http","message":"Express server listening on port 56471 in test mode"} 10 | {"level":"http","message":"Express server listening on port 56475 in test mode"} 11 | {"level":"http","message":"Express server listening on port 56938 in test mode"} 12 | {"level":"http","message":"Express server listening on port 57181 in test mode"} 13 | {"level":"http","message":"Express server listening on port 57204 in test mode"} 14 | {"level":"http","message":"Express server listening on port 57209 in test mode"} 15 | {"level":"http","message":"Express server listening on port 57333 in test mode"} 16 | {"level":"http","message":"Express server listening on port 57352 in test mode"} 17 | {"level":"http","message":"Express server listening on port 57391 in test mode"} 18 | {"level":"http","message":"Express server listening on port 58414 in test mode"} 19 | {"level":"http","message":"Express server listening on port 58416 in test mode"} 20 | {"level":"http","message":"Express server listening on port 58418 in test mode"} 21 | {"level":"http","message":"Express server listening on port 58420 in test mode"} 22 | {"level":"http","message":"Express server listening on port 58422 in test mode"} 23 | {"level":"http","message":"Express server listening on port 58424 in test mode"} 24 | -------------------------------------------------------------------------------- /test/client/lib/jasmine-1.1.0.rc1/jasmine.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; 3 | } 4 | 5 | 6 | .jasmine_reporter a:visited, .jasmine_reporter a { 7 | color: #303; 8 | } 9 | 10 | .jasmine_reporter a:hover, .jasmine_reporter a:active { 11 | color: blue; 12 | } 13 | 14 | .run_spec { 15 | float:right; 16 | padding-right: 5px; 17 | font-size: .8em; 18 | text-decoration: none; 19 | } 20 | 21 | .jasmine_reporter { 22 | margin: 0 5px; 23 | } 24 | 25 | .banner { 26 | color: #303; 27 | background-color: #fef; 28 | padding: 5px; 29 | } 30 | 31 | .logo { 32 | float: left; 33 | font-size: 1.1em; 34 | padding-left: 5px; 35 | } 36 | 37 | .logo .version { 38 | font-size: .6em; 39 | padding-left: 1em; 40 | } 41 | 42 | .runner.running { 43 | background-color: yellow; 44 | } 45 | 46 | 47 | .options { 48 | text-align: right; 49 | font-size: .8em; 50 | } 51 | 52 | 53 | 54 | 55 | .suite { 56 | border: 1px outset gray; 57 | margin: 5px 0; 58 | padding-left: 1em; 59 | } 60 | 61 | .suite .suite { 62 | margin: 5px; 63 | } 64 | 65 | .suite.passed { 66 | background-color: #dfd; 67 | } 68 | 69 | .suite.failed { 70 | background-color: #fdd; 71 | } 72 | 73 | .spec { 74 | margin: 5px; 75 | padding-left: 1em; 76 | clear: both; 77 | } 78 | 79 | .spec.failed, .spec.passed, .spec.skipped { 80 | padding-bottom: 5px; 81 | border: 1px solid gray; 82 | } 83 | 84 | .spec.failed { 85 | background-color: #fbb; 86 | border-color: red; 87 | } 88 | 89 | .spec.passed { 90 | background-color: #bfb; 91 | border-color: green; 92 | } 93 | 94 | .spec.skipped { 95 | background-color: #bbb; 96 | } 97 | 98 | .messages { 99 | border-left: 1px dashed gray; 100 | padding-left: 1em; 101 | padding-right: 1em; 102 | } 103 | 104 | .passed { 105 | background-color: #cfc; 106 | display: none; 107 | } 108 | 109 | .failed { 110 | background-color: #fbb; 111 | } 112 | 113 | .skipped { 114 | color: #777; 115 | background-color: #eee; 116 | display: none; 117 | } 118 | 119 | 120 | /*.resultMessage {*/ 121 | /*white-space: pre;*/ 122 | /*}*/ 123 | 124 | .resultMessage span.result { 125 | display: block; 126 | line-height: 2em; 127 | color: black; 128 | } 129 | 130 | .resultMessage .mismatch { 131 | color: black; 132 | } 133 | 134 | .stackTrace { 135 | white-space: pre; 136 | font-size: .8em; 137 | margin-left: 10px; 138 | max-height: 5em; 139 | overflow: auto; 140 | border: 1px inset red; 141 | padding: 1em; 142 | background: #eef; 143 | } 144 | 145 | .finished-at { 146 | padding-left: 1em; 147 | font-size: .6em; 148 | } 149 | 150 | .show-passed .passed, 151 | .show-skipped .skipped { 152 | display: block; 153 | } 154 | 155 | 156 | #jasmine_content { 157 | position:fixed; 158 | right: 100%; 159 | } 160 | 161 | .runner { 162 | border: 1px solid gray; 163 | display: block; 164 | margin: 5px 0; 165 | padding: 2px 0 2px 10px; 166 | } 167 | -------------------------------------------------------------------------------- /test/client/lib/jasmine-1.1.0.rc1/jasmine-html.js: -------------------------------------------------------------------------------- 1 | jasmine.TrivialReporter = function(doc) { 2 | this.document = doc || document; 3 | this.suiteDivs = {}; 4 | this.logRunningSpecs = false; 5 | }; 6 | 7 | jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { 8 | var el = document.createElement(type); 9 | 10 | for (var i = 2; i < arguments.length; i++) { 11 | var child = arguments[i]; 12 | 13 | if (typeof child === 'string') { 14 | el.appendChild(document.createTextNode(child)); 15 | } else { 16 | if (child) { el.appendChild(child); } 17 | } 18 | } 19 | 20 | for (var attr in attrs) { 21 | if (attr == "className") { 22 | el[attr] = attrs[attr]; 23 | } else { 24 | el.setAttribute(attr, attrs[attr]); 25 | } 26 | } 27 | 28 | return el; 29 | }; 30 | 31 | jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { 32 | var showPassed, showSkipped; 33 | 34 | this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' }, 35 | this.createDom('div', { className: 'banner' }, 36 | this.createDom('div', { className: 'logo' }, 37 | this.createDom('span', { className: 'title' }, "Jasmine"), 38 | this.createDom('span', { className: 'version' }, runner.env.versionString())), 39 | this.createDom('div', { className: 'options' }, 40 | "Show ", 41 | showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), 42 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), 43 | showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), 44 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") 45 | ) 46 | ), 47 | 48 | this.runnerDiv = this.createDom('div', { className: 'runner running' }, 49 | this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), 50 | this.runnerMessageSpan = this.createDom('span', {}, "Running..."), 51 | this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) 52 | ); 53 | 54 | this.document.body.appendChild(this.outerDiv); 55 | 56 | var suites = runner.suites(); 57 | for (var i = 0; i < suites.length; i++) { 58 | var suite = suites[i]; 59 | var suiteDiv = this.createDom('div', { className: 'suite' }, 60 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), 61 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); 62 | this.suiteDivs[suite.id] = suiteDiv; 63 | var parentDiv = this.outerDiv; 64 | if (suite.parentSuite) { 65 | parentDiv = this.suiteDivs[suite.parentSuite.id]; 66 | } 67 | parentDiv.appendChild(suiteDiv); 68 | } 69 | 70 | this.startedAt = new Date(); 71 | 72 | var self = this; 73 | showPassed.onclick = function(evt) { 74 | if (showPassed.checked) { 75 | self.outerDiv.className += ' show-passed'; 76 | } else { 77 | self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); 78 | } 79 | }; 80 | 81 | showSkipped.onclick = function(evt) { 82 | if (showSkipped.checked) { 83 | self.outerDiv.className += ' show-skipped'; 84 | } else { 85 | self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); 86 | } 87 | }; 88 | }; 89 | 90 | jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { 91 | var results = runner.results(); 92 | var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; 93 | this.runnerDiv.setAttribute("class", className); 94 | //do it twice for IE 95 | this.runnerDiv.setAttribute("className", className); 96 | var specs = runner.specs(); 97 | var specCount = 0; 98 | for (var i = 0; i < specs.length; i++) { 99 | if (this.specFilter(specs[i])) { 100 | specCount++; 101 | } 102 | } 103 | var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); 104 | message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; 105 | this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); 106 | 107 | this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); 108 | }; 109 | 110 | jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { 111 | var results = suite.results(); 112 | var status = results.passed() ? 'passed' : 'failed'; 113 | if (results.totalCount === 0) { // todo: change this to check results.skipped 114 | status = 'skipped'; 115 | } 116 | this.suiteDivs[suite.id].className += " " + status; 117 | }; 118 | 119 | jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { 120 | if (this.logRunningSpecs) { 121 | this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); 122 | } 123 | }; 124 | 125 | jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { 126 | var results = spec.results(); 127 | var status = results.passed() ? 'passed' : 'failed'; 128 | if (results.skipped) { 129 | status = 'skipped'; 130 | } 131 | var specDiv = this.createDom('div', { className: 'spec ' + status }, 132 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), 133 | this.createDom('a', { 134 | className: 'description', 135 | href: '?spec=' + encodeURIComponent(spec.getFullName()), 136 | title: spec.getFullName() 137 | }, spec.description)); 138 | 139 | 140 | var resultItems = results.getItems(); 141 | var messagesDiv = this.createDom('div', { className: 'messages' }); 142 | for (var i = 0; i < resultItems.length; i++) { 143 | var result = resultItems[i]; 144 | 145 | if (result.type == 'log') { 146 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); 147 | } else if (result.type == 'expect' && result.passed && !result.passed()) { 148 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 149 | 150 | if (result.trace.stack) { 151 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 152 | } 153 | } 154 | } 155 | 156 | if (messagesDiv.childNodes.length > 0) { 157 | specDiv.appendChild(messagesDiv); 158 | } 159 | 160 | this.suiteDivs[spec.suite.id].appendChild(specDiv); 161 | }; 162 | 163 | jasmine.TrivialReporter.prototype.log = function() { 164 | var console = jasmine.getGlobal().console; 165 | if (console && console.log) { 166 | if (console.log.apply) { 167 | console.log.apply(console, arguments); 168 | } else { 169 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie 170 | } 171 | } 172 | }; 173 | 174 | jasmine.TrivialReporter.prototype.getLocation = function() { 175 | return this.document.location; 176 | }; 177 | 178 | jasmine.TrivialReporter.prototype.specFilter = function(spec) { 179 | var paramMap = {}; 180 | var params = this.getLocation().search.substring(1).split('&'); 181 | for (var i = 0; i < params.length; i++) { 182 | var p = params[i].split('='); 183 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); 184 | } 185 | 186 | if (!paramMap.spec) { 187 | return true; 188 | } 189 | return spec.getFullName().indexOf(paramMap.spec) === 0; 190 | }; 191 | -------------------------------------------------------------------------------- /test/client/lib/jasmine-1.1.0.rc1/jasmine.js: -------------------------------------------------------------------------------- 1 | var isCommonJS = typeof window == "undefined"; 2 | 3 | /** 4 | * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. 5 | * 6 | * @namespace 7 | */ 8 | var jasmine = {}; 9 | if (isCommonJS) exports.jasmine = jasmine; 10 | /** 11 | * @private 12 | */ 13 | jasmine.unimplementedMethod_ = function() { 14 | throw new Error("unimplemented method"); 15 | }; 16 | 17 | /** 18 | * Use jasmine.undefined instead of undefined, since undefined is just 19 | * a plain old variable and may be redefined by somebody else. 20 | * 21 | * @private 22 | */ 23 | jasmine.undefined = jasmine.___undefined___; 24 | 25 | /** 26 | * Show diagnostic messages in the console if set to true 27 | * 28 | */ 29 | jasmine.VERBOSE = false; 30 | 31 | /** 32 | * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed. 33 | * 34 | */ 35 | jasmine.DEFAULT_UPDATE_INTERVAL = 250; 36 | 37 | /** 38 | * Default timeout interval in milliseconds for waitsFor() blocks. 39 | */ 40 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; 41 | 42 | jasmine.getGlobal = function() { 43 | function getGlobal() { 44 | return this; 45 | } 46 | 47 | return getGlobal(); 48 | }; 49 | 50 | /** 51 | * Allows for bound functions to be compared. Internal use only. 52 | * 53 | * @ignore 54 | * @private 55 | * @param base {Object} bound 'this' for the function 56 | * @param name {Function} function to find 57 | */ 58 | jasmine.bindOriginal_ = function(base, name) { 59 | var original = base[name]; 60 | if (original.apply) { 61 | return function() { 62 | return original.apply(base, arguments); 63 | }; 64 | } else { 65 | // IE support 66 | return jasmine.getGlobal()[name]; 67 | } 68 | }; 69 | 70 | jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); 71 | jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); 72 | jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); 73 | jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); 74 | 75 | jasmine.MessageResult = function(values) { 76 | this.type = 'log'; 77 | this.values = values; 78 | this.trace = new Error(); // todo: test better 79 | }; 80 | 81 | jasmine.MessageResult.prototype.toString = function() { 82 | var text = ""; 83 | for (var i = 0; i < this.values.length; i++) { 84 | if (i > 0) text += " "; 85 | if (jasmine.isString_(this.values[i])) { 86 | text += this.values[i]; 87 | } else { 88 | text += jasmine.pp(this.values[i]); 89 | } 90 | } 91 | return text; 92 | }; 93 | 94 | jasmine.ExpectationResult = function(params) { 95 | this.type = 'expect'; 96 | this.matcherName = params.matcherName; 97 | this.passed_ = params.passed; 98 | this.expected = params.expected; 99 | this.actual = params.actual; 100 | this.message = this.passed_ ? 'Passed.' : params.message; 101 | 102 | var trace = (params.trace || new Error(this.message)); 103 | this.trace = this.passed_ ? '' : trace; 104 | }; 105 | 106 | jasmine.ExpectationResult.prototype.toString = function () { 107 | return this.message; 108 | }; 109 | 110 | jasmine.ExpectationResult.prototype.passed = function () { 111 | return this.passed_; 112 | }; 113 | 114 | /** 115 | * Getter for the Jasmine environment. Ensures one gets created 116 | */ 117 | jasmine.getEnv = function() { 118 | var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); 119 | return env; 120 | }; 121 | 122 | /** 123 | * @ignore 124 | * @private 125 | * @param value 126 | * @returns {Boolean} 127 | */ 128 | jasmine.isArray_ = function(value) { 129 | return jasmine.isA_("Array", value); 130 | }; 131 | 132 | /** 133 | * @ignore 134 | * @private 135 | * @param value 136 | * @returns {Boolean} 137 | */ 138 | jasmine.isString_ = function(value) { 139 | return jasmine.isA_("String", value); 140 | }; 141 | 142 | /** 143 | * @ignore 144 | * @private 145 | * @param value 146 | * @returns {Boolean} 147 | */ 148 | jasmine.isNumber_ = function(value) { 149 | return jasmine.isA_("Number", value); 150 | }; 151 | 152 | /** 153 | * @ignore 154 | * @private 155 | * @param {String} typeName 156 | * @param value 157 | * @returns {Boolean} 158 | */ 159 | jasmine.isA_ = function(typeName, value) { 160 | return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; 161 | }; 162 | 163 | /** 164 | * Pretty printer for expecations. Takes any object and turns it into a human-readable string. 165 | * 166 | * @param value {Object} an object to be outputted 167 | * @returns {String} 168 | */ 169 | jasmine.pp = function(value) { 170 | var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); 171 | stringPrettyPrinter.format(value); 172 | return stringPrettyPrinter.string; 173 | }; 174 | 175 | /** 176 | * Returns true if the object is a DOM Node. 177 | * 178 | * @param {Object} obj object to check 179 | * @returns {Boolean} 180 | */ 181 | jasmine.isDomNode = function(obj) { 182 | return obj.nodeType > 0; 183 | }; 184 | 185 | /** 186 | * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. 187 | * 188 | * @example 189 | * // don't care about which function is passed in, as long as it's a function 190 | * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); 191 | * 192 | * @param {Class} clazz 193 | * @returns matchable object of the type clazz 194 | */ 195 | jasmine.any = function(clazz) { 196 | return new jasmine.Matchers.Any(clazz); 197 | }; 198 | 199 | /** 200 | * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. 201 | * 202 | * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine 203 | * expectation syntax. Spies can be checked if they were called or not and what the calling params were. 204 | * 205 | * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). 206 | * 207 | * Spies are torn down at the end of every spec. 208 | * 209 | * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. 210 | * 211 | * @example 212 | * // a stub 213 | * var myStub = jasmine.createSpy('myStub'); // can be used anywhere 214 | * 215 | * // spy example 216 | * var foo = { 217 | * not: function(bool) { return !bool; } 218 | * } 219 | * 220 | * // actual foo.not will not be called, execution stops 221 | * spyOn(foo, 'not'); 222 | 223 | // foo.not spied upon, execution will continue to implementation 224 | * spyOn(foo, 'not').andCallThrough(); 225 | * 226 | * // fake example 227 | * var foo = { 228 | * not: function(bool) { return !bool; } 229 | * } 230 | * 231 | * // foo.not(val) will return val 232 | * spyOn(foo, 'not').andCallFake(function(value) {return value;}); 233 | * 234 | * // mock example 235 | * foo.not(7 == 7); 236 | * expect(foo.not).toHaveBeenCalled(); 237 | * expect(foo.not).toHaveBeenCalledWith(true); 238 | * 239 | * @constructor 240 | * @see spyOn, jasmine.createSpy, jasmine.createSpyObj 241 | * @param {String} name 242 | */ 243 | jasmine.Spy = function(name) { 244 | /** 245 | * The name of the spy, if provided. 246 | */ 247 | this.identity = name || 'unknown'; 248 | /** 249 | * Is this Object a spy? 250 | */ 251 | this.isSpy = true; 252 | /** 253 | * The actual function this spy stubs. 254 | */ 255 | this.plan = function() { 256 | }; 257 | /** 258 | * Tracking of the most recent call to the spy. 259 | * @example 260 | * var mySpy = jasmine.createSpy('foo'); 261 | * mySpy(1, 2); 262 | * mySpy.mostRecentCall.args = [1, 2]; 263 | */ 264 | this.mostRecentCall = {}; 265 | 266 | /** 267 | * Holds arguments for each call to the spy, indexed by call count 268 | * @example 269 | * var mySpy = jasmine.createSpy('foo'); 270 | * mySpy(1, 2); 271 | * mySpy(7, 8); 272 | * mySpy.mostRecentCall.args = [7, 8]; 273 | * mySpy.argsForCall[0] = [1, 2]; 274 | * mySpy.argsForCall[1] = [7, 8]; 275 | */ 276 | this.argsForCall = []; 277 | this.calls = []; 278 | }; 279 | 280 | /** 281 | * Tells a spy to call through to the actual implemenatation. 282 | * 283 | * @example 284 | * var foo = { 285 | * bar: function() { // do some stuff } 286 | * } 287 | * 288 | * // defining a spy on an existing property: foo.bar 289 | * spyOn(foo, 'bar').andCallThrough(); 290 | */ 291 | jasmine.Spy.prototype.andCallThrough = function() { 292 | this.plan = this.originalValue; 293 | return this; 294 | }; 295 | 296 | /** 297 | * For setting the return value of a spy. 298 | * 299 | * @example 300 | * // defining a spy from scratch: foo() returns 'baz' 301 | * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); 302 | * 303 | * // defining a spy on an existing property: foo.bar() returns 'baz' 304 | * spyOn(foo, 'bar').andReturn('baz'); 305 | * 306 | * @param {Object} value 307 | */ 308 | jasmine.Spy.prototype.andReturn = function(value) { 309 | this.plan = function() { 310 | return value; 311 | }; 312 | return this; 313 | }; 314 | 315 | /** 316 | * For throwing an exception when a spy is called. 317 | * 318 | * @example 319 | * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' 320 | * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); 321 | * 322 | * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' 323 | * spyOn(foo, 'bar').andThrow('baz'); 324 | * 325 | * @param {String} exceptionMsg 326 | */ 327 | jasmine.Spy.prototype.andThrow = function(exceptionMsg) { 328 | this.plan = function() { 329 | throw exceptionMsg; 330 | }; 331 | return this; 332 | }; 333 | 334 | /** 335 | * Calls an alternate implementation when a spy is called. 336 | * 337 | * @example 338 | * var baz = function() { 339 | * // do some stuff, return something 340 | * } 341 | * // defining a spy from scratch: foo() calls the function baz 342 | * var foo = jasmine.createSpy('spy on foo').andCall(baz); 343 | * 344 | * // defining a spy on an existing property: foo.bar() calls an anonymnous function 345 | * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); 346 | * 347 | * @param {Function} fakeFunc 348 | */ 349 | jasmine.Spy.prototype.andCallFake = function(fakeFunc) { 350 | this.plan = fakeFunc; 351 | return this; 352 | }; 353 | 354 | /** 355 | * Resets all of a spy's the tracking variables so that it can be used again. 356 | * 357 | * @example 358 | * spyOn(foo, 'bar'); 359 | * 360 | * foo.bar(); 361 | * 362 | * expect(foo.bar.callCount).toEqual(1); 363 | * 364 | * foo.bar.reset(); 365 | * 366 | * expect(foo.bar.callCount).toEqual(0); 367 | */ 368 | jasmine.Spy.prototype.reset = function() { 369 | this.wasCalled = false; 370 | this.callCount = 0; 371 | this.argsForCall = []; 372 | this.calls = []; 373 | this.mostRecentCall = {}; 374 | }; 375 | 376 | jasmine.createSpy = function(name) { 377 | 378 | var spyObj = function() { 379 | spyObj.wasCalled = true; 380 | spyObj.callCount++; 381 | var args = jasmine.util.argsToArray(arguments); 382 | spyObj.mostRecentCall.object = this; 383 | spyObj.mostRecentCall.args = args; 384 | spyObj.argsForCall.push(args); 385 | spyObj.calls.push({object: this, args: args}); 386 | return spyObj.plan.apply(this, arguments); 387 | }; 388 | 389 | var spy = new jasmine.Spy(name); 390 | 391 | for (var prop in spy) { 392 | spyObj[prop] = spy[prop]; 393 | } 394 | 395 | spyObj.reset(); 396 | 397 | return spyObj; 398 | }; 399 | 400 | /** 401 | * Determines whether an object is a spy. 402 | * 403 | * @param {jasmine.Spy|Object} putativeSpy 404 | * @returns {Boolean} 405 | */ 406 | jasmine.isSpy = function(putativeSpy) { 407 | return putativeSpy && putativeSpy.isSpy; 408 | }; 409 | 410 | /** 411 | * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something 412 | * large in one call. 413 | * 414 | * @param {String} baseName name of spy class 415 | * @param {Array} methodNames array of names of methods to make spies 416 | */ 417 | jasmine.createSpyObj = function(baseName, methodNames) { 418 | if (!jasmine.isArray_(methodNames) || methodNames.length === 0) { 419 | throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); 420 | } 421 | var obj = {}; 422 | for (var i = 0; i < methodNames.length; i++) { 423 | obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); 424 | } 425 | return obj; 426 | }; 427 | 428 | /** 429 | * All parameters are pretty-printed and concatenated together, then written to the current spec's output. 430 | * 431 | * Be careful not to leave calls to jasmine.log in production code. 432 | */ 433 | jasmine.log = function() { 434 | var spec = jasmine.getEnv().currentSpec; 435 | spec.log.apply(spec, arguments); 436 | }; 437 | 438 | /** 439 | * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. 440 | * 441 | * @example 442 | * // spy example 443 | * var foo = { 444 | * not: function(bool) { return !bool; } 445 | * } 446 | * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops 447 | * 448 | * @see jasmine.createSpy 449 | * @param obj 450 | * @param methodName 451 | * @returns a Jasmine spy that can be chained with all spy methods 452 | */ 453 | var spyOn = function(obj, methodName) { 454 | return jasmine.getEnv().currentSpec.spyOn(obj, methodName); 455 | }; 456 | if (isCommonJS) exports.spyOn = spyOn; 457 | 458 | /** 459 | * Creates a Jasmine spec that will be added to the current suite. 460 | * 461 | * // TODO: pending tests 462 | * 463 | * @example 464 | * it('should be true', function() { 465 | * expect(true).toEqual(true); 466 | * }); 467 | * 468 | * @param {String} desc description of this specification 469 | * @param {Function} func defines the preconditions and expectations of the spec 470 | */ 471 | var it = function(desc, func) { 472 | return jasmine.getEnv().it(desc, func); 473 | }; 474 | if (isCommonJS) exports.it = it; 475 | 476 | /** 477 | * Creates a disabled Jasmine spec. 478 | * 479 | * A convenience method that allows existing specs to be disabled temporarily during development. 480 | * 481 | * @param {String} desc description of this specification 482 | * @param {Function} func defines the preconditions and expectations of the spec 483 | */ 484 | var xit = function(desc, func) { 485 | return jasmine.getEnv().xit(desc, func); 486 | }; 487 | if (isCommonJS) exports.xit = xit; 488 | 489 | /** 490 | * Starts a chain for a Jasmine expectation. 491 | * 492 | * It is passed an Object that is the actual value and should chain to one of the many 493 | * jasmine.Matchers functions. 494 | * 495 | * @param {Object} actual Actual value to test against and expected value 496 | */ 497 | var expect = function(actual) { 498 | return jasmine.getEnv().currentSpec.expect(actual); 499 | }; 500 | if (isCommonJS) exports.expect = expect; 501 | 502 | /** 503 | * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. 504 | * 505 | * @param {Function} func Function that defines part of a jasmine spec. 506 | */ 507 | var runs = function(func) { 508 | jasmine.getEnv().currentSpec.runs(func); 509 | }; 510 | if (isCommonJS) exports.runs = runs; 511 | 512 | /** 513 | * Waits a fixed time period before moving to the next block. 514 | * 515 | * @deprecated Use waitsFor() instead 516 | * @param {Number} timeout milliseconds to wait 517 | */ 518 | var waits = function(timeout) { 519 | jasmine.getEnv().currentSpec.waits(timeout); 520 | }; 521 | if (isCommonJS) exports.waits = waits; 522 | 523 | /** 524 | * Waits for the latchFunction to return true before proceeding to the next block. 525 | * 526 | * @param {Function} latchFunction 527 | * @param {String} optional_timeoutMessage 528 | * @param {Number} optional_timeout 529 | */ 530 | var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { 531 | jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); 532 | }; 533 | if (isCommonJS) exports.waitsFor = waitsFor; 534 | 535 | /** 536 | * A function that is called before each spec in a suite. 537 | * 538 | * Used for spec setup, including validating assumptions. 539 | * 540 | * @param {Function} beforeEachFunction 541 | */ 542 | var beforeEach = function(beforeEachFunction) { 543 | jasmine.getEnv().beforeEach(beforeEachFunction); 544 | }; 545 | if (isCommonJS) exports.beforeEach = beforeEach; 546 | 547 | /** 548 | * A function that is called after each spec in a suite. 549 | * 550 | * Used for restoring any state that is hijacked during spec execution. 551 | * 552 | * @param {Function} afterEachFunction 553 | */ 554 | var afterEach = function(afterEachFunction) { 555 | jasmine.getEnv().afterEach(afterEachFunction); 556 | }; 557 | if (isCommonJS) exports.afterEach = afterEach; 558 | 559 | /** 560 | * Defines a suite of specifications. 561 | * 562 | * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared 563 | * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization 564 | * of setup in some tests. 565 | * 566 | * @example 567 | * // TODO: a simple suite 568 | * 569 | * // TODO: a simple suite with a nested describe block 570 | * 571 | * @param {String} description A string, usually the class under test. 572 | * @param {Function} specDefinitions function that defines several specs. 573 | */ 574 | var describe = function(description, specDefinitions) { 575 | return jasmine.getEnv().describe(description, specDefinitions); 576 | }; 577 | if (isCommonJS) exports.describe = describe; 578 | 579 | /** 580 | * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. 581 | * 582 | * @param {String} description A string, usually the class under test. 583 | * @param {Function} specDefinitions function that defines several specs. 584 | */ 585 | var xdescribe = function(description, specDefinitions) { 586 | return jasmine.getEnv().xdescribe(description, specDefinitions); 587 | }; 588 | if (isCommonJS) exports.xdescribe = xdescribe; 589 | 590 | 591 | // Provide the XMLHttpRequest class for IE 5.x-6.x: 592 | jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { 593 | function tryIt(f) { 594 | try { 595 | return f(); 596 | } catch(e) { 597 | } 598 | return null; 599 | } 600 | 601 | var xhr = tryIt(function() { 602 | return new ActiveXObject("Msxml2.XMLHTTP.6.0"); 603 | }) || 604 | tryIt(function() { 605 | return new ActiveXObject("Msxml2.XMLHTTP.3.0"); 606 | }) || 607 | tryIt(function() { 608 | return new ActiveXObject("Msxml2.XMLHTTP"); 609 | }) || 610 | tryIt(function() { 611 | return new ActiveXObject("Microsoft.XMLHTTP"); 612 | }); 613 | 614 | if (!xhr) throw new Error("This browser does not support XMLHttpRequest."); 615 | 616 | return xhr; 617 | } : XMLHttpRequest; 618 | /** 619 | * @namespace 620 | */ 621 | jasmine.util = {}; 622 | 623 | /** 624 | * Declare that a child class inherit it's prototype from the parent class. 625 | * 626 | * @private 627 | * @param {Function} childClass 628 | * @param {Function} parentClass 629 | */ 630 | jasmine.util.inherit = function(childClass, parentClass) { 631 | /** 632 | * @private 633 | */ 634 | var subclass = function() { 635 | }; 636 | subclass.prototype = parentClass.prototype; 637 | childClass.prototype = new subclass(); 638 | }; 639 | 640 | jasmine.util.formatException = function(e) { 641 | var lineNumber; 642 | if (e.line) { 643 | lineNumber = e.line; 644 | } 645 | else if (e.lineNumber) { 646 | lineNumber = e.lineNumber; 647 | } 648 | 649 | var file; 650 | 651 | if (e.sourceURL) { 652 | file = e.sourceURL; 653 | } 654 | else if (e.fileName) { 655 | file = e.fileName; 656 | } 657 | 658 | var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); 659 | 660 | if (file && lineNumber) { 661 | message += ' in ' + file + ' (line ' + lineNumber + ')'; 662 | } 663 | 664 | return message; 665 | }; 666 | 667 | jasmine.util.htmlEscape = function(str) { 668 | if (!str) return str; 669 | return str.replace(/&/g, '&') 670 | .replace(//g, '>'); 672 | }; 673 | 674 | jasmine.util.argsToArray = function(args) { 675 | var arrayOfArgs = []; 676 | for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); 677 | return arrayOfArgs; 678 | }; 679 | 680 | jasmine.util.extend = function(destination, source) { 681 | for (var property in source) destination[property] = source[property]; 682 | return destination; 683 | }; 684 | 685 | /** 686 | * Environment for Jasmine 687 | * 688 | * @constructor 689 | */ 690 | jasmine.Env = function() { 691 | this.currentSpec = null; 692 | this.currentSuite = null; 693 | this.currentRunner_ = new jasmine.Runner(this); 694 | 695 | this.reporter = new jasmine.MultiReporter(); 696 | 697 | this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL; 698 | this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL; 699 | this.lastUpdate = 0; 700 | this.specFilter = function() { 701 | return true; 702 | }; 703 | 704 | this.nextSpecId_ = 0; 705 | this.nextSuiteId_ = 0; 706 | this.equalityTesters_ = []; 707 | 708 | // wrap matchers 709 | this.matchersClass = function() { 710 | jasmine.Matchers.apply(this, arguments); 711 | }; 712 | jasmine.util.inherit(this.matchersClass, jasmine.Matchers); 713 | 714 | jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass); 715 | }; 716 | 717 | 718 | jasmine.Env.prototype.setTimeout = jasmine.setTimeout; 719 | jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; 720 | jasmine.Env.prototype.setInterval = jasmine.setInterval; 721 | jasmine.Env.prototype.clearInterval = jasmine.clearInterval; 722 | 723 | /** 724 | * @returns an object containing jasmine version build info, if set. 725 | */ 726 | jasmine.Env.prototype.version = function () { 727 | if (jasmine.version_) { 728 | return jasmine.version_; 729 | } else { 730 | throw new Error('Version not set'); 731 | } 732 | }; 733 | 734 | /** 735 | * @returns string containing jasmine version build info, if set. 736 | */ 737 | jasmine.Env.prototype.versionString = function() { 738 | if (!jasmine.version_) { 739 | return "version unknown"; 740 | } 741 | 742 | var version = this.version(); 743 | var dotted_version = version.major + "." + version.minor + "." + version.build; 744 | if (version.rc) { 745 | dotted_version += ".rc" + version.rc; 746 | } 747 | return dotted_version + " revision " + version.revision; 748 | }; 749 | 750 | /** 751 | * @returns a sequential integer starting at 0 752 | */ 753 | jasmine.Env.prototype.nextSpecId = function () { 754 | return this.nextSpecId_++; 755 | }; 756 | 757 | /** 758 | * @returns a sequential integer starting at 0 759 | */ 760 | jasmine.Env.prototype.nextSuiteId = function () { 761 | return this.nextSuiteId_++; 762 | }; 763 | 764 | /** 765 | * Register a reporter to receive status updates from Jasmine. 766 | * @param {jasmine.Reporter} reporter An object which will receive status updates. 767 | */ 768 | jasmine.Env.prototype.addReporter = function(reporter) { 769 | this.reporter.addReporter(reporter); 770 | }; 771 | 772 | jasmine.Env.prototype.execute = function() { 773 | this.currentRunner_.execute(); 774 | }; 775 | 776 | jasmine.Env.prototype.describe = function(description, specDefinitions) { 777 | var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); 778 | 779 | var parentSuite = this.currentSuite; 780 | if (parentSuite) { 781 | parentSuite.add(suite); 782 | } else { 783 | this.currentRunner_.add(suite); 784 | } 785 | 786 | this.currentSuite = suite; 787 | 788 | var declarationError = null; 789 | try { 790 | specDefinitions.call(suite); 791 | } catch(e) { 792 | declarationError = e; 793 | } 794 | 795 | if (declarationError) { 796 | this.it("encountered a declaration exception", function() { 797 | throw declarationError; 798 | }); 799 | } 800 | 801 | this.currentSuite = parentSuite; 802 | 803 | return suite; 804 | }; 805 | 806 | jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { 807 | if (this.currentSuite) { 808 | this.currentSuite.beforeEach(beforeEachFunction); 809 | } else { 810 | this.currentRunner_.beforeEach(beforeEachFunction); 811 | } 812 | }; 813 | 814 | jasmine.Env.prototype.currentRunner = function () { 815 | return this.currentRunner_; 816 | }; 817 | 818 | jasmine.Env.prototype.afterEach = function(afterEachFunction) { 819 | if (this.currentSuite) { 820 | this.currentSuite.afterEach(afterEachFunction); 821 | } else { 822 | this.currentRunner_.afterEach(afterEachFunction); 823 | } 824 | 825 | }; 826 | 827 | jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { 828 | return { 829 | execute: function() { 830 | } 831 | }; 832 | }; 833 | 834 | jasmine.Env.prototype.it = function(description, func) { 835 | var spec = new jasmine.Spec(this, this.currentSuite, description); 836 | this.currentSuite.add(spec); 837 | this.currentSpec = spec; 838 | 839 | if (func) { 840 | spec.runs(func); 841 | } 842 | 843 | return spec; 844 | }; 845 | 846 | jasmine.Env.prototype.xit = function(desc, func) { 847 | return { 848 | id: this.nextSpecId(), 849 | runs: function() { 850 | } 851 | }; 852 | }; 853 | 854 | jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { 855 | if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { 856 | return true; 857 | } 858 | 859 | a.__Jasmine_been_here_before__ = b; 860 | b.__Jasmine_been_here_before__ = a; 861 | 862 | var hasKey = function(obj, keyName) { 863 | return obj !== null && obj[keyName] !== jasmine.undefined; 864 | }; 865 | 866 | for (var property in b) { 867 | if (!hasKey(a, property) && hasKey(b, property)) { 868 | mismatchKeys.push("expected has key '" + property + "', but missing from actual."); 869 | } 870 | } 871 | for (property in a) { 872 | if (!hasKey(b, property) && hasKey(a, property)) { 873 | mismatchKeys.push("expected missing key '" + property + "', but present in actual."); 874 | } 875 | } 876 | for (property in b) { 877 | if (property == '__Jasmine_been_here_before__') continue; 878 | if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { 879 | mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual."); 880 | } 881 | } 882 | 883 | if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { 884 | mismatchValues.push("arrays were not the same length"); 885 | } 886 | 887 | delete a.__Jasmine_been_here_before__; 888 | delete b.__Jasmine_been_here_before__; 889 | return (mismatchKeys.length === 0 && mismatchValues.length === 0); 890 | }; 891 | 892 | jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { 893 | mismatchKeys = mismatchKeys || []; 894 | mismatchValues = mismatchValues || []; 895 | 896 | for (var i = 0; i < this.equalityTesters_.length; i++) { 897 | var equalityTester = this.equalityTesters_[i]; 898 | var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); 899 | if (result !== jasmine.undefined) return result; 900 | } 901 | 902 | if (a === b) return true; 903 | 904 | if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { 905 | return (a == jasmine.undefined && b == jasmine.undefined); 906 | } 907 | 908 | if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { 909 | return a === b; 910 | } 911 | 912 | if (a instanceof Date && b instanceof Date) { 913 | return a.getTime() == b.getTime(); 914 | } 915 | 916 | if (a instanceof jasmine.Matchers.Any) { 917 | return a.matches(b); 918 | } 919 | 920 | if (b instanceof jasmine.Matchers.Any) { 921 | return b.matches(a); 922 | } 923 | 924 | if (jasmine.isString_(a) && jasmine.isString_(b)) { 925 | return (a == b); 926 | } 927 | 928 | if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { 929 | return (a == b); 930 | } 931 | 932 | if (typeof a === "object" && typeof b === "object") { 933 | return this.compareObjects_(a, b, mismatchKeys, mismatchValues); 934 | } 935 | 936 | //Straight check 937 | return (a === b); 938 | }; 939 | 940 | jasmine.Env.prototype.contains_ = function(haystack, needle) { 941 | if (jasmine.isArray_(haystack)) { 942 | for (var i = 0; i < haystack.length; i++) { 943 | if (this.equals_(haystack[i], needle)) return true; 944 | } 945 | return false; 946 | } 947 | return haystack.indexOf(needle) >= 0; 948 | }; 949 | 950 | jasmine.Env.prototype.addEqualityTester = function(equalityTester) { 951 | this.equalityTesters_.push(equalityTester); 952 | }; 953 | /** No-op base class for Jasmine reporters. 954 | * 955 | * @constructor 956 | */ 957 | jasmine.Reporter = function() { 958 | }; 959 | 960 | //noinspection JSUnusedLocalSymbols 961 | jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { 962 | }; 963 | 964 | //noinspection JSUnusedLocalSymbols 965 | jasmine.Reporter.prototype.reportRunnerResults = function(runner) { 966 | }; 967 | 968 | //noinspection JSUnusedLocalSymbols 969 | jasmine.Reporter.prototype.reportSuiteResults = function(suite) { 970 | }; 971 | 972 | //noinspection JSUnusedLocalSymbols 973 | jasmine.Reporter.prototype.reportSpecStarting = function(spec) { 974 | }; 975 | 976 | //noinspection JSUnusedLocalSymbols 977 | jasmine.Reporter.prototype.reportSpecResults = function(spec) { 978 | }; 979 | 980 | //noinspection JSUnusedLocalSymbols 981 | jasmine.Reporter.prototype.log = function(str) { 982 | }; 983 | 984 | /** 985 | * Blocks are functions with executable code that make up a spec. 986 | * 987 | * @constructor 988 | * @param {jasmine.Env} env 989 | * @param {Function} func 990 | * @param {jasmine.Spec} spec 991 | */ 992 | jasmine.Block = function(env, func, spec) { 993 | this.env = env; 994 | this.func = func; 995 | this.spec = spec; 996 | }; 997 | 998 | jasmine.Block.prototype.execute = function(onComplete) { 999 | try { 1000 | this.func.apply(this.spec); 1001 | } catch (e) { 1002 | this.spec.fail(e); 1003 | } 1004 | onComplete(); 1005 | }; 1006 | /** JavaScript API reporter. 1007 | * 1008 | * @constructor 1009 | */ 1010 | jasmine.JsApiReporter = function() { 1011 | this.started = false; 1012 | this.finished = false; 1013 | this.suites_ = []; 1014 | this.results_ = {}; 1015 | }; 1016 | 1017 | jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) { 1018 | this.started = true; 1019 | var suites = runner.topLevelSuites(); 1020 | for (var i = 0; i < suites.length; i++) { 1021 | var suite = suites[i]; 1022 | this.suites_.push(this.summarize_(suite)); 1023 | } 1024 | }; 1025 | 1026 | jasmine.JsApiReporter.prototype.suites = function() { 1027 | return this.suites_; 1028 | }; 1029 | 1030 | jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) { 1031 | var isSuite = suiteOrSpec instanceof jasmine.Suite; 1032 | var summary = { 1033 | id: suiteOrSpec.id, 1034 | name: suiteOrSpec.description, 1035 | type: isSuite ? 'suite' : 'spec', 1036 | children: [] 1037 | }; 1038 | 1039 | if (isSuite) { 1040 | var children = suiteOrSpec.children(); 1041 | for (var i = 0; i < children.length; i++) { 1042 | summary.children.push(this.summarize_(children[i])); 1043 | } 1044 | } 1045 | return summary; 1046 | }; 1047 | 1048 | jasmine.JsApiReporter.prototype.results = function() { 1049 | return this.results_; 1050 | }; 1051 | 1052 | jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) { 1053 | return this.results_[specId]; 1054 | }; 1055 | 1056 | //noinspection JSUnusedLocalSymbols 1057 | jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) { 1058 | this.finished = true; 1059 | }; 1060 | 1061 | //noinspection JSUnusedLocalSymbols 1062 | jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) { 1063 | }; 1064 | 1065 | //noinspection JSUnusedLocalSymbols 1066 | jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) { 1067 | this.results_[spec.id] = { 1068 | messages: spec.results().getItems(), 1069 | result: spec.results().failedCount > 0 ? "failed" : "passed" 1070 | }; 1071 | }; 1072 | 1073 | //noinspection JSUnusedLocalSymbols 1074 | jasmine.JsApiReporter.prototype.log = function(str) { 1075 | }; 1076 | 1077 | jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){ 1078 | var results = {}; 1079 | for (var i = 0; i < specIds.length; i++) { 1080 | var specId = specIds[i]; 1081 | results[specId] = this.summarizeResult_(this.results_[specId]); 1082 | } 1083 | return results; 1084 | }; 1085 | 1086 | jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){ 1087 | var summaryMessages = []; 1088 | var messagesLength = result.messages.length; 1089 | for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) { 1090 | var resultMessage = result.messages[messageIndex]; 1091 | summaryMessages.push({ 1092 | text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined, 1093 | passed: resultMessage.passed ? resultMessage.passed() : true, 1094 | type: resultMessage.type, 1095 | message: resultMessage.message, 1096 | trace: { 1097 | stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined 1098 | } 1099 | }); 1100 | } 1101 | 1102 | return { 1103 | result : result.result, 1104 | messages : summaryMessages 1105 | }; 1106 | }; 1107 | 1108 | /** 1109 | * @constructor 1110 | * @param {jasmine.Env} env 1111 | * @param actual 1112 | * @param {jasmine.Spec} spec 1113 | */ 1114 | jasmine.Matchers = function(env, actual, spec, opt_isNot) { 1115 | this.env = env; 1116 | this.actual = actual; 1117 | this.spec = spec; 1118 | this.isNot = opt_isNot || false; 1119 | this.reportWasCalled_ = false; 1120 | }; 1121 | 1122 | // todo: @deprecated as of Jasmine 0.11, remove soon [xw] 1123 | jasmine.Matchers.pp = function(str) { 1124 | throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); 1125 | }; 1126 | 1127 | // todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw] 1128 | jasmine.Matchers.prototype.report = function(result, failing_message, details) { 1129 | throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs"); 1130 | }; 1131 | 1132 | jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) { 1133 | for (var methodName in prototype) { 1134 | if (methodName == 'report') continue; 1135 | var orig = prototype[methodName]; 1136 | matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig); 1137 | } 1138 | }; 1139 | 1140 | jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) { 1141 | return function() { 1142 | var matcherArgs = jasmine.util.argsToArray(arguments); 1143 | var result = matcherFunction.apply(this, arguments); 1144 | 1145 | if (this.isNot) { 1146 | result = !result; 1147 | } 1148 | 1149 | if (this.reportWasCalled_) return result; 1150 | 1151 | var message; 1152 | if (!result) { 1153 | if (this.message) { 1154 | message = this.message.apply(this, arguments); 1155 | if (jasmine.isArray_(message)) { 1156 | message = message[this.isNot ? 1 : 0]; 1157 | } 1158 | } else { 1159 | var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); 1160 | message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate; 1161 | if (matcherArgs.length > 0) { 1162 | for (var i = 0; i < matcherArgs.length; i++) { 1163 | if (i > 0) message += ","; 1164 | message += " " + jasmine.pp(matcherArgs[i]); 1165 | } 1166 | } 1167 | message += "."; 1168 | } 1169 | } 1170 | var expectationResult = new jasmine.ExpectationResult({ 1171 | matcherName: matcherName, 1172 | passed: result, 1173 | expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0], 1174 | actual: this.actual, 1175 | message: message 1176 | }); 1177 | this.spec.addMatcherResult(expectationResult); 1178 | return jasmine.undefined; 1179 | }; 1180 | }; 1181 | 1182 | 1183 | 1184 | 1185 | /** 1186 | * toBe: compares the actual to the expected using === 1187 | * @param expected 1188 | */ 1189 | jasmine.Matchers.prototype.toBe = function(expected) { 1190 | return this.actual === expected; 1191 | }; 1192 | 1193 | /** 1194 | * toNotBe: compares the actual to the expected using !== 1195 | * @param expected 1196 | * @deprecated as of 1.0. Use not.toBe() instead. 1197 | */ 1198 | jasmine.Matchers.prototype.toNotBe = function(expected) { 1199 | return this.actual !== expected; 1200 | }; 1201 | 1202 | /** 1203 | * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. 1204 | * 1205 | * @param expected 1206 | */ 1207 | jasmine.Matchers.prototype.toEqual = function(expected) { 1208 | return this.env.equals_(this.actual, expected); 1209 | }; 1210 | 1211 | /** 1212 | * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual 1213 | * @param expected 1214 | * @deprecated as of 1.0. Use not.toNotEqual() instead. 1215 | */ 1216 | jasmine.Matchers.prototype.toNotEqual = function(expected) { 1217 | return !this.env.equals_(this.actual, expected); 1218 | }; 1219 | 1220 | /** 1221 | * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes 1222 | * a pattern or a String. 1223 | * 1224 | * @param expected 1225 | */ 1226 | jasmine.Matchers.prototype.toMatch = function(expected) { 1227 | return new RegExp(expected).test(this.actual); 1228 | }; 1229 | 1230 | /** 1231 | * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch 1232 | * @param expected 1233 | * @deprecated as of 1.0. Use not.toMatch() instead. 1234 | */ 1235 | jasmine.Matchers.prototype.toNotMatch = function(expected) { 1236 | return !(new RegExp(expected).test(this.actual)); 1237 | }; 1238 | 1239 | /** 1240 | * Matcher that compares the actual to jasmine.undefined. 1241 | */ 1242 | jasmine.Matchers.prototype.toBeDefined = function() { 1243 | return (this.actual !== jasmine.undefined); 1244 | }; 1245 | 1246 | /** 1247 | * Matcher that compares the actual to jasmine.undefined. 1248 | */ 1249 | jasmine.Matchers.prototype.toBeUndefined = function() { 1250 | return (this.actual === jasmine.undefined); 1251 | }; 1252 | 1253 | /** 1254 | * Matcher that compares the actual to null. 1255 | */ 1256 | jasmine.Matchers.prototype.toBeNull = function() { 1257 | return (this.actual === null); 1258 | }; 1259 | 1260 | /** 1261 | * Matcher that boolean not-nots the actual. 1262 | */ 1263 | jasmine.Matchers.prototype.toBeTruthy = function() { 1264 | return !!this.actual; 1265 | }; 1266 | 1267 | 1268 | /** 1269 | * Matcher that boolean nots the actual. 1270 | */ 1271 | jasmine.Matchers.prototype.toBeFalsy = function() { 1272 | return !this.actual; 1273 | }; 1274 | 1275 | 1276 | /** 1277 | * Matcher that checks to see if the actual, a Jasmine spy, was called. 1278 | */ 1279 | jasmine.Matchers.prototype.toHaveBeenCalled = function() { 1280 | if (arguments.length > 0) { 1281 | throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); 1282 | } 1283 | 1284 | if (!jasmine.isSpy(this.actual)) { 1285 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1286 | } 1287 | 1288 | this.message = function() { 1289 | return [ 1290 | "Expected spy " + this.actual.identity + " to have been called.", 1291 | "Expected spy " + this.actual.identity + " not to have been called." 1292 | ]; 1293 | }; 1294 | 1295 | return this.actual.wasCalled; 1296 | }; 1297 | 1298 | /** @deprecated Use expect(xxx).toHaveBeenCalled() instead */ 1299 | jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled; 1300 | 1301 | /** 1302 | * Matcher that checks to see if the actual, a Jasmine spy, was not called. 1303 | * 1304 | * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead 1305 | */ 1306 | jasmine.Matchers.prototype.wasNotCalled = function() { 1307 | if (arguments.length > 0) { 1308 | throw new Error('wasNotCalled does not take arguments'); 1309 | } 1310 | 1311 | if (!jasmine.isSpy(this.actual)) { 1312 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1313 | } 1314 | 1315 | this.message = function() { 1316 | return [ 1317 | "Expected spy " + this.actual.identity + " to not have been called.", 1318 | "Expected spy " + this.actual.identity + " to have been called." 1319 | ]; 1320 | }; 1321 | 1322 | return !this.actual.wasCalled; 1323 | }; 1324 | 1325 | /** 1326 | * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters. 1327 | * 1328 | * @example 1329 | * 1330 | */ 1331 | jasmine.Matchers.prototype.toHaveBeenCalledWith = function() { 1332 | var expectedArgs = jasmine.util.argsToArray(arguments); 1333 | if (!jasmine.isSpy(this.actual)) { 1334 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1335 | } 1336 | this.message = function() { 1337 | if (this.actual.callCount === 0) { 1338 | // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw] 1339 | return [ 1340 | "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.", 1341 | "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was." 1342 | ]; 1343 | } else { 1344 | return [ 1345 | "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall), 1346 | "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall) 1347 | ]; 1348 | } 1349 | }; 1350 | 1351 | return this.env.contains_(this.actual.argsForCall, expectedArgs); 1352 | }; 1353 | 1354 | /** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */ 1355 | jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith; 1356 | 1357 | /** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */ 1358 | jasmine.Matchers.prototype.wasNotCalledWith = function() { 1359 | var expectedArgs = jasmine.util.argsToArray(arguments); 1360 | if (!jasmine.isSpy(this.actual)) { 1361 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1362 | } 1363 | 1364 | this.message = function() { 1365 | return [ 1366 | "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was", 1367 | "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was" 1368 | ]; 1369 | }; 1370 | 1371 | return !this.env.contains_(this.actual.argsForCall, expectedArgs); 1372 | }; 1373 | 1374 | /** 1375 | * Matcher that checks that the expected item is an element in the actual Array. 1376 | * 1377 | * @param {Object} expected 1378 | */ 1379 | jasmine.Matchers.prototype.toContain = function(expected) { 1380 | return this.env.contains_(this.actual, expected); 1381 | }; 1382 | 1383 | /** 1384 | * Matcher that checks that the expected item is NOT an element in the actual Array. 1385 | * 1386 | * @param {Object} expected 1387 | * @deprecated as of 1.0. Use not.toNotContain() instead. 1388 | */ 1389 | jasmine.Matchers.prototype.toNotContain = function(expected) { 1390 | return !this.env.contains_(this.actual, expected); 1391 | }; 1392 | 1393 | jasmine.Matchers.prototype.toBeLessThan = function(expected) { 1394 | return this.actual < expected; 1395 | }; 1396 | 1397 | jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { 1398 | return this.actual > expected; 1399 | }; 1400 | 1401 | /** 1402 | * Matcher that checks that the expected item is equal to the actual item 1403 | * up to a given level of decimal precision (default 2). 1404 | * 1405 | * @param {Number} expected 1406 | * @param {Number} precision 1407 | */ 1408 | jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) { 1409 | if (!(precision === 0)) { 1410 | precision = precision || 2; 1411 | } 1412 | var multiplier = Math.pow(10, precision); 1413 | var actual = Math.round(this.actual * multiplier); 1414 | expected = Math.round(expected * multiplier); 1415 | return expected == actual; 1416 | }; 1417 | 1418 | /** 1419 | * Matcher that checks that the expected exception was thrown by the actual. 1420 | * 1421 | * @param {String} expected 1422 | */ 1423 | jasmine.Matchers.prototype.toThrow = function(expected) { 1424 | var result = false; 1425 | var exception; 1426 | if (typeof this.actual != 'function') { 1427 | throw new Error('Actual is not a function'); 1428 | } 1429 | try { 1430 | this.actual(); 1431 | } catch (e) { 1432 | exception = e; 1433 | } 1434 | if (exception) { 1435 | result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected)); 1436 | } 1437 | 1438 | var not = this.isNot ? "not " : ""; 1439 | 1440 | this.message = function() { 1441 | if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) { 1442 | return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' '); 1443 | } else { 1444 | return "Expected function to throw an exception."; 1445 | } 1446 | }; 1447 | 1448 | return result; 1449 | }; 1450 | 1451 | jasmine.Matchers.Any = function(expectedClass) { 1452 | this.expectedClass = expectedClass; 1453 | }; 1454 | 1455 | jasmine.Matchers.Any.prototype.matches = function(other) { 1456 | if (this.expectedClass == String) { 1457 | return typeof other == 'string' || other instanceof String; 1458 | } 1459 | 1460 | if (this.expectedClass == Number) { 1461 | return typeof other == 'number' || other instanceof Number; 1462 | } 1463 | 1464 | if (this.expectedClass == Function) { 1465 | return typeof other == 'function' || other instanceof Function; 1466 | } 1467 | 1468 | if (this.expectedClass == Object) { 1469 | return typeof other == 'object'; 1470 | } 1471 | 1472 | return other instanceof this.expectedClass; 1473 | }; 1474 | 1475 | jasmine.Matchers.Any.prototype.toString = function() { 1476 | return ''; 1477 | }; 1478 | 1479 | /** 1480 | * @constructor 1481 | */ 1482 | jasmine.MultiReporter = function() { 1483 | this.subReporters_ = []; 1484 | }; 1485 | jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); 1486 | 1487 | jasmine.MultiReporter.prototype.addReporter = function(reporter) { 1488 | this.subReporters_.push(reporter); 1489 | }; 1490 | 1491 | (function() { 1492 | var functionNames = [ 1493 | "reportRunnerStarting", 1494 | "reportRunnerResults", 1495 | "reportSuiteResults", 1496 | "reportSpecStarting", 1497 | "reportSpecResults", 1498 | "log" 1499 | ]; 1500 | for (var i = 0; i < functionNames.length; i++) { 1501 | var functionName = functionNames[i]; 1502 | jasmine.MultiReporter.prototype[functionName] = (function(functionName) { 1503 | return function() { 1504 | for (var j = 0; j < this.subReporters_.length; j++) { 1505 | var subReporter = this.subReporters_[j]; 1506 | if (subReporter[functionName]) { 1507 | subReporter[functionName].apply(subReporter, arguments); 1508 | } 1509 | } 1510 | }; 1511 | })(functionName); 1512 | } 1513 | })(); 1514 | /** 1515 | * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults 1516 | * 1517 | * @constructor 1518 | */ 1519 | jasmine.NestedResults = function() { 1520 | /** 1521 | * The total count of results 1522 | */ 1523 | this.totalCount = 0; 1524 | /** 1525 | * Number of passed results 1526 | */ 1527 | this.passedCount = 0; 1528 | /** 1529 | * Number of failed results 1530 | */ 1531 | this.failedCount = 0; 1532 | /** 1533 | * Was this suite/spec skipped? 1534 | */ 1535 | this.skipped = false; 1536 | /** 1537 | * @ignore 1538 | */ 1539 | this.items_ = []; 1540 | }; 1541 | 1542 | /** 1543 | * Roll up the result counts. 1544 | * 1545 | * @param result 1546 | */ 1547 | jasmine.NestedResults.prototype.rollupCounts = function(result) { 1548 | this.totalCount += result.totalCount; 1549 | this.passedCount += result.passedCount; 1550 | this.failedCount += result.failedCount; 1551 | }; 1552 | 1553 | /** 1554 | * Adds a log message. 1555 | * @param values Array of message parts which will be concatenated later. 1556 | */ 1557 | jasmine.NestedResults.prototype.log = function(values) { 1558 | this.items_.push(new jasmine.MessageResult(values)); 1559 | }; 1560 | 1561 | /** 1562 | * Getter for the results: message & results. 1563 | */ 1564 | jasmine.NestedResults.prototype.getItems = function() { 1565 | return this.items_; 1566 | }; 1567 | 1568 | /** 1569 | * Adds a result, tracking counts (total, passed, & failed) 1570 | * @param {jasmine.ExpectationResult|jasmine.NestedResults} result 1571 | */ 1572 | jasmine.NestedResults.prototype.addResult = function(result) { 1573 | if (result.type != 'log') { 1574 | if (result.items_) { 1575 | this.rollupCounts(result); 1576 | } else { 1577 | this.totalCount++; 1578 | if (result.passed()) { 1579 | this.passedCount++; 1580 | } else { 1581 | this.failedCount++; 1582 | } 1583 | } 1584 | } 1585 | this.items_.push(result); 1586 | }; 1587 | 1588 | /** 1589 | * @returns {Boolean} True if everything below passed 1590 | */ 1591 | jasmine.NestedResults.prototype.passed = function() { 1592 | return this.passedCount === this.totalCount; 1593 | }; 1594 | /** 1595 | * Base class for pretty printing for expectation results. 1596 | */ 1597 | jasmine.PrettyPrinter = function() { 1598 | this.ppNestLevel_ = 0; 1599 | }; 1600 | 1601 | /** 1602 | * Formats a value in a nice, human-readable string. 1603 | * 1604 | * @param value 1605 | */ 1606 | jasmine.PrettyPrinter.prototype.format = function(value) { 1607 | if (this.ppNestLevel_ > 40) { 1608 | throw new Error('jasmine.PrettyPrinter: format() nested too deeply!'); 1609 | } 1610 | 1611 | this.ppNestLevel_++; 1612 | try { 1613 | if (value === jasmine.undefined) { 1614 | this.emitScalar('undefined'); 1615 | } else if (value === null) { 1616 | this.emitScalar('null'); 1617 | } else if (value === jasmine.getGlobal()) { 1618 | this.emitScalar(''); 1619 | } else if (value instanceof jasmine.Matchers.Any) { 1620 | this.emitScalar(value.toString()); 1621 | } else if (typeof value === 'string') { 1622 | this.emitString(value); 1623 | } else if (jasmine.isSpy(value)) { 1624 | this.emitScalar("spy on " + value.identity); 1625 | } else if (value instanceof RegExp) { 1626 | this.emitScalar(value.toString()); 1627 | } else if (typeof value === 'function') { 1628 | this.emitScalar('Function'); 1629 | } else if (typeof value.nodeType === 'number') { 1630 | this.emitScalar('HTMLNode'); 1631 | } else if (value instanceof Date) { 1632 | this.emitScalar('Date(' + value + ')'); 1633 | } else if (value.__Jasmine_been_here_before__) { 1634 | this.emitScalar(''); 1635 | } else if (jasmine.isArray_(value) || typeof value == 'object') { 1636 | value.__Jasmine_been_here_before__ = true; 1637 | if (jasmine.isArray_(value)) { 1638 | this.emitArray(value); 1639 | } else { 1640 | this.emitObject(value); 1641 | } 1642 | delete value.__Jasmine_been_here_before__; 1643 | } else { 1644 | this.emitScalar(value.toString()); 1645 | } 1646 | } finally { 1647 | this.ppNestLevel_--; 1648 | } 1649 | }; 1650 | 1651 | jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { 1652 | for (var property in obj) { 1653 | if (property == '__Jasmine_been_here_before__') continue; 1654 | fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && 1655 | obj.__lookupGetter__(property) !== null) : false); 1656 | } 1657 | }; 1658 | 1659 | jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; 1660 | jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; 1661 | jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; 1662 | jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; 1663 | 1664 | jasmine.StringPrettyPrinter = function() { 1665 | jasmine.PrettyPrinter.call(this); 1666 | 1667 | this.string = ''; 1668 | }; 1669 | jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); 1670 | 1671 | jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { 1672 | this.append(value); 1673 | }; 1674 | 1675 | jasmine.StringPrettyPrinter.prototype.emitString = function(value) { 1676 | this.append("'" + value + "'"); 1677 | }; 1678 | 1679 | jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { 1680 | this.append('[ '); 1681 | for (var i = 0; i < array.length; i++) { 1682 | if (i > 0) { 1683 | this.append(', '); 1684 | } 1685 | this.format(array[i]); 1686 | } 1687 | this.append(' ]'); 1688 | }; 1689 | 1690 | jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { 1691 | var self = this; 1692 | this.append('{ '); 1693 | var first = true; 1694 | 1695 | this.iterateObject(obj, function(property, isGetter) { 1696 | if (first) { 1697 | first = false; 1698 | } else { 1699 | self.append(', '); 1700 | } 1701 | 1702 | self.append(property); 1703 | self.append(' : '); 1704 | if (isGetter) { 1705 | self.append(''); 1706 | } else { 1707 | self.format(obj[property]); 1708 | } 1709 | }); 1710 | 1711 | this.append(' }'); 1712 | }; 1713 | 1714 | jasmine.StringPrettyPrinter.prototype.append = function(value) { 1715 | this.string += value; 1716 | }; 1717 | jasmine.Queue = function(env) { 1718 | this.env = env; 1719 | this.blocks = []; 1720 | this.running = false; 1721 | this.index = 0; 1722 | this.offset = 0; 1723 | this.abort = false; 1724 | }; 1725 | 1726 | jasmine.Queue.prototype.addBefore = function(block) { 1727 | this.blocks.unshift(block); 1728 | }; 1729 | 1730 | jasmine.Queue.prototype.add = function(block) { 1731 | this.blocks.push(block); 1732 | }; 1733 | 1734 | jasmine.Queue.prototype.insertNext = function(block) { 1735 | this.blocks.splice((this.index + this.offset + 1), 0, block); 1736 | this.offset++; 1737 | }; 1738 | 1739 | jasmine.Queue.prototype.start = function(onComplete) { 1740 | this.running = true; 1741 | this.onComplete = onComplete; 1742 | this.next_(); 1743 | }; 1744 | 1745 | jasmine.Queue.prototype.isRunning = function() { 1746 | return this.running; 1747 | }; 1748 | 1749 | jasmine.Queue.LOOP_DONT_RECURSE = true; 1750 | 1751 | jasmine.Queue.prototype.next_ = function() { 1752 | var self = this; 1753 | var goAgain = true; 1754 | 1755 | while (goAgain) { 1756 | goAgain = false; 1757 | 1758 | if (self.index < self.blocks.length && !this.abort) { 1759 | var calledSynchronously = true; 1760 | var completedSynchronously = false; 1761 | 1762 | var onComplete = function () { 1763 | if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { 1764 | completedSynchronously = true; 1765 | return; 1766 | } 1767 | 1768 | if (self.blocks[self.index].abort) { 1769 | self.abort = true; 1770 | } 1771 | 1772 | self.offset = 0; 1773 | self.index++; 1774 | 1775 | var now = new Date().getTime(); 1776 | if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { 1777 | self.env.lastUpdate = now; 1778 | self.env.setTimeout(function() { 1779 | self.next_(); 1780 | }, 0); 1781 | } else { 1782 | if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { 1783 | goAgain = true; 1784 | } else { 1785 | self.next_(); 1786 | } 1787 | } 1788 | }; 1789 | self.blocks[self.index].execute(onComplete); 1790 | 1791 | calledSynchronously = false; 1792 | if (completedSynchronously) { 1793 | onComplete(); 1794 | } 1795 | 1796 | } else { 1797 | self.running = false; 1798 | if (self.onComplete) { 1799 | self.onComplete(); 1800 | } 1801 | } 1802 | } 1803 | }; 1804 | 1805 | jasmine.Queue.prototype.results = function() { 1806 | var results = new jasmine.NestedResults(); 1807 | for (var i = 0; i < this.blocks.length; i++) { 1808 | if (this.blocks[i].results) { 1809 | results.addResult(this.blocks[i].results()); 1810 | } 1811 | } 1812 | return results; 1813 | }; 1814 | 1815 | 1816 | /** 1817 | * Runner 1818 | * 1819 | * @constructor 1820 | * @param {jasmine.Env} env 1821 | */ 1822 | jasmine.Runner = function(env) { 1823 | var self = this; 1824 | self.env = env; 1825 | self.queue = new jasmine.Queue(env); 1826 | self.before_ = []; 1827 | self.after_ = []; 1828 | self.suites_ = []; 1829 | }; 1830 | 1831 | jasmine.Runner.prototype.execute = function() { 1832 | var self = this; 1833 | if (self.env.reporter.reportRunnerStarting) { 1834 | self.env.reporter.reportRunnerStarting(this); 1835 | } 1836 | self.queue.start(function () { 1837 | self.finishCallback(); 1838 | }); 1839 | }; 1840 | 1841 | jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { 1842 | beforeEachFunction.typeName = 'beforeEach'; 1843 | this.before_.splice(0,0,beforeEachFunction); 1844 | }; 1845 | 1846 | jasmine.Runner.prototype.afterEach = function(afterEachFunction) { 1847 | afterEachFunction.typeName = 'afterEach'; 1848 | this.after_.splice(0,0,afterEachFunction); 1849 | }; 1850 | 1851 | 1852 | jasmine.Runner.prototype.finishCallback = function() { 1853 | this.env.reporter.reportRunnerResults(this); 1854 | }; 1855 | 1856 | jasmine.Runner.prototype.addSuite = function(suite) { 1857 | this.suites_.push(suite); 1858 | }; 1859 | 1860 | jasmine.Runner.prototype.add = function(block) { 1861 | if (block instanceof jasmine.Suite) { 1862 | this.addSuite(block); 1863 | } 1864 | this.queue.add(block); 1865 | }; 1866 | 1867 | jasmine.Runner.prototype.specs = function () { 1868 | var suites = this.suites(); 1869 | var specs = []; 1870 | for (var i = 0; i < suites.length; i++) { 1871 | specs = specs.concat(suites[i].specs()); 1872 | } 1873 | return specs; 1874 | }; 1875 | 1876 | jasmine.Runner.prototype.suites = function() { 1877 | return this.suites_; 1878 | }; 1879 | 1880 | jasmine.Runner.prototype.topLevelSuites = function() { 1881 | var topLevelSuites = []; 1882 | for (var i = 0; i < this.suites_.length; i++) { 1883 | if (!this.suites_[i].parentSuite) { 1884 | topLevelSuites.push(this.suites_[i]); 1885 | } 1886 | } 1887 | return topLevelSuites; 1888 | }; 1889 | 1890 | jasmine.Runner.prototype.results = function() { 1891 | return this.queue.results(); 1892 | }; 1893 | /** 1894 | * Internal representation of a Jasmine specification, or test. 1895 | * 1896 | * @constructor 1897 | * @param {jasmine.Env} env 1898 | * @param {jasmine.Suite} suite 1899 | * @param {String} description 1900 | */ 1901 | jasmine.Spec = function(env, suite, description) { 1902 | if (!env) { 1903 | throw new Error('jasmine.Env() required'); 1904 | } 1905 | if (!suite) { 1906 | throw new Error('jasmine.Suite() required'); 1907 | } 1908 | var spec = this; 1909 | spec.id = env.nextSpecId ? env.nextSpecId() : null; 1910 | spec.env = env; 1911 | spec.suite = suite; 1912 | spec.description = description; 1913 | spec.queue = new jasmine.Queue(env); 1914 | 1915 | spec.afterCallbacks = []; 1916 | spec.spies_ = []; 1917 | 1918 | spec.results_ = new jasmine.NestedResults(); 1919 | spec.results_.description = description; 1920 | spec.matchersClass = null; 1921 | }; 1922 | 1923 | jasmine.Spec.prototype.getFullName = function() { 1924 | return this.suite.getFullName() + ' ' + this.description + '.'; 1925 | }; 1926 | 1927 | 1928 | jasmine.Spec.prototype.results = function() { 1929 | return this.results_; 1930 | }; 1931 | 1932 | /** 1933 | * All parameters are pretty-printed and concatenated together, then written to the spec's output. 1934 | * 1935 | * Be careful not to leave calls to jasmine.log in production code. 1936 | */ 1937 | jasmine.Spec.prototype.log = function() { 1938 | return this.results_.log(arguments); 1939 | }; 1940 | 1941 | jasmine.Spec.prototype.runs = function (func) { 1942 | var block = new jasmine.Block(this.env, func, this); 1943 | this.addToQueue(block); 1944 | return this; 1945 | }; 1946 | 1947 | jasmine.Spec.prototype.addToQueue = function (block) { 1948 | if (this.queue.isRunning()) { 1949 | this.queue.insertNext(block); 1950 | } else { 1951 | this.queue.add(block); 1952 | } 1953 | }; 1954 | 1955 | /** 1956 | * @param {jasmine.ExpectationResult} result 1957 | */ 1958 | jasmine.Spec.prototype.addMatcherResult = function(result) { 1959 | this.results_.addResult(result); 1960 | }; 1961 | 1962 | jasmine.Spec.prototype.expect = function(actual) { 1963 | var positive = new (this.getMatchersClass_())(this.env, actual, this); 1964 | positive.not = new (this.getMatchersClass_())(this.env, actual, this, true); 1965 | return positive; 1966 | }; 1967 | 1968 | /** 1969 | * Waits a fixed time period before moving to the next block. 1970 | * 1971 | * @deprecated Use waitsFor() instead 1972 | * @param {Number} timeout milliseconds to wait 1973 | */ 1974 | jasmine.Spec.prototype.waits = function(timeout) { 1975 | var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); 1976 | this.addToQueue(waitsFunc); 1977 | return this; 1978 | }; 1979 | 1980 | /** 1981 | * Waits for the latchFunction to return true before proceeding to the next block. 1982 | * 1983 | * @param {Function} latchFunction 1984 | * @param {String} optional_timeoutMessage 1985 | * @param {Number} optional_timeout 1986 | */ 1987 | jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { 1988 | var latchFunction_ = null; 1989 | var optional_timeoutMessage_ = null; 1990 | var optional_timeout_ = null; 1991 | 1992 | for (var i = 0; i < arguments.length; i++) { 1993 | var arg = arguments[i]; 1994 | switch (typeof arg) { 1995 | case 'function': 1996 | latchFunction_ = arg; 1997 | break; 1998 | case 'string': 1999 | optional_timeoutMessage_ = arg; 2000 | break; 2001 | case 'number': 2002 | optional_timeout_ = arg; 2003 | break; 2004 | } 2005 | } 2006 | 2007 | var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this); 2008 | this.addToQueue(waitsForFunc); 2009 | return this; 2010 | }; 2011 | 2012 | jasmine.Spec.prototype.fail = function (e) { 2013 | var expectationResult = new jasmine.ExpectationResult({ 2014 | passed: false, 2015 | message: e ? jasmine.util.formatException(e) : 'Exception', 2016 | trace: { stack: e.stack } 2017 | }); 2018 | this.results_.addResult(expectationResult); 2019 | }; 2020 | 2021 | jasmine.Spec.prototype.getMatchersClass_ = function() { 2022 | return this.matchersClass || this.env.matchersClass; 2023 | }; 2024 | 2025 | jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { 2026 | var parent = this.getMatchersClass_(); 2027 | var newMatchersClass = function() { 2028 | parent.apply(this, arguments); 2029 | }; 2030 | jasmine.util.inherit(newMatchersClass, parent); 2031 | jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass); 2032 | this.matchersClass = newMatchersClass; 2033 | }; 2034 | 2035 | jasmine.Spec.prototype.finishCallback = function() { 2036 | this.env.reporter.reportSpecResults(this); 2037 | }; 2038 | 2039 | jasmine.Spec.prototype.finish = function(onComplete) { 2040 | this.removeAllSpies(); 2041 | this.finishCallback(); 2042 | if (onComplete) { 2043 | onComplete(); 2044 | } 2045 | }; 2046 | 2047 | jasmine.Spec.prototype.after = function(doAfter) { 2048 | if (this.queue.isRunning()) { 2049 | this.queue.add(new jasmine.Block(this.env, doAfter, this)); 2050 | } else { 2051 | this.afterCallbacks.unshift(doAfter); 2052 | } 2053 | }; 2054 | 2055 | jasmine.Spec.prototype.execute = function(onComplete) { 2056 | var spec = this; 2057 | if (!spec.env.specFilter(spec)) { 2058 | spec.results_.skipped = true; 2059 | spec.finish(onComplete); 2060 | return; 2061 | } 2062 | 2063 | this.env.reporter.reportSpecStarting(this); 2064 | 2065 | spec.env.currentSpec = spec; 2066 | 2067 | spec.addBeforesAndAftersToQueue(); 2068 | 2069 | spec.queue.start(function () { 2070 | spec.finish(onComplete); 2071 | }); 2072 | }; 2073 | 2074 | jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { 2075 | var runner = this.env.currentRunner(); 2076 | var i; 2077 | 2078 | for (var suite = this.suite; suite; suite = suite.parentSuite) { 2079 | for (i = 0; i < suite.before_.length; i++) { 2080 | this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); 2081 | } 2082 | } 2083 | for (i = 0; i < runner.before_.length; i++) { 2084 | this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); 2085 | } 2086 | for (i = 0; i < this.afterCallbacks.length; i++) { 2087 | this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this)); 2088 | } 2089 | for (suite = this.suite; suite; suite = suite.parentSuite) { 2090 | for (i = 0; i < suite.after_.length; i++) { 2091 | this.queue.add(new jasmine.Block(this.env, suite.after_[i], this)); 2092 | } 2093 | } 2094 | for (i = 0; i < runner.after_.length; i++) { 2095 | this.queue.add(new jasmine.Block(this.env, runner.after_[i], this)); 2096 | } 2097 | }; 2098 | 2099 | jasmine.Spec.prototype.explodes = function() { 2100 | throw 'explodes function should not have been called'; 2101 | }; 2102 | 2103 | jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { 2104 | if (obj == jasmine.undefined) { 2105 | throw "spyOn could not find an object to spy upon for " + methodName + "()"; 2106 | } 2107 | 2108 | if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) { 2109 | throw methodName + '() method does not exist'; 2110 | } 2111 | 2112 | if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { 2113 | throw new Error(methodName + ' has already been spied upon'); 2114 | } 2115 | 2116 | var spyObj = jasmine.createSpy(methodName); 2117 | 2118 | this.spies_.push(spyObj); 2119 | spyObj.baseObj = obj; 2120 | spyObj.methodName = methodName; 2121 | spyObj.originalValue = obj[methodName]; 2122 | 2123 | obj[methodName] = spyObj; 2124 | 2125 | return spyObj; 2126 | }; 2127 | 2128 | jasmine.Spec.prototype.removeAllSpies = function() { 2129 | for (var i = 0; i < this.spies_.length; i++) { 2130 | var spy = this.spies_[i]; 2131 | spy.baseObj[spy.methodName] = spy.originalValue; 2132 | } 2133 | this.spies_ = []; 2134 | }; 2135 | 2136 | /** 2137 | * Internal representation of a Jasmine suite. 2138 | * 2139 | * @constructor 2140 | * @param {jasmine.Env} env 2141 | * @param {String} description 2142 | * @param {Function} specDefinitions 2143 | * @param {jasmine.Suite} parentSuite 2144 | */ 2145 | jasmine.Suite = function(env, description, specDefinitions, parentSuite) { 2146 | var self = this; 2147 | self.id = env.nextSuiteId ? env.nextSuiteId() : null; 2148 | self.description = description; 2149 | self.queue = new jasmine.Queue(env); 2150 | self.parentSuite = parentSuite; 2151 | self.env = env; 2152 | self.before_ = []; 2153 | self.after_ = []; 2154 | self.children_ = []; 2155 | self.suites_ = []; 2156 | self.specs_ = []; 2157 | }; 2158 | 2159 | jasmine.Suite.prototype.getFullName = function() { 2160 | var fullName = this.description; 2161 | for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { 2162 | fullName = parentSuite.description + ' ' + fullName; 2163 | } 2164 | return fullName; 2165 | }; 2166 | 2167 | jasmine.Suite.prototype.finish = function(onComplete) { 2168 | this.env.reporter.reportSuiteResults(this); 2169 | this.finished = true; 2170 | if (typeof(onComplete) == 'function') { 2171 | onComplete(); 2172 | } 2173 | }; 2174 | 2175 | jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { 2176 | beforeEachFunction.typeName = 'beforeEach'; 2177 | this.before_.unshift(beforeEachFunction); 2178 | }; 2179 | 2180 | jasmine.Suite.prototype.afterEach = function(afterEachFunction) { 2181 | afterEachFunction.typeName = 'afterEach'; 2182 | this.after_.unshift(afterEachFunction); 2183 | }; 2184 | 2185 | jasmine.Suite.prototype.results = function() { 2186 | return this.queue.results(); 2187 | }; 2188 | 2189 | jasmine.Suite.prototype.add = function(suiteOrSpec) { 2190 | this.children_.push(suiteOrSpec); 2191 | if (suiteOrSpec instanceof jasmine.Suite) { 2192 | this.suites_.push(suiteOrSpec); 2193 | this.env.currentRunner().addSuite(suiteOrSpec); 2194 | } else { 2195 | this.specs_.push(suiteOrSpec); 2196 | } 2197 | this.queue.add(suiteOrSpec); 2198 | }; 2199 | 2200 | jasmine.Suite.prototype.specs = function() { 2201 | return this.specs_; 2202 | }; 2203 | 2204 | jasmine.Suite.prototype.suites = function() { 2205 | return this.suites_; 2206 | }; 2207 | 2208 | jasmine.Suite.prototype.children = function() { 2209 | return this.children_; 2210 | }; 2211 | 2212 | jasmine.Suite.prototype.execute = function(onComplete) { 2213 | var self = this; 2214 | this.queue.start(function () { 2215 | self.finish(onComplete); 2216 | }); 2217 | }; 2218 | jasmine.WaitsBlock = function(env, timeout, spec) { 2219 | this.timeout = timeout; 2220 | jasmine.Block.call(this, env, null, spec); 2221 | }; 2222 | 2223 | jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); 2224 | 2225 | jasmine.WaitsBlock.prototype.execute = function (onComplete) { 2226 | if (jasmine.VERBOSE) { 2227 | this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); 2228 | } 2229 | this.env.setTimeout(function () { 2230 | onComplete(); 2231 | }, this.timeout); 2232 | }; 2233 | /** 2234 | * A block which waits for some condition to become true, with timeout. 2235 | * 2236 | * @constructor 2237 | * @extends jasmine.Block 2238 | * @param {jasmine.Env} env The Jasmine environment. 2239 | * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true. 2240 | * @param {Function} latchFunction A function which returns true when the desired condition has been met. 2241 | * @param {String} message The message to display if the desired condition hasn't been met within the given time period. 2242 | * @param {jasmine.Spec} spec The Jasmine spec. 2243 | */ 2244 | jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { 2245 | this.timeout = timeout || env.defaultTimeoutInterval; 2246 | this.latchFunction = latchFunction; 2247 | this.message = message; 2248 | this.totalTimeSpentWaitingForLatch = 0; 2249 | jasmine.Block.call(this, env, null, spec); 2250 | }; 2251 | jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); 2252 | 2253 | jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10; 2254 | 2255 | jasmine.WaitsForBlock.prototype.execute = function(onComplete) { 2256 | if (jasmine.VERBOSE) { 2257 | this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen')); 2258 | } 2259 | var latchFunctionResult; 2260 | try { 2261 | latchFunctionResult = this.latchFunction.apply(this.spec); 2262 | } catch (e) { 2263 | this.spec.fail(e); 2264 | onComplete(); 2265 | return; 2266 | } 2267 | 2268 | if (latchFunctionResult) { 2269 | onComplete(); 2270 | } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) { 2271 | var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen'); 2272 | this.spec.fail({ 2273 | name: 'timeout', 2274 | message: message 2275 | }); 2276 | 2277 | this.abort = true; 2278 | onComplete(); 2279 | } else { 2280 | this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; 2281 | var self = this; 2282 | this.env.setTimeout(function() { 2283 | self.execute(onComplete); 2284 | }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); 2285 | } 2286 | }; 2287 | // Mock setTimeout, clearTimeout 2288 | // Contributed by Pivotal Computer Systems, www.pivotalsf.com 2289 | 2290 | jasmine.FakeTimer = function() { 2291 | this.reset(); 2292 | 2293 | var self = this; 2294 | self.setTimeout = function(funcToCall, millis) { 2295 | self.timeoutsMade++; 2296 | self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); 2297 | return self.timeoutsMade; 2298 | }; 2299 | 2300 | self.setInterval = function(funcToCall, millis) { 2301 | self.timeoutsMade++; 2302 | self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); 2303 | return self.timeoutsMade; 2304 | }; 2305 | 2306 | self.clearTimeout = function(timeoutKey) { 2307 | self.scheduledFunctions[timeoutKey] = jasmine.undefined; 2308 | }; 2309 | 2310 | self.clearInterval = function(timeoutKey) { 2311 | self.scheduledFunctions[timeoutKey] = jasmine.undefined; 2312 | }; 2313 | 2314 | }; 2315 | 2316 | jasmine.FakeTimer.prototype.reset = function() { 2317 | this.timeoutsMade = 0; 2318 | this.scheduledFunctions = {}; 2319 | this.nowMillis = 0; 2320 | }; 2321 | 2322 | jasmine.FakeTimer.prototype.tick = function(millis) { 2323 | var oldMillis = this.nowMillis; 2324 | var newMillis = oldMillis + millis; 2325 | this.runFunctionsWithinRange(oldMillis, newMillis); 2326 | this.nowMillis = newMillis; 2327 | }; 2328 | 2329 | jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { 2330 | var scheduledFunc; 2331 | var funcsToRun = []; 2332 | for (var timeoutKey in this.scheduledFunctions) { 2333 | scheduledFunc = this.scheduledFunctions[timeoutKey]; 2334 | if (scheduledFunc != jasmine.undefined && 2335 | scheduledFunc.runAtMillis >= oldMillis && 2336 | scheduledFunc.runAtMillis <= nowMillis) { 2337 | funcsToRun.push(scheduledFunc); 2338 | this.scheduledFunctions[timeoutKey] = jasmine.undefined; 2339 | } 2340 | } 2341 | 2342 | if (funcsToRun.length > 0) { 2343 | funcsToRun.sort(function(a, b) { 2344 | return a.runAtMillis - b.runAtMillis; 2345 | }); 2346 | for (var i = 0; i < funcsToRun.length; ++i) { 2347 | try { 2348 | var funcToRun = funcsToRun[i]; 2349 | this.nowMillis = funcToRun.runAtMillis; 2350 | funcToRun.funcToCall(); 2351 | if (funcToRun.recurring) { 2352 | this.scheduleFunction(funcToRun.timeoutKey, 2353 | funcToRun.funcToCall, 2354 | funcToRun.millis, 2355 | true); 2356 | } 2357 | } catch(e) { 2358 | } 2359 | } 2360 | this.runFunctionsWithinRange(oldMillis, nowMillis); 2361 | } 2362 | }; 2363 | 2364 | jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { 2365 | this.scheduledFunctions[timeoutKey] = { 2366 | runAtMillis: this.nowMillis + millis, 2367 | funcToCall: funcToCall, 2368 | recurring: recurring, 2369 | timeoutKey: timeoutKey, 2370 | millis: millis 2371 | }; 2372 | }; 2373 | 2374 | /** 2375 | * @namespace 2376 | */ 2377 | jasmine.Clock = { 2378 | defaultFakeTimer: new jasmine.FakeTimer(), 2379 | 2380 | reset: function() { 2381 | jasmine.Clock.assertInstalled(); 2382 | jasmine.Clock.defaultFakeTimer.reset(); 2383 | }, 2384 | 2385 | tick: function(millis) { 2386 | jasmine.Clock.assertInstalled(); 2387 | jasmine.Clock.defaultFakeTimer.tick(millis); 2388 | }, 2389 | 2390 | runFunctionsWithinRange: function(oldMillis, nowMillis) { 2391 | jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); 2392 | }, 2393 | 2394 | scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { 2395 | jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); 2396 | }, 2397 | 2398 | useMock: function() { 2399 | if (!jasmine.Clock.isInstalled()) { 2400 | var spec = jasmine.getEnv().currentSpec; 2401 | spec.after(jasmine.Clock.uninstallMock); 2402 | 2403 | jasmine.Clock.installMock(); 2404 | } 2405 | }, 2406 | 2407 | installMock: function() { 2408 | jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; 2409 | }, 2410 | 2411 | uninstallMock: function() { 2412 | jasmine.Clock.assertInstalled(); 2413 | jasmine.Clock.installed = jasmine.Clock.real; 2414 | }, 2415 | 2416 | real: { 2417 | setTimeout: jasmine.getGlobal().setTimeout, 2418 | clearTimeout: jasmine.getGlobal().clearTimeout, 2419 | setInterval: jasmine.getGlobal().setInterval, 2420 | clearInterval: jasmine.getGlobal().clearInterval 2421 | }, 2422 | 2423 | assertInstalled: function() { 2424 | if (!jasmine.Clock.isInstalled()) { 2425 | throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); 2426 | } 2427 | }, 2428 | 2429 | isInstalled: function() { 2430 | return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; 2431 | }, 2432 | 2433 | installed: null 2434 | }; 2435 | jasmine.Clock.installed = jasmine.Clock.real; 2436 | 2437 | //else for IE support 2438 | jasmine.getGlobal().setTimeout = function(funcToCall, millis) { 2439 | if (jasmine.Clock.installed.setTimeout.apply) { 2440 | return jasmine.Clock.installed.setTimeout.apply(this, arguments); 2441 | } else { 2442 | return jasmine.Clock.installed.setTimeout(funcToCall, millis); 2443 | } 2444 | }; 2445 | 2446 | jasmine.getGlobal().setInterval = function(funcToCall, millis) { 2447 | if (jasmine.Clock.installed.setInterval.apply) { 2448 | return jasmine.Clock.installed.setInterval.apply(this, arguments); 2449 | } else { 2450 | return jasmine.Clock.installed.setInterval(funcToCall, millis); 2451 | } 2452 | }; 2453 | 2454 | jasmine.getGlobal().clearTimeout = function(timeoutKey) { 2455 | if (jasmine.Clock.installed.clearTimeout.apply) { 2456 | return jasmine.Clock.installed.clearTimeout.apply(this, arguments); 2457 | } else { 2458 | return jasmine.Clock.installed.clearTimeout(timeoutKey); 2459 | } 2460 | }; 2461 | 2462 | jasmine.getGlobal().clearInterval = function(timeoutKey) { 2463 | if (jasmine.Clock.installed.clearTimeout.apply) { 2464 | return jasmine.Clock.installed.clearInterval.apply(this, arguments); 2465 | } else { 2466 | return jasmine.Clock.installed.clearInterval(timeoutKey); 2467 | } 2468 | }; 2469 | 2470 | jasmine.version_= { 2471 | "major": 1, 2472 | "minor": 1, 2473 | "build": 0, 2474 | "revision": 1308187385, 2475 | "rc": 1 2476 | } 2477 | -------------------------------------------------------------------------------- /test/client/helpers/sinon-1.1.1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sinon.JS 1.1.1, 2011/05/17 3 | * 4 | * @author Christian Johansen (christian@cjohansen.no) 5 | * 6 | * (The BSD License) 7 | * 8 | * Copyright (c) 2010-2011, Christian Johansen, christian@cjohansen.no 9 | * All rights reserved. 10 | * 11 | * Redistribution and use in source and binary forms, with or without modification, 12 | * are permitted provided that the following conditions are met: 13 | * 14 | * * Redistributions of source code must retain the above copyright notice, 15 | * this list of conditions and the following disclaimer. 16 | * * Redistributions in binary form must reproduce the above copyright notice, 17 | * this list of conditions and the following disclaimer in the documentation 18 | * and/or other materials provided with the distribution. 19 | * * Neither the name of Christian Johansen nor the names of his contributors 20 | * may be used to endorse or promote products derived from this software 21 | * without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 24 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 26 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 27 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 29 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 31 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 32 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | */ 34 | 35 | "use strict"; 36 | /*jslint eqeqeq: false, onevar: false, forin: true, nomen: false, regexp: false, plusplus: false*/ 37 | /*global module, require, __dirname, document*/ 38 | /** 39 | * Sinon core utilities. For internal use only. 40 | * 41 | * @author Christian Johansen (christian@cjohansen.no) 42 | * @license BSD 43 | * 44 | * Copyright (c) 2010-2011 Christian Johansen 45 | */ 46 | 47 | var sinon = (function () { 48 | var div = typeof document != "undefined" && document.createElement("div"); 49 | 50 | function isNode(obj) { 51 | var success = false; 52 | 53 | try { 54 | obj.appendChild(div); 55 | success = div.parentNode == obj; 56 | } catch (e) { 57 | return false; 58 | } finally { 59 | try { 60 | obj.removeChild(div); 61 | } catch (e) {} 62 | } 63 | 64 | return success; 65 | } 66 | 67 | function isElement(obj) { 68 | return div && obj && obj.nodeType === 1 && isNode(obj); 69 | } 70 | 71 | return { 72 | wrapMethod: function wrapMethod(object, property, method) { 73 | if (!object) { 74 | throw new TypeError("Should wrap property of object"); 75 | } 76 | 77 | if (typeof method != "function") { 78 | throw new TypeError("Method wrapper should be function"); 79 | } 80 | 81 | var wrappedMethod = object[property]; 82 | var type = typeof wrappedMethod; 83 | 84 | if (type != "function") { 85 | throw new TypeError("Attempted to wrap " + type + " property " + property + 86 | " as function"); 87 | } 88 | 89 | if (wrappedMethod.restore && wrappedMethod.restore.sinon) { 90 | throw new TypeError("Attempted to wrap " + property + " which is already wrapped"); 91 | } 92 | 93 | if (wrappedMethod.calledBefore) { 94 | var verb = !!wrappedMethod.returns ? "stubbed" : "spied on"; 95 | throw new TypeError("Attempted to wrap " + property + " which is already " + verb); 96 | } 97 | 98 | object[property] = method; 99 | method.displayName = property; 100 | 101 | method.restore = function () { 102 | object[property] = wrappedMethod; 103 | }; 104 | 105 | method.restore.sinon = true; 106 | 107 | return method; 108 | }, 109 | 110 | extend: function extend(target) { 111 | for (var i = 1, l = arguments.length; i < l; i += 1) { 112 | for (var prop in arguments[i]) { 113 | if (arguments[i].hasOwnProperty(prop)) { 114 | target[prop] = arguments[i][prop]; 115 | } 116 | 117 | // DONT ENUM bug, only care about toString 118 | if (arguments[i].hasOwnProperty("toString") && 119 | arguments[i].toString != target.toString) { 120 | target.toString = arguments[i].toString; 121 | } 122 | } 123 | } 124 | 125 | return target; 126 | }, 127 | 128 | create: function create(proto) { 129 | var F = function () {}; 130 | F.prototype = proto; 131 | return new F(); 132 | }, 133 | 134 | deepEqual: function deepEqual(a, b) { 135 | if (typeof a != "object" || typeof b != "object") { 136 | return a === b; 137 | } 138 | 139 | if (isElement(a) || isElement(b)) { 140 | return a === b; 141 | } 142 | 143 | if (a === b) { 144 | return true; 145 | } 146 | 147 | if (Object.prototype.toString.call(a) == "[object Array]") { 148 | if (a.length !== b.length) { 149 | return false; 150 | } 151 | 152 | for (var i = 0, l = a.length; i < l; i += 1) { 153 | if (!deepEqual(a[i], b[i])) { 154 | return false; 155 | } 156 | } 157 | 158 | return true; 159 | } 160 | 161 | var prop, aLength = 0, bLength = 0; 162 | 163 | for (prop in a) { 164 | aLength += 1; 165 | 166 | if (!deepEqual(a[prop], b[prop])) { 167 | return false; 168 | } 169 | } 170 | 171 | for (prop in b) { 172 | bLength += 1; 173 | } 174 | 175 | if (aLength != bLength) { 176 | return false; 177 | } 178 | 179 | return true; 180 | }, 181 | 182 | keys: function keys(object) { 183 | var objectKeys = []; 184 | 185 | for (var prop in object) { 186 | if (object.hasOwnProperty(prop)) { 187 | objectKeys.push(prop); 188 | } 189 | } 190 | 191 | return objectKeys.sort(); 192 | }, 193 | 194 | functionName: function functionName(func) { 195 | var name = func.displayName || func.name; 196 | 197 | // Use function decomposition as a last resort to get function 198 | // name. Does not rely on function decomposition to work - if it 199 | // doesn't debugging will be slightly less informative 200 | // (i.e. toString will say 'spy' rather than 'myFunc'). 201 | if (!name) { 202 | var matches = func.toString().match(/function ([^\s\(]+)/); 203 | name = matches && matches[1]; 204 | } 205 | 206 | return name; 207 | }, 208 | 209 | functionToString: function toString() { 210 | if (this.getCall && this.callCount) { 211 | var thisValue, prop, i = this.callCount; 212 | 213 | while (i--) { 214 | thisValue = this.getCall(i).thisValue; 215 | 216 | for (prop in thisValue) { 217 | if (thisValue[prop] === this) { 218 | return prop; 219 | } 220 | } 221 | } 222 | } 223 | 224 | return this.displayName || "sinon fake"; 225 | }, 226 | 227 | getConfig: function (custom) { 228 | var config = {}; 229 | custom = custom || {}; 230 | var defaults = sinon.defaultConfig; 231 | 232 | for (var prop in defaults) { 233 | if (defaults.hasOwnProperty(prop)) { 234 | config[prop] = custom.hasOwnProperty(prop) ? custom[prop] : defaults[prop]; 235 | } 236 | } 237 | 238 | return config; 239 | }, 240 | 241 | format: function (val) { 242 | return "" + val; 243 | }, 244 | 245 | defaultConfig: { 246 | injectIntoThis: true, 247 | injectInto: null, 248 | properties: ["spy", "stub", "mock", "clock", "server", "requests"], 249 | useFakeTimers: true, 250 | useFakeServer: true 251 | } 252 | }; 253 | }()); 254 | 255 | if (typeof module == "object" && typeof require == "function") { 256 | module.exports = sinon; 257 | module.exports.spy = require("./sinon/spy"); 258 | module.exports.stub = require("./sinon/stub"); 259 | module.exports.mock = require("./sinon/mock"); 260 | module.exports.collection = require("./sinon/collection"); 261 | module.exports.assert = require("./sinon/assert"); 262 | module.exports.sandbox = require("./sinon/sandbox"); 263 | module.exports.test = require("./sinon/test"); 264 | module.exports.testCase = require("./sinon/test_case"); 265 | module.exports.assert = require("./sinon/assert"); 266 | } 267 | 268 | /* @depend ../sinon.js */ 269 | /*jslint eqeqeq: false, onevar: false, plusplus: false*/ 270 | /*global module, require, sinon*/ 271 | /** 272 | * Spy functions 273 | * 274 | * @author Christian Johansen (christian@cjohansen.no) 275 | * @license BSD 276 | * 277 | * Copyright (c) 2010-2011 Christian Johansen 278 | */ 279 | 280 | (function (sinon) { 281 | var commonJSModule = typeof module == "object" && typeof require == "function"; 282 | var spyCall; 283 | var callId = 0; 284 | 285 | if (!sinon && commonJSModule) { 286 | sinon = require("sinon"); 287 | } 288 | 289 | if (!sinon) { 290 | return; 291 | } 292 | 293 | function spy(object, property) { 294 | if (!property && typeof object == "function") { 295 | return spy.create(object); 296 | } 297 | 298 | if (!object || !property) { 299 | return spy.create(function () {}); 300 | } 301 | 302 | var method = object[property]; 303 | return sinon.wrapMethod(object, property, spy.create(method)); 304 | } 305 | 306 | sinon.extend(spy, (function () { 307 | var slice = Array.prototype.slice; 308 | 309 | function delegateToCalls(api, method, matchAny, actual) { 310 | api[method] = function () { 311 | if (!this.called) { 312 | return false; 313 | } 314 | 315 | var currentCall; 316 | var matches = 0; 317 | 318 | for (var i = 0, l = this.callCount; i < l; i += 1) { 319 | currentCall = this.getCall(i); 320 | 321 | if (currentCall[actual || method].apply(currentCall, arguments)) { 322 | matches += 1; 323 | 324 | if (matchAny) { 325 | return true; 326 | } 327 | } 328 | } 329 | 330 | return matches === this.callCount; 331 | }; 332 | } 333 | 334 | function matchingFake(fakes, args, strict) { 335 | if (!fakes) { 336 | return; 337 | } 338 | 339 | var alen = args.length; 340 | 341 | for (var i = 0, l = fakes.length; i < l; i++) { 342 | if (fakes[i].matches(args, strict)) { 343 | return fakes[i]; 344 | } 345 | } 346 | } 347 | 348 | var uuid = 0; 349 | 350 | // Public API 351 | var spyApi = { 352 | reset: function () { 353 | this.called = false; 354 | this.calledOnce = false; 355 | this.calledTwice = false; 356 | this.calledThrice = false; 357 | this.callCount = 0; 358 | this.args = []; 359 | this.returnValues = []; 360 | this.thisValues = []; 361 | this.exceptions = []; 362 | this.callIds = []; 363 | }, 364 | 365 | create: function create(func) { 366 | var name; 367 | 368 | if (typeof func != "function") { 369 | func = function () {}; 370 | } else { 371 | name = sinon.functionName(func); 372 | } 373 | 374 | function proxy() { 375 | return proxy.invoke(func, this, slice.call(arguments)); 376 | } 377 | 378 | sinon.extend(proxy, spy); 379 | delete proxy.create; 380 | sinon.extend(proxy, func); 381 | 382 | proxy.reset(); 383 | proxy.prototype = func.prototype; 384 | proxy.displayName = name || "spy"; 385 | proxy.toString = sinon.functionToString; 386 | proxy._create = sinon.spy.create; 387 | proxy.id = "spy#" + uuid++; 388 | 389 | return proxy; 390 | }, 391 | 392 | invoke: function invoke(func, thisValue, args) { 393 | var matching = matchingFake(this.fakes, args); 394 | var exception, returnValue; 395 | this.called = true; 396 | this.callCount += 1; 397 | this.calledOnce = this.callCount == 1; 398 | this.calledTwice = this.callCount == 2; 399 | this.calledThrice = this.callCount == 3; 400 | this.thisValues.push(thisValue); 401 | this.args.push(args); 402 | this.callIds.push(callId++); 403 | 404 | try { 405 | if (matching) { 406 | returnValue = matching.invoke(func, thisValue, args); 407 | } else { 408 | returnValue = (this.func || func).apply(thisValue, args); 409 | } 410 | } catch (e) { 411 | this.returnValues.push(undefined); 412 | exception = e; 413 | throw e; 414 | } finally { 415 | this.exceptions.push(exception); 416 | } 417 | 418 | this.returnValues.push(returnValue); 419 | 420 | return returnValue; 421 | }, 422 | 423 | getCall: function getCall(i) { 424 | if (i < 0 || i >= this.callCount) { 425 | return null; 426 | } 427 | 428 | return spyCall.create(this, this.thisValues[i], this.args[i], 429 | this.returnValues[i], this.exceptions[i], 430 | this.callIds[i]); 431 | }, 432 | 433 | calledBefore: function calledBefore(spyFn) { 434 | if (!this.called) { 435 | return false; 436 | } 437 | 438 | if (!spyFn.called) { 439 | return true; 440 | } 441 | 442 | return this.callIds[0] < spyFn.callIds[0]; 443 | }, 444 | 445 | calledAfter: function calledAfter(spyFn) { 446 | if (!this.called || !spyFn.called) { 447 | return false; 448 | } 449 | 450 | return this.callIds[this.callCount - 1] > spyFn.callIds[spyFn.callCount - 1]; 451 | }, 452 | 453 | withArgs: function () { 454 | var args = slice.call(arguments); 455 | 456 | if (this.fakes) { 457 | var match = matchingFake(this.fakes, args, true); 458 | 459 | if (match) { 460 | return match; 461 | } 462 | } else { 463 | this.fakes = []; 464 | } 465 | 466 | var original = this; 467 | var fake = this._create(); 468 | fake.matchingAguments = args; 469 | this.fakes.push(fake); 470 | 471 | fake.withArgs = function () { 472 | return original.withArgs.apply(original, arguments); 473 | }; 474 | 475 | return fake; 476 | }, 477 | 478 | matches: function (args, strict) { 479 | var margs = this.matchingAguments; 480 | 481 | if (margs.length <= args.length && 482 | sinon.deepEqual(margs, args.slice(0, margs.length))) { 483 | return !strict || margs.length == args.length; 484 | } 485 | } 486 | }; 487 | 488 | delegateToCalls(spyApi, "calledOn", true); 489 | delegateToCalls(spyApi, "alwaysCalledOn", false, "calledOn"); 490 | delegateToCalls(spyApi, "calledWith", true); 491 | delegateToCalls(spyApi, "alwaysCalledWith", false, "calledWith"); 492 | delegateToCalls(spyApi, "calledWithExactly", true); 493 | delegateToCalls(spyApi, "alwaysCalledWithExactly", false, "calledWithExactly"); 494 | delegateToCalls(spyApi, "threw", true); 495 | delegateToCalls(spyApi, "alwaysThrew", false, "threw"); 496 | delegateToCalls(spyApi, "returned", true); 497 | delegateToCalls(spyApi, "alwaysReturned", false, "returned"); 498 | 499 | return spyApi; 500 | }())); 501 | 502 | spyCall = (function () { 503 | return { 504 | create: function create(spy, thisValue, args, returnValue, exception, id) { 505 | var proxyCall = sinon.create(spyCall); 506 | delete proxyCall.create; 507 | proxyCall.proxy = spy; 508 | proxyCall.thisValue = thisValue; 509 | proxyCall.args = args; 510 | proxyCall.returnValue = returnValue; 511 | proxyCall.exception = exception; 512 | proxyCall.callId = typeof id == "number" && id || callId++; 513 | 514 | return proxyCall; 515 | }, 516 | 517 | calledOn: function calledOn(thisValue) { 518 | return this.thisValue === thisValue; 519 | }, 520 | 521 | calledWith: function calledWith() { 522 | for (var i = 0, l = arguments.length; i < l; i += 1) { 523 | if (!sinon.deepEqual(arguments[i], this.args[i])) { 524 | return false; 525 | } 526 | } 527 | 528 | return true; 529 | }, 530 | 531 | calledWithExactly: function calledWithExactly() { 532 | return arguments.length == this.args.length && 533 | this.calledWith.apply(this, arguments); 534 | }, 535 | 536 | returned: function returned(value) { 537 | return this.returnValue === value; 538 | }, 539 | 540 | threw: function threw(error) { 541 | if (typeof error == "undefined" || !this.exception) { 542 | return !!this.exception; 543 | } 544 | 545 | if (typeof error == "string") { 546 | return this.exception.name == error; 547 | } 548 | 549 | return this.exception === error; 550 | }, 551 | 552 | calledBefore: function (other) { 553 | return this.callId < other.callId; 554 | }, 555 | 556 | calledAfter: function (other) { 557 | return this.callId > other.callId; 558 | }, 559 | 560 | toString: function () { 561 | var callStr = this.proxy.toString() + "("; 562 | var args = []; 563 | 564 | for (var i = 0, l = this.args.length; i < l; ++i) { 565 | args.push(sinon.format(this.args[i])); 566 | } 567 | 568 | callStr = callStr + args.join(", ") + ")"; 569 | 570 | if (typeof this.returnValue != "undefined") { 571 | callStr += " => " + sinon.format(this.returnValue); 572 | } 573 | 574 | if (this.exception) { 575 | callStr += " !" + this.exception.name; 576 | 577 | if (this.exception.message) { 578 | callStr += "(" + this.exception.message + ")"; 579 | } 580 | } 581 | 582 | return callStr; 583 | } 584 | }; 585 | }()); 586 | 587 | if (commonJSModule) { 588 | module.exports = spy; 589 | } else { 590 | sinon.spy = spy; 591 | } 592 | 593 | sinon.spyCall = spyCall; 594 | }(typeof sinon == "object" && sinon || null)); 595 | 596 | /** 597 | * @depend ../sinon.js 598 | * @depend spy.js 599 | */ 600 | /*jslint eqeqeq: false, onevar: false*/ 601 | /*global module, require, sinon*/ 602 | /** 603 | * Stub functions 604 | * 605 | * @author Christian Johansen (christian@cjohansen.no) 606 | * @license BSD 607 | * 608 | * Copyright (c) 2010-2011 Christian Johansen 609 | */ 610 | 611 | (function (sinon) { 612 | var commonJSModule = typeof module == "object" && typeof require == "function"; 613 | 614 | if (!sinon && commonJSModule) { 615 | sinon = require("sinon"); 616 | } 617 | 618 | if (!sinon) { 619 | return; 620 | } 621 | 622 | function stub(object, property, func) { 623 | if (!!func && typeof func != "function") { 624 | throw new TypeError("Custom stub should be function"); 625 | } 626 | 627 | var wrapper; 628 | 629 | if (func) { 630 | wrapper = sinon.spy && sinon.spy.create ? sinon.spy.create(func) : func; 631 | } else { 632 | wrapper = stub.create(); 633 | } 634 | 635 | if (!object && !property) { 636 | return sinon.stub.create(); 637 | } 638 | 639 | if (!property && !!object && typeof object == "object") { 640 | for (var prop in object) { 641 | if (object.hasOwnProperty(prop) && typeof object[prop] == "function") { 642 | stub(object, prop); 643 | } 644 | } 645 | 646 | return object; 647 | } 648 | 649 | return sinon.wrapMethod(object, property, wrapper); 650 | } 651 | 652 | function getCallback(stub, args) { 653 | if (stub.callArgAt < 0) { 654 | for (var i = 0, l = args.length; i < l; ++i) { 655 | if (!stub.callArgProp && typeof args[i] == "function") { 656 | return args[i]; 657 | } 658 | 659 | if (stub.callArgProp && args[i] && 660 | typeof args[i][stub.callArgProp] == "function") { 661 | return args[i][stub.callArgProp]; 662 | } 663 | } 664 | 665 | return null; 666 | } 667 | 668 | return args[stub.callArgAt]; 669 | } 670 | 671 | var join = Array.prototype.join; 672 | 673 | function getCallbackError(stub, func, args) { 674 | if (stub.callArgAt < 0) { 675 | var msg; 676 | 677 | if (stub.callArgProp) { 678 | msg = sinon.functionName(stub) + 679 | " expected to yield to '" + stub.callArgProp + 680 | "', but no object with such a property was passed." 681 | } else { 682 | msg = sinon.functionName(stub) + 683 | " expected to yield, but no callback was passed." 684 | } 685 | 686 | if (args.length > 0) { 687 | msg += " Received [" + join.call(args, ", ") + "]"; 688 | } 689 | 690 | return msg; 691 | } 692 | 693 | return "argument at index " + stub.callArgAt + " is not a function: " + func; 694 | } 695 | 696 | function callCallback(stub, args) { 697 | if (typeof stub.callArgAt == "number") { 698 | var func = getCallback(stub, args); 699 | 700 | if (typeof func != "function") { 701 | throw new TypeError(getCallbackError(stub, func, args)); 702 | } 703 | 704 | func.apply(null, stub.callbackArguments); 705 | } 706 | } 707 | 708 | var uuid = 0; 709 | 710 | sinon.extend(stub, (function () { 711 | var slice = Array.prototype.slice; 712 | 713 | function throwsException(error, message) { 714 | if (typeof error == "string") { 715 | this.exception = new Error(message || ""); 716 | this.exception.name = error; 717 | } else if (!error) { 718 | this.exception = new Error("Error"); 719 | } else { 720 | this.exception = error; 721 | } 722 | 723 | return this; 724 | } 725 | 726 | return { 727 | create: function create() { 728 | var functionStub = function () { 729 | if (functionStub.exception) { 730 | throw functionStub.exception; 731 | } 732 | 733 | callCallback(functionStub, arguments); 734 | 735 | return functionStub.returnValue; 736 | }; 737 | 738 | functionStub.id = "stub#" + uuid++; 739 | var orig = functionStub; 740 | functionStub = sinon.spy.create(functionStub); 741 | functionStub.func = orig; 742 | 743 | sinon.extend(functionStub, stub); 744 | functionStub._create = sinon.stub.create; 745 | functionStub.displayName = "stub"; 746 | functionStub.toString = sinon.functionToString; 747 | 748 | return functionStub; 749 | }, 750 | 751 | returns: function returns(value) { 752 | this.returnValue = value; 753 | 754 | return this; 755 | }, 756 | 757 | "throws": throwsException, 758 | throwsException: throwsException, 759 | 760 | callsArg: function callsArg(pos) { 761 | if (typeof pos != "number") { 762 | throw new TypeError("argument index is not number"); 763 | } 764 | 765 | this.callArgAt = pos; 766 | this.callbackArguments = []; 767 | 768 | return this; 769 | }, 770 | 771 | callsArgWith: function callsArgWith(pos) { 772 | if (typeof pos != "number") { 773 | throw new TypeError("argument index is not number"); 774 | } 775 | 776 | this.callArgAt = pos; 777 | this.callbackArguments = slice.call(arguments, 1); 778 | 779 | return this; 780 | }, 781 | 782 | yields: function () { 783 | this.callArgAt = -1; 784 | this.callbackArguments = slice.call(arguments, 0); 785 | 786 | return this; 787 | }, 788 | 789 | yieldsTo: function (prop) { 790 | this.callArgAt = -1; 791 | this.callArgProp = prop; 792 | this.callbackArguments = slice.call(arguments, 1); 793 | 794 | return this; 795 | } 796 | }; 797 | }())); 798 | 799 | if (commonJSModule) { 800 | module.exports = stub; 801 | } else { 802 | sinon.stub = stub; 803 | } 804 | }(typeof sinon == "object" && sinon || null)); 805 | 806 | /** 807 | * @depend ../sinon.js 808 | * @depend stub.js 809 | */ 810 | /*jslint eqeqeq: false, onevar: false, nomen: false*/ 811 | /*global module, require, sinon*/ 812 | /** 813 | * Mock functions. 814 | * 815 | * @author Christian Johansen (christian@cjohansen.no) 816 | * @license BSD 817 | * 818 | * Copyright (c) 2010-2011 Christian Johansen 819 | */ 820 | 821 | (function (sinon) { 822 | var commonJSModule = typeof module == "object" && typeof require == "function"; 823 | 824 | if (!sinon && commonJSModule) { 825 | sinon = require("sinon"); 826 | } 827 | 828 | if (!sinon) { 829 | return; 830 | } 831 | 832 | function mock(object) { 833 | if (!object) { 834 | return sinon.expectation.create("Anonymous mock"); 835 | } 836 | 837 | return mock.create(object); 838 | } 839 | 840 | sinon.mock = mock; 841 | 842 | sinon.extend(mock, (function () { 843 | function each(collection, callback) { 844 | if (!collection) { 845 | return; 846 | } 847 | 848 | for (var i = 0, l = collection.length; i < l; i += 1) { 849 | callback(collection[i]); 850 | } 851 | } 852 | 853 | return { 854 | create: function create(object) { 855 | if (!object) { 856 | throw new TypeError("object is null"); 857 | } 858 | 859 | var mockObject = sinon.extend({}, mock); 860 | mockObject.object = object; 861 | delete mockObject.create; 862 | 863 | return mockObject; 864 | }, 865 | 866 | expects: function expects(method) { 867 | if (!method) { 868 | throw new TypeError("method is falsy"); 869 | } 870 | 871 | if (!this.expectations) { 872 | this.expectations = {}; 873 | this.proxies = []; 874 | } 875 | 876 | if (!this.expectations[method]) { 877 | this.expectations[method] = []; 878 | var mockObject = this; 879 | 880 | sinon.wrapMethod(this.object, method, function () { 881 | return mockObject.invokeMethod(method, this, arguments); 882 | }); 883 | 884 | this.proxies.push(method); 885 | } 886 | 887 | var expectation = sinon.expectation.create(method); 888 | this.expectations[method].push(expectation); 889 | 890 | return expectation; 891 | }, 892 | 893 | restore: function restore() { 894 | var object = this.object; 895 | 896 | each(this.proxies, function (proxy) { 897 | if (typeof object[proxy].restore == "function") { 898 | object[proxy].restore(); 899 | } 900 | }); 901 | }, 902 | 903 | verify: function verify() { 904 | var expectations = this.expectations || {}; 905 | var messages = [], met = []; 906 | 907 | each(this.proxies, function (proxy) { 908 | each(expectations[proxy], function (expectation) { 909 | if (!expectation.met()) { 910 | messages.push(expectation.toString()); 911 | } else { 912 | met.push(expectation.toString()); 913 | } 914 | }); 915 | }); 916 | 917 | this.restore(); 918 | 919 | if (messages.length > 0) { 920 | err(messages.concat(met).join("\n")); 921 | } 922 | 923 | return true; 924 | }, 925 | 926 | invokeMethod: function invokeMethod(method, thisValue, args) { 927 | var expectations = this.expectations && this.expectations[method]; 928 | var length = expectations && expectations.length || 0; 929 | 930 | for (var i = 0; i < length; i += 1) { 931 | if (!expectations[i].met() && 932 | expectations[i].allowsCall(thisValue, args)) { 933 | return expectations[i].apply(thisValue, args); 934 | } 935 | } 936 | 937 | var messages = []; 938 | 939 | for (i = 0; i < length; i += 1) { 940 | messages.push(" " + expectations[i].toString()); 941 | } 942 | 943 | messages.unshift("Unexpected call: " + sinon.spyCall.toString.call({ 944 | proxy: method, 945 | args: args 946 | })); 947 | 948 | err(messages.join("\n")); 949 | } 950 | }; 951 | }())); 952 | 953 | function err(message) { 954 | var exception = new Error(message); 955 | exception.name = "ExpectationError"; 956 | 957 | throw exception; 958 | } 959 | 960 | sinon.expectation = (function () { 961 | var slice = Array.prototype.slice; 962 | var _invoke = sinon.spy.invoke; 963 | 964 | function timesInWords(times) { 965 | if (times == 0) { 966 | return "never"; 967 | } else if (times == 1) { 968 | return "once"; 969 | } else if (times == 2) { 970 | return "twice"; 971 | } else if (times == 3) { 972 | return "thrice"; 973 | } 974 | 975 | return times + " times"; 976 | } 977 | 978 | function callCountInWords(callCount) { 979 | if (callCount == 0) { 980 | return "never called"; 981 | } else { 982 | return "called " + timesInWords(callCount); 983 | } 984 | } 985 | 986 | function expectedCallCountInWords(expectation) { 987 | var min = expectation.minCalls; 988 | var max = expectation.maxCalls; 989 | 990 | if (typeof min == "number" && typeof max == "number") { 991 | var str = timesInWords(min); 992 | 993 | if (min != max) { 994 | str = "at least " + str + " and at most " + timesInWords(max); 995 | } 996 | 997 | return str; 998 | } 999 | 1000 | if (typeof min == "number") { 1001 | return "at least " + timesInWords(min); 1002 | } 1003 | 1004 | return "at most " + timesInWords(max); 1005 | } 1006 | 1007 | function receivedMinCalls(expectation) { 1008 | var hasMinLimit = typeof expectation.minCalls == "number"; 1009 | return !hasMinLimit || expectation.callCount >= expectation.minCalls; 1010 | } 1011 | 1012 | function receivedMaxCalls(expectation) { 1013 | if (typeof expectation.maxCalls != "number") { 1014 | return false; 1015 | } 1016 | 1017 | return expectation.callCount == expectation.maxCalls; 1018 | } 1019 | 1020 | return { 1021 | minCalls: 1, 1022 | maxCalls: 1, 1023 | 1024 | create: function create(methodName) { 1025 | var expectation = sinon.extend(sinon.stub.create(), sinon.expectation); 1026 | delete expectation.create; 1027 | expectation.method = methodName; 1028 | 1029 | return expectation; 1030 | }, 1031 | 1032 | invoke: function invoke(func, thisValue, args) { 1033 | this.verifyCallAllowed(thisValue, args); 1034 | 1035 | return _invoke.apply(this, arguments); 1036 | }, 1037 | 1038 | atLeast: function atLeast(num) { 1039 | if (typeof num != "number") { 1040 | throw new TypeError("'" + num + "' is not number"); 1041 | } 1042 | 1043 | if (!this.limitsSet) { 1044 | this.maxCalls = null; 1045 | this.limitsSet = true; 1046 | } 1047 | 1048 | this.minCalls = num; 1049 | 1050 | return this; 1051 | }, 1052 | 1053 | atMost: function atMost(num) { 1054 | if (typeof num != "number") { 1055 | throw new TypeError("'" + num + "' is not number"); 1056 | } 1057 | 1058 | if (!this.limitsSet) { 1059 | this.minCalls = null; 1060 | this.limitsSet = true; 1061 | } 1062 | 1063 | this.maxCalls = num; 1064 | 1065 | return this; 1066 | }, 1067 | 1068 | never: function never() { 1069 | return this.exactly(0); 1070 | }, 1071 | 1072 | once: function once() { 1073 | return this.exactly(1); 1074 | }, 1075 | 1076 | twice: function twice() { 1077 | return this.exactly(2); 1078 | }, 1079 | 1080 | thrice: function thrice() { 1081 | return this.exactly(3); 1082 | }, 1083 | 1084 | exactly: function exactly(num) { 1085 | if (typeof num != "number") { 1086 | throw new TypeError("'" + num + "' is not a number"); 1087 | } 1088 | 1089 | this.atLeast(num); 1090 | return this.atMost(num); 1091 | }, 1092 | 1093 | met: function met() { 1094 | return !this.failed && receivedMinCalls(this); 1095 | }, 1096 | 1097 | verifyCallAllowed: function verifyCallAllowed(thisValue, args) { 1098 | if (receivedMaxCalls(this)) { 1099 | this.failed = true; 1100 | err(this.method + " already called " + timesInWords(this.maxCalls)); 1101 | } 1102 | 1103 | if ("expectedThis" in this && this.expectedThis !== thisValue) { 1104 | err(this.method + " called with " + thisValue + " as thisValue, expected " + 1105 | this.expectedThis); 1106 | } 1107 | 1108 | if (!("expectedArguments" in this)) { 1109 | return; 1110 | } 1111 | 1112 | if (!args || args.length === 0) { 1113 | err(this.method + " received no arguments, expected " + 1114 | this.expectedArguments.join()); 1115 | } 1116 | 1117 | if (args.length < this.expectedArguments.length) { 1118 | err(this.method + " received too few arguments (" + args.join() + 1119 | "), expected " + this.expectedArguments.join()); 1120 | } 1121 | 1122 | if (this.expectsExactArgCount && 1123 | args.length != this.expectedArguments.length) { 1124 | err(this.method + " received too many arguments (" + args.join() + 1125 | "), expected " + this.expectedArguments.join()); 1126 | } 1127 | 1128 | for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) { 1129 | if (!sinon.deepEqual(this.expectedArguments[i], args[i])) { 1130 | err(this.method + " received wrong arguments (" + args.join() + 1131 | "), expected " + this.expectedArguments.join()); 1132 | } 1133 | } 1134 | }, 1135 | 1136 | allowsCall: function allowsCall(thisValue, args) { 1137 | if (this.met()) { 1138 | return false; 1139 | } 1140 | 1141 | if ("expectedThis" in this && this.expectedThis !== thisValue) { 1142 | return false; 1143 | } 1144 | 1145 | if (!("expectedArguments" in this)) { 1146 | return true; 1147 | } 1148 | 1149 | args = args || []; 1150 | 1151 | if (args.length < this.expectedArguments.length) { 1152 | return false; 1153 | } 1154 | 1155 | if (this.expectsExactArgCount && 1156 | args.length != this.expectedArguments.length) { 1157 | return false; 1158 | } 1159 | 1160 | for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) { 1161 | if (!sinon.deepEqual(this.expectedArguments[i], args[i])) { 1162 | return false; 1163 | } 1164 | } 1165 | 1166 | return true; 1167 | }, 1168 | 1169 | withArgs: function withArgs() { 1170 | this.expectedArguments = slice.call(arguments); 1171 | return this; 1172 | }, 1173 | 1174 | withExactArgs: function withExactArgs() { 1175 | this.withArgs.apply(this, arguments); 1176 | this.expectsExactArgCount = true; 1177 | return this; 1178 | }, 1179 | 1180 | on: function on(thisValue) { 1181 | this.expectedThis = thisValue; 1182 | return this; 1183 | }, 1184 | 1185 | toString: function () { 1186 | var args = (this.expectedArguments || []).slice(); 1187 | 1188 | if (!this.expectsExactArgCount) { 1189 | args.push("[...]"); 1190 | } 1191 | 1192 | var callStr = sinon.spyCall.toString.call({ 1193 | proxy: this.method, args: args 1194 | }); 1195 | 1196 | var message = callStr.replace(", [...", "[, ...") + " " + 1197 | expectedCallCountInWords(this); 1198 | 1199 | if (this.met()) { 1200 | return "Expectation met: " + message; 1201 | } 1202 | 1203 | return "Expected " + message + " (" + 1204 | callCountInWords(this.callCount) + ")"; 1205 | }, 1206 | 1207 | verify: function verify() { 1208 | if (!this.met()) { 1209 | err(this.toString()); 1210 | } 1211 | 1212 | return true; 1213 | } 1214 | }; 1215 | }()); 1216 | 1217 | if (commonJSModule) { 1218 | module.exports = mock; 1219 | } else { 1220 | sinon.mock = mock; 1221 | } 1222 | }(typeof sinon == "object" && sinon || null)); 1223 | 1224 | /** 1225 | * @depend ../sinon.js 1226 | * @depend stub.js 1227 | * @depend mock.js 1228 | */ 1229 | /*jslint eqeqeq: false, onevar: false, forin: true*/ 1230 | /*global module, require, sinon*/ 1231 | /** 1232 | * Collections of stubs, spies and mocks. 1233 | * 1234 | * @author Christian Johansen (christian@cjohansen.no) 1235 | * @license BSD 1236 | * 1237 | * Copyright (c) 2010-2011 Christian Johansen 1238 | */ 1239 | 1240 | (function (sinon) { 1241 | var commonJSModule = typeof module == "object" && typeof require == "function"; 1242 | 1243 | if (!sinon && commonJSModule) { 1244 | sinon = require("sinon"); 1245 | } 1246 | 1247 | if (!sinon) { 1248 | return; 1249 | } 1250 | 1251 | function getFakes(fakeCollection) { 1252 | if (!fakeCollection.fakes) { 1253 | fakeCollection.fakes = []; 1254 | } 1255 | 1256 | return fakeCollection.fakes; 1257 | } 1258 | 1259 | function each(fakeCollection, method) { 1260 | var fakes = getFakes(fakeCollection); 1261 | 1262 | for (var i = 0, l = fakes.length; i < l; i += 1) { 1263 | if (typeof fakes[i][method] == "function") { 1264 | fakes[i][method](); 1265 | } 1266 | } 1267 | } 1268 | 1269 | var collection = { 1270 | verify: function resolve() { 1271 | each(this, "verify"); 1272 | }, 1273 | 1274 | restore: function restore() { 1275 | each(this, "restore"); 1276 | }, 1277 | 1278 | verifyAndRestore: function verifyAndRestore() { 1279 | var exception; 1280 | 1281 | try { 1282 | this.verify(); 1283 | } catch (e) { 1284 | exception = e; 1285 | } 1286 | 1287 | this.restore(); 1288 | 1289 | if (exception) { 1290 | throw exception; 1291 | } 1292 | }, 1293 | 1294 | add: function add(fake) { 1295 | getFakes(this).push(fake); 1296 | 1297 | return fake; 1298 | }, 1299 | 1300 | spy: function spy() { 1301 | return this.add(sinon.spy.apply(sinon, arguments)); 1302 | }, 1303 | 1304 | stub: function stub(object, property, value) { 1305 | if (property) { 1306 | var original = object[property]; 1307 | 1308 | if (typeof original != "function") { 1309 | if (!object.hasOwnProperty(property)) { 1310 | throw new TypeError("Cannot stub non-existent own property " + property); 1311 | } 1312 | 1313 | object[property] = value; 1314 | 1315 | return this.add({ 1316 | restore: function () { 1317 | object[property] = original; 1318 | } 1319 | }); 1320 | } 1321 | } 1322 | 1323 | return this.add(sinon.stub.apply(sinon, arguments)); 1324 | }, 1325 | 1326 | mock: function mock() { 1327 | return this.add(sinon.mock.apply(sinon, arguments)); 1328 | }, 1329 | 1330 | inject: function inject(obj) { 1331 | var col = this; 1332 | 1333 | obj.spy = function () { 1334 | return col.spy.apply(col, arguments); 1335 | }; 1336 | 1337 | obj.stub = function () { 1338 | return col.stub.apply(col, arguments); 1339 | }; 1340 | 1341 | obj.mock = function () { 1342 | return col.mock.apply(col, arguments); 1343 | }; 1344 | 1345 | return obj; 1346 | } 1347 | }; 1348 | 1349 | if (commonJSModule) { 1350 | module.exports = collection; 1351 | } else { 1352 | sinon.collection = collection; 1353 | } 1354 | }(typeof sinon == "object" && sinon || null)); 1355 | 1356 | /*jslint eqeqeq: false, plusplus: false, evil: true, onevar: false, browser: true, forin: false*/ 1357 | /*global module, require, window*/ 1358 | /** 1359 | * Fake timer API 1360 | * setTimeout 1361 | * setInterval 1362 | * clearTimeout 1363 | * clearInterval 1364 | * tick 1365 | * reset 1366 | * Date 1367 | * 1368 | * Inspired by jsUnitMockTimeOut from JsUnit 1369 | * 1370 | * @author Christian Johansen (christian@cjohansen.no) 1371 | * @license BSD 1372 | * 1373 | * Copyright (c) 2010-2011 Christian Johansen 1374 | */ 1375 | 1376 | if (typeof sinon == "undefined") { 1377 | var sinon = {}; 1378 | } 1379 | 1380 | sinon.clock = (function () { 1381 | var id = 0; 1382 | 1383 | function addTimer(args, recurring) { 1384 | if (args.length === 0) { 1385 | throw new Error("Function requires at least 1 parameter"); 1386 | } 1387 | 1388 | var toId = id++; 1389 | var delay = args[1] || 0; 1390 | 1391 | if (!this.timeouts) { 1392 | this.timeouts = {}; 1393 | } 1394 | 1395 | this.timeouts[toId] = { 1396 | id: toId, 1397 | func: args[0], 1398 | callAt: this.now + delay 1399 | }; 1400 | 1401 | if (recurring === true) { 1402 | this.timeouts[toId].interval = delay; 1403 | } 1404 | 1405 | return toId; 1406 | } 1407 | 1408 | function parseTime(str) { 1409 | if (!str) { 1410 | return 0; 1411 | } 1412 | 1413 | var strings = str.split(":"); 1414 | var l = strings.length, i = l; 1415 | var ms = 0, parsed; 1416 | 1417 | if (l > 3 || !/^(\d\d:){0,2}\d\d?$/.test(str)) { 1418 | throw new Error("tick only understands numbers and 'h:m:s'"); 1419 | } 1420 | 1421 | while (i--) { 1422 | parsed = parseInt(strings[i], 10); 1423 | 1424 | if (parsed >= 60) { 1425 | throw new Error("Invalid time " + str); 1426 | } 1427 | 1428 | ms += parsed * Math.pow(60, (l - i - 1)); 1429 | } 1430 | 1431 | return ms * 1000; 1432 | } 1433 | 1434 | function createObject(object) { 1435 | var newObject; 1436 | 1437 | if (Object.create) { 1438 | newObject = Object.create(object); 1439 | } else { 1440 | var F = function () {}; 1441 | F.prototype = object; 1442 | newObject = new F(); 1443 | } 1444 | 1445 | newObject.Date.clock = newObject; 1446 | return newObject; 1447 | } 1448 | 1449 | return { 1450 | now: 0, 1451 | 1452 | create: function create(now) { 1453 | var clock = createObject(this); 1454 | 1455 | if (typeof now == "number") { 1456 | this.now = now; 1457 | } 1458 | 1459 | return clock; 1460 | }, 1461 | 1462 | setTimeout: function setTimeout(callback, timeout) { 1463 | return addTimer.call(this, arguments, false); 1464 | }, 1465 | 1466 | clearTimeout: function clearTimeout(timerId) { 1467 | if (!this.timeouts) { 1468 | this.timeouts = []; 1469 | } 1470 | 1471 | delete this.timeouts[timerId]; 1472 | }, 1473 | 1474 | setInterval: function setInterval(callback, timeout) { 1475 | return addTimer.call(this, arguments, true); 1476 | }, 1477 | 1478 | clearInterval: function clearInterval(timerId) { 1479 | this.clearTimeout(timerId); 1480 | }, 1481 | 1482 | tick: function tick(ms) { 1483 | ms = typeof ms == "number" ? ms : parseTime(ms); 1484 | var tickFrom = this.now, tickTo = this.now + ms, previous = this.now; 1485 | var timer = this.firstTimerInRange(tickFrom, tickTo); 1486 | 1487 | while (timer && tickFrom <= tickTo) { 1488 | if (this.timeouts[timer.id]) { 1489 | tickFrom = this.now = timer.callAt; 1490 | this.callTimer(timer); 1491 | } 1492 | 1493 | timer = this.firstTimerInRange(previous, tickTo); 1494 | previous = tickFrom; 1495 | } 1496 | 1497 | this.now = tickTo; 1498 | }, 1499 | 1500 | firstTimerInRange: function (from, to) { 1501 | var timer, smallest, originalTimer; 1502 | 1503 | for (var id in this.timeouts) { 1504 | if (this.timeouts.hasOwnProperty(id)) { 1505 | if (this.timeouts[id].callAt < from || this.timeouts[id].callAt > to) { 1506 | continue; 1507 | } 1508 | 1509 | if (!smallest || this.timeouts[id].callAt < smallest) { 1510 | originalTimer = this.timeouts[id]; 1511 | smallest = this.timeouts[id].callAt; 1512 | 1513 | timer = { 1514 | func: this.timeouts[id].func, 1515 | callAt: this.timeouts[id].callAt, 1516 | interval: this.timeouts[id].interval, 1517 | id: this.timeouts[id].id 1518 | }; 1519 | } 1520 | } 1521 | } 1522 | 1523 | return timer || null; 1524 | }, 1525 | 1526 | callTimer: function (timer) { 1527 | try { 1528 | if (typeof timer.func == "function") { 1529 | timer.func.call(null); 1530 | } else { 1531 | eval(timer.func); 1532 | } 1533 | } catch (e) {} 1534 | 1535 | if (!this.timeouts[timer.id]) { 1536 | return; 1537 | } 1538 | 1539 | if (typeof timer.interval == "number") { 1540 | this.timeouts[timer.id].callAt += timer.interval; 1541 | } else { 1542 | delete this.timeouts[timer.id]; 1543 | } 1544 | }, 1545 | 1546 | reset: function reset() { 1547 | this.timeouts = {}; 1548 | }, 1549 | 1550 | Date: (function () { 1551 | var NativeDate = Date; 1552 | 1553 | function ClockDate(year, month, date, hour, minute, second, ms) { 1554 | // Defensive and verbose to avoid potential harm in passing 1555 | // explicit undefined when user does not pass argument 1556 | switch (arguments.length) { 1557 | case 0: 1558 | return new NativeDate(ClockDate.clock.now); 1559 | case 1: 1560 | return new NativeDate(year); 1561 | case 2: 1562 | return new NativeDate(year, month); 1563 | case 3: 1564 | return new NativeDate(year, month, date); 1565 | case 4: 1566 | return new NativeDate(year, month, date, hour); 1567 | case 5: 1568 | return new NativeDate(year, month, date, hour, minute); 1569 | case 6: 1570 | return new NativeDate(year, month, date, hour, minute, second); 1571 | default: 1572 | return new NativeDate(year, month, date, hour, minute, second, ms); 1573 | } 1574 | } 1575 | 1576 | if (NativeDate.now) { 1577 | ClockDate.now = function now() { 1578 | return ClockDate.clock.now; 1579 | }; 1580 | } 1581 | 1582 | if (NativeDate.toSource) { 1583 | ClockDate.toSource = function toSource() { 1584 | return NativeDate.toSource(); 1585 | }; 1586 | } 1587 | 1588 | ClockDate.toString = function toString() { 1589 | return NativeDate.toString(); 1590 | }; 1591 | 1592 | ClockDate.prototype = NativeDate.prototype; 1593 | ClockDate.parse = NativeDate.parse; 1594 | ClockDate.UTC = NativeDate.UTC; 1595 | 1596 | return ClockDate; 1597 | }()) 1598 | }; 1599 | }()); 1600 | 1601 | sinon.timers = { 1602 | setTimeout: setTimeout, 1603 | clearTimeout: clearTimeout, 1604 | setInterval: setInterval, 1605 | clearInterval: clearInterval, 1606 | Date: Date 1607 | }; 1608 | 1609 | sinon.useFakeTimers = (function (global) { 1610 | var methods = ["Date", "setTimeout", "setInterval", "clearTimeout", "clearInterval"]; 1611 | 1612 | function restore() { 1613 | var method; 1614 | 1615 | for (var i = 0, l = this.methods.length; i < l; i++) { 1616 | method = this.methods[i]; 1617 | global[method] = this["_" + method]; 1618 | } 1619 | } 1620 | 1621 | function stubGlobal(method, clock) { 1622 | clock["_" + method] = global[method]; 1623 | 1624 | global[method] = function () { 1625 | return clock[method].apply(clock, arguments); 1626 | }; 1627 | 1628 | for (var prop in clock[method]) { 1629 | if (clock[method].hasOwnProperty(prop)) { 1630 | global[method][prop] = clock[method][prop]; 1631 | } 1632 | } 1633 | 1634 | global[method].clock = clock; 1635 | } 1636 | 1637 | return function useFakeTimers(now) { 1638 | var clock = sinon.clock.create(now); 1639 | clock.restore = restore; 1640 | clock.methods = Array.prototype.slice.call(arguments, 1641 | typeof now == "number" ? 1 : 0); 1642 | 1643 | if (clock.methods.length === 0) { 1644 | clock.methods = methods; 1645 | } 1646 | 1647 | for (var i = 0, l = clock.methods.length; i < l; i++) { 1648 | stubGlobal(clock.methods[i], clock); 1649 | } 1650 | 1651 | return clock; 1652 | }; 1653 | }(typeof global != "undefined" ? global : this)); 1654 | 1655 | if (typeof module == "object" && typeof require == "function") { 1656 | module.exports = sinon; 1657 | } 1658 | 1659 | /*jslint eqeqeq: false, onevar: false*/ 1660 | /*global sinon, module, require, ActiveXObject, XMLHttpRequest, DOMParser*/ 1661 | /** 1662 | * Fake XMLHttpRequest object 1663 | * 1664 | * @author Christian Johansen (christian@cjohansen.no) 1665 | * @license BSD 1666 | * 1667 | * Copyright (c) 2010-2011 Christian Johansen 1668 | */ 1669 | 1670 | if (typeof sinon == "undefined") { 1671 | this.sinon = {}; 1672 | } 1673 | 1674 | sinon.xhr = { XMLHttpRequest: this.XMLHttpRequest }; 1675 | 1676 | sinon.FakeXMLHttpRequest = (function () { 1677 | /*jsl:ignore*/ 1678 | var unsafeHeaders = { 1679 | "Accept-Charset": true, 1680 | "Accept-Encoding": true, 1681 | "Connection": true, 1682 | "Content-Length": true, 1683 | "Cookie": true, 1684 | "Cookie2": true, 1685 | "Content-Transfer-Encoding": true, 1686 | "Date": true, 1687 | "Expect": true, 1688 | "Host": true, 1689 | "Keep-Alive": true, 1690 | "Referer": true, 1691 | "TE": true, 1692 | "Trailer": true, 1693 | "Transfer-Encoding": true, 1694 | "Upgrade": true, 1695 | "User-Agent": true, 1696 | "Via": true 1697 | }; 1698 | /*jsl:end*/ 1699 | 1700 | function FakeXMLHttpRequest() { 1701 | this.readyState = FakeXMLHttpRequest.UNSENT; 1702 | this.requestHeaders = {}; 1703 | this.requestBody = null; 1704 | this.status = 0; 1705 | this.statusText = ""; 1706 | 1707 | if (typeof FakeXMLHttpRequest.onCreate == "function") { 1708 | FakeXMLHttpRequest.onCreate(this); 1709 | } 1710 | } 1711 | 1712 | function verifyState(xhr) { 1713 | if (xhr.readyState !== FakeXMLHttpRequest.OPENED) { 1714 | throw new Error("INVALID_STATE_ERR"); 1715 | } 1716 | 1717 | if (xhr.sendFlag) { 1718 | throw new Error("INVALID_STATE_ERR"); 1719 | } 1720 | } 1721 | 1722 | sinon.extend(FakeXMLHttpRequest.prototype, { 1723 | async: true, 1724 | 1725 | open: function open(method, url, async, username, password) { 1726 | this.method = method; 1727 | this.url = url; 1728 | this.async = typeof async == "boolean" ? async : true; 1729 | this.username = username; 1730 | this.password = password; 1731 | this.responseText = null; 1732 | this.responseXML = null; 1733 | this.requestHeaders = {}; 1734 | this.sendFlag = false; 1735 | this.readyStateChange(FakeXMLHttpRequest.OPENED); 1736 | }, 1737 | 1738 | readyStateChange: function readyStateChange(state) { 1739 | this.readyState = state; 1740 | 1741 | if (typeof this.onreadystatechange == "function") { 1742 | this.onreadystatechange(); 1743 | } 1744 | }, 1745 | 1746 | setRequestHeader: function setRequestHeader(header, value) { 1747 | verifyState(this); 1748 | 1749 | if (unsafeHeaders[header] || /^(Sec-|Proxy-)/.test(header)) { 1750 | throw new Error("Refused to set unsafe header \"" + header + "\""); 1751 | } 1752 | 1753 | if (this.requestHeaders[header]) { 1754 | this.requestHeaders[header] += "," + value; 1755 | } else { 1756 | this.requestHeaders[header] = value; 1757 | } 1758 | }, 1759 | 1760 | // Helps testing 1761 | setResponseHeaders: function setResponseHeaders(headers) { 1762 | this.responseHeaders = {}; 1763 | 1764 | for (var header in headers) { 1765 | if (headers.hasOwnProperty(header)) { 1766 | this.responseHeaders[header] = headers[header]; 1767 | } 1768 | } 1769 | 1770 | if (this.async) { 1771 | this.readyStateChange(FakeXMLHttpRequest.HEADERS_RECEIVED); 1772 | } 1773 | }, 1774 | 1775 | // Currently treats ALL data as a DOMString (i.e. no Document) 1776 | send: function send(data) { 1777 | verifyState(this); 1778 | 1779 | if (!/^(get|head)$/i.test(this.method)) { 1780 | if (this.requestHeaders["Content-Type"]) { 1781 | var value = this.requestHeaders["Content-Type"].split(";"); 1782 | this.requestHeaders["Content-Type"] = value[0] + ";charset=utf-8"; 1783 | } else { 1784 | this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8"; 1785 | } 1786 | 1787 | this.requestBody = data; 1788 | } 1789 | 1790 | this.errorFlag = false; 1791 | this.sendFlag = this.async; 1792 | this.readyStateChange(FakeXMLHttpRequest.OPENED); 1793 | 1794 | if (typeof this.onSend == "function") { 1795 | this.onSend(this); 1796 | } 1797 | }, 1798 | 1799 | abort: function abort() { 1800 | this.aborted = true; 1801 | this.responseText = null; 1802 | this.errorFlag = true; 1803 | this.requestHeaders = {}; 1804 | 1805 | if (this.readyState > sinon.FakeXMLHttpRequest.OPENED) { 1806 | this.readyStateChange(sinon.FakeXMLHttpRequest.DONE); 1807 | this.sendFlag = false; 1808 | } 1809 | 1810 | this.readyState = sinon.FakeXMLHttpRequest.UNSENT; 1811 | }, 1812 | 1813 | getResponseHeader: function getResponseHeader(header) { 1814 | if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { 1815 | return null; 1816 | } 1817 | 1818 | if (/^Set-Cookie2?$/i.test(header)) { 1819 | return null; 1820 | } 1821 | 1822 | header = header.toLowerCase(); 1823 | 1824 | for (var h in this.responseHeaders) { 1825 | if (h.toLowerCase() == header) { 1826 | return this.responseHeaders[h]; 1827 | } 1828 | } 1829 | 1830 | return null; 1831 | }, 1832 | 1833 | getAllResponseHeaders: function getAllResponseHeaders() { 1834 | if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { 1835 | return ""; 1836 | } 1837 | 1838 | var headers = ""; 1839 | 1840 | for (var header in this.responseHeaders) { 1841 | if (this.responseHeaders.hasOwnProperty(header) && 1842 | !/^Set-Cookie2?$/i.test(header)) { 1843 | headers += header + ": " + this.responseHeaders[header] + "\r\n"; 1844 | } 1845 | } 1846 | 1847 | return headers; 1848 | }, 1849 | 1850 | setResponseBody: function setResponseBody(body) { 1851 | if (this.readyState == FakeXMLHttpRequest.DONE) { 1852 | throw new Error("Request done"); 1853 | } 1854 | 1855 | if (this.async && this.readyState != FakeXMLHttpRequest.HEADERS_RECEIVED) { 1856 | throw new Error("No headers received"); 1857 | } 1858 | 1859 | var chunkSize = this.chunkSize || 10; 1860 | var index = 0; 1861 | this.responseText = ""; 1862 | 1863 | do { 1864 | if (this.async) { 1865 | this.readyStateChange(FakeXMLHttpRequest.LOADING); 1866 | } 1867 | 1868 | this.responseText += body.substring(index, index + chunkSize); 1869 | index += chunkSize; 1870 | } while (index < body.length); 1871 | 1872 | var type = this.getResponseHeader("Content-Type"); 1873 | 1874 | if (this.responseText && 1875 | (!type || /(text\/xml)|(application\/xml)|(\+xml)/.test(type))) { 1876 | try { 1877 | this.responseXML = FakeXMLHttpRequest.parseXML(this.responseText); 1878 | } catch (e) {} 1879 | } 1880 | 1881 | if (this.async) { 1882 | this.readyStateChange(FakeXMLHttpRequest.DONE); 1883 | } else { 1884 | this.readyState = FakeXMLHttpRequest.DONE; 1885 | } 1886 | }, 1887 | 1888 | respond: function respond(status, headers, body) { 1889 | this.setResponseHeaders(headers || {}); 1890 | this.status = typeof status == "number" ? status : 200; 1891 | this.statusText = FakeXMLHttpRequest.statusCodes[this.status]; 1892 | this.setResponseBody(body || ""); 1893 | } 1894 | }); 1895 | 1896 | sinon.extend(FakeXMLHttpRequest, { 1897 | UNSENT: 0, 1898 | OPENED: 1, 1899 | HEADERS_RECEIVED: 2, 1900 | LOADING: 3, 1901 | DONE: 4 1902 | }); 1903 | 1904 | // Borrowed from JSpec 1905 | FakeXMLHttpRequest.parseXML = function parseXML(text) { 1906 | var xmlDoc; 1907 | 1908 | if (typeof DOMParser != "undefined") { 1909 | var parser = new DOMParser(); 1910 | xmlDoc = parser.parseFromString(text, "text/xml"); 1911 | } else { 1912 | xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); 1913 | xmlDoc.async = "false"; 1914 | xmlDoc.loadXML(text); 1915 | } 1916 | 1917 | return xmlDoc; 1918 | }; 1919 | 1920 | FakeXMLHttpRequest.statusCodes = { 1921 | 100: "Continue", 1922 | 101: "Switching Protocols", 1923 | 200: "OK", 1924 | 201: "Created", 1925 | 202: "Accepted", 1926 | 203: "Non-Authoritative Information", 1927 | 204: "No Content", 1928 | 205: "Reset Content", 1929 | 206: "Partial Content", 1930 | 300: "Multiple Choice", 1931 | 301: "Moved Permanently", 1932 | 302: "Found", 1933 | 303: "See Other", 1934 | 304: "Not Modified", 1935 | 305: "Use Proxy", 1936 | 307: "Temporary Redirect", 1937 | 400: "Bad Request", 1938 | 401: "Unauthorized", 1939 | 402: "Payment Required", 1940 | 403: "Forbidden", 1941 | 404: "Not Found", 1942 | 405: "Method Not Allowed", 1943 | 406: "Not Acceptable", 1944 | 407: "Proxy Authentication Required", 1945 | 408: "Request Timeout", 1946 | 409: "Conflict", 1947 | 410: "Gone", 1948 | 411: "Length Required", 1949 | 412: "Precondition Failed", 1950 | 413: "Request Entity Too Large", 1951 | 414: "Request-URI Too Long", 1952 | 415: "Unsupported Media Type", 1953 | 416: "Requested Range Not Satisfiable", 1954 | 417: "Expectation Failed", 1955 | 422: "Unprocessable Entity", 1956 | 500: "Internal Server Error", 1957 | 501: "Not Implemented", 1958 | 502: "Bad Gateway", 1959 | 503: "Service Unavailable", 1960 | 504: "Gateway Timeout", 1961 | 505: "HTTP Version Not Supported" 1962 | }; 1963 | 1964 | return FakeXMLHttpRequest; 1965 | }()); 1966 | 1967 | (function (global) { 1968 | var GlobalXMLHttpRequest = global.XMLHttpRequest; 1969 | var GlobalActiveXObject = global.ActiveXObject; 1970 | var supportsActiveX = typeof ActiveXObject != "undefined"; 1971 | var supportsXHR = typeof XMLHttpRequest != "undefined"; 1972 | 1973 | sinon.useFakeXMLHttpRequest = function () { 1974 | sinon.FakeXMLHttpRequest.restore = function restore(keepOnCreate) { 1975 | if (supportsXHR) { 1976 | global.XMLHttpRequest = GlobalXMLHttpRequest; 1977 | } 1978 | 1979 | if (supportsActiveX) { 1980 | global.ActiveXObject = GlobalActiveXObject; 1981 | } 1982 | 1983 | delete sinon.FakeXMLHttpRequest.restore; 1984 | 1985 | if (keepOnCreate !== true) { 1986 | delete sinon.FakeXMLHttpRequest.onCreate; 1987 | } 1988 | }; 1989 | 1990 | if (supportsXHR) { 1991 | global.XMLHttpRequest = sinon.FakeXMLHttpRequest; 1992 | } 1993 | 1994 | if (supportsActiveX) { 1995 | global.ActiveXObject = function ActiveXObject(objId) { 1996 | if (objId == "Microsoft.XMLHTTP" || /^Msxml2\.XMLHTTP/.test(objId)) { 1997 | return new sinon.FakeXMLHttpRequest(); 1998 | } 1999 | 2000 | return new GlobalActiveXObject(objId); 2001 | }; 2002 | } 2003 | 2004 | return sinon.FakeXMLHttpRequest; 2005 | }; 2006 | }(this)); 2007 | 2008 | if (typeof module == "object" && typeof require == "function") { 2009 | module.exports = sinon; 2010 | } 2011 | 2012 | /** 2013 | * @depend fake_xml_http_request.js 2014 | */ 2015 | /*jslint eqeqeq: false, onevar: false, regexp: false, plusplus: false*/ 2016 | /*global module, require, window*/ 2017 | /** 2018 | * The Sinon "server" mimics a web server that receives requests from 2019 | * sinon.FakeXMLHttpRequest and provides an API to respond to those requests, 2020 | * both synchronously and asynchronously. To respond synchronuously, canned 2021 | * answers have to be provided upfront. 2022 | * 2023 | * @author Christian Johansen (christian@cjohansen.no) 2024 | * @license BSD 2025 | * 2026 | * Copyright (c) 2010-2011 Christian Johansen 2027 | */ 2028 | 2029 | if (typeof sinon == "undefined") { 2030 | var sinon = {}; 2031 | } 2032 | 2033 | sinon.fakeServer = (function () { 2034 | function F() {} 2035 | 2036 | function create(proto) { 2037 | F.prototype = proto; 2038 | return new F(); 2039 | } 2040 | 2041 | function responseArray(handler) { 2042 | var response = handler; 2043 | 2044 | if (Object.prototype.toString.call(handler) != "[object Array]") { 2045 | response = [200, {}, handler]; 2046 | } 2047 | 2048 | if (typeof response[2] != "string") { 2049 | throw new TypeError("Fake server response body should be string, but was " + 2050 | typeof response[2]); 2051 | } 2052 | 2053 | return response; 2054 | } 2055 | 2056 | var wloc = window.location; 2057 | var rCurrLoc = new RegExp("^" + wloc.protocol + "//" + wloc.host); 2058 | 2059 | function matchOne(response, reqMethod, reqUrl) { 2060 | var rmeth = response.method; 2061 | var matchMethod = !rmeth || rmeth.toLowerCase() == reqMethod.toLowerCase(); 2062 | var url = response.url; 2063 | var matchUrl = !url || url == reqUrl || (typeof url.test == "function" && url.test(reqUrl)); 2064 | 2065 | return matchMethod && matchUrl; 2066 | } 2067 | 2068 | function match(response, request) { 2069 | var requestMethod = this.getHTTPMethod(request); 2070 | var requestUrl = request.url; 2071 | 2072 | if (!/^https?:\/\//.test(requestUrl) || rCurrLoc.test(requestUrl)) { 2073 | requestUrl = requestUrl.replace(rCurrLoc, ""); 2074 | } 2075 | 2076 | if (matchOne(response, this.getHTTPMethod(request), requestUrl)) { 2077 | if (typeof response.response == "function") { 2078 | var args = [request].concat(requestUrl.match(response.url).slice(1)); 2079 | return response.response.apply(response, args); 2080 | } 2081 | 2082 | return true; 2083 | } 2084 | 2085 | return false; 2086 | } 2087 | 2088 | return { 2089 | create: function () { 2090 | var server = create(this); 2091 | this.xhr = sinon.useFakeXMLHttpRequest(); 2092 | server.requests = []; 2093 | 2094 | this.xhr.onCreate = function (xhrObj) { 2095 | server.addRequest(xhrObj); 2096 | }; 2097 | 2098 | return server; 2099 | }, 2100 | 2101 | addRequest: function addRequest(xhrObj) { 2102 | var server = this; 2103 | this.requests.push(xhrObj); 2104 | 2105 | xhrObj.onSend = function () { 2106 | server.handleRequest(this); 2107 | }; 2108 | 2109 | if (this.autoRespond && !this.responding) { 2110 | setTimeout(function () { 2111 | server.responding = false; 2112 | server.respond(); 2113 | }, this.autoRespondAfter || 10); 2114 | 2115 | this.responding = true; 2116 | } 2117 | }, 2118 | 2119 | getHTTPMethod: function getHTTPMethod(request) { 2120 | if (this.fakeHTTPMethods && /post/i.test(request.method)) { 2121 | var matches = request.requestBody.match(/_method=([^\b;]+)/); 2122 | return !!matches ? matches[1] : request.method; 2123 | } 2124 | 2125 | return request.method; 2126 | }, 2127 | 2128 | handleRequest: function handleRequest(xhr) { 2129 | if (xhr.async) { 2130 | if (!this.queue) { 2131 | this.queue = []; 2132 | } 2133 | 2134 | this.queue.push(xhr); 2135 | } else { 2136 | this.processRequest(xhr); 2137 | } 2138 | }, 2139 | 2140 | respondWith: function respondWith(method, url, body) { 2141 | if (arguments.length == 1) { 2142 | this.response = responseArray(method); 2143 | } else { 2144 | if (!this.responses) { 2145 | this.responses = []; 2146 | } 2147 | 2148 | if (arguments.length == 2) { 2149 | body = url; 2150 | url = method; 2151 | method = null; 2152 | } 2153 | 2154 | this.responses.push({ 2155 | method: method, 2156 | url: url, 2157 | response: typeof body == "function" ? body : responseArray(body) 2158 | }); 2159 | } 2160 | }, 2161 | 2162 | respond: function respond() { 2163 | var queue = this.queue || []; 2164 | var request; 2165 | 2166 | while(request = queue.shift()) { 2167 | this.processRequest(request); 2168 | } 2169 | }, 2170 | 2171 | processRequest: function processRequest(request) { 2172 | try { 2173 | if (request.aborted) { 2174 | return; 2175 | } 2176 | 2177 | var response = this.response || [404, {}, ""]; 2178 | 2179 | if (this.responses) { 2180 | for (var i = 0, l = this.responses.length; i < l; i++) { 2181 | if (match.call(this, this.responses[i], request)) { 2182 | response = this.responses[i].response; 2183 | break; 2184 | } 2185 | } 2186 | } 2187 | 2188 | if (request.readyState != 4) { 2189 | request.respond(response[0], response[1], response[2]); 2190 | } 2191 | } catch (e) {} 2192 | }, 2193 | 2194 | restore: function restore() { 2195 | return this.xhr.restore && this.xhr.restore.apply(this.xhr, arguments); 2196 | } 2197 | }; 2198 | }()); 2199 | 2200 | if (typeof module == "object" && typeof require == "function") { 2201 | module.exports = sinon; 2202 | } 2203 | 2204 | /** 2205 | * @depend fake_server.js 2206 | * @depend fake_timers.js 2207 | */ 2208 | /*jslint browser: true, eqeqeq: false, onevar: false*/ 2209 | /*global sinon*/ 2210 | /** 2211 | * Add-on for sinon.fakeServer that automatically handles a fake timer along with 2212 | * the FakeXMLHttpRequest. The direct inspiration for this add-on is jQuery 2213 | * 1.3.x, which does not use xhr object's onreadystatehandler at all - instead, 2214 | * it polls the object for completion with setInterval. Dispite the direct 2215 | * motivation, there is nothing jQuery-specific in this file, so it can be used 2216 | * in any environment where the ajax implementation depends on setInterval or 2217 | * setTimeout. 2218 | * 2219 | * @author Christian Johansen (christian@cjohansen.no) 2220 | * @license BSD 2221 | * 2222 | * Copyright (c) 2010-2011 Christian Johansen 2223 | */ 2224 | 2225 | (function () { 2226 | function Server() {} 2227 | Server.prototype = sinon.fakeServer; 2228 | 2229 | sinon.fakeServerWithClock = new Server(); 2230 | 2231 | sinon.fakeServerWithClock.addRequest = function addRequest(xhr) { 2232 | if (xhr.async) { 2233 | if (typeof setTimeout.clock == "object") { 2234 | this.clock = setTimeout.clock; 2235 | } else { 2236 | this.clock = sinon.useFakeTimers(); 2237 | this.resetClock = true; 2238 | } 2239 | 2240 | if (!this.longestTimeout) { 2241 | var clockSetTimeout = this.clock.setTimeout; 2242 | var clockSetInterval = this.clock.setInterval; 2243 | var server = this; 2244 | 2245 | this.clock.setTimeout = function (fn, timeout) { 2246 | server.longestTimeout = Math.max(timeout, server.longestTimeout || 0); 2247 | 2248 | return clockSetTimeout.apply(this, arguments); 2249 | }; 2250 | 2251 | this.clock.setInterval = function (fn, timeout) { 2252 | server.longestTimeout = Math.max(timeout, server.longestTimeout || 0); 2253 | 2254 | return clockSetInterval.apply(this, arguments); 2255 | }; 2256 | } 2257 | } 2258 | 2259 | return sinon.fakeServer.addRequest.call(this, xhr); 2260 | }; 2261 | 2262 | sinon.fakeServerWithClock.respond = function respond() { 2263 | var returnVal = sinon.fakeServer.respond.apply(this, arguments); 2264 | 2265 | if (this.clock) { 2266 | this.clock.tick(this.longestTimeout || 0); 2267 | this.longestTimeout = 0; 2268 | 2269 | if (this.resetClock) { 2270 | this.clock.restore(); 2271 | this.resetClock = false; 2272 | } 2273 | } 2274 | 2275 | return returnVal; 2276 | }; 2277 | 2278 | sinon.fakeServerWithClock.restore = function restore() { 2279 | if (this.clock) { 2280 | this.clock.restore(); 2281 | } 2282 | 2283 | return sinon.fakeServer.restore.apply(this, arguments); 2284 | }; 2285 | }()); 2286 | 2287 | /** 2288 | * @depend ../sinon.js 2289 | * @depend collection.js 2290 | * @depend util/fake_timers.js 2291 | * @depend util/fake_server_with_clock.js 2292 | */ 2293 | /*jslint eqeqeq: false, onevar: false, plusplus: false*/ 2294 | /*global require, module*/ 2295 | /** 2296 | * Manages fake collections as well as fake utilities such as Sinon's 2297 | * timers and fake XHR implementation in one convenient object. 2298 | * 2299 | * @author Christian Johansen (christian@cjohansen.no) 2300 | * @license BSD 2301 | * 2302 | * Copyright (c) 2010-2011 Christian Johansen 2303 | */ 2304 | 2305 | if (typeof module == "object" && typeof require == "function") { 2306 | var sinon = require("sinon"); 2307 | sinon.extend(sinon, require("./util/fake_timers")); 2308 | } 2309 | 2310 | (function () { 2311 | function exposeValue(sandbox, config, key, value) { 2312 | if (!value) { 2313 | return; 2314 | } 2315 | 2316 | if (config.injectInto) { 2317 | config.injectInto[key] = value; 2318 | } else { 2319 | sandbox.args.push(value); 2320 | } 2321 | } 2322 | 2323 | function prepareSandboxFromConfig(config) { 2324 | var sandbox = sinon.create(sinon.sandbox); 2325 | 2326 | if (config.useFakeServer) { 2327 | if (typeof config.useFakeServer == "object") { 2328 | sandbox.serverPrototype = config.useFakeServer; 2329 | } 2330 | 2331 | sandbox.useFakeServer(); 2332 | } 2333 | 2334 | if (config.useFakeTimers) { 2335 | if (typeof config.useFakeTimers == "object") { 2336 | sandbox.useFakeTimers.apply(sandbox, config.useFakeTimers); 2337 | } else { 2338 | sandbox.useFakeTimers(); 2339 | } 2340 | } 2341 | 2342 | return sandbox; 2343 | } 2344 | 2345 | sinon.sandbox = sinon.extend(sinon.create(sinon.collection), { 2346 | useFakeTimers: function useFakeTimers() { 2347 | this.clock = sinon.useFakeTimers.apply(sinon, arguments); 2348 | 2349 | return this.add(this.clock); 2350 | }, 2351 | 2352 | serverPrototype: sinon.fakeServer, 2353 | 2354 | useFakeServer: function useFakeServer() { 2355 | var proto = this.serverPrototype || sinon.fakeServer; 2356 | 2357 | if (!proto || !proto.create) { 2358 | return null; 2359 | } 2360 | 2361 | this.server = proto.create(); 2362 | return this.add(this.server); 2363 | }, 2364 | 2365 | inject: function (obj) { 2366 | sinon.collection.inject.call(this, obj); 2367 | 2368 | if (this.clock) { 2369 | obj.clock = this.clock; 2370 | } 2371 | 2372 | if (this.server) { 2373 | obj.server = this.server; 2374 | obj.requests = this.server.requests; 2375 | } 2376 | 2377 | return obj; 2378 | }, 2379 | 2380 | create: function (config) { 2381 | if (!config) { 2382 | return sinon.create(sinon.sandbox); 2383 | } 2384 | 2385 | var sandbox = prepareSandboxFromConfig(config); 2386 | sandbox.args = sandbox.args || []; 2387 | var prop, value, exposed = sandbox.inject({}); 2388 | 2389 | if (config.properties) { 2390 | for (var i = 0, l = config.properties.length; i < l; i++) { 2391 | prop = config.properties[i]; 2392 | value = exposed[prop] || prop == "sandbox" && sandbox; 2393 | exposeValue(sandbox, config, prop, value); 2394 | } 2395 | } else { 2396 | exposeValue(sandbox, config, "sandbox", value); 2397 | } 2398 | 2399 | return sandbox; 2400 | } 2401 | }); 2402 | 2403 | sinon.sandbox.useFakeXMLHttpRequest = sinon.sandbox.useFakeServer; 2404 | 2405 | if (typeof module != "undefined") { 2406 | module.exports = sinon.sandbox; 2407 | } 2408 | }()); 2409 | 2410 | /** 2411 | * @depend ../sinon.js 2412 | * @depend stub.js 2413 | * @depend mock.js 2414 | * @depend sandbox.js 2415 | */ 2416 | /*jslint eqeqeq: false, onevar: false, forin: true, plusplus: false*/ 2417 | /*global module, require, sinon*/ 2418 | /** 2419 | * Test function, sandboxes fakes 2420 | * 2421 | * @author Christian Johansen (christian@cjohansen.no) 2422 | * @license BSD 2423 | * 2424 | * Copyright (c) 2010-2011 Christian Johansen 2425 | */ 2426 | 2427 | (function (sinon) { 2428 | var commonJSModule = typeof module == "object" && typeof require == "function"; 2429 | 2430 | if (!sinon && commonJSModule) { 2431 | sinon = require("sinon"); 2432 | } 2433 | 2434 | if (!sinon) { 2435 | return; 2436 | } 2437 | 2438 | function test(callback) { 2439 | var type = typeof callback; 2440 | 2441 | if (type != "function") { 2442 | throw new TypeError("sinon.test needs to wrap a test function, got " + type); 2443 | } 2444 | 2445 | return function () { 2446 | var config = sinon.getConfig(sinon.config); 2447 | config.injectInto = config.injectIntoThis && this || config.injectInto; 2448 | var sandbox = sinon.sandbox.create(config); 2449 | var exception, result; 2450 | var args = Array.prototype.slice.call(arguments).concat(sandbox.args); 2451 | 2452 | try { 2453 | result = callback.apply(this, args); 2454 | } catch (e) { 2455 | exception = e; 2456 | } 2457 | 2458 | sandbox.verifyAndRestore(); 2459 | 2460 | if (exception) { 2461 | throw exception; 2462 | } 2463 | 2464 | return result; 2465 | }; 2466 | } 2467 | 2468 | test.config = { 2469 | injectIntoThis: true, 2470 | injectInto: null, 2471 | properties: ["spy", "stub", "mock", "clock", "server", "requests"], 2472 | useFakeTimers: true, 2473 | useFakeServer: true 2474 | }; 2475 | 2476 | if (commonJSModule) { 2477 | module.exports = test; 2478 | } else { 2479 | sinon.test = test; 2480 | } 2481 | }(typeof sinon == "object" && sinon || null)); 2482 | 2483 | /** 2484 | * @depend ../sinon.js 2485 | * @depend test.js 2486 | */ 2487 | /*jslint eqeqeq: false, onevar: false, eqeqeq: false*/ 2488 | /*global module, require, sinon*/ 2489 | /** 2490 | * Test case, sandboxes all test functions 2491 | * 2492 | * @author Christian Johansen (christian@cjohansen.no) 2493 | * @license BSD 2494 | * 2495 | * Copyright (c) 2010-2011 Christian Johansen 2496 | */ 2497 | 2498 | (function (sinon) { 2499 | var commonJSModule = typeof module == "object" && typeof require == "function"; 2500 | 2501 | if (!sinon && commonJSModule) { 2502 | sinon = require("sinon"); 2503 | } 2504 | 2505 | if (!sinon || !Object.prototype.hasOwnProperty) { 2506 | return; 2507 | } 2508 | 2509 | function createTest(property, setUp, tearDown) { 2510 | return function () { 2511 | if (setUp) { 2512 | setUp.apply(this, arguments); 2513 | } 2514 | 2515 | var exception, result; 2516 | 2517 | try { 2518 | result = property.apply(this, arguments); 2519 | } catch (e) { 2520 | exception = e; 2521 | } 2522 | 2523 | if (tearDown) { 2524 | tearDown.apply(this, arguments); 2525 | } 2526 | 2527 | if (exception) { 2528 | throw exception; 2529 | } 2530 | 2531 | return result; 2532 | }; 2533 | } 2534 | 2535 | function testCase(tests, prefix) { 2536 | /*jsl:ignore*/ 2537 | if (!tests || typeof tests != "object") { 2538 | throw new TypeError("sinon.testCase needs an object with test functions"); 2539 | } 2540 | /*jsl:end*/ 2541 | 2542 | prefix = prefix || "test"; 2543 | var rPrefix = new RegExp("^" + prefix); 2544 | var methods = {}, testName, property, method; 2545 | var setUp = tests.setUp; 2546 | var tearDown = tests.tearDown; 2547 | 2548 | for (testName in tests) { 2549 | if (tests.hasOwnProperty(testName)) { 2550 | property = tests[testName]; 2551 | 2552 | if (/^(setUp|tearDown)$/.test(testName)) { 2553 | continue; 2554 | } 2555 | 2556 | if (typeof property == "function" && rPrefix.test(testName)) { 2557 | method = property; 2558 | 2559 | if (setUp || tearDown) { 2560 | method = createTest(property, setUp, tearDown); 2561 | } 2562 | 2563 | methods[testName] = sinon.test(method); 2564 | } else { 2565 | methods[testName] = tests[testName]; 2566 | } 2567 | } 2568 | } 2569 | 2570 | return methods; 2571 | } 2572 | 2573 | if (commonJSModule) { 2574 | module.exports = testCase; 2575 | } else { 2576 | sinon.testCase = testCase; 2577 | } 2578 | }(typeof sinon == "object" && sinon || null)); 2579 | 2580 | /** 2581 | * @depend ../sinon.js 2582 | * @depend stub.js 2583 | */ 2584 | /*jslint eqeqeq: false, onevar: false, nomen: false, plusplus: false*/ 2585 | /*global module, require, sinon*/ 2586 | /** 2587 | * Assertions matching the test spy retrieval interface. 2588 | * 2589 | * @author Christian Johansen (christian@cjohansen.no) 2590 | * @license BSD 2591 | * 2592 | * Copyright (c) 2010-2011 Christian Johansen 2593 | */ 2594 | 2595 | (function (sinon) { 2596 | var commonJSModule = typeof module == "object" && typeof require == "function"; 2597 | var slice = Array.prototype.slice; 2598 | var assert; 2599 | 2600 | if (!sinon && commonJSModule) { 2601 | sinon = require("sinon"); 2602 | } 2603 | 2604 | if (!sinon) { 2605 | return; 2606 | } 2607 | 2608 | function times(count) { 2609 | return count == 1 && "once" || 2610 | count == 2 && "twice" || 2611 | count == 3 && "thrice" || 2612 | (count || 0) + " times"; 2613 | } 2614 | 2615 | function verifyIsStub(method) { 2616 | if (!method) { 2617 | assert.fail("fake is not a spy"); 2618 | } 2619 | 2620 | if (typeof method != "function") { 2621 | assert.fail(method + " is not a function"); 2622 | } 2623 | 2624 | if (typeof method.getCall != "function") { 2625 | assert.fail(method + " is not stubbed"); 2626 | } 2627 | } 2628 | 2629 | function failAssertion(object, msg) { 2630 | var failMethod = object.fail || assert.fail; 2631 | failMethod.call(object, msg); 2632 | } 2633 | 2634 | function mirrorAssertion(method, message) { 2635 | assert[method] = function (fake) { 2636 | verifyIsStub(fake); 2637 | 2638 | var failed = typeof fake[method] == "function" ? 2639 | !fake[method].apply(fake, slice.call(arguments, 1)) : !fake[method]; 2640 | 2641 | if (failed) { 2642 | var msg = message.replace("%c", times(fake.callCount)); 2643 | msg = msg.replace("%n", fake + ""); 2644 | 2645 | msg = msg.replace("%C", function (m) { 2646 | return formatSpyCalls(fake); 2647 | }); 2648 | 2649 | msg = msg.replace("%t", function (m) { 2650 | return formatThisValues(fake); 2651 | }); 2652 | 2653 | msg = msg.replace("%*", [].slice.call(arguments, 1).join(", ")); 2654 | 2655 | for (var i = 0, l = arguments.length; i < l; i++) { 2656 | msg = msg.replace("%" + i, arguments[i]); 2657 | } 2658 | 2659 | failAssertion(this, msg); 2660 | } else { 2661 | assert.pass(method); 2662 | } 2663 | }; 2664 | } 2665 | 2666 | function formatSpyCalls(spy) { 2667 | var calls = []; 2668 | 2669 | for (var i = 0, l = spy.callCount; i < l; ++i) { 2670 | calls.push(" " + spy.getCall(i).toString()); 2671 | } 2672 | 2673 | return calls.length > 0 ? "\n" + calls.join("\n") : ""; 2674 | } 2675 | 2676 | function formatThisValues(spy) { 2677 | var objects = []; 2678 | 2679 | for (var i = 0, l = spy.callCount; i < l; ++i) { 2680 | objects.push(sinon.format(spy.thisValues[i])); 2681 | } 2682 | 2683 | return objects.join(", "); 2684 | } 2685 | 2686 | assert = { 2687 | failException: "AssertError", 2688 | 2689 | fail: function fail(message) { 2690 | var error = new Error(message); 2691 | error.name = this.failException || assert.failException; 2692 | 2693 | throw error; 2694 | }, 2695 | 2696 | pass: function pass(assertion) {}, 2697 | 2698 | called: function assertCalled(method) { 2699 | verifyIsStub(method); 2700 | 2701 | if (!method.called) { 2702 | failAssertion(this, "expected " + method + 2703 | " to have been called at least once but was never called"); 2704 | } else { 2705 | assert.pass("called"); 2706 | } 2707 | }, 2708 | 2709 | notCalled: function assertNotCalled(method) { 2710 | verifyIsStub(method); 2711 | 2712 | if (method.called) { 2713 | failAssertion( 2714 | this, "expected " + method + " to not have been called but was " + 2715 | "called " + times(method.callCount) + formatSpyCalls(method)); 2716 | } else { 2717 | assert.pass("notCalled"); 2718 | } 2719 | }, 2720 | 2721 | callOrder: function assertCallOrder() { 2722 | verifyIsStub(arguments[0]); 2723 | var expected = []; 2724 | var actual = []; 2725 | var failed = false; 2726 | expected.push(arguments[0]); 2727 | 2728 | for (var i = 1, l = arguments.length; i < l; i++) { 2729 | verifyIsStub(arguments[i]); 2730 | expected.push(arguments[i]); 2731 | 2732 | if (!arguments[i - 1].calledBefore(arguments[i])) { 2733 | failed = true; 2734 | } 2735 | } 2736 | 2737 | if (failed) { 2738 | actual = [].concat(expected).sort(function (a, b) { 2739 | var aId = a.getCall(0).callId; 2740 | var bId = b.getCall(0).callId; 2741 | 2742 | // uuid, won't ever be equal 2743 | return aId < bId ? -1 : 1; 2744 | }); 2745 | 2746 | var expectedStr, actualStr; 2747 | 2748 | try { 2749 | expectedStr = expected.join(", "); 2750 | actualStr = actual.join(", "); 2751 | } catch (e) {} 2752 | 2753 | failAssertion(this, "expected " + (expectedStr || "") + " to be " + 2754 | "called in order but were called as " + actualStr); 2755 | } else { 2756 | assert.pass("callOrder"); 2757 | } 2758 | }, 2759 | 2760 | callCount: function assertCallCount(method, count) { 2761 | verifyIsStub(method); 2762 | 2763 | if (method.callCount != count) { 2764 | failAssertion(this, "expected " + method + " to be called " + 2765 | times(count) + " but was called " + 2766 | times(method.callCount) + formatSpyCalls(method)); 2767 | } else { 2768 | assert.pass("callCount"); 2769 | } 2770 | }, 2771 | 2772 | expose: function expose(target, options) { 2773 | if (!target) { 2774 | throw new TypeError("target is null or undefined"); 2775 | } 2776 | 2777 | options = options || {}; 2778 | var prefix = typeof options.prefix == "undefined" && "assert" || options.prefix; 2779 | 2780 | var name = function (prop) { 2781 | if (!prefix) { 2782 | return prop; 2783 | } 2784 | 2785 | return prefix + prop.substring(0, 1).toUpperCase() + prop.substring(1); 2786 | }; 2787 | 2788 | for (var assertion in this) { 2789 | if (!/^(fail|expose)/.test(assertion)) { 2790 | target[name(assertion)] = this[assertion]; 2791 | } 2792 | } 2793 | 2794 | if (typeof options.includeFail == "undefined" || !!options.includeFail) { 2795 | target.fail = this.fail; 2796 | target.failException = this.failException; 2797 | } 2798 | 2799 | return target; 2800 | } 2801 | }; 2802 | 2803 | mirrorAssertion("calledOnce", "expected %n to be called once but was called %c%C"); 2804 | mirrorAssertion("calledTwice", "expected %n to be called twice but was called %c%C"); 2805 | mirrorAssertion("calledThrice", "expected %n to be called thrice but was called %c%C"); 2806 | mirrorAssertion("calledOn", "expected %n to be called with %1 as this but was called with %t"); 2807 | mirrorAssertion("alwaysCalledOn", "expected %n to always be called with %1 as this but was called with %t"); 2808 | mirrorAssertion("calledWith", "expected %n to be called with arguments %*%C"); 2809 | mirrorAssertion("alwaysCalledWith", "expected %n to always be called with arguments %*%C"); 2810 | mirrorAssertion("calledWithExactly", "expected %n to be called with exact arguments %*%C"); 2811 | mirrorAssertion("alwaysCalledWithExactly", "expected %n to always be called with exact arguments %*%C"); 2812 | mirrorAssertion("threw", "%n did not throw exception%C"); 2813 | mirrorAssertion("alwaysThrew", "%n did not always throw exception%C"); 2814 | 2815 | if (commonJSModule) { 2816 | module.exports = assert; 2817 | } else { 2818 | sinon.assert = assert; 2819 | } 2820 | }(typeof sinon == "object" && sinon || null)); 2821 | 2822 | --------------------------------------------------------------------------------