You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

unit.compiler.js 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  1. // the compiler tests are the exact same as the interpreter tests
  2. // so instead of writing all the tests twice, override the to_html
  3. // method
  4. module('Compiler', {
  5. setup: function() {
  6. this._oldToHtml = Mustache.to_html;
  7. Mustache.to_html = function(template, view, partials) {
  8. var compiler = Mustache.compile(template, partials);
  9. return compiler(view);
  10. }
  11. },
  12. teardown: function() {
  13. Mustache.to_html = this._oldToHtml;
  14. }
  15. });
  16. test("Parser", function() {
  17. expect(4);
  18. // matches whitespace_partial.html
  19. equals(
  20. Mustache.to_html(
  21. '<h1>{{ greeting }}</h1>\n{{> partial }}\n<h3>{{ farewell }}</h3>',
  22. {
  23. greeting: function() {
  24. return "Welcome";
  25. },
  26. farewell: function() {
  27. return "Fair enough, right?";
  28. },
  29. partial: {
  30. name: "Chris",
  31. value: 10000,
  32. taxed_value: function() {
  33. return this.value - (this.value * 0.4);
  34. },
  35. in_ca: true
  36. }
  37. },
  38. {partial:'Hello {{ name}}\nYou have just won ${{value }}!\n{{# in_ca }}\nWell, ${{ taxed_value }}, after taxes.\n{{/ in_ca }}\n'}
  39. ),
  40. '<h1>Welcome</h1>\nHello Chris\nYou have just won $10000!\n\nWell, $6000, after taxes.\n\n\n<h3>Fair enough, right?</h3>',
  41. 'Whitespace in Tag names'
  42. );
  43. equals(
  44. Mustache.to_html(
  45. '{{tag1}}\n\n\n{{tag2}}\n',
  46. { tag1: 'Hello', tag2: 'World' },
  47. {}
  48. ),
  49. 'Hello\n\n\nWorld\n',
  50. 'Preservation of white space'
  51. );
  52. try {
  53. Mustache.to_html(
  54. '{{=tag1}}',
  55. { tag1: 'Hello' },
  56. {}
  57. );
  58. ok(false);
  59. } catch (e) {
  60. equals(e.message, 'Unexpected end of document.');
  61. }
  62. var partials = { 'partial' : '{{key}}' };
  63. Mustache.compile('{{>partial}}', partials );
  64. equals(partials['partial'], '{{key}}', 'Partials compiler must be non-destructive');
  65. });
  66. test("Basic Variables", function() {
  67. expect(3);
  68. // matches escaped.html
  69. equals(
  70. Mustache.to_html(
  71. '<h1>{{title}}</h1>\nBut not {{entities}}.\n',
  72. {
  73. title: function() {
  74. return "Bear > Shark";
  75. },
  76. entities: "&quot;"
  77. },
  78. {}
  79. ),
  80. '<h1>Bear &gt; Shark</h1>\nBut not &amp;quot;.\n',
  81. 'HTML Escaping'
  82. );
  83. // matches null_string.html
  84. equals(
  85. Mustache.to_html(
  86. 'Hello {{name}}\nglytch {{glytch}}\nbinary {{binary}}\nvalue {{value}}\nnumeric {{numeric}}',
  87. {
  88. name: "Elise",
  89. glytch: true,
  90. binary: false,
  91. value: null,
  92. numeric: function() {
  93. return NaN;
  94. }
  95. },
  96. {}
  97. ),
  98. 'Hello Elise\nglytch true\nbinary false\nvalue \nnumeric NaN',
  99. 'Different variable types'
  100. );
  101. // matches two_in_a_row.html
  102. equals(
  103. Mustache.to_html(
  104. '{{greeting}}, {{name}}!',
  105. {
  106. name: "Joe",
  107. greeting: "Welcome"
  108. },
  109. {}
  110. ),
  111. 'Welcome, Joe!'
  112. );
  113. });
  114. test("'{' or '&' (Unescaped Variable)", function() {
  115. expect(2);
  116. // matches unescaped.html
  117. equals(
  118. Mustache.to_html(
  119. '<h1>{{{title}}}</h1>',
  120. {
  121. title: function() {
  122. return "Bear > Shark";
  123. }
  124. },
  125. {}
  126. ),
  127. '<h1>Bear > Shark</h1>',
  128. '{ character'
  129. );
  130. equals(
  131. Mustache.to_html(
  132. '<h1>{{&title}}</h1>',
  133. {
  134. title: function() {
  135. return "Bear > Shark";
  136. }
  137. },
  138. {}
  139. ),
  140. '<h1>Bear > Shark</h1>',
  141. '& character'
  142. );
  143. });
  144. test("'#' (Sections)", function() {
  145. expect(7);
  146. // matches array_of_partials_implicit_partial.html
  147. equals(
  148. Mustache.to_html(
  149. 'Here is some stuff!\n{{#numbers}}\n{{>partial}}\n{{/numbers}}',
  150. { numbers: ['1', '2', '3', '4'] },
  151. { partial: '{{.}}' }
  152. ),
  153. 'Here is some stuff!\n\n1\n\n2\n\n3\n\n4\n',
  154. 'Array of Partials (Implicit)'
  155. );
  156. // matches array_of_partials_partial.html
  157. equals(
  158. Mustache.to_html(
  159. 'Here is some stuff!\n{{#numbers}}\n{{>partial}}\n{{/numbers}}',
  160. { numbers: [{i: '1'}, {i: '2'}, {i: '3'}, {i: '4'}] },
  161. { partial: '{{i}}' }
  162. ),
  163. 'Here is some stuff!\n\n1\n\n2\n\n3\n\n4\n',
  164. 'Array of Partials (Explicit)'
  165. );
  166. // matches array_of_strings.html
  167. equals(
  168. Mustache.to_html(
  169. '{{#array_of_strings}}{{.}} {{/array_of_strings}}',
  170. {array_of_strings: ['hello', 'world']},
  171. {}
  172. ),
  173. 'hello world ',
  174. 'Array of Strings'
  175. );
  176. // mathces higher_order_sections.html
  177. equals(
  178. Mustache.to_html(
  179. '{{#bolder}}Hi {{name}}.{{/bolder}}\n',
  180. {
  181. "name": "Tater",
  182. "helper": "To tinker?",
  183. "bolder": function() {
  184. return function(text, render) {
  185. return "<b>" + render(text) + '</b> ' + this.helper;
  186. }
  187. }
  188. },
  189. {}
  190. ),
  191. '<b>Hi Tater.</b> To tinker?\n'
  192. );
  193. // matches recursion_with_same_names.html
  194. equals(
  195. Mustache.to_html(
  196. '{{ name }}\n{{ description }}\n\n{{#terms}}\n {{name}}\n {{index}}\n{{/terms}}\n',
  197. {
  198. name: 'name',
  199. description: 'desc',
  200. terms: [
  201. {name: 't1', index: 0},
  202. {name: 't2', index: 1},
  203. ]
  204. },
  205. {}
  206. ),
  207. 'name\ndesc\n\n\n t1\n 0\n\n t2\n 1\n\n'
  208. );
  209. // matches reuse_of_enumerables.html
  210. equals(
  211. Mustache.to_html(
  212. '{{#terms}}\n {{name}}\n {{index}}\n{{/terms}}\n{{#terms}}\n {{name}}\n {{index}}\n{{/terms}}\n',
  213. {
  214. terms: [
  215. {name: 't1', index: 0},
  216. {name: 't2', index: 1},
  217. ]
  218. },
  219. {}
  220. ),
  221. '\n t1\n 0\n\n t2\n 1\n\n\n t1\n 0\n\n t2\n 1\n\n',
  222. 'Lazy match of Section and Inverted Section'
  223. );
  224. // matches section_as_context.html
  225. equals(
  226. Mustache.to_html(
  227. '{{#a_object}}\n <h1>{{title}}</h1>\n <p>{{description}}</p>\n <ul>\n {{#a_list}}\n <li>{{label}}</li>\n {{/a_list}}\n </ul>\n{{/a_object}}\n',
  228. {
  229. a_object: {
  230. title: 'this is an object',
  231. description: 'one of its attributes is a list',
  232. a_list: [{label: 'listitem1'}, {label: 'listitem2'}]
  233. }
  234. },
  235. {}
  236. ),
  237. '\n <h1>this is an object</h1>\n <p>one of its attributes is a list</p>\n <ul>\n \n <li>listitem1</li>\n \n <li>listitem2</li>\n \n </ul>\n\n',
  238. 'Lazy match of Section and Inverted Section'
  239. );
  240. });
  241. test("'^' (Inverted Section)", function() {
  242. expect(1);
  243. // matches inverted_section.html
  244. equals(
  245. Mustache.to_html(
  246. '{{#repo}}<b>{{name}}</b>{{/repo}}\n{{^repo}}No repos :({{/repo}}\n',
  247. {
  248. "repo": []
  249. },
  250. {}
  251. ),
  252. '\nNo repos :(\n'
  253. );
  254. });
  255. test("'>' (Partials)", function() {
  256. expect(5);
  257. // matches view_partial.html
  258. equals(
  259. Mustache.to_html(
  260. '<h1>{{greeting}}</h1>\n{{>partial}}\n<h3>{{farewell}}</h3>',
  261. {
  262. greeting: function() {
  263. return "Welcome";
  264. },
  265. farewell: function() {
  266. return "Fair enough, right?";
  267. },
  268. partial: {
  269. name: "Chris",
  270. value: 10000,
  271. taxed_value: function() {
  272. return this.value - (this.value * 0.4);
  273. },
  274. in_ca: true
  275. }
  276. },
  277. {partial: 'Hello {{name}}\nYou have just won ${{value}}!\n{{#in_ca}}\nWell, ${{ taxed_value }}, after taxes.\n{{/in_ca}}\n'}
  278. ),
  279. '<h1>Welcome</h1>\nHello Chris\nYou have just won $10000!\n\nWell, $6000, after taxes.\n\n\n<h3>Fair enough, right?</h3>'
  280. );
  281. // matches array_partial.html
  282. equals(
  283. Mustache.to_html(
  284. '{{>partial}}',
  285. {
  286. partial: {
  287. array: ['1', '2', '3', '4']
  288. }
  289. },
  290. { partial: 'Here\'s a non-sense array of values\n{{#array}}\n {{.}}\n{{/array}}' }
  291. ),
  292. 'Here\'s a non-sense array of values\n\n 1\n\n 2\n\n 3\n\n 4\n'
  293. );
  294. // matches template_partial.html
  295. equals(
  296. Mustache.to_html(
  297. '<h1>{{title}}</h1>\n{{>partial}}',
  298. {
  299. title: function() {
  300. return "Welcome";
  301. },
  302. partial: {
  303. again: "Goodbye"
  304. }
  305. },
  306. {partial:'Again, {{again}}!'}
  307. ),
  308. '<h1>Welcome</h1>\nAgain, Goodbye!'
  309. );
  310. // matches partial_recursion.html
  311. equals(
  312. Mustache.to_html(
  313. '{{name}}\n{{#kids}}\n{{>partial}}\n{{/kids}}',
  314. {
  315. name: '1',
  316. kids: [
  317. {
  318. name: '1.1',
  319. children: [
  320. {name: '1.1.1'}
  321. ]
  322. }
  323. ]
  324. },
  325. {partial:'{{name}}\n{{#children}}\n{{>partial}}\n{{/children}}'}
  326. ),
  327. '1\n\n1.1\n\n1.1.1\n\n\n'
  328. );
  329. try {
  330. Mustache.to_html(
  331. '{{>partial}}',
  332. {},
  333. {partal: ''}
  334. );
  335. ok(false);
  336. } catch(e) {
  337. equals(e.message, "Unknown partial 'partial'");
  338. }
  339. });
  340. test("'=' (Set Delimiter)", function() {
  341. expect(1);
  342. // matches delimiter.html
  343. equals(
  344. Mustache.to_html(
  345. '{{=<% %>=}}*\n<% first %>\n* <% second %>\n<%=| |=%>\n* | third |\n|={{ }}=|\n* {{ fourth }}',
  346. {
  347. first: "It worked the first time.",
  348. second: "And it worked the second time.",
  349. third: "Then, surprisingly, it worked the third time.",
  350. fourth: "Fourth time also fine!."
  351. },
  352. {}
  353. ),
  354. '*\nIt worked the first time.\n* And it worked the second time.\n\n* Then, surprisingly, it worked the third time.\n\n* Fourth time also fine!.',
  355. 'Simple Set Delimiter'
  356. );
  357. });
  358. test("'!' (Comments)", function() {
  359. expect(1);
  360. // matches comments.html
  361. equals(
  362. Mustache.to_html(
  363. '<h1>{{title}}{{! just something interesting... or not... }}</h1>\n',
  364. {
  365. title: function() {
  366. return "A Comedy of Errors";
  367. }
  368. },
  369. {}
  370. ),
  371. '<h1>A Comedy of Errors</h1>\n'
  372. );
  373. });
  374. test("'%' (Pragmas)", function() {
  375. expect(3);
  376. // matches array_of_strings_options.html
  377. equals(
  378. Mustache.to_html(
  379. '{{%IMPLICIT-ITERATOR iterator=rob}}\n{{#array_of_strings_options}}{{rob}} {{/array_of_strings_options}}',
  380. {array_of_strings_options: ['hello', 'world']},
  381. {}
  382. ),
  383. '\nhello world ',
  384. 'IMPLICIT-ITERATOR pragma'
  385. );
  386. // matches unknown_pragma.txt
  387. try {
  388. equals(
  389. Mustache.to_html(
  390. '{{%I-HAVE-THE-GREATEST-MUSTACHE}}\n',
  391. {},
  392. {}
  393. ),
  394. 'hello world ',
  395. 'IMPLICIT-ITERATOR pragma'
  396. );
  397. ok(false);
  398. } catch (e) {
  399. equals(e.message, 'This implementation of mustache doesn\'t understand the \'I-HAVE-THE-GREATEST-MUSTACHE\' pragma');
  400. }
  401. equals(
  402. Mustache.to_html(
  403. '{{%IMPLICIT-ITERATOR}}{{#dataSet}}{{.}}:{{/dataSet}}',
  404. { dataSet: [ 'Object 1', 'Object 2', 'Object 3' ] },
  405. {}
  406. ),
  407. "Object 1:Object 2:Object 3:"
  408. );
  409. });
  410. test("Empty", function() {
  411. expect(2);
  412. // matches empty_template.html
  413. equals(
  414. Mustache.to_html(
  415. '<html><head></head><body><h1>Test</h1></body></html>',
  416. {},
  417. {}
  418. ),
  419. '<html><head></head><body><h1>Test</h1></body></html>',
  420. 'Empty Template'
  421. );
  422. // matches empty_partial.html
  423. equals(
  424. Mustache.to_html(
  425. 'hey {{foo}}\n{{>partial}}\n',
  426. {
  427. foo: 1
  428. },
  429. {partial: 'yo'}
  430. ),
  431. 'hey 1\nyo\n',
  432. 'Empty Partial'
  433. );
  434. });
  435. test("Demo", function() {
  436. expect(2);
  437. // matches simple.html
  438. equals(
  439. Mustache.to_html(
  440. 'Hello {{name}}\nYou have just won ${{value}}!\n{{#in_ca}}\nWell, ${{ taxed_value }}, after taxes.\n{{/in_ca}}',
  441. {
  442. name: "Chris",
  443. value: 10000,
  444. taxed_value: function() {
  445. return this.value - (this.value * 0.4);
  446. },
  447. in_ca: true
  448. },
  449. {}
  450. ),
  451. 'Hello Chris\nYou have just won $10000!\n\nWell, $6000, after taxes.\n',
  452. 'A simple template'
  453. );
  454. // matches complex.html
  455. var template = [
  456. '<h1>{{header}}</h1>',
  457. '{{#list}}',
  458. ' <ul>',
  459. ' {{#item}}',
  460. ' {{#current}}',
  461. ' <li><strong>{{name}}</strong></li>',
  462. ' {{/current}}',
  463. ' {{#link}}',
  464. ' <li><a href="{{url}}">{{name}}</a></li>',
  465. ' {{/link}}',
  466. ' {{/item}}',
  467. ' </ul>',
  468. '{{/list}}',
  469. '{{#empty}}',
  470. ' <p>The list is empty.</p>',
  471. '{{/empty}}',
  472. ].join('\n');
  473. var view = {
  474. header: function() {
  475. return "Colors";
  476. },
  477. item: [
  478. {name: "red", current: true, url: "#Red"},
  479. {name: "green", current: false, url: "#Green"},
  480. {name: "blue", current: false, url: "#Blue"}
  481. ],
  482. link: function() {
  483. return this["current"] !== true;
  484. },
  485. list: function() {
  486. return this.item.length !== 0;
  487. },
  488. empty: function() {
  489. return this.item.length === 0;
  490. }
  491. };
  492. var expected_result = '<h1>Colors</h1>\n\n <ul>\n \n \n <li><strong>red</strong></li>\n \n \n <li><a href=\"#Red\">red</a></li>\n \n \n \n \n <li><a href=\"#Green\">green</a></li>\n \n \n \n \n <li><a href=\"#Blue\">blue</a></li>\n \n \n </ul>\n\n';
  493. equals(
  494. Mustache.to_html(
  495. template,
  496. view,
  497. {}
  498. ),
  499. expected_result,
  500. 'A complex template'
  501. );
  502. });
  503. test("Performance", function() {
  504. expect(1);
  505. var start, end;
  506. var view = [];
  507. for (var i=0;i<1000;++i) {
  508. view.push({name:i});
  509. }
  510. var template = '{{#count}}{{name}}\n{{/count}}';
  511. start = Date.now();
  512. for (var j=0;j<1000;++j) {
  513. this._oldToHtml(template, view, {});
  514. }
  515. end = Date.now();
  516. var interpreter_time = end - start;
  517. start = Date.now();
  518. var compiler = Mustache.compile(template, {});
  519. for (var k=0;k<1000;++k) {
  520. compiler(view);
  521. }
  522. end = Date.now();
  523. var compiler_time = end - start;
  524. ok(compiler_time<interpreter_time, 'Compiler is faster.');
  525. });
  526. test("Regression Suite", function() {
  527. expect(3);
  528. // matches bug_11_eating_whitespace.html
  529. equals(
  530. Mustache.to_html(
  531. '{{tag}} foo',
  532. { tag: "yo" },
  533. {}
  534. ),
  535. 'yo foo',
  536. 'Issue 11'
  537. );
  538. // matches delimiters_partial.html
  539. equals(
  540. Mustache.to_html(
  541. '{{#enumerate}}\n{{>partial}}\n{{/enumerate}}',
  542. { enumerate: [ { text: 'A' }, { text: 'B' } ] },
  543. { partial: '{{=[[ ]]=}}\n{{text}}\n[[={{ }}=]]' }
  544. ),
  545. '\n\n{{text}}\n\n\n\n{{text}}\n\n',
  546. 'Issue 44'
  547. );
  548. // matches bug_46_set_delimiter.html
  549. equals(
  550. Mustache.to_html(
  551. '{{=[[ ]]=}}[[#IsMustacheAwesome]]mustache is awesome![[/IsMustacheAwesome]]',
  552. {IsMustacheAwesome: true},
  553. {}
  554. ),
  555. 'mustache is awesome!',
  556. 'Issue 46'
  557. );
  558. });