UNPKG

can-bind

Version:

Updates one observable value with the value of another observable.

528 lines (448 loc) 13.9 kB
var Bind = require("../can-bind"); var canReflect = require("can-reflect"); var canReflectDeps = require("can-reflect-dependencies"); var canTestHelpers = require("can-test-helpers"); var helpers = require("./helpers"); var Observation = require("can-observation"); var QUnit = require("steal-qunit"); var SettableObservable = require("can-simple-observable/settable/settable"); var SimpleMap = require("can-simple-map"); var SimpleObservable = require("can-simple-observable"); var queues = require("can-queues"); QUnit.module("can-bind core",helpers.moduleHooks); QUnit.test("one-way binding to child", function(assert) { var parentValue = new SimpleObservable(0); var parent = new Observation(function() { return parentValue.get(); }); var child = new SimpleObservable(0); var binding = new Bind({ child: child, parent: parent }); // Turn on the listeners binding.start(); // Set the parent’s value and expect the child to update parentValue.set(15); assert.equal(canReflect.getValue(child), 15, "child updates"); // Set the child observable’s value and the parent should not update child.set(22); assert.equal(canReflect.getValue(parent), 15, "parent does not update"); // Turn off the listeners binding.stop(); // Setting the parent’s value should no longer update the child parentValue.set(45); assert.equal(canReflect.getValue(child), 22, "parent listener correctly turned off"); }); canTestHelpers.dev.devOnlyTest("one-way binding to child - dependency data", function(assert) { var parentValue = new SimpleObservable(0); var parent = new Observation(function() { return parentValue.get(); }); var child = new SimpleObservable(0); var binding = new Bind({ child: child, parent: parent }); // Turn on the listeners binding.start(); // Child dependency/mutation data var childDepData = canReflectDeps.getDependencyDataOf(child); var valueDependencies = new Set(); valueDependencies.add(parent); assert.deepEqual( childDepData, { whatChangesMe: { mutate: { valueDependencies: valueDependencies } } }, "child observable has the correct mutation dependencies" ); // Parent dependency/mutation data var parentDepData = canReflectDeps.getDependencyDataOf(parent); assert.deepEqual( parentDepData, { whatChangesMe: { derive: { valueDependencies: new Set([parentValue]) } }, whatIChange: { mutate: { valueDependencies: new Set([child]) } } }, "parent observable has the correct mutation dependencies" ); // Turn off the listeners binding.stop(); // Child dependency/mutation data childDepData = canReflectDeps.getDependencyDataOf(child); assert.equal( childDepData, undefined, "child observable has no mutation dependencies after stop()" ); // Parent dependency/mutation data parentDepData = canReflectDeps.getDependencyDataOf(parent); assert.equal( parentDepData, undefined, "parent observable has no mutation dependencies after stop()" ); }); QUnit.test("one-way binding to parent", function(assert) { var parent = new SimpleObservable(0); var childValue = new SimpleObservable(0); var child = new Observation(function() { return childValue.get(); }); var binding = new Bind({ child: child, parent: parent }); // Turn on the listeners binding.start(); // Set the parent observable’s value and the child should not update parent.set(15); assert.equal(canReflect.getValue(child), 0, "child does not update"); // Set the child’s value and expect the parent to update childValue.set(22); assert.equal(canReflect.getValue(parent), 22, "parent updates"); // Turn off the listeners binding.stop(); // Setting the child’s value should no longer update the parent childValue.set(58); assert.equal(canReflect.getValue(parent), 22, "child listener correctly turned off"); }); canTestHelpers.dev.devOnlyTest("one-way binding to parent - dependency data", function(assert) { var parent = new SimpleObservable(0); var childValue = new SimpleObservable(0); var child = new Observation(function() { return childValue.get(); }); var binding = new Bind({ child: child, parent: parent }); // Turn on the listeners binding.start(); // Child dependency/mutation data var childDepData = canReflectDeps.getDependencyDataOf(child); assert.deepEqual( childDepData, { whatChangesMe: { derive: { valueDependencies: new Set([childValue]) } }, whatIChange: { mutate: { valueDependencies: new Set([parent]) } } }, "child observable has the correct mutation dependencies" ); // Parent dependency/mutation data var parentDepData = canReflectDeps.getDependencyDataOf(parent); assert.deepEqual( parentDepData, { whatChangesMe: { mutate: { valueDependencies: new Set([child]) } } }, "parent observable has the correct mutation dependencies" ); // Turn off the listeners binding.stop(); // Child dependency/mutation data childDepData = canReflectDeps.getDependencyDataOf(child); assert.equal( childDepData, undefined, "child observable has no mutation dependencies after stop()" ); // Parent dependency/mutation data parentDepData = canReflectDeps.getDependencyDataOf(parent); assert.equal( parentDepData, undefined, "parent observable has no mutation dependencies after stop()" ); }); QUnit.test("basic two-way binding", function(assert) { var parent = new SimpleObservable(0); var child = new SimpleObservable(0); var binding = new Bind({ child: child, parent: parent }); // Turn on the listeners binding.start(); // Set the parent observable’s value and expect the child to update parent.set(15); assert.equal(canReflect.getValue(child), 15, "child updates"); // Set the child observable’s value and expect the parent to update child.set(22); assert.equal(canReflect.getValue(parent), 22, "parent updates"); // Stopping the binding should remove all listeners assert.equal(child.handlers.get([]).length, 1, "1 child listener before calling stop()"); assert.equal(parent.handlers.get([]).length, 1, "1 parent listener before calling stop()"); binding.stop(); assert.equal(child.handlers.get([]).length, 0, "0 child listeners after calling stop()"); assert.equal(parent.handlers.get([]).length, 0, "0 parent listeners after calling stop()"); // Setting the parent observable’s value should no longer update the child parent.set(45); assert.equal(canReflect.getValue(child), 22, "parent listener correctly turned off"); // Setting the child observable’s value should no longer update the parent child.set(58); assert.equal(canReflect.getValue(parent), 45, "child listener correctly turned off"); }); canTestHelpers.dev.devOnlyTest("basic two-way binding - dependency data", function(assert) { var parent = new SimpleObservable(0); var child = new SimpleObservable(0); var binding = new Bind({ child: child, parent: parent }); // Turn on the listeners binding.start(); // Child dependency/mutation data var childDepData = canReflectDeps.getDependencyDataOf(child); assert.deepEqual( childDepData, { whatChangesMe: { mutate: { valueDependencies: new Set([parent]) } }, whatIChange: { mutate: { valueDependencies: new Set([parent]) } } }, "child observable has the correct mutation dependencies" ); // Parent dependency/mutation data var parentDepData = canReflectDeps.getDependencyDataOf(parent); assert.deepEqual( parentDepData, { whatChangesMe: { mutate: { valueDependencies: new Set([child]) } }, whatIChange: { mutate: { valueDependencies: new Set([child]) } } }, "parent observable has the correct mutation dependencies" ); // Turn off the listeners binding.stop(); // Child dependency/mutation data childDepData = canReflectDeps.getDependencyDataOf(child); assert.equal( childDepData, undefined, "child observable has no mutation dependencies after stop()" ); // Parent dependency/mutation data parentDepData = canReflectDeps.getDependencyDataOf(parent); assert.equal( parentDepData, undefined, "parent observable has no mutation dependencies after stop()" ); }); canTestHelpers.dev.devOnlyTest("updateChildName and updateParentName options", function(assert) { var parent = new SimpleObservable(0); var child = new SimpleObservable(0); var binding = new Bind({ child: child, parent: parent, updateChildName: "custom child name", updateParentName: "custom parent name" }); assert.equal(binding._updateChild.name, "custom child name", "child name is correct"); assert.equal(binding._updateParent.name, "custom parent name", "parent name is correct"); }); QUnit.test("two-way binding with both values undefined", function(assert) { var child = new SimpleObservable(undefined); var parent = new SimpleObservable(undefined); var setChildWasCalled = false; var binding = new Bind({ child: child, parent: parent, setChild: function() { setChildWasCalled = true; } }); // Turn on the listeners binding.start(); // Set the child observable’s value and both should update assert.equal(setChildWasCalled, true, "setChild was called"); // Turn off the listeners binding.stop(); }); QUnit.test("two-way binding updates are ignored after calling stop()", function(assert) { var child = new SimpleObservable(15); var parent = new SimpleObservable(15); var binding = new Bind({ child: child, parent: parent }); // When the parent changes, turn the binding off var turnOffBinding = function() { binding.stop(); }; canReflect.onValue(parent, turnOffBinding, "domUI"); // Turn on the listeners binding.start(); // Set the parent observable’s value; first, the onValue callback above will // be called, turning off the binding. Anything listening to the parent’s // value will still be called, including can-bind, which will then ignore the // new value and not set the child because the binding has been turned off. parent.set(undefined); assert.equal(canReflect.getValue(child), 15, "child stays the same"); assert.equal(canReflect.getValue(parent), undefined, "parent stays the same"); // Turn off the listener canReflect.offValue(parent, turnOffBinding, "domUI"); }); QUnit.test("parentValue property", function(assert) { var parent = new SimpleObservable(15); var child = new SimpleObservable(22); var binding = new Bind({ child: child, parent: parent, priority: 15 }); assert.equal(binding.parentValue, 15, "can get parentValue"); }); QUnit.test("priority option", function(assert) { var parent = new SettableObservable(helpers.incrementByOne, null, 0); var child = new SettableObservable(helpers.incrementByOne, null, 0); new Bind({ child: child, parent: parent, priority: 15 }); assert.equal(canReflect.getPriority(child), 15, "child priority set"); assert.equal(canReflect.getPriority(parent), 15, "parent priority set"); }); // This test is similar to what’s needed for can-route QUnit.test("setChild and setParent options", function(assert) { var parent = new SimpleObservable(undefined); var map = new SimpleMap({ prop: "value" }); var child = new Observation(function() { return map.serialize(); }); var binding = new Bind({ child: child, parent: parent, setChild: function(newValue) { var split = newValue.split("="); var objectValue = {}; objectValue[split[0]] = split[1]; map.set(objectValue); }, setParent: function(newValue) { parent.set("prop=" + newValue.prop); } }); // Turn on the listeners binding.start(); // Set the parent’s value and expect the child to update parent.set("prop=15"); assert.deepEqual( canReflect.getValue(child), {prop: "15"}, "child updates" ); // Set the child observable’s value and the parent should update map.set({ prop: 22 }); assert.equal(canReflect.getValue(parent), "prop=22", "parent updates"); // Turn off the listeners binding.stop(); // Setting the parent’s value should no longer update the child parent.set("prop=45"); assert.deepEqual( canReflect.getValue(child), {prop: 22}, "parent listener correctly turned off" ); }); QUnit.test("use onEmit if observable has Symbol('can.onEmit')", function (assert) { var child = new SimpleObservable(5); var parent = new SimpleObservable(1); var childOffEmitCalled = false; var childOnEmitCalled = false; var setParentWasCalled = false; var childEmitFn = null; canReflect.assignSymbols(child, { "can.onEmit": function (updateFn) { childOnEmitCalled = true; childEmitFn = updateFn; }, "can.offEmit": function () { childOffEmitCalled = true; }, "can.onValue": null }); var binding = new Bind({ child: child, parent: parent, onInitDoNotUpdateParent: true, childToParent: true, parentToChild: false, setParent: function(newValue) { setParentWasCalled = true; parent.set(newValue); } }); // When the binding is turned on, the parent's value should not be set binding.start(); assert.equal(canReflect.getValue(parent), 1, "has correct initial value"); // Emit a changed value childEmitFn(5); assert.equal(canReflect.getValue(parent), 5, "has emitted value"); assert.equal(childOnEmitCalled, true, "onEmit was fired"); assert.equal(setParentWasCalled, true, "parent was updated"); // Turn off the listeners binding.stop(); assert.equal(childOffEmitCalled, true, "offEmit was fired"); }); if(queues.domQueue) { QUnit.test("able to queue changes in dom queue", function(assert){ var child = new SimpleObservable(5); var parent = new SimpleObservable(1); var element = document.createElement("div"); var binding = new Bind({ child: child, parent: parent, element: element, queue: "dom" }); binding.start(); assert.equal(child.value, 1, "updated child"); }); }