log = console.log assert = require('chai').assert expect = require('chai').expect SyncedGraph = require('./graph.js').SyncedGraph describe 'SyncedGraph', -> describe 'constructor', -> it 'should successfully make an empty graph without connecting to rdfdb', -> g = new SyncedGraph() g.quads() assert.equal(g.quads().length, 0) describe 'auto dependencies', -> graph = new SyncedGraph() RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' U = (tail) -> graph.Uri('http://example.com/' + tail) A1 = U('a1') A2 = U('a2') A3 = U('a3') A4 = U('a4') ctx = U('ctx') quad = (s, p, o) -> graph.Quad(s, p, o, ctx) beforeEach (done) -> graph = new SyncedGraph() graph.loadTrig(" @prefix : . :ctx { :a1 :a2 :a3 . :a1 :someFloat 1.5 . :a1 :someString \"hello\" . :a1 :multipleObjects :a4, :a5 . :a2 a :Type1 . :a3 a :Type1 . } ", done) it 'calls a handler right away', -> called = 0 hand = -> called++ graph.runHandler(hand, 'run') assert.equal(1, called) it 'calls a handler a 2nd time if the graph is patched with relevant data', -> called = 0 hand = -> called++ graph.uriValue(A1, A2) graph.runHandler(hand, 'run') graph.applyAndSendPatch({ delQuads: [quad(A1, A2, A3)], addQuads: [quad(A1, A2, A4)]}) assert.equal(2, called) it 'notices new queries a handler makes upon rerun', -> called = 0 objsFound = [] hand = -> called++ graph.uriValue(A1, A2) if called > 1 objsFound.push(graph.objects(A1, A3)) graph.runHandler(hand, 'run') # first run looked up A1,A2,* graph.applyAndSendPatch({ delQuads: [quad(A1, A2, A3)], addQuads: [quad(A1, A2, A4)]}) # second run also looked up A1,A3,* (which matched none) graph.applyAndSendPatch({ delQuads: [], addQuads: [quad(A1, A3, A4)]}) # third run should happen here, noticing the new A1,A3,* match assert.equal(3, called) assert.deepEqual([[], [A4]], objsFound) it 'calls a handler again even if the handler throws an error', -> called = 0 hand = -> called++ graph.uriValue(A1, A2) throw new Error('this test handler throws an error') graph.runHandler(hand, 'run') graph.applyAndSendPatch({ delQuads: [quad(A1, A2, A3)], addQuads: [quad(A1, A2, A4)]}) assert.equal(2, called) describe 'works with nested handlers', -> innerResults = [] inner = -> console.log('\nninnerfetch') innerResults.push(graph.uriValue(A1, A2)) console.log("innerResults #{JSON.stringify(innerResults)}\n") outerResults = [] doRunInner = true outer = -> if doRunInner graph.runHandler(inner, 'runinner') console.log('push outer') outerResults.push(graph.floatValue(A1, U('someFloat'))) beforeEach -> innerResults = [] outerResults = [] doRunInner = true affectInner = { delQuads: [quad(A1, A2, A3)], addQuads: [quad(A1, A2, A4)] } affectOuter = { delQuads: [ quad(A1, U('someFloat'), graph.Literal(1.5)) ], addQuads: [ quad(A1, U('someFloat'), graph.LiteralRoundedFloat(2)) ]} affectBoth = { delQuads: affectInner.delQuads.concat(affectOuter.delQuads), addQuads: affectInner.addQuads.concat(affectOuter.addQuads) } it 'calls everything normally once', -> graph.runHandler(outer, 'run') assert.deepEqual([A3], innerResults) assert.deepEqual([1.5], outerResults) it.skip '[performance] reruns just the inner if its dependencies change', -> console.log(graph.quads()) graph.runHandler(outer, 'run') graph.applyAndSendPatch(affectInner) assert.deepEqual([A3, A4], innerResults) assert.deepEqual([1.5], outerResults) it.skip '[performance] reruns the outer (and therefore inner) if its dependencies change', -> graph.runHandler(outer, 'run') graph.applyAndSendPatch(affectOuter) assert.deepEqual([A3, A3], innerResults) assert.deepEqual([1.5, 2], outerResults) it.skip '[performance] does not send a redundant inner run if it is already rerunning outer', -> # Note that outer may or may not call inner each time, and we # don't want to redundantly call inner. We need to: # 1. build the set of handlers to rerun, # 2. call them from outside-in, and # 3. any runHandler calls that happen, they need to count as reruns. graph.runHandler(outer, 'run') graph.applyAndSendPatch(affectBoth) assert.deepEqual([A3, A4], innerResults) assert.deepEqual([1.5, 2], outerResults) it 'reruns the outer and the inner if all dependencies change, but outer omits calling inner this time', -> graph.runHandler(outer, 'run') doRunInner = false graph.applyAndSendPatch(affectBoth) assert.deepEqual([A3, A4], innerResults) assert.deepEqual([1.5, 2], outerResults) describe 'watches calls to:', -> it 'floatValue', -> values = [] hand = -> values.push(graph.floatValue(A1, U('someFloat'))) graph.runHandler(hand, 'run') graph.patchObject(A1, U('someFloat'), graph.LiteralRoundedFloat(2), ctx) assert.deepEqual([1.5, 2.0], values) it 'stringValue', -> values = [] hand = -> values.push(graph.stringValue(A1, U('someString'))) graph.runHandler(hand, 'run') graph.patchObject(A1, U('someString'), graph.Literal('world'), ctx) assert.deepEqual(['hello', 'world'], values) it 'uriValue', -> # covered above, but this one tests patchObject on a uri, too values = [] hand = -> values.push(graph.uriValue(A1, A2)) graph.runHandler(hand, 'run') graph.patchObject(A1, A2, A4, ctx) assert.deepEqual([A3, A4], values) it 'objects', -> values = [] hand = -> values.push(graph.objects(A1, U('multipleObjects'))) graph.runHandler(hand, 'run') graph.patchObject(A1, U('multipleObjects'), U('newOne'), ctx) expect(values[0]).to.deep.have.members([U('a4'), U('a5')]) expect(values[1]).to.deep.have.members([U('newOne')]) it 'subjects', -> values = [] rdfType = graph.Uri(RDF + 'type') hand = -> values.push(graph.subjects(rdfType, U('Type1'))) graph.runHandler(hand, 'run') graph.applyAndSendPatch( {delQuads: [], addQuads: [quad(A4, rdfType, U('Type1'))]}) expect(values[0]).to.deep.have.members([A2, A3]) expect(values[1]).to.deep.have.members([A2, A3, A4]) describe 'items', -> it 'when the list order changes', (done) -> values = [] successes = 0 hand = -> try head = graph.uriValue(U('x'), U('y')) catch # graph goes empty between clearGraph and loadTrig return values.push(graph.items(head)) successes++ graph.clearGraph() graph.loadTrig " @prefix : . :ctx { :x :y (:a1 :a2 :a3) } . ", () -> graph.runHandler(hand, 'run') graph.clearGraph() graph.loadTrig " @prefix : . :ctx { :x :y (:a1 :a3 :a2) } . ", () -> assert.deepEqual([[A1, A2, A3], [A1, A3, A2]], values) assert.equal(2, successes) done() describe 'contains', -> it 'when a new triple is added', -> values = [] hand = -> values.push(graph.contains(A1, A1, A1)) graph.runHandler(hand, 'run') graph.applyAndSendPatch( {delQuads: [], addQuads: [quad(A1, A1, A1)]}) assert.deepEqual([false, true], values) it 'when a relevant triple is removed', -> values = [] hand = -> values.push(graph.contains(A1, A2, A3)) graph.runHandler(hand, 'run') graph.applyAndSendPatch( {delQuads: [quad(A1, A2, A3)], addQuads: []}) assert.deepEqual([true, false], values) describe 'performs well', -> it "[performance] doesn't call handler a 2nd time if the graph gets an unrelated patch", -> called = 0 hand = -> called++ graph.uriValue(A1, A2) graph.runHandler(hand, 'run') graph.applyAndSendPatch({ delQuads: [], addQuads: [quad(A2, A3, A4)]}) assert.equal(1, called) it.skip '[performance] calls a handler 2x but then not again if the handler stopped caring about the data', -> assert.fail() it.skip "[performance] doesn't get slow if the handler makes tons of repetitive lookups", -> assert.fail()