UNPKG

sprotty

Version:

A next-gen framework for graphical views

315 lines (289 loc) 11.6 kB
/******************************************************************************** * 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 } from "inversify"; // eslint-disable-next-line max-len import { Action, ComputedBoundsAction, isResponseAction, RequestAction, RequestBoundsAction, ResponseAction, SetModelAction, UpdateModelAction } from 'sprotty-protocol/lib/actions'; import { Deferred } from 'sprotty-protocol/lib/utils/async'; import { TYPES } from "../base/types"; import { ViewerOptions, overrideViewerOptions } from "../base/views/viewer-options"; import { IActionDispatcher } from "../base/actions/action-dispatcher"; import { LocalModelSource } from "./local-model-source"; import { ComputedBoundsApplicator } from "./model-source"; import defaultContainerModule from "../base/di.config"; import { SModelElement } from "sprotty-protocol"; describe('LocalModelSource', () => { @injectable() class MockActionDispatcher implements IActionDispatcher { readonly actions: Action[] = []; readonly requests: { requestId: string, deferred: Deferred<ResponseAction> }[] = []; dispatchAll(actions: Action[]): Promise<void> { for (const action of actions) { this.dispatch(action); } return Promise.resolve(); } dispatch(action: Action): Promise<void> { if (isResponseAction(action)) { const request = this.requests.find(r => r.requestId === action.responseId); if (request !== undefined) { request.deferred.resolve(action); return Promise.resolve(); } return Promise.reject(`No matching request for response ${action}`); } this.actions.push(action); return Promise.resolve(); } request<Res extends ResponseAction>(action: RequestAction<Res>): Promise<Res> { if (!action.requestId) { return Promise.reject(new Error('Request without requestId')); } const deferred = new Deferred<Res>(); this.requests.push({ requestId: action.requestId, deferred: deferred as any }); this.dispatch(action); return deferred.promise; } } function setup(options: Partial<ViewerOptions>) { const container = new Container(); container.load(defaultContainerModule); container.bind(TYPES.ModelSource).to(LocalModelSource); container.bind(ComputedBoundsApplicator).toSelf().inSingletonScope(); container.rebind(TYPES.IActionDispatcher).to(MockActionDispatcher).inSingletonScope(); overrideViewerOptions(container, options); return container; } it('sets the model in fixed mode', () => { const container = setup({ needsClientLayout: false }); const modelSource = container.get<LocalModelSource>(TYPES.ModelSource); const dispatcher = container.get<MockActionDispatcher>(TYPES.IActionDispatcher); const root1: SModelElement = { type: 'root', id: 'root' }; modelSource.setModel(root1); const root2: SModelElement = { type: 'root', id: 'root', children: [ { type: 'blob', id: 'foo' } ] }; modelSource.updateModel(root2); expect(dispatcher.actions).to.have.lengthOf(2); const action0 = dispatcher.actions[0] as SetModelAction; expect(action0.kind).to.equal(SetModelAction.KIND); expect(action0.newRoot).to.equal(root1); const action1 = dispatcher.actions[1] as UpdateModelAction; expect(action1.kind).to.equal(UpdateModelAction.KIND); expect(action1.newRoot).to.equal(root2); }); it('requests bounds in dynamic mode', async () => { const container = setup({ needsClientLayout: true }); const modelSource = container.get<LocalModelSource>(TYPES.ModelSource); const dispatcher = container.get<MockActionDispatcher>(TYPES.IActionDispatcher); const root1: SModelElement = { type: 'root', id: 'root', children: [ { type: 'node', id: 'child1' } ] }; const promise1 = modelSource.setModel(root1); expect(dispatcher.requests).to.have.lengthOf(1); dispatcher.dispatch(ComputedBoundsAction.create([ { elementId: 'child1', newPosition: { x: 10, y: 10 }, newSize: { width: 20, height: 20 } } ], { requestId: dispatcher.requests[0].requestId })); const root2: SModelElement = { type: 'root', id: 'root', children: [ { type: 'node', id: 'bar' } ] }; await promise1; const promise2 = modelSource.updateModel(root2); expect(dispatcher.requests).to.have.lengthOf(2); dispatcher.dispatch(ComputedBoundsAction.create([ { elementId: 'bar', newPosition: { x: 10, y: 10 }, newSize: { width: 20, height: 20 } } ], { requestId: dispatcher.requests[1].requestId })); await promise2; expect(dispatcher.actions).to.have.lengthOf(4); const action0 = dispatcher.actions[0] as RequestBoundsAction; expect(action0.kind).to.equal(RequestBoundsAction.KIND); expect(action0.newRoot).to.equal(root1); const action1 = dispatcher.actions[1] as SetModelAction; expect(action1.kind).to.equal(SetModelAction.KIND); expect(action1.newRoot).to.deep.equal({ type: 'root', id: 'root', children: [ { type: 'node', id: 'child1', position: { x: 10, y: 10 }, size: { width: 20, height: 20 } } ] }); const action2 = dispatcher.actions[2] as RequestBoundsAction; expect(action2.kind).to.equal(RequestBoundsAction.KIND); expect(action2.newRoot).to.equal(root2); const action3 = dispatcher.actions[3] as UpdateModelAction; expect(action3.kind).to.equal(UpdateModelAction.KIND); }); it('adds and removes elements', () => { const container = setup({ needsClientLayout: false }); const modelSource = container.get<LocalModelSource>(TYPES.ModelSource); const dispatcher = container.get<MockActionDispatcher>(TYPES.IActionDispatcher); modelSource.setModel({ type: 'root', id: 'root' }); modelSource.addElements([ { type: 'node', id: 'child1' }, { type: 'node', id: 'child2' } ]); expect(modelSource.model).to.deep.equal({ type: 'root', id: 'root', children: [ { type: 'node', id: 'child1' }, { type: 'node', id: 'child2' } ] }); modelSource.removeElements(['child1']); expect(modelSource.model).to.deep.equal({ type: 'root', id: 'root', children: [ { type: 'node', id: 'child2' } ] }); expect(dispatcher.actions).to.have.lengthOf(3); const action1 = dispatcher.actions[1] as UpdateModelAction; expect(action1.matches).to.deep.equal([ { right: { type: 'node', id: 'child1' }, rightParentId: 'root' }, { right: { type: 'node', id: 'child2' }, rightParentId: 'root' } ]); const action2 = dispatcher.actions[2] as UpdateModelAction; expect(action2.matches).to.deep.equal([ { left: { type: 'node', id: 'child1' }, leftParentId: 'root' } ]); }); it('resolves promises in fixed mode', async () => { const container = setup({ needsClientLayout: false }); const modelSource = container.get<LocalModelSource>(TYPES.ModelSource); const dispatcher = container.get<MockActionDispatcher>(TYPES.IActionDispatcher); const root1: SModelElement = { type: 'root', id: 'root' }; await modelSource.setModel(root1); const root2: SModelElement = { type: 'root', id: 'root', children: [{ type: 'blob', id: 'foo' }] }; await modelSource.updateModel(root2); expect(dispatcher.actions).to.have.lengthOf(2); }); it('resolves promises in dynamic mode', async () => { const container = setup({ needsClientLayout: true }); const modelSource = container.get<LocalModelSource>(TYPES.ModelSource); const dispatcher = container.get<MockActionDispatcher>(TYPES.IActionDispatcher); const root1: SModelElement = { type: 'root', id: 'root', children: [{ type: 'node', id: 'child1' }] }; const promise1 = modelSource.setModel(root1); expect(dispatcher.requests).to.have.lengthOf(1); dispatcher.dispatch(ComputedBoundsAction.create([ { elementId: 'child1', newPosition: { x: 10, y: 10 }, newSize: { width: 20, height: 20 } } ], { requestId: dispatcher.requests[0].requestId })); await promise1; const root2: SModelElement = { type: 'root', id: 'root', children: [{ type: 'node', id: 'bar' }] }; const promise2 = modelSource.updateModel(root2); expect(dispatcher.requests).to.have.lengthOf(2); dispatcher.dispatch(ComputedBoundsAction.create([ { elementId: 'bar', newPosition: { x: 10, y: 10 }, newSize: { width: 20, height: 20 } } ], { requestId: dispatcher.requests[1].requestId })); await promise2; expect(dispatcher.actions).to.have.lengthOf(4); }); });