From d7dd12f6de46a1caec13568bd14aae8730bd4f73 Mon Sep 17 00:00:00 2001 From: Mickael Daniel Date: Mon, 25 Apr 2016 22:14:32 +0200 Subject: [PATCH] Document api --- .gitignore | 2 + package.json | 3 +- readme.md | 187 ++++++++++++++++++++++-------------------------- scripts/docs.js | 30 ++++++++ test.js | 78 ++++++++++++++------ test/cli.js | 21 ++++++ 6 files changed, 195 insertions(+), 126 deletions(-) create mode 100644 .gitignore create mode 100644 scripts/docs.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5abcb3d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +api.md +.tern-port diff --git a/package.json b/package.json index 219d3a4..e8065d1 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "CLI assertions made easy", "main": "main.js", "scripts": { - "test": "ava test/*.js" + "test": "DEBUG=gentle-cli ava test/*.js", + "docs": "tomdox test.js --json | node scripts/docs.js > api.md" }, "devDependencies": { "ava": "^0.14.0", diff --git a/readme.md b/readme.md index 83a899e..e113f6f 100644 --- a/readme.md +++ b/readme.md @@ -1,31 +1,21 @@ -clt -=== +gentle-cli +========== -**Inspired / Based off both -[cli-easy](https://github.com/flatiron/cli-easy) and -[supertest](https://github.com/visionmedia/supertest)** +**Inspired / Based off both [cli-easy](https://github.com/flatiron/cli-easy) and [supertest](https://github.com/visionmedia/supertest)** -> A fluent (i.e. chainable) syntax for generating vows tests for CLI applications. - -> HTTP assertions made easy via super-agent. - -Description ------------ +> CLI assertions made easy. - Struggling with testing cli tools. - cli-easy is super great, but designed for generating vows -- supertest is super great, but designed to make HTTP assertions via - super-agent. +- supertest is super great, but designed to make HTTP assertions via super-agent. -clt is nothing more than a simple, chainable API to ease the process of +gentle-cli is nothing more than a simple, chainable API to ease the process of testing CLI applications & tools. -Right now, it doesn't do anything fancy and just allow you to easily -test the exit code and stdout output, and make assertions on top of -that. +Right now, it doesn't do anything fancy and just allow you to easily test the +exit code and stdout output, and make assertions on top of that. -Example -------- +## Documentation It should work with any test framework, here is an example using any test framework at all. @@ -33,6 +23,17 @@ test framework at all. ```js var cli = require('clt'); +// promise +cli() + .use('whoami') + .expect(0, 'A fool') + .then(function(res) { + console.log(res.status); + console.log(res.text); + console.log(res.err); + }); + +// callback cli() .use('uname') .expect(0, 'Linux\n') @@ -41,6 +42,35 @@ cli() }); ``` +#### ava + +Check [gentle cli tests](./test), they're using ava: + +```js +import test from 'ava'; +import cli from '..'; +import constants from 'constants'; + +test('Testing on uname', t => { + return cli() + .use('uname') + .expect(0, process.platform === 'darwin' ? 'Darwin' : 'Linux') + .end(); +}); + +test('Testing on a wtf thing', t => { + return cli() + .use('wtfBinary') + .expect(constants.ENOENT) + .throws('ENOENT') + .end(); +}); +``` + +`cli().end()` returns a promise you can pass through ava, as well as `.then()` and `.catch()`. + +#### mocha + Here's an example with mocha, note how you can pass done straight to any of the `.expect()` calls (or `.end()`): @@ -68,120 +98,71 @@ describe('Testing on a wtf thing', function() { }); ``` -API ---- +### API -

Main assertion thingy. First rough work.

+#### module.exports = Runnable; -

Thx to @visionmedia, based off supertest's Runnable object:
https://github.com/visionmedia/supertest/blob/master/lib/Runnable.js

+Main assertion thingy -

-
+Thx to @visionmedia, based off supertest's Runnable object: +https://github.com/visionmedia/supertest/blob/master/lib/Runnable.js -

inherits from EventEmitter

+#### function Runnable(cmds, options) -

-util.inherits(Runnable, events.EventEmitter);
+Initialize a new `Runnable` with the given `options` Hash object. -### Runnable.prototype.use() -

Setup CLI command.

+#### Runnable#use() -

-Runnable.prototype.use = function use(command) {
-  this._command = command;
-  return this;
-};
+Setup CLI command. -### Runnable.prototype.expect() -

Adds a new expctations to this runnable instance.

+#### Runnable#expect() -

Examples

+Adds a new expectation to this runnable instance. -
.expect(0)
+```js:
+.expect(0)
 .expect(0, fn)
 .expect(0, body)
 .expect('Some body')
 .expect('Some body', fn)
-
- -

Returns the runnable.

- -

-Runnable.prototype.expect = function expect(a, b) {
-  var self = this;
-
-  if (typeof a === 'number') {
-    this._status = a;
-    if (b && typeof b !== 'function') this.addExpectation(b);
-    else if(typeof b === 'function') this.end(b);
-    return this;
-  }
-
-  this.addExpectation(a);
-
-  if (typeof b === 'function') this.end(b);
-
-  return this;
-};
- - -### Runnable.prototype.addExpectation() -

Adds a new expectation to the list of expected result. Can be either a
regexp or a string, in which case direct indexOf match

- -

-Runnable.prototype.addExpectation = function addExpectation(match) {
-  this._expects.push(match);
-};
- - -### Runnable.prototype.prompt() -

Adds a new prompt hook to the list of expected prompts, automatically
writes the answer string provided to child's stdin when the
matcher RegExp or String match a given prompt in child stdout.

+``` +#### Runnable#throws() -

-Runnable.prototype.prompt = function prompt(matcher, answer) {
-  matcher = matcher instanceof RegExp ? matcher : new RegExp(matcher, 'i');
-  this._prompts.push({
-    matcher: matcher,
-    answer: (answer || '') + '\n'
-  });
-  return this;
-};
+Adds a new expectation to this runnable instance. +```js: +.throws(0) +.throws('ENOENT') +.throws(require('constants').ENOENT) +``` -### Runnable.prototype.end() -

Defer invoking .end() until the command is done running.

+#### Runnable#end() -

Examples

+Defer invoking `.end()` until the command is done running. -
it('test thing', function(done) {
+```js:
+it('test thing', function(done) {
   cli()
     .use('thing')
     .expect(/run thing/)
     .end(done);
 });
-
+``` -

Returns the runnable instance.

+Returns a promise. -

-Runnable.prototype.end = function end(fn) {
-  var self = this;
-  fn = fn || function() {};
+#### Runnable#then()
 
-  this.run(function(err, code, stdout, stderr) {
-    self.emit('done');
-    self.emit('end');
+Automatically invokes `end()` and register the callback.
 
-    self.assert({
-      status: code,
-      text: (stdout || stderr),
-      err: err
-    }, fn);
-  });
+Returns a promise.
+
+
+#### Runnable#catch()
 
-  return this;
-};
+Automatically invokes `end()` and register the errback. +Returns a promise. diff --git a/scripts/docs.js b/scripts/docs.js new file mode 100644 index 0000000..71c7253 --- /dev/null +++ b/scripts/docs.js @@ -0,0 +1,30 @@ + +var stdin = process.stdin; + +stdin.setEncoding('utf8'); + +var json = '' +stdin.on('readable', () => { + var chunk = process.stdin.read(); + if (chunk !== null) { + json += chunk; + } +}); + +stdin.on('end', () => { + var data = JSON.parse(json); + + var file = data.find((f) => { + return f.file === 'test'; + }); + + file.tomdocs.forEach((doc) => { + console.log(`#### ${doc.identifier} + +${doc.description} + +${doc.examples ? '```js' + doc.examples + '```' : ''}`); + }); + + // console.log(file); +}); diff --git a/test.js b/test.js index 970b409..f334792 100644 --- a/test.js +++ b/test.js @@ -1,19 +1,17 @@ var spawn = require('child_process').spawn; var events = require('events'); var util = require('util'); -var debug = require('debug')('clt'); +var debug = require('debug')('gentle-cli'); var constants = require('constants'); -// -// Main assertion thingy. First rough work. + +// Public: Main assertion thingy // // Thx to @visionmedia, based off supertest's Runnable object: // https://github.com/visionmedia/supertest/blob/master/lib/Runnable.js -// module.exports = Runnable; -// Initialize a new `Runnable` with the given `options` Hash object. - +// Public: Initialize a new `Runnable` with the given `options` Hash object. function Runnable(cmds, options) { this.options = options || {}; this._body = null; @@ -27,14 +25,14 @@ function Runnable(cmds, options) { // inherits from EventEmitter util.inherits(Runnable, events.EventEmitter); -// Setup CLI command. +// Public: Setup CLI command. Runnable.prototype.use = function use(command) { this._command = command; return this; }; -// Adds a new expctations to this runnable instance. +// Public: Adds a new expectation to this runnable instance. // // Examples: // @@ -62,6 +60,15 @@ Runnable.prototype.expect = function expect(a, b) { return this; }; +// Public: Adds a new expectation to this runnable instance. +// +// Examples: +// +// .throws(0) +// .throws('ENOENT') +// .throws(require('constants').ENOENT) +// +// Returns the runnable. Runnable.prototype.throws = function throws(errcode) { var code = constants[errcode]; if (!code) { @@ -72,8 +79,17 @@ Runnable.prototype.throws = function throws(errcode) { return this; }; -// Adds a new expectation to the list of expected result. Can be either a -// regexp or a string, in which case direct indexOf match +// Private: Adds a new expectation to the list of expected result +// +// Can be either a regexp or a string, in which case direct indexOf match +// +// Examples: +// +// .throws(0) +// .throws('ENOENT') +// .throws(require('constants').ENOENT) +// +// Returns the runnable. Runnable.prototype.addExpectation = function addExpectation(match) { this._expects.push(match); }; @@ -90,7 +106,7 @@ Runnable.prototype.prompt = function prompt(matcher, answer) { return this; }; -// Defer invoking `.end()` until the command is done running. +// Public: Defer invoking `.end()` until the command is done running. // // Examples: // @@ -103,7 +119,7 @@ Runnable.prototype.prompt = function prompt(matcher, answer) { // // Returns a Promise. Runnable.prototype.end = function end(done) { - return new Promise(function(r, errback) { + var promise = new Promise(function(r, errback) { this.run(function(err, code, stdout, stderr) { this.emit('done'); this.emit('end'); @@ -125,13 +141,28 @@ Runnable.prototype.end = function end(done) { }); }.bind(this)); }.bind(this)); + + promise.catch(done || function(err) { + debug('Error: %s', err.stack || err.message); + }); + + return promise; }; -// Add topic to current (or root). Execute defined command with arguments and -// passed options, case of redirect options turned on, pipe back all stdout / -// stderr output to parent process +// Public: Automatically invokes `end()` and register the callback. +Runnable.prototype.then = function then(fn) { + return this.end().then(fn); +}; + +// Public: Automatically invokes `end()` and register the errback. +Runnable.prototype.catch = function then(fn) { + return this.end().catch(fn); +}; + +// Private: Execute defined command with arguments and passed options // -// @api private +// Case of redirect options turned on, pipe back all stdout / stderr output to +// parent process Runnable.prototype.run = function run(fn) { var self = this; var cmds = this._command; @@ -174,6 +205,7 @@ Runnable.prototype.run = function run(fn) { var errcode = 0; child.on('error', function(err) { + debug('Spawn error:', err); errcode = err.code; }); @@ -191,15 +223,16 @@ Runnable.prototype.run = function run(fn) { }; -// Perform assertions and invoke `fn(err)`. -// -// @api private +// Private: Perform assertions and invoke `fn(err)`. Runnable.prototype.assert = function assert(res, fn) { var status = this._status; var expects = this._expects; + var err; if (status && res.status !== status) { - return fn(new Error('expected ' + status + ', got ' + res.status), res); + err = new Error('expected ' + status + ', got ' + res.status); + err.code = status || 1; + return fn(err, res); } var errors = []; @@ -218,12 +251,13 @@ Runnable.prototype.assert = function assert(res, fn) { }); if(!errors.length) return fn(null, res); + err = new Error(msg); + err.code = status || 1; var msg = 'Expected ' + util.inspect(res.text) + '\n to match:\n'; msg += errors.map(function(expected) { return ' - ' + expected; }).join('\n'); - fn(new Error(msg), res); + fn(err, res); }; - diff --git a/test/cli.js b/test/cli.js index 1b6294b..0e8dd75 100644 --- a/test/cli.js +++ b/test/cli.js @@ -16,3 +16,24 @@ test('Testing on a wtf thing', t => { .throws('ENOENT') .end(); }); + +test('Testing promise style', t => { + t.plan(2); + return cli() + .use('ls') + .expect(0) + .then((res) => { + t.is(res.status, 0); + t.is(res.text, 'cli.js\n'); + }); +}); + +test('Testing promise style catch', t => { + t.plan(1); + return cli() + .use('ls') + .expect(2) + .catch((err) => { + t.is(err.code, 2); + }); +});