/** This module specifically tests integration with Handlebars and SproutCore-specific Handlebars extensions. If you add additional template support to SC.TemplateView, you should create a new file in which to test. */ module("SC.TemplateView - handlebars integration"); test("template view should call the function of the associated template", function() { var view = SC.TemplateView.create({ templateName: 'test_template', templates: SC.Object.create({ test_template: SC.Handlebars.compile("

template was called

") }) }); view.createLayer(); ok(view.$('#twas-called').length, "the named template was called"); }); test("template view should call the function of the associated template with itself as the context", function() { var view = SC.TemplateView.create({ templateName: 'test_template', _personName: "Tom DAAAALE", _i: 0, personName: function() { this._i++; return this._personName + this._i; }.property().cacheable(), templates: SC.Object.create({ test_template: SC.Handlebars.compile("

template was called for {{personName}}. Yea {{personName}}

") }) }); view.createLayer(); equals("template was called for Tom DAAAALE1. Yea Tom DAAAALE1", view.$('#twas-called').text(), "the named template was called with the view as the data source"); }); TemplateTests = {}; test("child views can be inserted using the {{view}} Handlebars helper", function() { var templates = SC.Object.create({ nester: SC.Handlebars.compile("

Hello {{world}}

{{view \"TemplateTests.LabelView\"}}"), nested: SC.Handlebars.compile("
Goodbye {{cruel}} {{world}}
") }); TemplateTests.LabelView = SC.TemplateView.extend({ tagName: "aside", cruel: "cruel", world: "world?", templateName: 'nested', templates: templates }); var view = SC.TemplateView.create({ world: "world!", templateName: 'nester', templates: templates }); view.createLayer(); ok(view.$("#hello-world:contains('Hello world!')").length, "The parent view renders its contents"); ok(view.$("#child-view:contains('Goodbye cruel world?')").length === 1, "The child view renders its content once"); ok(view.$().html().match(/Hello world!.*Hello {{world}}{{view \"TemplateTests.LabelView\"}}"), nested: SC.Handlebars.compile("
Goodbye {{#bind \"content\"}}{{blah}} {{view \"TemplateTests.OtherView\"}}{{/bind}} {{world}}
"), other: SC.Handlebars.compile("cruel") }); TemplateTests.LabelView = SC.TemplateView.extend({ tagName: "aside", cruel: "cruel", world: "world?", content: SC.Object.create({ blah: "wot" }), templateName: 'nested', templates: templates }); TemplateTests.OtherView = SC.TemplateView.extend({ templates: templates, templateName: 'other' }); var view = SC.TemplateView.create({ world: "world!", templateName: 'nester', templates: templates }); view.createLayer(); ok(view.$("#hello-world:contains('Hello world!')").length, "The parent view renders its contents"); ok(view.$("aside:contains('Goodbye wot cruel world?')").length === 1, "The child view renders its content once"); ok(view.$().html().match(/Hello world!.*{{#with content}}{{bind "wham"}}{{/with}}') }); var view = SC.TemplateView.create({ templateName: 'foo', templates: templates, content: SC.Object.create({ wham: 'bam', thankYou: "ma'am" }) }); view.createLayer(); equals(view.$('#first').text(), "bam", "precond - view renders Handlebars template"); SC.run(function() { view.get('content').set('wham', 'bazam'); }); equals(view.$('#first').text(), "bazam", "view updates when a bound property changes"); }); test("should not update when a property is removed from the view", function() { var templates = SC.Object.create({ foo: SC.Handlebars.compile('

{{#bind "content"}}{{#bind "foo"}}{{bind "baz"}}{{/bind}}{{/bind}}

') }); var removeCalled = 0; var view = SC.TemplateView.create({ templateName: 'foo', templates: templates, content: SC.Object.create({ foo: SC.Object.create({ baz: "unicorns", removeObserver: function(property, func) { sc_super(); removeCalled++; } }) }) }); view.createLayer(); equals(view.$('#first').text(), "unicorns", "precond - renders the bound value"); var oldContent = view.get('content'); SC.run(function() { view.set('content', SC.Object.create({ foo: SC.Object.create({ baz: "ninjas" }) })); }); equals(view.$('#first').text(), 'ninjas', "updates to new content value"); SC.run(function() { oldContent.setPath('foo.baz', 'rockstars'); }); SC.run(function() { oldContent.setPath('foo.baz', 'ewoks'); }); equals(removeCalled, 1, "does not try to remove observer more than once"); equals(view.$('#first').text(), "ninjas", "does not update removed object"); }); test("Handlebars templates update properties if a content object changes", function() { var templates; templates = SC.Object.create({ menu: SC.Handlebars.compile('

Today\'s Menu

{{#bind "coffee"}}

{{color}} coffee

{{bind "price"}}{{/bind}}') }); var view = SC.TemplateView.create({ templateName: 'menu', templates: templates, coffee: SC.Object.create({ color: 'brown', price: '$4' }) }); view.createLayer(); equals(view.$('h2').text(), "brown coffee", "precond - renders color correctly"); equals(view.$('#price').text(), '$4', "precond - renders price correctly"); view.set('coffee', SC.Object.create({ color: "mauve", price: "$4.50" })); equals(view.$('h2').text(), "mauve coffee", "should update name field when content changes"); equals(view.$('#price').text(), "$4.50", "should update price field when content changes"); view.set('coffee', SC.Object.create({ color: "mauve", price: "$5.50" })); equals(view.$('h2').text(), "mauve coffee", "should update name field when content changes"); equals(view.$('#price').text(), "$5.50", "should update price field when content changes"); view.setPath('coffee.price', "$5"); equals(view.$('#price').text(), "$5", "should update price field when price property is changed"); }); test("Template updates correctly if a path is passed to the bind helper", function() { var templates; templates = SC.Object.create({ menu: SC.Handlebars.compile('

{{bind "coffee.price"}}

') }); var view = SC.TemplateView.create({ templateName: 'menu', templates: templates, coffee: SC.Object.create({ price: '$4' }) }); view.createLayer(); equals(view.$('h1').text(), "$4", "precond - renders price"); view.setPath('coffee.price', "$5"); equals(view.$('h1').text(), "$5", "updates when property changes"); view.set('coffee', { price: "$6" }); equals(view.$('h1').text(), "$6", "updates when parent property changes"); }); test("Template updates correctly if a path is passed to the bind helper and the context object is an SC.ObjectController", function() { var templates; templates = SC.Object.create({ menu: SC.Handlebars.compile('

{{bind "coffee.price"}}

') }); var controller = SC.ObjectController.create(); var realObject = SC.Object.create({ price: "$4" }); controller.set('content', realObject); var view = SC.TemplateView.create({ templateName: 'menu', templates: templates, coffee: controller }); view.createLayer(); equals(view.$('h1').text(), "$4", "precond - renders price"); realObject.set('price', "$5"); equals(view.$('h1').text(), "$5", "updates when property is set on real object"); SC.run(function() { controller.set('price', "$6" ); }); equals(view.$('h1').text(), "$6", "updates when property is set on object controller"); }); test("Should insert a localized string if the {{loc}} helper is used", function() { SC.stringsFor('en', { 'Brazil': 'Brasilia' }); templates = SC.Object.create({ 'loc': SC.Handlebars.compile('

Country: {{loc "Brazil"}}') }); var view = SC.TemplateView.create({ templateName: 'loc', templates: templates, country: 'Brazil' }); view.createLayer(); equals(view.$('h1').text(), 'Country: Brasilia', "returns localized value"); }); test("Template views return a no-op function if their template cannot be found", function() { var view = SC.TemplateView.create({ templateName: 'cantBeFound' }); var template = view.get('template'); ok(SC.typeOf(template) === 'function', 'template should be a function'); equals(template(), '', 'should return an empty string'); }); test("Template views can belong to a pane and a parent view", function() { var templates = SC.Object.create({ toDo: SC.Handlebars.compile('

{{title}}

(Created at {{createdAt}})') }); var didCreateLayerWasCalled = NO; var pane = SC.MainPane.design({ childViews: ['container'], container: SC.View.design({ childViews: ['normalView', 'template'], normalView: SC.View, template: SC.TemplateView.design({ templates: templates, templateName: 'toDo', title: 'Do dishes', createdAt: "Today", didCreateLayer: function() { didCreateLayerWasCalled = YES; } }) }) }); pane = pane.create().append(); equals(pane.$().children().length, 1, "pane has one child DOM element"); equals(pane.$().children().children().length, 2, "container view has two child DOM elements"); equals(pane.$().children().children().eq(1).html(), "

Do dishes

(Created at Today)", "renders template to the correct DOM element"); ok(didCreateLayerWasCalled, "didCreateLayer gets called on a template view after it gets rendered"); pane.remove(); }); test("Template views add a layerId to child views created using the view helper", function() { var templates = SC.Object.create({ parent: SC.Handlebars.compile(''), child: SC.Handlebars.compile("I can't believe it's not butter.") }); TemplateTests.ChildView = SC.TemplateView.extend({ templates: templates, templateName: 'child' }); var view = SC.TemplateView.create({ templates: templates, templateName: 'parent' }); view.createLayer(); var childView = view.getPath('childViews.firstObject'); equals(view.$().children().first().children().first().attr('id'), childView.get('layerId')); }); test("Template views set the template of their children to a passed block", function() { var templates = SC.Object.create({ parent: SC.Handlebars.compile('

{{#view "TemplateTests.NoTemplateView"}}It worked!{{/view}}') }); TemplateTests.NoTemplateView = SC.TemplateView.extend(); var view = SC.TemplateView.create({ templates: templates, templateName: 'parent' }); view.createLayer(); ok(view.$().html().match(/

.*.*<\/span>.*<\/h1>/), "renders the passed template inside the parent template"); }); test("Child views created using the view helper should have their parent view set properly", function() { TemplateTests = {}; var template = '{{#view "SC.TemplateView"}}{{#view "SC.TemplateView"}}{{view "SC.TemplateView"}}{{/view}}{{/view}}'; var view = SC.TemplateView.create({ template: SC.Handlebars.compile(template) }); view.createLayer(); var childView = view.childViews[0].childViews[0]; equals(childView, childView.childViews[0].parentView, 'parent view is correct'); }); test("Collection views that specify an example view class have their children be of that class", function() { TemplateTests.ExampleViewCollection = SC.TemplateCollectionView.create({ itemView: SC.TemplateView.extend({ isCustom: YES }), content: ['foo'] }); var parentView = SC.TemplateView.create({ template: SC.Handlebars.compile('{{#collection "TemplateTests.ExampleViewCollection"}}OHAI{{/collection}}') }); parentView.createLayer(); ok(parentView.childViews[0].childViews[0].isCustom, "uses the example view class"); }); test("should update boundIf blocks if the conditional changes", function() { var templates = SC.Object.create({ foo: SC.Handlebars.compile('

{{#boundIf "content.myApp.isEnabled"}}{{content.wham}}{{/boundIf}}

') }); var view = SC.TemplateView.create({ templateName: 'foo', templates: templates, content: SC.Object.create({ wham: 'bam', thankYou: "ma'am", myApp: SC.Object.create({ isEnabled: YES }) }) }); view.createLayer(); equals(view.$('#first').text(), "bam", "renders block when condition is true"); SC.run(function() { view.get('content').setPath('myApp.isEnabled', NO); }); equals(view.$('#first').text(), "", "re-renders without block when condition is false"); }); test("{{view}} id attribute should set id on layer", function() { var templates = SC.Object.create({ foo: SC.Handlebars.compile('{{#view "TemplateTests.IdView" id="bar"}}baz{{/view}}') }); TemplateTests.IdView = SC.TemplateView.create(); var view = SC.TemplateView.create({ templateName: 'foo', templates: templates }); view.createLayer(); equals(view.$('#bar').length, 1, "adds id attribute to layer"); equals(view.$('#bar').text(), 'baz', "emits content"); }); test("{{view}} class attribute should set class on layer", function() { var templates = SC.Object.create({ foo: SC.Handlebars.compile('{{#view "TemplateTests.IdView" class="bar"}}baz{{/view}}') }); TemplateTests.IdView = SC.TemplateView.create(); var view = SC.TemplateView.create({ templateName: 'foo', templates: templates }); view.createLayer(); equals(view.$('.bar').length, 1, "adds class attribute to layer"); equals(view.$('.bar').text(), 'baz', "emits content"); }); test("should be able to bind view class names to properties", function() { var templates = SC.Object.create({ template: SC.Handlebars.compile('{{#view "TemplateTests.classBindingView" classBinding="isDone"}}foo{{/view}}') }); TemplateTests.classBindingView = SC.TemplateView.create({ isDone: YES }); var view = SC.TemplateView.create({ templateName: 'template', templates: templates }); view.createLayer(); equals(view.$('.is-done').length, 1, "dasherizes property and sets class name"); SC.run(function() { TemplateTests.classBindingView.set('isDone', NO); }); equals(view.$('.is-done').length, 0, "removes class name if bound property is set to false"); }); test("should be able to bind element attributes using {{bindAttr}}", function() { var template = SC.Handlebars.compile('content.title'); var view = SC.TemplateView.create({ template: template, content: SC.Object.create({ url: "http://www.sproutcore.com/assets/images/logo.png", title: "The SproutCore Logo" }) }); view.createLayer(); equals(view.$('img').attr('src'), "http://www.sproutcore.com/assets/images/logo.png", "sets src attribute"); equals(view.$('img').attr('alt'), "The SproutCore Logo", "sets alt attribute"); SC.run(function() { view.setPath('content.title', "El logo de Esproutcore"); }); equals(view.$('img').attr('alt'), "El logo de Esproutcore", "updates alt attribute when content's title attribute changes"); }); test("should be able to bind boolean element attributes using {{bindAttr}}", function() { var template = SC.Handlebars.compile(''); var content = SC.Object.create({ isDisabled: false, isChecked: true, }); var view = SC.TemplateView.create({ template: template, content: content }); view.createLayer(); ok(!view.$('input').attr('disabled'), 'attribute does not exist upon initial render'); ok(view.$('input').attr('checked'), 'attribute is present upon initial render'); content.set('isDisabled', true); content.set('isChecked', false); ok(view.$('input').attr('disabled'), 'attribute exists after update'); ok(!view.$('input').attr('checked'), 'attribute is not present after update'); });