Commit 15cd074f authored by Daniel Shumway's avatar Daniel Shumway

initial commit with dependencies

parents
image: node
before_script:
- npm install
test:
script:
- npm test
\ No newline at end of file
node_modules/*
\ No newline at end of file
<div align="center">
<a href="https://gitlab.com/distilled/distilled/tree/develop"><img alt="Distilled logo breakdown" src="https://gitlab.com/distilled/distilled/raw/develop/icons/distilled-details.jpg" width="200px" ></a>
<a href="https://gitlab.com/distilled/distilled/tree/stable"><img alt="Distilled logo" src="https://gitlab.com/distilled/distilled/raw/develop/icons/distilled.jpg" width="200px" ></a>
<br><br>
<a href="https://nodei.co/npm/@latinfor/distilled/"><img alt="npm stats" src="https://nodei.co/npm/@latinfor/distilled.png?compact=true"></a>
<br>
<a href="https://gitlab.com/distilled/distilled/commits/stable"><img alt="build status" src="https://gitlab.com/distilled/distilled/badges/stable/build.svg" /></a>
<a href="https://badge.fury.io/js/distilled-distilled"><img src="https://badge.fury.io/js/distilled-distilled.svg" alt="npm version" height="18"></a>
<a href="https://opensource.org/licenses/mit-license.php"><img alt="MIT License" src="https://badges.frapsoft.com/os/mit/mit.png?v=103"></a>
<a href="https://gratipay.com/Distilled"><img alt="Donate" src="https://img.shields.io/gratipay/Distilled.svg?style=social&label=Donate&maxAge=2592000"></a>
</div>
# Distilled
Distilled is an aggressively elegant testing framework, inspired by ideals of simplicity, flexibility, and consistency.
<br>
## Why use Distilled?
The Javascript ecosystem is filled with testing frameworks to choose from. Distilled knows all the buzz words, so of course it's asynchronous-first and totally compatible with modern programming paradigms like Promises. It is smart and sexy and may even someday be compatible with the cool tools like Grunt.
But you don't really care about that. In actuality, Distilled is special because it closely hews to three pillars of design:
<br>
### Simplicity
Distilled's entire testing API is one method.
```js
var Suite = new Distilled();
suite.test('test label', function () {
if (condition) {
throw 'exception';
}
});
```
A radical approach to simplicity makes Distilled easy to learn and work with. Even beginning coders who are unfamiliar with testing can get started quickly and easily.
Distilled achieves enlightenment by liberally culling any unneeded and unnecessary features. Distilled is proud to ship _without_ the following features:
- An assertion library
- ``setup`` or ``teardown`` hooks
- ``ignore`` options for old tests
- test-specific timeouts
- test retry support
- test perfomance logging
- global variable detection
- A CLI runner?!?
<br>
Don't be afraid. The majority of these features are unnecessary for your workflow, and any features that you do need are simple and painless for you to re-introduce on the fly.
<br>
_Speaking of which..._
<br>
### Flexibility
Distilled is so flexible and adaptable that it's almost scandalous. Its general policy is to give users options, not
to enforce specific coding styles or paradigms. This utterly agnostic, syntax-tolerant approach means that almost anything you try will work.
<br>
#### The ``test`` method:
```js
suite.test('Accept promise', Promise.resolve()); //passes
suite.test('Accept boolean', false); //fails
suite.test('Accept nothing'); //passes
suite.test('Accept function', function () {}); //passes
suite.test('Accept function that returns boolean', function () {
return false;
}); //fails
suite.test('Accept function that returns promise', function () {
return Promise.reject();
}); //fails
suite.test('Accept function that returns function that returns promise', function () {
return function () {
return Promise.reject();
};
}); //Fails
suite.test('Accept promise that resolves to function that returns promise that resolves to boolean', Promise.resolve(function () {
return Promise.resolve(false);
})); //Fails
```
Because Distilled's ``test`` method can accept almost anything, it's easy to introduce custom hooks, wrappers, and to generally extend the heck out of it.
<br>
#### Assertions:
Distilled does not use an assertion library. Rather, it listens for exceptions.
```js
suite.test(function () { throw 'error' });
```
In practice, this means that Distilled can use _any_ assertion library that throws exceptions.
Or, if you don't want to bother with that, you can even use it with no assertion library at all!
<br>
#### Test Structure and Organization:
Typically, testing frameworks are opinionated about how tests are supposed to be structured and organized. Distilled is not.
Distilled allows you to infinitely nest and group tests as you see fit. Nested tests are _conditional_, a term which here means that children will only be executed and reported if their parents pass.
Every ``test`` call returns a new parent for you to test on.
```js
var setup = suite.test('Parent', Promise.resolve()); //Passes
var child = setup.test('child', function () {
return Promise.reject('error');
}); //Fails once the parent has finished
var subchild = setup.test('sub-child', function () {
return Promise.resolve();
}); //Is never run
```
<br>
Distilled can afford to offer this flexibility because it truly is asynchronous-first, down to its very core. In practice, this all-encompassing idea of Promise-like test chaining allows for a high degree of flexibility in deciding how your tests are structured and reported.
- Empty parent tests can be used as descriptive groups.
- If a parent test fails (say, connecting to a database) you don't need to waste time waiting for its children to fail.
- If you're building a harness runner, you can conditionally fetch new tests on the fly, reducing the necessary overhead to start running your tests.
<br>
My recommendation is to set up tests descriptively by treating them like promises. However, if you're not a really a "Promise" kind of coder, and you prefer building pyramids of code, _of course_ Distilled still has you covered.
```js
suite.test('Parent', function (parent) {
if (condition) { throw 'error'; }
parent.test('child', function () {
if (sub_condition) { throw 'error'; }
});
});
```
Just to be sassy, Distilled also exposes ``this`` as a third option:
```js
suite.test('Parent', function () {
if (condition) { throw 'error'; }
this.test('child', function () {
if (sub_condition) { throw 'error'; }
});
});
```
<br>
Don't be afraid! Wrapping your brain around and embracing the possibilities of infinitely nested tests is the key to adapting Distilled into the perfect workflow for you. With just a little imagination, you'll be building your own opinionated frameworks, spectacular assertion libraries, and innovative test runners in no time at all.
<br>
_The other key is embracing..._
<br>
### Consistency
When starting out with Distilled, feel free to stick with just the style you're immediately comfortable with. If you do though, keep in mind that the API you're using will be the same no matter what context you're in or what you're trying to do with the library.
- ``this`` will always refer to your current testing context, no matter where you are.
- You'll always have that testing context available as a parameter.
- And you'll always be able to launch a new test off of that context.
Those three principles are _never_ false, no matter where you are or what you're doing.
<br>
## Putting it all together
And now you know how Distilled works! Let's go through a simple example (via NodeJS) describing quite literally everything you need to do to get up and running right now.
In your terminal, install Distilled:
```bash
npm install --save-dev distilled-distilled
```
Make a new file for your tests:
```js
var Distilled = require('distilled-distilled');
var assert = require('assert');
var library = require('../my-library');
var suite = new Distilled();
var module = suite.test('My Library Test');
module.test('Methods: ', function (test) {
test.test('`add` adds two numbers together', function () {
assert.deepEqual(library.add(2, 3), 5);
});
test.test('`subtract` subtracts two numbers', function () {
assert.deepEqual(library.subtract(3, 2), 1, 'normal usage');
assert.deepEqual(library.subtract(2, 3), -1, 'less than zero');
});
});
```
Open up your ``package.json`` and add a test runner:
```json
{
"scripts": {
"test": "node tests/libraryTest.js"
}
}
```
And you're done! In your command line, run ``npm test`` and check out the results. You now know everything you need to know to get started building great, readable, and maintainable unit tests.
<br>
## Secret Advanced Usage
Okay, if you're still around, I'll admit it - I wasn't quite honest. See, if you want to get _really_ advanced with Distilled, you're going to have to learn one more method. That's like double the work!
Also, there might be some extra properties floating around that you can use to build things like crazy test reporters!
<br>
### the ``then`` method
Every suite can have callbacks attached by calling ``then``. These will fire off when a given test (and all of its children) have finished running.
Just like tests, ``then`` calls are chainable. But unlike ``test``, ``then`` resolves to the same suite you called it on.
```js
var suite = new Distilled();
suite.test('a', true)
.test('b', true);
suite.then(function (test) { //Called when all children have finished
(this === test); //just like above
});
```
You'll notice that in keeping with the theme of consistency, you can access a test within its callback, similarly to how ``test`` works - either through a parameter or the ``this`` keyword.
<br>
Why would you want to do this? Because it turns out that after completing, tests have a number of interesting properties attached to them:
```js
{
label: 'My Test', //the label you passed in when creating the test
status: 'failed', //passed, failed
error : Error(), //Error object (if one exists)
parent : Parent, //Reference to the parent testing context
children : [] //Reference to any child testing contexts
}
```
<br>
If short, tests have all of the information you would need attached to them to determine both how they ran and where they sit in the overall hierarchy of tests.
### the callback
When you're initializing a new suite, you have the option to pass in a callback. This method will get called whenever _any_ test inside the suite finishes executing, regardless of whether it passed or failed.
In short, the callback is a way to specify a default ``then`` for each test inside your suite, which is an extremely handy place to put a custom test reporter!
```js
var suite = new Distilled(function (test) {
console.log(test.label, ': ', test.status);
});
```
And in fact, when you passed in that function, you just overrode Distilled's built in test reporter. This callback offers you all the tools you need to build your own test reporter that spits out whatever format you'd like. Or, you could take advantage of someone else's existing test reporter.
```js
var reporter = require('awesome-reporter');
var suite = new Distilled(reporter);
```
<br>
### Understanding shared context
Want to go a bit deeper? Remember that the context getting passed into both the ``test`` method and your callback is the exact same.
This means that you can use it to communicate between tests and your reporter.
```js
var skip = function () {
this.skip = true;
return false;
}
var suite = new Distilled(function () {
if (this.skip) {
//don't log or report this test, even if it shows up as a failure.
}
});
suite.test('ignore test', skip).test('my ignored test' //....
```
<br>
I told you adding back any other features you wanted would be simple and easy!
With the combined power of two whole methods, you'll be able to sculpt Distilled into any configuration or setup you can imagine.
<br>
{
"_from": "@latinfor/distilled",
"_id": "@latinfor/distilled@1.0.4",
"_inBundle": false,
"_integrity": "sha512-Q+g1KbnPiewSx4BFC3PYqvaMn9Hd+RwKU71yTTGh2LPZRqHVa+6GboZwsZSKB3WNCgXoZC9IspMEMIOTsFisfQ==",
"_location": "/@latinfor/distilled",
"_phantomChildren": {},
"_requested": {
"type": "tag",
"registry": true,
"raw": "@latinfor/distilled",
"name": "@latinfor/distilled",
"escapedName": "@latinfor%2fdistilled",
"scope": "@latinfor",
"rawSpec": "",
"saveSpec": null,
"fetchSpec": "latest"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/@latinfor/distilled/-/distilled-1.0.4.tgz",
"_shasum": "316d77dbf6dedf91a3423d1190bd175a2a5f5e1b",
"_spec": "@latinfor/distilled",
"_where": "/media/danshumway/files/Code/site",
"author": {
"name": "danShumway"
},
"bugs": {
"url": "https://gitlab.com/distilled/distilled/issues"
},
"bundleDependencies": false,
"dependencies": {
"distilled-reporters-checklist": "0.0.1"
},
"deprecated": false,
"description": "An aggressively elegant testing framework, inspired by ideals of simplicity, flexibility, and consistency",
"devDependencies": {
"tape": "^4.6.3"
},
"homepage": "https://gitlab.com/distilled/distilled#README",
"keywords": [
"unit",
"test",
"distilled",
"carbination",
"mineral"
],
"license": "MIT",
"main": "src/node_modules/Distilled.js",
"name": "@latinfor/distilled",
"repository": {
"type": "git",
"url": "git+ssh://git@gitlab.com/distilled/distilled.git"
},
"scripts": {
"test": "node src/tests/distilled.js && node src/tests/tape.js"
},
"version": "1.0.4"
}
var STATUS = require('constants/status');
var MESSAGES = require('constants/messages').en;
var REPORTERS = Object.freeze({
dot : require('reporters/dot'),
checklist : require('distilled-reporters-checklist'),
});
(function () {
if (!Promise) {
console.log(MESSAGES.promiseMissing());
}
});
var uniqueId = (function (i) {
return function () { return Math.random() + '' + (++i); };
}(0));
var Suite = (function () {
var PRIVATE = uniqueId();
function Suite(options) {
options = options || {};
var that = this;
var _that = this[PRIVATE] = {
//---------------INTERNAL STATE---------------------------
assertion : options.assertion || Promise.resolve(),
suite : null,
finally : null,
//-----------------USER OPTIONS----------------------------
attachPostResolve : !!options.attachPostResolve, //Can you call `test` on a suite that's already finished?
reporter : options.reporter, //Default `then` that fires off after every other `then` is done.
//---------USER-FACING INFORMATION-------------------------
parent : options.parent || null,
children : [],
label : options.label || null,
status : STATUS.PENDING,
error : null,
//-----------------MISC (should be kept small)--------------
callbacks : [], //List of callbacks attached to `then`
};
_that[PRIVATE] = _that;
/* Chain result place so status will always be set on resolution. */
_that.assertion = _that.assertion.then(function () {
_that.status = STATUS.PASSED;
}, function (error) {
if (_that.status !== STATUS.SKIPPED) {
_that.status = STATUS.FAILED;
_that.error = error;
}
return Promise.reject(error);
});
/* Resolve suites once all children have finished. */
_that.suite = new Promise(function (resolve) {
/* When the assertion finishes, check children. */
_that.assertion.then(checkSuite, checkSuite);
function checkSuite(size) {
size = size || 0;
if (size === _that.children.length) {
resolve(); /* If no new children have been added, resolve */
return;
}
/* Otherwise repeat, including the new children */
var map = _that.children.map(function (child) {
var _child = child[PRIVATE];
return _child.finally;
});
Promise.all(map).then(checkSuite.bind(null, _that.children.length));
}
});
_that.finally = new Promise(function (resolve) {
/* When the suite finishes, check callbacks. */
_that.suite.then(checkFinally, checkFinally);
function checkFinally(size) {
size = size || 0;
if (size === _that.callbacks.length) {
resolve(); /* If no new callbacks have been added, resolve */
return;
}
/* Otherwise repeat, including the new callbacks */
Promise.all(_that.callbacks).then(checkFinally.bind(null, _that.callbacks.length));
}
});
/* Errors are handled elsewhere, so blindly catch any remaining assertion failures */
_that.assertion.catch(function () {});
//Make sure suite resolves correctly.
that.then(function () {}); // @huh: Is this still necessary? You should make `finally` resolve cleaner.
/* And attach reporter */
_that.finally.then(function () {
_that.reporter.call(that, that);
});
}
Suite.prototype = {
constructor : Suite,
get error() { return this[PRIVATE].error; },
get status() { return this[PRIVATE].status; },
get label() { return this[PRIVATE].label; },
get parent() { return this[PRIVATE].parent; },
get children() {
//Wrapper to prevent tampering with the array
return Object.create(this[PRIVATE].children);
},
get STATUS () { return STATUS },
get REPORTERS () { return REPORTERS },
get MESSAGES () { return MESSAGES },
assert : function (assertion) {
var that = this;
var _that = this[PRIVATE];
return (function promisify (assertion) {
if (assertion == null) {
return Promise.resolve();
}
if (assertion instanceof Promise) {
return assertion.then(function (resolution) {
return promisify(resolution);
});
}
if (typeof assertion === 'function') {
//For the API to be completely, 100% consistent, allow recursion.
return Promise.resolve().then(function () {
return promisify(assertion.call(that, that));
});
}
return assertion ? Promise.resolve() : Promise.reject();
}(assertion));
},
test : function (label, assertion) {
var that = this;
var _that = this[PRIVATE];
/*
* Prevent the user from attaching new tests after a suite has resolved.
*
* A suite is considered "finished" if:
* - it isn't pending
* - it has no unresolved `then` calls
* - the user hasn't turned off the "finished" state
*/
if(!_that.status === STATUS.PENDING &&
!_that.pendingCallbacks &&
!_that.attachPostResolve) {
assertion = Promise.reject(new Error(MESSAGES.attachPostResolve));
}
/*
* Be careful to use the attached constructor. This makes it easier to write extensions.
*/
var test = Object.create(that.constructor.prototype);
Suite.call(test, {
label : label,
parent : that,
reporter : _that.reporter, //@huh: Still not sure what the best way is to handle this.
attachPostResolve : _that.attachPostResolve,
assertion : _that.assertion.then(function () {
return test.assert(assertion);
}, function (error) {
test[PRIVATE].status = STATUS.SKIPPED; //@huh: possibly an encapsulation violation here.
throw error;
})
});
_that.children.push(test); // basic attachment
return test;
},
then : function (callback) {
var that = this,
_that = this[PRIVATE];
//For now (?) allow passing in blank callbacks.
callback = callback || function () {};
callback = callback.bind(that, that);
//Polyfill while waiting for https://github.com/nodejs/node/pull/8217
var promise = _that.suite.then(callback, callback).catch(function (err) {
console.log(MESSAGES.callbackError, err);
//TODO: should this break?
});
_that.callbacks.push(promise);
return that;
},
};
return Suite;
}());
function Distilled(reporter, options) {
options = options || {};
Suite.call(this, {
label : null,
assertion : null,
parent : null,
attachPostResolve : options.attachPostResolve,
reporter : reporter || REPORTERS.checklist,
});
}
Distilled.prototype = Suite.prototype;
Distilled.prototype.constructor = Suite;
Distilled.STATUS = STATUS;
Distilled.REPORTERS = REPORTERS;
Distilled.MESSAGES = MESSAGES;
Distilled.prototype.STATUS = STATUS;
Distilled.prototype.REPORTERS = REPORTERS;
Distilled.prototype.MESSAGES = MESSAGES;
module.exports = Distilled;
module.exports = {
en : {
attachPostResolve: function () {
return [
"Test attached after the suite has already finished. Usually, this is a mistake.",
"Set `options.attachPostResolve` if you really know what you're doing."
].join("\n");
},
callbackError: function () {
return ["Uncaught error in suite callback:", ""].join("\n");
},
promiseMissing : function () {
return [
"Distilled relies on Promise support or an equivalent polyfill: ",
"Expect to see a LOT of errors soon!"
].join("");
}
}
};
module.exports = Object.freeze({
PASSED : 'passed',
FAILED : 'failed',
PENDING : 'pending',
SKIPPED : 'skipped'
});
var STATUS = require('constants/status');
module.exports = (function () {
var buffer = '';
var log = function (message) {
if (process && process.stdout) {
process.stdout.write(message);
} else if (console.clear) {
buffer = buffer + message;
console.clear();
console.log(buffer);
} else {
console.log(message);
}</