├── .gitignore ├── .npmignore ├── support ├── mocha.css └── mocha.js ├── test ├── common.js ├── index.html └── expect.js ├── Makefile ├── package.json ├── History.md ├── README.md └── expect.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | support 2 | test 3 | Makefile 4 | -------------------------------------------------------------------------------- /support/mocha.css: -------------------------------------------------------------------------------- 1 | ../node_modules/mocha/mocha.css -------------------------------------------------------------------------------- /support/mocha.js: -------------------------------------------------------------------------------- 1 | ../node_modules/mocha/mocha.js -------------------------------------------------------------------------------- /test/common.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Common test dependencies. 4 | */ 5 | 6 | // expose the globals that are obtained through ` 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 0.2.0 / 2012-10-19 3 | ================== 4 | 5 | * fix isRegExp bug in some edge cases 6 | * add closure to all assertion messages deferring costly inspects 7 | until there is actually a failure 8 | * fix `make test` for recent mochas 9 | * add inspect() case for DOM elements 10 | * relax failure msg null check 11 | * add explicit failure through `expect().fail()` 12 | * clarified all `empty` functionality in README example 13 | * added docs for throwException fn/regexp signatures 14 | 15 | 0.1.2 / 2012-02-04 16 | ================== 17 | 18 | * Added regexp matching support for exceptions. 19 | * Added support for throwException callback. 20 | * Added `throwError` synonym to `throwException`. 21 | * Added object support for `.empty`. 22 | * Fixed `.a('object')` with nulls, and english error in error message. 23 | * Fix bug `indexOf` (IE). [hokaccha] 24 | * Fixed object property checking with `undefined` as value. [vovik] 25 | 26 | 0.1.1 / 2011-12-18 27 | ================== 28 | 29 | * Fixed typo 30 | 31 | 0.1.0 / 2011-12-18 32 | ================== 33 | 34 | * Initial import 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Expect 2 | 3 | Minimalistic BDD assertion toolkit based on 4 | [should.js](http://github.com/visionmedia/should.js) 5 | 6 | ```js 7 | expect(window.r).to.be(undefined); 8 | expect({ a: 'b' }).to.eql({ a: 'b' }) 9 | expect(5).to.be.a('number'); 10 | expect([]).to.be.an('array'); 11 | expect(window).not.to.be.an(Image); 12 | ``` 13 | 14 | ## Features 15 | 16 | - Cross-browser: works on IE6+, Firefox, Safari, Chrome, Opera. 17 | - Compatible with all test frameworks. 18 | - Node.JS ready (`require('expect.js')`). 19 | - Standalone. Single global with no prototype extensions or shims. 20 | 21 | ## How to use 22 | 23 | ### Node 24 | 25 | Install it with NPM or add it to your `package.json`: 26 | 27 | ``` 28 | $ npm install expect.js 29 | ``` 30 | 31 | Then: 32 | 33 | ```js 34 | var expect = require('expect.js'); 35 | ``` 36 | 37 | ### Browser 38 | 39 | Expose the `expect.js` found at the top level of this repository. 40 | 41 | ```html 42 | 43 | ``` 44 | 45 | ## API 46 | 47 | **ok**: asserts that the value is _truthy_ or not 48 | 49 | ```js 50 | expect(1).to.be.ok(); 51 | expect(true).to.be.ok(); 52 | expect({}).to.be.ok(); 53 | expect(0).to.not.be.ok(); 54 | ``` 55 | 56 | **be** / **equal**: asserts `===` equality 57 | 58 | ```js 59 | expect(1).to.be(1) 60 | expect(NaN).not.to.equal(NaN); 61 | expect(1).not.to.be(true) 62 | expect('1').to.not.be(1); 63 | ``` 64 | 65 | **eql**: asserts loose equality that works with objects 66 | 67 | ```js 68 | expect({ a: 'b' }).to.eql({ a: 'b' }); 69 | expect(1).to.eql('1'); 70 | ``` 71 | 72 | **a**/**an**: asserts `typeof` with support for `array` type and `instanceof` 73 | 74 | ```js 75 | // typeof with optional `array` 76 | expect(5).to.be.a('number'); 77 | expect([]).to.be.an('array'); // works 78 | expect([]).to.be.an('object'); // works too, since it uses `typeof` 79 | 80 | // constructors 81 | expect(5).to.be.a(Number); 82 | expect([]).to.be.an(Array); 83 | expect(tobi).to.be.a(Ferret); 84 | expect(person).to.be.a(Mammal); 85 | ``` 86 | 87 | **match**: asserts `String` regular expression match 88 | 89 | ```js 90 | expect(program.version).to.match(/[0-9]+\.[0-9]+\.[0-9]+/); 91 | ``` 92 | 93 | **contain**: asserts indexOf for an array or string 94 | 95 | ```js 96 | expect([1, 2]).to.contain(1); 97 | expect('hello world').to.contain('world'); 98 | ``` 99 | 100 | **length**: asserts array `.length` 101 | 102 | ```js 103 | expect([]).to.have.length(0); 104 | expect([1,2,3]).to.have.length(3); 105 | ``` 106 | 107 | **empty**: asserts that an array is empty or not 108 | 109 | ```js 110 | expect([]).to.be.empty(); 111 | expect({}).to.be.empty(); 112 | expect({ length: 0, duck: 'typing' }).to.be.empty(); 113 | expect({ my: 'object' }).to.not.be.empty(); 114 | expect([1,2,3]).to.not.be.empty(); 115 | ``` 116 | 117 | **property**: asserts presence of an own property (and value optionally) 118 | 119 | ```js 120 | expect(window).to.have.property('expect') 121 | expect(window).to.have.property('expect', expect) 122 | expect({a: 'b'}).to.have.property('a'); 123 | ``` 124 | 125 | **key**/**keys**: asserts the presence of a key. Supports the `only` modifier 126 | 127 | ```js 128 | expect({ a: 'b' }).to.have.key('a'); 129 | expect({ a: 'b', c: 'd' }).to.only.have.keys('a', 'c'); 130 | expect({ a: 'b', c: 'd' }).to.only.have.keys(['a', 'c']); 131 | expect({ a: 'b', c: 'd' }).to.not.only.have.key('a'); 132 | ``` 133 | 134 | **throwException**/**throwError**: asserts that the `Function` throws or not when called 135 | 136 | ```js 137 | expect(fn).to.throwError(); // synonym of throwException 138 | expect(fn).to.throwException(function (e) { // get the exception object 139 | expect(e).to.be.a(SyntaxError); 140 | }); 141 | expect(fn).to.throwException(/matches the exception message/); 142 | expect(fn2).to.not.throwException(); 143 | ``` 144 | 145 | **withArgs**: creates anonymous function to call fn with arguments 146 | 147 | ```js 148 | expect(fn).withArgs(invalid, arg).to.throwException(); 149 | expect(fn).withArgs(valid, arg).to.not.throwException(); 150 | ``` 151 | 152 | **within**: asserts a number within a range 153 | 154 | ```js 155 | expect(1).to.be.within(0, Infinity); 156 | ``` 157 | 158 | **greaterThan**/**above**: asserts `>` 159 | 160 | ```js 161 | expect(3).to.be.above(0); 162 | expect(5).to.be.greaterThan(3); 163 | ``` 164 | 165 | **lessThan**/**below**: asserts `<` 166 | 167 | ```js 168 | expect(0).to.be.below(3); 169 | expect(1).to.be.lessThan(3); 170 | ``` 171 | 172 | **fail**: explicitly forces failure. 173 | 174 | ```js 175 | expect().fail() 176 | expect().fail("Custom failure message") 177 | ``` 178 | 179 | **result**: displays the number of the passed tests 180 | 181 | ```js 182 | expect().result() 183 | ``` 184 | 185 | ## Using with a test framework 186 | 187 | For example, if you create a test suite with 188 | [mocha](http://github.com/visionmedia/mocha). 189 | 190 | Let's say we wanted to test the following program: 191 | 192 | **math.js** 193 | 194 | ```js 195 | function add (a, b) { return a + b; }; 196 | ``` 197 | 198 | Our test file would look like this: 199 | 200 | ```js 201 | describe('test suite', function () { 202 | it('should expose a function', function () { 203 | expect(add).to.be.a('function'); 204 | }); 205 | 206 | it('should do math', function () { 207 | expect(add(1, 3)).to.equal(4); 208 | }); 209 | }); 210 | ``` 211 | 212 | If a certain expectation fails, an exception will be raised which gets captured 213 | and shown/processed by the test runner. 214 | 215 | ## Differences with should.js 216 | 217 | - No need for static `should` methods like `should.strictEqual`. For example, 218 | `expect(obj).to.be(undefined)` works well. 219 | - Some API simplifications / changes. 220 | - API changes related to browser compatibility. 221 | 222 | ## Running tests 223 | 224 | Clone the repository and install the developer dependencies: 225 | 226 | ``` 227 | git clone git://github.com/LearnBoost/expect.js.git expect 228 | cd expect && npm install 229 | ``` 230 | 231 | ### Node 232 | 233 | `make test` 234 | 235 | ### Browser 236 | 237 | `make test-browser` 238 | 239 | and point your browser(s) to `http://localhost:3000/test/` 240 | 241 | ## Credits 242 | 243 | (The MIT License) 244 | 245 | Copyright (c) 2011 Guillermo Rauch <guillermo@learnboost.com> 246 | 247 | Permission is hereby granted, free of charge, to any person obtaining 248 | a copy of this software and associated documentation files (the 249 | 'Software'), to deal in the Software without restriction, including 250 | without limitation the rights to use, copy, modify, merge, publish, 251 | distribute, sublicense, and/or sell copies of the Software, and to 252 | permit persons to whom the Software is furnished to do so, subject to 253 | the following conditions: 254 | 255 | The above copyright notice and this permission notice shall be 256 | included in all copies or substantial portions of the Software. 257 | 258 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 259 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 260 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 261 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 262 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 263 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 264 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 265 | 266 | ### 3rd-party 267 | 268 | Heavily borrows from [should.js](http://github.com/visionmedia/should.js) by TJ 269 | Holowaychuck - MIT. 270 | -------------------------------------------------------------------------------- /test/expect.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | function err (fn, msg) { 7 | try { 8 | fn(); 9 | throw new Error('Expected an error'); 10 | } catch (err) { 11 | expect(msg).to.be(err.message); 12 | } 13 | } 14 | 15 | /** 16 | * Feature detection for `name` support. 17 | */ 18 | 19 | var nameSupported; 20 | 21 | (function a () { 22 | nameSupported = 'a' == a.name; 23 | })(); 24 | 25 | /** 26 | * Tests. 27 | */ 28 | 29 | describe('expect', function () { 30 | 31 | it('should have .version', function () { 32 | expect(expect.version).to.match(/^\d+\.\d+\.\d+$/); 33 | }); 34 | 35 | it('should work in its basic form', function () { 36 | expect('test').to.be.a('string'); 37 | }); 38 | 39 | it('should test true', function () { 40 | expect(true).to.be(true); 41 | expect(false).to.not.be(true); 42 | expect(1).to.not.be(true); 43 | 44 | err(function () { 45 | expect('test').to.be(true); 46 | }, "expected 'test' to equal true") 47 | }); 48 | 49 | it('should allow not.to', function () { 50 | expect(true).not.to.be(false); 51 | 52 | err(function () { 53 | expect(false).not.to.be(false); 54 | }, "expected false to not equal false") 55 | }); 56 | 57 | it('should test ok', function () { 58 | expect(true).to.be.ok(); 59 | expect(false).to.not.be.ok(); 60 | expect(1).to.be.ok(); 61 | expect(0).to.not.be.ok(); 62 | 63 | err(function () { 64 | expect('').to.be.ok(); 65 | }, "expected '' to be truthy"); 66 | 67 | err(function () { 68 | expect('test').to.not.be.ok(); 69 | }, "expected 'test' to be falsy"); 70 | }); 71 | 72 | it('should test false', function () { 73 | expect(false).to.be(false); 74 | expect(true).to.not.be(false); 75 | expect(0).to.not.be(false); 76 | 77 | err(function () { 78 | expect('').to.be(false); 79 | }, "expected '' to equal false") 80 | }); 81 | 82 | it('should test functions with arguments', function () { 83 | function itThrowsSometimes (first, second) { 84 | if (first ^ second) { 85 | throw new Error('tell'); 86 | } 87 | } 88 | 89 | expect(itThrowsSometimes).withArgs(false, false).to.not.throwException(); 90 | expect(itThrowsSometimes).withArgs(false, true).to.throwException(/tell/); 91 | expect(itThrowsSometimes).withArgs(true, false).to.throwException(/tell/); 92 | expect(itThrowsSometimes).withArgs(true, true).to.not.throwException(); 93 | }); 94 | 95 | it('should test for exceptions', function () { 96 | function itThrows () { 97 | a.b.c; 98 | } 99 | 100 | function itThrowsString () { 101 | throw 'aaa'; 102 | } 103 | 104 | function itThrowsMessage () { 105 | throw new Error('tobi'); 106 | } 107 | 108 | var anonItThrows = function () { 109 | a.b.c; 110 | } 111 | 112 | function itWorks () { 113 | return 114 | } 115 | 116 | var anonItWorks = function () { } 117 | 118 | expect(itThrows).to.throwException(); 119 | expect(itWorks).to.not.throwException(); 120 | 121 | var subject; 122 | 123 | expect(itThrows).to.throwException(function (e) { 124 | subject = e; 125 | }); 126 | 127 | expect(subject).to.be.an(Error); 128 | 129 | expect(itThrowsMessage).to.throwException(/tobi/); 130 | expect(itThrowsMessage).to.not.throwException(/test/); 131 | 132 | err(function () { 133 | expect(itThrowsMessage).to.throwException(/no match/); 134 | }, 'expected \'tobi\' to match /no match/'); 135 | 136 | var subject2; 137 | 138 | expect(itThrowsString).to.throwException(function (str) { 139 | subject2 = str; 140 | }); 141 | 142 | expect(subject2).to.be('aaa'); 143 | 144 | expect(itThrowsString).to.throwException(/aaa/); 145 | expect(itThrowsString).to.not.throwException(/bbb/); 146 | 147 | err(function () { 148 | expect(itThrowsString).to.throwException(/no match/i); 149 | }, 'expected \'aaa\' to match /no match/i'); 150 | 151 | var called = false; 152 | 153 | expect(itWorks).to.not.throwError(function () { 154 | called = true; 155 | }); 156 | 157 | expect(called).to.be(false); 158 | 159 | err(function () { 160 | expect(5).to.throwException(); 161 | }, 'expected 5 to be a function'); 162 | 163 | err(function () { 164 | expect(anonItThrows).not.to.throwException(); 165 | }, 'expected fn not to throw an exception'); 166 | 167 | err(function () { 168 | expect(anonItWorks).to.throwException(); 169 | }, 'expected fn to throw an exception'); 170 | 171 | if (nameSupported) { 172 | err(function () { 173 | expect(itWorks).to.throwException(); 174 | }, 'expected itWorks to throw an exception'); 175 | } else { 176 | err(function () { 177 | expect(itWorks).to.throwException(); 178 | }, 'expected fn to throw an exception'); 179 | } 180 | 181 | if (nameSupported) { 182 | err(function () { 183 | expect(itThrows).not.to.throwException(); 184 | }, 'expected itThrows not to throw an exception'); 185 | } else { 186 | err(function () { 187 | expect(anonItThrows).not.to.throwException(); 188 | }, 'expected fn not to throw an exception'); 189 | } 190 | }); 191 | 192 | it('should test arrays', function () { 193 | expect([]).to.be.a('array'); 194 | expect([]).to.be.an('array'); 195 | 196 | err(function () { 197 | expect({}).to.be.an('array'); 198 | }, 'expected {} to be an array'); 199 | }); 200 | 201 | it('should test regex', function () { 202 | expect(/a/).to.be.an('regexp'); 203 | expect(/a/).to.be.a('regexp'); 204 | 205 | err(function () { 206 | expect(null).to.be.a('regexp'); 207 | }, 'expected null to be a regexp'); 208 | }); 209 | 210 | it('should test objects', function () { 211 | expect({}).to.be.an('object'); 212 | 213 | err(function () { 214 | expect(null).to.be.an('object'); 215 | }, 'expected null to be an object'); 216 | }); 217 | 218 | it('should test .equal()', function () { 219 | var foo; 220 | expect(foo).to.be(undefined); 221 | }); 222 | 223 | it('should test typeof', function () { 224 | expect('test').to.be.a('string'); 225 | 226 | err(function () { 227 | expect('test').to.not.be.a('string'); 228 | }, "expected 'test' not to be a string"); 229 | 230 | expect(5).to.be.a('number'); 231 | 232 | err(function () { 233 | expect(5).to.not.be.a('number'); 234 | }, "expected 5 not to be a number"); 235 | }); 236 | 237 | it('should test instanceof', function () { 238 | function Foo(){} 239 | expect(new Foo()).to.be.a(Foo); 240 | 241 | if (nameSupported) { 242 | err(function () { 243 | expect(3).to.be.a(Foo); 244 | }, "expected 3 to be an instance of Foo"); 245 | } else { 246 | err(function () { 247 | expect(3).to.be.a(Foo); 248 | }, "expected 3 to be an instance of supplied constructor"); 249 | } 250 | }); 251 | 252 | it('should test within(start, finish)', function () { 253 | expect(5).to.be.within(3,6); 254 | expect(5).to.be.within(3,5); 255 | expect(5).to.not.be.within(1,3); 256 | 257 | err(function () { 258 | expect(5).to.not.be.within(4,6); 259 | }, "expected 5 to not be within 4..6"); 260 | 261 | err(function () { 262 | expect(10).to.be.within(50,100); 263 | }, "expected 10 to be within 50..100"); 264 | }); 265 | 266 | it('should test above(n)', function () { 267 | expect(5).to.be.above(2); 268 | expect(5).to.be.greaterThan(2); 269 | expect(5).to.not.be.above(5); 270 | expect(5).to.not.be.above(6); 271 | 272 | err(function () { 273 | expect(5).to.be.above(6); 274 | }, "expected 5 to be above 6"); 275 | 276 | err(function () { 277 | expect(10).to.not.be.above(6); 278 | }, "expected 10 to be below 6"); 279 | }); 280 | 281 | it('should test match(regexp)', function () { 282 | expect('foobar').to.match(/^foo/) 283 | expect('foobar').to.not.match(/^bar/) 284 | 285 | err(function () { 286 | expect('foobar').to.match(/^bar/i) 287 | }, "expected 'foobar' to match /^bar/i"); 288 | 289 | err(function () { 290 | expect('foobar').to.not.match(/^foo/i) 291 | }, "expected 'foobar' not to match /^foo/i"); 292 | }); 293 | 294 | it('should test length(n)', function () { 295 | expect('test').to.have.length(4); 296 | expect('test').to.not.have.length(3); 297 | expect([1,2,3]).to.have.length(3); 298 | 299 | err(function () { 300 | expect(4).to.have.length(3); 301 | }, 'expected 4 to have a property \'length\''); 302 | 303 | err(function () { 304 | expect('asd').to.not.have.length(3); 305 | }, "expected 'asd' to not have a length of 3"); 306 | }); 307 | 308 | it('should test eql(val)', function () { 309 | expect('test').to.eql('test'); 310 | expect({ foo: 'bar' }).to.eql({ foo: 'bar' }); 311 | expect(1).to.eql(1); 312 | expect('4').to.eql(4); 313 | expect(/a/gmi).to.eql(/a/mig); 314 | 315 | err(function () { 316 | expect(4).to.eql(3); 317 | }, 'expected 4 to sort of equal 3'); 318 | }); 319 | 320 | it('should test equal(val)', function () { 321 | expect('test').to.equal('test'); 322 | expect(1).to.equal(1); 323 | 324 | err(function () { 325 | expect(4).to.equal(3); 326 | }, 'expected 4 to equal 3'); 327 | 328 | err(function () { 329 | expect('4').to.equal(4); 330 | }, "expected '4' to equal 4"); 331 | }); 332 | 333 | it('should test be(val)', function () { 334 | expect('test').to.be('test'); 335 | expect(1).to.be(1); 336 | 337 | err(function () { 338 | expect(4).to.be(3); 339 | }, 'expected 4 to equal 3'); 340 | 341 | err(function () { 342 | expect('4').to.be(4); 343 | }, "expected '4' to equal 4"); 344 | }); 345 | 346 | it('should test empty', function () { 347 | expect('').to.be.empty(); 348 | expect({}).to.be.empty(); 349 | expect([]).to.be.empty(); 350 | expect({ length: 0 }).to.be.empty(); 351 | 352 | err(function () { 353 | expect(null).to.be.empty(); 354 | }, 'expected null to be an object'); 355 | 356 | err(function () { 357 | expect({ a: 'b' }).to.be.empty(); 358 | }, 'expected { a: \'b\' } to be empty'); 359 | 360 | err(function () { 361 | expect({ length: '0' }).to.be.empty(); 362 | }, 'expected { length: \'0\' } to be empty'); 363 | 364 | err(function () { 365 | expect('asd').to.be.empty(); 366 | }, "expected 'asd' to be empty"); 367 | 368 | err(function () { 369 | expect('').to.not.be.empty(); 370 | }, "expected '' to not be empty"); 371 | 372 | err(function () { 373 | expect({}).to.not.be.empty(); 374 | }, "expected {} to not be empty"); 375 | }); 376 | 377 | it('should test property(name)', function () { 378 | expect('test').to.have.property('length'); 379 | expect(4).to.not.have.property('length'); 380 | expect({ length: undefined }).to.have.property('length'); 381 | 382 | err(function () { 383 | expect('asd').to.have.property('foo'); 384 | }, "expected 'asd' to have a property 'foo'"); 385 | 386 | err(function () { 387 | expect({ length: undefined }).to.not.have.property('length'); 388 | }, "expected { length: undefined } to not have a property 'length'"); 389 | }); 390 | 391 | it('should test property(name, val)', function () { 392 | expect('test').to.have.property('length', 4); 393 | expect({ length: undefined }).to.have.property('length', undefined); 394 | 395 | err(function () { 396 | expect('asd').to.have.property('length', 4); 397 | }, "expected 'asd' to have a property 'length' of 4, but got 3"); 398 | 399 | err(function () { 400 | expect('asd').to.not.have.property('length', 3); 401 | }, "expected 'asd' to not have a property 'length' of 3"); 402 | 403 | err(function () { 404 | expect('asd').to.not.have.property('foo', 3); 405 | }, "'asd' has no property 'foo'"); 406 | 407 | err(function () { 408 | expect({ length: undefined }).to.not.have.property('length', undefined); 409 | }, "expected { length: undefined } to not have a property 'length'"); 410 | }); 411 | 412 | it('should test own.property(name)', function () { 413 | expect('test').to.have.own.property('length'); 414 | expect({ length: 12 }).to.have.own.property('length'); 415 | 416 | err(function () { 417 | expect({ length: 12 }).to.not.have.own.property('length'); 418 | }, "expected { length: 12 } to not have own property 'length'"); 419 | }); 420 | 421 | it('should test string()', function () { 422 | expect('foobar').to.contain('bar'); 423 | expect('foobar').to.contain('foo'); 424 | expect('foobar').to.include.string('foo'); 425 | expect('foobar').to.not.contain('baz'); 426 | expect('foobar').to.not.include.string('baz'); 427 | 428 | err(function () { 429 | expect(3).to.contain('baz'); 430 | }, "expected 3 to contain 'baz'"); 431 | 432 | err(function () { 433 | expect('foobar').to.contain('baz'); 434 | }, "expected 'foobar' to contain 'baz'"); 435 | 436 | err(function () { 437 | expect('foobar').to.not.contain('bar'); 438 | }, "expected 'foobar' to not contain 'bar'"); 439 | }); 440 | 441 | it('should test contain()', function () { 442 | expect(['foo', 'bar']).to.contain('foo'); 443 | expect(['foo', 'bar']).to.contain('foo'); 444 | expect(['foo', 'bar']).to.contain('bar'); 445 | expect([1,2]).to.contain(1); 446 | expect(['foo', 'bar']).to.not.contain('baz'); 447 | expect(['foo', 'bar']).to.not.contain(1); 448 | 449 | err(function () { 450 | expect(['foo']).to.contain('bar'); 451 | }, "expected [ 'foo' ] to contain 'bar'"); 452 | 453 | err(function () { 454 | expect(['bar', 'foo']).to.not.contain('foo'); 455 | }, "expected [ 'bar', 'foo' ] to not contain 'foo'"); 456 | }); 457 | 458 | it('should test keys(array)', function () { 459 | expect({ foo: 1 }).to.have.keys(['foo']); 460 | expect({ foo: 1, bar: 2 }).to.have.keys(['foo', 'bar']); 461 | expect({ foo: 1, bar: 2 }).to.have.keys('foo', 'bar'); 462 | expect({ foo: 1, bar: 2, baz: 3 }).to.include.keys('foo', 'bar'); 463 | expect({ foo: 1, bar: 2, baz: 3 }).to.include.keys('bar', 'foo'); 464 | expect({ foo: 1, bar: 2, baz: 3 }).to.include.keys('baz'); 465 | 466 | expect({ foo: 1, bar: 2 }).to.include.keys('foo'); 467 | expect({ foo: 1, bar: 2 }).to.include.keys('bar', 'foo'); 468 | expect({ foo: 1, bar: 2 }).to.include.keys(['foo']); 469 | expect({ foo: 1, bar: 2 }).to.include.keys(['bar']); 470 | expect({ foo: 1, bar: 2 }).to.include.keys(['bar', 'foo']); 471 | 472 | expect({ foo: 1, bar: 2 }).to.not.have.keys('baz'); 473 | expect({ foo: 1, bar: 2 }).to.not.have.keys('foo', 'baz'); 474 | expect({ foo: 1, bar: 2 }).to.not.include.keys('baz'); 475 | expect({ foo: 1, bar: 2 }).to.not.include.keys('foo', 'baz'); 476 | expect({ foo: 1, bar: 2 }).to.not.include.keys('baz', 'foo'); 477 | 478 | err(function () { 479 | expect({ foo: 1 }).to.have.keys(); 480 | }, "keys required"); 481 | 482 | err(function () { 483 | expect({ foo: 1 }).to.have.keys([]); 484 | }, "keys required"); 485 | 486 | err(function () { 487 | expect({ foo: 1 }).to.not.have.keys([]); 488 | }, "keys required"); 489 | 490 | err(function () { 491 | expect({ foo: 1 }).to.include.keys([]); 492 | }, "keys required"); 493 | 494 | err(function () { 495 | expect({ foo: 1 }).to.have.keys(['bar']); 496 | }, "expected { foo: 1 } to include key 'bar'"); 497 | 498 | err(function () { 499 | expect({ foo: 1 }).to.have.keys(['bar', 'baz']); 500 | }, "expected { foo: 1 } to include keys 'bar', and 'baz'"); 501 | 502 | err(function () { 503 | expect({ foo: 1 }).to.have.keys(['foo', 'bar', 'baz']); 504 | }, "expected { foo: 1 } to include keys 'foo', 'bar', and 'baz'"); 505 | 506 | err(function () { 507 | expect({ foo: 1 }).to.not.have.keys(['foo']); 508 | }, "expected { foo: 1 } to not include key 'foo'"); 509 | 510 | err(function () { 511 | expect({ foo: 1 }).to.not.have.keys(['foo']); 512 | }, "expected { foo: 1 } to not include key 'foo'"); 513 | 514 | err(function () { 515 | expect({ foo: 1, bar: 2 }).to.not.have.keys(['foo', 'bar']); 516 | }, "expected { foo: 1, bar: 2 } to not include keys 'foo', and 'bar'"); 517 | 518 | err(function () { 519 | expect({ foo: 1 }).to.not.include.keys(['foo']); 520 | }, "expected { foo: 1 } to not include key 'foo'"); 521 | 522 | err(function () { 523 | expect({ foo: 1 }).to.include.keys('foo', 'bar'); 524 | }, "expected { foo: 1 } to include keys 'foo', and 'bar'"); 525 | 526 | // only 527 | expect({ foo: 1, bar: 1 }).to.only.have.keys('foo', 'bar'); 528 | expect({ foo: 1, bar: 1 }).to.only.have.keys(['foo', 'bar']); 529 | 530 | err(function () { 531 | expect({ a: 'b', c: 'd' }).to.only.have.keys('a', 'b', 'c'); 532 | }, "expected { a: 'b', c: 'd' } to only have keys 'a', 'b', and 'c'"); 533 | 534 | err(function () { 535 | expect({ a: 'b', c: 'd' }).to.only.have.keys('a'); 536 | }, "expected { a: 'b', c: 'd' } to only have key 'a'"); 537 | }); 538 | 539 | it('should allow chaining with `and`', function () { 540 | expect(5).to.be.a('number').and.be(5); 541 | expect(5).to.be.a('number').and.not.be(6); 542 | expect(5).to.be.a('number').and.not.be(6).and.not.be('5'); 543 | 544 | err(function () { 545 | expect(5).to.be.a('number').and.not.be(5); 546 | }, "expected 5 to not equal 5"); 547 | 548 | err(function () { 549 | expect(5).to.be.a('number').and.not.be(6).and.not.be.above(4); 550 | }, "expected 5 to be below 4"); 551 | }); 552 | 553 | it('should fail with `fail`', function () { 554 | err(function () { 555 | expect().fail(); 556 | }, "explicit failure"); 557 | }); 558 | 559 | it('should fail with `fail` and custom message', function () { 560 | err(function () { 561 | expect().fail("explicit failure with message"); 562 | }, "explicit failure with message"); 563 | }); 564 | 565 | }); 566 | -------------------------------------------------------------------------------- /expect.js: -------------------------------------------------------------------------------- 1 | (function (global, module) { 2 | 3 | var exports = module.exports; 4 | 5 | /** 6 | * Exports. 7 | */ 8 | 9 | module.exports = expect; 10 | expect.Assertion = Assertion; 11 | 12 | /** 13 | * Exports version. 14 | */ 15 | 16 | expect.version = '0.1.2'; 17 | 18 | /** 19 | * A number of the passed tests. 20 | */ 21 | 22 | expect.passedTests = 0; 23 | 24 | /** 25 | * Possible assertion flags. 26 | */ 27 | 28 | var flags = { 29 | not: ['to', 'be', 'have', 'include', 'only'] 30 | , to: ['be', 'have', 'include', 'only', 'not'] 31 | , only: ['have'] 32 | , have: ['own'] 33 | , be: ['an'] 34 | }; 35 | 36 | function expect (obj) { 37 | expect.passedTests++; 38 | return new Assertion(obj); 39 | } 40 | 41 | /** 42 | * Constructor 43 | * 44 | * @api private 45 | */ 46 | 47 | function Assertion (obj, flag, parent) { 48 | this.obj = obj; 49 | this.flags = {}; 50 | 51 | if (undefined != parent) { 52 | this.flags[flag] = true; 53 | 54 | for (var i in parent.flags) { 55 | if (parent.flags.hasOwnProperty(i)) { 56 | this.flags[i] = true; 57 | } 58 | } 59 | } 60 | 61 | var $flags = flag ? flags[flag] : keys(flags) 62 | , self = this; 63 | 64 | if ($flags) { 65 | for (var i = 0, l = $flags.length; i < l; i++) { 66 | // avoid recursion 67 | if (this.flags[$flags[i]]) continue; 68 | 69 | var name = $flags[i] 70 | , assertion = new Assertion(this.obj, name, this) 71 | 72 | if ('function' == typeof Assertion.prototype[name]) { 73 | // clone the function, make sure we dont touch the prot reference 74 | var old = this[name]; 75 | this[name] = function () { 76 | return old.apply(self, arguments); 77 | }; 78 | 79 | for (var fn in Assertion.prototype) { 80 | if (Assertion.prototype.hasOwnProperty(fn) && fn != name) { 81 | this[name][fn] = bind(assertion[fn], assertion); 82 | } 83 | } 84 | } else { 85 | this[name] = assertion; 86 | } 87 | } 88 | } 89 | } 90 | 91 | /** 92 | * Performs an assertion 93 | * 94 | * @api private 95 | */ 96 | 97 | Assertion.prototype.assert = function (truth, msg, error, expected) { 98 | var msg = this.flags.not ? error : msg 99 | , ok = this.flags.not ? !truth : truth 100 | , err; 101 | 102 | if (!ok) { 103 | err = new Error(msg.call(this)); 104 | if (arguments.length > 3) { 105 | err.actual = this.obj; 106 | err.expected = expected; 107 | err.showDiff = true; 108 | } 109 | throw err; 110 | } 111 | 112 | this.and = new Assertion(this.obj); 113 | }; 114 | 115 | /** 116 | * Check if the value is truthy 117 | * 118 | * @api public 119 | */ 120 | 121 | Assertion.prototype.ok = function () { 122 | this.assert( 123 | !!this.obj 124 | , function(){ return 'expected ' + i(this.obj) + ' to be truthy' } 125 | , function(){ return 'expected ' + i(this.obj) + ' to be falsy' }); 126 | }; 127 | 128 | /** 129 | * Creates an anonymous function which calls fn with arguments. 130 | * 131 | * @api public 132 | */ 133 | 134 | Assertion.prototype.withArgs = function() { 135 | expect(this.obj).to.be.a('function'); 136 | var fn = this.obj; 137 | var args = Array.prototype.slice.call(arguments); 138 | return expect(function() { fn.apply(null, args); }); 139 | }; 140 | 141 | /** 142 | * Assert that the function throws. 143 | * 144 | * @param {Function|RegExp} callback, or regexp to match error string against 145 | * @api public 146 | */ 147 | 148 | Assertion.prototype.throwError = 149 | Assertion.prototype.throwException = function (fn) { 150 | expect(this.obj).to.be.a('function'); 151 | 152 | var thrown = false 153 | , not = this.flags.not; 154 | 155 | try { 156 | this.obj(); 157 | } catch (e) { 158 | if (isRegExp(fn)) { 159 | var subject = 'string' == typeof e ? e : e.message; 160 | if (not) { 161 | expect(subject).to.not.match(fn); 162 | } else { 163 | expect(subject).to.match(fn); 164 | } 165 | } else if ('function' == typeof fn) { 166 | fn(e); 167 | } 168 | thrown = true; 169 | } 170 | 171 | if (isRegExp(fn) && not) { 172 | // in the presence of a matcher, ensure the `not` only applies to 173 | // the matching. 174 | this.flags.not = false; 175 | } 176 | 177 | var name = this.obj.name || 'fn'; 178 | this.assert( 179 | thrown 180 | , function(){ return 'expected ' + name + ' to throw an exception' } 181 | , function(){ return 'expected ' + name + ' not to throw an exception' }); 182 | }; 183 | 184 | /** 185 | * Checks if the array is empty. 186 | * 187 | * @api public 188 | */ 189 | 190 | Assertion.prototype.empty = function () { 191 | var expectation; 192 | 193 | if ('object' == typeof this.obj && null !== this.obj && !isArray(this.obj)) { 194 | if ('number' == typeof this.obj.length) { 195 | expectation = !this.obj.length; 196 | } else { 197 | expectation = !keys(this.obj).length; 198 | } 199 | } else { 200 | if ('string' != typeof this.obj) { 201 | expect(this.obj).to.be.an('object'); 202 | } 203 | 204 | expect(this.obj).to.have.property('length'); 205 | expectation = !this.obj.length; 206 | } 207 | 208 | this.assert( 209 | expectation 210 | , function(){ return 'expected ' + i(this.obj) + ' to be empty' } 211 | , function(){ return 'expected ' + i(this.obj) + ' to not be empty' }); 212 | return this; 213 | }; 214 | 215 | /** 216 | * Checks if the obj exactly equals another. 217 | * 218 | * @api public 219 | */ 220 | 221 | Assertion.prototype.be = 222 | Assertion.prototype.equal = function (obj) { 223 | this.assert( 224 | obj === this.obj 225 | , function(){ return 'expected ' + i(this.obj) + ' to equal ' + i(obj) } 226 | , function(){ return 'expected ' + i(this.obj) + ' to not equal ' + i(obj) }); 227 | return this; 228 | }; 229 | 230 | /** 231 | * Checks if the obj sortof equals another. 232 | * 233 | * @api public 234 | */ 235 | 236 | Assertion.prototype.eql = function (obj) { 237 | this.assert( 238 | expect.eql(this.obj, obj) 239 | , function(){ return 'expected ' + i(this.obj) + ' to sort of equal ' + i(obj) } 240 | , function(){ return 'expected ' + i(this.obj) + ' to sort of not equal ' + i(obj) } 241 | , obj); 242 | return this; 243 | }; 244 | 245 | /** 246 | * Assert within start to finish (inclusive). 247 | * 248 | * @param {Number} start 249 | * @param {Number} finish 250 | * @api public 251 | */ 252 | 253 | Assertion.prototype.within = function (start, finish) { 254 | var range = start + '..' + finish; 255 | this.assert( 256 | this.obj >= start && this.obj <= finish 257 | , function(){ return 'expected ' + i(this.obj) + ' to be within ' + range } 258 | , function(){ return 'expected ' + i(this.obj) + ' to not be within ' + range }); 259 | return this; 260 | }; 261 | 262 | /** 263 | * Assert typeof / instance of 264 | * 265 | * @api public 266 | */ 267 | 268 | Assertion.prototype.a = 269 | Assertion.prototype.an = function (type) { 270 | if ('string' == typeof type) { 271 | // proper english in error msg 272 | var n = /^[aeiou]/.test(type) ? 'n' : ''; 273 | 274 | // typeof with support for 'array' 275 | this.assert( 276 | 'array' == type ? isArray(this.obj) : 277 | 'regexp' == type ? isRegExp(this.obj) : 278 | 'object' == type 279 | ? 'object' == typeof this.obj && null !== this.obj 280 | : type == typeof this.obj 281 | , function(){ return 'expected ' + i(this.obj) + ' to be a' + n + ' ' + type } 282 | , function(){ return 'expected ' + i(this.obj) + ' not to be a' + n + ' ' + type }); 283 | } else { 284 | // instanceof 285 | var name = type.name || 'supplied constructor'; 286 | this.assert( 287 | this.obj instanceof type 288 | , function(){ return 'expected ' + i(this.obj) + ' to be an instance of ' + name } 289 | , function(){ return 'expected ' + i(this.obj) + ' not to be an instance of ' + name }); 290 | } 291 | 292 | return this; 293 | }; 294 | 295 | /** 296 | * Assert numeric value above _n_. 297 | * 298 | * @param {Number} n 299 | * @api public 300 | */ 301 | 302 | Assertion.prototype.greaterThan = 303 | Assertion.prototype.above = function (n) { 304 | this.assert( 305 | this.obj > n 306 | , function(){ return 'expected ' + i(this.obj) + ' to be above ' + n } 307 | , function(){ return 'expected ' + i(this.obj) + ' to be below ' + n }); 308 | return this; 309 | }; 310 | 311 | /** 312 | * Assert numeric value below _n_. 313 | * 314 | * @param {Number} n 315 | * @api public 316 | */ 317 | 318 | Assertion.prototype.lessThan = 319 | Assertion.prototype.below = function (n) { 320 | this.assert( 321 | this.obj < n 322 | , function(){ return 'expected ' + i(this.obj) + ' to be below ' + n } 323 | , function(){ return 'expected ' + i(this.obj) + ' to be above ' + n }); 324 | return this; 325 | }; 326 | 327 | /** 328 | * Assert string value matches _regexp_. 329 | * 330 | * @param {RegExp} regexp 331 | * @api public 332 | */ 333 | 334 | Assertion.prototype.match = function (regexp) { 335 | this.assert( 336 | regexp.exec(this.obj) 337 | , function(){ return 'expected ' + i(this.obj) + ' to match ' + regexp } 338 | , function(){ return 'expected ' + i(this.obj) + ' not to match ' + regexp }); 339 | return this; 340 | }; 341 | 342 | /** 343 | * Assert property "length" exists and has value of _n_. 344 | * 345 | * @param {Number} n 346 | * @api public 347 | */ 348 | 349 | Assertion.prototype.length = function (n) { 350 | expect(this.obj).to.have.property('length'); 351 | var len = this.obj.length; 352 | this.assert( 353 | n == len 354 | , function(){ return 'expected ' + i(this.obj) + ' to have a length of ' + n + ' but got ' + len } 355 | , function(){ return 'expected ' + i(this.obj) + ' to not have a length of ' + len }); 356 | return this; 357 | }; 358 | 359 | /** 360 | * Assert property _name_ exists, with optional _val_. 361 | * 362 | * @param {String} name 363 | * @param {Mixed} val 364 | * @api public 365 | */ 366 | 367 | Assertion.prototype.property = function (name, val) { 368 | if (this.flags.own) { 369 | this.assert( 370 | Object.prototype.hasOwnProperty.call(this.obj, name) 371 | , function(){ return 'expected ' + i(this.obj) + ' to have own property ' + i(name) } 372 | , function(){ return 'expected ' + i(this.obj) + ' to not have own property ' + i(name) }); 373 | return this; 374 | } 375 | 376 | if (this.flags.not && undefined !== val) { 377 | if (undefined === this.obj[name]) { 378 | throw new Error(i(this.obj) + ' has no property ' + i(name)); 379 | } 380 | } else { 381 | var hasProp; 382 | try { 383 | hasProp = name in this.obj 384 | } catch (e) { 385 | hasProp = undefined !== this.obj[name] 386 | } 387 | 388 | this.assert( 389 | hasProp 390 | , function(){ return 'expected ' + i(this.obj) + ' to have a property ' + i(name) } 391 | , function(){ return 'expected ' + i(this.obj) + ' to not have a property ' + i(name) }); 392 | } 393 | 394 | if (undefined !== val) { 395 | this.assert( 396 | val === this.obj[name] 397 | , function(){ return 'expected ' + i(this.obj) + ' to have a property ' + i(name) 398 | + ' of ' + i(val) + ', but got ' + i(this.obj[name]) } 399 | , function(){ return 'expected ' + i(this.obj) + ' to not have a property ' + i(name) 400 | + ' of ' + i(val) }); 401 | } 402 | 403 | this.obj = this.obj[name]; 404 | return this; 405 | }; 406 | 407 | /** 408 | * Assert that the array contains _obj_ or string contains _obj_. 409 | * 410 | * @param {Mixed} obj|string 411 | * @api public 412 | */ 413 | 414 | Assertion.prototype.string = 415 | Assertion.prototype.contain = function (obj) { 416 | if ('string' == typeof this.obj) { 417 | this.assert( 418 | ~this.obj.indexOf(obj) 419 | , function(){ return 'expected ' + i(this.obj) + ' to contain ' + i(obj) } 420 | , function(){ return 'expected ' + i(this.obj) + ' to not contain ' + i(obj) }); 421 | } else { 422 | this.assert( 423 | ~indexOf(this.obj, obj) 424 | , function(){ return 'expected ' + i(this.obj) + ' to contain ' + i(obj) } 425 | , function(){ return 'expected ' + i(this.obj) + ' to not contain ' + i(obj) }); 426 | } 427 | return this; 428 | }; 429 | 430 | /** 431 | * Assert exact keys or inclusion of keys by using 432 | * the `.own` modifier. 433 | * 434 | * @param {Array|String ...} keys 435 | * @api public 436 | */ 437 | 438 | Assertion.prototype.key = 439 | Assertion.prototype.keys = function ($keys) { 440 | var str 441 | , ok = true; 442 | 443 | $keys = isArray($keys) 444 | ? $keys 445 | : Array.prototype.slice.call(arguments); 446 | 447 | if (!$keys.length) throw new Error('keys required'); 448 | 449 | var actual = keys(this.obj) 450 | , len = $keys.length; 451 | 452 | // Inclusion 453 | ok = every($keys, function (key) { 454 | return ~indexOf(actual, key); 455 | }); 456 | 457 | // Strict 458 | if (!this.flags.not && this.flags.only) { 459 | ok = ok && $keys.length == actual.length; 460 | } 461 | 462 | // Key string 463 | if (len > 1) { 464 | $keys = map($keys, function (key) { 465 | return i(key); 466 | }); 467 | var last = $keys.pop(); 468 | str = $keys.join(', ') + ', and ' + last; 469 | } else { 470 | str = i($keys[0]); 471 | } 472 | 473 | // Form 474 | str = (len > 1 ? 'keys ' : 'key ') + str; 475 | 476 | // Have / include 477 | str = (!this.flags.only ? 'include ' : 'only have ') + str; 478 | 479 | // Assertion 480 | this.assert( 481 | ok 482 | , function(){ return 'expected ' + i(this.obj) + ' to ' + str } 483 | , function(){ return 'expected ' + i(this.obj) + ' to not ' + str }); 484 | 485 | return this; 486 | }; 487 | 488 | /** 489 | * Assert a failure. 490 | * 491 | * @param {String ...} custom message 492 | * @api public 493 | */ 494 | Assertion.prototype.fail = function (msg) { 495 | var error = function() { return msg || "explicit failure"; } 496 | this.assert(false, error, error); 497 | return this; 498 | }; 499 | 500 | /** 501 | * Showing results. 502 | * 503 | * @api public 504 | */ 505 | Assertion.prototype.result = function() { 506 | console.log("Passed tests: " + (expect.passedTests-1)); 507 | expect.passedTests = 0; 508 | }; 509 | 510 | /** 511 | * Function bind implementation. 512 | */ 513 | 514 | function bind (fn, scope) { 515 | return function () { 516 | return fn.apply(scope, arguments); 517 | } 518 | } 519 | 520 | /** 521 | * Array every compatibility 522 | * 523 | * @see bit.ly/5Fq1N2 524 | * @api public 525 | */ 526 | 527 | function every (arr, fn, thisObj) { 528 | var scope = thisObj || global; 529 | for (var i = 0, j = arr.length; i < j; ++i) { 530 | if (!fn.call(scope, arr[i], i, arr)) { 531 | return false; 532 | } 533 | } 534 | return true; 535 | } 536 | 537 | /** 538 | * Array indexOf compatibility. 539 | * 540 | * @see bit.ly/a5Dxa2 541 | * @api public 542 | */ 543 | 544 | function indexOf (arr, o, i) { 545 | if (Array.prototype.indexOf) { 546 | return Array.prototype.indexOf.call(arr, o, i); 547 | } 548 | 549 | if (arr.length === undefined) { 550 | return -1; 551 | } 552 | 553 | for (var j = arr.length, i = i < 0 ? i + j < 0 ? 0 : i + j : i || 0 554 | ; i < j && arr[i] !== o; i++); 555 | 556 | return j <= i ? -1 : i; 557 | } 558 | 559 | // https://gist.github.com/1044128/ 560 | var getOuterHTML = function(element) { 561 | if ('outerHTML' in element) return element.outerHTML; 562 | var ns = "http://www.w3.org/1999/xhtml"; 563 | var container = document.createElementNS(ns, '_'); 564 | var xmlSerializer = new XMLSerializer(); 565 | var html; 566 | if (document.xmlVersion) { 567 | return xmlSerializer.serializeToString(element); 568 | } else { 569 | container.appendChild(element.cloneNode(false)); 570 | html = container.innerHTML.replace('><', '>' + element.innerHTML + '<'); 571 | container.innerHTML = ''; 572 | return html; 573 | } 574 | }; 575 | 576 | // Returns true if object is a DOM element. 577 | var isDOMElement = function (object) { 578 | if (typeof HTMLElement === 'object') { 579 | return object instanceof HTMLElement; 580 | } else { 581 | return object && 582 | typeof object === 'object' && 583 | object.nodeType === 1 && 584 | typeof object.nodeName === 'string'; 585 | } 586 | }; 587 | 588 | /** 589 | * Inspects an object. 590 | * 591 | * @see taken from node.js `util` module (copyright Joyent, MIT license) 592 | * @api private 593 | */ 594 | 595 | function i (obj, showHidden, depth) { 596 | var seen = []; 597 | 598 | function stylize (str) { 599 | return str; 600 | } 601 | 602 | function format (value, recurseTimes) { 603 | // Provide a hook for user-specified inspect functions. 604 | // Check that value is an object with an inspect function on it 605 | if (value && typeof value.inspect === 'function' && 606 | // Filter out the util module, it's inspect function is special 607 | value !== exports && 608 | // Also filter out any prototype objects using the circular check. 609 | !(value.constructor && value.constructor.prototype === value)) { 610 | return value.inspect(recurseTimes); 611 | } 612 | 613 | // Primitive types cannot have properties 614 | switch (typeof value) { 615 | case 'undefined': 616 | return stylize('undefined', 'undefined'); 617 | 618 | case 'string': 619 | var simple = '\'' + json.stringify(value).replace(/^"|"$/g, '') 620 | .replace(/'/g, "\\'") 621 | .replace(/\\"/g, '"') + '\''; 622 | return stylize(simple, 'string'); 623 | 624 | case 'number': 625 | return stylize('' + value, 'number'); 626 | 627 | case 'boolean': 628 | return stylize('' + value, 'boolean'); 629 | } 630 | // For some reason typeof null is "object", so special case here. 631 | if (value === null) { 632 | return stylize('null', 'null'); 633 | } 634 | 635 | if (isDOMElement(value)) { 636 | return getOuterHTML(value); 637 | } 638 | 639 | // Look up the keys of the object. 640 | var visible_keys = keys(value); 641 | var $keys = showHidden ? Object.getOwnPropertyNames(value) : visible_keys; 642 | 643 | // Functions without properties can be shortcutted. 644 | if (typeof value === 'function' && $keys.length === 0) { 645 | if (isRegExp(value)) { 646 | return stylize('' + value, 'regexp'); 647 | } else { 648 | var name = value.name ? ': ' + value.name : ''; 649 | return stylize('[Function' + name + ']', 'special'); 650 | } 651 | } 652 | 653 | // Dates without properties can be shortcutted 654 | if (isDate(value) && $keys.length === 0) { 655 | return stylize(value.toUTCString(), 'date'); 656 | } 657 | 658 | // Error objects can be shortcutted 659 | if (value instanceof Error) { 660 | return stylize("["+value.toString()+"]", 'Error'); 661 | } 662 | 663 | var base, type, braces; 664 | // Determine the object type 665 | if (isArray(value)) { 666 | type = 'Array'; 667 | braces = ['[', ']']; 668 | } else { 669 | type = 'Object'; 670 | braces = ['{', '}']; 671 | } 672 | 673 | // Make functions say that they are functions 674 | if (typeof value === 'function') { 675 | var n = value.name ? ': ' + value.name : ''; 676 | base = (isRegExp(value)) ? ' ' + value : ' [Function' + n + ']'; 677 | } else { 678 | base = ''; 679 | } 680 | 681 | // Make dates with properties first say the date 682 | if (isDate(value)) { 683 | base = ' ' + value.toUTCString(); 684 | } 685 | 686 | if ($keys.length === 0) { 687 | return braces[0] + base + braces[1]; 688 | } 689 | 690 | if (recurseTimes < 0) { 691 | if (isRegExp(value)) { 692 | return stylize('' + value, 'regexp'); 693 | } else { 694 | return stylize('[Object]', 'special'); 695 | } 696 | } 697 | 698 | seen.push(value); 699 | 700 | var output = map($keys, function (key) { 701 | var name, str; 702 | if (value.__lookupGetter__) { 703 | if (value.__lookupGetter__(key)) { 704 | if (value.__lookupSetter__(key)) { 705 | str = stylize('[Getter/Setter]', 'special'); 706 | } else { 707 | str = stylize('[Getter]', 'special'); 708 | } 709 | } else { 710 | if (value.__lookupSetter__(key)) { 711 | str = stylize('[Setter]', 'special'); 712 | } 713 | } 714 | } 715 | if (indexOf(visible_keys, key) < 0) { 716 | name = '[' + key + ']'; 717 | } 718 | if (!str) { 719 | if (indexOf(seen, value[key]) < 0) { 720 | if (recurseTimes === null) { 721 | str = format(value[key]); 722 | } else { 723 | str = format(value[key], recurseTimes - 1); 724 | } 725 | if (str.indexOf('\n') > -1) { 726 | if (isArray(value)) { 727 | str = map(str.split('\n'), function (line) { 728 | return ' ' + line; 729 | }).join('\n').substr(2); 730 | } else { 731 | str = '\n' + map(str.split('\n'), function (line) { 732 | return ' ' + line; 733 | }).join('\n'); 734 | } 735 | } 736 | } else { 737 | str = stylize('[Circular]', 'special'); 738 | } 739 | } 740 | if (typeof name === 'undefined') { 741 | if (type === 'Array' && key.match(/^\d+$/)) { 742 | return str; 743 | } 744 | name = json.stringify('' + key); 745 | if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { 746 | name = name.substr(1, name.length - 2); 747 | name = stylize(name, 'name'); 748 | } else { 749 | name = name.replace(/'/g, "\\'") 750 | .replace(/\\"/g, '"') 751 | .replace(/(^"|"$)/g, "'"); 752 | name = stylize(name, 'string'); 753 | } 754 | } 755 | 756 | return name + ': ' + str; 757 | }); 758 | 759 | seen.pop(); 760 | 761 | var numLinesEst = 0; 762 | var length = reduce(output, function (prev, cur) { 763 | numLinesEst++; 764 | if (indexOf(cur, '\n') >= 0) numLinesEst++; 765 | return prev + cur.length + 1; 766 | }, 0); 767 | 768 | if (length > 50) { 769 | output = braces[0] + 770 | (base === '' ? '' : base + '\n ') + 771 | ' ' + 772 | output.join(',\n ') + 773 | ' ' + 774 | braces[1]; 775 | 776 | } else { 777 | output = braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; 778 | } 779 | 780 | return output; 781 | } 782 | return format(obj, (typeof depth === 'undefined' ? 2 : depth)); 783 | } 784 | 785 | expect.stringify = i; 786 | 787 | function isArray (ar) { 788 | return Object.prototype.toString.call(ar) === '[object Array]'; 789 | } 790 | 791 | function isRegExp(re) { 792 | var s; 793 | try { 794 | s = '' + re; 795 | } catch (e) { 796 | return false; 797 | } 798 | 799 | return re instanceof RegExp || // easy case 800 | // duck-type for context-switching evalcx case 801 | typeof(re) === 'function' && 802 | re.constructor.name === 'RegExp' && 803 | re.compile && 804 | re.test && 805 | re.exec && 806 | s.match(/^\/.*\/[gim]{0,3}$/); 807 | } 808 | 809 | function isDate(d) { 810 | return d instanceof Date; 811 | } 812 | 813 | function keys (obj) { 814 | if (Object.keys) { 815 | return Object.keys(obj); 816 | } 817 | 818 | var keys = []; 819 | 820 | for (var i in obj) { 821 | if (Object.prototype.hasOwnProperty.call(obj, i)) { 822 | keys.push(i); 823 | } 824 | } 825 | 826 | return keys; 827 | } 828 | 829 | function map (arr, mapper, that) { 830 | if (Array.prototype.map) { 831 | return Array.prototype.map.call(arr, mapper, that); 832 | } 833 | 834 | var other= new Array(arr.length); 835 | 836 | for (var i= 0, n = arr.length; i= 2) { 862 | var rv = arguments[1]; 863 | } else { 864 | do { 865 | if (i in this) { 866 | rv = this[i++]; 867 | break; 868 | } 869 | 870 | // if array contains no values, no initial value to return 871 | if (++i >= len) 872 | throw new TypeError(); 873 | } while (true); 874 | } 875 | 876 | for (; i < len; i++) { 877 | if (i in this) 878 | rv = fun.call(null, rv, this[i], i, this); 879 | } 880 | 881 | return rv; 882 | } 883 | 884 | /** 885 | * Asserts deep equality 886 | * 887 | * @see taken from node.js `assert` module (copyright Joyent, MIT license) 888 | * @api private 889 | */ 890 | 891 | expect.eql = function eql(actual, expected) { 892 | // 7.1. All identical values are equivalent, as determined by ===. 893 | if (actual === expected) { 894 | return true; 895 | } else if ('undefined' != typeof Buffer 896 | && Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) { 897 | if (actual.length != expected.length) return false; 898 | 899 | for (var i = 0; i < actual.length; i++) { 900 | if (actual[i] !== expected[i]) return false; 901 | } 902 | 903 | return true; 904 | 905 | // 7.2. If the expected value is a Date object, the actual value is 906 | // equivalent if it is also a Date object that refers to the same time. 907 | } else if (actual instanceof Date && expected instanceof Date) { 908 | return actual.getTime() === expected.getTime(); 909 | 910 | // 7.3. Other pairs that do not both pass typeof value == "object", 911 | // equivalence is determined by ==. 912 | } else if (typeof actual != 'object' && typeof expected != 'object') { 913 | return actual == expected; 914 | // If both are regular expression use the special `regExpEquiv` method 915 | // to determine equivalence. 916 | } else if (isRegExp(actual) && isRegExp(expected)) { 917 | return regExpEquiv(actual, expected); 918 | // 7.4. For all other Object pairs, including Array objects, equivalence is 919 | // determined by having the same number of owned properties (as verified 920 | // with Object.prototype.hasOwnProperty.call), the same set of keys 921 | // (although not necessarily the same order), equivalent values for every 922 | // corresponding key, and an identical "prototype" property. Note: this 923 | // accounts for both named and indexed properties on Arrays. 924 | } else { 925 | return objEquiv(actual, expected); 926 | } 927 | }; 928 | 929 | function isUndefinedOrNull (value) { 930 | return value === null || value === undefined; 931 | } 932 | 933 | function isArguments (object) { 934 | return Object.prototype.toString.call(object) == '[object Arguments]'; 935 | } 936 | 937 | function regExpEquiv (a, b) { 938 | return a.source === b.source && a.global === b.global && 939 | a.ignoreCase === b.ignoreCase && a.multiline === b.multiline; 940 | } 941 | 942 | function objEquiv (a, b) { 943 | if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) 944 | return false; 945 | // an identical "prototype" property. 946 | if (a.prototype !== b.prototype) return false; 947 | //~~~I've managed to break Object.keys through screwy arguments passing. 948 | // Converting to array solves the problem. 949 | if (isArguments(a)) { 950 | if (!isArguments(b)) { 951 | return false; 952 | } 953 | a = pSlice.call(a); 954 | b = pSlice.call(b); 955 | return expect.eql(a, b); 956 | } 957 | try{ 958 | var ka = keys(a), 959 | kb = keys(b), 960 | key, i; 961 | } catch (e) {//happens when one is a string literal and the other isn't 962 | return false; 963 | } 964 | // having the same number of owned properties (keys incorporates hasOwnProperty) 965 | if (ka.length != kb.length) 966 | return false; 967 | //the same set of keys (although not necessarily the same order), 968 | ka.sort(); 969 | kb.sort(); 970 | //~~~cheap key test 971 | for (i = ka.length - 1; i >= 0; i--) { 972 | if (ka[i] != kb[i]) 973 | return false; 974 | } 975 | //equivalent values for every corresponding key, and 976 | //~~~possibly expensive deep test 977 | for (i = ka.length - 1; i >= 0; i--) { 978 | key = ka[i]; 979 | if (!expect.eql(a[key], b[key])) 980 | return false; 981 | } 982 | return true; 983 | } 984 | 985 | var json = (function () { 986 | "use strict"; 987 | 988 | if ('object' == typeof JSON && JSON.parse && JSON.stringify) { 989 | return { 990 | parse: nativeJSON.parse 991 | , stringify: nativeJSON.stringify 992 | } 993 | } 994 | 995 | var JSON = {}; 996 | 997 | function f(n) { 998 | // Format integers to have at least two digits. 999 | return n < 10 ? '0' + n : n; 1000 | } 1001 | 1002 | function date(d, key) { 1003 | return isFinite(d.valueOf()) ? 1004 | d.getUTCFullYear() + '-' + 1005 | f(d.getUTCMonth() + 1) + '-' + 1006 | f(d.getUTCDate()) + 'T' + 1007 | f(d.getUTCHours()) + ':' + 1008 | f(d.getUTCMinutes()) + ':' + 1009 | f(d.getUTCSeconds()) + 'Z' : null; 1010 | } 1011 | 1012 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 1013 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 1014 | gap, 1015 | indent, 1016 | meta = { // table of character substitutions 1017 | '\b': '\\b', 1018 | '\t': '\\t', 1019 | '\n': '\\n', 1020 | '\f': '\\f', 1021 | '\r': '\\r', 1022 | '"' : '\\"', 1023 | '\\': '\\\\' 1024 | }, 1025 | rep; 1026 | 1027 | 1028 | function quote(string) { 1029 | 1030 | // If the string contains no control characters, no quote characters, and no 1031 | // backslash characters, then we can safely slap some quotes around it. 1032 | // Otherwise we must also replace the offending characters with safe escape 1033 | // sequences. 1034 | 1035 | escapable.lastIndex = 0; 1036 | return escapable.test(string) ? '"' + string.replace(escapable, function (a) { 1037 | var c = meta[a]; 1038 | return typeof c === 'string' ? c : 1039 | '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 1040 | }) + '"' : '"' + string + '"'; 1041 | } 1042 | 1043 | 1044 | function str(key, holder) { 1045 | 1046 | // Produce a string from holder[key]. 1047 | 1048 | var i, // The loop counter. 1049 | k, // The member key. 1050 | v, // The member value. 1051 | length, 1052 | mind = gap, 1053 | partial, 1054 | value = holder[key]; 1055 | 1056 | // If the value has a toJSON method, call it to obtain a replacement value. 1057 | 1058 | if (value instanceof Date) { 1059 | value = date(key); 1060 | } 1061 | 1062 | // If we were called with a replacer function, then call the replacer to 1063 | // obtain a replacement value. 1064 | 1065 | if (typeof rep === 'function') { 1066 | value = rep.call(holder, key, value); 1067 | } 1068 | 1069 | // What happens next depends on the value's type. 1070 | 1071 | switch (typeof value) { 1072 | case 'string': 1073 | return quote(value); 1074 | 1075 | case 'number': 1076 | 1077 | // JSON numbers must be finite. Encode non-finite numbers as null. 1078 | 1079 | return isFinite(value) ? String(value) : 'null'; 1080 | 1081 | case 'boolean': 1082 | case 'null': 1083 | 1084 | // If the value is a boolean or null, convert it to a string. Note: 1085 | // typeof null does not produce 'null'. The case is included here in 1086 | // the remote chance that this gets fixed someday. 1087 | 1088 | return String(value); 1089 | 1090 | // If the type is 'object', we might be dealing with an object or an array or 1091 | // null. 1092 | 1093 | case 'object': 1094 | 1095 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 1096 | // so watch out for that case. 1097 | 1098 | if (!value) { 1099 | return 'null'; 1100 | } 1101 | 1102 | // Make an array to hold the partial results of stringifying this object value. 1103 | 1104 | gap += indent; 1105 | partial = []; 1106 | 1107 | // Is the value an array? 1108 | 1109 | if (Object.prototype.toString.apply(value) === '[object Array]') { 1110 | 1111 | // The value is an array. Stringify every element. Use null as a placeholder 1112 | // for non-JSON values. 1113 | 1114 | length = value.length; 1115 | for (i = 0; i < length; i += 1) { 1116 | partial[i] = str(i, value) || 'null'; 1117 | } 1118 | 1119 | // Join all of the elements together, separated with commas, and wrap them in 1120 | // brackets. 1121 | 1122 | v = partial.length === 0 ? '[]' : gap ? 1123 | '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : 1124 | '[' + partial.join(',') + ']'; 1125 | gap = mind; 1126 | return v; 1127 | } 1128 | 1129 | // If the replacer is an array, use it to select the members to be stringified. 1130 | 1131 | if (rep && typeof rep === 'object') { 1132 | length = rep.length; 1133 | for (i = 0; i < length; i += 1) { 1134 | if (typeof rep[i] === 'string') { 1135 | k = rep[i]; 1136 | v = str(k, value); 1137 | if (v) { 1138 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 1139 | } 1140 | } 1141 | } 1142 | } else { 1143 | 1144 | // Otherwise, iterate through all of the keys in the object. 1145 | 1146 | for (k in value) { 1147 | if (Object.prototype.hasOwnProperty.call(value, k)) { 1148 | v = str(k, value); 1149 | if (v) { 1150 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 1151 | } 1152 | } 1153 | } 1154 | } 1155 | 1156 | // Join all of the member texts together, separated with commas, 1157 | // and wrap them in braces. 1158 | 1159 | v = partial.length === 0 ? '{}' : gap ? 1160 | '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : 1161 | '{' + partial.join(',') + '}'; 1162 | gap = mind; 1163 | return v; 1164 | } 1165 | } 1166 | 1167 | // If the JSON object does not yet have a stringify method, give it one. 1168 | 1169 | JSON.stringify = function (value, replacer, space) { 1170 | 1171 | // The stringify method takes a value and an optional replacer, and an optional 1172 | // space parameter, and returns a JSON text. The replacer can be a function 1173 | // that can replace values, or an array of strings that will select the keys. 1174 | // A default replacer method can be provided. Use of the space parameter can 1175 | // produce text that is more easily readable. 1176 | 1177 | var i; 1178 | gap = ''; 1179 | indent = ''; 1180 | 1181 | // If the space parameter is a number, make an indent string containing that 1182 | // many spaces. 1183 | 1184 | if (typeof space === 'number') { 1185 | for (i = 0; i < space; i += 1) { 1186 | indent += ' '; 1187 | } 1188 | 1189 | // If the space parameter is a string, it will be used as the indent string. 1190 | 1191 | } else if (typeof space === 'string') { 1192 | indent = space; 1193 | } 1194 | 1195 | // If there is a replacer, it must be a function or an array. 1196 | // Otherwise, throw an error. 1197 | 1198 | rep = replacer; 1199 | if (replacer && typeof replacer !== 'function' && 1200 | (typeof replacer !== 'object' || 1201 | typeof replacer.length !== 'number')) { 1202 | throw new Error('JSON.stringify'); 1203 | } 1204 | 1205 | // Make a fake root object containing our value under the key of ''. 1206 | // Return the result of stringifying the value. 1207 | 1208 | return str('', {'': value}); 1209 | }; 1210 | 1211 | // If the JSON object does not yet have a parse method, give it one. 1212 | 1213 | JSON.parse = function (text, reviver) { 1214 | // The parse method takes a text and an optional reviver function, and returns 1215 | // a JavaScript value if the text is a valid JSON text. 1216 | 1217 | var j; 1218 | 1219 | function walk(holder, key) { 1220 | 1221 | // The walk method is used to recursively walk the resulting structure so 1222 | // that modifications can be made. 1223 | 1224 | var k, v, value = holder[key]; 1225 | if (value && typeof value === 'object') { 1226 | for (k in value) { 1227 | if (Object.prototype.hasOwnProperty.call(value, k)) { 1228 | v = walk(value, k); 1229 | if (v !== undefined) { 1230 | value[k] = v; 1231 | } else { 1232 | delete value[k]; 1233 | } 1234 | } 1235 | } 1236 | } 1237 | return reviver.call(holder, key, value); 1238 | } 1239 | 1240 | 1241 | // Parsing happens in four stages. In the first stage, we replace certain 1242 | // Unicode characters with escape sequences. JavaScript handles many characters 1243 | // incorrectly, either silently deleting them, or treating them as line endings. 1244 | 1245 | text = String(text); 1246 | cx.lastIndex = 0; 1247 | if (cx.test(text)) { 1248 | text = text.replace(cx, function (a) { 1249 | return '\\u' + 1250 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 1251 | }); 1252 | } 1253 | 1254 | // In the second stage, we run the text against regular expressions that look 1255 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 1256 | // because they can cause invocation, and '=' because it can cause mutation. 1257 | // But just to be safe, we want to reject all unexpected forms. 1258 | 1259 | // We split the second stage into 4 regexp operations in order to work around 1260 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 1261 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 1262 | // replace all simple value tokens with ']' characters. Third, we delete all 1263 | // open brackets that follow a colon or comma or that begin the text. Finally, 1264 | // we look to see that the remaining characters are only whitespace or ']' or 1265 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 1266 | 1267 | if (/^[\],:{}\s]*$/ 1268 | .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') 1269 | .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') 1270 | .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 1271 | 1272 | // In the third stage we use the eval function to compile the text into a 1273 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 1274 | // in JavaScript: it can begin a block or an object literal. We wrap the text 1275 | // in parens to eliminate the ambiguity. 1276 | 1277 | j = eval('(' + text + ')'); 1278 | 1279 | // In the optional fourth stage, we recursively walk the new structure, passing 1280 | // each name/value pair to a reviver function for possible transformation. 1281 | 1282 | return typeof reviver === 'function' ? 1283 | walk({'': j}, '') : j; 1284 | } 1285 | 1286 | // If the text is not JSON parseable, then a SyntaxError is thrown. 1287 | 1288 | throw new SyntaxError('JSON.parse'); 1289 | }; 1290 | 1291 | return JSON; 1292 | })(); 1293 | 1294 | if ('undefined' != typeof window) { 1295 | window.expect = module.exports; 1296 | } 1297 | 1298 | })( 1299 | this 1300 | , 'undefined' != typeof module ? module : {exports: {}} 1301 | ); 1302 | --------------------------------------------------------------------------------