From 2ddf2b1f83f41ca6bca5d2045f2e15757cb23c80 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 13 Aug 2012 10:45:04 -0700 Subject: [PATCH] preserve/events/helpers tests (and fixes) --- packages/handlebars/evaluate.js | 2 +- packages/templating/deftemplate.js | 6 +- packages/templating/html_scanner.js | 3 +- packages/templating/templating_tests.html | 32 ++++++ packages/templating/templating_tests.js | 115 ++++++++++++++++++++++ 5 files changed, 154 insertions(+), 4 deletions(-) diff --git a/packages/handlebars/evaluate.js b/packages/handlebars/evaluate.js index 7c5e28d37f..4064e338ce 100644 --- a/packages/handlebars/evaluate.js +++ b/packages/handlebars/evaluate.js @@ -136,7 +136,7 @@ Handlebars.evaluate = function (ast, data, options) { var dataThis = stack.data; var data; - if (id[0] === 0 && (id[1] in helpers) && ! scopedToContext) { + if (id[0] === 0 && helpers.hasOwnProperty(id[1]) && ! scopedToContext) { // first path segment is a helper data = helpers[id[1]]; } else { diff --git a/packages/templating/deftemplate.js b/packages/templating/deftemplate.js index 42fedb6fcd..746791b439 100644 --- a/packages/templating/deftemplate.js +++ b/packages/templating/deftemplate.js @@ -70,7 +70,8 @@ // XXX forms hooks into this to add "bind"? Meteor._template_decl_methods = { - _tmpl_data: {}, // methods store data here (event map, etc.) + // methods store data here (event map, etc.). initialized per template. + _tmpl_data: null, // these functions must be generic (i.e. use `this`) events: function (eventMap) { var events = @@ -90,7 +91,7 @@ }, helpers: function (helperMap) { var helpers = - (this._tmpl_data.events = (this._tmpl_data.events || {})); + (this._tmpl_data.helpers = (this._tmpl_data.helpers || {})); for(var h in helperMap) helpers[h] = helperMap[h]; } @@ -179,6 +180,7 @@ Template[name] = partial; _.extend(partial, Meteor._template_decl_methods); + partial._tmpl_data = {}; Meteor._partials[name] = partial; } diff --git a/packages/templating/html_scanner.js b/packages/templating/html_scanner.js index 238b291e91..d5ac1954ae 100644 --- a/packages/templating/html_scanner.js +++ b/packages/templating/html_scanner.js @@ -111,7 +111,8 @@ var html_scanner = { _handleTag: function (results, tag, attribs, contents, parseError) { - // trim the tag contents + // trim the tag contents. + // this is a courtesy and is also relied on by some unit tests. contents = contents.match(/^[ \t\r\n]*([\s\S]*?)[ \t\r\n]*$/)[1]; // do we have 1 or more attribs? diff --git a/packages/templating/templating_tests.html b/packages/templating/templating_tests.html index 4b79606bf4..f6e9d05d8d 100644 --- a/packages/templating/templating_tests.html +++ b/packages/templating/templating_tests.html @@ -234,3 +234,35 @@ + + + + + + + + + + + + diff --git a/packages/templating/templating_tests.js b/packages/templating/templating_tests.js index a451fbfa21..11c5cd9718 100644 --- a/packages/templating/templating_tests.js +++ b/packages/templating/templating_tests.js @@ -619,3 +619,118 @@ Tinytest.add("templating - template arg", function (test) { div.kill(); Meteor.flush(); }); + +Tinytest.add("templating - preserve", function (test) { + var R = ReactiveVar('foo'); + + var tmpl = Template.test_template_preserve_a; + tmpl.preserve(['.b']); + tmpl.preserve(['.c']); + tmpl.preserve({'.d': true}); + tmpl.preserve({'span': function (n) { + return _.contains(['e','f'], n.className) && n.className; + }}); + tmpl.preserve(['span.a']); + tmpl['var'] = function () { return R.get(); }; + + var div = OnscreenDiv(Meteor.render(tmpl)); + Meteor.flush(); + test.equal(div.node().lastChild.nodeValue.match(/\S+/)[0], 'foo'); + var spans1 = {}; + _.each(DomUtils.findAll(div.node(), 'span'), function (sp) { + spans1[sp.className] = sp; + }); + + R.set('bar'); + Meteor.flush(); + test.equal(div.node().lastChild.nodeValue.match(/\S+/)[0], 'bar'); + var spans2 = {}; + _.each(DomUtils.findAll(div.node(), 'span'), function (sp) { + spans2[sp.className] = sp; + }); + + test.isTrue(spans1.a === spans2.a); + test.isTrue(spans1.b === spans2.b); + test.isTrue(spans1.c === spans2.c); + test.isTrue(spans1.d === spans2.d); + test.isTrue(spans1.e === spans2.e); + test.isTrue(spans1.f === spans2.f); + test.isFalse(spans1.y === spans2.y); + test.isFalse(spans1.z === spans2.z); + + div.kill(); + Meteor.flush(); +}); + +Tinytest.add("templating - helpers", function (test) { + var tmpl = Template.test_template_helpers_a; + + tmpl.foo = 'z'; + tmpl.helpers({bar: 'b'}); + // helpers(...) takes precendence of assigned helper + tmpl.helpers({foo: 'a', baz: function() { return 'c'; }}); + + var div = OnscreenDiv(Meteor.render(tmpl)); + test.equal(div.text().match(/\S+/)[0], 'abc'); + div.kill(); + Meteor.flush(); + + tmpl = Template.test_template_helpers_b; + + tmpl.helpers({ + 'name': 'A', + 'arity': 'B', + 'toString': 'C', + 'length': 4, + 'var': 'D' + }); + + div = OnscreenDiv(Meteor.render(tmpl)); + test.equal(div.text().match(/\S+/)[0], 'ABC4D'); + div.kill(); + Meteor.flush(); + + // test that helpers don't "leak" + tmpl = Template.test_template_helpers_c; + div = OnscreenDiv(Meteor.render(tmpl)); + test.equal(div.text(), ''); + div.kill(); + Meteor.flush(); +}); + +Tinytest.add("templating - events", function (test) { + var tmpl = Template.test_template_events_a; + + var buf = []; + + // old style + tmpl.events = { + 'click b': function () { buf.push('b'); } + }; + + var div = OnscreenDiv(Meteor.render(tmpl)); + clickElement(DomUtils.find(div.node(), 'b')); + test.equal(buf, ['b']); + div.kill(); + Meteor.flush(); + + /// + + tmpl = Template.test_template_events_b; + buf = []; + // new style + tmpl.events({ + 'click u': function () { buf.push('u'); } + }); + tmpl.events({ + 'click i': function () { buf.push('i'); } + }); + + var div = OnscreenDiv(Meteor.render(tmpl)); + clickElement(DomUtils.find(div.node(), 'u')); + clickElement(DomUtils.find(div.node(), 'i')); + test.equal(buf, ['u', 'i']); + div.kill(); + Meteor.flush(); + +});