objectstate
Version:
Lightweight, streaming state management.
421 lines (290 loc) • 7.9 kB
JavaScript
// Copyright 2014 Urban Airship and Contributors
var EE = require('events').EventEmitter
var isError = require('util').isError
var through = require('through')
var test = require('tape')
var objectState = require('./')
test('is a function', function (assert) {
assert.equal(typeof objectState, 'function')
assert.end()
})
test('throws error if initialized with a bad state', function (assert) {
assert.plan(3)
assert.throws(function () {
objectState(5)
})
assert.throws(function () {
objectState('lol')
})
assert.throws(function () {
objectState(function () {})
})
})
test('emits error if written to with a bad state', function (assert) {
assert.plan(3)
var os = objectState()
os.on('error', function (err) {
assert.true(isError(err))
})
os.write(5)
os.write('lol')
os.write(function () {})
})
test('is a readable stream', function (assert) {
assert.plan(1)
var ws = through(write)
var ee = new EE()
function write (obj) {
assert.deepEqual(obj, {salutation: 'sup'})
}
var os = objectState()
os.pipe(ws)
os.listenOn(ee, 'data', ['salutation'])
ee.emit('data', 'sup')
})
test('can write to object state', function (assert) {
assert.plan(1)
var os = objectState()
var testState = {cats: true}
os.on('data', function (state) {
assert.deepEqual(state, testState)
})
os.write(testState)
})
test('is a writable stream', function (assert) {
assert.plan(1)
var os = objectState()
var stream = through()
var testState = {cats: true}
stream.pipe(os)
os.on('data', function (state) {
assert.deepEqual(state, testState)
})
stream.queue(testState)
})
test('`.emitState()` emits current state', function (assert) {
assert.plan(1)
var os = objectState({cats: true})
os.on('data', function (state) {
assert.deepEqual(state, {cats: true})
})
os.emitState()
})
test('`.listen()` listens to streams for attributes', function (assert) {
assert.plan(3)
var stream = through()
var os = objectState()
var otherStream = through()
var cats = ['top cat', 'smarf']
var dogs = ['droopy d']
os.once('data', function (state) {
assert.deepEqual(state, {cats: cats})
})
os.listen(stream, 'cats')
stream.queue(cats)
os.once('data', function (state) {
assert.deepEqual(state, {cats: cats, dogs: dogs})
})
os.listen(otherStream, 'dogs')
otherStream.queue(dogs)
otherStream.end()
os.once('data', function (state) {
assert.deepEqual(state, {cats: cats, dogs: dogs})
})
os.emitState()
})
test('`.listenOn()` maps emitted values to parameters', function (assert) {
assert.plan(2)
var eeOne = new EE()
var eeTwo = new EE()
var os = objectState()
os.listenOn(eeOne, 'data', ['hat', 'bat', 'cat'])
os.listenOn(eeTwo, 'data', ['rat', null, 'cat'])
os.once('data', function (state) {
assert.deepEqual(state, {hat: 1, bat: 2, cat: 3})
})
eeOne.emit('data', 1, 2, 3)
os.once('data', function (state) {
assert.deepEqual(state, {rat: 4, hat: 1, bat: 2})
})
eeTwo.emit('data', 4, 5, undefined)
})
test('`.wait()` will hold events within given function', function (assert) {
var value2 = Math.random() + 10
var value1 = Math.random()
var os = objectState()
var ee = new EE()
var count = 0
os.listenOn(ee, 'data', ['value'])
os.on('data', function (state) {
++count
assert.equal(state.value, value2 + (count === 2 ? 1 : 0))
})
os.wait(function () {
ee.emit('data', value1)
ee.emit('data', value2)
})
assert.equal(count, 1)
ee.emit('data', value2 + 1)
assert.equal(count, 2)
assert.end()
})
test('`.wait()` sends no event if nothing was emitted', function (assert) {
var os = objectState()
var ee = new EE()
var count = 0
os.listenOn(ee, 'data', ['value'])
os.on('data', function () {
++count
})
os.wait(noop)
assert.equal(count, 0)
assert.end()
})
test('does not emit if value is unchanged', function (assert) {
assert.plan(1)
var os = objectState()
var ee = new EE()
var count = 0
os.listen(ee, 'cats')
os.on('data', function () {
++count
})
ee.emit('data', true)
ee.emit('data', true)
os.set('cats', true)
os.write({cats: true})
os.remove('dogs')
assert.equal(count, 1)
})
test('always emits copies', function (assert) {
assert.plan(4)
var original = {cats: true}
var written = {cats: false}
var os = objectState(original)
os.once('data', function (state) {
// verifies that the state and original do not share a reference,
// but that they are identical otherwise.
assert.notEqual(state, original)
assert.deepEqual(state, original)
})
os.emitState()
os.once('data', function (state) {
assert.notEqual(state, written)
assert.deepEqual(state, written)
})
os.write(written)
})
test('`.state()` returns deep copy of state', function (assert) {
assert.plan(3)
var expect = {}
var inner = {}
var result
var os
expect.inner = inner
expect.inner.a = Math.random()
expect.inner.b = [Math.random()]
os = objectState(expect)
result = os.state()
assert.notEqual(result.inner, expect.inner)
assert.notEqual(result, expect)
assert.deepEqual(result, expect)
})
test('emits a deep-copy of state', function (assert) {
assert.plan(4)
var original = {cats: true}
var result2
var result
var os
os = objectState(original)
os.once('data', function (state) {
result = state
})
os.emitState()
assert.deepEqual(original, result)
assert.notEqual(original, result)
os.once('data', function (state) {
result2 = state
})
os.emitState()
assert.deepEqual(result, result2)
assert.notEqual(result, result2)
})
test('deep copies on write', function (assert) {
assert.plan(2)
var written = {cats: {hats: true}}
var os = objectState()
os.write(written)
os.set('cats.hats', false)
assert.equal(written.cats.hats, true)
assert.equal(os.get('cats.hats'), false)
})
test('can nest `.wait()` statements', function (assert) {
assert.plan(1)
var os = objectState()
var stream = through()
var count = 0
os.listen(stream, 'herp')
os.on('data', function () {
++count
})
os.wait(function () {
os.wait(function () {
stream.queue('flerp')
stream.queue('werp')
})
stream.queue('herp')
stream.queue('merp')
})
assert.equal(count, 1)
})
test('can set attr via `.set()` method', function (assert) {
assert.plan(2)
var os = objectState()
os.once('data', function (state) {
assert.deepEqual(state, {herp: 'derp'})
})
os.set('herp', 'derp')
os.once('data', function (state) {
assert.deepEqual(state, {herp: 'derp', derp: {herp: 'flerp'}})
})
os.set('derp.herp', 'flerp')
})
test('can get attr via `.get()` method', function (assert) {
assert.plan(4)
var os = objectState({a: false, b: '1', d: {hey: 'cats'}})
assert.equal(os.get('a'), false)
assert.equal(os.get('b'), '1')
assert.equal(os.get('c'), undefined)
assert.equal(os.get('d.hey'), 'cats')
})
test('can remove an attr via `.remove()`', function (assert) {
assert.plan(2)
var os = objectState({why: 'yes', no: {sure: 'whynot'}})
os.once('data', function (state) {
assert.deepEqual(state, {no: {sure: 'whynot'}})
})
os.remove('why')
os.once('data', function (state) {
assert.deepEqual(state, {no: {}})
})
os.remove('no.sure')
})
test('correctly sets values with understanding of type', function (assert) {
assert.plan(1)
var os = objectState({cats: true})
os.set('cats', [])
assert.notEqual(true, os.get('cats'))
})
test('drops writes of `undefined`', function (assert) {
assert.plan(1)
var os = objectState({cats: true})
os.on('data', function () {
assert.fail()
})
os.write(undefined)
assert.deepEqual(os.state(), {cats: true})
})
function noop () {
// no-op
}