diff --git a/.eslintignore b/.eslintignore index 89963b9..e9cf2e8 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ node_modules coverage -test/fixtures \ No newline at end of file +test/fixtures +test-output \ No newline at end of file diff --git a/README.md b/README.md index 278c18b..a0a6a25 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ [![NPM version](https://badge.fury.io/js/thought.svg)](http://badge.fury.io/js/thought) [![Travis Build Status](https://travis-ci.org/nknapp/thought.svg?branch=master)](https://travis-ci.org/nknapp/thought) [![Coverage Status](https://img.shields.io/coveralls/nknapp/thought.svg)](https://coveralls.io/r/nknapp/thought) -[![Greenkeeper badge](https://badges.greenkeeper.io/nknapp/thought.svg)](https://greenkeeper.io/) > A customizable documentation generator for github projects @@ -66,9 +65,8 @@ In the default configuration, this will generate a `README.md` from the informat Consider the following example

-
 ├── LICENSE.md
-├── examples
+├── examples/
 ├── index.js
 └── package.json
 
@@ -164,11 +162,10 @@ You can find the default configuration in the [partials/](partials/) directory. has the structure.

-
 ├── helpers.js
-├─┬ partials
+├─┬ partials/
 │ ├── api.md.hbs
-│ ├─┬ badge
+│ ├─┬ badge/
 │ │ ├── appveyor.md.hbs
 │ │ ├── coveralls.md.hbs
 │ │ ├── greenkeeper.md.hbs
@@ -183,7 +180,7 @@ has the structure.
 │ ├── overview.md.hbs
 │ └── usage.md.hbs
 ├── preprocessor.js
-└─┬ templates
+└─┬ templates/
   ├── CONTRIBUTING.md.hbs
   └── README.md.hbs
 
@@ -229,30 +226,15 @@ as context data, but if you return promises, they will be resolved seamlessly. -## API-reference - - - -## thought(options) -Execute Thought in the current directory - -**Kind**: global function -**Api**: public - -| Param | Type | Description | -| --- | --- | --- | -| options | object | | -| [options.cwd] | string | the working directory to use as project root | -| [options.addToGit] | boolean | add created files to git | - - ## License -`thought` is published under the MIT-license. +`thought` is published under the MIT-license. + See [LICENSE.md](LICENSE.md) for details. + ## Release-Notes For release notes, see [CHANGELOG.md](CHANGELOG.md) diff --git a/bin/thought.js b/bin/thought.js index 1227609..06a2b56 100755 --- a/bin/thought.js +++ b/bin/thought.js @@ -37,16 +37,20 @@ program changeDir() var packageJson = findPackage() if (!(packageJson.scripts && packageJson.scripts.thought)) { + /* eslint-disable no-console */ console.log('\nNot registered in package.json yet!\n' + 'I can add a `scripts`-property to your package.json to ensure that ' + 'documentation is generated automatically on version bumps.\n' + 'If you want that, run `thought init`\n') + /* eslint-enable no-console */ } thought({ addToGit: options.addToGit, debug: program.debug }).done(function (filenames) { + /* eslint-disable no-console */ console.log('The following files were updated: ' + filenames.join(', ')) + /* eslint-enable no-console */ }) }) @@ -58,7 +62,7 @@ program require('../lib/check-engines.js')() .then(require('../lib/init.js')) .done(function () { - console.log('OK') + console.log('OK') // eslint-disable-line no-console }) }) @@ -68,7 +72,7 @@ program .action(function () { require('../lib/check-engines.js')() .done(function () { - console.log('OK') + console.log('OK') // eslint-disable-line no-console }) }) @@ -78,7 +82,7 @@ program .action(function () { changeDir() require('../lib/up-to-date.js')().done(function () { - console.log('OK') + console.log('OK') // eslint-disable-line no-console }) }) @@ -101,5 +105,6 @@ function changeDir () { var moduleRoot = path.dirname(packageJson.paths.absolute) process.chdir(moduleRoot) + // eslint-disable-next-line no-console console.log("I'm running inside module '" + packageJson.name + "' in '" + moduleRoot) } diff --git a/examples/.eslintrc.js b/examples/.eslintrc.js index 05c39c7..e3611dd 100644 --- a/examples/.eslintrc.js +++ b/examples/.eslintrc.js @@ -3,5 +3,8 @@ module.exports = { "plugins": [ "standard", "promise" - ] + ], + "rules": { + "no-console": "off" + } }; diff --git a/handlebars/helpers.js b/handlebars/helpers.js index 67b1629..20971b2 100644 --- a/handlebars/helpers.js +++ b/handlebars/helpers.js @@ -1,9 +1,9 @@ -var fs = require('fs') var path = require('path') var cp = require('child_process') var _ = require('lodash') var debug = require('debug')('thought:helpers') -var glob = require('glob') +var Promise = require('bluebird') +var glob = Promise.promisify(require('glob')) var findPackage = require('find-package') var Handlebars = require('handlebars') var qfs = require('m-io/fs') @@ -31,10 +31,10 @@ module.exports = { include: function (filename, language) { return qfs.read(filename).then(function (contents) { return '```' + - (_.isString(language) ? language : path.extname(filename).substr(1)) + - '\n' + - contents + - '\n```\n' + (_.isString(language) ? language : path.extname(filename).substr(1)) + + '\n' + + contents + + '\n```\n' }) }, @@ -71,9 +71,9 @@ module.exports = { .then(function (contents) { // Relative path to the current module (e.g. "../"). This path must be replaced // by the module name in the - var modulePath = path.relative(path.dirname(filename), '.') + '/' + var modulePath = path.relative(path.dirname(filename), '.') debug('example modulepath', modulePath) - var requireModuleRegex = new RegExp(`require\\('${_.escapeRegExp(modulePath)}(.*?)'\\)`, 'm') + var requireModuleRegex = new RegExp(regex`require\('${modulePath}/?(.*?)'\)`, 'g') if (options && options.hash && options.hash.snippet) { contents = contents.match(/------.*\n([\S\s]*?)\n.*---<\/snip>---/)[1] } @@ -98,8 +98,10 @@ module.exports = { }, /** - * Execute a commad and include the output in a fenced code-block. + * Execute a command and include the output in a fenced code-block. + * * @param {string} command the command, passed to `child-process#execSync()` + * @param {object} options optional arguments and Handlebars internal args. * @param {string} options.hash.lang the language tag that should be attached to the fence * (like `js` or `bash`). If this is set to `raw`, the output is included as-is, without fences. * @param {string} options.hash.cwd the current working directory of the example process @@ -136,35 +138,53 @@ module.exports = { * @returns {string} a display of the directory tree of the selected files and directories. * @api public */ - dirTree: function (baseDir, globPattern) { + dirTree: function (baseDir, globPattern, options) { // Is basedir is not a string, it is probably the handlebars "options" object if (!_.isString(globPattern)) { globPattern = '**' } - var defer = Q.defer() - glob(globPattern, {cwd: baseDir}, function (err, files) { - if (err) { - return defer.reject(err) - } - debug('dirTree glob result', files) - files.sort() - // Split paths into components - var components = files.map(function (file) { - return _.compact(file.split(path.sep)) + + const label = options && options.data && options.data.label + return glob(globPattern, {cwd: baseDir, mark: true}) + .then((files) => { + debug('dirTree glob result', files) + if (files.length === 0) { + throw new Error('Cannot find a single file for \'' + globPattern + '\' in \'' + baseDir + '\'') + } + files.sort() + // Split paths into components + const pathComponents = files.map((file) => { + // a/b/c or a/b/dir/ + return file.split(path.sep) + // a, b, c or a, b, dir, '' + .map((component, index, all) => component + (index < all.length - 1 ? '/' : '')) + // a/, b/, c or a/, b/, dir/, '' + .filter(component => component) // Filter empty parts + }) + const treeObject = treeFromPathComponents(pathComponents, label) + const tree = require('archy')(treeObject) + return '
\n' + tree.trim() + '\n
' }) - if (components.length === 0) { - defer.reject("Cannot find a single file for '" + globPattern + "' in '" + baseDir + "'") - return - } - var treeObject = treeFromPathComponents(components) - var tree = require('archy')(treeObject) - defer.fulfill('
\n' + tree + '
') - }) - return defer.promise }, /** - * Render an object hierarchy + * Render an object hierarchy of the form + * + * ``` + * { + * prop1: 'value', + * prop2: 'value', + * ..., + * children: [ + * { + * prop1: 'value', + * propt2: 'value', + * ..., + * children: ... + * } + * ] + * } + * ``` * @param object * @param options * @param {function} options.fn computes the label for a node based on the node itself @@ -184,9 +204,7 @@ module.exports = { * @param options block-helper options */ withPackageOf: function (filePath, options) { - if (options.data) { - var data = Handlebars.createFrame(options.data || {}) - } + const data = Handlebars.createFrame(options.data) data.url = githubUrl(filePath) data.package = findPackage(path.resolve(filePath), false) return options.fn(this, {data: data}) @@ -200,7 +218,7 @@ module.exports = { * Returns the path to the github repository (below github.com) based on the $.repository.url. * @param options * @returns {string=} the repository path within github.com (or null) - */ + */ githubRepo: githubRepo, /** @@ -341,7 +359,7 @@ function treeFromPathComponents (files, label) { // Condense path if directory only has one entry if (result.nodes.length === 1 && _.isPlainObject(result.nodes[0])) { return { - label: (result.label ? result.label + '/' : '') + result.nodes[0].label, + label: (result.label || '') + result.nodes[0].label, nodes: result.nodes[0].nodes } } else { @@ -362,9 +380,8 @@ function githubUrl (filePath) { } function githubRepo (options) { - var url = null try { - url = options.data.root.package.repository.url + var url = options.data.root.package.repository.url var match = url.match(/.*?(:\/\/|@)github\.com[/:](.*?)(#.*?)?$/) if (match) { return match[2].replace(/\.git$/, '') @@ -372,7 +389,11 @@ function githubRepo (options) { return null } } catch (e) { - console.log('Cannot find repository url') - url = null + // No repositor-url exists + return null } } + +function regex (strings, ...args) { + return String.raw(strings, ...args.map(_.escapeRegExp)) +} diff --git a/lib/check-engines.js b/lib/check-engines.js index 87518fc..526a4a6 100644 --- a/lib/check-engines.js +++ b/lib/check-engines.js @@ -14,6 +14,7 @@ module.exports = function () { throw new Error('npm<2.13.0 will not execute the `version`-script in your package.json.\n' + 'Please upgrade to at least 2.13.0.') } else { + // eslint-disable-next-line no-console console.log('npm@' + stdout.trim() + ': OK') } }) diff --git a/lib/dev-server.js b/lib/dev-server.js index 8ceed18..4284cdd 100644 --- a/lib/dev-server.js +++ b/lib/dev-server.js @@ -5,6 +5,7 @@ var thought = customize().load(require('../customize.js')('.')) function handleMessage (message) { switch (message.cmd) { case 'run': { + // eslint-disable-next-line no-console console.log('Running thought') return thought.run() } @@ -16,7 +17,6 @@ process.on('message', (message) => { .then((result) => { process.send({ message: message, result: result }) }, (err) => { - console.error('error', err) process.send({ error: err }) }) }) diff --git a/lib/init.js b/lib/init.js index 60d96a1..e1f4751 100644 --- a/lib/init.js +++ b/lib/init.js @@ -46,6 +46,7 @@ module.exports = function () { }) .spread(function (stderr, stdout) { debug('git commit package.json...', 'stdout', stdout, 'stderr', stderr) + // eslint-disable-next-line no-console console.log('\nI have committed a new package.json. Please verify the changes made to the file!') }) } diff --git a/package.json b/package.json index 9b661e8..ea1b164 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ }, "dependencies": { "archy": "^1.0.0", + "bluebird": "^3.5.0", "cheerio": "^0.22.0", "commander": "^2.8.1", "customize": "^1.0.0", @@ -49,7 +50,6 @@ "trace-and-clarify-if-possible": "^1.0.0" }, "devDependencies": { - "bluebird": "^3.5.0", "chai": "^3.2.0", "chai-as-promised": "^5.1.0", "customize-engine-handlebars": "^2.0.0-alpha.0", diff --git a/test/dev-server-spec.js b/test/dev-server-spec.js index e48aa99..80cf79a 100644 --- a/test/dev-server-spec.js +++ b/test/dev-server-spec.js @@ -36,9 +36,9 @@ xdescribe('the thought dev-server', function () { 'result': { 'handlebars': { 'CONTRIBUTING.md': fs.readFileSync('test/fixtures/scenarios/simple-project/expected/CONTRIBUTING.md', - { encoding: 'utf-8'}), + {encoding: 'utf-8'}), 'README.md': fs.readFileSync('test/fixtures/scenarios/simple-project/expected/README.md', - { encoding: 'utf-8'}) + {encoding: 'utf-8'}) } } }) diff --git a/test/fixtures/dir-tree.output.complex.filter.txt b/test/fixtures/dir-tree.output.complex.filter.txt index 8753eae..7379c1d 100644 --- a/test/fixtures/dir-tree.output.complex.filter.txt +++ b/test/fixtures/dir-tree.output.complex.filter.txt @@ -1,7 +1,8 @@ -
dir-tree/
-├── subdirA/
-│   ├── bDir/
-│   └── cFile.txt
-└── subdirB/
-    ├── bFile.md
-    └── subdirC/
\ No newline at end of file +

+├─┬ subdirA/
+│ ├── bDir/
+│ └── cFile.txt
+└─┬ subdirB/
+  ├── bFile.md
+  └── subdirC/
+
\ No newline at end of file diff --git a/test/fixtures/dir-tree.output.condensed.txt b/test/fixtures/dir-tree.output.condensed.txt new file mode 100644 index 0000000..2314ecc --- /dev/null +++ b/test/fixtures/dir-tree.output.condensed.txt @@ -0,0 +1,8 @@ +

+├─┬ subdirA/
+│ ├── aFile.txt
+│ └─┬ bDir/
+│   └── aFile.txt
+└─┬ subdirB/subdirC/
+  └── aFile.txt
+
\ No newline at end of file diff --git a/test/fixtures/dir-tree.output.filtered.txt b/test/fixtures/dir-tree.output.filtered.txt index 0f37672..37e51a2 100644 --- a/test/fixtures/dir-tree.output.filtered.txt +++ b/test/fixtures/dir-tree.output.filtered.txt @@ -1,7 +1,8 @@ -
dir-tree/
-└── subdirA/
-    ├── aFile.txt
-    ├── bDir/
-    │   ├── aFile.txt
-    │   └── bFile.txt
-    └── cFile.txt
\ No newline at end of file +

+subdirA/
+├── aFile.txt
+├─┬ bDir/
+│ ├── aFile.txt
+│ └── bFile.txt
+└── cFile.txt
+
\ No newline at end of file diff --git a/test/fixtures/dir-tree.output.txt b/test/fixtures/dir-tree.output.txt index 59af554..779c712 100644 --- a/test/fixtures/dir-tree.output.txt +++ b/test/fixtures/dir-tree.output.txt @@ -1,12 +1,13 @@ -
dir-tree/
-├── subdirA/
-│   ├── aFile.txt
-│   ├── bDir/
-│   │   ├── aFile.txt
-│   │   └── bFile.txt
-│   └── cFile.txt
-└── subdirB/
-    ├── bFile.md
-    └── subdirC/
-        ├── aFile.txt
-        └── bFile.txt
\ No newline at end of file +

+├─┬ subdirA/
+│ ├── aFile.txt
+│ ├─┬ bDir/
+│ │ ├── aFile.txt
+│ │ └── bFile.txt
+│ └── cFile.txt
+└─┬ subdirB/
+  ├── bFile.md
+  └─┬ subdirC/
+    ├── aFile.txt
+    └── bFile.txt
+
\ No newline at end of file diff --git a/test/fixtures/example-helper/examples/full.js b/test/fixtures/example-helper/examples/full.js new file mode 100644 index 0000000..610f912 --- /dev/null +++ b/test/fixtures/example-helper/examples/full.js @@ -0,0 +1,3 @@ +require('../') +require('..') +require('../file') diff --git a/test/fixtures/example-helper/examples/output.full.md b/test/fixtures/example-helper/examples/output.full.md new file mode 100644 index 0000000..a0955be --- /dev/null +++ b/test/fixtures/example-helper/examples/output.full.md @@ -0,0 +1,5 @@ +```js +require('example-helper') +require('example-helper') +require('example-helper/file') +``` \ No newline at end of file diff --git a/test/fixtures/example-helper/examples/output.snippet-full.md b/test/fixtures/example-helper/examples/output.snippet-full.md new file mode 100644 index 0000000..c9cf1e2 --- /dev/null +++ b/test/fixtures/example-helper/examples/output.snippet-full.md @@ -0,0 +1,9 @@ +```js +var project = require('abc') +var file2 = require('file2') + +// ----------------------------- +console.log(project) +console.log(file2) +// ---------------------------- +``` \ No newline at end of file diff --git a/test/fixtures/example-helper/examples/output.snippet.md b/test/fixtures/example-helper/examples/output.snippet.md new file mode 100644 index 0000000..6a459c9 --- /dev/null +++ b/test/fixtures/example-helper/examples/output.snippet.md @@ -0,0 +1,4 @@ +```js +console.log(project) +console.log(file2) +``` \ No newline at end of file diff --git a/test/fixtures/example.js b/test/fixtures/example-helper/examples/snippet.js similarity index 56% rename from test/fixtures/example.js rename to test/fixtures/example-helper/examples/snippet.js index 70a2be9..cbe07bb 100644 --- a/test/fixtures/example.js +++ b/test/fixtures/example-helper/examples/snippet.js @@ -1,5 +1,7 @@ -var project = require('../../') +var project = require('abc') +var file2 = require('file2') // ----------------------------- console.log(project) +console.log(file2) // ---------------------------- diff --git a/test/fixtures/example-helper/package.json b/test/fixtures/example-helper/package.json new file mode 100644 index 0000000..03eb33d --- /dev/null +++ b/test/fixtures/example-helper/package.json @@ -0,0 +1,15 @@ +{ + "name": "example-helper", + "version": "0.0.0-rc.1", + "description": "", + "main": "full.js", + "directories": { + "example": "examples" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/test/fixtures/exec.output.as.js.md b/test/fixtures/exec.output.as.js.md new file mode 100644 index 0000000..1101d80 --- /dev/null +++ b/test/fixtures/exec.output.as.js.md @@ -0,0 +1,3 @@ +```js +TEST/FIXTURES/SHOUT.JS +``` \ No newline at end of file diff --git a/test/fixtures/exec.output.cwd.md b/test/fixtures/exec.output.cwd.md new file mode 100644 index 0000000..2ae36f8 --- /dev/null +++ b/test/fixtures/exec.output.cwd.md @@ -0,0 +1,3 @@ +``` +SHOUT.JS +``` \ No newline at end of file diff --git a/test/fixtures/exec.output.default.md b/test/fixtures/exec.output.default.md new file mode 100644 index 0000000..a39e2b3 --- /dev/null +++ b/test/fixtures/exec.output.default.md @@ -0,0 +1,3 @@ +``` +TEST/FIXTURES/SHOUT.JS +``` \ No newline at end of file diff --git a/test/fixtures/exec.output.inline.md b/test/fixtures/exec.output.inline.md new file mode 100644 index 0000000..c2403b6 --- /dev/null +++ b/test/fixtures/exec.output.inline.md @@ -0,0 +1 @@ +`TEST/FIXTURES/SHOUT.JS` \ No newline at end of file diff --git a/test/fixtures/exec.output.raw.md b/test/fixtures/exec.output.raw.md new file mode 100644 index 0000000..082e5e6 --- /dev/null +++ b/test/fixtures/exec.output.raw.md @@ -0,0 +1 @@ +TEST/FIXTURES/SHOUT.JS \ No newline at end of file diff --git a/test/fixtures/hasCoveralls-helper-false/package.json b/test/fixtures/hasCoveralls-helper-false/package.json new file mode 100644 index 0000000..6865832 --- /dev/null +++ b/test/fixtures/hasCoveralls-helper-false/package.json @@ -0,0 +1,12 @@ +{ + "name": "coveralls-helper-false", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "" +} diff --git a/test/fixtures/hasCoveralls-helper/.travis.yml b/test/fixtures/hasCoveralls-helper/.travis.yml new file mode 100644 index 0000000..8174ef4 --- /dev/null +++ b/test/fixtures/hasCoveralls-helper/.travis.yml @@ -0,0 +1,12 @@ +sudo: false +language: "node_js" +node_js: + - "6" + - "7" +script: + - npm install + - npm run coverage + - npm run lint +after_script: + - npm install coveralls + - cat ./coverage/lcov.info | coveralls diff --git a/test/fixtures/hasCoveralls-helper/package.json b/test/fixtures/hasCoveralls-helper/package.json new file mode 100644 index 0000000..56df5a9 --- /dev/null +++ b/test/fixtures/hasCoveralls-helper/package.json @@ -0,0 +1,12 @@ +{ + "name": "coveralls-helper", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/test/fixtures/include/github.default.md b/test/fixtures/include/github.default.md new file mode 100644 index 0000000..b1104fc --- /dev/null +++ b/test/fixtures/include/github.default.md @@ -0,0 +1 @@ +https://github.com/nknapp/thought/blob/THOUGHT_VERSION/test/fixtures/shout.js \ No newline at end of file diff --git a/test/fixtures/include/github.dependency.md b/test/fixtures/include/github.dependency.md new file mode 100644 index 0000000..fabbc7b --- /dev/null +++ b/test/fixtures/include/github.dependency.md @@ -0,0 +1 @@ +https://github.com/bootprint/customize/blob/CUSTOMIZE_VERSION/helpers-io.js \ No newline at end of file diff --git a/test/fixtures/include/javascript.js b/test/fixtures/include/javascript.js new file mode 100644 index 0000000..fa0d902 --- /dev/null +++ b/test/fixtures/include/javascript.js @@ -0,0 +1,3 @@ +var x = 2 +x = x + 2 +process.stdout.write(x) diff --git a/test/fixtures/include/output.javascript-as-text.md b/test/fixtures/include/output.javascript-as-text.md new file mode 100644 index 0000000..591b3b5 --- /dev/null +++ b/test/fixtures/include/output.javascript-as-text.md @@ -0,0 +1,6 @@ +```txt +var x = 2 +x = x + 2 +process.stdout.write(x) + +``` \ No newline at end of file diff --git a/test/fixtures/include/output.javascript.md b/test/fixtures/include/output.javascript.md new file mode 100644 index 0000000..00cc4ef --- /dev/null +++ b/test/fixtures/include/output.javascript.md @@ -0,0 +1,6 @@ +```js +var x = 2 +x = x + 2 +process.stdout.write(x) + +``` \ No newline at end of file diff --git a/test/fixtures/include/output.text.md b/test/fixtures/include/output.text.md new file mode 100644 index 0000000..e9dcd89 --- /dev/null +++ b/test/fixtures/include/output.text.md @@ -0,0 +1,3 @@ +```txt +Dies ist ein Text +``` \ No newline at end of file diff --git a/test/fixtures/include/text.txt b/test/fixtures/include/text.txt new file mode 100644 index 0000000..16c55cf --- /dev/null +++ b/test/fixtures/include/text.txt @@ -0,0 +1 @@ +Dies ist ein Text \ No newline at end of file diff --git a/test/fixtures/include/withPackageOf.default.md b/test/fixtures/include/withPackageOf.default.md new file mode 100644 index 0000000..aa080b0 --- /dev/null +++ b/test/fixtures/include/withPackageOf.default.md @@ -0,0 +1 @@ +https://github.com/nknapp/thought/blob/THOUGHT_VERSION/test/fixtures/shout.js - thought \ No newline at end of file diff --git a/test/fixtures/include/withPackageOf.dependency.md b/test/fixtures/include/withPackageOf.dependency.md new file mode 100644 index 0000000..cbe133d --- /dev/null +++ b/test/fixtures/include/withPackageOf.dependency.md @@ -0,0 +1 @@ +https://github.com/bootprint/customize/blob/CUSTOMIZE_VERSION/helpers-io.js - customize \ No newline at end of file diff --git a/test/fixtures/include/withPackageOf.no-repo.md b/test/fixtures/include/withPackageOf.no-repo.md new file mode 100644 index 0000000..48b46f5 --- /dev/null +++ b/test/fixtures/include/withPackageOf.no-repo.md @@ -0,0 +1 @@ +- withPackageOf-helper-no-repo \ No newline at end of file diff --git a/test/fixtures/json.output.simple.md b/test/fixtures/json.output.simple.md new file mode 100644 index 0000000..9235fa6 --- /dev/null +++ b/test/fixtures/json.output.simple.md @@ -0,0 +1,13 @@ +```json +{ + "a": { + "b": 2 + }, + "c": [ + 1, + 2, + "a", + "b" + ] +} +``` diff --git a/test/fixtures/no-git-repo/package.json b/test/fixtures/no-git-repo/package.json new file mode 100644 index 0000000..eba1c80 --- /dev/null +++ b/test/fixtures/no-git-repo/package.json @@ -0,0 +1,12 @@ +{ + "name": "withPackageOf-helper-no-repo", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/test/fixtures/renderTree.output.md b/test/fixtures/renderTree.output.md new file mode 100644 index 0000000..18d49d6 --- /dev/null +++ b/test/fixtures/renderTree.output.md @@ -0,0 +1,5 @@ +

+--parent (2)--
+├── --child (0)--
+└── --emptyChild ()--
+
\ No newline at end of file diff --git a/test/fixtures/shout.js b/test/fixtures/shout.js new file mode 100644 index 0000000..0bfe729 --- /dev/null +++ b/test/fixtures/shout.js @@ -0,0 +1,2 @@ +// Output the path of this script relative to the cwd (for the exec-helper-test +console.log(require('path').relative(process.cwd(), __filename).toUpperCase()) diff --git a/test/git-spec.js b/test/git-spec.js index 8545885..6e324a5 100644 --- a/test/git-spec.js +++ b/test/git-spec.js @@ -31,13 +31,13 @@ describe('The "addToGit" option', function () { .then(() => git.statusAsync()) // Check only which files have been added to the index .then((status) => { - return status.files + return status.files .filter(fileEntry => fileEntry.index === 'A') .map(fileEntry => fileEntry.path) .sort() - } + } ) - .then((files) => expect(files).to.deep.equal(['CONTRIBUTING.md','README.md'])) + .then((files) => expect(files).to.deep.equal(['CONTRIBUTING.md', 'README.md'])) }) }) }) diff --git a/test/helper-spec.js b/test/helper-spec.js index 28f03dc..3c4f9f8 100644 --- a/test/helper-spec.js +++ b/test/helper-spec.js @@ -5,48 +5,358 @@ * Released under the MIT license. */ -/* global describe */ -/* global it */ -// /* global xdescribe */ -// /* global xit */ +/* eslint-env mocha */ 'use strict' var fs = require('fs') -var helpers = require('../handlebars/helpers.js') +var handlebars = require('promised-handlebars')(require('handlebars')) +const helpers = require('../handlebars/helpers.js') +handlebars.registerHelper(helpers) var chai = require('chai') var chaiAsPromised = require('chai-as-promised') chai.use(chaiAsPromised) var expect = chai.expect +var path = require('path') +function executeInDir (directory) { + var oldCwd = null -describe('thought-helper', function () { - describe("'dirTree'", function () { + before(function () { + oldCwd = process.cwd() + process.chdir(directory) + }) + + after(function () { + process.chdir(oldCwd) + }) +} + +/** + * Return an expectation for the result of a handlebars run + * @param {string} template the handlebars-template + * @param {object} input the input object + * @param {string=} workingDirectory the working directory to execute the test in + * @returns {*} + */ +function expectHbs (template, input) { + return expect( + Promise.resolve() + .then(() => { + return handlebars.compile(template, {noEscape: true})(input) + }) + .then((result) => result.trim()) + ) +} + +/** + * + * @param {string} filename + * @returns {string} + */ +function fixture (filename) { + const absPath = path.join(__dirname, 'fixtures', filename) + try { + const result = fs.readFileSync(absPath, {encoding: 'utf-8'}).trim() + return result + } catch (e) { + if (e.code === 'ENOENT') { + fs.writeFileSync(absPath, 'Automatically created fixture template') + return 'Automatically created fixture template' + } + } +} + +describe('thought-helpers:', function () { + describe('The "dirTree" helper', function () { it('should return a file-hierarchy as markdown code', function () { - expect(helpers.dirTree('test/fixtures/dir-tree')) - .to.eventually.equal(fs.readFileSync('test/fixtures/dir-tree.output.txt', { encoding: 'utf-8' }).trim()) + return expectHbs('{{dirTree directory}}', {directory: 'test/fixtures/dir-tree'}) + .to.eventually.equal(fixture('dir-tree.output.txt')) }) it('should filter specific entries throw globs', function () { - expect(helpers.dirTree('test/fixtures/dir-tree', '!**/subdirB')) - .to.eventually.equal(fs.readFileSync('test/fixtures/dir-tree.output.filtered.txt', { encoding: 'utf-8' }).trim()) + return expectHbs('{{dirTree directory glob}}', {directory: 'test/fixtures/dir-tree', glob: '!(subdirB)/**'}) + .to.eventually.equal(fixture('dir-tree.output.filtered.txt')) + }) + + it('should condense paths with a single subdirectory into a single node', function () { + return expectHbs('{{dirTree directory glob}}', {directory: 'test/fixtures/dir-tree', glob: '**/aFile.txt'}) + .to.eventually.equal(fixture('dir-tree.output.condensed.txt')) }) it('should work with more complex globs', function () { - expect(helpers.dirTree('test/fixtures/dir-tree', '!**/+(aFile.txt|bFile.txt)')) - .to.eventually.equal(fs.readFileSync('test/fixtures/dir-tree.output.complex.filter.txt', { encoding: 'utf-8' }).trim()) + return expectHbs('{{dirTree directory glob}}', { + directory: 'test/fixtures/dir-tree', + glob: '**/!(aFile.txt|bFile.txt)' + }) + .to.eventually.equal(fixture('dir-tree.output.complex.filter.txt')) + }) + + it('should throw an error if the glob does not resolve to any files', function () { + return expectHbs('{{dirTree directory}}', {directory: 'non-existing-dir'}) + .to.be.rejectedWith('Cannot find a single file for \'**\' in \'non-existing-dir\'') }) }) - describe("'example'", function () { + describe('The "example" helper', function () { + executeInDir('test/fixtures/example-helper') + it('should resolve the current project properly', function () { - return expect(helpers.example('test/fixtures/example.js')) - .to.eventually.contain("require('thought')") + return expectHbs( + '{{example file}}', + {file: 'examples/full.js'} + ) + .to.eventually.equal(fixture('example-helper/examples/output.full.md')) }) it('should return the marked part of the file if `options.hash.snippet` is true', function () { - return expect(helpers.example('test/fixtures/example.js', { hash: { snippet: true } })) - .to.eventually.equal('```js\nconsole.log(project)\n```') + return expectHbs( + '{{example file snippet=true}}', + {file: 'examples/snippet.js'} + ) + .to.eventually.equal(fixture('example-helper/examples/output.snippet.md')) + }) + + it('should ignore the snippet markers, if "snippet=true" is not set`', function () { + return expectHbs( + '{{example file}}', + {file: 'examples/snippet.js'} + ) + .to.eventually.equal(fixture('example-helper/examples/output.snippet-full.md')) + }) + }) + + describe('The "json"-helper', function () { + it('should display a javascript-object as JSON', function () { + return expectHbs('{{json .}}', {a: {b: 2}, c: [1, 2, 'a', 'b']}) + .to.eventually.equal(fixture('json.output.simple.md')) + }) + }) + + describe('The "include"-helper', function () { + it('should include a file into fences', function () { + return expectHbs('{{include file}}', {file: 'test/fixtures/include/javascript.js'}) + .to.eventually.equal(fixture('include/output.javascript.md')) + }) + + it('should use the file extension as language descriptor', function () { + return expectHbs('{{include file}}', {file: 'test/fixtures/include/text.txt'}) + .to.eventually.equal(fixture('include/output.text.md')) + }) + + it('should prefer the file language descriptor provided as second parameter', function () { + return expectHbs('{{include file "txt"}}', {file: 'test/fixtures/include/javascript.js'}) + .to.eventually.equal(fixture('include/output.javascript-as-text.md')) + }) + }) + + describe('The "includeRaw"-helper', function () { + it('should include a file without any fences', function () { + return expectHbs('{{includeRaw file}}', {file: 'test/fixtures/include/javascript.js'}) + .to.eventually.equal(fixture('include/javascript.js')) + }) + }) + + describe('The "exec"-helper', function () { + it('should execute a command and return the output in a fence', function () { + return expectHbs('{{exec "node test/fixtures/shout.js"}}', {}) + .to.eventually.equal(fixture('exec.output.default.md')) + }) + + it('should add language to the fence if the "lang"-option is set', function () { + return expectHbs('{{exec "node test/fixtures/shout.js" lang="js"}}', {}) + .to.eventually.equal(fixture('exec.output.as.js.md')) + }) + + it('should not output fences if lang="raw"', function () { + return expectHbs('{{exec "node test/fixtures/shout.js" lang="raw"}}', {}) + .to.eventually.equal(fixture('exec.output.raw.md')) + }) + + it('should surround the output with single backticks if lang="inline"', function () { + return expectHbs('{{exec "node test/fixtures/shout.js" lang="inline"}}', {}) + .to.eventually.equal(fixture('exec.output.inline.md')) + }) + + it('should chdir to the directory provided as cwd="..."', function () { + return expectHbs('{{exec "node shout.js" cwd="test/fixtures"}}', {}) + .to.eventually.equal(fixture('exec.output.cwd.md')) + }) + }) + + describe('The "renderTree"-helper', function () { + it('render a tree', function () { + return expectHbs('{{#renderTree tree}}--{{prop1}} ({{children.length}})--{{/renderTree}}', {tree: tree()}) + .to.eventually.equal(fixture('renderTree.output.md')) + }) + + function tree () { + return { + prop1: 'parent', + children: [ + { + prop1: 'child', + children: [] + }, + { + prop1: 'emptyChild' + } + + ] + } + } + }) + + /** + * Replace some versions by versions currently in package.json + * @param expectedString + */ + function versions (expectedString) { + var thought = require('../package').version + var customize = require('customize/package').version + return expectedString + .replace(/THOUGHT_VERSION/g, `v${thought}`) + .replace(/CUSTOMIZE_VERSION/g, `v${customize}`) + } + + describe('The "withPackageOf"-helper', function () { + it('should create a url and package.json for file on github (based on the current package version)', function () { + return expectHbs( + '{{#withPackageOf file}} {{@url}} - {{@package.name}} {{/withPackageOf}}', + {file: 'test/fixtures/shout.js'} + ) + .to.eventually.equal(versions(fixture('include/withPackageOf.default.md'))) + }) + + it('should create a url and package.json for files in dependency projects (based on the their current package version)', function () { + return expectHbs( + '{{#withPackageOf file}} {{@url}} - {{@package.name}} {{/withPackageOf}}', + {file: require.resolve('customize/helpers-io.js')} + ) + .to.eventually.equal(versions(fixture('include/withPackageOf.dependency.md'))) + }) + + it('should not create an url for files without repository-property in the pacakge.json', function () { + return expectHbs( + '{{#withPackageOf file}} {{@url}} - {{@package.name}} {{/withPackageOf}}', + {file: require.resolve('./fixtures/no-git-repo/package.json')} + ) + .to.eventually.equal(versions(fixture('include/withPackageOf.no-repo.md'))) + }) + }) + + describe('The "github"-helper', function () { + it('should create a url to file on github (based on the current package version)', function () { + return expectHbs( + '{{github file}}', + {file: 'test/fixtures/shout.js'} + ) + .to.eventually.equal(versions(fixture('include/github.default.md'))) + }) + + it('should create a url files in dependency projects (based on the their current package version)', function () { + return expectHbs( + '{{github file}}', + {file: require.resolve('customize/helpers-io.js')} + ) + .to.eventually.equal(versions(fixture('include/github.dependency.md'))) + }) + }) + + describe('The "htmlId"-helper', function () { + it('should keep valid names intact', function () { + expect(helpers.htmlId('abc')).to.equal('abc') + }) + + it('should replace spaces by minus', function () { + expect(helpers.htmlId('abc cde')).to.equal('abc-cde') + }) + + it('should remove invalid characters', function () { + expect(helpers.htmlId('a$b&c%d')).to.equal('abcd') + }) + + it('should keep umlaut characters', function () { + expect(helpers.htmlId('mäxchen')).to.equal('mäxchen') + }) + + it('should convert everything to lower-case', function () { + expect(helpers.htmlId('ABCDE')).to.equal('abcde') + }) + + it('should not remove japanese characters', function () { + expect(helpers.htmlId('ハッピークリスマス')).to.equal('ハッピークリスマス') + }) + }) + + describe('The "npm"-helper', function () { + it('should create a link to the npm-page, given the package name', function () { + return expectHbs( + '{{npm file}}', + {file: 'bootprint-openapi'} + ) + .to.eventually.equal('[bootprint-openapi](https://npmjs.com/package/bootprint-openapi)') + }) + }) + + describe('The "hasCoveralls"-helper (positive)', function () { + executeInDir('test/fixtures/hasCoveralls-helper') + + it('should return true, if coveralls is mentioned in the .travis.yml file', function () { + return expectHbs( + '{{#if (hasCoveralls)}}yeah{{else}}nope{{/if}}', + {} + ) + .to.eventually.equal('yeah') + }) + }) + + describe('The "hasCoveralls"-helper (negative)', function () { + executeInDir('test/fixtures/hasCoveralls-helper-false') + + it('should return true, if coveralls is not configured', function () { + return expectHbs( + '{{#if (hasCoveralls)}}yeah{{else}}nope{{/if}}', + {} + ) + .to.eventually.equal('nope') + }) + }) + + describe('The "githubRepo"-helper', function () { + it('should return the name of organization/repository in a module with a configured repository on github (e.g. Thought itself)', function () { + return expectHbs('{{githubRepo}}', { + 'package': { + 'name': 'no-github-repo', + 'repository': { + 'type': 'git', + 'url': 'https://github.com/nknapp/thought.git' + } + } + }) + .to.eventually.equal('nknapp/thought') + }) + + it('should return null in a module with no configured git repo', function () { + return expectHbs('{{githubRepo}}', { + package: { + name: 'no-git-repo' + } + }) + .not.to.eventually.be.ok() + }) + + it('should return null in a module with a repository that is not on github', function () { + return expectHbs('{{githubRepo}}', { + 'package': { + 'name': 'no-github-repo', + 'repository': { + 'type': 'git', + 'url': 'https://custom-git.com/somepath.git' + } + } + }) + .not.to.eventually.be.ok() }) }) }) diff --git a/test/lib/run-in-scenario.js b/test/lib/run-in-scenario.js deleted file mode 100644 index 14445c7..0000000 --- a/test/lib/run-in-scenario.js +++ /dev/null @@ -1,28 +0,0 @@ -var qfs = require('m-io/fs') - -/** - * Remove "tmpDir", copy "scenario" recursively to "tmpDir", - * chdir() to "tmpDir", execute "tester" and go back to the - * previouse cwd. - * - * @param {string} scenario a directory containing the scenario (test-project) - * @param {string} tmpDir the directory where the tests are executed - * @param {function():Promise} tester a function executing the tests - */ -module.exports = function (scenario, tmpDir, tester) { - var oldCwd = process.cwd() - - if (!tmpDir.match(/testOutput|actual/)) { - throw new Error(`${tmpDir} must be inside testOutput or the 'actual' directory of a scenario.`) - // I don't want to risk deleting important files - } - - return qfs.removeTree(tmpDir) - .then(() => copy(scenario.input, scenario.actual)) - .then(() => process.chdir(scenario.actual)) - .then(() => tester()) - .then(() => process.chdir(oldCwd), (err) => { - process.chdir(oldCwd) - throw err - }) -} diff --git a/test/lib/scenarios.js b/test/lib/scenarios.js index 613bf8e..5563efc 100644 --- a/test/lib/scenarios.js +++ b/test/lib/scenarios.js @@ -22,7 +22,6 @@ class Scenario { * Prepare setup of the scenario */ prepare () { - console.log("preparing actual", this.actual, "for input", this.input) return qfs.removeTree(this.actual) .then(() => copy(this.input, this.actual)) .then(() => this) @@ -39,7 +38,7 @@ class Scenario { try { process.chdir(this.actual) } catch (e) { - if (e.code = 'ENOENT') { + if (e.code === 'ENOENT') { throw new Error(this.actual + ' does not exist. Have you called ".prepare()"') } } @@ -95,7 +94,6 @@ class Scenario { readExpected (relativePath) { return fs.readFileSync(path.join(this.expected, relativePath), 'utf-8') } - } Scenario.all = function () { diff --git a/test/scenarios-spec.js b/test/scenarios-spec.js index 3feb397..8ce4f04 100644 --- a/test/scenarios-spec.js +++ b/test/scenarios-spec.js @@ -12,7 +12,6 @@ var fs = require('fs') var qfs = require('m-io/fs') var deep = require('deep-aplus')(Promise) -var copy = require('recursive-copy') var path = require('path') var chai = require('chai') chai.use(require('chai-as-promised')) @@ -21,7 +20,6 @@ var expect = chai.expect var thought = require('../') var Scenario = require('./lib/scenarios') - function listTreeRelative (baseDir, filter) { return qfs.listTree(baseDir, filter) .then((result) => { @@ -58,10 +56,8 @@ describe('the integation test: ', function () { this.timeout(10000) Scenario.all().forEach((scenario) => { describe(`In the scenario "${scenario.name}",`, function () { - if (scenario.expectFailure) { it('running Thought should produce an error', function () { - // This scenario must be rejected return expect(scenario.run(() => thought())).to.be.rejected })