reactive-function
Advanced tools
Comparing version 0.11.0 to 0.12.0
@@ -124,3 +124,2 @@ var ReactiveProperty = require("reactive-property"); | ||
ReactiveFunction.digest = function (){ | ||
graph | ||
@@ -185,2 +184,10 @@ .topologicalSort(Object.keys(changed), false) | ||
ReactiveFunction.link = function (propertyA, propertyB){ | ||
return ReactiveFunction({ | ||
inputs: [propertyA], | ||
output: propertyB, | ||
callback: function (x){ return x; } | ||
}); | ||
} | ||
module.exports = ReactiveFunction; |
{ | ||
"name": "reactive-function", | ||
"version": "0.11.0", | ||
"version": "0.12.0", | ||
"description": "A library for managing data flows and changing state.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -14,3 +14,3 @@ # reactive-function | ||
<br> | ||
This library is built on top of <a href="https://github.com/datavis-tech/reactive-property">reactive-property</a> and <a href="https://github.com/datavis-tech/graph-data-structure">graph-data-structure</a> | ||
This library is built on top of <a href="https://github.com/datavis-tech/reactive-property">reactive-property</a> and <a href="https://github.com/datavis-tech/graph-data-structure">graph-data-structure</a>. | ||
</p> | ||
@@ -24,2 +24,3 @@ | ||
* [Tricky Case](#tricky-case) | ||
* [Ohm's Law](#ohms-law) | ||
* [Installing](#installing) | ||
@@ -144,2 +145,43 @@ * [API Reference](#api-reference) | ||
### Ohm's Law | ||
[Ohm's Law](https://en.wikipedia.org/wiki/Ohm%27s_law) is a mathematical relationship between 3 quantities in electrical circuits: | ||
* V, voltage. V = IR | ||
* I, current. I = V/R | ||
* R, resistance. R = V/I | ||
Given any two of these values, one can calculate the third. Here's an example where if any two of the values are set, the third will automatically be calculated. | ||
<p align="center"> | ||
<img src="https://cloud.githubusercontent.com/assets/68416/15575699/db091b10-2371-11e6-9b0e-8c77878039f5.png"> | ||
<br> | ||
The data flow graph for Ohm's Law. | ||
</p> | ||
```javascript | ||
var I = ReactiveProperty(); | ||
var V = ReactiveProperty(); | ||
var R = ReactiveProperty(); | ||
ReactiveFunction({ output: V, inputs: [I, R], callback: function (i, r){ return i * r; } }); | ||
ReactiveFunction({ output: I, inputs: [V, R], callback: function (v, r){ return v / r; } }); | ||
ReactiveFunction({ output: R, inputs: [V, I], callback: function (v, i){ return v / i; } }); | ||
V(9) | ||
I(2) | ||
ReactiveFunction.digest(); | ||
console.log(R()); // Prints 4.5 | ||
R(6) | ||
I(2) | ||
ReactiveFunction.digest(); | ||
console.log(V()); // Prints 12 | ||
V(9); | ||
R(18); | ||
ReactiveFunction.digest(); | ||
console.log(I()); // Prints 0.5 | ||
``` | ||
For more detailed example code, have a look at the [tests](https://github.com/datavis-tech/reactive-function/blob/master/test.js). | ||
@@ -210,2 +252,31 @@ | ||
<a name="link" href="#link">#</a> <i>ReactiveFunction</i>.<b>link</b>(<i>propertyA</i>, <i>propertyB</i>) | ||
Sets up one-way data binding from *propertyA* to *propertyB*. Returns an instance of **[ReactiveFunction](#constructor)**. | ||
Arguments: | ||
* *propertyA* - A [reactive-property](https://github.com/datavis-tech/reactive-property). | ||
* *propertyB* - A [reactive-property](https://github.com/datavis-tech/reactive-property) that will be set to the value of *propertyA* and updated whenever *propertyA* changes. | ||
Example: | ||
```javascript | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
var link = ReactiveFunction.link(a, b); | ||
``` | ||
This is equivalent to: | ||
```javascript | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
var link = ReactiveFunction({ | ||
inputs: [a], | ||
output: b, | ||
callback: function (a){ return a; } | ||
}); | ||
``` | ||
### Data Flow Execution | ||
@@ -229,8 +300,13 @@ | ||
Data flow graphs can be serialized to JSON, then visualized using [graph-diagrams](https://github.com/datavis-tech/graph-diagrams/). | ||
<a name="serialize" href="#serialize">#</a> ReactiveFunction.<b>serializeGraph</b>() | ||
Returns a serialized form of the graph. Node names are derived from `property.propertyName` for each property. If `propertyName` is not specified, then the automaticelly generated node id (an integer) is used as the node name. | ||
Serializes the data flow graph. Returns an object with the following properties. | ||
* `nodes` An array of objects, each with the following properties. | ||
* `id` The node identifier string. | ||
* `propertyName` The property name for this node, derived from `property.propertyName` for each property. | ||
* `links` An array of objects representing edges, each with the following properties. | ||
* `source` The node identifier string of the source node (**u**). | ||
* `target` The node identifier string of the target node (**v**). | ||
Example: | ||
@@ -263,9 +339,9 @@ | ||
"nodes": [ | ||
{ "id": "fullName" }, | ||
{ "id": "firstName" }, | ||
{ "id": "lastName" } | ||
{ "id": "95", "propertyName": "fullName" }, | ||
{ "id": "96", "propertyName": "firstName" }, | ||
{ "id": "97", "propertyName": "lastName" } | ||
], | ||
"links": [ | ||
{ "source": "firstName", "target": "fullName" }, | ||
{ "source": "lastName", "target": "fullName" } | ||
{ "source": "96", "target": "95" }, | ||
{ "source": "97", "target": "95" } | ||
] | ||
@@ -275,4 +351,7 @@ } | ||
See also <a href="https://github.com/datavis-tech/graph-data-structure#serialize"><i>graph</i>.<b>serialize</b>()</a>. | ||
See also: | ||
* <a href="https://github.com/datavis-tech/graph-data-structure#serialize"><i>graph</i>.<b>serialize</b>()</a> | ||
* [graph-diagrams](https://github.com/datavis-tech/graph-diagrams/) | ||
## Related Work | ||
@@ -279,0 +358,0 @@ |
993
test.js
@@ -13,3 +13,3 @@ // Unit tests for reactive-function. | ||
// If true, writes graph files to ../graph-diagrams for visualization. | ||
outputGraphs: true, | ||
outputGraphs: false, | ||
project: "reactive-function" | ||
@@ -24,651 +24,638 @@ }); | ||
it("Should depend on two reactive properties.", function () { | ||
describe("Core Functionality", function() { | ||
it("Should depend on two reactive properties.", function () { | ||
var firstName = ReactiveProperty("Jane"); | ||
var lastName = ReactiveProperty("Smith"); | ||
var firstName = ReactiveProperty("Jane"); | ||
var lastName = ReactiveProperty("Smith"); | ||
var fullName = ReactiveProperty(); | ||
var fullName = ReactiveProperty(); | ||
var reactiveFunction = ReactiveFunction({ | ||
inputs: [firstName, lastName], | ||
output: fullName, | ||
callback: function (first, last){ | ||
return first + " " + last; | ||
} | ||
}); | ||
var reactiveFunction = ReactiveFunction({ | ||
inputs: [firstName, lastName], | ||
output: fullName, | ||
callback: function (first, last){ | ||
return first + " " + last; | ||
} | ||
}); | ||
ReactiveFunction.digest(); | ||
assert.equal(fullName(), "Jane Smith"); | ||
ReactiveFunction.digest(); | ||
assert.equal(fullName(), "Jane Smith"); | ||
firstName("John"); | ||
ReactiveFunction.digest(); | ||
assert.equal(fullName(), "John Smith"); | ||
firstName("John"); | ||
ReactiveFunction.digest(); | ||
assert.equal(fullName(), "John Smith"); | ||
lastName("Lennon"); | ||
ReactiveFunction.digest(); | ||
assert.equal(fullName(), "John Lennon"); | ||
lastName("Lennon"); | ||
ReactiveFunction.digest(); | ||
assert.equal(fullName(), "John Lennon"); | ||
// For serialization. | ||
firstName.propertyName = "firstName"; | ||
lastName.propertyName = "lastName"; | ||
fullName.propertyName = "fullName"; | ||
output("full-name"); | ||
// For serialization. | ||
firstName.propertyName = "firstName"; | ||
lastName.propertyName = "lastName"; | ||
fullName.propertyName = "fullName"; | ||
output("full-name"); | ||
reactiveFunction.destroy(); | ||
}); | ||
it("Should depend on any number of reactive properties.", function () { | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
var c = ReactiveProperty(15); | ||
var d = ReactiveProperty(); | ||
var e = ReactiveProperty(); | ||
var rf1 = ReactiveFunction({ | ||
inputs: [a], | ||
output: d, | ||
callback: function (a){ | ||
return a * 2; | ||
} | ||
reactiveFunction.destroy(); | ||
}); | ||
var rf2 = ReactiveFunction({ | ||
inputs: [a, b, c], | ||
output: e, | ||
callback: function (a, b, c){ | ||
return a + b + c; | ||
} | ||
}); | ||
it("Should depend on any number of reactive properties.", function () { | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
var c = ReactiveProperty(15); | ||
ReactiveFunction.digest(); | ||
var d = ReactiveProperty(); | ||
var e = ReactiveProperty(); | ||
assert.equal(d(), 10); | ||
assert.equal(e(), 30); | ||
var rf1 = ReactiveFunction({ | ||
inputs: [a], | ||
output: d, | ||
callback: function (a){ | ||
return a * 2; | ||
} | ||
}); | ||
// For serialization. | ||
a.propertyName = "a"; | ||
b.propertyName = "b"; | ||
c.propertyName = "c"; | ||
d.propertyName = "d"; | ||
e.propertyName = "e"; | ||
output("any-number"); | ||
var rf2 = ReactiveFunction({ | ||
inputs: [a, b, c], | ||
output: e, | ||
callback: function (a, b, c){ | ||
return a + b + c; | ||
} | ||
}); | ||
rf1.destroy(); | ||
rf2.destroy(); | ||
}); | ||
ReactiveFunction.digest(); | ||
it("Should depend on a reactive function output.", function () { | ||
assert.equal(d(), 10); | ||
assert.equal(e(), 30); | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(); | ||
var c = ReactiveProperty(); | ||
// For serialization. | ||
a.propertyName = "a"; | ||
b.propertyName = "b"; | ||
c.propertyName = "c"; | ||
d.propertyName = "d"; | ||
e.propertyName = "e"; | ||
output("any-number"); | ||
var rf1 = ReactiveFunction({ | ||
inputs: [a], | ||
output: b, | ||
callback: function (a){ | ||
return a * 2; | ||
} | ||
rf1.destroy(); | ||
rf2.destroy(); | ||
}); | ||
var rf2 = ReactiveFunction({ | ||
inputs: [b], | ||
output: c, | ||
callback: function (b){ | ||
return b / 2; | ||
} | ||
}); | ||
it("Should depend on a reactive function output.", function () { | ||
ReactiveFunction.digest(); | ||
assert.equal(c(), 5); | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(); | ||
var c = ReactiveProperty(); | ||
// For serialization. | ||
a.propertyName = "a"; | ||
b.propertyName = "b"; | ||
c.propertyName = "c"; | ||
output("abc"); | ||
var rf1 = ReactiveFunction({ | ||
inputs: [a], | ||
output: b, | ||
callback: function (a){ | ||
return a * 2; | ||
} | ||
}); | ||
rf1.destroy(); | ||
rf2.destroy(); | ||
}); | ||
var rf2 = ReactiveFunction({ | ||
inputs: [b], | ||
output: c, | ||
callback: function (b){ | ||
return b / 2; | ||
} | ||
}); | ||
it("Should depend on a property and a reactive function output.", function () { | ||
ReactiveFunction.digest(); | ||
assert.equal(c(), 5); | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
// For serialization. | ||
a.propertyName = "a"; | ||
b.propertyName = "b"; | ||
c.propertyName = "c"; | ||
output("abc"); | ||
var c = ReactiveProperty(); | ||
var d = ReactiveProperty(); | ||
var rf1 = ReactiveFunction({ | ||
inputs: [a], | ||
output: c, | ||
callback: function (a){ | ||
return a * 2; | ||
} | ||
rf1.destroy(); | ||
rf2.destroy(); | ||
}); | ||
var rf2 = ReactiveFunction({ | ||
inputs: [b, c], | ||
output: d, | ||
callback: function (b, c){ | ||
return b + c; | ||
} | ||
}); | ||
it("Should depend on a property and a reactive function output.", function () { | ||
ReactiveFunction.digest(); | ||
assert.equal(d(), 20); | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
// For serialization. | ||
a.propertyName = "a"; | ||
b.propertyName = "b"; | ||
c.propertyName = "c"; | ||
d.propertyName = "d"; | ||
output("abcd"); | ||
var c = ReactiveProperty(); | ||
var d = ReactiveProperty(); | ||
rf1.destroy(); | ||
rf2.destroy(); | ||
}); | ||
var rf1 = ReactiveFunction({ | ||
inputs: [a], | ||
output: c, | ||
callback: function (a){ | ||
return a * 2; | ||
} | ||
}); | ||
it("Should handle tricky case.", function () { | ||
var rf2 = ReactiveFunction({ | ||
inputs: [b, c], | ||
output: d, | ||
callback: function (b, c){ | ||
return b + c; | ||
} | ||
}); | ||
// This is the case where model-js failed (https://github.com/curran/model), | ||
// which cropped up as flashes of inconstistent states in Chiasm visualizations. | ||
// For example, it happens when you change the X column in this example | ||
// "Magic Heat Map" http://bl.ocks.org/curran/a54fc3a6578efcdc19f4 | ||
// This flaw in model-js is the main inspiration for making this library and using topological sort. | ||
// | ||
// a | ||
// / \ | ||
// b | | ||
// | d | ||
// c | | ||
// \ / | ||
// e | ||
// | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(); | ||
var c = ReactiveProperty(); | ||
var d = ReactiveProperty(); | ||
var e = ReactiveProperty(); | ||
ReactiveFunction.digest(); | ||
assert.equal(d(), 20); | ||
var rfs = [ | ||
ReactiveFunction({ inputs: [a], output: b, callback: function (a){ return a * 2; } }), | ||
ReactiveFunction({ inputs: [b], output: c, callback: function (b){ return b + 5; } }), | ||
ReactiveFunction({ inputs: [a], output: d, callback: function (a){ return a * 3; } }), | ||
ReactiveFunction({ inputs: [c, d], output: e, callback: function (c, d){ return c + d; } }) | ||
]; | ||
// For serialization. | ||
a.propertyName = "a"; | ||
b.propertyName = "b"; | ||
c.propertyName = "c"; | ||
d.propertyName = "d"; | ||
output("abcd"); | ||
ReactiveFunction.digest(); | ||
assert.equal(e(), ((a() * 2) + 5) + (a() * 3)); | ||
rf1.destroy(); | ||
rf2.destroy(); | ||
}); | ||
a(10); | ||
ReactiveFunction.digest(); | ||
assert.equal(e(), ((a() * 2) + 5) + (a() * 3)); | ||
it("Should handle tricky case.", function () { | ||
// For serialization. | ||
a.propertyName = "a"; | ||
b.propertyName = "b"; | ||
c.propertyName = "c"; | ||
d.propertyName = "d"; | ||
e.propertyName = "e"; | ||
output("tricky-case"); | ||
// This is the case where model-js failed (https://github.com/curran/model), | ||
// which cropped up as flashes of inconstistent states in Chiasm visualizations. | ||
// For example, it happens when you change the X column in this example | ||
// "Magic Heat Map" http://bl.ocks.org/curran/a54fc3a6578efcdc19f4 | ||
// This flaw in model-js is the main inspiration for making this library and using topological sort. | ||
// | ||
// a | ||
// / \ | ||
// b | | ||
// | d | ||
// c | | ||
// \ / | ||
// e | ||
// | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(); | ||
var c = ReactiveProperty(); | ||
var d = ReactiveProperty(); | ||
var e = ReactiveProperty(); | ||
rfs.forEach(function (reactiveFunction){ | ||
reactiveFunction.destroy(); | ||
}); | ||
}); | ||
var rfs = [ | ||
ReactiveFunction({ inputs: [a], output: b, callback: function (a){ return a * 2; } }), | ||
ReactiveFunction({ inputs: [b], output: c, callback: function (b){ return b + 5; } }), | ||
ReactiveFunction({ inputs: [a], output: d, callback: function (a){ return a * 3; } }), | ||
ReactiveFunction({ inputs: [c, d], output: e, callback: function (c, d){ return c + d; } }) | ||
]; | ||
it("Should clear changed nodes on digest.", function () { | ||
var numInvocations = 0; | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(); | ||
ReactiveFunction.digest(); | ||
assert.equal(e(), ((a() * 2) + 5) + (a() * 3)); | ||
var reactiveFunction = ReactiveFunction({ | ||
inputs: [a], | ||
output: b, | ||
callback: function (a){ | ||
numInvocations++; | ||
return a * 2; | ||
} | ||
}); | ||
a(10); | ||
ReactiveFunction.digest(); | ||
assert.equal(e(), ((a() * 2) + 5) + (a() * 3)); | ||
ReactiveFunction.digest(); | ||
ReactiveFunction.digest(); | ||
assert.equal(numInvocations, 1); | ||
// For serialization. | ||
a.propertyName = "a"; | ||
b.propertyName = "b"; | ||
c.propertyName = "c"; | ||
d.propertyName = "d"; | ||
e.propertyName = "e"; | ||
output("tricky-case"); | ||
reactiveFunction.destroy(); | ||
}); | ||
it("Should automatically digest on next tick.", function (done) { | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
var c = ReactiveProperty(); | ||
var reactiveFunction = ReactiveFunction({ | ||
inputs: [a, b], | ||
output: c, | ||
callback: function (a, b){ | ||
return a + b; | ||
} | ||
rfs.forEach(function (reactiveFunction){ | ||
reactiveFunction.destroy(); | ||
}); | ||
}); | ||
ReactiveFunction.nextFrame(function (){ | ||
assert.equal(c(), 15); | ||
reactiveFunction.destroy(); | ||
done(); | ||
}, 0); | ||
it("Should automatically digest on next tick.", function (done) { | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
var c = ReactiveProperty(); | ||
}); | ||
var reactiveFunction = ReactiveFunction({ | ||
inputs: [a, b], | ||
output: c, | ||
callback: function (a, b){ | ||
return a + b; | ||
} | ||
}); | ||
it("Should remove listeners on destroy.", function (done){ | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
var c = ReactiveProperty(); | ||
ReactiveFunction.nextFrame(function (){ | ||
assert.equal(c(), 15); | ||
reactiveFunction.destroy(); | ||
done(); | ||
}, 0); | ||
var rf = ReactiveFunction({ | ||
inputs: [a, b], | ||
output: c, | ||
callback: function (a, b){ | ||
return a + b; | ||
} | ||
}); | ||
// Wait until after the first auto-digest. | ||
ReactiveFunction.nextFrame(function (){ | ||
it("Should not invoke if an input is undefined.", function (){ | ||
var numInvocations = 0; | ||
var a = ReactiveProperty(); | ||
var b = ReactiveProperty(10); | ||
var c = ReactiveProperty(); | ||
rf.destroy(); | ||
var reactiveFunction = ReactiveFunction({ | ||
inputs: [a, b], | ||
output: c, | ||
callback: function (a, b){ | ||
numInvocations++; | ||
return a + b; | ||
} | ||
}); | ||
// Without the call to destroy(), this would trigger a digest. | ||
a(20); | ||
ReactiveFunction.digest(); | ||
assert.equal(numInvocations, 0); | ||
// Give time for an auto-digest to occur. | ||
ReactiveFunction.nextFrame(function (){ | ||
reactiveFunction.destroy(); | ||
}); | ||
// Confirm that a digest did not occur. | ||
assert.equal(c(), 15); | ||
it("Should invoke if an input is null.", function (){ | ||
var numInvocations = 0; | ||
var a = ReactiveProperty(null); | ||
var b = ReactiveProperty(10); | ||
var c = ReactiveProperty(); | ||
done(); | ||
}, 0); | ||
}, 0); | ||
}); | ||
var reactiveFunction = ReactiveFunction({ | ||
inputs: [a, b], | ||
output: c, | ||
callback: function (a, b){ | ||
numInvocations++; | ||
return a + b; | ||
} | ||
}); | ||
it("Should remove edges on destroy.", function (){ | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
var c = ReactiveProperty(); | ||
ReactiveFunction.digest(); | ||
assert.equal(numInvocations, 1); | ||
var rf = ReactiveFunction({ | ||
inputs: [a, b], | ||
output: c, | ||
callback: function (a, b){ | ||
return a + b; | ||
} | ||
reactiveFunction.destroy(); | ||
}); | ||
ReactiveFunction.digest(); | ||
it("Should invoke if a single input is null.", function (){ | ||
var numInvocations = 0; | ||
var a = ReactiveProperty(null); | ||
assert.equal(c(), 15); | ||
var reactiveFunction = ReactiveFunction({ | ||
inputs: [a], | ||
callback: function (a, b){ | ||
numInvocations++; | ||
} | ||
}); | ||
rf.destroy(); | ||
ReactiveFunction.digest(); | ||
assert.equal(numInvocations, 1); | ||
a(20); | ||
reactiveFunction.destroy(); | ||
}); | ||
ReactiveFunction.digest(); | ||
assert.equal(c(), 15); | ||
}); | ||
it("Should not invoke if an input is undefined.", function (){ | ||
var numInvocations = 0; | ||
var a = ReactiveProperty(); | ||
var b = ReactiveProperty(10); | ||
var c = ReactiveProperty(); | ||
var reactiveFunction = ReactiveFunction({ | ||
inputs: [a, b], | ||
output: c, | ||
callback: function (a, b){ | ||
numInvocations++; | ||
return a + b; | ||
} | ||
it("Should work without any output specified.", function (){ | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
var sideEffect = 0; | ||
var rf = ReactiveFunction({ | ||
inputs: [a, b], | ||
callback: function (a, b){ sideEffect++; } | ||
}); | ||
ReactiveFunction.digest(); | ||
assert(sideEffect); | ||
rf.destroy(); | ||
}); | ||
ReactiveFunction.digest(); | ||
assert.equal(numInvocations, 0); | ||
it("Should work asynchronously.", function (done){ | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
var c = ReactiveProperty(); | ||
reactiveFunction.destroy(); | ||
}); | ||
var rf = ReactiveFunction({ | ||
inputs: [a, b], | ||
callback: function (a, b) { | ||
setTimeout(function(){ | ||
c(a + b); | ||
}, 10); | ||
} | ||
}); | ||
it("Should invoke if an input is null.", function (){ | ||
var numInvocations = 0; | ||
var a = ReactiveProperty(null); | ||
var b = ReactiveProperty(10); | ||
var c = ReactiveProperty(); | ||
var reactiveFunction = ReactiveFunction({ | ||
inputs: [a, b], | ||
output: c, | ||
callback: function (a, b){ | ||
numInvocations++; | ||
return a + b; | ||
} | ||
c.on(function(value){ | ||
assert.equal(value, 15); | ||
rf.destroy(); | ||
done(); | ||
}); | ||
}); | ||
ReactiveFunction.digest(); | ||
assert.equal(numInvocations, 1); | ||
it("Should be able to implement unidirectional data binding.", function (){ | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
var reactiveFunction = ReactiveFunction({ | ||
inputs: [a], | ||
output: b, | ||
callback: function (a){ | ||
return a; | ||
} | ||
}); | ||
reactiveFunction.destroy(); | ||
}); | ||
ReactiveFunction.digest(); | ||
assert.equal(b(), 5); | ||
it("Should invoke if a single input is null.", function (){ | ||
var numInvocations = 0; | ||
var a = ReactiveProperty(null); | ||
// For serialization. | ||
a.propertyName = "a"; | ||
b.propertyName = "b"; | ||
output("ab"); | ||
var reactiveFunction = ReactiveFunction({ | ||
inputs: [a], | ||
callback: function (a, b){ | ||
numInvocations++; | ||
} | ||
reactiveFunction.destroy(); | ||
}); | ||
ReactiveFunction.digest(); | ||
assert.equal(numInvocations, 1); | ||
it("Should support unidirectional data binding via link().", function (){ | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
reactiveFunction.destroy(); | ||
}); | ||
var link = ReactiveFunction.link(a, b); | ||
it("Should be able to implement unidirectional data binding.", function (){ | ||
ReactiveFunction.digest(); | ||
assert.equal(b(), 5); | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
var reactiveFunction = ReactiveFunction({ | ||
inputs: [a], | ||
output: b, | ||
callback: function (a){ | ||
return a; | ||
} | ||
link.destroy(); | ||
}); | ||
ReactiveFunction.digest(); | ||
it("Should compute Ohm's Law.", function (){ | ||
assert.equal(b(), 5); | ||
// These symbols are used in the definition of Ohm's law. | ||
// See https://en.wikipedia.org/wiki/Ohm%27s_law | ||
var I = ReactiveProperty(); | ||
var V = ReactiveProperty(); | ||
var R = ReactiveProperty(); | ||
// For serialization. | ||
a.propertyName = "a"; | ||
b.propertyName = "b"; | ||
output("ab"); | ||
var rfs = [ | ||
reactiveFunction.destroy(); | ||
}); | ||
// I = V / R | ||
ReactiveFunction({ | ||
inputs: [V, R], | ||
output: I, | ||
callback: function (v, r){ return v / r; } | ||
}), | ||
it("Should be able to implement bidirectional data binding.", function (){ | ||
// V = I * R | ||
ReactiveFunction({ | ||
inputs: [I, R], | ||
output: V, | ||
callback: function (i, r){ return i * r; } | ||
}), | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
// R = V / I | ||
ReactiveFunction({ | ||
inputs: [V, I], | ||
output: R, | ||
callback: function (v, i){ return v / i; } | ||
}) | ||
]; | ||
function identity(x){ return x; } | ||
V(9) | ||
I(2) | ||
ReactiveFunction.digest(); | ||
assert.equal(R(), 4.5); | ||
var rf1 = ReactiveFunction({ inputs: [a], output: b, callback: identity }); | ||
var rf2 = ReactiveFunction({ inputs: [b], output: a, callback: identity }); | ||
R(6) | ||
I(2) | ||
ReactiveFunction.digest(); | ||
assert.equal(V(), 12); | ||
ReactiveFunction.digest(); | ||
V(9); | ||
R(18); | ||
ReactiveFunction.digest(); | ||
assert.equal(I(), 0.5); | ||
// The most recently added edge takes precedence. | ||
assert.equal(b(), 10); | ||
V(9) | ||
I(2) | ||
ReactiveFunction.digest(); | ||
assert.equal(R(), 4.5); | ||
a(50); | ||
ReactiveFunction.digest(); | ||
assert.equal(b(), 50); | ||
// For serialization. | ||
V.propertyName = "V"; | ||
I.propertyName = "I"; | ||
R.propertyName = "R"; | ||
output("ohms-law"); | ||
b(100); | ||
ReactiveFunction.digest(); | ||
assert.equal(a(), 100); | ||
rfs.forEach(function (reactiveFunction){ | ||
reactiveFunction.destroy(); | ||
}); | ||
}); | ||
// For serialization. | ||
a.propertyName = "a"; | ||
b.propertyName = "b"; | ||
output("data-binding"); | ||
rf1.destroy(); | ||
rf2.destroy(); | ||
}); | ||
it("Should compute Ohm's Law.", function (){ | ||
describe("Cleanup", function (){ | ||
// These symbols are used in the definition of Ohm's law. | ||
// See https://en.wikipedia.org/wiki/Ohm%27s_law | ||
var I = ReactiveProperty(); | ||
var V = ReactiveProperty(); | ||
var R = ReactiveProperty(); | ||
it("Should clear changed nodes on digest.", function () { | ||
var numInvocations = 0; | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(); | ||
var rfs = [ | ||
var reactiveFunction = ReactiveFunction({ | ||
inputs: [a], | ||
output: b, | ||
callback: function (a){ | ||
numInvocations++; | ||
return a * 2; | ||
} | ||
}); | ||
// I = V / R | ||
ReactiveFunction({ | ||
inputs: [V, R], | ||
output: I, | ||
callback: function (v, r){ return v / r; } | ||
}), | ||
ReactiveFunction.digest(); | ||
ReactiveFunction.digest(); | ||
assert.equal(numInvocations, 1); | ||
// V = I * R | ||
ReactiveFunction({ | ||
inputs: [I, R], | ||
output: V, | ||
callback: function (i, r){ return i * r; } | ||
}), | ||
reactiveFunction.destroy(); | ||
}); | ||
// R = V / I | ||
ReactiveFunction({ | ||
inputs: [V, I], | ||
output: R, | ||
callback: function (v, i){ return v / i; } | ||
}) | ||
]; | ||
it("Should remove listeners on destroy.", function (done){ | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
var c = ReactiveProperty(); | ||
V(9) | ||
I(2) | ||
ReactiveFunction.digest(); | ||
assert.equal(R(), 4.5); | ||
var rf = ReactiveFunction({ | ||
inputs: [a, b], | ||
output: c, | ||
callback: function (a, b){ | ||
return a + b; | ||
} | ||
}); | ||
R(6) | ||
I(2) | ||
ReactiveFunction.digest(); | ||
assert.equal(V(), 12); | ||
// Wait until after the first auto-digest. | ||
ReactiveFunction.nextFrame(function (){ | ||
V(9); | ||
R(18); | ||
ReactiveFunction.digest(); | ||
assert.equal(I(), 0.5); | ||
rf.destroy(); | ||
V(9) | ||
I(2) | ||
ReactiveFunction.digest(); | ||
assert.equal(R(), 4.5); | ||
// Without the call to destroy(), this would trigger a digest. | ||
a(20); | ||
// For serialization. | ||
V.propertyName = "V"; | ||
I.propertyName = "I"; | ||
R.propertyName = "R"; | ||
output("ohms-law"); | ||
// Give time for an auto-digest to occur. | ||
ReactiveFunction.nextFrame(function (){ | ||
rfs.forEach(function (reactiveFunction){ | ||
reactiveFunction.destroy(); | ||
// Confirm that a digest did not occur. | ||
assert.equal(c(), 15); | ||
done(); | ||
}, 0); | ||
}, 0); | ||
}); | ||
}); | ||
it("Should remove the 'evaluate' function from the output on destroy.", function (){ | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
var c = ReactiveProperty(); | ||
it("Should remove edges on destroy.", function (){ | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
var c = ReactiveProperty(); | ||
var rf = ReactiveFunction({ | ||
inputs: [a, b], | ||
output: c, | ||
callback: function (a, b){ | ||
return a + b; | ||
} | ||
}); | ||
var rf = ReactiveFunction({ | ||
inputs: [a, b], | ||
output: c, | ||
callback: function (a, b){ | ||
return a + b; | ||
} | ||
}); | ||
assert.equal(typeof c.evaluate, "function"); | ||
ReactiveFunction.digest(); | ||
rf.destroy(); | ||
assert.equal(typeof c.evaluate, "undefined"); | ||
}); | ||
assert.equal(c(), 15); | ||
it("Should work without any output specified.", function (){ | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
var sideEffect = 0; | ||
rf.destroy(); | ||
var rf = ReactiveFunction({ | ||
inputs: [a, b], | ||
callback: function (a, b){ | ||
sideEffect++; | ||
} | ||
a(20); | ||
ReactiveFunction.digest(); | ||
assert.equal(c(), 15); | ||
}); | ||
ReactiveFunction.digest(); | ||
assert(sideEffect); | ||
rf.destroy(); | ||
}); | ||
it("Should remove the 'evaluate' function from the output on destroy.", function (){ | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
var c = ReactiveProperty(); | ||
it("Should work asynchronously.", function (done){ | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
var c = ReactiveProperty(); | ||
var rf = ReactiveFunction({ | ||
inputs: [a, b], | ||
output: c, | ||
callback: function (a, b){ | ||
return a + b; | ||
} | ||
}); | ||
var rf = ReactiveFunction({ | ||
inputs: [a, b], | ||
callback: function (a, b) { | ||
setTimeout(function(){ | ||
c(a + b); | ||
}, 10); | ||
} | ||
}); | ||
assert.equal(typeof c.evaluate, "function"); | ||
c.on(function(value){ | ||
assert.equal(value, 15); | ||
rf.destroy(); | ||
done(); | ||
assert.equal(typeof c.evaluate, "undefined"); | ||
}); | ||
}); | ||
describe("Serialization", function() { | ||
it("Should serialize the data flow graph.", function (){ | ||
// These tests may easily break if upstream tests are modified. | ||
// Fix by changing the value of initialId. | ||
var initialId = 56; | ||
var firstName = ReactiveProperty("Jane"); | ||
var lastName = ReactiveProperty("Smith"); | ||
var fullName = ReactiveProperty(); | ||
it("Should serialize the data flow graph.", function (){ | ||
// For serialization. | ||
firstName.propertyName = "firstName"; | ||
lastName.propertyName = "lastName"; | ||
fullName.propertyName = "fullName"; | ||
var firstName = ReactiveProperty("Jane"); | ||
var lastName = ReactiveProperty("Smith"); | ||
var fullName = ReactiveProperty(); | ||
var rf = ReactiveFunction({ | ||
inputs: [firstName, lastName], | ||
output: fullName, | ||
callback: function (first, last){ | ||
return first + " " + last; | ||
} | ||
}); | ||
// For serialization. | ||
firstName.propertyName = "firstName"; | ||
lastName.propertyName = "lastName"; | ||
fullName.propertyName = "fullName"; | ||
var serialized = ReactiveFunction.serializeGraph(); | ||
var rf = ReactiveFunction({ | ||
inputs: [firstName, lastName], | ||
output: fullName, | ||
callback: function (first, last){ | ||
return first + " " + last; | ||
} | ||
}); | ||
//console.log(JSON.stringify(serialized, null, 2)); | ||
var serialized = ReactiveFunction.serializeGraph(); | ||
assert.equal(serialized.nodes.length, 3); | ||
assert.equal(serialized.links.length, 2); | ||
//console.log(JSON.stringify(serialized, null, 2)); | ||
assert.equal(serialized.nodes[0].id, "56"); | ||
assert.equal(serialized.nodes[1].id, "57"); | ||
assert.equal(serialized.nodes[2].id, "58"); | ||
assert.equal(serialized.nodes.length, 3); | ||
assert.equal(serialized.links.length, 2); | ||
assert.equal(serialized.nodes[0].propertyName, "fullName"); | ||
assert.equal(serialized.nodes[1].propertyName, "firstName"); | ||
assert.equal(serialized.nodes[2].propertyName, "lastName"); | ||
var idStart = initialId; | ||
assert.equal(serialized.links[0].source, "57"); | ||
assert.equal(serialized.links[0].target, "56"); | ||
assert.equal(serialized.links[1].source, "58"); | ||
assert.equal(serialized.links[1].target, "56"); | ||
assert.equal(serialized.nodes[0].id, String(idStart)); | ||
assert.equal(serialized.nodes[1].id, String(idStart + 1)); | ||
assert.equal(serialized.nodes[2].id, String(idStart + 2)); | ||
rf.destroy(); | ||
}); | ||
assert.equal(serialized.nodes[0].propertyName, "fullName"); | ||
assert.equal(serialized.nodes[1].propertyName, "firstName"); | ||
assert.equal(serialized.nodes[2].propertyName, "lastName"); | ||
it("Should serialize the data flow graph and omit property names if they are not present.", function (){ | ||
var firstName = ReactiveProperty("Jane"); | ||
var lastName = ReactiveProperty("Smith"); | ||
var fullName = ReactiveProperty(); | ||
var rf = ReactiveFunction({ | ||
inputs: [firstName, lastName], | ||
output: fullName, | ||
callback: function (first, last){ | ||
return first + " " + last; | ||
} | ||
assert.equal(serialized.links[0].source, String(idStart + 1)); | ||
assert.equal(serialized.links[0].target, String(idStart)); | ||
assert.equal(serialized.links[1].source, String(idStart + 2)); | ||
assert.equal(serialized.links[1].target, String(idStart)); | ||
rf.destroy(); | ||
}); | ||
var serialized = ReactiveFunction.serializeGraph(); | ||
it("Should serialize the data flow graph and omit property names if they are not present.", function (){ | ||
assert.equal(serialized.nodes.length, 3); | ||
assert.equal(serialized.links.length, 2); | ||
var firstName = ReactiveProperty("Jane"); | ||
var lastName = ReactiveProperty("Smith"); | ||
var fullName = ReactiveProperty(); | ||
var rf = ReactiveFunction({ | ||
inputs: [firstName, lastName], | ||
output: fullName, | ||
callback: function (first, last){ | ||
return first + " " + last; | ||
} | ||
}); | ||
// These tests may easily break if earlier tests are modified. | ||
// Fix by copying values from: | ||
//console.log(JSON.stringify(serialized, null, 2)); | ||
var serialized = ReactiveFunction.serializeGraph(); | ||
assert.equal(serialized.nodes[0].id, "59"); | ||
assert.equal(serialized.nodes[1].id, "60"); | ||
assert.equal(serialized.nodes[2].id, "61"); | ||
assert.equal(serialized.nodes.length, 3); | ||
assert.equal(serialized.links.length, 2); | ||
assert.equal(typeof serialized.nodes[0].propertyName, "undefined"); | ||
var idStart = initialId + 3; | ||
assert.equal(serialized.links[0].source, "60"); | ||
assert.equal(serialized.links[0].target, "59"); | ||
assert.equal(serialized.links[1].source, "61"); | ||
assert.equal(serialized.links[1].target, "59"); | ||
assert.equal(serialized.nodes[0].id, String(idStart)); | ||
assert.equal(serialized.nodes[1].id, String(idStart + 1)); | ||
assert.equal(serialized.nodes[2].id, String(idStart + 2)); | ||
rf.destroy(); | ||
}); | ||
assert.equal(typeof serialized.nodes[0].propertyName, "undefined"); | ||
it("Should serialize case without any output specified and use empty string as property name.", function (){ | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
var sideEffect = 0; | ||
assert.equal(serialized.links[0].source, String(idStart + 1)); | ||
assert.equal(serialized.links[0].target, String(idStart)); | ||
assert.equal(serialized.links[1].source, String(idStart + 2)); | ||
assert.equal(serialized.links[1].target, String(idStart)); | ||
var rf = ReactiveFunction({ | ||
inputs: [a, b], | ||
callback: function (a, b){ | ||
sideEffect++; | ||
} | ||
rf.destroy(); | ||
}); | ||
// For serialization. | ||
a.propertyName = "a"; | ||
b.propertyName = "b"; | ||
var serialized = ReactiveFunction.serializeGraph(); | ||
it("Should serialize case without any output specified and use empty string as property name.", function (){ | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(10); | ||
var sideEffect = 0; | ||
assert.equal(serialized.nodes.length, 3); | ||
assert.equal(serialized.links.length, 2); | ||
var rf = ReactiveFunction({ | ||
inputs: [a, b], | ||
callback: function (a, b){ | ||
sideEffect++; | ||
} | ||
}); | ||
//console.log(JSON.stringify(serialized, null, 2)); | ||
// For serialization. | ||
a.propertyName = "a"; | ||
b.propertyName = "b"; | ||
var serialized = ReactiveFunction.serializeGraph(); | ||
assert.equal(serialized.nodes[0].id, "62"); | ||
assert.equal(serialized.nodes[1].id, "63"); | ||
assert.equal(serialized.nodes[2].id, "64"); | ||
assert.equal(serialized.nodes.length, 3); | ||
assert.equal(serialized.links.length, 2); | ||
assert.equal(serialized.nodes[0].propertyName, ""); | ||
assert.equal(serialized.nodes[1].propertyName, "a"); | ||
assert.equal(serialized.nodes[2].propertyName, "b"); | ||
//console.log(JSON.stringify(serialized, null, 2)); | ||
assert.equal(serialized.links[0].source, "63"); | ||
assert.equal(serialized.links[0].target, "62"); | ||
assert.equal(serialized.links[1].source, "64"); | ||
assert.equal(serialized.links[1].target, "62"); | ||
rf.destroy(); | ||
var idStart = initialId + 6; | ||
assert.equal(serialized.nodes[0].id, String(idStart)); | ||
assert.equal(serialized.nodes[1].id, String(idStart + 1)); | ||
assert.equal(serialized.nodes[2].id, String(idStart + 2)); | ||
assert.equal(serialized.nodes[0].propertyName, ""); | ||
assert.equal(serialized.nodes[1].propertyName, "a"); | ||
assert.equal(serialized.nodes[2].propertyName, "b"); | ||
assert.equal(serialized.links[0].source, String(idStart + 1)); | ||
assert.equal(serialized.links[0].target, String(idStart)); | ||
assert.equal(serialized.links[1].source, String(idStart + 2)); | ||
assert.equal(serialized.links[1].target, String(idStart)); | ||
rf.destroy(); | ||
}); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
38671
367