mirror of
https://github.com/xodio/xod.git
synced 2026-03-24 01:26:53 +01:00
refactor(xod-project): introduce $Pin type
This commit is contained in:
@@ -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}`;
|
||||
|
||||
@@ -209,7 +209,8 @@ export const getNodeById = def(
|
||||
* @param {Patch} patch
|
||||
* @returns {Either<Error|Patch>}
|
||||
*/
|
||||
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);
|
||||
|
||||
@@ -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<Error|PIN_TYPE>}
|
||||
*/
|
||||
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<Error|PIN_DIRECTION>}
|
||||
*/
|
||||
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<Error|string>}
|
||||
*/
|
||||
export const validatePinKey = Tools.errOnFalse(
|
||||
CONST.ERROR.PIN_KEY_INVALID,
|
||||
Utils.validateId
|
||||
);
|
||||
|
||||
/**
|
||||
* Validates pin correctness
|
||||
*
|
||||
* @function validatePin
|
||||
* @param {Pin} pin
|
||||
* @returns {Either<Error|Pin>}
|
||||
*/
|
||||
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<Error|Pin>}
|
||||
*/
|
||||
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),
|
||||
})
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: {},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
Reference in New Issue
Block a user