= 0) {
+ type = "array";
+ } else {
+ type = typeof obj;
+ }
+ return type;
+ },
+ separator:function() {
+ return this.multiline ? this.HTML ? '
' : '\n' : this.HTML ? ' ' : ' ';
+ },
+ indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
+ if ( !this.multiline )
+ return '';
+ var chr = this.indentChar;
+ if ( this.HTML )
+ chr = chr.replace(/\t/g,' ').replace(/ /g,' ');
+ return Array( this._depth_ + (extra||0) ).join(chr);
+ },
+ up:function( a ) {
+ this._depth_ += a || 1;
+ },
+ down:function( a ) {
+ this._depth_ -= a || 1;
+ },
+ setParser:function( name, parser ) {
+ this.parsers[name] = parser;
+ },
+ // The next 3 are exposed so you can use them
+ quote:quote,
+ literal:literal,
+ join:join,
+ //
+ _depth_: 1,
+ // This is the list of parsers, to modify them, use jsDump.setParser
+ parsers:{
+ window: '[Window]',
+ document: '[Document]',
+ error:'[ERROR]', //when no parser is found, shouldn't happen
+ unknown: '[Unknown]',
+ 'null':'null',
+ undefined:'undefined',
+ 'function':function( fn ) {
+ var ret = 'function',
+ name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
+ if ( name )
+ ret += ' ' + name;
+ ret += '(';
+
+ ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join('');
+ return join( ret, this.parse(fn,'functionCode'), '}' );
+ },
+ array: array,
+ nodelist: array,
+ arguments: array,
+ object:function( map ) {
+ var ret = [ ];
+ this.up();
+ for ( var key in map )
+ ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) );
+ this.down();
+ return join( '{', ret, '}' );
+ },
+ node:function( node ) {
+ var open = this.HTML ? '<' : '<',
+ close = this.HTML ? '>' : '>';
+
+ var tag = node.nodeName.toLowerCase(),
+ ret = open + tag;
+
+ for ( var a in this.DOMAttrs ) {
+ var val = node[this.DOMAttrs[a]];
+ if ( val )
+ ret += ' ' + a + '=' + this.parse( val, 'attribute' );
+ }
+ return ret + close + open + '/' + tag + close;
+ },
+ functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function
+ var l = fn.length;
+ if ( !l ) return '';
+
+ var args = Array(l);
+ while ( l-- )
+ args[l] = String.fromCharCode(97+l);//97 is 'a'
+ return ' ' + args.join(', ') + ' ';
+ },
+ key:quote, //object calls it internally, the key part of an item in a map
+ functionCode:'[code]', //function calls it internally, it's the content of the function
+ attribute:quote, //node calls it internally, it's an html attribute value
+ string:quote,
+ date:quote,
+ regexp:literal, //regex
+ number:literal,
+ 'boolean':literal
+ },
+ DOMAttrs:{//attributes to dump from nodes, name=>realName
+ id:'id',
+ name:'name',
+ 'class':'className'
+ },
+ HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
+ indentChar:' ',//indentation unit
+ multiline:false //if true, items in a collection, are separated by a \n, else just a space.
+ };
+
+ return jsDump;
+})();
+
+})(this);
diff --git a/test/unit.html b/test/unit.html
new file mode 100644
index 0000000..5f373f2
--- /dev/null
+++ b/test/unit.html
@@ -0,0 +1,17 @@
+
+
+
+ mustache.js unit tests
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/unit.js b/test/unit.js
new file mode 100644
index 0000000..e60dc40
--- /dev/null
+++ b/test/unit.js
@@ -0,0 +1,546 @@
+test("Parser", function() {
+ expect(2);
+
+ // matches whitespace_partial.html
+ equals(
+ Mustache.to_html(
+ '{{ greeting }}
\n{{> partial }}\n{{ farewell }}
',
+ {
+ greeting: function() {
+ return "Welcome";
+ },
+
+ farewell: function() {
+ return "Fair enough, right?";
+ },
+
+ partial: {
+ name: "Chris",
+ value: 10000,
+ taxed_value: function() {
+ return this.value - (this.value * 0.4);
+ },
+ in_ca: true
+ }
+ },
+ {partial:'Hello {{ name}}\nYou have just won ${{value }}!\n{{# in_ca }}\nWell, ${{ taxed_value }}, after taxes.\n{{/ in_ca }}\n'}
+ ),
+ 'Welcome
\nHello Chris\nYou have just won $10000!\nWell, $6000, after taxes.\n\nFair enough, right?
',
+ 'Whitespace in Tag names'
+ );
+
+ equals(
+ Mustache.to_html(
+ '{{tag1}}\n\n\n{{tag2}}\n',
+ { tag1: 'Hello', tag2: 'World' },
+ {}
+ ),
+ 'Hello\n\n\nWorld\n',
+ 'Preservation of white space'
+ );
+});
+
+test("Basic Variables", function() {
+ expect(3);
+
+ // matches escaped.html
+ equals(
+ Mustache.to_html(
+ '{{title}}
\nBut not {{entities}}.\n',
+ {
+ title: function() {
+ return "Bear > Shark";
+ },
+ entities: """
+ },
+ {}
+ ),
+ 'Bear > Shark
\nBut not ".\n',
+ 'HTML Escaping'
+ );
+
+ // matches null_string.html
+ equals(
+ Mustache.to_html(
+ 'Hello {{name}}\nglytch {{glytch}}\nbinary {{binary}}\nvalue {{value}}\nnumeric {{numeric}}',
+ {
+ name: "Elise",
+ glytch: true,
+ binary: false,
+ value: null,
+ numeric: function() {
+ return NaN;
+ }
+ },
+ {}
+ ),
+ 'Hello Elise\nglytch true\nbinary false\nvalue \nnumeric NaN',
+ 'Different variable types'
+ );
+
+ // matches two_in_a_row.html
+ equals(
+ Mustache.to_html(
+ '{{greeting}}, {{name}}!',
+ {
+ name: "Joe",
+ greeting: "Welcome"
+ },
+ {}
+ ),
+ 'Welcome, Joe!'
+ );
+
+});
+
+test("'{' or '&' (Unescaped Variable)", function() {
+ expect(2);
+
+ // matches unescaped.html
+ equals(
+ Mustache.to_html(
+ '{{{title}}}
',
+ {
+ title: function() {
+ return "Bear > Shark";
+ }
+ },
+ {}
+ ),
+ 'Bear > Shark
',
+ '{ character'
+ );
+
+ equals(
+ Mustache.to_html(
+ '{{&title}}
',
+ {
+ title: function() {
+ return "Bear > Shark";
+ }
+ },
+ {}
+ ),
+ 'Bear > Shark
',
+ '& character'
+ );
+});
+
+test("'#' (Sections)", function() {
+ expect(7);
+
+ // matches array_of_partials_implicit_partial.html
+ equals(
+ Mustache.to_html(
+ 'Here is some stuff!\n{{#numbers}}\n{{>partial}}\n{{/numbers}}',
+ { numbers: ['1', '2', '3', '4'] },
+ { partial: '{{.}}' }
+ ),
+ 'Here is some stuff!\n1\n2\n3\n4\n',
+ 'Array of Partials (Implicit)'
+ );
+
+ // matches array_of_partials_partial.html
+ equals(
+ Mustache.to_html(
+ 'Here is some stuff!\n{{#numbers}}\n{{>partial}}\n{{/numbers}}',
+ { numbers: [{i: '1'}, {i: '2'}, {i: '3'}, {i: '4'}] },
+ { partial: '{{i}}' }
+ ),
+ 'Here is some stuff!\n1\n2\n3\n4\n',
+ 'Array of Partials (Explicit)'
+ );
+
+ // matches array_of_strings.html
+ equals(
+ Mustache.to_html(
+ '{{#array_of_strings}}{{.}} {{/array_of_strings}}',
+ {array_of_strings: ['hello', 'world']},
+ {}
+ ),
+ 'hello world ',
+ 'Array of Strings'
+ );
+
+ // mathces higher_order_sections.html
+ equals(
+ Mustache.to_html(
+ '{{#bolder}}Hi {{name}}.{{/bolder}}\n',
+ {
+ "name": "Tater",
+ "helper": "To tinker?",
+ "bolder": function() {
+ return function(text, render) {
+ return "" + render(text) + ' ' + this.helper;
+ }
+ }
+ },
+ {}
+ ),
+ 'Hi Tater. To tinker?'
+ );
+
+ // matches recursion_with_same_names.html
+ equals(
+ Mustache.to_html(
+ '{{ name }}\n{{ description }}\n\n{{#terms}}\n {{name}}\n {{index}}\n{{/terms}}\n',
+ {
+ name: 'name',
+ description: 'desc',
+ terms: [
+ {name: 't1', index: 0},
+ {name: 't2', index: 1},
+ ]
+ },
+ {}
+ ),
+ 'name\ndesc\n\n t1\n 0\n t2\n 1\n'
+ );
+
+ // matches reuse_of_enumerables.html
+ equals(
+ Mustache.to_html(
+ '{{#terms}}\n {{name}}\n {{index}}\n{{/terms}}\n{{#terms}}\n {{name}}\n {{index}}\n{{/terms}}\n',
+ {
+ terms: [
+ {name: 't1', index: 0},
+ {name: 't2', index: 1},
+ ]
+ },
+ {}
+ ),
+ ' t1\n 0\n t2\n 1\n t1\n 0\n t2\n 1\n',
+ 'Lazy match of Section and Inverted Section'
+ );
+
+ // matches section_as_context.html
+ equals(
+ Mustache.to_html(
+ '{{#a_object}}\n {{title}}
\n {{description}}
\n \n {{#a_list}}\n - {{label}}
\n {{/a_list}}\n
\n{{/a_object}}\n',
+ {
+ a_object: {
+ title: 'this is an object',
+ description: 'one of its attributes is a list',
+ a_list: [{label: 'listitem1'}, {label: 'listitem2'}]
+ }
+ },
+ {}
+ ),
+ ' this is an object
\n one of its attributes is a list
\n \n - listitem1
\n - listitem2
\n
\n',
+ 'Lazy match of Section and Inverted Section'
+ );
+});
+
+test("'^' (Inverted Section)", function() {
+ expect(1);
+
+ // matches inverted_section.html
+ equals(
+ Mustache.to_html(
+ '{{#repo}}{{name}}{{/repo}}\n{{^repo}}No repos :({{/repo}}\n',
+ {
+ "repo": []
+ },
+ {}
+ ),
+ 'No repos :('
+ );
+});
+
+test("'>' (Partials)", function() {
+ expect(4);
+
+ // matches view_partial.html
+ equals(
+ Mustache.to_html(
+ '{{greeting}}
\n{{>partial}}\n{{farewell}}
',
+ {
+ greeting: function() {
+ return "Welcome";
+ },
+
+ farewell: function() {
+ return "Fair enough, right?";
+ },
+
+ partial: {
+ name: "Chris",
+ value: 10000,
+ taxed_value: function() {
+ return this.value - (this.value * 0.4);
+ },
+ in_ca: true
+ }
+ },
+ {partial: 'Hello {{name}}\nYou have just won ${{value}}!\n{{#in_ca}}\nWell, ${{ taxed_value }}, after taxes.\n{{/in_ca}}\n'}
+ ),
+ 'Welcome
\nHello Chris\nYou have just won $10000!\nWell, $6000, after taxes.\n\nFair enough, right?
'
+ );
+
+ // matches array_partial.html
+ equals(
+ Mustache.to_html(
+ '{{>partial}}',
+ {
+ partial: {
+ array: ['1', '2', '3', '4']
+ }
+ },
+ { partial: 'Here\'s a non-sense array of values\n{{#array}}\n {{.}}\n{{/array}}' }
+ ),
+ 'Here\'s a non-sense array of values\n 1\n 2\n 3\n 4\n'
+ );
+
+ // matches template_partial.html
+ equals(
+ Mustache.to_html(
+ '{{title}}
\n{{>partial}}',
+ {
+ title: function() {
+ return "Welcome";
+ },
+ partial: {
+ again: "Goodbye"
+ }
+ },
+ {partial:'Again, {{again}}!'}
+ ),
+ 'Welcome
\nAgain, Goodbye!'
+ );
+
+ // matches partial_recursion.html
+ equals(
+ Mustache.to_html(
+ '{{name}}\n{{#kids}}\n{{>partial}}\n{{/kids}}',
+ {
+ name: '1',
+ kids: [
+ {
+ name: '1.1',
+ children: [
+ {name: '1.1.1'}
+ ]
+ }
+ ]
+ },
+ {partial:'{{name}}\n{{#children}}\n{{>partial}}\n{{/children}}'}
+ ),
+ '1\n1.1\n1.1.1\n\n\n'
+ );
+});
+
+test("'=' (Set Delimiter)", function() {
+ expect(1);
+
+ // matches delimiter.html
+ equals(
+ Mustache.to_html(
+ '{{=<% %>=}}*\n<% first %>\n* <% second %>\n<%=| |=%>\n* | third |\n|={{ }}=|\n* {{ fourth }}',
+ {
+ first: "It worked the first time.",
+ second: "And it worked the second time.",
+ third: "Then, surprisingly, it worked the third time.",
+ fourth: "Fourth time also fine!."
+ },
+ {}
+ ),
+ '*\nIt worked the first time.\n* And it worked the second time.\n* Then, surprisingly, it worked the third time.\n* Fourth time also fine!.',
+ 'Simple Set Delimiter'
+ );
+});
+
+test("'!' (Comments)", function() {
+ expect(1);
+
+ // matches comments.html
+ equals(
+ Mustache.to_html(
+ '{{title}}{{! just something interesting... or not... }}
\n',
+ {
+ title: function() {
+ return "A Comedy of Errors";
+ }
+ },
+ {}
+ ),
+ 'A Comedy of Errors
\n'
+ );
+});
+
+test("'%' (Pragmas)", function() {
+ expect(2);
+
+ // matches array_of_strings_options.html
+ equals(
+ Mustache.to_html(
+ '{{%IMPLICIT-ITERATOR iterator=rob}}\n{{#array_of_strings_options}}{{rob}} {{/array_of_strings_options}}',
+ {array_of_strings_options: ['hello', 'world']},
+ {}
+ ),
+ '\nhello world ',
+ 'IMPLICIT-ITERATOR pragma'
+ );
+
+ // matches unknown_pragma.txt
+ try {
+ equals(
+ Mustache.to_html(
+ '{{%I-HAVE-THE-GREATEST-MUSTACHE}}\n',
+ {},
+ {}
+ ),
+ 'hello world ',
+ 'IMPLICIT-ITERATOR pragma'
+ );
+ ok(false);
+ } catch (e) {
+ equals(e.message, 'This implementation of mustache doesn\'t understand the \'I-HAVE-THE-GREATEST-MUSTACHE\' pragma');
+ }
+});
+
+test("Empty", function() {
+ expect(2);
+
+ // matches empty_template.html
+ equals(
+ Mustache.to_html(
+ 'Test
',
+ {},
+ {}
+ ),
+ 'Test
',
+ 'Empty Template'
+ );
+
+ // matches empty_partial.html
+ equals(
+ Mustache.to_html(
+ 'hey {{foo}}\n{{>partial}}\n',
+ {
+ foo: 1
+ },
+ {partial: 'yo'}
+ ),
+ 'hey 1\nyo\n',
+ 'Empty Partial'
+ );
+});
+
+test("Demo", function() {
+ expect(2);
+
+ // matches simple.html
+ equals(
+ Mustache.to_html(
+ 'Hello {{name}}\nYou have just won ${{value}}!\n{{#in_ca}}\nWell, ${{ taxed_value }}, after taxes.\n{{/in_ca}}',
+ {
+ name: "Chris",
+ value: 10000,
+ taxed_value: function() {
+ return this.value - (this.value * 0.4);
+ },
+ in_ca: true
+ },
+ {}
+ ),
+ 'Hello Chris\nYou have just won $10000!\nWell, $6000, after taxes.\n',
+ 'A simple template'
+ );
+
+ // matches complex.html
+ var template = [
+ '{{header}}
',
+ '{{#list}}',
+ ' ',
+ ' {{#item}}',
+ ' {{#current}}',
+ ' - {{name}}
',
+ ' {{/current}}',
+ ' {{#link}}',
+ ' - {{name}}
',
+ ' {{/link}}',
+ ' {{/item}}',
+ '
',
+ '{{/list}}',
+ '{{#empty}}',
+ ' The list is empty.
',
+ '{{/empty}}',
+ ].join('\n');
+
+ var view = {
+ header: function() {
+ return "Colors";
+ },
+ item: [
+ {name: "red", current: true, url: "#Red"},
+ {name: "green", current: false, url: "#Green"},
+ {name: "blue", current: false, url: "#Blue"}
+ ],
+ link: function() {
+ return this["current"] !== true;
+ },
+ list: function() {
+ return this.item.length !== 0;
+ },
+ empty: function() {
+ return this.item.length === 0;
+ }
+ };
+
+ var expected_result = [
+ 'Colors
',
+ ' ',
+ ''
+ ].join('\n');
+
+ equals(
+ Mustache.to_html(
+ template,
+ view,
+ {}
+ ),
+ expected_result,
+ 'A complex template'
+ );
+});
+
+test("Regression Suite", function() {
+ expect(3);
+
+ // matches bug_11_eating_whitespace.html
+ equals(
+ Mustache.to_html(
+ '{{tag}} foo',
+ { tag: "yo" },
+ {}
+ ),
+ 'yo foo',
+ 'Issue 11'
+ );
+
+ // matches delimiters_partial.html
+ equals(
+ Mustache.to_html(
+ '{{#enumerate}}\n{{>partial}}\n{{/enumerate}}',
+ { enumerate: [ { text: 'A' }, { text: 'B' } ] },
+ { partial: '{{=[[ ]]=}}\n{{text}}\n[[={{ }}=]]' }
+ ),
+ '{{text}}\n{{text}}\n',
+ 'Issue 44'
+ );
+
+ // matches bug_46_set_delimiter.html
+ equals(
+ Mustache.to_html(
+ '{{=[[ ]]=}}[[#IsMustacheAwesome]]mustache is awesome![[/IsMustacheAwesome]]',
+ {IsMustacheAwesome: true},
+ {}
+ ),
+ 'mustache is awesome!',
+ 'Issue 46'
+ );
+});