From 30baf5380acc6b5b145ff232a3fc6c62eae2ceed Mon Sep 17 00:00:00 2001 From: Victor Nakoryakov Date: Thu, 16 Feb 2017 13:55:04 +0300 Subject: [PATCH] refactor(xod-project): introduce $Pin type --- packages/xod-project/src/flatten.js | 4 +- packages/xod-project/src/patch.js | 3 +- packages/xod-project/src/pin.js | 65 ++++----------- packages/xod-project/src/types.js | 37 ++++++++- packages/xod-project/src/utils.js | 18 ++++ packages/xod-project/test/helpers.js | 12 ++- packages/xod-project/test/patch.spec.js | 33 ++++---- packages/xod-project/test/pin.spec.js | 106 ------------------------ 8 files changed, 99 insertions(+), 179 deletions(-) diff --git a/packages/xod-project/src/flatten.js b/packages/xod-project/src/flatten.js index fbfb5918..bd39bb2f 100644 --- a/packages/xod-project/src/flatten.js +++ b/packages/xod-project/src/flatten.js @@ -13,8 +13,8 @@ import { err, errOnNothing } from './func-tools'; const terminalRegExp = /^xod\/core\/(input|output)/; // :: String -> Pin[] const getTerminalPins = type => ([ - { key: '__in__', type, direction: CONST.PIN_DIRECTION.INPUT }, - { key: '__out__', type, direction: CONST.PIN_DIRECTION.OUTPUT }, + explode(Pin.createPin('__in__', type, CONST.PIN_DIRECTION.INPUT)), + explode(Pin.createPin('__out__', type, CONST.PIN_DIRECTION.OUTPUT)), ]); // :: String -> String const getTerminalType = type => `terminal${type}`; diff --git a/packages/xod-project/src/patch.js b/packages/xod-project/src/patch.js index f6a70210..7cb75390 100644 --- a/packages/xod-project/src/patch.js +++ b/packages/xod-project/src/patch.js @@ -209,7 +209,8 @@ export const getNodeById = def( * @param {Patch} patch * @returns {Either} */ -export const assocPin = R.curry( +export const assocPin = def( + 'assocPin :: Pin -> Patch -> Either Error Patch', (pin, patch) => Pin.validatePin(pin).map( (validPin) => { const key = Pin.getPinKey(validPin); diff --git a/packages/xod-project/src/pin.js b/packages/xod-project/src/pin.js index a9f1c6f4..34e97bc2 100644 --- a/packages/xod-project/src/pin.js +++ b/packages/xod-project/src/pin.js @@ -1,7 +1,9 @@ import R from 'ramda'; +import { Either } from 'ramda-fantasy'; import * as Tools from './func-tools'; import * as Utils from './utils'; import * as CONST from './constants'; +import { def } from './types'; /** * An object representing patch pin @@ -114,54 +116,12 @@ export const isTerminalPin = R.compose( getPinKey ); -/** - * Validates for correct pin type - * - * @function validatePinType - * @param {PIN_TYPE} type - * @returns {Either} - */ -export const validatePinType = Tools.errOnFalse( - CONST.ERROR.PIN_TYPE_INVALID, - R.flip(Tools.hasPropEq)(CONST.PIN_TYPE) +// TODO: remove me +export const validatePin = def( + 'validatePin :: Pin -> Either Error Pin', + Either.of ); -/** - * Validates for correct pin direction - * - * @function validatePinDirection - * @param {PIN_DIRECTION} type - * @returns {Either} - */ -export const validatePinDirection = Tools.errOnFalse( - CONST.ERROR.PIN_DIRECTION_INVALID, - R.flip(Tools.hasPropEq)(CONST.PIN_DIRECTION) -); - -/** - * Validates for correct pin key - * - * @function validatePinKey - * @param {string} pinKey - * @returns {Either} - */ -export const validatePinKey = Tools.errOnFalse( - CONST.ERROR.PIN_KEY_INVALID, - Utils.validateId -); - -/** - * Validates pin correctness - * - * @function validatePin - * @param {Pin} pin - * @returns {Either} - */ -export const validatePin = pin => validatePinType(getPinType(pin)) - .chain(() => validatePinDirection(getPinDirection(pin))) - .chain(() => validatePinKey(getPinKey(pin))) - .map(R.always(pin)); - /** * @function createPin * @param {string} key @@ -169,6 +129,15 @@ export const validatePin = pin => validatePinType(getPinType(pin)) * @param {PIN_DIRECTION} direction * @returns {Either} */ -export const createPin = R.curry( - (key, type, direction) => validatePin({ key, type, direction }) +export const createPin = def( + 'createPin :: PinKey -> DataType -> PinDirection -> Either Error Pin', + (key, type, direction) => Either.of({ + key, + type, + direction, + label: key, + description: '', + order: 0, + value: Utils.defaultValueOfType(type), + }) ); diff --git a/packages/xod-project/src/types.js b/packages/xod-project/src/types.js index 39c7baa0..5c304e64 100644 --- a/packages/xod-project/src/types.js +++ b/packages/xod-project/src/types.js @@ -3,6 +3,7 @@ import R from 'ramda'; import RF from 'ramda-fantasy'; import $ from 'sanctuary-def'; import HMDef from 'hm-def'; +import * as C from './constants'; /* Types are by convention starts with a capital leter, so: */ /* eslint-disable new-cap */ @@ -13,12 +14,26 @@ import HMDef from 'hm-def'; // //----------------------------------------------------------------------------- +// :: String -> String +const qualifiedTypeName = typeName => `xod-project/${typeName}`; + +// :: String -> String +const typeUrl = typeName => `http://xod.io/docs/dev/xod-project/#${typeName}`; + +// :: (String, Any -> Boolean) -> Type const NullaryType = (typeName, predicate) => $.NullaryType( - `xod-project/${typeName}`, - `http://xod.io/docs/dev/xod-project/#${typeName}`, + qualifiedTypeName(typeName), + typeUrl(typeName), predicate ); +// :: (String, [Any]) -> Type +const EnumType = (typeName, values) => $.EnumType( + qualifiedTypeName(typeName), + typeUrl(typeName), + values +); + // :: Type -> Any -> Boolean const hasType = type => x => type._test(x); @@ -78,7 +93,19 @@ export const LinkId = AliasType('LinkId', ShortId); export const NodeId = AliasType('NodeId', ShortId); export const PinKey = AliasType('PinKey', $.String); export const PatchPath = AliasType('PatchPath', $.String); -export const Pin = AliasType('Pin', $.Object); // TODO: enforce model +export const PinDirection = EnumType('PinDirection', R.values(C.PIN_DIRECTION)); +export const DataType = EnumType('DataType', R.values(C.PIN_TYPE)); +export const DataValue = NullaryType('DataValue', R.complement(R.isNil)); + +export const Pin = Model('Pin', { + key: PinKey, + direction: PinDirection, + label: Label, + type: DataType, + value: DataValue, + order: $.Number, + description: $.String, +}); export const NodePosition = Model('NodePosition', { x: $.Number, @@ -128,8 +155,12 @@ export const env = $.env.concat([ NodeId, NodeOrId, Patch, + Pin, PinKey, PinRef, + PinDirection, + DataType, + DataValue, ShortId, Label, Source, diff --git a/packages/xod-project/src/utils.js b/packages/xod-project/src/utils.js index 98aaf0cd..a92d3c7b 100644 --- a/packages/xod-project/src/utils.js +++ b/packages/xod-project/src/utils.js @@ -5,6 +5,7 @@ import shortid from 'shortid'; import * as Node from './node'; import * as Tools from './func-tools'; import * as CONST from './constants'; +import { def } from './types'; /** * Contains resulting value or error @@ -138,6 +139,23 @@ export const validateId = R.test(/^[a-zA-Z0-9\-_]+$/); */ export const getCastPatchPath = (typeIn, typeOut) => `xod/core/cast-${typeIn}-to-${typeOut}`; +/** + * Returns a default (empty) value for a given data type. + * + * @function defaultValueOfType + * @param {PIN_TYPE} t + * @returns {*} + */ +export const defaultValueOfType = def( + 'defaultValueOfType :: DataType -> DataValue', + R.cond([ + [ R.equals(CONST.PIN_TYPE.STRING), R.always('') ], + [ R.equals(CONST.PIN_TYPE.NUMBER), R.always(0) ], + [ R.equals(CONST.PIN_TYPE.BOOLEAN), R.always(false) ], + [ R.equals(CONST.PIN_TYPE.PULSE), R.always(false) ], + ]) +); + // ============================================================================= // // Transforming node ids in the patch diff --git a/packages/xod-project/test/helpers.js b/packages/xod-project/test/helpers.js index f20a5c3a..2c467199 100644 --- a/packages/xod-project/test/helpers.js +++ b/packages/xod-project/test/helpers.js @@ -101,12 +101,22 @@ export const defaultizeNode = R.merge({ type: '@/defaultType', }); +export const defaultizePin = R.merge({ + key: '$$defaultPinKey', + direction: '$$defaultDirection', + label: '$$defaultLabel', + type: 'number', + value: 0, + order: 0, + description: '$$defaultDesription', +}); + export const defaultizePatch = R.compose( R.evolve({ nodes: R.map(defaultizeNode), links: R.map(defaultizeLink), impls: R.identity, - pins: R.identity, + pins: R.map(defaultizePin), }), R.merge({ nodes: {}, diff --git a/packages/xod-project/test/patch.spec.js b/packages/xod-project/test/patch.spec.js index 477fcf02..dafe5955 100644 --- a/packages/xod-project/test/patch.spec.js +++ b/packages/xod-project/test/patch.spec.js @@ -431,17 +431,13 @@ describe('Patch', () => { // entity setters describe('assocPin', () => { - it('should return Either.Left for invalid pin', () => { - const newPatch = Patch.assocPin({}, {}); - expect(newPatch.isLeft).to.be.true(); - }); it('should return Either.Right with new patch with new pin', () => { - const pin = { + const pin = Helper.defaultizePin({ key: 'A', type: CONST.PIN_TYPE.STRING, direction: CONST.PIN_DIRECTION.OUTPUT, - }; - const newPatch = Patch.assocPin(pin, {}); + }); + const newPatch = Patch.assocPin(pin, emptyPatch); Helper.expectEither( (validPatch) => { @@ -455,17 +451,17 @@ describe('Patch', () => { ); }); it('should not affect on other pins', () => { - const patchWithPins = { + const patchWithPins = Helper.defaultizePatch({ pins: { A: { key: 'A', type: CONST.PIN_TYPE.NUMBER, direction: CONST.PIN_DIRECTION.INPUT }, C: { key: 'C', type: CONST.PIN_TYPE.STRING, direction: CONST.PIN_DIRECTION.OUTPUT }, }, - }; - const pin = { + }); + const pin = Helper.defaultizePin({ key: 'B', type: CONST.PIN_TYPE.BOOLEAN, direction: CONST.PIN_DIRECTION.INPUT, - }; + }); const newPatch = Patch.assocPin(pin, patchWithPins); const expectedPatch = R.assocPath(['pins', pin.key], pin, patchWithPins); @@ -562,7 +558,7 @@ describe('Patch', () => { expect(newPatch) .to.have.property('pins') .that.have.property('1') - .that.have.keys(['key', 'type', 'direction']); + .that.include.keys('key', 'type', 'direction'); }); it('should update pin by associating pinNode', () => { const patch = Helper.defaultizePatch({ @@ -581,12 +577,13 @@ describe('Patch', () => { const newPatch = Patch.assocNode(node, patch); expect(newPatch) .to.have.property('pins') - .that.have.property('1') - .that.deep.equal({ - key: '1', - type: 'number', - direction: 'input', - }); + .that.have.property('1'); + + + const newPin = newPatch.pins['1']; + expect(newPin).to.have.property('key', '1'); + expect(newPin).to.have.property('type', 'number'); + expect(newPin).to.have.property('direction', 'input'); }); }); // TODO: Add test for deleting pinNode diff --git a/packages/xod-project/test/pin.spec.js b/packages/xod-project/test/pin.spec.js index 23d16e50..9e14cb9b 100644 --- a/packages/xod-project/test/pin.spec.js +++ b/packages/xod-project/test/pin.spec.js @@ -9,112 +9,6 @@ import * as Helper from './helpers'; chai.use(dirtyChai); describe('Pin', () => { - // Validations - describe('validatePinType', () => { - it('should return Either.Left for invalid type', () => { - const invalid = Pin.validatePinType('a'); - expect(invalid.isLeft).to.be.true(); - Helper.expectErrorMessage(expect, invalid, CONST.ERROR.PIN_TYPE_INVALID); - }); - it('should return Either.Right for valid type', () => { - const type = CONST.PIN_TYPE.NUMBER; - const valid = Pin.validatePinType(type); - expect(valid.isRight).to.be.true(); - Helper.expectEither( - (val) => { expect(val).to.be.equal(type); }, - valid - ); - }); - }); - describe('validatePinDirection', () => { - it('should return Either.Left for invalid direction', () => { - const invalid = Pin.validatePinDirection('a'); - expect(invalid.isLeft).to.be.true(); - Helper.expectErrorMessage(expect, invalid, CONST.ERROR.PIN_DIRECTION_INVALID); - }); - it('should return Either.Right for valid direction', () => { - const direction = CONST.PIN_DIRECTION.INPUT; - const valid = Pin.validatePinDirection(direction); - expect(valid.isRight).to.be.true(); - Helper.expectEither( - (val) => { expect(val).to.be.equal(direction); }, - valid - ); - }); - }); - describe('validatePinKey', () => { - it('should return Either.Left for invalid key', () => { - const pinKey = 'i have $paceZ и немного кириллицы'; - const result = Pin.validatePinKey(pinKey); - expect(result.isLeft).to.be.true(); - Helper.expectErrorMessage(expect, result, CONST.ERROR.PIN_KEY_INVALID); - }); - it('should return Either.Right for valid key', () => { - const pinKey = '123aBc'; - const result = Pin.validatePinKey(pinKey); - expect(result.isRight).to.be.true(); - Helper.expectEither( - val => expect(val).to.be.equal(pinKey), - result - ); - }); - }); - describe('validatePin', () => { - it('should return Either.Left for empty object', () => { - const invalid = Pin.validatePin({}); - expect(invalid.isLeft).to.be.true(); - }); - it('should return Either.Left for wrong type', () => { - const invalid = Pin.validatePin({ type: 'a' }); - expect(invalid.isLeft).to.be.true(); - }); - it('should return Either.Left for wrong direction', () => { - const invalid = Pin.validatePin({ type: CONST.PIN_TYPE.NUMBER, direction: 'z' }); - expect(invalid.isLeft).to.be.true(); - }); - it('should return Either.Left for wrong key', () => { - const invalid = Pin.validatePin({ type: CONST.PIN_TYPE.NUMBER, direction: CONST.PIN_DIRECTION.INPUT, key: 'неправильный ключ для пина' }); - expect(invalid.isLeft).to.be.true(); - }); - it('should return Either.Right for valid pin', () => { - const valid = Pin.validatePin({ - key: 'a', - type: CONST.PIN_TYPE.NUMBER, - direction: CONST.PIN_DIRECTION.INPUT, - }); - expect(valid.isRight).to.be.true(); - }); - }); - // constructors - describe('createPin', () => { - it('should return Either.Left for wrong pin type', () => { - const newPin = Pin.createPin('test', 'a', 'i'); - expect(newPin.isLeft).to.be.true(); - }); - it('should return Either.Left for wrong pin direction', () => { - const newPin = Pin.createPin('test', CONST.PIN_TYPE.NUMBER, 'i'); - expect(newPin.isLeft).to.be.true(); - }); - it('should return Either.Right with new Pin', () => { - const key = 'test'; - const type = CONST.PIN_TYPE.NUMBER; - const direction = CONST.PIN_DIRECTION.INPUT; - const newPin = Pin.createPin(key, type, direction); - expect(newPin.isRight).to.be.true(); - Helper.expectEither( - (pinObj) => { - expect(pinObj) - .to.be.an('object') - .that.deep.equal({ - key, - type, - direction, - }); - }, - newPin - ); - }); - }); // props required describe('getPinType', () => { it('should return pin type', () => {