Parcourir la source

Merge branch 'compiler' of http://github.com/thegrandpoobah/mustache.js into compiler

tags/0.5.0-vsc
thegrandpoobah il y a 16 ans
Parent
révision
b718b44cdf
4 fichiers modifiés avec 973 ajouts et 96 suppressions
  1. +328
    -95
      mustache.js
  2. +632
    -0
      test/unit.compiler.js
  3. +2
    -1
      test/unit.html
  4. +11
    -0
      test/unit.interpreter.js

+ 328
- 95
mustache.js Voir le fichier

@@ -9,8 +9,24 @@ var Mustache = function() {
this.message = message;
}
var Renderer = function(send_func) {
this.send_func = send_func;
var Renderer = function(send_func, mode) {
this.user_send_func = send_func;
if (mode==='interpreter' || !mode) {
this.commandSet = this.interpreter;
this.send_func = function(text) {
this.user_send_func(text);
}
} else if (mode==='compiler') {
this.commandSet = this.compiler;
this.cached_output = [];
this.send_func = function(text) {
this.cached_output.push(text);
}
} else {
throw new ParserException('Unsupported mode.');
}
this.pragmas = {};
};
@@ -120,6 +136,8 @@ var Mustache = function() {
// make sure the parser finished at an appropriate terminal state
if (state!=='text') {
this.stateMachine['endOfDoc'].call(this, parserContext, contextStack);
} else {
this.commandSet.text.call(this);
}
},
@@ -142,6 +160,8 @@ var Mustache = function() {
text: function(parserContext, contextStack) {
switch (parserContext.token()) {
case parserContext.openTag:
this.commandSet.text.call(this);
return 'openMustache';
default:
this.send_func(parserContext.token());
@@ -366,13 +386,13 @@ var Mustache = function() {
parserContext.stack.push({sectionType:command.tagType, key:key, content:[], depth:1});
return 'endSectionScan';
case 'variable':
this.render_variable(key, contextStack);
this.commandSet.variable.call(this, key, contextStack);
return 'text';
case 'unescapedVariable':
this.render_unescaped_variable(key, contextStack);
this.commandSet.unescaped_variable.call(this, key, contextStack);
return 'text';
case 'partial':
this.render_partial(key,
this.commandSet.partial.call(this, key,
contextStack,
parserContext.partials,
parserContext.openTag,
@@ -383,7 +403,7 @@ var Mustache = function() {
var section = parserContext.stack.pop();
if (--section.depth === 0) {
if (section.key === key) {
this.render_section(section.sectionType,
this.commandSet.section.call(this, section.sectionType,
section.content.join(''),
key,
contextStack,
@@ -465,109 +485,279 @@ var Mustache = function() {
return Object.prototype.toString.call(a) === '[object Array]';
},
render_variable: function(key, contextStack) {
function escapeHTML(str) {
return ('' + str).replace(/&/g,'&')
.replace(/</g,'&lt;')
.replace(/>/g,'&gt;');
}
interpreter: {
text: function() {
// in this implementation, rendering text is meaningless
// since the send_func method simply forwards to user_send_func
},
variable: function(key, contextStack) {
function escapeHTML(str) {
return ('' + str).replace(/&/g,'&amp;')
.replace(/</g,'&lt;')
.replace(/>/g,'&gt;');
}

var result = this.find_in_stack(key, contextStack);
if (result!==undefined) {
this.send_func(escapeHTML(result));
}
},
render_unescaped_variable: function(key, contextStack) {
var result = this.find_in_stack(key, contextStack);
if (result!==undefined) {
this.send_func(result);
}
},
render_partial: function(key, contextStack, partials, openTag, closeTag) {
if (!partials || partials[key] === undefined) {
throw new ParserException('Unknown partial \'' + key + '\'');
}
var res = this.find_in_stack(key, contextStack);
if (this.is_object(res)) {
contextStack.push(res);
}
var tokens = this.tokenize(partials[key], openTag, closeTag);
var result = this.find_in_stack(key, contextStack);
if (result!==undefined) {
this.user_send_func(escapeHTML(result));
}
},
unescaped_variable: function(key, contextStack) {
var result = this.find_in_stack(key, contextStack);
if (result!==undefined) {
this.user_send_func(result);
}
},
partial: function(key, contextStack, partials, openTag, closeTag) {
if (!partials || partials[key] === undefined) {
throw new ParserException('Unknown partial \'' + key + '\'');
}
var res = this.find_in_stack(key, contextStack);
if (this.is_object(res)) {
contextStack.push(res);
}
var tokens = this.tokenize(partials[key], openTag, closeTag);

this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack);
this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack);
if (this.is_object(res)) {
contextStack.pop();
}
},
section: function(sectionType, mustacheFragment, key, contextStack, partials, openTag, closeTag) {
// by @langalex, support for arrays of strings
var that = this;
function create_context(_context) {
if(that.is_object(_context)) {
return _context;
} else {
var iterator = '.';
if(that.pragmas["IMPLICIT-ITERATOR"] &&
that.pragmas["IMPLICIT-ITERATOR"].iterator) {
iterator = that.pragmas["IMPLICIT-ITERATOR"].iterator;
}
var ctx = {};
ctx[iterator] = _context;
return ctx;
}
}
if (this.is_object(res)) {
contextStack.pop();
}
},
render_section: function(sectionType, mustacheFragment, key, contextStack, partials, openTag, closeTag) {
// by @langalex, support for arrays of strings
var that = this;
function create_context(_context) {
if(that.is_object(_context)) {
return _context;
} else {
var iterator = '.';
if(that.pragmas["IMPLICIT-ITERATOR"] &&
that.pragmas["IMPLICIT-ITERATOR"].iterator) {
iterator = that.pragmas["IMPLICIT-ITERATOR"].iterator;
var value = this.find_in_stack(key, contextStack);

var tokens;
if (sectionType==='invertedSection') {
if (!value || this.is_array(value) && value.length === 0) {
// false or empty list, render it
tokens = this.tokenize(mustacheFragment, openTag, closeTag);
this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack);
}
var ctx = {};
ctx[iterator] = _context;
return ctx;
} else if (sectionType==='section') {
if (this.is_array(value)) { // Enumerable, Let's loop!
tokens = this.tokenize(mustacheFragment, openTag, closeTag);
for (var i=0, n=value.length; i<n; ++i) {
contextStack.push(create_context(value[i]));
this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack);
contextStack.pop();
}
} else if (this.is_object(value)) { // Object, Use it as subcontext!
tokens = this.tokenize(mustacheFragment, openTag, closeTag);
contextStack.push(value);
this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack);
contextStack.pop();
} else if (this.is_function(value)) {
// higher order section
var that = this;
var result = value.call(contextStack[contextStack.length-1], mustacheFragment, function(resultFragment) {
var tempStream = [];
var old_send_func = that.user_send_func;
that.user_send_func = function(text) { tempStream.push(text); };
tokens = that.tokenize(resultFragment, openTag, closeTag);
that.parse(that.createParserContext(tokens, partials, openTag, closeTag), contextStack);
that.user_send_func = old_send_func;
return tempStream.join('');
});
this.user_send_func(result);
} else if (value) {
tokens = this.tokenize(mustacheFragment, openTag, closeTag);
this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack);
}
} else {
throw new ParserException('Unknown section type ' + sectionType);
}
}
},
var value = this.find_in_stack(key, contextStack);
compiler: {
text: function() {
var outputText = this.cached_output.join('');
this.cached_output = [];
this.user_send_func(function(contextStack, send_func) {
send_func(outputText);
});
},
variable: function(key/*, contextStack*/) {
function escapeHTML(str) {
return ('' + str).replace(/&/g,'&amp;')
.replace(/</g,'&lt;')
.replace(/>/g,'&gt;');
}

var tokens;
if (sectionType==='invertedSection') {
if (!value || this.is_array(value) && value.length === 0) {
// false or empty list, render it
tokens = this.tokenize(mustacheFragment, openTag, closeTag);
this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack);
var that = this;
this.user_send_func(function(contextStack, send_func) {
var result = that.find_in_stack(key, contextStack);
if (result!==undefined) {
send_func(escapeHTML(result));
}
});
},
unescaped_variable: function(key/*, contextStack*/) {
var that = this;
this.user_send_func(function(contextStack, send_func) {
var result = that.find_in_stack(key, contextStack);
if (result!==undefined) {
send_func(result);
}
});
},
partial: function(key, reserved/*contextStack*/, partials, openTag, closeTag) {
if (!partials || partials[key] === undefined) {
throw new ParserException('Unknown partial \'' + key + '\'');
}
} else if (sectionType==='section') {
if (this.is_array(value)) { // Enumerable, Let's loop!
tokens = this.tokenize(mustacheFragment, openTag, closeTag);
if (!this.is_function(partials[key])) {
var old_user_send_func = this.user_send_func;
var commands = [];
this.user_send_func = function(command) { commands.push(command); };
var tokens = this.tokenize(partials[key], openTag, closeTag);
partials[key] = function() {}; // blank out the paritals so that infinite recursion doesn't happen
this.parse(this.createParserContext(tokens, partials, openTag, closeTag), reserved);
this.user_send_func = old_user_send_func;
for (var i=0, n=value.length; i<n; ++i) {
contextStack.push(create_context(value[i]));
this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack);
contextStack.pop();
}
} else if (this.is_object(value)) { // Object, Use it as subcontext!
tokens = this.tokenize(mustacheFragment, openTag, closeTag);
contextStack.push(value);
this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack);
contextStack.pop();
} else if (this.is_function(value)) {
// higher order section
var that = this;
partials[key] = function(contextStack, send_func) {
var res = that.find_in_stack(key, contextStack);
if (that.is_object(res)) {
contextStack.push(res);
}
var result = value.call(contextStack[contextStack.length-1], mustacheFragment, function(resultFragment) {
var tempStream = [];
var old_send_func = that.send_func;
that.send_func = function(text) { tempStream.push(text); };
for (var i=0,n=commands.length; i<n; ++i) {
commands[i](contextStack, send_func);
}
tokens = that.tokenize(resultFragment, openTag, closeTag);
that.parse(that.createParserContext(tokens, partials, openTag, closeTag), contextStack);
if (that.is_object(res)) {
contextStack.pop();
}
};
}
this.user_send_func(function(contextStack, send_func) { partials[key](contextStack, send_func); });
},
section: function(sectionType, mustacheFragment, key, reserved/*contextStack*/, partials, openTag, closeTag) {
// by @langalex, support for arrays of strings
var that = this;
function create_context(_context) {
if(that.is_object(_context)) {
return _context;
} else {
var iterator = '.';
that.send_func = old_send_func;
if(that.pragmas["IMPLICIT-ITERATOR"] &&
that.pragmas["IMPLICIT-ITERATOR"].iterator) {
iterator = that.pragmas["IMPLICIT-ITERATOR"].iterator;
}
var ctx = {};
ctx[iterator] = _context;
return ctx;
}
}
var old_user_send_func = this.user_send_func;
var commands = [];
this.user_send_func = function(command) { commands.push(command); };
var tokens = this.tokenize(mustacheFragment, openTag, closeTag);
this.parse(this.createParserContext(tokens, partials, openTag, closeTag), reserved);
this.user_send_func = old_user_send_func;
var section_command = function(contextStack, send_func) {
for (var i=0, n=commands.length; i<n; ++i) {
commands[i](contextStack, send_func);
}
};
var that = this;
if (sectionType==='invertedSection') {
this.user_send_func(function(contextStack, send_func) {
var value = that.find_in_stack(key, contextStack);
return tempStream.join('');
if (!value || that.is_array(value) && value.length === 0) {
// false or empty list, render it
section_command(contextStack, send_func);
}
});
this.send_func(result);
} else if (value) { // boolean section
tokens = this.tokenize(mustacheFragment, openTag, closeTag);
this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack);
}
} else {
throw new ParserException('Unknown section type ' + sectionType);
} else if (sectionType==='section') {
this.user_send_func(function(contextStack, send_func) {
var value = that.find_in_stack(key, contextStack);
if (that.is_array(value)) { // Enumerable, Let's loop!
for (var i=0, n=value.length; i<n; ++i) {
contextStack.push(create_context(value[i]));
section_command(contextStack, send_func);
contextStack.pop();
}
} else if (that.is_object(value)) { // Object, Use it as subcontext!
contextStack.push(value);
section_command(contextStack, send_func);
contextStack.pop();
} else if (that.is_function(value)) {
// higher order section
// note that HOS triggers a compilation on the resultFragment.
// this is slow (in relation to a fully compiled template)
// since it invokes a call to the parser
var result = value.call(contextStack[contextStack.length-1], mustacheFragment, function(resultFragment) {
var cO = [];
var s = function(command) { cO.push(command); };
var hos_renderer = new Renderer(s, 'compiler');

resultFragment = hos_renderer.parse_pragmas(resultFragment, openTag, closeTag);
var tokens = hos_renderer.tokenize(resultFragment, openTag, closeTag);
hos_renderer.parse(hos_renderer.createParserContext(tokens, partials, openTag, closeTag), contextStack);

var o = [];
var sT = function(output) { o.push(output); };
for (var i=0,n=cO.length; i<n; ++i) {
commands[i](contextStack, sT);
}
return o.join('');
});
send_func(result);
} else if (value) {
section_command(contextStack, send_func);
}
});
} else {
throw new ParserException('Unknown section type ' + sectionType);
}
}
}
}
@@ -580,15 +770,58 @@ var Mustache = function() {
Turns a template and view into HTML
*/
to_html: function(template, view, partials, send_func) {
var o = [];
if (!template) { return ''; }
partials = partials || {};
view = view || {};
var o = send_func ? undefined : [];
var s = send_func || function(output) { o.push(output); };
var renderer = new Renderer(s);
var renderer = new Renderer(s, 'interpreter');
renderer.render(template, view, partials);
if (!send_func) {
return o.join('');
}
},
/*
Compiles a template into an equivalent JS function for faster
repeated execution.
*/
compile: function(template, partials) {
if (!template) { return function() { return '' }; }
var p = {};
if (partials) {
for (var key in partials) {
if (partials.hasOwnProperty(key)) {
p[key] = partials[key];
}
}
}
var commands = [];
var s = function(command) { commands.push(command); };
var renderer = new Renderer(s, 'compiler');
renderer.render(template, {}, p);

return function(view, send_func) {
view = view || {};
var o = send_func ? undefined : [];
var s = send_func || function(output) { o.push(output); };
for (var i=0,n=commands.length; i<n; ++i) {
commands[i]([view], s);
}
if (!send_func) {
return o.join('');
}
};
}
});
}();

+ 632
- 0
test/unit.compiler.js Voir le fichier

@@ -0,0 +1,632 @@
// the compiler tests are the exact same as the interpreter tests
// so instead of writing all the tests twice, override the to_html
// method
module('Compiler', {
setup: function() {
this._oldToHtml = Mustache.to_html;
Mustache.to_html = function(template, view, partials) {
var compiler = Mustache.compile(template, partials);
return compiler(view);
}
},
teardown: function() {
Mustache.to_html = this._oldToHtml;
}
});

test("Argument validation", function() {
expect(4);
equals(Mustache.to_html(undefined), '', 'No parameters');
equals(Mustache.to_html('{{hi}}'), '', ' No View or Partials');
equals(Mustache.to_html('{{hi}}', {hi:'Hi.'}), 'Hi.', 'No Partials');
equals(Mustache.to_html('{{>hi}}', undefined, {hi:'{{p}}'}), '', 'Partial but no view');
});

test("Parser", function() {
expect(4);

// matches whitespace_partial.html
equals(
Mustache.to_html(
'<h1>{{ greeting }}</h1>\n{{> partial }}\n<h3>{{ farewell }}</h3>',
{
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'}
),
'<h1>Welcome</h1>\nHello Chris\nYou have just won $10000!\n\nWell, $6000, after taxes.\n\n\n<h3>Fair enough, right?</h3>',
'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'
);
try {
Mustache.to_html(
'{{=tag1}}',
{ tag1: 'Hello' },
{}
);

ok(false);
} catch (e) {
equals(e.message, 'Unexpected end of document.');
}
var partials = { 'partial' : '{{key}}' };
Mustache.compile('{{>partial}}', partials );
equals(partials['partial'], '{{key}}', 'Partials compiler must be non-destructive');
});

test("Basic Variables", function() {
expect(3);
// matches escaped.html
equals(
Mustache.to_html(
'<h1>{{title}}</h1>\nBut not {{entities}}.\n',
{
title: function() {
return "Bear > Shark";
},
entities: "&quot;"
},
{}
),
'<h1>Bear &gt; Shark</h1>\nBut not &amp;quot;.\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(
'<h1>{{{title}}}</h1>',
{
title: function() {
return "Bear > Shark";
}
},
{}
),
'<h1>Bear > Shark</h1>',
'{ character'
);
equals(
Mustache.to_html(
'<h1>{{&title}}</h1>',
{
title: function() {
return "Bear > Shark";
}
},
{}
),
'<h1>Bear > Shark</h1>',
'& 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!\n\n1\n\n2\n\n3\n\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!\n\n1\n\n2\n\n3\n\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 "<b>" + render(text) + '</b> ' + this.helper;
}
}
},
{}
),
'<b>Hi Tater.</b> To tinker?\n'
);
// 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\n t1\n 0\n\n t2\n 1\n\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},
]
},
{}
),
'\n t1\n 0\n\n t2\n 1\n\n\n t1\n 0\n\n t2\n 1\n\n',
'Lazy match of Section and Inverted Section'
);
// matches section_as_context.html
equals(
Mustache.to_html(
'{{#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',
{
a_object: {
title: 'this is an object',
description: 'one of its attributes is a list',
a_list: [{label: 'listitem1'}, {label: 'listitem2'}]
}
},
{}
),
'\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',
'Lazy match of Section and Inverted Section'
);
});

test("'^' (Inverted Section)", function() {
expect(1);
// matches inverted_section.html
equals(
Mustache.to_html(
'{{#repo}}<b>{{name}}</b>{{/repo}}\n{{^repo}}No repos :({{/repo}}\n',
{
"repo": []
},
{}
),
'\nNo repos :(\n'
);
});

test("'>' (Partials)", function() {
expect(5);
// matches view_partial.html
equals(
Mustache.to_html(
'<h1>{{greeting}}</h1>\n{{>partial}}\n<h3>{{farewell}}</h3>',
{
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'}
),
'<h1>Welcome</h1>\nHello Chris\nYou have just won $10000!\n\nWell, $6000, after taxes.\n\n\n<h3>Fair enough, right?</h3>'
);
// 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\n 1\n\n 2\n\n 3\n\n 4\n'
);
// matches template_partial.html
equals(
Mustache.to_html(
'<h1>{{title}}</h1>\n{{>partial}}',
{
title: function() {
return "Welcome";
},
partial: {
again: "Goodbye"
}
},
{partial:'Again, {{again}}!'}
),
'<h1>Welcome</h1>\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\n\n1.1\n\n1.1.1\n\n\n'
);
try {
Mustache.to_html(
'{{>partial}}',
{},
{partal: ''}
);
ok(false);
} catch(e) {
equals(e.message, "Unknown partial 'partial'");
}
});

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\n* Then, surprisingly, it worked the third time.\n\n* Fourth time also fine!.',
'Simple Set Delimiter'
);
});

test("'!' (Comments)", function() {
expect(1);
// matches comments.html
equals(
Mustache.to_html(
'<h1>{{title}}{{! just something interesting... or not... }}</h1>\n',
{
title: function() {
return "A Comedy of Errors";
}
},
{}
),
'<h1>A Comedy of Errors</h1>\n'
);
});

test("'%' (Pragmas)", function() {
expect(3);
// 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');
}
equals(
Mustache.to_html(
'{{%IMPLICIT-ITERATOR}}{{#dataSet}}{{.}}:{{/dataSet}}',
{ dataSet: [ 'Object 1', 'Object 2', 'Object 3' ] },
{}
),
"Object 1:Object 2:Object 3:"
);
});

test("Empty", function() {
expect(2);
// matches empty_template.html
equals(
Mustache.to_html(
'<html><head></head><body><h1>Test</h1></body></html>',
{},
{}
),
'<html><head></head><body><h1>Test</h1></body></html>',
'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!\n\nWell, $6000, after taxes.\n',
'A simple template'
);
// matches complex.html
var template = [
'<h1>{{header}}</h1>',
'{{#list}}',
' <ul>',
' {{#item}}',
' {{#current}}',
' <li><strong>{{name}}</strong></li>',
' {{/current}}',
' {{#link}}',
' <li><a href="{{url}}">{{name}}</a></li>',
' {{/link}}',
' {{/item}}',
' </ul>',
'{{/list}}',
'{{#empty}}',
' <p>The list is empty.</p>',
'{{/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 = '<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';
equals(
Mustache.to_html(
template,
view,
{}
),
expected_result,
'A complex template'
);
});

test("Performance", function() {
expect(1);
var start, end;
var view = [];
for (var i=0;i<1000;++i) {
view.push({name:i});
}
var template = '{{#count}}{{name}}\n{{/count}}';
start = Date.now();
for (var j=0;j<1000;++j) {
this._oldToHtml(template, view, {});
}
end = Date.now();
var interpreter_time = end - start;
start = Date.now();
var compiler = Mustache.compile(template, {});
for (var k=0;k<1000;++k) {
compiler(view);
}
end = Date.now();
var compiler_time = end - start;
ok(compiler_time<interpreter_time, 'Compiler is faster.');
});

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[[={{ }}=]]' }
),
'\n\n{{text}}\n\n\n\n{{text}}\n\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'
);
});

+ 2
- 1
test/unit.html Voir le fichier

@@ -5,7 +5,8 @@
<link rel="stylesheet" href="qunit/qunit.css" type="text/css" media="screen">
<script type="text/javascript" src="qunit/qunit.js"></script>
<script type="text/javascript" src="../mustache.js"></script>
<script type="text/javascript" src="unit.js"></script>
<script type="text/javascript" src="unit.interpreter.js"></script>
<script type="text/javascript" src="unit.compiler.js"></script>
</head>
<body>
<h1 id="qunit-header">mustache.js unit tests</h1>


test/unit.js → test/unit.interpreter.js Voir le fichier

@@ -1,3 +1,14 @@
module('Interpreter');

test("Argument validation", function() {
expect(4);
equals(Mustache.to_html(undefined), '', 'No parameters');
equals(Mustache.to_html('{{hi}}'), '', ' No View or Partials');
equals(Mustache.to_html('{{hi}}', {hi:'Hi.'}), 'Hi.', 'No Partials');
equals(Mustache.to_html('{{>hi}}', undefined, {hi:'{{p}}'}), '', 'Partial but no view');
});

test("Parser", function() {
expect(3);


Chargement…
Annuler
Enregistrer