can-stache
Version:
Live binding handlebars templates
1,825 lines (1,419 loc) • 191 kB
JavaScript
/* jshint asi:true,multistr:true,indent:false,latedef:nofunc*/
var stache = require('../can-stache');
var core = require('../src/mustache_core');
var clone = require('steal-clone');
var canSymbol = require("can-symbol");
var canReflect = require("can-reflect");
var QUnit = require('steal-qunit');
var queues = require('can-queues');
var DefineList = require("can-define/list/list");
var DefineMap = require('can-define/map/map');
var Observation = require('can-observation');
var SimpleMap = require('can-simple-map');
var SimpleObservable = require("can-simple-observable");
var encoder = require('can-attribute-encoder');
var viewCallbacks = require('can-view-callbacks');
var Scope = require('can-view-scope');
var parser = require('can-view-parser');
var makeDocument = require('can-vdom/make-document/make-document');
var globals = require('can-globals');
var getChildNodes = require('can-child-nodes');
var domData = require('can-dom-data');
var domMutateNode = require('can-dom-mutate/node');
var DOCUMENT = require('can-globals/document/document');
var canDev = require('can-log/dev/dev');
var string = require('can-string');
var joinURIs = require('can-join-uris');
var getBaseURL = require('can-globals/base-url/base-url');
var testHelpers = require('can-test-helpers');
var canLog = require('can-log');
var debug = require('../helpers/-debugger');
var helpersCore = require('can-stache/helpers/core');
var makeStacheTestHelpers = require("../test/helpers");
var browserDoc = DOCUMENT();
makeTest('can-stache dom', browserDoc);
makeTest('can-stache vdom', makeDocument());
// HELPERS
function overwriteGlobalHelper(name, fn, method) {
var origHelper = helpersCore.getHelper(name);
var newHelper = function() {
return fn.apply(this, arguments);
};
newHelper.requiresOptionsArgument = origHelper.requiresOptionsArgument;
helpersCore[method || 'registerHelper'](name, newHelper);
return origHelper;
}
function makeTest(name, doc, mutation) {
var stacheTestHelpers = makeStacheTestHelpers(doc);
var isNormalDOM = doc === window.document;
var innerHTML = function(node){
return "innerHTML" in node ?
stacheTestHelpers.cloneAndClean(node).innerHTML :
undefined;
};
var getValue = function(node){
// textareas are cross bound to their internal innerHTML
if(node.nodeName.toLowerCase() === "textarea") {
return innerHTML(node);
} else {
return node.value;
}
};
var empty = function(node){
var last = node.lastChild;
while(last) {
node.removeChild(last);
last = node.lastChild;
}
};
var getText = function(template, data, options){
var div = doc.createElement("div");
div.appendChild( stache(template)(data, options) );
return cleanHTMLTextForIE( innerHTML(stacheTestHelpers.cloneAndClean(div)) );
},
getAttr = function (el, attrName) {
return attrName === "class" ?
el.className :
el.getAttribute(attrName);
},
cleanHTMLTextForIE = function(html){ // jshint ignore:line
return html.replace(/ stache_0\.\d+="[^"]+"/g,"").replace(/<(\/?[-A-Za-z0-9_]+)/g, function(whole, tagName){
return "<"+tagName.toLowerCase();
}).replace(/\r?\n/g,"");
},
getTextFromFrag = function(node){
var txt = "";
node = node.firstChild;
while(node) {
if(node.nodeType === 3) {
txt += node.nodeValue;
} else {
txt += getTextFromFrag(node);
}
node = node.nextSibling;
}
return txt;
};
var oldDoc;
QUnit.module(name, {
beforeEach: function(assert) {
if(doc === window.document) {
DOCUMENT(null);
globals.deleteKeyValue('MutationObserver');
} else {
oldDoc = window.document;
DOCUMENT(doc);
globals.setKeyValue('MutationObserver', null);
}
this.fixture = doc.createElement("div");
doc.body.appendChild(this.fixture);
this.animals = ['sloth', 'bear', 'monkey'];
// reset stache helpers so that any helpers registered in
// the previous test do not conflict with scope properties
helpersCore.__resetHelpers();
},
afterEach: function(assert) {
doc.body.removeChild(this.fixture);
var done = assert.async();
setTimeout(function(){
DOCUMENT(window.document);
globals.deleteKeyValue('MutationObserver');
done();
},1)
}
});
QUnit.test("html to html", function(assert) {
var stashed = stache("<h1 class='foo'><span>Hello World!</span></h1>");
var frag = stashed();
assert.equal( innerHTML(frag.childNodes.item(0)).toLowerCase(), "<span>hello world!</span>","got back the right text");
});
QUnit.test("basic replacement", function(assert) {
var stashed = stache("<h1 class='foo'><span>Hello {{message}}!</span></h1>");
var frag = stashed({
message: "World"
});
assert.equal( innerHTML(stacheTestHelpers.cloneAndClean(frag).firstChild).toLowerCase(), "<span>hello world!</span>","got back the right text");
});
QUnit.test("a section helper", function(assert) {
stache.registerHelper("helper", function(options){
return options.fn({message: "World"});
});
var stashed = stache("<h1 class='foo'>{{#helper()}}<span>Hello {{message}}!</span>{{/helper}}</h1>");
var frag = stashed({});
assert.equal(stacheTestHelpers.cloneAndClean(frag).firstChild.firstChild.nodeName.toLowerCase(), "span", "got a span");
assert.equal(innerHTML(stacheTestHelpers.cloneAndClean(frag).firstChild.firstChild), "Hello World!","got back the right text");
});
QUnit.test('helpers used as section should have helperOptionArg.isSection set', function (assert) {
var done = assert.async();
stache.registerHelper('genericTestHelper', function (options) {
assert.equal(options.isSection, true, 'isSection should be true');
done();
});
var template = '<div>{{#genericTestHelper()}}<span>Test</span>{{/genericTestHelper}}</div>';
var viewModel = {};
stache(template)(viewModel);
});
QUnit.test('helpers used inline should have helperOptionArg.isSection unset', function (assert) {
var done = assert.async();
stache.registerHelper('genericTestHelper2', function (options) {
assert.equal(options.isSection, false, 'isSection should be false');
done();
});
var template = '<div>{{genericTestHelper2()}}</div>';
var viewModel = {};
stache(template)(viewModel);
});
testHelpers.dev.devOnlyTest("helpers warn on overwrite (canjs/can-stache-converters#24)", function (assert) {
stache.registerHelper('foobar', function() {});
// have to do this after the first registration b/c if the dom and vdom tests run, "foobar"
// will already have been registered.
var teardown = testHelpers.dev.willWarn(/already been registered/, function(message, matched) {
if(matched) {
assert.ok(true, "received warning");
}
});
stache.registerHelper('foobar', function() {});
assert.equal(teardown(), 1, "Exactly one warning called");
});
QUnit.test("attributes sections", function(assert) {
var template = stache("<div {{attributes}}/>");
var frag = template({
attributes: "foo='bar'"
});
assert.equal(stacheTestHelpers.cloneAndClean(frag).firstChild.getAttribute('foo'), "bar", "{{attributes}} set");
template = stache("<div {{#if truthy}}foo='{{baz}}'{{/if}}/>");
frag = template({
truthy: true,
baz: "bar"
});
assert.equal(stacheTestHelpers.cloneAndClean(frag).firstChild.getAttribute('foo'), "bar", "foo='{{baz}}' set");
frag = template({
truthy: false,
baz: "bar"
});
assert.equal(stacheTestHelpers.cloneAndClean(frag).firstChild.getAttribute('foo'), null, "attribute not set if not truthy");
});
QUnit.test("boxes example", function(assert) {
var boxes = [],
Box = DefineMap.extend({
count: {value: 0},
content: {value: 0},
top: {value: 0},
left: {value: 0},
color: {value: 0},
tick: function () {
var count = this.count + 1;
this.assign({
//count: count,
left: Math.cos(count / 10) * 10,
top: Math.sin(count / 10) * 10,
//color: count % 255,
//content: count
});
}
});
for (var i = 0; i < 1; i++) {
boxes.push(new Box({
number: i
}));
}
var stashed = stache(
"<div class='box-view'>"+
"<div class='box' style='top: {{top}}px; left: {{left}}px;'>"+
"</div>"+
"</div>");
var frag = stashed(boxes[0]);
//equal(frag.children.length, 2, "there are 2 childNodes");
assert.ok(/top: 0px/.test( stacheTestHelpers.cloneAndClean(frag).firstChild.firstChild.getAttribute("style") ), "0px");
boxes[0].tick();
assert.ok(! /top: 0px/.test( stacheTestHelpers.cloneAndClean(frag).firstChild.firstChild.getAttribute("style")) , "!0px");
});
QUnit.test('Tokens returning 0 where they should display the number', function(assert) {
var template = "<div id='zero'>{{completed}}</div>";
var frag = stache( template )({
completed: 0
});
assert.equal( stacheTestHelpers.cloneAndClean(frag).firstChild.firstChild.nodeValue, "0", 'zero shown' );
});
QUnit.test('Inverted section function returning numbers', function(assert) {
var template = "<div id='completed'>{{^todos.completed()}}hidden{{/todos.completed}}</div>";
var obsvr = new SimpleMap({
named: false
});
var todos = {
completed: function () {
return obsvr.get('named');
}
};
// check hidden there
var frag = stache( template ) ({
todos: todos
});
assert.deepEqual(stacheTestHelpers.cloneAndClean(frag).firstChild.firstChild.nodeValue, "hidden", 'hidden shown');
// now update the named attribute
obsvr.set('named', true);
var hiddenTextNode = stacheTestHelpers.cloneAndClean(frag).firstChild.firstChild;
assert.notOk(hiddenTextNode && hiddenTextNode.nodeValue, 'hidden gone');
});
QUnit.test("live-binding with escaping", function(assert) {
var template = "<span id='binder1'>{{ name }}</span><span id='binder2'>{{{name}}}</span>";
var teacher = new SimpleMap({
name: "<strong>Mrs Peters</strong>"
});
var tpl = stache( template );
var frag = tpl(teacher);
assert.deepEqual(innerHTML(stacheTestHelpers.cloneAndClean(frag).firstChild), "<strong>Mrs Peters</strong>");
assert.deepEqual(innerHTML(stacheTestHelpers.cloneAndClean(frag).lastChild.firstChild), "Mrs Peters");
teacher.set('name', '<i>Mr Scott</i>');
assert.deepEqual(innerHTML(stacheTestHelpers.cloneAndClean(frag).firstChild), "<i>Mr Scott</i>");
assert.deepEqual(innerHTML(stacheTestHelpers.cloneAndClean(frag).lastChild.firstChild), "Mr Scott");
});
QUnit.test("truthy", function(assert) {
var t = {
template: "{{#name}}Do something, {{this}}!{{/name}}",
expected: "Do something, Andy!",
data: {
name: 'Andy'
}
};
var expected = t.expected.replace(/"/g, '"')
.replace(/\r\n/g, '\n');
assert.deepEqual( getText( t.template , t.data), expected);
});
QUnit.test("falsey", function(assert) {
var t = {
template: "{{^cannot}}Don't do it, {{name}}!{{/cannot}}",
expected: "Don't do it, Andy!",
data: {
name: 'Andy'
}
};
var expected = t.expected.replace(/"/g, '"')
.replace(/\r\n/g, '\n');
assert.deepEqual(getText( t.template, t.data), expected);
});
QUnit.test("Handlebars helpers", function(assert) {
stache.registerHelper('hello', function (options) {
return 'Should not hit this';
});
stache.registerHelper('there', function (options) {
return 'there';
});
// Test for #1985
stache.registerHelper('zero', function (options) {
return 0;
});
stache.registerHelper('bark', function (obj, str, number, options) {
var hash = options.hash || {};
return 'The ' + obj + ' barked at ' + str + ' ' + number + ' times, ' +
'then the ' + hash.obj + ' ' + hash.action + ' ' +
hash.where + ' times' + (hash.loud === true ? ' loudly' : '') + '.';
});
var t = {
template: "{{hello}} {{there()}}! {{bark name 'Austin and Andy' 3 obj=name action='growled and snarled' where=2 loud=true}} Then there were {{zero()}} barks :(",
expected: "Hello there! The dog barked at Austin and Andy 3 times, then the dog growled and snarled 2 times loudly. Then there were 0 barks :(",
data: {
name: 'dog',
hello: 'Hello'
}
};
var expected = t.expected.replace(/"/g, '"')
.replace(/\r\n/g, '\n');
assert.deepEqual( getText(t.template, t.data) , expected);
});
QUnit.test("Handlebars advanced helpers (from docs)", function(assert) {
stache.addHelper('exercise', function (group, action, num, options) {
if (group && group.length > 0 && action && num > 0) {
return options.fn({
group: group,
action: action,
where: options.hash.where,
when: options.hash.when,
num: num
});
} else {
return options.inverse(this);
}
});
var t = {
template: "{{#exercise pets 'walked' 3 where='around the block' when=time}}" +
"Along with the {{#group}}{{.}}, {{/group}}" +
"we {{action}} {{where}} {{num}} times {{when}}." +
"{{else}}" +
"We were lazy today." +
"{{/exercise}}",
expected: "Along with the cat, dog, parrot, we walked around the block 3 times this morning.",
expected2: "We were lazy today.",
data: {
pets: ['cat', 'dog', 'parrot'],
time: 'this morning'
}
};
var template = stache(t.template);
var frag = template(t.data);
var div = doc.createElement("div");
div.appendChild(frag);
assert.equal(innerHTML( stacheTestHelpers.cloneAndClean(div) ), t.expected);
assert.equal(getText(t.template, {}), t.expected2);
});
QUnit.test("Passing functions as data, then executing them", function(assert) {
var t = {
template: "{{#nested}}{{welcome ../name}}{{/nested}}",
expected: "Welcome Andy!",
data: {
name: 'Andy',
nested: {
welcome: function (name) {
return 'Welcome ' + name + '!';
}
}
}
};
var expected = t.expected.replace(/"/g, '"')
.replace(/\r\n/g, '\n');
assert.deepEqual( getText(t.template, t.data), expected);
});
QUnit.test("No arguments passed to helper", function(assert) {
var template = stache("{{noargHelper()}}");
stache.registerHelper("noargHelper", function () {
return "foo"
});
var div1 = doc.createElement('div');
var div2 = doc.createElement('div');
div1.appendChild(template({}));
div2.appendChild(template(new SimpleMap()));
assert.deepEqual(innerHTML(div1), "foo");
assert.deepEqual(innerHTML(div2), "foo");
});
QUnit.test("String literals passed to helper should work (#1143)", function(assert) {
assert.expect(1);
stache.registerHelper("concatStrings", function(arg1, arg2) {
return arg1 + arg2;
});
// Test with '=' because the regexp to find arguments uses that char
// to delimit a keyword-arg name from its value.
var template = stache('{{concatStrings "==" "word"}}');
var div = doc.createElement('div');
div.appendChild(template({}));
assert.equal(innerHTML(div), '==word');
});
QUnit.test("No arguments passed to helper with list", function(assert) {
var template = stache("{{#items}}{{noargHelper()}}{{/items}}");
var div = doc.createElement('div');
div.appendChild(template({
items: new DefineList([{
name: "Brian"
}])
}, {
noargHelper: function () {
return "foo"
}
}));
assert.deepEqual(innerHTML(stacheTestHelpers.cloneAndClean(div)), "foo");
});
if(isNormalDOM) {
QUnit.test("Partials and observes", function(assert) {
var template;
var div = doc.createElement('div');
template = stache("<table><thead><tr>{{#theData}}{{>list}}{{/theData}}</tr></thead></table>")
var dom = template({
theData: new SimpleMap({
list: ["hi", "there"]
})
},{
partials: {
list: stache("{{#list}}<th>{{.}}</th>{{/list}}")
}
});
div.appendChild(dom);
var ths = div.getElementsByTagName('th');
assert.equal(ths.length, 2, 'Got two table headings');
assert.equal(innerHTML(ths[0]), 'hi', 'First column heading correct');
assert.equal(innerHTML(ths[1]), 'there', 'Second column heading correct');
});
}
QUnit.test("Handlebars helper: if/else", function(assert) {
var expected;
var t = {
template: "{{#if name}}{{name}}{{/if}}{{#if missing}} is missing!{{/if}}",
expected: "Andy",
data: {
name: 'Andy',
missing: undefined
}
};
expected = t.expected.replace(/"/g, '"')
.replace(/\r\n/g, '\n');
assert.deepEqual(getText(t.template,t.data), expected);
t.data.missing = null;
expected = t.expected.replace(/"/g, '"')
.replace(/\r\n/g, '\n');
assert.deepEqual(getText(t.template,t.data), expected);
});
QUnit.test("Handlebars helper: unless", function(assert) {
var t = {
template: "{{#unless missing}}Andy is missing!{{/unless}}" +
"{{#unless isCool}} But he wasn't cool anyways.{{/unless}}",
expected: "Andy is missing! But he wasn't cool anyways.",
data: {
name: 'Andy'
},
liveData: new SimpleMap({
name: 'Andy',
// #1202 #unless does not work with computes
isCool: new Observation(function isCool() {
return t.liveData.get("missing");
})
})
};
var expected = t.expected.replace(/"/g, '"')
.replace(/\r\n/g, '\n');
//deepEqual(getText(t.template, t.data), expected);
// #1019 #unless does not live bind
var div = doc.createElement('div');
div.appendChild(stache(t.template)(t.liveData));
assert.deepEqual( innerHTML(div), expected, '#unless condition false = '+expected);
t.liveData.set('missing', true);
assert.deepEqual( innerHTML(div), '', '#unless condition true');
});
QUnit.test("Handlebars helper: each", function(assert) {
var t = {
template: "{{#each names}}{{this}} {{/each}}",
expected: "Andy Austin Justin ",
data: {
names: ['Andy', 'Austin', 'Justin']
},
data2: {
names: new DefineList(['Andy', 'Austin', 'Justin'])
}
};
var expected = t.expected.replace(/"/g, '"')
.replace(/\r\n/g, '\n');
assert.deepEqual( getText(t.template,t.data) , expected);
var div = doc.createElement('div');
div.appendChild(stache(t.template)(t.data2));
assert.deepEqual( innerHTML(stacheTestHelpers.cloneAndClean(div)), expected, 'Using Observe.List');
t.data2.names.push('What');
});
QUnit.test("Handlebars helper: with", function(assert) {
var t = {
template: "{{#with person}}{{name}}{{/with}}",
expected: "Andy",
data: {
person: {
name: 'Andy'
}
}
};
var expected = t.expected.replace(/"/g, '"')
.replace(/\r\n/g, '\n');
assert.deepEqual(getText(t.template,t.data), expected, '#with person');
var v = {
template: "{{#with person}}{{name}}{{/with}}",
expected: "",
data: {
person: null,
name: "Andy"
}
};
expected = v.expected.replace(/"/g, '"')
.replace(/\r\n/g, '\n');
assert.deepEqual(getText(v.template,v.data), expected, '#with person - person === null');
});
QUnit.test("render with double angle", function(assert) {
var text = "{{& replace_me }}{{{ replace_me_too }}}" +
"<ul>{{#animals}}" +
"<li>{{.}}</li>" +
"{{/animals}}</ul>";
var compiled = getText(text,{
animals: this.animals
});
assert.equal(compiled, "<ul><li>sloth</li><li>bear</li><li>monkey</li></ul>", "works")
});
QUnit.test("comments", function(assert) {
var text = "{{! replace_me }}" +
"<ul>{{#animals}}" +
"<li>{{.}}</li>" +
"{{/animals}}</ul>";
var compiled = getText(text,{
animals: this.animals
});
assert.equal(compiled, "<ul><li>sloth</li><li>bear</li><li>monkey</li></ul>")
});
QUnit.test("multi line", function(assert) {
var text = "a \n b \n c";
assert.equal(getTextFromFrag( stache(text)({}) ), text)
});
QUnit.test("multi line elements", function(assert) {
var text = "<div\n class=\"{{myClass}}\" />",
result = stache(text)({myClass: 'a'});
assert.equal(result.firstChild.className, "a", "class name is right");
});
QUnit.test("escapedContent", function(assert) {
var text = "<span>{{ tags }}</span><label>&</label><strong>{{ number }}</strong><input value='{{ quotes }}'/>";
var div = doc.createElement('div');
div.appendChild( stache(text)({
tags: "foo < bar < car > zar > poo",
quotes: "I use 'quote' fingers & &ersands \"a lot\"",
number: 123
}) );
assert.equal(div.getElementsByTagName('span')[0].firstChild.nodeValue, "foo < bar < car > zar > poo");
assert.equal(div.getElementsByTagName('strong')[0].firstChild.nodeValue, 123);
assert.equal(div.getElementsByTagName('input')[0].value, "I use 'quote' fingers & &ersands \"a lot\"", "attributes are always safe, and strings are kept as-is without additional escaping");
assert.equal( innerHTML(div.getElementsByTagName('label')[0]), "&", "text-based html entities work fine");
});
QUnit.test("unescapedContent", function(assert) {
var text = "<span>{{{ tags }}}</span><div>{{{ tags }}}</div><input value='{{{ quotes }}}'/>";
var div = doc.createElement('div');
div.appendChild( stache(text)({
tags: "<strong>foo</strong><strong>bar</strong>",
quotes: 'I use \'quote\' fingers "a lot"'
}) );
assert.equal(div.getElementsByTagName('span')[0].firstChild.nodeType, 1,"");
assert.equal( innerHTML(div.getElementsByTagName('div')[0]).toLowerCase(), "<strong>foo</strong><strong>bar</strong>");
assert.equal( innerHTML(div.getElementsByTagName('span')[0]).toLowerCase(), "<strong>foo</strong><strong>bar</strong>");
assert.equal(div.getElementsByTagName('input')[0].value, "I use 'quote' fingers \"a lot\"", "escaped no matter what");
});
QUnit.test("attribute single unescaped, html single unescaped", function(assert) {
var text = "<div id='me' class='{{#task.completed}}complete{{/task.completed}}'>{{ task.name }}</div>";
var task = new SimpleMap({
name: 'dishes'
});
var div = doc.createElement('div');
div.appendChild(stache(text)({
task: task
}));
assert.equal( innerHTML(div.getElementsByTagName('div')[0]), "dishes", "html correctly dishes")
assert.equal(div.getElementsByTagName('div')[0].className, "", "class empty")
task.set('name', 'lawn')
assert.equal( innerHTML(div.getElementsByTagName('div')[0]), "lawn", "html correctly lawn")
assert.equal(div.getElementsByTagName('div')[0].className, "", "class empty")
task.set('completed', true);
assert.equal(div.getElementsByTagName('div')[0].className, "complete", "class changed to complete")
});
QUnit.test("select live binding", function(assert) {
var text = "<select>{{ #todos }}<option>{{ name }}</option>{{ /todos }}</select>";
var todos, div;
todos = new DefineList([{
id: 1,
name: 'Dishes'
}]);
div = doc.createElement('div');
div.appendChild( stache(text)({todos: todos}) );
assert.equal(div.getElementsByTagName('option')
.length, 1, '1 item in list')
todos.push({
id: 2,
name: 'Laundry'
});
assert.equal(div.getElementsByTagName('option')
.length, 2, '2 items in list')
todos.splice(0, 2);
assert.equal(div.getElementsByTagName('option')
.length, 0, '0 items in list')
});
QUnit.test('multiple hookups in a single attribute', function(assert) {
var text = '<div class=\'{{ obs.foo }}' +
'{{ obs.bar }}{{ obs.baz }}{{ obs.nest.what }}\'></div>';
var obs = new SimpleMap({
foo: 'a',
bar: 'b',
baz: 'c',
nest: new SimpleMap({
what: 'd'
})
});
var div = doc.createElement('div');
div.appendChild( stache(text)({
obs: obs
}) );
var innerDiv = div.firstChild;
assert.equal(getAttr(innerDiv, 'class'), "abcd", 'initial render');
obs.set('bar', 'e');
assert.equal(getAttr(innerDiv, 'class'), "aecd", 'initial render');
obs.set('bar', 'f');
assert.equal(getAttr(innerDiv, 'class'), "afcd", 'initial render');
obs.get('nest').set('what', 'g');
assert.equal(getAttr(innerDiv, 'class'), "afcg", 'nested observe');
});
QUnit.test('adding and removing multiple html content within a single element', function(assert) {
var text, obs;
text = '<div>{{ obs.a }}{{ obs.b }}{{ obs.c }}</div>';
obs = new SimpleMap({
a: 'a',
b: 'b',
c: 'c'
});
var div = doc.createElement('div');
div.appendChild(stache(text)({
obs: obs
}));
assert.equal( innerHTML(div.firstChild), 'abc', 'initial render');
obs.set({
a: '',
b: '',
c: ''
});
assert.equal(innerHTML(div.firstChild), '', 'updated values');
obs.set({
c: 'c'
});
assert.equal( innerHTML(div.firstChild), 'c', 'updated values');
});
QUnit.test('live binding and removeAttr', function(assert) {
var text = '{{ #obs.show }}' +
'<p {{ ../obs.attributes }} class="{{ ../obs.className }}"><span>{{ ../obs.message }}</span></p>' +
'{{ /obs.show }}',
obs = new SimpleMap({
show: true,
className: 'myMessage',
attributes: 'some=\"myText\"',
message: 'Live long and prosper'
}),
div = doc.createElement('div');
div.appendChild(stache(text)({
obs: obs
}));
var p = div.getElementsByTagName('p')[0],
span = p.getElementsByTagName('span')[0];
assert.equal(p.getAttribute("some"), "myText", 'initial render attr');
assert.equal(getAttr(p, "class"), "myMessage", 'initial render class');
assert.equal( innerHTML(span), 'Live long and prosper', 'initial render innerHTML');
obs.set('className', undefined);
assert.equal(getAttr(p, "class"), '', 'class is undefined');
obs.set('className', 'newClass');
assert.equal(getAttr(p, "class"), 'newClass', 'class updated');
obs.set('attributes',undefined);
assert.equal(p.getAttribute('some'), null, 'attribute is undefined');
obs.set('attributes', 'some="newText"');
//
assert.equal(p.getAttribute('some'), 'newText', 'attribute updated');
obs.set('message',undefined);
assert.equal(innerHTML(span), '', 'text node value is empty');
obs.set('message', 'Warp drive, Mr. Sulu');
assert.equal(innerHTML(span), 'Warp drive, Mr. Sulu', 'text node updated');
obs.set('show',undefined);
assert.equal( innerHTML(div), '', 'value in block statement is undefined');
obs.set('show', true);
p = div.getElementsByTagName('p')[0];
span = p.getElementsByTagName('span')[0];
assert.equal(p.getAttribute("some"), "newText", 'value in block statement updated attr');
assert.equal(getAttr(p, "class"), "newClass", 'value in block statement updated class');
assert.equal( innerHTML(span), 'Warp drive, Mr. Sulu', 'value in block statement updated innerHTML');
});
QUnit.test('hookup within a tag', function(assert) {
var text = '<div {{ obs.foo }} ' + '{{ obs.baz }}>lorem ipsum</div>',
obs = new SimpleMap({
foo: 'class="a"',
baz: 'some=\'property\''
}),
compiled = stache(text)({obs: obs})
var div = doc.createElement('div');
div.appendChild(compiled);
var anchor = div.getElementsByTagName('div')[0];
assert.equal(getAttr(anchor, 'class'), 'a');
assert.equal(anchor.getAttribute('some'), 'property');
obs.set('foo', 'class="b"');
assert.equal(getAttr(anchor, 'class'), 'b');
assert.equal(anchor.getAttribute('some'), 'property');
obs.set('baz', 'some=\'new property\'');
assert.equal(getAttr(anchor, 'class'), 'b');
assert.equal(anchor.getAttribute('some'), 'new property');
obs.set('foo', 'class=""');
obs.set('baz', '');
assert.equal(getAttr(anchor, 'class'), "", 'anchor class blank');
assert.equal(anchor.getAttribute('some'), undefined, 'attribute "some" is undefined');
});
QUnit.test('single escaped tag, removeAttr', function(assert) {
var text = '<div {{ obs.foo }}>lorem ipsum</div>',
obs = new SimpleMap({
foo: 'data-bar="john doe\'s bar"'
}),
compiled = stache(text)({obs: obs})
var div = doc.createElement('div');
div.appendChild(compiled);
var anchor = div.getElementsByTagName('div')[0];
assert.equal(anchor.getAttribute('data-bar'), "john doe's bar");
obs.set('foo',undefined);
assert.equal(anchor.getAttribute('data-bar'), null);
obs.set('foo', 'data-bar="baz"');
assert.equal(anchor.getAttribute('data-bar'), 'baz');
});
QUnit.test('html comments', function(assert) {
var text = '<!-- bind to changes in the todo list --> <div>{{obs.foo}}</div>';
var obs = new SimpleMap({
foo: 'foo'
});
var compiled = stache(text)({
obs: obs
});
var div = doc.createElement('div');
div.appendChild(compiled);
assert.equal( innerHTML(div.getElementsByTagName('div')[0]), 'foo', 'Element as expected');
});
QUnit.test("hookup and live binding", function(assert) {
var text = "<div class='{{ task.completed }}' {{ domData 'task' task }}>" +
"{{ task.name }}" +
"</div>",
task = new SimpleMap({
completed: false,
className: 'someTask',
name: 'My Name'
}),
compiled = stache(text)({
task: task
}),
div = doc.createElement('div');
div.appendChild(compiled)
var child = div.getElementsByTagName('div')[0];
assert.ok(child.className.indexOf("false") > -1, "is incomplete")
assert.ok( !! domData.get(child, 'task'), "has data")
assert.equal(innerHTML(child), "My Name", "has name")
task.set({
completed: true,
name: 'New Name'
});
assert.ok(child.className.indexOf("true") !== -1, "is complete")
assert.equal(innerHTML(child), "New Name", "has new name")
});
QUnit.test('multiple curly braces in a block', function(assert) {
var text = '{{^obs.items}}' +
'<li>No items</li>' +
'{{/obs.items}}' +
'{{#obs.items}}' +
'<li>{{name}}</li>' +
'{{/obs.items}}',
obs = new SimpleMap({
items: []
}),
compiled = stache(text)({obs: obs})
var ul = doc.createElement('ul');
ul.appendChild(compiled);
assert.equal( innerHTML(ul.getElementsByTagName('li')[0]), 'No items', 'initial observable state');
obs.set('items', [{
name: 'foo'
}]);
assert.equal( innerHTML(ul.getElementsByTagName('li')[0]), 'foo', 'updated observable');
});
QUnit.test("unescape bindings change", function(assert) {
var l = new DefineList([{
complete: true
}, {
complete: false
}, {
complete: true
}]);
var completed = function () {
l.get('length');
var num = 0;
l.forEach(function (item) {
if (item.get('complete')) {
num++;
}
})
return num;
};
var text = '<div>{{ completed() }}</div>',
compiled = stache(text)({
completed: completed
});
var div = doc.createElement('div');
div.appendChild(compiled);
var child = div.getElementsByTagName('div')[0];
assert.equal( innerHTML(child), "2", "at first there are 2 true bindings");
var item = new SimpleMap({
complete: true,
id: "THIS ONE"
});
l.push(item);
assert.equal(innerHTML(child), "3", "now there are 3 complete");
item.set('complete', false);
assert.equal(innerHTML(child), "2", "now there are 2 complete");
l.pop();
item.set('complete', true);
assert.equal(innerHTML(child), "2", "there are still 2 complete");
});
QUnit.test("escape bindings change", function(assert) {
var l = new DefineList([{
complete: true
}, {
complete: false
}, {
complete: true
}]);
var completed = function () {
l.get('length');
var num = 0;
l.forEach(function (item) {
if (item.get('complete')) {
num++;
}
})
return num;
};
var text = '<div>{{{ completed() }}}</div>',
compiled = stache(text)({
completed: completed
});
var div = doc.createElement('div');
div.appendChild(compiled);
var child = div.getElementsByTagName('div')[0];
assert.equal(innerHTML(child), "2", "at first there are 2 true bindings");
var item = new SimpleMap({
complete: true
})
l.push(item);
assert.equal(innerHTML(child), "3", "now there are 3 complete");
item.set('complete', false);
assert.equal(innerHTML(child), "2", "now there are 2 complete");
});
QUnit.test("tag bindings change", function(assert) {
var l = new DefineList([{
complete: true
}, {
complete: false
}, {
complete: true
}]);
var completed = function () {
l.get('length');
var num = 0;
l.forEach(function (item) {
if (item.get('complete')) {
num++;
}
})
return "items='" + num + "'";
};
var text = '<div {{{ completed() }}}></div>',
compiled = stache(text)({
completed: completed
});
var div = doc.createElement('div');
div.appendChild(compiled);
var child = div.getElementsByTagName('div')[0];
assert.equal(child.getAttribute("items"), "2", "at first there are 2 true bindings");
var item = new SimpleMap({
complete: true
})
l.push(item);
assert.equal(child.getAttribute("items"), "3", "now there are 3 complete");
item.set('complete', false);
assert.equal(child.getAttribute("items"), "2", "now there are 2 complete");
})
QUnit.test("attribute value bindings change", function(assert) {
var l = new DefineList([{
complete: true
}, {
complete: false
}, {
complete: true
}]);
var completed = function () {
l.get('length');
var num = 0;
l.forEach(function (item) {
if (item.get('complete')) {
num++;
}
});
return num;
};
var text = '<div items="{{{ completed() }}}"></div>',
compiled = stache(text)({
completed: completed
});
var div = doc.createElement('div');
div.appendChild(compiled);
var child = div.getElementsByTagName('div')[0];
assert.equal(child.getAttribute("items"), "2", "at first there are 2 true bindings");
var item = new SimpleMap({
complete: true
});
l.push(item);
assert.equal(child.getAttribute("items"), "3", "now there are 3 complete");
item.set('complete', false);
assert.equal(child.getAttribute("items"), "2", "now there are 2 complete");
});
QUnit.test("in tag toggling", function(assert) {
var text = "<div {{ obs.val }}></div>"
var obs = new SimpleMap({
val: 'foo="bar"'
})
var compiled = stache(text)({
obs: obs
});
var div = doc.createElement('div');
div.appendChild(compiled);
obs.set('val', "bar='foo'");
obs.set('val', 'foo="bar"')
var d2 = div.getElementsByTagName('div')[0];
// toUpperCase added to normalize cases for IE8
assert.equal(d2.getAttribute("foo"), "bar", "bar set");
assert.equal(d2.getAttribute("bar"), null, "bar set")
});
// not sure about this w/ mustache
QUnit.test("nested properties", function(assert) {
var text = "<div>{{ obs.name.first }}</div>"
var obs = new SimpleMap({
name: new SimpleMap({
first: "Justin"
})
})
var compiled = stache(text)({
obs: obs
});
var div = doc.createElement('div');
div.appendChild(compiled);
div = div.getElementsByTagName('div')[0];
assert.equal(innerHTML(div), "Justin")
obs.get('name').set('first', "Brian")
assert.equal(innerHTML(div), "Brian")
});
QUnit.test("tags without chidren or ending with /> do not change the state", function(assert) {
var text = "<table><tr><td/>{{{ obs.content }}}</tr></div>"
var obs = new SimpleMap({
content: "<td>Justin</td>"
})
var compiled = stache(text)({
obs: obs
});
var div = doc.createElement('div');
var html = compiled;
div.appendChild(html);
assert.equal(div.getElementsByTagName('span')
.length, 0, "there are no spans");
assert.equal(div.getElementsByTagName('td')
.length, 2, "there are 2 td");
})
QUnit.test("nested live bindings", function(assert) {
assert.expect(0);
var items = new DefineList([{
title: 0,
is_done: false,
id: 0
}]);
var div = doc.createElement('div');
var template = stache('<form>{{#items}}{{^is_done}}<div id="{{title}}"></div>{{/is_done}}{{/items}}</form>')
div.appendChild(template({
items: items
}));
items.push({
title: 1,
is_done: false,
id: 1
});
// this will throw an error unless Mustache protects against
// nested objects
items[0].set('is_done', true);
});
QUnit.test("list nested in observe live bindings", function(assert) {
var template = stache("<ul>{{#data.items}}<li>{{name}}</li>{{/data.items}}</ul>");
var data = new SimpleMap({
items: new DefineList([{
name: "Brian"
}, {
name: "Fara"
}])
});
var div = doc.createElement('div');
div.appendChild(template({
data: data
}));
data.get("items").push(new SimpleMap({
name: "Scott"
}))
assert.ok(/Brian/.test(innerHTML(div)), "added first name")
assert.ok(/Fara/.test(innerHTML(div)), "added 2nd name")
assert.ok(/Scott/.test(innerHTML(div)), "added name after push")
});
QUnit.test("trailing text", function(assert) {
var template = stache("There are {{ length }} todos")
var div = doc.createElement('div');
div.appendChild(template(new DefineList([{}, {}])));
assert.ok(/There are 2 todos/.test(innerHTML(div)), "got all text");
});
if(isNormalDOM) {
QUnit.test("recursive views", function(assert) {
var template = stache('<div class="template">'+
'{{#items}}'+
'<div class="loop">'+
'{{#item.children}}'+
'<div class="node">'+
'{{>recursive}}'+
'</div>'+
'{{/item.children}}'+
'{{^item.children}}'+
'<div class="leaf">L</div>'+
'{{/item.children}}'+
'</div>'+
'{{/items}}'+
'</div>');
var data = new DefineList([{
label: 'branch1',
children: [{
id: 2,
label: 'branch2'
}]
}]);
var div = doc.createElement('div');
var frag = template({
items: data
},{
partials: {
recursive: template
}
})
div.appendChild(frag);
assert.ok(/class="?leaf"?/.test(innerHTML(div)), "we have a leaf")
});
}
QUnit.test("live binding textarea", function(assert) {
var template = stache("<textarea>Before{{ obs.middle }}After</textarea>");
var obs = new SimpleMap({
middle: "yes"
}),
div = doc.createElement('div');
div.appendChild(template({
obs: obs
}));
var textarea = div.firstChild;
assert.equal(getValue(textarea), "BeforeyesAfter");
obs.set("middle", "Middle");
assert.equal(getValue(textarea), "BeforeMiddleAfter");
});
QUnit.test("helper parameters don't convert functions", function(assert) {
stache.registerHelper('helperWithFn', function (fn) {
assert.ok(typeof fn === "function", 'Parameter is a function');
assert.equal(fn(), 'Hit me!', 'Got the expected function');
});
var renderer = stache('{{helperWithFn test}}');
renderer({
test: function () {
return 'Hit me!';
}
});
})
QUnit.test("computes as helper parameters don't get converted", function(assert) {
stache.registerHelper('computeTest', function (no) {
assert.equal(no(), 5, 'Got computed calue');
assert.ok(no.isComputed, 'no is still a compute')
});
var renderer = stache('{{computeTest test}}');
renderer({
test: new SimpleObservable(5)
});
});
QUnit.test("computes are supported in default helpers", function(assert) {
var staches = {
"if": "{{#if test}}if{{else}}else{{/if}}",
"not_if": "not_{{^if test}}not{{/if}}if",
"each": "{{#each test}}{{.}}{{/each}}",
"with": "wit{{#with test}}<span>{{[3]}}</span>{{/with}}"
};
var template = stache("There are {{ length }} todos");
var div = doc.createElement('div');
div.appendChild(template(new DefineList([{}, {}])));
assert.ok(/There are 2 todos/.test(innerHTML(div)), "got all text");
var renderer, result, data, actual, span;
for (result in staches) {
renderer = stache(staches[result]);
data = ["e", "a", "c", "h"];
div = doc.createElement("div");
actual = renderer({
test: new DefineList(data)
});
div.appendChild(actual);
span = div.getElementsByTagName("span")[0];
if (span && span.firstChild) {
div.insertBefore(span.firstChild, span);
div.removeChild(span);
}
actual = innerHTML(stacheTestHelpers.cloneAndClean(div));
assert.equal(actual, result, "canCompute resolved for helper " + result);
}
var inv_staches = {
"else": "{{#if test}}if{{else}}else{{/if}}",
"not_not_if": "not_{{^if test}}not_{{/if}}if",
"not_each": "not_{{#each test}}_{{/each}}each"
//"not_with": "not{{#with test}}_{{/with}}_with" //{{#with}} *always* renders non-inverse block
};
for (result in inv_staches) {
renderer = stache(inv_staches[result]);
data = null;
div = doc.createElement("div");
actual = renderer({
test: null
});
div.appendChild(actual);
actual = innerHTML(div);
assert.equal(actual, result, "canCompute resolved for helper " + result);
}
});
//Issue 233
QUnit.test("multiple tbodies in table hookup", function(assert) {
var text = "<table>" +
"{{#people}}" +
"<tbody><tr><td>{{name}}</td></tr></tbody>" +
"{{/people}}" +
"</table>",
people = new DefineList([{
name: "Steve"
}, {
name: "Doug"
}]),
compiled = stache(text)({
people: people
});
assert.equal( compiled.firstChild.getElementsByTagName("tbody").length, 2, "two tbodies");
});
// http://forum.javascriptmvc.com/topic/live-binding-on-mustache-template-does-not-seem-to-be-working-with-nested-properties
QUnit.test("Observe with array attributes", function(assert) {
var renderer = stache('<ul>{{#todos}}<li>{{.}}</li>{{/todos}}</ul><div>{{message}}</div>');
var div = doc.createElement('div');
var data = new SimpleMap({
todos: new DefineList( ['Line #1', 'Line #2', 'Line #3'] ),
message: 'Hello',
count: 2
});
div.appendChild(renderer(data));
assert.equal(innerHTML(div.getElementsByTagName('li')[1]), 'Line #2', 'Check initial array');
assert.equal(innerHTML(div.getElementsByTagName('div')[0]), 'Hello', 'Check initial message');
data.get('todos').set(1, 'Line #2 changed');
data.set('message', 'Hello again');
assert.equal(innerHTML(div.getElementsByTagName('li')[1]), 'Line #2 changed', 'Check updated array');
assert.equal(innerHTML(div.getElementsByTagName('div')[0]), 'Hello again', 'Check updated message');
});
QUnit.test("Observe list returned from the function", function(assert) {
var renderer = stache('<ul>{{#todos()}}<li>{{.}}</li>{{/todos}}</ul>');
var div = doc.createElement('div');
var todos = new DefineList();
var data = {
todos: function () {
return todos;
}
};
div.appendChild(renderer(data));
todos.push("Todo #1")
assert.equal(div.getElementsByTagName('li')
.length, 1, 'Todo is successfuly created');
assert.equal(innerHTML(div.getElementsByTagName('li')[0]), 'Todo #1', 'Pushing to the list works');
});
// https://github.com/canjs/canjs/issues/228
QUnit.test("Contexts within helpers not always resolved correctly", function(assert) {
stache.registerHelper("bad_context", function (context, options) {
return ["<span>"+this.text+"</span> should not be ",options.fn(context)];
});
var renderer = stache('{{#bad_context next_level}}<span>{{text}}</span><br/><span>{{other_text}}</span>{{/bad_context}}'),
data = {
next_level: {
text: "bar",
other_text: "In the inner context"
},
text: "foo"
},
div = doc.createElement('div');
div.appendChild(renderer(data));
assert.equal(innerHTML(div.getElementsByTagName('span')[0]), "foo", 'Incorrect context passed to helper');
assert.equal(innerHTML(div.getElementsByTagName('span')[1]), "bar", 'Incorrect text in helper inner template');
assert.equal(innerHTML(div.getElementsByTagName('span')[2]), "In the inner context", 'Incorrect other_text in helper inner template');
});
// https://github.com/canjs/canjs/issues/227
QUnit.test("Contexts are not always passed to partials properly", function(assert) {
var inner = stache('{{#if ../other_first_level}}{{../other_first_level}}{{else}}{{second_level}}{{/if}}');
var renderer = stache('{{#first_level}}<span>{{> inner}}</span> should equal <span>{{../other_first_level}}</span>{{/first_level}}'),
data = {
first_level: {
second_level: "bar"
},
other_first_level: "foo"
},
div = doc.createElement('div');
div.appendChild(renderer(data, {partials: {inner: inner}}));
assert.equal(innerHTML(div.getElementsByTagName('span')[0]), "foo", 'Incorrect context passed to helper');
assert.equal(innerHTML(div.getElementsByTagName('span')[1]), "foo", 'Incorrect text in helper inner template');
});
// https://github.com/canjs/canjs/issues/231
QUnit.test("Functions and helpers should be passed the same context", function(assert) {
var textNodes = function(el, cb) {
var cur = el.firstChild;
while(cur){
if(cur.nodeType === 3) {
cb(cur)
} else if(el.nodeType === 1) {
textNodes(cur, cb)
}
cur = cur.nextSibling;
}
};
stache.registerHelper("to_upper", function (options) {
var frag = options.fn(options.context);
textNodes(frag, function(el){
el.nodeValue = el.nodeValue.toUpperCase();
});
return frag;
});
var renderer = stache(' "<span>{{#to_upper()}}{{next_level.text()}}{{/to_upper}}</span>"'),
data = {
next_level: {
text: function () {
return this.other_text;
},
other_text: "In the inner context"
}
},
div = doc.createElement('div');
div.appendChild(renderer(data));
assert.equal(innerHTML(div.getElementsByTagName('span')[0]), data.next_level.other_text.toUpperCase(), 'correct context passed to helper');
});
// https://github.com/canjs/canjs/issues/153
QUnit.test("Interpolated values when iterating through an Observe.List should still render when not surrounded by a DOM node", function(assert) {
var renderer = stache('{{ #todos }}{{ name }}{{ /todos }}'),
renderer2 = stache('{{ #todos }}<span>{{ name }}</span>{{ /todos }}'),
todos = [{
id: 1,
name: 'Dishes'
}, {
id: 2,
name: 'Forks'
}],
liveData = {
todos: new DefineList(todos)
},
plainData = {
todos: todos
},
div = doc.createElement('div');
div.appendChild(renderer2(plainData));
assert.equal(innerHTML(div.getElementsByTagName('span')[0]), "Dishes", 'Array item rendered with DOM container');
assert.equal(innerHTML(div.getElementsByTagName('span')[1]), "Forks", 'Array item rendered with DOM container');
div.innerHTML = '';
div.appendChild(renderer2(liveData));
assert.equal(innerHTML(div.getElementsByTagName('span')[0]), "Dishes", 'List item rendered with DOM container');
assert.equal(innerHTML(div.getElementsByTagName('span')[1]), "Forks", 'List item rendered with DOM container');
div = doc.createElement('div');
div.appendChild(renderer(plainData));
assert.equal(innerHTML(div), "DishesForks", 'Array item rendered without DOM container');
div = doc.createElement('div');
div.appendChild(renderer(liveData));
assert.equal(innerHTML(div), "DishesForks", 'List item rendered without DOM container');
liveData.todos.push({
id: 3,
name: 'Knives'
});
assert.equal(innerHTML(div), "DishesForksKnives", 'New list item rendered without DOM container');
});
QUnit.test("objects with a 'key' or 'index' property should work in helpers", function(assert) {
var renderer = stache('{{ #obj }}{{ show_name(this) }}{{ /obj }}'),
div = doc.createElement('div');
div.appendChild(renderer({
obj: {
id: 2,
name: 'Forks',
key: 'bar'
}
}, {
show_name: function (obj) {
return obj.name;
}
}));
assert.equal(innerHTML(div), "Forks", 'item name rendered');
div = doc.createElement('div');
div.appendChild(renderer({
obj: {
id: 2,
name: 'Forks',
index: 'bar'
}
}, {
show_name: function (obj) {
return obj.name;
}
}));
assert.equal(innerHTML(div), "Forks", 'item name rendered');
});
QUnit.test("2 way binding helpers", function(assert) {
var Value = function (el, value) {
this.updateElement = function (ev, newVal) {
el.value = newVal || "";
};
value.bind("change", this.updateElement);
el.onchange = function () {
value(el.value)
}
this.teardown = function () {
value.unbind("change", this.updateElement);
el.onchange = null;
}
el.value = value() || "";
}
var val;
stache.registerHelper('myValue', function (value) {
return function (el) {
val = new Value(el, value);
}
});
var renderer = stache('<input {{myValue user.name}}/>');
var div = doc.createElement('div'),
u = new SimpleMap({
name: "Justin"
});
div.appendChild(renderer({
user: u
}));
var input = div.getElementsByTagName('input')[0];
assert.equal(input.value, "Justin", "Name is set correctly")
u.set('name', 'Eli')
assert.equal(input.value, "Eli", "Changing observe updates value");
input.value = "Austin";
input.onchange();
assert.equal(u.get('name'), "Austin", "Name changed by input field");
val.teardown();
// name is undefined
renderer = stache('<input {{myValue user.name}}/>');
div = doc.createElement('div');
u = new SimpleMap({});
div.appendChild(renderer({
user: u
}));
input = div.getElementsByTagName('input')[0];
assert.equal(input.value, "", "Name is set correctly")
u.set('name', 'Eli')
assert.equal(input.value, "Eli", "Changing observe updates value");
input.value = "Austin";
input.onchange();
assert.equal(u.get('name'), "Austin", "Name changed by input field");
val.teardown();
// name is null
renderer = stache('<input {{myValue user.name}}/>');
div = doc.createElement('div');
u = new SimpleMap({
name: null
});
div.appendChild(renderer({
user: u
}));
input = div.getElementsByTagName('input')[0];
assert.equal(input.value, "", "Name is set correctly with null")
u.set('name', 'Eli')
assert.equal(input.value, "Eli", "Changing observe updates value");
input.value = "Austin";
input.onchange();
assert.equal(u.get('name'), "Austin", "Name changed by input field");
val.teardown();
});
QUnit.test("can pass in partials", function(assert) {
var hello = stache("<p>Hello {{> name}}</p>");
var fancyName = stache("<span class='fancy'>{{name}}</span>");
var result = hello({
name: "World"
}, {
partials: {
name: fancyName
}
});
assert.ok(/World/.test(innerHTML(result.firstChild)), "Hello World worked");
});
QUnit.test("can pass in helpers", function(assert) {
var helpers = stache("<p>Hello {{cap name}}</p>");
var result = helper