diff --git a/packages/xod-project/src/adapter.js b/packages/xod-project/src/adapter.js index 10eba6cc..36717574 100644 --- a/packages/xod-project/src/adapter.js +++ b/packages/xod-project/src/adapter.js @@ -204,7 +204,7 @@ const convertLinksInPatches = R.curry( const copyImpls = R.compose( R.map(R.assoc('impls')), maybeEmpty, - R.prop('impl') + R.propOr({}, 'impl') ); // :: ProjectOld -> Maybe Function fn diff --git a/packages/xod-project/src/flatten.js b/packages/xod-project/src/flatten.js index 3bb4e2aa..fbfb5918 100644 --- a/packages/xod-project/src/flatten.js +++ b/packages/xod-project/src/flatten.js @@ -369,6 +369,7 @@ const getPinType = R.curry((patchTuples, nodes, idGetter, keyGetter, link) => const createCastNode = R.curry((patchTuples, nodes, link) => R.compose( R.chain(type => ({ id: `${Link.getLinkOutputNodeId(link)}-to-${Link.getLinkInputNodeId(link)}`, + position: { x: 0, y: 0 }, type, })), // Link -> Maybe String diff --git a/packages/xod-project/src/patch.js b/packages/xod-project/src/patch.js index e5c82bfc..704641e9 100644 --- a/packages/xod-project/src/patch.js +++ b/packages/xod-project/src/patch.js @@ -8,6 +8,7 @@ import * as Link from './link'; import * as Pin from './pin'; import * as Utils from './utils'; import { sortGraph } from './gmath'; +import { def } from './types'; /** * An object representing single patch in a project @@ -26,6 +27,8 @@ import { sortGraph } from './gmath'; export const createPatch = () => ({ nodes: {}, links: {}, + impls: {}, + pins: {}, }); /** @@ -382,10 +385,9 @@ export const listLinksByPin = R.curry( * @param {Patch} patch - a patch to operate on * @returns {Either} validation errors or valid {@link Link} */ -export const validateLink = R.curry( - (link, patch) => Link.validateLinkId(link) - .chain(Link.validateLinkInput) - .chain(Link.validateLinkOutput) +export const validateLink = def( + 'validateLink :: Link -> Patch -> Either Error Link', + (link, patch) => Either.of(link) .chain(() => Tools.errOnNothing( CONST.ERROR.LINK_INPUT_NODE_NOT_FOUND, getNodeById(Link.getLinkInputNodeId(link), patch) diff --git a/packages/xod-project/src/types.js b/packages/xod-project/src/types.js index afdf50f6..47bf9be3 100644 --- a/packages/xod-project/src/types.js +++ b/packages/xod-project/src/types.js @@ -1,5 +1,6 @@ import R from 'ramda'; +import RF from 'ramda-fantasy'; import $ from 'sanctuary-def'; import HMDef from 'hm-def'; @@ -41,6 +42,20 @@ const AliasType = (typeName, type) => NullaryType( hasType(type) ); +//----------------------------------------------------------------------------- +// +// Fantasy land types +// +//----------------------------------------------------------------------------- + +export const $Either = $.BinaryType( + 'ramda-fantasy/Either', + 'https://github.com/ramda/ramda-fantasy/blob/master/docs/Either.md', + R.is(RF.Either), + either => either.isLeft ? [either.value] : [], + either => either.isRight ? [either.value] : [] +); + //----------------------------------------------------------------------------- // // Domain types @@ -49,11 +64,14 @@ const AliasType = (typeName, type) => NullaryType( const ObjectWithId = NullaryType('ObjectWithId', R.has('id')); +export const Label = AliasType('Label', $.String); +export const Source = AliasType('Source', $.String); export const ShortId = AliasType('ShortId', $.String); 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 NodePosition = Model('NodePosition', { x: $.Number, @@ -77,6 +95,14 @@ export const Link = Model('Link', { output: PinRef, }); +export const Patch = Model('Patch', { + nodes: $.StrMap(Node), + links: $.StrMap(Link), + impls: $.StrMap(Source), + pins: $.StrMap(Pin), + //label: Label, +}); + export const NodeOrId = OneOfType('NodeOrId', [NodeId, ObjectWithId]); export const LinkOrId = OneOfType('LinkOrId', [LinkId, ObjectWithId]); @@ -85,15 +111,18 @@ export const LinkOrId = OneOfType('LinkOrId', [LinkId, ObjectWithId]); // Environment // //----------------------------------------------------------------------------- + export const env = $.env.concat([ + $Either, Link, - PinRef, - PinKey, - NodeId, LinkId, - ShortId, - NodeOrId, LinkOrId, + NodeId, + NodeOrId, + Patch, + PinKey, + PinRef, + ShortId, ]); export const def = HMDef.create({ checkTypes: true, env }); diff --git a/packages/xod-project/test/flatten.spec.js b/packages/xod-project/test/flatten.spec.js index 03760637..9692f7a5 100644 --- a/packages/xod-project/test/flatten.spec.js +++ b/packages/xod-project/test/flatten.spec.js @@ -11,7 +11,7 @@ chai.use(dirtyChai); describe('Flatten', () => { describe('trivial', () => { - const project = { + const project = Helper.defaultizeProject({ patches: { '@/main': { nodes: { @@ -63,7 +63,7 @@ describe('Flatten', () => { }, }, }, - }; + }); it('should return error if implementation not found', () => { const flatProject = flatten(project, '@/main', ['cpp']); @@ -118,7 +118,7 @@ describe('Flatten', () => { }); describe('recursive', () => { - const project = { + const project = Helper.defaultizeProject({ patches: { '@/main': { nodes: { @@ -270,7 +270,7 @@ describe('Flatten', () => { }, }, }, - }; + }); it('should ignore not referred patches', () => { const flatProject = flatten(project, '@/foo', ['js']); @@ -374,7 +374,7 @@ describe('Flatten', () => { }; describe('no links to terminal', () => { - const project = { + const project = Helper.defaultizeProject({ patches: { '@/main': { nodes: { @@ -432,7 +432,7 @@ describe('Flatten', () => { impls: {}, }, }, - }; + }); it('should return patches without cast patch', () => { const flatProject = flatten(project, '@/main', ['js']); @@ -465,7 +465,7 @@ describe('Flatten', () => { describe('through output terminal', () => { const createCastOutputTest = (typeIn, typeOut) => { it(`${typeIn} -> ${getCastPatchPath(typeIn, typeOut)} -> ${typeOut}`, () => { - const project = { + const project = Helper.defaultizeProject({ patches: { '@/main': { nodes: { @@ -581,7 +581,7 @@ describe('Flatten', () => { impls: {}, }, }, - }; + }); if (typeOut === typeIn) { project.patches[`xod/core/${typeOut}`].pins = { @@ -622,7 +622,7 @@ describe('Flatten', () => { describe('through input terminal', () => { const createCastInputTest = (typeIn, typeOut) => { it(`${typeIn} -> ${getCastPatchPath(typeIn, typeOut)} -> ${typeOut}`, () => { - const project = { + const project = Helper.defaultizeProject({ patches: { '@/main': { nodes: { @@ -738,7 +738,7 @@ describe('Flatten', () => { impls: {}, }, }, - }; + }); if (typeOut === typeIn) { project.patches[`xod/core/${typeOut}`].pins = { @@ -788,7 +788,7 @@ describe('Flatten', () => { describe('three different types', () => {}); describe('with same types', () => { - const project = { + const project = Helper.defaultizeProject({ patches: { '@/main': { nodes: { @@ -878,7 +878,7 @@ describe('Flatten', () => { }, }, }, - }; + }); it('should return patches without cast patch', () => { const flatProject = flatten(project, '@/main', ['js']); @@ -933,7 +933,7 @@ describe('Flatten', () => { }); describe('needed, but missing in the project', () => { - const project = { + const project = Helper.defaultizeProject({ patches: { '@/main': { nodes: { @@ -996,7 +996,7 @@ describe('Flatten', () => { }, }, }, - }; + }); it(`should return Either.Left with error "${CONST.ERROR.CAST_PATCH_NOT_FOUND}"`, () => { const flatProject = flatten(project, '@/main', ['js']); @@ -1008,7 +1008,7 @@ describe('Flatten', () => { }); describe('injected pins', () => { - const project = { + const project = Helper.defaultizeProject({ patches: { '@/main': { nodes: { @@ -1241,7 +1241,7 @@ describe('Flatten', () => { }, }, }, - }; + }); it('should return node b~b with injected pin a2', () => { const flatProject = flatten(project, '@/main', ['js']); @@ -1264,7 +1264,7 @@ describe('Flatten', () => { }); describe('implementations', () => { - const project = { + const project = Helper.defaultizeProject({ patches: { '@/main': { nodes: { @@ -1296,7 +1296,7 @@ describe('Flatten', () => { }, }, }, - }; + }); describe('single', () => { it('defined implementation exists', () => { diff --git a/packages/xod-project/test/helpers.js b/packages/xod-project/test/helpers.js index 4471eea3..f20a5c3a 100644 --- a/packages/xod-project/test/helpers.js +++ b/packages/xod-project/test/helpers.js @@ -84,3 +84,43 @@ export const expectOptionalNumberSetter = R.curry((expect, method, propName) => .to.have.property(propName).to.be.equal(0); }); }); + +//----------------------------------------------------------------------------- +// Defaultizers +//----------------------------------------------------------------------------- + +export const defaultizeLink = R.merge({ + id: '$$defaultLinkId', + input: { nodeId: '$$defaultInputNodeId', pinKey: '$$defaultInputPin' }, + output: { nodeId: '$$defaultOutputNodeId', pinKey: '$$defaultOutputPin' }, +}); + +export const defaultizeNode = R.merge({ + id: '$$defaultNodeId', + position: { x: 0, y: 0 }, + type: '@/defaultType', +}); + +export const defaultizePatch = R.compose( + R.evolve({ + nodes: R.map(defaultizeNode), + links: R.map(defaultizeLink), + impls: R.identity, + pins: R.identity, + }), + R.merge({ + nodes: {}, + links: {}, + impls: {}, + pins: {}, + }) +); + +export const defaultizeProject = R.compose( + R.evolve({ + patches: R.map(defaultizePatch), + }), + R.merge({ + patches: {}, + }) +); diff --git a/packages/xod-project/test/patch.spec.js b/packages/xod-project/test/patch.spec.js index ef400b01..34254113 100644 --- a/packages/xod-project/test/patch.spec.js +++ b/packages/xod-project/test/patch.spec.js @@ -737,12 +737,12 @@ describe('Patch', () => { }); }); describe('validateLink', () => { - const patch = { + const patch = Helper.defaultizePatch({ nodes: { out: { id: 'out' }, in: { id: 'in' }, }, - }; + }); const linkId = '1'; const validInput = { nodeId: 'in', @@ -753,26 +753,12 @@ describe('Patch', () => { pinKey: 'out', }; - it('should return Either.Left for link without id', () => { - const err = Patch.validateLink({}, {}); - expect(err.isLeft).to.be.true(); - }); - it('should return Either.Left if input property is not exist or invalid', () => { - const link = { id: linkId }; - const err = Patch.validateLink(link, patch); - expect(err.isLeft).to.be.true(); - }); it('should return Either.Left for non-existent input node in the patch', () => { const link = { id: linkId, input: { nodeId: 'non-existent', pinKey: 'a' }, output: validOutput }; const err = Patch.validateLink(link, patch); expect(err.isLeft).to.be.true(); Helper.expectErrorMessage(expect, err, CONST.ERROR.LINK_INPUT_NODE_NOT_FOUND); }); - it('should return Either.Left if output property is not exist or invalid', () => { - const link = { id: linkId, input: validInput }; - const err = Patch.validateLink(link, patch); - expect(err.isLeft).to.be.true(); - }); it('should return Either.Left for non-existent output node in the patch', () => { const link = { id: linkId, input: validInput, output: { nodeId: 'non-existent', pinKey: 'a' } }; const err = Patch.validateLink(link, patch);