diff --git a/lib/network/modules/EdgesHandler.js b/lib/network/modules/EdgesHandler.js index f28499f1..82bb5b48 100644 --- a/lib/network/modules/EdgesHandler.js +++ b/lib/network/modules/EdgesHandler.js @@ -117,7 +117,7 @@ class EdgesHandler { value: undefined }; - util.extend(this.options, this.defaultOptions); + util.deepExtend(this.options, this.defaultOptions); this.bindEventListeners(); } @@ -140,9 +140,9 @@ class EdgesHandler { // only forcibly remove the smooth curve if the data has been set of the edge has the smooth curves defined. // this is because a change in the global would not affect these curves. if (edgeData !== undefined) { - let edgeOptions = edgeData.smooth; - if (edgeOptions !== undefined) { - if (edgeOptions.enabled === true && edgeOptions.type === 'dynamic') { + let smoothOptions = edgeData.smooth; + if (smoothOptions !== undefined) { + if (smoothOptions.enabled === true && smoothOptions.type === 'dynamic') { if (type === undefined) { edge.setOptions({smooth: false}); } @@ -197,7 +197,7 @@ class EdgesHandler { this.edgeOptions = options; if (options !== undefined) { // use the parser from the Edge class to fill in all shorthand notations - Edge.parseOptions(this.options, options); + Edge.parseOptions(this.options, options, true, this.defaultOptions, true); // update smooth settings in all edges let dataChanged = false; @@ -390,6 +390,16 @@ class EdgesHandler { * @returns {Edge} */ create(properties) { + // It is not at all clear why all these separate options should be passed: + // + // - this.edgeOptions is set in setOptions() + // the value of which is also added to this.options with parseOptions() + // - this.defaultOptions has been added to this.options with util.extend() in ctor + // + // So, in theory, this.options should be enough. + // The only reason I can think of for this, is that precedence is important. + // TODO: make unit tests for this, to check if edgeOptions and defaultOptions are redundant + // return new Edge(properties, this.body, this.options, this.defaultOptions, this.edgeOptions) } diff --git a/lib/network/modules/components/Edge.js b/lib/network/modules/components/Edge.js index 851b866d..30138196 100644 --- a/lib/network/modules/components/Edge.js +++ b/lib/network/modules/components/Edge.js @@ -1,36 +1,33 @@ var util = require('../../../util'); var Label = require('./shared/Label').default; +var ComponentUtil = require('./shared/ComponentUtil').default; var CubicBezierEdge = require('./edges/CubicBezierEdge').default; var BezierEdgeDynamic = require('./edges/BezierEdgeDynamic').default; var BezierEdgeStatic = require('./edges/BezierEdgeStatic').default; var StraightEdge = require('./edges/StraightEdge').default; /** - * A edge connects two nodes - * @class Edge + * An edge connects two nodes and has a specific direction. */ class Edge { + /** - * - * @param {Object} options Object with options. Must contain - * At least options from and to. - * Available options: from (number), - * to (number), label (string, color (string), - * width (number), style (string), - * length (number), title (string) - * @param {Object} body A Network object, used to find and edge to - * nodes. - * @param {Object} globalOptions An object with default values for - * example for the color - * @param {Object} defaultOptions - * @param {Object} edgeOptions - * @constructor Edge + * @param {Object} options values specific to this edge, must contain at least 'from' and 'to' + * @param {Object} body shared state from Network instance + * @param {Object} globalOptions options from the EdgesHandler instance + * @param {Object} defaultOptions default options from the EdgeHandler instance. Value and reference are constant + * @param {Object} edgeOptions option values specific for edges. */ constructor(options, body, globalOptions, defaultOptions, edgeOptions) { if (body === undefined) { throw "No body provided"; } + + // Since globalOptions is constant in values as well as reference, + // Following needs to be done only once. + + this.options = util.bridgeObject(globalOptions); this.globalOptions = globalOptions; this.defaultOptions = defaultOptions; @@ -88,7 +85,8 @@ class Edge { options.value = parseFloat(options.value); } - this.choosify(options); + let pile = [options, this.options, this.edgeOptions, this.defaultOptions]; + this.chooser = ComponentUtil.choosify('edge', pile); // update label Module this.updateLabelModule(options); @@ -109,14 +107,16 @@ class Edge { return dataChanged; } + /** * * @param {Object} parentOptions * @param {Object} newOptions * @param {boolean} [allowDeletion=false] * @param {Object} [globalOptions={}] + * @param {boolean} [copyFromGlobals=false] */ - static parseOptions(parentOptions, newOptions, allowDeletion = false, globalOptions = {}) { + static parseOptions(parentOptions, newOptions, allowDeletion = false, globalOptions = {}, copyFromGlobals = false) { var fields = [ 'arrowStrikethrough', 'id', @@ -184,24 +184,44 @@ class Edge { // handle multiple input cases for color if (newOptions.color !== undefined && newOptions.color !== null) { - // make a copy of the parent object in case this is referring to the global one (due to object create once, then update) - parentOptions.color = util.deepExtend({}, parentOptions.color, true); - if (util.isString(newOptions.color)) { - parentOptions.color.color = newOptions.color; - parentOptions.color.highlight = newOptions.color; - parentOptions.color.hover = newOptions.color; - parentOptions.color.inherit = false; + let fromColor = newOptions.color; + let toColor = parentOptions.color; + + // If passed, fill in values from default options - required in the case of no prototype bridging + if (copyFromGlobals) { + util.deepExtend(toColor, globalOptions.color, false, allowDeletion); + } else { + // Clear local properties - need to do it like this in order to retain prototype bridges + for (var i in toColor) { + if (toColor.hasOwnProperty(i)) { + delete toColor[i]; + } + } + } + + if (util.isString(toColor)) { + toColor.color = toColor; + toColor.highlight = toColor; + toColor.hover = toColor; + toColor.inherit = false; + if (fromColor.opacity === undefined) { + toColor.opacity = 1.0; // set default + } } else { let colorsDefined = false; - if (newOptions.color.color !== undefined) {parentOptions.color.color = newOptions.color.color; colorsDefined = true;} - if (newOptions.color.highlight !== undefined) {parentOptions.color.highlight = newOptions.color.highlight; colorsDefined = true;} - if (newOptions.color.hover !== undefined) {parentOptions.color.hover = newOptions.color.hover; colorsDefined = true;} - if (newOptions.color.inherit !== undefined) {parentOptions.color.inherit = newOptions.color.inherit;} - if (newOptions.color.opacity !== undefined) {parentOptions.color.opacity = Math.min(1,Math.max(0,newOptions.color.opacity));} - - if (newOptions.color.inherit === undefined && colorsDefined === true) { - parentOptions.color.inherit = false; + if (fromColor.color !== undefined) {toColor.color = fromColor.color; colorsDefined = true;} + if (fromColor.highlight !== undefined) {toColor.highlight = fromColor.highlight; colorsDefined = true;} + if (fromColor.hover !== undefined) {toColor.hover = fromColor.hover; colorsDefined = true;} + if (fromColor.inherit !== undefined) {toColor.inherit = fromColor.inherit;} + if (fromColor.opacity !== undefined) {toColor.opacity = Math.min(1,Math.max(0,fromColor.opacity));} + + if (colorsDefined === true) { + toColor.inherit = false; + } else { + if (toColor.inherit === undefined) { + toColor.inherit = 'from'; // Set default + } } } } @@ -218,25 +238,6 @@ class Edge { } } - /** - * - * @param {Object} options - */ - choosify(options) { - this.chooser = true; - - let pile = [options, this.options, this.edgeOptions, this.defaultOptions]; - - let chosen = util.topMost(pile, 'chosen'); - if (typeof chosen === 'boolean') { - this.chooser = chosen; - } else if (typeof chosen === 'object') { - let chosenEdge = util.topMost(pile, ['chosen', 'edge']); - if ((typeof chosenEdge === 'boolean') || (typeof chosenEdge === 'function')) { - this.chooser = chosenEdge; - } - } - } /** * @@ -321,12 +322,12 @@ class Edge { * @param {Object} options */ updateLabelModule(options) { - this.labelModule.setOptions(this.options, true); + let pile = [options, this.edgeOptions, this.defaultOptions]; + this.labelModule.update(this.options, pile); + if (this.labelModule.baseSize !== undefined) { this.baseFontSize = this.labelModule.baseSize; } - this.labelModule.constrain(this.edgeOptions, options, this.defaultOptions); - this.labelModule.choosify(this.edgeOptions, options, this.defaultOptions); } /** diff --git a/lib/network/modules/components/Node.js b/lib/network/modules/components/Node.js index 69500c82..cd3c31b6 100644 --- a/lib/network/modules/components/Node.js +++ b/lib/network/modules/components/Node.js @@ -1,7 +1,7 @@ var util = require('../../../util'); var Label = require('./shared/Label').default; - +var ComponentUtil = require('./shared/ComponentUtil').default; var Box = require('./nodes/shapes/Box').default; var Circle = require('./nodes/shapes/Circle').default; var CircularImage = require('./nodes/shapes/CircularImage').default; @@ -146,7 +146,8 @@ class Node { // this transforms all shorthands into fully defined options Node.parseOptions(this.options, options, true, this.globalOptions); - this.choosify(options); + let pile = [options, this.options, this.defaultOptions]; + this.chooser = ComponentUtil.choosify('node', pile); this._load_images(); this.updateLabelModule(options); @@ -259,25 +260,6 @@ class Node { } } - /** - * - * @param {Object} options - */ - choosify(options) { - this.chooser = true; - - let pile = [options, this.options, this.defaultOptions]; - - let chosen = util.topMost(pile, 'chosen'); - if (typeof chosen === 'boolean') { - this.chooser = chosen; - } else if (typeof chosen === 'object') { - let chosenNode = util.topMost(pile, ['chosen', 'node']); - if ((typeof chosenNode === 'boolean') || (typeof chosenNode === 'function')) { - this.chooser = chosenNode; - } - } - } /** * @@ -335,14 +317,15 @@ class Node { if (this.options.label === undefined || this.options.label === null) { this.options.label = ''; } - this.labelModule.setOptions(this.options, true); + let pile = [options, this.nodeOptions, this.defaultOptions]; + this.labelModule.update(this.options, pile); + if (this.labelModule.baseSize !== undefined) { this.baseFontSize = this.labelModule.baseSize; } - this.labelModule.constrain(this.nodeOptions, options, this.defaultOptions); - this.labelModule.choosify(this.nodeOptions, options, this.defaultOptions); } + /** * * @param {string} currentShape diff --git a/lib/network/modules/components/shared/ComponentUtil.js b/lib/network/modules/components/shared/ComponentUtil.js new file mode 100644 index 00000000..e87bdf16 --- /dev/null +++ b/lib/network/modules/components/shared/ComponentUtil.js @@ -0,0 +1,54 @@ +let util = require("../../../../util"); + +/** + * Helper functions for components + * @class + */ +class ComponentUtil { + /** + * Determine values to use for (sub)options of 'chosen'. + * + * This option is either a boolean or an object whose values should be examined further. + * The relevant structures are: + * + * - chosen: + * - chosen: { subOption: } + * + * Where subOption is 'node', 'edge' or 'label'. + * + * The intention of this method appears to be to set a specific priority to the options; + * Since most properties are either bridged or merged into the local options objects, there + * is not much point in handling them separately. + * TODO: examine if 'most' in previous sentence can be replaced with 'all'. In that case, we + * should be able to get rid of this method. + * + * @param {String} subOption option within object 'chosen' to consider; either 'node', 'edge' or 'label' + * @param {Object} pile array of options objects to consider + * + * @return {boolean|function} value for passed subOption of 'chosen' to use + */ + static choosify(subOption, pile) { + // allowed values for subOption + let allowed = [ 'node', 'edge', 'label']; + let value = true; + + let chosen = util.topMost(pile, 'chosen'); + if (typeof chosen === 'boolean') { + value = chosen; + } else if (typeof chosen === 'object') { + if (allowed.indexOf(subOption) === -1 ) { + throw new Error('choosify: subOption \'' + subOption + '\' should be one of ' + + "'" + allowed.join("', '") + "'"); + } + + let chosenEdge = util.topMost(pile, ['chosen', subOption]); + if ((typeof chosenEdge === 'boolean') || (typeof chosenEdge === 'function')) { + value = chosenEdge; + } + } + + return value; + } +} + +export default ComponentUtil; diff --git a/lib/network/modules/components/shared/Label.js b/lib/network/modules/components/shared/Label.js index e37cb52b..47c344d4 100644 --- a/lib/network/modules/components/shared/Label.js +++ b/lib/network/modules/components/shared/Label.js @@ -1,4 +1,5 @@ let util = require('../../../../util'); +let ComponentUtil = require('./ComponentUtil').default; /** @@ -236,18 +237,14 @@ class Label { /** * Set the width and height constraints based on 'nearest' value - * - * @param {Object} elementOptions - * @param {Object} options - * @param {Object} defaultOptions + * @param {Array} pile array of option objects to consider + * @private */ - constrain(elementOptions, options, defaultOptions) { + constrain(pile) { this.fontOptions.constrainWidth = false; this.fontOptions.maxWdt = -1; this.fontOptions.minWdt = -1; - let pile = [options, elementOptions, defaultOptions]; - let widthConstraint = util.topMost(pile, 'widthConstraint'); if (typeof widthConstraint === 'number') { this.fontOptions.maxWdt = Number(widthConstraint); @@ -284,29 +281,20 @@ class Label { } } + /** - * Set the selected functions based on 'nearest' value + * Set options and update internal state * - * @param {Object} elementOptions - * @param {Object} options - * @param {Object} defaultOptions + * @param {Object} options options to set + * @param {Array} pile array of option objects to consider for option 'chosen' */ - choosify(elementOptions, options, defaultOptions) { - this.fontOptions.chooser = true; - - let pile = [options, elementOptions, defaultOptions]; - - let chosen = util.topMost(pile, 'chosen'); - if (typeof chosen === 'boolean') { - this.fontOptions.chooser = chosen; - } else if (typeof chosen === 'object') { - let chosenLabel = util.topMost(pile, ['chosen', 'label']); - if ((typeof chosenLabel === 'boolean') || (typeof chosenLabel === 'function')) { - this.fontOptions.chooser = chosenLabel; - } - } + update(options, pile) { + this.setOptions(options, true); + this.constrain(pile); + this.fontOptions.chooser = ComponentUtil.choosify('label', pile); } + /** * When margins are set in an element, adjust sizes is called to remove them * from the width/height constraints. This must be done prior to label sizing. diff --git a/lib/util.js b/lib/util.js index 7cabb6f3..a3309f7f 100644 --- a/lib/util.js +++ b/lib/util.js @@ -305,7 +305,7 @@ exports.selectiveNotDeepExtend = function (props, a, b, allowDeletion = false) { * @param {Object} b * @param {Boolean} [protoExtend] --> optional parameter. If true, the prototype values will also be extended. * (ie. the options objects that inherit from others will also get the inherited options) - * @param {Boolean} [allowDeletion] --> optional parameter. If true, the values of fields that are null will not deleted + * @param {Boolean} [allowDeletion] --> optional parameter. If true, the values of fields that are null will be deleted * @returns {Object} */ exports.deepExtend = function (a, b, protoExtend, allowDeletion) { diff --git a/test/Network.test.js b/test/Network.test.js index 082d8e65..a5d0bfd6 100644 --- a/test/Network.test.js +++ b/test/Network.test.js @@ -11,6 +11,34 @@ var Validator = require("./../lib/shared/Validator").default; // console.log(JSON.stringify(output, null, 2)); +/** + * Merge all options of object b into object b + * @param {Object} a + * @param {Object} b + * @return {Object} a + * + * Adapted merge() in dotparser.js + */ +function merge (a, b) { + if (!a) { + a = {}; + } + + if (b) { + for (var name in b) { + if (b.hasOwnProperty(name)) { + if (typeof b[name] === 'object') { + a[name] = merge(a[name], b[name]); + } else { + a[name] = b[name]; + } + } + } + } + return a; +} + + /** * Load legacy-style (i.e. not module) javascript files into the given context. */ @@ -19,11 +47,11 @@ function include(list, context) { list = [list]; } - for (var n in list) { - var path = list[n]; - var arr = [fs.readFileSync(path) + '']; - eval.apply(context, arr); - } + for (var n in list) { + var path = list[n]; + var arr = [fs.readFileSync(path) + '']; + eval.apply(context, arr); + } } @@ -35,7 +63,7 @@ function include(list, context) { * * For reference, this is the sample network of issue #1218 */ -function createSampleNetwork() { +function createSampleNetwork(options) { var NumInitialNodes = 8; var NumInitialEdges = 6; @@ -65,7 +93,7 @@ function createSampleNetwork() { edges: edges }; - var options = { + var defaultOptions = { layout: { randomSeed: 8 }, @@ -76,6 +104,8 @@ function createSampleNetwork() { } }; + options = merge(defaultOptions, options); + var network = new vis.Network(container, data, options); assertNumNodes(network, NumInitialNodes); @@ -94,7 +124,6 @@ function createSampleNetwork() { * a cluster is made of two nodes, each from one of the sub-networks. */ function createCluster(network) { - //console.log("clustering 1 and 11") var clusterOptionsByData = { joinCondition: function(node) { if (node.id == 1 || node.id == 11) return true; @@ -177,7 +206,7 @@ describe('Network', function () { //console.log("Creating edge 21 pointing to 1"); // '1' is part of the cluster so should // connect to cluster instead - data.edges.update([{from: 21, to: 1}]); + data.edges.update([{from: 21, to: 1}]); assertNumNodes(network, numNodes, numNodes - 2); // nodes unchanged numEdges += 2; // A new clustering edge is hiding a new edge assertNumEdges(network, numEdges, numEdges - 3); @@ -213,7 +242,7 @@ describe('Network', function () { numEdges -= 3; // clustering edge c1-12 and 2 edges of 12 gone assertNumEdges(network, numEdges, numEdges - 1); - //console.log("Unclustering c1"); + //console.log("Unclustering c1"); network.openCluster("c1"); numNodes -= 1; // cluster node removed, one less node assertNumNodes(network, numNodes, numNodes); // all are visible again @@ -243,22 +272,313 @@ describe('Network', function () { numEdges += 1; // 1 cluster edge expected assertNumEdges(network, numEdges, numEdges - 3); // 3 edges hidden - //console.log("removing node 2, which is inside the cluster"); + //console.log("removing node 2, which is inside the cluster"); data.nodes.remove(2); numNodes -= 1; // clustered node removed assertNumNodes(network, numNodes, numNodes - 2); // view doesn't change numEdges -= 2; // edges removed hidden in cluster assertNumEdges(network, numEdges, numEdges - 1); // view doesn't change - //console.log("Unclustering c1"); + //console.log("Unclustering c1"); network.openCluster("c1") numNodes -= 1; // cluster node gone assertNumNodes(network, numNodes, numNodes); // all visible numEdges -= 1; // cluster edge gone assertNumEdges(network, numEdges, numEdges); // all visible - //log(network); + //log(network); + }); + + +///////////////////////////////////////////////////// +// Local helper methods for Edge and Node testing +///////////////////////////////////////////////////// + + /** + * Simplify network creation for local tests + */ + function createNetwork(options) { + var [network, data, numNodes, numEdges] = createSampleNetwork(options); + + return network; + } + + + function firstNode(network) { + for (var id in network.body.nodes) { + return network.body.nodes[id]; + } + + return undefined; + } + + function firstEdge(network) { + for (var id in network.body.edges) { + return network.body.edges[id]; + } + + return undefined; + } + + + function checkChooserValues(item, chooser, labelChooser) { + if (chooser === 'function') { + assert.equal(typeof item.chooser, 'function'); + } else { + assert.equal(item.chooser, chooser); + } + + if (labelChooser === 'function') { + assert.equal(typeof item.labelModule.fontOptions.chooser, 'function'); + } else { + assert.equal(item.labelModule.fontOptions.chooser, labelChooser); + } + } + + +describe('Node', function () { + + /** + * NOTE: choosify tests of Node and Edge are parallel + * TODO: consolidate this is necessary + */ + it('properly handles choosify input', function () { + // check defaults + var options = {}; + var network = createNetwork(options); + checkChooserValues(firstNode(network), true, true); + + // There's no point in checking invalid values here; these are detected by the options parser + // and subsequently handled as missing input, thus assigned defaults + + // check various combinations of valid input + + options = {nodes: {chosen: false}}; + network = createNetwork(options); + checkChooserValues(firstNode(network), false, false); + + options = {nodes: {chosen: { node:true, label:false}}}; + network = createNetwork(options); + checkChooserValues(firstNode(network), true, false); + + options = {nodes: {chosen: { + node:true, + label: function(value, id, selected, hovering) {} + }}}; + network = createNetwork(options); + checkChooserValues(firstNode(network), true, 'function'); + + options = {nodes: {chosen: { + node: function(value, id, selected, hovering) {}, + label:false, + }}}; + network = createNetwork(options); + checkChooserValues(firstNode(network), 'function', false); + }); +}); // Node + + +describe('Edge', function () { + + /** + * NOTE: choosify tests of Node and Edge are parallel + * TODO: consolidate this is necessary + */ + it('properly handles choosify input', function () { + // check defaults + var options = {}; + var network = createNetwork(options); + checkChooserValues(firstEdge(network), true, true); + + // There's no point in checking invalid values here; these are detected by the options parser + // and subsequently handled as missing input, thus assigned defaults + + // check various combinations of valid input + + options = {edges: {chosen: false}}; + network = createNetwork(options); + checkChooserValues(firstEdge(network), false, false); + + options = {edges: {chosen: { edge:true, label:false}}}; + network = createNetwork(options); + checkChooserValues(firstEdge(network), true, false); + + options = {edges: {chosen: { + edge:true, + label: function(value, id, selected, hovering) {} + }}}; + network = createNetwork(options); + checkChooserValues(firstEdge(network), true, 'function'); + + options = {edges: {chosen: { + edge: function(value, id, selected, hovering) {}, + label:false, + }}}; + network = createNetwork(options); + checkChooserValues(firstEdge(network), 'function', false); + }); + + + /** + * Support routine for next unit test + */ + function createDataforColorChange() { + var nodes = new vis.DataSet([ + {id: 1, label: 'Node 1' }, // group:'Group1'}, + {id: 2, label: 'Node 2', group:'Group2'}, + {id: 3, label: 'Node 3'}, + ]); + + // create an array with edges + var edges = new vis.DataSet([ + {id: 1, from: 1, to: 2}, + {id: 2, from: 1, to: 3, color: { inherit: 'to'}}, + {id: 3, from: 3, to: 3, color: { color: '#00FF00'}}, + {id: 4, from: 2, to: 3, color: { inherit: 'from'}}, + ]); + + + var data = { + nodes: nodes, + edges: edges + }; + + return data; + } + + + /** + * Unit test for fix of #3350 + * + * The issue is that changing color options is not registered in the nodes. + * We test the updates the color options in the general edges options here. + */ + it('sets inherit color option for edges on call to Network.setOptions()', function () { + var container = document.getElementById('mynetwork'); + var data = createDataforColorChange(); + + var options = { + "edges" : { "color" : { "inherit" : "to" } }, + }; + + // Test passing options on init. + var network = new vis.Network(container, data, options); + var edges = network.body.edges; + assert.equal(edges[1].options.color.inherit, 'to'); // new default + assert.equal(edges[2].options.color.inherit, 'to'); // set in edge + assert.equal(edges[3].options.color.inherit, false); // has explicit color + assert.equal(edges[4].options.color.inherit, 'from'); // set in edge + + // Sanity check: colors should still be defaults + assert.equal(edges[1].options.color.color, network.edgesHandler.options.color.color); + + // Override the color value - inherit returns to default + network.setOptions({ edges:{color: {}}}); + assert.equal(edges[1].options.color.inherit, 'from'); // default + assert.equal(edges[2].options.color.inherit, 'to'); // set in edge + assert.equal(edges[3].options.color.inherit, false); // has explicit color + assert.equal(edges[4].options.color.inherit, 'from'); // set in edge + + // Check no options + network = new vis.Network(container, data, {}); + edges = network.body.edges; + assert.equal(edges[1].options.color.inherit, 'from'); // default + assert.equal(edges[2].options.color.inherit, 'to'); // set in edge + assert.equal(edges[3].options.color.inherit, false); // has explicit color + assert.equal(edges[4].options.color.inherit, 'from'); // set in edge + + // Set new value + network.setOptions(options); + assert.equal(edges[1].options.color.inherit, 'to'); + assert.equal(edges[2].options.color.inherit, 'to'); // set in edge + assert.equal(edges[3].options.color.inherit, false); // has explicit color + assert.equal(edges[4].options.color.inherit, 'from'); // set in edge + +/* + // Useful for debugging + console.log('==================================='); + console.log(edges[1].options.color); + console.log(edges[1].options.color.__proto__); + console.log(edges[1].options); + console.log(edges[1].options.__proto__); + console.log(edges[1].edgeOptions); +*/ + }); + + + it('sets inherit color option for specific edge', function () { + var container = document.getElementById('mynetwork'); + var data = createDataforColorChange(); + + // Check no options + var network = new vis.Network(container, data, {}); + var edges = network.body.edges; + assert.equal(edges[1].options.color.inherit, 'from'); // default + assert.equal(edges[2].options.color.inherit, 'to'); // set in edge + assert.equal(edges[3].options.color.inherit, false); // has explicit color + assert.equal(edges[4].options.color.inherit, 'from'); // set in edge + + // Set new value + data.edges.update({id: 1, color: { inherit: 'to'}}); + assert.equal(edges[1].options.color.inherit, 'to'); // Only this changed + assert.equal(edges[2].options.color.inherit, 'to'); + assert.equal(edges[3].options.color.inherit, false); // has explicit color + assert.equal(edges[4].options.color.inherit, 'from'); + }); + + + /** + * Perhaps TODO: add unit test for passing string value for color option + */ + it('sets color value for edges on call to Network.setOptions()', function () { + var container = document.getElementById('mynetwork'); + var data = createDataforColorChange(); + + var defaultColor = '#848484'; // From defaults + var color = '#FF0000'; + + var options = { + "edges" : { "color" : { "color" : color } }, + }; + + // Test passing options on init. + var network = new vis.Network(container, data, options); + var edges = network.body.edges; + assert.equal(edges[1].options.color.color, color); + assert.equal(edges[1].options.color.inherit, false); // Explicit color, so no inherit + assert.equal(edges[2].options.color.color, color); + assert.equal(edges[2].options.color.inherit, 'to'); // Local value overrides! (bug according to docs) + assert.notEqual(edges[3].options.color.color, color); // Has own value + assert.equal(edges[3].options.color.inherit, false); // Explicit color, so no inherit + assert.equal(edges[4].options.color.color, color); + + // Override the color value - all should return to default + network.setOptions({ edges:{color: {}}}); + assert.equal(edges[1].options.color.color, defaultColor); + assert.equal(edges[1].options.color.inherit, 'from'); + assert.equal(edges[2].options.color.color, defaultColor); + assert.notEqual(edges[3].options.color.color, color); // Has own value + assert.equal(edges[4].options.color.color, defaultColor); + + // Check no options + network = new vis.Network(container, data, {}); + edges = network.body.edges; + // At this point, color has not changed yet + assert.equal(edges[1].options.color.color, defaultColor); + assert.equal(edges[1].options.color.highlight, defaultColor); + assert.equal(edges[1].options.color.inherit, 'from'); + assert.notEqual(edges[3].options.color.color, color); // Has own value + + // Set new Value + network.setOptions(options); + assert.equal(edges[1].options.color.color, color); + assert.equal(edges[1].options.color.highlight, defaultColor); // Should not be changed + assert.equal(edges[1].options.color.inherit, false); // Explicit color, so no inherit + assert.equal(edges[2].options.color.color, color); + assert.notEqual(edges[3].options.color.color, color); // Has own value + assert.equal(edges[4].options.color.color, color); }); +}); // Edge describe('on node.js', function () {