diff --git a/.travis.yml b/.travis.yml index 08e53c2c..6a2a7ac4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,16 @@ node_js: - "1.8" - "2.5" - "3.3" - - "4.4" + - "4.6" + - "5.12" + - "6.9" + - "7.0" sudo: false +cache: + directories: + - node_modules +before_install: + # Update Node.js modules + - "test ! -d node_modules || npm prune" + - "test ! -d node_modules || npm rebuild" script: "npm run-script test-ci" diff --git a/LICENSE b/LICENSE index d23e93ce..d7ec3cd2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ (The MIT License) Copyright (c) 2009-2013 TJ Holowaychuk +Copyright (c) 2015-2016 Douglas Christopher Wilson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/README.md b/README.md index 59e1a0c0..98dd0495 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ The quickest way to get started with express is to utilize the executable `expre Create the app: ```bash -$ express /tmp/foo && cd /tmp/foo +$ express --view=hbs /tmp/foo && cd /tmp/foo ``` Install dependencies: @@ -30,7 +30,7 @@ Install dependencies: $ npm install ``` -Rock and Roll +Start your Express.js app at `http://localhost:3000/`: ```bash $ npm start @@ -41,10 +41,12 @@ $ npm start This generator can also be further configured with the following command line flags. -h, --help output usage information - -V, --version output the version number - -e, --ejs add ejs engine support (defaults to jade) + --version output the version number + -e, --ejs add ejs engine support --hbs add handlebars engine support + --pug add pug engine support -H, --hogan add hogan.js engine support + -v, --view add view support (ejs|hbs|hjs|jade|pug|twig|vash) (defaults to jade) -c, --css add stylesheet support (less|stylus|compass|sass) (defaults to plain css) --git add .gitignore -f, --force force on non-empty directory diff --git a/appveyor.yml b/appveyor.yml index 94d8136e..380d0b61 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,9 +5,16 @@ environment: - nodejs_version: "1.8" - nodejs_version: "2.5" - nodejs_version: "3.3" - - nodejs_version: "4.4" + - nodejs_version: "4.6" + - nodejs_version: "5.12" + - nodejs_version: "6.9" + - nodejs_version: "7.0" +cache: + - node_modules install: - ps: Install-Product node $env:nodejs_version + - if exist node_modules npm prune + - if exist node_modules npm rebuild - npm install build: off test_script: diff --git a/bin/express b/bin/express index af9e522d..166179da 100755 --- a/bin/express +++ b/bin/express @@ -7,6 +7,7 @@ var fs = require('fs'); var path = require('path'); var readline = require('readline'); var sortedObject = require('sorted-object'); +var util = require('util'); var _exit = process.exit; var eol = os.EOL; @@ -20,16 +21,35 @@ process.exit = exit // CLI +around(program, 'optionMissingArgument', function (fn, args) { + program.outputHelp() + fn.apply(this, args) + return { args: [], unknown: [] } +}) + before(program, 'outputHelp', function () { - this.allowUnknownOption(); + // track if help was shown for unknown option + this._helpShown = true }); +before(program, 'unknownOption', function () { + // allow unknown options if help was shown, to prevent trailing error + this._allowUnknownOption = this._helpShown + + // show help if not yet shown + if (!this._helpShown) { + program.outputHelp() + } +}) + program - .version(version) + .version(version, ' --version') .usage('[options] [dir]') - .option('-e, --ejs', 'add ejs engine support (defaults to jade)') - .option(' --hbs', 'add handlebars engine support') - .option('-H, --hogan', 'add hogan.js engine support') + .option('-e, --ejs', 'add ejs engine support', renamedOption('--ejs', '--view=ejs')) + .option(' --pug', 'add pug engine support', renamedOption('--pug', '--view=pug')) + .option(' --hbs', 'add handlebars engine support', renamedOption('--hbs', '--view=hbs')) + .option('-H, --hogan', 'add hogan.js engine support', renamedOption('--hogan', '--view=hogan')) + .option('-v, --view ', 'add view support (ejs|hbs|hjs|jade|pug|twig|vash) (defaults to jade)') .option('-c, --css ', 'add stylesheet support (less|stylus|compass|sass) (defaults to plain css)') .option(' --git', 'add .gitignore') .option('-f, --force', 'force on non-empty directory') @@ -39,6 +59,20 @@ if (!exit.exited) { main(); } +/** + * Install an around function; AOP. + */ + +function around(obj, method, fn) { + var old = obj[method] + + obj[method] = function () { + var args = new Array(arguments.length) + for (var i = 0; i < args.length; i++) args[i] = arguments[i] + return fn.call(this, old, args) + } +} + /** * Install a before function; AOP. */ @@ -141,7 +175,7 @@ function createApplication(app_name, path) { }); mkdir(path + '/views', function(){ - switch (program.template) { + switch (program.view) { case 'ejs': copy_template('ejs/index.ejs', path + '/views/index.ejs'); copy_template('ejs/error.ejs', path + '/views/error.ejs'); @@ -160,6 +194,21 @@ function createApplication(app_name, path) { copy_template('hbs/layout.hbs', path + '/views/layout.hbs'); copy_template('hbs/error.hbs', path + '/views/error.hbs'); break; + case 'pug': + copy_template('pug/index.pug', path + '/views/index.pug'); + copy_template('pug/layout.pug', path + '/views/layout.pug'); + copy_template('pug/error.pug', path + '/views/error.pug'); + break; + case 'twig': + copy_template('twig/index.twig', path + '/views/index.twig'); + copy_template('twig/layout.twig', path + '/views/layout.twig'); + copy_template('twig/error.twig', path + '/views/error.twig'); + break; + case 'vash': + copy_template('vash/index.vash', path + '/views/index.vash'); + copy_template('vash/layout.vash', path + '/views/layout.vash'); + copy_template('vash/error.vash', path + '/views/error.vash'); + break; } complete(); }); @@ -183,7 +232,7 @@ function createApplication(app_name, path) { } // Template support - app = app.replace('{views}', program.template); + app = app.replace('{views}', program.view); // package.json var pkg = { @@ -192,8 +241,8 @@ function createApplication(app_name, path) { , private: true , scripts: { start: 'node ./bin/www' } , dependencies: { - 'express': '~4.13.4', - 'body-parser': '~1.15.1', + 'express': '~4.14.0', + 'body-parser': '~1.15.2', 'cookie-parser': '~1.4.3', 'debug': '~2.2.0', 'morgan': '~1.7.0', @@ -201,18 +250,27 @@ function createApplication(app_name, path) { } } - switch (program.template) { + switch (program.view) { case 'jade': pkg.dependencies['jade'] = '~1.11.0'; break; case 'ejs': - pkg.dependencies['ejs'] = '~2.4.1'; + pkg.dependencies['ejs'] = '~2.5.2'; break; case 'hjs': pkg.dependencies['hjs'] = '~0.0.6'; break; case 'hbs': - pkg.dependencies['hbs'] = '~4.0.0'; + pkg.dependencies['hbs'] = '~4.0.1'; + break; + case 'pug': + pkg.dependencies['pug'] = '~2.0.0-beta6'; + break; + case 'twig': + pkg.dependencies['twig'] = '~0.9.5'; + break; + case 'vash': + pkg.dependencies['vash'] = '~0.12.2'; break; default: } @@ -220,7 +278,7 @@ function createApplication(app_name, path) { // CSS Engine support switch (program.css) { case 'less': - pkg.dependencies['less-middleware'] = '1.0.x'; + pkg.dependencies['less-middleware'] = '~2.2.0'; break; case 'compass': pkg.dependencies['node-compass'] = '0.2.3'; @@ -229,7 +287,7 @@ function createApplication(app_name, path) { pkg.dependencies['stylus'] = '0.54.5'; break; case 'sass': - pkg.dependencies['node-sass-middleware'] = '0.8.0'; + pkg.dependencies['node-sass-middleware'] = '0.9.8'; break; default: } @@ -258,6 +316,18 @@ function copy_template(from, to) { from = path.join(__dirname, '..', 'templates', from); write(to, fs.readFileSync(from, 'utf-8')); } +/** + * Create an app name from a directory path, fitting npm naming requirements. + * + * @param {String} pathName + */ + +function createAppName(pathName) { + return path.basename(pathName) + .replace(/[^A-Za-z0-9\.()!~*'-]+/g, '-') + .replace(/^[-_\.]+|-+$/g, '') + .toLowerCase() +} /** * Check if the given directory `path` is empty. @@ -325,13 +395,21 @@ function main() { var destinationPath = program.args.shift() || '.'; // App name - var appName = path.basename(path.resolve(destinationPath)); + var appName = createAppName(path.resolve(destinationPath)) || 'hello-world' + + // View engine + if (program.view === undefined) { + if (program.ejs) program.view = 'ejs' + if (program.hbs) program.view = 'hbs' + if (program.hogan) program.view = 'hjs' + if (program.pug) program.view = 'pug' + } - // Template engine - program.template = 'jade'; - if (program.ejs) program.template = 'ejs'; - if (program.hogan) program.template = 'hjs'; - if (program.hbs) program.template = 'hbs'; + // Default view engine + if (program.view === undefined) { + warning("the default view engine will not be jade in future releases\nuse `--view=jade' or `--help' for additional options") + program.view = 'jade' + } // Generate application emptyDirectory(destinationPath, function (empty) { @@ -351,6 +429,34 @@ function main() { }); } +/** + * Generate a callback function for commander to warn about renamed option. + * + * @param {String} originalName + * @param {String} newName + */ + +function renamedOption(originalName, newName) { + return function (val) { + warning(util.format("option `%s' has been renamed to `%s'", originalName, newName)) + return val + } +} + +/** + * Display a warning similar to how errors are displayed by commander. + * + * @param {String} message + */ + +function warning(message) { + console.error() + message.split('\n').forEach(function (line) { + console.error(' warning: %s', line) + }) + console.error() +} + /** * echo str > path. * diff --git a/package.json b/package.json index 897b140c..194dc52a 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "repository": "expressjs/generator", "license": "MIT", "dependencies": { - "commander": "2.7.1", + "commander": "2.9.0", "mkdirp": "0.5.1", "sorted-object": "2.0.0" }, @@ -35,9 +35,10 @@ "express": "./bin/express" }, "devDependencies": { - "mocha": "2.4.5", + "mocha": "2.5.3", "rimraf": "2.5.2", - "supertest": "1.2.0" + "supertest": "1.2.0", + "validate-npm-package-name": "2.2.2" }, "engines": { "node": ">= 0.10" diff --git a/templates/js/app.js b/templates/js/app.js index 269b55f8..235940d0 100644 --- a/templates/js/app.js +++ b/templates/js/app.js @@ -5,7 +5,7 @@ var logger = require('morgan'); var cookieParser = require('cookie-parser'); var bodyParser = require('body-parser'); -var routes = require('./routes/index'); +var index = require('./routes/index'); var users = require('./routes/users'); var app = express(); @@ -22,7 +22,7 @@ app.use(bodyParser.urlencoded({ extended: false })); app.use(cookieParser());{css} app.use(express.static(path.join(__dirname, 'public'))); -app.use('/', routes); +app.use('/', index); app.use('/users', users); // catch 404 and forward to error handler @@ -32,29 +32,15 @@ app.use(function(req, res, next) { next(err); }); -// error handlers - -// development error handler -// will print stacktrace -if (app.get('env') === 'development') { - app.use(function(err, req, res, next) { - res.status(err.status || 500); - res.render('error', { - message: err.message, - error: err - }); - }); -} - -// production error handler -// no stacktraces leaked to user +// error handler app.use(function(err, req, res, next) { + // set locals, only providing error in development + res.locals.message = err.message; + res.locals.error = req.app.get('env') === 'development' ? err : {}; + + // render the error page res.status(err.status || 500); - res.render('error', { - message: err.message, - error: {} - }); + res.render('error'); }); - module.exports = app; diff --git a/templates/pug/error.pug b/templates/pug/error.pug new file mode 100644 index 00000000..51ec12c6 --- /dev/null +++ b/templates/pug/error.pug @@ -0,0 +1,6 @@ +extends layout + +block content + h1= message + h2= error.status + pre #{error.stack} diff --git a/templates/pug/index.pug b/templates/pug/index.pug new file mode 100644 index 00000000..3d63b9a0 --- /dev/null +++ b/templates/pug/index.pug @@ -0,0 +1,5 @@ +extends layout + +block content + h1= title + p Welcome to #{title} diff --git a/templates/pug/layout.pug b/templates/pug/layout.pug new file mode 100644 index 00000000..15af079b --- /dev/null +++ b/templates/pug/layout.pug @@ -0,0 +1,7 @@ +doctype html +html + head + title= title + link(rel='stylesheet', href='/stylesheets/style.css') + body + block content diff --git a/templates/twig/error.twig b/templates/twig/error.twig new file mode 100644 index 00000000..caf7ce0f --- /dev/null +++ b/templates/twig/error.twig @@ -0,0 +1,7 @@ +{% extends 'layout.twig' %} + +{% block body %} +

{{message}}

+

{{error.status}}

+
{{error.stack}}
+{% endblock %} diff --git a/templates/twig/index.twig b/templates/twig/index.twig new file mode 100644 index 00000000..20372e3b --- /dev/null +++ b/templates/twig/index.twig @@ -0,0 +1,6 @@ +{% extends 'layout.twig' %} + +{% block body %} +

{{title}}

+

Welcome to {{title}}

+{% endblock %} diff --git a/templates/twig/layout.twig b/templates/twig/layout.twig new file mode 100644 index 00000000..bce6dd55 --- /dev/null +++ b/templates/twig/layout.twig @@ -0,0 +1,10 @@ + + + + {{ title }} + + + + {% block body %}{% endblock %} + + diff --git a/templates/vash/error.vash b/templates/vash/error.vash new file mode 100644 index 00000000..a4a50d9f --- /dev/null +++ b/templates/vash/error.vash @@ -0,0 +1,7 @@ +@html.extend('layout', function(model) { + @html.block('content', function(model) { +

@model.message

+

@model.error.status

+
@model.error.stack
+ }) +}) diff --git a/templates/vash/index.vash b/templates/vash/index.vash new file mode 100644 index 00000000..574a1183 --- /dev/null +++ b/templates/vash/index.vash @@ -0,0 +1,6 @@ +@html.extend('layout', function(model) { + @html.block('content', function(model) { +

@model.title

+

Welcome to @model.title

+ }) +}) diff --git a/templates/vash/layout.vash b/templates/vash/layout.vash new file mode 100644 index 00000000..ab67afb9 --- /dev/null +++ b/templates/vash/layout.vash @@ -0,0 +1,11 @@ + + + + + @model.title + + + + @html.block('content') + + diff --git a/test/cmd.js b/test/cmd.js index a091609a..9774fae5 100644 --- a/test/cmd.js +++ b/test/cmd.js @@ -3,85 +3,75 @@ var assert = require('assert'); var exec = require('child_process').exec; var fs = require('fs'); var mkdirp = require('mkdirp'); -var mocha = require('mocha'); var path = require('path'); var request = require('supertest'); var rimraf = require('rimraf'); var spawn = require('child_process').spawn; +var validateNpmName = require('validate-npm-package-name') var binPath = path.resolve(__dirname, '../bin/express'); -var tempDir = path.resolve(__dirname, '../temp'); +var TEMP_DIR = path.resolve(__dirname, '..', 'temp', String(process.pid + Math.random())) describe('express(1)', function () { - mocha.before(function (done) { + before(function (done) { this.timeout(30000); cleanup(done); }); - mocha.after(function (done) { + after(function (done) { this.timeout(30000); cleanup(done); }); describe('(no args)', function () { - var dir; - var files; - var output; - - mocha.before(function (done) { - createEnvironment(function (err, newDir) { - if (err) return done(err); - dir = newDir; - done(); - }); - }); - - mocha.after(function (done) { - this.timeout(30000); - cleanup(dir, done); - }); + var ctx = setupTestEnvironment(this.fullTitle()) it('should create basic app', function (done) { - run(dir, [], function (err, stdout) { + runRaw(ctx.dir, [], function (err, code, stdout, stderr) { if (err) return done(err); - files = parseCreatedFiles(stdout, dir); - output = stdout; - assert.equal(files.length, 17); + ctx.files = parseCreatedFiles(stdout, ctx.dir) + ctx.stderr = stderr + ctx.stdout = stdout + assert.equal(ctx.files.length, 17) done(); }); }); + it('should print jade view warning', function () { + assert.equal(ctx.stderr, "\n warning: the default view engine will not be jade in future releases\n warning: use `--view=jade' or `--help' for additional options\n\n") + }) + it('should provide debug instructions', function () { - assert.ok(/DEBUG=app-(?:[0-9\.]+):\* (?:\& )?npm start/.test(output)); + assert.ok(/DEBUG=express\(1\)-\(no-args\):\* (?:\& )?npm start/.test(ctx.stdout)) }); it('should have basic files', function () { - assert.notEqual(files.indexOf('bin/www'), -1); - assert.notEqual(files.indexOf('app.js'), -1); - assert.notEqual(files.indexOf('package.json'), -1); + assert.notEqual(ctx.files.indexOf('bin/www'), -1) + assert.notEqual(ctx.files.indexOf('app.js'), -1) + assert.notEqual(ctx.files.indexOf('package.json'), -1) }); it('should have jade templates', function () { - assert.notEqual(files.indexOf('views/error.jade'), -1); - assert.notEqual(files.indexOf('views/index.jade'), -1); - assert.notEqual(files.indexOf('views/layout.jade'), -1); + assert.notEqual(ctx.files.indexOf('views/error.jade'), -1) + assert.notEqual(ctx.files.indexOf('views/index.jade'), -1) + assert.notEqual(ctx.files.indexOf('views/layout.jade'), -1) }); it('should have a package.json file', function () { - var file = path.resolve(dir, 'package.json'); + var file = path.resolve(ctx.dir, 'package.json'); var contents = fs.readFileSync(file, 'utf8'); assert.equal(contents, '{\n' - + ' "name": ' + JSON.stringify(path.basename(dir)) + ',\n' + + ' "name": "express(1)-(no-args)",\n' + ' "version": "0.0.0",\n' + ' "private": true,\n' + ' "scripts": {\n' + ' "start": "node ./bin/www"\n' + ' },\n' + ' "dependencies": {\n' - + ' "body-parser": "~1.15.1",\n' + + ' "body-parser": "~1.15.2",\n' + ' "cookie-parser": "~1.4.3",\n' + ' "debug": "~2.2.0",\n' - + ' "express": "~4.13.4",\n' + + ' "express": "~4.14.0",\n' + ' "jade": "~1.11.0",\n' + ' "morgan": "~1.7.0",\n' + ' "serve-favicon": "~2.3.0"\n' @@ -91,18 +81,18 @@ describe('express(1)', function () { it('should have installable dependencies', function (done) { this.timeout(30000); - npmInstall(dir, done); + npmInstall(ctx.dir, done); }); it('should export an express app from app.js', function () { - var file = path.resolve(dir, 'app.js'); + var file = path.resolve(ctx.dir, 'app.js'); var app = require(file); assert.equal(typeof app, 'function'); assert.equal(typeof app.handle, 'function'); }); it('should respond to HTTP request', function (done) { - var file = path.resolve(dir, 'app.js'); + var file = path.resolve(ctx.dir, 'app.js'); var app = require(file); request(app) @@ -111,66 +101,153 @@ describe('express(1)', function () { }); it('should generate a 404', function (done) { - var file = path.resolve(dir, 'app.js'); + var file = path.resolve(ctx.dir, 'app.js'); var app = require(file); request(app) .get('/does_not_exist') .expect(404, /

Not Found<\/h1>/, done); }); + + describe('when directory contains spaces', function () { + var ctx = setupTestEnvironment('foo bar (BAZ!)') + + it('should create basic app', function (done) { + run(ctx.dir, [], function (err, output) { + if (err) return done(err) + assert.equal(parseCreatedFiles(output, ctx.dir).length, 17) + done() + }) + }) + + it('should have a valid npm package name', function () { + var file = path.resolve(ctx.dir, 'package.json') + var contents = fs.readFileSync(file, 'utf8') + var name = JSON.parse(contents).name + assert.ok(validateNpmName(name).validForNewPackages) + assert.equal(name, 'foo-bar-(baz!)') + }) + }) + + describe('when directory is not a valid name', function () { + var ctx = setupTestEnvironment('_') + + it('should create basic app', function (done) { + run(ctx.dir, [], function (err, output) { + if (err) return done(err) + assert.equal(parseCreatedFiles(output, ctx.dir).length, 17) + done() + }) + }) + + it('should default to name "hello-world"', function () { + var file = path.resolve(ctx.dir, 'package.json') + var contents = fs.readFileSync(file, 'utf8') + var name = JSON.parse(contents).name + assert.ok(validateNpmName(name).validForNewPackages) + assert.equal(name, 'hello-world') + }) + }) + }); + + describe('(unknown args)', function () { + var ctx = setupTestEnvironment(this.fullTitle()) + + it('should exit with code 1', function (done) { + runRaw(ctx.dir, ['--foo'], function (err, code, stdout, stderr) { + if (err) return done(err); + assert.strictEqual(code, 1); + done(); + }); + }); + + it('should print usage', function (done) { + runRaw(ctx.dir, ['--foo'], function (err, code, stdout, stderr) { + if (err) return done(err); + assert.ok(/Usage: express/.test(stdout)); + assert.ok(/--help/.test(stdout)); + assert.ok(/--version/.test(stdout)); + assert.ok(/error: unknown option/.test(stderr)); + done(); + }); + }); + + it('should print unknown option', function (done) { + runRaw(ctx.dir, ['--foo'], function (err, code, stdout, stderr) { + if (err) return done(err); + assert.ok(/error: unknown option/.test(stderr)); + done(); + }); + }); }); describe('--css ', function () { - describe('less', function () { - var dir; - var files; + describe('(no engine)', function () { + var ctx = setupTestEnvironment(this.fullTitle()) - mocha.before(function (done) { - createEnvironment(function (err, newDir) { + it('should exit with code 1', function (done) { + runRaw(ctx.dir, ['--css'], function (err, code, stdout, stderr) { if (err) return done(err); - dir = newDir; + assert.strictEqual(code, 1); done(); }); }); - mocha.after(function (done) { - this.timeout(30000); - cleanup(dir, done); + it('should print usage', function (done) { + runRaw(ctx.dir, ['--css'], function (err, code, stdout) { + if (err) return done(err); + assert.ok(/Usage: express/.test(stdout)); + assert.ok(/--help/.test(stdout)); + assert.ok(/--version/.test(stdout)); + done(); + }); }); + it('should print argument missing', function (done) { + runRaw(ctx.dir, ['--css'], function (err, code, stdout, stderr) { + if (err) return done(err); + assert.ok(/error: option .* argument missing/.test(stderr)); + done(); + }); + }); + }); + + describe('less', function () { + var ctx = setupTestEnvironment(this.fullTitle()) + it('should create basic app with less files', function (done) { - run(dir, ['--css', 'less'], function (err, stdout) { + run(ctx.dir, ['--css', 'less'], function (err, stdout) { if (err) return done(err); - files = parseCreatedFiles(stdout, dir); - assert.equal(files.length, 17, 'should have 17 files'); + ctx.files = parseCreatedFiles(stdout, ctx.dir) + assert.equal(ctx.files.length, 17, 'should have 17 files') done(); }); }); it('should have basic files', function () { - assert.notEqual(files.indexOf('bin/www'), -1, 'should have bin/www file'); - assert.notEqual(files.indexOf('app.js'), -1, 'should have app.js file'); - assert.notEqual(files.indexOf('package.json'), -1, 'should have package.json file'); + assert.notEqual(ctx.files.indexOf('bin/www'), -1, 'should have bin/www file') + assert.notEqual(ctx.files.indexOf('app.js'), -1, 'should have app.js file') + assert.notEqual(ctx.files.indexOf('package.json'), -1, 'should have package.json file') }); it('should have less files', function () { - assert.notEqual(files.indexOf('public/stylesheets/style.less'), -1, 'should have style.less file'); + assert.notEqual(ctx.files.indexOf('public/stylesheets/style.less'), -1, 'should have style.less file') }); it('should have installable dependencies', function (done) { this.timeout(30000); - npmInstall(dir, done); + npmInstall(ctx.dir, done); }); it('should export an express app from app.js', function () { - var file = path.resolve(dir, 'app.js'); + var file = path.resolve(ctx.dir, 'app.js'); var app = require(file); assert.equal(typeof app, 'function'); assert.equal(typeof app.handle, 'function'); }); it('should respond to HTTP request', function (done) { - var file = path.resolve(dir, 'app.js'); + var file = path.resolve(ctx.dir, 'app.js'); var app = require(file); request(app) @@ -179,7 +256,7 @@ describe('express(1)', function () { }); it('should respond with stylesheet', function (done) { - var file = path.resolve(dir, 'app.js'); + var file = path.resolve(ctx.dir, 'app.js'); var app = require(file); request(app) @@ -189,55 +266,41 @@ describe('express(1)', function () { }); describe('stylus', function () { - var dir; - var files; - - mocha.before(function (done) { - createEnvironment(function (err, newDir) { - if (err) return done(err); - dir = newDir; - done(); - }); - }); - - mocha.after(function (done) { - this.timeout(30000); - cleanup(dir, done); - }); + var ctx = setupTestEnvironment(this.fullTitle()) it('should create basic app with stylus files', function (done) { - run(dir, ['--css', 'stylus'], function (err, stdout) { + run(ctx.dir, ['--css', 'stylus'], function (err, stdout) { if (err) return done(err); - files = parseCreatedFiles(stdout, dir); - assert.equal(files.length, 17, 'should have 17 files'); + ctx.files = parseCreatedFiles(stdout, ctx.dir) + assert.equal(ctx.files.length, 17, 'should have 17 files') done(); }); }); it('should have basic files', function () { - assert.notEqual(files.indexOf('bin/www'), -1, 'should have bin/www file'); - assert.notEqual(files.indexOf('app.js'), -1, 'should have app.js file'); - assert.notEqual(files.indexOf('package.json'), -1, 'should have package.json file'); + assert.notEqual(ctx.files.indexOf('bin/www'), -1, 'should have bin/www file') + assert.notEqual(ctx.files.indexOf('app.js'), -1, 'should have app.js file') + assert.notEqual(ctx.files.indexOf('package.json'), -1, 'should have package.json file') }); it('should have stylus files', function () { - assert.notEqual(files.indexOf('public/stylesheets/style.styl'), -1, 'should have style.styl file'); + assert.notEqual(ctx.files.indexOf('public/stylesheets/style.styl'), -1, 'should have style.styl file') }); it('should have installable dependencies', function (done) { this.timeout(30000); - npmInstall(dir, done); + npmInstall(ctx.dir, done); }); it('should export an express app from app.js', function () { - var file = path.resolve(dir, 'app.js'); + var file = path.resolve(ctx.dir, 'app.js'); var app = require(file); assert.equal(typeof app, 'function'); assert.equal(typeof app.handle, 'function'); }); it('should respond to HTTP request', function (done) { - var file = path.resolve(dir, 'app.js'); + var file = path.resolve(ctx.dir, 'app.js'); var app = require(file); request(app) @@ -246,7 +309,7 @@ describe('express(1)', function () { }); it('should respond with stylesheet', function (done) { - var file = path.resolve(dir, 'app.js'); + var file = path.resolve(ctx.dir, 'app.js'); var app = require(file); request(app) @@ -257,136 +320,65 @@ describe('express(1)', function () { }); describe('--ejs', function () { - var dir; - var files; - - mocha.before(function (done) { - createEnvironment(function (err, newDir) { - if (err) return done(err); - dir = newDir; - done(); - }); - }); - - mocha.after(function (done) { - this.timeout(30000); - cleanup(dir, done); - }); + var ctx = setupTestEnvironment(this.fullTitle()) it('should create basic app with ejs templates', function (done) { - run(dir, ['--ejs'], function (err, stdout) { + run(ctx.dir, ['--ejs'], function (err, stdout) { if (err) return done(err); - files = parseCreatedFiles(stdout, dir); - assert.equal(files.length, 16, 'should have 16 files'); + ctx.files = parseCreatedFiles(stdout, ctx.dir) + assert.equal(ctx.files.length, 16, 'should have 16 files') done(); }); }); it('should have basic files', function () { - assert.notEqual(files.indexOf('bin/www'), -1, 'should have bin/www file'); - assert.notEqual(files.indexOf('app.js'), -1, 'should have app.js file'); - assert.notEqual(files.indexOf('package.json'), -1, 'should have package.json file'); + assert.notEqual(ctx.files.indexOf('bin/www'), -1, 'should have bin/www file') + assert.notEqual(ctx.files.indexOf('app.js'), -1, 'should have app.js file') + assert.notEqual(ctx.files.indexOf('package.json'), -1, 'should have package.json file') }); it('should have ejs templates', function () { - assert.notEqual(files.indexOf('views/error.ejs'), -1, 'should have views/error.ejs file'); - assert.notEqual(files.indexOf('views/index.ejs'), -1, 'should have views/index.ejs file'); - }); - - it('should have installable dependencies', function (done) { - this.timeout(30000); - npmInstall(dir, done); - }); - - it('should export an express app from app.js', function () { - var file = path.resolve(dir, 'app.js'); - var app = require(file); - assert.equal(typeof app, 'function'); - assert.equal(typeof app.handle, 'function'); - }); - - it('should respond to HTTP request', function (done) { - var file = path.resolve(dir, 'app.js'); - var app = require(file); - - request(app) - .get('/') - .expect(200, /Express<\/title>/, done); - }); - - it('should generate a 404', function (done) { - var file = path.resolve(dir, 'app.js'); - var app = require(file); - - request(app) - .get('/does_not_exist') - .expect(404, /<h1>Not Found<\/h1>/, done); + assert.notEqual(ctx.files.indexOf('views/error.ejs'), -1, 'should have views/error.ejs file') + assert.notEqual(ctx.files.indexOf('views/index.ejs'), -1, 'should have views/index.ejs file') }); }); describe('--git', function () { - var dir; - var files; - - mocha.before(function (done) { - createEnvironment(function (err, newDir) { - if (err) return done(err); - dir = newDir; - done(); - }); - }); - - mocha.after(function (done) { - this.timeout(30000); - cleanup(dir, done); - }); + var ctx = setupTestEnvironment(this.fullTitle()) it('should create basic app with git files', function (done) { - run(dir, ['--git'], function (err, stdout) { + run(ctx.dir, ['--git'], function (err, stdout) { if (err) return done(err); - files = parseCreatedFiles(stdout, dir); - assert.equal(files.length, 18, 'should have 18 files'); + ctx.files = parseCreatedFiles(stdout, ctx.dir) + assert.equal(ctx.files.length, 18, 'should have 18 files') done(); }); }); it('should have basic files', function () { - assert.notEqual(files.indexOf('bin/www'), -1, 'should have bin/www file'); - assert.notEqual(files.indexOf('app.js'), -1, 'should have app.js file'); - assert.notEqual(files.indexOf('package.json'), -1, 'should have package.json file'); + assert.notEqual(ctx.files.indexOf('bin/www'), -1, 'should have bin/www file') + assert.notEqual(ctx.files.indexOf('app.js'), -1, 'should have app.js file') + assert.notEqual(ctx.files.indexOf('package.json'), -1, 'should have package.json file') }); it('should have .gitignore', function () { - assert.notEqual(files.indexOf('.gitignore'), -1, 'should have .gitignore file'); + assert.notEqual(ctx.files.indexOf('.gitignore'), -1, 'should have .gitignore file') }); it('should have jade templates', function () { - assert.notEqual(files.indexOf('views/error.jade'), -1); - assert.notEqual(files.indexOf('views/index.jade'), -1); - assert.notEqual(files.indexOf('views/layout.jade'), -1); + assert.notEqual(ctx.files.indexOf('views/error.jade'), -1) + assert.notEqual(ctx.files.indexOf('views/index.jade'), -1) + assert.notEqual(ctx.files.indexOf('views/layout.jade'), -1) }); }); describe('-h', function () { - var dir; - - mocha.before(function (done) { - createEnvironment(function (err, newDir) { - if (err) return done(err); - dir = newDir; - done(); - }); - }); - - mocha.after(function (done) { - this.timeout(30000); - cleanup(dir, done); - }); + var ctx = setupTestEnvironment(this.fullTitle()) it('should print usage', function (done) { - run(dir, ['-h'], function (err, stdout) { + run(ctx.dir, ['-h'], function (err, stdout) { if (err) return done(err); - var files = parseCreatedFiles(stdout, dir); + var files = parseCreatedFiles(stdout, ctx.dir); assert.equal(files.length, 0); assert.ok(/Usage: express/.test(stdout)); assert.ok(/--help/.test(stdout)); @@ -397,101 +389,44 @@ describe('express(1)', function () { }); describe('--hbs', function () { - var dir; - var files; - - mocha.before(function (done) { - createEnvironment(function (err, newDir) { - if (err) return done(err); - dir = newDir; - done(); - }); - }); - - mocha.after(function (done) { - this.timeout(30000); - cleanup(dir, done); - }); + var ctx = setupTestEnvironment(this.fullTitle()) it('should create basic app with hbs templates', function (done) { - run(dir, ['--hbs'], function (err, stdout) { + run(ctx.dir, ['--hbs'], function (err, stdout) { if (err) return done(err); - files = parseCreatedFiles(stdout, dir); - assert.equal(files.length, 17); + ctx.files = parseCreatedFiles(stdout, ctx.dir); + assert.equal(ctx.files.length, 17); done(); }); }); it('should have basic files', function () { - assert.notEqual(files.indexOf('bin/www'), -1); - assert.notEqual(files.indexOf('app.js'), -1); - assert.notEqual(files.indexOf('package.json'), -1); + assert.notEqual(ctx.files.indexOf('bin/www'), -1) + assert.notEqual(ctx.files.indexOf('app.js'), -1) + assert.notEqual(ctx.files.indexOf('package.json'), -1) }); it('should have hbs in package dependencies', function () { - var file = path.resolve(dir, 'package.json'); + var file = path.resolve(ctx.dir, 'package.json'); var contents = fs.readFileSync(file, 'utf8'); var dependencies = JSON.parse(contents).dependencies; assert.ok(typeof dependencies.hbs === 'string'); }); it('should have hbs templates', function () { - assert.notEqual(files.indexOf('views/error.hbs'), -1); - assert.notEqual(files.indexOf('views/index.hbs'), -1); - assert.notEqual(files.indexOf('views/layout.hbs'), -1); - }); - - it('should have installable dependencies', function (done) { - this.timeout(30000); - npmInstall(dir, done); - }); - - it('should export an express app from app.js', function () { - var file = path.resolve(dir, 'app.js'); - var app = require(file); - assert.equal(typeof app, 'function'); - assert.equal(typeof app.handle, 'function'); - }); - - it('should respond to HTTP request', function (done) { - var file = path.resolve(dir, 'app.js'); - var app = require(file); - - request(app) - .get('/') - .expect(200, /<title>Express<\/title>/, done); - }); - - it('should generate a 404', function (done) { - var file = path.resolve(dir, 'app.js'); - var app = require(file); - - request(app) - .get('/does_not_exist') - .expect(404, /<h1>Not Found<\/h1>/, done); + assert.notEqual(ctx.files.indexOf('views/error.hbs'), -1) + assert.notEqual(ctx.files.indexOf('views/index.hbs'), -1) + assert.notEqual(ctx.files.indexOf('views/layout.hbs'), -1) }); }); describe('--help', function () { - var dir; - - mocha.before(function (done) { - createEnvironment(function (err, newDir) { - if (err) return done(err); - dir = newDir; - done(); - }); - }); - - mocha.after(function (done) { - this.timeout(30000); - cleanup(dir, done); - }); + var ctx = setupTestEnvironment(this.fullTitle()) it('should print usage', function (done) { - run(dir, ['--help'], function (err, stdout) { + run(ctx.dir, ['--help'], function (err, stdout) { if (err) return done(err); - var files = parseCreatedFiles(stdout, dir); + var files = parseCreatedFiles(stdout, ctx.dir); assert.equal(files.length, 0); assert.ok(/Usage: express/.test(stdout)); assert.ok(/--help/.test(stdout)); @@ -500,29 +435,480 @@ describe('express(1)', function () { }); }); }); + + describe('--hogan', function () { + var ctx = setupTestEnvironment(this.fullTitle()) + + it('should create basic app with hogan templates', function (done) { + run(ctx.dir, ['--hogan'], function (err, stdout) { + if (err) return done(err) + ctx.files = parseCreatedFiles(stdout, ctx.dir) + assert.equal(ctx.files.length, 16) + done() + }) + }) + + it('should have basic files', function () { + assert.notEqual(ctx.files.indexOf('bin/www'), -1) + assert.notEqual(ctx.files.indexOf('app.js'), -1) + assert.notEqual(ctx.files.indexOf('package.json'), -1) + }) + + it('should have hjs in package dependencies', function () { + var file = path.resolve(ctx.dir, 'package.json') + var contents = fs.readFileSync(file, 'utf8') + var dependencies = JSON.parse(contents).dependencies + assert.ok(typeof dependencies.hjs === 'string') + }) + + it('should have hjs templates', function () { + assert.notEqual(ctx.files.indexOf('views/error.hjs'), -1) + assert.notEqual(ctx.files.indexOf('views/index.hjs'), -1) + }) + }) + + describe('--pug', function () { + var ctx = setupTestEnvironment(this.fullTitle()) + + it('should create basic app with pug templates', function (done) { + run(ctx.dir, ['--pug'], function (err, stdout) { + if (err) return done(err) + ctx.files = parseCreatedFiles(stdout, ctx.dir) + assert.equal(ctx.files.length, 17) + done() + }) + }) + + it('should have basic files', function () { + assert.notEqual(ctx.files.indexOf('bin/www'), -1) + assert.notEqual(ctx.files.indexOf('app.js'), -1) + assert.notEqual(ctx.files.indexOf('package.json'), -1) + }) + + it('should have pug in package dependencies', function () { + var file = path.resolve(ctx.dir, 'package.json') + var contents = fs.readFileSync(file, 'utf8') + var dependencies = JSON.parse(contents).dependencies + assert.ok(typeof dependencies.pug === 'string') + }) + + it('should have pug templates', function () { + assert.notEqual(ctx.files.indexOf('views/error.pug'), -1) + assert.notEqual(ctx.files.indexOf('views/index.pug'), -1) + assert.notEqual(ctx.files.indexOf('views/layout.pug'), -1) + }) + }) + + describe('--view <engine>', function () { + describe('(no engine)', function () { + var ctx = setupTestEnvironment(this.fullTitle()) + + it('should exit with code 1', function (done) { + runRaw(ctx.dir, ['--view'], function (err, code, stdout, stderr) { + if (err) return done(err) + assert.strictEqual(code, 1) + done() + }) + }) + + it('should print usage', function (done) { + runRaw(ctx.dir, ['--view'], function (err, code, stdout) { + if (err) return done(err) + assert.ok(/Usage: express/.test(stdout)) + assert.ok(/--help/.test(stdout)) + assert.ok(/--version/.test(stdout)) + done() + }) + }) + + it('should print argument missing', function (done) { + runRaw(ctx.dir, ['--view'], function (err, code, stdout, stderr) { + if (err) return done(err) + assert.ok(/error: option .* argument missing/.test(stderr)) + done() + }) + }) + }) + + describe('ejs', function () { + var ctx = setupTestEnvironment(this.fullTitle()) + + it('should create basic app with ejs templates', function (done) { + run(ctx.dir, ['--view', 'ejs'], function (err, stdout) { + if (err) return done(err) + ctx.files = parseCreatedFiles(stdout, ctx.dir) + assert.equal(ctx.files.length, 16, 'should have 16 files') + done() + }) + }) + + it('should have basic files', function () { + assert.notEqual(ctx.files.indexOf('bin/www'), -1, 'should have bin/www file') + assert.notEqual(ctx.files.indexOf('app.js'), -1, 'should have app.js file') + assert.notEqual(ctx.files.indexOf('package.json'), -1, 'should have package.json file') + }) + + it('should have ejs templates', function () { + assert.notEqual(ctx.files.indexOf('views/error.ejs'), -1, 'should have views/error.ejs file') + assert.notEqual(ctx.files.indexOf('views/index.ejs'), -1, 'should have views/index.ejs file') + }) + + it('should have installable dependencies', function (done) { + this.timeout(30000) + npmInstall(ctx.dir, done) + }) + + it('should export an express app from app.js', function () { + var file = path.resolve(ctx.dir, 'app.js') + var app = require(file) + assert.equal(typeof app, 'function') + assert.equal(typeof app.handle, 'function') + }) + + it('should respond to HTTP request', function (done) { + var file = path.resolve(ctx.dir, 'app.js') + var app = require(file) + + request(app) + .get('/') + .expect(200, /<title>Express<\/title>/, done) + }) + + it('should generate a 404', function (done) { + var file = path.resolve(ctx.dir, 'app.js') + var app = require(file) + + request(app) + .get('/does_not_exist') + .expect(404, /<h1>Not Found<\/h1>/, done) + }) + }) + + describe('hbs', function () { + var ctx = setupTestEnvironment(this.fullTitle()) + + it('should create basic app with hbs templates', function (done) { + run(ctx.dir, ['--view', 'hbs'], function (err, stdout) { + if (err) return done(err) + ctx.files = parseCreatedFiles(stdout, ctx.dir) + assert.equal(ctx.files.length, 17) + done() + }) + }) + + it('should have basic files', function () { + assert.notEqual(ctx.files.indexOf('bin/www'), -1) + assert.notEqual(ctx.files.indexOf('app.js'), -1) + assert.notEqual(ctx.files.indexOf('package.json'), -1) + }) + + it('should have hbs in package dependencies', function () { + var file = path.resolve(ctx.dir, 'package.json') + var contents = fs.readFileSync(file, 'utf8') + var dependencies = JSON.parse(contents).dependencies + assert.ok(typeof dependencies.hbs === 'string') + }) + + it('should have hbs templates', function () { + assert.notEqual(ctx.files.indexOf('views/error.hbs'), -1) + assert.notEqual(ctx.files.indexOf('views/index.hbs'), -1) + assert.notEqual(ctx.files.indexOf('views/layout.hbs'), -1) + }) + + it('should have installable dependencies', function (done) { + this.timeout(30000) + npmInstall(ctx.dir, done) + }) + + it('should export an express app from app.js', function () { + var file = path.resolve(ctx.dir, 'app.js') + var app = require(file) + assert.equal(typeof app, 'function') + assert.equal(typeof app.handle, 'function') + }) + + it('should respond to HTTP request', function (done) { + var file = path.resolve(ctx.dir, 'app.js') + var app = require(file) + + request(app) + .get('/') + .expect(200, /<title>Express<\/title>/, done) + }) + + it('should generate a 404', function (done) { + var file = path.resolve(ctx.dir, 'app.js') + var app = require(file) + + request(app) + .get('/does_not_exist') + .expect(404, /<h1>Not Found<\/h1>/, done) + }) + }) + + describe('hjs', function () { + var ctx = setupTestEnvironment(this.fullTitle()) + + it('should create basic app with hogan templates', function (done) { + run(ctx.dir, ['--view', 'hjs'], function (err, stdout) { + if (err) return done(err) + ctx.files = parseCreatedFiles(stdout, ctx.dir) + assert.equal(ctx.files.length, 16) + done() + }) + }) + + it('should have basic files', function () { + assert.notEqual(ctx.files.indexOf('bin/www'), -1) + assert.notEqual(ctx.files.indexOf('app.js'), -1) + assert.notEqual(ctx.files.indexOf('package.json'), -1) + }) + + it('should have hjs in package dependencies', function () { + var file = path.resolve(ctx.dir, 'package.json') + var contents = fs.readFileSync(file, 'utf8') + var dependencies = JSON.parse(contents).dependencies + assert.ok(typeof dependencies.hjs === 'string') + }) + + it('should have hjs templates', function () { + assert.notEqual(ctx.files.indexOf('views/error.hjs'), -1) + assert.notEqual(ctx.files.indexOf('views/index.hjs'), -1) + }) + + it('should have installable dependencies', function (done) { + this.timeout(30000) + npmInstall(ctx.dir, done) + }) + + it('should export an express app from app.js', function () { + var file = path.resolve(ctx.dir, 'app.js') + var app = require(file) + assert.equal(typeof app, 'function') + assert.equal(typeof app.handle, 'function') + }) + + it('should respond to HTTP request', function (done) { + var file = path.resolve(ctx.dir, 'app.js') + var app = require(file) + + // the "hjs" module has a global leak + this.runnable().globals('renderPartials') + + request(app) + .get('/') + .expect(200, /<title>Express<\/title>/, done) + }) + + it('should generate a 404', function (done) { + var file = path.resolve(ctx.dir, 'app.js') + var app = require(file) + + request(app) + .get('/does_not_exist') + .expect(404, /<h1>Not Found<\/h1>/, done) + }) + }) + + describe('pug', function () { + var ctx = setupTestEnvironment(this.fullTitle()) + + it('should create basic app with pug templates', function (done) { + run(ctx.dir, ['--view', 'pug'], function (err, stdout) { + if (err) return done(err) + ctx.files = parseCreatedFiles(stdout, ctx.dir) + assert.equal(ctx.files.length, 17) + done() + }) + }) + + it('should have basic files', function () { + assert.notEqual(ctx.files.indexOf('bin/www'), -1) + assert.notEqual(ctx.files.indexOf('app.js'), -1) + assert.notEqual(ctx.files.indexOf('package.json'), -1) + }) + + it('should have pug in package dependencies', function () { + var file = path.resolve(ctx.dir, 'package.json') + var contents = fs.readFileSync(file, 'utf8') + var dependencies = JSON.parse(contents).dependencies + assert.ok(typeof dependencies.pug === 'string') + }) + + it('should have pug templates', function () { + assert.notEqual(ctx.files.indexOf('views/error.pug'), -1) + assert.notEqual(ctx.files.indexOf('views/index.pug'), -1) + assert.notEqual(ctx.files.indexOf('views/layout.pug'), -1) + }) + + it('should have installable dependencies', function (done) { + this.timeout(30000) + npmInstall(ctx.dir, done) + }) + + it('should export an express app from app.js', function () { + var file = path.resolve(ctx.dir, 'app.js') + var app = require(file) + assert.equal(typeof app, 'function') + assert.equal(typeof app.handle, 'function') + }) + + it('should respond to HTTP request', function (done) { + var file = path.resolve(ctx.dir, 'app.js') + var app = require(file) + + request(app) + .get('/') + .expect(200, /<title>Express<\/title>/, done) + }) + + it('should generate a 404', function (done) { + var file = path.resolve(ctx.dir, 'app.js') + var app = require(file) + + request(app) + .get('/does_not_exist') + .expect(404, /<h1>Not Found<\/h1>/, done) + }) + }) + + describe('twig', function () { + var ctx = setupTestEnvironment(this.fullTitle()) + + it('should create basic app with twig templates', function (done) { + run(ctx.dir, ['--view', 'twig'], function (err, stdout) { + if (err) return done(err) + ctx.files = parseCreatedFiles(stdout, ctx.dir) + assert.equal(ctx.files.length, 17) + done() + }) + }) + + it('should have basic files', function () { + assert.notEqual(ctx.files.indexOf('bin/www'), -1) + assert.notEqual(ctx.files.indexOf('app.js'), -1) + assert.notEqual(ctx.files.indexOf('package.json'), -1) + }) + + it('should have twig in package dependencies', function () { + var file = path.resolve(ctx.dir, 'package.json') + var contents = fs.readFileSync(file, 'utf8') + var dependencies = JSON.parse(contents).dependencies + assert.ok(typeof dependencies.twig === 'string') + }) + + it('should have twig templates', function () { + assert.notEqual(ctx.files.indexOf('views/error.twig'), -1) + assert.notEqual(ctx.files.indexOf('views/index.twig'), -1) + assert.notEqual(ctx.files.indexOf('views/layout.twig'), -1) + }) + + it('should have installable dependencies', function (done) { + this.timeout(30000) + npmInstall(ctx.dir, done) + }) + + it('should export an express app from app.js', function () { + var file = path.resolve(ctx.dir, 'app.js') + var app = require(file) + assert.equal(typeof app, 'function') + assert.equal(typeof app.handle, 'function') + }) + + it('should respond to HTTP request', function (done) { + var file = path.resolve(ctx.dir, 'app.js') + var app = require(file) + + request(app) + .get('/') + .expect(200, /<title>Express<\/title>/, done) + }) + + it('should generate a 404', function (done) { + var file = path.resolve(ctx.dir, 'app.js') + var app = require(file) + + request(app) + .get('/does_not_exist') + .expect(404, /<h1>Not Found<\/h1>/, done) + }) + }) + + describe('vash', function () { + var ctx = setupTestEnvironment(this.fullTitle()) + + it('should create basic app with vash templates', function (done) { + run(ctx.dir, ['--view', 'vash'], function (err, stdout) { + if (err) return done(err) + ctx.files = parseCreatedFiles(stdout, ctx.dir) + assert.equal(ctx.files.length, 17) + done() + }) + }) + + it('should have basic files', function () { + assert.notEqual(ctx.files.indexOf('bin/www'), -1) + assert.notEqual(ctx.files.indexOf('app.js'), -1) + assert.notEqual(ctx.files.indexOf('package.json'), -1) + }) + + it('should have vash in package dependencies', function () { + var file = path.resolve(ctx.dir, 'package.json') + var contents = fs.readFileSync(file, 'utf8') + var dependencies = JSON.parse(contents).dependencies + assert.ok(typeof dependencies.vash === 'string') + }) + + it('should have vash templates', function () { + assert.notEqual(ctx.files.indexOf('views/error.vash'), -1) + assert.notEqual(ctx.files.indexOf('views/index.vash'), -1) + assert.notEqual(ctx.files.indexOf('views/layout.vash'), -1) + }) + + it('should have installable dependencies', function (done) { + this.timeout(30000) + npmInstall(ctx.dir, done) + }) + + it('should export an express app from app.js', function () { + var file = path.resolve(ctx.dir, 'app.js') + var app = require(file) + assert.equal(typeof app, 'function') + assert.equal(typeof app.handle, 'function') + }) + + it('should respond to HTTP request', function (done) { + var file = path.resolve(ctx.dir, 'app.js') + var app = require(file) + + request(app) + .get('/') + .expect(200, /<title>Express<\/title>/, done) + }) + + it('should generate a 404', function (done) { + var file = path.resolve(ctx.dir, 'app.js') + var app = require(file) + + request(app) + .get('/does_not_exist') + .expect(404, /<h1>Not Found<\/h1>/, done) + }) + }) + }) }); function cleanup(dir, callback) { if (typeof dir === 'function') { callback = dir; - dir = tempDir; + dir = TEMP_DIR; } - rimraf(tempDir, function (err) { + rimraf(dir, function (err) { callback(err); }); } -function createEnvironment(callback) { - var num = process.pid + Math.random(); - var dir = path.join(tempDir, ('app-' + num)); - - mkdirp(dir, function ondir(err) { - if (err) return callback(err); - callback(null, dir); - }); -} - function npmInstall(dir, callback) { var env = Object.create(null) @@ -567,6 +953,25 @@ function parseCreatedFiles(output, dir) { } function run(dir, args, callback) { + runRaw(dir, args, function (err, code, stdout, stderr) { + if (err) { + return callback(err); + } + + process.stderr.write(stripWarnings(stderr)) + + try { + assert.equal(stripWarnings(stderr), '') + assert.strictEqual(code, 0); + } catch (e) { + return callback(e); + } + + callback(null, stripColors(stdout)) + }); +} + +function runRaw(dir, args, callback) { var argv = [binPath].concat(args); var exec = process.argv[0]; var stderr = ''; @@ -582,7 +987,6 @@ function run(dir, args, callback) { }); child.stderr.setEncoding('utf8'); child.stderr.on('data', function ondata(str) { - process.stderr.write(str); stderr += str; }); @@ -590,15 +994,30 @@ function run(dir, args, callback) { child.on('error', callback); function onclose(code) { - var err = null; + callback(null, code, stdout, stderr); + } +} - try { - assert.equal(stderr, ''); - assert.strictEqual(code, 0); - } catch (e) { - err = e; - } +function setupTestEnvironment (name) { + var ctx = {} - callback(err, stdout.replace(/\x1b\[(\d+)m/g, '_color_$1_')); - } + before('create environment', function (done) { + ctx.dir = path.join(TEMP_DIR, name.replace(/[<>]/g, '')) + mkdirp(ctx.dir, done) + }) + + after('cleanup environment', function (done) { + this.timeout(30000) + cleanup(ctx.dir, done) + }) + + return ctx +} + +function stripColors (str) { + return str.replace(/\x1b\[(\d+)m/g, '_color_$1_') +} + +function stripWarnings (str) { + return str.replace(/\n(?: warning: [^\n]+\n)+\n/g, '') }