Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

783 строки
16KB

  1. test("Argument validation", function() {
  2. equals(Mustache.to_html(undefined), '', 'No parameters');
  3. equals(Mustache.to_html('{{hi}}'), '', ' No View or Partials');
  4. equals(Mustache.to_html('{{hi}}', {hi:'Hi.'}), 'Hi.', 'No Partials');
  5. equals(Mustache.to_html('{{>hi}}', undefined, {hi:'{{p}}'}), '', 'Partial but no view');
  6. });
  7. test("Parser", function() {
  8. // matches whitespace_partial.html
  9. equals(
  10. Mustache.to_html(
  11. '<h1>{{ greeting }}</h1>\n{{> partial }}\n<h3>{{ farewell }}</h3>',
  12. {
  13. greeting: function() {
  14. return "Welcome";
  15. },
  16. farewell: function() {
  17. return "Fair enough, right?";
  18. },
  19. partial: {
  20. name: "Chris",
  21. value: 10000,
  22. taxed_value: function() {
  23. return this.value - (this.value * 0.4);
  24. },
  25. in_ca: true
  26. }
  27. },
  28. {partial:'Hello {{ name}}\nYou have just won ${{value }}!\n{{# in_ca }}\nWell, ${{ taxed_value }}, after taxes.\n{{/ in_ca }}\n'}
  29. ),
  30. '<h1>Welcome</h1>\nHello Chris\nYou have just won $10000!\n\nWell, $6000, after taxes.\n\n\n<h3>Fair enough, right?</h3>',
  31. 'Whitespace in Tag names'
  32. );
  33. equals(
  34. Mustache.to_html(
  35. '{{tag1}}\n\n\n{{tag2}}\n',
  36. { tag1: 'Hello', tag2: 'World' },
  37. {}
  38. ),
  39. 'Hello\n\n\nWorld\n',
  40. 'Preservation of white space'
  41. );
  42. raises(
  43. function() {
  44. Mustache.to_html(
  45. '{{=tag1}}',
  46. { tag1: 'Hello' },
  47. {}
  48. );
  49. }, function(e) {
  50. return e instanceof Mustache.Error && e.message === '(1,1): Malformed change delimiter token "{{=tag1}}".';
  51. },
  52. 'Malformed tags should be handled correctly.'
  53. );
  54. raises(
  55. function() {
  56. Mustache.to_html(
  57. '{{tag blah}}'
  58. )
  59. }, function(e) {
  60. return e.message === '(1,1): Malformed variable name "tag blah".';
  61. },
  62. 'Malformed tags should be handled correctly.'
  63. );
  64. var partials = { 'partial' : '{{key}}' };
  65. Mustache.compile('{{>partial}}', partials );
  66. equals(partials['partial'], '{{key}}', 'Partials compiler must be non-destructive');
  67. });
  68. test("Basic Variables", function() {
  69. // matches escaped.html
  70. equals(
  71. Mustache.to_html(
  72. '<h1>{{title}}</h1>\nBut not {{entities}}.\n',
  73. {
  74. title: function() {
  75. return "Bear > Shark";
  76. },
  77. entities: "&quot;"
  78. },
  79. {}
  80. ),
  81. '<h1>Bear &gt; Shark</h1>\nBut not &amp;quot;.\n',
  82. 'HTML Escaping'
  83. );
  84. // matches apostrophe.html (except in this implementation, apostrophes are not escaped.
  85. equals(
  86. Mustache.to_html(
  87. '{{apos}}{{control}}',
  88. { apos: '\'', control: 'X' },
  89. {}
  90. ),
  91. '\'X',
  92. 'Apostrophe escaping'
  93. );
  94. // matches null_string.html
  95. equals(
  96. Mustache.to_html(
  97. 'Hello {{name}}\nglytch {{glytch}}\nbinary {{binary}}\nvalue {{value}}\nnumeric {{numeric}}',
  98. {
  99. name: "Elise",
  100. glytch: true,
  101. binary: false,
  102. value: null,
  103. numeric: function() {
  104. return NaN;
  105. }
  106. },
  107. {}
  108. ),
  109. 'Hello Elise\nglytch true\nbinary false\nvalue \nnumeric NaN',
  110. 'Different variable types'
  111. );
  112. // matches two_in_a_row.html
  113. equals(
  114. Mustache.to_html(
  115. '{{greeting}}, {{name}}!',
  116. {
  117. name: "Joe",
  118. greeting: "Welcome"
  119. },
  120. {}
  121. ),
  122. 'Welcome, Joe!'
  123. );
  124. });
  125. test("Dot Notation", function() {
  126. equals(
  127. Mustache.to_html(
  128. '{{a.b.c}}',
  129. { a: { b: { c: 0 } } },
  130. {}
  131. ),
  132. '0'
  133. );
  134. equals(
  135. Mustache.to_html(
  136. '{{a.b.c}}',
  137. { a: { b: {} } },
  138. {}
  139. ),
  140. ''
  141. );
  142. equals(
  143. Mustache.to_html(
  144. '{{a.b.c}}',
  145. { a: { b: 0 } },
  146. {}
  147. ),
  148. ''
  149. );
  150. equals(
  151. Mustache.to_html(
  152. '{{a.b.c}}',
  153. { a: { b: function() { return { c: 5 } } } },
  154. {}
  155. ),
  156. '5'
  157. );
  158. });
  159. test("'{' or '&' (Unescaped Variable)", function() {
  160. // matches unescaped.html
  161. equals(
  162. Mustache.to_html(
  163. '<h1>{{{title}}}</h1>',
  164. {
  165. title: function() {
  166. return "Bear > Shark";
  167. }
  168. },
  169. {}
  170. ),
  171. '<h1>Bear > Shark</h1>',
  172. '{ character'
  173. );
  174. equals(
  175. Mustache.to_html(
  176. '<h1>{{&title}}</h1>',
  177. {
  178. title: function() {
  179. return "Bear > Shark";
  180. }
  181. },
  182. {}
  183. ),
  184. '<h1>Bear > Shark</h1>',
  185. '& character'
  186. );
  187. equals(
  188. Mustache.to_html(
  189. '<h1>{{title}}}</h1>',
  190. { title: 'Bear > Shark' }
  191. , {}
  192. ),
  193. '<h1>Bear &gt; Shark}</h1>'
  194. , 'Potential false positive'
  195. );
  196. });
  197. test("'#' (Sections)", function() {
  198. // matches array_of_partials_implicit_partial.html
  199. equals(
  200. Mustache.to_html(
  201. 'Here is some stuff!\n{{#numbers}}\n{{>partial}}\n{{/numbers}}',
  202. { numbers: ['1', '2', '3', '4'] },
  203. { partial: '{{.}}' }
  204. ),
  205. 'Here is some stuff!\n\n1\n\n2\n\n3\n\n4\n',
  206. 'Array of Partials (Implicit)'
  207. );
  208. // matches array_of_partials_partial.html
  209. equals(
  210. Mustache.to_html(
  211. 'Here is some stuff!\n{{#numbers}}\n{{>partial}}\n{{/numbers}}',
  212. { numbers: [{i: '1'}, {i: '2'}, {i: '3'}, {i: '4'}] },
  213. { partial: '{{i}}' }
  214. ),
  215. 'Here is some stuff!\n\n1\n\n2\n\n3\n\n4\n',
  216. 'Array of Partials (Explicit)'
  217. );
  218. // matches array_of_strings.html
  219. equals(
  220. Mustache.to_html(
  221. '{{#array_of_strings}}{{.}} {{/array_of_strings}}',
  222. {array_of_strings: ['hello', 'world']},
  223. {}
  224. ),
  225. 'hello world ',
  226. 'Array of Strings'
  227. );
  228. // mathces higher_order_sections.html
  229. equals(
  230. Mustache.to_html(
  231. '{{#bolder}}Hi {{name}}.{{/bolder}}\n',
  232. {
  233. "name": "Tater",
  234. "helper": "To tinker?",
  235. "bolder": function() {
  236. return function(text, render) {
  237. return "<b>" + render(text) + '</b> ' + this.helper;
  238. }
  239. }
  240. },
  241. {}
  242. ),
  243. '<b>Hi Tater.</b> To tinker?\n'
  244. );
  245. // matches recursion_with_same_names.html
  246. equals(
  247. Mustache.to_html(
  248. '{{ name }}\n{{ description }}\n\n{{#terms}}\n {{name}}\n {{index}}\n{{/terms}}\n',
  249. {
  250. name: 'name',
  251. description: 'desc',
  252. terms: [
  253. {name: 't1', index: 0},
  254. {name: 't2', index: 1}
  255. ]
  256. },
  257. {}
  258. ),
  259. 'name\ndesc\n\n\n t1\n 0\n\n t2\n 1\n\n'
  260. );
  261. // matches reuse_of_enumerables.html
  262. equals(
  263. Mustache.to_html(
  264. '{{#terms}}\n {{name}}\n {{index}}\n{{/terms}}\n{{#terms}}\n {{name}}\n {{index}}\n{{/terms}}\n',
  265. {
  266. terms: [
  267. {name: 't1', index: 0},
  268. {name: 't2', index: 1}
  269. ]
  270. },
  271. {}
  272. ),
  273. '\n t1\n 0\n\n t2\n 1\n\n\n t1\n 0\n\n t2\n 1\n\n',
  274. 'Lazy match of Section and Inverted Section'
  275. );
  276. // matches section_as_context.html
  277. equals(
  278. Mustache.to_html(
  279. '{{#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',
  280. {
  281. a_object: {
  282. title: 'this is an object',
  283. description: 'one of its attributes is a list',
  284. a_list: [{label: 'listitem1'}, {label: 'listitem2'}]
  285. }
  286. },
  287. {}
  288. ),
  289. '\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',
  290. 'Lazy match of Section and Inverted Section'
  291. );
  292. // matches nesting.html
  293. equals(
  294. Mustache.to_html(
  295. '{{#foo}}\n {{#a}}\n {{b}}\n {{/a}}\n{{/foo}}',
  296. {
  297. foo: [
  298. {a: {b: 1}},
  299. {a: {b: 2}},
  300. {a: {b: 3}}
  301. ]
  302. },
  303. {}
  304. ),
  305. '\n \n 1\n \n\n \n 2\n \n\n \n 3\n \n',
  306. 'Context Nesting'
  307. );
  308. });
  309. test("'^' (Inverted Section)", function() {
  310. // matches inverted_section.html
  311. equals(
  312. Mustache.to_html(
  313. '{{#repo}}<b>{{name}}</b>{{/repo}}\n{{^repo}}No repos :({{/repo}}\n',
  314. {
  315. "repo": []
  316. },
  317. {}
  318. ),
  319. '\nNo repos :(\n'
  320. );
  321. });
  322. test("'>' (Partials)", function() {
  323. // matches view_partial.html
  324. equals(
  325. Mustache.to_html(
  326. '<h1>{{greeting}}</h1>\n{{>partial}}\n<h3>{{farewell}}</h3>',
  327. {
  328. greeting: function() {
  329. return "Welcome";
  330. },
  331. farewell: function() {
  332. return "Fair enough, right?";
  333. },
  334. partial: {
  335. name: "Chris",
  336. value: 10000,
  337. taxed_value: function() {
  338. return this.value - (this.value * 0.4);
  339. },
  340. in_ca: true
  341. }
  342. },
  343. {partial: 'Hello {{name}}\nYou have just won ${{value}}!\n{{#in_ca}}\nWell, ${{ taxed_value }}, after taxes.\n{{/in_ca}}\n'}
  344. ),
  345. '<h1>Welcome</h1>\nHello Chris\nYou have just won $10000!\n\nWell, $6000, after taxes.\n\n\n<h3>Fair enough, right?</h3>'
  346. );
  347. // matches array_partial.html
  348. equals(
  349. Mustache.to_html(
  350. '{{>partial}}',
  351. {
  352. partial: {
  353. array: ['1', '2', '3', '4']
  354. }
  355. },
  356. { partial: 'Here\'s a non-sense array of values\n{{#array}}\n {{.}}\n{{/array}}' }
  357. ),
  358. 'Here\'s a non-sense array of values\n\n 1\n\n 2\n\n 3\n\n 4\n'
  359. );
  360. // matches template_partial.html
  361. equals(
  362. Mustache.to_html(
  363. '<h1>{{title}}</h1>\n{{>partial}}',
  364. {
  365. title: function() {
  366. return "Welcome";
  367. },
  368. partial: {
  369. again: "Goodbye"
  370. }
  371. },
  372. {partial:'Again, {{again}}!'}
  373. ),
  374. '<h1>Welcome</h1>\nAgain, Goodbye!'
  375. );
  376. // matches partial_recursion.html
  377. equals(
  378. Mustache.to_html(
  379. '{{name}}\n{{#kids}}\n{{>partial}}\n{{/kids}}',
  380. {
  381. name: '1',
  382. kids: [
  383. {
  384. name: '1.1',
  385. children: [
  386. {name: '1.1.1'}
  387. ]
  388. }
  389. ]
  390. },
  391. {partial:'{{name}}\n{{#children}}\n{{>partial}}\n{{/children}}'}
  392. ),
  393. '1\n\n1.1\n\n1.1.1\n\n\n'
  394. );
  395. raises(
  396. function() {
  397. Mustache.to_html(
  398. '{{>partial}}',
  399. {},
  400. {partal: ''}
  401. );
  402. }, function(e) {
  403. return e.message === '(1,1): Unknown partial "partial".';
  404. },
  405. 'Missing partials should be handled correctly.'
  406. );
  407. });
  408. test("'=' (Set Delimiter)", function() {
  409. // matches delimiter.html
  410. equals(
  411. Mustache.to_html(
  412. '{{=<% %>=}}*\n<% first %>\n* <% second %>\n<%=| |=%>\n* | third |\n|={{ }}=|\n* {{ fourth }}',
  413. {
  414. first: "It worked the first time.",
  415. second: "And it worked the second time.",
  416. third: "Then, surprisingly, it worked the third time.",
  417. fourth: "Fourth time also fine!."
  418. },
  419. {}
  420. ),
  421. '*\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!.',
  422. 'Simple Set Delimiter'
  423. );
  424. equals(
  425. Mustache.to_html(
  426. '{{#noData}}{{=~~ ~~=}}Set Change Delimiter ~~data~~ ~~={{ }}=~~{{/noData}}'
  427. , {
  428. noData: true
  429. , data: false
  430. }
  431. , {}
  432. )
  433. , 'Set Change Delimiter false '
  434. , 'Change Delimiter inside Section');
  435. });
  436. test("'!' (Comments)", function() {
  437. equals(
  438. Mustache.to_html('{{! this is a single line comment !}}'),
  439. '',
  440. 'Single Line Comments');
  441. equals(
  442. Mustache.to_html('{{!this is a multiline comment\ni said this is a multiline comment!}}'),
  443. '',
  444. 'Multiline Comments');
  445. equals(
  446. Mustache.to_html('{{!this {{is}} {{#a}} {{/multiline}} comment\ni {{^said}} ! hello !! bye!}}'),
  447. '',
  448. 'Correct tokenization');
  449. // matches comments.html
  450. equals(
  451. Mustache.to_html(
  452. '<h1>{{title}}{{! just something interesting... or not... !}}</h1>\n',
  453. {
  454. title: function() {
  455. return "A Comedy of Errors";
  456. }
  457. },
  458. {}
  459. ),
  460. '<h1>A Comedy of Errors</h1>\n'
  461. );
  462. });
  463. test("'%' (Pragmas)", function() {
  464. // matches array_of_strings_options.html
  465. equals(
  466. Mustache.to_html(
  467. '{{%IMPLICIT-ITERATOR iterator=rob}}\n{{#array_of_strings_options}}{{rob}} {{/array_of_strings_options}}',
  468. {array_of_strings_options: ['hello', 'world']},
  469. {}
  470. ),
  471. '\nhello world ',
  472. 'IMPLICIT-ITERATOR pragma'
  473. );
  474. // matches unknown_pragma.txt
  475. raises(
  476. function() {
  477. Mustache.to_html(
  478. '{{%I-HAVE-THE-GREATEST-MUSTACHE}}\n',
  479. {},
  480. {}
  481. );
  482. },
  483. function(e) {
  484. return e.message === 'This implementation of mustache does not implement the "I-HAVE-THE-GREATEST-MUSTACHE" pragma.';
  485. },
  486. 'Notification of unimplemented pragmas'
  487. );
  488. equals(
  489. Mustache.to_html(
  490. '{{%IMPLICIT-ITERATOR}}{{#dataSet}}{{.}}:{{/dataSet}}',
  491. { dataSet: [ 'Object 1', 'Object 2', 'Object 3' ] },
  492. {}
  493. ),
  494. "Object 1:Object 2:Object 3:",
  495. 'Default behaviour for IMPLICIT ITERATOR'
  496. );
  497. equals(
  498. Mustache.to_html(
  499. '{{%IMPLICIT-ITERATOR iterator=rob}}{{=<% %>=}}<%#dataSet%><%rob%>:<%/dataSet%>',
  500. { dataSet: [ 'Object 1', 'Object 2', 'Object 3' ] },
  501. {}
  502. ),
  503. "Object 1:Object 2:Object 3:",
  504. 'Change Delimiter and Pragma mixes'
  505. );
  506. raises(
  507. function() {
  508. Mustache.to_html(
  509. '{{%IMPLICIT-ITERATOR iterator=rob}}{{%I-HAVE-THE-GREATEST-MUSTACHE}}',
  510. {},
  511. {}
  512. );
  513. },
  514. function(e) {
  515. return e.message === 'This implementation of mustache does not implement the "I-HAVE-THE-GREATEST-MUSTACHE" pragma.';
  516. },
  517. 'Multiple Pragmas'
  518. );
  519. });
  520. test("Empty", function() {
  521. // matches empty_template.html
  522. equals(
  523. Mustache.to_html(
  524. '<html><head></head><body><h1>Test</h1></body></html>',
  525. {},
  526. {}
  527. ),
  528. '<html><head></head><body><h1>Test</h1></body></html>',
  529. 'Empty Template'
  530. );
  531. // matches empty_partial.html
  532. equals(
  533. Mustache.to_html(
  534. 'hey {{foo}}\n{{>partial}}\n',
  535. {
  536. foo: 1
  537. },
  538. {partial: 'yo'}
  539. ),
  540. 'hey 1\nyo\n',
  541. 'Empty Partial'
  542. );
  543. });
  544. test("Demo", function() {
  545. // matches simple.html
  546. equals(
  547. Mustache.to_html(
  548. 'Hello {{name}}\nYou have just won ${{value}}!\n{{#in_ca}}\nWell, ${{ taxed_value }}, after taxes.\n{{/in_ca}}',
  549. {
  550. name: "Chris",
  551. value: 10000,
  552. taxed_value: function() {
  553. return this.value - (this.value * 0.4);
  554. },
  555. in_ca: true
  556. },
  557. {}
  558. ),
  559. 'Hello Chris\nYou have just won $10000!\n\nWell, $6000, after taxes.\n',
  560. 'A simple template'
  561. );
  562. // matches complex.html
  563. var template = [
  564. '<h1>{{header}}</h1>',
  565. '{{#list}}',
  566. ' <ul>',
  567. ' {{#item}}',
  568. ' {{#current}}',
  569. ' <li><strong>{{name}}</strong></li>',
  570. ' {{/current}}',
  571. ' {{#link}}',
  572. ' <li><a href="{{url}}">{{name}}</a></li>',
  573. ' {{/link}}',
  574. ' {{/item}}',
  575. ' </ul>',
  576. '{{/list}}',
  577. '{{#empty}}',
  578. ' <p>The list is empty.</p>',
  579. '{{/empty}}'
  580. ].join('\n');
  581. var view = {
  582. header: function() {
  583. return "Colors";
  584. },
  585. item: [
  586. {name: "red", current: true, url: "#Red"},
  587. {name: "green", current: false, url: "#Green"},
  588. {name: "blue", current: false, url: "#Blue"}
  589. ],
  590. link: function() {
  591. return this["current"] !== true;
  592. },
  593. list: function() {
  594. return this.item.length !== 0;
  595. },
  596. empty: function() {
  597. return this.item.length === 0;
  598. }
  599. };
  600. 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';
  601. equals(
  602. Mustache.to_html(
  603. template,
  604. view,
  605. {}
  606. ),
  607. expected_result,
  608. 'A complex template'
  609. );
  610. });
  611. test("Error Handling", function() {
  612. raises(
  613. function() {
  614. Mustache.to_html(
  615. 'this is a partial\nyes it is. {{>partial}}',
  616. {},
  617. {partal: ''}
  618. );
  619. }, function(e) {
  620. return e.line === 2 && e.character === 12;
  621. },
  622. 'Missing partial line and character correctness.'
  623. );
  624. raises(
  625. function() {
  626. Mustache.to_html(
  627. 'this is a partial\nyes it is. {{>partial}}',
  628. {},
  629. {partial: 'error in {{#foobar}}'}
  630. );
  631. }, function(e) {
  632. return e.message === '[partial](1,21): Closing section tag "foobar" expected.'
  633. },
  634. 'Unbalanced section correctness (Part 1).'
  635. );
  636. raises(
  637. function() {
  638. Mustache.to_html(
  639. 'something something something {{/darkside}}.',
  640. {},
  641. {}
  642. );
  643. }, function(e) {
  644. return e.message === '(1,31): Unbalanced End Section tag "{{/darkside}}".'
  645. },
  646. 'Unbalanced section correctness (Part 2).'
  647. );
  648. raises(
  649. function() {
  650. Mustache.to_html(
  651. 'this is a partial\nyes it is. {{>maria}}',
  652. {},
  653. {maria: 'error in {{#foobar}}this is the most aw\ns\nme think {{#evar}}hello joe{{/foobar}}{{/evar}}'}
  654. );
  655. }, function(e) {
  656. return e.message === '[maria](3,28): Unexpected section end tag "foobar", expected "evar".'
  657. },
  658. 'Partials metric correctness.'
  659. );
  660. });
  661. test("Regression Suite", function() {
  662. // matches bug_11_eating_whitespace.html
  663. equals(
  664. Mustache.to_html(
  665. '{{tag}} foo',
  666. { tag: "yo" },
  667. {}
  668. ),
  669. 'yo foo',
  670. 'Issue 11'
  671. );
  672. // matches delimiters_partial.html
  673. equals(
  674. Mustache.to_html(
  675. '{{#enumerate}}\n{{>partial}}\n{{/enumerate}}',
  676. { enumerate: [ { text: 'A' }, { text: 'B' } ] },
  677. { partial: '{{=[[ ]]=}}\n{{text}}\n[[={{ }}=]]' }
  678. ),
  679. '\n\n{{text}}\n\n\n\n{{text}}\n\n',
  680. 'Issue 44'
  681. );
  682. // matches bug_46_set_delimiter.html
  683. equals(
  684. Mustache.to_html(
  685. '{{=[[ ]]=}}[[#IsMustacheAwesome]]mustache is awesome![[/IsMustacheAwesome]]',
  686. {IsMustacheAwesome: true},
  687. {}
  688. ),
  689. 'mustache is awesome!',
  690. 'Issue 46'
  691. );
  692. // matches Issue #79
  693. equals(
  694. Mustache.to_html(
  695. '{{#inner}}{{f}}{{#inner}}{{b}}{{/inner}}{{/inner}}'
  696. , {
  697. inner: [{
  698. f: 'foo'
  699. , inner: [{
  700. b: 'bar'
  701. }]
  702. }]
  703. }
  704. , {}
  705. )
  706. , 'foobar'
  707. , 'Nested Sections with the same name'
  708. );
  709. equals(
  710. Mustache.to_html(
  711. '{{=~~ ~~=}} ~~>staticInfoPanel~~ ~~={{ }}=~~'
  712. , {}
  713. , { staticInfoPanel: 'Hello' }
  714. )
  715. , ' Hello '
  716. , 'Change Delimiter + Partial');
  717. });