620 lines
19 KiB
JavaScript
620 lines
19 KiB
JavaScript
'use strict';
|
|
|
|
var fs = require('fs');
|
|
var path = require('path');
|
|
var assert = require('assert');
|
|
var cp = require('child_process');
|
|
var mkdirp = require('mkdirp');
|
|
var rimraf = require('rimraf');
|
|
|
|
// Sets directory to output coverage data to
|
|
// Incremented every time getRunner() is called.
|
|
var covCount = 1;
|
|
var isIstanbul = process.env.running_under_istanbul;
|
|
|
|
/*
|
|
* I/O utilities for temporary directory.
|
|
*/
|
|
function j(paths) {
|
|
return path.join.apply(path, paths);
|
|
}
|
|
|
|
function t(paths) {
|
|
paths = Array.isArray(paths) ? paths : [paths];
|
|
var args = [__dirname, 'temp'].concat(paths);
|
|
return j(args);
|
|
}
|
|
|
|
function r(paths) {
|
|
return fs.readFileSync(t(paths), 'utf8');
|
|
}
|
|
|
|
function rs(paths) {
|
|
return fs.createReadStream(t(paths));
|
|
}
|
|
|
|
function w(paths, content) {
|
|
return fs.writeFileSync(t(paths), content);
|
|
}
|
|
|
|
function a(paths, content) {
|
|
return fs.appendFileSync(t(paths), content);
|
|
}
|
|
|
|
function u(paths) {
|
|
return fs.unlinkSync(t(paths));
|
|
}
|
|
|
|
/**
|
|
* Gets an array containing the routine to run the pug CLI. If this file is
|
|
* being processed with istanbul then this function will return a routine
|
|
* asking istanbul to store coverage data to a unique directory
|
|
* (cov-pt<covCount>/).
|
|
*/
|
|
function getRunner() {
|
|
var pugExe = j([__dirname, '..', 'index.js']);
|
|
|
|
if (!isIstanbul) return [process.argv[0], [pugExe]];
|
|
else {
|
|
return [ 'istanbul',
|
|
[ 'cover',
|
|
'--print', 'none',
|
|
'--report', 'none',
|
|
'--root', process.cwd(),
|
|
'--dir', process.cwd() + '/cov-pt' + (covCount++),
|
|
pugExe,
|
|
'--' ] ];
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Run Pug CLI.
|
|
*
|
|
* @param args Array of arguments
|
|
* @param [stdin] Stream of standard input
|
|
* @param callback Function to call when the process finishes
|
|
*/
|
|
function run(args, stdin, callback) {
|
|
if (arguments.length === 2) {
|
|
callback = stdin;
|
|
stdin = null;
|
|
}
|
|
var runner = getRunner();
|
|
var proc = cp.execFile(runner[0], runner[1].concat(args), {
|
|
cwd: t([])
|
|
}, callback);
|
|
if (stdin) stdin.pipe(proc.stdin);
|
|
}
|
|
|
|
/**
|
|
* Set timing limits for a test case
|
|
*/
|
|
function timing(testCase) {
|
|
if (isIstanbul) {
|
|
testCase.timeout(20000);
|
|
testCase.slow(3000);
|
|
} else {
|
|
testCase.timeout(12500);
|
|
testCase.slow(2000);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Make temporary directories
|
|
*/
|
|
rimraf.sync(t([]));
|
|
mkdirp.sync(t(['depwatch']));
|
|
mkdirp.sync(t(['inputs', 'level-1-1']));
|
|
mkdirp.sync(t(['inputs', 'level-1-2']));
|
|
mkdirp.sync(t(['outputs', 'level-1-1']));
|
|
mkdirp.sync(t(['outputs', 'level-1-2']));
|
|
|
|
/*
|
|
* CLI utilities
|
|
*/
|
|
describe('miscellanea', function () {
|
|
timing(this);
|
|
it('--version', function (done) {
|
|
run(['-V'], function (err, stdout) {
|
|
if (err) done(err);
|
|
assert.equal(stdout.trim(), 'pug version: ' + require('pug/package.json').version + '\npug-cli version: ' + require('../package.json').version);
|
|
run(['--version'], function (err, stdout) {
|
|
if (err) done(err);
|
|
assert.equal(stdout.trim(), 'pug version: ' + require('pug/package.json').version + '\npug-cli version: ' + require('../package.json').version);
|
|
done()
|
|
});
|
|
});
|
|
});
|
|
it('--help', function (done) {
|
|
// only check that it doesn't crash
|
|
run(['-h'], function (err, stdout) {
|
|
if (err) done(err);
|
|
run(['--help'], function (err, stdout) {
|
|
if (err) done(err);
|
|
done()
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('HTML output', function () {
|
|
timing(this);
|
|
it('works', function (done) {
|
|
w('input.pug', '.foo bar');
|
|
w('input.html', '<p>output not written</p>');
|
|
|
|
run(['--no-debug', 'input.pug'], function (err) {
|
|
if (err) return done(err);
|
|
var html = r('input.html');
|
|
assert(html === '<div class="foo">bar</div>');
|
|
done();
|
|
});
|
|
});
|
|
it('--extension', function (done) {
|
|
w('input.pug', '.foo bar');
|
|
w('input.special-html', '<p>output not written</p>');
|
|
|
|
run(['--no-debug', '-E', 'special-html', 'input.pug'], function (err) {
|
|
if (err) return done(err);
|
|
var html = r('input.special-html');
|
|
assert(html === '<div class="foo">bar</div>');
|
|
done();
|
|
});
|
|
});
|
|
it('--basedir', function (done) {
|
|
w('input.pug', 'extends /dependency1.pug');
|
|
w('input.html', '<p>output not written</p>');
|
|
run(['--no-debug', '-b', j([__dirname, 'dependencies']), 'input.pug'], function (err, stdout) {
|
|
if (err) return done(err);
|
|
var html = r('input.html');
|
|
assert.equal(html, '<html><body></body></html>');
|
|
done();
|
|
});
|
|
});
|
|
context('--obj', function () {
|
|
it('JavaScript syntax works', function (done) {
|
|
w('input.pug', '.foo= loc');
|
|
w('input.html', '<p>output not written</p>');
|
|
run(['--no-debug', '--obj', "{'loc':'str'}", 'input.pug'], function (err) {
|
|
if (err) return done(err);
|
|
var html = r('input.html');
|
|
assert(html === '<div class="foo">str</div>');
|
|
done();
|
|
});
|
|
});
|
|
it('JavaScript syntax does not accept UTF newlines', function (done) {
|
|
w('input.pug', '.foo= loc');
|
|
w('input.html', '<p>output not written</p>');
|
|
run(['--no-debug', '--obj', "{'loc':'st\u2028r'}", 'input.pug'], function (err) {
|
|
if (!err) return done(new Error('expecting error'));
|
|
done();
|
|
});
|
|
});
|
|
it('JSON syntax accept UTF newlines', function (done) {
|
|
w('input.pug', '.foo= loc');
|
|
w('input.html', '<p>output not written</p>');
|
|
run(['--no-debug', '--obj', '{"loc":"st\u2028r"}', 'input.pug'], function (err) {
|
|
if (err) return done(err);
|
|
var html = r('input.html');
|
|
assert.equal(html, '<div class="foo">st\u2028r</div>');
|
|
done();
|
|
});
|
|
});
|
|
it('JSON file', function (done) {
|
|
w('obj.json', '{"loc":"str"}');
|
|
w('input.pug', '.foo= loc');
|
|
w('input.html', '<p>output not written</p>');
|
|
run(['--no-debug', '--obj', 'obj.json', 'input.pug'], function (err) {
|
|
if (err) return done(err);
|
|
var html = r('input.html');
|
|
assert(html === '<div class="foo">str</div>');
|
|
done();
|
|
});
|
|
});
|
|
it('JavaScript module', function (done) {
|
|
w('obj.js', 'module.exports = {loc: "str"};');
|
|
w('input.pug', '.foo= loc');
|
|
w('input.html', '<p>output not written</p>');
|
|
run(['--no-debug', '--obj', 'obj.js', 'input.pug'], function (err) {
|
|
if (err) return done(err);
|
|
var html = r('input.html');
|
|
assert(html === '<div class="foo">str</div>');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
it('stdio', function (done) {
|
|
w('input.pug', '.foo bar');
|
|
run(['--no-debug'], rs('input.pug'), function (err, stdout, stderr) {
|
|
if (err) return done(err);
|
|
assert(stdout === '<div class="foo">bar</div>');
|
|
done();
|
|
});
|
|
});
|
|
context('--out', function () {
|
|
it('works', function (done) {
|
|
w('input.pug', '.foo bar');
|
|
w('input.html', '<p>output not written</p>');
|
|
run(['--no-debug', '--out', 'outputs', 'input.pug'], function (err) {
|
|
if (err) return done(err);
|
|
var html = r(['outputs', 'input.html']);
|
|
assert(html === '<div class="foo">bar</div>');
|
|
done();
|
|
});
|
|
});
|
|
it('works when input is a directory', function (done) {
|
|
w(['inputs', 'input.pug'], '.foo bar 1');
|
|
w(['inputs', 'level-1-1', 'input.pug'], '.foo bar 1-1');
|
|
w(['inputs', 'level-1-2', 'input.pug'], '.foo bar 1-2');
|
|
w(['outputs', 'input.html'], 'BIG FAT HEN 1');
|
|
w(['outputs', 'level-1-1', 'input.html'], 'BIG FAT HEN 1-1');
|
|
w(['outputs', 'level-1-2', 'input.html'], 'BIG FAT HEN 1-2');
|
|
|
|
run(['--no-debug', '--hierarchy', '--out', 'outputs', 'inputs'], function (err) {
|
|
if (err) return done(err);
|
|
var html = r(['outputs', 'input.html']);
|
|
assert(html === '<div class="foo">bar 1</div>');
|
|
var html = r(['outputs', 'level-1-1', 'input.html']);
|
|
assert(html === '<div class="foo">bar 1-1</div>');
|
|
var html = r(['outputs', 'level-1-2', 'input.html']);
|
|
assert(html === '<div class="foo">bar 1-2</div>');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
it('--silent', function (done) {
|
|
w('input.pug', '.foo bar');
|
|
w('input.html', '<p>output not written</p>');
|
|
run(['--no-debug', '-s', 'input.pug'], function (err, stdout) {
|
|
if (err) return done(err);
|
|
var html = r('input.html');
|
|
assert.equal(html, '<div class="foo">bar</div>');
|
|
assert.equal(stdout, '');
|
|
|
|
w('input.html', '<p>output not written</p>');
|
|
run(['--no-debug', '--silent', 'input.pug'], function (err, stdout) {
|
|
if (err) return done(err);
|
|
var html = r('input.html');
|
|
assert.equal(html, '<div class="foo">bar</div>');
|
|
assert.equal(stdout, '');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('client JavaScript output', function () {
|
|
timing(this);
|
|
it('works', function (done) {
|
|
w('input.pug', '.foo bar');
|
|
w('input.js', 'throw new Error("output not written");');
|
|
run(['--no-debug', '--client', 'input.pug'], function (err) {
|
|
if (err) return done(err);
|
|
var template = Function('', r('input.js') + ';return template;')();
|
|
assert(template() === '<div class="foo">bar</div>');
|
|
done();
|
|
});
|
|
});
|
|
it('--name', function (done) {
|
|
w('input.pug', '.foo bar');
|
|
w('input.js', 'throw new Error("output not written");');
|
|
run(['--no-debug', '--client', '--name', 'myTemplate', 'input.pug'], function (err) {
|
|
if (err) return done(err);
|
|
var template = Function('', r('input.js') + ';return myTemplate;')();
|
|
assert(template() === '<div class="foo">bar</div>');
|
|
done();
|
|
});
|
|
});
|
|
it('--name --extension', function (done) {
|
|
w('input.pug', '.foo bar');
|
|
w('input.special-js', 'throw new Error("output not written");');
|
|
run(['--no-debug', '--client', '-E', 'special-js', 'input.pug'], function (err) {
|
|
if (err) return done(err);
|
|
var template = Function('', r('input.special-js') + ';return template;')();
|
|
assert(template() === '<div class="foo">bar</div>');
|
|
done();
|
|
});
|
|
});
|
|
it('stdio', function (done) {
|
|
w('input.pug', '.foo bar');
|
|
w('input.js', 'throw new Error("output not written");');
|
|
run(['--no-debug', '--client'], rs('input.pug'), function (err, stdout) {
|
|
if (err) return done(err);
|
|
var template = Function('', stdout + ';return template;')();
|
|
assert(template() === '<div class="foo">bar</div>');
|
|
done();
|
|
});
|
|
});
|
|
it('--name-after-file', function (done) {
|
|
w('input-file.pug', '.foo bar');
|
|
w('input-file.js', 'throw new Error("output not written");');
|
|
run(['--no-debug', '--client', '--name-after-file', 'input-file.pug'], function (err, stdout, stderr) {
|
|
if (err) return done(err);
|
|
var template = Function('', r('input-file.js') + ';return inputFileTemplate;')();
|
|
assert(template() === '<div class="foo">bar</div>');
|
|
return done();
|
|
});
|
|
});
|
|
it('--name-after-file _InPuTwIthWEiRdNaMME.pug', function (done) {
|
|
w('_InPuTwIthWEiRdNaMME.pug', '.foo bar');
|
|
w('_InPuTwIthWEiRdNaMME.js', 'throw new Error("output not written");');
|
|
run(['--no-debug', '--client', '--name-after-file', '_InPuTwIthWEiRdNaMME.pug'], function (err, stdout, stderr) {
|
|
if (err) return done(err);
|
|
var template = Function('', r('_InPuTwIthWEiRdNaMME.js') + ';return InputwithweirdnammeTemplate;')();
|
|
assert(template() === '<div class="foo">bar</div>');
|
|
return done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('--watch', function () {
|
|
var watchProc;
|
|
var stdout = '';
|
|
|
|
function cleanup() {
|
|
stdout = '';
|
|
if (!watchProc) return;
|
|
|
|
watchProc.stderr.removeAllListeners('data');
|
|
watchProc.stdout.removeAllListeners('data');
|
|
watchProc.removeAllListeners('error');
|
|
watchProc.removeAllListeners('close');
|
|
}
|
|
|
|
after(function () {
|
|
cleanup();
|
|
watchProc.kill('SIGINT');
|
|
watchProc = null;
|
|
});
|
|
|
|
beforeEach(cleanup);
|
|
|
|
afterEach(function (done) {
|
|
// pug --watch can only detect changes that are at least 1 second apart
|
|
setTimeout(done, 1000);
|
|
});
|
|
|
|
it('pass 1: initial compilation', function (done) {
|
|
timing(this);
|
|
|
|
w('input-file.pug', '.foo bar');
|
|
w('input-file.js', 'throw new Error("output not written (pass 1)");');
|
|
var cmd = getRunner();
|
|
cmd[1].push('--no-debug', '--client', '--name-after-file', '--watch', 'input-file.pug');
|
|
watchProc = cp.spawn(cmd[0], cmd[1], {
|
|
cwd: t([])
|
|
});
|
|
|
|
watchProc.stdout.setEncoding('utf8');
|
|
watchProc.stderr.setEncoding('utf8');
|
|
watchProc.on('error', done);
|
|
watchProc.stdout.on('data', function(buf) {
|
|
stdout += buf;
|
|
if (/rendered/.test(stdout)) {
|
|
cleanup();
|
|
|
|
var template = Function('', r('input-file.js') + ';return inputFileTemplate;')();
|
|
assert(template() === '<div class="foo">bar</div>');
|
|
|
|
return done();
|
|
}
|
|
});
|
|
});
|
|
it('pass 2: change the file', function (done) {
|
|
w('input-file.js', 'throw new Error("output not written (pass 2)");');
|
|
|
|
watchProc.on('error', done);
|
|
watchProc.stdout.on('data', function(buf) {
|
|
stdout += buf;
|
|
if (/rendered/.test(stdout)) {
|
|
cleanup();
|
|
|
|
var template = Function('', r('input-file.js') + ';return inputFileTemplate;')();
|
|
assert(template() === '<div class="foo">baz</div>');
|
|
|
|
return done();
|
|
}
|
|
});
|
|
|
|
w('input-file.pug', '.foo baz');
|
|
});
|
|
it('pass 3: remove the file then add it back', function (done) {
|
|
w('input-file.js', 'throw new Error("output not written (pass 3)");');
|
|
|
|
watchProc.on('error', done)
|
|
watchProc.stdout.on('data', function(buf) {
|
|
stdout += buf;
|
|
if (/rendered/.test(stdout)) {
|
|
cleanup();
|
|
|
|
var template = Function('', r('input-file.js') + ';return inputFileTemplate;')();
|
|
assert(template() === '<div class="foo">bat</div>');
|
|
|
|
return done();
|
|
}
|
|
});
|
|
|
|
u('input-file.pug');
|
|
setTimeout(function () {
|
|
w('input-file.pug', '.foo bat');
|
|
}, 250);
|
|
});
|
|
it('pass 4: intentional errors in the pug file', function (done) {
|
|
var stderr = '';
|
|
var errored = false;
|
|
|
|
watchProc.on('error', done);
|
|
watchProc.on('close', function() {
|
|
errored = true;
|
|
return done(new Error('Pug should not terminate in watch mode'));
|
|
});
|
|
watchProc.stdout.on('data', function(buf) {
|
|
stdout += buf;
|
|
if (/rendered/.test(stdout)) {
|
|
stdout = '';
|
|
return done(new Error('Pug compiles an erroneous file w/o error'));
|
|
}
|
|
});
|
|
watchProc.stderr.on('data', function(buf) {
|
|
stderr += buf;
|
|
if (!/Invalid indentation/.test(stderr)) return;
|
|
stderr = '';
|
|
var template = Function('', r('input-file.js') + ';return inputFileTemplate;')();
|
|
assert(template() === '<div class="foo">bat</div>');
|
|
|
|
watchProc.stderr.removeAllListeners('data');
|
|
watchProc.stdout.removeAllListeners('data');
|
|
watchProc.removeAllListeners('error');
|
|
watchProc.removeAllListeners('exit');
|
|
// The stderr event will always fire sooner than the close event.
|
|
// Wait for it.
|
|
setTimeout(function() {
|
|
if (!errored) done();
|
|
}, 100);
|
|
});
|
|
|
|
w('input-file.pug', [
|
|
'div',
|
|
' div',
|
|
'\tarticle'
|
|
].join('\n'));
|
|
});
|
|
});
|
|
|
|
describe('--watch with dependencies', function () {
|
|
var watchProc;
|
|
var stdout = '';
|
|
|
|
before(function () {
|
|
function copy(file) {
|
|
w(['depwatch', file],
|
|
fs.readFileSync(j([__dirname, 'dependencies', file])));
|
|
}
|
|
copy('include2.pug');
|
|
copy('dependency2.pug');
|
|
copy('dependency3.pug');
|
|
});
|
|
|
|
function cleanup() {
|
|
stdout = '';
|
|
|
|
if (!watchProc) return;
|
|
|
|
watchProc.stderr.removeAllListeners('data');
|
|
watchProc.stdout.removeAllListeners('data');
|
|
watchProc.removeAllListeners('error');
|
|
watchProc.removeAllListeners('close');
|
|
}
|
|
|
|
after(function () {
|
|
cleanup();
|
|
watchProc.kill('SIGINT');
|
|
watchProc = null;
|
|
});
|
|
|
|
beforeEach(cleanup);
|
|
|
|
afterEach(function (done) {
|
|
// pug --watch can only detect changes that are at least 1 second apart
|
|
setTimeout(done, 1000);
|
|
});
|
|
|
|
it('pass 1: initial compilation', function (done) {
|
|
timing(this);
|
|
|
|
w(['depwatch', 'include2.html'], 'output not written (pass 1)');
|
|
w(['depwatch', 'dependency2.html'], 'output not written (pass 1)');
|
|
var cmd = getRunner();
|
|
cmd[1].push('--watch', 'include2.pug', 'dependency2.pug');
|
|
watchProc = cp.spawn(cmd[0], cmd[1], {
|
|
cwd: t('depwatch')
|
|
});
|
|
|
|
watchProc.stdout.setEncoding('utf8');
|
|
watchProc.stderr.setEncoding('utf8');
|
|
watchProc.on('error', done);
|
|
watchProc.stdout.on('data', function(buf) {
|
|
stdout += buf;
|
|
if ((stdout.match(/rendered/g) || []).length === 2) {
|
|
cleanup();
|
|
|
|
var output = r(['depwatch', 'include2.html']);
|
|
assert.equal(output.trim(), '<strong>dependency3</strong>');
|
|
output = r(['depwatch', 'dependency2.html']);
|
|
assert.equal(output.trim(), '<strong>dependency3</strong>');
|
|
|
|
return done();
|
|
}
|
|
});
|
|
});
|
|
it('pass 2: change a dependency', function (done) {
|
|
timing(this);
|
|
|
|
w(['depwatch', 'include2.html'], 'output not written (pass 2)');
|
|
w(['depwatch', 'dependency2.html'], 'output not written (pass 2)');
|
|
|
|
watchProc.on('error', done);
|
|
watchProc.stdout.on('data', function(buf) {
|
|
stdout += buf;
|
|
if ((stdout.match(/rendered/g) || []).length === 2) {
|
|
cleanup();
|
|
|
|
var output = r(['depwatch', 'include2.html']);
|
|
assert.equal(output.trim(), '<strong>dependency3</strong><p>Hey</p>');
|
|
output = r(['depwatch', 'dependency2.html']);
|
|
assert.equal(output.trim(), '<strong>dependency3</strong><p>Hey</p>');
|
|
|
|
return done();
|
|
}
|
|
});
|
|
|
|
a(['depwatch', 'dependency2.pug'], '\np Hey\n');
|
|
});
|
|
it('pass 3: change a deeper dependency', function (done) {
|
|
timing(this);
|
|
|
|
w(['depwatch', 'include2.html'], 'output not written (pass 3)');
|
|
w(['depwatch', 'dependency2.html'], 'output not written (pass 3)');
|
|
|
|
watchProc.on('error', done)
|
|
watchProc.stdout.on('data', function(buf) {
|
|
stdout += buf;
|
|
if ((stdout.match(/rendered/g) || []).length === 2) {
|
|
cleanup();
|
|
|
|
var output = r(['depwatch', 'include2.html']);
|
|
assert.equal(output.trim(), '<strong>dependency3</strong><p>Foo</p><p>Hey</p>');
|
|
output = r(['depwatch', 'dependency2.html']);
|
|
assert.equal(output.trim(), '<strong>dependency3</strong><p>Foo</p><p>Hey</p>');
|
|
|
|
return done();
|
|
}
|
|
});
|
|
|
|
a(['depwatch', 'dependency3.pug'], '\np Foo\n');
|
|
});
|
|
it('pass 4: change main file', function (done) {
|
|
timing(this);
|
|
|
|
w(['depwatch', 'include2.html'], 'output not written (pass 4)');
|
|
w(['depwatch', 'dependency2.html'], 'output not written (pass 4)');
|
|
|
|
watchProc.on('error', done);
|
|
watchProc.stdout.on('data', function(buf) {
|
|
stdout += buf;
|
|
if ((stdout.match(/rendered/g) || []).length === 1) {
|
|
cleanup();
|
|
|
|
var output = r(['depwatch', 'include2.html']);
|
|
assert.equal(output.trim(), '<strong>dependency3</strong><p>Foo</p><p>Hey</p><p>Baz</p>');
|
|
output = r(['depwatch', 'dependency2.html']);
|
|
assert.equal(output.trim(), 'output not written (pass 4)');
|
|
|
|
return done();
|
|
}
|
|
});
|
|
|
|
a(['depwatch', 'include2.pug'], '\np Baz\n');
|
|
});
|
|
});
|