Stelescope/node_modules/ejs/test/ejs.js

833 lines
24 KiB
JavaScript
Raw Permalink Normal View History

2020-08-31 16:25:01 +00:00
/* jshint mocha: true */
/**
* Module dependencies.
*/
var ejs = require('..')
, fs = require('fs')
, read = fs.readFileSync
, assert = require('assert')
, path = require('path')
, LRU = require('lru-cache');
try {
fs.mkdirSync(__dirname + '/tmp');
} catch (ex) {
if (ex.code !== 'EEXIST') {
throw ex;
}
}
// From https://gist.github.com/pguillory/729616
function hook_stdio(stream, callback) {
var old_write = stream.write;
stream.write = (function() {
return function(string, encoding, fd) {
callback(string, encoding, fd);
};
})(stream.write);
return function() {
stream.write = old_write;
};
}
/**
* Load fixture `name`.
*/
function fixture(name) {
return read('test/fixtures/' + name, 'utf8');
}
/**
* User fixtures.
*/
var users = [];
users.push({name: 'geddy'});
users.push({name: 'neil'});
users.push({name: 'alex'});
suite('ejs.compile(str, options)', function () {
test('compile to a function', function () {
var fn = ejs.compile('<p>yay</p>');
assert.equal(fn(), '<p>yay</p>');
});
test('empty input works', function () {
var fn = ejs.compile('');
assert.equal(fn(), '');
});
test('throw if there are syntax errors', function () {
try {
ejs.compile(fixture('fail.ejs'));
}
catch (err) {
assert.ok(err.message.indexOf('compiling ejs') > -1);
try {
ejs.compile(fixture('fail.ejs'), {filename: 'fail.ejs'});
}
catch (err) {
assert.ok(err.message.indexOf('fail.ejs') > -1);
return;
}
}
throw new Error('no error reported when there should be');
});
test('allow customizing delimiter local var', function () {
var fn;
fn = ejs.compile('<p><?= name ?></p>', {delimiter: '?'});
assert.equal(fn({name: 'geddy'}), '<p>geddy</p>');
fn = ejs.compile('<p><:= name :></p>', {delimiter: ':'});
assert.equal(fn({name: 'geddy'}), '<p>geddy</p>');
fn = ejs.compile('<p><$= name $></p>', {delimiter: '$'});
assert.equal(fn({name: 'geddy'}), '<p>geddy</p>');
});
test('default to using ejs.delimiter', function () {
var fn;
ejs.delimiter = '&';
fn = ejs.compile('<p><&= name &></p>');
assert.equal(fn({name: 'geddy'}), '<p>geddy</p>');
fn = ejs.compile('<p><|= name |></p>', {delimiter: '|'});
assert.equal(fn({name: 'geddy'}), '<p>geddy</p>');
delete ejs.delimiter;
});
test('have a working client option', function () {
var fn
, str
, preFn;
fn = ejs.compile('<p><%= foo %></p>', {client: true});
str = fn.toString();
if (!process.env.running_under_istanbul) {
eval('var preFn = ' + str);
assert.equal(preFn({foo: 'bar'}), '<p>bar</p>');
}
});
test('support client mode without locals', function () {
var fn
, str
, preFn;
fn = ejs.compile('<p><%= "foo" %></p>', {client: true});
str = fn.toString();
if (!process.env.running_under_istanbul) {
eval('var preFn = ' + str);
assert.equal(preFn(), '<p>foo</p>');
}
});
test('not include rethrow() in client mode if compileDebug is false', function () {
var fn = ejs.compile('<p><%= "foo" %></p>', {
client: true
, compileDebug: false
});
// There could be a `rethrow` in the function declaration
assert((fn.toString().match(/rethrow/g) || []).length <= 1);
});
});
suite('ejs.render(str, data)', function () {
test('render the template', function () {
assert.equal(ejs.render('<p>yay</p>'), '<p>yay</p>');
});
test('empty input works', function () {
assert.equal(ejs.render(''), '');
});
test('undefined renders nothing escaped', function () {
assert.equal(ejs.render('<%= undefined %>'), '');
});
test('undefined renders nothing raw', function () {
assert.equal(ejs.render('<%- undefined %>'), '');
});
test('null renders nothing escaped', function () {
assert.equal(ejs.render('<%= null %>'), '');
});
test('null renders nothing raw', function () {
assert.equal(ejs.render('<%- null %>'), '');
});
test('zero-value data item renders something escaped', function () {
assert.equal(ejs.render('<%= 0 %>'), '0');
});
test('zero-value data object renders something raw', function () {
assert.equal(ejs.render('<%- 0 %>'), '0');
});
test('accept locals', function () {
assert.equal(ejs.render('<p><%= name %></p>', {name: 'geddy'}),
'<p>geddy</p>');
});
test('accept locals without using with() {}', function () {
assert.equal(ejs.render('<p><%= locals.name %></p>', {name: 'geddy'},
{_with: false}),
'<p>geddy</p>');
assert.throws(function() {
ejs.render('<p><%= name %></p>', {name: 'geddy'},
{_with: false});
}, /name is not defined/);
});
test('accept custom name for locals', function () {
ejs.localsName = 'it';
assert.equal(ejs.render('<p><%= it.name %></p>', {name: 'geddy'},
{_with: false}),
'<p>geddy</p>');
assert.throws(function() {
ejs.render('<p><%= name %></p>', {name: 'geddy'},
{_with: false});
}, /name is not defined/);
ejs.localsName = 'locals';
});
test('support caching', function () {
var file = __dirname + '/tmp/render.ejs'
, options = {cache: true, filename: file}
, out = ejs.render('<p>Old</p>', {}, options)
, expected = '<p>Old</p>';
assert.equal(out, expected);
// Assert no change, still in cache
out = ejs.render('<p>New</p>', {}, options);
assert.equal(out, expected);
});
test('support LRU caching', function () {
var oldCache = ejs.cache
, file = __dirname + '/tmp/render.ejs'
, options = {cache: true, filename: file}
, out
, expected = '<p>Old</p>';
// Switch to LRU
ejs.cache = LRU();
out = ejs.render('<p>Old</p>', {}, options);
assert.equal(out, expected);
// Assert no change, still in cache
out = ejs.render('<p>New</p>', {}, options);
assert.equal(out, expected);
// Restore system cache
ejs.cache = oldCache;
});
});
suite('ejs.renderFile(path, [data], [options], fn)', function () {
test('render a file', function(done) {
ejs.renderFile('test/fixtures/para.ejs', function(err, html) {
if (err) {
return done(err);
}
assert.equal(html, '<p>hey</p>\n');
done();
});
});
test('accept locals', function(done) {
var data = {name: 'fonebone'}
, options = {delimiter: '$'};
ejs.renderFile('test/fixtures/user.ejs', data, options, function(err, html) {
if (err) {
return done(err);
}
assert.equal(html, '<h1>fonebone</h1>\n');
done();
});
});
test('accept locals without using with() {}', function(done) {
var data = {name: 'fonebone'}
, options = {delimiter: '$', _with: false}
, doneCount = 0;
ejs.renderFile('test/fixtures/user-no-with.ejs', data, options,
function(err, html) {
if (err) {
if (doneCount === 2) {
return;
}
doneCount = 2;
return done(err);
}
assert.equal(html, '<h1>fonebone</h1>\n');
doneCount++;
if (doneCount === 2) {
done();
}
});
ejs.renderFile('test/fixtures/user.ejs', data, options, function(err) {
if (!err) {
if (doneCount === 2) {
return;
}
doneCount = 2;
return done(new Error('error not thrown'));
}
doneCount++;
if (doneCount === 2) {
done();
}
});
});
test('not catch err thrown by callback', function(done) {
var data = {name: 'fonebone'}
, options = {delimiter: '$'}
, counter = 0;
var d = require('domain').create();
d.on('error', function (err) {
assert.equal(counter, 1);
assert.equal(err.message, 'Exception in callback');
done();
});
d.run(function () {
// process.nextTick() needed to work around mochajs/mocha#513
//
// tl;dr: mocha doesn't support synchronous exception throwing in
// domains. Have to make it async. Ticket closed because: "domains are
// deprecated :D"
process.nextTick(function () {
ejs.renderFile('test/fixtures/user.ejs', data, options,
function(err) {
counter++;
if (err) {
assert.notEqual(err.message, 'Exception in callback');
return done(err);
}
throw new Error('Exception in callback');
});
});
});
});
test('support caching', function (done) {
var expected = '<p>Old</p>'
, file = __dirname + '/tmp/renderFile.ejs'
, options = {cache: true};
fs.writeFileSync(file, '<p>Old</p>');
ejs.renderFile(file, {}, options, function (err, out) {
if (err) {
done(err);
}
fs.writeFileSync(file, '<p>New</p>');
assert.equal(out, expected);
ejs.renderFile(file, {}, options, function (err, out) {
if (err) {
done(err);
}
// Assert no change, still in cache
assert.equal(out, expected);
done();
});
});
});
});
suite('cache specific', function () {
test('`clearCache` work properly', function () {
var expected = '<p>Old</p>'
, file = __dirname + '/tmp/clearCache.ejs'
, options = {cache: true, filename: file}
, out = ejs.render('<p>Old</p>', {}, options);
assert.equal(out, expected);
ejs.clearCache();
expected = '<p>New</p>';
out = ejs.render('<p>New</p>', {}, options);
assert.equal(out, expected);
});
test('`clearCache` work properly, LRU', function () {
var expected = '<p>Old</p>'
, oldCache = ejs.cache
, file = __dirname + '/tmp/clearCache.ejs'
, options = {cache: true, filename: file}
, out;
ejs.cache = LRU();
out = ejs.render('<p>Old</p>', {}, options);
assert.equal(out, expected);
ejs.clearCache();
expected = '<p>New</p>';
out = ejs.render('<p>New</p>', {}, options);
assert.equal(out, expected);
ejs.cache = oldCache;
});
test('LRU with cache-size 1', function () {
var oldCache = ejs.cache
, options
, out
, expected
, file;
ejs.cache = LRU(1);
file = __dirname + '/tmp/render1.ejs';
options = {cache: true, filename: file};
out = ejs.render('<p>File1</p>', {}, options);
expected = '<p>File1</p>';
assert.equal(out, expected);
// Same filename, different template, but output
// should be the same because cache
file = __dirname + '/tmp/render1.ejs';
options = {cache: true, filename: file};
out = ejs.render('<p>ChangedFile1</p>', {}, options);
expected = '<p>File1</p>';
assert.equal(out, expected);
// Different filiename -- output should be different,
// and previous cache-entry should be evicted
file = __dirname + '/tmp/render2.ejs';
options = {cache: true, filename: file};
out = ejs.render('<p>File2</p>', {}, options);
expected = '<p>File2</p>';
assert.equal(out, expected);
// Entry with first filename should now be out of cache,
// results should be different
file = __dirname + '/tmp/render1.ejs';
options = {cache: true, filename: file};
out = ejs.render('<p>ChangedFile1</p>', {}, options);
expected = '<p>ChangedFile1</p>';
assert.equal(out, expected);
ejs.cache = oldCache;
});
});
suite('<%', function () {
test('without semicolons', function () {
assert.equal(ejs.render(fixture('no.semicolons.ejs')),
fixture('no.semicolons.html'));
});
});
suite('<%=', function () {
test('escape &amp;<script>', function () {
assert.equal(ejs.render('<%= name %>', {name: '&nbsp;<script>'}),
'&amp;nbsp;&lt;script&gt;');
});
test('should escape \'', function () {
assert.equal(ejs.render('<%= name %>', {name: 'The Jones\'s'}),
'The Jones&#39;s');
});
test('should escape &foo_bar;', function () {
assert.equal(ejs.render('<%= name %>', {name: '&foo_bar;'}),
'&amp;foo_bar;');
});
});
suite('<%-', function () {
test('not escape', function () {
assert.equal(ejs.render('<%- name %>', {name: '<script>'}),
'<script>');
});
test('terminate gracefully if no close tag is found', function () {
try {
ejs.compile('<h1>oops</h1><%- name ->');
throw new Error('Expected parse failure');
}
catch (err) {
assert.ok(err.message.indexOf('Could not find matching close tag for') > -1);
}
});
});
suite('%>', function () {
test('produce newlines', function () {
assert.equal(ejs.render(fixture('newlines.ejs'), {users: users}),
fixture('newlines.html'));
});
test('works with `-%>` interspersed', function () {
assert.equal(ejs.render(fixture('newlines.mixed.ejs'), {users: users}),
fixture('newlines.mixed.html'));
});
test('consecutive tags work', function () {
assert.equal(ejs.render(fixture('consecutive-tags.ejs')),
fixture('consecutive-tags.html'));
});
});
suite('-%>', function () {
test('not produce newlines', function () {
assert.equal(ejs.render(fixture('no.newlines.ejs'), {users: users}),
fixture('no.newlines.html'));
});
test('stack traces work', function () {
try {
ejs.render(fixture('no.newlines.error.ejs'));
}
catch (e) {
if (e.message.indexOf('>> 4| <%= qdata %>') > -1) {
return;
}
throw e;
}
throw new Error('Expected ReferenceError');
});
});
suite('<%%', function () {
test('produce literals', function () {
assert.equal(ejs.render('<%%- "foo" %>'),
'<%- "foo" %>');
});
test('work without an end tag', function () {
assert.equal(ejs.render('<%%'), '<%');
assert.equal(ejs.render(fixture('literal.ejs'), {}, {delimiter: ' '}),
fixture('literal.html'));
});
});
suite('single quotes', function () {
test('not mess up the constructed function', function () {
assert.equal(ejs.render(fixture('single-quote.ejs')),
fixture('single-quote.html'));
});
});
suite('double quotes', function () {
test('not mess up the constructed function', function () {
assert.equal(ejs.render(fixture('double-quote.ejs')),
fixture('double-quote.html'));
});
});
suite('backslashes', function () {
test('escape', function () {
assert.equal(ejs.render(fixture('backslash.ejs')),
fixture('backslash.html'));
});
});
suite('messed up whitespace', function () {
test('work', function () {
assert.equal(ejs.render(fixture('messed.ejs'), {users: users}),
fixture('messed.html'));
});
});
suite('exceptions', function () {
test('produce useful stack traces', function () {
try {
ejs.render(fixture('error.ejs'), {}, {filename: 'error.ejs'});
}
catch (err) {
assert.equal(err.path, 'error.ejs');
assert.equal(err.stack.split('\n').slice(0, 8).join('\n'), fixture('error.out'));
return;
}
throw new Error('no error reported when there should be');
});
test('not include fancy stack info if compileDebug is false', function () {
try {
ejs.render(fixture('error.ejs'), {}, {
filename: 'error.ejs',
compileDebug: false
});
}
catch (err) {
assert.ok(!err.path);
assert.notEqual(err.stack.split('\n').slice(0, 8).join('\n'), fixture('error.out'));
return;
}
throw new Error('no error reported when there should be');
});
var unhook = null;
test('log JS source when debug is set', function (done) {
var out = ''
, needToExit = false;
unhook = hook_stdio(process.stdout, function (str) {
out += str;
if (needToExit) {
return;
}
if (out.indexOf('__output')) {
needToExit = true;
unhook();
unhook = null;
return done();
}
});
ejs.render(fixture('hello-world.ejs'), {}, {debug: true});
});
teardown(function() {
if (!unhook) {
return;
}
unhook();
unhook = null;
});
});
suite('rmWhitespace', function () {
test('works', function () {
assert.equal(ejs.render(fixture('rmWhitespace.ejs'), {}, {rmWhitespace: true}),
fixture('rmWhitespace.html'));
});
});
suite('include()', function () {
test('include ejs', function () {
var file = 'test/fixtures/include-simple.ejs';
assert.equal(ejs.render(fixture('include-simple.ejs'), {}, {filename: file}),
fixture('include-simple.html'));
});
test('include ejs fails without `filename`', function () {
try {
ejs.render(fixture('include-simple.ejs'));
}
catch (err) {
assert.ok(err.message.indexOf('requires the \'filename\' option') > -1);
return;
}
throw new Error('expected inclusion error');
});
test('strips BOM', function () {
assert.equal(
ejs.render('<%- include("fixtures/includes/bom.ejs") %>',
{}, {filename: path.join(__dirname, 'f.ejs')}),
'<p>This is a file with BOM.</p>\n');
});
test('include ejs with locals', function () {
var file = 'test/fixtures/include.ejs';
assert.equal(ejs.render(fixture('include.ejs'), {pets: users}, {filename: file, delimiter: '@'}),
fixture('include.html'));
});
test('include ejs with absolute path and locals', function () {
var file = 'test/fixtures/include-abspath.ejs';
assert.equal(ejs.render(fixture('include-abspath.ejs'),
{dir: path.join(__dirname, 'fixtures'), pets: users, path: path},
{filename: file, delimiter: '@'}),
fixture('include.html'));
});
test('work when nested', function () {
var file = 'test/fixtures/menu.ejs';
assert.equal(ejs.render(fixture('menu.ejs'), {pets: users}, {filename: file}),
fixture('menu.html'));
});
test('work with a variable path', function () {
var file = 'test/fixtures/menu_var.ejs',
includePath = 'includes/menu-item';
assert.equal(ejs.render(fixture('menu.ejs'), {pets: users, varPath: includePath}, {filename: file}),
fixture('menu.html'));
});
test('include arbitrary files as-is', function () {
var file = 'test/fixtures/include.css.ejs';
assert.equal(ejs.render(fixture('include.css.ejs'), {pets: users}, {filename: file}),
fixture('include.css.html'));
});
test('pass compileDebug to include', function () {
var file = 'test/fixtures/include.ejs'
, fn;
fn = ejs.compile(fixture('include.ejs'), {
filename: file
, delimiter: '@'
, compileDebug: false
});
try {
// Render without a required variable reference
fn({foo: 'asdf'});
}
catch(e) {
assert.equal(e.message, 'pets is not defined');
assert.ok(!e.path);
return;
}
throw new Error('no error reported when there should be');
});
test('is dynamic', function () {
fs.writeFileSync(__dirname + '/tmp/include.ejs', '<p>Old</p>');
var file = 'test/fixtures/include_cache.ejs'
, options = {filename: file}
, out = ejs.compile(fixture('include_cache.ejs'), options);
assert.equal(out(), '<p>Old</p>\n');
fs.writeFileSync(__dirname + '/tmp/include.ejs', '<p>New</p>');
assert.equal(out(), '<p>New</p>\n');
});
test('support caching', function () {
fs.writeFileSync(__dirname + '/tmp/include.ejs', '<p>Old</p>');
var file = 'test/fixtures/include_cache.ejs'
, options = {cache: true, filename: file}
, out = ejs.render(fixture('include_cache.ejs'), {}, options)
, expected = fixture('include_cache.html');
assert.equal(out, expected);
out = ejs.render(fixture('include_cache.ejs'), {}, options);
// No change, still in cache
assert.equal(out, expected);
fs.writeFileSync(__dirname + '/tmp/include.ejs', '<p>New</p>');
out = ejs.render(fixture('include_cache.ejs'), {}, options);
assert.equal(out, expected);
});
});
suite('preprocessor include', function () {
test('work', function () {
var file = 'test/fixtures/include_preprocessor.ejs';
assert.equal(ejs.render(fixture('include_preprocessor.ejs'), {pets: users}, {filename: file, delimiter: '@'}),
fixture('include_preprocessor.html'));
});
test('fails without `filename`', function () {
try {
ejs.render(fixture('include_preprocessor.ejs'), {pets: users}, {delimiter: '@'});
}
catch (err) {
assert.ok(err.message.indexOf('requires the \'filename\' option') > -1);
return;
}
throw new Error('expected inclusion error');
});
test('strips BOM', function () {
assert.equal(
ejs.render('<% include fixtures/includes/bom.ejs %>',
{}, {filename: path.join(__dirname, 'f.ejs')}),
'<p>This is a file with BOM.</p>\n');
});
test('work when nested', function () {
var file = 'test/fixtures/menu_preprocessor.ejs';
assert.equal(ejs.render(fixture('menu_preprocessor.ejs'), {pets: users}, {filename: file}),
fixture('menu_preprocessor.html'));
});
test('include arbitrary files as-is', function () {
var file = 'test/fixtures/include_preprocessor.css.ejs';
assert.equal(ejs.render(fixture('include_preprocessor.css.ejs'), {pets: users}, {filename: file}),
fixture('include_preprocessor.css.html'));
});
test('pass compileDebug to include', function () {
var file = 'test/fixtures/include_preprocessor.ejs'
, fn;
fn = ejs.compile(fixture('include_preprocessor.ejs'), {
filename: file
, delimiter: '@'
, compileDebug: false
});
try {
// Render without a required variable reference
fn({foo: 'asdf'});
}
catch(e) {
assert.equal(e.message, 'pets is not defined');
assert.ok(!e.path);
return;
}
throw new Error('no error reported when there should be');
});
test('is static', function () {
fs.writeFileSync(__dirname + '/tmp/include_preprocessor.ejs', '<p>Old</p>');
var file = 'test/fixtures/include_preprocessor_cache.ejs'
, options = {filename: file}
, out = ejs.compile(fixture('include_preprocessor_cache.ejs'), options);
assert.equal(out(), '<p>Old</p>\n');
fs.writeFileSync(__dirname + '/tmp/include_preprocessor.ejs', '<p>New</p>');
assert.equal(out(), '<p>Old</p>\n');
});
test('support caching', function () {
fs.writeFileSync(__dirname + '/tmp/include_preprocessor.ejs', '<p>Old</p>');
var file = 'test/fixtures/include_preprocessor_cache.ejs'
, options = {cache: true, filename: file}
, out = ejs.render(fixture('include_preprocessor_cache.ejs'), {}, options)
, expected = fixture('include_preprocessor_cache.html');
assert.equal(out, expected);
fs.writeFileSync(__dirname + '/tmp/include_preprocessor.ejs', '<p>New</p>');
out = ejs.render(fixture('include_preprocessor_cache.ejs'), {}, options);
assert.equal(out, expected);
});
});
suite('comments', function () {
test('fully render with comments removed', function () {
assert.equal(ejs.render(fixture('comments.ejs')),
fixture('comments.html'));
});
});
suite('require', function () {
// Only works with inline/preprocessor includes
test('allow ejs templates to be required as node modules', function () {
var file = 'test/fixtures/include_preprocessor.ejs'
, template = require(__dirname + '/fixtures/menu_preprocessor.ejs');
if (!process.env.running_under_istanbul) {
assert.equal(template({filename: file, pets: users}),
fixture('menu_preprocessor.html'));
}
});
});
suite('examples', function () {
function noop () {}
fs.readdirSync('examples').forEach(function (f) {
if (!/\.js$/.test(f)) {
return;
}
suite(f, function () {
test('doesn\'t throw any errors', function () {
var stderr = hook_stdio(process.stderr, noop)
, stdout = hook_stdio(process.stdout, noop);
try {
require('../examples/' + f);
}
catch (ex) {
stdout();
stderr();
throw ex;
}
stdout();
stderr();
});
});
});
});