Files @ 8d3569829198
Branch filter:

Location: light9/light9/web/graph_test.coffee

drewp@bigasterisk.com
more upgrades for n3 api. not quite passing
Ignore-this: dc6e911a0d7d3ebf20496f158811e717
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 : <http://example.com/> .
        :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 : <http://example.com/> .
            :ctx { :x :y (:a1 :a2 :a3) } .
          ", () ->
            graph.runHandler(hand, 'run')
            graph.clearGraph()
            graph.loadTrig "
              @prefix : <http://example.com/> .
              :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()