@eclipse-glsp/client
Version:
A sprotty-based client for GLSP
184 lines (163 loc) • 8.7 kB
text/typescript
/********************************************************************************
* Copyright (c) 2020-2023 EclipseSource 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 { BoundsAware, GChildElement, GIssue, GModelElement, GModelRoot, GNode, GParentElement } from '@eclipse-glsp/sprotty';
import { expect } from 'chai';
import { Container } from 'inversify';
import 'mocha';
import 'reflect-metadata';
import { defaultModule } from '../../base/default.module';
import { GGraph } from '../../model';
import { decorationModule } from '../decoration/decoration-module';
import { GIssueMarker } from './issue-marker';
import { MarkerNavigator } from './marker-navigator';
import { markerNavigatorModule } from './validation-modules';
describe('MarkerNavigator', () => {
const container = new Container();
container.load(defaultModule, decorationModule, markerNavigatorModule);
const markerNavigator = container.get<MarkerNavigator>(MarkerNavigator);
function createRoot(...nodeIds: string[]): GModelRoot {
const root = new GGraph();
root.features = new Set<symbol>(GGraph.DEFAULT_FEATURES);
root.id = 'root';
root.type = 'graph';
nodeIds.forEach(id => {
const node = new GNode();
node.id = id;
node.type = 'node';
node.features = new Set<symbol>(GNode.DEFAULT_FEATURES);
root.add(node);
});
return root;
}
const rootWithoutAnyMarkers = createRoot('1');
const rootWithMarkers = createRoot('bottom-right', 'top-right', 'top-left', 'bottom-left');
const elementTopLeft = rootWithMarkers.children[2] as BoundsAware & GChildElement;
elementTopLeft.bounds = { width: 10, height: 10, x: 100, y: 100 };
const elementTopRight = rootWithMarkers.children[1] as BoundsAware & GChildElement;
elementTopRight.bounds = { width: 10, height: 10, x: 200, y: 100 };
const elementBottomLeft = rootWithMarkers.children[3] as BoundsAware & GChildElement;
elementBottomLeft.bounds = { width: 10, height: 10, x: 100, y: 200 };
const elementBottomRight = rootWithMarkers.children[0] as BoundsAware & GChildElement;
elementBottomRight.bounds = { width: 10, height: 10, x: 200, y: 200 };
beforeEach('clear issue marker', () => {
[elementTopLeft, elementTopRight, elementBottomLeft, elementBottomRight].forEach(clearMarker);
});
it('next(undefined) without any markers returns undefined', () => {
expect(markerNavigator.next(rootWithoutAnyMarkers)).to.be.undefined;
});
it('previous(undefined) without any markers returns undefined', () => {
expect(markerNavigator.previous(rootWithoutAnyMarkers)).to.be.undefined;
});
it('next(undefined) with one marker returns the one marker', () => {
const marker = setIssues(elementTopLeft, [{ message: 'msg', severity: 'error' }]);
const next = markerNavigator.next(rootWithMarkers);
expect(next).to.eql(marker);
});
it('next(firstMarker) with only one marker returns again the first marker', () => {
const marker = setIssues(elementTopLeft, [{ message: 'msg', severity: 'error' }]);
const next = markerNavigator.next(rootWithMarkers, marker);
expect(next).to.eql(marker);
// and again and again
const nextNext = markerNavigator.next(rootWithMarkers, next);
expect(nextNext).to.eql(marker);
});
it('previous(firstMarker) with only one marker returns again the first marker', () => {
const marker = setIssues(elementTopLeft, [{ message: 'msg', severity: 'error' }]);
const previous = markerNavigator.previous(rootWithMarkers, marker);
expect(previous).to.eql(marker);
// and again and again
const previousPrevious = markerNavigator.previous(rootWithMarkers, previous);
expect(previousPrevious).to.eql(marker);
});
it('next(firstMarker) with two marker returns second marker then again first marker', () => {
const marker1 = setIssues(elementTopLeft, [{ message: 'msg', severity: 'error' }]);
const marker2 = setIssues(elementTopRight, [{ message: 'msg', severity: 'error' }]);
const next = markerNavigator.next(rootWithMarkers, marker1);
expect(next).to.eql(marker2);
// and again and again
const nextNext = markerNavigator.next(rootWithMarkers, next);
expect(nextNext).to.eql(marker1);
});
it('previous(firstMarker) with two marker returns second marker then again first marker', () => {
const marker1 = setIssues(elementTopLeft, [{ message: 'msg', severity: 'error' }]);
const marker2 = setIssues(elementTopRight, [{ message: 'msg', severity: 'error' }]);
const next = markerNavigator.previous(rootWithMarkers, marker1);
expect(next).to.eql(marker2);
// and again and again
const nextNext = markerNavigator.previous(rootWithMarkers, next);
expect(nextNext).to.eql(marker1);
});
it('returns markers in the order left-to-right, top-to-bottom with next()', () => {
const marker1 = setIssues(elementTopLeft, [{ message: 'msg', severity: 'error' }]);
const marker2 = setIssues(elementTopRight, [{ message: 'msg', severity: 'error' }]);
const marker3 = setIssues(elementBottomLeft, [{ message: 'msg', severity: 'error' }]);
const marker4 = setIssues(elementBottomRight, [{ message: 'msg', severity: 'error' }]);
const found1 = markerNavigator.next(rootWithMarkers);
const found2 = markerNavigator.next(rootWithMarkers, found1);
const found3 = markerNavigator.next(rootWithMarkers, found2);
const found4 = markerNavigator.next(rootWithMarkers, found3);
const found5 = markerNavigator.next(rootWithMarkers, found4);
expect(found1).to.eql(marker1);
expect(found2).to.eql(marker2);
expect(found3).to.eql(marker3);
expect(found4).to.eql(marker4);
expect(found5).to.eql(marker1);
});
it('returns markers in the order left-to-right, top-to-bottom with previous()', () => {
const marker1 = setIssues(elementTopLeft, [{ message: 'msg', severity: 'error' }]);
const marker2 = setIssues(elementTopRight, [{ message: 'msg', severity: 'error' }]);
const marker3 = setIssues(elementBottomLeft, [{ message: 'msg', severity: 'error' }]);
const marker4 = setIssues(elementBottomRight, [{ message: 'msg', severity: 'error' }]);
const found1 = markerNavigator.previous(rootWithMarkers);
const found2 = markerNavigator.previous(rootWithMarkers, found1);
const found3 = markerNavigator.previous(rootWithMarkers, found2);
const found4 = markerNavigator.previous(rootWithMarkers, found3);
const found5 = markerNavigator.previous(rootWithMarkers, found4);
expect(found1).to.eql(marker1);
expect(found2).to.eql(marker4);
expect(found3).to.eql(marker3);
expect(found4).to.eql(marker2);
expect(found5).to.eql(marker1);
});
});
function clearMarker(elem: GParentElement): void {
elem.children.filter(isMarker).forEach(marker => elem.remove(marker));
}
function setIssues(elem: GParentElement, issues: GIssue[]): GIssueMarker {
const marker = getOrCreateMarker(elem);
marker.issues = issues;
return marker;
}
function getOrCreateMarker(elem: GParentElement): GIssueMarker {
const marker = findMarker(elem);
if (marker instanceof GIssueMarker) {
return marker;
}
return createMarker(elem as GParentElement);
}
function findMarker(elem: GParentElement): GIssueMarker | undefined {
return elem.children.find(isMarker);
}
function isMarker(element: GModelElement): element is GIssueMarker {
return element instanceof GIssueMarker;
}
function createMarker(elem: GParentElement): GIssueMarker {
const newMarker = new GIssueMarker();
newMarker.type = 'marker';
newMarker.id = `${elem.id}_marker`;
elem.add(newMarker);
return newMarker;
}