UNPKG

iface-js

Version:
412 lines (325 loc) 14 kB
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]])) }) }) })