[Pkg-javascript-commits] [node-assertive] 01/02: Imported Upstream version 2.1.1
Ross Gammon
ross-guest at moszumanska.debian.org
Tue Dec 20 18:32:29 UTC 2016
This is an automated email from the git hooks/post-receive script.
ross-guest pushed a commit to branch master
in repository node-assertive.
commit ff7659640d30bca7e624a1866376f4344c8d5c46
Author: Ross Gammon <rossgammon at mail.dk>
Date: Sat Nov 19 19:20:42 2016 +0100
Imported Upstream version 2.1.1
---
.editorconfig | 11 +
.gitignore | 5 +
.npmrc | 1 +
.travis.yml | 16 ++
CHANGELOG.md | 27 ++
CONTRIBUTING.md | 147 ++++++++++
LICENSE | 29 ++
README.md | 214 ++++++++++++++
coffeelint.json | 122 ++++++++
lib/assertive.js | 504 +++++++++++++++++++++++++++++++++
package.json | 61 ++++
src/assertive.coffee | 390 +++++++++++++++++++++++++
test/assertive.test.coffee | 661 +++++++++++++++++++++++++++++++++++++++++++
test/mocha.opts | 4 +
test/promisified.test.coffee | 96 +++++++
15 files changed, 2288 insertions(+)
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..beffa30
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,11 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ddbce9a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+node_modules/
+npm-debug.log
+/tmp
+*.log
+*.gz
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..38f11c6
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+registry=https://registry.npmjs.org
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..b1a0247
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,16 @@
+language: node_js
+node_js:
+ - '0.10'
+ - '4'
+before_install:
+ - npm install -g npm at latest-2
+before_deploy:
+ - 'git config --global user.email "opensource at groupon.com"'
+ - 'git config --global user.name "Groupon"'
+deploy:
+ provider: script
+ script: ./node_modules/.bin/nlm release
+ skip_cleanup: true
+ 'on':
+ branch: master
+ node: '4'
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..30a6809
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,27 @@
+### 2.1.1
+
+* chore: fix who travis credits for auto-commits - **[@dbushong](https://github.com/dbushong)** [#27](https://github.com/groupon/assertive/pull/27)
+ - [`0ff64b5`](https://github.com/groupon/assertive/commit/0ff64b53cdbdcbe82fe6cf5df21a0255305c0300) **chore:** fix who travis credits for auto-commits
+
+
+### 2.1.0
+
+* feat: integrate assertive-as-promised functionality - **[@dbushong](https://github.com/dbushong)** [#26](https://github.com/groupon/assertive/pull/26)
+ - [`c9be116`](https://github.com/groupon/assertive/commit/c9be1165235696c773b50b0b80e15ec6bb143633) **feat:** integrate assertive-as-promised functionality
+
+
+### 2.0.3
+
+* Apply latest nlm generator - **[@i-tier-bot](https://github.com/i-tier-bot)** [#25](https://github.com/groupon/assertive/pull/25)
+ - [`1eb5321`](https://github.com/groupon/assertive/commit/1eb5321691d6ca51287ded93d1ac00bee5037baa) **chore:** Apply latest nlm generator
+ - [`e4939c3`](https://github.com/groupon/assertive/commit/e4939c385b9ca79c0476918b43ae0c917e929004) **fix:** Use lodash 4 method names
+
+
+2.0.2
+-----
+* Change Fn.bind to _.partial to support non-ES5 environments - @rduenasf #23
+
+2.0.1
+-----
+* add coffeelint & npub - @chkhoo #22
+* replace underscore & redux with lodash & coffee-script - @chkhoo #21
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..cfd3015
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,147 @@
+<!-- Generated by generator-nlm -->
+
+# Contributing
+
+🎉🏅 Thanks for helping us improve this project! 🙏
+
+This document outlines some of the practices we care about.
+If you have any questions or suggestions about the process,
+feel free to [open an issue](#reporting-issues)
+.
+## How Can I Contribute?
+
+### Reporting Issues
+
+If you find any mistakes in the docs or a bug in the code,
+please [open an issue in Github](https://github.com/groupon/assertive/issues/new) so we can look into it.
+You can also [create a PR](#contributing-code) fixing it yourself, or course.
+
+If you report a bug, please follow these guidelines:
+
+* Make sure the bug exists in the latest version.
+* Include instructions on how to reproduce the issue.
+ The instructions should be as minimal as possible
+ and answer the three big questions:
+ 1. What are the exact steps you took? This includes the exact versions of node, npm, and any packages involved.
+ 1. What result are you expecting?
+ 1. What is the actual result?
+
+### Improving Documentation
+
+For small documentation changes, you can use [Github's editing feature](https://help.github.com/articles/editing-files-in-another-user-s-repository/).
+The only thing to keep in mind is to prefix the commit message with "docs: ".
+The detault commit message generated by Github will lead to a failing CI build.
+
+For larger updates to the documentation
+it might be better to follow the [instructions for contributing code below](#contributing-code).
+
+### Contributing Code
+
+**Note:** If you're planning on making substantial changes,
+please [open an issue first to discuss your idea](#reporting-issues).
+Otherwise you might end up investing a lot of work
+only to discover that it conflicts with plans the maintainers might have.
+
+The general steps for creating a pull request are:
+
+1. Create a branch for your change.
+ Always start your branch from the latest `master`.
+ We often prefix the branch name with our initials, e.g. `jk-a-change`.
+1. Run `npm install` to install the dependencies.
+1. If you're fixing a bug, be sure to write a test *first*.
+ That way you can validate that the test actually catches the bug and doesn't pass.
+1. Make your changes to the code.
+ Remember to update the tests if you add new features or change behavior.
+1. Run the tests via `npm test`. This will also run style checks and other validations.
+ You might see errors about uncommitted files.
+ This is expected until you commit your changes.
+1. Once you're done, `git add .` and `git commit`.
+ Please follow the [commit message conventions](#commits--commit-messages) described below.
+1. Push your branch to Github & create a PR.
+
+#### Code Style
+
+In addition to any linting rules the project might include,
+a few general rules of thumb:
+
+* Try to match the style of the rest of the code.
+* We prefer simple code that is easy to understand over terse, expressive code.
+* We try to structure projects by semantics instead of role.
+ E.g. we'd rather have a `tree.js` module that contains tree traversal-related helpers
+ than a `helpers.js` module.
+* Actually, if you create helpers you might want to put those into a separate package.
+ That way it's easier to reuse them.
+
+#### Commits & Commit Messages
+
+Please follow the [angular commit message conventions](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#-git-commit-guidelines).
+We use an automated tool for generating releases
+that depends on the conventions to determine the next version and the content of the changelog.
+Commit messages that don't follow the conventions will cause `npm test` (and thus CI) to fail.
+
+The short summary - a commit message should look like this:
+
+```
+<type>: <subject>
+
+<body>
+
+<references>
+
+<footer>
+```
+
+Everything but the first line is optional.
+The empty lines between the different parts are required.
+
+* `<type>`: One of the following:
+ - **feat:** Introduces a new feature. This will cause the minor version to go up.
+ - **fix:** A bug fix. Causes a patch version bump.
+ - **docs:** Changes to the documentation.
+ This will also cause an increase of the patch version so that the changes show up in the npm registry.
+ - **style:** Cleanup & lint rule fixes.
+ Note that often it's better to just amend the previous commit if it introduced lint errors.
+ - **refactor:** Changes to the code structure without fixing bugs or adding features.
+ - **perf:** Performance optimizations.
+ - **test:** Fixing existing tests or adding missing ones.
+ Just like with **style**, if you add tests to a feature you just introduced in the previous commit,
+ consider keeping the tests and the feature in the same commit instead.
+ - **chore:** Changes to the project setup and tools, dependency bumps, house-keeping.
+* `<subject>`: A [good git commit message subject](http://chris.beams.io/posts/git-commit/#limit-50).
+ - Keep it brief. If possible the whole first line should have at most 50 characters.
+ - Use imperative mood. "Create" instead of "creates" or "created".
+ - No period (".") at the end.
+* `<body>`: Motivation for the change and any context required for understanding the choices made.
+ Just like the subject, it should use imperative mood.
+* `<references>`: Any URLs relevant to the PR go here.
+ Use one line per URL and prefix it with the kind of relationship, e.g. "Closes: " or "See: ".
+ If you are referencing an issue in your commit body or PR description,
+ never use `#123` but the full URL to the issue or PR you are referencing.
+ That way the reference is easy to resolve from the git history without having to "guess" the correct link
+ even if the commit got cherry-picked or merged into a different project.
+* `<footer>`: This part only applies if your commit introduces a breaking change.
+ It's important this is present, otherwise the major version will not increase.
+ See below for an example.
+
+##### Examples
+
+A feature that introduces a breaking change:
+
+```
+feat: Support --yes CLI option
+
+For existing projects all prompts can be inferred automatically.
+Manual confirmation for each default provides no value in that case.
+
+Closes https://github.com/my/project/issues/123
+
+BREAKING CHANGE: This removes support for interactive password entry.
+Users will have to login beforehand.
+```
+
+A simple bug fix:
+
+```
+fix: Handle multi-byte characters in search logic
+```
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f8fda65
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,29 @@
+Copyright (c) 2013, Groupon, Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+Neither the name of GROUPON nor the names of its contributors may be
+used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9b2dc4e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,214 @@
+assertive
+=========
+
+A terse, yet expressive assertion library
+
+Is Assertive different from other assertion libraries?
+----------------------------------------------------------------------
+
+Assertive aims to make the exact cause of breakage and intent of tests
+as fast and easy to spot as possible, with much attention paid to both
+the colour and alignment of expected and actual data, so you should be
+able to glean what you need immediately.
+
+It also tries to pre-empt false negative tests from ever happening, by
+rigorously testing for correct assertion invocation and by avoiding to
+pick names for assertions with a track record of being misinterpreted,
+not just by people reading the code, but also by programmers _writing_
+them, which can make even 100%-test-coverage code fail on behalf of it
+testing for the wrong thing.
+
+Semantic Versioning
+----------------------------------------------------------------------
+
+Assertive uses [semver](http://semver.org/) version numbers, though we
+should point out that we may tighten assertion checks in minor version
+number updates, making code that previously silently passed, now fail.
+
+Case in point: before v1.3.0, code using an assertion to verify that a
+string included the empty string, would do just that. In other words -
+nothing, since that assertion does not test anything. Now, such a test
+is flagged as a bug in your test suite that you should fix, as that is
+not asserting something about your code, but about strings in general.
+
+In Assertive, breaking changes implying a major version bump, would be
+things like argument order changes. If you really do not want improved
+coverage against this type of error with a random minor version update
+you should pin a version you like in your `package.json` rather than a
+version range.
+
+Usage
+----------------------------------------------------------------------
+
+Each assertion lets you state a condition and an optional help message
+about what semantics your test asserts, which gets presented first, if
+the assertion fails. (This is generally much more useful than messages
+along the lines of "expected true to be false", especially when it may
+be hard to tell later what the intended purpose of a test really was.)
+
+Besides failing when what each assertion guards against, they also all
+fail if you pass too few, too many or otherwise illegal parameters, as
+when a tired programmer expects "expect" to compare the two parameters
+he passed in some way and trip when they mismatch, though all it would
+ever test is that the first was truthy. To not get test suites full of
+almost-no-op tests like that, Assertive fails straight away like this:
+
+```
+Expected: true
+Actually: 10
+```
+
+There have been test suites full of no-op tests similar to this, which
+have gone undetected for months or years, giving a false sense of what
+regressions you are guarded against.
+
+You may pass any of the functions an item to be tested as a promise,
+and it will be tested after the promise is resolved. In this case, the
+test will return a promise which will be resolved or rejected as appropriate.
+A promise-aware test runner (e.g. [Mocha](https://mochajs.org/)
+version >= 1.18.0) is highly recommended.
+
+These docs show a typical invocation, and what you see when it failed:
+
+
+### `expect`
+```
+assert.expect(bool)
+# fail if bool != true
+```
+
+```
+expect '2 > 1', 2 > 1
+
+Assertion failed: 2 > 1
+```
+
+
+### `truthy`
+
+**Note**: Using `truthy` in your tests is a code smell.
+More often than not there is another, more precise test.
+Only use `truthy` when there is no way of knowing what the actual value will be.
+If `bool` is the result of a boolean operation, use `expect`.
+If `bool` is an unknown value, use `match` or `include` to narrow it down.
+
+```
+assert.truthy(bool)
+assert.truthy(explanation, bool)
+# fail if !bool
+```
+
+```
+truthy 'something was populated in the email field', form.email.value
+
+Assertion failed: something was populated in the email field
+expected undefined to be truthy
+```
+
+
+### `equal`
+```
+assert.equal(expected, actual)
+assert.equal(explanation, expected, actual)
+# fail unless actual === expected
+
+Assertion failed: decode the Epoch to 0s after Jan 1st, 1970
+Expected 86400000 to be
+equal to 0
+```
+
+### `deepEqual`
+```
+assert.deepEqual(expected, actual)
+assert.deepEqual(explanation, expected, actual)
+# fail unless _.isEqual(expected, actual)
+
+Assertion failed: ensure that all methods we tested were handled, and in the right order
+mismatch: {"methods":["GET"]} didn't
+deepEqual {"methods":["GET","POST","PUT","DELETE"]}
+```
+
+### `include`
+```
+assert.include(needle, haystack)
+assert.include(explanation, needle, haystack)
+# fail unless haystack has a substring needle, or _.include haystack, needle
+
+Assertion failed: only accept supported, case-normalized method names
+expected ["GET","POST","PUT","DELETE"]
+to include "get"
+```
+
+### `match`
+```
+assert.match(regexp, string)
+assert.match(explanation, regexp, needle)
+# fail unless regexp matches the given string, or regexp.test string
+
+Assertion failed: only affirmative pirate answers accepted
+Expected: /aye|yar+/
+to match: "nay"
+```
+
+### `throws`
+```
+err = assert.throws(functionThatThrows)
+err = assert.throws(explanation, functionThatThrows)
+# fail unless the provided functionThatThrows() calls throw
+# (on non-failures the return value is whatever was thrown)
+
+Assertion failed: ensure that bad inputs throw an error
+didn't throw an exception as expected to
+```
+
+### `hasType`
+```
+assert.hasType(<type>, value);
+assert.hasType(explanation, <type>, value);
+assert.hasType(null, value)
+assert.hasType(Date, value)
+assert.hasType(Array, value)
+assert.hasType(String, value)
+assert.hasType(RegExp, value)
+assert.hasType(Boolean, value)
+assert.hasType(Function, value)
+assert.hasType(Object, value)
+assert.hasType(NaN, value)
+assert.hasType(Number, value)
+assert.hasType(undefined, value)
+# fail unless _.isType(value) is true for given Type, or the
+# same test for a more specific type (listed above) was true
+```
+
+### `resolves`
+```
+promise = assert.resolves(promise)
+promise = assert.resolves(explanation, promise)
+# Wait for promise to resolve, then resolve if successful, reject otherwise
+# Always returns a promise, unless called with non-promise (not allowed)
+
+Assertion failed: should resolve to good stuff
+Promise was rejected despite resolves assertion:
+Timeout in 10000ms
+```
+
+### `rejects`
+```
+promiseForErr = assert.rejects(promise)
+promiseForErr = assert.rejects(explanation, promise)
+# Wait for promise to reject, resolve with error if it does, reject otherwise
+# Basically inverse of resolves(), but resolves with the error for more testing
+# Always returns a promise, unless called with non-promise (not allowed)
+
+Assertion failed: should reject after Timeout
+Promise wasn't rejected as expected to
+```
+
+### `falsey`, `notEqual`, `notDeepEqual`, `notInclude`, `notMatch`, `notThrows`, `notHasType`
+Versions of the above functions taking the same arguments, but asserting
+the opposite outcome. The assertion failure messages are just as helpful.
+
+License
+----------------------------------------------------------------------
+
+[BSD 3-Clause open source license](LICENSE)
diff --git a/coffeelint.json b/coffeelint.json
new file mode 100644
index 0000000..f7eeb24
--- /dev/null
+++ b/coffeelint.json
@@ -0,0 +1,122 @@
+{
+ "arrow_spacing": {
+ "level": "error"
+ },
+ "camel_case_classes": {
+ "level": "error"
+ },
+ "coffeescript_error": {
+ "level": "error"
+ },
+ "colon_assignment_spacing": {
+ "level": "error",
+ "spacing": {
+ "left": 0,
+ "right": 1
+ }
+ },
+ "cyclomatic_complexity": {
+ "value": 10,
+ "level": "ignore"
+ },
+ "duplicate_key": {
+ "level": "error"
+ },
+ "empty_constructor_needs_parens": {
+ "level": "ignore"
+ },
+ "ensure_comprehensions": {
+ "level": "ignore"
+ },
+ "indentation": {
+ "value": 2,
+ "level": "error"
+ },
+ "line_endings": {
+ "level": "error",
+ "value": "unix"
+ },
+ "max_line_length": {
+ "value": 80,
+ "level": "ignore",
+ "limitComments": false
+ },
+ "missing_fat_arrows": {
+ "level": "ignore"
+ },
+ "newlines_after_classes": {
+ "value": 1,
+ "level": "error"
+ },
+ "no_backticks": {
+ "level": "error"
+ },
+ "no_debugger": {
+ "level": "error"
+ },
+ "no_empty_functions": {
+ "level": "ignore"
+ },
+ "no_empty_param_list": {
+ "level": "error"
+ },
+ "no_implicit_braces": {
+ "level": "ignore",
+ "strict": true
+ },
+ "no_implicit_parens": {
+ "strict": true,
+ "level": "ignore"
+ },
+ "no_interpolation_in_single_quotes": {
+ "level": "error"
+ },
+ "no_plusplus": {
+ "level": "ignore"
+ },
+ "no_stand_alone_at": {
+ "level": "error"
+ },
+ "no_tabs": {
+ "level": "error"
+ },
+ "no_throwing_strings": {
+ "level": "ignore"
+ },
+ "no_trailing_semicolons": {
+ "level": "error"
+ },
+ "no_trailing_whitespace": {
+ "level": "error",
+ "allowed_in_comments": false,
+ "allowed_in_empty_lines": false
+ },
+ "no_unnecessary_double_quotes": {
+ "level": "error"
+ },
+ "no_unnecessary_fat_arrows": {
+ "level": "error"
+ },
+ "non_empty_constructor_needs_parens": {
+ "level": "ignore"
+ },
+ "prefer_english_operator": {
+ "level": "ignore",
+ "doubleNotLevel": "ignore"
+ },
+ "space_operators": {
+ "level": "error"
+ },
+ "spacing_after_comma": {
+ "level": "error"
+ },
+ "transform_messes_up_line_numbers": {
+ "level": "ignore"
+ },
+ "use_strict": {
+ "module": "coffeelint-use-strict",
+ "level": "error",
+ "allowGlobal": true,
+ "requireGlobal": true
+ }
+}
diff --git a/lib/assertive.js b/lib/assertive.js
new file mode 100644
index 0000000..b7d7117
--- /dev/null
+++ b/lib/assertive.js
@@ -0,0 +1,504 @@
+'use strict';
+
+/*
+Copyright (c) 2013, Groupon, Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+Neither the name of GROUPON nor the names of its contributors may be
+used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+var _, abbreviate, asRegExp, assert, assertSync, clear, error, fn, fn1, getNameOfType, getTypeName, global, green, handleArgs, i, implodeNicely, includes, isArray, isEqual, isFunction, isNumber, isPromiseAlike, isRegExp, isString, isType, len, map, name, nameNegative, positiveAssertions, red, ref, ref1, ref2, stringify, toString, type, types,
+ indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
+ slice = [].slice,
+ hasProp = {}.hasOwnProperty;
+
+global = Function('return this')();
+
+ref1 = _ = (ref = global._) != null ? ref : require('lodash'), includes = ref1.includes, isEqual = ref1.isEqual, isString = ref1.isString, isNumber = ref1.isNumber, isRegExp = ref1.isRegExp, isArray = ref1.isArray, isFunction = ref1.isFunction, map = ref1.map;
+
+assertSync = {
+ truthy: function(bool) {
+ var explanation, name, negated, ref2;
+ ref2 = handleArgs(this, [1, 2], arguments, 'truthy'), name = ref2[0], negated = ref2[1];
+ if (arguments.length === 2) {
+ explanation = arguments[0], bool = arguments[1];
+ }
+ if (!((!!bool) ^ negated)) {
+ throw error("Expected " + (red(stringify(bool))) + " to be " + name, explanation);
+ }
+ },
+ expect: function(bool) {
+ var explanation;
+ if (arguments.length === 2) {
+ explanation = arguments[0], bool = arguments[1];
+ }
+ if (explanation) {
+ return assertSync.equal(explanation, true, bool);
+ } else {
+ return assertSync.equal(true, bool);
+ }
+ },
+ equal: function(expected, actual) {
+ var explanation, name, negated, ref2;
+ ref2 = handleArgs(this, [2, 3], arguments, 'equal'), name = ref2[0], negated = ref2[1];
+ if (arguments.length === 3) {
+ explanation = arguments[0], expected = arguments[1], actual = arguments[2];
+ }
+ if (negated) {
+ if (expected === actual) {
+ throw error('notEqual assertion expected ' + red(stringify(actual)) + ' to be exactly anything else', explanation);
+ }
+ } else if (expected !== actual) {
+ throw error("Expected: " + (green(stringify(expected))) + "\nActually: " + (red(stringify(actual))), explanation);
+ }
+ },
+ deepEqual: function(expected, actual) {
+ var explanation, message, name, negated, ref2, rightLooks, wrongLooks;
+ ref2 = handleArgs(this, [2, 3], arguments, 'deepEqual'), name = ref2[0], negated = ref2[1];
+ if (arguments.length === 3) {
+ explanation = arguments[0], expected = arguments[1], actual = arguments[2];
+ }
+ if (!(isEqual(expected, actual) ^ negated)) {
+ wrongLooks = stringify(actual);
+ if (negated) {
+ throw error("notDeepEqual assertion expected exactly anything else but\n" + (red(wrongLooks)), explanation);
+ }
+ rightLooks = stringify(expected);
+ message = wrongLooks === rightLooks ? "deepEqual " + (green(rightLooks)) + " failed on something that\nserializes to the same result (likely some function)" : "Expected: " + (green(rightLooks)) + "\nActually: " + (red(wrongLooks));
+ throw error(message, explanation);
+ }
+ },
+ include: function(needle, haystack) {
+ var contained, explanation, message, name, negated, problem, ref2, verb, what;
+ ref2 = handleArgs(this, [2, 3], arguments, 'include'), name = ref2[0], negated = ref2[1];
+ if (arguments.length === 3) {
+ explanation = arguments[0], needle = arguments[1], haystack = arguments[2];
+ }
+ if (isString(haystack)) {
+ if (needle === '') {
+ what = negated ? 'always-failing test' : 'no-op test';
+ throw error(what + " detected: all strings contain the empty string!");
+ }
+ if (!(isString(needle) || isNumber(needle) || isRegExp(needle))) {
+ problem = 'needs a RegExp/String/Number needle for a String haystack';
+ throw new TypeError(name + " " + problem + "; you used:\n" + name + " " + (green(stringify(haystack))) + ", " + (red(stringify(needle))));
+ }
+ } else if (!isArray(haystack)) {
+ needle = stringify(needle);
+ throw new TypeError(name + " takes a String or Array haystack; you used:\n" + name + " " + (red(stringify(haystack))) + ", " + needle);
+ }
+ if (isString(haystack)) {
+ if (isRegExp(needle)) {
+ contained = haystack.match(needle);
+ } else {
+ contained = haystack.indexOf(needle) !== -1;
+ }
+ } else {
+ contained = includes(haystack, needle);
+ }
+ verb = isRegExp(needle) ? 'match' : 'include';
+ if (negated) {
+ if (contained) {
+ message = "notInclude expected needle not to be found in haystack\n- needle: " + (stringify(needle)) + "\nhaystack: " + (abbreviate('', haystack));
+ if (isString(haystack) && isRegExp(needle)) {
+ message += ', but found:\n';
+ if (needle.global) {
+ message += contained.map(function(s) {
+ return "* " + (red(stringify(s)));
+ }).join('\n');
+ } else {
+ message += "* " + (red(stringify(contained[0])));
+ }
+ }
+ throw error(message, explanation);
+ }
+ } else if (!contained) {
+ throw error(name + " expected needle to be found in haystack\n- needle: " + (stringify(needle)) + "\nhaystack: " + (abbreviate('', haystack)), explanation);
+ }
+ },
+ match: function(regexp, string) {
+ var called, count, explanation, matched, message, name, negated, oops, re, ref2;
+ ref2 = handleArgs(this, [2, 3], arguments, 'match'), name = ref2[0], negated = ref2[1];
+ if (arguments.length === 3) {
+ explanation = arguments[0], regexp = arguments[1], string = arguments[2];
+ }
+ if (!((re = isRegExp(regexp)) && isString(string))) {
+ string = abbreviate('string', string);
+ called = name + " " + (stringify(regexp)) + ", " + (red(string));
+ if (!re) {
+ oops = 'regexp arg is not a RegExp';
+ } else {
+ oops = 'string arg is not a String';
+ }
+ throw new TypeError(name + ": " + oops + "; you used:\n" + called);
+ }
+ matched = regexp.test(string);
+ if (negated) {
+ if (!matched) {
+ return;
+ }
+ message = "Expected: " + (stringify(regexp)) + "\nnot to match: " + (red(abbreviate('string', string)));
+ if (regexp.global) {
+ count = string.match(regexp).length;
+ message += "\nMatches: " + (red(count));
+ }
+ throw error(message, explanation);
+ }
+ if (matched) {
+ return;
+ }
+ throw error("Expected: " + (stringify(regexp)) + "\nto match: " + (red(abbreviate('string', string))), explanation);
+ },
+ throws: function(fn) {
+ var err, error1, explanation, name, negated, ref2;
+ ref2 = handleArgs(this, [1, 2], arguments, 'throws'), name = ref2[0], negated = ref2[1];
+ if (arguments.length === 2) {
+ explanation = arguments[0], fn = arguments[1];
+ }
+ if (typeof explanation === 'function') {
+ fn = explanation;
+ explanation = void 0;
+ }
+ if (typeof fn !== 'function') {
+ throw error(name + " expects " + (green('a function')) + " but got " + (red(fn)));
+ }
+ try {
+ fn();
+ } catch (error1) {
+ err = error1;
+ if (negated) {
+ throw error("Threw an exception despite " + name + " assertion:\n" + err.message, explanation);
+ }
+ return err;
+ }
+ if (negated) {
+ return;
+ }
+ throw error("Didn't throw an exception as expected to", explanation);
+ },
+ hasType: function(expectedType, value) {
+ var badArg, explanation, message, name, negated, ref2, stringType, suggestions, toBeOrNotToBe;
+ ref2 = handleArgs(this, [2, 3], arguments, 'hasType'), name = ref2[0], negated = ref2[1];
+ if (arguments.length === 3) {
+ explanation = arguments[0], expectedType = arguments[1], value = arguments[2];
+ }
+ stringType = getNameOfType(expectedType);
+ if (indexOf.call(types, stringType) < 0) {
+ badArg = stringify(expectedType);
+ suggestions = implodeNicely(types, 'or');
+ throw new TypeError(name + ": unknown expectedType " + badArg + "; you used:\n" + name + " " + (red(badArg)) + ", " + (stringify(value)) + "\nDid you mean " + suggestions + "?");
+ }
+ if (!(stringType === getTypeName(value) ^ negated)) {
+ value = red(stringify(value));
+ toBeOrNotToBe = (negated ? 'not ' : '') + 'to be';
+ message = "Expected value " + value + " " + toBeOrNotToBe + " of type " + stringType;
+ throw error(message, explanation);
+ }
+ }
+};
+
+nameNegative = function(name) {
+ if (name === 'truthy') {
+ return 'falsey';
+ }
+ if (name === 'resolves') {
+ return 'rejects';
+ }
+ return 'not' + name.charAt().toUpperCase() + name.slice(1);
+};
+
+positiveAssertions = ['truthy', 'equal', 'deepEqual', 'include', 'match', 'throws', 'hasType'];
+
+for (i = 0, len = positiveAssertions.length; i < len; i++) {
+ name = positiveAssertions[i];
+ assertSync[nameNegative(name)] = (function(name) {
+ return function() {
+ return assertSync[name].apply('!', arguments);
+ };
+ })(name);
+}
+
+assert = {
+ resolves: function(testee) {
+ var explanation, negated, ref2;
+ ref2 = handleArgs(this, [1, 2], arguments, 'resolves'), name = ref2[0], negated = ref2[1];
+ if (arguments.length === 2) {
+ explanation = arguments[0], testee = arguments[1];
+ }
+ if (!isPromiseAlike(testee)) {
+ throw error(name + " expects " + (green('a promise')) + " but got " + (red(stringify(testee))));
+ }
+ if (name === 'rejects') {
+ return testee.then((function() {
+ throw error("Promise wasn't rejected as expected to", explanation);
+ }), function(err) {
+ return err;
+ });
+ } else {
+ return testee["catch"](function(err) {
+ var ref3;
+ throw error("Promise was rejected despite resolves assertion:\n" + ((ref3 = err != null ? err.message : void 0) != null ? ref3 : err), explanation);
+ });
+ }
+ },
+ rejects: function(testee) {
+ return assert.resolves.apply('!', arguments);
+ }
+};
+
+fn1 = function(name, fn) {
+ return assert[name] = function() {
+ var args, testee;
+ args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+ if (!args.length) {
+ return fn();
+ }
+ testee = args.pop();
+ if (isPromiseAlike(testee)) {
+ return testee.then(function(val) {
+ return fn.apply(null, slice.call(args).concat([val]));
+ });
+ } else {
+ return fn.apply(null, slice.call(args).concat([testee]));
+ }
+ };
+};
+for (name in assertSync) {
+ if (!hasProp.call(assertSync, name)) continue;
+ fn = assertSync[name];
+ fn1(name, fn);
+}
+
+types = ['null', 'Date', 'Array', 'String', 'RegExp', 'Boolean', 'Function', 'Object', 'NaN', 'Number', 'undefined'];
+
+isType = function(value, typeName) {
+ if (typeName === 'Date') {
+ return _.isDate(value) && !_.isNaN(+value);
+ }
+ return _['is' + typeName.charAt(0).toUpperCase() + typeName.slice(1)](value);
+};
+
+getTypeName = function(value) {
+ return _.find(types, _.partial(isType, value));
+};
+
+getNameOfType = function(x) {
+ switch (false) {
+ case !(x == null):
+ return "" + x;
+ case !isString(x):
+ return x;
+ case !isFunction(x):
+ return x.name;
+ case !_.isNaN(x):
+ return 'NaN';
+ default:
+ return x;
+ }
+};
+
+green = function(x) {
+ return "\x1B[32m" + x + "\x1B[39m";
+};
+
+red = function(x) {
+ return "\x1B[31m" + x + "\x1B[39m";
+};
+
+clear = '\x1b[39;49;00m';
+
+if (!(typeof process !== "undefined" && process !== null ? (ref2 = process.stdout) != null ? ref2.isTTY : void 0 : void 0)) {
+ green = red = function(x) {
+ return "" + x;
+ };
+ clear = '';
+}
+
+implodeNicely = function(list, conjunction) {
+ var first, last;
+ if (conjunction == null) {
+ conjunction = 'and';
+ }
+ first = list.slice(0, -1).join(', ');
+ last = list[list.length - 1];
+ return first + " " + conjunction + " " + last;
+};
+
+abbreviate = function(name, value, threshold) {
+ var desc, str;
+ if (threshold == null) {
+ threshold = 1024;
+ }
+ if ((str = stringify(value)).length <= threshold) {
+ return str;
+ }
+ desc = "length: " + value.length;
+ if (isArray(value)) {
+ desc += "; " + str.length + " JSON encoded";
+ }
+ if (name) {
+ name += ' ';
+ }
+ return "" + name + (type(value)) + "[" + desc + "]";
+};
+
+type = function(x) {
+ if (isString(x)) {
+ return 'String';
+ }
+ if (isNumber(x)) {
+ return 'Number';
+ }
+ if (isRegExp(x)) {
+ return 'RegExp';
+ }
+ if (isArray(x)) {
+ return 'Array';
+ }
+ throw new TypeError("unsupported type: " + x);
+};
+
+asRegExp = function(re) {
+ var flags;
+ flags = '';
+ if (re.global) {
+ flags += 'g';
+ }
+ if (re.multiline) {
+ flags += 'm';
+ }
+ if (re.ignoreCase) {
+ flags += 'i';
+ }
+ return "/" + re.source + "/" + flags;
+};
+
+toString = Object.prototype.toString;
+
+stringify = function(x) {
+ var className, e, error1, json;
+ if (x == null) {
+ return "" + x;
+ }
+ if (_.isNaN(x)) {
+ return 'NaN';
+ }
+ if (isRegExp(x)) {
+ return asRegExp(x);
+ }
+ if (typeof x === 'symbol') {
+ return x.toString();
+ }
+ json = JSON.stringify(x, function(key, val) {
+ if (typeof val === 'function') {
+ return toString(val);
+ }
+ if (isRegExp(val)) {
+ return asRegExp(val);
+ }
+ return val;
+ });
+ if (typeof x !== 'object' || includes(['Object', 'Array'], className = x.constructor.name)) {
+ return json;
+ }
+ if (x instanceof Error || /Error/.test(className)) {
+ if (json === '{}') {
+ return x.stack;
+ }
+ return x.stack + "\nwith error metadata:\n" + json;
+ }
+ if (x.toString === toString) {
+ return className;
+ }
+ try {
+ return className + "[" + x + "]";
+ } catch (error1) {
+ e = error1;
+ return className;
+ }
+};
+
+error = function(message, explanation) {
+ if (explanation != null) {
+ message = "Assertion failed: " + explanation + "\n" + clear + message;
+ }
+ return new Error(message);
+};
+
+handleArgs = function(self, count, args, name, help) {
+ var actual, actualArgs, argc, functionSource, max, message, n, negated, wanted, wantedArgCount, wantedArgNames;
+ negated = false;
+ if (isString(self)) {
+ negated = true;
+ name = nameNegative(name);
+ }
+ argc = args.length;
+ if (argc === count) {
+ return [name, negated];
+ }
+ max = '';
+ if (isArray(count) && indexOf.call(count, argc) >= 0) {
+ n = count[count.length - 1];
+ if ((argc !== n) || isString(args[0])) {
+ return [name, negated];
+ }
+ max = ",\nand when called with " + n + " args, the first arg must be a docstring";
+ }
+ if (isNumber(count)) {
+ wantedArgCount = count + " argument";
+ } else {
+ wantedArgCount = count.slice(0, -1).join(', ');
+ count = count.pop();
+ wantedArgCount = wantedArgCount + " or " + count + " argument";
+ }
+ if (count !== 1) {
+ wantedArgCount += 's';
+ }
+ actualArgs = stringify([].slice.call(args)).slice(1, -1);
+ functionSource = Function.prototype.toString.call(assert[name]);
+ wantedArgNames = functionSource.match(/^function\s*[^(]*\s*\(([^)]*)/)[1];
+ if (max) {
+ wantedArgNames = "explanation, " + wantedArgNames;
+ }
+ wanted = name + "(" + wantedArgNames + ")";
+ actual = name + "(" + actualArgs + ")";
+ message = (green(wanted)) + " needs " + (wantedArgCount + max) + "\nyour usage: " + (red(actual));
+ if (typeof help === 'function') {
+ help = help();
+ }
+ throw error(message, help);
+};
+
+isPromiseAlike = function(p) {
+ return p === Object(p) && 'function' === typeof p.then;
+};
+
+if (((typeof module !== "undefined" && module !== null ? module.exports : void 0) != null)) {
+ module.exports = assert;
+} else {
+ global.assert = assert;
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..64998b1
--- /dev/null
+++ b/package.json
@@ -0,0 +1,61 @@
+{
+ "name": "assertive",
+ "version": "2.1.1",
+ "description": "Assertive is a terse yet expressive assertion library, designed and ideally suited for coffee-script",
+ "license": "BSD-3-Clause",
+ "main": "lib/assertive.js",
+ "homepage": "https://github.com/groupon/assertive",
+ "repository": {
+ "type": "git",
+ "url": "git+ssh://git@github.com/groupon/assertive"
+ },
+ "bugs": {
+ "url": "https://github.com/groupon/assertive/issues"
+ },
+ "scripts": {
+ "build": "rm -rf lib && coffee --no-header -cbo lib src",
+ "pretest": "npm run build",
+ "test": "mocha",
+ "posttest": "nlm verify",
+ "test-run": "coffeelint src test && mocha",
+ "watch": "coffee --no-header -wcbo lib src & nodemon -w lib -w test -e coffee,js,json -x \"mocha\""
+ },
+ "engines": {
+ "node": ">=0.8"
+ },
+ "nlm": {
+ "license": {
+ "files": [
+ "src"
+ ]
+ }
+ },
+ "dependencies": {
+ "lodash": "^4.6.1"
+ },
+ "devDependencies": {
+ "bluebird": "^3.3.4",
+ "coffee-script": "^1.10.0",
+ "coffeelint": "^1.10.1",
+ "coffeelint-use-strict": "0.0.1",
+ "mocha": "^2.0.0",
+ "nlm": "^2.0.0",
+ "nodemon": "^1.0.0"
+ },
+ "author": {
+ "name": "Groupon",
+ "email": "opensource at groupon.com"
+ },
+ "contributors": [
+ "David Bushong <dbushong at groupon.com>",
+ "Johan Sundström <jsundstrom at groupon.com>",
+ "Sean Massa <smassa at groupon.com>"
+ ],
+ "files": [
+ "*.js",
+ "lib"
+ ],
+ "publishConfig": {
+ "registry": "https://registry.npmjs.org"
+ }
+}
diff --git a/src/assertive.coffee b/src/assertive.coffee
new file mode 100644
index 0000000..4f350d5
--- /dev/null
+++ b/src/assertive.coffee
@@ -0,0 +1,390 @@
+'use strict'
+
+###
+Copyright (c) 2013, Groupon, Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+Neither the name of GROUPON nor the names of its contributors may be
+used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+###
+
+# eat _ off the global scope, or require it ourselves if missing
+
+global = Function('return this')()
+{includes, isEqual, isString, isNumber, isRegExp, isArray, isFunction, map} =
+_ = global._ ? require 'lodash'
+
+
+assertSync =
+ truthy: (bool) ->
+ [name, negated] = handleArgs this, [1, 2], arguments, 'truthy'
+ [explanation, bool] = arguments if arguments.length is 2
+ unless (!!bool) ^ negated
+ throw error "Expected #{red stringify bool} to be #{name}", explanation
+
+ expect: (bool) ->
+ [explanation, bool] = arguments if arguments.length is 2
+ if explanation
+ assertSync.equal explanation, true, bool
+ else
+ assertSync.equal true, bool
+
+ equal: (expected, actual) ->
+ [name, negated] = handleArgs this, [2, 3], arguments, 'equal'
+ [explanation, expected, actual] = arguments if arguments.length is 3
+ if negated
+ if expected is actual
+ throw error 'notEqual assertion expected ' + red(stringify actual) +
+ ' to be exactly anything else', explanation
+ else if expected isnt actual
+ throw error """Expected: #{green stringify expected}
+ Actually: #{red stringify actual}""", explanation
+
+ deepEqual: (expected, actual) ->
+ [name, negated] = handleArgs this, [2, 3], arguments, 'deepEqual'
+ [explanation, expected, actual] = arguments if arguments.length is 3
+ unless isEqual(expected, actual) ^ negated
+ wrongLooks = stringify actual
+ if negated
+ throw error """notDeepEqual assertion expected exactly anything else but
+ #{red(wrongLooks)}""", explanation
+
+ rightLooks = stringify expected
+ message = if wrongLooks is rightLooks
+ """deepEqual #{green rightLooks} failed on something that
+ serializes to the same result (likely some function)"""
+ else
+ """Expected: #{green rightLooks}
+ Actually: #{red wrongLooks}"""
+ throw error message, explanation
+
+ include: (needle, haystack) ->
+ [name, negated] = handleArgs this, [2, 3], arguments, 'include'
+ [explanation, needle, haystack] = arguments if arguments.length is 3
+ if isString haystack
+ if needle is ''
+ what = if negated then 'always-failing test' else 'no-op test'
+ throw error "#{what} detected: all strings contain the empty string!"
+ unless isString(needle) or isNumber(needle) or isRegExp(needle)
+ problem = 'needs a RegExp/String/Number needle for a String haystack'
+ throw new TypeError """#{name} #{problem}; you used:
+ #{name} #{green stringify haystack}, #{red stringify needle}"""
+ else unless isArray haystack
+ needle = stringify needle
+ throw new TypeError """#{name} takes a String or Array haystack; you used:
+ #{name} #{red stringify haystack}, #{needle}"""
+
+ if isString haystack
+ if isRegExp needle
+ contained = haystack.match needle
+ else
+ contained = haystack.indexOf(needle) isnt -1
+ else
+ contained = includes haystack, needle
+
+ verb = if isRegExp needle then 'match' else 'include'
+ if negated
+ if contained
+ message = """notInclude expected needle not to be found in haystack
+ - needle: #{stringify needle}
+ haystack: #{abbreviate '', haystack}"""
+ if isString(haystack) and isRegExp(needle)
+ message += ', but found:\n'
+ if needle.global
+ message += contained.map((s) -> "* #{red stringify s}").join '\n'
+ else
+ message += "* #{red stringify contained[0]}"
+ throw error message, explanation
+ else if not contained
+ throw error """#{name} expected needle to be found in haystack
+ - needle: #{stringify needle}
+ haystack: #{abbreviate '', haystack}""", explanation
+
+ match: (regexp, string) ->
+ [name, negated] = handleArgs this, [2, 3], arguments, 'match'
+ [explanation, regexp, string] = arguments if arguments.length is 3
+ unless (re = isRegExp regexp) and isString string
+ string = abbreviate 'string', string
+ called = "#{name} #{stringify regexp}, #{red string}"
+ if not re
+ oops = 'regexp arg is not a RegExp'
+ else
+ oops = 'string arg is not a String'
+ throw new TypeError "#{name}: #{oops}; you used:\n#{called}"
+
+ matched = regexp.test string
+ if negated
+ return unless matched
+ message = """Expected: #{stringify regexp}
+ not to match: #{red abbreviate 'string', string}"""
+ if regexp.global
+ count = string.match(regexp).length
+ message += "\nMatches: #{red count}"
+ throw error message, explanation
+ return if matched
+ throw error """Expected: #{stringify regexp}
+ to match: #{red abbreviate 'string', string}""", explanation
+
+
+ throws: (fn) ->
+ [name, negated] = handleArgs this, [1, 2], arguments, 'throws'
+ [explanation, fn] = arguments if arguments.length is 2
+ if typeof explanation is 'function'
+ fn = explanation
+ explanation = undefined
+ unless typeof fn is 'function'
+ throw error "#{name} expects #{green 'a function'} but got #{red fn}"
+
+ try
+ fn()
+ catch err
+ if negated
+ throw error """Threw an exception despite #{name} assertion:
+ #{err.message}""", explanation
+ return err
+
+ return if negated
+ throw error "Didn't throw an exception as expected to", explanation
+
+ hasType: (expectedType, value) ->
+ [name, negated] = handleArgs this, [2, 3], arguments, 'hasType'
+ [explanation, expectedType, value] = arguments if arguments.length is 3
+
+ stringType = getNameOfType expectedType
+ unless stringType in types
+ badArg = stringify expectedType
+ suggestions = implodeNicely types, 'or'
+ throw new TypeError """#{name}: unknown expectedType #{badArg}; you used:
+ #{name} #{red badArg}, #{stringify value}
+ Did you mean #{suggestions}?"""
+
+ unless stringType is getTypeName(value) ^ negated
+ value = red stringify value
+ toBeOrNotToBe = (if negated then 'not ' else '') + 'to be'
+ message = "Expected value #{value} #{toBeOrNotToBe} of type #{stringType}"
+ throw error message, explanation
+
+
+nameNegative = (name) ->
+ return 'falsey' if name is 'truthy'
+ return 'rejects' if name is 'resolves'
+ 'not' + name.charAt().toUpperCase() + name.slice 1
+
+# produce negatived versions of all the common assertion functions
+positiveAssertions = [
+ 'truthy'
+ 'equal'
+ 'deepEqual'
+ 'include'
+ 'match'
+ 'throws'
+ 'hasType'
+]
+for name in positiveAssertions
+ assertSync[nameNegative name] = do (name) -> ->
+ assertSync[name].apply '!', arguments
+
+# promise-specific tests
+assert =
+ resolves: (testee) ->
+ [name, negated] = handleArgs this, [1, 2], arguments, 'resolves'
+ [explanation, testee] = arguments if arguments.length is 2
+
+ unless isPromiseAlike testee
+ throw error(
+ "#{name} expects #{green 'a promise'} but got #{red stringify testee}"
+ )
+
+ if name is 'rejects'
+ testee.then(
+ (-> throw error "Promise wasn't rejected as expected to", explanation),
+ (err) -> err
+ )
+ else
+ testee.catch (err) ->
+ throw error """Promise was rejected despite resolves assertion:
+ #{err?.message ? err}""", explanation
+
+ rejects: (testee) -> assert.resolves.apply '!', arguments
+
+# union of promise-specific and promise-aware wrapped synchronous tests
+for own name, fn of assertSync
+ do (name, fn) ->
+ assert[name] = (args...) ->
+ return fn() unless args.length
+ testee = args.pop()
+ if isPromiseAlike testee
+ testee.then (val) -> fn args..., val
+ else
+ fn args..., testee
+
+# listing the most specific types first lets us iterate in order and verify that
+# the expected type was the first match
+types = [
+ 'null'
+ 'Date'
+ 'Array'
+ 'String'
+ 'RegExp'
+ 'Boolean'
+ 'Function'
+ 'Object'
+ 'NaN'
+ 'Number'
+ 'undefined'
+]
+
+isType = (value, typeName) ->
+ return _.isDate(value) and not _.isNaN(+value) if typeName is 'Date'
+ _['is' + typeName.charAt(0).toUpperCase() + typeName.slice(1)] value
+
+# gets the name of the type that value is an incarnation of
+getTypeName = (value) ->
+ _.find types, _.partial isType, value
+
+# translates any argument we were meant to interpret as a type, into its name
+getNameOfType = (x) ->
+ switch
+ when not x? then "#{x}" # null / undefined
+ when isString x then x
+ when isFunction x then x.name
+ when _.isNaN x then 'NaN'
+ else x
+
+green = (x) -> "\x1B[32m#{ x }\x1B[39m"
+red = (x) -> "\x1B[31m#{ x }\x1B[39m"
+clear = '\x1b[39;49;00m'
+
+unless process?.stdout?.isTTY
+ green = red = (x) -> "#{x}"
+ clear = ''
+
+implodeNicely = (list, conjunction = 'and') ->
+ first = list.slice(0, -1).join(', ')
+ last = list[list.length - 1]
+ "#{first} #{conjunction} #{last}"
+
+abbreviate = (name, value, threshold = 1024) ->
+ return str if (str = stringify value).length <= threshold
+ desc = "length: #{value.length}"
+ desc += "; #{str.length} JSON encoded" if isArray value
+ name += ' ' if name
+ "#{name}#{type value}[#{desc}]"
+
+type = (x) ->
+ return 'String' if isString x
+ return 'Number' if isNumber x
+ return 'RegExp' if isRegExp x
+ return 'Array' if isArray x
+ throw new TypeError "unsupported type: #{x}"
+
+asRegExp = (re) ->
+ flags = ''
+ flags += 'g' if re.global
+ flags += 'm' if re.multiline
+ flags += 'i' if re.ignoreCase
+ "/#{re.source}/#{flags}"
+
+toString = Object::toString
+
+stringify = (x) ->
+ return "#{x}" unless x?
+ return 'NaN' if _.isNaN x
+ return asRegExp x if isRegExp x
+ return x.toString() if typeof x is 'symbol'
+ json = JSON.stringify x, (key, val) ->
+ return toString val if typeof val is 'function'
+ return asRegExp val if isRegExp val
+ return val
+ if typeof x isnt 'object' \
+ or includes ['Object', 'Array'], className = x.constructor.name
+ return json
+
+ if x instanceof Error or /Error/.test className
+ return x.stack if json is '{}'
+ return "#{x.stack}\nwith error metadata:\n#{json}"
+ return className if x.toString is toString
+ try
+ return "#{className}[#{x}]"
+ catch e
+ return className
+
+error = (message, explanation) ->
+ if explanation?
+ message = "Assertion failed: #{explanation}\n#{clear}#{message}"
+ new Error message
+
+# assert that the function got `count` args (if an integer), one of the number
+# of args (if an array of legal counts), and if it was an array and the count
+# was equal to the last option (fully populated), that the first arg is a String
+# (that test's semantic explanation)
+handleArgs = (self, count, args, name, help) ->
+ negated = false
+ if isString self
+ negated = true
+ name = nameNegative name
+
+ argc = args.length
+ return [name, negated] if argc is count
+
+ max = ''
+ if isArray(count) and argc in count
+ n = count[count.length - 1]
+ return [name, negated] if (argc isnt n) or isString args[0]
+ max = """,
+ and when called with #{n} args, the first arg must be a docstring"""
+
+ if isNumber count
+ wantedArgCount = "#{count} argument"
+ else
+ wantedArgCount = count.slice(0, -1).join(', ')
+ count = count.pop()
+ wantedArgCount = "#{wantedArgCount} or #{count} argument"
+ wantedArgCount += 's' unless count is 1
+
+ actualArgs = stringify([].slice.call args).slice 1, -1
+
+ functionSource = Function::toString.call assert[name]
+ wantedArgNames = functionSource.match(/^function\s*[^(]*\s*\(([^)]*)/)[1]
+ wantedArgNames = "explanation, #{wantedArgNames}" if max
+
+ wanted = "#{name}(#{wantedArgNames})"
+ actual = "#{name}(#{actualArgs})"
+ message = """#{green wanted} needs #{wantedArgCount + max}
+ your usage: #{red actual}"""
+
+ help = help() if typeof help is 'function'
+ throw error message, help
+
+# borrowed from Q
+isPromiseAlike = (p) -> p is Object(p) and 'function' is typeof p.then
+
+# export as a module to node - or to the global scope, if not
+if (module?.exports?)
+ module.exports = assert
+else
+ global.assert = assert
diff --git a/test/assertive.test.coffee b/test/assertive.test.coffee
new file mode 100644
index 0000000..17d9047
--- /dev/null
+++ b/test/assertive.test.coffee
@@ -0,0 +1,661 @@
+'use strict'
+
+Promise = require 'bluebird'
+
+{ truthy, falsey
+, expect, notExpect
+, equal, notEqual
+, deepEqual, notDeepEqual
+, include, notInclude
+, match, notMatch
+, throws, notThrows
+, hasType, notHasType
+, resolves, rejects
+} = require '../lib/assertive'
+
+describe 'throws', ->
+ it 'errors out when you provide too few or too many args', ->
+ throws -> throws()
+ throws -> throws(1, 2, 3)
+
+ it 'notes that arg 1 must be a string when called with 2 args', ->
+ err = throws -> throws 1, 'description in wrong arg'
+ include 'called with 2 args, the first arg must be a docstring', err.message
+
+ it 'errors out unless you pass a function to execute', ->
+ throws -> throws(68000)
+ throws -> throws('more awesomeness', 68020)
+
+ it 'returns the exception that was thrown by the provided function', ->
+ exception = new Error 68040
+ equal throws(-> throw exception), exception
+ equal throws(-> throw 'we suck'), 'we suck'
+
+ it 'includes your helpful explanation, when provided', ->
+ explanation = 'No error was thrown - this is a problem'
+ err = throws -> throws explanation, ->
+ include explanation, err.message
+
+
+describe 'notThrows', ->
+ it 'errors out when you provide too few or too many args', ->
+ throws -> notThrows()
+ throws -> notThrows(1, 2, 3)
+
+ it 'notes that arg 1 must be a string when called with 2 args', ->
+ err = throws -> notThrows 1, 'description in wrong arg'
+ include 'called with 2 args, the first arg must be a docstring', err.message
+
+ it 'errors out unless you pass a function to execute', ->
+ throws -> notThrows(68000)
+ throws -> notThrows('more awesomeness', 68020)
+
+ it 'captures and shows exceptions thrown by the provided function', ->
+ thrown = new Error 68040
+ caught = throws -> notThrows -> throw thrown
+ truthy 'the exception thrown was caught and a new exception thrown', caught
+ include 'Threw an exception despite notThrows assertion:', caught.message
+ include 'assertion:\n68040', caught.message
+
+ it 'includes your helpful explanation, when provided', ->
+ explanation = 'No error was thrown - this is a problem'
+ err = throws -> notThrows explanation, -> throw new Error 'aiee!'
+ include explanation, err.message
+
+nonTrueOutcome = (fn, outcome) ->
+ ->
+ outcome -> fn 1
+ outcome -> fn {}
+ outcome -> fn []
+ outcome -> fn '!'
+ outcome -> fn 0
+ outcome -> fn ''
+ outcome -> fn null
+ outcome -> fn false
+ outcome -> fn undefined
+
+truthyOutcome = (fn, outcome) ->
+ ->
+ outcome -> fn 1
+ outcome -> fn {}
+ outcome -> fn []
+ outcome -> fn '!'
+ outcome -> fn true
+
+falseyOutcome = (fn, outcome) ->
+ ->
+ outcome -> fn 0
+ outcome -> fn ''
+ outcome -> fn null
+ outcome -> fn false
+ outcome -> fn undefined
+
+truthyIsNoOp = (fn) -> truthyOutcome fn, notThrows
+falseyIsNoOp = (fn) -> falseyOutcome fn, notThrows
+truthyThrows = (fn) -> truthyOutcome fn, throws
+falseyThrows = (fn) -> falseyOutcome fn, throws
+nonTrueThrows = (fn) -> nonTrueOutcome fn, throws
+
+describe 'truthy', ->
+ it 'errors out when you provide too few or too many args', ->
+ throws -> truthy()
+ throws -> truthy('description', true, 3)
+
+ it 'notes that arg 1 must be a string when called with 2 args', ->
+ err = throws -> truthy true, 'description in wrong arg'
+ include 'called with 2 args, the first arg must be a docstring', err.message
+
+ it "doesn't do anything when passed a truthy value", -> truthyIsNoOp truthy
+
+ it 'errors out when passed a falsey value', falseyThrows truthy
+
+ it 'includes your helpful explanation, when provided', ->
+ explanation = 'Given falsum, we can derive anything, which is awesome!'
+ err = throws -> truthy explanation, false
+ include explanation, err.message
+
+
+describe 'falsey', ->
+ it 'errors out when you provide too few or too many args', ->
+ throws -> falsey()
+ throws -> falsey('description', false, 3)
+
+ it 'notes that arg 1 must be a string when called with 2 args', ->
+ err = throws -> falsey false, 'description in wrong arg'
+ include 'called with 2 args, the first arg must be a docstring', err.message
+
+ it "doesn't do anything when passed a falsey value", falseyIsNoOp falsey
+
+ it 'errors out when passed a truthy value', truthyThrows falsey
+
+ it 'includes your helpful explanation, when provided', ->
+ explanation = 'falsey will confidently reject all your truths'
+ err = throws -> falsey explanation, true
+ include explanation, err.message
+
+
+describe 'expect', ->
+ it 'errors out when you provide too few or way too many args', ->
+ throws -> expect()
+ throws -> expect('desc', 1, 2)
+
+ it "doesn't do anything when passed true", ->
+ expect 'It really is true', true
+ expect 2 > 1
+
+ it 'errors out when passed anything but true', nonTrueThrows expect
+
+ it 'is not very likely to accept mistakes', ->
+ err = throws -> expect 'te' + 'st', 'test' # people try this kind of stuff
+ include /Expected: .*true/, err.message
+ include /Actually: .*"test"/, err.message
+
+
+describe 'equal', ->
+ it 'errors out when you provide too few or too many args', ->
+ throws -> equal()
+ throws -> equal(1)
+ throws -> equal('description', 1, 1, 4)
+
+ it 'notes that arg 1 must be a string when called with 3 args', ->
+ err = throws -> equal 1, 1, 'description in wrong arg'
+ include 'called with 3 args, the first arg must be a docstring', err.message
+
+ it "doesn't do anything when passed two identical values", ->
+ equal 1, 1
+ equal this, this
+ equal null, null
+ equal undefined, undefined
+
+ it 'errors out when passed two non-identical values', ->
+ throws -> equal 0, 1
+ throws -> equal {}, {}
+ throws -> equal null, undefined
+ throws -> equal false, 0
+ throws -> equal [], []
+
+ it 'includes your helpful explanation, when provided', ->
+ explanation = 'Given falsum, we can derive anything, which is awesome!'
+ err = throws -> equal explanation, 0, 1
+ include explanation, err.message
+
+ if typeof Symbol != 'undefined'
+ it 'nicely formats non-matching symbols', ->
+ err = throws -> equal Symbol('some'), Symbol('other')
+ include 'Symbol(some)', err.message
+ include 'Symbol(other)', err.message
+
+
+describe 'notEqual', ->
+ it 'errors out when you provide too few or too many args', ->
+ throws -> notEqual()
+ throws -> notEqual(1)
+ throws -> notEqual('description', 1, 2, 4)
+
+ it 'notes that arg 1 must be a string when called with 3 args', ->
+ err = throws -> notEqual 1, 2, 'description in wrong arg'
+ include 'called with 3 args, the first arg must be a docstring', err.message
+
+ it "doesn't do anything when passed two different values", ->
+ notEqual 1, 2
+ notEqual this, Object.create this
+ notEqual NaN, NaN
+ notEqual null, undefined
+
+ it 'errors out when passed two identical values', ->
+ throws -> notEqual 0, 0
+ throws -> notEqual this, this
+ throws -> notEqual null, null
+ throws -> notEqual (x = []), x
+ throws -> notEqual undefined, undefined
+ throws -> notEqual false, false
+ throws -> notEqual true, true
+
+ it 'includes your helpful explanation, when provided', ->
+ explanation = "it doesn't get more equal than identical"
+ err = throws -> notEqual explanation, Math.PI, Math.PI
+ include explanation, err.message
+
+
+describe 'deepEqual', ->
+ it 'errors out when you provide too few or too many args', ->
+ throws -> deepEqual()
+ throws -> deepEqual(1)
+ throws -> deepEqual('description', 1, 1, 4)
+
+ it 'notes that arg 1 must be a string when called with 3 args', ->
+ err = throws -> deepEqual 1, 1, 'description in wrong arg'
+ include 'called with 3 args, the first arg must be a docstring', err.message
+
+ it "doesn't do anything when passed two deepEqual values", ->
+ deepEqual 1, 1
+ deepEqual this, this
+ deepEqual null, null
+ deepEqual undefined, undefined
+ deepEqual [1], [1]
+ deepEqual { a: this }, { a: this }
+ deepEqual [null], [null]
+ deepEqual { u: undefined }, { u: undefined }
+
+ it 'errors out when passed two non-deepEqual values', ->
+ throws -> deepEqual {}, []
+ throws -> deepEqual 0, 1
+ throws -> deepEqual null, undefined
+ throws -> deepEqual false, 0
+ throws -> deepEqual [1], [2]
+ throws -> deepEqual { a: {} }, { a: [] }
+ throws -> deepEqual [null], [undefined]
+ throws -> deepEqual {}, { u: undefined }
+
+ it 'includes your helpful explanation, when provided', ->
+ explanation = 'Given falsum, we can derive anything, which is awesome!'
+ err = throws -> deepEqual explanation, [null], [undefined]
+ include explanation, err.message
+
+
+describe 'notDeepEqual', ->
+ it 'errors out when you provide too few or too many args', ->
+ throws -> notDeepEqual()
+ throws -> notDeepEqual(1)
+ throws -> notDeepEqual('description', 1, 2, 4)
+
+ it 'notes that arg 1 must be a string when called with 3 args', ->
+ err = throws -> notDeepEqual 1, 2, 'description in wrong arg'
+ include 'called with 3 args, the first arg must be a docstring', err.message
+
+ it "doesn't do anything when passed two not deepEqual values", ->
+ notDeepEqual 1, 2
+ notDeepEqual 2, []
+ notDeepEqual this, {}
+ notDeepEqual null, undefined
+ notDeepEqual String, Number
+ notDeepEqual [1], [2]
+ notDeepEqual { a: this }, { b: this }
+ notDeepEqual [null], [undefined]
+ notDeepEqual {}, { u: undefined }
+
+ it 'errors out when passed two deepEqual values', ->
+ throws -> notDeepEqual {}, {}
+ throws -> notDeepEqual 0, 0
+ throws -> notDeepEqual null, null
+ throws -> notDeepEqual false, false
+ throws -> notDeepEqual [1], [1]
+ throws -> notDeepEqual { a: {} }, { a: {} }
+ throws -> notDeepEqual [null], [null]
+ throws -> notDeepEqual [[{}]], [[{}]]
+
+ it 'includes your helpful explanation, when provided', ->
+ explanation = 'Given falsum, we can derive anything, which is awesome!'
+ err = throws -> notDeepEqual explanation, [null], [null]
+ include explanation, err.message
+
+
+describe 'include', ->
+ it 'errors out when you provide too few or too many args', ->
+ throws -> include()
+ throws -> include([1])
+ throws -> include('description', 1, [1], 4)
+
+ it 'notes that arg 1 must be a string when called with 3 args', ->
+ err = throws -> include 1, [1], 'description in wrong arg'
+ include 'called with 3 args, the first arg must be a docstring', err.message
+
+ it 'throws TypeErrors on bad needles', ->
+ saneNeedlesPlease = /needs a .* needle for a String haystack/
+ e = throws -> include undefined, 'undefined? baad'
+ match saneNeedlesPlease, e.message
+ e = throws -> include (->), 'function? worse'
+ match saneNeedlesPlease, e.message
+ truthy 'should throw TypeError', e instanceof TypeError
+
+ it 'throws an error for the no-op assertion string-contains-empty-string', ->
+ err = throws -> include '', 'all strings include the empty string'
+ expect = 'no-op test detected: all strings contain the empty string!'
+ include expect, err.message
+
+ it 'raises a helpful TypeError when you pass a bad haystack', ->
+ err = throws -> include 0, 1000
+ include 'include takes a String or Array haystack', err.message
+ truthy 'include throws a TypeError on bad args', err instanceof TypeError
+
+ it "doesn't do anything when passed an array including the value", ->
+ include 1, [1]
+ include this, [1, this, 3]
+ include null, [null, 2]
+ include undefined, [1, undefined]
+
+ it "doesn't do anything when passed a string including the value", ->
+ include 'ho', 'hey ho'
+ include 'ho', 'hoopla'
+ include 'ho', 'what ho, Jeeves?'
+
+ it "doesn't do anything when passed a string matching the RegExp", ->
+ include /h[oe]/, 'hey ho'
+ include /^ho+pla$/, 'hoopla'
+ include /jeeves/i, 'what ho, Jeeves?'
+ include /^$/, ''
+
+ it "errors out when the string passed doesn't include the needle", ->
+ throws -> include '/spelunking/', 'spelunking'
+ throws -> include 'SAY, WHAT?', 'say, what?'
+
+ it 'errors out the same way when testing for a needle in an empty array', ->
+ err = throws -> include 'not present in array', []
+ include '''include expected needle to be found in haystack
+ - needle: "not present in array"
+ haystack: []''', err.message
+
+ it 'errors out the same way when testing for a needle in an empty string', ->
+ err = throws -> include 'not present in string', ''
+ include '''include expected needle to be found in haystack
+ - needle: "not present in string"
+ haystack: ""''', err.message
+
+ it "errors out when the string passed doesn't match the RegExp", ->
+ throws -> include /SPELUNKING/, 'spelunking'
+ throws -> include /SAY, WHAT$/i, 'say, what?'
+
+ it "errors out when the array passed doesn't include the value", ->
+ throws -> include [], [{}]
+ throws -> include 1, [0]
+ throws -> include undefined, [null]
+ throws -> include null, [undefined]
+
+ it 'includes your helpful explanation, when provided', ->
+ explanation = 'Given falsum, we can derive anything, which is awesome!'
+ err = throws -> include explanation, [undefined], [null]
+ include explanation, err.message
+
+ it 'shortens larger haystacks in the assertion message', ->
+ err = throws -> include 2001, [1..2000]
+ match /^include [\s\S]*haystack: Array\[length: 2000; \d* JSON/, err.message
+
+
+describe 'notInclude', ->
+ it 'errors out when you provide too few or too many args', ->
+ throws -> notInclude()
+ throws -> notInclude(2)
+ throws -> notInclude('description', 2, [1], 4)
+
+ it 'notes that arg 1 must be a string when called with 3 args', ->
+ e = throws -> notInclude 2, [1], 'description in wrong arg'
+ include 'called with 3 args, the first arg must be a docstring', e.message
+
+ it 'throws TypeErrors on bad needles', ->
+ saneNeedlesPlease = /needs a .* needle for a String haystack/
+ e = throws -> notInclude undefined, 'undefined? baad'
+ match saneNeedlesPlease, e.message
+ e = throws -> notInclude (->), 'function? worse'
+ match saneNeedlesPlease, e.message
+ truthy 'should throw TypeError', e instanceof TypeError
+
+ it 'raises a helpful TypeError when you pass a bad haystack', ->
+ e = throws -> notInclude 0, 1000
+ include 'notInclude takes a String or Array haystack', e.message
+ truthy 'notInclude throws a TypeError on bad args', e instanceof TypeError
+
+ it 'throws an error for the bad assertion string-lacks-empty-string', ->
+ err = throws -> notInclude '', 'all strings include the empty string'
+ want = 'always-failing test detected: all strings contain the empty string!'
+ include want, err.message
+
+ it 'lets you pass an empty haystack if you want to', ->
+ notThrows -> notInclude 'Your expectations are just fine, love', []
+
+ it "doesn't do anything when passed an array not including the value", ->
+ notInclude 2, [1]
+ notInclude Object.create(this), [1, this, 3]
+ notInclude 1, [null, 2]
+ notInclude null, [1, undefined]
+
+ it 'errors out when passed a string including the value', ->
+ throws -> notInclude 'ho', 'hey ho'
+ throws -> notInclude 'ho', 'hoopla'
+ throws -> notInclude 'ho', 'what ho, Jeeves?'
+
+ it 'errors out when passed a string matching the RegExp', ->
+ throws -> notInclude /h[oe]/, 'hey ho'
+ throws -> notInclude /^ho+pla$/, 'hoopla'
+ throws -> notInclude /jeeves/i, 'what ho, Jeeves?'
+
+ it "doesn't do anything when the string passed doesn't include the needle", ->
+ notInclude '/spelunking/', 'spelunking'
+ notInclude 'SAY, WHAT?', 'say, what?'
+
+ it "doesn't do anything when the string passed doesn't match the RegExp", ->
+ notInclude /SPELUNKING/, 'spelunking'
+ notInclude /SAY, WHAT$/i, 'say, what?'
+
+ it 'errors out when the array passed does include the value', ->
+ throws -> notInclude 0, [0]
+ throws -> notInclude this, [this]
+ throws -> notInclude null, [null]
+ throws -> notInclude undefined, [undefined]
+
+ it 'includes your helpful explanation, when provided', ->
+ explanation = 'Given falsum, we can derive anything, which is awesome!'
+ err = throws -> notInclude explanation, null, [null]
+ include explanation, err.message
+
+ it 'shortens larger haystacks in the assertion message', ->
+ err = throws -> notInclude 2000, [1..2000]
+ match /notInclude[\s\S]*stack: Array\[length: 2000; \d* JSON/, err.message
+
+
+describe 'match', ->
+ it 'errors out when you provide too few or too many args', ->
+ throws -> match()
+ throws -> match(/foo/)
+ throws -> match('description', /foo/, 'foo', 'bar')
+
+ it 'notes that arg 1 must be a string when called with 3 args', ->
+ e = throws -> match /foo/, 'foo', 'description in wrong arg'
+ include 'called with 3 args, the first arg must be a docstring', e.message
+
+ it 'throws TypeErrors when called with a non-RegExp', ->
+ e = throws -> match 'foo', 'foobar'
+ match /regexp arg is not a RegExp; you used:\nmatch .*"foo"/, e.message
+ truthy 'should throw TypeError', e instanceof TypeError
+
+ it 'throws TypeErrors when called with a non-String', ->
+ e = throws -> match /X/, undefined
+ match /string arg is not a String; you used:\nmatch .*X.*undefin/, e.message
+ truthy 'should throw TypeError', e instanceof TypeError
+
+ it "doesn't do anything when passed a RegExp that the string matches", ->
+ match /^fo*/i, 'FOOBAR'
+
+ it "doesn't break when also passed a docstring", ->
+ match 'still fine and dandy', /^fo*/i, 'FOOBAR'
+
+ it 'errors out when the RegExp does not match the passed string', ->
+ e = throws -> match /wrong case/, 'WRONG CASE'
+ match /Expected: \/wrong case\/\nto match: .*"WRONG CASE"/, e.message
+
+ it 'includes your helpful explanation, when provided', ->
+ explanation = 'Given falsum, we can derive anything, which is awesome!'
+ e = throws -> match explanation, /aye/, 'nay'
+ include explanation, e.message
+
+ it 'shortens larger strings in the assertion message', ->
+ e = throws -> match /4711/, JSON.stringify [1..2000]
+ include 'string String[length: 8894]', e.message
+
+
+describe 'notMatch', ->
+ it 'errors out when you provide too few or too many args', ->
+ throws -> notMatch()
+ throws -> notMatch(/foo/)
+ throws -> notMatch('description', /foo/, 'foo', 'bar')
+
+ it 'notes that arg 1 must be a string when called with 3 args', ->
+ e = throws -> notMatch /foo/, 'foo', 'description in wrong arg'
+ include 'called with 3 args, the first arg must be a docstring', e.message
+
+ it 'throws TypeErrors when called with a non-RegExp', ->
+ e = throws -> notMatch 'bar', 'barfoo'
+ match /regexp arg is not a RegExp; you used:\nnotMatch .*"bar"/, e.message
+ truthy 'should throw TypeError', e instanceof TypeError
+
+ it 'throws TypeErrors when called with a non-String', ->
+ e = throws -> notMatch /x/, undefined
+ match /string arg is not a String; you used:\nnotMatch.*undefine/, e.message
+ truthy 'should throw TypeError', e instanceof TypeError
+
+ it "doesn't do anything when passed a RegExp not matching the string", ->
+ notMatch /^not/, 'FOOBAR'
+
+ it "doesn't break when also passed a docstring", ->
+ notMatch 'still fine and dandy', /^not/, 'FOOBAR'
+
+ it 'errors out when the RegExp matches the passed string', ->
+ e = throws -> notMatch /problem/i, 'Problems found!'
+ match /Expected: \/problem\/i\nnot to match: .*"Problems found!"/, e.message
+
+ it 'shows how many matches we did find when passed a global RegExp', ->
+ e = throws -> notMatch /yo+/gi, 'Yo, yo, yo, yooo, man!'
+ match /Matches: .*4\b/, e.message
+
+ it 'includes your helpful explanation, when provided', ->
+ explanation = 'Given falsum, we can derive anything, which is awesome!'
+ e = throws -> notMatch explanation, /woo/, 'woo!'
+ include explanation, e.message
+
+ it 'shortens larger strings in the assertion message', ->
+ e = throws -> notMatch /200/, JSON.stringify [1..2000]
+ include 'string String[length: 8894]', e.message
+
+
+describe 'hasType', ->
+ it 'errors out when you provide too few, too many, or incorrect args', ->
+ throws -> hasType()
+ throws -> hasType String
+ throws -> hasType 42, 42
+ throws -> hasType 'explanation', String, 'some thing', 'extra'
+
+ it 'explains correct types for wrong ones', ->
+ e = throws -> hasType 42, 42
+ match /unknown expectedType/, e.message
+ truthy 'should throw TypeError', e instanceof TypeError
+
+ it 'recognizes Strings', ->
+ hasType String, '42'
+ throws -> hasType String, 42
+
+ it 'recognizes Numbers', ->
+ hasType '42 is a Number', Number, 42
+ throws -> hasType Number, '42'
+
+ it 'recognizes NaN', ->
+ hasType NaN, NaN
+ throws 'Number tested as being NaN', -> hasType NaN, 68881
+
+ it 'recognizes RegExps', ->
+ hasType RegExp, /howdy/
+ throws -> hasType RegExp, '/howdy/'
+
+ it 'recognizes Arrays', ->
+ hasType Array, [1, 2, 3]
+ throws -> hasType Array, '[1, 2, 3]'
+
+ it 'recognizes Functions', ->
+ hasType Function, ->
+ throws -> hasType Function, 'function () { }'
+
+ it 'recognizes Objects', ->
+ hasType Object, foo: 42
+ throws 'Array tested as being an Object', -> hasType Object, [1, 2, 3]
+
+ it 'recognizes Dates', ->
+ hasType Date, new Date()
+ invalidDate = new Date 'Invalid Date'
+ throws 'Invalid Date tested as being a Date', -> hasType Date, invalidDate
+ throws 'Object tested as being a Date', -> hasType Date, getTime: -> 0
+
+ it 'recognizes null', ->
+ hasType null, null
+ throws 'Object tested as being null', -> hasType null, {}
+
+ it 'recognizes undefined', ->
+ hasType undefined, undefined
+ throws 'Object tested as being undefined', -> hasType undefined, {}
+
+
+describe 'notHasType', ->
+ it 'errors out when you provide too few, too many, or incorrect args', ->
+ throws -> notHasType()
+ throws -> notHasType String
+ throws -> notHasType 42, 42
+ throws -> notHasType 'explanation', String, 'some thing', 'extra'
+
+ it 'explains correct types for wrong ones', ->
+ e = throws -> notHasType 42, 42
+ match /unknown expectedType/, e.message
+ truthy 'should throw TypeError', e instanceof TypeError
+
+ it 'phrases error messages correctly', ->
+ e = throws -> notHasType Object, {}
+ match /not to be of type Object/, e.message
+
+ it 'recognizes non-Strings', ->
+ notHasType String, 42
+ throws -> notHasType String, '42'
+
+ it 'recognizes non-Numbers', ->
+ notHasType Number, '42'
+ throws -> notHasType Number, 42
+
+ it 'recognizes non-NaNs', ->
+ notHasType NaN, 6502
+ throws 'NaN tested as not being NaN', -> notHasType NaN, NaN
+
+ it 'recognizes non-RegExps', ->
+ notHasType RegExp, '/howdy/'
+ throws -> notHasType RegExp, /howdy/
+
+ it 'recognizes non-Arrays', ->
+ notHasType Array, '[1, 2, 3]'
+ throws -> notHasType Array, [1, 2, 3]
+
+ it 'recognizes non-Functions', ->
+ notHasType Function, 'function () { }'
+ throws -> notHasType Function, ->
+
+ it 'recognizes non-Objects', ->
+ notHasType Object, [1, 2, 3]
+ throws -> notHasType Object, foo: 42
+
+ it 'recognizes non-Dates', ->
+ notHasType Date, getTime: -> 0
+ notHasType Date, new Date 'Invalid Date'
+ throws 'Date tested as not being a Date', -> notHasType Date, new Date
+
+ it 'recognizes not-null', ->
+ notHasType null, undefined
+ throws 'null tested as not being null', -> notHasType null, null
+
+ it 'recognizes not-undefined', ->
+ notHasType undefined, null
+ throws -> notHasType undefined, undefined
+
+describe 'rejects', ->
+ it 'errors synchronously on non-promise', ->
+ match /^rejects expects/, throws(-> rejects 42).message
+
+ it 'resolves for a rejected promise', ->
+ equal 'kittens', rejects Promise.reject 'kittens'
+
+ it 'rejects a resolved promise', ->
+ equal "Promise wasn't rejected as expected to",
+ rejects(rejects Promise.resolve 42).get('message')
+
+describe 'resolves', ->
+ it 'errors synchronously on non-promise', ->
+ match /^resolves expects/, throws(-> resolves {}).message
+
+ it 'rejects for a rejected promise', ->
+ include 'Promise was rejected despite resolves assertion:\n42',
+ rejects(resolves Promise.reject new Error 42).get('message')
+
+ it 'resolves for a resolved promise', ->
+ equal 'kittens', resolves Promise.resolve 'kittens'
diff --git a/test/mocha.opts b/test/mocha.opts
new file mode 100644
index 0000000..69b4f0c
--- /dev/null
+++ b/test/mocha.opts
@@ -0,0 +1,4 @@
+--check-leaks
+--colors
+--compilers test.coffee:coffee-script/register
+--recursive
diff --git a/test/promisified.test.coffee b/test/promisified.test.coffee
new file mode 100644
index 0000000..b81ce80
--- /dev/null
+++ b/test/promisified.test.coffee
@@ -0,0 +1,96 @@
+# test the auto-promise-awarified versions of common tests
+
+Promise = require 'bluebird'
+assert = require '../lib/assertive'
+
+syncFuncs =
+ truthy:
+ pass:
+ args: [true], descr: 'a truthy promise resolution'
+ fail:
+ args: [false]
+ descr: 'a falsey promise resolution'
+ explain: 'resolves to true'
+
+ equal:
+ pass: args: [5, 5], descr: 'an equal promise resolution'
+ fail:
+ args: [5, 6]
+ descr: 'an inequal promise resolution'
+ explain: '5 is 5'
+
+ deepEqual:
+ pass:
+ args: [['a', 'b'], ['a', 'b']]
+ descr: 'a deep equal promise resolution'
+ fail:
+ args: [['a', 'b'], 'x']
+ descr: 'a deep inequal promise resolution'
+ explain: 'a,b is a,b'
+
+ include:
+ pass:
+ args: ['x', 'fox']
+ descr: 'needle inclusion in haystack promise resolution'
+ fail:
+ args: ['x', 'dog']
+ descr: 'needle exclusion from haystack promise resolution'
+ explain: 'x in word'
+
+ match:
+ pass: args: [/x/, 'fox'], descr: 'match /x/'
+ fail:
+ args: [/x/, 'dog']
+ descr: 'needle exclusion from haystack promise resolution'
+ explain: '/x/ matches word'
+
+ throws:
+ pass:
+ args: [-> throw 'foo']
+ descr: 'a promise for an excepting function'
+ fail:
+ args: [ -> 'foo' ]
+ descr: 'a promise for a non-excepting function'
+ explain: 'function throws an exception'
+
+ notThrows:
+ pass:
+ args: [-> 42]
+ descr: 'a non-excepting function'
+ fail:
+ args: [ -> throw 'foo' ]
+ descr: 'a promise for an excepting function'
+ explain: 'function does not throw an exception'
+
+ hasType:
+ pass:
+ args: [Boolean, true]
+ descr: 'matched type on promise resolution'
+ fail:
+ args: [Boolean, 'true']
+ descr: 'mismatched type on promise resolution'
+ explain: 'result is a boolean'
+
+describe 'promise-aware functionality', ->
+ for name, bits of syncFuncs
+ do (name, bits) ->
+ {pass, fail} = bits
+ for pf, {args} of bits
+ # replace last argument with promised resolution of same
+ bits[pf].pargs = args[0...args.length-1].concat(
+ [Promise.resolve(args[args.length-1])])
+
+ describe name, ->
+ it 'returns a promise when passed a promise', ->
+ assert.expect assert[name](pass.pargs...) instanceof Promise
+
+ it 'does not return a promise when not passed one', ->
+ assert.expect not (assert[name](pass.args...) instanceof Promise)
+
+ it "resolves for #{pass.descr}", ->
+ assert.resolves "#{name} should succeed", assert[name] pass.pargs...
+
+ it "rejects for #{fail.descr}", ->
+ assert.rejects "#{name} should throw",
+ assert[name] fail.explain, fail.pargs...
+ .then (err) -> assert.include fail.explain, err.message
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-assertive.git
More information about the Pkg-javascript-commits
mailing list