├── .babelrc ├── .eslintrc ├── .gitignore ├── .npmignore ├── .npmrc ├── .travis.yml ├── CHANGES.md ├── LICENSE.md ├── README.md ├── karma.conf.js ├── modules ├── Expectation.js ├── SpyUtils.js ├── TestUtils.js ├── __tests__ │ ├── .eslintrc │ ├── chainable-test.js │ ├── createSpy-test.js │ ├── defaultExport-test.js │ ├── extend-test.js │ ├── restoreSpies-test.js │ ├── spyOn-test.js │ ├── toBeA-test.js │ ├── toBeGreaterThan-test.js │ ├── toBeGreaterThanOrEqualTo-test.js │ ├── toBeLessThan-test.js │ ├── toBeLessThanOrEqualTo-test.js │ ├── toBeTruthy-test.js │ ├── toEqual-test.js │ ├── toExclude-test.js │ ├── toExcludeKey-test.js │ ├── toExcludeKeys-test.js │ ├── toExist-test.js │ ├── toInclude-test.js │ ├── toIncludeKey-test.js │ ├── toIncludeKeys-test.js │ ├── toMatch-test.js │ ├── toNotEqual-test.js │ ├── toNotMatch-test.js │ ├── withArgs-test.js │ └── withContext-test.js ├── assert.js ├── extend.js └── index.js ├── package.json ├── scripts ├── build.js └── release.js ├── tests.webpack.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "plugins": [ 4 | "import" 5 | ], 6 | "env": { 7 | "es6": true, 8 | "node": true 9 | }, 10 | "extends": [ 11 | "eslint:recommended", 12 | "plugin:import/errors" 13 | ], 14 | "settings": { 15 | "import/ignore": [ 16 | "node_modules", 17 | "modules/index.js" 18 | ] 19 | }, 20 | "rules": { 21 | "array-bracket-spacing": [ 2, "always" ], 22 | "semi": [ 2, "never" ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # gitignore 2 | lib 3 | umd 4 | node_modules 5 | 6 | # Only apps should have lockfiles 7 | npm-shrinkwrap.json 8 | package-lock.json 9 | yarn.lock 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .babelrc 2 | .eslintrc 3 | .travis.yml 4 | __tests__ 5 | karma.conf.js 6 | scripts 7 | tests.webpack.js 8 | webpack.config.js 9 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | os: 3 | - linux 4 | node_js: 5 | - "9" 6 | - "8" 7 | - "7" 8 | - "6" 9 | - "5" 10 | - "4" 11 | before_install: 12 | - 'nvm install-latest-npm' 13 | install: 14 | - 'if [ "${TRAVIS_NODE_VERSION}" = "0.6" ] || [ "${TRAVIS_NODE_VERSION}" = "0.9" ]; then nvm install --latest-npm 0.8 && npm install && nvm use "${TRAVIS_NODE_VERSION}"; else npm install; fi;' 15 | script: 16 | - 'if [ -n "${PRETEST-}" ]; then npm run pretest ; fi' 17 | - 'if [ -n "${POSTTEST-}" ]; then npm run posttest ; fi' 18 | - 'if [ -n "${KARMA-}" ]; then npm run karma ; fi' 19 | - 'if [ -n "${COVERAGE-}" ]; then npm run coverage ; fi' 20 | - 'if [ -n "${TEST-}" ]; then npm run tests-only ; fi' 21 | sudo: false 22 | env: 23 | - TEST=true 24 | matrix: 25 | fast_finish: true 26 | include: 27 | - node_js: "lts/*" 28 | env: PRETEST=true 29 | - node_js: "lts/*" 30 | env: KARMA=true 31 | allow_failures: 32 | - os: osx 33 | - env: TEST=true ALLOW_FAILURE=true 34 | - env: COVERAGE=true 35 | - node_js: "9" 36 | - node_js: "7" 37 | - node_js: "5" 38 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | ## [HEAD] 2 | 3 | - `toMatch` and `toNotMatch` use `tmatch` directly, so a wider array 4 | of objects and patterns are supported 5 | 6 | [HEAD]: https://github.com/mjackson/expect/compare/v1.20.1...HEAD 7 | 8 | ## [v1.20.1] 9 | > May 7, 2016 10 | 11 | - Objects that have different prototypes are not considered "equal". It 12 | was a bug to ever treat them as such. 13 | 14 | [v1.20.1]: https://github.com/mjackson/expect/compare/v1.20.0...v1.20.1 15 | 16 | ## [v1.20.0] 17 | > May 6, 2016 18 | 19 | - Objects that differ only by prototype are considered "equal". This means 20 | e.g. that `expect(Object.create(null)).toEqual({})` passes 21 | - Restored `isEqual` to behaving more like `==` instead of `===`. This is a 22 | regression that was introduced in 1.13.1 ([#62]) 23 | - Handle non-array keys in `toIncludeKeys` ([#94], thanks @wuct) 24 | 25 | [v1.20.0]: https://github.com/mjackson/expect/compare/v1.19.0...v1.20.0 26 | [#62]: https://github.com/mjackson/expect/issues/62 27 | [#94]: https://github.com/mjackson/expect/pull/94 28 | 29 | ## [v1.19.0] 30 | > May 2, 2016 31 | 32 | - Spies preserve `length` property of original function ([#90], thanks @nfcampos) 33 | - Added ability to pass a `createMessage` function to `assert` that is 34 | only called when the assertion fails 35 | - Added `toNotIncludeKey(s)` alias 36 | 37 | [v1.19.0]: https://github.com/mjackson/expect/compare/v1.18.0...v1.19.0 38 | 39 | ## [v1.18.0] 40 | > Apr 18, 2016 41 | 42 | - Added support for using [tmatch] in `expect(object).toMatch` 43 | 44 | [v1.18.0]: https://github.com/mjackson/expect/compare/v1.17.0...v1.18.0 45 | [tmatch]: https://github.com/tapjs/tmatch 46 | 47 | ## [v1.17.0] 48 | > Apr 18, 2016 49 | 50 | - Added support for objects in `toExclude` ([#86], thanks @calebmer) 51 | - Added `toIncludeKeys` and `toExcludeKeys` ([#87], thanks @calebmer) 52 | - Added `toNotInclude` alias for `toExclude` 53 | - Deprecated `withContext` and `withArgs`. Use a closure instead. 54 | - Updated `is-equal` and `object-inspect` dependencies 55 | 56 | [v1.17.0]: https://github.com/mjackson/expect/compare/v1.16.0...v1.17.0 57 | [#86]: https://github.com/mjackson/expect/pull/86 58 | [#87]: https://github.com/mjackson/expect/pull/87 59 | 60 | ## [v1.16.0] 61 | > Mar 23, 1016 62 | 63 | - Added support for objects in `toInclude` (thanks @elado) 64 | - Minor fixes to docs 65 | 66 | [v1.16.0]: https://github.com/mjackson/expect/compare/v1.15.2...v1.16.0 67 | 68 | ## [v1.15.2] 69 | > Mar 11, 2016 70 | 71 | - Removed named exports, fixed a bad 1.15.0 release ([#72]) 72 | 73 | [#72]: https://github.com/mjackson/expect/issues/72 74 | [v1.15.2]: https://github.com/mjackson/expect/compare/v1.15.0...v1.15.2 75 | 76 | ## [v1.15.0] 77 | > Mar 10, 2016 78 | 79 | - Various build system improvements 80 | 81 | [v1.15.0]: https://github.com/mjackson/expect/compare/v1.14.0...v1.15.0 82 | 83 | ## [v1.14.0] 84 | > Feb 1, 2016 85 | 86 | - Added `toBeGreaterThanOrEqualTo` and `toBeLessThanOrEqualTo` ([#11] and [#59]) 87 | - Added `spy.reset()` ([#57]) 88 | 89 | [v1.14.0]: https://github.com/mjackson/expect/compare/v1.13.4...v1.14.0 90 | [#11]: https://github.com/mjackson/expect/issues/11 91 | [#59]: https://github.com/mjackson/expect/issues/59 92 | [#57]: https://github.com/mjackson/expect/pull/57 93 | 94 | ## [v1.13.4] 95 | > Dec 16, 2015 96 | 97 | - Fixed comparing two arrays of nested objects when the first items are not equal ([#53]) 98 | 99 | [v1.13.4]: https://github.com/mjackson/expect/compare/v1.13.3...v1.13.4 100 | [#53]: https://github.com/mjackson/expect/issues/53 101 | 102 | ## [v1.13.3] 103 | > Dec 14, 2015 104 | 105 | - Fix failing Map/Set tests 106 | 107 | [v1.13.3]: https://github.com/mjackson/expect/compare/v1.13.2...v1.13.3 108 | 109 | ## [v1.13.2] 110 | > Dec 11, 2015 111 | 112 | - Bump is-equal dependency to 1.4 113 | 114 | [v1.13.2]: https://github.com/mjackson/expect/compare/v1.13.1...v1.13.2 115 | 116 | ## [v1.13.1] 117 | > Dec 10, 2015 118 | 119 | - Fix comparisons of ES6 iterables Map and Set ([#47]) 120 | - Fix comparisons of objects with circular references ([#50]) 121 | - Better error messages in `toThrow`/`toNotThrow` 122 | 123 | [v1.13.1]: https://github.com/mjackson/expect/compare/v1.13.0...v1.13.1 124 | [#47]: https://github.com/mjackson/expect/issues/47 125 | [#50]: https://github.com/mjackson/expect/issues/50 126 | 127 | ## [v1.13.0] 128 | > Nov 13, 2015 129 | 130 | - Fix `toInclude` to use `deepEqual` for comparisons ([#44]) 131 | - Run test suite in browsers 132 | 133 | [v1.13.0]: https://github.com/mjackson/expect/compare/v1.12.2...v1.13.0 134 | [#44]: https://github.com/mjackson/expect/issues/44 135 | 136 | ## [v1.12.2] 137 | > Oct 13, 2015 138 | 139 | - Fix postinstall script on Windows (see [#39]) 140 | 141 | [v1.12.2]: https://github.com/mjackson/expect/compare/v1.12.1...v1.12.2 142 | [#39]: https://github.com/mjackson/expect/issues/39 143 | 144 | ## [v1.12.1] 145 | > Oct 10, 2015 146 | 147 | - Add support for building on Windows 148 | - Add postinstall npm script for installing from git repo 149 | 150 | [v1.12.1]: https://github.com/mjackson/expect/compare/v1.12.0...v1.12.1 151 | 152 | ## [v1.12.0] 153 | > Oct 5, 2015 154 | 155 | - Add `expect.extend(assertions)` (see [#34]) 156 | - Add `expect.restoreSpies()` (see [#12]) 157 | - Show object diffs using `toEqual()` in Mocha (see [#29]) 158 | 159 | [v1.12.0]: https://github.com/mjackson/expect/compare/v1.11.1...v1.12.0 160 | [#29]: https://github.com/mjackson/expect/issues/29 161 | [#34]: https://github.com/mjackson/expect/pull/34 162 | 163 | ## [v1.11.1] 164 | > Sep 26, 2015 165 | 166 | - Add `spy.destroy()` (see [#12]) 167 | 168 | [v1.11.1]: https://github.com/mjackson/expect/compare/v1.11.0...v1.11.1 169 | [#12]: https://github.com/mjackson/expect/issues/12 170 | 171 | ## [v1.11.0] 172 | > Sep 12, 2015 173 | 174 | - Add `expect.isSpy()` 175 | - Significant internal refactoring to use ES6 classes and the Babel transpiler 176 | 177 | [v1.11.0]: https://github.com/mjackson/expect/compare/v1.10.0...v1.11.0 178 | 179 | ## [v1.10.0] 180 | > Sep 3, 2015 181 | 182 | - Add `expect(spy).toNotHaveBeenCalled()` 183 | - Add `expect(obj).toBeAn('array')` 184 | - Add `expect(str).toNotMatch(regexp)` 185 | - Use [invariant](https://www.npmjs.com/package/invariant) instead of `assert` where applicable 186 | - Improve expectation error messages 187 | - Internal: use [eslint](https://www.npmjs.com/package/eslint) for linting 188 | 189 | [v1.10.0]: https://github.com/mjackson/expect/compare/v1.9.0...v1.10.0 190 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Michael Jackson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # expect [![Travis][build-badge]][build] [![npm package][npm-badge]][npm] 2 | 3 | [build-badge]: https://img.shields.io/travis/mjackson/expect/master.svg?style=flat-square 4 | [build]: https://travis-ci.org/mjackson/expect 5 | 6 | [npm-badge]: https://img.shields.io/npm/v/expect.svg?style=flat-square 7 | [npm]: https://www.npmjs.org/package/expect 8 | 9 | 10 | ## Notice 11 | 12 | This package [has been donated](https://github.com/facebook/jest/issues/1679) to [Jest](https://github.com/facebook/jest/tree/master/packages/expect). This means that all future development of `expect` v21+ will take place at [facebook/jest](https://github.com/facebook/jest). 13 | 14 | You can use [`jest-codemods`](https://github.com/skovhus/jest-codemods) to automatically migrate your old tests using `expect@1.x` to the new jest version of `expect` (>= 21) 15 | 16 | Versions prior to v21 will receive limited support and bugfixes, and any future < v21 releases will be published on an npm tag that is not "latest", to avoid causing problems for v21+ users. 17 | 18 | 19 | ## expect@1.x documentation 20 | 21 | [expect](https://github.com/mjackson/expect) lets you write better assertions. 22 | 23 | When you use `expect`, you write assertions similarly to how you would say them, e.g. "I expect this value to be equal to 3" or "I expect this array to contain 3". When you write assertions in this way, you don't need to remember the order of actual and expected arguments to functions like `assert.equal`, which helps you write better tests. 24 | 25 | You can think of `expect` as a more compact alternative to [Chai](http://chaijs.com/) or [Sinon.JS](http://sinonjs.org/), just without the pretty website. ;) 26 | 27 | ## Installation 28 | 29 | Using [npm](https://www.npmjs.org/): 30 | 31 | $ npm install --save expect 32 | 33 | Then, use as you would anything else: 34 | 35 | ```js 36 | // using ES6 modules 37 | import expect, { createSpy, spyOn, isSpy } from 'expect' 38 | 39 | // using CommonJS modules 40 | var expect = require('expect') 41 | var createSpy = expect.createSpy 42 | var spyOn = expect.spyOn 43 | var isSpy = expect.isSpy 44 | ``` 45 | 46 | The UMD build is also available on [unpkg](https://unpkg.com): 47 | 48 | ```html 49 | 50 | ``` 51 | 52 | You can find the library on `window.expect`. 53 | 54 | ## Assertions 55 | 56 | ### toExist 57 | 58 | > `expect(object).toExist([message])` 59 | 60 | Asserts the given `object` is truthy. 61 | 62 | ```js 63 | expect('something truthy').toExist() 64 | ``` 65 | 66 | Aliases: 67 | - `toBeTruthy` 68 | 69 | ### toNotExist 70 | 71 | > `expect(object).toNotExist([message])` 72 | 73 | Asserts the given `object` is falsy. 74 | 75 | ```js 76 | expect(null).toNotExist() 77 | ``` 78 | 79 | Aliases: 80 | - `toBeFalsy` 81 | 82 | ### toBe 83 | 84 | > `expect(object).toBe(value, [message])` 85 | 86 | Asserts that `object` is strictly equal to `value` using `===`. 87 | 88 | ### toNotBe 89 | 90 | > `expect(object).toNotBe(value, [message])` 91 | 92 | Asserts that `object` is not strictly equal to `value` using `===`. 93 | 94 | ### toEqual 95 | 96 | > `expect(object).toEqual(value, [message])` 97 | 98 | Asserts that the given `object` equals `value` using [is-equal](https://www.npmjs.com/package/is-equal). 99 | 100 | ### toNotEqual 101 | 102 | > `expect(object).toNotEqual(value, [message])` 103 | 104 | Asserts that the given `object` is not equal to `value` using [is-equal](https://www.npmjs.com/package/is-equal). 105 | 106 | ### toThrow 107 | 108 | > `expect(block).toThrow([error], [message])` 109 | 110 | Asserts that the given `block` `throw`s an error. The `error` argument may be a constructor (to test using `instanceof`), or a string/`RegExp` to test against `error.message`. 111 | 112 | ```js 113 | expect(function () { 114 | throw new Error('boom!') 115 | }).toThrow(/boom/) 116 | ``` 117 | 118 | ### toNotThrow 119 | 120 | > `expect(block).toNotThrow([message])` 121 | 122 | Asserts that the given `block` does not `throw`. 123 | 124 | ### toBeA(constructor) 125 | 126 | > `expect(object).toBeA(constructor, [message])`
127 | > `expect(object).toBeAn(constructor, [message])` 128 | 129 | Asserts the given `object` is an `instanceof constructor`. 130 | 131 | ```js 132 | expect(new User).toBeA(User) 133 | expect(new Asset).toBeAn(Asset) 134 | ``` 135 | 136 | Aliases: 137 | - `toBeAn` 138 | 139 | ### toBeA(string) 140 | 141 | > `expect(object).toBeA(string, [message])`
142 | > `expect(object).toBeAn(string, [message])` 143 | 144 | Asserts the `typeof` the given `object` is `string`. 145 | 146 | ```js 147 | expect(2).toBeA('number') 148 | ``` 149 | 150 | Aliases: 151 | - `toBeAn` 152 | 153 | ### toNotBeA(constructor) 154 | 155 | > `expect(object).toNotBeA(constructor, [message])`
156 | > `expect(object).toNotBeAn(constructor, [message])` 157 | 158 | Asserts the given `object` is *not* an `instanceof constructor`. 159 | 160 | ```js 161 | expect(new Asset).toNotBeA(User) 162 | expect(new User).toNotBeAn(Asset) 163 | ``` 164 | 165 | Aliases: 166 | - `toNotBeAn` 167 | 168 | ### toNotBeA(string) 169 | 170 | > `expect(object).toNotBeA(string, [message])`
171 | > `expect(object).toNotBeAn(string, [message])` 172 | 173 | Asserts the `typeof` the given `object` is *not* `string`. 174 | 175 | ```js 176 | expect('a string').toNotBeA('number') 177 | expect(2).toNotBeAn('object') 178 | ``` 179 | 180 | Aliases: 181 | - `toNotBeAn` 182 | 183 | ### toMatch 184 | 185 | > `expect(string).toMatch(pattern, [message])`
186 | > `expect(object).toMatch(pattern, [message])` 187 | 188 | Asserts the given `string` or `object` matches a `pattern`. When using a string, `pattern` must be a `RegExp`. When using an object, `pattern` may be anything acceptable to [`tmatch`](https://github.com/tapjs/tmatch). 189 | 190 | ```js 191 | expect('a string').toMatch(/string/) 192 | expect({ 193 | statusCode: 200, 194 | headers: { 195 | server: 'nginx/1.6.5' 196 | } 197 | }).toMatch({ 198 | headers: { 199 | server: /nginx/ 200 | } 201 | }) 202 | ``` 203 | 204 | ### toNotMatch 205 | 206 | > `expect(string).toNotMatch(pattern, [message])`
207 | > `expect(object).toNotMatch(pattern, [message])` 208 | 209 | Asserts the given `string` or `object` does not match a `pattern`. When using a string, `pattern` must be a `RegExp`. When using an object, `pattern` may be anything acceptable to [`tmatch`](https://github.com/tapjs/tmatch). 210 | 211 | ```js 212 | expect('a string').toMatch(/string/) 213 | expect({ 214 | statusCode: 200, 215 | headers: { 216 | server: 'nginx/1.6.5' 217 | } 218 | }).toNotMatch({ 219 | headers: { 220 | server: /apache/ 221 | } 222 | }) 223 | ``` 224 | 225 | ### toBeLessThan 226 | 227 | > `expect(number).toBeLessThan(value, [message])`
228 | > `expect(number).toBeFewerThan(value, [message])` 229 | 230 | Asserts the given `number` is less than `value`. 231 | 232 | ```js 233 | expect(2).toBeLessThan(3) 234 | ``` 235 | 236 | Aliases: 237 | - `toBeFewerThan` 238 | 239 | ### toBeLessThanOrEqualTo 240 | 241 | > `expect(number).toBeLessThanOrEqualTo(value, [message])`
242 | 243 | Asserts the given `number` is less than or equal to `value`. 244 | 245 | ```js 246 | expect(2).toBeLessThanOrEqualTo(3) 247 | ``` 248 | 249 | ### toBeGreaterThan 250 | 251 | > `expect(number).toBeGreaterThan(value, [message])`
252 | > `expect(number).toBeMoreThan(value, [message])` 253 | 254 | Asserts the given `number` is greater than `value`. 255 | 256 | ```js 257 | expect(3).toBeGreaterThan(2) 258 | ``` 259 | 260 | Aliases: 261 | - `toBeMoreThan` 262 | 263 | ### toBeGreaterThanOrEqualTo 264 | 265 | > `expect(number).toBeGreaterThanOrEqualTo(value, [message])`
266 | 267 | Asserts the given `number` is greater than or equal to `value`. 268 | 269 | ```js 270 | expect(3).toBeGreaterThanOrEqualTo(2) 271 | ``` 272 | 273 | ### toInclude 274 | 275 | > `expect(array).toInclude(value, [comparator], [message])`
276 | > `expect(object).toInclude(value, [comparator], [message])`
277 | > `expect(string).toInclude(value, [message])` 278 | 279 | Asserts that a given `value` is included (or "contained") within another. The `actual` value may be an array, object, or a string. The `comparator` function, if given, should compare two objects and `return false` if they are not equal. The default is to use [`isEqual`](https://github.com/ljharb/is-equal). 280 | 281 | ```js 282 | expect([ 1, 2, 3 ]).toInclude(3) 283 | expect({ a: 1, b: 2 }).toInclude({ b: 2 }) 284 | expect({ a: 1, b: 2, c: { d: 3 } }).toInclude({ b: 2, c: { d: 3 } }) 285 | expect('hello world').toInclude('world') 286 | ``` 287 | 288 | Aliases: 289 | - `toContain` 290 | 291 | ### toExclude 292 | 293 | > `expect(array).toExclude(value, [comparator], [message])`
294 | > `expect(object).toExclude(value, [comparator], [message])`
295 | > `expect(string).toExclude(value, [message])` 296 | 297 | Asserts that a given `value` is not included (or "contained") within another. The `actual` value may be an array, object, or a string. The `comparator` function, if given, should compare two objects and `return false` if they are not equal. The default is to use [`isEqual`](https://github.com/ljharb/is-equal). 298 | 299 | ```js 300 | expect([ 1, 2, 3 ]).toExclude(4) 301 | expect({ a: 1, b: 2 }).toExclude({ c: 2 }) 302 | expect({ a: 1, b: 2 }).toExclude({ b: 3 }) 303 | expect({ a: 1, b: 2, c: { d: 3 } }).toExclude({ c: { d: 4 } }) 304 | expect('hello world').toExclude('goodbye') 305 | ``` 306 | 307 | Aliases: 308 | - `toNotContain` 309 | - `toNotInclude` 310 | 311 | ### toIncludeKey(s) 312 | 313 | > `expect(object).toIncludeKeys(keys, [comparator], [message])`
314 | > `expect(object).toIncludeKey(key, [comparator], [message])` 315 | 316 | Asserts that the given `object` (may be an array, or a function, or anything with keys) contains *all* of the provided keys. The optional parameter `comparator` is a function which if given an object and a string key, it should return a boolean detailing whether or not the key exists in the object. By default, a shallow check with `Object.prototype.hasOwnProperty` is performed. 317 | 318 | ```js 319 | expect({ a: 1 }).toIncludeKey('a') 320 | expect({ a: 1, b: 2 }).toIncludeKeys([ 'a', 'b' ]) 321 | ``` 322 | 323 | Aliases: 324 | - `toContainKey(s)` 325 | 326 | ### toExcludeKey(s) 327 | 328 | > `expect(object).toExcludeKeys(keys, [comparator], [message])`
329 | > `expect(object).toExcludeKey(key, [comparator], [message])` 330 | 331 | Asserts that the given `object` (may be an array, or a function, or anything with keys) does not contain *any* of the provided keys. The optional parameter `comparator` is a function which if given an object and a string key, it should return a boolean detailing whether or not the key exists in the object. By default, a shallow check with `Object.prototype.hasOwnProperty` is performed. 332 | 333 | ```js 334 | expect({ a: 1 }).toExcludeKey('b') 335 | expect({ a: 1, b: 2 }).toExcludeKeys([ 'c', 'd' ]) 336 | ``` 337 | 338 | Aliases: 339 | - `toNotContainKey(s)` 340 | - `toNotIncludeKey(s)` 341 | 342 | ### (spy) toHaveBeenCalled 343 | 344 | > `expect(spy).toHaveBeenCalled([message])` 345 | 346 | Asserts the given `spy` function has been called at least once. 347 | 348 | ```js 349 | expect(spy).toHaveBeenCalled() 350 | ``` 351 | 352 | ### (spy) toNotHaveBeenCalled 353 | 354 | > `expect(spy).toNotHaveBeenCalled([message])` 355 | 356 | Asserts the given `spy` function has *not* been called. 357 | 358 | ```js 359 | expect(spy).toNotHaveBeenCalled() 360 | ``` 361 | 362 | ### (spy) toHaveBeenCalledWith 363 | 364 | > `expect(spy).toHaveBeenCalledWith(...args)` 365 | 366 | Asserts the given `spy` function has been called with the expected arguments. 367 | 368 | ```js 369 | expect(spy).toHaveBeenCalledWith('foo', 'bar') 370 | ``` 371 | 372 | ## Chaining Assertions 373 | 374 | Every assertion returns an `Expectation` object, so you can chain assertions together. 375 | 376 | ```js 377 | expect(3.14) 378 | .toExist() 379 | .toBeLessThan(4) 380 | .toBeGreaterThan(3) 381 | ``` 382 | 383 | ## Spies 384 | 385 | expect also includes the ability to create spy functions that can track the calls that are made to other functions and make various assertions based on the arguments and context that were used. 386 | 387 | ```js 388 | var video = { 389 | play: function () {}, 390 | pause: function () {}, 391 | rewind: function () {} 392 | } 393 | 394 | var spy = expect.spyOn(video, 'play') 395 | 396 | video.play('some', 'args') 397 | 398 | expect(spy.calls.length).toEqual(1) 399 | expect(spy.calls[0].context).toBe(video) 400 | expect(spy.calls[0].arguments).toEqual([ 'some', 'args' ]) 401 | expect(spy).toHaveBeenCalled() 402 | expect(spy).toHaveBeenCalledWith('some', 'args') 403 | 404 | spy.restore() 405 | expect.restoreSpies() 406 | ``` 407 | 408 | ### createSpy 409 | 410 | > `expect.createSpy([fn], [restore])` 411 | 412 | Creates a spy function with an (optional) implementation and (optional) restore logic. (In order for your provided implementation to be used, you must call [`andCallThrough`](https://github.com/mjackson/expect#andcallthrough).) For this reason, it's better to use [`andCall`](https://github.com/mjackson/expect#andcall) if you don't need custom restore logic. 413 | 414 | ```js 415 | var spy = expect.createSpy() 416 | ``` 417 | 418 | ### spyOn 419 | 420 | > `expect.spyOn(target, method)` 421 | 422 | Replaces the `method` in `target` with a spy. 423 | 424 | ```js 425 | var video = { 426 | play: function () {} 427 | } 428 | 429 | var spy = expect.spyOn(video, 'play') 430 | video.play() 431 | 432 | spy.restore() 433 | ``` 434 | 435 | ### restoreSpies 436 | 437 | > `expect.restoreSpies()` 438 | 439 | Restores all spies created with `expect.spyOn()`. This is the same as calling `spy.restore()` on all spies created. 440 | 441 | ```js 442 | // mocha.js example 443 | beforeEach(function () { 444 | expect.spyOn(profile, 'load') 445 | }) 446 | 447 | afterEach(function () { 448 | expect.restoreSpies() 449 | }) 450 | 451 | it('works', function () { 452 | profile.load() 453 | expect(profile.load).toHaveBeenCalled() 454 | }) 455 | ``` 456 | 457 | ## Spy methods and properties 458 | 459 | ### andCall 460 | 461 | > `spy.andCall(fn)` 462 | 463 | Makes the spy invoke a function `fn` when called. 464 | 465 | ```js 466 | var dice = createSpy().andCall(function () { 467 | return (Math.random() * 6) | 0 468 | }) 469 | ``` 470 | 471 | ### andCallThrough 472 | 473 | > `spy.andCallThrough()` 474 | 475 | Makes the spy call the original function it's spying on. 476 | 477 | ```js 478 | spyOn(profile, 'load').andCallThrough() 479 | 480 | var getEmail = createSpy(function () { 481 | return "hi@gmail.com" 482 | }).andCallThrough() 483 | ``` 484 | 485 | ### andReturn 486 | 487 | > `spy.andReturn(object)` 488 | 489 | Makes the spy return a value. 490 | 491 | ```js 492 | var dice = expect.createSpy().andReturn(3) 493 | ``` 494 | 495 | ### andThrow 496 | 497 | > `spy.andThrow(error)` 498 | 499 | Makes the spy throw an `error` when called. 500 | 501 | ```js 502 | var failing = expect.createSpy() 503 | .andThrow(new Error('Not working')) 504 | ``` 505 | 506 | ### restore 507 | 508 | > `spy.restore()` 509 | 510 | Restores a spy originally created with `expect.spyOn()`. 511 | 512 | ### reset 513 | 514 | > `spy.reset()` 515 | 516 | Clears out all saved calls to the spy. 517 | 518 | ### calls 519 | 520 | > `spy.calls` 521 | 522 | An array of objects representing all saved calls to the spy. 523 | 524 | You can use the length of the `calls` array to make assertions about how many times you expect the spy to have been called. 525 | 526 | ```js 527 | expect(spy.calls.length).toEqual(3) 528 | ``` 529 | 530 | You can also use the array to make assertions about each individual call. Each call object contains the following properties: 531 | 532 | #### context 533 | 534 | > `spy.calls[index].context` 535 | 536 | The `this` value of the call's execution context. 537 | 538 | #### arguments 539 | 540 | > `spy.calls[index].arguments` 541 | 542 | An array of the arguments passed to the spy for the particular call. 543 | 544 | ## Extending expect 545 | 546 | You can add your own assertions using `expect.extend` and `expect.assert`: 547 | 548 | ```js 549 | expect.extend({ 550 | toBeAColor() { 551 | expect.assert( 552 | this.actual.match(/^#[a-fA-F0-9]{3,6}$/), 553 | 'expected %s to be an HTML color', 554 | this.actual 555 | ) 556 | return this 557 | } 558 | }) 559 | 560 | expect('#ff00ff').toBeAColor() 561 | ``` 562 | 563 | ## Extensions 564 | 565 | - [expect-element](https://github.com/mjackson/expect-element) Adds assertions that are useful for DOM elements 566 | - [expect-jsx](https://github.com/algolia/expect-jsx) Adds things like `expect(ReactComponent).toEqualJSX()` 567 | - [expect-predicate](https://github.com/erikras/expect-predicate) Adds assertions based on arbitrary predicates 568 | - [expect-enzyme](https://github.com/PsychoLlama/expect-enzyme) Augments and extends expect to supercharge your enzyme assertions 569 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const projectName = require('./package').name 3 | 4 | module.exports = (config) => { 5 | const customLaunchers = { 6 | BS_Chrome: { 7 | base: 'BrowserStack', 8 | os: 'Windows', 9 | os_version: '10', 10 | browser: 'chrome', 11 | browser_version: '47.0' 12 | }, 13 | BS_Firefox: { 14 | base: 'BrowserStack', 15 | os: 'Windows', 16 | os_version: '10', 17 | browser: 'firefox', 18 | browser_version: '43.0' 19 | }, 20 | BS_Safari: { 21 | base: 'BrowserStack', 22 | os: 'OS X', 23 | os_version: 'El Capitan', 24 | browser: 'safari', 25 | browser_version: '9.0' 26 | }, 27 | BS_MobileSafari9: { 28 | base: 'BrowserStack', 29 | os: 'ios', 30 | os_version: '9.1', 31 | browser: 'iphone', 32 | real_mobile: false 33 | }, 34 | BS_InternetExplorer10: { 35 | base: 'BrowserStack', 36 | os: 'Windows', 37 | os_version: '8', 38 | browser: 'ie', 39 | browser_version: '10.0' 40 | }, 41 | BS_InternetExplorer11: { 42 | base: 'BrowserStack', 43 | os: 'Windows', 44 | os_version: '10', 45 | browser: 'ie', 46 | browser_version: '11.0' 47 | } 48 | } 49 | 50 | config.set({ 51 | customLaunchers: customLaunchers, 52 | 53 | browsers: [ 'Chrome' ], 54 | frameworks: [ 'mocha' ], 55 | reporters: [ 'mocha' ], 56 | 57 | files: [ 58 | 'tests.webpack.js' 59 | ], 60 | 61 | preprocessors: { 62 | 'tests.webpack.js': [ 'webpack', 'sourcemap' ] 63 | }, 64 | 65 | webpack: { 66 | devtool: 'inline-source-map', 67 | module: { 68 | loaders: [ 69 | { test: /\.js$/, exclude: /node_modules/, loader: 'babel' } 70 | ] 71 | }, 72 | plugins: [ 73 | new webpack.DefinePlugin({ 74 | 'process.env.NODE_ENV': JSON.stringify('test') 75 | }) 76 | ] 77 | }, 78 | 79 | webpackServer: { 80 | noInfo: true 81 | } 82 | }) 83 | 84 | if (process.env.USE_CLOUD) { 85 | config.browsers = Object.keys(customLaunchers) 86 | config.reporters = [ 'dots' ] 87 | config.concurrency = 2 88 | 89 | config.browserDisconnectTimeout = 10000 90 | config.browserDisconnectTolerance = 3 91 | 92 | if (process.env.TRAVIS) { 93 | config.browserStack = { 94 | project: projectName, 95 | build: process.env.TRAVIS_BUILD_NUMBER, 96 | name: process.env.TRAVIS_JOB_NUMBER 97 | } 98 | 99 | config.singleRun = true 100 | } else { 101 | config.browserStack = { 102 | project: projectName 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /modules/Expectation.js: -------------------------------------------------------------------------------- 1 | import has from 'has' 2 | import tmatch from 'tmatch' 3 | import assert from './assert' 4 | import { isSpy } from './SpyUtils' 5 | import { 6 | isA, 7 | isFunction, 8 | isArray, 9 | isEqual, 10 | isObject, 11 | functionThrows, 12 | arrayContains, 13 | objectContains, 14 | stringContains 15 | } from './TestUtils' 16 | 17 | /** 18 | * An Expectation is a wrapper around an assertion that allows it to be written 19 | * in a more natural style, without the need to remember the order of arguments. 20 | * This helps prevent you from making mistakes when writing tests. 21 | */ 22 | class Expectation { 23 | constructor(actual) { 24 | this.actual = actual 25 | 26 | if (isFunction(actual)) { 27 | this.context = null 28 | this.args = [] 29 | } 30 | } 31 | 32 | toExist(message) { 33 | assert( 34 | this.actual, 35 | (message || 'Expected %s to exist'), 36 | this.actual 37 | ) 38 | 39 | return this 40 | } 41 | 42 | toNotExist(message) { 43 | assert( 44 | !this.actual, 45 | (message || 'Expected %s to not exist'), 46 | this.actual 47 | ) 48 | 49 | return this 50 | } 51 | 52 | toBe(value, message) { 53 | assert( 54 | this.actual === value, 55 | (message || 'Expected %s to be %s'), 56 | this.actual, 57 | value 58 | ) 59 | 60 | return this 61 | } 62 | 63 | toNotBe(value, message) { 64 | assert( 65 | this.actual !== value, 66 | (message || 'Expected %s to not be %s'), 67 | this.actual, 68 | value 69 | ) 70 | 71 | return this 72 | } 73 | 74 | toEqual(value, message) { 75 | try { 76 | assert( 77 | isEqual(this.actual, value), 78 | (message || 'Expected %s to equal %s'), 79 | this.actual, 80 | value 81 | ) 82 | } catch (error) { 83 | // These attributes are consumed by Mocha to produce a diff output. 84 | error.actual = this.actual 85 | error.expected = value 86 | error.showDiff = true 87 | throw error 88 | } 89 | 90 | return this 91 | } 92 | 93 | toNotEqual(value, message) { 94 | assert( 95 | !isEqual(this.actual, value), 96 | (message || 'Expected %s to not equal %s'), 97 | this.actual, 98 | value 99 | ) 100 | 101 | return this 102 | } 103 | 104 | toThrow(value, message) { 105 | assert( 106 | isFunction(this.actual), 107 | 'The "actual" argument in expect(actual).toThrow() must be a function, %s was given', 108 | this.actual 109 | ) 110 | 111 | assert( 112 | functionThrows(this.actual, this.context, this.args, value), 113 | (message || 'Expected %s to throw %s'), 114 | this.actual, 115 | value || 'an error' 116 | ) 117 | 118 | return this 119 | } 120 | 121 | toNotThrow(value, message) { 122 | assert( 123 | isFunction(this.actual), 124 | 'The "actual" argument in expect(actual).toNotThrow() must be a function, %s was given', 125 | this.actual 126 | ) 127 | 128 | assert( 129 | !functionThrows(this.actual, this.context, this.args, value), 130 | (message || 'Expected %s to not throw %s'), 131 | this.actual, 132 | value || 'an error' 133 | ) 134 | 135 | return this 136 | } 137 | 138 | toBeA(value, message) { 139 | assert( 140 | isFunction(value) || typeof value === 'string', 141 | 'The "value" argument in toBeA(value) must be a function or a string' 142 | ) 143 | 144 | assert( 145 | isA(this.actual, value), 146 | (message || 'Expected %s to be a %s'), 147 | this.actual, 148 | value 149 | ) 150 | 151 | return this 152 | } 153 | 154 | toNotBeA(value, message) { 155 | assert( 156 | isFunction(value) || typeof value === 'string', 157 | 'The "value" argument in toNotBeA(value) must be a function or a string' 158 | ) 159 | 160 | assert( 161 | !isA(this.actual, value), 162 | (message || 'Expected %s to not be a %s'), 163 | this.actual, 164 | value 165 | ) 166 | 167 | return this 168 | } 169 | 170 | toMatch(pattern, message) { 171 | assert( 172 | tmatch(this.actual, pattern), 173 | (message || 'Expected %s to match %s'), 174 | this.actual, 175 | pattern 176 | ) 177 | 178 | return this 179 | } 180 | 181 | toNotMatch(pattern, message) { 182 | assert( 183 | !tmatch(this.actual, pattern), 184 | (message || 'Expected %s to not match %s'), 185 | this.actual, 186 | pattern 187 | ) 188 | 189 | return this 190 | } 191 | 192 | toBeLessThan(value, message) { 193 | assert( 194 | typeof this.actual === 'number', 195 | 'The "actual" argument in expect(actual).toBeLessThan() must be a number' 196 | ) 197 | 198 | assert( 199 | typeof value === 'number', 200 | 'The "value" argument in toBeLessThan(value) must be a number' 201 | ) 202 | 203 | assert( 204 | this.actual < value, 205 | (message || 'Expected %s to be less than %s'), 206 | this.actual, 207 | value 208 | ) 209 | 210 | return this 211 | } 212 | 213 | toBeLessThanOrEqualTo(value, message) { 214 | assert( 215 | typeof this.actual === 'number', 216 | 'The "actual" argument in expect(actual).toBeLessThanOrEqualTo() must be a number' 217 | ) 218 | 219 | assert( 220 | typeof value === 'number', 221 | 'The "value" argument in toBeLessThanOrEqualTo(value) must be a number' 222 | ) 223 | 224 | assert( 225 | this.actual <= value, 226 | (message || 'Expected %s to be less than or equal to %s'), 227 | this.actual, 228 | value 229 | ) 230 | 231 | return this 232 | } 233 | 234 | toBeGreaterThan(value, message) { 235 | assert( 236 | typeof this.actual === 'number', 237 | 'The "actual" argument in expect(actual).toBeGreaterThan() must be a number' 238 | ) 239 | 240 | assert( 241 | typeof value === 'number', 242 | 'The "value" argument in toBeGreaterThan(value) must be a number' 243 | ) 244 | 245 | assert( 246 | this.actual > value, 247 | (message || 'Expected %s to be greater than %s'), 248 | this.actual, 249 | value 250 | ) 251 | 252 | return this 253 | } 254 | 255 | toBeGreaterThanOrEqualTo(value, message) { 256 | assert( 257 | typeof this.actual === 'number', 258 | 'The "actual" argument in expect(actual).toBeGreaterThanOrEqualTo() must be a number' 259 | ) 260 | 261 | assert( 262 | typeof value === 'number', 263 | 'The "value" argument in toBeGreaterThanOrEqualTo(value) must be a number' 264 | ) 265 | 266 | assert( 267 | this.actual >= value, 268 | (message || 'Expected %s to be greater than or equal to %s'), 269 | this.actual, 270 | value 271 | ) 272 | 273 | return this 274 | } 275 | 276 | toInclude(value, compareValues, message) { 277 | if (typeof compareValues === 'string') { 278 | message = compareValues 279 | compareValues = null 280 | } 281 | 282 | if (compareValues == null) 283 | compareValues = isEqual 284 | 285 | let contains = false 286 | 287 | if (isArray(this.actual)) { 288 | contains = arrayContains(this.actual, value, compareValues) 289 | } else if (isObject(this.actual)) { 290 | contains = objectContains(this.actual, value, compareValues) 291 | } else if (typeof this.actual === 'string') { 292 | contains = stringContains(this.actual, value) 293 | } else { 294 | assert( 295 | false, 296 | 'The "actual" argument in expect(actual).toInclude() must be an array, object, or a string' 297 | ) 298 | } 299 | 300 | assert( 301 | contains, 302 | message || 'Expected %s to include %s', 303 | this.actual, 304 | value 305 | ) 306 | 307 | return this 308 | } 309 | 310 | toExclude(value, compareValues, message) { 311 | if (typeof compareValues === 'string') { 312 | message = compareValues 313 | compareValues = null 314 | } 315 | 316 | if (compareValues == null) 317 | compareValues = isEqual 318 | 319 | let contains = false 320 | 321 | if (isArray(this.actual)) { 322 | contains = arrayContains(this.actual, value, compareValues) 323 | } else if (isObject(this.actual)) { 324 | contains = objectContains(this.actual, value, compareValues) 325 | } else if (typeof this.actual === 'string') { 326 | contains = stringContains(this.actual, value) 327 | } else { 328 | assert( 329 | false, 330 | 'The "actual" argument in expect(actual).toExclude() must be an array, object, or a string' 331 | ) 332 | } 333 | 334 | assert( 335 | !contains, 336 | message || 'Expected %s to exclude %s', 337 | this.actual, 338 | value 339 | ) 340 | 341 | return this 342 | } 343 | 344 | toIncludeKeys(keys, comparator, message) { 345 | if (typeof comparator === 'string') { 346 | message = comparator 347 | comparator = null 348 | } 349 | 350 | if (comparator == null) 351 | comparator = has 352 | 353 | assert( 354 | typeof this.actual === 'object', 355 | 'The "actual" argument in expect(actual).toIncludeKeys() must be an object, not %s', 356 | this.actual 357 | ) 358 | 359 | assert( 360 | isArray(keys), 361 | 'The "keys" argument in expect(actual).toIncludeKeys(keys) must be an array, not %s', 362 | keys 363 | ) 364 | 365 | const contains = keys.every(key => comparator(this.actual, key)) 366 | 367 | assert( 368 | contains, 369 | message || 'Expected %s to include key(s) %s', 370 | this.actual, 371 | keys.join(', ') 372 | ) 373 | 374 | return this 375 | } 376 | 377 | toIncludeKey(key, ...args) { 378 | return this.toIncludeKeys([ key ], ...args) 379 | } 380 | 381 | toExcludeKeys(keys, comparator, message) { 382 | if (typeof comparator === 'string') { 383 | message = comparator 384 | comparator = null 385 | } 386 | 387 | if (comparator == null) 388 | comparator = has 389 | 390 | assert( 391 | typeof this.actual === 'object', 392 | 'The "actual" argument in expect(actual).toExcludeKeys() must be an object, not %s', 393 | this.actual 394 | ) 395 | 396 | assert( 397 | isArray(keys), 398 | 'The "keys" argument in expect(actual).toIncludeKeys(keys) must be an array, not %s', 399 | keys 400 | ) 401 | 402 | const contains = keys.every(key => comparator(this.actual, key)) 403 | 404 | assert( 405 | !contains, 406 | message || 'Expected %s to exclude key(s) %s', 407 | this.actual, 408 | keys.join(', ') 409 | ) 410 | 411 | return this 412 | } 413 | 414 | toExcludeKey(key, ...args) { 415 | return this.toExcludeKeys([ key ], ...args) 416 | } 417 | 418 | toHaveBeenCalled(message) { 419 | const spy = this.actual 420 | 421 | assert( 422 | isSpy(spy), 423 | 'The "actual" argument in expect(actual).toHaveBeenCalled() must be a spy' 424 | ) 425 | 426 | assert( 427 | spy.calls.length > 0, 428 | (message || 'spy was not called') 429 | ) 430 | 431 | return this 432 | } 433 | 434 | toHaveBeenCalledWith(...expectedArgs) { 435 | const spy = this.actual 436 | 437 | assert( 438 | isSpy(spy), 439 | 'The "actual" argument in expect(actual).toHaveBeenCalledWith() must be a spy' 440 | ) 441 | 442 | assert( 443 | spy.calls.some(call => isEqual(call.arguments, expectedArgs)), 444 | 'spy was never called with %s', 445 | expectedArgs 446 | ) 447 | 448 | return this 449 | } 450 | 451 | toNotHaveBeenCalled(message) { 452 | const spy = this.actual 453 | 454 | assert( 455 | isSpy(spy), 456 | 'The "actual" argument in expect(actual).toNotHaveBeenCalled() must be a spy' 457 | ) 458 | 459 | assert( 460 | spy.calls.length === 0, 461 | (message || 'spy was not supposed to be called') 462 | ) 463 | 464 | return this 465 | } 466 | } 467 | 468 | const deprecate = (fn, message) => { 469 | let alreadyWarned = false 470 | 471 | return function (...args) { 472 | if (!alreadyWarned) { 473 | alreadyWarned = true 474 | console.warn(message) // eslint-disable-line no-console 475 | } 476 | 477 | return fn.apply(this, args) 478 | } 479 | } 480 | 481 | Expectation.prototype.withContext = deprecate(function (context) { 482 | assert( 483 | isFunction(this.actual), 484 | 'The "actual" argument in expect(actual).withContext() must be a function' 485 | ) 486 | 487 | this.context = context 488 | 489 | return this 490 | }, ` 491 | withContext is deprecated; use a closure instead. 492 | 493 | expect(fn).withContext(context).toThrow() 494 | 495 | becomes 496 | 497 | expect(() => fn.call(context)).toThrow() 498 | `) 499 | 500 | Expectation.prototype.withArgs = deprecate(function (...args) { 501 | assert( 502 | isFunction(this.actual), 503 | 'The "actual" argument in expect(actual).withArgs() must be a function' 504 | ) 505 | 506 | if (args.length) 507 | this.args = this.args.concat(...args) 508 | 509 | return this 510 | }, ` 511 | withArgs is deprecated; use a closure instead. 512 | 513 | expect(fn).withArgs(a, b, c).toThrow() 514 | 515 | becomes 516 | 517 | expect(() => fn(a, b, c)).toThrow() 518 | `) 519 | 520 | const aliases = { 521 | toBeAn: 'toBeA', 522 | toNotBeAn: 'toNotBeA', 523 | toBeTruthy: 'toExist', 524 | toBeFalsy: 'toNotExist', 525 | toBeFewerThan: 'toBeLessThan', 526 | toBeMoreThan: 'toBeGreaterThan', 527 | toContain: 'toInclude', 528 | toNotContain: 'toExclude', 529 | toNotInclude: 'toExclude', 530 | toContainKeys: 'toIncludeKeys', 531 | toNotContainKeys: 'toExcludeKeys', 532 | toNotIncludeKeys: 'toExcludeKeys', 533 | toContainKey: 'toIncludeKey', 534 | toNotContainKey: 'toExcludeKey', 535 | toNotIncludeKey: 'toExcludeKey' 536 | } 537 | 538 | for (const alias in aliases) 539 | if (aliases.hasOwnProperty(alias)) 540 | Expectation.prototype[alias] = Expectation.prototype[aliases[alias]] 541 | 542 | export default Expectation 543 | -------------------------------------------------------------------------------- /modules/SpyUtils.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable prefer-rest-params, no-underscore-dangle*/ 2 | import { supportsDescriptors } from 'define-properties' 3 | import assert from './assert' 4 | import { isFunction } from './TestUtils' 5 | 6 | const noop = () => {} 7 | 8 | const supportsConfigurableFnLength = supportsDescriptors && 9 | Object.getOwnPropertyDescriptor(() => {}, 'length').configurable 10 | 11 | export const isSpy = (object) => 12 | object && object.__isSpy === true 13 | 14 | let spies = [] 15 | 16 | export const restoreSpies = () => { 17 | for (let i = spies.length - 1; i >= 0; i--) 18 | spies[i].restore() 19 | 20 | spies = [] 21 | } 22 | 23 | export const createSpy = (fn, restore = noop) => { 24 | if (fn == null) 25 | fn = noop 26 | 27 | assert( 28 | isFunction(fn), 29 | 'createSpy needs a function' 30 | ) 31 | 32 | let targetFn, thrownValue, returnValue, spy 33 | 34 | function spyLogic() { 35 | spy.calls.push({ 36 | context: this, 37 | arguments: Array.prototype.slice.call(arguments, 0) 38 | }) 39 | 40 | if (targetFn) 41 | return targetFn.apply(this, arguments) 42 | 43 | if (thrownValue) 44 | throw thrownValue 45 | 46 | return returnValue 47 | } 48 | 49 | if (supportsConfigurableFnLength) { 50 | spy = Object.defineProperty(spyLogic, 'length', 51 | { value: fn.length, writable: false, enumerable: false, configurable: true }) 52 | } else { 53 | spy = new Function('spy', `return function(${ // eslint-disable-line no-new-func 54 | [ ...Array(fn.length) ].map((_, i) => `_${i}`).join(',') 55 | }) { 56 | return spy.apply(this, arguments) 57 | }`)(spyLogic) 58 | } 59 | 60 | spy.calls = [] 61 | 62 | spy.andCall = (otherFn) => { 63 | targetFn = otherFn 64 | return spy 65 | } 66 | 67 | spy.andCallThrough = () => 68 | spy.andCall(fn) 69 | 70 | spy.andThrow = (value) => { 71 | thrownValue = value 72 | return spy 73 | } 74 | 75 | spy.andReturn = (value) => { 76 | returnValue = value 77 | return spy 78 | } 79 | 80 | spy.getLastCall = () => 81 | spy.calls[spy.calls.length - 1] 82 | 83 | spy.reset = () => { 84 | spy.calls = [] 85 | } 86 | 87 | spy.restore = spy.destroy = restore 88 | 89 | spy.__isSpy = true 90 | 91 | spies.push(spy) 92 | 93 | return spy 94 | } 95 | 96 | export const spyOn = (object, methodName) => { 97 | const original = object[methodName] 98 | 99 | if (!isSpy(original)) { 100 | assert( 101 | isFunction(original), 102 | 'Cannot spyOn the %s property; it is not a function', 103 | methodName 104 | ) 105 | 106 | object[methodName] = createSpy(original, () => { 107 | object[methodName] = original 108 | }) 109 | } 110 | 111 | return object[methodName] 112 | } 113 | -------------------------------------------------------------------------------- /modules/TestUtils.js: -------------------------------------------------------------------------------- 1 | import isRegExp from 'is-regex' 2 | import whyNotStrictlyEqual from 'is-equal/why' 3 | import objectKeys from 'object-keys' 4 | 5 | /** 6 | * Returns the reason why the given arguments are not *conceptually* 7 | * equal, if any; the empty string otherwise. 8 | */ 9 | export const whyNotEqual = (a, b) => 10 | (a == b ? '' : whyNotStrictlyEqual(a, b)) // eslint-disable-line eqeqeq 11 | 12 | /** 13 | * Returns true if the given arguments are *conceptually* equal. 14 | */ 15 | export const isEqual = (a, b) => 16 | whyNotEqual(a, b) === '' 17 | 18 | /** 19 | * Returns true if the given object is a function. 20 | */ 21 | export const isFunction = (object) => 22 | typeof object === 'function' 23 | 24 | /** 25 | * Returns true if the given object is an array. 26 | */ 27 | export const isArray = (object) => 28 | Array.isArray(object) 29 | 30 | /** 31 | * Returns true if the given object is an object. 32 | */ 33 | export const isObject = (object) => 34 | object && !isArray(object) && typeof object === 'object' 35 | 36 | /** 37 | * Returns true if the given object is an instanceof value 38 | * or its typeof is the given value. 39 | */ 40 | export const isA = (object, value) => { 41 | if (isFunction(value)) 42 | return object instanceof value 43 | 44 | if (value === 'array') 45 | return Array.isArray(object) 46 | 47 | return typeof object === value 48 | } 49 | 50 | /** 51 | * Returns true if the given function throws the given value 52 | * when invoked. The value may be: 53 | * 54 | * - undefined, to merely assert there was a throw 55 | * - a constructor function, for comparing using instanceof 56 | * - a regular expression, to compare with the error message 57 | * - a string, to find in the error message 58 | */ 59 | export const functionThrows = (fn, context, args, value) => { 60 | try { 61 | fn.apply(context, args) 62 | } catch (error) { 63 | if (value == null) 64 | return true 65 | 66 | if (isFunction(value) && error instanceof value) 67 | return true 68 | 69 | const message = error.message || error 70 | 71 | if (typeof message === 'string') { 72 | if (isRegExp(value) && value.test(error.message)) 73 | return true 74 | 75 | if (typeof value === 'string' && message.indexOf(value) !== -1) 76 | return true 77 | } 78 | } 79 | 80 | return false 81 | } 82 | 83 | /** 84 | * Returns true if the given array contains the value, false 85 | * otherwise. The compareValues function must return false to 86 | * indicate a non-match. 87 | */ 88 | export const arrayContains = (array, value, compareValues) => 89 | array.some(item => compareValues(item, value) !== false) 90 | 91 | const ownEnumerableKeys = (object) => { 92 | if (typeof Reflect === 'object' && typeof Reflect.ownKeys === 'function') { 93 | return Reflect.ownKeys(object) 94 | .filter(key => Object.getOwnPropertyDescriptor(object, key).enumerable) 95 | } 96 | 97 | if (typeof Object.getOwnPropertySymbols === 'function') { 98 | return Object.getOwnPropertySymbols(object) 99 | .filter(key => Object.getOwnPropertyDescriptor(object, key).enumerable) 100 | .concat(objectKeys(object)) 101 | } 102 | 103 | return objectKeys(object) 104 | } 105 | 106 | /** 107 | * Returns true if the given object contains the value, false 108 | * otherwise. The compareValues function must return false to 109 | * indicate a non-match. 110 | */ 111 | export const objectContains = (object, value, compareValues) => 112 | ownEnumerableKeys(value).every(k => { 113 | if (isObject(object[k]) && isObject(value[k])) 114 | return objectContains(object[k], value[k], compareValues) 115 | 116 | return compareValues(object[k], value[k]) 117 | }) 118 | 119 | /** 120 | * Returns true if the given string contains the value, false otherwise. 121 | */ 122 | export const stringContains = (string, value) => 123 | string.indexOf(value) !== -1 124 | -------------------------------------------------------------------------------- /modules/__tests__/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /modules/__tests__/chainable-test.js: -------------------------------------------------------------------------------- 1 | import expect from '../index' 2 | 3 | describe('chaining assertions', () => { 4 | it('should allow chaining for array-like applications', () => { 5 | expect([ 1, 2, 'foo', 3 ]) 6 | .toExist() 7 | .toBeAn(Array) 8 | .toInclude('foo') 9 | .toExclude('bar') 10 | }) 11 | 12 | it('should allow chaining for number checking', () => { 13 | expect(3.14) 14 | .toExist() 15 | .toBeLessThan(4.2) 16 | .toBeLessThanOrEqualTo(3.14) 17 | .toBeGreaterThan(3.0) 18 | .toBeGreaterThanOrEqualTo(3.14) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /modules/__tests__/createSpy-test.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable prefer-rest-params*/ 2 | import expect, { createSpy, isSpy } from '../index' 3 | 4 | describe('createSpy', () => { 5 | describe('when given a function', () => { 6 | it('returns a spy function', () => { 7 | const spy = createSpy(() => {}) 8 | expect(spy).toBeA(Function) 9 | }) 10 | }) 11 | 12 | describe('when not given a function', () => { 13 | it('throws an error', () => { 14 | expect(() => { 15 | createSpy(0) 16 | }).toThrow() 17 | }) 18 | }) 19 | }) 20 | 21 | describe('A spy', () => { 22 | let targetContext, targetArguments 23 | const target = { 24 | method() { 25 | targetContext = this 26 | targetArguments = Array.prototype.slice.call(arguments, 0) 27 | } 28 | } 29 | 30 | let spy 31 | beforeEach(() => { 32 | spy = createSpy(target.method) 33 | targetContext = targetArguments = null 34 | }) 35 | 36 | it('is a spy', () => { 37 | expect(isSpy(spy)).toBe(true) 38 | }) 39 | 40 | it('has the same length as the function passed in', () => { 41 | expect(spy.length).toBe(0) 42 | expect(createSpy(a => a).length).toBe(1) 43 | expect(createSpy((a, b, c) => a * b * c).length).toBe(3) 44 | }) 45 | 46 | it('has a destroy method', () => { 47 | expect(spy.destroy).toBeA(Function) 48 | }) 49 | 50 | it('has a restore method', () => { 51 | expect(spy.restore).toBeA(Function) 52 | }) 53 | 54 | it('has a reset method', () => { 55 | expect(spy.reset).toBeA(Function) 56 | }) 57 | 58 | it('reset clears out all previous calls', () => { 59 | spy() 60 | expect(spy.calls.length).toEqual(1) 61 | spy.reset() 62 | expect(spy.calls.length).toEqual(0) 63 | }) 64 | 65 | it('knows how many times it has been called', () => { 66 | spy() 67 | spy() 68 | expect(spy.calls.length).toEqual(2) 69 | }) 70 | 71 | it('knows the arguments it was called with', () => { 72 | spy(1, 2, 3) 73 | expect(spy).toHaveBeenCalledWith(1, 2, 3) 74 | }) 75 | 76 | describe('that calls some other function', () => { 77 | let otherContext, otherArguments 78 | function otherFn() { 79 | otherContext = this 80 | otherArguments = Array.prototype.slice.call(arguments, 0) 81 | } 82 | 83 | beforeEach(() => { 84 | spy.andCall(otherFn) 85 | otherContext = otherArguments = null 86 | }) 87 | 88 | it('calls that function', () => { 89 | spy() 90 | expect(otherContext).toNotBe(null) 91 | }) 92 | 93 | it('uses the correct context', () => { 94 | const context = {} 95 | spy.call(context) 96 | expect(otherContext).toBe(context) 97 | }) 98 | 99 | it('passes the arguments through', () => { 100 | spy(1, 2, 3) 101 | expect(otherArguments).toEqual([ 1, 2, 3 ]) 102 | }) 103 | }) 104 | 105 | describe('that calls through', () => { 106 | beforeEach(() => { 107 | spy.andCallThrough() 108 | }) 109 | 110 | it('calls the original function', () => { 111 | spy() 112 | expect(targetContext).toNotBe(null) 113 | }) 114 | 115 | it('uses the correct context', () => { 116 | const context = {} 117 | spy.call(context) 118 | expect(targetContext).toBe(context) 119 | }) 120 | 121 | it('passes the arguments through', () => { 122 | spy(1, 2, 3) 123 | expect(targetArguments).toEqual([ 1, 2, 3 ]) 124 | }) 125 | }) 126 | 127 | describe('with a thrown value', () => { 128 | beforeEach(() => { 129 | spy.andThrow('hello') 130 | }) 131 | 132 | it('throws the correct value', () => { 133 | expect(spy).toThrow('hello') 134 | }) 135 | }) 136 | 137 | describe('with a return value', () => { 138 | beforeEach(() => { 139 | spy.andReturn('hello') 140 | }) 141 | 142 | it('returns the correct value', () => { 143 | expect(spy()).toEqual('hello') 144 | }) 145 | }) 146 | }) 147 | -------------------------------------------------------------------------------- /modules/__tests__/defaultExport-test.js: -------------------------------------------------------------------------------- 1 | import expect from '../index' 2 | const cjsExpect = require('../index') 3 | 4 | describe('default export', () => { 5 | it('is a function', () => { 6 | expect(typeof cjsExpect).toEqual('function') 7 | }) 8 | 9 | it('is the same whether import-ed or require-d', () => { 10 | expect(cjsExpect).toEqual(expect) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /modules/__tests__/extend-test.js: -------------------------------------------------------------------------------- 1 | import expect, { assert, extend } from '../index' 2 | 3 | describe('expect.extend', () => { 4 | const ColorAssertions = { 5 | toBeAColor() { 6 | assert( 7 | this.actual.match(/^#[a-fA-F0-9]{6}$/), 8 | 'expected %s to be an HTML color', 9 | this.actual 10 | ) 11 | } 12 | } 13 | 14 | it('works', () => { 15 | expect(expect().toBeAColor).toNotExist() 16 | extend(ColorAssertions) 17 | expect(expect().toBeAColor).toBeA(Function) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /modules/__tests__/restoreSpies-test.js: -------------------------------------------------------------------------------- 1 | import expect, { createSpy, spyOn, restoreSpies } from '../index' 2 | 3 | describe('restoreSpies', () => { 4 | describe('with one spy', () => { 5 | const original = () => {} 6 | const target = { method: original } 7 | 8 | beforeEach(() => { 9 | spyOn(target, 'method') 10 | }) 11 | 12 | it('works with spyOn()', () => { 13 | expect(target.method).toNotEqual(original) 14 | restoreSpies() 15 | expect(target.method).toEqual(original) 16 | }) 17 | 18 | it('is idempotent', () => { 19 | expect(target.method).toNotEqual(original) 20 | restoreSpies() 21 | restoreSpies() 22 | expect(target.method).toEqual(original) 23 | }) 24 | 25 | it('can work even on createSpy()', () => { 26 | createSpy(original) 27 | restoreSpies() 28 | }) 29 | }) 30 | 31 | describe('with multiple spies', () => { 32 | const originals = [ () => {}, () => {} ] 33 | const targets = [ 34 | { method: originals[0] }, 35 | { method: originals[1] } 36 | ] 37 | 38 | it('still works', () => { 39 | spyOn(targets[0], 'method') 40 | spyOn(targets[1], 'method') 41 | 42 | expect(targets[0].method).toNotEqual(originals[0]) 43 | expect(targets[1].method).toNotEqual(originals[1]) 44 | 45 | restoreSpies() 46 | 47 | expect(targets[0].method).toEqual(originals[0]) 48 | expect(targets[1].method).toEqual(originals[1]) 49 | }) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /modules/__tests__/spyOn-test.js: -------------------------------------------------------------------------------- 1 | import expect, { spyOn } from '../index' 2 | 3 | describe('A function that was spied on', () => { 4 | const video = { 5 | play: () => {} 6 | } 7 | 8 | let spy 9 | beforeEach(() => { 10 | spy = spyOn(video, 'play') 11 | video.play('some', 'args') 12 | }) 13 | 14 | it('tracks the number of calls', () => { 15 | expect(spy.calls.length).toEqual(1) 16 | }) 17 | 18 | it('tracks the context that was used', () => { 19 | expect(spy.calls[0].context).toBe(video) 20 | }) 21 | 22 | it('tracks the arguments that were used', () => { 23 | expect(spy.calls[0].arguments).toEqual([ 'some', 'args' ]) 24 | }) 25 | 26 | it('was called', () => { 27 | expect(spy).toHaveBeenCalled() 28 | }) 29 | 30 | it('was called with the correct args', () => { 31 | expect(spy).toHaveBeenCalledWith('some', 'args') 32 | }) 33 | 34 | it('can be restored', () => { 35 | expect(video.play).toEqual(spy) 36 | spy.restore() 37 | expect(video.play).toNotEqual(spy) 38 | }) 39 | }) 40 | 41 | describe('A function that was spied on but not called', () => { 42 | const video = { 43 | play: () => {} 44 | } 45 | 46 | let spy 47 | beforeEach(() => { 48 | spy = spyOn(video, 'play') 49 | }) 50 | 51 | it('number of calls to be zero', () => { 52 | expect(spy.calls.length).toEqual(0) 53 | }) 54 | 55 | it('was not called', () => { 56 | expect(spy).toNotHaveBeenCalled() 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /modules/__tests__/toBeA-test.js: -------------------------------------------------------------------------------- 1 | import expect from '../index' 2 | import Expectation from '../Expectation' 3 | 4 | describe('toBeA', () => { 5 | it('requires the value to be a function or string', () => { 6 | expect(() => { 7 | expect('actual').toBeA(4) 8 | }).toThrow(/must be a function or a string/) 9 | }) 10 | 11 | it('does not throw when the actual value is an instanceof the constructor', () => { 12 | expect(() => { 13 | expect(new Expectation).toBeA(Expectation) 14 | }).toNotThrow() 15 | }) 16 | 17 | it('throws when the actual value is not an instanceof the constructor', () => { 18 | expect(() => { 19 | expect('actual').toBeA(Expectation) 20 | }).toThrow(/to be/) 21 | }) 22 | 23 | it('does not throw when the expected value is the typeof the actual value', () => { 24 | expect(() => { 25 | expect(4).toBeA('number') 26 | expect(NaN).toBeA('number') // hahaha 27 | }).toNotThrow() 28 | }) 29 | 30 | it('throws when the expected value is not the typeof the actual value', () => { 31 | expect(() => { 32 | expect('actual').toBeA('number') 33 | }).toThrow(/to be/) 34 | }) 35 | 36 | it('does not throw when the actual value is an array', () => { 37 | expect(() => { 38 | expect([]).toBeAn('array') 39 | }).toNotThrow() 40 | }) 41 | 42 | it('throws when the actual value is not an array', () => { 43 | expect(() => { 44 | expect('actual').toBeAn('array') 45 | }).toThrow(/to be/) 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /modules/__tests__/toBeGreaterThan-test.js: -------------------------------------------------------------------------------- 1 | import expect from '../index' 2 | 3 | describe('toBeGreaterThan', () => { 4 | it('does not throw when the actual value is greater than the expected value', () => { 5 | expect(() => { 6 | expect(3).toBeGreaterThan(2) 7 | }).toNotThrow() 8 | }) 9 | 10 | it('throws when the actual value is not greater than the expected value', () => { 11 | expect(() => { 12 | expect(2).toBeGreaterThan(3) 13 | }).toThrow(/to be greater than/) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /modules/__tests__/toBeGreaterThanOrEqualTo-test.js: -------------------------------------------------------------------------------- 1 | import expect from '../index' 2 | 3 | describe('toBeGreaterThanOrEqualTo', () => { 4 | it('does not throw when the actual value is greater than the expected value', () => { 5 | expect(() => { 6 | expect(3).toBeGreaterThanOrEqualTo(2) 7 | }).toNotThrow() 8 | }) 9 | 10 | it('does not throw when the actual value is equal to the expected value', () => { 11 | expect(() => { 12 | expect(3).toBeGreaterThanOrEqualTo(3) 13 | }).toNotThrow() 14 | }) 15 | 16 | it('throws when the actual value is not greater than or equal to the expected value', () => { 17 | expect(() => { 18 | expect(2).toBeGreaterThanOrEqualTo(3) 19 | }).toThrow(/to be greater than or equal to/) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /modules/__tests__/toBeLessThan-test.js: -------------------------------------------------------------------------------- 1 | import expect from '../index' 2 | 3 | describe('toBeLessThan', () => { 4 | it('does not throw when the actual value is less than the expected value', () => { 5 | expect(() => { 6 | expect(2).toBeLessThan(3) 7 | }).toNotThrow() 8 | }) 9 | 10 | it('throws when the actual value is not less than the expected value', () => { 11 | expect(() => { 12 | expect(3).toBeLessThan(2) 13 | }).toThrow(/to be less than/) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /modules/__tests__/toBeLessThanOrEqualTo-test.js: -------------------------------------------------------------------------------- 1 | import expect from '../index' 2 | 3 | describe('toBeLessThanOrEqualTo', () => { 4 | it('does not throw when the actual value is less than the expected value', () => { 5 | expect(() => { 6 | expect(2).toBeLessThanOrEqualTo(3) 7 | }).toNotThrow() 8 | }) 9 | 10 | it('does not throw when the actual value is equal to the expected value', () => { 11 | expect(() => { 12 | expect(2).toBeLessThanOrEqualTo(2) 13 | }).toNotThrow() 14 | }) 15 | 16 | it('throws when the actual value is not less than the expected value', () => { 17 | expect(() => { 18 | expect(3).toBeLessThanOrEqualTo(2) 19 | }).toThrow(/to be less than or equal to/) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /modules/__tests__/toBeTruthy-test.js: -------------------------------------------------------------------------------- 1 | import expect from '../index' 2 | 3 | describe('toBeTruthy', () => { 4 | it('does not throw on truthy actual values', () => { 5 | expect(() => { 6 | expect(1).toBeTruthy() 7 | expect({ hello: 'world' }).toBeTruthy() 8 | expect([ 1, 2, 3 ]).toBeTruthy() 9 | }).toNotThrow() 10 | }) 11 | 12 | it('throws on falsy actual values', () => { 13 | expect(() => { 14 | expect(0).toBeTruthy() 15 | }).toThrow() 16 | 17 | expect(() => { 18 | expect(null).toBeTruthy() 19 | }).toThrow() 20 | 21 | expect(() => { 22 | expect(undefined).toBeTruthy() 23 | }).toThrow() 24 | }) 25 | }) 26 | 27 | describe('toBeFalsy', () => { 28 | it('throws on truthy values', () => { 29 | expect(() => { 30 | expect(42).toBeFalsy() 31 | }).toThrow() 32 | 33 | expect(() => { 34 | expect({ foo: 'bar' }).toBeFalsy() 35 | }).toThrow() 36 | 37 | expect(() => { 38 | expect([]).toBeFalsy() 39 | }).toThrow() 40 | }) 41 | 42 | it('does not throw with falsy actual values', () => { 43 | expect(() => { 44 | expect(0).toBeFalsy() 45 | expect(null).toBeFalsy() 46 | expect(undefined).toBeFalsy() 47 | }).toNotThrow() 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /modules/__tests__/toEqual-test.js: -------------------------------------------------------------------------------- 1 | import expect from '../index' 2 | 3 | describe('toEqual', () => { 4 | it('works', () => { 5 | expect('actual').toEqual('actual') 6 | }) 7 | 8 | it('works with numbers and strings that have the same conceptual value', () => { 9 | expect(2).toEqual('2') 10 | }) 11 | 12 | it('works with null and undefined', () => { 13 | expect(null).toEqual(undefined) 14 | }) 15 | 16 | it('works with objects that have the same keys in different order', () => { 17 | const a = { a: 'a', b: 'b', c: 'c' } 18 | const b = { b: 'b', c: 'c', a: 'a' } 19 | expect(a).toEqual(b) 20 | }) 21 | 22 | it('works when object has circular reference', () => { 23 | function Circular() { 24 | this.circularRef = this 25 | } 26 | 27 | const a = new Circular() 28 | const b = new Circular() 29 | 30 | expect(a).toEqual(b) 31 | }) 32 | 33 | if (typeof Map !== 'undefined') { 34 | it('works with Map', () => { 35 | const a = new Map() 36 | a.set('key', 'value') 37 | 38 | const b = new Map() 39 | b.set('key', 'value') 40 | 41 | expect(a).toEqual(b) 42 | }) 43 | } 44 | 45 | if (typeof Set !== 'undefined') { 46 | it('works with Set', () => { 47 | const a = new Set() 48 | a.add('a') 49 | 50 | const b = new Set() 51 | b.add('a') 52 | 53 | expect(a).toEqual(b) 54 | }) 55 | } 56 | 57 | it('shows diff', () => { 58 | try { 59 | expect('actual').toEqual('expected') 60 | } catch (err) { 61 | expect(err.actual).toEqual('actual') 62 | expect(err.expected).toEqual('expected') 63 | expect(err.showDiff).toEqual(true) 64 | } 65 | }) 66 | }) 67 | -------------------------------------------------------------------------------- /modules/__tests__/toExclude-test.js: -------------------------------------------------------------------------------- 1 | import expect from '../index' 2 | 3 | describe('toExclude', () => { 4 | it('requires the actual value to be an array or string', () => { 5 | expect(() => { 6 | expect(1).toExclude(2) 7 | }).toThrow(/must be an array, object, or a string/) 8 | }) 9 | 10 | it('does not throw when an array does not contain the expected value', () => { 11 | expect(() => { 12 | expect([ 1, 2, 3 ]).toExclude(4) 13 | }).toNotThrow() 14 | }) 15 | 16 | it('throws when an array contains the expected value', () => { 17 | expect(() => { 18 | expect([ 1, 2, 3 ]).toExclude(2) 19 | }).toThrow(/to exclude/) 20 | }) 21 | 22 | it('throws when an object contains an expected object', () => { 23 | expect(() => { 24 | expect({ a: 1 }).toExclude({ a: 1 }) 25 | }).toThrow(/to exclude/) 26 | }) 27 | 28 | it('does not throw when an array contains an unexpected object', () => { 29 | expect(() => { 30 | expect({ a: 1 }).toExclude({ b: 2 }) 31 | }).toNotThrow() 32 | }) 33 | 34 | it('does not throw when an object contains an expected object with an unexpected value', () => { 35 | expect(() => { 36 | expect({ a: 1 }).toExclude({ a: 2 }) 37 | }).toNotThrow() 38 | }) 39 | 40 | it('does not throw when an array does not contain the expected value', () => { 41 | expect(() => { 42 | expect('hello world').toExclude('goodbye') 43 | }).toNotThrow() 44 | }) 45 | 46 | it('throws when a string contains the expected value', () => { 47 | expect(() => { 48 | expect('hello world').toExclude('hello') 49 | }).toThrow(/to exclude/) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /modules/__tests__/toExcludeKey-test.js: -------------------------------------------------------------------------------- 1 | import expect from '../index' 2 | 3 | describe('toExcludeKey', () => { 4 | it('requires the actual value to have keys', () => { 5 | expect(() => { 6 | expect(1).toExcludeKey('hello') 7 | }).toThrow(/must be an object/) 8 | }) 9 | 10 | it('throws when there is a key that exists', () => { 11 | expect(() => { 12 | expect({ a: 1 }).toExcludeKey('a') 13 | }).toThrow(/exclude key/) 14 | }) 15 | 16 | it('does not throw when there is a key that does not exist', () => { 17 | expect(() => { 18 | expect({ a: 1 }).toExcludeKey('b') 19 | }).toNotThrow() 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /modules/__tests__/toExcludeKeys-test.js: -------------------------------------------------------------------------------- 1 | import expect from '../index' 2 | 3 | describe('toExcludeKeys', () => { 4 | it('requires the actual value to have keys', () => { 5 | expect(() => { 6 | expect(1).toExcludeKeys('hello') 7 | }).toThrow(/must be an object/) 8 | }) 9 | 10 | it('requires the keys to be an array', () => { 11 | expect(() => { 12 | expect({ a: 1 }).toIncludeKeys({ b: 2 }) 13 | }).toThrow(/must be an array/) 14 | }) 15 | 16 | it('throws when there is a key that exists', () => { 17 | expect(() => { 18 | expect({ a: 1 }).toExcludeKeys([ 'a' ]) 19 | }).toThrow(/exclude key/) 20 | }) 21 | 22 | it('does not throw when there is a key that does not exist', () => { 23 | expect(() => { 24 | expect({ a: 1 }).toExcludeKeys([ 'b' ]) 25 | }).toNotThrow() 26 | }) 27 | 28 | it('throws when all keys exist', () => { 29 | expect(() => { 30 | expect({ a: 1, b: 2, c: 3 }).toExcludeKeys([ 'a', 'b', 'c' ]) 31 | }).toThrow(/exclude key/) 32 | }) 33 | 34 | it('does not throw when even one key does not exist', () => { 35 | expect(() => { 36 | expect({ a: 1, c: 3 }).toExcludeKeys([ 'a', 'b', 'c' ]) 37 | }).toNotThrow() 38 | }) 39 | 40 | it('works with arrays', () => { 41 | expect(() => { 42 | expect([ 0, 1, 2 ]).toExcludeKeys([ '0', 1, 2 ]) 43 | }).toThrow(/exclude key/) 44 | 45 | expect(() => { 46 | expect([ 0, 1, 2 ]).toExcludeKeys([ 3 ]) 47 | }).toNotThrow() 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /modules/__tests__/toExist-test.js: -------------------------------------------------------------------------------- 1 | import expect from '../index' 2 | 3 | describe('toExist', () => { 4 | it('does not throw on truthy actual values', () => { 5 | expect(() => { 6 | expect(1).toExist() 7 | expect({ hello: 'world' }).toExist() 8 | expect([ 1, 2, 3 ]).toExist() 9 | }).toNotThrow() 10 | }) 11 | 12 | it('throws on falsy actual values', () => { 13 | expect(() => { 14 | expect(0).toExist() 15 | }).toThrow() 16 | 17 | expect(() => { 18 | expect(null).toExist() 19 | }).toThrow() 20 | 21 | expect(() => { 22 | expect(undefined).toExist() 23 | }).toThrow() 24 | }) 25 | }) 26 | 27 | describe('toNotExist', () => { 28 | it('throws on truthy values', () => { 29 | expect(() => { 30 | expect(42).toNotExist() 31 | }).toThrow() 32 | 33 | expect(() => { 34 | expect({ foo: 'bar' }).toNotExist() 35 | }).toThrow() 36 | 37 | expect(() => { 38 | expect([]).toNotExist() 39 | }).toThrow() 40 | }) 41 | 42 | it('does not throw with falsy actual values', () => { 43 | expect(() => { 44 | expect(0).toNotExist() 45 | expect(null).toNotExist() 46 | expect(undefined).toNotExist() 47 | }).toNotThrow() 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /modules/__tests__/toInclude-test.js: -------------------------------------------------------------------------------- 1 | import expect from '../index' 2 | 3 | describe('toInclude', () => { 4 | it('requires the actual value to be an array, object, or a string', () => { 5 | expect(() => { 6 | expect(1).toInclude(2) 7 | }).toThrow(/must be an array, object, or a string/) 8 | }) 9 | 10 | it('does not throw when an array contains an expected integer', () => { 11 | expect(() => { 12 | expect([ 1, 2, 3 ]).toInclude(2) 13 | expect([ { a: 1 }, { c: 2 } ]).toInclude({ c: 2 }) 14 | }).toNotThrow() 15 | }) 16 | 17 | it('does not throw when an array contains an expected object', () => { 18 | expect(() => { 19 | expect([ { a: 1 }, { c: 2 } ]).toInclude({ c: 2 }) 20 | }).toNotThrow() 21 | }) 22 | 23 | it('does not throw when an object contains an expected object', () => { 24 | expect(() => { 25 | expect({ a: 1, b: 2, c: 3 }).toInclude({ b: 2 }) 26 | }).toNotThrow() 27 | 28 | expect(() => { 29 | expect({ a: 1, b: 2 }).toInclude({}) 30 | }).toNotThrow() 31 | }) 32 | 33 | it('throws when an object does not contain an expected object', () => { 34 | expect(() => { 35 | expect({ a: 1, b: 2, c: 3 }).toInclude({ d: 4 }) 36 | }).toThrow(/to include/) 37 | 38 | expect(() => { 39 | expect({ a: 1, b: 2, c: 3 }).toInclude({ b: 4 }) 40 | }).toThrow(/to include/) 41 | }) 42 | 43 | it('does not throw when an object contains an expected object in a deep key', () => { 44 | expect(() => { 45 | expect( 46 | { a: 1, b: 2, c: { d: 4, e: { f: 5, g: 6, h: 7 } } } 47 | ).toInclude( 48 | { b: 2, c: { e: { f: 5, g: 6, h: 7 } } } 49 | ) 50 | }).toNotThrow() 51 | }) 52 | 53 | it('throws when an object does not contain an expected object in a deep key', () => { 54 | expect(() => { 55 | expect( 56 | { a: 1, b: 2, c: { d: 4, e: { f: 5, g: 6, h: 7 } } } 57 | ).toInclude( 58 | { b: 2, c: { e: { f: 5, g: 999, h: 7 } } } 59 | ) 60 | }).toThrow(/to include/) 61 | }) 62 | 63 | it('compares partial object only when both expected and actual properties are object', () => { 64 | expect(() => { 65 | expect({ a: { b: 2 } }).toInclude({ a: {} }) 66 | }).toNotThrow() 67 | 68 | expect(() => { 69 | expect({ a: 1 }).toInclude({ a: {} }) 70 | }).toThrow(/to include/) 71 | 72 | expect(() => { 73 | expect({ a: [] }).toInclude({ a: {} }) 74 | }).toThrow(/to include/) 75 | 76 | expect(() => { 77 | expect({ a: '1' }).toInclude({ a: {} }) 78 | }).toThrow(/to include/) 79 | 80 | expect(() => { 81 | expect({ a: () => {} }).toInclude({ a: {} }) 82 | }).toThrow(/to include/) 83 | }) 84 | 85 | if (typeof Object.create === 'function') { 86 | it('ignores nonenumerable properties of an expected object', () => { 87 | expect(() => { 88 | expect({}).toInclude(Object.create({}, { a: { value: 1 } })) 89 | }).toNotThrow() 90 | }) 91 | } 92 | 93 | if (typeof Symbol === 'function') { 94 | const symbol = Symbol() 95 | 96 | it('does not throw when an object contains an expected object with a symbol key', () => { 97 | expect(() => { 98 | expect({ [symbol]: 1, b: 2 }).toInclude({ [symbol]: 1 }) 99 | }).toNotThrow() 100 | }) 101 | 102 | it('throws when an object contain an expected object without a symbol key', () => { 103 | expect(() => { 104 | expect({ a: 1, b: 2 }).toInclude({ [symbol]: 1 }) 105 | }).toThrow(/to include/) 106 | }) 107 | } 108 | 109 | it('throws when an array does not contain an expected integer', () => { 110 | expect(() => { 111 | expect([ 1, 2, 3 ]).toInclude(4) 112 | }).toThrow(/to include/) 113 | }) 114 | 115 | it('throws when an array does not contain an expected object', () => { 116 | expect(() => { 117 | expect([ { a: 1 }, { c: 2 } ]).toInclude({ a: 2 }) 118 | }).toThrow(/to include/) 119 | }) 120 | 121 | it('does not throw when a string contains the expected value', () => { 122 | expect(() => { 123 | expect('hello world').toInclude('world') 124 | }).toNotThrow() 125 | }) 126 | 127 | it('throws when a string does not contain the expected value', () => { 128 | expect(() => { 129 | expect('hello world').toInclude('goodbye') 130 | }).toThrow(/to include/) 131 | }) 132 | }) 133 | -------------------------------------------------------------------------------- /modules/__tests__/toIncludeKey-test.js: -------------------------------------------------------------------------------- 1 | import expect from '../index' 2 | 3 | describe('toIncludeKey', () => { 4 | it('requires the actual value to have keys', () => { 5 | expect(() => { 6 | expect(1).toIncludeKey('hello') 7 | }).toThrow(/must be an object/) 8 | }) 9 | 10 | it('does not throw when there is a key that exists', () => { 11 | expect(() => { 12 | expect({ a: 1 }).toIncludeKey('a') 13 | }).toNotThrow() 14 | }) 15 | 16 | it('throws when there is a key that does not exist', () => { 17 | expect(() => { 18 | expect({ a: 1 }).toIncludeKey('b') 19 | }).toThrow(/include key/) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /modules/__tests__/toIncludeKeys-test.js: -------------------------------------------------------------------------------- 1 | import expect from '../index' 2 | 3 | describe('toIncludeKeys', () => { 4 | it('requires the actual value to have keys', () => { 5 | expect(() => { 6 | expect(1).toIncludeKeys([ 'hello' ]) 7 | }).toThrow(/must be an object/) 8 | }) 9 | 10 | it('requires the keys to be an array', () => { 11 | expect(() => { 12 | expect({ a: 1 }).toIncludeKeys({ b: 2 }) 13 | }).toThrow(/must be an array/) 14 | }) 15 | 16 | it('does not throw when there is a key that exists', () => { 17 | expect(() => { 18 | expect({ a: 1 }).toIncludeKeys([ 'a' ]) 19 | }).toNotThrow() 20 | }) 21 | 22 | it('throws when there is a key that does not exist', () => { 23 | expect(() => { 24 | expect({ a: 1 }).toIncludeKeys([ 'b' ]) 25 | }).toThrow(/include key/) 26 | }) 27 | 28 | it('does not throw when all keys exist', () => { 29 | expect(() => { 30 | expect({ a: 1, b: 2, c: 3 }).toIncludeKeys([ 'a', 'b', 'c' ]) 31 | }).toNotThrow() 32 | }) 33 | 34 | it('throws when even one key does not exist', () => { 35 | expect(() => { 36 | expect({ a: 1, c: 3 }).toIncludeKeys([ 'a', 'b', 'c' ]) 37 | }).toThrow(/include key/) 38 | }) 39 | 40 | it('works with arrays', () => { 41 | expect(() => { 42 | expect([ 0, 1, 2 ]).toIncludeKeys([ '0', 1, 2 ]) 43 | }).toNotThrow() 44 | 45 | expect(() => { 46 | expect([ 0, 1, 2 ]).toIncludeKeys([ 3 ]) 47 | }).toThrow(/include key/) 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /modules/__tests__/toMatch-test.js: -------------------------------------------------------------------------------- 1 | import expect from '../index' 2 | 3 | describe('expect(string).toMatch', () => { 4 | it('does not throw when the actual value matches the pattern', () => { 5 | expect(() => { 6 | expect('actual').toMatch(/^actual$/) 7 | }).toNotThrow() 8 | }) 9 | 10 | it('throws when the actual value does not match the pattern', () => { 11 | expect(() => { 12 | expect('actual').toMatch(/nope/) 13 | }).toThrow(/to match/) 14 | }) 15 | }) 16 | 17 | describe('expect(object).toMatch', () => { 18 | it('does not throw when the actual value matches the pattern', () => { 19 | expect(() => { 20 | expect({ 21 | statusCode: 200, 22 | headers: { 23 | server: 'express web server' 24 | } 25 | }).toMatch({ 26 | statusCode: 200, 27 | headers: { 28 | server: /express/ 29 | } 30 | }) 31 | }).toNotThrow() 32 | }) 33 | 34 | it('throws when the actual value does not match the pattern', () => { 35 | expect(() => { 36 | expect({ 37 | statusCode: 200, 38 | headers: { 39 | server: 'nginx web server' 40 | } 41 | }).toMatch({ 42 | statusCode: 200, 43 | headers: { 44 | server: /express/ 45 | } 46 | }) 47 | }).toThrow(/to match/) 48 | }) 49 | }) 50 | 51 | describe('expect(array).toMatch', () => { 52 | it('does not throw when the array contains an object that matches the pattern', () => { 53 | const array = [ 54 | { one: 'one' }, 55 | { two: 'two' }, 56 | { three: 'three' } 57 | ] 58 | 59 | expect(array).toMatch([ { one: /one/ } ]) 60 | }) 61 | }) 62 | -------------------------------------------------------------------------------- /modules/__tests__/toNotEqual-test.js: -------------------------------------------------------------------------------- 1 | import expect from '../index' 2 | 3 | describe('toNotEqual', () => { 4 | it('works', () => { 5 | expect('actual').toNotEqual('expected') 6 | }) 7 | 8 | it('works with objects that do not have the same keys', () => { 9 | const a = { a: 'a', b: 'b', c: 'c' } 10 | const b = { a: 'a', b: 'b', d: 'c' } 11 | expect(a).toNotEqual(b) 12 | }) 13 | 14 | it('works with objects that have different prototypes and different keys', () => { 15 | class A { 16 | constructor(prop) { 17 | this.a = prop 18 | } 19 | } 20 | 21 | class B { 22 | constructor(prop) { 23 | this.b = prop 24 | } 25 | } 26 | 27 | const a = new A('hi') 28 | const b = new B('hi') 29 | 30 | expect(a).toNotEqual(b) 31 | }) 32 | 33 | it('works with arrays of objects', () => { 34 | const a = [ 35 | { 36 | id: 0, 37 | text: 'Array Object 0', 38 | boo: false 39 | }, 40 | { 41 | id: 1, 42 | text: 'Array Object 1', 43 | boo: false 44 | } 45 | ] 46 | 47 | const b = [ 48 | { 49 | id: 0, 50 | text: 'Array Object 0', 51 | boo: true // value of boo is changed to true here 52 | }, 53 | { 54 | id: 1, 55 | text: 'Array Object 1', 56 | boo: false 57 | } 58 | ] 59 | 60 | expect(a).toNotEqual(b) 61 | }) 62 | 63 | if (typeof Map !== 'undefined') { 64 | it('works with Map', () => { 65 | const a = new Map() 66 | a.set('key', 'value') 67 | 68 | const b = new Map() 69 | b.set('key', 'another value') 70 | 71 | expect(a).toNotEqual(b) 72 | }) 73 | } 74 | 75 | if (typeof Set !== 'undefined') { 76 | it('works with Set', () => { 77 | const a = new Set() 78 | a.add('a') 79 | 80 | const b = new Set() 81 | b.add('b') 82 | 83 | expect(a).toNotEqual(b) 84 | }) 85 | } 86 | }) 87 | -------------------------------------------------------------------------------- /modules/__tests__/toNotMatch-test.js: -------------------------------------------------------------------------------- 1 | import expect from '../index' 2 | 3 | describe('expect(string).toNotMatch', () => { 4 | it('does not throw when the actual value does not match the pattern', () => { 5 | expect(() => { 6 | expect('actual').toNotMatch(/nope/) 7 | }).toNotThrow() 8 | }) 9 | 10 | it('throws when the actual value matches the pattern', () => { 11 | expect(() => { 12 | expect('actual').toNotMatch(/^actual$/) 13 | }).toThrow(/to not match/) 14 | }) 15 | }) 16 | 17 | describe('expect(object).toNotMatch', () => { 18 | it('does not throw when the actual value does not match the pattern', () => { 19 | expect(() => { 20 | expect({ 21 | statusCode: 200, 22 | headers: { 23 | server: 'nginx web server' 24 | } 25 | }).toNotMatch({ 26 | statusCode: 200, 27 | headers: { 28 | server: /express/ 29 | } 30 | }) 31 | }).toNotThrow() 32 | }) 33 | 34 | it('throws when the actual value matches the pattern', () => { 35 | expect(() => { 36 | expect({ 37 | statusCode: 200, 38 | headers: { 39 | server: 'express web server' 40 | } 41 | }).toNotMatch({ 42 | statusCode: 200, 43 | headers: { 44 | server: /express/ 45 | } 46 | }) 47 | }).toThrow(/to not match/) 48 | }) 49 | }) 50 | 51 | describe('expect(array).toNotMatch', () => { 52 | it('does not throw when the array does not contain an object that matches the pattern', () => { 53 | const array = [ 54 | { one: 'one' }, 55 | { two: 'two' }, 56 | { three: 'three' } 57 | ] 58 | 59 | expect(array).toNotMatch([ { one: /two/ } ]) 60 | }) 61 | }) 62 | -------------------------------------------------------------------------------- /modules/__tests__/withArgs-test.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable newline-per-chained-call*/ 2 | import expect from '../index' 3 | 4 | describe('withArgs', () => { 5 | const fn = (arg1, arg2) => { 6 | if (arg1 === 'first' && typeof arg2 === 'undefined') { 7 | throw new Error('first arg found') 8 | } 9 | if (arg1 === 'first' && arg2 === 'second') { 10 | throw new Error('both args found') 11 | } 12 | } 13 | 14 | it('invokes actual function with args', () => { 15 | expect(() => { 16 | expect(fn).withArgs('first').toThrow(/first arg found/) 17 | }).toNotThrow() 18 | }) 19 | 20 | it('can be chained', () => { 21 | expect(() => { 22 | expect(fn).withArgs('first').withArgs('second').toThrow(/both args found/) 23 | }).toNotThrow() 24 | }) 25 | 26 | it('throws when actual is not a function', () => { 27 | expect(() => { 28 | expect('not a function').withArgs('first') 29 | }).toThrow(/must be a function/) 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /modules/__tests__/withContext-test.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable newline-per-chained-call*/ 2 | import expect from '../index' 3 | 4 | describe('withContext', () => { 5 | const context = { 6 | check: true 7 | } 8 | 9 | const fn = function fn(arg) { 10 | if (this.check && typeof arg === 'undefined') 11 | throw new Error('context found') 12 | 13 | if (this.check && arg === 'good') 14 | throw new Error('context and args found') 15 | } 16 | 17 | it('calls function with context', () => { 18 | expect(() => { 19 | expect(fn).withContext(context).toThrow(/context found/) 20 | }).toNotThrow() 21 | }) 22 | 23 | it('calls function with context and args', () => { 24 | expect(() => { 25 | expect(fn).withContext(context).withArgs('good').toThrow(/context and args found/) 26 | }).toNotThrow() 27 | }) 28 | 29 | it('throws when actual is not a function', () => { 30 | expect(() => { 31 | expect('not a function').withContext(context).toThrow() 32 | }).toThrow(/must be a function/) 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /modules/assert.js: -------------------------------------------------------------------------------- 1 | import inspect from 'object-inspect' 2 | 3 | const formatString = (string, args) => { 4 | let index = 0 5 | return string.replace(/%s/g, () => inspect(args[index++])) 6 | } 7 | 8 | const assert = (condition, createMessage, ...extraArgs) => { 9 | if (condition) 10 | return 11 | 12 | const message = (typeof createMessage === 'string') 13 | ? formatString(createMessage, extraArgs) 14 | : createMessage(extraArgs) 15 | 16 | throw new Error(message) 17 | } 18 | 19 | export default assert 20 | -------------------------------------------------------------------------------- /modules/extend.js: -------------------------------------------------------------------------------- 1 | import Expectation from './Expectation' 2 | 3 | const Extensions = [] 4 | 5 | function extend(extension) { 6 | if (Extensions.indexOf(extension) === -1) { 7 | Extensions.push(extension) 8 | 9 | for (const p in extension) 10 | if (extension.hasOwnProperty(p)) 11 | Expectation.prototype[p] = extension[p] 12 | } 13 | } 14 | 15 | export default extend 16 | -------------------------------------------------------------------------------- /modules/index.js: -------------------------------------------------------------------------------- 1 | import Expectation from './Expectation' 2 | import { createSpy, spyOn, isSpy, restoreSpies } from './SpyUtils' 3 | import assert from './assert' 4 | import extend from './extend' 5 | 6 | function expect(actual) { 7 | return new Expectation(actual) 8 | } 9 | 10 | expect.createSpy = createSpy 11 | expect.spyOn = spyOn 12 | expect.isSpy = isSpy 13 | expect.restoreSpies = restoreSpies 14 | expect.assert = assert 15 | expect.extend = extend 16 | 17 | module.exports = expect 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "expect", 3 | "version": "1.20.2", 4 | "description": "Write better assertions", 5 | "repository": "mjackson/expect", 6 | "author": "Michael Jackson", 7 | "license": "MIT", 8 | "main": "lib", 9 | "files": [ 10 | "lib", 11 | "umd" 12 | ], 13 | "scripts": { 14 | "build": "node ./scripts/build.js", 15 | "prebuild-lib": "rimraf lib", 16 | "build-lib": "babel ./modules -d lib --ignore '__tests__'", 17 | "build-umd": "webpack modules/index.js umd/expect.js", 18 | "build-min": "webpack -p modules/index.js umd/expect.min.js", 19 | "lint": "eslint modules", 20 | "pretest": "npm run lint", 21 | "test": "npm run tests-only", 22 | "tests-only": "npm run jest", 23 | "jest": "jest", 24 | "posttest": "npm run karma", 25 | "karma": "karma start", 26 | "release": "node ./scripts/release.js", 27 | "prepublish": "safe-publish-latest && npm run build" 28 | }, 29 | "dependencies": { 30 | "define-properties": "~1.1.2", 31 | "has": "^1.0.1", 32 | "is-equal": "^1.5.5", 33 | "is-regex": "^1.0.3", 34 | "object-inspect": "^1.1.0", 35 | "object-keys": "^1.0.9", 36 | "tmatch": "^2.0.1" 37 | }, 38 | "devDependencies": { 39 | "babel-cli": "^6.26.0", 40 | "babel-eslint": "^7.2.3", 41 | "babel-jest": "^21.2.0", 42 | "babel-loader": "^6.4.1", 43 | "babel-preset-es2015": "^6.24.1", 44 | "eslint": "^4.11.0", 45 | "eslint-plugin-import": "^2.8.0", 46 | "gzip-size": "^3.0.0", 47 | "in-publish": "^2.0.0", 48 | "jest": "^21.2.1", 49 | "karma": "^1.7.1", 50 | "karma-browserstack-launcher": "^1.3.0", 51 | "karma-chrome-launcher": "^2.2.0", 52 | "karma-mocha": "^1.3.0", 53 | "karma-mocha-reporter": "^2.2.5", 54 | "karma-sourcemap-loader": "^0.3.7", 55 | "karma-webpack": "^1.8.1", 56 | "mocha": "^3.5.3", 57 | "pretty-bytes": "^4.0.2", 58 | "readline-sync": "^1.4.7", 59 | "rimraf": "^2.6.2", 60 | "safe-publish-latest": "^1.1.1", 61 | "webpack": "^1.15.0" 62 | }, 63 | "keywords": [ 64 | "expect", 65 | "assert", 66 | "test", 67 | "spec" 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | const readFileSync = require('fs').readFileSync 2 | const execSync = require('child_process').execSync 3 | const inInstall = require('in-publish').inInstall 4 | const prettyBytes = require('pretty-bytes') 5 | const gzipSize = require('gzip-size') 6 | 7 | if (inInstall()) 8 | process.exit(0) 9 | 10 | const exec = (command, env) => 11 | execSync(command, { stdio: 'inherit', env }) 12 | 13 | const webpackEnv = Object.assign({}, process.env, { 14 | NODE_ENV: 'production' 15 | }) 16 | 17 | exec('npm run build-lib') 18 | exec('npm run build-umd', webpackEnv) 19 | exec('npm run build-min', webpackEnv) 20 | 21 | console.log( 22 | '\ngzipped, the UMD build is ' + prettyBytes( 23 | gzipSize.sync(readFileSync('umd/expect.min.js')) 24 | ) 25 | ) 26 | -------------------------------------------------------------------------------- /scripts/release.js: -------------------------------------------------------------------------------- 1 | const resolvePath = require('path').resolve 2 | const readFileSync = require('fs').readFileSync 3 | const execSync = require('child_process').execSync 4 | const prompt = require('readline-sync').question 5 | 6 | const exec = (command) => 7 | execSync(command, { stdio: 'inherit' }) 8 | 9 | const getPackageVersion = () => 10 | JSON.parse(readFileSync(resolvePath(__dirname, '../package.json'))).version 11 | 12 | if (process.cwd() !== resolvePath(__dirname, '..')) { 13 | console.error('The release script must be run from the repo root') 14 | process.exit(1) 15 | } 16 | 17 | // Get the next version, which may be specified as a semver 18 | // version number or anything `npm version` recognizes. This 19 | // is a "pre-release" if nextVersion is premajor, preminor, 20 | // prepatch, or prerelease 21 | const nextVersion = prompt(`Next version (current version is ${getPackageVersion()})? `) 22 | const isPrerelease = nextVersion.substring(0, 3) === 'pre' 23 | 24 | // 1) Make sure the tests pass 25 | exec('npm test -- --single-run') 26 | 27 | // 2) Increment the package version in package.json 28 | // 3) Create a new commit 29 | // 4) Create a v* tag that points to that commit 30 | exec(`npm version ${nextVersion} -m "Version %s"`) 31 | 32 | // 5) Push to the same branch on the git remote (GitHub). 33 | // Do this before we publish in case anyone has pushed 34 | // since we last pulled 35 | exec('git push origin') 36 | 37 | // 6) Publish to npm. Use the "next" tag for pre-releases, 38 | // "latest" for all others 39 | exec(`npm publish --tag ${isPrerelease ? 'next' : 'latest'}`) 40 | 41 | // 7) Push the v* tag to GitHub 42 | exec(`git push -f origin v${getPackageVersion()}`) 43 | 44 | // 8) Push the "latest" tag to GitHub 45 | if (!isPrerelease) { 46 | exec('git tag -f latest') 47 | exec('git push -f origin latest') 48 | } 49 | -------------------------------------------------------------------------------- /tests.webpack.js: -------------------------------------------------------------------------------- 1 | var context = require.context('./modules', true, /-test\.js$/) 2 | context.keys().forEach(context) 3 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | 3 | module.exports = { 4 | output: { 5 | library: 'expect', 6 | libraryTarget: 'umd' 7 | }, 8 | 9 | module: { 10 | loaders: [ 11 | { test: /\.js$/, exclude: /node_modules/, loader: 'babel' } 12 | ] 13 | }, 14 | 15 | plugins: [ 16 | new webpack.DefinePlugin({ 17 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development') 18 | }) 19 | ] 20 | } 21 | --------------------------------------------------------------------------------