sprotty
Version:
A next-gen framework for graphical views
188 lines (162 loc) • 7.37 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, injectable, interfaces } from "inversify";
import { Action, RedoAction, RejectAction, RequestModelAction, SetModelAction, UndoAction } from 'sprotty-protocol/lib/actions';
import { Bounds } from 'sprotty-protocol/lib/utils/geometry';
import { TYPES } from "../types";
import { InitializeCanvasBoundsAction } from '../features/initialize-canvas';
import { Command, CommandExecutionContext, CommandReturn, ICommand } from '../commands/command';
import { ICommandStack } from "../commands/command-stack";
import { ActionDispatcher } from "./action-dispatcher";
import defaultModule from "../di.config";
import { EMPTY_ROOT } from '../model/smodel-factory';
import { IActionHandler, configureActionHandler } from './action-handler';
describe('ActionDispatcher', () => {
()
class MockCommand extends Command {
static KIND = 'mock';
execute(context: CommandExecutionContext): CommandReturn {
return context.root;
}
undo(context: CommandExecutionContext): CommandReturn {
return context.root;
}
redo(context: CommandExecutionContext): CommandReturn {
return context.root;
}
}
class MockAction implements Action {
kind = MockCommand.KIND;
}
()
class ResolvingHandler implements IActionHandler {
handle(action: RequestModelAction): Action {
return SetModelAction.create({ type: 'root', id: 'foo' }, action.requestId);
}
}
()
class RejectingHandler implements IActionHandler {
handle(action: RequestModelAction): Action {
return RejectAction.create({ message: 'because bar', requestId: action.requestId });
}
}
function setup(options: { requestHandler?: interfaces.Newable<IActionHandler>, initialize?: boolean } = {}) {
const state = {
execCount: 0,
undoCount: 0,
redoCount: 0
};
const mockCommandStack: ICommandStack = {
execute(command: ICommand): Promise<any> {
++state.execCount;
return Promise.resolve();
},
executeAll(commands: ICommand[]): Promise<any> {
++state.execCount;
return Promise.resolve();
},
undo(): Promise<any> {
++state.undoCount;
return Promise.resolve();
},
redo(): Promise<any> {
++state.redoCount;
return Promise.resolve();
}
};
const container = new Container();
container.load(defaultModule);
container.rebind(TYPES.ICommandStack).toConstantValue(mockCommandStack);
if (options.requestHandler) {
configureActionHandler(container, RequestModelAction.KIND, options.requestHandler);
}
const actionDispatcher = container.get<ActionDispatcher>(TYPES.IActionDispatcher);
if (options.initialize) {
actionDispatcher.dispatch(InitializeCanvasBoundsAction.create(Bounds.EMPTY));
}
return { actionDispatcher, state };
}
it('should execute/undo/redo', async () => {
const { actionDispatcher, state } = setup();
await actionDispatcher.initialize();
// an initial SetModelAction is fired automatically
expect(state.execCount).to.be.equal(1);
expect(state.undoCount).to.be.equal(0);
expect(state.redoCount).to.be.equal(0);
// actions are postponed until InitializeCanvasBoundsAction comes in
// no await here, as it is blocking
actionDispatcher.dispatch(UndoAction.create());
expect(state.execCount).to.be.equal(1);
expect(state.undoCount).to.be.equal(0);
expect(state.redoCount).to.be.equal(0);
await actionDispatcher.dispatch(InitializeCanvasBoundsAction.create(Bounds.EMPTY));
// postponed actions are fired as well
expect(state.execCount).to.be.equal(2);
expect(state.undoCount).to.be.equal(1);
expect(state.redoCount).to.be.equal(0);
await actionDispatcher.dispatch(RedoAction.create());
expect(state.execCount).to.be.equal(2);
expect(state.undoCount).to.be.equal(1);
expect(state.redoCount).to.be.equal(1);
await actionDispatcher.dispatch({ kind: 'unknown' }).catch(() => {});
expect(state.execCount).to.be.equal(2);
expect(state.undoCount).to.be.equal(1);
expect(state.redoCount).to.be.equal(1);
// MockAction is not registered by default
await actionDispatcher.dispatch(new MockAction()).catch(() => {});
expect(state.execCount).to.be.equal(2);
expect(state.undoCount).to.be.equal(1);
expect(state.redoCount).to.be.equal(1);
});
it('should resolve/reject promises', async () => {
const { actionDispatcher } = setup({ initialize: true });
// We expect this promise to be resolved
await actionDispatcher.dispatch(SetModelAction.create(EMPTY_ROOT));
// Remove the blocking
await actionDispatcher.dispatch(InitializeCanvasBoundsAction.create(Bounds.EMPTY));
try {
await actionDispatcher.dispatch({ kind: 'unknown' });
expect.fail();
} catch {
// We expect this promise to be rejected
}
});
it('should reject requests without handler', async () => {
const { actionDispatcher } = setup({ initialize: true });
try {
await actionDispatcher.request(RequestModelAction.create());
expect.fail();
} catch (err) {
// We expect this promise to be rejected
}
});
it('should be able to resolve requests', async () => {
const { actionDispatcher } = setup({ requestHandler: ResolvingHandler, initialize: true });
const response = await actionDispatcher.request<SetModelAction>(RequestModelAction.create());
expect(response.newRoot.id).to.equal('foo');
});
it('should be able to reject requests', async () => {
const { actionDispatcher } = setup({ requestHandler: RejectingHandler, initialize: true });
try {
await actionDispatcher.request(RequestModelAction.create());
expect.fail();
} catch (err) {
expect(err.message).to.equal('because bar');
}
});
});