@missive/ndex
Version:
Ndex is an indexedDB wrapper
580 lines (473 loc) • 26.7 kB
text/coffeescript
Ndex = require('ndex')
Connection = require('../../lib/ndex/connection')
BrowserAdapter = require('../../lib/ndex/adapters/browser_adapter')
WorkerAdapter = require('../../lib/ndex/adapters/worker_adapter')
{ simple, expect, helpers } = require('../spec_helper.coffee')
{ delay } = helpers
describe 'Connection', ->
describe 'Migrations', ->
beforeEach -> @connection = new Connection()
it 'sorts migrations by key', ->
migrations = @connection.parseMigrations({ 54321: (->), 12345: (->) })
expect(migrations.map (m) -> m.key).to.deep.equal ['12345', '54321']
it 'parses migrations', ->
migrations = @connection.parseMigrations({ '54321_CreateFooBar': {}, '12345_CreateBarFoo': {} })
expected = [
{ version: 12345, title: "Create bar foo", key: "12345_CreateBarFoo" }
{ version: 54321, title: "Create foo bar", key: "54321_CreateFooBar" }
]
for migration, i in migrations
expect(migration).to.have.deep.property('version', expected[i].version)
expect(migration).to.have.deep.property('title', expected[i].title)
expect(migration).to.have.deep.property('key', expected[i].key)
it 'creates a namespace for object stores', ->
simple.mock(@connection, 'add', ->)
expect(@connection.foozle).not.to.exist
@connection.createNamespaceForObjectStore('foozle')
expect(@connection.foozle).to.exist
@connection.foozle.add({ foo: 'bar' })
expect(@connection.add.firstCall.args).to.deep.equal(['foozle', { foo: 'bar' }])
it 'handles connecting to DB with lower version provided', ->
connection1 = new Connection('versions', { version: 2 })
connection2 = new Connection('versions', { version: 1 })
connection1.open()
connection2.open()
describe 'Requests', ->
beforeEach ->
@connection = new Connection('foo', {})
simple.mock(@connection, 'createTransaction', ->)
describe 'Queue', ->
it 'enqueues requests', ->
simple.mock(@connection, 'scheduleTransaction', ->)
@connection.enqueue('write', 'foo') for i in [1..5]
expect(@connection.queue.foo.length).to.equal(5)
it 'clears requests on event loop', (done) ->
@connection.enqueue('write', 'foo') for i in [1..5]
expect(@connection.queue.foo.length).to.equal(5)
delay 0, done, =>
expect(@connection.queue.foo.length).to.equal(0)
it 'schedules 1 transaction per event loop', (done) ->
simple.mock(@connection, 'scheduleTransaction')
@connection.enqueue('read', 'foo')
@connection.enqueue('write', 'foo')
delay 0, =>
@connection.enqueue('read', 'foo')
@connection.enqueue('write', 'foo')
delay 1, done, =>
expect(@connection.scheduleTransaction.calls.length).to.equal(2)
describe 'Transaction', ->
it 'handles readonly', (done) ->
@connection.enqueue('read', 'foo')
@connection.enqueue('read', 'bar')
delay 0, done, =>
expect(@connection.createTransaction.firstCall.args[0]).to.equal('readonly')
it 'handles readwrite', (done) ->
@connection.enqueue('read', 'foo')
@connection.enqueue('write', 'foo')
delay 0, done, =>
expect(@connection.createTransaction.firstCall.args[0]).to.equal('readwrite')
# https://github.com/missive/ndex/issues/1
it 'creates 1 transaction per objectStore', (done) ->
@connection.enqueue('read', 'foo')
@connection.enqueue('write', 'foo')
@connection.enqueue('read', 'bar')
@connection.enqueue('write', 'bar')
delay 0, done, =>
expect(@connection.createTransaction.calls.length).to.equal(2)
it 'handles unknown object stores', (done) ->
connection = new Connection('foo', require('../fixtures/migrations'))
doneCount = 0
successCount = 0
errorCount = 0
handleDone = ->
doneCount++
return unless doneCount == 4
expect(successCount).to.equal(3)
expect(errorCount).to.equal(1)
done()
handleSuccess = ->
successCount++
handleDone()
handleErr = ->
errorCount++
handleDone()
connection.getAll('migratatations').then(handleSuccess).catch(handleErr)
connection.getAll('migrations').then(handleSuccess)
connection.getAll('migrations').then(handleSuccess)
connection.getAll('users').then(handleSuccess)
it 'doesn’t create a transaction with empty storeNames', (done) ->
connection = new Connection('foo', require('../fixtures/migrations'))
connection.getAll('migratatations').catch ->
done()
describe 'Timeouts', ->
beforeEach (done) ->
connection = new Connection('foo', require('../fixtures/migrations'))
adapter = new BrowserAdapter(connection)
adapter.handleMethod('open').then (objectStoreNames) =>
adapter.proxyObjectStoresNamespace(objectStoreNames)
@connection = adapter
@connection.clearAll()
.then => Promise.all [1..1000].map (id) =>
@connection.add('users', { name: "Foo-#{id}", job: 'developer', id: id })
.then ->
connection.REQUEST_TIMEOUT = 0
done()
it 'rejects connection promise when time runs out', ->
connection = new Connection('foo', require('../fixtures/migrations'))
connection.CONNECTION_TIMEOUT = 0
expect(connection.open()).to.be.rejectedWith('Connection timed out')
it 'rejects request promise when time runs out', ->
expect(@connection.users.getAll()).to.be.rejectedWith('The transaction was aborted, so the request cannot be fulfilled.')
expect(@connection.users.add([{ id: 'a' }])).to.be.rejectedWith('The transaction was aborted, so the request cannot be fulfilled.')
describe 'Logging', ->
beforeEach ->
@connection = new Connection('foo', require('../fixtures/migrations'))
@connection.logging.handleLog = (@spy = simple.spy())
it 'calls the handler', (done) ->
expect(@spy.calls.length).to.equal(0)
@connection.add('users', { id: 1 })
@connection.add('users', { id: 2 })
delay 100, done, =>
expect(@spy.calls.length).to.equal(4)
expect(@spy.calls[0].arg.type).to.equal('transaction.start')
expect(@spy.calls[1].arg.type).to.equal('request')
expect(@spy.calls[2].arg.type).to.equal('request')
expect(@spy.calls[3].arg.type).to.equal('transaction.end')
describe 'Methods', ->
[BrowserAdapter, WorkerAdapter].forEach (AdapterClass) ->
describe AdapterClass.name, ->
beforeEach (done) ->
connection = new Connection('foo', require('../fixtures/migrations'))
adapter = new AdapterClass(connection)
adapter.handleMethod('open').then (objectStoreNames) =>
adapter.proxyObjectStoresNamespace(objectStoreNames)
@connection = adapter
@connection.clearAll()
.then => Promise.all([
@connection.add('users', { name: 'e', job: 'developer', id: 1, interests: ['a'] })
@connection.add('users', { name: 'r', job: 'developer', id: 2, interests: ['a', 'b'] })
@connection.add('users', { name: 'p', job: 'developer', id: 3, interests: ['b'] })
@connection.add('users', { name: 't', job: 'designer' , id: 4, interests: [] })
])
.then -> done()
describe '#get', ->
it 'gets an item', ->
promise = @connection.get('users', 2)
expect(promise).to.eventually.deep.equal \
{ name: 'r', job: 'developer', id: 2, interests: ['a', 'b'] }
it 'gets multiple items', ->
promise = @connection.users.get([1, 3])
expect(promise).to.eventually.deep.equal [
{ name: 'e', job: 'developer', id: 1, interests: ['a'] }
{ name: 'p', job: 'developer', id: 3, interests: ['b'] }
]
describe '#getFirst', ->
it 'returns first item', ->
promise = @connection.getFirst('users')
expect(promise).to.eventually.deep.equal { name: 'e', job: 'developer', id: 1, interests: ['a'] }
describe 'without a key', ->
beforeEach ->
@connection.add('organizations', 'heliom', { name: 'Heliom' })
it 'includes row key', ->
promise = @connection.organizations.getFirst()
expect(promise).to.eventually.deep.equal \
{ name: 'Heliom', _key: 'heliom' }
describe '#getAll', ->
it 'returns all items', ->
promise = @connection.getAll('users')
expect(promise).to.eventually.deep.equal [
{ name: 'e', job: 'developer', id: 1, interests: ['a'] }
{ name: 'r', job: 'developer', id: 2, interests: ['a', 'b'] }
{ name: 'p', job: 'developer', id: 3, interests: ['b'] }
{ name: 't', job: 'designer', id: 4, interests: [] }
]
describe 'without a key', ->
beforeEach ->
@connection.add('organizations', ['heliom', 'abrico'], [{ name: 'Heliom' }, { name: 'Abrico' }])
it 'includes row key', ->
promise = @connection.organizations.getAll()
expect(promise).to.eventually.deep.equal [
{ name: 'Abrico', _key: 'abrico' }
{ name: 'Heliom', _key: 'heliom' }
]
describe '#count', ->
it 'returns items count', ->
promise = @connection.count('users')
expect(promise).to.eventually.equal 4
describe '#add', ->
describe 'with a key', ->
it 'adds an item', ->
addPromise = @connection.add('organizations', 'heliom', { name: 'heliom' })
expect(addPromise).to.eventually.deep.equal({ name: 'heliom', _key: 'heliom' })
expect(@connection.organizations.get('heliom')).to.eventually.deep.equal({ name: 'heliom' })
it 'adds multiple items', ->
addPromise = @connection.add('organizations', ['heliom', 'abrico'], [{ name: 'heliom' }, { name: 'abrico' }])
expect(addPromise).to.eventually.deep.equal([{ name: 'heliom', _key: 'heliom' }, { name: 'abrico', _key: 'abrico' }])
expect(@connection.organizations.get('heliom')).to.eventually.deep.equal({ name: 'heliom' })
expect(@connection.organizations.get('abrico')).to.eventually.deep.equal({ name: 'abrico' })
describe 'without a key', ->
it 'adds an item', ->
addPromise = @connection.add('users', { name: 'foo', id: 5 })
expect(addPromise).to.eventually.deep.equal({ name: 'foo', id: 5, _key: 5 })
expect(@connection.users.get(5)).to.eventually.deep.equal({ name: 'foo', id: 5 })
it 'adds multiple items', ->
addPromise = @connection.add('users', [{ foo: 'bar1', id: 1 }, { foo: 'bar2', id: 2 }])
expect(addPromise).to.eventually.deep.equal([{ foo: 'bar1', id: 1, _key: 1 }, { foo: 'bar2', id: 2, _key: 2 }])
expect(@connection.users.get(1)).to.eventually.deep.equal({ foo: 'bar1', id: 1 })
expect(@connection.users.get(2)).to.eventually.deep.equal({ foo: 'bar2', id: 2 })
describe '#update', ->
describe 'with a key', ->
it 'updates received params', ->
@connection.organizations.add('missive', { est: 2014 })
expect(@connection.organizations.get('missive')).to.eventually.deep.equal({ est: 2014 })
@connection.organizations.add('missive', { name: 'missive' })
expect(@connection.organizations.get('missive')).to.eventually.deep.equal({ name: 'missive' })
@connection.organizations.update('missive', { est: 2014 }).then =>
promise = @connection.organizations.get('missive')
expect(promise).to.eventually.deep.equal({ name: 'missive', est: 2014 })
it 'inserts a new entry', ->
expect(@connection.organizations.get('missive')).to.eventually.be.null
@connection.organizations.update('missive', { name: 'missive' }).then =>
promise = @connection.organizations.get('missive')
expect(promise).to.eventually.deep.equal({ name: 'missive' })
it 'deeply updates object attributes', ->
@connection.organizations.add('missive', { name: 'missive', employees: { count: 3, status: 'working' } })
@connection.organizations.update('missive', { est: 2014, employees: { count: 4 } }).then =>
promise = @connection.organizations.get('missive')
expect(promise).to.eventually.deep.equal({ name: 'missive', est: 2014, employees: { count: 4, status: 'working' } })
describe 'without a key', ->
it 'updates received params', ->
@connection.users.add({ id: 1337, name: 'Frodo' })
expect(@connection.users.get(1337)).to.eventually.deep.equal({ id: 1337, name: 'Frodo' })
@connection.users.add({ id: 1337, job: 'Ring-bearer' })
expect(@connection.users.get(1337)).to.eventually.deep.equal({ id: 1337, job: 'Ring-bearer' })
@connection.users.update(1337, { name: 'Frodo' }).then =>
promise = @connection.users.get(1337)
expect(promise).to.eventually.deep.equal({ id: 1337, name: 'Frodo', job: 'Ring-bearer' })
it 'inserts a new entry', ->
expect(@connection.users.get(1337)).to.eventually.be.null
@connection.users.update(1337, { name: 'Frodo' }).then (data) =>
promise = @connection.users.get(1337)
expect(promise).to.eventually.deep.equal({ id: 1337, name: 'Frodo' })
it 'deeply updates object attributes', ->
@connection.users.add({ id: 1337, name: 'Sam', info: { age: 33 } })
@connection.users.update(1337, { name: 'Frodo', info: { job: 'Ring-bearer' } }).then =>
promise = @connection.users.get(1337)
expect(promise).to.eventually.deep.equal({ id: 1337, name: 'Frodo', info: { age: 33, job: 'Ring-bearer' } })
describe '#increment', ->
describe 'with a key', ->
it 'increments the entry', ->
@connection.organizations.add('foo', 5)
@connection.organizations.increment('foo').then =>
promise = @connection.organizations.get('foo')
expect(promise).to.eventually.equal(6)
it 'initializes attribute to zero if null', ->
@connection.organizations.increment('foo').then =>
promise = @connection.organizations.get('foo')
expect(promise).to.eventually.equal(1)
describe 'without a key', ->
it 'increments an object attribute', ->
@connection.users.add({ id: 100, count: 5 })
@connection.users.increment(100, { count: 4 }).then =>
promise = @connection.users.get(100)
expect(promise).to.eventually.deep.equal({ id: 100, count: 9 })
it 'deeply increments multiple object attributes', ->
@connection.users.add({ id: 100, count: 1, foo: { bar: 'baz', count: 5 } })
@connection.users.increment(100, { count: 1, foo: { count: 2 }}).then =>
promise = @connection.users.get(100)
expect(promise).to.eventually.deep.equal({ id: 100, count: 2, foo: { bar: 'baz', count: 7 } })
it 'initializes attribute to zero if null', ->
@connection.users.add({ id: 100, foo: { bar: 'baz' }})
@connection.users.increment(100, { count: 1, foo: { count: 2 }}).then =>
promise = @connection.users.get(100)
expect(promise).to.eventually.deep.equal({ id: 100, count: 1, foo: { bar: 'baz', count: 2 } })
describe '#decrement', ->
describe 'with a key', ->
it 'increments the entry', ->
@connection.organizations.add('foo', 5)
@connection.organizations.decrement('foo').then =>
promise = @connection.organizations.get('foo')
expect(promise).to.eventually.equal(4)
it 'initializes attribute to zero if null', ->
@connection.organizations.decrement('foo').then =>
promise = @connection.organizations.get('foo')
expect(promise).to.eventually.equal(-1)
describe 'without a key', ->
it 'increments an object attribute', ->
@connection.users.add({ id: 100, count: 5 })
@connection.users.decrement(100, { count: 4 }).then =>
promise = @connection.users.get(100)
expect(promise).to.eventually.deep.equal({ id: 100, count: 1 })
it 'deeply increments multiple object attributes', ->
@connection.users.add({ id: 100, count: 1, foo: { bar: 'baz', count: 5 } })
@connection.users.decrement(100, { count: 1, foo: { count: 2 }}).then =>
promise = @connection.users.get(100)
expect(promise).to.eventually.deep.equal({ id: 100, count: 0, foo: { bar: 'baz', count: 3 } })
it 'initializes attribute to zero if null', ->
@connection.users.add({ id: 100, foo: { bar: 'baz' }})
@connection.users.decrement(100, { count: 1, foo: { count: 2 }}).then =>
promise = @connection.users.get(100)
expect(promise).to.eventually.deep.equal({ id: 100, count: -1, foo: { bar: 'baz', count: -2 } })
describe '#delete', ->
it 'removes an item', ->
@connection.users.delete(2)
expect(@connection.users.getAll()).to.eventually.deep.equal [
{ name: 'e', job: 'developer', id: 1, interests: ['a'] }
{ name: 'p', job: 'developer', id: 3, interests: ['b'] }
{ name: 't', job: 'designer', id: 4, interests: [] }
]
it 'removes multiple items', ->
@connection.users.delete([1, 3])
expect(@connection.users.getAll()).to.eventually.deep.equal [
{ name: 'r', job: 'developer', id: 2, interests: ['a', 'b'] }
{ name: 't', job: 'designer', id: 4, interests: [] }
]
describe '#clear', ->
it 'clears an objectStore', ->
@connection.users.clear()
expect(@connection.users.getAll()).to.eventually.deep.equal []
describe '#clearAll', ->
it 'clears all objectStores', ->
@connection.users.add({ foo: 'bar' })
@connection.organizations.add('bar', { name: 'bar' })
@connection.clearAll()
expect(@connection.users.getAll()).to.eventually.deep.equal []
expect(@connection.organizations.getAll()).to.eventually.deep.equal []
describe '#reset', ->
it 'clears an objectStore and adds items', (done) ->
@connection.users.reset({ name: 'm', job: 'janitor', id: 5 }).then =>
@connection.users.getAll().then (data) ->
expect(data).to.deep.equal [{ name: 'm', job: 'janitor', id: 5 }]
done()
describe '#index', ->
it '#get', ->
promise = @connection.users.index('job').get('designer')
expect(promise).to.eventually.deep.equal \
{ name: 't', job: 'designer', id: 4, interests: [] }
it '#getAll', ->
promise = @connection.users.index('job').getAll()
expect(promise).to.eventually.deep.equal [
{ name: 't', job: 'designer', id: 4, interests: [] }
{ name: 'e', job: 'developer', id: 1, interests: ['a'] }
{ name: 'r', job: 'developer', id: 2, interests: ['a', 'b'] }
{ name: 'p', job: 'developer', id: 3, interests: ['b'] }
]
it '#where', ->
promise = @connection.users.index('job').where(limit: 1, offset: 2)
expect(promise).to.eventually.deep.equal [
{ name: 'r', job: 'developer', id: 2, interests: ['a', 'b'] }
]
describe '#where', ->
describe ':lt', ->
it 'returns all items lower than', ->
promise = @connection.users.where(lt: 3)
expect(promise).to.eventually.deep.equal [
{ name: 'e', job: 'developer', id: 1, interests: ['a'] }
{ name: 'r', job: 'developer', id: 2, interests: ['a', 'b'] }
]
describe ':lteq', ->
it 'returns all items lower or equal to', ->
promise = @connection.users.where(lteq: 3)
expect(promise).to.eventually.deep.equal [
{ name: 'e', job: 'developer', id: 1, interests: ['a'] }
{ name: 'r', job: 'developer', id: 2, interests: ['a', 'b'] }
{ name: 'p', job: 'developer', id: 3, interests: ['b'] }
]
describe ':gt', ->
it 'returns all items greater than', ->
promise = @connection.users.where(gt: 3)
expect(promise).to.eventually.deep.equal [
{ name: 't', job: 'designer', id: 4, interests: [] }
]
describe ':gteq', ->
it 'returns all items greater or equal to', ->
promise = @connection.users.where(gteq: 3)
expect(promise).to.eventually.deep.equal [
{ name: 'p', job: 'developer', id: 3, interests: ['b'] }
{ name: 't', job: 'designer', id: 4, interests: [] }
]
describe ':eq', ->
it 'returns all items equal to', ->
promise = @connection.users.where(eq: 1)
expect(promise).to.eventually.deep.equal [
{ name: 'e', job: 'developer', id: 1, interests: ['a'] }
]
it 'supports array', ->
promise = @connection.users.where(eq: [4, 1])
expect(promise).to.eventually.deep.equal [
{ name: 'e', job: 'developer', id: 1, interests: ['a'] }
{ name: 't', job: 'designer', id: 4, interests: [] }
]
it 'merges :only when an array', ->
promise = @connection.users.where(eq: [1, 4], only: { job: 'developer' })
expect(promise).to.eventually.deep.equal [
{ name: 'e', job: 'developer', id: 1, interests: ['a'] }
]
describe ':limit', ->
it 'limits to a number', ->
promise = @connection.users.where(limit: 2)
expect(promise).to.eventually.deep.equal [
{ name: 'e', job: 'developer', id: 1, interests: ['a'] }
{ name: 'r', job: 'developer', id: 2, interests: ['a', 'b'] }
]
describe ':offset', ->
it 'offsets the items', ->
promise = @connection.users.where(offset: 2)
expect(promise).to.eventually.deep.equal [
{ name: 'p', job: 'developer', id: 3, interests: ['b'] }
{ name: 't', job: 'designer', id: 4, interests: [] }
]
describe ':only', ->
it 'returns items matching only', ->
promise = @connection.users.where(only: { job: 'developer' })
expect(promise).to.eventually.deep.equal [
{ name: 'e', job: 'developer', id: 1, interests: ['a'] }
{ name: 'r', job: 'developer', id: 2, interests: ['a', 'b'] }
{ name: 'p', job: 'developer', id: 3, interests: ['b'] }
]
describe ':contains', ->
it 'returns items that contains an item', ->
promise = @connection.users.where(contains: { interests: 'a' })
expect(promise).to.eventually.deep.equal [
{ name: 'e', job: 'developer', id: 1, interests: ['a'] }
{ name: 'r', job: 'developer', id: 2, interests: ['a', 'b'] }
]
it 'returns items that contains items', ->
promise = @connection.users.where(contains: { interests: ['a', 'b'] })
expect(promise).to.eventually.deep.equal [
{ name: 'e', job: 'developer', id: 1, interests: ['a'] }
{ name: 'r', job: 'developer', id: 2, interests: ['a', 'b'] }
{ name: 'p', job: 'developer', id: 3, interests: ['b'] }
]
describe ':except', ->
it 'returns items except matching', ->
promise = @connection.users.where(except: { job: 'developer' })
expect(promise).to.eventually.deep.equal [
{ name: 't', job: 'designer', id: 4, interests: [] }
]
describe ':uniq', ->
it 'returns only 1 item per uniq', ->
promise = @connection.users.where(uniq: 'job')
expect(promise).to.eventually.deep.equal [
{ name: 'e', job: 'developer', id: 1, interests: ['a'] }
{ name: 't', job: 'designer', id: 4, interests: [] }
]
describe ':order', ->
it 'returns items in desc order', ->
promise = @connection.users.where(order: 'desc')
expect(promise).to.eventually.deep.equal [
{ name: 'e', job: 'developer', id: 1, interests: ['a'] }
{ name: 'r', job: 'developer', id: 2, interests: ['a', 'b'] }
{ name: 'p', job: 'developer', id: 3, interests: ['b'] }
{ name: 't', job: 'designer', id: 4, interests: [] }
]
describe ':remove', ->
# Can only be tested in BrowserAdapter
(if AdapterClass is BrowserAdapter then it else it.skip) 'creates a write transaction', ->
simple.mock(@connection.connection, 'enqueue')
@connection.users.where(eq: 1, remove: true)
expect(@connection.connection.enqueue.firstCall.args[0]).to.equal('write')
it 'removes items from indexedDB', ->
@connection.users.index('job').where(eq: 'developer', remove: true).then (data) =>
promise = @connection.users.index('job').where(eq: 'developer')
expect(promise).to.eventually.deep.equal []