test("should accept bindings as a string or an Ember.Binding", function() { var viewClass = EmberView.extend({ template: EmberHandlebars.compile("binding: {{view.bindingTest}}, string: {{view.stringTest}}") }); EmberHandlebars.registerHelper('boogie', function(id, options) { options.hash = options.hash || {}; options.hashTypes = options.hashTypes || {}; options.hash.bindingTestBinding = Binding.oneWay('context.' + id); options.hash.stringTestBinding = id; var result; if (Ember.FEATURES.isEnabled('ember-htmlbars')) { result = htmlbarsViewHelper.helper(viewClass, options.hash, options, options); } else { result = handlebarsViewHelper.helper(this, viewClass, options); } return result; }); view = EmberView.create({ context: EmberObject.create({ direction: 'down' }), template: EmberHandlebars.compile("{{boogie direction}}") }); appendView(); equal(trim(view.$().text()), "binding: down, string: down"); });
test("{{view}} should not override class bindings defined on a child view", function() { var LabelView = EmberView.extend({ container: container, templateName: 'nested', classNameBindings: ['something'], something: 'visible' }); container.register('controller:label', ObjectController, { instantiate: true }); container.register('view:label', LabelView); container.register('template:label', EmberHandlebars.compile('<div id="child-view"></div>')); container.register('template:nester', EmberHandlebars.compile('{{render "label"}}')); view = EmberView.create({ container: container, templateName: 'nester', controller: ObjectController.create({ container: container }) }); appendView(); ok(view.$('.visible').length > 0, 'class bindings are not overriden'); });
test("Inside a yield, the target points at the original target", function() { var watted = false; var component = EmberComponent.extend({ boundText: "inner", truthy: true, obj: {}, layout: EmberHandlebars.compile("<div>{{boundText}}</div><div>{{#if truthy}}{{#with obj}}{{yield}}{{/with}}{{/if}}</div>") }); view = EmberView.create({ controller: { boundText: "outer", truthy: true, wat: function() { watted = true; }, obj: { component: component, truthy: true, boundText: 'insideWith' } }, template: EmberHandlebars.compile('{{#with obj}}{{#if truthy}}{{#view component}}{{#if truthy}}<div {{action "wat"}} class="wat">{{boundText}}</div>{{/if}}{{/view}}{{/if}}{{/with}}') }); appendView(); run(function() { view.$(".wat").click(); }); equal(watted, true, "The action was called on the right context"); });
test("should expose a controller keyword that persists through Ember.ContainerView", function() { var templateString = "{{view view.containerView}}"; view = EmberView.create({ containerView: ContainerView, container: container, controller: EmberObject.create({ foo: "bar" }), template: EmberHandlebars.compile(templateString) }); appendView(); var containerView = get(view, 'childViews.firstObject'); var viewInstanceToBeInserted = EmberView.create({ template: EmberHandlebars.compile('{{controller.foo}}') }); run(function() { containerView.pushObject(viewInstanceToBeInserted); }); equal(trim(viewInstanceToBeInserted.$().text()), "bar", "renders value from parent's controller"); });
test("child views can be inserted using the {{view}} Handlebars helper", function() { container.register('template:nester', EmberHandlebars.compile("<h1 id='hello-world'>Hello {{world}}</h1>{{view view.labelView}}")); container.register('template:nested', EmberHandlebars.compile("<div id='child-view'>Goodbye {{cruel}} {{world}}</div>")); var context = { world: "world!" }; var LabelView = EmberView.extend({ container: container, tagName: "aside", templateName: 'nested' }); view = EmberView.create({ labelView: LabelView, container: container, templateName: 'nester', context: context }); set(context, 'cruel', "cruel"); appendView(); 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.$().text().match(/Hello world!.*Goodbye cruel world\!/), "parent view should appear before the child view"); });
test("{{view}} should be able to point to a local view", function() { view = EmberView.create({ template: EmberHandlebars.compile("{{view view.common}}"), common: EmberView.extend({ template: EmberHandlebars.compile("common") }) }); appendView(); equal(view.$().text(), "common", "tries to look up view name locally"); });
run(function() { indexHitCount = 0; App = EmberApplication.create({ rootElement: '#ember-testing' }); App.Router.map(function() { this.route('posts'); this.route('comments'); this.route('abort_transition'); }); App.IndexRoute = EmberRoute.extend({ model: function(){ indexHitCount += 1; } }); App.PostsRoute = EmberRoute.extend({ renderTemplate: function() { currentRoute = 'posts'; this._super(); } }); App.PostsView = EmberView.extend({ defaultTemplate: EmberHandlebars.compile("<a class=\"dummy-link\"></a><div id=\"comments-link\">{{#link-to 'comments'}}Comments{{/link-to}}</div>"), classNames: ['posts-view'] }); App.CommentsRoute = EmberRoute.extend({ renderTemplate: function() { currentRoute = 'comments'; this._super(); } }); App.CommentsView = EmberView.extend({ defaultTemplate: EmberHandlebars.compile('{{input type="text"}}') }); App.AbortTransitionRoute = EmberRoute.extend({ beforeModel: function(transition) { transition.abort(); } }); App.setupForTesting(); });
test("should update bound values after the view is removed and then re-appended", function() { view = EmberView.create({ template: EmberHandlebars.compile("{{#if view.showStuff}}{{view.boundValue}}{{else}}Not true.{{/if}}"), showStuff: true, boundValue: "foo" }); appendView(); equal(trim(view.$().text()), "foo"); run(function() { set(view, 'showStuff', false); }); equal(trim(view.$().text()), "Not true."); run(function() { set(view, 'showStuff', true); }); equal(trim(view.$().text()), "foo"); run(function() { view.remove(); set(view, 'showStuff', false); }); run(function() { set(view, 'showStuff', true); }); appendView(); run(function() { set(view, 'boundValue', "bar"); }); equal(trim(view.$().text()), "bar"); });
test("should not enter an infinite loop when binding an attribute in Handlebars", function() { var LinkView = EmberView.extend({ classNames: ['app-link'], tagName: 'a', attributeBindings: ['href'], href: '#none', click: function() { return false; } }); var parentView = EmberView.create({ linkView: LinkView, test: EmberObject.create({ href: 'test' }), template: EmberHandlebars.compile('{{#view view.linkView hrefBinding="view.test.href"}} Test {{/view}}') }); run(function() { parentView.appendTo('#qunit-fixture'); }); // Use match, since old IE appends the whole URL var href = parentView.$('a').attr('href'); ok(href.match(/(^|\/)test$/), "Expected href to be 'test' but got '"+href+"'"); run(function() { parentView.destroy(); }); });
test("should be able to update when bound property updates", function() { MyApp.set('controller', EmberObject.create({name: 'first'})); var View = EmberView.extend({ template: EmberHandlebars.compile('<i>{{view.value.name}}, {{view.computed}}</i>'), valueBinding: 'MyApp.controller', computed: computed(function() { return this.get('value.name') + ' - computed'; }).property('value') }); run(function() { view = View.create(); }); appendView(); run(function() { MyApp.set('controller', EmberObject.create({ name: 'second' })); }); equal(view.get('computed'), "second - computed", "view computed properties correctly update"); equal(view.$('i').text(), 'second, second - computed', "view rerenders when bound properties change"); });
test("{{view}} should evaluate class bindings set in the current context", function() { view = EmberView.create({ isView: true, isEditable: true, directClass: "view-direct", isEnabled: true, textField: TextField, template: EmberHandlebars.compile('{{view view.textField class="unbound" classBinding="view.isEditable:editable view.directClass view.isView view.isEnabled:enabled:disabled"}}') }); appendView(); ok(view.$('input').hasClass('unbound'), "sets unbound classes directly"); ok(view.$('input').hasClass('editable'), "evaluates classes bound in the current context"); ok(view.$('input').hasClass('view-direct'), "evaluates classes bound directly in the current context"); ok(view.$('input').hasClass('is-view'), "evaluates classes bound directly to booleans in the current context - dasherizes and sets class when true"); ok(view.$('input').hasClass('enabled'), "evaluates ternary operator in classBindings"); ok(!view.$('input').hasClass('disabled'), "evaluates ternary operator in classBindings"); run(function() { view.set('isView', false); view.set('isEnabled', false); }); ok(!view.$('input').hasClass('is-view'), "evaluates classes bound directly to booleans in the current context - removes class when false"); ok(!view.$('input').hasClass('enabled'), "evaluates ternary operator in classBindings"); ok(view.$('input').hasClass('disabled'), "evaluates ternary operator in classBindings"); });
test("should target the with-controller inside an {{#with controller='person'}}", function() { var registeredTarget; ActionHelper.registerAction = function(actionName, options) { registeredTarget = options.target; }; var PersonController = EmberObjectController.extend(); var container = new Container(); var parentController = EmberObject.create({ container: container }); view = EmberView.create({ container: container, template: EmberHandlebars.compile('{{#with view.person controller="person"}}{{action "editTodo"}}{{/with}}'), person: EmberObject.create(), controller: parentController }); container.register('controller:person', PersonController); appendView(); ok(registeredTarget.root instanceof PersonController, "the with-controller is the target of action"); ActionHelper.registerAction = originalRegisterAction; });
test("View should update when the property used with the #with helper changes [DEPRECATED]", function() { container.register('template:foo', EmberHandlebars.compile('<h1 id="first">{{#with view.content}}{{wham}}{{/with}}</h1>')); view = EmberView.create({ container: container, templateName: 'foo', content: EmberObject.create({ wham: 'bam', thankYou: "ma'am" }) }); expectDeprecation(function() { appendView(); }, 'Using the context switching form of `{{with}}` is deprecated. Please use the keyword form (`{{with foo as bar}}`) instead. See http://emberjs.com/guides/deprecations/#toc_more-consistent-handlebars-scope for more details.'); equal(view.$('#first').text(), "bam", "precond - view renders Handlebars template"); run(function() { set(view, 'content', EmberObject.create({ wham: 'bazam' })); }); equal(view.$('#first').text(), "bazam", "view updates when a bound property changes"); });
test("should target the current controller inside an {{each}} loop", function() { var registeredTarget; ActionHelper.registerAction = function(actionName, options) { registeredTarget = options.target; }; var itemController = EmberObjectController.create(); var ArrayController = EmberArrayController.extend({ itemController: 'stub', controllerAt: function(idx, object) { return itemController; } }); var controller = ArrayController.create({ model: Ember.A([1]) }); view = EmberView.create({ controller: controller, template: EmberHandlebars.compile('{{#each controller}}{{action "editTodo"}}{{/each}}') }); appendView(); equal(registeredTarget.root, itemController, "the item controller is the target of action"); ActionHelper.registerAction = originalRegisterAction; });
test("should allow a target to be specified", function() { var registeredTarget; ActionHelper.registerAction = function(actionName, options) { registeredTarget = options.target; }; var anotherTarget = EmberView.create(); view = EmberView.create({ controller: {}, template: EmberHandlebars.compile('<a href="#" {{action "edit" target="view.anotherTarget"}}>edit</a>'), anotherTarget: anotherTarget }); appendView(); equal(registeredTarget.options.data.keywords.view, view, "The specified target was registered"); equal(registeredTarget.target, 'view.anotherTarget', "The specified target was registered"); ActionHelper.registerAction = originalRegisterAction; run(function() { anotherTarget.destroy(); }); });
test("view should support disconnectOutlet for the main outlet", function() { var template = "<h1>HI</h1>{{outlet}}"; view = EmberView.create({ template: EmberHandlebars.compile(template) }); appendView(view); equal(view.$().text(), 'HI'); run(function() { view.connectOutlet('main', EmberView.create({ template: compile("<p>BYE</p>") })); }); // Replace whitespace for older IE equal(trim(view.$().text()), 'HIBYE'); run(function() { view.disconnectOutlet('main'); }); // Replace whitespace for older IE equal(trim(view.$().text()), 'HI'); });
test("handles whitelisted modifier keys", function() { var eventHandlerWasCalled = false; var shortcutHandlerWasCalled = false; var controller = EmberController.extend({ actions: { edit: function() { eventHandlerWasCalled = true; }, shortcut: function() { shortcutHandlerWasCalled = true; } } }).create(); view = EmberView.create({ controller: controller, template: EmberHandlebars.compile('<a href="#" {{action "edit" allowedKeys="alt"}}>click me</a> <div {{action "shortcut" allowedKeys="any"}}>click me too</div>') }); appendView(); var actionId = view.$('a[data-ember-action]').attr('data-ember-action'); ok(ActionManager.registeredActions[actionId], "The action was registered"); var e = jQuery.Event('click'); e.altKey = true; view.$('a').trigger(e); ok(eventHandlerWasCalled, "The event handler was called"); e = jQuery.Event('click'); e.ctrlKey = true; view.$('div').trigger(e); ok(shortcutHandlerWasCalled, "The \"any\" shortcut's event handler was called"); });
test("outlet should correctly lookup a view", function() { var template, ContainerView, childView; ContainerView = EmberContainerView.extend(); container.register("view:containerView", ContainerView); template = "<h1>HI</h1>{{outlet view='containerView'}}"; view = EmberView.create({ template: EmberHandlebars.compile(template), container : container }); childView = EmberView.create({ template: compile("<p>BYE</p>") }); appendView(view); equal(view.$().text(), 'HI'); run(function() { view.connectOutlet('main', childView); }); ok(ContainerView.detectInstance(childView.get('_parentView')), "The custom view class should be used for the outlet"); // Replace whitespace for older IE equal(trim(view.$().text()), 'HIBYE'); });
test("should be able to add multiple classes using {{bind-attr class}}", function() { var template = EmberHandlebars.compile('<div {{bind-attr class="view.content.isAwesomeSauce view.content.isAlsoCool view.content.isAmazing:amazing :is-super-duper view.content.isEnabled:enabled:disabled"}}></div>'); var content = EmberObject.create({ isAwesomeSauce: true, isAlsoCool: true, isAmazing: true, isEnabled: true }); view = EmberView.create({ template: template, content: content }); appendView(); ok(view.$('div').hasClass('is-awesome-sauce'), "dasherizes first property and sets classname"); ok(view.$('div').hasClass('is-also-cool'), "dasherizes second property and sets classname"); ok(view.$('div').hasClass('amazing'), "uses alias for third property and sets classname"); ok(view.$('div').hasClass('is-super-duper'), "static class is present"); ok(view.$('div').hasClass('enabled'), "truthy class in ternary classname definition is rendered"); ok(!view.$('div').hasClass('disabled'), "falsy class in ternary classname definition is not rendered"); run(function() { set(content, 'isAwesomeSauce', false); set(content, 'isAmazing', false); set(content, 'isEnabled', false); }); ok(!view.$('div').hasClass('is-awesome-sauce'), "removes dasherized class when property is set to false"); ok(!view.$('div').hasClass('amazing'), "removes aliased class when property is set to false"); ok(view.$('div').hasClass('is-super-duper'), "static class is still present"); ok(!view.$('div').hasClass('enabled'), "truthy class in ternary classname definition is not rendered"); ok(view.$('div').hasClass('disabled'), "falsy class in ternary classname definition is rendered"); });
test("should send the view, event and current Handlebars context to the action", function() { var passedTarget; var passedContext; var aTarget = EmberController.extend({ actions: { edit: function(context) { passedTarget = this; passedContext = context; } } }).create(); var aContext = { aTarget: aTarget }; view = EmberView.create({ aContext: aContext, template: EmberHandlebars.compile('{{#with view.aContext}}<a id="edit" href="#" {{action "edit" this target="aTarget"}}>edit</a>{{/with}}') }); appendView(); view.$('#edit').trigger('click'); strictEqual(passedTarget, aTarget, "the action is called with the target as this"); strictEqual(passedContext, aContext, "the parameter is passed along"); });
test("should allow multiple contexts to be specified", function() { var passedContexts; var models = [EmberObject.create(), EmberObject.create()]; var controller = EmberController.extend({ actions: { edit: function() { passedContexts = [].slice.call(arguments); } } }).create(); view = EmberView.create({ controller: controller, modelA: models[0], modelB: models[1], template: EmberHandlebars.compile('<button {{action "edit" view.modelA view.modelB}}>edit</button>') }); appendView(); view.$('button').trigger('click'); deepEqual(passedContexts, models, "the action was called with the passed contexts"); });
test("should target the with-controller inside an {{each}} in a {{#with controller='person'}}", function() { var eventsCalled = []; var PeopleController = EmberArrayController.extend({ actions: { robert: function() { eventsCalled.push('robert'); }, brian: function() { eventsCalled.push('brian'); } } }); var container = new Container(); var parentController = EmberObject.create({ container: container, people: Ember.A([ {name: 'robert'}, {name: 'brian'} ]) }); view = EmberView.create({ container: container, template: EmberHandlebars.compile('{{#with people controller="people"}}{{#each}}<a href="#" {{action name}}>{{name}}</a>{{/each}}{{/with}}'), controller: parentController }); container.register('controller:people', PeopleController); appendView(); view.$('a').trigger('click'); deepEqual(eventsCalled, ['robert', 'brian'], 'the events are fired properly'); });
run(function() { app = Application.create({ rootElement: '#qunit-fixture' }); app.Router.reopen({ location: 'none' }); app.register('template:application', EmberHandlebars.compile("{{outlet}}") ); Ember.TEMPLATES.index = EmberHandlebars.compile( "<h1>Hi from index</h1>" ); });
throws(function() { view = EmberView.create({ context: context, template: EmberHandlebars.compile('{{#group}}{{#each name in content}}{{name}}{{/each}}{{/group}}') }); appendView(); }, "Missing helper: 'group'");
test("should update bound values after view's parent is removed and then re-appended", function() { expectDeprecation("Setting `childViews` on a Container is deprecated."); var controller = EmberObject.create(); var parentView = ContainerView.create({ childViews: ['testView'], controller: controller, testView: EmberView.create({ template: EmberHandlebars.compile("{{#if showStuff}}{{boundValue}}{{else}}Not true.{{/if}}") }) }); controller.setProperties({ showStuff: true, boundValue: "foo" }); run(function() { parentView.appendTo('#qunit-fixture'); }); view = parentView.get('testView'); equal(trim(view.$().text()), "foo"); run(function() { set(controller, 'showStuff', false); }); equal(trim(view.$().text()), "Not true."); run(function() { set(controller, 'showStuff', true); }); equal(trim(view.$().text()), "foo"); run(function() { parentView.remove(); set(controller, 'showStuff', false); }); run(function() { set(controller, 'showStuff', true); }); run(function() { parentView.appendTo('#qunit-fixture'); }); run(function() { set(controller, 'boundValue', "bar"); }); equal(trim(view.$().text()), "bar"); run(function() { parentView.destroy(); }); });
test("should output a data attribute with a guid", function() { view = EmberView.create({ template: EmberHandlebars.compile('<a href="#" {{action "edit"}}>edit</a>') }); appendView(); ok(view.$('a').attr('data-ember-action').match(/\d+/), "A data-ember-action attribute with a guid was added"); });
test("should be able to explicitly set a view's context", function() { var context = EmberObject.create({ test: 'test' }); var CustomContextView = EmberView.extend({ context: context, template: EmberHandlebars.compile("{{test}}") }); view = EmberView.create({ customContextView: CustomContextView, template: EmberHandlebars.compile("{{view view.customContextView}}") }); appendView(); equal(view.$().text(), "test"); });
test("Template views add an elementId to child views created using the view helper", function() { container.register('template:parent', EmberHandlebars.compile('<div>{{view view.childView}}</div>')); container.register('template:child', EmberHandlebars.compile("I can't believe it's not butter.")); var ChildView = EmberView.extend({ container: container, templateName: 'child' }); view = EmberView.create({ container: container, childView: ChildView, templateName: 'parent' }); appendView(); var childView = get(view, 'childViews.firstObject'); equal(view.$().children().first().children().first().attr('id'), get(childView, 'elementId')); });
expectAssertion(function () { view = EmberView.create({ template: EmberHandlebars.compile(template), container : container }); appendView(view); });
test("{{view}} should evaluate other attributes bindings set in the current context", function() { view = EmberView.create({ name: "myView", textField: TextField, template: EmberHandlebars.compile('{{view view.textField valueBinding="view.name"}}') }); appendView(); equal(view.$('input').val(), "myView", "evaluates attributes bound in the current context"); });