UNPKG

grunt-doctrine

Version:

Grunt plugin to convert doctrine xml annotations into backbone models and collections. Useful in conjunction with doctrine apigility. Includes option to scaffold an entire BBB application

2,082 lines (1,612 loc) 56.5 kB
(function() { "use strict"; QUnit.module("views", { setup: function() { var setup = this; // Override the default template fetching behavior such that the tests can // run in the absense of the DOM (for Node.js). Store a reference to the // default `fetchTemplate` method to be restored in the teardown of this test // module. this.origFetch = Backbone.Layout.prototype.fetchTemplate; Backbone.Layout.configure({ fetchTemplate: function(name) { return _.template(testUtil.templates[name]); }, suppressWarnings: true }); // Custom View this.View = Backbone.Layout.extend({ template: "test", serialize: function() { return { text: this.msg }; }, initialize: function(opts) { this.msg = opts.msg; } }); // Initialize View this.InitView = Backbone.Layout.extend({ template: "test", serialize: function() { return { text: this.msg }; }, initialize: function(opts) { this.msg = opts.msg; this.setViews({ ".inner-right": new setup.SubView() }); } }); this.SubView = Backbone.Layout.extend({ template: "testSub", serialize: function() { return { text: "Right" }; } }); this.EventedListView = Backbone.Layout.extend({ template: "list", initialize: function() { this.collection.on("reset", this.render, this); }, cleanup: function() { this.collection.off(null, null, this); }, beforeRender: function() { this.collection.each(function(model) { this.insertView("ul", new setup.ItemView({ model: model.toJSON() })); }, this); } }); this.ListView = Backbone.Layout.extend({ template: "list", beforeRender: function() { // Iterate over the passed collection and insert into the view _.each(this.collection, function(model) { this.insertView("ul", new setup.ItemView({ model: model })); }, this); } }); this.ItemView = Backbone.Layout.extend({ template: "testSub", tagName: "li", serialize: function() { return this.model; } }); }, teardown: function() { Backbone.Layout.configure({ fetchTemplate: this.origFetch }); // Remove `supressWarnings: true`. delete Backbone.Layout.prototype.suppressWarnings; delete Backbone.View.prototype.suppressWarnings; } }); asyncTest("render outside defined partial", 2, function() { var main = new Backbone.Layout({ template: "main" }); main.setView(".right", new this.View({ msg: "Right" })); main.render().promise().then(function() { var trimmed = testUtil.trim(this.$(".inner-left").html()); ok(testUtil.isDomNode(this.el), "Contents is a DOM Node"); equal(trimmed, "Right", "Correct render"); start(); }); }); asyncTest("render inside defined partial", function() { expect(2); var main = new Backbone.Layout({ template: "main", views: { ".right": new this.View({ msg: "Right" }) } }); main.render().promise().then(function() { var trimmed = testUtil.trim( this.$(".inner-left").html() ); ok(testUtil.isDomNode(this.el), "Contents is a DOM Node"); equal(trimmed, "Right", "Correct render"); start(); }); }); asyncTest("Subclassed view uses correct template when rendered.", function() { expect(1); var layout = new Backbone.Layout(); var BaseView = Backbone.View.extend({ template: function() { return "Base view template"; }, manage: true }); var templateFunction = function() { return "Extended view template"; }; var ExtendedBaseView = BaseView.extend({ constructor: function() { this.template = templateFunction; BaseView.prototype.constructor.apply(this, arguments); } }); layout.setView("", new ExtendedBaseView()); layout.render().promise().then(function() { var contents = testUtil.trim(this.$el.text()); equal(contents, "Extended view template", "Correct template is used"); start(); }); }); asyncTest("re-render a view defined after initialization", function(){ expect(2); var trimmed; var setup = this; var main = new Backbone.Layout({ template: "main" }); main.setView(".right", new this.View({ msg: "Right" })); main.render().promise().then(function() { trimmed = testUtil.trim( this.$(".inner-left").html() ); equal(trimmed, "Right", "Correct re-render"); main.setView(".right", new setup.View({ msg: "Right Again" })).render().promise().then(function() { trimmed = testUtil.trim( this.$(".inner-left").html() ); equal(trimmed, "Right Again", "Correct re-render"); start(); }); }); }); asyncTest("nested views", function() { expect(2); var main = new Backbone.Layout({ template: "main", views: { ".right": new this.View({ msg: "Left", views: { ".inner-right": new this.SubView({ lol: "hi" }) } }) } }); main.render().promise().then(function() { var trimmed = testUtil.trim(this.$(".inner-right div").html()); ok(testUtil.isDomNode(this.el), "Contents is a DOM Node"); equal(trimmed, "Right", "Correct render"); start(); }); }); asyncTest("serialize on Layout is a function", function() { expect(1); var testText = "test text"; var main = new Backbone.Layout({ template: "testSub", serialize: { text: testText } }); main.render().promise().then(function() { equal(testUtil.trim( this.$el.text() ), testText, "correct data"); start(); }); }); asyncTest("data on Layout is an object", function() { expect(1); var testText = "test text"; var main = new Backbone.Layout({ template: "testSub", serialize: { text: "test text" } }); main.render().promise().then(function() { equal(testUtil.trim( this.$el.text() ), testText, "correct data"); start(); }); }); // TODO THIS TEST asyncTest("rendered event", function() { expect(4); var main = new Backbone.Layout({ template: "main", views: { ".right": new this.ListView({ collection: [{ text: "one" }, { text: "two" }] }) } }); main.render().promise().then(function() { ok(testUtil.isDomNode(this.el), "Contents is a DOM Node"); equal(this.$("ul li").length, 2, "Correct number of nested li's"); equal(testUtil.trim( this.$("ul li").eq(0).html() ), "one", "Correct first li content"); equal(testUtil.trim( this.$("ul li").eq(1).html() ), "two", "Correct second li content"); start(); }); }); asyncTest("insert views", function() { expect(4); var main = new Backbone.Layout({ template: "main", views: { ".right": new this.ListView({ collection: [{ text: "one" }, { text: "two" }] }) } }); main.render().promise().then(function() { ok(testUtil.isDomNode(this.el), "Contents is a DOM Node"); equal(this.$("ul li").length, 2, "Correct number of nested li's"); equal(testUtil.trim( this.$("ul li").eq(0).html() ), "one", "Correct first li content"); equal(testUtil.trim( this.$("ul li").eq(1).html() ), "two", "Correct second li content"); start(); }); }); asyncTest("using setViews", function() { expect(2); var main = new Backbone.Layout({ template: "main" }); main.setViews({ ".right": new this.View({ msg: "Left", views: { ".inner-right": new this.SubView() } }) }); main.render().promise().then(function() { var trimmed = testUtil.trim(this.$(".inner-right div").html()); ok(testUtil.isDomNode(this.el), "Contents is a DOM Node"); equal(trimmed, "Right", "Correct render"); start(); }); }); asyncTest("using setViews inside initialize", function() { expect(2); var main = new Backbone.Layout({ template: "main" }); main.setViews({ ".right": new this.InitView({ msg: "Left" }) }); main.render().promise().then(function() { var trimmed = testUtil.trim( this.$(".inner-right div").html() ); ok(testUtil.isDomNode(this.el), "Contents is a DOM Node"); equal(trimmed, "Right", "Correct render"); start(); }); }); asyncTest("extend layoutmanager", 1, function() { var testText = "test text"; var BaseLayout = Backbone.Layout.extend({ template: "testSub", serialize: { text: "test text" } }); var main = new BaseLayout(); main.render().promise().then(function() { equal(testUtil.trim( this.$el.text() ), testText, "correct data"); start(); }); }); asyncTest("appending views with array literal", 3, function() { var main = new Backbone.Layout({ template: "main" }); main.setViews({ ".right": [ new this.View({ msg: "One", keep: true }), new this.View({ msg: "Two", keep: true }) ] }); main.render().promise().then(function() { equal(this.$(".right").children().length, 2, "correct children length"); equal(testUtil.trim(this.$(".right").children().eq(0).text() ), "One", "correct value set for the first child"); equal(testUtil.trim(this.$(".right").children().eq(1).text() ), "Two", "correct value set for the second child"); start(); }); }); asyncTest("single render per view", function() { expect(1); var count = 0; var main = new Backbone.Layout({ template: "main" }); var right = main.setView(".right", new this.View({ msg: "1" })); // Level 1 right.render().promise().then(function() { count++; }); // Level 2 main.setView(".inner-right", new this.View({ msg: "2" })).render().promise().then(function() { count++; }); // Level 3 var innerRight = main.views[".inner-right"]; innerRight.setViews({ ".inner-right": [ new this.SubView(), new this.SubView() ] }); innerRight.views[".inner-right"][0].render().promise().then(function() { count++; }); innerRight.views[".inner-right"][1].render().promise().then(function() { count++; }); main.render().promise().then(function() { equal(count, 4, "Render is only called once for each view"); start(); }); }); test("render callback and deferred context is view", 6, function() { // Since we have multiple start() calls, we use stop() with the // number of start calls instead of using asyncTest stop(6); var main = new Backbone.Layout({ template: "main", views: { ".right": new this.View({ msg: "Right" }), ".left": [ new this.View({ keep: true, msg: "Left 1" }), new this.View({ msg: "Left 2", keep: true, views: { ".inner-left": new this.SubView({ lol: "hi" }) } }) ] } }); main.render().promise().then(function() { equal(this, main, "Layout render callback context is Layout"); start(); }).then(function() { equal(this, main, "Layout render deferred context is Layout"); start(); }); main.views[".right"].render().promise().then(function() { equal(this, main.views[".right"], "View render callback context is View"); start(); }).then(function() { equal(this, main.views[".right"], "View render deferred context is View"); start(); }); main.views[".left"][1].views[".inner-left"].render().promise().then(function() { equal(this, main.views[".left"][1].views[".inner-left"], "Nested View render callback context is View"); start(); }).then(function() { equal(this, main.views[".left"][1].views[".inner-left"], "Nested View render deferred context is View"); start(); }); }); asyncTest("list items don't duplicate", 2, function() { var main = new Backbone.Layout({ template: "main" }); var view = main.setView(".right", new this.EventedListView({ collection: new Backbone.Collection() })); view.collection.reset([ { text: 5 } ]); main.render().promise().then(function() { view.collection.reset([ { text: 5 } ]); }); view.collection.reset([ { text: 5 } ]); setTimeout(function() { view.collection.reset([ { text: 1 }, { text: 2 }, { text: 3 }, { text: 4 } ]); view.render().promise().then(function() { equal(view.$("ul").children().length, 4, "Only four elements"); equal(view.views.ul.length, 4, "Only four Views"); start(); }); }, 5); }); asyncTest("afterRender triggers for nested views", 1, function() { var triggered = false; var main = new Backbone.Layout({ el: "#prefilled" }); main.setViews({ ".test": new this.SubView({ serialize: { text: "Here" }, afterRender: function() { triggered = true; } }) }); main.render().promise().then(function() { ok(triggered === true, "afterRender is called"); start(); }); }); // Do this one without a custom render function as well. asyncTest("view render can be attached inside initalize", 1, function() { var main = new Backbone.Layout({ template: "main" }); var TestRender = Backbone.View.extend({ manage: true, initialize: function() { this.model.on("change", this.render, this); }, beforeRender: function() { this.$el.html("This works now!"); } }); var testModel = new Backbone.Model(); var testRender = main.setView(".right", new TestRender({ model: testModel })); // Initial render. main.render().promise().then(function() { equal(testRender.$el.html(), "This works now!", "Content correctly set"); testRender.remove(); start(); }); testModel.trigger("change"); }); test("Allow normal Views to co-exist with LM", 1, function() { var called = false; var View = Backbone.View.extend({ render: function() { called = true; } }); var view = new View(); view.render(); ok(called, "Render methods work without being in LM"); }); test("setView works going from append mode to normal", 1, function() { var main = new Backbone.Layout({ template: "main", views: { ".left": [ new this.View({ keep: true, msg: "Left 1" }), new this.View({ msg: "Left 2", keep: true, views: { ".inner-left": new this.SubView({ lol: "hi" }) } }) ] } }); main.setView(".left", new this.View({ msg: "Right" })); ok(true, "setView does not crash"); }); test("setView and insertView not working after model change", 1, function() { var setup = this; var m = new Backbone.Model(); var View = Backbone.View.extend({ manage: true, initialize: function() { this.model.on("change", function() { this.render(); equal(layout.$(".inner-left").length, 2, "rendered twice"); }, this); }, beforeRender: function() { this.insertView(new setup.View({ msg: "insert", // Need keep true. keep: true })); } }); var view = new View({ model: m }); var layout = new Backbone.Layout({ template: "main", views: { ".left": view } }); layout.render(); m.set("some", "change"); }); asyncTest("Ensure afterRender can access element's parent.", 1, function() { var view = new Backbone.Layout({ template: "main", views: { ".left": new Backbone.Layout({ afterRender: function() { ok(this.contains(view.el, this.el), "Parent can be found in afterRender"); start(); } }) } }); view.render(); }); // https://github.com/tbranyen/backbone.layoutmanager/issues/108 test("render callback vs deferred resolve when called twice", 1, function() { // Create a new View. var view = new Backbone.View(); // Set it up to work with Layout. Backbone.Layout.setupView(view); // Two renders using callback style. view.render().promise().then(function() { view.render().promise().then(function() { ok(true, "Two render's using callback style work."); }); }); }); // https://github.com/tbranyen/backbone.layoutmanager/issues/115 test("Uncaught RangeError: Maximum call stack size exceeded", 1, function() { var View = Backbone.View.extend({ manage: true }); new View({ model: new Backbone.Model() }).render(); ok(true, "No call stack exceeded error"); }); // https://github.com/tbranyen/backbone.layoutmanager/issues/117 asyncTest("Views getting appended in the wrong order", 3, function() { var View = Backbone.View.extend({ manage: true, template: "testing", fetchTemplate: function(name) { var done = this.async(); setTimeout(function() { done( _.template(name)); }, Math.random()*5); } }); var view = new View({ views: { "": [ new View({ order: 1 }), new View({ order: 2 }) ] } }); view.render().on("afterRender", function() { equal(this.views[""].length, 2, "There should be two views"); equal(this.views[""][0].options.order, 1, "The first order should be 1"); equal(this.views[""][1].options.order, 2, "The second order should be 2"); start(); }); }); // https://github.com/tbranyen/backbone.layoutmanager/issues/116 test("Re-rendering of inserted views causes append at the end of the list", 1, function() { var ItemView = Backbone.Layout.extend({ tagName: "tr", template: "<%= msg %>", fetchTemplate: function(name) { return _.template(name); }, serialize: function() { return { msg: this.options.msg }; } }); var item1 = new ItemView({ msg: "hello" }); var item2 = new ItemView({ msg: "goodbye" }); var list = new Backbone.Layout({ template: "<tbody></tbody>", fetchTemplate: function(name) { return _.template(name); }, beforeRender: function() { this.insertView("tbody", item1); this.insertView("tbody", item2); } }); var main = new Backbone.Layout({ tagName: "table" }); main.insertView(list); main.render().promise().then(function() { list.views.tbody[0].render().promise().then(function() { var $tr = main.$("tbody").first().find("tr"); equal($tr.html(), "hello", "Correct tbody order."); }); }); }); // https://github.com/tbranyen/backbone.layoutmanager/issues/122 test("afterRender() not called on item added with insertView()", 2, function() { var hitAfter = 0; var hitBefore = 0; var m = new Backbone.Model(); var Item = Backbone.Layout.extend({ template: "", fetchTemplate: function(path) { return _.template(path); }, tagName: "tr", beforeRender: function() { hitBefore = hitBefore + 1; }, afterRender: function() { hitAfter = hitAfter + 1; }, cleanup: function() { this.model.off(null, null, this); }, initialize: function() { this.model.on("change", this.render, this); } }); var List = Backbone.Layout.extend({ template: "<tbody></tbody>", fetchTemplate: function(path) { return _.template(path); }, beforeRender: function() { // Pass the model through. this.insertView("tbody", new Item({ model: m })); } }); var list = new List({ model: m }); list.render().promise().then(function() { m.set("something", "changed"); equal(hitBefore, 2, "beforeRender hit twice"); equal(hitAfter, 2, "afterRender hit twice"); }); }); test("Render doesn't work inside insertView", 1, function() { var V = Backbone.Layout.extend({ template: "<p class='inner'><%= lol %></p>", fetchTemplate: function(path) { return _.template(path); } }); var n = new Backbone.Layout({ template: "<p></p>", fetchTemplate: function(path) { return _.template(path); } }); n.render(); n.insertView("p", new V({ serialize: { lol: "hi" } })).render(); equal(n.$("p.inner").html(), "hi", "Render works with insertView"); }); // https://github.com/tbranyen/backbone.layoutmanager/issues/131 test("Ensure global paths are adhered to", 1, function() { Backbone.Layout.configure({ prefix: "test/" }); var t = new Backbone.Layout({ template: "here" }); equal(t.prefix, "test/", "Prefix properly hooked up"); Backbone.Layout.configure({ prefix: "" }); }); // https://github.com/tbranyen/backbone.layoutmanager/issues/137 test("afterRender not firing", 1, function() { var hit = false; var l = new Backbone.Layout({ template: "<p></p>", fetchTemplate: function(path) { return _.template(path); } }); l.render(); var V = Backbone.Layout.extend({ template: "<span>hey</span>", fetchTemplate: function(path) { return _.template(path); }, afterRender: function() { hit = true; } }); l.setView("p", new V()).render(); ok(hit, "afterRender was hit successfully"); }); test("multiple subclasses afterRender works", 1, function() { var hit = 0; var SubClass1 = Backbone.Layout.extend({ template: "<p></p>", fetchTemplate: function(path) { return _.template(path); }, afterRender: function() { hit--; } }); var SubClass2 = SubClass1.extend({ template: "<p></p>", fetchTemplate: function(path) { return _.template(path); }, afterRender: function lol() { hit++; } }); var ParentTest = Backbone.Layout.extend({ template: "<p></p>", fetchTemplate: function(path) { return _.template(path); }, beforeRender: function() { this.setView("", new SubClass2()); } }); var Test = Backbone.Layout.extend({ template: "<p></p>", fetchTemplate: function(path) { return _.template(path); }, triggerRender: function() { this.insertView("p", new ParentTest()).render(); } }); var test = new Test(); test.render().promise().then(function() { test.triggerRender(); equal(hit, 1, "Hit was correctly fired once"); }); }); // https://github.com/tbranyen/backbone.layoutmanager/issues/148 test("Views cannot be removed once added to a Layout", 3, function() { var v; var Child = Backbone.Layout.extend({ className: "child" }); var layout = new Backbone.Layout(); v = layout.setView("", new Child()); layout.render(); equal(layout.$(".child").length, 1, "Only one child"); v.remove(); equal(layout.$(".child").length, 0, "No children"); layout.render(); equal(layout.$(".child").length, 0, "No children"); }); // https://github.com/tbranyen/backbone.layoutmanager/issues/150 asyncTest("Views intermittently render multiple times", 1, function() { // Simulating fetchTemplate, should only execute once per template and then cache. var fetchTemplate = function(name) { var done = this.async(); setTimeout(function() { done(_.template(testUtil.templates[name])); }, 1); }; // Set the collection full of items. var collection = new Backbone.Collection([ { item: "Item 1" }, { item: "Item 2" }, { item: "Item 3" }, { item: "Item 4" }, { item: "Item 5" } ]); var View1 = Backbone.Layout.extend({ template: "view1", fetchTemplate: fetchTemplate }); var ListItem = Backbone.Layout.extend({ template: "listItem", fetchTemplate: fetchTemplate, serialize: function() { return { item: this.model.get("item") }; } }); var View2 = Backbone.Layout.extend({ template: "view2", fetchTemplate: fetchTemplate, beforeRender: function() { this.collection.each(function(model) { this.insertView(new ListItem({ model: model })); }, this); } }); var View3 = Backbone.Layout.extend({ template: "view3", fetchTemplate: fetchTemplate }); var main = new Backbone.Layout({ template: "view0", fetchTemplate: fetchTemplate }); main.setView(".view0", new View1()); //main.render().promise().then(function() { main.insertViews({ ".view1": [ new View2({ collection: collection }), new View3() ] }).render().promise().then(function() { equal(main.$(".listItem").length, 5, "Only five list items"); start(); }); //}); }); // https://github.com/tbranyen/backbone.layoutmanager/issues/160 test("remove method not working as expected", function() { var it; var Item = Backbone.Layout.extend({}); var list = new Backbone.Layout({ template: "<ul></ul>", fetchTemplate: function(path) { return _.template(path); }, views: { "ul": [ new Item(), (it = new Item()), new Item() ] } }); list.render(); equal(list.getViews().value().length, 3, "Length before remove is correct"); // Remove the second sub view. it.remove(); equal(list.getViews().value().length, 2, "Length after remove is correct"); }); // https://github.com/tbranyen/backbone.layoutmanager/issues/156 test("Shouldn't calling $('#app').html(new Backbone.Layout().render().el) work?", function() { ok(testUtil.isDomNode(new Backbone.Layout().render().el), "Is an element?"); }); // Async rendering. asyncTest("beforeRender and afterRender called twice in async", 3, function() { var hitAfter = 0; var hitBefore = 0; var renderNum = 0; var m = new Backbone.Model(); var Item = Backbone.Layout.extend({ template: "lol", fetchTemplate: function(path) { var done = this.async(); setTimeout(function() { done(_.template(path)); }, Math.random()*5 + 1); }, tagName: "tr", beforeRender: function() { hitBefore = hitBefore + 1; }, afterRender: function() { hitAfter = hitAfter + 1; }, renderTemplate: function(tmpl, data) { renderNum++; return tmpl(data); }, initialize: function() { this.listenTo(this.model, "change", this.render); } }); var List = Backbone.Layout.extend({ template: "<tbody></tbody>", fetchTemplate: function(path) { var done = this.async(); setTimeout(function() { done(_.template(path)); }, Math.random()*5 + 1); }, initialize: function() { // Pass the model through. this.setView("tbody", new Item({ model: m })); } }); var list = new List({ model: m }); list.when([list.render(), list.render()]).then(function() { list.getView("tbody").on("afterRender", function() { if (hitAfter === renderNum) { equal(hitBefore, 3, "beforeRender hit four times"); equal(hitAfter, 3, "afterRender hit four times"); equal(renderNum, 3, "render called four times"); start(); } }); m.set("something", "changed"); }); }); test("Array syntax for rendering a list", 2, function() { var Test = Backbone.Layout.extend({ views: { "": [new this.SubView()] } }); var test = new Test(); test.render(); equal(test.views[""].length, 1, "Correct length"); equal(testUtil.trim(test.$("div").text()), "Right", "Correct text"); }); test("Remove a View from its parent", 1, function() { var Parent = Backbone.Layout.extend({ views: { "lol": new this.SubView() } }); var parent = new Parent(); parent._removeViews(true); ok(!parent.views.lol, "View has been removed"); }); test("View attributes should be copied over to new View", 1, function() { var parent = new Backbone.Layout({ views: { "hi": new Backbone.Layout({ id: "hi" }) } }); var view = parent.setView("hi", new Backbone.Layout({ id: "you" })); equal(view.$el.attr("id"), "you", "Correct id set."); }); // https://github.com/tbranyen/backbone.layoutmanager/issues/178 test("view is not refreshed according to model.on", 2, function() { var beforeCount = 0; var afterCount = 0; var model = new Backbone.Model(); var AutoView = Backbone.Layout.extend({ beforeRender: function() { beforeCount++; }, afterRender: function() { afterCount++; }, initialize: function() { this.model.on("change", this.render, this); } }); var autoView = new AutoView({ model: model }); autoView.render().promise().then(function() { model.set("test", "this"); equal(beforeCount, 2, "beforeRender was triggered"); equal(afterCount, 2, "afterRender was triggered"); }); }); test("cleanup is not called erroneously", 1, function() { var called = 0; var Child = Backbone.Layout.extend({ cleanup: function() { called++; } }); var Parent = Backbone.Layout.extend({ initialize: function() { this.setView("", new Child()); } }); var parent = new Parent(); parent.render(); parent.render(); ok(!called, "The cleanup method was never called"); }); test("cleanup called on View w/o parent when removed", 1, function() { var hit = false; var V = Backbone.Layout.extend({ cleanup: function() { hit = true; } }); var v = new V(); v.remove(); ok(hit, "Cleanup was successfully hit"); }); asyncTest("cleanup called on subview when parent view removed", function() { expect(2); var hitSub = false; var hitParent = false; var subview = new this.View({msg: "Right"}); subview = _.extend(subview, { cleanup: function(){ hitSub = true; }}); var main = _.extend(new Backbone.Layout({ template: "main", views: { ".right": subview } }), {cleanup: function() {hitParent = true;}}); _.extend(main, {cleanup: function(){ hitParent=true;}}); main.render().promise().then(function() { main.remove(); ok(hitSub, "Cleanup successfully called on a subview when parent removed"); ok(hitParent, "Cleanup successfully called on parent view when removed"); start(); }); }); test("attached even if already rendered", 1, function() { var view = new Backbone.Layout({ className: "test" }); view.render(); var layout = new Backbone.Layout(); layout.setView(view).render(); ok(view.contains(layout.el, view.el), "View exists inside Layout"); }); test("correctly remove inserted child views", function() { // parent view // child view via setView // chld views via insertViews // render // insert new child view via insertViews // render // old child view is not removed correctly var ItemView = Backbone.Layout.extend({ tagName: "tr", template: "<%= msg %>", fetchTemplate: function(name) { return _.template(name); }, serialize: function() { return { msg: this.options.msg }; } }); var item1 = new ItemView({ msg: "1" }); var item2 = new ItemView({ msg: "2" }); var item3 = new ItemView({ msg: "3" }); var list = new Backbone.Layout({ template: "<tbody></tbody>", fetchTemplate: function(name) { return _.template(name); }, beforeRender: function() { this.setView("subview", item1); this.insertView("tbody", item2); this.insertView("tbody", item3); } }); list.render(); equal(list.getViews().value().length, 3, "Correct number of views."); list.insertView("tbody", item3); list.render(); equal(list.getViews().value().length, 3, "Correct number of views after reinsert."); list.render(); equal(list.getViews().value().length, 3, "Correct number of views after re-render."); }); // https://github.com/tbranyen/backbone.layoutmanager/issues/200 test("getView should accept a selector name too", 3, function() { var view = new Backbone.Layout(); var a = view.setView("a", new Backbone.Layout()); var b = view.setView("b", new Backbone.Layout()); view.insertView("c", new Backbone.Layout()); view.insertView("c", new Backbone.Layout()); equal(view.getView("a"), a, "Single getView returns single view"); equal(view.getViews("b").value()[0], b, "Using getViews will return the single view in an array"); equal(view.getViews("c").value().length, 2, "Two Views returned from getViews"); }); // https://github.com/tbranyen/backbone.layoutmanager/issues/302 test("getView should accept a `_.where` object too", 4, function() { var view = new Backbone.Layout(); var model = new Backbone.Model(); var model2 = new Backbone.Model(); var a = view.setView("a", new Backbone.Layout({ model: model })); var b = view.setView("b", new Backbone.Layout({ id: 4 })); var d = view.setView("d", new Backbone.Layout({ model: model2 })); view.insertView("c", new Backbone.Layout({ model: model })); view.insertView("c", new Backbone.Layout({ id: 4 })); equal(view.getView({ model: model }), a, "Single getView returns single view"); equal(view.getView({ model: model2 }), d, "Single getView returns single view"); equal(view.getViews({ id: 4 }).first().value(), b, "Using getViews will return the single view in an array"); equal(view.getViews({ id: 4 }).value().length, 2, "Two Views returned from getViews"); }); asyncTest("insertViews should accept a single array", 1, function() { var main = new Backbone.Layout({ template: "main" }); var listElems = [new Backbone.Layout({tagName: "li"}), new Backbone.Layout({tagName: "li"})]; var list = new Backbone.Layout({ tagName: "ul", beforeRender: function() { this.insertViews(listElems); } }); main.setView(".right", list); main.render().promise().then(function() { var items = this.$(".right ul li"); equal(items.length, 2, "Proper array insert"); start(); }); }); // https://github.com/tbranyen/backbone.layoutmanager/issues/197 asyncTest("Allow async custom rendering of templates", 1, function() { var Test = Backbone.View.extend({ manage: true, template: "Hello World!", fetchTemplate: _.identity, renderTemplate: function(template) { var done = this.async(); setTimeout(function() { done(template); }, 1); } }); var test = new Test(); test.render().promise().then(function() { equal(this.$el.html(), "Hello World!", "Contents match correctly"); start(); }); }); // To ensure the collection cleanup is hit correctly. test("cleanup hit", 1, function() { var View = Backbone.View.extend({ manage: true, renderTemplate: function() { ok(false); }, initialize: function() { this.collection.on("reset", this.render, this); } }); var collection = new Backbone.Collection(); var view = new View({ collection: collection }); view.remove(); collection.trigger("reset"); ok(true); }); asyncTest("Duplicate sub-views are removed when their parent view is rendered repeatedly", 1, function() { var ListItemView = Backbone.Layout.extend({ // Set a template source so Layout calls this view's `fetchTemplate` method // (the actual value is unimportant for this test) template: "#bogus", // Generic asynchronous `fetchTemplate` method fetchTemplate: function() { var done = this.async(); setTimeout(function() { done(_.template("")); }, 0); } }); var list = new Backbone.Layout({ beforeRender: function() { this.insertView(new ListItemView()); } }); list.when([list.render(), list.render()]).done(function() { equal(list.views[""].length, 1, "All repeated sub-views have been removed"); start(); }); }); // https://github.com/tbranyen/backbone.layoutmanager/issues/218 test("Scoping nested view assignment selector to parent", 1, function() { var layout = new Backbone.Layout({ template: _.template("<div class='test'></div>"), fetchTemplate: _.identity, views: { ".test": new Backbone.Layout({ afterRender: function() { this.$el.html("lol"); } }) } }); layout.render(); equal(layout.$(".test div").html(), "lol", "Correct placeholder text"); }); // https://github.com/tbranyen/backbone.layoutmanager/issues/222 test("Different Orders of Rendering for a Re-Render", 2, function() { var msg = ""; var parent = new Backbone.Layout(); var child = new Backbone.Layout(); // Append to msg; parent.on("afterRender", function() { msg += "a"; }); child.on("afterRender", function() { msg += "b"; }); parent.setView("test", child); parent.render(); equal(msg, "ab", "Initial correct ordering"); parent.render(); equal(msg, "abab", "Even after re-render, maintains correct ordering"); }); test("Calling child.render() before parent.insertView() should still insert the rendered child.", 2, function() { var parent = new Backbone.Layout(); // You need to set `keep: true` if you wish to reuse an appended item. var child = new Backbone.Layout({ keep: true }); // Render both Views. parent.render(); child.render(); // If you `insertView` this marks the View in the hierarchy, but does not // actually modify the DOM. parent.insertView(child); equal(parent.getView().tagName, "div", "Children inserted into parent"); // When you call `render` this actually modifies the DOM structure to match // the virtual structure. parent.render(); equal(parent.$("div").length, 1, "Children inserted into parent"); }); // https://github.com/tbranyen/backbone.layoutmanager/issues/189 test("Allow layout to remove views", 2, function() { var view = new Backbone.View({ manage: true }); var anotherView = new Backbone.View({ manage: true }); view.setView("lol", anotherView); equal(view.getViews().value().length, 1, "Correct length"); view.removeView("lol"); equal(view.getViews().value().length, 0, "All nested views under lol removed"); }); // https://github.com/tbranyen/backbone.layoutmanager/issues/238 test("Lost triggered events in cached sub-view", 2, function() { // Sub view. var TestView = Backbone.Layout.extend({ afterRender: function() { this.trigger("event"); } }); var test = new TestView(); // Main view. var MainView = Backbone.Layout.extend({ beforeRender: function() { test.on("event", function() { ok(true); }); // Render cached view. this.insertView(test); } }); new MainView().render().render(); }); // https://github.com/tbranyen/backbone.layoutmanager/issues/243 test("afterRender callback will be triggered twice while beforeRender only once", 2, function() { var count = { before: 0, after: 0 }; var View = Backbone.Layout.extend({ initialize: function() { this.listenTo(this.model, "change", this.render); }, beforeRender: function() { count.before++; }, afterRender: function() { count.after++; } }); var view = new View({ model: new Backbone.Model() }); view.render(); view.model.set("a", "b"); equal(count.before, 2, "beforeRender hit twice"); equal(count.after, 2, "afterRender hit twice"); }); test("manage your own view element", 1, function() { var layout = new Backbone.Layout({ template: "list" }); layout.insertView("ul", new Backbone.View({ manage: true, el: false, template: "item", serialize: { text: "lol" } })); layout.render(); equal(layout.$el.html(), "<ul><li>lol</li></ul>", "Nested element is an LI"); }); test("additional testing that view's without a parent can manage", 1, function() { var layout = new Backbone.Layout({ el: false, template: "main", beforeRender: function() { for (var i = 0; i < 2; i++) { this.insertView(".right", new Backbone.View({ manage: true, el: false, template: "view0" })); } } }); layout.render(); equal(layout.$el.filter(".right").children(".view0").length, 2, "Correct length"); }); asyncTest("Ordering sub-views with varying render delays", 1, function() { var Outside = Backbone.View.extend({ manage: true, template: "[outside]", fetchTemplate: _.template }); var Inside = Backbone.View.extend({ manage: true, template: "[inside]", fetchTemplate: function(str) { var done = this.async(); setTimeout(function() { done(_.template(str)); }, 0); } }); var Sandwich = Backbone.View.extend({ manage: true, beforeRender: function() { this.insertView(new Outside()); this.insertView(new Inside()); this.insertView(new Outside()); } }); var sw = new Sandwich(); sw.render().promise().then(function() { equal(sw.$el.text(), "[outside][inside][outside]", "sub-view fetch delays do not affect ordering"); start(); }); }); asyncTest("Re-render confused when `el: false`", 1, function() { var View = Backbone.Layout.extend({ el: false, template: _.template("<li>Test</li>") }); var view = new View(); var layout = new Backbone.Layout({ template: _.template("<ul></ul>"), views: { ul: view } }); layout.render(); setTimeout(function() { view.render().promise().then(function() { equal(layout.$("li").length, 1, "only one LI"); start(); }); }, 1); }); test("re-rendering a template works correctly", 1, function() { var View = Backbone.Layout.extend({ template: _.template("<li><%= Math.random() %></li>") }); var view = new View(); var layout = new Backbone.Layout({ template: _.template("<ul></ul>"), views: { ul: view } }); layout.render(); var text = layout.getView("ul").$el.html(); view.render().promise().then(function() { var newText = layout.getView("ul").$el.html(); notEqual(newText, text, "different contents"); }); }); // https://github.com/tbranyen/backbone.layoutmanager/issues/349 test("re-rendering a template with a blank result should remove old markup", 4, function() { var View = Backbone.Layout.extend({ template: _.template("<%= content %>"), content: "", serialize: function() { return { content: this.content }; } }); var textView = new (View.extend({ content: "Hello" }))(); var markupView = new (View.extend({ content: "<b>world</b>" }))(); var layout = new Backbone.Layout({ template: _.template("<div class='text'></div><div class='markup'></div>"), views: { ".text": textView, ".markup": markupView } }); layout.render(); var text = layout.getView(".text").$el.html(); var markup = layout.getView(".markup").$el.html(); equal(text, "Hello", "initial text view render"); equal(markup, "<b>world</b>", "initial markup view render"); textView.content = ""; markupView.content = ""; layout.render().promise().done(function() { var newText = layout.getView(".text").$el.html(); var newMarkup = layout.getView(".markup").$el.html(); equal(newText, "", "second text view render is empty"); equal(newMarkup, "", "second markup view render is empty"); }); }); // https://github.com/tbranyen/backbone.layoutmanager/issues/271 test("`el: false` with deeply nested views", 1, function() { var Lvl2 = Backbone.Layout.extend({ el: false, template: _.template("<div class='lvl2'>foo</div>") }); var Lvl1 = Backbone.Layout.extend({ el: false, template: _.template("<div class='lvl1'><div class='lvl2container'></div></div>") }); var Lvl0 = Backbone.Layout.extend({ el: false, template: _.template("<div class='lvl0'><div class='lvl1container'></div></div>") }); var view = new Lvl0({ views: { ".lvl1container": new Lvl1({ views: { ".lvl2container": new Lvl2() } }) } }); view.render(); var expected = [ "<div class=\"lvl1container\">", "<div class=\"lvl1\">", "<div class=\"lvl2container\">", "<div class=\"lvl2\">foo</div>", "</div>", "</div>", "</div>" ]; equal(view.$el.html(), expected.join(""), "the same HTML"); }); // https://github.com/tbranyen/backbone.layoutmanager/issues/286 test("`el: false` with rerendering inserted child views doesn't replicate views", 1, function() { var app = _.extend({}, Backbone.Events); var Item = Backbone.Layout.extend({ el: false, template: _.template("<div class='item'><span class='title <%= model.theClass %>'>Item <%= model.index %></span></div>"), initialize: function(options){ this.model = options.model; this.listenTo(app, "event", this.addHighlight); }, addHighlight: function(){ this.model.set("theClass", "highlight"); this.render(); }, serialize: function(){ return {model: this.model.toJSON()}; } }); var List = Backbone.Layout.extend({ template: _.template("<div class='listContainer'>List Container<div class='list'></div></div>"), initialize: function(){ this.models = new Backbone.Collection([ new Backbone.Model({index: 1}), new Backbone.Model({index: 2}) ]); }, beforeRender: function(){ this.models.each(function(model){ this.insertView(".list", new Item({ model: model })); }, this); } }); var view = new List(); view.render(); // Add highlight to elements to cause them to rerender. Should not replicate views. app.trigger("event"); var expected = [ "<div class=\"listContainer\">", "List Container", "<div class=\"list\">", "<div class=\"item\">", "<span class=\"title highlight\">Item 1</span>", "</div>", "<div class=\"item\">", "<span class=\"title highlight\">Item 2</span>", "</div>", "</div>", "</div>" ]; equal(view.$el.html(), expected.join(""), "the same HTML"); }); test("`el: false` with non-container element will not be duplicated", 2, function() { var expected = "<p>Paragraph 1</p><p>Paragraph 2</p>"; var layout = new Backbone.Layout({ template: _.template("<div class='layout'><div class='content'></div></div>") }); var view = new Backbone.Layout({ el: false, template: _.template(expected) }); layout.setViews({ ".content": view }).render().promise().done(function() { equal(layout.$(".content").html(), expected); view.render().promise().then(function() { equal(layout.$(".content").html(), expected); }); }); }); test("trigger callback on a view with `keep: true`", 1, function() { var myView = new Backbone.Layout({ keep: true, cleanup: function() { ok(true, "Cleanup triggered"); } }); var layout = new Backbone.Layout(); layout.insertView(myView); // Render. layout.render(); // Cleanup. layout.removeView(); }); // https://github.com/tbranyen/backbone.layoutmanager/issues/323 test("templates strings enclosed in whitespace should render without error", 1, function() { var layout = new Backbone.Layout({ el: false, template: function() { return "\n <div>Hey</div>\n "; } }); layout.render(); equal(testUtil.trim(layout.$el.text()), "Hey"); }); test("<script> tags should not be removed from templates", 1, function() { var layout = new Backbone.Layout({ el: false, template: function() { return "<div><script></script></div>"; } }); layout.render(); equal(layout.$el.find("script").length, 1); }); asyncTest("asynchronous beforeRender", 1, function() { var View = Backbone.Layout.extend({ template: _.template("<div class='hello'></div>"), beforeRender: function() { var done = this.async(); setTimeout(function() { done(); }, 1); } }); var view = new View(); view.render().promise().then(function() { ok(view.$(".hello").length, "Render works correctly"); start(); }); }); test("getViews returns an empty array for unrecognized selectors", function() { var layout = new Backbone.Layout(); equal(layout.getViews(".whats-the-buzz").value().length, 0); }); // https://github.com/tbranyen/backbone.layoutmanager/issues/328 test("cleanViews invokes cleanup method in the context of the layout", function() { var layout = new Backbone.Layout({ cleanup: function() { equal(this, layout); } }); Backbone.Layout.cleanViews(layout); }); asyncTest("Render views using named DOM section", 2, function() { var main = new Backbone.Layout({ template: "main", sections: { "foo": ".right", "bar": ".left" }, views: { "bar": new Backbone.Layout({ afterRender: function() { this.$el.text("1"); } }) } }); main.setView("foo", new Backbone.Layout({ afterRender: function() { this.$el.text("2"); } })); main.render().promise().then(function() { equal(main.$(".left div").text(), "1", "Rendered in a section at initialization"); equal(main.$(".right div").text(), "2", "Rendered in a section"); start(); }); }); test("Get views via named section and selector", 2, function() { var subView = new Backbone.Layout({ afterRender: function() { this.$el.text("1"); } }); var main = new Backbone.Layout({ template: "main", sections: { "foo": ".right" }, views: { "foo": subView } }); equal(main.getView("foo"), subView, "Get subview assigned to a section"); equal(main.getView(".right"), subView, "Get subview assigned to a section"); }); test("Named DOM sections take precedence over selector", 2, function() { var subView = new Backbone.Layout({ afterRender: function() { this.$el.text("1"); } }); var main = new Backbone.Layout({ template: "main", sections: { ".left": ".right" }, views: { ".left": subView } }); main.getView(".right"); equal(main.getView(".right"), subView, "subView placed in the right selector"); equal(main.getView(".left"), subView, "Return view based on it's named section"); }); // Even though this was indirectly patched by @jugglinmike, this test will // ensure future compatibility. test("removeView fails if no subView exists.", 1, function() { var main = new Backbone.Layout(); try {