| @@ -1,3 +1,7 @@ | |||
| = HEAD | |||
| * Converted tests to use mocha instead of vows. | |||
| = 0.7.1 / 6 Dec 2012 | |||
| * Handle empty templates gracefully. Fixes #265, #267, and #270. | |||
| @@ -6,12 +6,12 @@ | |||
| "keywords": ["mustache", "template", "templates", "ejs"], | |||
| "main": "./mustache.js", | |||
| "devDependencies": { | |||
| "vows": "0.6.x" | |||
| "mocha": "1.5.0" | |||
| }, | |||
| "volo": { | |||
| "url": "https://raw.github.com/janl/mustache.js/0.7.1/mustache.js" | |||
| }, | |||
| "scripts": { | |||
| "test": "vows --spec" | |||
| "test": "mocha test" | |||
| } | |||
| } | |||
| @@ -0,0 +1,51 @@ | |||
| require('./helper'); | |||
| var Context = Mustache.Context; | |||
| describe('A new Mustache.Context', function () { | |||
| var context; | |||
| beforeEach(function () { | |||
| context = new Context({ name: 'parent', message: 'hi', a: { b: 'b' } }); | |||
| }); | |||
| it('is able to lookup properties of its own view', function () { | |||
| assert.equal(context.lookup('name'), 'parent'); | |||
| }); | |||
| it('is able to lookup nested properties of its own view', function () { | |||
| assert.equal(context.lookup('a.b'), 'b'); | |||
| }); | |||
| describe('when pushed', function () { | |||
| beforeEach(function () { | |||
| context = context.push({ name: 'child', c: { d: 'd' } }); | |||
| }); | |||
| it('returns the child context', function () { | |||
| assert.equal(context.view.name, 'child'); | |||
| assert.equal(context.parent.view.name, 'parent'); | |||
| }); | |||
| it('is able to lookup properties of its own view', function () { | |||
| assert.equal(context.lookup('name'), 'child'); | |||
| }); | |||
| it("is able to lookup properties of the parent context's view", function () { | |||
| assert.equal(context.lookup('message'), 'hi'); | |||
| }); | |||
| it('is able to lookup nested properties of its own view', function () { | |||
| assert.equal(context.lookup('c.d'), 'd'); | |||
| }); | |||
| it('is able to lookup nested properties of its parent view', function () { | |||
| assert.equal(context.lookup('a.b'), 'b'); | |||
| }); | |||
| }); | |||
| }); | |||
| describe('Mustache.Context.make', function () { | |||
| it('returns the same object when given a Context', function () { | |||
| var context = new Context; | |||
| assert.strictEqual(Context.make(context), context); | |||
| }); | |||
| }); | |||
| @@ -1,47 +0,0 @@ | |||
| var assert = require("assert"); | |||
| var vows = require("vows"); | |||
| var Context = require("./../mustache").Context; | |||
| vows.describe("Mustache.Context").addBatch({ | |||
| "A Context": { | |||
| topic: function () { | |||
| var view = { name: 'parent', message: 'hi', a: { b: 'b' } }; | |||
| var context = new Context(view); | |||
| return context; | |||
| }, | |||
| "is able to lookup properties of its own view": function (context) { | |||
| assert.equal(context.lookup("name"), "parent"); | |||
| }, | |||
| "is able to lookup nested properties of its own view": function (context) { | |||
| assert.equal(context.lookup("a.b"), "b"); | |||
| }, | |||
| "when pushed": { | |||
| topic: function (context) { | |||
| var view = { name: 'child', c: { d: 'd' } }; | |||
| return context.push(view); | |||
| }, | |||
| "returns the child context": function (context) { | |||
| assert.equal(context.view.name, "child"); | |||
| assert.equal(context.parent.view.name, "parent"); | |||
| }, | |||
| "is able to lookup properties of its own view": function (context) { | |||
| assert.equal(context.lookup("name"), "child"); | |||
| }, | |||
| "is able to lookup properties of the parent context's view": function (context) { | |||
| assert.equal(context.lookup("message"), "hi"); | |||
| }, | |||
| "is able to lookup nested properties of its own view": function (context) { | |||
| assert.equal(context.lookup("c.d"), "d"); | |||
| }, | |||
| "is able to lookup nested properties of its parent view": function (context) { | |||
| assert.equal(context.lookup("a.b"), "b"); | |||
| } | |||
| } // when pushed | |||
| }, // A Context | |||
| "make": { | |||
| "returns the same object when given a Context": function () { | |||
| var context = new Context; | |||
| assert.strictEqual(Context.make(context), context); | |||
| } | |||
| } | |||
| }).export(module); | |||
| @@ -0,0 +1,2 @@ | |||
| assert = require('assert'); | |||
| Mustache = require('../mustache'); | |||
| @@ -1,9 +1,7 @@ | |||
| var assert = require('assert'); | |||
| var vows = require('vows'); | |||
| var Mustache = require('./../mustache'); | |||
| require('./helper'); | |||
| // A map of templates to their expected token output. Tokens are in the format: | |||
| // [type, value, startIndex, endIndex]. | |||
| // [type, value, startIndex, endIndex, subTokens]. | |||
| var expectations = { | |||
| '' : [], | |||
| '{{hi}}' : [ [ 'name', 'hi', 0, 6 ] ], | |||
| @@ -55,16 +53,14 @@ var expectations = { | |||
| : [ [ '#', 'foo', 0, 8, [ [ '#', 'a', 11, 17, [ [ 'text', ' ', 18, 22 ], [ 'name', 'b', 22, 27 ], [ 'text', '\n', 27, 28 ] ] ] ] ] ] | |||
| }; | |||
| var spec = {}; | |||
| describe('Mustache.parse', function () { | |||
| for (var template in expectations) { | |||
| (function (template, tokens) { | |||
| spec['knows how to parse ' + JSON.stringify(template)] = function () { | |||
| assert.deepEqual(Mustache.parse(template), tokens); | |||
| }; | |||
| })(template, expectations[template]); | |||
| } | |||
| for (var template in expectations) { | |||
| (function (template, tokens) { | |||
| it('knows how to parse ' + JSON.stringify(template), function () { | |||
| assert.deepEqual(Mustache.parse(template), tokens); | |||
| }); | |||
| })(template, expectations[template]); | |||
| } | |||
| vows.describe('Mustache.parse').addBatch({ | |||
| 'parse': spec | |||
| }).export(module); | |||
| }); | |||
| @@ -0,0 +1,68 @@ | |||
| require('./helper'); | |||
| var fs = require('fs'); | |||
| var path = require('path'); | |||
| var _files = path.join(__dirname, '_files'); | |||
| function getContents(testName, ext) { | |||
| return fs.readFileSync(path.join(_files, testName + '.' + ext), 'utf8'); | |||
| } | |||
| function getView(testName) { | |||
| var view = getContents(testName, 'js'); | |||
| if (!view) throw new Error('Cannot find view for test "' + testName + '"'); | |||
| return eval(view); | |||
| } | |||
| function getPartial(testName) { | |||
| try { | |||
| return getContents(testName, 'partial'); | |||
| } catch (e) { | |||
| // No big deal. Not all tests need to test partial support. | |||
| } | |||
| } | |||
| function getTest(testName) { | |||
| var test = {}; | |||
| test.view = getView(testName); | |||
| test.template = getContents(testName, 'mustache'); | |||
| test.partial = getPartial(testName); | |||
| test.expect = getContents(testName, 'txt'); | |||
| return test; | |||
| } | |||
| // You can put the name of a specific test to run in the TEST environment | |||
| // variable (e.g. TEST=backslashes vows test/render-test.js) | |||
| var testToRun = process.env.TEST; | |||
| var testNames; | |||
| if (testToRun) { | |||
| testNames = [testToRun]; | |||
| } else { | |||
| testNames = fs.readdirSync(_files).filter(function (file) { | |||
| return (/\.js$/).test(file); | |||
| }).map(function (file) { | |||
| return path.basename(file).replace(/\.js$/, ''); | |||
| }); | |||
| } | |||
| describe('Mustache.render', function () { | |||
| beforeEach(function () { | |||
| Mustache.clearCache(); | |||
| }); | |||
| testNames.forEach(function (testName) { | |||
| var test = getTest(testName); | |||
| it('knows how to render ' + testName, function () { | |||
| var output; | |||
| if (test.partial) { | |||
| output = Mustache.render(test.template, test.view, { partial: test.partial }); | |||
| } else { | |||
| output = Mustache.render(test.template, test.view); | |||
| } | |||
| assert.equal(output, test.expect); | |||
| }); | |||
| }); | |||
| }); | |||
| @@ -1,66 +0,0 @@ | |||
| var fs = require("fs"); | |||
| var path = require("path"); | |||
| var assert = require("assert"); | |||
| var vows = require("vows"); | |||
| var Mustache = require("./../mustache"); | |||
| var _files = path.join(__dirname, "_files"); | |||
| function getContents(testName, ext) { | |||
| return fs.readFileSync(path.join(_files, testName + "." + ext), "utf8"); | |||
| } | |||
| // You can put the name of a specific test to run in the TEST environment | |||
| // variable (e.g. TEST=backslashes vows test/render_test.js) | |||
| var testToRun = process.env["TEST"]; | |||
| var testNames; | |||
| if (testToRun) { | |||
| testNames = [testToRun]; | |||
| } else { | |||
| testNames = fs.readdirSync(_files).filter(function (file) { | |||
| return (/\.js$/).test(file); | |||
| }).map(function (file) { | |||
| return path.basename(file).replace(/\.js$/, ""); | |||
| }); | |||
| } | |||
| var spec = {}; | |||
| testNames.forEach(function (testName) { | |||
| var view = getContents(testName, "js"); | |||
| if (view) { | |||
| view = eval(view); | |||
| } else { | |||
| console.log("Cannot find view for test: " + testName); | |||
| process.exit(); | |||
| } | |||
| var template = getContents(testName, "mustache"); | |||
| var expect = getContents(testName, "txt"); | |||
| var partial; | |||
| try { | |||
| partial = getContents(testName, "partial"); | |||
| } catch (e) { | |||
| // No big deal. | |||
| } | |||
| spec["knows how to render " + testName] = function () { | |||
| Mustache.clearCache(); | |||
| var output; | |||
| if (partial) { | |||
| output = Mustache.render(template, view, {partial: partial}); | |||
| } else { | |||
| output = Mustache.render(template, view); | |||
| } | |||
| assert.equal(output, expect); | |||
| }; | |||
| }); | |||
| vows.describe("Mustache.render").addBatch({ | |||
| "render": spec | |||
| }).export(module); | |||
| @@ -0,0 +1,78 @@ | |||
| require('./helper'); | |||
| var Scanner = Mustache.Scanner; | |||
| describe('A new Mustache.Scanner', function () { | |||
| describe('for an empty string', function () { | |||
| it('is at the end', function () { | |||
| var scanner = new Scanner(''); | |||
| assert(scanner.eos()); | |||
| }); | |||
| }); | |||
| describe('for a non-empty string', function () { | |||
| var scanner; | |||
| beforeEach(function () { | |||
| scanner = new Scanner('a b c'); | |||
| }); | |||
| describe('scan', function () { | |||
| describe('when the RegExp matches the entire string', function () { | |||
| it('returns the entire string', function () { | |||
| var match = scanner.scan(/a b c/); | |||
| assert.equal(match, scanner.string); | |||
| assert(scanner.eos()); | |||
| }); | |||
| }); | |||
| describe('when the RegExp matches at index 0', function () { | |||
| it('returns the portion of the string that matched', function () { | |||
| var match = scanner.scan(/a/); | |||
| assert.equal(match, 'a'); | |||
| assert.equal(scanner.pos, 1); | |||
| }); | |||
| }); | |||
| describe('when the RegExp matches at some index other than 0', function () { | |||
| it('returns the empty string', function () { | |||
| var match = scanner.scan(/b/); | |||
| assert.equal(match, ''); | |||
| assert.equal(scanner.pos, 0); | |||
| }); | |||
| }); | |||
| describe('when the RegExp does not match', function () { | |||
| it('returns the empty string', function () { | |||
| var match = scanner.scan(/z/); | |||
| assert.equal(match, ''); | |||
| assert.equal(scanner.pos, 0); | |||
| }); | |||
| }); | |||
| }); // scan | |||
| describe('scanUntil', function () { | |||
| describe('when the RegExp matches at index 0', function () { | |||
| it('returns the empty string', function () { | |||
| var match = scanner.scanUntil(/a/); | |||
| assert.equal(match, ''); | |||
| assert.equal(scanner.pos, 0); | |||
| }); | |||
| }); | |||
| describe('when the RegExp matches at some index other than 0', function () { | |||
| it('returns the string up to that index', function () { | |||
| var match = scanner.scanUntil(/b/); | |||
| assert.equal(match, 'a '); | |||
| assert.equal(scanner.pos, 2); | |||
| }); | |||
| }); | |||
| describe('when the RegExp does not match', function () { | |||
| it('returns the entire string', function () { | |||
| var match = scanner.scanUntil(/z/); | |||
| assert.equal(match, scanner.string); | |||
| assert(scanner.eos()); | |||
| }); | |||
| }); | |||
| }); // scanUntil | |||
| }); // for a non-empty string | |||
| }); | |||
| @@ -1,117 +0,0 @@ | |||
| var assert = require("assert"); | |||
| var vows = require("vows"); | |||
| var Scanner = require("./../mustache").Scanner; | |||
| vows.describe("Mustache.Scanner").addBatch({ | |||
| "A Scanner": { | |||
| "for an empty string": { | |||
| topic: new Scanner(""), | |||
| "is at the end of the string": function () { | |||
| var scanner = new Scanner(""); | |||
| assert(scanner.eos()); | |||
| } | |||
| }, | |||
| "for a non-empty string": { | |||
| topic: "a b c", | |||
| "when calling scan": { | |||
| "when the regexp matches the entire string": { | |||
| topic: function (string) { | |||
| var scanner = new Scanner(string); | |||
| var match = scanner.scan(/a b c/); | |||
| this.callback(scanner, match); | |||
| }, | |||
| "returns the entire string": function (scanner, match) { | |||
| assert.equal(match, scanner.string); | |||
| }, | |||
| "is at the end of the string": function (scanner, match) { | |||
| assert(scanner.eos()); | |||
| } | |||
| }, // when the regexp matches the entire string | |||
| "when the regexp matches": { | |||
| "at the 0th index": { | |||
| topic: function (string) { | |||
| var scanner = new Scanner(string); | |||
| var match = scanner.scan(/a/); | |||
| this.callback(scanner, match); | |||
| }, | |||
| "returns the portion of the string that was matched": function (scanner, match) { | |||
| assert.equal(match, "a"); | |||
| }, | |||
| "advances the internal pointer the length of the match": function (scanner, match) { | |||
| assert.equal(scanner.pos, 1); | |||
| } | |||
| }, // at the 0th index | |||
| "at some index other than 0": { | |||
| topic: function (string) { | |||
| var scanner = new Scanner(string); | |||
| var match = scanner.scan(/b/); | |||
| this.callback(scanner, match); | |||
| }, | |||
| "returns the empty string": function (scanner, match) { | |||
| assert.equal(match, ""); | |||
| }, | |||
| "does not advance the internal pointer": function (scanner, match) { | |||
| assert.equal(scanner.pos, 0); | |||
| } | |||
| } // at some index other than 0 | |||
| }, // when the regexp matches | |||
| "when the regexp doesn't match": { | |||
| topic: function (string) { | |||
| var scanner = new Scanner(string); | |||
| var match = scanner.scan(/z/); | |||
| this.callback(scanner, match); | |||
| }, | |||
| "returns the empty string": function (scanner, match) { | |||
| assert.equal(match, ""); | |||
| }, | |||
| "does not advance the internal pointer": function (scanner, match) { | |||
| assert.equal(scanner.pos, 0); | |||
| } | |||
| } | |||
| }, // when calling scan | |||
| "when calling scanUntil": { | |||
| "when the regexp matches": { | |||
| "at the 0th index": { | |||
| topic: function (string) { | |||
| var scanner = new Scanner(string); | |||
| var match = scanner.scanUntil(/a/); | |||
| this.callback(scanner, match); | |||
| }, | |||
| "returns the empty string": function (scanner, match) { | |||
| assert.equal(match, "") | |||
| }, | |||
| "does not advance the internal pointer": function (scanner, match) { | |||
| assert.equal(scanner.pos, 0); | |||
| } | |||
| }, | |||
| "at index 2": { | |||
| topic: function (string) { | |||
| var scanner = new Scanner(string); | |||
| var match = scanner.scanUntil(/b/); | |||
| this.callback(scanner, match); | |||
| }, | |||
| "returns the portion of the string it scanned": function (scanner, match) { | |||
| assert.equal(match, "a "); | |||
| }, | |||
| "advances the internal pointer the length of the match": function (scanner, match) { | |||
| assert.equal(scanner.pos, 2); | |||
| } | |||
| } | |||
| }, // when the regexp matches | |||
| "when the regexp doesn't match": { | |||
| topic: function (string) { | |||
| var scanner = new Scanner(string); | |||
| var match = scanner.scanUntil(/z/); | |||
| this.callback(scanner, match); | |||
| }, | |||
| "returns the entire string": function (scanner, match) { | |||
| assert.equal(match, scanner.string); | |||
| }, | |||
| "is at the end of the string": function (scanner, match) { | |||
| assert(scanner.eos()); | |||
| } | |||
| } // when the regexp doesn't match | |||
| } // when calling scanUntil | |||
| } // for a non-empty string | |||
| } | |||
| }).export(module); | |||
| @@ -0,0 +1,43 @@ | |||
| require('./helper'); | |||
| var Writer = Mustache.Writer; | |||
| describe('A new Mustache.Writer', function () { | |||
| var writer; | |||
| beforeEach(function () { | |||
| writer = new Writer; | |||
| }); | |||
| it('loads partials correctly', function () { | |||
| var partial = 'The content of the partial.'; | |||
| var result = writer.render('{{>partial}}', {}, function (name) { | |||
| assert.equal(name, 'partial'); | |||
| return partial; | |||
| }); | |||
| assert.equal(result, partial); | |||
| }); | |||
| it('caches partials by content, not name', function () { | |||
| var result = writer.render('{{>partial}}', {}, { | |||
| partial: 'partial one' | |||
| }); | |||
| assert.equal(result, 'partial one'); | |||
| result = writer.render('{{>partial}}', {}, { | |||
| partial: 'partial two' | |||
| }); | |||
| assert.equal(result, 'partial two'); | |||
| }); | |||
| it('can compile an array of tokens', function () { | |||
| var template = 'Hello {{name}}!'; | |||
| var tokens = Mustache.parse(template); | |||
| var render = writer.compileTokens(tokens, template); | |||
| var result = render({ name: 'Michael' }); | |||
| assert.equal(result, 'Hello Michael!'); | |||
| }); | |||
| }); | |||
| @@ -1,44 +0,0 @@ | |||
| var assert = require("assert"); | |||
| var vows = require("vows"); | |||
| var Mustache = require("./../mustache"); | |||
| var Writer = Mustache.Writer; | |||
| vows.describe("Mustache.Writer").addBatch({ | |||
| "A Writer": { | |||
| topic: function () { | |||
| var writer = new Writer(); | |||
| return writer; | |||
| }, | |||
| "loads partials correctly": function (writer) { | |||
| var partial = "The content of the partial."; | |||
| var result = writer.render("{{>partial}}", {}, function (name) { | |||
| assert.equal(name, "partial"); | |||
| return partial; | |||
| }); | |||
| assert.equal(result, partial); | |||
| }, | |||
| "caches partials by content, not by name": function (writer) { | |||
| var result = writer.render("{{>partial}}", {}, { | |||
| partial: "partial one" | |||
| }); | |||
| assert.equal(result, "partial one"); | |||
| result = writer.render("{{>partial}}", {}, { | |||
| partial: "partial two" | |||
| }); | |||
| assert.equal(result, "partial two"); | |||
| }, | |||
| "can compile an array of tokens": function (writer) { | |||
| var template = "Hello {{name}}!"; | |||
| var tokens = Mustache.parse(template); | |||
| var render = writer.compileTokens(tokens, template); | |||
| var result = render({ name: 'Michael' }); | |||
| assert.equal(result, 'Hello Michael!'); | |||
| } | |||
| } | |||
| }).export(module); | |||