ejs.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. /**
  2. * Module dependencies.
  3. */
  4. var ejs = require('..')
  5. , fs = require('fs')
  6. , read = fs.readFileSync
  7. , assert = require('should');
  8. /**
  9. * Load fixture `name`.
  10. */
  11. function fixture(name) {
  12. return read('test/fixtures/' + name, 'utf8').replace(/\r/g, '');
  13. }
  14. /**
  15. * User fixtures.
  16. */
  17. var users = [];
  18. users.push({ name: 'tobi' });
  19. users.push({ name: 'loki' });
  20. users.push({ name: 'jane' });
  21. describe('ejs.compile(str, options)', function(){
  22. it('should compile to a function', function(){
  23. var fn = ejs.compile('<p>yay</p>');
  24. fn().should.equal('<p>yay</p>');
  25. })
  26. it('should throw if there are syntax errors', function(){
  27. try {
  28. ejs.compile(fixture('fail.ejs'));
  29. } catch (err) {
  30. err.message.should.include('compiling ejs');
  31. try {
  32. ejs.compile(fixture('fail.ejs'), { filename: 'fail.ejs' });
  33. } catch (err) {
  34. err.message.should.include('fail.ejs');
  35. return;
  36. }
  37. }
  38. assert(false, 'compiling a file with invalid syntax should throw an exception');
  39. })
  40. it('should allow customizing delimiters', function(){
  41. var fn = ejs.compile('<p>{= name }</p>', { open: '{', close: '}' });
  42. fn({ name: 'tobi' }).should.equal('<p>tobi</p>');
  43. var fn = ejs.compile('<p>::= name ::</p>', { open: '::', close: '::' });
  44. fn({ name: 'tobi' }).should.equal('<p>tobi</p>');
  45. var fn = ejs.compile('<p>(= name )</p>', { open: '(', close: ')' });
  46. fn({ name: 'tobi' }).should.equal('<p>tobi</p>');
  47. })
  48. it('should default to using ejs.open and ejs.close', function(){
  49. ejs.open = '{';
  50. ejs.close = '}';
  51. var fn = ejs.compile('<p>{= name }</p>');
  52. fn({ name: 'tobi' }).should.equal('<p>tobi</p>');
  53. var fn = ejs.compile('<p>|= name |</p>', { open: '|', close: '|' });
  54. fn({ name: 'tobi' }).should.equal('<p>tobi</p>');
  55. delete ejs.open;
  56. delete ejs.close;
  57. })
  58. it('should have a working client option', function(){
  59. var fn = ejs.compile('<p><%= foo %></p>', { client: true });
  60. var str = fn.toString();
  61. eval('var preFn = ' + str);
  62. preFn({ foo: 'bar' }).should.equal('<p>bar</p>');
  63. })
  64. })
  65. describe('ejs.render(str, options)', function(){
  66. it('should render the template', function(){
  67. ejs.render('<p>yay</p>')
  68. .should.equal('<p>yay</p>');
  69. })
  70. it('should accept locals', function(){
  71. ejs.render('<p><%= name %></p>', { name: 'tobi' })
  72. .should.equal('<p>tobi</p>');
  73. })
  74. })
  75. describe('ejs.renderFile(path, options, fn)', function(){
  76. it('should render a file', function(done){
  77. ejs.renderFile('test/fixtures/para.ejs', function(err, html){
  78. if (err) return done(err);
  79. html.should.equal('<p>hey</p>');
  80. done();
  81. });
  82. })
  83. it('should accept locals', function(done){
  84. var options = { name: 'tj', open: '{', close: '}' };
  85. ejs.renderFile('test/fixtures/user.ejs', options, function(err, html){
  86. if (err) return done(err);
  87. html.should.equal('<h1>tj</h1>');
  88. done();
  89. });
  90. })
  91. it('should not catch err threw by callback', function(done){
  92. var options = { name: 'tj', open: '{', close: '}' };
  93. var counter = 0;
  94. try {
  95. ejs.renderFile('test/fixtures/user.ejs', options, function(err, html){
  96. counter++;
  97. if (err) {
  98. err.message.should.not.equal('Exception in callback');
  99. return done(err);
  100. }
  101. throw new Error('Exception in callback');
  102. });
  103. } catch (err) {
  104. counter.should.equal(1);
  105. err.message.should.equal('Exception in callback');
  106. done();
  107. }
  108. })
  109. })
  110. describe('<%=', function(){
  111. it('should escape &amp;<script>', function(){
  112. ejs.render('<%= name %>', { name: '&nbsp;<script>' })
  113. .should.equal('&amp;nbsp;&lt;script&gt;');
  114. })
  115. it("should escape '", function(){
  116. ejs.render('<%= name %>', { name: "The Jones's" })
  117. .should.equal('The Jones&#39;s');
  118. })
  119. it("should escape &foo_bar;", function(){
  120. ejs.render('<%= name %>', { name: "&foo_bar;" })
  121. .should.equal('&amp;foo_bar;');
  122. })
  123. })
  124. describe('<%-', function(){
  125. it('should not escape', function(){
  126. ejs.render('<%- name %>', { name: '<script>' })
  127. .should.equal('<script>');
  128. })
  129. it('should terminate gracefully if no close tag is found', function(){
  130. try {
  131. ejs.compile('<h1>oops</h1><%- name ->')
  132. throw new Error('Expected parse failure');
  133. } catch (err) {
  134. err.message.should.equal('Could not find matching close tag "%>".');
  135. }
  136. })
  137. })
  138. describe('%>', function(){
  139. it('should produce newlines', function(){
  140. ejs.render(fixture('newlines.ejs'), { users: users })
  141. .should.equal(fixture('newlines.html'));
  142. })
  143. })
  144. describe('-%>', function(){
  145. it('should not produce newlines', function(){
  146. ejs.render(fixture('no.newlines.ejs'), { users: users })
  147. .should.equal(fixture('no.newlines.html'));
  148. })
  149. })
  150. describe('single quotes', function(){
  151. it('should not mess up the constructed function', function(){
  152. ejs.render(fixture('single-quote.ejs'))
  153. .should.equal(fixture('single-quote.html'));
  154. })
  155. })
  156. describe('double quotes', function(){
  157. it('should not mess up the constructed function', function(){
  158. ejs.render(fixture('double-quote.ejs'))
  159. .should.equal(fixture('double-quote.html'));
  160. })
  161. })
  162. describe('backslashes', function(){
  163. it('should escape', function(){
  164. ejs.render(fixture('backslash.ejs'))
  165. .should.equal(fixture('backslash.html'));
  166. })
  167. })
  168. describe('messed up whitespace', function(){
  169. it('should work', function(){
  170. ejs.render(fixture('messed.ejs'), { users: users })
  171. .should.equal(fixture('messed.html'));
  172. })
  173. })
  174. describe('filters', function(){
  175. it('should work', function(){
  176. var items = ['foo', 'bar', 'baz'];
  177. ejs.render('<%=: items | reverse | first | reverse | capitalize %>', { items: items })
  178. .should.equal('Zab');
  179. })
  180. it('should accept arguments', function(){
  181. ejs.render('<%=: users | map:"name" | join:", " %>', { users: users })
  182. .should.equal('tobi, loki, jane');
  183. })
  184. it('should truncate string', function(){
  185. ejs.render('<%=: word | truncate: 3 %>', { word: 'World' })
  186. .should.equal('Wor');
  187. })
  188. it('should append string if string is longer', function(){
  189. ejs.render('<%=: word | truncate: 2,"..." %>', { word: 'Testing' })
  190. .should.equal('Te...');
  191. })
  192. it('should not append string if string is shorter', function(){
  193. ejs.render('<%=: word | truncate: 10,"..." %>', { word: 'Testing' })
  194. .should.equal('Testing');
  195. })
  196. it('should accept arguments containing :', function(){
  197. ejs.render('<%=: users | map:"name" | join:"::" %>', { users: users })
  198. .should.equal('tobi::loki::jane');
  199. })
  200. })
  201. describe('exceptions', function(){
  202. it('should produce useful stack traces', function(done){
  203. try {
  204. ejs.render(fixture('error.ejs'), { filename: 'error.ejs' });
  205. } catch (err) {
  206. err.path.should.equal('error.ejs');
  207. err.stack.split('\n').slice(0, 8).join('\n').should.equal(fixture('error.out'));
  208. done();
  209. }
  210. })
  211. it('should not include __stack if compileDebug is false', function() {
  212. try {
  213. ejs.render(fixture('error.ejs'), {
  214. filename: 'error.ejs',
  215. compileDebug: false
  216. });
  217. } catch (err) {
  218. err.should.not.have.property('path');
  219. err.stack.split('\n').slice(0, 8).join('\n').should.not.equal(fixture('error.out'));
  220. }
  221. });
  222. })
  223. describe('includes', function(){
  224. it('should include ejs', function(){
  225. var file = 'test/fixtures/include.ejs';
  226. ejs.render(fixture('include.ejs'), { filename: file, pets: users, open: '[[', close: ']]' })
  227. .should.equal(fixture('include.html'));
  228. })
  229. it('should work when nested', function(){
  230. var file = 'test/fixtures/menu.ejs';
  231. ejs.render(fixture('menu.ejs'), { filename: file, pets: users })
  232. .should.equal(fixture('menu.html'));
  233. })
  234. it('should include arbitrary files as-is', function(){
  235. var file = 'test/fixtures/include.css.ejs';
  236. ejs.render(fixture('include.css.ejs'), { filename: file, pets: users })
  237. .should.equal(fixture('include.css.html'));
  238. })
  239. it('should pass compileDebug to include', function(){
  240. var file = 'test/fixtures/include.ejs';
  241. var fn = ejs.compile(fixture('include.ejs'), { filename: file, open: '[[', close: ']]', compileDebug: false, client: true })
  242. var str = fn.toString();
  243. eval('var preFn = ' + str);
  244. str.should.not.match(/__stack/);
  245. (function() {
  246. preFn({ pets: users });
  247. }).should.not.throw();
  248. })
  249. })
  250. describe('comments', function() {
  251. it('should fully render with comments removed', function() {
  252. ejs.render(fixture('comments.ejs'))
  253. .should.equal(fixture('comments.html'));
  254. })
  255. })
  256. describe('require', function() {
  257. it('should allow ejs templates to be required as node modules', function() {
  258. var file = 'test/fixtures/include.ejs'
  259. , template = require(__dirname + '/fixtures/menu.ejs');
  260. template({ filename: file, pets: users })
  261. .should.equal(fixture('menu.html'));
  262. })
  263. })