UNPKG

can-compute

Version:

CanJS’s legacy way of composing observables. Use can-observation instead.

447 lines (352 loc) 10.3 kB
var QUnit = require('steal-qunit'); var Compute = require('can-compute/proto-compute'); var queues = require('can-queues'); var canSymbol = require("can-symbol"); var canReflect = require("can-reflect"); QUnit.module('can/Compute'); QUnit.test('single value compute', function(assert) { assert.expect(2); var num = new Compute(1); num.bind('change', function (ev, newVal, oldVal) { assert.equal(newVal, 2, 'newVal'); assert.equal(oldVal, 1, 'oldVal'); }); num.set(2); }); QUnit.test('inner computes values are not bound to', function(assert) { var num = new Compute(1); var outer = new Compute(function() { var inner = new Compute(function() { return num.get() + 1; }); return 2 * inner.get(); }); var handler = function() {}; outer.bind('change', handler); // We do a timeout because we temporarily bind on num so that we can use its cached value. var done = assert.async(); setTimeout(function() { var handlers = num[canSymbol.for("can.meta")].handlers; assert.equal(handlers.get([]).length, 1, 'compute only bound to once'); done(); }, 50); }); QUnit.test('compute.truthy', function(assert) { var result = 0; var num = new Compute(3); var truthy = Compute.truthy(num); var tester = new Compute(function() { if(truthy.get()) { return ++result; } else { return ++result; } }); tester.bind('change', function(ev, newVal, oldVal) { if (num.get() === 0) { assert.equal(newVal, 2, '2 is the new val'); } else if (num.get() === -1) { assert.equal(newVal, 3, '3 is the new val'); } else { assert.ok(false, 'change should not be called'); } }); assert.equal(tester.get(), 1, 'on bind, we call tester once'); num.set(2); num.set(1); num.set(0); num.set(-1); }); QUnit.test('a binding compute does not double read', function(assert) { var sourceAge = 30, timesComputeIsCalled = 0; var age = new Compute(function (newVal) { timesComputeIsCalled++; if (timesComputeIsCalled === 1) { assert.ok(true, 'reading age to get value'); } else if (timesComputeIsCalled === 2) { assert.equal(newVal, 31, 'the second time should be an update'); } else if (timesComputeIsCalled === 3) { assert.ok(true, 'called after set to get the value'); } else { assert.ok(false, 'You\'ve called the callback ' + timesComputeIsCalled + ' times'); } if (arguments.length) { sourceAge = newVal; } else { return sourceAge; } }); var info = new Compute(function () { return 'I am ' + age.get(); }); var k = function () {}; info.bind('change', k); assert.equal(info.get(), 'I am 30'); age.set(31); assert.equal(info.get(), 'I am 31'); }); QUnit.test('cloning a setter compute (#547)', function(assert) { var name = new Compute('', function(newVal) { return this.txt + newVal; }); var cloned = name.clone({ txt: '.' }); cloned.set('-'); assert.equal(cloned.get(), '.-'); }); QUnit.test('compute updated method uses get and old value (#732)', function(assert) { assert.expect(9); var input = { value: 1 }; var value = new Compute('', { get: function () { return input.value; }, set: function (newVal) { input.value = newVal; }, on: function (update) { input.onchange = update; }, off: function () { delete input.onchange; } }); assert.equal(value.get(), 1, 'original value'); assert.ok(!input.onchange, 'nothing bound'); value.set(2); assert.equal(value.get(), 2, 'updated value'); assert.equal(input.value, 2, 'updated input.value'); value.bind('change', function (ev, newVal, oldVal) { assert.equal(newVal, 3, 'newVal'); assert.equal(oldVal, 2, 'oldVal'); value.unbind('change', this.Constructor); }); assert.ok(input.onchange, 'binding to onchange'); input.value = 3; input.onchange({}); assert.ok(!input.onchange, 'removed binding'); assert.equal(value.get(), 3); }); QUnit.test('a compute updated by source changes within a batch is part of that batch', function(assert) { var computeA = new Compute('a'); var computeB = new Compute('b'); var combined1 = new Compute(function() { return computeA.get() + ' ' + computeB.get(); }); var combined2 = new Compute(function() { return computeA.get() + ' ' + computeB.get(); }); var combo = new Compute(function() { return combined1.get() + ' ' + combined2.get(); }); var callbacks = 0; combo.bind('change', function(){ if(callbacks === 0){ assert.ok(true, 'called change once'); } else { assert.ok(false, 'called change multiple times'); } callbacks++; }); queues.batch.start(); computeA.set('A'); computeB.set('B'); queues.batch.stop(); }); QUnit.test('Compute.async can be like a normal getter', function(assert) { var first = new Compute('Justin'), last = new Compute('Meyer'), fullName = Compute.async('', function(){ return first.get() + ' ' + last.get(); }); assert.equal(fullName.get(), 'Justin Meyer'); }); QUnit.test('Compute.async operate on single value', function(assert) { var a = new Compute(1); var b = new Compute(2); var obj = Compute.async({}, function(curVal) { if(a.get()) { curVal.a = a.get(); } else { delete curVal.a; } if(b.get()) { curVal.b = b.get(); } else { delete curVal.b; } return curVal; }); obj.bind('change', function() {}); assert.deepEqual(obj.get(), {a: 1, b: 2}, 'object has all properties'); a.set(0); assert.deepEqual(obj.get(), {b: 2}, 'removed a'); b.set(0); assert.deepEqual(obj.get(), {}, 'removed b'); }); QUnit.test('Compute.async async changing value', function(assert) { var a = new Compute(1); var b = new Compute(2); var async = Compute.async(undefined, function(curVal, setVal) { if(a.get()) { setTimeout(function() { setVal('a'); }, 10); } else if(b.get()) { setTimeout(function() { setVal('b'); }, 10); } else { return null; } }); var done = assert.async(); var changeArgs = [ {newVal: 'a', oldVal: undefined, run: function() { a.set(0); } }, {newVal: 'b', oldVal: 'a', run: function() { b.set(0); }}, {newVal: null, oldVal: 'b', run: function() { done(); }} ], changeNum = 0; async.bind('change', function(ev, newVal, oldVal) { var data = changeArgs[changeNum++]; assert.equal( newVal, data.newVal, 'newVal is correct' ); assert.equal( oldVal, data.oldVal, 'oldVal is correct' ); setTimeout(data.run, 10); }); }); QUnit.test('Compute.async read without binding', function(assert) { var source = new Compute(1); var async = Compute.async([],function( curVal, setVal ) { curVal.push(source.get()); return curVal; }); assert.ok(async.get(), 'calling async worked'); }); QUnit.test('Compute.async set uses last set or initial value', function(assert) { var add = new Compute(1); var fnCount = 0; var async = Compute.async(10,function( curVal ) { switch(fnCount++) { case 0: assert.equal(curVal, 10); break; case 1: assert.equal(curVal, 20); break; case 2: assert.equal(curVal, 30, "on bind"); break; case 3: assert.equal(curVal, 30, "on bind"); break; } return curVal+add.get(); }); assert.equal(async.get(), 11, "initial value"); async.set(20); async.bind("change", function(){}); async.set(20); async.set(30); }); QUnit.test("Change propagation in a batch with late bindings (#2412)", function(assert) { var rootA = new Compute('a'); var rootB = new Compute('b'); var childA = new Compute(function() { return "childA"+rootA.get(); }); var grandChild = new Compute(function() { var b = rootB.get(); if (b === "b") { return "grandChild->b"; } var a = childA.get(); return "grandChild->"+a; }); childA.bind('change', function(ev, newVal, oldVal) {}); grandChild.bind('change', function(ev, newVal, oldVal) { assert.equal(newVal, "grandChild->childAA"); }); queues.batch.start(); rootA.set('A'); rootB.set('B'); queues.batch.stop(); }); if (Compute.prototype.trace) { QUnit.test("trace", function(assert) { var rootA = new Compute('a'); var rootB = new Compute('b'); var childA = new Compute(function() { return "childA"+rootA.get(); }); var fn = function() { var b = rootB.get(); if (b === "b") { return "grandChild->b"; } var a = childA.get(); return "grandChild->"+a; }; var grandChild = new Compute(fn); childA.bind('change', function(ev, newVal, oldVal) {}); grandChild.bind('change', function(ev, newVal, oldVal) { assert.equal(newVal, "grandChild->childAA"); }); var out = grandChild.trace(); assert.equal(out.definition, fn, "got the right function"); assert.equal(out.computeValue, "grandChild->b"); grandChild.log(); queues.batch.start(); rootA.set('A'); rootB.set('B'); queues.batch.stop(); grandChild.log(); }); } QUnit.test("works with can-reflect", function(assert) { assert.expect(5); var c = new Compute(0); assert.equal( canReflect.getValue(c), 0, "unbound value"); assert.ok(canReflect.isValueLike(c), "isValueLike is true"); assert.ok( !canReflect.valueHasDependencies(c), "valueHasDependencies -- false"); var d = new Compute(function() { return c.get(); }); d.on("change", function() {}); assert.ok( canReflect.valueHasDependencies(d), "valueHasDependencies -- true"); c.set(1); assert.equal( canReflect.getValue(d), 1, "bound value"); c.set(2); }); QUnit.test("can-reflect setValue", function(assert) { var a = new Compute("a"); canReflect.setValue(a, "A"); assert.equal(a.get(), "A", "compute"); }); QUnit.test("registered symbols", function(assert) { var a = new Compute("a"); assert.ok(a[canSymbol.for("can.isValueLike")], "can.isValueLike"); assert.equal(a[canSymbol.for("can.getValue")](), "a", "can.getValue"); a[canSymbol.for("can.setValue")]("b"); assert.equal(a.get(), "b", "can.setValue"); function handler(val) { assert.equal(val, "c", "can.onValue"); } a[canSymbol.for("can.onValue")](handler); a.set("c"); a[canSymbol.for("can.offValue")](handler); a.set("d"); // doesn't trigger handler }); QUnit.test("canReflect.onValue should get the previous value", function(assert) { var a = new Compute("a"); var done = assert.async(); canReflect.onValue(a, function(newVal, oldVal) { assert.equal(newVal, "b"); assert.equal(oldVal, "a"); done(); }); a.set("b"); });