sprotty
Version:
A next-gen framework for graphical views
173 lines (149 loc) • 6.31 kB
text/typescript
/********************************************************************************
* Copyright (c) 2017-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 { Container } from 'inversify';
import { TYPES } from '../../base/types';
import { ConsoleLogger } from '../../utils/logging';
import { CommandExecutionContext } from '../../base/commands/command';
import { SModelRootImpl } from '../../base/model/smodel';
import { SGraphImpl, SNodeImpl } from '../../graph/sgraph';
import { AnimationFrameSyncer } from '../../base/animations/animation-frame-syncer';
import { ElementMove, MoveCommand } from './move';
import defaultModule from '../../base/di.config';
import { MoveAction, Point } from 'sprotty-protocol';
import { IModelFactory } from '../../base/model/smodel-factory';
import { registerModelElement } from '../../base/model/smodel-utils';
describe('move', () => {
const container = new Container();
container.load(defaultModule);
registerModelElement(container, 'graph', SGraphImpl);
registerModelElement(container, 'node:circle', SNodeImpl);
const graphFactory = container.get<IModelFactory>(TYPES.IModelFactory);
const pointNW: Point = { x: 0, y: 0 };
const pointNE: Point = { x: 300, y: 1 };
const pointSW: Point = { x: 1, y: 300 };
const pointSE: Point = { x: 301, y: 301 };
// nodes start at pointNW
const myNode0 = {
id: 'node0', type: 'node:circle',
x: pointNW.x, y: pointNW.y,
selected: false
};
const myNode1 = {
id: 'node1', type: 'node:circle',
x: pointNW.x, y: pointNW.y,
selected: false
};
const myNode2 = {
id: 'node2', type: 'node:circle',
x: pointNW.x, y: pointNW.y,
selected: false
};
// setup the GModel
const model = graphFactory.createRoot({
id: 'model1',
type: 'graph',
children: [myNode0, myNode1, myNode2]
});
// move each node to a different corner
const moves: ElementMove[] = [
{
elementId: myNode0.id,
toPosition: {
x: pointNE.x, y: pointNE.y
}
},
{
elementId: myNode1.id,
toPosition: {
x: pointSW.x, y: pointSW.y
}
},
{
elementId: myNode2.id,
toPosition: {
x: pointSE.x, y: pointSE.y
}
}
];
// create the action
const moveAction = MoveAction.create(moves, { animate: false });
// create the command
const cmd = new MoveCommand(moveAction);
const context: CommandExecutionContext = {
root: model,
modelFactory: graphFactory,
duration: 0,
modelChanged: undefined!,
logger: new ConsoleLogger(),
syncer: new AnimationFrameSyncer()
};
// global so we can carry-over the model, as it's updated,
// from test case to test case (i,e, select, undo, redo, merge)
let newModel: SModelRootImpl;
function getNode(nodeId: string, root: SModelRootImpl) {
return root.index.getById(nodeId) as SNodeImpl;
}
it('execute() works as expected', () => {
// execute command
newModel = cmd.execute(context) as SModelRootImpl;
// node0 => PointNE
expect(pointNE.x).equals(getNode('node0', newModel).bounds.x);
expect(pointNE.y).equals(getNode('node0', newModel).bounds.y);
// node1 => pointSW
expect(pointSW.x).equals(getNode('node1', newModel).bounds.x);
expect(pointSW.y).equals(getNode('node1', newModel).bounds.y);
// node2 => PointSE
expect(pointSE.x).equals(getNode('node2', newModel).bounds.x);
expect(pointSE.y).equals(getNode('node2', newModel).bounds.y);
});
// TODO: undo, redo, merge
// note: not sure how to deal with promise returned by undo()
// and redo()...
// Should undo()/redo() check whether the move action wants
// animation, and if not just return an updated model?
let undoneModel: SModelRootImpl;
it('undo() works as expected', async () => {
// test 'undo'
context.root = newModel;
undoneModel = await cmd.undo(context);
// confirm that each node is back at original
// coordinates
// node0, node1 and node2 => pointNW
expect(pointNW.x).equals(getNode('node0', undoneModel).bounds.x);
expect(pointNW.y).equals(getNode('node0', undoneModel).bounds.y);
expect(pointNW.x).equals(getNode('node1', undoneModel).bounds.x);
expect(pointNW.y).equals(getNode('node1', undoneModel).bounds.y);
expect(pointNW.x).equals(getNode('node2', undoneModel).bounds.x);
expect(pointNW.y).equals(getNode('node2', undoneModel).bounds.y);
});
it('redo() works as expected', async () => {
// test 'redo':
context.root = undoneModel;
const redoneModel = await cmd.redo(context);
// confirm that each node is back where ordered to move
// node0 => PointNE
expect(pointNE.x).equals(getNode('node0', redoneModel).bounds.x);
expect(pointNE.y).equals(getNode('node0', redoneModel).bounds.y);
// node1 => pointSW
expect(pointSW.x).equals(getNode('node1', redoneModel).bounds.x);
expect(pointSW.y).equals(getNode('node1', redoneModel).bounds.y);
// node2 => PointSE
expect(pointSE.x).equals(getNode('node2', redoneModel).bounds.x);
expect(pointSE.y).equals(getNode('node2', redoneModel).bounds.y);
});
});