'use strict'; var assert = require('assert'); var fs = require('fs'); var path = require('path'); var pug = require('../'); var perfTest = fs.readFileSync(__dirname + '/fixtures/perf.pug', 'utf8'); try { fs.mkdirSync(__dirname + '/temp'); } catch (ex) { if (ex.code !== 'EEXIST') { throw ex; } } describe('pug', function() { describe('unit tests with .render()', function() { it('should support doctypes', function() { assert.equal( '', pug.render('doctype xml') ); assert.equal('', pug.render('doctype html')); assert.equal('', pug.render('doctype foo bar baz')); assert.equal('', pug.render('doctype html')); assert.equal('', pug.render('doctype', {doctype: 'html'})); assert.equal( '', pug.render('doctype html', {doctype: 'xml'}) ); assert.equal('', pug.render('html')); assert.equal( '', pug.render('html', {doctype: 'html'}) ); assert.equal( '', pug.render('doctype html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN') ); }); it('should support Buffers', function() { assert.equal('

foo

', pug.render(Buffer.from('p foo'))); }); it('should support line endings', function() { var src = ['p', 'div', 'img']; var html = ['

', '
', ''].join(''); assert.equal(html, pug.render(src.join('\n'))); assert.equal(html, pug.render(src.join('\r'))); assert.equal(html, pug.render(src.join('\r\n'))); html = ['

', '
', ''].join(''); assert.equal(html, pug.render(src.join('\n'), {doctype: 'html'})); assert.equal(html, pug.render(src.join('\r'), {doctype: 'html'})); assert.equal(html, pug.render(src.join('\r\n'), {doctype: 'html'})); }); it('should support single quotes', function() { assert.equal("

'foo'

", pug.render("p 'foo'")); assert.equal("

'foo'

", pug.render("p\n | 'foo'")); assert.equal( '', pug.render("- var path = 'foo';\na(href='/' + path)") ); }); it('should support block-expansion', function() { assert.equal( '
  • foo
  • bar
  • baz
  • ', pug.render('li: a foo\nli: a bar\nli: a baz') ); assert.equal( '
  • foo
  • bar
  • baz
  • ', pug.render('li.first: a foo\nli: a bar\nli: a baz') ); assert.equal( '
    baz
    ', pug.render('.foo: .bar baz') ); }); it('should support tags', function() { var str = ['p', 'div', 'img', 'br/'].join('\n'); var html = ['

    ', '
    ', '', '
    '].join(''); assert.equal(html, pug.render(str), 'Test basic tags'); assert.equal( '', pug.render('fb:foo-bar'), 'Test hyphens' ); assert.equal( '
    ', pug.render('div.something'), 'Test classes' ); assert.equal( '
    ', pug.render('div#something'), 'Test ids' ); assert.equal( '
    ', pug.render('.something'), 'Test stand-alone classes' ); assert.equal( '
    ', pug.render('#something'), 'Test stand-alone ids' ); assert.equal('
    ', pug.render('#foo.bar')); assert.equal('
    ', pug.render('.bar#foo')); assert.equal( '
    ', pug.render('div#foo(class="bar")') ); assert.equal( '
    ', pug.render('div(class="bar")#foo') ); assert.equal( '
    ', pug.render('div(id="bar").foo') ); assert.equal( '
    ', pug.render('div.foo.bar.baz') ); assert.equal( '
    ', pug.render('div(class="foo").bar.baz') ); assert.equal( '
    ', pug.render('div.foo(class="bar").baz') ); assert.equal( '
    ', pug.render('div.foo.bar(class="baz")') ); assert.equal('
    ', pug.render('div.a-b2')); assert.equal('
    ', pug.render('div.a_b2')); assert.equal('', pug.render('fb:user')); assert.equal('', pug.render('fb:user:role')); assert.equal( '', pug.render('colgroup\n col.test') ); }); it('should support nested tags', function() { var str = [ 'ul', ' li a', ' li b', ' li', ' ul', ' li c', ' li d', ' li e', ].join('\n'); var html = [ '', ].join(''); assert.equal(html, pug.render(str)); var str = ['a(href="#")', ' | foo ', ' | bar ', ' | baz'].join('\n'); assert.equal('foo \nbar \nbaz', pug.render(str)); var str = ['ul', ' li one', ' ul', ' | two', ' li three'].join( '\n' ); var html = [ '', ].join(''); assert.equal(html, pug.render(str)); }); it('should support variable length newlines', function() { var str = [ 'ul', ' li a', ' ', ' li b', ' ', ' ', ' li', ' ul', ' li c', '', ' li d', ' li e', ].join('\n'); var html = [ '', ].join(''); assert.equal(html, pug.render(str)); }); it('should support tab conversion', function() { var str = [ 'ul', '\tli a', '\t', '\tli b', '\t\t', '\t\t\t\t\t\t', '\tli', '\t\tul', '\t\t\tli c', '', '\t\t\tli d', '\tli e', ].join('\n'); var html = [ '', ].join(''); assert.equal(html, pug.render(str)); }); it('should support newlines', function() { var str = [ 'ul', ' li a', ' ', ' ', '', ' ', ' li b', ' li', ' ', ' ', ' ', ' ul', ' ', ' li c', ' li d', ' li e', ].join('\n'); var html = [ '', ].join(''); assert.equal(html, pug.render(str)); var str = [ 'html', ' ', ' head', ' != "test"', ' ', ' ', ' ', ' body', ].join('\n'); var html = [ '', '', 'test', '', '', '', ].join(''); assert.equal(html, pug.render(str)); assert.equal( 'something', pug.render('foo\n= "something"\nbar') ); assert.equal( 'somethingelse', pug.render('foo\n= "something"\nbar\n= "else"') ); }); it('should support text', function() { assert.equal('foo\nbar\nbaz', pug.render('| foo\n| bar\n| baz')); assert.equal('foo \nbar \nbaz', pug.render('| foo \n| bar \n| baz')); assert.equal('(hey)', pug.render('| (hey)')); assert.equal('some random text', pug.render('| some random text')); assert.equal(' foo', pug.render('| foo')); assert.equal(' foo ', pug.render('| foo ')); assert.equal(' foo \n bar ', pug.render('| foo \n| bar ')); }); it('should support pipe-less text', function() { assert.equal( '
    ', pug.render('pre\n code\n foo\n\n bar') ); assert.equal('

    foo\n\nbar

    ', pug.render('p.\n foo\n\n bar')); assert.equal( '

    foo\n\n\n\nbar

    ', pug.render('p.\n foo\n\n\n\n bar') ); assert.equal( '

    foo\n bar\nfoo

    ', pug.render('p.\n foo\n bar\n foo') ); assert.equal( '', pug.render('script.\n s.parentNode.insertBefore(g,s)\n') ); assert.equal( '', pug.render('script.\n s.parentNode.insertBefore(g,s)') ); }); it('should support tag text', function() { assert.equal('

    some random text

    ', pug.render('p some random text')); assert.equal( '

    clickGoogle.

    ', pug.render('p\n | click\n a Google\n | .') ); assert.equal('

    (parens)

    ', pug.render('p (parens)')); assert.equal( '

    (parens)

    ', pug.render('p(foo="bar") (parens)') ); assert.equal( '', pug.render('option(value="") -- (optional) foo --') ); }); it('should support tag text block', function() { assert.equal( '

    foo \nbar \nbaz

    ', pug.render('p\n | foo \n | bar \n | baz') ); assert.equal( '', pug.render('label\n | Password:\n input') ); assert.equal( '', pug.render('label Password:\n input') ); }); it('should support tag text interpolation', function() { assert.equal( 'yo, pug is cool', pug.render('| yo, #{name} is cool\n', {name: 'pug'}) ); assert.equal( '

    yo, pug is cool

    ', pug.render('p yo, #{name} is cool', {name: 'pug'}) ); assert.equal( 'yo, pug is cool', pug.render('| yo, #{name || "pug"} is cool', {name: null}) ); assert.equal( "yo, 'pug' is cool", pug.render('| yo, #{name || "\'pug\'"} is cool', {name: null}) ); assert.equal( 'foo <script> bar', pug.render('| foo #{code} bar', {code: '', '', '', ].join(''); assert.equal(html, pug.render(str)); }); it('should support comments', function() { // Regular var str = ['//foo', 'p bar'].join('\n'); var html = ['', '

    bar

    '].join(''); assert.equal(html, pug.render(str)); // Between tags var str = ['p foo', '// bar ', 'p baz'].join('\n'); var html = ['

    foo

    ', '', '

    baz

    '].join(''); assert.equal(html, pug.render(str)); // Quotes var str = "", js = "// script(src: '/js/validate.js') "; assert.equal(str, pug.render(js)); }); it('should support unbuffered comments', function() { var str = ['//- foo', 'p bar'].join('\n'); var html = ['

    bar

    '].join(''); assert.equal(html, pug.render(str)); var str = ['p foo', '//- bar ', 'p baz'].join('\n'); var html = ['

    foo

    ', '

    baz

    '].join(''); assert.equal(html, pug.render(str)); }); it('should support literal html', function() { assert.equal( '', pug.render('') ); }); it('should support code', function() { assert.equal('test', pug.render('!= "test"')); assert.equal('test', pug.render('= "test"')); assert.equal('test', pug.render('- var foo = "test"\n=foo')); assert.equal( 'footestbar', pug.render('- var foo = "test"\n| foo\nem= foo\n| bar') ); assert.equal( 'test

    something

    ', pug.render('!= "test"\nh2 something') ); var str = ['- var foo = "', pug.render(str, {filename: __dirname + '/pug.test.js'}) ); }); it('should not fail on js newlines', function() { assert.equal('

    foo\u2028bar

    ', pug.render('p foo\u2028bar')); assert.equal('

    foo\u2029bar

    ', pug.render('p foo\u2029bar')); }); it('should display error line number correctly up to token level', function() { var str = [ 'p.', ' Lorem ipsum dolor sit amet, consectetur', ' adipisicing elit, sed do eiusmod tempor', ' incididunt ut labore et dolore magna aliqua.', 'p.', ' Ut enim ad minim veniam, quis nostrud', ' exercitation ullamco laboris nisi ut aliquip', ' ex ea commodo consequat.', 'p.', ' Duis aute irure dolor in reprehenderit', ' in voluptate velit esse cillum dolore eu', ' fugiat nulla pariatur.', 'a(href="#" Next', ].join('\n'); var errorLocation = function(str) { try { pug.render(str); } catch (err) { return err.message.split('\n')[0]; } }; assert.equal(errorLocation(str), 'Pug:13:16'); }); }); describe('.compileFile()', function() { it('does not produce warnings for issue-1593', function() { pug.compileFile(__dirname + '/fixtures/issue-1593/index.pug'); }); it('should support caching (pass 1)', function() { fs.writeFileSync(__dirname + '/temp/input-compileFile.pug', '.foo bar'); var fn = pug.compileFile(__dirname + '/temp/input-compileFile.pug', { cache: true, }); var expected = '
    bar
    '; assert(fn() === expected); }); it('should support caching (pass 2)', function() { // Poison the input file fs.writeFileSync( __dirname + '/temp/input-compileFile.pug', '.big fat hen' ); var fn = pug.compileFile(__dirname + '/temp/input-compileFile.pug', { cache: true, }); var expected = '
    bar
    '; assert(fn() === expected); }); }); describe('.render()', function() { it('should support .pug.render(str, fn)', function() { pug.render('p foo bar', function(err, str) { assert.ok(!err); assert.equal('

    foo bar

    ', str); }); }); it('should support .pug.render(str, options, fn)', function() { pug.render('p #{foo}', {foo: 'bar'}, function(err, str) { assert.ok(!err); assert.equal('

    bar

    ', str); }); }); it('should support .pug.render(str, options, fn) cache', function() { pug.render('p bar', {cache: true}, function(err, str) { assert.ok( /the "filename" option is required for caching/.test(err.message) ); }); pug.render('p foo bar', {cache: true, filename: 'test'}, function( err, str ) { assert.ok(!err); assert.equal('

    foo bar

    ', str); }); }); }); describe('.compile()', function() { it('should support .compile()', function() { var fn = pug.compile('p foo'); assert.equal('

    foo

    ', fn()); }); it('should support .compile() locals', function() { var fn = pug.compile('p= foo'); assert.equal('

    bar

    ', fn({foo: 'bar'})); }); it("should support .compile() locals in 'self' hash", function() { var fn = pug.compile('p= self.foo', {self: true}); assert.equal('

    bar

    ', fn({foo: 'bar'})); }); it('should support .compile() no debug', function() { var fn = pug.compile('p foo\np #{bar}', {compileDebug: false}); assert.equal('

    foo

    baz

    ', fn({bar: 'baz'})); }); it('should support .compile() no debug and global helpers', function() { var fn = pug.compile('p foo\np #{bar}', { compileDebug: false, helpers: 'global', }); assert.equal('

    foo

    baz

    ', fn({bar: 'baz'})); }); it('should be reasonably fast', function() { pug.compile(perfTest, {}); }); it('allows trailing space (see #1586)', function() { var res = pug.render('ul \n li An Item'); assert.equal('', res); }); }); describe('.compileClient()', function() { it('should support pug.compileClient(str)', function() { var src = fs.readFileSync(__dirname + '/cases/basic.pug'); var expected = fs .readFileSync(__dirname + '/cases/basic.html', 'utf8') .replace(/\s/g, ''); var fn = pug.compileClient(src); fn = Function('pug', fn.toString() + '\nreturn template;')(pug.runtime); var actual = fn({name: 'foo'}).replace(/\s/g, ''); expect(actual).toBe(expected); }); it('should support pug.compileClient(str, options)', function() { var src = '.bar= self.foo'; var fn = pug.compileClient(src, {self: true}); fn = Function('pug', fn.toString() + '\nreturn template;')(pug.runtime); var actual = fn({foo: 'baz'}); expect(actual).toBe('
    baz
    '); }); it('should support module syntax in pug.compileClient(str, options) when inlineRuntimeFunctions it true', function() { var src = '.bar= self.foo'; var fn = pug.compileClient(src, { self: true, module: true, inlineRuntimeFunctions: true, }); expect(fn).toMatchSnapshot(); fs.writeFileSync( __dirname + '/temp/input-compileModuleFileClient.js', fn ); var fn = require(__dirname + '/temp/input-compileModuleFileClient.js'); expect(fn({foo: 'baz'})).toBe('
    baz
    '); }); it('should support module syntax in pug.compileClient(str, options) when inlineRuntimeFunctions it false', function() { var src = '.bar= self.foo'; var fn = pug.compileClient(src, { self: true, module: true, inlineRuntimeFunctions: false, }); expect(fn).toMatchSnapshot(); fs.writeFileSync( __dirname + '/temp/input-compileModuleFileClient.js', fn ); var fn = require(__dirname + '/temp/input-compileModuleFileClient.js'); expect(fn({foo: 'baz'})).toBe('
    baz
    '); }); }); describe('.renderFile()', function() { it('will synchronously return a string', function() { var expected = fs .readFileSync(__dirname + '/cases/basic.html', 'utf8') .replace(/\s/g, ''); var actual = pug .renderFile(__dirname + '/cases/basic.pug', {name: 'foo'}) .replace(/\s/g, ''); assert(actual === expected); }); it('when given a callback, it calls that rather than returning', function(done) { var expected = fs .readFileSync(__dirname + '/cases/basic.html', 'utf8') .replace(/\s/g, ''); pug.renderFile(__dirname + '/cases/basic.pug', {name: 'foo'}, function( err, actual ) { if (err) return done(err); assert(actual.replace(/\s/g, '') === expected); done(); }); }); it('when given a callback, it calls that rather than returning even if there are no options', function(done) { var expected = fs .readFileSync(__dirname + '/cases/basic.html', 'utf8') .replace(/\s/g, ''); pug.renderFile(__dirname + '/cases/basic.pug', function(err, actual) { if (err) return done(err); assert(actual.replace(/\s/g, '') === expected); done(); }); }); it('when given a callback, it calls that with any errors', function(done) { pug.renderFile(__dirname + '/fixtures/runtime.error.pug', function( err, actual ) { assert.ok(err); done(); }); }); it('should support caching (pass 1)', function(done) { fs.writeFileSync(__dirname + '/temp/input-renderFile.pug', '.foo bar'); pug.renderFile( __dirname + '/temp/input-renderFile.pug', {cache: true}, function(err, actual) { if (err) return done(err); assert.equal('
    bar
    ', actual); done(); } ); }); it('should support caching (pass 2)', function(done) { // Poison the input file fs.writeFileSync( __dirname + '/temp/input-renderFile.pug', '.big fat hen' ); pug.renderFile( __dirname + '/temp/input-renderFile.pug', {cache: true}, function(err, actual) { if (err) return done(err); assert.equal('
    bar
    ', actual); done(); } ); }); }); describe('.compileFileClient(path, options)', function() { it('returns a string form of a function called `template`', function() { var src = pug.compileFileClient(__dirname + '/cases/basic.pug'); var expected = fs .readFileSync(__dirname + '/cases/basic.html', 'utf8') .replace(/\s/g, ''); var fn = Function('pug', src + '\nreturn template;')(pug.runtime); var actual = fn({name: 'foo'}).replace(/\s/g, ''); assert(actual === expected); }); it('accepts the `name` option to rename the resulting function', function() { var src = pug.compileFileClient(__dirname + '/cases/basic.pug', { name: 'myTemplateName', }); var expected = fs .readFileSync(__dirname + '/cases/basic.html', 'utf8') .replace(/\s/g, ''); var fn = Function('pug', src + '\nreturn myTemplateName;')(pug.runtime); var actual = fn({name: 'foo'}).replace(/\s/g, ''); assert(actual === expected); }); it('should support caching (pass 1)', function() { fs.writeFileSync( __dirname + '/temp/input-compileFileClient.pug', '.foo bar' ); var src = pug.compileFileClient( __dirname + '/temp/input-compileFileClient.pug', {name: 'myTemplateName', cache: true} ); var expected = '
    bar
    '; var fn = Function('pug', src + '\nreturn myTemplateName;')(pug.runtime); assert(fn() === expected); }); it('should support caching (pass 2)', function() { // Poison the input file fs.writeFileSync( __dirname + '/temp/input-compileFileClient.pug', '.big fat hen' ); var src = pug.compileFileClient( __dirname + '/temp/input-compileFileClient.pug', {name: 'myTemplateName', cache: true} ); var expected = '
    bar
    '; var fn = Function('pug', src + '\nreturn myTemplateName;')(pug.runtime); assert(fn() === expected); }); }); describe('.runtime', function() { describe('.merge', function() { it('merges two attribute objects, giving precedensce to the second object', function() { assert.deepEqual( pug.runtime.merge({}, {class: ['foo', 'bar'], foo: 'bar'}), {class: ['foo', 'bar'], foo: 'bar'} ); assert.deepEqual( pug.runtime.merge( {class: ['foo'], foo: 'baz'}, {class: ['bar'], foo: 'bar'} ), {class: ['foo', 'bar'], foo: 'bar'} ); assert.deepEqual( pug.runtime.merge({class: ['foo', 'bar'], foo: 'bar'}, {}), {class: ['foo', 'bar'], foo: 'bar'} ); }); }); describe('.attrs', function() { it('Renders the given attributes object', function() { assert.equal(pug.runtime.attrs({}), ''); assert.equal(pug.runtime.attrs({class: []}), ''); assert.equal(pug.runtime.attrs({class: ['foo']}), ' class="foo"'); assert.equal( pug.runtime.attrs({class: ['foo'], id: 'bar'}), ' class="foo" id="bar"' ); }); }); }); describe('filter indentation', function() { it('is maintained', function() { var filters = { indents: function(str) { return str .split(/\n/) .map(function(line) { return line.match(/^ */)[0].length; }) .join(','); }, }; var indents = [ ':indents', ' x', ' x', ' x', ' x', ' x', ' x', ' x', ' x', ' x', ' x', ' x', ' x', ' x', ' x', ' x', ].join('\n'); assert.equal( pug.render(indents, {filters: filters}), '0,1,2,3,0,4,4,3,3,4,2,0,2,0,1' ); }); }); describe('.compile().dependencies', function() { it('should list the filename of the template referenced by extends', function() { var filename = __dirname + '/dependencies/extends1.pug'; var str = fs.readFileSync(filename, 'utf8'); var info = pug.compile(str, {filename: filename}); assert.deepEqual( [path.resolve(__dirname + '/dependencies/dependency1.pug')], info.dependencies ); }); it('should list the filename of the template referenced by an include', function() { var filename = __dirname + '/dependencies/include1.pug'; var str = fs.readFileSync(filename, 'utf8'); var info = pug.compile(str, {filename: filename}); assert.deepEqual( [path.resolve(__dirname + '/dependencies/dependency1.pug')], info.dependencies ); }); it('should list the dependencies of extends dependencies', function() { var filename = __dirname + '/dependencies/extends2.pug'; var str = fs.readFileSync(filename, 'utf8'); var info = pug.compile(str, {filename: filename}); assert.deepEqual( [ path.resolve(__dirname + '/dependencies/dependency2.pug'), path.resolve(__dirname + '/dependencies/dependency3.pug'), ], info.dependencies ); }); it('should list the dependencies of include dependencies', function() { var filename = __dirname + '/dependencies/include2.pug'; var str = fs.readFileSync(filename, 'utf8'); var info = pug.compile(str, {filename: filename}); assert.deepEqual( [ path.resolve(__dirname + '/dependencies/dependency2.pug'), path.resolve(__dirname + '/dependencies/dependency3.pug'), ], info.dependencies ); }); }); describe('.name', function() { it('should have a name attribute', function() { assert.strictEqual(pug.name, 'Pug'); }); }); });