@labshare/data-structures
Advanced tools
Comparing version 1.0.0 to 1.1.5
{ | ||
"name": "@labshare/data-structures", | ||
"main": "./src/index.ts", | ||
"version": "1.0.0", | ||
"version": "1.1.5", | ||
"description": "", | ||
@@ -17,7 +17,7 @@ "contributors": "https://github.com/LabShare/data-structures/graphs/contributors", | ||
"test": "karma start ./test/karma.conf.js", | ||
"coverage": "karma start ./test/karma.conf.js", | ||
"test:watch": "karma start ./test/karma.conf.js --single-run false", | ||
"coverage": "npm run test", | ||
"test:watch": "npm run test -- --single-run false", | ||
"lint": "tslint -p tsconfig.json -c tslint.json", | ||
"lint:fix": "tslint --fix -p tsconfig.json -c tslint.json", | ||
"build": "tsc", | ||
"lint:fix": "npm run lint -- --fix", | ||
"build": "tsc --outDir build/", | ||
"commitmsg": "commitlint -e $GIT_PARAMS", | ||
@@ -57,3 +57,9 @@ "semantic-release": "semantic-release", | ||
"tslint": "^5.9.1" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"release": { | ||
"extends": "@labshare/semantic-release-config" | ||
} | ||
} |
@@ -271,2 +271,112 @@ import {Collection} from './collection' | ||
describe('getOne', () => { | ||
it('should return a value from the Collection', () => { | ||
let c = Collection.fromArray([ | ||
'firstItem' | ||
]); | ||
let item; | ||
expect(() => item = c.getOne()).not.toThrow(); | ||
expect(item).toEqual('firstItem'); | ||
c = Collection.fromArray([]); | ||
expect(() => item = c.getOne()).not.toThrow(); | ||
expect(item).toBeUndefined(); | ||
c = Collection.fromArray([ | ||
'firstItem', | ||
'secondItem', | ||
'thirdItem' | ||
]); | ||
expect(() => item = c.getOne()).not.toThrow(); | ||
/* must be any of the three */ | ||
expect(['firstItem', 'secondItem', 'thirdItem']).toContain(item); | ||
}) | ||
}) | ||
describe('isEmpty', () => { | ||
it ('should return true for empty collection', () => { | ||
let c = Collection.fromArray([]); | ||
expect(c.isEmpty()).toBeTruthy(); | ||
c = Collection.fromArray([ | ||
'firstItem' | ||
]); | ||
expect(c.isEmpty()).toBeFalsy(); | ||
c = Collection.fromArray([ | ||
'firstItem', | ||
'secondItem' | ||
]); | ||
expect(c.isEmpty()).toBeFalsy(); | ||
}) | ||
}) | ||
describe('difference', () => { | ||
it('should allow difference from another collection', () => { | ||
let c = Collection.fromArray([5, 7, 8, 9, 12]), | ||
c2 = Collection.fromArray([1, 2, 3, 4, 9, 6, 7]), | ||
c3; | ||
expect(() => c3 = c.difference(c2)).not.toThrow(); | ||
expect(c3.size()).toBe(3); | ||
expect(c3.has(7)).toBeFalsy(); | ||
expect(c3.has(9)).toBeFalsy(); | ||
expect(c3.has(5)).toBeTruthy(); | ||
expect(c3.has(8)).toBeTruthy(); | ||
expect(c3.has(12)).toBeTruthy(); | ||
}) | ||
it('should return self reference when difference equals self', () => { | ||
let c = Collection.fromArray([5, 7, 8, 9, 12]), | ||
c2 = Collection.fromArray([89, 90, 8172, 123]), | ||
c3; | ||
expect(() => c3 = c.difference(c2)).not.toThrow(); | ||
expect(c3).toBe(c); | ||
c2 = Collection.fromArray([5]); | ||
expect(() => c3 = c.difference(c2)).not.toThrow(); | ||
expect(c3).not.toBe(c); | ||
}) | ||
}) | ||
describe('iterators', () => { | ||
it('should contain iterator for "for ... of" loops', () => { | ||
let c = Collection.fromArray([1, 2, 3, 4]); | ||
let str = ""; | ||
for (let item of c) { | ||
str += `${item};`; | ||
} | ||
expect(str).toBe('2;1;3;4;') | ||
}) | ||
it('should contain forEach method', () => { | ||
const arr = [ | ||
'abc', | ||
'def', | ||
'ghi', | ||
'jkl' | ||
]; | ||
let c = Collection.fromArray(arr); | ||
c.forEach(item => { | ||
expect(c.has(item)).toBeTruthy() | ||
expect(arr.find((i) => i === item)).toEqual(item); | ||
arr.splice(arr.indexOf(item), 1); | ||
}) | ||
expect(arr.length).toBe(0); | ||
}) | ||
it('should contain filter method', () => { | ||
const arr = ['Walked', 'Talked', 'Faked', 'Made', 'Did'].map( | ||
i => ({id: i}) | ||
); | ||
let c = Collection.fromArray(arr); | ||
let c2 = c.filter(word => { | ||
return word.id.match(/ed$/) | ||
}); | ||
expect(c2.has({id: 'Walked'})).toBeTruthy(); | ||
expect(c2.has({id: 'Talked'})).toBeTruthy(); | ||
expect(c2.has({id: 'Faked'})).toBeTruthy(); | ||
expect(c2.has({id: 'Made'})).toBeFalsy(); | ||
expect(c2.has({id: 'Did'})).toBeFalsy(); | ||
}) | ||
}) | ||
}) |
@@ -168,2 +168,6 @@ import {SSet} from '../sset/sset'; | ||
public isEmpty(): any { | ||
return this.size() === 0; | ||
} | ||
public remove(item) { | ||
@@ -195,2 +199,12 @@ const newSet = this.internal.set.remove(item); | ||
public forEach(fn) { | ||
this.internal.set.forEach(fn); | ||
} | ||
public filter(fn) { | ||
return new Collection({ | ||
set: this.internal.set.filter(fn), | ||
}); | ||
} | ||
public size() { | ||
@@ -197,0 +211,0 @@ return this.internal.set.size(); |
@@ -89,2 +89,11 @@ import {Graph} from './graph' | ||
}) | ||
it('should create empty graph', () => { | ||
let graph; | ||
expect(() => graph = Graph.fromEmpty()).not.toThrow(); | ||
expect(graph.toObject()).toEqual({ | ||
edges: [], | ||
nodes: [] | ||
}) | ||
}) | ||
}) | ||
@@ -396,2 +405,597 @@ | ||
describe('nodes addition', () => { | ||
it('should throw error if node already exists', () => { | ||
let graph = Graph.fromObject({ | ||
edges: [], | ||
nodes: [ | ||
{ | ||
name: 'Colin', | ||
lastName: 'Creevey', | ||
id: 'the-photographer' | ||
} | ||
] | ||
}); | ||
expect(() => graph.nodes.add({ | ||
name: 'Colin', | ||
lastName: 'Creevey', | ||
id: 'the-photographer' | ||
})).toThrowError( | ||
`Could not add Node to Graph: Node already exists` | ||
) | ||
}) | ||
it('should add node to empty graph and preserve edges', () => { | ||
let graph = Graph.fromEmpty(); | ||
expect(() => graph = graph.nodes.add({ | ||
name: 'Mundungus', | ||
lastName: 'Fletcher', | ||
id: 'mundungus' | ||
})).not.toThrow() | ||
expect(graph.toObject()).toEqual({ | ||
edges: [], | ||
nodes: [{ | ||
name: 'Mundungus', | ||
lastName: 'Fletcher', | ||
id: 'mundungus' | ||
}] | ||
}) | ||
expect(() => graph = Graph.fromObject({ | ||
edges: [{ | ||
from: 'mundungus', | ||
to: 'harry', | ||
id: 'm-h', | ||
labels: ['TELL_ON'] | ||
}], | ||
nodes: [{ | ||
id: 'mundungus' | ||
}] | ||
})).not.toThrow() | ||
expect(() => graph = graph.nodes.add({ | ||
id: 'harry' | ||
})).not.toThrow(); | ||
expect(graph.toObject()).toEqual({ | ||
edges: [{ | ||
from: 'mundungus', | ||
to: 'harry', | ||
id: 'm-h', | ||
labels: ['TELL_ON'] | ||
}], | ||
nodes: [{ | ||
id: 'mundungus' | ||
}, { | ||
id: 'harry' | ||
}] | ||
}) | ||
}) | ||
}) | ||
describe('nodes hasId', () => { | ||
it('should return true when it exists', () => { | ||
let g = Graph.fromObject({ | ||
edges: [], | ||
nodes: [{ | ||
id: 'node0' | ||
}] | ||
}); | ||
expect(g.nodes.hasId('node0')).toEqual(true); | ||
expect(g.nodes.hasId('another-node')).toEqual(false); | ||
}) | ||
}) | ||
describe('nodes update', () => { | ||
it('should throw error when node does not exist', () => { | ||
let g = Graph.fromEmpty(); | ||
expect(() => g = g.nodes.update({ | ||
id: 'inexistentId' | ||
}, { | ||
id: 'newId' | ||
})).toThrowError( | ||
`Could not update Node from Graph: Node does not exist` | ||
); | ||
g = Graph.fromObject({ | ||
edges: [], | ||
nodes: [{ | ||
id: '1234' | ||
}] | ||
}) | ||
expect(() => g = g.nodes.update({ | ||
id: 'inexistentId' | ||
}, { | ||
id: 'newId' | ||
})).toThrowError( | ||
`Could not update Node from Graph: Node does not exist` | ||
); | ||
}) | ||
it('should update graph when node exists', () => { | ||
let g = Graph.fromObject({ | ||
edges: [], | ||
nodes: [{ | ||
id: '1234' | ||
}, { | ||
id: 'another-node' | ||
}] | ||
}) | ||
expect(() => g = g.nodes.update({ | ||
id: '1234' | ||
}, { | ||
id: 'newId' | ||
})).not.toThrow(); | ||
expect(g.toObject().nodes).toContain({ | ||
id: 'newId' | ||
}) | ||
expect(g.toObject().nodes).toContain({ | ||
id: 'another-node' | ||
}) | ||
}) | ||
}) | ||
describe('nodes oneReachedById', () => { | ||
it('should return one node reached by given node id', () => { | ||
let g = Graph.fromObject({ | ||
edges: [{ | ||
from: 'a', | ||
to: 'b', | ||
id: 'a-b' | ||
}], | ||
nodes: [{ | ||
id: 'a' | ||
}, { | ||
id: 'b' | ||
}] | ||
}); | ||
expect(g.nodes.oneReachedById('a')).toEqual({ | ||
id: 'b' | ||
}) | ||
}) | ||
it('should return undefined when there are no nodes reached by id', () => { | ||
let g = Graph.fromObject({ | ||
edges: [{ | ||
from: 'a', | ||
to: 'b', | ||
id: 'a-b' | ||
}], | ||
nodes: [{ | ||
id: 'a' | ||
}, { | ||
id: 'b' | ||
}] | ||
}); | ||
expect(g.nodes.oneReachedById('inexistent')).toBeUndefined() | ||
}) | ||
}) | ||
describe('nodes find', () => { | ||
let g; | ||
beforeEach(() => { | ||
g = Graph.fromObject({ | ||
edges: [], | ||
nodes: [{ | ||
id: 'n1' | ||
}, { | ||
id: 'n2', | ||
name: 'Node 2' | ||
}, { | ||
id: 1 | ||
}] | ||
}); | ||
}) | ||
it('should find node by id', () => { | ||
expect(g.nodes.find({ | ||
id: 'n1' | ||
}).toObject().nodes).toEqual([{ | ||
id: 'n1' | ||
}]); | ||
expect(g.nodes.find({ | ||
id: '1' | ||
}).toObject().nodes).toEqual([]) | ||
expect(g.nodes.find({ | ||
id: 1 | ||
}).toObject().nodes).toEqual([{ | ||
id: 1 | ||
}]) | ||
}) | ||
it('should not fail for inexistent key', () => { | ||
let result; | ||
expect(() => result = g.nodes.find({ | ||
anyKey: 'anyValue' | ||
})).not.toThrow(); | ||
expect(result.toObject().nodes).toEqual([]); | ||
}) | ||
it('should not fail for keys that exist in only one object', () => { | ||
let result; | ||
expect(() => result = g.nodes.find({ | ||
name: 'Node 2' | ||
})).not.toThrow(); | ||
expect(result.toObject().nodes).toEqual([{ | ||
id: 'n2', | ||
name: 'Node 2' | ||
}]); | ||
}) | ||
}) | ||
describe('nodes filter', () => { | ||
it('should filter the nodes', () => { | ||
let g = Graph.fromObject({ | ||
edges: [], | ||
nodes: [{ | ||
name: 'California', | ||
country: 'USA' | ||
}, { | ||
name: 'DC', | ||
country: 'USA' | ||
}, { | ||
name: 'Sao Paulo', | ||
country: 'Brazil' | ||
}, { | ||
name: 'Missouri', | ||
country: 'USA' | ||
}, { | ||
name: 'Maranhao', | ||
country: 'Brazil' | ||
}] | ||
}) | ||
let fromUS = g.nodes.filter(n => n.country === 'USA') | ||
expect(fromUS.toObject().nodes).toContain({ | ||
name: 'California', | ||
country: 'USA' | ||
}) | ||
expect(fromUS.toObject().nodes).toContain({ | ||
name: 'DC', | ||
country: 'USA' | ||
}) | ||
expect(fromUS.toObject().nodes).toContain({ | ||
name: 'Missouri', | ||
country: 'USA' | ||
}) | ||
expect(fromUS.toObject().nodes).not.toContain({ | ||
name: 'Sao Paulo', | ||
country: 'Brazil' | ||
}) | ||
expect(fromUS.toObject().nodes).not.toContain({ | ||
name: 'Maranhao', | ||
country: 'Brazil' | ||
}) | ||
let startsWithM = g.nodes.filter(n => n.name[0] === 'M'); | ||
expect(startsWithM.toObject().nodes).not.toContain({ | ||
name: 'California', | ||
country: 'USA' | ||
}) | ||
expect(startsWithM.toObject().nodes).not.toContain({ | ||
name: 'DC', | ||
country: 'USA' | ||
}) | ||
expect(startsWithM.toObject().nodes).toContain({ | ||
name: 'Missouri', | ||
country: 'USA' | ||
}) | ||
expect(startsWithM.toObject().nodes).not.toContain({ | ||
name: 'Sao Paulo', | ||
country: 'Brazil' | ||
}) | ||
expect(startsWithM.toObject().nodes).toContain({ | ||
name: 'Maranhao', | ||
country: 'Brazil' | ||
}) | ||
}) | ||
}) | ||
describe('nodes forEach', () => { | ||
it('should iterate on nodes', () => { | ||
let arr = [ | ||
{id: 'abc'}, | ||
{id: 'def'}, | ||
{id: 'ghi'}, | ||
{id: 'jkl'} | ||
]; | ||
let c = Graph.fromObject({ | ||
nodes: arr, | ||
edges: [] | ||
}); | ||
c.nodes.forEach(item => { | ||
let index = arr.findIndex(i => i.id === item.id); | ||
arr.splice(arr.indexOf(item), 1); | ||
}) | ||
expect(arr.length).toBe(0); | ||
}) | ||
}) | ||
describe('nodes has', () => { | ||
it('should return true when node exists', () => { | ||
let g = Graph.fromObject({ | ||
edges: [], | ||
nodes: [{ | ||
name: 'California', | ||
country: 'USA' | ||
}, { | ||
name: 'DC', | ||
country: 'USA' | ||
}, { | ||
name: 'Sao Paulo', | ||
country: 'Brazil' | ||
}, { | ||
name: 'Missouri', | ||
country: 'USA' | ||
}, { | ||
name: 'Maranhao', | ||
country: 'Brazil' | ||
}] | ||
}) | ||
expect(g.nodes.has({ | ||
name: 'California', | ||
country: 'USA' | ||
})).toBe(true); | ||
expect(g.nodes.has({ | ||
name: 'Florida', | ||
country: 'USA' | ||
})).toBe(false); | ||
}) | ||
}) | ||
describe('nodes isEmpty', () => { | ||
it('should return true when empty', () => { | ||
let g = Graph.fromObject({ | ||
edges: [], | ||
nodes: [{ | ||
name: 'California', | ||
country: 'USA' | ||
}, { | ||
name: 'DC', | ||
country: 'USA' | ||
}] | ||
}) | ||
expect(g.nodes.isEmpty()).toBe(false); | ||
g = Graph.fromEmpty(); | ||
expect(g.nodes.isEmpty()).toBe(true); | ||
}) | ||
}) | ||
describe('nodes getStartingNodes', () => { | ||
it('should return nodes without incoming connections', () => { | ||
let g = Graph.fromObject({ | ||
edges: [{ | ||
from: 1, | ||
to: 2 | ||
}], | ||
nodes: [{ | ||
id: 1 | ||
}, { | ||
id: 2 | ||
}] | ||
}); | ||
expect(g.nodes.getStartingNodes().toObject()).toEqual({ | ||
edges: [], | ||
nodes: [{ | ||
id: 1 | ||
}] | ||
}) | ||
g = Graph.fromObject({ | ||
edges: [{ | ||
from: 1, | ||
to: 2 | ||
}, { | ||
from: 2, | ||
to: 1 | ||
}], | ||
nodes: [{ | ||
id: 1 | ||
}, { | ||
id: 2 | ||
}] | ||
}) | ||
expect(g.nodes.getStartingNodes().toObject()).toEqual({ | ||
edges: [], | ||
nodes: [] | ||
}) | ||
g = Graph.fromObject({ | ||
edges: [{ | ||
from: 1, | ||
to: 2 | ||
}, { | ||
from: 1, | ||
to: 3 | ||
}], | ||
nodes: [{ | ||
id: 1 | ||
}, { | ||
id: 2 | ||
}, { | ||
id: 3 | ||
}] | ||
}) | ||
expect(g.nodes.getStartingNodes().toObject()).toEqual({ | ||
edges: [], | ||
nodes: [{ | ||
id: 1 | ||
}] | ||
}) | ||
g = Graph.fromObject({ | ||
edges: [{ | ||
from: 1, | ||
to: 3 | ||
}, { | ||
from: 2, | ||
to: 3 | ||
}], | ||
nodes: [{ | ||
id: 1 | ||
}, { | ||
id: 2 | ||
}, { | ||
id: 3 | ||
}] | ||
}) | ||
let n = g.nodes.getStartingNodes().toObject().nodes; | ||
expect(n).toContain({ | ||
id: 1 | ||
}) | ||
expect(n).toContain({ | ||
id: 2 | ||
}) | ||
}) | ||
}) | ||
describe('nodes topologicalSort', () => { | ||
it('should return topological levels', () => { | ||
let g = Graph.fromObject({ | ||
edges: [{ | ||
from: 1, | ||
to: 2 | ||
}], | ||
nodes: [{ | ||
id: 1 | ||
}, { | ||
id: 2 | ||
}] | ||
}) | ||
let toArray = (arr) => arr.map(l => l.toObject().nodes); | ||
expect(toArray(g.nodes.topologicalSort())).toEqual([ | ||
[{id: 1}], | ||
[{id: 2}] | ||
]) | ||
g = Graph.fromObject({ | ||
edges: [{ | ||
from: 1, | ||
to: 2 | ||
}, { | ||
from: 2, | ||
to: 3 | ||
}], | ||
nodes: [{ | ||
id: 1 | ||
}, { | ||
id: 2 | ||
}, { | ||
id: 3 | ||
}] | ||
}) | ||
expect(toArray(g.nodes.topologicalSort())).toEqual([ | ||
[{id: 1}], | ||
[{id: 2}], | ||
[{id: 3}] | ||
]) | ||
g = Graph.fromObject({ | ||
edges: [{ | ||
from: 1, | ||
to: 2 | ||
}, { | ||
from: 1, | ||
to: 3 | ||
}, { | ||
from: 2, | ||
to: 4 | ||
}, { | ||
from: 3, | ||
to: 4 | ||
}], | ||
nodes: [{ | ||
id: 1 | ||
}, { | ||
id: 2 | ||
}, { | ||
id: 3 | ||
}, { | ||
id: 4 | ||
}] | ||
}) | ||
expect(toArray(g.nodes.topologicalSort())).toEqual([ | ||
[{id: 1}], | ||
[{id: 3}, {id: 2}], | ||
[{id: 4}] | ||
]) | ||
}) | ||
}) | ||
describe('nodes difference', () => { | ||
it('should return self if difference equals itself', () => { | ||
let g = Graph.fromObject({ | ||
edges: [], | ||
nodes: [{id: 5}, {id: 7}, {id: 8}, {id: 9}, {id: 12}] | ||
}), | ||
g2 = Graph.fromObject({ | ||
edges: [], | ||
nodes: [{id: 89}, {id: 90}, {id: 8172}, {id: 123}] | ||
}), | ||
g3; | ||
expect(() => g3 = g.nodes.difference(g2)).not.toThrow(); | ||
expect(g3).toBe(g); | ||
g2 = Graph.fromObject({ | ||
edges: [], | ||
nodes: [{id:5}] | ||
}); | ||
expect(() => g3 = g.nodes.difference(g2)).not.toThrow(); | ||
expect(g3).not.toBe(g); | ||
}) | ||
}) | ||
describe('nodes iterator', () => { | ||
it('should provide items to for ... of loop', () => { | ||
let g = Graph.fromObject({ | ||
edges: [], | ||
nodes: [{id: 5}, {id: 7}, {id: 11}, {id: 13}, {id: 1}] | ||
}) | ||
let s = 0; | ||
for (let i of (g.nodes as any)){ | ||
s += i.id; | ||
} | ||
expect(s).toBe(37); | ||
}) | ||
}) | ||
describe('edges iterator', () => { | ||
it('should provide items to for ... of loop', () => { | ||
let g = Graph.fromObject({ | ||
edges: [{ | ||
from: 0, | ||
to: 0, | ||
id: 5 | ||
}, { | ||
id: 7, | ||
from: 0, | ||
to: 0 | ||
}, { | ||
id: 11, | ||
from: 0, | ||
to: 0, | ||
}, { | ||
id: 13, | ||
from: 0, | ||
to: 0, | ||
}, { | ||
id: 1, | ||
from: 0, | ||
to: 0, | ||
}], | ||
nodes: [] | ||
}) | ||
let s = 0; | ||
for (let i of (g.edges as any)){ | ||
s += i.id; | ||
} | ||
expect(s).toBe(37); | ||
}) | ||
}) | ||
describe('edges fromId', () => { | ||
@@ -444,2 +1048,166 @@ it('should return graph with edges from given node id', () => { | ||
describe('edges add', () => { | ||
it('should not allow existing edge to be re-added', () => { | ||
let g = Graph.fromObject({ | ||
edges: [{ | ||
from: 0, | ||
to: 1, | ||
id: 987 | ||
}], | ||
nodes: [] | ||
}) | ||
expect(() => g.edges.add({ | ||
from: 0, | ||
to: 1, | ||
id: 987 | ||
})).toThrowError( | ||
`Could not add Edge to Graph: Edge already exists` | ||
) | ||
}) | ||
}) | ||
describe('edges size', () => { | ||
it('should return size of collection', () => { | ||
let g = Graph.fromObject({ | ||
edges: [{ | ||
from: 0, | ||
to: 1, | ||
id: 987 | ||
}], | ||
nodes: [] | ||
}) | ||
expect(g.edges.size()).toEqual(1) | ||
g = Graph.fromObject({ | ||
edges: [{ | ||
from: 0, | ||
to: 1, | ||
id: 987 | ||
}, { | ||
from: 1, | ||
to: 2, | ||
id: 765 | ||
}], | ||
nodes: [] | ||
}) | ||
expect(g.edges.size()).toEqual(2); | ||
g = Graph.fromEmpty(); | ||
expect(g.edges.size()).toEqual(0); | ||
}) | ||
}) | ||
describe('edges update', () => { | ||
it('should throw error if edge does not exist', () => { | ||
let g = Graph.fromEmpty(); | ||
expect(() => g.edges.update({ | ||
from: 0, | ||
to: 1 | ||
})).toThrowError( | ||
`Could not update Edge from Graph: Edge does not exist` | ||
) | ||
}) | ||
it('should update edge', () => { | ||
let g = Graph.fromObject({ | ||
edges: [{ | ||
from: 0, | ||
to: 1, | ||
labels: [] | ||
}], | ||
nodes: [{ | ||
name: 'abc' | ||
}] | ||
}); | ||
expect(() => g = g.edges.update({ | ||
from: 0, | ||
to: 1, | ||
labels: [] | ||
}, { | ||
from: 1, | ||
to: 2, | ||
labels: ['TEST'] | ||
})).not.toThrow(); | ||
expect(g.toObject()).toEqual({ | ||
edges: [{ | ||
from: 1, | ||
to: 2, | ||
labels: ['TEST'] | ||
}], | ||
nodes: [{ | ||
name: 'abc' | ||
}] | ||
}) | ||
}) | ||
}) | ||
describe('edges updateId', () => { | ||
it('should throw error if edge does not exist', () => { | ||
let g = Graph.fromEmpty(); | ||
expect(() => g.edges.updateId({ | ||
id: 'inexistent' | ||
})).toThrowError( | ||
`Could not update Edge from Graph: Edge does not exist` | ||
) | ||
}) | ||
it('should update edge', () => { | ||
let edge = { | ||
from: 0, | ||
id: 'edge', | ||
to: 1, | ||
labels: [] | ||
} | ||
let g = Graph.fromObject({ | ||
edges: [edge], | ||
nodes: [{ | ||
name: 'abc' | ||
}] | ||
}); | ||
let g2; | ||
expect(() => g2 = g.edges.updateId('edge', { | ||
from: 1, | ||
to: 2, | ||
labels: ['TEST'] | ||
})).not.toThrow(); | ||
expect(g2.toObject()).toEqual({ | ||
edges: [{ | ||
from: 1, | ||
to: 2, | ||
labels: ['TEST'] | ||
}], | ||
nodes: [{ | ||
name: 'abc' | ||
}] | ||
}) | ||
expect(g2).not.toBe(g); | ||
}) | ||
it('should return self if there were no changes', () => { | ||
let obj = { | ||
from: 0, | ||
id: 'edge', | ||
to: 1, | ||
labels: [] | ||
} | ||
let g = Graph.fromObject({ | ||
edges: [obj], | ||
nodes: [{ | ||
name: 'abc' | ||
}] | ||
}); | ||
let g2; | ||
expect(() => g2 = g.edges.updateId('edge', { | ||
from: 0, | ||
id: 'edge', | ||
to: 1, | ||
labels: [] | ||
})).not.toThrow(); | ||
expect(g2).toBe(g); | ||
}) | ||
}) | ||
describe('edges union', () => { | ||
@@ -621,2 +1389,178 @@ it('should merge empty edges graph into new graph', () => { | ||
describe('edges map', () => { | ||
it('should map edges into new values', () => { | ||
let g = Graph.fromObject({ | ||
edges: [{ | ||
from: 0, | ||
to: 1, | ||
id: '0-1' | ||
}, { | ||
from: 1, | ||
to: 2, | ||
id: '0-2' | ||
}], | ||
nodes: [] | ||
}) | ||
let newGraph = g.edges.map(i => ({ | ||
...i, | ||
to: 3 | ||
})) | ||
expect(newGraph.toObject().edges).toContain({ | ||
from: 0, | ||
to: 3, | ||
id: '0-1' | ||
}) | ||
expect(newGraph.toObject().edges).toContain({ | ||
from: 1, | ||
to: 3, | ||
id: '0-2' | ||
}) | ||
expect(newGraph.toObject().edges).not.toContain({ | ||
from: 0, | ||
to: 1, | ||
id: '0-1' | ||
}) | ||
expect(newGraph.toObject().edges).not.toContain({ | ||
from: 0, | ||
to: 2, | ||
id: '0-2' | ||
}) | ||
}) | ||
}) | ||
describe('edges filter', () => { | ||
it('should filter the edges', () => { | ||
let g = Graph.fromObject({ | ||
edges: [{ | ||
from: 0, | ||
to: 0, | ||
name: 'California', | ||
country: 'USA' | ||
}, { | ||
from: 0, | ||
to: 0, | ||
name: 'DC', | ||
country: 'USA' | ||
}, { | ||
from: 0, | ||
to: 0, | ||
name: 'Sao Paulo', | ||
country: 'Brazil' | ||
}, { | ||
from: 0, | ||
to: 0, | ||
name: 'Missouri', | ||
country: 'USA' | ||
}, { | ||
from: 0, | ||
to: 0, | ||
name: 'Maranhao', | ||
country: 'Brazil' | ||
}], | ||
nodes: [] | ||
}) | ||
let fromUS = g.edges.filter(n => n.country === 'USA') | ||
expect(fromUS.toObject().edges).toContain({ | ||
from: 0, | ||
to: 0, | ||
name: 'California', | ||
country: 'USA' | ||
}) | ||
expect(fromUS.toObject().edges).toContain({ | ||
from: 0, | ||
to: 0, | ||
name: 'DC', | ||
country: 'USA' | ||
}) | ||
expect(fromUS.toObject().edges).toContain({ | ||
from: 0, | ||
to: 0, | ||
name: 'Missouri', | ||
country: 'USA' | ||
}) | ||
expect(fromUS.toObject().edges).not.toContain({ | ||
from: 0, | ||
to: 0, | ||
name: 'Sao Paulo', | ||
country: 'Brazil' | ||
}) | ||
expect(fromUS.toObject().edges).not.toContain({ | ||
from: 0, | ||
to: 0, | ||
name: 'Maranhao', | ||
country: 'Brazil' | ||
}) | ||
let startsWithM = g.edges.filter(n => n.name[0] === 'M'); | ||
expect(startsWithM.toObject().edges).not.toContain({ | ||
from: 0, | ||
to: 0, | ||
name: 'California', | ||
country: 'USA' | ||
}) | ||
expect(startsWithM.toObject().edges).not.toContain({ | ||
from: 0, | ||
to: 0, | ||
name: 'DC', | ||
country: 'USA' | ||
}) | ||
expect(startsWithM.toObject().edges).toContain({ | ||
from: 0, | ||
to: 0, | ||
name: 'Missouri', | ||
country: 'USA' | ||
}) | ||
expect(startsWithM.toObject().edges).not.toContain({ | ||
from: 0, | ||
to: 0, | ||
name: 'Sao Paulo', | ||
country: 'Brazil' | ||
}) | ||
expect(startsWithM.toObject().edges).toContain({ | ||
from: 0, | ||
to: 0, | ||
name: 'Maranhao', | ||
country: 'Brazil' | ||
}) | ||
}) | ||
}) | ||
describe('edges removal', () => { | ||
it('should throw error if edge does not exist', () => { | ||
let g1; | ||
let result; | ||
expect(() => g1 = Graph.fromObject({ | ||
edges: [ | ||
{ | ||
from: 0, | ||
to: 0, | ||
name: 'Albus', | ||
lastName: 'Dumbledore' | ||
}, | ||
{ | ||
from: 0, | ||
to: 0, | ||
name: 'Lord', | ||
lastName: 'Voldemort' | ||
} | ||
], | ||
nodes: [ | ||
] | ||
})).not.toThrow() | ||
expect(() => result = g1.edges.remove({ | ||
name: 'Inexistent', | ||
lastName: 'Node' | ||
})).toThrowError( | ||
`Could not remove Edge from Graph: Edge does not exist` | ||
); | ||
}) | ||
}) | ||
describe('union', () => { | ||
@@ -724,2 +1668,51 @@ it('should merge 2 graphs into a new one', () => { | ||
}) | ||
describe('edges getId', () => { | ||
it ('should throw error if id does not exist', () => { | ||
let g1; | ||
let result; | ||
expect(() => g1 = Graph.fromObject({ | ||
nodes: [], | ||
edges: [] | ||
})).not.toThrow(); | ||
expect(() => result = g1.edges.getId('inexistentId')).toThrowError( | ||
`Could not get Edge: Edge Id 'inexistentId' does not exist in Graph` | ||
) | ||
}) | ||
it ('should return an edge given its id', () => { | ||
let g1; | ||
let result; | ||
expect(() => g1 = Graph.fromObject({ | ||
nodes: [ | ||
{ | ||
name: 'Lord', | ||
lastName: 'Voldemort', | ||
id: 'the-evil-one' | ||
}, | ||
{ | ||
name: 'Harry', | ||
lastName: 'Potter', | ||
id: 'expelliarmus!' | ||
} | ||
], | ||
edges: [{ | ||
from: 'expelliarmus', | ||
to: 'the-evil-one', | ||
labels: ['SURVIVED'], | ||
id: 'edge' | ||
}] | ||
})).not.toThrow(); | ||
expect(() => result = g1.edges.getId('inexistentId')).toThrowError( | ||
`Could not get Edge: Edge Id 'inexistentId' does not exist in Graph` | ||
) | ||
expect(() => result = g1.edges.getId('edge')).not.toThrow() | ||
expect(result).toEqual({ | ||
from: 'expelliarmus', | ||
to: 'the-evil-one', | ||
labels: ['SURVIVED'], | ||
id: 'edge' | ||
}) | ||
}) | ||
}) | ||
}) |
@@ -42,9 +42,19 @@ import * as _ from 'lodash'; | ||
public static fromEmpty() { | ||
return Graph.fromObject({ | ||
edges: [], | ||
nodes: [], | ||
}); | ||
} | ||
/* TODO: add wrapper for bind functions */ | ||
public nodes = { | ||
add: ((item: any) => { | ||
/** Adds a new Node to the Graph. | ||
* Will throw an error if Node already exists | ||
*/ | ||
add: ((item: any): Graph => { | ||
const {nodes, edges} = this.internal; | ||
if (nodes.has(item)) { | ||
throw new Error ( | ||
`Could not add Node from Graph: Node already exists`, | ||
`Could not add Node to Graph: Node already exists`, | ||
); | ||
@@ -58,3 +68,6 @@ } | ||
remove: ((item: any) => { | ||
/** Removes a Node from the Graph. | ||
* Will throw an error if Node does not exist in Graph | ||
*/ | ||
remove: ((item: any): Graph => { | ||
const {nodes, edges} = this.internal; | ||
@@ -72,3 +85,7 @@ if (!nodes.has(item)) { | ||
union : ((g2: Graph) => { | ||
/** | ||
* Adds Nodes Graphs and keeps Edges as is for "this" Graph | ||
* If you would like to add Edges as well, please use Graph.union() | ||
*/ | ||
union : ((g2: Graph): Graph => { | ||
const {nodes, edges} = this.internal; | ||
@@ -81,7 +98,13 @@ return new Graph({ | ||
getAll : (() => { | ||
/** Returns Nodes as Collection Type instead of Graph */ | ||
getAll : ((): Collection => { | ||
return this.internal.nodes; | ||
}).bind(this), | ||
update: ((item, newItem) => { | ||
/** Update a Node from the Graph. | ||
* This method will update only one matching Node. | ||
* Query must be exactly equal to the Node. If you'd like to | ||
* query only by "id" property, please use updateId() | ||
*/ | ||
update: ((item, newItem): Graph => { | ||
const {nodes, edges} = this.internal; | ||
@@ -99,2 +122,13 @@ if (!nodes.has(item)) { | ||
/** Check if a given item is contained in the Graph Nodes | ||
* Query must be exactly equal to the Node. If you'd like to | ||
* query only by "id" property, please use hasId() | ||
*/ | ||
has: ((item): boolean => { | ||
return this.internal.nodes.has(item); | ||
}).bind(this), | ||
/** Gets Node by querying its id | ||
* If Node Id does not exist in Graph, will throw an error. | ||
*/ | ||
getId : ((id: any) => { | ||
@@ -111,2 +145,3 @@ const {nodes, edges} = this.internal; | ||
/** Check if a given item id is contained in the Graph Nodes */ | ||
hasId : ((id: any) => { | ||
@@ -118,4 +153,8 @@ const {nodes, edges} = this.internal; | ||
/** Get one Node that contains an incoming edge from id supplied */ | ||
oneReachedById: ((id) => { | ||
const edgesGraph = this.edges.fromId(id); | ||
if (edgesGraph.edges.isEmpty()) { | ||
return; | ||
} | ||
const edge = edgesGraph.edges.getAll().getOne(); | ||
@@ -125,2 +164,5 @@ return this.nodes.getId(edge.to); | ||
/* Get all Nodes that contain an incoming edge from id supplied | ||
* Return does not contain Edges | ||
*/ | ||
reachedById: ((id) => { | ||
@@ -138,2 +180,7 @@ const edgesGraph = this.edges.fromId(id); | ||
/** Find Nodes that satisfy a given query. Exactly matches one-level-deep | ||
* property indexes, such as "id". | ||
* If you would like to run a complex query, please use filter() instead | ||
* Return does not contain Edges. | ||
*/ | ||
find: ((query) => { | ||
@@ -147,2 +194,85 @@ const {nodes, edges} = this.internal; | ||
/** Run a query function against all Nodes and return the ones that pass the test | ||
* Return does not contain Edges | ||
*/ | ||
filter: ((query) => { | ||
const {nodes, edges} = this.internal; | ||
return new Graph({ | ||
edges: Collection.fromArray([]), | ||
nodes: nodes.filter(query), | ||
}); | ||
}).bind(this), | ||
/** Run a function against all Nodes */ | ||
forEach: ((fn) => { | ||
const {nodes, edges} = this.internal; | ||
nodes.forEach(fn); | ||
}).bind(this), | ||
/** Check whether Graph contains no Nodes */ | ||
isEmpty: (() => { | ||
return this.internal.nodes.isEmpty(); | ||
}).bind(this), | ||
/** Get all Nodes that do not have incoming edges | ||
* Return does not contain edges | ||
*/ | ||
getStartingNodes: (() => { | ||
return this.nodes.filter((i) => { | ||
return this.edges.findOne({ | ||
to: i.id, | ||
}) === undefined; | ||
}); | ||
}).bind(this), | ||
/** Sort Nodes topologically. | ||
* Return contains an Array of Graphs that do not contain Edges | ||
*/ | ||
topologicalSort: (() => { | ||
/* TODO: Add cycle detection */ | ||
const step = (graph, arr = []) => { | ||
if (graph.nodes.isEmpty()) { | ||
return arr; | ||
} | ||
/* Get Nodes without Incoming Edges - "Starting" nodes */ | ||
const levelNodes = graph.nodes.getStartingNodes(); | ||
/* Remove levelNodes from graph */ | ||
const graphWithoutLevelNodes = graph.nodes.difference(levelNodes); | ||
/* Remove edges attached to removed nodes */ | ||
/* TODO: Abstract step into custom method */ | ||
const nodesCollection = Collection.fromArray(levelNodes.toObject().nodes); | ||
let graphWithoutLevelEdges = graphWithoutLevelNodes; | ||
nodesCollection.forEach((item) => { | ||
graphWithoutLevelEdges = graphWithoutLevelEdges.edges.findAndDifference({ | ||
from: item.id, | ||
}); | ||
}); | ||
return step( | ||
graphWithoutLevelEdges, | ||
[...arr, levelNodes], | ||
); | ||
}; | ||
return step(this); | ||
}).bind(this), | ||
/** Get the Nodes difference between two Graphs. | ||
* Edges remain unaltered for "this" Graph | ||
*/ | ||
difference: ((g2: Graph) => { | ||
const {nodes, edges} = this.internal; | ||
const result = nodes.difference(g2.nodes.getAll()); | ||
/* Performance optimization */ | ||
if (result === nodes) { | ||
return this; | ||
} | ||
return new Graph({ | ||
edges, | ||
nodes: result, | ||
}); | ||
}).bind(this), | ||
/** Iterator for Nodes. Can be used in for ... of loops, for example */ | ||
[Symbol.iterator]: (() => { | ||
@@ -155,2 +285,6 @@ return this.internal.nodes[Symbol.iterator](); | ||
public edges = { | ||
/** | ||
* Adds Edges Graphs and keeps Nodes as is for "this" Graph | ||
* If you would like to add Nodes as well, please use Graph.union() | ||
*/ | ||
union : ((g2: Graph) => { | ||
@@ -164,2 +298,5 @@ const {nodes, edges} = this.internal; | ||
/** Get the Edges difference between two Graphs. | ||
* Nodes remain unaltered for "this" Graph | ||
*/ | ||
difference: ((g2: Graph) => { | ||
@@ -178,2 +315,5 @@ const {nodes, edges} = this.internal; | ||
/** Maps Edges to a new Graph | ||
* Resulting Graph keep Nodes unaltered | ||
*/ | ||
map: ((fn) => { | ||
@@ -188,2 +328,18 @@ const {nodes, edges} = this.internal; | ||
/** Run a query function against all Edges and return the ones that pass the test | ||
* Return does not contain Nodes | ||
*/ | ||
filter: ((query) => { | ||
const {nodes, edges} = this.internal; | ||
return new Graph({ | ||
edges: edges.filter(query), | ||
nodes: Collection.fromArray([]), | ||
}); | ||
}).bind(this), | ||
/** Find Edges that satisfy a given query. Exactly matches one-level-deep | ||
* property indexes, such as "id". | ||
* If you would like to run a complex query, please use filter() instead | ||
* Return does not contain Nodes. | ||
*/ | ||
find: ((query) => { | ||
@@ -197,2 +353,3 @@ const {nodes, edges} = this.internal; | ||
/** Shortcut method for piping Edges.find() => Edges.difference */ | ||
findAndDifference: ((query) => { | ||
@@ -202,2 +359,3 @@ return this.edges.difference(this.edges.find(query)); | ||
/** Returns Edges as Collection Type instead of Graph */ | ||
getAll : (() => { | ||
@@ -221,2 +379,5 @@ return this.internal.edges; | ||
/** Removes an Edge from the Graph. | ||
* Will throw an error if Edge does not exist in Graph | ||
*/ | ||
remove: ((item: any) => { | ||
@@ -235,2 +396,5 @@ const {nodes, edges} = this.internal; | ||
/** Adds a new Edge to the Graph. | ||
* Will throw an error if Edge already exists | ||
*/ | ||
add: ((item: any) => { | ||
@@ -249,2 +413,7 @@ const {nodes, edges} = this.internal; | ||
/** Updates an Edge from the Graph. | ||
* This method will update only one matching Edge. | ||
* Query must be exactly equal to the Edge. If you'd like to | ||
* query only by "id" property, please use updateId() | ||
*/ | ||
update: ((item, newItem) => { | ||
@@ -263,2 +432,3 @@ const {nodes, edges} = this.internal; | ||
/** Updates an Edge from Graph by querying its id */ | ||
updateId: ((id, newItem) => { | ||
@@ -272,2 +442,6 @@ const {nodes, edges} = this.internal; | ||
} | ||
/* Performance improvement */ | ||
if (SSet.hashOf(item) === SSet.hashOf(newItem)) { | ||
return this; | ||
} | ||
return new Graph({ | ||
@@ -279,2 +453,3 @@ edges: edges.remove(item).add(newItem), | ||
/** Checks whether id is contained in Graph Edges */ | ||
hasId : ((id: any) => { | ||
@@ -286,2 +461,5 @@ const {nodes, edges} = this.internal; | ||
/** Gets All Edges that have "from" property set to given id | ||
* Return does not contain Nodes | ||
*/ | ||
fromId: ((from) => { | ||
@@ -295,2 +473,3 @@ const {nodes, edges} = this.internal; | ||
/** Iterator for Edges. Can be used in for ... of loops, for example */ | ||
[Symbol.iterator]: (() => { | ||
@@ -300,2 +479,3 @@ return this.internal.edges[Symbol.iterator](); | ||
/** Finds one item in Edges that satifies the query provided */ | ||
findOne: ((query) => { | ||
@@ -306,2 +486,26 @@ const {nodes, edges} = this.internal; | ||
/** Gets the number of Edges in the Graph */ | ||
size: (() => { | ||
return this.internal.edges.size(); | ||
}).bind(this), | ||
/** Check whether Graph contains no Edges */ | ||
isEmpty: (() => { | ||
return this.internal.edges.isEmpty(); | ||
}).bind(this), | ||
/** Gets Edge by querying its id | ||
* If Edge Id does not exist in Graph, will throw an error. | ||
*/ | ||
getId : ((id: any) => { | ||
const {edges} = this.internal; | ||
const maybeEdge = edges.findOne({id}); | ||
if (_.isUndefined(maybeEdge)) { | ||
throw new Error ( | ||
`Could not get Edge: Edge Id '${id}' does not exist in Graph`, | ||
); | ||
} | ||
return maybeEdge; | ||
}).bind(this), | ||
/* TODO: Follow up method, used for Tree Graphs. | ||
@@ -308,0 +512,0 @@ Starting from a given node, will return the path order */ |
@@ -141,7 +141,6 @@ import _ = require('lodash'); | ||
let result; | ||
for (const key in state) { | ||
if (!state.hasOwnProperty(key)) { continue; } | ||
_.forEach(state, (value, key) => { | ||
result = key; | ||
break; | ||
} | ||
return false; | ||
}); | ||
return result; | ||
@@ -148,0 +147,0 @@ } |
@@ -20,4 +20,3 @@ { | ||
], | ||
"downlevelIteration": true, | ||
"outDir": "build" | ||
"downlevelIteration": true | ||
}, | ||
@@ -24,0 +23,0 @@ "files": [ |
Sorry, the diff of this file is not supported yet
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
187759
6085
2