From 2ac027a2af7e77aafdfed1330625257fd79c5d8c Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Fri, 6 Feb 2015 23:41:31 -0500 Subject: [PATCH] tests for components --- mithril.js | 17 +-- tests/mithril-tests.js | 237 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 247 insertions(+), 7 deletions(-) diff --git a/mithril.js b/mithril.js index 59409247..8a7e826e 100644 --- a/mithril.js +++ b/mithril.js @@ -234,8 +234,7 @@ var m = (function app(window, undefined) { if (data.controller) { var module = data var controller = cached.controller || new (module.controller || function() {}) - var args = module.controller.$$args ? [controller].concat(module.controller.$$args) : controller - data = module.view.apply(module, args) + data = module.view(controller) if (!data.tag) throw new Error(module.view.toString() + "\n\nThis template must return a virtual element, not an array, string, etc.") } if (!data.attrs) data.attrs = {}; @@ -509,11 +508,15 @@ var m = (function app(window, undefined) { var controller = function() { return module.controller.apply(this, args) || this } - controller.$$args = args //private, do not use - return { - controller: controller, - view: module.view, + var view = function(ctrl) { + if (arguments.length > 1) args = args.concat([].slice.call(arguments, 1)) + var template = module.view.apply(module, args ? [ctrl].concat(args) : [ctrl]) + if (args[0] && args[0].key != null) template.attrs.key = args[0].key + return template } + var output = {controller: controller, view: view} + if (args[0] && args[0].key != null) output.attrs = {key: args[0].key} + return output } m.module = function(root, module) { if (!root) throw new Error("Please ensure the DOM element exists before rendering a template into it."); @@ -533,7 +536,7 @@ var m = (function app(window, undefined) { roots[index] = root; if (arguments.length > 2) module = submodule(module, [].slice.call(arguments, 2)) var currentModule = topModule = module = module || {}; - var constructor = module.controller || m.prop() + var constructor = module.controller || function() {} var controller = new constructor; //controllers may call m.module recursively (via m.route redirects, for example) //this conditional ensures only the last recursive m.module call is applied diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index a6c161ed..15e24a5b 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -86,6 +86,243 @@ function testMithril(mock) { return unloaded }) + test(function() { + //module should pass args to both controller and view + mock.requestAnimationFrame.$resolve() + + var root = mock.document.createElement("div") + var slot1, slot2 + var module = { + controller: function(options) {slot1 = options.a}, + view: function(ctrl, options) {slot2 = options.a} + } + m.module(root, module, {a: 1}) + + mock.requestAnimationFrame.$resolve() + + return slot1 == 1 && slot2 == 1 + }) + test(function() { + //arguments should update in component's view + mock.requestAnimationFrame.$resolve() + + var root = mock.document.createElement("div") + var args = {a: 1} + var slot1, slot2 + var module = { + controller: function() {}, + view: function(ctrl, options) { + return m.module(sub, args) + } + } + var sub = { + controller: function(options) {slot1 = options.a}, + view: function(ctrl, options) { + slot2 = options.a + return m("div") + } + } + m.module(root, module) + + mock.requestAnimationFrame.$resolve() + + args.a = 2 + m.redraw(true) + + mock.requestAnimationFrame.$resolve() + + return slot1 == 1 && slot2 == 2 + }) + test(function() { + //module(mod, args) should also pass args to both controller and view + mock.requestAnimationFrame.$resolve() + + var root = mock.document.createElement("div") + var slot1, slot2 + var module = { + controller: function(options) {slot1 = options.a}, + view: function(ctrl, options) {slot2 = options.a} + } + var moduleWithArgs = m.module(module, {a: 1}) + m.module(root, moduleWithArgs) + + mock.requestAnimationFrame.$resolve() + + return slot1 == 1 && slot2 == 1 + }) + test(function() { + //component controller should only run once + mock.requestAnimationFrame.$resolve() + + var root = mock.document.createElement("div") + var count1 = 0, count2 = 0 + var module = { + view: function(ctrl) { + return m.module(sub) + } + } + var sub = { + controller: function() { + count1++ + }, + view: function(ctrl) { + count2++ + return m("div", "test") + } + } + m.module(root, module) + + mock.requestAnimationFrame.$resolve() + + m.redraw(true) + + mock.requestAnimationFrame.$resolve() + + return count1 == 1 && count2 == 2 + }) + test(function() { + //keys in components should work + mock.requestAnimationFrame.$resolve() + + var root = mock.document.createElement("div") + var list = [1, 2, 3] + var module = { + controller: function() {}, + view: function(ctrl) { + return list.map(function(i) { + return m.module(sub, {key: i}) + }) + } + } + var sub = { + controller: function() {}, + view: function() { + return m("div") + } + } + m.module(root, module) + + var firstBefore = root.childNodes[0] + + mock.requestAnimationFrame.$resolve() + + list.reverse() + m.redraw(true) + + mock.requestAnimationFrame.$resolve() + + var firstAfter = root.childNodes[2] + + return firstBefore === firstAfter + }) + test(function() { + //keys in components should work even if component internally messes them up + mock.requestAnimationFrame.$resolve() + + var root = mock.document.createElement("div") + var list = [1, 2, 3] + var module = { + controller: function() {}, + view: function(ctrl) { + return list.map(function(i) { + return m.module(sub, {key: i}) + }) + } + } + var sub = { + controller: function() {}, + view: function() { + return m("div", {key: 1}) + } + } + m.module(root, module) + + var firstBefore = root.childNodes[0] + + mock.requestAnimationFrame.$resolve() + + list.reverse() + m.redraw(true) + + mock.requestAnimationFrame.$resolve() + + var firstAfter = root.childNodes[2] + + return firstBefore === firstAfter + }) + test(function() { + //component identity should stay intact if components are descendants of keyed elements + mock.requestAnimationFrame.$resolve() + + var root = mock.document.createElement("div") + var list = [1, 2, 3] + var module = { + controller: function() {}, + view: function(ctrl) { + return list.map(function(i) { + return m("div", {key: i}, m.module(sub)) + }) + } + } + var sub = { + controller: function() {}, + view: function() { + return m("div") + } + } + m.module(root, module) + + var firstBefore = root.childNodes[0].childNodes[0] + + mock.requestAnimationFrame.$resolve() + + list.reverse() + m.redraw(true) + + mock.requestAnimationFrame.$resolve() + + var firstAfter = root.childNodes[2].childNodes[0] + + return firstBefore === firstAfter + }) + test(function() { + //component should call onunload when removed from template + mock.requestAnimationFrame.$resolve() + + var root = mock.document.createElement("div") + var list = [1, 2, 3] + var unloaded + var module = { + controller: function() {}, + view: function(ctrl) { + return list.map(function(i) { + return m.module(sub, {key: i}) + }) + } + } + var sub = { + controller: function(opts) { + this.onunload = function() { + unloaded = opts.key + } + }, + view: function() { + return m("div") + } + } + m.module(root, module) + + var firstBefore = root.childNodes[0] + + mock.requestAnimationFrame.$resolve() + + list.pop() + m.redraw(true) + + mock.requestAnimationFrame.$resolve() + + return unloaded === 3 + }) //m.withAttr test(function() {