iface-js
Version:
412 lines (325 loc) • 14 kB
JavaScript
import {
iface,
literal,
constr,
proto,
record,
map,
array,
tuple,
any,
all,
LiteralIface,
ConstructorIface,
PrototypeIface,
RecordIface,
MapIface,
ArrayIface,
TupleIface,
AnyIface,
AllIface
} from '../dist/iface'
import {assert} from 'chai'
import {isEqual} from 'lodash'
describe('iface-js', () => {
describe('LiteralIface', () => {
it('matches values with strict equality by default', () => {
assert.ok(literal(3).match(3))
assert.notOk(literal(3).match('3'))
assert.notOk(literal({}).match({}))
})
it('matches values with provided equality function when provided', () => {
assert.ok(literal({foo: 3}, isEqual).match({foo: 3}))
assert.ok(literal(['bar'], isEqual).match(['bar']))
assert.notOk(literal(['bar'], isEqual).match({foo: 3}))
})
it('has a depth equal one', () => {
assert.strictEqual(1, literal(null).depth)
assert.strictEqual(1, literal({foo: 3}).depth)
assert.strictEqual(1, literal([3, 14, 15]).depth)
})
})
describe('ConstructorIface', () => {
it('throws an error when non-function given as a Constructor arg', () => {
assert.throw((() => constr('foo')), 'Function expected for ConstructorIface')
assert.throw((() => constr(12345)), 'Function expected for ConstructorIface')
})
it('stores provided constructor', () => {
assert.strictEqual(Number, constr(Number).constr)
})
it('matches instances of the constructor including scalars', () => {
assert.ok(constr(Number).match(3))
assert.ok(constr(String).match('foo'))
assert.ok(constr(Date).match(new Date))
})
it('does not match wrong values', () => {
assert.notOk(constr(Number).match(new Date))
assert.notOk(constr(String).match(3))
assert.notOk(constr(Date).match('foo'))
})
it('has a depth equal one', () => {
assert.strictEqual(1, constr(Number).depth)
assert.strictEqual(1, constr(Date).depth)
})
})
describe('PrototypeIface', () => {
it('throws an error when non-object given as a Constructor arg', () => {
assert.throw((() => proto('foo')), 'Object expected for PrototypeIface')
assert.throw((() => proto(12345)), 'Object expected for PrototypeIface')
})
it('stores provided prototype', () => {
let p = {}
assert.strictEqual(p, proto(p).proto)
})
it('matches descendances of the prototype including scalars', () => {
assert.ok(proto(Number.prototype).match(3))
assert.ok(proto(String.prototype).match('foo'))
assert.ok(proto(Date.prototype).match(new Date))
})
it('does not match wrong values', () => {
assert.notOk(proto(Number.prototype).match(new Date))
assert.notOk(proto(String.prototype).match(3))
assert.notOk(proto(Date.prototype).match('foo'))
})
it('has a depth equal one', () => {
assert.strictEqual(1, proto({}).depth)
assert.strictEqual(1, proto([14, 15]).depth)
})
})
describe('RecordIface', () => {
it('throws an error when non-object given', () => {
let msg = 'Expected object as RecordIface struct'
assert.throw(() => record(null), msg)
assert.throw(() => record(3), msg)
assert.throw(() => record('foo'), msg)
})
it('makes ifaces from spec properties', () => {
let sig = record({foo: 3, bar: String})
assert.instanceOf(sig.struct.foo, LiteralIface)
assert.instanceOf(sig.struct.bar, ConstructorIface)
})
it('matches when all properties presented and have right ifaces', () => {
let sig = record({foo: 3, bar: String})
assert.ok(sig.match({foo: 3, bar: 'baz'}))
})
it('matches when object has additional properties by default', () => {
let sig = record({foo: 3, bar: String})
assert.ok(sig.match({foo: 3, bar: 'baz', qux: 14}))
})
it('does not match when object has additional properties in strict mode', () => {
let sig = record({foo: 3, bar: String}, true)
assert.notOk(sig.match({foo: 3, bar: 'baz', qux: 14}))
})
it('has a depth of a max subiface depth plus one', () => {
assert.strictEqual(1, record({}).depth)
assert.strictEqual(2, record({foo: 3}).depth)
assert.strictEqual(3, record({foo: [3, 14], bar: 15}).depth)
})
})
describe('MapIface', () => {
it('makes iface from given spec and stores it', () => {
let sig1 = map('foo'),
sig2 = map(Number)
assert.instanceOf(sig1.prop, LiteralIface)
assert.instanceOf(sig2.prop, ConstructorIface)
})
it('matches empty objects', () => {
assert.ok(map(Number).match({}))
})
it('matches when stored iface match all properties', () => {
let sig1 = map(Number),
sig2 = map(record({bar: Number, baz: String}))
assert.ok(sig1.match({foo: 3, bar: 14}))
assert.ok(sig2.match({foo: {bar: 3, baz: 'bar'}, qux: {bar: 14, baz: 'mux'}}))
})
it('does not match non-objects', () => {
let sig = map(Number)
assert.notOk(sig.match('foo'))
})
it('does not match when any property does not match the iface', () => {
let sig1 = map(Number),
sig2 = map(record({bar: Number, baz: String}))
assert.notOk(sig1.match({foo: 'bar'}))
assert.notOk(sig2.match({foo: {bar: 3, baz: 'bar'}, qux: 14}))
})
it('has a depth of it\'s prop plus one', () => {
assert.strictEqual(2, map(3).depth)
assert.strictEqual(3, map([3]).depth)
assert.strictEqual(4, map({foo: 3, bar: [14]}).depth)
})
})
describe('ArrayIface', () => {
it('makes iface from given spec and stores it', () => {
let sig1 = array('foo'),
sig2 = array(Number)
assert.instanceOf(sig1.elem, LiteralIface)
assert.instanceOf(sig2.elem, ConstructorIface)
})
it('matches empty arrays', () => {
assert.ok(array(Number).match([]))
})
it('matches when stored iface match all elements', () => {
let sig1 = array(Number),
sig2 = array(record({bar: Number, baz: String}))
assert.ok(sig1.match([3, 14]))
assert.ok(sig2.match([{bar: 3, baz: 'bar'}, {bar: 14, baz: 'mux'}]))
})
it('does not match non-arrays', () => {
let sig = array(Number)
assert.notOk(sig.match('foo'))
})
it('does not match when any element does not match the iface', () => {
let sig1 = array(Number),
sig2 = array(record({bar: Number, baz: String}))
assert.notOk(sig1.match(['bar']))
assert.notOk(sig2.match([{bar: 3, baz: 'bar'}, 14]))
})
it('has a depth of it\'s elem plus one', () => {
assert.strictEqual(2, array(3).depth)
assert.strictEqual(3, array([3]).depth)
assert.strictEqual(4, array({foo: 3, bar: [14]}).depth)
})
})
describe('TupleIface', () => {
it('throws an error when non-array given', () => {
let msg = 'Array of ifaces expected for TupleIface'
assert.throw(() => tuple(null), msg)
assert.throw(() => tuple(3), msg)
assert.throw(() => tuple('foo'), msg)
})
it('makes ifaces from spec elements', () => {
let sig = tuple([3, String])
assert.instanceOf(sig.struct[0], LiteralIface)
assert.instanceOf(sig.struct[1], ConstructorIface)
})
it('matches when all elements presented and have right ifaces', () => {
let sig = tuple([3, String])
assert.ok(sig.match([3, 'foo']))
})
it('matches when object has additional elements by default', () => {
let sig = tuple([3, String])
assert.ok(sig.match([3, 'foo', 14, 'bar']))
})
it('does not match when object has additional elements in strict mode', () => {
let sig = tuple([3, String], true)
assert.notOk(sig.match([3, 'foo', 14, 'bar']))
})
it('has a depth of a max subiface depth plus one', () => {
assert.strictEqual(1, tuple([]).depth)
assert.strictEqual(2, tuple([3]).depth)
assert.strictEqual(3, tuple([3, {foo: 14}]).depth)
})
})
describe('AnyIface', () => {
it('throws an error when non-array given', () => {
let msg = 'Array of ifaces expected for AnyIface'
assert.throw(() => any(null), msg)
assert.throw(() => any(3), msg)
assert.throw(() => any('foo'), msg)
})
it('makes ifaces from spec elements', () => {
let sig = any([3, String])
assert.instanceOf(sig.struct[0], LiteralIface)
assert.instanceOf(sig.struct[1], ConstructorIface)
})
it('matches when value matches any stored ifaces', () => {
let sig = any([3, String])
assert.ok(sig.match(3))
assert.ok(sig.match('foo'))
})
it('does not match when value does not match any of stored ifaces', () => {
let sig = any([3, String])
assert.notOk(sig.match(4))
assert.notOk(sig.match({}))
assert.notOk(sig.match(new Date))
})
it('returns the same object when given null, undefined or empty array', () => {
assert.strictEqual(any(), any())
})
it('never matches anyhting when the spec is empty', () => {
assert.notOk(any().match(null))
assert.notOk(any().match('foo'))
assert.notOk(any().match(3))
assert.notOk(any().match({bar: 14}))
assert.notOk(any().match([15, 92]))
})
it('has a depth of a max subiface depth plus one', () => {
assert.strictEqual(1, any([]).depth)
assert.strictEqual(2, any([3]).depth)
assert.strictEqual(3, any([3, {foo: 14}]).depth)
})
})
describe('AllIface', () => {
it('throws an error when non-array given', () => {
let msg = 'Array of ifaces expected for AllIface'
assert.throw(() => all(null), msg)
assert.throw(() => all(3), msg)
assert.throw(() => all('foo'), msg)
})
it('makes ifaces from spec elements', () => {
let sig = all([3, String])
assert.instanceOf(sig.struct[0], LiteralIface)
assert.instanceOf(sig.struct[1], ConstructorIface)
})
it('matches when value matches all stored ifaces', () => {
let sig = all([map(Number), record({foo: Number})])
assert.ok(sig.match({foo: 3}))
assert.ok(sig.match({foo: 3, bar: 14}))
})
it('does not match when value does not match any of stored ifaces', () => {
let sig = all([map(Number), record({foo: Number})])
assert.notOk(sig.match(3))
assert.notOk(sig.match({}))
assert.notOk(sig.match({bar: 3}))
})
it('returns the same object when given null, undefined or empty array', () => {
assert.strictEqual(all(), all())
})
it('matches all values when the spec is empty', () => {
assert.ok(all().match(null))
assert.ok(all().match('foo'))
assert.ok(all().match(3))
assert.ok(all().match({bar: 14}))
assert.ok(all().match([15, 92]))
})
it('has a depth of a max subiface depth plus one', () => {
assert.strictEqual(1, all([]).depth)
assert.strictEqual(2, all([3]).depth)
assert.strictEqual(3, all([3, {foo: 14}]).depth)
})
})
describe('iface()', () => {
it('turns scalar into LiteralIface', () => {
let sig = iface(14)
assert.instanceOf(sig, LiteralIface)
assert.strictEqual(sig.val, 14)
})
it('turns function into ConstructorIface', () => {
let sig = iface(String)
assert.instanceOf(sig, ConstructorIface)
assert.strictEqual(sig.constr, String)
})
it('turns array into ArrayIface', () => {
let sig = iface([Number])
assert.instanceOf(sig, ArrayIface)
assert.instanceOf(sig.elem, ConstructorIface)
assert.strictEqual(sig.elem.constr, Number)
})
it('turns objects with registered fields into correspondent ifaces', () => {
let sig1 = iface({$tuple: [Number, String]}),
sig2 = iface({$array: Date})
assert.instanceOf(sig1, TupleIface)
assert.instanceOf(sig2, ArrayIface)
})
it('takes a nested spec and creates a complex iface', () => {
let sig = iface({$tuple: [
{$record: {foo: Number, bar: String}},
{$any: ['baz', [Date]]}
]})
assert.ok(sig.match([{foo: 3, bar: '14'}, 'baz']))
assert.ok(sig.match([{foo: 15, bar: 'z'}, [new Date, new Date]]))
assert.notOk(sig.match([{foo: 3, bar: '14'}]))
assert.notOk(sig.match([null, [new Date]]))
})
})
})