UNPKG

@platform/cell.typesystem

Version:

The 'strongly typed sheets' system of the CellOS.

381 lines (380 loc) 17.3 kB
import { expect, Uri } from '../test'; import { TypeBuilder } from '.'; import { TypeBuilderNs } from './TypeBuilderNs'; import { TypeBuilderType } from './TypeBuilderType'; describe('TypeBuilder', () => { it('create', () => { const builder = TypeBuilder.create(); expect(builder).to.be.an.instanceof(TypeBuilder); }); describe('toObject', () => { it('empty', () => { const builder = TypeBuilder.create(); expect(builder.toObject()).to.eql({}); }); it('empty namespaces', () => { const builder = TypeBuilder.create(); builder.ns('foo.1'); builder.ns('foo.2'); expect(builder.toObject()).to.eql({ 'ns:foo.1': { columns: {} }, 'ns:foo.2': { columns: {} }, }); }); it('simple', () => { var _a, _b, _c, _d; const builder = TypeBuilder.create(); builder .ns('foo') .type('MyType') .prop(' title ', { target: 'inline:title', default: 'Untitled' }) .prop('count', 'number'); const obj = builder.toObject(); const columns = obj['ns:foo'].columns; const A = (_b = (_a = columns.A) === null || _a === void 0 ? void 0 : _a.props) === null || _b === void 0 ? void 0 : _b.def; const B = (_d = (_c = columns.B) === null || _c === void 0 ? void 0 : _c.props) === null || _d === void 0 ? void 0 : _d.def; expect(A.prop).to.eql('MyType.title'); expect(A.type).to.eql('string'); expect(A.target).to.eql('inline:title'); expect(A.default).to.eql('Untitled'); expect(B.prop).to.eql('MyType.count'); expect(B.type).to.eql('number'); expect(B.target).to.eql(undefined); expect(B.default).to.eql(undefined); }); it('multi-type', () => { var _a, _b; const builder = TypeBuilder.create(); const ns = builder.ns('foo'); ns.type('Type1').prop('foo'); ns.type('Type2').prop('foo', prop => prop.type('number').default(123)); const obj = builder.toObject(); const columns = obj['ns:foo'].columns; const A = (_b = (_a = columns.A) === null || _a === void 0 ? void 0 : _a.props) === null || _b === void 0 ? void 0 : _b.def; expect(A[0].prop).to.eql('Type1.foo'); expect(A[0].type).to.eql('string'); expect(A[0].target).to.eql(undefined); expect(A[0].default).to.eql(undefined); expect(A[1].prop).to.eql('Type2.foo'); expect(A[1].type).to.eql('number'); expect(A[1].target).to.eql(undefined); expect(A[1].default).to.eql(123); }); describe('lookup namespace reference for typename (starts with "/")', () => { it('from explicit namespaces', () => { var _a, _b, _c, _d; const builder = TypeBuilder.create(); const type1 = builder.ns('foo.1').type('Type1'); const type2 = builder.ns('foo.2').type('Type2'); type1.prop('one', '/Type2[]'); type2.prop('two', prop => prop.type('/Type1').target('ref')); const obj = builder.toObject(); const columns1 = obj['ns:foo.1'].columns; const columns2 = obj['ns:foo.2'].columns; const def1 = (_b = (_a = columns1.A) === null || _a === void 0 ? void 0 : _a.props) === null || _b === void 0 ? void 0 : _b.def; const def2 = (_d = (_c = columns2.A) === null || _c === void 0 ? void 0 : _c.props) === null || _d === void 0 ? void 0 : _d.def; expect(def1.type).to.eql('ns:foo.2/Type2[]'); expect(def2.type).to.eql('ns:foo.1/Type1'); }); it('from generated namespaces (eg. builder.type(typename)) ', () => { var _a, _b, _c, _d; const builder = TypeBuilder.create(); const type1 = builder.type('Type1'); const type2 = builder.type('Type2'); type1.prop('one', '/Type2[]'); type2.prop('two', prop => prop.type('/Type1').target('ref')); const obj = builder.toObject(); const columns1 = obj[type1.uri.toString()].columns; const columns2 = obj[type2.uri.toString()].columns; const def1 = (_b = (_a = columns1.A) === null || _a === void 0 ? void 0 : _a.props) === null || _b === void 0 ? void 0 : _b.def; const def2 = (_d = (_c = columns2.A) === null || _c === void 0 ? void 0 : _c.props) === null || _d === void 0 ? void 0 : _d.def; expect(def1.type).to.eql(`${type2.uri.toString()}/Type2[]`); expect(def2.type).to.eql(`${type1.uri.toString()}/Type1`); }); it('throw: failed lookup (not found)', () => { const builder = TypeBuilder.create(); const foo1 = builder.ns('foo.1').type('Type1'); foo1.prop('one', '/Type2'); const fn = () => builder.toObject(); expect(fn).to.throw(/Failed to prefix type '\/Type2' with namespace/); }); }); }); describe('builder.ns', () => { it('from uri: string', () => { const ns = TypeBuilder.create().ns('foo'); expect(ns).to.be.an.instanceof(TypeBuilderNs); expect(ns.uri.toString()).to.eql('ns:foo'); }); it('from uri: object', () => { const uri = Uri.ns('foo'); const ns = TypeBuilder.create().ns(uri); expect(ns).to.be.an.instanceof(TypeBuilderNs); expect(ns.uri.toString()).to.eql('ns:foo'); }); describe('errors', () => { it('throw: namespace already added', () => { const builder = TypeBuilder.create(); builder.ns('foo'); const fn = () => builder.ns('foo'); expect(fn).to.throw(/namespace 'ns:foo' already exists/); }); }); }); describe('builder.type (generate namespace)', () => { it('typename', () => { const builder = TypeBuilder.create(); const res = builder.type('MyType'); expect(res).to.be.an.instanceof(TypeBuilderType); expect(res.typename).to.eql('MyType'); expect(res.uri.type).to.eql('NS'); expect(res.uri.id.length).to.greaterThan(10); }); it('typename (options)', () => { const builder = TypeBuilder.create(); const res = builder.type('MyType', { startColumn: 3 }); expect(res.typename).to.eql('MyType'); expect(res.startColumn).to.eql(3); }); }); describe('builder.ns.type', () => { it('typename', () => { const ns = TypeBuilder.create().ns('foo'); const type = ns.type(' MyType '); expect(type).to.be.an.instanceof(TypeBuilderType); expect(type.uri.toString()).to.eql('ns:foo'); expect(type.typename).to.eql('MyType'); }); it('store type-builder reference', () => { const ns = TypeBuilder.create().ns('foo'); const type = ns.type('MyType'); expect(ns.types.some(item => item === type)).to.eql(true); }); it('startColumn: "A" ➔ 0', () => { const test = (startColumn, expected) => { const type = TypeBuilder.create() .ns('foo') .type('MyType', { startColumn }); expect(type.startColumn).to.eql(expected); }; test('A', 0); test('B', 1); test(0, 0); test(99, 99); }); describe('errors', () => { it('throw: invalid typename', () => { const test = (typename) => { const ns = TypeBuilder.create().ns('foo'); const fn = () => ns.type(typename); expect(fn).to.throw(); }; test('foo'); test('Foo Bar'); }); it('throw: duplicate typename', () => { const ns = TypeBuilder.create().ns('foo'); ns.type('MyType'); const fn = () => ns.type('MyType'); expect(fn).to.throw(/The typename 'MyType' already exists/); }); it('throw: startColumn not valid (eg "A1" rather than "A")', () => { const test = (startColumn) => { const ns = TypeBuilder.create().ns('foo'); const fn = () => ns.type('MyType', { startColumn }); expect(fn).to.throw(); }; test('A1'); test('1'); test(-1); }); }); }); describe('builder.ns.type.prop', () => { it('prop (no param) - default "string"', () => { const ns = TypeBuilder.create().ns('foo'); const type = ns.type('MyType'); const res = type.prop('foo'); expect(res.props.length).to.eql(1); const prop = res.props[0].toObject(); expect(prop.name).to.eql('foo'); expect(prop.type).to.eql('string'); }); it('prop (param: string)', () => { const ns = TypeBuilder.create().ns('foo'); const type = ns.type('MyType'); const res = type .prop('foo', 'string') .prop('bar', 'number') .prop('baz', '"red" | "green"'); expect(res.props.length).to.eql(3); const test = (prop, name, type) => { const obj = prop.toObject(); expect(obj.name).to.eql(name); expect(obj.type).to.eql(type); expect(obj.default).to.eql(undefined); expect(obj.target).to.eql(undefined); }; test(res.props[0], 'foo', 'string'); test(res.props[1], 'bar', 'number'); test(res.props[2], 'baz', `"red" | "green"`); }); it('prop (param: {options} | fn)', async () => { const ns = TypeBuilder.create().ns('foo'); const type1 = ns.type('Type1'); const type2 = ns.type('Type2'); const res1 = type1 .prop('foo', { type: 'string', target: 'inline:title', default: 'Untitled' }) .prop('bar', { type: 'number', default: 123 }) .prop('baz', { type: '"red" | "green"', target: 'inline:baz.color', default: 'red' }); const res2 = type2 .prop('foo', prop => prop .type('string') .default('Untitled') .target('inline:title')) .prop('bar', prop => prop .type('number') .default(123) .target('ref')) .prop('baz', prop => prop .type('"red" | "green"') .default('red') .target('inline:baz.color')); expect(res1.props.length).to.eql(3); const test = (prop, name, type, target, defaultValue) => { const obj = prop.toObject(); expect(obj.name).to.eql(name); expect(obj.type).to.eql(type); expect(obj.target).to.eql(target); expect(obj.default).to.eql(defaultValue); }; test(res1.props[0], 'foo', 'string', 'inline:title', 'Untitled'); test(res1.props[1], 'bar', 'number', undefined, 123); test(res1.props[2], 'baz', '"red" | "green"', 'inline:baz.color', 'red'); test(res2.props[0], 'foo', 'string', 'inline:title', 'Untitled'); test(res2.props[1], 'bar', 'number', 'ref', 123); test(res2.props[2], 'baz', '"red" | "green"', 'inline:baz.color', 'red'); }); describe('column', () => { it('starts at column 0 ("A")', () => { const type = TypeBuilder.create() .ns('foo') .type('MyType') .prop('title'); const prop = type.props[0].toObject(); expect(prop.column).to.eql('A'); }); it('starts at column 2 ("C")', () => { const ns = TypeBuilder.create().ns('foo'); const type1 = ns.type('MyType1', { startColumn: 2 }); const type2 = ns.type('MyType2', { startColumn: 'C' }); expect(type1.startColumn).to.eql(2); expect(type2.startColumn).to.eql(2); const res1 = type1.prop('foo').props[0].toObject(); const res2 = type2.prop('foo').props[0].toObject(); expect(res1.column).to.eql('C'); expect(res2.column).to.eql('C'); }); it('auto-increments column', () => { const ns = TypeBuilder.create().ns('foo'); const type = ns .type('MyType') .prop('foo') .prop('bar'); const props = type.props.map(prop => prop.toObject()); expect(props[0].column).to.eql('A'); expect(props[1].column).to.eql('B'); }); it('auto-increments column (start offset)', () => { const ns = TypeBuilder.create().ns('foo'); const type = ns .type('MyType', { startColumn: 'C' }) .prop('foo') .prop('bar'); const props = type.props.map(prop => prop.toObject()); expect(props[0].column).to.eql('C'); expect(props[1].column).to.eql('D'); }); it('explicitly sets column in {options} param', () => { const type = TypeBuilder.create() .ns('foo') .type('MyType') .prop('foo1') .prop('foo2', { column: 'E' }) .prop('foo3'); const columns = type.props.map(prop => prop.toObject().column); expect(columns).to.eql(['A', 'E', 'F']); }); it('explicitly sets column in [fn] param', () => { const type = TypeBuilder.create() .ns('foo') .type('MyType') .prop('foo1') .prop('foo2', prop => prop.column('E')) .prop('foo3') .prop('foo4', { column: 1 }) .prop('foo5'); const columns = type.props.map(prop => prop.toObject().column); expect(columns).to.eql(['A', 'E', 'F', 'B', 'G']); }); }); describe('errors', () => { it('throw: invalid property name', () => { const test = (name) => { const type = TypeBuilder.create() .ns('foo') .type('MyType'); const fn = () => type.prop(name); expect(fn).to.throw(); }; test('foo.1'); test('foo 1'); test('1foo'); test('foo*'); }); it('throw: property name already used', () => { const type = TypeBuilder.create() .ns('foo') .type('MyType') .prop('foo'); const fn = () => type.prop(' foo '); expect(fn).to.throw(/property named 'foo' has already been added/); }); it('throw: target invalid', () => { const test = (target) => { const type = TypeBuilder.create() .ns('foo') .type('MyType'); const fn = () => type.prop('foo', prop => prop.target(target)); expect(fn).to.throw(); }; test('-'); test('foobar'); }); it('throw: target type (UNKNOWN)', () => { const test = (value) => { const type = TypeBuilder.create() .ns('foo') .type('MyType'); const fn = () => type.prop('foo', prop => prop.type(value)); expect(fn).to.throw(); }; test('-'); test(''); }); it('throw: column invalid', () => { const test = (value) => { const type = TypeBuilder.create() .ns('foo') .type('MyType'); const fn = () => type.prop('foo', prop => prop.column(value)); expect(fn).to.throw(); }; test(-1); test('-1'); test(''); test(' '); }); }); }); });