Improve data model & fix tests
This commit is contained in:
@@ -4,7 +4,7 @@ const sha1 = require('sha1')
|
||||
export class Edge implements Hashable {
|
||||
public name: string
|
||||
|
||||
public node!: TreeNode
|
||||
public target!: TreeNode
|
||||
public source?: TreeNode | undefined
|
||||
private cachedHash?: string
|
||||
|
||||
@@ -13,7 +13,7 @@ export class Edge implements Hashable {
|
||||
}
|
||||
|
||||
public edges() {
|
||||
return this.node ? Object.values(this.node.edges) : []
|
||||
return this.target ? Object.values(this.target.edges) : []
|
||||
}
|
||||
|
||||
public hash(): string {
|
||||
|
||||
3
backend/src/Model/Message.ts
Normal file
3
backend/src/Model/Message.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface Message {
|
||||
value?: any | undefined
|
||||
}
|
||||
@@ -1,20 +1,27 @@
|
||||
import { Edge } from './'
|
||||
import { Edge, Message } from './'
|
||||
import { EventEmitter } from 'events'
|
||||
|
||||
export class TreeNode extends EventEmitter {
|
||||
public sourceEdge?: Edge
|
||||
public value?: any | null
|
||||
public message?: Message
|
||||
public edges: {[s: string]: Edge} = {}
|
||||
public collapsed = false
|
||||
public messages: number = 0
|
||||
|
||||
constructor(sourceEdge?: Edge, value?: any) {
|
||||
constructor(sourceEdge?: Edge, message?: Message) {
|
||||
super()
|
||||
|
||||
if (sourceEdge) {
|
||||
this.sourceEdge = sourceEdge
|
||||
sourceEdge.node = this
|
||||
sourceEdge.target = this
|
||||
}
|
||||
this.value = value
|
||||
|
||||
this.setMessage(message)
|
||||
}
|
||||
|
||||
public setMessage(value: any) {
|
||||
this.message = value
|
||||
this.messages += 1
|
||||
}
|
||||
|
||||
public hash(): string {
|
||||
@@ -22,7 +29,7 @@ export class TreeNode extends EventEmitter {
|
||||
}
|
||||
|
||||
public firstNode(): TreeNode {
|
||||
return this.sourceEdge ? this.sourceEdge.firstEdge().node : this
|
||||
return this.sourceEdge && this.sourceEdge.source ? this.sourceEdge.source.firstNode() : this
|
||||
}
|
||||
|
||||
public path(): string {
|
||||
@@ -52,9 +59,10 @@ export class TreeNode extends EventEmitter {
|
||||
}
|
||||
|
||||
public updateWithNode(node: TreeNode) {
|
||||
if (node.value !== undefined) {
|
||||
this.value = node.value
|
||||
if (node.message) {
|
||||
this.setMessage(node.message)
|
||||
}
|
||||
|
||||
this.mergeEdges(node)
|
||||
this.emit('update')
|
||||
}
|
||||
@@ -65,7 +73,7 @@ export class TreeNode extends EventEmitter {
|
||||
}
|
||||
|
||||
return Object.values(this.edges)
|
||||
.map(e => e.node.leafes())
|
||||
.map(e => e.target.leafes())
|
||||
.reduce((a, b) => a.concat(b), [])
|
||||
}
|
||||
|
||||
@@ -74,7 +82,7 @@ export class TreeNode extends EventEmitter {
|
||||
for (let edgeKey of edgeKeys) {
|
||||
let matchingEdge = this.edges[edgeKey]
|
||||
if (matchingEdge) {
|
||||
matchingEdge.node.updateWithNode(node.edges[edgeKey].node)
|
||||
matchingEdge.target.updateWithNode(node.edges[edgeKey].target)
|
||||
} else {
|
||||
this.addEdge(node.edges[edgeKey])
|
||||
}
|
||||
|
||||
@@ -1,32 +1,17 @@
|
||||
import { Edge, Tree, TreeNode } from './'
|
||||
import { Edge, Message, Tree, TreeNode } from './'
|
||||
|
||||
export abstract class TreeNodeFactory {
|
||||
public static fromEdgesAndValue(edgeNames: Array<string>, value: any): TreeNode {
|
||||
const lastEdgeIndex = edgeNames.length - 1
|
||||
var edges = edgeNames
|
||||
.map((name, idx) => {
|
||||
const edge = new Edge(name)
|
||||
|
||||
const nodeValue = lastEdgeIndex == idx ? value : undefined
|
||||
const node = new TreeNode(edge, nodeValue)
|
||||
edge.node = node
|
||||
return edge
|
||||
})
|
||||
|
||||
let reversed: Array<Edge> = edges.reverse()
|
||||
let previous: Edge | undefined = undefined;
|
||||
for (let edge of reversed) {
|
||||
if (previous) {
|
||||
edge.node.addEdge(previous)
|
||||
}
|
||||
previous = edge;
|
||||
let currentNode: TreeNode = new Tree()
|
||||
for (const edgeName of edgeNames) {
|
||||
const edge = new Edge(edgeName)
|
||||
let newNode = new TreeNode(edge)
|
||||
edge.target = newNode
|
||||
currentNode.addEdge(edge)
|
||||
currentNode = newNode
|
||||
}
|
||||
|
||||
let leaf = reversed[0].node
|
||||
|
||||
let sourceTree = new Tree()
|
||||
sourceTree.updateWithNode(leaf.firstNode())
|
||||
|
||||
return leaf
|
||||
currentNode.setMessage({ value: value })
|
||||
return currentNode
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { Edge } from './Edge'
|
||||
import { TreeNode } from './TreeNode'
|
||||
import { TreeNodeFactory } from './TreeNodeFactory'
|
||||
import { Tree } from './Tree'
|
||||
import { TopicProperties } from './TopicProperties'
|
||||
import { Hashable } from './Hashable'
|
||||
|
||||
export {
|
||||
Edge, TreeNode, TreeNodeFactory, Tree, TopicProperties, Hashable
|
||||
}
|
||||
export { Edge } from './Edge'
|
||||
export { TreeNode } from './TreeNode'
|
||||
export { Message } from './Message'
|
||||
export { TreeNodeFactory } from './TreeNodeFactory'
|
||||
export { Tree } from './Tree'
|
||||
export { TopicProperties } from './TopicProperties'
|
||||
export { Hashable } from './Hashable'
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
import { Edge, TreeNode } from '../'
|
||||
import { expect } from 'chai';
|
||||
import 'mocha';
|
||||
import { Edge, TreeNode, TreeNodeFactory } from '../'
|
||||
import { expect } from 'chai'
|
||||
import 'mocha'
|
||||
|
||||
describe('Edge', () => {
|
||||
|
||||
it('should contain a name', () => {
|
||||
let e = new Edge('foo')
|
||||
expect(e.name).to.equal('foo')
|
||||
});
|
||||
|
||||
it('firstEdge should retireve the first edge', () => {
|
||||
const topics = 'foo/bar/baz'.split('/')
|
||||
const leaf = TreeNodeFactory.fromEdgesAndValue(topics, 5)
|
||||
let bazEdge = leaf.sourceEdge
|
||||
|
||||
if (!bazEdge) {
|
||||
expect.fail('should not be undefined')
|
||||
return;
|
||||
}
|
||||
|
||||
expect(bazEdge.name).to.eq('baz')
|
||||
expect(bazEdge.firstEdge().name).to.eq('foo')
|
||||
});
|
||||
|
||||
it('hash should not be empty', () => {
|
||||
let e = new Edge('bar')
|
||||
expect(e.hash().length).to.be.gt(0)
|
||||
@@ -20,12 +33,17 @@ describe('Edge', () => {
|
||||
expect(e.hash()).to.eq(previousHash)
|
||||
});
|
||||
|
||||
it('hash should change when parent is present', () => {
|
||||
let foo = new Edge('foo')
|
||||
let bar = new Edge('bar')
|
||||
it('hash should include change if parents are different', () => {
|
||||
const topics1 = 'foo/bar/baz'.split('/')
|
||||
const bazEdge1 = TreeNodeFactory.fromEdgesAndValue(topics1, 5).sourceEdge
|
||||
|
||||
var previousHash = bar.hash()
|
||||
bar.source = new TreeNode(foo, undefined)
|
||||
expect(bar.hash()).to.not.eq(previousHash)
|
||||
const topics2 = 'foo/foo/baz'.split('/')
|
||||
const bazEdge2 = TreeNodeFactory.fromEdgesAndValue(topics2, 5).sourceEdge
|
||||
|
||||
if (!bazEdge1 || !bazEdge2) {
|
||||
throw Error('should not happen')
|
||||
}
|
||||
|
||||
expect(bazEdge1.hash()).to.not.eq(bazEdge2.hash())
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Edge, Tree, TreeNode, TreeNodeFactory } from '../'
|
||||
import { expect } from 'chai';
|
||||
import 'mocha';
|
||||
import { Tree, TreeNodeFactory } from '../'
|
||||
import { expect } from 'chai'
|
||||
import 'mocha'
|
||||
|
||||
import './TreeNode.findNode'
|
||||
|
||||
@@ -10,7 +10,7 @@ describe('Tree', () => {
|
||||
|
||||
const topics = 'foo/bar'.split('/')
|
||||
const leaf = TreeNodeFactory.fromEdgesAndValue(topics, 3)
|
||||
debugger
|
||||
|
||||
tree.updateWithNode(leaf.firstNode())
|
||||
let expectedNode = tree.findNode('foo/bar')
|
||||
expect(expectedNode).to.eq(leaf)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { TreeNode, TreeNodeFactory } from '../'
|
||||
import { expect } from 'chai';
|
||||
import 'mocha';
|
||||
import { TreeNodeFactory } from '../'
|
||||
import { expect } from 'chai'
|
||||
import 'mocha'
|
||||
|
||||
import './TreeNode.findNode'
|
||||
|
||||
@@ -10,20 +10,20 @@ describe('TreeNode.findNode', () => {
|
||||
const leaf = TreeNodeFactory.fromEdgesAndValue(topics, 5)
|
||||
|
||||
let root = leaf.firstNode()
|
||||
expect(root.sourceEdge.name).to.eq('')
|
||||
expect(root.sourceEdge).to.eq(undefined)
|
||||
|
||||
let barNode = root.findNode('foo/bar')
|
||||
if (!barNode) {
|
||||
expect.fail('did not find node')
|
||||
return
|
||||
}
|
||||
expect(barNode.sourceEdge.name).to.eq('bar')
|
||||
expect(barNode.sourceEdge && barNode.sourceEdge.name).to.eq('bar')
|
||||
|
||||
let bazNode = root.findNode('foo/bar/baz')
|
||||
if (!bazNode) {
|
||||
expect.fail('did not find node')
|
||||
return
|
||||
}
|
||||
expect(bazNode.sourceEdge.name).to.eq('baz')
|
||||
expect(bazNode.sourceEdge && bazNode.sourceEdge.name).to.eq('baz')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { TreeNode } from '../'
|
||||
|
||||
declare module "../" {
|
||||
import { TreeNode } from '../'
|
||||
declare module '../' {
|
||||
interface TreeNode {
|
||||
findNode(path: String): TreeNode | undefined
|
||||
}
|
||||
@@ -11,9 +10,9 @@ TreeNode.prototype.findNode = function(path: String): TreeNode | undefined {
|
||||
let edge = this.edges[topics[0]]
|
||||
let remainingTopics = topics.slice(1, topics.length)
|
||||
if (edge && remainingTopics.length === 0) {
|
||||
return edge.node
|
||||
return edge.target
|
||||
} else if (edge) {
|
||||
return edge.node.findNode(remainingTopics.join('/'))
|
||||
return edge.target.findNode(remainingTopics.join('/'))
|
||||
}
|
||||
|
||||
return undefined
|
||||
|
||||
@@ -1,36 +1,44 @@
|
||||
import { Edge, Tree, TreeNode, TreeNodeFactory } from '../'
|
||||
import { expect } from 'chai';
|
||||
import 'mocha';
|
||||
|
||||
import { TreeNodeFactory } from '../'
|
||||
import './TreeNode.findNode'
|
||||
import { expect } from 'chai'
|
||||
import 'mocha'
|
||||
|
||||
describe('TreeNode', () => {
|
||||
it('firstNode should retrieve first node', () => {
|
||||
const topics = 'foo/bar'.split('/')
|
||||
const leaf = TreeNodeFactory.fromEdgesAndValue(topics, 3)
|
||||
|
||||
expect(leaf.firstNode().edges['foo']).to.not.eq(undefined)
|
||||
})
|
||||
|
||||
it('updateWithNode should update value', () => {
|
||||
const topics = 'foo/bar'.split('/')
|
||||
const leaf = TreeNodeFactory.fromEdgesAndValue(topics, 3)
|
||||
expect(leaf.value).to.eq(3)
|
||||
expect(leaf.message && leaf.message.value).to.eq(3)
|
||||
const updateLeave = TreeNodeFactory.fromEdgesAndValue(topics, 5)
|
||||
leaf.firstNode().updateWithNode(updateLeave.firstNode())
|
||||
|
||||
expect(leaf.firstNode().sourceEdge.name).to.eq(updateLeave.firstNode().sourceEdge.name)
|
||||
expect(leaf.value).to.eq(5)
|
||||
let root = leaf.firstNode()
|
||||
root.updateWithNode(updateLeave.firstNode())
|
||||
|
||||
expect(root.sourceEdge).to.eq(undefined)
|
||||
expect(leaf.message && leaf.message.value).to.eq(5)
|
||||
})
|
||||
|
||||
it('updateWithNode should update intermediate nodes', () => {
|
||||
const topics1 = 'foo/bar/baz'.split('/')
|
||||
const leaf = TreeNodeFactory.fromEdgesAndValue(topics1, 3)
|
||||
expect(leaf.value).to.eq(3)
|
||||
expect(leaf.message && leaf.message.value).to.eq(3)
|
||||
|
||||
const topics2 = 'foo/bar'.split('/')
|
||||
const updateLeave = TreeNodeFactory.fromEdgesAndValue(topics2, 5)
|
||||
leaf.firstNode().updateWithNode(updateLeave.firstNode())
|
||||
|
||||
let barNode = leaf.firstNode().findNode('foo/bar')
|
||||
expect(barNode && barNode.sourceEdge.name).to.eq('bar')
|
||||
expect(barNode && barNode.value).to.eq(5)
|
||||
expect(barNode && barNode.sourceEdge && barNode.sourceEdge.name).to.eq('bar')
|
||||
expect(barNode && barNode.message && barNode.message.value).to.eq(5)
|
||||
|
||||
expect(leaf.sourceEdge.name).to.eq('baz')
|
||||
expect(leaf.value).to.eq(3)
|
||||
expect(leaf.sourceEdge && leaf.sourceEdge.name).to.eq('baz')
|
||||
expect(leaf.message && leaf.message.value).to.eq(3)
|
||||
})
|
||||
|
||||
it('updateWithNode should add nodes to the tree', () => {
|
||||
|
||||
@@ -1,24 +1,33 @@
|
||||
import { Edge, TreeNode, TreeNodeFactory } from '../'
|
||||
import { expect } from 'chai';
|
||||
import 'mocha';
|
||||
import { TreeNodeFactory } from '../'
|
||||
import { expect } from 'chai'
|
||||
import 'mocha'
|
||||
import './TreeNode.findNode'
|
||||
|
||||
describe('TreeNodeFactory', () => {
|
||||
it('root node must not have a sourceEdge', () => {
|
||||
let topic = 'foo/bar'
|
||||
let edges = topic.split('/')
|
||||
let leaf = TreeNodeFactory.fromEdgesAndValue(edges, 5)
|
||||
|
||||
expect(leaf.firstNode().sourceEdge).to.eq(undefined)
|
||||
});
|
||||
|
||||
it('should create node', () => {
|
||||
let topic = 'foo/bar'
|
||||
let edges = topic.split('/')
|
||||
let node = TreeNodeFactory.fromEdgesAndValue(edges, 5)
|
||||
|
||||
expect(node).to.not.eq(undefined)
|
||||
expect(node.sourceEdge.name).to.eq('bar')
|
||||
expect(node.value).to.eq(5)
|
||||
|
||||
if (!node.sourceEdge.source) {
|
||||
if (!node.sourceEdge || !node.sourceEdge.source || !node.message) {
|
||||
expect.fail('should not happen')
|
||||
return
|
||||
}
|
||||
|
||||
let foo = node.sourceEdge.source.sourceEdge
|
||||
expect(foo.name).to.eq('foo')
|
||||
expect(node).to.not.eq(undefined)
|
||||
expect(node.sourceEdge.name).to.eq('bar')
|
||||
expect(node.message.value).to.eq(5)
|
||||
|
||||
let foo = node.firstNode().findNode('foo')
|
||||
expect(foo && foo.sourceEdge && foo.sourceEdge.name).to.eq('foo')
|
||||
});
|
||||
|
||||
it('node should contain edges in order', () => {
|
||||
@@ -26,18 +35,23 @@ describe('TreeNodeFactory', () => {
|
||||
let edges = topic.split('/')
|
||||
let node = TreeNodeFactory.fromEdgesAndValue(edges, 5)
|
||||
|
||||
expect(node.value).to.eq(5)
|
||||
if (!node.sourceEdge || !node.sourceEdge.source || !node.message) {
|
||||
expect.fail('should not happen')
|
||||
return
|
||||
}
|
||||
|
||||
expect(node.message.value).to.eq(5)
|
||||
expect(node.sourceEdge.name).to.eq('baz')
|
||||
|
||||
const barNode = node.sourceEdge.source
|
||||
if (!barNode) {
|
||||
if (!barNode || !barNode.sourceEdge) {
|
||||
expect.fail('should not fail')
|
||||
return
|
||||
}
|
||||
expect(barNode.sourceEdge.name).to.eq('bar')
|
||||
|
||||
const fooNode = barNode.sourceEdge.source
|
||||
if (!fooNode) {
|
||||
if (!fooNode || !fooNode.sourceEdge) {
|
||||
expect.fail('should not fail')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -16,13 +16,13 @@ io.on('connection', client => {
|
||||
a.forEach(b => {
|
||||
io.emit('message', b)
|
||||
})
|
||||
client.on('event', data => { /* … */ });
|
||||
client.on('disconnect', () => { /* … */ });
|
||||
});
|
||||
server.listen(3000);
|
||||
|
||||
let state = dataSource.connect(options)
|
||||
dataSource.onMessage((topic: string, payload: Buffer) => {
|
||||
a.push({ topic, payload: payload.toString('base64') })
|
||||
if (payload.length > 10000) {
|
||||
payload = payload.slice(0, 10000)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user