var assert = require('assert'); var ko = require('knockout'); var sinon = require('sinon'); var dom = require('app/client/lib/dom'); var kd = require('app/client/lib/koDom'); var koArray = require('app/client/lib/koArray'); var clientUtil = require('../clientUtil'); describe('koDom', function() { clientUtil.setTmpMochaGlobals(); describe("simple properties", function() { it("should update dynamically", function() { var obs = ko.observable('bar'); var width = ko.observable(17); var elem = dom('div', kd.attr('a1', 'foo'), kd.attr('a2', obs), kd.attr('a3', function() { return "a3" + obs(); }), kd.text(obs), kd.style('width', function() { return width() + 'px'; }), kd.toggleClass('isbar', function() { return obs() === 'bar'; }), kd.cssClass(function() { return 'class' + obs(); })); assert.equal(elem.getAttribute('a1'), 'foo'); assert.equal(elem.getAttribute('a2'), 'bar'); assert.equal(elem.getAttribute('a3'), 'a3bar'); assert.equal(elem.textContent, 'bar'); assert.equal(elem.style.width, '17px'); assert.equal(elem.className, 'isbar classbar'); obs('BAZ'); width('34'); assert.equal(elem.getAttribute('a1'), 'foo'); assert.equal(elem.getAttribute('a2'), 'BAZ'); assert.equal(elem.getAttribute('a3'), 'a3BAZ'); assert.equal(elem.textContent, 'BAZ'); assert.equal(elem.style.width, '34px'); assert.equal(elem.className, 'classBAZ'); obs('bar'); assert.equal(elem.className, 'isbar classbar'); }); }); describe("domData", function() { it("should set domData and reflect observables", function() { var foo = ko.observable(null); var elem = dom('div', kd.domData('foo', foo), kd.domData('bar', 'BAR') ); assert.equal(ko.utils.domData.get(elem, 'foo'), null); assert.equal(ko.utils.domData.get(elem, 'bar'), 'BAR'); foo(123); assert.equal(ko.utils.domData.get(elem, 'foo'), 123); }); }); describe("scope", function() { it("should handle any number of children", function() { var obs = ko.observable(); var elem = dom('div', 'Hello', kd.scope(obs, function(value) { return value; }), 'World'); assert.equal(elem.textContent, "HelloWorld"); obs("Foo"); assert.equal(elem.textContent, "HelloFooWorld"); obs([]); assert.equal(elem.textContent, "HelloWorld"); obs(["Foo", "Bar"]); assert.equal(elem.textContent, "HelloFooBarWorld"); obs(null); assert.equal(elem.textContent, "HelloWorld"); obs([dom.frag("Foo", dom("span", "Bar")), dom("div", "Baz")]); assert.equal(elem.textContent, "HelloFooBarBazWorld"); }); it("should cope with children getting removed outside", function() { var obs = ko.observable(); var elem = dom('div', 'Hello', kd.scope(obs, function(v) { return v; }), 'World'); assert.equal(elem.innerHTML, 'HelloWorld'); obs(dom.frag(dom('div', 'Foo'), dom('div', 'Bar'))); assert.equal(elem.innerHTML, 'Hello
Foo
Bar
World'); elem.removeChild(elem.childNodes[2]); assert.equal(elem.innerHTML, 'Hello
Bar
World'); obs(null); assert.equal(elem.innerHTML, 'HelloWorld'); obs(dom.frag(dom('div', 'Foo'), dom('div', 'Bar'))); elem.removeChild(elem.childNodes[3]); assert.equal(elem.innerHTML, 'Hello
Foo
World'); obs(dom.frag(dom('div', 'Foo'), dom('div', 'Bar'))); assert.equal(elem.innerHTML, 'Hello
Foo
Bar
World'); }); }); describe("maybe", function() { it("should handle any number of children", function() { var obs = ko.observable(0); var elem = dom('div', 'Hello', kd.maybe(function() { return obs() > 0; }, function() { return dom("span", "Foo"); }), kd.maybe(function() { return obs() > 1; }, function() { return [dom("span", "Foo"), dom("span", "Bar")]; }), "World"); assert.equal(elem.textContent, "HelloWorld"); obs(1); assert.equal(elem.textContent, "HelloFooWorld"); obs(2); assert.equal(elem.textContent, "HelloFooFooBarWorld"); obs(0); assert.equal(elem.textContent, "HelloWorld"); }); it("should pass truthy value to content function", function() { var obs = ko.observable(null); var elem = dom('div', 'Hello', kd.maybe(obs, function(x) { return x; }), 'World'); assert.equal(elem.innerHTML, 'HelloWorld'); obs(dom('span', 'Foo')); assert.equal(elem.innerHTML, 'HelloFooWorld'); obs(0); // Falsy values should destroy the content assert.equal(elem.innerHTML, 'HelloWorld'); }); }); describe("foreach", function() { it("should work with koArray", function() { var model = koArray(); // Make sure the loop notices elements already in the model. model.assign(["a", "b", "c"]); var elem = dom('div', "[", kd.foreach(model, function(item) { return dom('span', ":", dom('span', kd.text(item))); }), "]" ); assert.equal(elem.textContent, "[:a:b:c]"); // Delete all elements. model.splice(0); assert.equal(elem.textContent, "[]"); // Test push. model.push("hello"); assert.equal(elem.textContent, "[:hello]"); model.push("world"); assert.equal(elem.textContent, "[:hello:world]"); // Test splice that replaces some elements with more. model.splice(0, 1, "foo", "bar", "baz"); assert.equal(elem.textContent, "[:foo:bar:baz:world]"); // Test splice which removes some elements. model.splice(-3, 2); assert.equal(elem.textContent, "[:foo:world]"); // Test splice which adds some elements in the middle. model.splice(1, 0, "test2", "test3"); assert.equal(elem.textContent, "[:foo:test2:test3:world]"); }); it("should work when items disappear from under it", function() { var elements = [dom('span', 'a'), dom('span', 'b'), dom('span', 'c')]; var model = koArray(); model.assign(elements); var elem = dom('div', '[', kd.foreach(model, function(item) { return item; }), ']'); assert.equal(elem.textContent, "[abc]"); // Plain splice out. var removed = model.splice(1, 1); assert.deepEqual(removed, [elements[1]]); assert.deepEqual(model.peek(), [elements[0], elements[2]]); assert.equal(elem.textContent, "[ac]"); // Splice it back in. model.splice(1, 0, elements[1]); assert.equal(elem.textContent, "[abc]"); // Now remove the element from DOM manually. elem.removeChild(elements[1]); assert.equal(elem.textContent, "[ac]"); assert.deepEqual(model.peek(), elements); // Use splice again, and make sure it still does the right thing. removed = model.splice(2, 1); assert.deepEqual(removed, [elements[2]]); assert.deepEqual(model.peek(), [elements[0], elements[1]]); assert.equal(elem.textContent, "[a]"); removed = model.splice(0, 2); assert.deepEqual(removed, [elements[0], elements[1]]); assert.deepEqual(model.peek(), []); assert.equal(elem.textContent, "[]"); }); it("should work when items are null", function() { var model = koArray(); var elem = dom('div', '[', kd.foreach(model, function(item) { return item && dom('span', item); }), ']'); assert.equal(elem.textContent, "[]"); model.splice(0, 0, "a", "b", "c"); assert.equal(elem.textContent, "[abc]"); var childCount = elem.childNodes.length; model.splice(1, 1, null); assert.equal(elem.childNodes.length, childCount - 1); // One child removed, non added. assert.equal(elem.textContent, "[ac]"); model.splice(1, 0, "x"); assert.equal(elem.textContent, "[axc]"); model.splice(3, 0, "y"); assert.equal(elem.textContent, "[axyc]"); model.splice(1, 2); assert.equal(elem.textContent, "[ayc]"); model.splice(0, 3); assert.equal(elem.textContent, "[]"); }); it("should dispose subscribables for detached nodes", function() { var obs = ko.observable("AAA"); var cb = sinon.spy(function(x) { return x; }); var data = koArray([ko.observable("foo"), ko.observable("bar")]); var elem = dom('div', kd.foreach(data, function(item) { return dom('div', kd.text(function() { return cb(item() + ":" + obs()); })); })); assert.equal(elem.innerHTML, '
foo:AAA
bar:AAA
'); obs("BBB"); assert.equal(elem.innerHTML, '
foo:BBB
bar:BBB
'); data.splice(1, 1); assert.equal(elem.innerHTML, '
foo:BBB
'); cb.resetHistory(); // Below is the core of the test: we are checking that the computed observable created for // the second item of the array ("bar") does NOT trigger a call to cb. obs("CCC"); assert.equal(elem.innerHTML, '
foo:CCC
'); sinon.assert.calledOnce(cb); sinon.assert.calledWith(cb, "foo:CCC"); }); }); });