sprotty
Version:
A next-gen framework for graphical views
197 lines (184 loc) • 8.08 kB
text/typescript
/********************************************************************************
* Copyright (c) 2018 TypeFox and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import 'reflect-metadata';
import { expect, describe, it } from 'vitest';
import { SRoutingHandleImpl } from '../features/routing/model';
import { RectangularNode, RectangularPort } from '../lib/model';
import { SNodeImpl, SEdgeImpl, SGraphImpl, SPortImpl } from './sgraph';
import { RoutedPoint } from "../features/routing/routing";
import { PolylineEdgeRouter } from "../features/routing/polyline-edge-router";
import { Container } from "inversify";
import { AnchorComputerRegistry } from "../features/routing/anchor";
import { RectangleAnchor } from "../features/routing/polyline-anchors";
import { TYPES } from "../base/types";
import routingModule from "../features/routing/di.config";
describe('SEdgeImpl', () => {
const graph = new SGraphImpl();
const source = new SNodeImpl();
source.id = 'node0';
source.position = { x: 10, y: 10 };
source.size = { width: 0, height: 0 };
graph.add(source);
const target = new SNodeImpl();
target.id = 'node1';
target.position = { x: 20, y: 20 };
target.size = { width: 0, height: 0 };
graph.add(target);
const edge = new SEdgeImpl();
edge.id = 'edge0';
edge.sourceId = 'node0';
edge.targetId = 'node1';
graph.add(edge);
const container = new Container();
container.load(routingModule);
const router = container.get<PolylineEdgeRouter>(PolylineEdgeRouter);
it('computes a simple route', () => {
edge.routingPoints = [{ x: 14, y: 12 }, { x: 16, y: 18 }];
const route = router.route(edge);
expect(route).to.deep.equal(<RoutedPoint[]>[
{ x: 10, y: 10, kind: 'source' },
{ x: 14, y: 12, kind: 'linear', pointIndex: 0 },
{ x: 16, y: 18, kind: 'linear', pointIndex: 1 },
{ x: 20, y: 20, kind: 'target' }
]);
});
it('skips a routing handle that is dragged for removal', () => {
edge.routingPoints = [{ x: 12, y: 15 }, { x: 15, y: 15 }, { x: 18, y: 15 }];
router.createRoutingHandles(edge);
const route1 = router.route(edge);
expect(route1).to.deep.equal(<RoutedPoint[]>[
{ x: 10, y: 10, kind: 'source' },
{ x: 12, y: 15, kind: 'linear', pointIndex: 0 },
{ x: 15, y: 15, kind: 'linear', pointIndex: 1 },
{ x: 18, y: 15, kind: 'linear', pointIndex: 2 },
{ x: 20, y: 20, kind: 'target' }
]);
const handle1 = edge.children.find(child => (child as SRoutingHandleImpl).pointIndex === 1) as SRoutingHandleImpl;
handle1.editMode = true;
const route2 = router.route(edge);
expect(route2).to.deep.equal(<RoutedPoint[]>[
{ x: 10, y: 10, kind: 'source' },
{ x: 12, y: 15, kind: 'linear', pointIndex: 0 },
{ x: 18, y: 15, kind: 'linear', pointIndex: 2 },
{ x: 20, y: 20, kind: 'target' }
]);
});
});
describe('SGraphIndex', () => {
function setup() {
const root = new SGraphImpl();
const node1 = new SNodeImpl();
node1.id = 'node1';
root.add(node1);
const node2 = new SNodeImpl();
node2.id = 'node2';
root.add(node2);
const edge1 = new SEdgeImpl();
edge1.id = 'edge1';
edge1.sourceId = node1.id;
edge1.targetId = node2.id;
root.add(edge1);
return { root, node1, node2, edge1 };
}
it('tracks outgoing edges of a node', () => {
const ctx = setup();
const a = Array.from(ctx.node1.outgoingEdges);
expect(a).to.be.of.length(1);
expect(a[0].id).to.equal('edge1');
});
it('tracks incoming edges of a node', () => {
const ctx = setup();
const a = Array.from(ctx.node2.incomingEdges);
expect(a).to.be.of.length(1);
expect(a[0].id).to.equal('edge1');
});
it('does not contain outgoing or incoming edges after removing them', () => {
const ctx = setup();
ctx.root.remove(ctx.edge1);
expect(Array.from(ctx.node1.outgoingEdges)).to.be.of.length(0);
expect(Array.from(ctx.node2.incomingEdges)).to.be.of.length(0);
});
});
describe('anchor computation', () => {
const container = new Container();
container.bind(PolylineEdgeRouter).toSelf().inSingletonScope();
container.bind(AnchorComputerRegistry).toSelf().inSingletonScope();
container.bind(TYPES.IAnchorComputer).to(RectangleAnchor).inSingletonScope();
const router = container.get<PolylineEdgeRouter>(PolylineEdgeRouter);
function createModel() {
const model = new SGraphImpl();
model.type = 'graph';
model.id = 'graph';
const node1 = new RectangularNode();
node1.type = 'node';
node1.id = 'node1';
node1.position = { x: 10, y: 10 };
node1.size = { width: 10, height: 10 };
model.add(node1);
const port1 = new RectangularPort();
port1.type = 'port';
port1.id = 'port1';
port1.position = { x: 10, y: 4 };
port1.size = { width: 2, height: 2 };
port1.strokeWidth = 0;
node1.add(port1);
const node2 = new RectangularNode();
node2.type = 'node';
node2.id = 'node2';
node2.position = { x: 30, y: 10 };
node2.size = { width: 10, height: 10 };
model.add(node2);
const port2 = new RectangularPort();
port2.type = 'port';
port2.id = 'port2';
port2.position = { x: -2, y: 4 };
port2.size = { width: 2, height: 2 };
port2.strokeWidth = 0;
node2.add(port2);
const edge1 = new SEdgeImpl();
edge1.type = 'edge';
edge1.id = 'edge1';
edge1.sourceId = 'port1';
edge1.targetId = 'port2';
model.add(edge1);
return model;
}
it('correctly translates edge source position', () => {
const model = createModel();
const edge = model.index.getById('edge1') as SEdgeImpl;
const sourcePort = model.index.getById('port1') as SPortImpl;
const refPoint = { x: 30, y: 15 };
const translated = router.getTranslatedAnchor(sourcePort, refPoint, edge.parent, edge);
expect(translated).to.deep.equal({ x: 22, y: 15, width: -1, height: -1 });
});
it('correctly translates edge target position', () => {
const model = createModel();
const edge = model.index.getById('edge1') as SEdgeImpl;
const targetPort = model.index.getById('port2') as SPortImpl;
const refPoint = { x: 20, y: 15 };
const translated = router.getTranslatedAnchor(targetPort, refPoint, edge.parent, edge);
expect(translated).to.deep.equal({ x: 28, y: 15, width: -1, height: -1 });
});
it('correctly translates edge source to target position', () => {
const model = createModel();
const edge = model.index.getById('edge1') as SEdgeImpl;
const sourcePort = model.index.getById('port1') as SPortImpl;
const targetPort = model.index.getById('port2') as SPortImpl;
const refPoint = { x: 10, y: 5 };
const translated = router.getTranslatedAnchor(targetPort, refPoint, sourcePort.parent, edge);
expect(translated).to.deep.equal({ x: 28, y: 15, width: -1, height: -1 });
});
});