var fs = require('fs'); var assert = require('assert'); var vis = require('../dist/vis'); var Network = vis.network; var jsdom_global = require('jsdom-global'); var stdout = require('test-console').stdout; var Validator = require("./../lib/shared/Validator").default; //var {printStyle} = require('./../lib/shared/Validator'); // Useful during debugging: // 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. */ function include(list, context) { if (!(list instanceof Array)) { list = [list]; } for (var n in list) { var path = list[n]; var arr = [fs.readFileSync(path) + '']; eval.apply(context, arr); } } /** * Defined network consists of two sub-networks: * * - 1-2-3-4 * - 11-12-13-14 * * For reference, this is the sample network of issue #1218 */ function createSampleNetwork(options) { var NumInitialNodes = 8; var NumInitialEdges = 6; var nodes = new vis.DataSet([ {id: 1, label: '1'}, {id: 2, label: '2'}, {id: 3, label: '3'}, {id: 4, label: '4'}, {id: 11, label: '11'}, {id: 12, label: '12'}, {id: 13, label: '13'}, {id: 14, label: '14'}, ]); var edges = new vis.DataSet([ {from: 1, to: 2}, {from: 2, to: 3}, {from: 3, to: 4}, {from: 11, to: 12}, {from: 12, to: 13}, {from: 13, to: 14}, ]); // create a network var container = document.getElementById('mynetwork'); var data = { nodes: nodes, edges: edges }; var defaultOptions = { layout: { randomSeed: 8 }, edges: { smooth: { type: 'continuous' // avoid dynamic here, it adds extra hidden nodes } } }; options = merge(defaultOptions, options); var network = new vis.Network(container, data, options); assertNumNodes(network, NumInitialNodes); assertNumEdges(network, NumInitialEdges); return [network, data, NumInitialNodes, NumInitialEdges]; }; /** * Create a cluster for the dynamic data change cases. * * Works on the network created by createSampleNetwork(). * * This is actually a pathological case; there are two separate sub-networks and * a cluster is made of two nodes, each from one of the sub-networks. */ function createCluster(network) { var clusterOptionsByData = { joinCondition: function(node) { if (node.id == 1 || node.id == 11) return true; return false; }, clusterNodeProperties: {id:"c1", label:'c1'} } network.cluster(clusterOptionsByData); } /** * Display node/edge state, useful during debugging */ function log(network) { console.log(Object.keys(network.body.nodes)); console.log(network.body.nodeIndices); console.log(Object.keys(network.body.edges)); console.log(network.body.edgeIndices); }; /** * Note that only the node and edges counts are asserted. * This might be done more thoroughly by explicitly checking the id's */ function assertNumNodes(network, expectedPresent, expectedVisible) { if (expectedVisible === undefined) expectedVisible = expectedPresent; assert.equal(Object.keys(network.body.nodes).length, expectedPresent); assert.equal(network.body.nodeIndices.length, expectedVisible); }; /** * Comment at assertNumNodes() also applies. */ function assertNumEdges(network, expectedPresent, expectedVisible) { if (expectedVisible === undefined) expectedVisible = expectedPresent; assert.equal(Object.keys(network.body.edges).length, expectedPresent); assert.equal(network.body.edgeIndices.length, expectedVisible); }; describe('Network', function () { before(function() { this.jsdom_global = jsdom_global( "
", { skipWindowCheck: true} ); this.container = document.getElementById('mynetwork'); }); after(function() { this.jsdom_global(); }); ///////////////////////////////////////////////////// // 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('Clustering', function () { /** * Check on fix for #1218 */ it('connects a new edge to a clustering node instead of the clustered node', function () { var [network, data, numNodes, numEdges] = createSampleNetwork(); createCluster(network); numNodes += 1; // A clustering node is now hiding two nodes assertNumNodes(network, numNodes, numNodes - 2); numEdges += 2; // Two clustering edges now hide two edges assertNumEdges(network, numEdges, numEdges - 2); //console.log("Creating node 21") data.nodes.update([{id: 21, label: '21'}]); numNodes += 1; // New unconnected node added assertNumNodes(network, numNodes, numNodes - 2); assertNumEdges(network, numEdges, numEdges - 2); // edges unchanged //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}]); assertNumNodes(network, numNodes, numNodes - 2); // nodes unchanged numEdges += 2; // A new clustering edge is hiding a new edge assertNumEdges(network, numEdges, numEdges - 3); }); /** * Check on fix for #1315 */ it('can uncluster a clustered node when a node is removed that has an edge to that cluster', function () { // NOTE: this block is same as previous test var [network, data, numNodes, numEdges] = createSampleNetwork(); createCluster(network); numNodes += 1; // A clustering node is now hiding two nodes assertNumNodes(network, numNodes, numNodes - 2); numEdges += 2; // Two clustering edges now hide two edges assertNumEdges(network, numEdges, numEdges - 2); // End block same as previous test //console.log("removing 12"); data.nodes.remove(12); // NOTE: // At this particular point, there are still the two edges for node 12 in the edges DataSet. // If you want to do the delete correctly, these should also be deleted explictly from // the edges DataSet. In the Network instance, however, this.body.nodes and this.body.edges // should be correct, with the edges of 12 all cleared out. // 12 was connected to 11, which is clustered numNodes -= 1; // 12 removed, one less node assertNumNodes(network, numNodes, numNodes - 2); numEdges -= 3; // clustering edge c1-12 and 2 edges of 12 gone assertNumEdges(network, numEdges, numEdges - 1); //console.log("Unclustering c1"); network.openCluster("c1"); numNodes -= 1; // cluster node removed, one less node assertNumNodes(network, numNodes, numNodes); // all are visible again numEdges -= 1; // clustering edge gone, regular edge visible assertNumEdges(network, numEdges, numEdges); // all are visible again }); /** * Check on fix for #1291 */ it('can remove a node inside a cluster and then open that cluster', function () { var [network, data, numNodes, numEdges] = createSampleNetwork(); var clusterOptionsByData = { joinCondition: function(node) { if (node.id == 1 || node.id == 2 || node.id == 3) return true; return false; }, clusterNodeProperties: {id:"c1", label:'c1'} } network.cluster(clusterOptionsByData); numNodes += 1; // new cluster node assertNumNodes(network, numNodes, numNodes - 3); // 3 clustered nodes numEdges += 1; // 1 cluster edge expected assertNumEdges(network, numEdges, numEdges - 3); // 3 edges hidden //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"); 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); }); /** * Helper function for setting up a graph for testing clusterByEdgeCount() */ function createOutlierGraph() { // create an array with nodes var nodes = new vis.DataSet([ {id: 1, label: '1', group:'Group1'}, {id: 2, label: '2', group:'Group2'}, {id: 3, label: '3', group:'Group3'}, {id: 4, label: '4', group:'Group4'}, {id: 5, label: '5', group:'Group4'} ]); // create an array with edges var edges = new vis.DataSet([ {from: 1, to: 3}, {from: 1, to: 2}, {from: 2, to: 4}, {from: 2, to: 5} ]); // create a network var container = document.getElementById('mynetwork'); var data = { nodes: nodes, edges: edges }; var options = { "groups" : { "Group1" : { level:1 }, "Group2" : { level:2 }, "Group3" : { level:3 }, "Group4" : { level:4 } } }; var network = new vis.Network (container, data, options); return network; } /** * Check on fix for #3367 */ it('correctly handles edge cases of clusterByEdgeCount()', function () { /** * Collect clustered id's * * All node id's in clustering nodes are collected into an array; * The results for all clusters are returned as an array. * * Ordering of output depends on the order in which they are defined * within nodes.clustering; strictly, speaking, the array and its items * are collections, so order should not matter. */ var collectClusters = function(network) { var clusters = []; for(var n in network.body.nodes) { var node = network.body.nodes[n]; if (node.containedNodes === undefined) continue; // clusters only // Collect id's of nodes in the cluster var nodes = []; for(var m in node.containedNodes) { nodes.push(m); } clusters.push(nodes); } return clusters; } /** * Compare cluster data * * params are arrays of arrays of id's, e.g: * * [[1,3],[2,4]] * * Item arrays are the id's of nodes in a given cluster * * This comparison depends on the ordering; better * would be to treat the items and values as collections. */ var compareClusterInfo = function(recieved, expected) { if (recieved.length !== expected.length) return false; for (var n = 0; n < recieved.length; ++n) { var itema = recieved[n]; var itemb = expected[n]; if (itema.length !== itemb.length) return false; for (var m = 0; m < itema.length; ++m) { if (itema[m] != itemb[m]) return false; // != because values can be string or number } } return true; } var assertJoinCondition = function(joinCondition, expected) { var network = createOutlierGraph(); network.clusterOutliers({joinCondition: joinCondition}); var recieved = collectClusters(network); //console.log(recieved); assert(compareClusterInfo(recieved, expected), 'recieved:' + JSON.stringify(recieved) + '; ' + 'expected: ' + JSON.stringify(expected)); }; // Should cluster 3,4,5: var joinAll_ = function(n) { return true ; } // Should cluster none: var joinNone_ = function(n) { return false ; } // Should cluster 4 & 5: var joinLevel_ = function(n) { return n.level > 3 ; } assertJoinCondition(undefined , [[1,3],[2,4,5]]); assertJoinCondition(null , [[1,3],[2,4,5]]); assertJoinCondition(joinNone_ , []); assertJoinCondition(joinLevel_ , [[2,4,5]]); }); }); // Clustering describe('on node.js', function () { it('should be running', function () { assert(this.container !== null, 'Container div not found'); // The following should now just plain succeed var [network, data] = createSampleNetwork(); assert.equal(Object.keys(network.body.nodes).length, 8); assert.equal(Object.keys(network.body.edges).length, 6); }); describe('runs example ', function () { function loadExample(path, noPhysics) { include(path, this); var container = document.getElementById('mynetwork'); // create a network var data = { nodes: new vis.DataSet(nodes), edges: new vis.DataSet(edges) }; if (noPhysics) { // Avoid excessive processor time due to load. // We're just interested that the load itself is good options.physics = false; } var network = new vis.Network(container, data, options); return network; }; it('basicUsage', function () { var network = loadExample('./test/network/basicUsage.js'); //console.log(Object.keys(network.body.edges)); // Count in following also contains the helper nodes for dynamic edges assert.equal(Object.keys(network.body.nodes).length, 10); assert.equal(Object.keys(network.body.edges).length, 5); }); it('WorlCup2014', function () { // This is a huge example (which is why it's tested here!), so it takes a long time to load. this.timeout(10000); var network = loadExample('./examples/network/datasources/WorldCup2014.js', true); // Count in following also contains the helper nodes for dynamic edges assert.equal(Object.keys(network.body.nodes).length, 9964); assert.equal(Object.keys(network.body.edges).length, 9228); }); // This actually failed to load, added for this reason it('disassemblerExample', function () { var network = loadExample('./examples/network/exampleApplications/disassemblerExample.js'); // console.log(Object.keys(network.body.nodes)); // console.log(Object.keys(network.body.edges)); // Count in following also contains the helper nodes for dynamic edges assert.equal(Object.keys(network.body.nodes).length, 9); assert.equal(Object.keys(network.body.edges).length, 14 - 3); // NB 3 edges in data not displayed }); }); // runs example }); // on node.js }); // Network